Skip to content

Commit

Permalink
Update dependencies and lint repo
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante committed Nov 5, 2021
1 parent 4ed4fb7 commit 05102c5
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 190 deletions.
21 changes: 12 additions & 9 deletions .github/workflows/esm-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ env:
IMPORT_TEXT: import storageCache from
NPM_MODULE_NAME: webext-storage-cache

# DO NOT EDIT BELOW, USE: npx ghat fregante/ghatemplates/esm-lint --exclude 'jobs.Node' --exclude 'jobs.Rollup'
# FILE GENERATED WITH: npx ghat fregante/ghatemplates/esm-lint
# SOURCE: https://github.com/fregante/ghatemplates
# OPTIONS: {"exclude":["jobs.Node","jobs.Rollup"]}

name: ESM
on:
Expand All @@ -12,6 +14,7 @@ on:
push:
branches:
- master
- main
jobs:
Pack:
runs-on: ubuntu-latest
Expand All @@ -20,7 +23,7 @@ jobs:
- run: npm install
- run: npm run build --if-present
- run: npm pack --dry-run
- run: npm pack --silent 2>/dev/null | xargs cat | tar -xz
- run: npm pack --silent 2>/dev/null | xargs -n1 tar -xzf
- uses: actions/upload-artifact@v2
with:
path: package
Expand All @@ -30,25 +33,25 @@ jobs:
steps:
- uses: actions/download-artifact@v2
- run: npm install ./artifact
- run: 'echo "${{ env.IMPORT_TEXT }} ''${{ env.NPM_MODULE_NAME }}''" > index.js'
- run: webpack ./index.js
- run: echo "${{ env.IMPORT_TEXT }} '${{ env.NPM_MODULE_NAME }}'" > index.js
- run: webpack --entry ./index.js
- run: cat dist/main.js
Parcel:
runs-on: ubuntu-latest
needs: Pack
steps:
- uses: actions/download-artifact@v2
- run: npm install ./artifact
- run: 'echo "${{ env.IMPORT_TEXT }} ''${{ env.NPM_MODULE_NAME }}''" > index.js'
- run: npx parcel@1 build index.js
- run: echo "${{ env.IMPORT_TEXT }} '${{ env.NPM_MODULE_NAME }}'" > index.js
- run: npx parcel@2 build index.js
- run: cat dist/index.js
Snowpack:
runs-on: ubuntu-latest
needs: Pack
steps:
- uses: actions/download-artifact@v2
- run: 'echo ''{}'' > package.json'
- run: 'echo "${{ env.IMPORT_TEXT }} ''${{ env.NPM_MODULE_NAME }}''" > index.js'
- run: echo '{}' > package.json
- run: echo "${{ env.IMPORT_TEXT }} '${{ env.NPM_MODULE_NAME }}'" > index.js
- run: npm install ./artifact
- run: npx snowpack@2 build
- run: cat build/web_modules/$NPM_MODULE_NAME.js
Expand All @@ -58,6 +61,6 @@ jobs:
steps:
- uses: actions/download-artifact@v2
- run: npm install ./artifact
- run: 'echo "${{ env.IMPORT_TEXT }} ''${{ env.NPM_MODULE_NAME }}''" > index.ts'
- run: echo "${{ env.IMPORT_TEXT }} '${{ env.NPM_MODULE_NAME }}'" > index.ts
- run: tsc index.ts
- run: cat index.js
28 changes: 14 additions & 14 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ expectType<(n: number) => Promise<number>>(cachedPower);
expectType<number>(await cachedPower(1));

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n))
cache.function(async (n: string) => Number(n)),
);

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n))
cache.function(async (n: string) => Number(n)),
);

async function identity(x: string): Promise <string>;
async function identity(x: number): Promise <number>;
async function identity(x: number | string): Promise <number | string> {
async function identity(x: string): Promise<string>;
async function identity(x: number): Promise<number>;
async function identity(x: number | string): Promise<number | string> {
return x;
}

Expand All @@ -42,30 +42,30 @@ expectNotAssignable<Promise<number>>(cache.function(identity)('1'));

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n), {
maxAge: {days: 20}
})
maxAge: {days: 20},
}),
);

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n), {
maxAge: {days: 20},
staleWhileRevalidate: {days: 5}
})
staleWhileRevalidate: {days: 5},
}),
);

expectType<(date: Date) => Promise<string>>(
cache.function(async (date: Date) => String(date.getHours()), {
cacheKey: ([date]) => date.toLocaleString()
})
cacheKey: ([date]) => date.toLocaleString(),
}),
);

expectType<(date: Date) => Promise<string>>(
cache.function(async (date: Date) => String(date.getHours()), {
shouldRevalidate: date => typeof date === 'string'
})
shouldRevalidate: date => typeof date === 'string',
}),
);

// This function won't be cached
expectType<(n: undefined[]) => Promise<undefined>>(
cache.function(async (n: undefined[]) => n[1])
cache.function(async (n: undefined[]) => n[1]),
);
110 changes: 55 additions & 55 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,34 @@
import microMemoize from 'micro-memoize';
import {isBackgroundPage} from 'webext-detect-page';
import toMilliseconds, {TimeDescriptor} from '@sindresorhus/to-milliseconds';

// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/promise-function-async
const getPromise = (executor: () => void) => <T>(key?): Promise<T> => new Promise((resolve, reject) => {
// @ts-expect-error
executor(key, result => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(result);
}
});
});
import chromeP from 'webext-polyfill-kinda';

function timeInTheFuture(time: TimeDescriptor): number {
return Date.now() + toMilliseconds(time);
}

// @ts-expect-error
const storageGet = getPromise((...args) => chrome.storage.local.get(...args));
// @ts-expect-error
const storageSet = getPromise((...args) => chrome.storage.local.set(...args));
// @ts-expect-error
const storageRemove = getPromise((...args) => chrome.storage.local.remove(...args));

type Primitive = boolean | number | string;
type Value = Primitive | Primitive[] | Record<string, any>;
// No circular references: Record<string, Value> https://github.com/Microsoft/TypeScript/issues/14174
// No index signature: {[key: string]: Value} https://github.com/microsoft/TypeScript/issues/15300#issuecomment-460226926

interface CacheItem<TValue> {
data: TValue;
interface CacheItem<Value> {
data: Value;
maxAge: number;
}

type Cache<TValue extends Value = Value> = Record<string, CacheItem<TValue>>;
type Cache<ScopedValue extends Value = Value> = Record<string, CacheItem<ScopedValue>>;

async function has(key: string): Promise<boolean> {
return await _get(key, false) !== undefined;
return (await _get(key, false)) !== undefined;
}

async function _get<TValue extends Value>(key: string, remove: boolean): Promise<CacheItem<TValue> | undefined> {
async function _get<ScopedValue extends Value>(
key: string,
remove: boolean,
): Promise<CacheItem<ScopedValue> | undefined> {
const internalKey = `cache:${key}`;
const storageData = await storageGet<Cache<TValue>>(internalKey);
const storageData = await chromeP.storage.local.get(internalKey) as Cache<ScopedValue>;
const cachedItem = storageData[internalKey];

if (cachedItem === undefined) {
Expand All @@ -54,7 +38,7 @@ async function _get<TValue extends Value>(key: string, remove: boolean): Promise

if (Date.now() > cachedItem.maxAge) {
if (remove) {
await storageRemove(internalKey);
await chromeP.storage.local.remove(internalKey);
}

return;
Expand All @@ -63,11 +47,17 @@ async function _get<TValue extends Value>(key: string, remove: boolean): Promise
return cachedItem;
}

async function get<TValue extends Value>(key: string): Promise<TValue | undefined> {
return (await _get<TValue>(key, true))?.data;
async function get<ScopedValue extends Value>(
key: string,
): Promise<ScopedValue | undefined> {
return (await _get<ScopedValue>(key, true))?.data;
}

async function set<TValue extends Value>(key: string, value: TValue, maxAge: TimeDescriptor = {days: 30}): Promise<TValue> {
async function set<ScopedValue extends Value>(
key: string,
value: ScopedValue,
maxAge: TimeDescriptor = {days: 30},
): Promise<ScopedValue> {
if (arguments.length < 2) {
throw new TypeError('Expected a value as the second argument');
}
Expand All @@ -76,11 +66,11 @@ async function set<TValue extends Value>(key: string, value: TValue, maxAge: Tim
await delete_(key);
} else {
const internalKey = `cache:${key}`;
await storageSet({
await chromeP.storage.local.set({
[internalKey]: {
data: value,
maxAge: timeInTheFuture(maxAge)
}
maxAge: timeInTheFuture(maxAge),
},
});
}

Expand All @@ -89,11 +79,13 @@ async function set<TValue extends Value>(key: string, value: TValue, maxAge: Tim

async function delete_(key: string): Promise<void> {
const internalKey = `cache:${key}`;
return storageRemove(internalKey);
return chromeP.storage.local.remove(internalKey);
}

async function deleteWithLogic(logic?: (x: CacheItem<Value>) => boolean): Promise<void> {
const wholeCache = await storageGet<Record<string, any>>();
async function deleteWithLogic(
logic?: (x: CacheItem<Value>) => boolean,
): Promise<void> {
const wholeCache = (await chromeP.storage.local.get()) as Record<string, any>;
const removableItems = [];
for (const [key, value] of Object.entries(wholeCache)) {
if (key.startsWith('cache:') && (logic?.(value) ?? true)) {
Expand All @@ -102,7 +94,7 @@ async function deleteWithLogic(logic?: (x: CacheItem<Value>) => boolean): Promis
}

if (removableItems.length > 0) {
await storageRemove(removableItems);
await chromeP.storage.local.remove(removableItems);
}
}

Expand All @@ -114,22 +106,30 @@ async function clear(): Promise<void> {
await deleteWithLogic();
}

interface MemoizedFunctionOptions<TArgs extends any[], TValue> {
interface MemoizedFunctionOptions<Arguments extends any[], ScopedValue> {
maxAge?: TimeDescriptor;
staleWhileRevalidate?: TimeDescriptor;
cacheKey?: (args: TArgs) => string;
shouldRevalidate?: (cachedValue: TValue) => boolean;
cacheKey?: (args: Arguments) => string;
shouldRevalidate?: (cachedValue: ScopedValue) => boolean;
}

function function_<
TValue extends Value,
TFunction extends (...args: any[]) => Promise<TValue | undefined>,
TArgs extends Parameters<TFunction>
ScopedValue extends Value,
Getter extends (...args: any[]) => Promise<ScopedValue | undefined>,
Arguments extends Parameters<Getter>,
>(
getter: TFunction,
{cacheKey, maxAge = {days: 30}, staleWhileRevalidate = {days: 0}, shouldRevalidate}: MemoizedFunctionOptions<TArgs, TValue> = {}
): TFunction {
const getSet = async (key: string, args: TArgs): Promise<TValue | undefined> => {
getter: Getter,
{
cacheKey,
maxAge = {days: 30},
staleWhileRevalidate = {days: 0},
shouldRevalidate,
}: MemoizedFunctionOptions<Arguments, ScopedValue> = {},
): Getter {
const getSet = async (
key: string,
args: Arguments,
): Promise<ScopedValue | undefined> => {
const freshValue = await getter(...args);
if (freshValue === undefined) {
await delete_(key);
Expand All @@ -138,12 +138,12 @@ function function_<

const milliseconds = toMilliseconds(maxAge) + toMilliseconds(staleWhileRevalidate);

return set<TValue>(key, freshValue, {milliseconds});
return set<ScopedValue>(key, freshValue, {milliseconds});
};

return microMemoize((async (...args: TArgs) => {
const userKey = cacheKey ? cacheKey(args) : args[0] as string;
const cachedItem = await _get<TValue>(userKey, false);
return microMemoize((async (...args: Arguments) => {
const userKey = cacheKey ? cacheKey(args) : (args[0] as string);
const cachedItem = await _get<ScopedValue>(userKey, false);
if (cachedItem === undefined || shouldRevalidate?.(cachedItem.data)) {
return getSet(userKey, args);
}
Expand All @@ -154,7 +154,7 @@ function function_<
}

return cachedItem.data;
}) as TFunction);
}) as Getter);
}

const cache = {
Expand All @@ -163,7 +163,7 @@ const cache = {
set,
clear,
function: function_,
delete: delete_
delete: delete_,
};

function init(): void {
Expand All @@ -178,7 +178,7 @@ function init(): void {
if (chrome.alarms) {
chrome.alarms.create('webext-storage-cache', {
delayInMinutes: 1,
periodInMinutes: 60 * 24
periodInMinutes: 60 * 24,
});

let lastRun = 0; // Homemade debouncing due to `chrome.alarms` potentially queueing this function
Expand All @@ -189,7 +189,7 @@ function init(): void {
}
});
} else {
setTimeout(deleteExpired, 60000); // Purge cache on launch, but wait a bit
setTimeout(deleteExpired, 60_000); // Purge cache on launch, but wait a bit
setInterval(deleteExpired, 1000 * 3600 * 24);
}
}
Expand Down
2 changes: 1 addition & 1 deletion license
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) Federico Brigante <opensource@bfred.it> (bfred.it)
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
Loading

0 comments on commit 05102c5

Please sign in to comment.