@sanity/cli-core 0.0.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/SanityCommand.d.ts +56 -0
- package/dist/SanityCommand.js +72 -0
- package/dist/SanityCommand.js.map +1 -0
- package/dist/config/__tests__/cliToken.test.js +74 -0
- package/dist/config/__tests__/cliToken.test.js.map +1 -0
- package/dist/config/__tests__/cliUserConfig.test.js +131 -0
- package/dist/config/__tests__/cliUserConfig.test.js.map +1 -0
- package/dist/config/__tests__/findProjectRoot.test.js +159 -0
- package/dist/config/__tests__/findProjectRoot.test.js.map +1 -0
- package/dist/config/cli/getCliConfig.d.ts +16 -0
- package/dist/config/cli/getCliConfig.js +67 -0
- package/dist/config/cli/getCliConfig.js.map +1 -0
- package/dist/config/cli/getCliConfig.worker.d.ts +1 -0
- package/dist/config/cli/getCliConfig.worker.js +14 -0
- package/dist/config/cli/getCliConfig.worker.js.map +1 -0
- package/dist/config/cli/schemas.d.ts +204 -0
- package/dist/config/cli/schemas.js +77 -0
- package/dist/config/cli/schemas.js.map +1 -0
- package/dist/config/cli/types.d.ts +13 -0
- package/dist/config/cli/types.js +3 -0
- package/dist/config/cli/types.js.map +1 -0
- package/dist/config/findProjectRoot.d.ts +14 -0
- package/dist/config/findProjectRoot.js +56 -0
- package/dist/config/findProjectRoot.js.map +1 -0
- package/dist/config/studio/getStudioConfig.d.ts +14 -0
- package/dist/config/studio/getStudioConfig.js +16 -0
- package/dist/config/studio/getStudioConfig.js.map +1 -0
- package/dist/config/studio/readStudioConfig.d.ts +190 -0
- package/dist/config/studio/readStudioConfig.js +45 -0
- package/dist/config/studio/readStudioConfig.js.map +1 -0
- package/dist/config/studio/readStudioConfig.worker.d.ts +1 -0
- package/dist/config/studio/readStudioConfig.worker.js +64 -0
- package/dist/config/studio/readStudioConfig.worker.js.map +1 -0
- package/dist/config/util/findAppConfigPath.d.ts +8 -0
- package/dist/config/util/findAppConfigPath.js +22 -0
- package/dist/config/util/findAppConfigPath.js.map +1 -0
- package/dist/config/util/findConfigsPaths.d.ts +16 -0
- package/dist/config/util/findConfigsPaths.js +21 -0
- package/dist/config/util/findConfigsPaths.js.map +1 -0
- package/dist/config/util/findStudioConfigPath.d.ts +9 -0
- package/dist/config/util/findStudioConfigPath.js +31 -0
- package/dist/config/util/findStudioConfigPath.js.map +1 -0
- package/dist/config/util/isSanityV2StudioRoot.d.ts +8 -0
- package/dist/config/util/isSanityV2StudioRoot.js +19 -0
- package/dist/config/util/isSanityV2StudioRoot.js.map +1 -0
- package/dist/config/util/recursivelyResolveProjectRoot.d.ts +27 -0
- package/dist/config/util/recursivelyResolveProjectRoot.js +28 -0
- package/dist/config/util/recursivelyResolveProjectRoot.js.map +1 -0
- package/dist/debug.d.ts +15 -0
- package/dist/debug.js +15 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/studio/stubs.d.ts +254 -0
- package/dist/loaders/studio/stubs.js +257 -0
- package/dist/loaders/studio/stubs.js.map +1 -0
- package/dist/loaders/studio/studioWorkerLoader.worker.d.ts +1 -0
- package/dist/loaders/studio/studioWorkerLoader.worker.js +117 -0
- package/dist/loaders/studio/studioWorkerLoader.worker.js.map +1 -0
- package/dist/loaders/studio/studioWorkerTask.d.ts +40 -0
- package/dist/loaders/studio/studioWorkerTask.js +69 -0
- package/dist/loaders/studio/studioWorkerTask.js.map +1 -0
- package/dist/loaders/tsx/tsxWorkerLoader.worker.d.ts +1 -0
- package/dist/loaders/tsx/tsxWorkerLoader.worker.js +12 -0
- package/dist/loaders/tsx/tsxWorkerLoader.worker.js.map +1 -0
- package/dist/loaders/tsx/tsxWorkerTask.d.ts +28 -0
- package/dist/loaders/tsx/tsxWorkerTask.js +61 -0
- package/dist/loaders/tsx/tsxWorkerTask.js.map +1 -0
- package/dist/services/apiClient.d.ts +39 -0
- package/dist/services/apiClient.js +88 -0
- package/dist/services/apiClient.js.map +1 -0
- package/dist/services/cliUserConfig.d.ts +57 -0
- package/dist/services/cliUserConfig.js +103 -0
- package/dist/services/cliUserConfig.js.map +1 -0
- package/dist/services/getCliToken.d.ts +7 -0
- package/dist/services/getCliToken.js +21 -0
- package/dist/services/getCliToken.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/util/NotFoundError.d.ts +20 -0
- package/dist/util/NotFoundError.js +27 -0
- package/dist/util/NotFoundError.js.map +1 -0
- package/dist/util/__tests__/createExpiringConfig.test.js +309 -0
- package/dist/util/__tests__/createExpiringConfig.test.js.map +1 -0
- package/dist/util/createExpiringConfig.d.ts +32 -0
- package/dist/util/createExpiringConfig.js +35 -0
- package/dist/util/createExpiringConfig.js.map +1 -0
- package/dist/util/fileExists.d.ts +9 -0
- package/dist/util/fileExists.js +13 -0
- package/dist/util/fileExists.js.map +1 -0
- package/dist/util/generateHelpUrl.d.ts +8 -0
- package/dist/util/generateHelpUrl.js +11 -0
- package/dist/util/generateHelpUrl.js.map +1 -0
- package/dist/util/getSanityEnvVar.d.ts +19 -0
- package/dist/util/getSanityEnvVar.js +24 -0
- package/dist/util/getSanityEnvVar.js.map +1 -0
- package/dist/util/getSanityUrl.d.ts +5 -0
- package/dist/util/getSanityUrl.js +8 -0
- package/dist/util/getSanityUrl.js.map +1 -0
- package/dist/util/getUserConfig.d.ts +2 -0
- package/dist/util/getUserConfig.js +15 -0
- package/dist/util/getUserConfig.js.map +1 -0
- package/dist/util/isCi.d.ts +1 -0
- package/dist/util/isCi.js +7 -0
- package/dist/util/isCi.js.map +1 -0
- package/dist/util/isHttpError.d.ts +29 -0
- package/dist/util/isHttpError.js +18 -0
- package/dist/util/isHttpError.js.map +1 -0
- package/dist/util/isInteractive.d.ts +1 -0
- package/dist/util/isInteractive.js +5 -0
- package/dist/util/isInteractive.js.map +1 -0
- package/dist/util/isRecord.d.ts +8 -0
- package/dist/util/isRecord.js +11 -0
- package/dist/util/isRecord.js.map +1 -0
- package/dist/util/isTrueish.d.ts +1 -0
- package/dist/util/isTrueish.js +10 -0
- package/dist/util/isTrueish.js.map +1 -0
- package/dist/util/readJsonFile.d.ts +8 -0
- package/dist/util/readJsonFile.js +26 -0
- package/dist/util/readJsonFile.js.map +1 -0
- package/dist/util/safeStructuredClone.d.ts +8 -0
- package/dist/util/safeStructuredClone.js +40 -0
- package/dist/util/safeStructuredClone.js.map +1 -0
- package/dist/util/writeJsonFile.d.ts +9 -0
- package/dist/util/writeJsonFile.js +19 -0
- package/dist/util/writeJsonFile.js.map +1 -0
- package/dist/ux/colorizeJson.d.ts +1 -0
- package/dist/ux/colorizeJson.js +32 -0
- package/dist/ux/colorizeJson.js.map +1 -0
- package/dist/ux/formatObject.d.ts +1 -0
- package/dist/ux/formatObject.js +9 -0
- package/dist/ux/formatObject.js.map +1 -0
- package/dist/ux/logSymbols.d.ts +1 -0
- package/dist/ux/logSymbols.js +3 -0
- package/dist/ux/logSymbols.js.map +1 -0
- package/dist/ux/printKeyValue.d.ts +1 -0
- package/dist/ux/printKeyValue.js +16 -0
- package/dist/ux/printKeyValue.js.map +1 -0
- package/dist/ux/spinner.d.ts +1 -0
- package/dist/ux/spinner.js +3 -0
- package/dist/ux/spinner.js.map +1 -0
- package/dist/ux/timer.d.ts +12 -0
- package/dist/ux/timer.js +29 -0
- package/dist/ux/timer.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join as joinPath } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { debug } from '../debug.js';
|
|
6
|
+
import { readJsonFile } from '../util/readJsonFile.js';
|
|
7
|
+
import { writeJsonFile } from '../util/writeJsonFile.js';
|
|
8
|
+
const cliUserConfigSchema = {
|
|
9
|
+
authToken: z.string().optional(),
|
|
10
|
+
telemetryConsent: z.object({
|
|
11
|
+
updatedAt: z.number().optional(),
|
|
12
|
+
value: z.object({
|
|
13
|
+
status: z.enum([
|
|
14
|
+
'undetermined',
|
|
15
|
+
'unset',
|
|
16
|
+
'granted',
|
|
17
|
+
'denied'
|
|
18
|
+
]),
|
|
19
|
+
type: z.string()
|
|
20
|
+
}).passthrough()
|
|
21
|
+
}).optional()
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Set the config value for the given property.
|
|
25
|
+
* Validates that the passed value adheres to the defined CLI config schema.
|
|
26
|
+
*
|
|
27
|
+
* @param prop - The property to set the value for
|
|
28
|
+
* @param value - The value to set
|
|
29
|
+
* @internal
|
|
30
|
+
*/ export async function setConfig(prop, value) {
|
|
31
|
+
const config = await readConfig();
|
|
32
|
+
const valueSchema = cliUserConfigSchema[prop];
|
|
33
|
+
if (!valueSchema) {
|
|
34
|
+
throw new Error(`No schema defined for config property "${prop}"`);
|
|
35
|
+
}
|
|
36
|
+
const { error, success } = valueSchema.safeParse(value);
|
|
37
|
+
if (!success) {
|
|
38
|
+
const message = error.issues.map(({ message, path })=>`[${path.join('.')}] ${message}`).join('\n');
|
|
39
|
+
throw new Error(`Invalid value for config property "${prop}": ${message}`);
|
|
40
|
+
}
|
|
41
|
+
const configPath = getCliUserConfigPath();
|
|
42
|
+
await mkdir(dirname(configPath), {
|
|
43
|
+
recursive: true
|
|
44
|
+
});
|
|
45
|
+
await writeJsonFile(configPath, {
|
|
46
|
+
...config,
|
|
47
|
+
[prop]: value
|
|
48
|
+
}, {
|
|
49
|
+
pretty: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the config value for the given property
|
|
54
|
+
*
|
|
55
|
+
* @param prop - The property to get the value for
|
|
56
|
+
* @returns The value of the given property
|
|
57
|
+
* @internal
|
|
58
|
+
*/ export async function getConfig(prop) {
|
|
59
|
+
const config = await readConfig();
|
|
60
|
+
const valueSchema = cliUserConfigSchema[prop];
|
|
61
|
+
if (!valueSchema) {
|
|
62
|
+
throw new Error(`No schema defined for config property "${prop}"`);
|
|
63
|
+
}
|
|
64
|
+
const { error, success } = valueSchema.safeParse(config[prop]);
|
|
65
|
+
if (!success) {
|
|
66
|
+
const message = error.issues.map(({ message, path })=>`[${path.join('.')}] ${message}`).join('\n');
|
|
67
|
+
throw new Error(`Invalid value for config property "${prop}": ${message}`);
|
|
68
|
+
}
|
|
69
|
+
return config[prop];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read the whole configuration from file system. If the file does not exist or could
|
|
73
|
+
* not be loaded, an empty configuration object is returned.
|
|
74
|
+
*
|
|
75
|
+
* @returns The whole CLI configuration.
|
|
76
|
+
* @internal
|
|
77
|
+
*/ async function readConfig() {
|
|
78
|
+
const defaultConfig = {};
|
|
79
|
+
try {
|
|
80
|
+
const config = await readJsonFile(getCliUserConfigPath());
|
|
81
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
82
|
+
throw new Error('Invalid config file - expected an object');
|
|
83
|
+
}
|
|
84
|
+
return config;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
debug('Failed to read CLI config file: %s', err instanceof Error ? err.message : `${err}`);
|
|
87
|
+
return defaultConfig;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the file system location for the CLI user configuration file.
|
|
92
|
+
* Takes into account the active environment (staging vs production).
|
|
93
|
+
* The file is located in the user's home directory under the `.config` directory.
|
|
94
|
+
*
|
|
95
|
+
* @returns The path to the CLI configuration file.
|
|
96
|
+
* @internal
|
|
97
|
+
*/ function getCliUserConfigPath() {
|
|
98
|
+
const sanityEnvSuffix = process.env.SANITY_INTERNAL_ENV === 'staging' ? '-staging' : '';
|
|
99
|
+
const cliConfigPath = process.env.SANITY_CLI_CONFIG_PATH || joinPath(homedir(), '.config', `sanity${sanityEnvSuffix}`, 'config.json');
|
|
100
|
+
return cliConfigPath;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=cliUserConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/services/cliUserConfig.ts"],"sourcesContent":["import {mkdir} from 'node:fs/promises'\nimport {homedir} from 'node:os'\nimport {dirname, join as joinPath} from 'node:path'\n\nimport {z} from 'zod'\n\nimport {debug} from '../debug.js'\nimport {readJsonFile} from '../util/readJsonFile.js'\nimport {writeJsonFile} from '../util/writeJsonFile.js'\n\nconst cliUserConfigSchema = {\n authToken: z.string().optional(),\n telemetryConsent: z\n .object({\n updatedAt: z.number().optional(),\n value: z\n .object({\n status: z.enum(['undetermined', 'unset', 'granted', 'denied']),\n type: z.string(),\n })\n .passthrough(),\n })\n .optional(),\n}\n\n/**\n * The CLI user configuration schema.\n *\n * @internal\n */\ntype CliUserConfig = z.infer<z.ZodObject<typeof cliUserConfigSchema>>\n\n/**\n * Set the config value for the given property.\n * Validates that the passed value adheres to the defined CLI config schema.\n *\n * @param prop - The property to set the value for\n * @param value - The value to set\n * @internal\n */\nexport async function setConfig<P extends keyof CliUserConfig>(prop: P, value: CliUserConfig[P]) {\n const config = await readConfig()\n const valueSchema = cliUserConfigSchema[prop]\n if (!valueSchema) {\n throw new Error(`No schema defined for config property \"${prop}\"`)\n }\n\n const {error, success} = valueSchema.safeParse(value)\n if (!success) {\n const message = error.issues\n .map(({message, path}) => `[${path.join('.')}] ${message}`)\n .join('\\n')\n\n throw new Error(`Invalid value for config property \"${prop}\": ${message}`)\n }\n\n const configPath = getCliUserConfigPath()\n await mkdir(dirname(configPath), {recursive: true})\n await writeJsonFile(configPath, {...config, [prop]: value}, {pretty: true})\n}\n\n/**\n * Get the config value for the given property\n *\n * @param prop - The property to get the value for\n * @returns The value of the given property\n * @internal\n */\nexport async function getConfig<P extends keyof CliUserConfig>(prop: P): Promise<CliUserConfig[P]> {\n const config = await readConfig()\n const valueSchema = cliUserConfigSchema[prop]\n if (!valueSchema) {\n throw new Error(`No schema defined for config property \"${prop}\"`)\n }\n\n const {error, success} = valueSchema.safeParse(config[prop])\n if (!success) {\n const message = error.issues\n .map(({message, path}) => `[${path.join('.')}] ${message}`)\n .join('\\n')\n\n throw new Error(`Invalid value for config property \"${prop}\": ${message}`)\n }\n\n return config[prop]\n}\n\n/**\n * Read the whole configuration from file system. If the file does not exist or could\n * not be loaded, an empty configuration object is returned.\n *\n * @returns The whole CLI configuration.\n * @internal\n */\nasync function readConfig(): Promise<CliUserConfig> {\n const defaultConfig: CliUserConfig = {}\n try {\n const config = await readJsonFile(getCliUserConfigPath())\n if (!config || typeof config !== 'object' || Array.isArray(config)) {\n throw new Error('Invalid config file - expected an object')\n }\n return config\n } catch (err: unknown) {\n debug('Failed to read CLI config file: %s', err instanceof Error ? err.message : `${err}`)\n return defaultConfig\n }\n}\n\n/**\n * Get the file system location for the CLI user configuration file.\n * Takes into account the active environment (staging vs production).\n * The file is located in the user's home directory under the `.config` directory.\n *\n * @returns The path to the CLI configuration file.\n * @internal\n */\nfunction getCliUserConfigPath() {\n const sanityEnvSuffix = process.env.SANITY_INTERNAL_ENV === 'staging' ? '-staging' : ''\n const cliConfigPath =\n process.env.SANITY_CLI_CONFIG_PATH ||\n joinPath(homedir(), '.config', `sanity${sanityEnvSuffix}`, 'config.json')\n\n return cliConfigPath\n}\n"],"names":["mkdir","homedir","dirname","join","joinPath","z","debug","readJsonFile","writeJsonFile","cliUserConfigSchema","authToken","string","optional","telemetryConsent","object","updatedAt","number","value","status","enum","type","passthrough","setConfig","prop","config","readConfig","valueSchema","Error","error","success","safeParse","message","issues","map","path","configPath","getCliUserConfigPath","recursive","pretty","getConfig","defaultConfig","Array","isArray","err","sanityEnvSuffix","process","env","SANITY_INTERNAL_ENV","cliConfigPath","SANITY_CLI_CONFIG_PATH"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getConfig } from './cliUserConfig.js';
|
|
2
|
+
let cachedToken;
|
|
3
|
+
/**
|
|
4
|
+
* Get the CLI authentication token from the environment or the config file
|
|
5
|
+
*
|
|
6
|
+
* @returns A promise that resolves to a CLI token, or undefined if no token is found
|
|
7
|
+
* @internal
|
|
8
|
+
*/ export async function getCliToken() {
|
|
9
|
+
if (cachedToken !== undefined) {
|
|
10
|
+
return cachedToken;
|
|
11
|
+
}
|
|
12
|
+
const token = process.env.SANITY_AUTH_TOKEN;
|
|
13
|
+
if (token) {
|
|
14
|
+
cachedToken = token.trim();
|
|
15
|
+
return cachedToken;
|
|
16
|
+
}
|
|
17
|
+
cachedToken = await getConfig('authToken');
|
|
18
|
+
return cachedToken;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=getCliToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/services/getCliToken.ts"],"sourcesContent":["import {getConfig} from './cliUserConfig.js'\n\nlet cachedToken: string | undefined\n\n/**\n * Get the CLI authentication token from the environment or the config file\n *\n * @returns A promise that resolves to a CLI token, or undefined if no token is found\n * @internal\n */\nexport async function getCliToken(): Promise<string | undefined> {\n if (cachedToken !== undefined) {\n return cachedToken\n }\n\n const token = process.env.SANITY_AUTH_TOKEN\n if (token) {\n cachedToken = token.trim()\n return cachedToken\n }\n\n cachedToken = await getConfig('authToken')\n return cachedToken\n}\n"],"names":["getConfig","cachedToken","getCliToken","undefined","token","process","env","SANITY_AUTH_TOKEN","trim"],"mappings":"AAAA,SAAQA,SAAS,QAAO,qBAAoB;AAE5C,IAAIC;AAEJ;;;;;CAKC,GACD,OAAO,eAAeC;IACpB,IAAID,gBAAgBE,WAAW;QAC7B,OAAOF;IACT;IAEA,MAAMG,QAAQC,QAAQC,GAAG,CAACC,iBAAiB;IAC3C,IAAIH,OAAO;QACTH,cAAcG,MAAMI,IAAI;QACxB,OAAOP;IACT;IAEAA,cAAc,MAAMD,UAAU;IAC9B,OAAOC;AACT"}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import {type Command} from '@oclif/core'\n\nexport interface Output {\n error: Command['error']\n log: Command['log']\n warn: Command['warn']\n}\n\nexport type RequireProps<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>\n"],"names":[],"mappings":"AAQA,WAAkF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when a file or directory is not found
|
|
3
|
+
*
|
|
4
|
+
* `code` is always `ENOENT` to mirror Node.js behavior when a file is not found
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare class NotFoundError extends Error {
|
|
9
|
+
code: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
constructor(message: string, path?: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether or not the given error is a `NotFoundError`
|
|
15
|
+
*
|
|
16
|
+
* @param err - The error to check
|
|
17
|
+
* @returns `true` if the error is a `NotFoundError`, `false` otherwise
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function isNotFoundError(err: unknown): err is NotFoundError;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isRecord } from './isRecord.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a file or directory is not found
|
|
4
|
+
*
|
|
5
|
+
* `code` is always `ENOENT` to mirror Node.js behavior when a file is not found
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/ export class NotFoundError extends Error {
|
|
9
|
+
code = 'ENOENT';
|
|
10
|
+
path;
|
|
11
|
+
constructor(message, path){
|
|
12
|
+
super(message);
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.name = 'NotFoundError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns whether or not the given error is a `NotFoundError`
|
|
19
|
+
*
|
|
20
|
+
* @param err - The error to check
|
|
21
|
+
* @returns `true` if the error is a `NotFoundError`, `false` otherwise
|
|
22
|
+
* @internal
|
|
23
|
+
*/ export function isNotFoundError(err) {
|
|
24
|
+
return isRecord(err) && 'name' in err && err.name === 'NotFoundError' && 'code' in err && err.code === 'ENOENT' && 'message' in err && typeof err.message === 'string';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=NotFoundError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/NotFoundError.ts"],"sourcesContent":["import {isRecord} from './isRecord.js'\n\n/**\n * Error thrown when a file or directory is not found\n *\n * `code` is always `ENOENT` to mirror Node.js behavior when a file is not found\n *\n * @internal\n */\nexport class NotFoundError extends Error {\n code = 'ENOENT'\n path?: string\n\n constructor(message: string, path?: string) {\n super(message)\n this.path = path\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Returns whether or not the given error is a `NotFoundError`\n *\n * @param err - The error to check\n * @returns `true` if the error is a `NotFoundError`, `false` otherwise\n * @internal\n */\nexport function isNotFoundError(err: unknown): err is NotFoundError {\n return (\n isRecord(err) &&\n 'name' in err &&\n err.name === 'NotFoundError' &&\n 'code' in err &&\n err.code === 'ENOENT' &&\n 'message' in err &&\n typeof err.message === 'string'\n )\n}\n"],"names":["isRecord","NotFoundError","Error","code","path","message","name","isNotFoundError","err"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,gBAAe;AAEtC;;;;;;CAMC,GACD,OAAO,MAAMC,sBAAsBC;IACjCC,OAAO,SAAQ;IACfC,KAAa;IAEb,YAAYC,OAAe,EAAED,IAAa,CAAE;QAC1C,KAAK,CAACC;QACN,IAAI,CAACD,IAAI,GAAGA;QACZ,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,gBAAgBC,GAAY;IAC1C,OACER,SAASQ,QACT,UAAUA,OACVA,IAAIF,IAAI,KAAK,mBACb,UAAUE,OACVA,IAAIL,IAAI,KAAK,YACb,aAAaK,OACb,OAAOA,IAAIH,OAAO,KAAK;AAE3B"}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { createExpiringConfig } from '../createExpiringConfig.js';
|
|
3
|
+
describe('createExpiringConfig', ()=>{
|
|
4
|
+
let mockStore;
|
|
5
|
+
let fetchValue;
|
|
6
|
+
let onCacheHit;
|
|
7
|
+
let onFetch;
|
|
8
|
+
let onRevalidate;
|
|
9
|
+
beforeEach(()=>{
|
|
10
|
+
// Mock ConfigStore
|
|
11
|
+
mockStore = {
|
|
12
|
+
delete: vi.fn(),
|
|
13
|
+
get: vi.fn(),
|
|
14
|
+
set: vi.fn()
|
|
15
|
+
};
|
|
16
|
+
// Reset all mocks
|
|
17
|
+
fetchValue = vi.fn();
|
|
18
|
+
onCacheHit = vi.fn();
|
|
19
|
+
onFetch = vi.fn();
|
|
20
|
+
onRevalidate = vi.fn();
|
|
21
|
+
});
|
|
22
|
+
test('returns fetched value when cache is empty', async ()=>{
|
|
23
|
+
const testValue = 'test-value';
|
|
24
|
+
const config = createExpiringConfig({
|
|
25
|
+
fetchValue: fetchValue.mockResolvedValue(testValue),
|
|
26
|
+
key: 'test-key',
|
|
27
|
+
onCacheHit,
|
|
28
|
+
onFetch,
|
|
29
|
+
onRevalidate,
|
|
30
|
+
store: mockStore,
|
|
31
|
+
ttl: 5000
|
|
32
|
+
});
|
|
33
|
+
// Mock empty cache
|
|
34
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
35
|
+
const result = await config.get();
|
|
36
|
+
expect(result).toBe(testValue);
|
|
37
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
38
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
39
|
+
expect(onCacheHit).not.toHaveBeenCalled();
|
|
40
|
+
expect(onRevalidate).not.toHaveBeenCalled();
|
|
41
|
+
expect(mockStore.set).toHaveBeenCalledWith('test-key', {
|
|
42
|
+
updatedAt: expect.any(Number),
|
|
43
|
+
value: testValue
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
test('returns cached value when it has not expired', async ()=>{
|
|
47
|
+
const cachedValue = 'cached-value';
|
|
48
|
+
const ttl = 5000;
|
|
49
|
+
const updatedAt = Date.now() - 1000 // 1 second ago (not expired)
|
|
50
|
+
;
|
|
51
|
+
const config = createExpiringConfig({
|
|
52
|
+
fetchValue,
|
|
53
|
+
key: 'test-key',
|
|
54
|
+
onCacheHit,
|
|
55
|
+
onFetch,
|
|
56
|
+
onRevalidate,
|
|
57
|
+
store: mockStore,
|
|
58
|
+
ttl
|
|
59
|
+
});
|
|
60
|
+
// Mock cached value that hasn't expired
|
|
61
|
+
vi.mocked(mockStore.get).mockReturnValue({
|
|
62
|
+
updatedAt,
|
|
63
|
+
value: cachedValue
|
|
64
|
+
});
|
|
65
|
+
const result = await config.get();
|
|
66
|
+
expect(result).toBe(cachedValue);
|
|
67
|
+
expect(fetchValue).not.toHaveBeenCalled();
|
|
68
|
+
expect(onCacheHit).toHaveBeenCalledOnce();
|
|
69
|
+
expect(onFetch).not.toHaveBeenCalled();
|
|
70
|
+
expect(onRevalidate).not.toHaveBeenCalled();
|
|
71
|
+
expect(mockStore.set).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
test('fetches new value when cached value has expired', async ()=>{
|
|
74
|
+
const newValue = 'new-value';
|
|
75
|
+
const ttl = 1000;
|
|
76
|
+
const updatedAt = Date.now() - 2000 // 2 seconds ago (expired)
|
|
77
|
+
;
|
|
78
|
+
const config = createExpiringConfig({
|
|
79
|
+
fetchValue: fetchValue.mockResolvedValue(newValue),
|
|
80
|
+
key: 'test-key',
|
|
81
|
+
onCacheHit,
|
|
82
|
+
onFetch,
|
|
83
|
+
onRevalidate,
|
|
84
|
+
store: mockStore,
|
|
85
|
+
ttl
|
|
86
|
+
});
|
|
87
|
+
// Mock expired cached value
|
|
88
|
+
vi.mocked(mockStore.get).mockReturnValue({
|
|
89
|
+
updatedAt,
|
|
90
|
+
value: 'old-value'
|
|
91
|
+
});
|
|
92
|
+
const result = await config.get();
|
|
93
|
+
expect(result).toBe(newValue);
|
|
94
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
95
|
+
expect(onRevalidate).toHaveBeenCalledOnce();
|
|
96
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
97
|
+
expect(onCacheHit).not.toHaveBeenCalled();
|
|
98
|
+
expect(mockStore.set).toHaveBeenCalledWith('test-key', {
|
|
99
|
+
updatedAt: expect.any(Number),
|
|
100
|
+
value: newValue
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
test('deletes cached value from store', ()=>{
|
|
104
|
+
const config = createExpiringConfig({
|
|
105
|
+
fetchValue,
|
|
106
|
+
key: 'test-key',
|
|
107
|
+
store: mockStore,
|
|
108
|
+
ttl: 5000
|
|
109
|
+
});
|
|
110
|
+
config.delete();
|
|
111
|
+
expect(mockStore.delete).toHaveBeenCalledWith('test-key');
|
|
112
|
+
});
|
|
113
|
+
test('handles concurrent get() calls correctly', async ()=>{
|
|
114
|
+
const testValue = 'test-value';
|
|
115
|
+
let resolvePromise;
|
|
116
|
+
const delayedFetch = new Promise((resolve)=>{
|
|
117
|
+
resolvePromise = resolve;
|
|
118
|
+
});
|
|
119
|
+
const config = createExpiringConfig({
|
|
120
|
+
fetchValue: fetchValue.mockReturnValue(delayedFetch),
|
|
121
|
+
key: 'test-key',
|
|
122
|
+
onFetch,
|
|
123
|
+
store: mockStore,
|
|
124
|
+
ttl: 5000
|
|
125
|
+
});
|
|
126
|
+
// Mock empty cache
|
|
127
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
128
|
+
// Start multiple concurrent get() calls
|
|
129
|
+
const promise1 = config.get();
|
|
130
|
+
const promise2 = config.get();
|
|
131
|
+
const promise3 = config.get();
|
|
132
|
+
// Resolve the fetch
|
|
133
|
+
resolvePromise(testValue);
|
|
134
|
+
const [result1, result2, result3] = await Promise.all([
|
|
135
|
+
promise1,
|
|
136
|
+
promise2,
|
|
137
|
+
promise3
|
|
138
|
+
]);
|
|
139
|
+
expect(result1).toBe(testValue);
|
|
140
|
+
expect(result2).toBe(testValue);
|
|
141
|
+
expect(result3).toBe(testValue);
|
|
142
|
+
expect(fetchValue).toHaveBeenCalledOnce(); // Only one fetch should happen
|
|
143
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
144
|
+
});
|
|
145
|
+
test('handles synchronous fetchValue function', async ()=>{
|
|
146
|
+
const testValue = 'sync-value';
|
|
147
|
+
const syncFetchValue = vi.fn().mockReturnValue(testValue);
|
|
148
|
+
const config = createExpiringConfig({
|
|
149
|
+
fetchValue: syncFetchValue,
|
|
150
|
+
key: 'test-key',
|
|
151
|
+
store: mockStore,
|
|
152
|
+
ttl: 5000
|
|
153
|
+
});
|
|
154
|
+
// Mock empty cache
|
|
155
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
156
|
+
const result = await config.get();
|
|
157
|
+
expect(result).toBe(testValue);
|
|
158
|
+
expect(syncFetchValue).toHaveBeenCalledOnce();
|
|
159
|
+
});
|
|
160
|
+
test('handles fetchValue throwing an error', async ()=>{
|
|
161
|
+
const error = new Error('Fetch failed');
|
|
162
|
+
const config = createExpiringConfig({
|
|
163
|
+
fetchValue: fetchValue.mockRejectedValue(error),
|
|
164
|
+
key: 'test-key',
|
|
165
|
+
store: mockStore,
|
|
166
|
+
ttl: 5000
|
|
167
|
+
});
|
|
168
|
+
// Mock empty cache
|
|
169
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
170
|
+
await expect(config.get()).rejects.toThrow('Fetch failed');
|
|
171
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
172
|
+
expect(mockStore.set).not.toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
test('handles different data types as cached values', async ()=>{
|
|
175
|
+
const objectValue = {
|
|
176
|
+
key: 'value',
|
|
177
|
+
number: 42
|
|
178
|
+
};
|
|
179
|
+
const config = createExpiringConfig({
|
|
180
|
+
fetchValue: fetchValue.mockResolvedValue(objectValue),
|
|
181
|
+
key: 'test-key',
|
|
182
|
+
store: mockStore,
|
|
183
|
+
ttl: 5000
|
|
184
|
+
});
|
|
185
|
+
// Mock empty cache
|
|
186
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
187
|
+
const result = await config.get();
|
|
188
|
+
expect(result).toEqual(objectValue);
|
|
189
|
+
expect(mockStore.set).toHaveBeenCalledWith('test-key', {
|
|
190
|
+
updatedAt: expect.any(Number),
|
|
191
|
+
value: objectValue
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
test('works with TTL of 0 (immediate expiration)', async ()=>{
|
|
195
|
+
const testValue = 'test-value';
|
|
196
|
+
const config = createExpiringConfig({
|
|
197
|
+
fetchValue: fetchValue.mockResolvedValue(testValue),
|
|
198
|
+
key: 'test-key',
|
|
199
|
+
onRevalidate,
|
|
200
|
+
store: mockStore,
|
|
201
|
+
ttl: 0
|
|
202
|
+
});
|
|
203
|
+
// Mock cached value that would be immediately expired
|
|
204
|
+
// Use a timestamp from 1ms ago to ensure it's > ttl (0)
|
|
205
|
+
vi.mocked(mockStore.get).mockReturnValue({
|
|
206
|
+
updatedAt: Date.now() - 1,
|
|
207
|
+
value: 'old-value'
|
|
208
|
+
});
|
|
209
|
+
const result = await config.get();
|
|
210
|
+
expect(result).toBe(testValue);
|
|
211
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
212
|
+
expect(onRevalidate).toHaveBeenCalledOnce();
|
|
213
|
+
});
|
|
214
|
+
test('works without optional callback functions', async ()=>{
|
|
215
|
+
const testValue = 'test-value';
|
|
216
|
+
const config = createExpiringConfig({
|
|
217
|
+
fetchValue: fetchValue.mockResolvedValue(testValue),
|
|
218
|
+
key: 'test-key',
|
|
219
|
+
store: mockStore,
|
|
220
|
+
ttl: 5000
|
|
221
|
+
});
|
|
222
|
+
// Mock empty cache
|
|
223
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
224
|
+
const result = await config.get();
|
|
225
|
+
expect(result).toBe(testValue);
|
|
226
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
227
|
+
});
|
|
228
|
+
test('handles cached value without updatedAt timestamp', async ()=>{
|
|
229
|
+
const newValue = 'new-value';
|
|
230
|
+
const config = createExpiringConfig({
|
|
231
|
+
fetchValue: fetchValue.mockResolvedValue(newValue),
|
|
232
|
+
key: 'test-key',
|
|
233
|
+
onFetch,
|
|
234
|
+
store: mockStore,
|
|
235
|
+
ttl: 5000
|
|
236
|
+
});
|
|
237
|
+
// Mock cached value without updatedAt (invalid cache entry)
|
|
238
|
+
vi.mocked(mockStore.get).mockReturnValue({
|
|
239
|
+
value: 'old-value'
|
|
240
|
+
});
|
|
241
|
+
const result = await config.get();
|
|
242
|
+
expect(result).toBe(newValue);
|
|
243
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
244
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
245
|
+
});
|
|
246
|
+
test('handles cached value without value property', async ()=>{
|
|
247
|
+
const newValue = 'new-value';
|
|
248
|
+
const config = createExpiringConfig({
|
|
249
|
+
fetchValue: fetchValue.mockResolvedValue(newValue),
|
|
250
|
+
key: 'test-key',
|
|
251
|
+
onFetch,
|
|
252
|
+
store: mockStore,
|
|
253
|
+
ttl: 5000
|
|
254
|
+
});
|
|
255
|
+
// Mock cached entry without value property
|
|
256
|
+
vi.mocked(mockStore.get).mockReturnValue({
|
|
257
|
+
updatedAt: Date.now()
|
|
258
|
+
});
|
|
259
|
+
const result = await config.get();
|
|
260
|
+
expect(result).toBe(newValue);
|
|
261
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
262
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
263
|
+
});
|
|
264
|
+
test('stores timestamp correctly when caching new values', async ()=>{
|
|
265
|
+
const testValue = 'test-value';
|
|
266
|
+
const config = createExpiringConfig({
|
|
267
|
+
fetchValue: fetchValue.mockResolvedValue(testValue),
|
|
268
|
+
key: 'test-key',
|
|
269
|
+
store: mockStore,
|
|
270
|
+
ttl: 5000
|
|
271
|
+
});
|
|
272
|
+
// Mock empty cache
|
|
273
|
+
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
274
|
+
await config.get();
|
|
275
|
+
expect(mockStore.set).toHaveBeenCalledWith('test-key', {
|
|
276
|
+
updatedAt: expect.any(Number),
|
|
277
|
+
value: testValue
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
test('subsequent requests after cache is populated use cached value', async ()=>{
|
|
281
|
+
const testValue = 'test-value';
|
|
282
|
+
const config = createExpiringConfig({
|
|
283
|
+
fetchValue: fetchValue.mockResolvedValue(testValue),
|
|
284
|
+
key: 'test-key',
|
|
285
|
+
onCacheHit,
|
|
286
|
+
onFetch,
|
|
287
|
+
store: mockStore,
|
|
288
|
+
ttl: 5000
|
|
289
|
+
});
|
|
290
|
+
// Mock empty cache for first request
|
|
291
|
+
vi.mocked(mockStore.get).mockReturnValueOnce(undefined);
|
|
292
|
+
// First request should fetch
|
|
293
|
+
const result1 = await config.get();
|
|
294
|
+
// Mock cache populated for subsequent request
|
|
295
|
+
vi.mocked(mockStore.get).mockReturnValueOnce({
|
|
296
|
+
updatedAt: Date.now(),
|
|
297
|
+
value: testValue
|
|
298
|
+
});
|
|
299
|
+
// Second request should hit cache
|
|
300
|
+
const result2 = await config.get();
|
|
301
|
+
expect(result1).toBe(testValue);
|
|
302
|
+
expect(result2).toBe(testValue);
|
|
303
|
+
expect(fetchValue).toHaveBeenCalledOnce();
|
|
304
|
+
expect(onFetch).toHaveBeenCalledOnce();
|
|
305
|
+
expect(onCacheHit).toHaveBeenCalledOnce();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
//# sourceMappingURL=createExpiringConfig.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/util/__tests__/createExpiringConfig.test.ts"],"sourcesContent":["import type ConfigStore from 'configstore'\n\nimport {beforeEach, describe, expect, test, vi} from 'vitest'\n\nimport {createExpiringConfig} from '../createExpiringConfig.js'\n\ndescribe('createExpiringConfig', () => {\n let mockStore: ConfigStore\n let fetchValue: ReturnType<typeof vi.fn>\n let onCacheHit: ReturnType<typeof vi.fn>\n let onFetch: ReturnType<typeof vi.fn>\n let onRevalidate: ReturnType<typeof vi.fn>\n\n beforeEach(() => {\n // Mock ConfigStore\n mockStore = {\n delete: vi.fn(),\n get: vi.fn(),\n set: vi.fn(),\n } as unknown as ConfigStore\n\n // Reset all mocks\n fetchValue = vi.fn()\n onCacheHit = vi.fn()\n onFetch = vi.fn()\n onRevalidate = vi.fn()\n })\n\n test('returns fetched value when cache is empty', async () => {\n const testValue = 'test-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(testValue),\n key: 'test-key',\n onCacheHit,\n onFetch,\n onRevalidate,\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n const result = await config.get()\n\n expect(result).toBe(testValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n expect(onCacheHit).not.toHaveBeenCalled()\n expect(onRevalidate).not.toHaveBeenCalled()\n expect(mockStore.set).toHaveBeenCalledWith('test-key', {\n updatedAt: expect.any(Number),\n value: testValue,\n })\n })\n\n test('returns cached value when it has not expired', async () => {\n const cachedValue = 'cached-value'\n const ttl = 5000\n const updatedAt = Date.now() - 1000 // 1 second ago (not expired)\n\n const config = createExpiringConfig({\n fetchValue,\n key: 'test-key',\n onCacheHit,\n onFetch,\n onRevalidate,\n store: mockStore,\n ttl,\n })\n\n // Mock cached value that hasn't expired\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt,\n value: cachedValue,\n })\n\n const result = await config.get()\n\n expect(result).toBe(cachedValue)\n expect(fetchValue).not.toHaveBeenCalled()\n expect(onCacheHit).toHaveBeenCalledOnce()\n expect(onFetch).not.toHaveBeenCalled()\n expect(onRevalidate).not.toHaveBeenCalled()\n expect(mockStore.set).not.toHaveBeenCalled()\n })\n\n test('fetches new value when cached value has expired', async () => {\n const newValue = 'new-value'\n const ttl = 1000\n const updatedAt = Date.now() - 2000 // 2 seconds ago (expired)\n\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(newValue),\n key: 'test-key',\n onCacheHit,\n onFetch,\n onRevalidate,\n store: mockStore,\n ttl,\n })\n\n // Mock expired cached value\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt,\n value: 'old-value',\n })\n\n const result = await config.get()\n\n expect(result).toBe(newValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onRevalidate).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n expect(onCacheHit).not.toHaveBeenCalled()\n expect(mockStore.set).toHaveBeenCalledWith('test-key', {\n updatedAt: expect.any(Number),\n value: newValue,\n })\n })\n\n test('deletes cached value from store', () => {\n const config = createExpiringConfig({\n fetchValue,\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n config.delete()\n\n expect(mockStore.delete).toHaveBeenCalledWith('test-key')\n })\n\n test('handles concurrent get() calls correctly', async () => {\n const testValue = 'test-value'\n let resolvePromise: (value: string) => void\n const delayedFetch = new Promise<string>((resolve) => {\n resolvePromise = resolve\n })\n\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockReturnValue(delayedFetch),\n key: 'test-key',\n onFetch,\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n // Start multiple concurrent get() calls\n const promise1 = config.get()\n const promise2 = config.get()\n const promise3 = config.get()\n\n // Resolve the fetch\n resolvePromise!(testValue)\n\n const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3])\n\n expect(result1).toBe(testValue)\n expect(result2).toBe(testValue)\n expect(result3).toBe(testValue)\n expect(fetchValue).toHaveBeenCalledOnce() // Only one fetch should happen\n expect(onFetch).toHaveBeenCalledOnce()\n })\n\n test('handles synchronous fetchValue function', async () => {\n const testValue = 'sync-value'\n const syncFetchValue = vi.fn().mockReturnValue(testValue)\n\n const config = createExpiringConfig({\n fetchValue: syncFetchValue,\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n const result = await config.get()\n\n expect(result).toBe(testValue)\n expect(syncFetchValue).toHaveBeenCalledOnce()\n })\n\n test('handles fetchValue throwing an error', async () => {\n const error = new Error('Fetch failed')\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockRejectedValue(error),\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n await expect(config.get()).rejects.toThrow('Fetch failed')\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(mockStore.set).not.toHaveBeenCalled()\n })\n\n test('handles different data types as cached values', async () => {\n const objectValue = {key: 'value', number: 42}\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(objectValue),\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n const result = await config.get()\n\n expect(result).toEqual(objectValue)\n expect(mockStore.set).toHaveBeenCalledWith('test-key', {\n updatedAt: expect.any(Number),\n value: objectValue,\n })\n })\n\n test('works with TTL of 0 (immediate expiration)', async () => {\n const testValue = 'test-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(testValue),\n key: 'test-key',\n onRevalidate,\n store: mockStore,\n ttl: 0,\n })\n\n // Mock cached value that would be immediately expired\n // Use a timestamp from 1ms ago to ensure it's > ttl (0)\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt: Date.now() - 1,\n value: 'old-value',\n })\n\n const result = await config.get()\n\n expect(result).toBe(testValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onRevalidate).toHaveBeenCalledOnce()\n })\n\n test('works without optional callback functions', async () => {\n const testValue = 'test-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(testValue),\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n const result = await config.get()\n\n expect(result).toBe(testValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n })\n\n test('handles cached value without updatedAt timestamp', async () => {\n const newValue = 'new-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(newValue),\n key: 'test-key',\n onFetch,\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock cached value without updatedAt (invalid cache entry)\n vi.mocked(mockStore.get).mockReturnValue({\n value: 'old-value',\n // updatedAt is missing\n })\n\n const result = await config.get()\n\n expect(result).toBe(newValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n })\n\n test('handles cached value without value property', async () => {\n const newValue = 'new-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(newValue),\n key: 'test-key',\n onFetch,\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock cached entry without value property\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt: Date.now(),\n // value is missing\n })\n\n const result = await config.get()\n\n expect(result).toBe(newValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n })\n\n test('stores timestamp correctly when caching new values', async () => {\n const testValue = 'test-value'\n\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(testValue),\n key: 'test-key',\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n await config.get()\n\n expect(mockStore.set).toHaveBeenCalledWith('test-key', {\n updatedAt: expect.any(Number),\n value: testValue,\n })\n })\n\n test('subsequent requests after cache is populated use cached value', async () => {\n const testValue = 'test-value'\n const config = createExpiringConfig({\n fetchValue: fetchValue.mockResolvedValue(testValue),\n key: 'test-key',\n onCacheHit,\n onFetch,\n store: mockStore,\n ttl: 5000,\n })\n\n // Mock empty cache for first request\n vi.mocked(mockStore.get).mockReturnValueOnce(undefined)\n\n // First request should fetch\n const result1 = await config.get()\n\n // Mock cache populated for subsequent request\n vi.mocked(mockStore.get).mockReturnValueOnce({\n updatedAt: Date.now(),\n value: testValue,\n })\n\n // Second request should hit cache\n const result2 = await config.get()\n\n expect(result1).toBe(testValue)\n expect(result2).toBe(testValue)\n expect(fetchValue).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n expect(onCacheHit).toHaveBeenCalledOnce()\n })\n})\n"],"names":["beforeEach","describe","expect","test","vi","createExpiringConfig","mockStore","fetchValue","onCacheHit","onFetch","onRevalidate","delete","fn","get","set","testValue","config","mockResolvedValue","key","store","ttl","mocked","mockReturnValue","undefined","result","toBe","toHaveBeenCalledOnce","not","toHaveBeenCalled","toHaveBeenCalledWith","updatedAt","any","Number","value","cachedValue","Date","now","newValue","resolvePromise","delayedFetch","Promise","resolve","promise1","promise2","promise3","result1","result2","result3","all","syncFetchValue","error","Error","mockRejectedValue","rejects","toThrow","objectValue","number","toEqual","mockReturnValueOnce"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type ConfigStore from 'configstore';
|
|
2
|
+
export interface ExpiringConfigOptions<Type> {
|
|
3
|
+
/** Fetch value */
|
|
4
|
+
fetchValue: () => Promise<Type> | Type;
|
|
5
|
+
/** Config key */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Config store */
|
|
8
|
+
store: ConfigStore;
|
|
9
|
+
/** TTL (milliseconds) */
|
|
10
|
+
ttl: number;
|
|
11
|
+
/** Subscribe to cache hit event */
|
|
12
|
+
onCacheHit?: () => void;
|
|
13
|
+
/** Subscribe to fetch event */
|
|
14
|
+
onFetch?: () => void;
|
|
15
|
+
/** Subscribe to revalidate event */
|
|
16
|
+
onRevalidate?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export interface ExpiringConfigApi<Type> {
|
|
19
|
+
/**
|
|
20
|
+
* Delete the cached value.
|
|
21
|
+
*/
|
|
22
|
+
delete: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Attempt to get the cached value. If there is no cached value, or the cached value has expired,
|
|
25
|
+
* fetch, cache, and return the value.
|
|
26
|
+
*/
|
|
27
|
+
get: () => Promise<Type>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a config in the provided config store that expires after the provided TTL.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createExpiringConfig<Type>({ fetchValue, key, onCacheHit, onFetch, onRevalidate, store, ttl, }: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a config in the provided config store that expires after the provided TTL.
|
|
3
|
+
*/ export function createExpiringConfig({ fetchValue, key, onCacheHit = ()=>null, onFetch = ()=>null, onRevalidate = ()=>null, store, ttl }) {
|
|
4
|
+
let currentFetch = null;
|
|
5
|
+
return {
|
|
6
|
+
delete () {
|
|
7
|
+
store.delete(key);
|
|
8
|
+
},
|
|
9
|
+
async get () {
|
|
10
|
+
const { updatedAt, value } = store.get(key) ?? {};
|
|
11
|
+
if (value && updatedAt) {
|
|
12
|
+
const hasExpired = Date.now() - updatedAt > ttl;
|
|
13
|
+
if (!hasExpired) {
|
|
14
|
+
onCacheHit();
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
onRevalidate();
|
|
18
|
+
}
|
|
19
|
+
if (currentFetch) {
|
|
20
|
+
return currentFetch;
|
|
21
|
+
}
|
|
22
|
+
onFetch();
|
|
23
|
+
currentFetch = Promise.resolve(fetchValue());
|
|
24
|
+
const nextValue = await currentFetch;
|
|
25
|
+
currentFetch = null;
|
|
26
|
+
store.set(key, {
|
|
27
|
+
updatedAt: Date.now(),
|
|
28
|
+
value: nextValue
|
|
29
|
+
});
|
|
30
|
+
return nextValue;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=createExpiringConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/createExpiringConfig.ts"],"sourcesContent":["import type ConfigStore from 'configstore'\n\nexport interface ExpiringConfigOptions<Type> {\n /** Fetch value */\n fetchValue: () => Promise<Type> | Type\n /** Config key */\n key: string\n /** Config store */\n store: ConfigStore\n /** TTL (milliseconds) */\n ttl: number\n\n /** Subscribe to cache hit event */\n onCacheHit?: () => void\n /** Subscribe to fetch event */\n onFetch?: () => void\n /** Subscribe to revalidate event */\n onRevalidate?: () => void\n}\n\nexport interface ExpiringConfigApi<Type> {\n /**\n * Delete the cached value.\n */\n delete: () => void\n /**\n * Attempt to get the cached value. If there is no cached value, or the cached value has expired,\n * fetch, cache, and return the value.\n */\n get: () => Promise<Type>\n}\n\n/**\n * Create a config in the provided config store that expires after the provided TTL.\n */\nexport function createExpiringConfig<Type>({\n fetchValue,\n key,\n onCacheHit = () => null,\n onFetch = () => null,\n onRevalidate = () => null,\n store,\n ttl,\n}: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type> {\n let currentFetch: Promise<Type> | null = null\n return {\n delete() {\n store.delete(key)\n },\n async get() {\n const {updatedAt, value} = store.get(key) ?? {}\n\n if (value && updatedAt) {\n const hasExpired = Date.now() - updatedAt > ttl\n\n if (!hasExpired) {\n onCacheHit()\n return value\n }\n\n onRevalidate()\n }\n\n if (currentFetch) {\n return currentFetch\n }\n onFetch()\n\n currentFetch = Promise.resolve(fetchValue())\n const nextValue = await currentFetch\n currentFetch = null\n\n store.set(key, {\n updatedAt: Date.now(),\n value: nextValue,\n })\n\n return nextValue\n },\n }\n}\n"],"names":["createExpiringConfig","fetchValue","key","onCacheHit","onFetch","onRevalidate","store","ttl","currentFetch","delete","get","updatedAt","value","hasExpired","Date","now","Promise","resolve","nextValue","set"],"mappings":"AAgCA;;CAEC,GACD,OAAO,SAASA,qBAA2B,EACzCC,UAAU,EACVC,GAAG,EACHC,aAAa,IAAM,IAAI,EACvBC,UAAU,IAAM,IAAI,EACpBC,eAAe,IAAM,IAAI,EACzBC,KAAK,EACLC,GAAG,EACyB;IAC5B,IAAIC,eAAqC;IACzC,OAAO;QACLC;YACEH,MAAMG,MAAM,CAACP;QACf;QACA,MAAMQ;YACJ,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAC,GAAGN,MAAMI,GAAG,CAACR,QAAQ,CAAC;YAE9C,IAAIU,SAASD,WAAW;gBACtB,MAAME,aAAaC,KAAKC,GAAG,KAAKJ,YAAYJ;gBAE5C,IAAI,CAACM,YAAY;oBACfV;oBACA,OAAOS;gBACT;gBAEAP;YACF;YAEA,IAAIG,cAAc;gBAChB,OAAOA;YACT;YACAJ;YAEAI,eAAeQ,QAAQC,OAAO,CAAChB;YAC/B,MAAMiB,YAAY,MAAMV;YACxBA,eAAe;YAEfF,MAAMa,GAAG,CAACjB,KAAK;gBACbS,WAAWG,KAAKC,GAAG;gBACnBH,OAAOM;YACT;YAEA,OAAOA;QACT;IACF;AACF"}
|