@sanity/cli-core 0.0.2-alpha.2 → 0.1.0-alpha.10
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/dist/SanityCommand.js +34 -3
- package/dist/SanityCommand.js.map +1 -1
- package/dist/_exports/tree.d.ts +47 -0
- package/dist/_exports/tree.js +3 -0
- package/dist/_exports/tree.js.map +1 -0
- package/dist/_exports/ux.d.ts +40 -0
- package/dist/_exports/ux.js +6 -0
- package/dist/_exports/ux.js.map +1 -0
- package/dist/config/cli/getCliConfig.worker.js +2 -1
- package/dist/config/cli/getCliConfig.worker.js.map +1 -1
- package/dist/config/cli/schemas.js +5 -2
- package/dist/config/cli/schemas.js.map +1 -1
- package/dist/config/cli/types/cliConfig.js.map +1 -1
- package/dist/config/findProjectRoot.js +2 -2
- package/dist/config/findProjectRoot.js.map +1 -1
- package/dist/config/studio/getStudioConfig.js +0 -3
- package/dist/config/studio/getStudioConfig.js.map +1 -1
- package/dist/config/studio/getStudioWorkspaces.js +50 -0
- package/dist/config/studio/getStudioWorkspaces.js.map +1 -0
- package/dist/config/studio/isStudioConfig.js +19 -0
- package/dist/config/studio/isStudioConfig.js.map +1 -0
- package/dist/config/studio/readStudioConfig.js +13 -9
- package/dist/config/studio/readStudioConfig.js.map +1 -1
- package/dist/config/studio/readStudioConfig.worker.js +4 -30
- package/dist/config/studio/readStudioConfig.worker.js.map +1 -1
- package/dist/config/util/findStudioConfigPath.js +24 -3
- package/dist/config/util/findStudioConfigPath.js.map +1 -1
- package/dist/config/util/recursivelyResolveProjectRoot.js.map +1 -1
- package/dist/index.d.ts +6027 -36
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/loaders/studio/studioWorkerLoader.worker.js +10 -24
- package/dist/loaders/studio/studioWorkerLoader.worker.js.map +1 -1
- package/dist/loaders/studio/studioWorkerTask.js +41 -11
- package/dist/loaders/studio/studioWorkerTask.js.map +1 -1
- package/dist/loaders/tsx/tsxWorkerTask.js +3 -0
- package/dist/loaders/tsx/tsxWorkerTask.js.map +1 -1
- package/dist/services/apiClient.js +18 -17
- package/dist/services/apiClient.js.map +1 -1
- package/dist/telemetry/cleanupOldTelemetryFiles.js +30 -0
- package/dist/telemetry/cleanupOldTelemetryFiles.js.map +1 -0
- package/dist/telemetry/createTelemetryStore.js +95 -0
- package/dist/telemetry/createTelemetryStore.js.map +1 -0
- package/dist/telemetry/createTraceId.js +10 -0
- package/dist/telemetry/createTraceId.js.map +1 -0
- package/dist/telemetry/findTelemetryFiles.js +36 -0
- package/dist/telemetry/findTelemetryFiles.js.map +1 -0
- package/dist/telemetry/flushTelemetryFiles.js +107 -0
- package/dist/telemetry/flushTelemetryFiles.js.map +1 -0
- package/dist/telemetry/generateTelemetryFilePath.js +30 -0
- package/dist/telemetry/generateTelemetryFilePath.js.map +1 -0
- package/dist/telemetry/getTelemetryBaseInfo.js +33 -0
- package/dist/telemetry/getTelemetryBaseInfo.js.map +1 -0
- package/dist/telemetry/logger.js +54 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/telemetryStoreDebug.js +7 -0
- package/dist/telemetry/telemetryStoreDebug.js.map +1 -0
- package/dist/telemetry/trace.js +150 -0
- package/dist/telemetry/trace.js.map +1 -0
- package/dist/telemetry/types.js +5 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/util/doImport.js +16 -0
- package/dist/util/doImport.js.map +1 -0
- package/dist/util/environment/getStudioEnvironmentVariables.js +2 -1
- package/dist/util/environment/getStudioEnvironmentVariables.js.map +1 -1
- package/dist/util/getCliTelemetry.js +36 -0
- package/dist/util/getCliTelemetry.js.map +1 -0
- package/dist/util/isStaging.js +10 -0
- package/dist/util/isStaging.js.map +1 -0
- package/dist/util/normalizePath.js +12 -0
- package/dist/util/normalizePath.js.map +1 -0
- package/dist/util/parseStringFlag.js +19 -0
- package/dist/util/parseStringFlag.js.map +1 -0
- package/dist/util/readNDJSON.js +18 -0
- package/dist/util/readNDJSON.js.map +1 -0
- package/dist/util/resolveLocalPackage.js +34 -0
- package/dist/util/resolveLocalPackage.js.map +1 -0
- package/dist/util/tree.js +108 -0
- package/dist/util/tree.js.map +1 -0
- package/dist/util/waitForAsync.js +5 -0
- package/dist/util/waitForAsync.js.map +1 -0
- package/dist/ux/boxen.js +3 -0
- package/dist/ux/boxen.js.map +1 -0
- package/dist/ux/colorizeJson.js +6 -6
- package/dist/ux/colorizeJson.js.map +1 -1
- package/dist/ux/prompts.js +3 -0
- package/dist/ux/prompts.js.map +1 -0
- package/dist/ux/spinner.js +1 -1
- package/dist/ux/spinner.js.map +1 -1
- package/package.json +53 -28
- package/dist/SanityCommand.d.ts +0 -56
- package/dist/config/__tests__/cliToken.test.js +0 -74
- package/dist/config/__tests__/cliToken.test.js.map +0 -1
- package/dist/config/__tests__/cliUserConfig.test.js +0 -131
- package/dist/config/__tests__/cliUserConfig.test.js.map +0 -1
- package/dist/config/__tests__/findProjectRoot.test.js +0 -159
- package/dist/config/__tests__/findProjectRoot.test.js.map +0 -1
- package/dist/config/__tests__/findProjectRootSync.test.js +0 -112
- package/dist/config/__tests__/findProjectRootSync.test.js.map +0 -1
- package/dist/config/__tests__/getCliConfigSync.test.js +0 -31
- package/dist/config/__tests__/getCliConfigSync.test.js.map +0 -1
- package/dist/config/cli/getCliConfig.d.ts +0 -16
- package/dist/config/cli/getCliConfig.worker.d.ts +0 -1
- package/dist/config/cli/getCliConfigSync.d.ts +0 -12
- package/dist/config/cli/schemas.d.ts +0 -255
- package/dist/config/cli/types/cliConfig.d.ts +0 -74
- package/dist/config/cli/types/userViteConfig.d.ts +0 -5
- package/dist/config/findProjectRoot.d.ts +0 -14
- package/dist/config/findProjectRootSync.d.ts +0 -27
- package/dist/config/studio/getStudioConfig.d.ts +0 -14
- package/dist/config/studio/readStudioConfig.d.ts +0 -190
- package/dist/config/studio/readStudioConfig.worker.d.ts +0 -1
- package/dist/config/util/configPathsSync.d.ts +0 -17
- package/dist/config/util/findAppConfigPath.d.ts +0 -8
- package/dist/config/util/findConfigsPaths.d.ts +0 -16
- package/dist/config/util/findStudioConfigPath.d.ts +0 -9
- package/dist/config/util/isSanityV2StudioRoot.d.ts +0 -8
- package/dist/config/util/recursivelyResolveProjectRoot.d.ts +0 -27
- package/dist/debug.d.ts +0 -15
- package/dist/loaders/studio/studioWorkerLoader.worker.d.ts +0 -1
- package/dist/loaders/studio/studioWorkerTask.d.ts +0 -40
- package/dist/loaders/tsx/tsxWorkerLoader.worker.d.ts +0 -1
- package/dist/loaders/tsx/tsxWorkerTask.d.ts +0 -28
- package/dist/services/apiClient.d.ts +0 -53
- package/dist/services/cliUserConfig.d.ts +0 -57
- package/dist/services/getCliToken.d.ts +0 -7
- package/dist/types.d.ts +0 -7
- package/dist/util/NotFoundError.d.ts +0 -20
- package/dist/util/__tests__/createExpiringConfig.test.js +0 -400
- package/dist/util/__tests__/createExpiringConfig.test.js.map +0 -1
- package/dist/util/createExpiringConfig.d.ts +0 -37
- package/dist/util/environment/getStudioEnvironmentVariables.d.ts +0 -12
- package/dist/util/environment/mockBrowserEnvironment.d.ts +0 -17
- package/dist/util/environment/setupBrowserStubs.d.ts +0 -10
- package/dist/util/environment/stubs.d.ts +0 -254
- package/dist/util/fileExists.d.ts +0 -9
- package/dist/util/generateHelpUrl.d.ts +0 -8
- package/dist/util/getEmptyAuth.d.ts +0 -5
- package/dist/util/getSanityEnvVar.d.ts +0 -19
- package/dist/util/getSanityUrl.d.ts +0 -5
- package/dist/util/getUserConfig.d.ts +0 -2
- package/dist/util/isCi.d.ts +0 -1
- package/dist/util/isHttpError.d.ts +0 -29
- package/dist/util/isHttpError.js +0 -18
- package/dist/util/isHttpError.js.map +0 -1
- package/dist/util/isInteractive.d.ts +0 -1
- package/dist/util/isRecord.d.ts +0 -8
- package/dist/util/isTrueish.d.ts +0 -1
- package/dist/util/readJsonFile.d.ts +0 -14
- package/dist/util/safeStructuredClone.d.ts +0 -8
- package/dist/util/tryGetDefaultExport.d.ts +0 -5
- package/dist/util/writeJsonFile.d.ts +0 -9
- package/dist/ux/colorizeJson.d.ts +0 -1
- package/dist/ux/formatObject.d.ts +0 -1
- package/dist/ux/logSymbols.d.ts +0 -1
- package/dist/ux/printKeyValue.d.ts +0 -1
- package/dist/ux/spinner.d.ts +0 -1
- package/dist/ux/timer.d.ts +0 -12
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Result of finding a project configuration
|
|
3
|
-
*
|
|
4
|
-
* @internal
|
|
5
|
-
*/
|
|
6
|
-
export interface ProjectRootResult {
|
|
7
|
-
directory: string;
|
|
8
|
-
/**
|
|
9
|
-
* Path to the project configuration file, if found.
|
|
10
|
-
*/
|
|
11
|
-
path: string;
|
|
12
|
-
/**
|
|
13
|
-
* Type of project root.
|
|
14
|
-
*/
|
|
15
|
-
type: 'app' | 'studio';
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Generic recursive search function for project configuration files.
|
|
19
|
-
*
|
|
20
|
-
* @param basePath - The base path to start searching from
|
|
21
|
-
* @param findConfigFn - Function that looks for config files in a given directory
|
|
22
|
-
* @param projectType - The type of project ('app' | 'studio')
|
|
23
|
-
* @param iterations - Current iteration count, passed internally to prevent infinite recursion
|
|
24
|
-
* @returns A promise that resolves to an object if config is found, false otherwise
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export declare function recursivelyResolveProjectRoot(basePath: string, findConfigFn: (path: string) => Promise<string | undefined>, projectType: 'app' | 'studio', iterations?: number): Promise<false | ProjectRootResult>;
|
package/dist/debug.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import debugIt from 'debug';
|
|
2
|
-
/**
|
|
3
|
-
* `debug` instance for the CLI
|
|
4
|
-
*
|
|
5
|
-
* @internal
|
|
6
|
-
*/
|
|
7
|
-
export declare const debug: debugIt.Debugger;
|
|
8
|
-
/**
|
|
9
|
-
* Get a `debug` instance which extends the CLI debug instance with the given namespace,
|
|
10
|
-
* eg namespace would be `sanity:cli:<providedNamespace>`
|
|
11
|
-
*
|
|
12
|
-
* @param namespace - The namespace to extend the CLI debug instance with
|
|
13
|
-
* @returns The extended `debug` instance
|
|
14
|
-
*/
|
|
15
|
-
export declare const subdebug: (namespace: string) => debugIt.Debugger;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { type WorkerOptions } from 'node:worker_threads';
|
|
2
|
-
import { type RequireProps } from '../../types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Options for the studio worker task
|
|
5
|
-
*
|
|
6
|
-
* @internal
|
|
7
|
-
*/
|
|
8
|
-
interface StudioWorkerTaskOptions extends RequireProps<WorkerOptions, 'name'> {
|
|
9
|
-
studioRootPath: string;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Executes a worker file in a Sanity Studio browser context.
|
|
13
|
-
*
|
|
14
|
-
* This uses a combination of vite for "bundling" + jsdom for emulating a browser
|
|
15
|
-
* environment under the hood, which means that the same thing that will work in vite
|
|
16
|
-
* _should_ work in the worker - to a degree. If the user has defined any typescript
|
|
17
|
-
* path aliases, these will have to be added as aliases to the vite config - the same
|
|
18
|
-
* behavior as you would see with regular vite. Other things that are accounted for:
|
|
19
|
-
*
|
|
20
|
-
* - TypeScript support (+JSX, enums and other "compilation needed" features)
|
|
21
|
-
* - CSS, font and other file imports will resolve to a file path
|
|
22
|
-
* - CSS module imports will resolve to a javascript object of class names
|
|
23
|
-
* - Environment variables are available both as `import.meta.env` and `process.env`,
|
|
24
|
-
* and `.env` files are loaded in the same way that they would in a Sanity studio.
|
|
25
|
-
* - Browser globals not available in a Node.js environment but _are_ provided by JSDOM
|
|
26
|
-
* are defined directly to the Node environment as globals. While this polutes the
|
|
27
|
-
* global namespace, it is done only in the worker thread.
|
|
28
|
-
* - Certain browser globals that are _not_ available in JSDOM are also provided to the
|
|
29
|
-
* global namespace - things like `requestIdleCallback`, `IntersectionObserver` etc.
|
|
30
|
-
* These are provided with a minimal stub implementation to make them not crash.
|
|
31
|
-
*
|
|
32
|
-
* @param filePath - Path to the worker file (`.ts` works and is encouraged)
|
|
33
|
-
* @param options - Options to pass to the worker
|
|
34
|
-
* @returns A promise that resolves with the message from the worker
|
|
35
|
-
* @throws If the file does not exist
|
|
36
|
-
* @throws If the worker exits with a non-zero code
|
|
37
|
-
* @internal
|
|
38
|
-
*/
|
|
39
|
-
export declare function studioWorkerTask(filePath: URL, options: StudioWorkerTaskOptions): Promise<unknown>;
|
|
40
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { URL } from 'node:url';
|
|
2
|
-
import { type WorkerOptions } from 'node:worker_threads';
|
|
3
|
-
import { type RequireProps } from '../../types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Options for the tsx worker task
|
|
6
|
-
*
|
|
7
|
-
* @internal
|
|
8
|
-
*/
|
|
9
|
-
interface TsxWorkerTaskOptions extends RequireProps<WorkerOptions, 'name'> {
|
|
10
|
-
rootPath: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Executes a worker file with tsx registered. This means you can import other
|
|
14
|
-
* typescript with fairly rich syntax, and still have that only apply to the worker
|
|
15
|
-
* thread instead of the full parent process. The worker should emit a message when
|
|
16
|
-
* complete using `parentPort`. Once it has received a single message will resolve the
|
|
17
|
-
* returned promise with that message. If you are expecting multiple messages, you will
|
|
18
|
-
* have to implement another method ;)
|
|
19
|
-
*
|
|
20
|
-
* @param filePath - Path to the worker file
|
|
21
|
-
* @param options - Options to pass to the worker
|
|
22
|
-
* @returns A promise that resolves with the message from the worker
|
|
23
|
-
* @throws If the file does not exist
|
|
24
|
-
* @throws If the worker exits with a non-zero code
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export declare function tsxWorkerTask<T = unknown>(filePath: URL, options: TsxWorkerTaskOptions): Promise<T>;
|
|
28
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { type ClientConfig, type SanityClient } from '@sanity/client';
|
|
2
|
-
/**
|
|
3
|
-
* @internal
|
|
4
|
-
*/
|
|
5
|
-
export interface GlobalCliClientOptions extends ClientConfig {
|
|
6
|
-
/**
|
|
7
|
-
* The API version to use for this client.
|
|
8
|
-
*/
|
|
9
|
-
apiVersion: string;
|
|
10
|
-
/**
|
|
11
|
-
* Whether to require a user to be authenticated to use this client.
|
|
12
|
-
* Default: `false`.
|
|
13
|
-
* Throws an error if `true` and user is not authenticated.
|
|
14
|
-
*/
|
|
15
|
-
requireUser?: boolean;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Create a "global" (unscoped) Sanity API client.
|
|
19
|
-
*
|
|
20
|
-
* @param options - The options to use for the client.
|
|
21
|
-
* @returns Promise that resolves to a configured Sanity API client.
|
|
22
|
-
*/
|
|
23
|
-
export declare function getGlobalCliClient({ requireUser, ...config }: GlobalCliClientOptions): Promise<SanityClient>;
|
|
24
|
-
/**
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export interface ProjectCliClientOptions extends ClientConfig {
|
|
28
|
-
/**
|
|
29
|
-
* The API version to use for this client.
|
|
30
|
-
*/
|
|
31
|
-
apiVersion: string;
|
|
32
|
-
/**
|
|
33
|
-
* The project ID to use for this client.
|
|
34
|
-
*/
|
|
35
|
-
projectId: string;
|
|
36
|
-
/**
|
|
37
|
-
* The dataset to use for this client.
|
|
38
|
-
*/
|
|
39
|
-
dataset?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Whether to require a user to be authenticated to use this client.
|
|
42
|
-
* Default: `false`.
|
|
43
|
-
* Throws an error if `true` and user is not authenticated.
|
|
44
|
-
*/
|
|
45
|
-
requireUser?: boolean;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Create a "global" (unscoped) Sanity API client.
|
|
49
|
-
*
|
|
50
|
-
* @param options - The options to use for the client.
|
|
51
|
-
* @returns Promise that resolves to a configured Sanity API client.
|
|
52
|
-
*/
|
|
53
|
-
export declare function getProjectCliClient({ requireUser, ...config }: ProjectCliClientOptions): Promise<SanityClient>;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
declare const cliUserConfigSchema: {
|
|
3
|
-
authToken: z.ZodOptional<z.ZodString>;
|
|
4
|
-
telemetryConsent: z.ZodOptional<z.ZodObject<{
|
|
5
|
-
updatedAt: z.ZodOptional<z.ZodNumber>;
|
|
6
|
-
value: z.ZodObject<{
|
|
7
|
-
status: z.ZodEnum<["undetermined", "unset", "granted", "denied"]>;
|
|
8
|
-
type: z.ZodString;
|
|
9
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
10
|
-
status: z.ZodEnum<["undetermined", "unset", "granted", "denied"]>;
|
|
11
|
-
type: z.ZodString;
|
|
12
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
13
|
-
status: z.ZodEnum<["undetermined", "unset", "granted", "denied"]>;
|
|
14
|
-
type: z.ZodString;
|
|
15
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
16
|
-
}, "strip", z.ZodTypeAny, {
|
|
17
|
-
value: {
|
|
18
|
-
type: string;
|
|
19
|
-
status: "undetermined" | "unset" | "granted" | "denied";
|
|
20
|
-
} & {
|
|
21
|
-
[k: string]: unknown;
|
|
22
|
-
};
|
|
23
|
-
updatedAt?: number | undefined;
|
|
24
|
-
}, {
|
|
25
|
-
value: {
|
|
26
|
-
type: string;
|
|
27
|
-
status: "undetermined" | "unset" | "granted" | "denied";
|
|
28
|
-
} & {
|
|
29
|
-
[k: string]: unknown;
|
|
30
|
-
};
|
|
31
|
-
updatedAt?: number | undefined;
|
|
32
|
-
}>>;
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* The CLI user configuration schema.
|
|
36
|
-
*
|
|
37
|
-
* @internal
|
|
38
|
-
*/
|
|
39
|
-
type CliUserConfig = z.infer<z.ZodObject<typeof cliUserConfigSchema>>;
|
|
40
|
-
/**
|
|
41
|
-
* Set the config value for the given property.
|
|
42
|
-
* Validates that the passed value adheres to the defined CLI config schema.
|
|
43
|
-
*
|
|
44
|
-
* @param prop - The property to set the value for
|
|
45
|
-
* @param value - The value to set
|
|
46
|
-
* @internal
|
|
47
|
-
*/
|
|
48
|
-
export declare function setConfig<P extends keyof CliUserConfig>(prop: P, value: CliUserConfig[P]): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Get the config value for the given property
|
|
51
|
-
*
|
|
52
|
-
* @param prop - The property to get the value for
|
|
53
|
-
* @returns The value of the given property
|
|
54
|
-
* @internal
|
|
55
|
-
*/
|
|
56
|
-
export declare function getConfig<P extends keyof CliUserConfig>(prop: P): Promise<CliUserConfig[P]>;
|
|
57
|
-
export {};
|
package/dist/types.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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;
|
|
@@ -1,400 +0,0 @@
|
|
|
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
|
-
test('throws when cached value fails validateValue', async ()=>{
|
|
308
|
-
const invalidCached = 123;
|
|
309
|
-
const ttl = 10_000;
|
|
310
|
-
const validateValue = vi.fn((v)=>typeof v === 'string');
|
|
311
|
-
const config = createExpiringConfig({
|
|
312
|
-
fetchValue,
|
|
313
|
-
key: 'test-key',
|
|
314
|
-
onCacheHit,
|
|
315
|
-
onFetch,
|
|
316
|
-
onRevalidate,
|
|
317
|
-
store: mockStore,
|
|
318
|
-
ttl,
|
|
319
|
-
// @ts-expect-error vitest mocks don't jive with assertions
|
|
320
|
-
validateValue
|
|
321
|
-
});
|
|
322
|
-
// Cached entry that is not expired but invalid per validateValue
|
|
323
|
-
vi.mocked(mockStore.get).mockReturnValue({
|
|
324
|
-
updatedAt: Date.now(),
|
|
325
|
-
value: invalidCached
|
|
326
|
-
});
|
|
327
|
-
await expect(config.get()).rejects.toThrow('Stored value is invalid');
|
|
328
|
-
expect(validateValue).toHaveBeenCalledOnce();
|
|
329
|
-
expect(onCacheHit).not.toHaveBeenCalled();
|
|
330
|
-
expect(onFetch).not.toHaveBeenCalled();
|
|
331
|
-
expect(onRevalidate).not.toHaveBeenCalled();
|
|
332
|
-
expect(mockStore.set).not.toHaveBeenCalled();
|
|
333
|
-
});
|
|
334
|
-
test('throws when fetched value fails validateValue (cache miss)', async ()=>{
|
|
335
|
-
const validateValue = vi.fn((v)=>typeof v === 'string');
|
|
336
|
-
const config = createExpiringConfig({
|
|
337
|
-
fetchValue: fetchValue.mockResolvedValue(42),
|
|
338
|
-
key: 'test-key',
|
|
339
|
-
onFetch,
|
|
340
|
-
store: mockStore,
|
|
341
|
-
ttl: 5000,
|
|
342
|
-
// @ts-expect-error vitest mocks don't jive with assertions
|
|
343
|
-
validateValue
|
|
344
|
-
});
|
|
345
|
-
// Empty cache
|
|
346
|
-
vi.mocked(mockStore.get).mockReturnValue(undefined);
|
|
347
|
-
await expect(config.get()).rejects.toThrow('Fetched value is invalid');
|
|
348
|
-
expect(onFetch).toHaveBeenCalledOnce();
|
|
349
|
-
expect(validateValue).toHaveBeenCalledOnce();
|
|
350
|
-
expect(mockStore.set).not.toHaveBeenCalled();
|
|
351
|
-
});
|
|
352
|
-
test('returns cached value when validateValue accepts it', async ()=>{
|
|
353
|
-
const cachedValue = 'ok';
|
|
354
|
-
const validateValue = vi.fn((v)=>typeof v === 'string');
|
|
355
|
-
const config = createExpiringConfig({
|
|
356
|
-
fetchValue,
|
|
357
|
-
key: 'test-key',
|
|
358
|
-
onCacheHit,
|
|
359
|
-
store: mockStore,
|
|
360
|
-
ttl: 5000,
|
|
361
|
-
// @ts-expect-error vitest mocks don't jive with assertions
|
|
362
|
-
validateValue
|
|
363
|
-
});
|
|
364
|
-
vi.mocked(mockStore.get).mockReturnValue({
|
|
365
|
-
updatedAt: Date.now(),
|
|
366
|
-
value: cachedValue
|
|
367
|
-
});
|
|
368
|
-
const result = await config.get();
|
|
369
|
-
expect(result).toBe(cachedValue);
|
|
370
|
-
expect(validateValue).toHaveBeenCalledOnce();
|
|
371
|
-
expect(onCacheHit).toHaveBeenCalledOnce();
|
|
372
|
-
expect(fetchValue).not.toHaveBeenCalled();
|
|
373
|
-
});
|
|
374
|
-
test('revalidation path validates fetched value and throws if invalid', async ()=>{
|
|
375
|
-
const validateValue = vi.fn((v)=>typeof v === 'string');
|
|
376
|
-
const config = createExpiringConfig({
|
|
377
|
-
fetchValue: fetchValue.mockResolvedValue(99),
|
|
378
|
-
key: 'test-key',
|
|
379
|
-
onFetch,
|
|
380
|
-
onRevalidate,
|
|
381
|
-
store: mockStore,
|
|
382
|
-
ttl: 1,
|
|
383
|
-
// @ts-expect-error vitest mocks don't jive with assertions
|
|
384
|
-
validateValue
|
|
385
|
-
});
|
|
386
|
-
// Cached value that has expired but is otherwise valid in shape and passes validate
|
|
387
|
-
vi.mocked(mockStore.get).mockReturnValue({
|
|
388
|
-
updatedAt: Date.now() - 10,
|
|
389
|
-
value: 'stale'
|
|
390
|
-
});
|
|
391
|
-
await expect(config.get()).rejects.toThrow('Fetched value is invalid');
|
|
392
|
-
expect(onRevalidate).toHaveBeenCalledOnce();
|
|
393
|
-
expect(onFetch).toHaveBeenCalledOnce();
|
|
394
|
-
// validateValue called for stored value and fetched value
|
|
395
|
-
expect(validateValue).toHaveBeenCalledTimes(2);
|
|
396
|
-
expect(mockStore.set).not.toHaveBeenCalled();
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
//# sourceMappingURL=createExpiringConfig.test.js.map
|