@sanity/cli-core 0.0.2-alpha.0 → 0.0.2-alpha.2

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.
Files changed (71) hide show
  1. package/dist/SanityCommand.d.ts +1 -1
  2. package/dist/SanityCommand.js.map +1 -1
  3. package/dist/config/__tests__/findProjectRootSync.test.js +112 -0
  4. package/dist/config/__tests__/findProjectRootSync.test.js.map +1 -0
  5. package/dist/config/__tests__/getCliConfigSync.test.js +31 -0
  6. package/dist/config/__tests__/getCliConfigSync.test.js.map +1 -0
  7. package/dist/config/cli/getCliConfig.d.ts +1 -1
  8. package/dist/config/cli/getCliConfig.js +9 -8
  9. package/dist/config/cli/getCliConfig.js.map +1 -1
  10. package/dist/config/cli/getCliConfigSync.d.ts +12 -0
  11. package/dist/config/cli/getCliConfigSync.js +51 -0
  12. package/dist/config/cli/getCliConfigSync.js.map +1 -0
  13. package/dist/config/cli/schemas.d.ts +121 -70
  14. package/dist/config/cli/schemas.js +13 -41
  15. package/dist/config/cli/schemas.js.map +1 -1
  16. package/dist/config/cli/types/cliConfig.d.ts +74 -0
  17. package/dist/config/cli/types/cliConfig.js +5 -0
  18. package/dist/config/cli/types/cliConfig.js.map +1 -0
  19. package/dist/config/cli/types/userViteConfig.d.ts +5 -0
  20. package/dist/config/cli/types/userViteConfig.js +5 -0
  21. package/dist/config/cli/types/userViteConfig.js.map +1 -0
  22. package/dist/config/findProjectRootSync.d.ts +27 -0
  23. package/dist/config/findProjectRootSync.js +82 -0
  24. package/dist/config/findProjectRootSync.js.map +1 -0
  25. package/dist/config/studio/readStudioConfig.worker.js +2 -16
  26. package/dist/config/studio/readStudioConfig.worker.js.map +1 -1
  27. package/dist/config/util/configPathsSync.d.ts +17 -0
  28. package/dist/config/util/configPathsSync.js +85 -0
  29. package/dist/config/util/configPathsSync.js.map +1 -0
  30. package/dist/config/util/isSanityV2StudioRoot.js +3 -0
  31. package/dist/config/util/isSanityV2StudioRoot.js.map +1 -1
  32. package/dist/index.d.ts +9 -1
  33. package/dist/index.js +7 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/loaders/studio/studioWorkerLoader.worker.js +9 -27
  36. package/dist/loaders/studio/studioWorkerLoader.worker.js.map +1 -1
  37. package/dist/services/apiClient.d.ts +19 -5
  38. package/dist/services/apiClient.js +18 -14
  39. package/dist/services/apiClient.js.map +1 -1
  40. package/dist/util/__tests__/createExpiringConfig.test.js +91 -0
  41. package/dist/util/__tests__/createExpiringConfig.test.js.map +1 -1
  42. package/dist/util/createExpiringConfig.d.ts +6 -1
  43. package/dist/util/createExpiringConfig.js +28 -3
  44. package/dist/util/createExpiringConfig.js.map +1 -1
  45. package/dist/util/environment/getStudioEnvironmentVariables.d.ts +12 -0
  46. package/dist/util/environment/getStudioEnvironmentVariables.js +33 -0
  47. package/dist/util/environment/getStudioEnvironmentVariables.js.map +1 -0
  48. package/dist/util/environment/mockBrowserEnvironment.d.ts +17 -0
  49. package/dist/util/environment/mockBrowserEnvironment.js +46 -0
  50. package/dist/util/environment/mockBrowserEnvironment.js.map +1 -0
  51. package/dist/util/environment/setupBrowserStubs.d.ts +10 -0
  52. package/dist/util/environment/setupBrowserStubs.js +43 -0
  53. package/dist/util/environment/setupBrowserStubs.js.map +1 -0
  54. package/dist/{loaders/studio → util/environment}/stubs.d.ts +6 -6
  55. package/dist/util/environment/stubs.js.map +1 -0
  56. package/dist/util/getEmptyAuth.d.ts +5 -0
  57. package/dist/util/getEmptyAuth.js +16 -0
  58. package/dist/util/getEmptyAuth.js.map +1 -0
  59. package/dist/util/readJsonFile.d.ts +7 -1
  60. package/dist/util/readJsonFile.js.map +1 -1
  61. package/dist/util/safeStructuredClone.js +3 -0
  62. package/dist/util/safeStructuredClone.js.map +1 -1
  63. package/dist/util/tryGetDefaultExport.d.ts +5 -0
  64. package/dist/util/tryGetDefaultExport.js +18 -0
  65. package/dist/util/tryGetDefaultExport.js.map +1 -0
  66. package/package.json +10 -9
  67. package/dist/config/cli/types.d.ts +0 -13
  68. package/dist/config/cli/types.js +0 -3
  69. package/dist/config/cli/types.js.map +0 -1
  70. package/dist/loaders/studio/stubs.js.map +0 -1
  71. /package/dist/{loaders/studio → util/environment}/stubs.js +0 -0
@@ -1,15 +1,13 @@
1
- import { resolve } from 'node:path';
2
- import { pathToFileURL } from 'node:url';
3
1
  import { isMainThread } from 'node:worker_threads';
4
- import { moduleResolve } from 'import-meta-resolve';
5
2
  import { createServer, loadEnv, mergeConfig } from 'vite';
6
3
  import { ViteNodeRunner } from 'vite-node/client';
7
4
  import { ViteNodeServer } from 'vite-node/server';
8
5
  import { installSourcemapsSupport } from 'vite-node/source-map';
9
6
  import { getCliConfig } from '../../config/cli/getCliConfig.js';
7
+ import { getStudioEnvironmentVariables } from '../../util/environment/getStudioEnvironmentVariables.js';
8
+ import { setupBrowserStubs } from '../../util/environment/setupBrowserStubs.js';
10
9
  import { isRecord } from '../../util/isRecord.js';
11
10
  import { isNotFoundError } from '../../util/NotFoundError.js';
12
- import * as stubs from './stubs.js';
13
11
  if (isMainThread) {
14
12
  throw new Error('Should be child of thread, not the main thread');
15
13
  }
@@ -21,34 +19,18 @@ const workerScriptPath = process.env.STUDIO_WORKER_TASK_FILE;
21
19
  if (!workerScriptPath) {
22
20
  throw new Error('Missing `STUDIO_WORKER_TASK_FILE` environment variable');
23
21
  }
24
- const mockStubs = stubs;
25
- const mockedGlobalThis = globalThis;
26
- for(const key in stubs){
27
- if (!(key in mockedGlobalThis)) {
28
- mockedGlobalThis[key] = mockStubs[key];
29
- }
30
- }
31
- // Doesn't have to be correct, just need the root path to be
32
- const fakeConfigUrl = pathToFileURL(resolve(rootPath, 'sanity.config.mjs'));
33
- // We'll load `getStudioEnvironmentVariables` from the `sanity/cli` module installed
34
- // relative to where the studio is located, instead of resolving from where this CLI is
35
- // running in, in order to ensure we're using the same version as the studio would.
36
- const sanityCliUrl = await moduleResolve('sanity/cli', fakeConfigUrl);
37
- const { getStudioEnvironmentVariables } = await import(sanityCliUrl.href);
38
- if (typeof getStudioEnvironmentVariables !== 'function') {
39
- throw new TypeError('Expected `getStudioEnvironmentVariables` from `sanity/cli` to be a function');
40
- }
22
+ await setupBrowserStubs();
23
+ const studioEnvVars = await getStudioEnvironmentVariables(rootPath);
41
24
  const defaultViteConfig = {
42
25
  build: {
43
26
  target: 'node'
44
27
  },
45
28
  configFile: false,
46
- define: {
47
- ...getStudioEnvironmentVariables({
48
- jsonEncode: true,
49
- prefix: 'process.env.'
50
- })
51
- },
29
+ // Inject environment variables as compile-time constants for Vite
30
+ define: Object.fromEntries(Object.entries(studioEnvVars).map(([key, value])=>[
31
+ `process.env.${key}`,
32
+ JSON.stringify(value)
33
+ ])),
52
34
  logLevel: 'error',
53
35
  optimizeDeps: {
54
36
  disabled: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/loaders/studio/studioWorkerLoader.worker.ts"],"sourcesContent":["import {resolve} from 'node:path'\nimport {pathToFileURL} from 'node:url'\nimport {isMainThread} from 'node:worker_threads'\n\nimport {moduleResolve} from 'import-meta-resolve'\nimport {createServer, type InlineConfig, loadEnv, mergeConfig} from 'vite'\nimport {ViteNodeRunner} from 'vite-node/client'\nimport {ViteNodeServer} from 'vite-node/server'\nimport {installSourcemapsSupport} from 'vite-node/source-map'\n\nimport {getCliConfig} from '../../config/cli/getCliConfig.js'\nimport {type CliConfig} from '../../config/cli/types.js'\nimport {isRecord} from '../../util/isRecord.js'\nimport {isNotFoundError} from '../../util/NotFoundError.js'\nimport * as stubs from './stubs.js'\n\nif (isMainThread) {\n throw new Error('Should be child of thread, not the main thread')\n}\n\nconst rootPath = process.env.STUDIO_WORKER_STUDIO_ROOT_PATH\nif (!rootPath) {\n throw new Error('Missing `STUDIO_WORKER_STUDIO_ROOT_PATH` environment variable')\n}\n\nconst workerScriptPath = process.env.STUDIO_WORKER_TASK_FILE\nif (!workerScriptPath) {\n throw new Error('Missing `STUDIO_WORKER_TASK_FILE` environment variable')\n}\n\nconst mockStubs = stubs as Record<string, unknown>\nconst mockedGlobalThis: Record<string, unknown> = globalThis\nfor (const key in stubs) {\n if (!(key in mockedGlobalThis)) {\n mockedGlobalThis[key] = mockStubs[key]\n }\n}\n\n// Doesn't have to be correct, just need the root path to be\nconst fakeConfigUrl = pathToFileURL(resolve(rootPath, 'sanity.config.mjs'))\n\n// We'll load `getStudioEnvironmentVariables` from the `sanity/cli` module installed\n// relative to where the studio is located, instead of resolving from where this CLI is\n// running in, in order to ensure we're using the same version as the studio would.\nconst sanityCliUrl = await moduleResolve('sanity/cli', fakeConfigUrl)\nconst {getStudioEnvironmentVariables} = await import(sanityCliUrl.href)\nif (typeof getStudioEnvironmentVariables !== 'function') {\n throw new TypeError('Expected `getStudioEnvironmentVariables` from `sanity/cli` to be a function')\n}\n\nconst defaultViteConfig: InlineConfig = {\n build: {target: 'node'},\n configFile: false, // @todo Should use `vite` prop from `sanity.cli.ts` (if any)\n define: {\n ...getStudioEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'}),\n },\n logLevel: 'error',\n optimizeDeps: {disabled: true}, // @todo is this necessary? cant remember why was added\n root: rootPath,\n server: {\n hmr: false,\n watch: null,\n },\n}\n\n// Allow the CLI config (`sanity.cli.(js|ts)`) to define a `vite` property which can\n// extend/modify the default vite configuration for the studio.\nlet cliConfig: CliConfig | undefined\ntry {\n cliConfig = await getCliConfig(rootPath)\n} catch (err) {\n if (!isNotFoundError(err)) {\n console.warn('[warn] Failed to load CLI config:', err)\n }\n}\n\nlet viteConfig = defaultViteConfig\nif (typeof cliConfig?.vite === 'function') {\n viteConfig = (await cliConfig.vite(viteConfig, {\n command: 'build',\n isSsrBuild: true,\n mode: 'production',\n })) as InlineConfig\n} else if (isRecord(cliConfig?.vite)) {\n viteConfig = mergeConfig(viteConfig, cliConfig.vite)\n}\n\n// Vite will build the files we give it - targetting Node.js instead of the browser.\n// We include the inject plugin in order to provide the stubs for the undefined global APIs.\nconst server = await createServer(viteConfig)\n\n// Bit of a hack, but seems necessary based on the `node-vite` binary implementation\nawait server.pluginContainer.buildStart({})\n\n// Load environment variables from `.env` files in the same way as Vite does.\n// Note that Sanity also provides environment variables through `process.env.*` for compat reasons,\n// and so we need to do the same here.\n// @todo is this in line with sanity?\nconst env = loadEnv(server.config.mode, server.config.envDir, '')\nfor (const key in env) {\n process.env[key] ??= env[key]\n}\n\n// Now we're providing the glue that ensures node-specific loading and execution works.\nconst node = new ViteNodeServer(server)\n\n// Should make it easier to debug any crashes in the imported code…\ninstallSourcemapsSupport({\n getSourceMap: (source) => node.getSourceMap(source),\n})\n\nconst runner = new ViteNodeRunner({\n base: server.config.base,\n async fetchModule(id) {\n return node.fetchModule(id)\n },\n resolveId(id, importer) {\n return node.resolveId(id, importer)\n },\n root: server.config.root,\n})\n\n// Copied from `vite-node` - it appears that this applies the `define` config from\n// vite, but it also takes a surprisingly long time to execute. Not clear at this\n// point why this is, so we should investigate whether it's necessary or not.\nawait runner.executeId('/@vite/env')\n\nawait runner.executeId(workerScriptPath)\n"],"names":["resolve","pathToFileURL","isMainThread","moduleResolve","createServer","loadEnv","mergeConfig","ViteNodeRunner","ViteNodeServer","installSourcemapsSupport","getCliConfig","isRecord","isNotFoundError","stubs","Error","rootPath","process","env","STUDIO_WORKER_STUDIO_ROOT_PATH","workerScriptPath","STUDIO_WORKER_TASK_FILE","mockStubs","mockedGlobalThis","globalThis","key","fakeConfigUrl","sanityCliUrl","getStudioEnvironmentVariables","href","TypeError","defaultViteConfig","build","target","configFile","define","jsonEncode","prefix","logLevel","optimizeDeps","disabled","root","server","hmr","watch","cliConfig","err","console","warn","viteConfig","vite","command","isSsrBuild","mode","pluginContainer","buildStart","config","envDir","node","getSourceMap","source","runner","base","fetchModule","id","resolveId","importer","executeId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/loaders/studio/studioWorkerLoader.worker.ts"],"sourcesContent":["import {isMainThread} from 'node:worker_threads'\n\nimport {createServer, type InlineConfig, loadEnv, mergeConfig} from 'vite'\nimport {ViteNodeRunner} from 'vite-node/client'\nimport {ViteNodeServer} from 'vite-node/server'\nimport {installSourcemapsSupport} from 'vite-node/source-map'\n\nimport {getCliConfig} from '../../config/cli/getCliConfig.js'\nimport {type CliConfig} from '../../config/cli/types/cliConfig.js'\nimport {getStudioEnvironmentVariables} from '../../util/environment/getStudioEnvironmentVariables.js'\nimport {setupBrowserStubs} from '../../util/environment/setupBrowserStubs.js'\nimport {isRecord} from '../../util/isRecord.js'\nimport {isNotFoundError} from '../../util/NotFoundError.js'\n\nif (isMainThread) {\n throw new Error('Should be child of thread, not the main thread')\n}\n\nconst rootPath = process.env.STUDIO_WORKER_STUDIO_ROOT_PATH\nif (!rootPath) {\n throw new Error('Missing `STUDIO_WORKER_STUDIO_ROOT_PATH` environment variable')\n}\n\nconst workerScriptPath = process.env.STUDIO_WORKER_TASK_FILE\nif (!workerScriptPath) {\n throw new Error('Missing `STUDIO_WORKER_TASK_FILE` environment variable')\n}\n\nawait setupBrowserStubs()\n\nconst studioEnvVars = await getStudioEnvironmentVariables(rootPath)\n\nconst defaultViteConfig: InlineConfig = {\n build: {target: 'node'},\n configFile: false, // @todo Should use `vite` prop from `sanity.cli.ts` (if any)\n // Inject environment variables as compile-time constants for Vite\n define: Object.fromEntries(\n Object.entries(studioEnvVars).map(([key, value]) => [\n `process.env.${key}`,\n JSON.stringify(value),\n ]),\n ),\n logLevel: 'error',\n optimizeDeps: {disabled: true}, // @todo is this necessary? cant remember why was added\n root: rootPath,\n server: {\n hmr: false,\n watch: null,\n },\n}\n\n// Allow the CLI config (`sanity.cli.(js|ts)`) to define a `vite` property which can\n// extend/modify the default vite configuration for the studio.\nlet cliConfig: CliConfig | undefined\ntry {\n cliConfig = await getCliConfig(rootPath)\n} catch (err) {\n if (!isNotFoundError(err)) {\n console.warn('[warn] Failed to load CLI config:', err)\n }\n}\n\nlet viteConfig = defaultViteConfig\nif (typeof cliConfig?.vite === 'function') {\n viteConfig = (await cliConfig.vite(viteConfig, {\n command: 'build',\n isSsrBuild: true,\n mode: 'production',\n })) as InlineConfig\n} else if (isRecord(cliConfig?.vite)) {\n viteConfig = mergeConfig(viteConfig, cliConfig.vite)\n}\n\n// Vite will build the files we give it - targetting Node.js instead of the browser.\n// We include the inject plugin in order to provide the stubs for the undefined global APIs.\nconst server = await createServer(viteConfig)\n\n// Bit of a hack, but seems necessary based on the `node-vite` binary implementation\nawait server.pluginContainer.buildStart({})\n\n// Load environment variables from `.env` files in the same way as Vite does.\n// Note that Sanity also provides environment variables through `process.env.*` for compat reasons,\n// and so we need to do the same here.\n// @todo is this in line with sanity?\nconst env = loadEnv(server.config.mode, server.config.envDir, '')\nfor (const key in env) {\n process.env[key] ??= env[key]\n}\n\n// Now we're providing the glue that ensures node-specific loading and execution works.\nconst node = new ViteNodeServer(server)\n\n// Should make it easier to debug any crashes in the imported code…\ninstallSourcemapsSupport({\n getSourceMap: (source) => node.getSourceMap(source),\n})\n\nconst runner = new ViteNodeRunner({\n base: server.config.base,\n async fetchModule(id) {\n return node.fetchModule(id)\n },\n resolveId(id, importer) {\n return node.resolveId(id, importer)\n },\n root: server.config.root,\n})\n\n// Copied from `vite-node` - it appears that this applies the `define` config from\n// vite, but it also takes a surprisingly long time to execute. Not clear at this\n// point why this is, so we should investigate whether it's necessary or not.\nawait runner.executeId('/@vite/env')\n\nawait runner.executeId(workerScriptPath)\n"],"names":["isMainThread","createServer","loadEnv","mergeConfig","ViteNodeRunner","ViteNodeServer","installSourcemapsSupport","getCliConfig","getStudioEnvironmentVariables","setupBrowserStubs","isRecord","isNotFoundError","Error","rootPath","process","env","STUDIO_WORKER_STUDIO_ROOT_PATH","workerScriptPath","STUDIO_WORKER_TASK_FILE","studioEnvVars","defaultViteConfig","build","target","configFile","define","Object","fromEntries","entries","map","key","value","JSON","stringify","logLevel","optimizeDeps","disabled","root","server","hmr","watch","cliConfig","err","console","warn","viteConfig","vite","command","isSsrBuild","mode","pluginContainer","buildStart","config","envDir","node","getSourceMap","source","runner","base","fetchModule","id","resolveId","importer","executeId"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- import { type SanityClient } from '@sanity/client';
1
+ import { type ClientConfig, type SanityClient } from '@sanity/client';
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export interface GlobalCliClientOptions {
5
+ export interface GlobalCliClientOptions extends ClientConfig {
6
6
  /**
7
7
  * The API version to use for this client.
8
8
  */
@@ -20,14 +20,28 @@ export interface GlobalCliClientOptions {
20
20
  * @param options - The options to use for the client.
21
21
  * @returns Promise that resolves to a configured Sanity API client.
22
22
  */
23
- export declare function getGlobalCliClient({ apiVersion, requireUser, }: GlobalCliClientOptions): Promise<SanityClient>;
23
+ export declare function getGlobalCliClient({ requireUser, ...config }: GlobalCliClientOptions): Promise<SanityClient>;
24
24
  /**
25
25
  * @internal
26
26
  */
27
- export interface ProjectCliClientOptions {
27
+ export interface ProjectCliClientOptions extends ClientConfig {
28
+ /**
29
+ * The API version to use for this client.
30
+ */
28
31
  apiVersion: string;
32
+ /**
33
+ * The project ID to use for this client.
34
+ */
29
35
  projectId: string;
36
+ /**
37
+ * The dataset to use for this client.
38
+ */
30
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
+ */
31
45
  requireUser?: boolean;
32
46
  }
33
47
  /**
@@ -36,4 +50,4 @@ export interface ProjectCliClientOptions {
36
50
  * @param options - The options to use for the client.
37
51
  * @returns Promise that resolves to a configured Sanity API client.
38
52
  */
39
- export declare function getProjectCliClient({ apiVersion, dataset, projectId, requireUser, }: ProjectCliClientOptions): Promise<SanityClient>;
53
+ export declare function getProjectCliClient({ requireUser, ...config }: ProjectCliClientOptions): Promise<SanityClient>;
@@ -12,25 +12,28 @@ const CLI_REQUEST_TAG_PREFIX = 'sanity.cli';
12
12
  *
13
13
  * @param options - The options to use for the client.
14
14
  * @returns Promise that resolves to a configured Sanity API client.
15
- */ export async function getGlobalCliClient({ apiVersion, requireUser }) {
15
+ */ export async function getGlobalCliClient({ requireUser, ...config }) {
16
16
  const requester = defaultRequester.clone();
17
17
  requester.use(authErrors());
18
18
  const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production';
19
- const token = await getCliToken();
20
19
  const apiHost = apiHosts[sanityEnv];
21
- if (requireUser && !token) {
22
- throw new Error('You must login first - run "sanity login"');
20
+ let token;
21
+ if (requireUser) {
22
+ token = await getCliToken();
23
+ if (!token) {
24
+ throw new Error('You must login first - run "sanity login"');
25
+ }
23
26
  }
24
27
  return createClient({
25
28
  ...apiHost ? {
26
29
  apiHost
27
30
  } : {},
28
- apiVersion,
29
31
  requester,
30
32
  requestTagPrefix: CLI_REQUEST_TAG_PREFIX,
31
33
  token,
32
34
  useCdn: false,
33
- useProjectHostname: false
35
+ useProjectHostname: false,
36
+ ...config
34
37
  });
35
38
  }
36
39
  /**
@@ -38,27 +41,28 @@ const CLI_REQUEST_TAG_PREFIX = 'sanity.cli';
38
41
  *
39
42
  * @param options - The options to use for the client.
40
43
  * @returns Promise that resolves to a configured Sanity API client.
41
- */ export async function getProjectCliClient({ apiVersion, dataset, projectId, requireUser }) {
44
+ */ export async function getProjectCliClient({ requireUser, ...config }) {
42
45
  const requester = defaultRequester.clone();
43
46
  requester.use(authErrors());
44
47
  const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production';
45
- const token = await getCliToken();
46
48
  const apiHost = apiHosts[sanityEnv];
47
- if (requireUser && !token) {
48
- throw new Error('You must login first - run "sanity login"');
49
+ let token;
50
+ if (requireUser) {
51
+ token = await getCliToken();
52
+ if (!token) {
53
+ throw new Error('You must login first - run "sanity login"');
54
+ }
49
55
  }
50
56
  return createClient({
51
57
  ...apiHost ? {
52
58
  apiHost
53
59
  } : {},
54
- apiVersion,
55
- dataset,
56
- projectId,
57
60
  requester,
58
61
  requestTagPrefix: CLI_REQUEST_TAG_PREFIX,
59
62
  token,
60
63
  useCdn: false,
61
- useProjectHostname: true
64
+ useProjectHostname: true,
65
+ ...config
62
66
  });
63
67
  }
64
68
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/apiClient.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {\n type ClientError,\n createClient,\n requester as defaultRequester,\n type SanityClient,\n type ServerError,\n} from '@sanity/client'\n\nimport {generateHelpUrl} from '../util/generateHelpUrl.js'\nimport {isHttpError} from '../util/isHttpError.js'\nimport {getCliToken} from './getCliToken.js'\n\nconst apiHosts: Record<string, string | undefined> = {\n staging: 'https://api.sanity.work',\n}\n\nconst CLI_REQUEST_TAG_PREFIX = 'sanity.cli'\n\n/**\n * @internal\n */\nexport interface GlobalCliClientOptions {\n /**\n * The API version to use for this client.\n */\n apiVersion: string\n\n /**\n * Whether to require a user to be authenticated to use this client.\n * Default: `false`.\n * Throws an error if `true` and user is not authenticated.\n */\n requireUser?: boolean\n}\n\n/**\n * Create a \"global\" (unscoped) Sanity API client.\n *\n * @param options - The options to use for the client.\n * @returns Promise that resolves to a configured Sanity API client.\n */\nexport async function getGlobalCliClient({\n apiVersion,\n requireUser,\n}: GlobalCliClientOptions): Promise<SanityClient> {\n const requester = defaultRequester.clone()\n requester.use(authErrors())\n\n const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production'\n\n const token = await getCliToken()\n const apiHost = apiHosts[sanityEnv]\n\n if (requireUser && !token) {\n throw new Error('You must login first - run \"sanity login\"')\n }\n\n return createClient({\n ...(apiHost ? {apiHost} : {}),\n apiVersion,\n requester,\n requestTagPrefix: CLI_REQUEST_TAG_PREFIX,\n token,\n useCdn: false,\n useProjectHostname: false,\n })\n}\n\n/**\n * @internal\n */\nexport interface ProjectCliClientOptions {\n apiVersion: string\n projectId: string\n\n dataset?: string\n\n requireUser?: boolean\n}\n\n/**\n * Create a \"global\" (unscoped) Sanity API client.\n *\n * @param options - The options to use for the client.\n * @returns Promise that resolves to a configured Sanity API client.\n */\nexport async function getProjectCliClient({\n apiVersion,\n dataset,\n projectId,\n requireUser,\n}: ProjectCliClientOptions): Promise<SanityClient> {\n const requester = defaultRequester.clone()\n requester.use(authErrors())\n\n const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production'\n\n const token = await getCliToken()\n const apiHost = apiHosts[sanityEnv]\n\n if (requireUser && !token) {\n throw new Error('You must login first - run \"sanity login\"')\n }\n\n return createClient({\n ...(apiHost ? {apiHost} : {}),\n apiVersion,\n dataset,\n projectId,\n requester,\n requestTagPrefix: CLI_REQUEST_TAG_PREFIX,\n token,\n useCdn: false,\n useProjectHostname: true,\n })\n}\n\n/**\n * `get-it` middleware that checks for 401 authentication errors and extends the error with more\n * helpful guidance on what to do next.\n *\n * @returns get-it middleware with `onError` handler\n * @internal\n */\nfunction authErrors() {\n return {\n onError: (err: Error | null) => {\n if (!err || !isReqResError(err)) {\n return err\n }\n\n const statusCode = isHttpError(err) && err.response.body.statusCode\n if (statusCode === 401) {\n err.message = `${err.message}. You may need to login again with ${ux.colorize('cyan', 'sanity login')}.\\nFor more information, see ${generateHelpUrl('cli-errors')}.`\n }\n\n return err\n },\n }\n}\n\nfunction isReqResError(err: Error): err is ClientError | ServerError {\n return Object.prototype.hasOwnProperty.call(err, 'response')\n}\n"],"names":["ux","createClient","requester","defaultRequester","generateHelpUrl","isHttpError","getCliToken","apiHosts","staging","CLI_REQUEST_TAG_PREFIX","getGlobalCliClient","apiVersion","requireUser","clone","use","authErrors","sanityEnv","process","env","SANITY_INTERNAL_ENV","token","apiHost","Error","requestTagPrefix","useCdn","useProjectHostname","getProjectCliClient","dataset","projectId","onError","err","isReqResError","statusCode","response","body","message","colorize","Object","prototype","hasOwnProperty","call"],"mappings":"AAAA,SAAQA,EAAE,QAAO,cAAa;AAC9B,SAEEC,YAAY,EACZC,aAAaC,gBAAgB,QAGxB,iBAAgB;AAEvB,SAAQC,eAAe,QAAO,6BAA4B;AAC1D,SAAQC,WAAW,QAAO,yBAAwB;AAClD,SAAQC,WAAW,QAAO,mBAAkB;AAE5C,MAAMC,WAA+C;IACnDC,SAAS;AACX;AAEA,MAAMC,yBAAyB;AAmB/B;;;;;CAKC,GACD,OAAO,eAAeC,mBAAmB,EACvCC,UAAU,EACVC,WAAW,EACY;IACvB,MAAMV,YAAYC,iBAAiBU,KAAK;IACxCX,UAAUY,GAAG,CAACC;IAEd,MAAMC,YAAYC,QAAQC,GAAG,CAACC,mBAAmB,IAAI;IAErD,MAAMC,QAAQ,MAAMd;IACpB,MAAMe,UAAUd,QAAQ,CAACS,UAAU;IAEnC,IAAIJ,eAAe,CAACQ,OAAO;QACzB,MAAM,IAAIE,MAAM;IAClB;IAEA,OAAOrB,aAAa;QAClB,GAAIoB,UAAU;YAACA;QAAO,IAAI,CAAC,CAAC;QAC5BV;QACAT;QACAqB,kBAAkBd;QAClBW;QACAI,QAAQ;QACRC,oBAAoB;IACtB;AACF;AAcA;;;;;CAKC,GACD,OAAO,eAAeC,oBAAoB,EACxCf,UAAU,EACVgB,OAAO,EACPC,SAAS,EACThB,WAAW,EACa;IACxB,MAAMV,YAAYC,iBAAiBU,KAAK;IACxCX,UAAUY,GAAG,CAACC;IAEd,MAAMC,YAAYC,QAAQC,GAAG,CAACC,mBAAmB,IAAI;IAErD,MAAMC,QAAQ,MAAMd;IACpB,MAAMe,UAAUd,QAAQ,CAACS,UAAU;IAEnC,IAAIJ,eAAe,CAACQ,OAAO;QACzB,MAAM,IAAIE,MAAM;IAClB;IAEA,OAAOrB,aAAa;QAClB,GAAIoB,UAAU;YAACA;QAAO,IAAI,CAAC,CAAC;QAC5BV;QACAgB;QACAC;QACA1B;QACAqB,kBAAkBd;QAClBW;QACAI,QAAQ;QACRC,oBAAoB;IACtB;AACF;AAEA;;;;;;CAMC,GACD,SAASV;IACP,OAAO;QACLc,SAAS,CAACC;YACR,IAAI,CAACA,OAAO,CAACC,cAAcD,MAAM;gBAC/B,OAAOA;YACT;YAEA,MAAME,aAAa3B,YAAYyB,QAAQA,IAAIG,QAAQ,CAACC,IAAI,CAACF,UAAU;YACnE,IAAIA,eAAe,KAAK;gBACtBF,IAAIK,OAAO,GAAG,GAAGL,IAAIK,OAAO,CAAC,mCAAmC,EAAEnC,GAAGoC,QAAQ,CAAC,QAAQ,gBAAgB,6BAA6B,EAAEhC,gBAAgB,cAAc,CAAC,CAAC;YACvK;YAEA,OAAO0B;QACT;IACF;AACF;AAEA,SAASC,cAAcD,GAAU;IAC/B,OAAOO,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACV,KAAK;AACnD"}
1
+ {"version":3,"sources":["../../src/services/apiClient.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {\n type ClientConfig,\n type ClientError,\n createClient,\n requester as defaultRequester,\n type SanityClient,\n type ServerError,\n} from '@sanity/client'\n\nimport {generateHelpUrl} from '../util/generateHelpUrl.js'\nimport {isHttpError} from '../util/isHttpError.js'\nimport {getCliToken} from './getCliToken.js'\n\nconst apiHosts: Record<string, string | undefined> = {\n staging: 'https://api.sanity.work',\n}\n\nconst CLI_REQUEST_TAG_PREFIX = 'sanity.cli'\n\n/**\n * @internal\n */\nexport interface GlobalCliClientOptions extends ClientConfig {\n /**\n * The API version to use for this client.\n */\n apiVersion: string\n\n /**\n * Whether to require a user to be authenticated to use this client.\n * Default: `false`.\n * Throws an error if `true` and user is not authenticated.\n */\n requireUser?: boolean\n}\n\n/**\n * Create a \"global\" (unscoped) Sanity API client.\n *\n * @param options - The options to use for the client.\n * @returns Promise that resolves to a configured Sanity API client.\n */\nexport async function getGlobalCliClient({\n requireUser,\n ...config\n}: GlobalCliClientOptions): Promise<SanityClient> {\n const requester = defaultRequester.clone()\n requester.use(authErrors())\n\n const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production'\n\n const apiHost = apiHosts[sanityEnv]\n\n let token: string | undefined\n if (requireUser) {\n token = await getCliToken()\n if (!token) {\n throw new Error('You must login first - run \"sanity login\"')\n }\n }\n\n return createClient({\n ...(apiHost ? {apiHost} : {}),\n requester,\n requestTagPrefix: CLI_REQUEST_TAG_PREFIX,\n token,\n useCdn: false,\n useProjectHostname: false,\n ...config,\n })\n}\n\n/**\n * @internal\n */\nexport interface ProjectCliClientOptions extends ClientConfig {\n /**\n * The API version to use for this client.\n */\n apiVersion: string\n\n /**\n * The project ID to use for this client.\n */\n projectId: string\n\n /**\n * The dataset to use for this client.\n */\n dataset?: string\n\n /**\n * Whether to require a user to be authenticated to use this client.\n * Default: `false`.\n * Throws an error if `true` and user is not authenticated.\n */\n requireUser?: boolean\n}\n\n/**\n * Create a \"global\" (unscoped) Sanity API client.\n *\n * @param options - The options to use for the client.\n * @returns Promise that resolves to a configured Sanity API client.\n */\nexport async function getProjectCliClient({\n requireUser,\n ...config\n}: ProjectCliClientOptions): Promise<SanityClient> {\n const requester = defaultRequester.clone()\n requester.use(authErrors())\n\n const sanityEnv = process.env.SANITY_INTERNAL_ENV || 'production'\n\n const apiHost = apiHosts[sanityEnv]\n\n let token: string | undefined\n if (requireUser) {\n token = await getCliToken()\n if (!token) {\n throw new Error('You must login first - run \"sanity login\"')\n }\n }\n\n return createClient({\n ...(apiHost ? {apiHost} : {}),\n requester,\n requestTagPrefix: CLI_REQUEST_TAG_PREFIX,\n token,\n useCdn: false,\n useProjectHostname: true,\n ...config,\n })\n}\n\n/**\n * `get-it` middleware that checks for 401 authentication errors and extends the error with more\n * helpful guidance on what to do next.\n *\n * @returns get-it middleware with `onError` handler\n * @internal\n */\nfunction authErrors() {\n return {\n onError: (err: Error | null) => {\n if (!err || !isReqResError(err)) {\n return err\n }\n\n const statusCode = isHttpError(err) && err.response.body.statusCode\n if (statusCode === 401) {\n err.message = `${err.message}. You may need to login again with ${ux.colorize('cyan', 'sanity login')}.\\nFor more information, see ${generateHelpUrl('cli-errors')}.`\n }\n\n return err\n },\n }\n}\n\nfunction isReqResError(err: Error): err is ClientError | ServerError {\n return Object.prototype.hasOwnProperty.call(err, 'response')\n}\n"],"names":["ux","createClient","requester","defaultRequester","generateHelpUrl","isHttpError","getCliToken","apiHosts","staging","CLI_REQUEST_TAG_PREFIX","getGlobalCliClient","requireUser","config","clone","use","authErrors","sanityEnv","process","env","SANITY_INTERNAL_ENV","apiHost","token","Error","requestTagPrefix","useCdn","useProjectHostname","getProjectCliClient","onError","err","isReqResError","statusCode","response","body","message","colorize","Object","prototype","hasOwnProperty","call"],"mappings":"AAAA,SAAQA,EAAE,QAAO,cAAa;AAC9B,SAGEC,YAAY,EACZC,aAAaC,gBAAgB,QAGxB,iBAAgB;AAEvB,SAAQC,eAAe,QAAO,6BAA4B;AAC1D,SAAQC,WAAW,QAAO,yBAAwB;AAClD,SAAQC,WAAW,QAAO,mBAAkB;AAE5C,MAAMC,WAA+C;IACnDC,SAAS;AACX;AAEA,MAAMC,yBAAyB;AAmB/B;;;;;CAKC,GACD,OAAO,eAAeC,mBAAmB,EACvCC,WAAW,EACX,GAAGC,QACoB;IACvB,MAAMV,YAAYC,iBAAiBU,KAAK;IACxCX,UAAUY,GAAG,CAACC;IAEd,MAAMC,YAAYC,QAAQC,GAAG,CAACC,mBAAmB,IAAI;IAErD,MAAMC,UAAUb,QAAQ,CAACS,UAAU;IAEnC,IAAIK;IACJ,IAAIV,aAAa;QACfU,QAAQ,MAAMf;QACd,IAAI,CAACe,OAAO;YACV,MAAM,IAAIC,MAAM;QAClB;IACF;IAEA,OAAOrB,aAAa;QAClB,GAAImB,UAAU;YAACA;QAAO,IAAI,CAAC,CAAC;QAC5BlB;QACAqB,kBAAkBd;QAClBY;QACAG,QAAQ;QACRC,oBAAoB;QACpB,GAAGb,MAAM;IACX;AACF;AA6BA;;;;;CAKC,GACD,OAAO,eAAec,oBAAoB,EACxCf,WAAW,EACX,GAAGC,QACqB;IACxB,MAAMV,YAAYC,iBAAiBU,KAAK;IACxCX,UAAUY,GAAG,CAACC;IAEd,MAAMC,YAAYC,QAAQC,GAAG,CAACC,mBAAmB,IAAI;IAErD,MAAMC,UAAUb,QAAQ,CAACS,UAAU;IAEnC,IAAIK;IACJ,IAAIV,aAAa;QACfU,QAAQ,MAAMf;QACd,IAAI,CAACe,OAAO;YACV,MAAM,IAAIC,MAAM;QAClB;IACF;IAEA,OAAOrB,aAAa;QAClB,GAAImB,UAAU;YAACA;QAAO,IAAI,CAAC,CAAC;QAC5BlB;QACAqB,kBAAkBd;QAClBY;QACAG,QAAQ;QACRC,oBAAoB;QACpB,GAAGb,MAAM;IACX;AACF;AAEA;;;;;;CAMC,GACD,SAASG;IACP,OAAO;QACLY,SAAS,CAACC;YACR,IAAI,CAACA,OAAO,CAACC,cAAcD,MAAM;gBAC/B,OAAOA;YACT;YAEA,MAAME,aAAazB,YAAYuB,QAAQA,IAAIG,QAAQ,CAACC,IAAI,CAACF,UAAU;YACnE,IAAIA,eAAe,KAAK;gBACtBF,IAAIK,OAAO,GAAG,GAAGL,IAAIK,OAAO,CAAC,mCAAmC,EAAEjC,GAAGkC,QAAQ,CAAC,QAAQ,gBAAgB,6BAA6B,EAAE9B,gBAAgB,cAAc,CAAC,CAAC;YACvK;YAEA,OAAOwB;QACT;IACF;AACF;AAEA,SAASC,cAAcD,GAAU;IAC/B,OAAOO,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACV,KAAK;AACnD"}
@@ -304,6 +304,97 @@ describe('createExpiringConfig', ()=>{
304
304
  expect(onFetch).toHaveBeenCalledOnce();
305
305
  expect(onCacheHit).toHaveBeenCalledOnce();
306
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
+ });
307
398
  });
308
399
 
309
400
  //# sourceMappingURL=createExpiringConfig.test.js.map
@@ -1 +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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"}
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 test('throws when cached value fails validateValue', async () => {\n const invalidCached = 123\n const ttl = 10_000\n\n const validateValue = vi.fn((v: unknown): v is string => typeof v === 'string')\n\n const config = createExpiringConfig<string>({\n fetchValue,\n key: 'test-key',\n onCacheHit,\n onFetch,\n onRevalidate,\n store: mockStore,\n ttl,\n // @ts-expect-error vitest mocks don't jive with assertions\n validateValue,\n })\n\n // Cached entry that is not expired but invalid per validateValue\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt: Date.now(),\n value: invalidCached,\n })\n\n await expect(config.get()).rejects.toThrow('Stored value is invalid')\n expect(validateValue).toHaveBeenCalledOnce()\n expect(onCacheHit).not.toHaveBeenCalled()\n expect(onFetch).not.toHaveBeenCalled()\n expect(onRevalidate).not.toHaveBeenCalled()\n expect(mockStore.set).not.toHaveBeenCalled()\n })\n\n test('throws when fetched value fails validateValue (cache miss)', async () => {\n const validateValue = vi.fn((v: unknown): v is string => typeof v === 'string')\n const config = createExpiringConfig<string>({\n fetchValue: fetchValue.mockResolvedValue(42 as unknown as string),\n key: 'test-key',\n onFetch,\n store: mockStore,\n ttl: 5000,\n // @ts-expect-error vitest mocks don't jive with assertions\n validateValue,\n })\n\n // Empty cache\n vi.mocked(mockStore.get).mockReturnValue(undefined)\n\n await expect(config.get()).rejects.toThrow('Fetched value is invalid')\n expect(onFetch).toHaveBeenCalledOnce()\n expect(validateValue).toHaveBeenCalledOnce()\n expect(mockStore.set).not.toHaveBeenCalled()\n })\n\n test('returns cached value when validateValue accepts it', async () => {\n const cachedValue = 'ok'\n const validateValue = vi.fn((v: unknown): v is string => typeof v === 'string')\n const config = createExpiringConfig<string>({\n fetchValue,\n key: 'test-key',\n onCacheHit,\n store: mockStore,\n ttl: 5000,\n // @ts-expect-error vitest mocks don't jive with assertions\n validateValue,\n })\n\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt: Date.now(),\n value: cachedValue,\n })\n\n const result = await config.get()\n\n expect(result).toBe(cachedValue)\n expect(validateValue).toHaveBeenCalledOnce()\n expect(onCacheHit).toHaveBeenCalledOnce()\n expect(fetchValue).not.toHaveBeenCalled()\n })\n\n test('revalidation path validates fetched value and throws if invalid', async () => {\n const validateValue = vi.fn((v: unknown): v is string => typeof v === 'string')\n const config = createExpiringConfig<string>({\n fetchValue: fetchValue.mockResolvedValue(99 as unknown as string),\n key: 'test-key',\n onFetch,\n onRevalidate,\n store: mockStore,\n ttl: 1, // ensure expiration\n // @ts-expect-error vitest mocks don't jive with assertions\n validateValue,\n })\n\n // Cached value that has expired but is otherwise valid in shape and passes validate\n vi.mocked(mockStore.get).mockReturnValue({\n updatedAt: Date.now() - 10,\n value: 'stale',\n })\n\n await expect(config.get()).rejects.toThrow('Fetched value is invalid')\n expect(onRevalidate).toHaveBeenCalledOnce()\n expect(onFetch).toHaveBeenCalledOnce()\n // validateValue called for stored value and fetched value\n expect(validateValue).toHaveBeenCalledTimes(2)\n expect(mockStore.set).not.toHaveBeenCalled()\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","invalidCached","validateValue","v","toHaveBeenCalledTimes"],"mappings":"AAEA,SAAQA,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,EAAE,QAAO,SAAQ;AAE7D,SAAQC,oBAAoB,QAAO,6BAA4B;AAE/DJ,SAAS,wBAAwB;IAC/B,IAAIK;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJV,WAAW;QACT,mBAAmB;QACnBM,YAAY;YACVK,QAAQP,GAAGQ,EAAE;YACbC,KAAKT,GAAGQ,EAAE;YACVE,KAAKV,GAAGQ,EAAE;QACZ;QAEA,kBAAkB;QAClBL,aAAaH,GAAGQ,EAAE;QAClBJ,aAAaJ,GAAGQ,EAAE;QAClBH,UAAUL,GAAGQ,EAAE;QACfF,eAAeN,GAAGQ,EAAE;IACtB;IAEAT,KAAK,6CAA6C;QAChD,MAAMY,YAAY;QAClB,MAAMC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACF;YACzCG,KAAK;YACLV;YACAC;YACAC;YACAS,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMC,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACV;QACpBb,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOO,SAASiB,oBAAoB;QACpCxB,OAAOM,YAAYmB,GAAG,CAACC,gBAAgB;QACvC1B,OAAOQ,cAAciB,GAAG,CAACC,gBAAgB;QACzC1B,OAAOI,UAAUQ,GAAG,EAAEe,oBAAoB,CAAC,YAAY;YACrDC,WAAW5B,OAAO6B,GAAG,CAACC;YACtBC,OAAOlB;QACT;IACF;IAEAZ,KAAK,gDAAgD;QACnD,MAAM+B,cAAc;QACpB,MAAMd,MAAM;QACZ,MAAMU,YAAYK,KAAKC,GAAG,KAAK,KAAK,6BAA6B;;QAEjE,MAAMpB,SAASX,qBAAqB;YAClCE;YACAW,KAAK;YACLV;YACAC;YACAC;YACAS,OAAOb;YACPc;QACF;QAEA,wCAAwC;QACxChB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ;YACAG,OAAOC;QACT;QAEA,MAAMV,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACS;QACpBhC,OAAOK,YAAYoB,GAAG,CAACC,gBAAgB;QACvC1B,OAAOM,YAAYkB,oBAAoB;QACvCxB,OAAOO,SAASkB,GAAG,CAACC,gBAAgB;QACpC1B,OAAOQ,cAAciB,GAAG,CAACC,gBAAgB;QACzC1B,OAAOI,UAAUQ,GAAG,EAAEa,GAAG,CAACC,gBAAgB;IAC5C;IAEAzB,KAAK,mDAAmD;QACtD,MAAMkC,WAAW;QACjB,MAAMjB,MAAM;QACZ,MAAMU,YAAYK,KAAKC,GAAG,KAAK,KAAK,0BAA0B;;QAE9D,MAAMpB,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACoB;YACzCnB,KAAK;YACLV;YACAC;YACAC;YACAS,OAAOb;YACPc;QACF;QAEA,4BAA4B;QAC5BhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ;YACAG,OAAO;QACT;QAEA,MAAMT,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACY;QACpBnC,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOQ,cAAcgB,oBAAoB;QACzCxB,OAAOO,SAASiB,oBAAoB;QACpCxB,OAAOM,YAAYmB,GAAG,CAACC,gBAAgB;QACvC1B,OAAOI,UAAUQ,GAAG,EAAEe,oBAAoB,CAAC,YAAY;YACrDC,WAAW5B,OAAO6B,GAAG,CAACC;YACtBC,OAAOI;QACT;IACF;IAEAlC,KAAK,mCAAmC;QACtC,MAAMa,SAASX,qBAAqB;YAClCE;YACAW,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEAJ,OAAOL,MAAM;QAEbT,OAAOI,UAAUK,MAAM,EAAEkB,oBAAoB,CAAC;IAChD;IAEA1B,KAAK,4CAA4C;QAC/C,MAAMY,YAAY;QAClB,IAAIuB;QACJ,MAAMC,eAAe,IAAIC,QAAgB,CAACC;YACxCH,iBAAiBG;QACnB;QAEA,MAAMzB,SAASX,qBAAqB;YAClCE,YAAYA,WAAWe,eAAe,CAACiB;YACvCrB,KAAK;YACLT;YACAU,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,wCAAwC;QACxC,MAAMmB,WAAW1B,OAAOH,GAAG;QAC3B,MAAM8B,WAAW3B,OAAOH,GAAG;QAC3B,MAAM+B,WAAW5B,OAAOH,GAAG;QAE3B,oBAAoB;QACpByB,eAAgBvB;QAEhB,MAAM,CAAC8B,SAASC,SAASC,QAAQ,GAAG,MAAMP,QAAQQ,GAAG,CAAC;YAACN;YAAUC;YAAUC;SAAS;QAEpF1C,OAAO2C,SAASpB,IAAI,CAACV;QACrBb,OAAO4C,SAASrB,IAAI,CAACV;QACrBb,OAAO6C,SAAStB,IAAI,CAACV;QACrBb,OAAOK,YAAYmB,oBAAoB,IAAG,+BAA+B;QACzExB,OAAOO,SAASiB,oBAAoB;IACtC;IAEAvB,KAAK,2CAA2C;QAC9C,MAAMY,YAAY;QAClB,MAAMkC,iBAAiB7C,GAAGQ,EAAE,GAAGU,eAAe,CAACP;QAE/C,MAAMC,SAASX,qBAAqB;YAClCE,YAAY0C;YACZ/B,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMC,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACV;QACpBb,OAAO+C,gBAAgBvB,oBAAoB;IAC7C;IAEAvB,KAAK,wCAAwC;QAC3C,MAAM+C,QAAQ,IAAIC,MAAM;QACxB,MAAMnC,SAASX,qBAAqB;YAClCE,YAAYA,WAAW6C,iBAAiB,CAACF;YACzChC,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMrB,OAAOc,OAAOH,GAAG,IAAIwC,OAAO,CAACC,OAAO,CAAC;QAC3CpD,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOI,UAAUQ,GAAG,EAAEa,GAAG,CAACC,gBAAgB;IAC5C;IAEAzB,KAAK,iDAAiD;QACpD,MAAMoD,cAAc;YAACrC,KAAK;YAASsC,QAAQ;QAAE;QAC7C,MAAMxC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACsC;YACzCrC,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMC,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQiC,OAAO,CAACF;QACvBrD,OAAOI,UAAUQ,GAAG,EAAEe,oBAAoB,CAAC,YAAY;YACrDC,WAAW5B,OAAO6B,GAAG,CAACC;YACtBC,OAAOsB;QACT;IACF;IAEApD,KAAK,8CAA8C;QACjD,MAAMY,YAAY;QAClB,MAAMC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACF;YACzCG,KAAK;YACLR;YACAS,OAAOb;YACPc,KAAK;QACP;QAEA,sDAAsD;QACtD,wDAAwD;QACxDhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ,WAAWK,KAAKC,GAAG,KAAK;YACxBH,OAAO;QACT;QAEA,MAAMT,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACV;QACpBb,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOQ,cAAcgB,oBAAoB;IAC3C;IAEAvB,KAAK,6CAA6C;QAChD,MAAMY,YAAY;QAClB,MAAMC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACF;YACzCG,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMC,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACV;QACpBb,OAAOK,YAAYmB,oBAAoB;IACzC;IAEAvB,KAAK,oDAAoD;QACvD,MAAMkC,WAAW;QACjB,MAAMrB,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACoB;YACzCnB,KAAK;YACLT;YACAU,OAAOb;YACPc,KAAK;QACP;QAEA,4DAA4D;QAC5DhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCW,OAAO;QAET;QAEA,MAAMT,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACY;QACpBnC,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOO,SAASiB,oBAAoB;IACtC;IAEAvB,KAAK,+CAA+C;QAClD,MAAMkC,WAAW;QACjB,MAAMrB,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACoB;YACzCnB,KAAK;YACLT;YACAU,OAAOb;YACPc,KAAK;QACP;QAEA,2CAA2C;QAC3ChB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ,WAAWK,KAAKC,GAAG;QAErB;QAEA,MAAMZ,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACY;QACpBnC,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOO,SAASiB,oBAAoB;IACtC;IAEAvB,KAAK,sDAAsD;QACzD,MAAMY,YAAY;QAElB,MAAMC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACF;YACzCG,KAAK;YACLC,OAAOb;YACPc,KAAK;QACP;QAEA,mBAAmB;QACnBhB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMP,OAAOH,GAAG;QAEhBX,OAAOI,UAAUQ,GAAG,EAAEe,oBAAoB,CAAC,YAAY;YACrDC,WAAW5B,OAAO6B,GAAG,CAACC;YACtBC,OAAOlB;QACT;IACF;IAEAZ,KAAK,iEAAiE;QACpE,MAAMY,YAAY;QAClB,MAAMC,SAASX,qBAAqB;YAClCE,YAAYA,WAAWU,iBAAiB,CAACF;YACzCG,KAAK;YACLV;YACAC;YACAU,OAAOb;YACPc,KAAK;QACP;QAEA,qCAAqC;QACrChB,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAE6C,mBAAmB,CAACnC;QAE7C,6BAA6B;QAC7B,MAAMsB,UAAU,MAAM7B,OAAOH,GAAG;QAEhC,8CAA8C;QAC9CT,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAE6C,mBAAmB,CAAC;YAC3C5B,WAAWK,KAAKC,GAAG;YACnBH,OAAOlB;QACT;QAEA,kCAAkC;QAClC,MAAM+B,UAAU,MAAM9B,OAAOH,GAAG;QAEhCX,OAAO2C,SAASpB,IAAI,CAACV;QACrBb,OAAO4C,SAASrB,IAAI,CAACV;QACrBb,OAAOK,YAAYmB,oBAAoB;QACvCxB,OAAOO,SAASiB,oBAAoB;QACpCxB,OAAOM,YAAYkB,oBAAoB;IACzC;IAEAvB,KAAK,gDAAgD;QACnD,MAAMwD,gBAAgB;QACtB,MAAMvC,MAAM;QAEZ,MAAMwC,gBAAgBxD,GAAGQ,EAAE,CAAC,CAACiD,IAA4B,OAAOA,MAAM;QAEtE,MAAM7C,SAASX,qBAA6B;YAC1CE;YACAW,KAAK;YACLV;YACAC;YACAC;YACAS,OAAOb;YACPc;YACA,2DAA2D;YAC3DwC;QACF;QAEA,iEAAiE;QACjExD,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ,WAAWK,KAAKC,GAAG;YACnBH,OAAO0B;QACT;QAEA,MAAMzD,OAAOc,OAAOH,GAAG,IAAIwC,OAAO,CAACC,OAAO,CAAC;QAC3CpD,OAAO0D,eAAelC,oBAAoB;QAC1CxB,OAAOM,YAAYmB,GAAG,CAACC,gBAAgB;QACvC1B,OAAOO,SAASkB,GAAG,CAACC,gBAAgB;QACpC1B,OAAOQ,cAAciB,GAAG,CAACC,gBAAgB;QACzC1B,OAAOI,UAAUQ,GAAG,EAAEa,GAAG,CAACC,gBAAgB;IAC5C;IAEAzB,KAAK,8DAA8D;QACjE,MAAMyD,gBAAgBxD,GAAGQ,EAAE,CAAC,CAACiD,IAA4B,OAAOA,MAAM;QACtE,MAAM7C,SAASX,qBAA6B;YAC1CE,YAAYA,WAAWU,iBAAiB,CAAC;YACzCC,KAAK;YACLT;YACAU,OAAOb;YACPc,KAAK;YACL,2DAA2D;YAC3DwC;QACF;QAEA,cAAc;QACdxD,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAACC;QAEzC,MAAMrB,OAAOc,OAAOH,GAAG,IAAIwC,OAAO,CAACC,OAAO,CAAC;QAC3CpD,OAAOO,SAASiB,oBAAoB;QACpCxB,OAAO0D,eAAelC,oBAAoB;QAC1CxB,OAAOI,UAAUQ,GAAG,EAAEa,GAAG,CAACC,gBAAgB;IAC5C;IAEAzB,KAAK,sDAAsD;QACzD,MAAM+B,cAAc;QACpB,MAAM0B,gBAAgBxD,GAAGQ,EAAE,CAAC,CAACiD,IAA4B,OAAOA,MAAM;QACtE,MAAM7C,SAASX,qBAA6B;YAC1CE;YACAW,KAAK;YACLV;YACAW,OAAOb;YACPc,KAAK;YACL,2DAA2D;YAC3DwC;QACF;QAEAxD,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ,WAAWK,KAAKC,GAAG;YACnBH,OAAOC;QACT;QAEA,MAAMV,SAAS,MAAMR,OAAOH,GAAG;QAE/BX,OAAOsB,QAAQC,IAAI,CAACS;QACpBhC,OAAO0D,eAAelC,oBAAoB;QAC1CxB,OAAOM,YAAYkB,oBAAoB;QACvCxB,OAAOK,YAAYoB,GAAG,CAACC,gBAAgB;IACzC;IAEAzB,KAAK,mEAAmE;QACtE,MAAMyD,gBAAgBxD,GAAGQ,EAAE,CAAC,CAACiD,IAA4B,OAAOA,MAAM;QACtE,MAAM7C,SAASX,qBAA6B;YAC1CE,YAAYA,WAAWU,iBAAiB,CAAC;YACzCC,KAAK;YACLT;YACAC;YACAS,OAAOb;YACPc,KAAK;YACL,2DAA2D;YAC3DwC;QACF;QAEA,oFAAoF;QACpFxD,GAAGiB,MAAM,CAACf,UAAUO,GAAG,EAAES,eAAe,CAAC;YACvCQ,WAAWK,KAAKC,GAAG,KAAK;YACxBH,OAAO;QACT;QAEA,MAAM/B,OAAOc,OAAOH,GAAG,IAAIwC,OAAO,CAACC,OAAO,CAAC;QAC3CpD,OAAOQ,cAAcgB,oBAAoB;QACzCxB,OAAOO,SAASiB,oBAAoB;QACpC,0DAA0D;QAC1DxB,OAAO0D,eAAeE,qBAAqB,CAAC;QAC5C5D,OAAOI,UAAUQ,GAAG,EAAEa,GAAG,CAACC,gBAAgB;IAC5C;AACF"}
@@ -14,6 +14,11 @@ export interface ExpiringConfigOptions<Type> {
14
14
  onFetch?: () => void;
15
15
  /** Subscribe to revalidate event */
16
16
  onRevalidate?: () => void;
17
+ /**
18
+ * Assert the fetched value is valid, or throw if invalid.
19
+ * If none is provided, it will always accept the fetched value.
20
+ */
21
+ validateValue?: (value: unknown) => value is Type;
17
22
  }
18
23
  export interface ExpiringConfigApi<Type> {
19
24
  /**
@@ -29,4 +34,4 @@ export interface ExpiringConfigApi<Type> {
29
34
  /**
30
35
  * Create a config in the provided config store that expires after the provided TTL.
31
36
  */
32
- export declare function createExpiringConfig<Type>({ fetchValue, key, onCacheHit, onFetch, onRevalidate, store, ttl, }: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type>;
37
+ export declare function createExpiringConfig<Type>({ fetchValue, key, onCacheHit, onFetch, onRevalidate, store, ttl, validateValue, }: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type>;
@@ -1,14 +1,18 @@
1
1
  /**
2
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 }) {
3
+ */ export function createExpiringConfig({ fetchValue, key, onCacheHit = ()=>null, onFetch = ()=>null, onRevalidate = ()=>null, store, ttl, validateValue = (value)=>true }) {
4
4
  let currentFetch = null;
5
5
  return {
6
6
  delete () {
7
7
  store.delete(key);
8
8
  },
9
9
  async get () {
10
- const { updatedAt, value } = store.get(key) ?? {};
11
- if (value && updatedAt) {
10
+ const stored = store.get(key);
11
+ if (isExpiringValue(stored)) {
12
+ const { updatedAt, value } = stored;
13
+ if (!validateValue(value)) {
14
+ throw new Error('Stored value is invalid');
15
+ }
12
16
  const hasExpired = Date.now() - updatedAt > ttl;
13
17
  if (!hasExpired) {
14
18
  onCacheHit();
@@ -22,6 +26,9 @@
22
26
  onFetch();
23
27
  currentFetch = Promise.resolve(fetchValue());
24
28
  const nextValue = await currentFetch;
29
+ if (!validateValue(nextValue)) {
30
+ throw new Error('Fetched value is invalid');
31
+ }
25
32
  currentFetch = null;
26
33
  store.set(key, {
27
34
  updatedAt: Date.now(),
@@ -31,5 +38,23 @@
31
38
  }
32
39
  };
33
40
  }
41
+ /**
42
+ * Checks if the given stored value is valid (does not check if expired, only verified shape)
43
+ *
44
+ * @param stored - The stored value to check
45
+ * @returns True if the stored value is valid
46
+ * @internal
47
+ */ function isExpiringValue(stored) {
48
+ if (typeof stored !== 'object' || stored === null || Array.isArray(stored)) {
49
+ return false;
50
+ }
51
+ if (!('updatedAt' in stored) || typeof stored.updatedAt !== 'number') {
52
+ return false;
53
+ }
54
+ if (!('value' in stored)) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
34
59
 
35
60
  //# sourceMappingURL=createExpiringConfig.js.map
@@ -1 +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"}
1
+ {"version":3,"sources":["../../src/util/createExpiringConfig.ts"],"sourcesContent":["import type ConfigStore from 'configstore'\n\ninterface ExpiringConfigValue {\n updatedAt: number\n value: unknown\n}\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 /**\n * Assert the fetched value is valid, or throw if invalid.\n * If none is provided, it will always accept the fetched value.\n */\n validateValue?: (value: unknown) => value is Type\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 validateValue = (value: unknown): value is Type => true,\n}: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type> {\n let currentFetch: Promise<Type> | null = null\n return {\n delete() {\n store.delete(key)\n },\n async get(): Promise<Type> {\n const stored = store.get(key)\n\n if (isExpiringValue(stored)) {\n const {updatedAt, value} = stored\n if (!validateValue(value)) {\n throw new Error('Stored value is invalid')\n }\n\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 if (!validateValue(nextValue)) {\n throw new Error('Fetched value is invalid')\n }\n\n currentFetch = null\n\n store.set(key, {\n updatedAt: Date.now(),\n value: nextValue,\n })\n\n return nextValue\n },\n }\n}\n\n/**\n * Checks if the given stored value is valid (does not check if expired, only verified shape)\n *\n * @param stored - The stored value to check\n * @returns True if the stored value is valid\n * @internal\n */\nfunction isExpiringValue(stored: unknown): stored is ExpiringConfigValue {\n if (typeof stored !== 'object' || stored === null || Array.isArray(stored)) {\n return false\n }\n\n if (!('updatedAt' in stored) || typeof stored.updatedAt !== 'number') {\n return false\n }\n\n if (!('value' in stored)) {\n return false\n }\n\n return true\n}\n"],"names":["createExpiringConfig","fetchValue","key","onCacheHit","onFetch","onRevalidate","store","ttl","validateValue","value","currentFetch","delete","get","stored","isExpiringValue","updatedAt","Error","hasExpired","Date","now","Promise","resolve","nextValue","set","Array","isArray"],"mappings":"AA2CA;;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,EACHC,gBAAgB,CAACC,QAAkC,IAAI,EAC3B;IAC5B,IAAIC,eAAqC;IACzC,OAAO;QACLC;YACEL,MAAMK,MAAM,CAACT;QACf;QACA,MAAMU;YACJ,MAAMC,SAASP,MAAMM,GAAG,CAACV;YAEzB,IAAIY,gBAAgBD,SAAS;gBAC3B,MAAM,EAACE,SAAS,EAAEN,KAAK,EAAC,GAAGI;gBAC3B,IAAI,CAACL,cAAcC,QAAQ;oBACzB,MAAM,IAAIO,MAAM;gBAClB;gBAEA,MAAMC,aAAaC,KAAKC,GAAG,KAAKJ,YAAYR;gBAE5C,IAAI,CAACU,YAAY;oBACfd;oBACA,OAAOM;gBACT;gBAEAJ;YACF;YAEA,IAAIK,cAAc;gBAChB,OAAOA;YACT;YACAN;YAEAM,eAAeU,QAAQC,OAAO,CAACpB;YAC/B,MAAMqB,YAAY,MAAMZ;YACxB,IAAI,CAACF,cAAcc,YAAY;gBAC7B,MAAM,IAAIN,MAAM;YAClB;YAEAN,eAAe;YAEfJ,MAAMiB,GAAG,CAACrB,KAAK;gBACba,WAAWG,KAAKC,GAAG;gBACnBV,OAAOa;YACT;YAEA,OAAOA;QACT;IACF;AACF;AAEA;;;;;;CAMC,GACD,SAASR,gBAAgBD,MAAe;IACtC,IAAI,OAAOA,WAAW,YAAYA,WAAW,QAAQW,MAAMC,OAAO,CAACZ,SAAS;QAC1E,OAAO;IACT;IAEA,IAAI,CAAE,CAAA,eAAeA,MAAK,KAAM,OAAOA,OAAOE,SAAS,KAAK,UAAU;QACpE,OAAO;IACT;IAEA,IAAI,CAAE,CAAA,WAAWF,MAAK,GAAI;QACxB,OAAO;IACT;IAEA,OAAO;AACT"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Loads the `getStudioEnvironmentVariables` function from the studio's
3
+ * installed `sanity` package and returns the environment variables.
4
+ *
5
+ * This is used to ensure we're using the same version of environment variable
6
+ * logic as the studio itself.
7
+ *
8
+ * @param rootPath - The root path of the Sanity Studio project
9
+ * @returns Object containing studio environment variables
10
+ * @internal
11
+ */
12
+ export declare function getStudioEnvironmentVariables(rootPath: string): Promise<Record<string, string>>;
@@ -0,0 +1,33 @@
1
+ import { resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { moduleResolve } from 'import-meta-resolve';
4
+ /**
5
+ * Loads the `getStudioEnvironmentVariables` function from the studio's
6
+ * installed `sanity` package and returns the environment variables.
7
+ *
8
+ * This is used to ensure we're using the same version of environment variable
9
+ * logic as the studio itself.
10
+ *
11
+ * @param rootPath - The root path of the Sanity Studio project
12
+ * @returns Object containing studio environment variables
13
+ * @internal
14
+ */ export async function getStudioEnvironmentVariables(rootPath) {
15
+ // Create a fake config URL - doesn't have to be correct, just need the root path
16
+ const fakeConfigUrl = pathToFileURL(resolve(rootPath, 'sanity.config.mjs'));
17
+ // Load `getStudioEnvironmentVariables` from the `sanity/cli` module installed
18
+ // relative to where the studio is located, instead of resolving from where this CLI is
19
+ // running, in order to ensure we're using the same version as the studio would.
20
+ const sanityCliUrl = moduleResolve('sanity/cli', fakeConfigUrl);
21
+ try {
22
+ const { getStudioEnvironmentVariables: getEnvVars } = await import(sanityCliUrl.href);
23
+ if (typeof getEnvVars !== 'function') {
24
+ throw new TypeError('Expected `getStudioEnvironmentVariables` from `sanity/cli` to be a function');
25
+ }
26
+ return getEnvVars(rootPath);
27
+ } catch (err) {
28
+ const message = err instanceof Error ? err.message : String(err);
29
+ throw new Error(`Failed to import getStudioEnvironmentVariables from sanity/cli module: ${message}`);
30
+ }
31
+ }
32
+
33
+ //# sourceMappingURL=getStudioEnvironmentVariables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/environment/getStudioEnvironmentVariables.ts"],"sourcesContent":["import {resolve} from 'node:path'\nimport {pathToFileURL} from 'node:url'\n\nimport {moduleResolve} from 'import-meta-resolve'\n\n/**\n * Loads the `getStudioEnvironmentVariables` function from the studio's\n * installed `sanity` package and returns the environment variables.\n *\n * This is used to ensure we're using the same version of environment variable\n * logic as the studio itself.\n *\n * @param rootPath - The root path of the Sanity Studio project\n * @returns Object containing studio environment variables\n * @internal\n */\nexport async function getStudioEnvironmentVariables(\n rootPath: string,\n): Promise<Record<string, string>> {\n // Create a fake config URL - doesn't have to be correct, just need the root path\n const fakeConfigUrl = pathToFileURL(resolve(rootPath, 'sanity.config.mjs'))\n\n // Load `getStudioEnvironmentVariables` from the `sanity/cli` module installed\n // relative to where the studio is located, instead of resolving from where this CLI is\n // running, in order to ensure we're using the same version as the studio would.\n const sanityCliUrl = moduleResolve('sanity/cli', fakeConfigUrl)\n try {\n const {getStudioEnvironmentVariables: getEnvVars} = await import(sanityCliUrl.href)\n if (typeof getEnvVars !== 'function') {\n throw new TypeError(\n 'Expected `getStudioEnvironmentVariables` from `sanity/cli` to be a function',\n )\n }\n return getEnvVars(rootPath)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Failed to import getStudioEnvironmentVariables from sanity/cli module: ${message}`,\n )\n }\n}\n"],"names":["resolve","pathToFileURL","moduleResolve","getStudioEnvironmentVariables","rootPath","fakeConfigUrl","sanityCliUrl","getEnvVars","href","TypeError","err","message","Error","String"],"mappings":"AAAA,SAAQA,OAAO,QAAO,YAAW;AACjC,SAAQC,aAAa,QAAO,WAAU;AAEtC,SAAQC,aAAa,QAAO,sBAAqB;AAEjD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,8BACpBC,QAAgB;IAEhB,iFAAiF;IACjF,MAAMC,gBAAgBJ,cAAcD,QAAQI,UAAU;IAEtD,8EAA8E;IAC9E,uFAAuF;IACvF,gFAAgF;IAChF,MAAME,eAAeJ,cAAc,cAAcG;IACjD,IAAI;QACF,MAAM,EAACF,+BAA+BI,UAAU,EAAC,GAAG,MAAM,MAAM,CAACD,aAAaE,IAAI;QAClF,IAAI,OAAOD,eAAe,YAAY;YACpC,MAAM,IAAIE,UACR;QAEJ;QACA,OAAOF,WAAWH;IACpB,EAAE,OAAOM,KAAK;QACZ,MAAMC,UAAUD,eAAeE,QAAQF,IAAIC,OAAO,GAAGE,OAAOH;QAC5D,MAAM,IAAIE,MACR,CAAC,uEAAuE,EAAED,SAAS;IAEvF;AACF"}