@stackframe/stack-shared 2.8.36 → 2.8.40

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 (114) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/config/schema.d.mts +91 -15
  3. package/dist/config/schema.d.ts +91 -15
  4. package/dist/config/schema.js +24 -4
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/esm/config/schema.js +24 -4
  7. package/dist/esm/config/schema.js.map +1 -1
  8. package/dist/esm/helpers/vault/server-side.js +5 -1
  9. package/dist/esm/helpers/vault/server-side.js.map +1 -1
  10. package/dist/esm/interface/admin-interface.js +53 -2
  11. package/dist/esm/interface/admin-interface.js.map +1 -1
  12. package/dist/esm/interface/client-interface.js +29 -26
  13. package/dist/esm/interface/client-interface.js.map +1 -1
  14. package/dist/esm/interface/crud/oauth-providers.js +2 -0
  15. package/dist/esm/interface/crud/oauth-providers.js.map +1 -1
  16. package/dist/esm/interface/crud/transactions.js +21 -0
  17. package/dist/esm/interface/crud/transactions.js.map +1 -0
  18. package/dist/esm/interface/server-interface.js +5 -7
  19. package/dist/esm/interface/server-interface.js.map +1 -1
  20. package/dist/esm/known-errors.js +21 -1
  21. package/dist/esm/known-errors.js.map +1 -1
  22. package/dist/esm/schema-fields.js +23 -4
  23. package/dist/esm/schema-fields.js.map +1 -1
  24. package/dist/esm/sessions.js +19 -9
  25. package/dist/esm/sessions.js.map +1 -1
  26. package/dist/esm/utils/arrays.js +25 -0
  27. package/dist/esm/utils/arrays.js.map +1 -1
  28. package/dist/esm/utils/esbuild.js +63 -6
  29. package/dist/esm/utils/esbuild.js.map +1 -1
  30. package/dist/esm/utils/jwt.js +16 -1
  31. package/dist/esm/utils/jwt.js.map +1 -1
  32. package/dist/esm/utils/numbers.js +14 -9
  33. package/dist/esm/utils/numbers.js.map +1 -1
  34. package/dist/esm/utils/paginated-lists.js +230 -0
  35. package/dist/esm/utils/paginated-lists.js.map +1 -0
  36. package/dist/esm/utils/promises.js +2 -1
  37. package/dist/esm/utils/promises.js.map +1 -1
  38. package/dist/esm/utils/types.js.map +1 -1
  39. package/dist/helpers/password.d.mts +3 -3
  40. package/dist/helpers/password.d.ts +3 -3
  41. package/dist/helpers/vault/server-side.js +5 -1
  42. package/dist/helpers/vault/server-side.js.map +1 -1
  43. package/dist/index.d.mts +6 -5
  44. package/dist/index.d.ts +6 -5
  45. package/dist/interface/admin-interface.d.mts +37 -7
  46. package/dist/interface/admin-interface.d.ts +37 -7
  47. package/dist/interface/admin-interface.js +53 -2
  48. package/dist/interface/admin-interface.js.map +1 -1
  49. package/dist/interface/client-interface.d.mts +5 -22
  50. package/dist/interface/client-interface.d.ts +5 -22
  51. package/dist/interface/client-interface.js +29 -26
  52. package/dist/interface/client-interface.js.map +1 -1
  53. package/dist/interface/crud/current-user.d.mts +4 -4
  54. package/dist/interface/crud/current-user.d.ts +4 -4
  55. package/dist/interface/crud/oauth-providers.d.mts +12 -4
  56. package/dist/interface/crud/oauth-providers.d.ts +12 -4
  57. package/dist/interface/crud/oauth-providers.js +1 -0
  58. package/dist/interface/crud/oauth-providers.js.map +1 -1
  59. package/dist/interface/crud/project-api-keys.d.mts +2 -2
  60. package/dist/interface/crud/project-api-keys.d.ts +2 -2
  61. package/dist/interface/crud/projects.d.mts +12 -12
  62. package/dist/interface/crud/projects.d.ts +12 -12
  63. package/dist/interface/crud/team-member-profiles.d.mts +6 -6
  64. package/dist/interface/crud/team-member-profiles.d.ts +6 -6
  65. package/dist/interface/crud/transactions.d.mts +56 -0
  66. package/dist/interface/crud/transactions.d.ts +56 -0
  67. package/dist/interface/crud/transactions.js +46 -0
  68. package/dist/interface/crud/transactions.js.map +1 -0
  69. package/dist/interface/crud/users.d.mts +6 -6
  70. package/dist/interface/crud/users.d.ts +6 -6
  71. package/dist/interface/server-interface.d.mts +8 -44
  72. package/dist/interface/server-interface.d.ts +8 -44
  73. package/dist/interface/server-interface.js +5 -7
  74. package/dist/interface/server-interface.js.map +1 -1
  75. package/dist/known-errors.d.mts +9 -3
  76. package/dist/known-errors.d.ts +9 -3
  77. package/dist/known-errors.js +21 -1
  78. package/dist/known-errors.js.map +1 -1
  79. package/dist/schema-fields.d.mts +38 -8
  80. package/dist/schema-fields.d.ts +38 -8
  81. package/dist/schema-fields.js +26 -5
  82. package/dist/schema-fields.js.map +1 -1
  83. package/dist/sessions.d.mts +26 -4
  84. package/dist/sessions.d.ts +26 -4
  85. package/dist/sessions.js +19 -9
  86. package/dist/sessions.js.map +1 -1
  87. package/dist/utils/arrays.d.mts +6 -1
  88. package/dist/utils/arrays.d.ts +6 -1
  89. package/dist/utils/arrays.js +30 -0
  90. package/dist/utils/arrays.js.map +1 -1
  91. package/dist/utils/esbuild.d.mts +1 -0
  92. package/dist/utils/esbuild.d.ts +1 -0
  93. package/dist/utils/esbuild.js +63 -6
  94. package/dist/utils/esbuild.js.map +1 -1
  95. package/dist/utils/jwt.d.mts +34 -1
  96. package/dist/utils/jwt.d.ts +34 -1
  97. package/dist/utils/jwt.js +16 -0
  98. package/dist/utils/jwt.js.map +1 -1
  99. package/dist/utils/numbers.js +14 -9
  100. package/dist/utils/numbers.js.map +1 -1
  101. package/dist/utils/paginated-lists.d.mts +176 -0
  102. package/dist/utils/paginated-lists.d.ts +176 -0
  103. package/dist/utils/paginated-lists.js +256 -0
  104. package/dist/utils/paginated-lists.js.map +1 -0
  105. package/dist/utils/promises.d.mts +1 -1
  106. package/dist/utils/promises.d.ts +1 -1
  107. package/dist/utils/promises.js +2 -1
  108. package/dist/utils/promises.js.map +1 -1
  109. package/dist/utils/stores.d.mts +6 -6
  110. package/dist/utils/stores.d.ts +6 -6
  111. package/dist/utils/types.d.mts +10 -1
  112. package/dist/utils/types.d.ts +10 -1
  113. package/dist/utils/types.js.map +1 -1
  114. package/package.json +3 -2
@@ -11,12 +11,26 @@ globalThis.self ??= globalThis;
11
11
  function initializeEsbuild() {
12
12
  if (!esbuildInitializePromise) {
13
13
  esbuildInitializePromise = withTraceSpan("initializeEsbuild", async () => {
14
- await esbuild.initialize(isBrowserLike() ? {
15
- wasmURL: esbuildWasmUrl
16
- } : {
17
- wasmModule: await fetch(esbuildWasmUrl).then((wasm) => wasm.arrayBuffer()).then((wasm) => new WebAssembly.Module(wasm)),
18
- worker: false
19
- });
14
+ if (isBrowserLike()) {
15
+ await esbuild.initialize({
16
+ wasmURL: esbuildWasmUrl
17
+ });
18
+ } else {
19
+ const esbuildWasmResponse = await fetch(esbuildWasmUrl);
20
+ if (!esbuildWasmResponse.ok) {
21
+ throw new StackAssertionError(`Failed to fetch esbuild.wasm: ${esbuildWasmResponse.status} ${esbuildWasmResponse.statusText}: ${await esbuildWasmResponse.text()}`);
22
+ }
23
+ const esbuildWasm = await esbuildWasmResponse.arrayBuffer();
24
+ const esbuildWasmArray = new Uint8Array(esbuildWasm);
25
+ if (esbuildWasmArray[0] !== 0 || esbuildWasmArray[1] !== 97 || esbuildWasmArray[2] !== 115 || esbuildWasmArray[3] !== 109) {
26
+ throw new StackAssertionError(`Invalid esbuild.wasm file: ${new TextDecoder().decode(esbuildWasmArray)}`);
27
+ }
28
+ const esbuildWasmModule = new WebAssembly.Module(esbuildWasm);
29
+ await esbuild.initialize({
30
+ wasmModule: esbuildWasmModule,
31
+ worker: false
32
+ });
33
+ }
20
34
  })();
21
35
  }
22
36
  return esbuildInitializePromise;
@@ -26,6 +40,7 @@ async function bundleJavaScript(sourceFiles, options = {}) {
26
40
  const sourceFilesMap = new Map(Object.entries(sourceFiles));
27
41
  const externalPackagesMap = new Map(Object.entries(options.externalPackages ?? {}));
28
42
  const keepAsImports = options.keepAsImports ?? [];
43
+ const httpImportCache = /* @__PURE__ */ new Map();
29
44
  const extToLoader = /* @__PURE__ */ new Map([
30
45
  ["tsx", "tsx"],
31
46
  ["ts", "ts"],
@@ -47,6 +62,48 @@ async function bundleJavaScript(sourceFiles, options = {}) {
47
62
  sourcemap: options.sourcemap ?? "inline",
48
63
  external: keepAsImports,
49
64
  plugins: [
65
+ ...options.allowHttpImports ? [{
66
+ name: "esm-sh-only",
67
+ setup(build2) {
68
+ build2.onResolve({ filter: /.*/ }, (args) => {
69
+ const isHttp = args.path.startsWith("http://") || args.path.startsWith("https://");
70
+ const fromEsmNs = args.namespace === "esm-sh";
71
+ if (!isHttp && !fromEsmNs) return null;
72
+ const url = new URL(args.path, fromEsmNs ? args.importer : void 0);
73
+ if (url.protocol !== "https:" || url.host !== "esm.sh") {
74
+ throw new Error(`Blocked non-esm.sh URL import: ${url.href}`);
75
+ }
76
+ return { path: url.href, namespace: "esm-sh" };
77
+ });
78
+ build2.onLoad({ filter: /.*/, namespace: "esm-sh" }, async (args) => {
79
+ if (httpImportCache.has(args.path)) return httpImportCache.get(args.path);
80
+ const res = await fetch(args.path, { redirect: "follow" });
81
+ if (!res.ok) throw new Error(`Fetch ${res.status} ${res.statusText} for ${args.path}`);
82
+ const finalUrl = new URL(res.url);
83
+ if (finalUrl.host !== "esm.sh") {
84
+ throw new Error(`Redirect escaped esm.sh: ${finalUrl.href}`);
85
+ }
86
+ const ct = (res.headers.get("content-type") || "").toLowerCase();
87
+ let loader = ct.includes("css") ? "css" : ct.includes("json") ? "json" : ct.includes("typescript") ? "ts" : ct.includes("jsx") ? "jsx" : ct.includes("tsx") ? "tsx" : "js";
88
+ const p = finalUrl.pathname;
89
+ if (p.endsWith(".css")) loader = "css";
90
+ else if (p.endsWith(".json")) loader = "json";
91
+ else if (p.endsWith(".ts")) loader = "ts";
92
+ else if (p.endsWith(".tsx")) loader = "tsx";
93
+ else if (p.endsWith(".jsx")) loader = "jsx";
94
+ const contents = await res.text();
95
+ const result2 = {
96
+ contents,
97
+ loader,
98
+ // Ensures relative imports inside that module resolve against the file’s URL
99
+ resolveDir: new URL(".", finalUrl.href).toString(),
100
+ watchFiles: [finalUrl.href]
101
+ };
102
+ httpImportCache.set(args.path, result2);
103
+ return result2;
104
+ });
105
+ }
106
+ }] : [],
50
107
  {
51
108
  name: "replace-packages-with-globals",
52
109
  setup(build2) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/esbuild.tsx"],"sourcesContent":["import * as esbuild from 'esbuild-wasm/lib/browser.js';\nimport { join } from 'path';\nimport { isBrowserLike } from './env';\nimport { StackAssertionError, throwErr } from \"./errors\";\nimport { Result } from \"./results\";\nimport { traceSpan, withTraceSpan } from './telemetry';\n\nconst esbuildWasmUrl = `https://unpkg.com/esbuild-wasm@${esbuild.version}/esbuild.wasm`;\n\nlet esbuildInitializePromise: Promise<void> | null = null;\n// esbuild requires self property to be set, and it is not set by default in nodejs\n(globalThis.self as any) ??= globalThis as any;\n\nexport function initializeEsbuild(): Promise<void> {\n if (!esbuildInitializePromise) {\n esbuildInitializePromise = withTraceSpan('initializeEsbuild', async () => {\n await esbuild.initialize(isBrowserLike() ? {\n wasmURL: esbuildWasmUrl,\n } : {\n wasmModule: (\n await fetch(esbuildWasmUrl)\n .then(wasm => wasm.arrayBuffer())\n .then(wasm => new WebAssembly.Module(wasm))\n ),\n worker: false,\n });\n })();\n }\n\n return esbuildInitializePromise;\n}\n\nexport async function bundleJavaScript(sourceFiles: Record<string, string> & { '/entry.js': string }, options: {\n format?: 'iife' | 'esm' | 'cjs',\n externalPackages?: Record<string, string>,\n keepAsImports?: string[],\n sourcemap?: false | 'inline',\n} = {}): Promise<Result<string, string>> {\n await initializeEsbuild();\n\n const sourceFilesMap = new Map(Object.entries(sourceFiles));\n const externalPackagesMap = new Map(Object.entries(options.externalPackages ?? {}));\n const keepAsImports = options.keepAsImports ?? [];\n\n const extToLoader: Map<string, esbuild.Loader> = new Map([\n ['tsx', 'tsx'],\n ['ts', 'ts'],\n ['js', 'js'],\n ['jsx', 'jsx'],\n ['json', 'json'],\n ['css', 'css'],\n ]);\n let result;\n try {\n result = await traceSpan('bundleJavaScript', async () => await esbuild.build({\n entryPoints: ['/entry.js'],\n bundle: true,\n write: false,\n format: options.format ?? 'iife',\n platform: 'browser',\n target: 'es2015',\n jsx: 'automatic',\n sourcemap: options.sourcemap ?? 'inline',\n external: keepAsImports,\n plugins: [\n {\n name: 'replace-packages-with-globals',\n setup(build) {\n build.onResolve({ filter: /.*/ }, args => {\n // Skip packages that should remain external (not be shimmed)\n if (keepAsImports.includes(args.path)) {\n return undefined;\n }\n if (externalPackagesMap.has(args.path)) {\n return { path: args.path, namespace: 'package-shim' };\n }\n return undefined;\n });\n\n build.onLoad({ filter: /.*/, namespace: 'package-shim' }, (args) => {\n const contents = externalPackagesMap.get(args.path);\n if (contents == null) throw new StackAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);\n\n return { contents, loader: 'ts' };\n });\n },\n },\n {\n name: 'virtual-fs',\n setup(build) {\n build.onResolve({ filter: /.*/ }, args => {\n const absolutePath = join(\"/\", args.path);\n if (sourceFilesMap.has(absolutePath)) {\n return { path: absolutePath, namespace: 'virtual' };\n }\n return undefined;\n });\n\n /* 2️⃣ Load the module from the map */\n build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {\n const contents = sourceFilesMap.get(args.path);\n if (contents == null) throw new StackAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);\n\n const ext = args.path.split('.').pop() ?? '';\n const loader = extToLoader.get(ext) ?? throwErr(`esbuild requested file ${args.path} with unknown extension ${ext}`);\n\n return { contents, loader };\n });\n },\n },\n ],\n }));\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"Build failed with \")) {\n return Result.error(e.message);\n }\n throw e;\n }\n\n if (result.errors.length > 0) {\n return Result.error(result.errors.map(e => e.text).join('\\n'));\n }\n\n if (result.outputFiles.length > 0) {\n return Result.ok(result.outputFiles[0].text);\n }\n return throwErr(\"No output generated??\");\n}\n"],"mappings":"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","names":["build"]}
1
+ {"version":3,"sources":["../../../src/utils/esbuild.tsx"],"sourcesContent":["import * as esbuild from 'esbuild-wasm/lib/browser.js';\nimport { join } from 'path';\nimport { isBrowserLike } from './env';\nimport { StackAssertionError, throwErr } from \"./errors\";\nimport { Result } from \"./results\";\nimport { traceSpan, withTraceSpan } from './telemetry';\n\nconst esbuildWasmUrl = `https://unpkg.com/esbuild-wasm@${esbuild.version}/esbuild.wasm`;\n\nlet esbuildInitializePromise: Promise<void> | null = null;\n// esbuild requires self property to be set, and it is not set by default in nodejs\n(globalThis.self as any) ??= globalThis as any;\n\nexport function initializeEsbuild(): Promise<void> {\n if (!esbuildInitializePromise) {\n esbuildInitializePromise = withTraceSpan('initializeEsbuild', async () => {\n if (isBrowserLike()) {\n await esbuild.initialize({\n wasmURL: esbuildWasmUrl,\n });\n } else {\n const esbuildWasmResponse = await fetch(esbuildWasmUrl);\n if (!esbuildWasmResponse.ok) {\n throw new StackAssertionError(`Failed to fetch esbuild.wasm: ${esbuildWasmResponse.status} ${esbuildWasmResponse.statusText}: ${await esbuildWasmResponse.text()}`);\n }\n const esbuildWasm = await esbuildWasmResponse.arrayBuffer();\n const esbuildWasmArray = new Uint8Array(esbuildWasm);\n if (esbuildWasmArray[0] !== 0x00 || esbuildWasmArray[1] !== 0x61 || esbuildWasmArray[2] !== 0x73 || esbuildWasmArray[3] !== 0x6d) {\n throw new StackAssertionError(`Invalid esbuild.wasm file: ${new TextDecoder().decode(esbuildWasmArray)}`);\n }\n const esbuildWasmModule = new WebAssembly.Module(esbuildWasm);\n await esbuild.initialize({\n wasmModule: esbuildWasmModule,\n worker: false,\n });\n }\n })();\n }\n\n return esbuildInitializePromise;\n}\n\nexport async function bundleJavaScript(sourceFiles: Record<string, string> & { '/entry.js': string }, options: {\n format?: 'iife' | 'esm' | 'cjs',\n externalPackages?: Record<string, string>,\n keepAsImports?: string[],\n sourcemap?: false | 'inline',\n allowHttpImports?: boolean,\n} = {}): Promise<Result<string, string>> {\n await initializeEsbuild();\n\n const sourceFilesMap = new Map(Object.entries(sourceFiles));\n const externalPackagesMap = new Map(Object.entries(options.externalPackages ?? {}));\n const keepAsImports = options.keepAsImports ?? [];\n\n const httpImportCache = new Map<string, { contents: string, loader: esbuild.Loader, resolveDir: string }>();\n\n const extToLoader: Map<string, esbuild.Loader> = new Map([\n ['tsx', 'tsx'],\n ['ts', 'ts'],\n ['js', 'js'],\n ['jsx', 'jsx'],\n ['json', 'json'],\n ['css', 'css'],\n ]);\n let result;\n try {\n result = await traceSpan('bundleJavaScript', async () => await esbuild.build({\n entryPoints: ['/entry.js'],\n bundle: true,\n write: false,\n format: options.format ?? 'iife',\n platform: 'browser',\n target: 'es2015',\n jsx: 'automatic',\n sourcemap: options.sourcemap ?? 'inline',\n external: keepAsImports,\n plugins: [\n ...options.allowHttpImports ? [{\n name: \"esm-sh-only\",\n setup(build: esbuild.PluginBuild) {\n // Handle absolute URLs and relative imports from esm.sh modules.\n build.onResolve({ filter: /.*/ }, (args) => {\n // Only touch absolute http(s) specifiers or children of our own namespace\n const isHttp = args.path.startsWith(\"http://\") || args.path.startsWith(\"https://\");\n const fromEsmNs = args.namespace === \"esm-sh\";\n\n if (!isHttp && !fromEsmNs) return null; // Let other plugins handle bare/relative/local\n\n // Resolve relative URLs inside esm.sh-fetched modules\n const url = new URL(args.path, fromEsmNs ? args.importer : undefined);\n\n if (url.protocol !== \"https:\" || url.host !== \"esm.sh\") {\n throw new Error(`Blocked non-esm.sh URL import: ${url.href}`);\n }\n\n return { path: url.href, namespace: \"esm-sh\" };\n });\n\n build.onLoad({ filter: /.*/, namespace: \"esm-sh\" }, async (args) => {\n if (httpImportCache.has(args.path)) return httpImportCache.get(args.path)!;\n\n const res = await fetch(args.path, { redirect: \"follow\" });\n if (!res.ok) throw new Error(`Fetch ${res.status} ${res.statusText} for ${args.path}`);\n const finalUrl = new URL(res.url);\n // Defensive: follow shouldn’t leave esm.sh, but re-check.\n if (finalUrl.host !== \"esm.sh\") {\n throw new Error(`Redirect escaped esm.sh: ${finalUrl.href}`);\n }\n\n const ct = (res.headers.get(\"content-type\") || \"\").toLowerCase();\n let loader: esbuild.Loader =\n ct.includes(\"css\") ? \"css\" :\n ct.includes(\"json\") ? \"json\" :\n ct.includes(\"typescript\") ? \"ts\" :\n ct.includes(\"jsx\") ? \"jsx\" :\n ct.includes(\"tsx\") ? \"tsx\" :\n \"js\";\n\n // Fallback by extension (esm.sh sometimes omits CT)\n const p = finalUrl.pathname;\n if (p.endsWith(\".css\")) loader = \"css\";\n else if (p.endsWith(\".json\")) loader = \"json\";\n else if (p.endsWith(\".ts\")) loader = \"ts\";\n else if (p.endsWith(\".tsx\")) loader = \"tsx\";\n else if (p.endsWith(\".jsx\")) loader = \"jsx\";\n\n const contents = await res.text();\n const result = {\n contents,\n loader,\n // Ensures relative imports inside that module resolve against the file’s URL\n resolveDir: new URL(\".\", finalUrl.href).toString(),\n watchFiles: [finalUrl.href],\n };\n httpImportCache.set(args.path, result);\n return result;\n });\n },\n } as esbuild.Plugin] : [],\n {\n name: 'replace-packages-with-globals',\n setup(build) {\n build.onResolve({ filter: /.*/ }, args => {\n // Skip packages that should remain external (not be shimmed)\n if (keepAsImports.includes(args.path)) {\n return undefined;\n }\n if (externalPackagesMap.has(args.path)) {\n return { path: args.path, namespace: 'package-shim' };\n }\n return undefined;\n });\n\n build.onLoad({ filter: /.*/, namespace: 'package-shim' }, (args) => {\n const contents = externalPackagesMap.get(args.path);\n if (contents == null) throw new StackAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);\n\n return { contents, loader: 'ts' };\n });\n },\n },\n {\n name: 'virtual-fs',\n setup(build) {\n build.onResolve({ filter: /.*/ }, args => {\n const absolutePath = join(\"/\", args.path);\n if (sourceFilesMap.has(absolutePath)) {\n return { path: absolutePath, namespace: 'virtual' };\n }\n return undefined;\n });\n\n /* 2️⃣ Load the module from the map */\n build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {\n const contents = sourceFilesMap.get(args.path);\n if (contents == null) throw new StackAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);\n\n const ext = args.path.split('.').pop() ?? '';\n const loader = extToLoader.get(ext) ?? throwErr(`esbuild requested file ${args.path} with unknown extension ${ext}`);\n\n return { contents, loader };\n });\n },\n },\n ],\n }));\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"Build failed with \")) {\n return Result.error(e.message);\n }\n throw e;\n }\n\n if (result.errors.length > 0) {\n return Result.error(result.errors.map(e => e.text).join('\\n'));\n }\n\n if (result.outputFiles.length > 0) {\n return Result.ok(result.outputFiles[0].text);\n }\n return throwErr(\"No output generated??\");\n}\n"],"mappings":"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","names":["build","result"]}
@@ -5,9 +5,11 @@ import * as jose from "jose";
5
5
  import { JOSEError } from "jose/errors";
6
6
  import { encodeBase64Url } from "./bytes.js";
7
7
  import { getEnvVariable } from "./env.js";
8
- import { StackAssertionError } from "./errors.js";
8
+ import { StackAssertionError, errorToNiceString } from "./errors.js";
9
9
  import { globalVar } from "./globals.js";
10
10
  import { pick } from "./objects.js";
11
+ import { Result } from "./results.js";
12
+ import { nicify } from "./strings.js";
11
13
  function getStackServerSecret() {
12
14
  const STACK_SERVER_SECRET = getEnvVariable("STACK_SERVER_SECRET");
13
15
  try {
@@ -17,6 +19,18 @@ function getStackServerSecret() {
17
19
  }
18
20
  return STACK_SERVER_SECRET;
19
21
  }
22
+ async function getJwtInfo(options) {
23
+ try {
24
+ if (typeof options.jwt !== "string") return Result.error({ error: "JWT input is not a string!", stringifiedInput: nicify(options.jwt) });
25
+ if (!options.jwt.startsWith("ey")) return Result.error({ error: "Input is a string, but not a JWT!", input: options.jwt });
26
+ const decodedJwt = jose.decodeJwt(options.jwt);
27
+ return Result.ok({ payload: decodedJwt });
28
+ } catch (e) {
29
+ return Result.error({
30
+ exception: errorToNiceString(e)
31
+ });
32
+ }
33
+ }
20
34
  async function signJWT(options) {
21
35
  const privateJwks = await getPrivateJwks({ audience: options.audience });
22
36
  const privateKey = await jose.importJWK(privateJwks[0]);
@@ -83,6 +97,7 @@ function oldGetKid(options) {
83
97
  ).slice(0, 12);
84
98
  }
85
99
  export {
100
+ getJwtInfo,
86
101
  getPrivateJwks,
87
102
  getPublicJwkSet,
88
103
  oldGetKid,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/jwt.tsx"],"sourcesContent":["import crypto from \"crypto\";\nimport elliptic from \"elliptic\";\nimport * as jose from \"jose\";\nimport { JOSEError } from \"jose/errors\";\nimport { encodeBase64Url } from \"./bytes\";\nimport { getEnvVariable } from \"./env\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\nimport { pick } from \"./objects\";\n\nfunction getStackServerSecret() {\n const STACK_SERVER_SECRET = getEnvVariable(\"STACK_SERVER_SECRET\");\n try {\n jose.base64url.decode(STACK_SERVER_SECRET);\n } catch (e) {\n throw new StackAssertionError(\"STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.\", { cause: e });\n }\n return STACK_SERVER_SECRET;\n}\n\nexport async function signJWT(options: {\n issuer: string,\n audience: string,\n payload: any,\n expirationTime?: string,\n}) {\n const privateJwks = await getPrivateJwks({ audience: options.audience });\n const privateKey = await jose.importJWK(privateJwks[0]);\n\n return await new jose.SignJWT(options.payload)\n .setProtectedHeader({ alg: \"ES256\", kid: privateJwks[0].kid })\n .setIssuer(options.issuer)\n .setIssuedAt()\n .setAudience(options.audience)\n .setExpirationTime(options.expirationTime || \"5m\")\n .sign(privateKey);\n}\n\nexport async function verifyJWT(options: {\n allowedIssuers: string[],\n jwt: string,\n}) {\n const decodedJwt = jose.decodeJwt(options.jwt);\n const audience = decodedJwt.aud;\n if (!audience || typeof audience !== \"string\") {\n throw new JOSEError(\"Invalid JWT audience\");\n }\n\n const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(await getPrivateJwks({ audience })));\n const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.allowedIssuers });\n return verified.payload;\n}\n\nexport type PrivateJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n d: string,\n x: string,\n y: string,\n};\nasync function getPrivateJwkFromDerivedSecret(derivedSecret: string, kid: string): Promise<PrivateJwk> {\n const secretHash = await globalVar.crypto.subtle.digest(\"SHA-256\", jose.base64url.decode(derivedSecret));\n const priv = new Uint8Array(secretHash);\n\n const ec = new elliptic.ec('p256');\n const key = ec.keyFromPrivate(priv);\n const publicKey = key.getPublic();\n\n return {\n kty: 'EC',\n crv: 'P-256',\n alg: 'ES256',\n kid: kid,\n d: encodeBase64Url(priv),\n x: encodeBase64Url(publicKey.getX().toBuffer()),\n y: encodeBase64Url(publicKey.getY().toBuffer()),\n };\n}\n\n/**\n * Returns a list of valid private JWKs for the given audience, with the first one taking precedence when signing new\n * JWTs.\n */\nexport async function getPrivateJwks(options: {\n audience: string,\n}): Promise<PrivateJwk[]> {\n const getHashOfJwkInfo = (type: string) => jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([type, getStackServerSecret(), {\n audience: options.audience,\n }]))\n .digest()\n );\n const perAudienceSecret = getHashOfJwkInfo(\"stack-jwk-audience-secret\");\n const perAudienceKid = getHashOfJwkInfo(\"stack-jwk-kid\").slice(0, 12);\n\n const oldPerAudienceSecret = oldGetPerAudienceSecret({ audience: options.audience });\n const oldPerAudienceKid = oldGetKid({ secret: oldPerAudienceSecret });\n\n return [\n // TODO next-release: make this not take precedence; then, in the release after that, remove it entirely\n await getPrivateJwkFromDerivedSecret(oldPerAudienceSecret, oldPerAudienceKid),\n\n await getPrivateJwkFromDerivedSecret(perAudienceSecret, perAudienceKid),\n ];\n}\n\nexport type PublicJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n x: string,\n y: string,\n};\nexport async function getPublicJwkSet(privateJwks: PrivateJwk[]): Promise<{ keys: PublicJwk[] }> {\n return {\n keys: privateJwks.map(jwk => pick(jwk, [\"kty\", \"alg\", \"crv\", \"x\", \"y\", \"kid\"])),\n };\n}\n\nfunction oldGetPerAudienceSecret(options: {\n audience: string,\n}) {\n if (options.audience === \"kid\") {\n throw new StackAssertionError(\"You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx\");\n }\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n // TODO we should prefix a string like \"stack-audience-secret\" before we hash so you can't use `getKid(...)` to get the secret for eg. the \"kid\" audience if the same secret value is used\n // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer\n .update(JSON.stringify([getStackServerSecret(), options.audience]))\n .digest()\n );\n};\n\nexport function oldGetKid(options: {\n secret: string,\n}) {\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([options.secret, \"kid\"])) // TODO see above in getPerAudienceSecret\n .digest()\n ).slice(0, 12);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/utils/jwt.tsx"],"sourcesContent":["import crypto from \"crypto\";\nimport elliptic from \"elliptic\";\nimport * as jose from \"jose\";\nimport { JOSEError } from \"jose/errors\";\nimport { encodeBase64Url } from \"./bytes\";\nimport { getEnvVariable } from \"./env\";\nimport { StackAssertionError, errorToNiceString } from \"./errors\";\nimport { globalVar } from \"./globals\";\nimport { pick } from \"./objects\";\nimport { Result } from \"./results\";\nimport { nicify } from \"./strings\";\n\nfunction getStackServerSecret() {\n const STACK_SERVER_SECRET = getEnvVariable(\"STACK_SERVER_SECRET\");\n try {\n jose.base64url.decode(STACK_SERVER_SECRET);\n } catch (e) {\n throw new StackAssertionError(\"STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.\", { cause: e });\n }\n return STACK_SERVER_SECRET;\n}\n\nexport async function getJwtInfo(options: {\n jwt: string,\n}) {\n try {\n if (typeof options.jwt !== \"string\") return Result.error({ error: \"JWT input is not a string!\", stringifiedInput: nicify(options.jwt) });\n if (!options.jwt.startsWith(\"ey\")) return Result.error({ error: \"Input is a string, but not a JWT!\", input: options.jwt });\n const decodedJwt = jose.decodeJwt(options.jwt);\n return Result.ok({ payload: decodedJwt });\n } catch (e) {\n return Result.error({\n exception: errorToNiceString(e),\n });\n }\n}\n\nexport async function signJWT(options: {\n issuer: string,\n audience: string,\n payload: any,\n expirationTime?: string,\n}) {\n const privateJwks = await getPrivateJwks({ audience: options.audience });\n const privateKey = await jose.importJWK(privateJwks[0]);\n\n return await new jose.SignJWT(options.payload)\n .setProtectedHeader({ alg: \"ES256\", kid: privateJwks[0].kid })\n .setIssuer(options.issuer)\n .setIssuedAt()\n .setAudience(options.audience)\n .setExpirationTime(options.expirationTime || \"5m\")\n .sign(privateKey);\n}\n\nexport async function verifyJWT(options: {\n allowedIssuers: string[],\n jwt: string,\n}) {\n const decodedJwt = jose.decodeJwt(options.jwt);\n const audience = decodedJwt.aud;\n if (!audience || typeof audience !== \"string\") {\n throw new JOSEError(\"Invalid JWT audience\");\n }\n\n const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(await getPrivateJwks({ audience })));\n const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.allowedIssuers });\n return verified.payload;\n}\n\nexport type PrivateJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n d: string,\n x: string,\n y: string,\n};\nasync function getPrivateJwkFromDerivedSecret(derivedSecret: string, kid: string): Promise<PrivateJwk> {\n const secretHash = await globalVar.crypto.subtle.digest(\"SHA-256\", jose.base64url.decode(derivedSecret));\n const priv = new Uint8Array(secretHash);\n\n const ec = new elliptic.ec('p256');\n const key = ec.keyFromPrivate(priv);\n const publicKey = key.getPublic();\n\n return {\n kty: 'EC',\n crv: 'P-256',\n alg: 'ES256',\n kid: kid,\n d: encodeBase64Url(priv),\n x: encodeBase64Url(publicKey.getX().toBuffer()),\n y: encodeBase64Url(publicKey.getY().toBuffer()),\n };\n}\n\n/**\n * Returns a list of valid private JWKs for the given audience, with the first one taking precedence when signing new\n * JWTs.\n */\nexport async function getPrivateJwks(options: {\n audience: string,\n}): Promise<PrivateJwk[]> {\n const getHashOfJwkInfo = (type: string) => jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([type, getStackServerSecret(), {\n audience: options.audience,\n }]))\n .digest()\n );\n const perAudienceSecret = getHashOfJwkInfo(\"stack-jwk-audience-secret\");\n const perAudienceKid = getHashOfJwkInfo(\"stack-jwk-kid\").slice(0, 12);\n\n const oldPerAudienceSecret = oldGetPerAudienceSecret({ audience: options.audience });\n const oldPerAudienceKid = oldGetKid({ secret: oldPerAudienceSecret });\n\n return [\n // TODO next-release: make this not take precedence; then, in the release after that, remove it entirely\n await getPrivateJwkFromDerivedSecret(oldPerAudienceSecret, oldPerAudienceKid),\n\n await getPrivateJwkFromDerivedSecret(perAudienceSecret, perAudienceKid),\n ];\n}\n\nexport type PublicJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n x: string,\n y: string,\n};\nexport async function getPublicJwkSet(privateJwks: PrivateJwk[]): Promise<{ keys: PublicJwk[] }> {\n return {\n keys: privateJwks.map(jwk => pick(jwk, [\"kty\", \"alg\", \"crv\", \"x\", \"y\", \"kid\"])),\n };\n}\n\nfunction oldGetPerAudienceSecret(options: {\n audience: string,\n}) {\n if (options.audience === \"kid\") {\n throw new StackAssertionError(\"You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx\");\n }\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n // TODO we should prefix a string like \"stack-audience-secret\" before we hash so you can't use `getKid(...)` to get the secret for eg. the \"kid\" audience if the same secret value is used\n // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer\n .update(JSON.stringify([getStackServerSecret(), options.audience]))\n .digest()\n );\n};\n\nexport function oldGetKid(options: {\n secret: string,\n}) {\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([options.secret, \"kid\"])) // TODO see above in getPerAudienceSecret\n .digest()\n ).slice(0, 12);\n}\n"],"mappings":"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","names":[]}
@@ -1,22 +1,27 @@
1
1
  // src/utils/numbers.tsx
2
2
  var magnitudes = [
3
- [1e15, "trln"],
4
- [1e12, "bln"],
5
- [1e9, "bn"],
6
- [1e6, "M"],
7
- [1e3, "k"]
3
+ [1e3, "k"],
4
+ [1e3, "M"],
5
+ [1e3, "bn"],
6
+ [1e3, "bln"],
7
+ [1e3, "trln"]
8
8
  ];
9
9
  function prettyPrintWithMagnitudes(num) {
10
10
  if (typeof num !== "number") throw new Error("Expected a number");
11
11
  if (Number.isNaN(num)) return "NaN";
12
12
  if (num < 0) return "-" + prettyPrintWithMagnitudes(-num);
13
13
  if (!Number.isFinite(num)) return "\u221E";
14
- for (const [magnitude, suffix] of magnitudes) {
15
- if (num >= magnitude) {
16
- return toFixedMax(num / magnitude, 1) + suffix;
14
+ let current = toFixedMax(num, 1);
15
+ let lastSuffix = "";
16
+ for (const [difference, suffix] of magnitudes) {
17
+ if (+current >= difference) {
18
+ current = toFixedMax(+current / difference, 1);
19
+ lastSuffix = suffix;
20
+ } else {
21
+ break;
17
22
  }
18
23
  }
19
- return toFixedMax(num, 1);
24
+ return current + lastSuffix;
20
25
  }
21
26
  function toFixedMax(num, maxDecimals) {
22
27
  return num.toFixed(maxDecimals).replace(/\.?0+$/, "");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/numbers.tsx"],"sourcesContent":["const magnitudes = [\n [1_000_000_000_000_000, \"trln\"],\n [1_000_000_000_000, \"bln\"],\n [1_000_000_000, \"bn\"],\n [1_000_000, \"M\"],\n [1_000, \"k\"],\n] as const;\n\nexport function prettyPrintWithMagnitudes(num: number): string {\n if (typeof num !== \"number\") throw new Error(\"Expected a number\");\n if (Number.isNaN(num)) return \"NaN\";\n if (num < 0) return \"-\" + prettyPrintWithMagnitudes(-num);\n if (!Number.isFinite(num)) return \"∞\";\n\n for (const [magnitude, suffix] of magnitudes) {\n if (num >= magnitude) {\n return toFixedMax(num / magnitude, 1) + suffix;\n }\n }\n return toFixedMax(num, 1); // Handle numbers less than 1,000 without suffix.\n}\nundefined?.test(\"prettyPrintWithMagnitudes\", ({ expect }) => {\n // Test different magnitudes\n expect(prettyPrintWithMagnitudes(999)).toBe(\"999\");\n expect(prettyPrintWithMagnitudes(1000)).toBe(\"1k\");\n expect(prettyPrintWithMagnitudes(1500)).toBe(\"1.5k\");\n expect(prettyPrintWithMagnitudes(1000000)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1500000)).toBe(\"1.5M\");\n expect(prettyPrintWithMagnitudes(1000000000)).toBe(\"1bn\");\n expect(prettyPrintWithMagnitudes(1500000000)).toBe(\"1.5bn\");\n expect(prettyPrintWithMagnitudes(1000000000000)).toBe(\"1bln\");\n expect(prettyPrintWithMagnitudes(1500000000000)).toBe(\"1.5bln\");\n expect(prettyPrintWithMagnitudes(1000000000000000)).toBe(\"1trln\");\n expect(prettyPrintWithMagnitudes(1500000000000000)).toBe(\"1.5trln\");\n // Test small numbers\n expect(prettyPrintWithMagnitudes(100)).toBe(\"100\");\n expect(prettyPrintWithMagnitudes(0)).toBe(\"0\");\n expect(prettyPrintWithMagnitudes(0.5)).toBe(\"0.5\");\n // Test negative numbers\n expect(prettyPrintWithMagnitudes(-1000)).toBe(\"-1k\");\n expect(prettyPrintWithMagnitudes(-1500000)).toBe(\"-1.5M\");\n // Test special cases\n expect(prettyPrintWithMagnitudes(NaN)).toBe(\"NaN\");\n expect(prettyPrintWithMagnitudes(Infinity)).toBe(\"∞\");\n expect(prettyPrintWithMagnitudes(-Infinity)).toBe(\"-∞\");\n});\n\nexport function toFixedMax(num: number, maxDecimals: number): string {\n return num.toFixed(maxDecimals).replace(/\\.?0+$/, \"\");\n}\nundefined?.test(\"toFixedMax\", ({ expect }) => {\n expect(toFixedMax(1, 2)).toBe(\"1\");\n expect(toFixedMax(1.2, 2)).toBe(\"1.2\");\n expect(toFixedMax(1.23, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.234, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.0, 2)).toBe(\"1\");\n expect(toFixedMax(1.20, 2)).toBe(\"1.2\");\n expect(toFixedMax(0, 2)).toBe(\"0\");\n});\n\nexport function numberCompare(a: number, b: number): number {\n return Math.sign(a - b);\n}\nundefined?.test(\"numberCompare\", ({ expect }) => {\n expect(numberCompare(1, 2)).toBe(-1);\n expect(numberCompare(2, 1)).toBe(1);\n expect(numberCompare(1, 1)).toBe(0);\n expect(numberCompare(0, 0)).toBe(0);\n expect(numberCompare(-1, -2)).toBe(1);\n expect(numberCompare(-2, -1)).toBe(-1);\n expect(numberCompare(-1, 1)).toBe(-1);\n expect(numberCompare(1, -1)).toBe(1);\n});\n"],"mappings":";AAAA,IAAM,aAAa;AAAA,EACjB,CAAC,MAAuB,MAAM;AAAA,EAC9B,CAAC,MAAmB,KAAK;AAAA,EACzB,CAAC,KAAe,IAAI;AAAA,EACpB,CAAC,KAAW,GAAG;AAAA,EACf,CAAC,KAAO,GAAG;AACb;AAEO,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAChE,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,MAAM,EAAG,QAAO,MAAM,0BAA0B,CAAC,GAAG;AACxD,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAElC,aAAW,CAAC,WAAW,MAAM,KAAK,YAAY;AAC5C,QAAI,OAAO,WAAW;AACpB,aAAO,WAAW,MAAM,WAAW,CAAC,IAAI;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,WAAW,KAAK,CAAC;AAC1B;AA2BO,SAAS,WAAW,KAAa,aAA6B;AACnE,SAAO,IAAI,QAAQ,WAAW,EAAE,QAAQ,UAAU,EAAE;AACtD;AAWO,SAAS,cAAc,GAAW,GAAmB;AAC1D,SAAO,KAAK,KAAK,IAAI,CAAC;AACxB;","names":[]}
1
+ {"version":3,"sources":["../../../src/utils/numbers.tsx"],"sourcesContent":["const magnitudes = [\n [1_000, \"k\"],\n [1_000, \"M\"],\n [1_000, \"bn\"],\n [1_000, \"bln\"],\n [1_000, \"trln\"],\n] as const;\n\nexport function prettyPrintWithMagnitudes(num: number): string {\n if (typeof num !== \"number\") throw new Error(\"Expected a number\");\n if (Number.isNaN(num)) return \"NaN\";\n if (num < 0) return \"-\" + prettyPrintWithMagnitudes(-num);\n if (!Number.isFinite(num)) return \"∞\";\n\n let current = toFixedMax(num, 1);\n let lastSuffix = \"\";\n for (const [difference, suffix] of magnitudes) {\n if (+current >= difference) {\n current = toFixedMax(+current / difference, 1);\n lastSuffix = suffix;\n } else {\n break;\n }\n }\n return current + lastSuffix;\n}\nundefined?.test(\"prettyPrintWithMagnitudes\", ({ expect }) => {\n // Test different magnitudes\n expect(prettyPrintWithMagnitudes(999)).toBe(\"999\");\n expect(prettyPrintWithMagnitudes(1000)).toBe(\"1k\");\n expect(prettyPrintWithMagnitudes(1500)).toBe(\"1.5k\");\n expect(prettyPrintWithMagnitudes(999499)).toBe(\"999.5k\");\n expect(prettyPrintWithMagnitudes(999500)).toBe(\"999.5k\");\n expect(prettyPrintWithMagnitudes(999949)).toBe(\"999.9k\");\n expect(prettyPrintWithMagnitudes(999950)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1000000)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1500000)).toBe(\"1.5M\");\n expect(prettyPrintWithMagnitudes(1000000000)).toBe(\"1bn\");\n expect(prettyPrintWithMagnitudes(1500000000)).toBe(\"1.5bn\");\n expect(prettyPrintWithMagnitudes(1000000000000)).toBe(\"1bln\");\n expect(prettyPrintWithMagnitudes(1500000000000)).toBe(\"1.5bln\");\n expect(prettyPrintWithMagnitudes(1000000000000000)).toBe(\"1trln\");\n expect(prettyPrintWithMagnitudes(1500000000000000)).toBe(\"1.5trln\");\n // Test small numbers\n expect(prettyPrintWithMagnitudes(100)).toBe(\"100\");\n expect(prettyPrintWithMagnitudes(0)).toBe(\"0\");\n expect(prettyPrintWithMagnitudes(0.5)).toBe(\"0.5\");\n // Test negative numbers\n expect(prettyPrintWithMagnitudes(-1000)).toBe(\"-1k\");\n expect(prettyPrintWithMagnitudes(-1500000)).toBe(\"-1.5M\");\n // Test special cases\n expect(prettyPrintWithMagnitudes(NaN)).toBe(\"NaN\");\n expect(prettyPrintWithMagnitudes(Infinity)).toBe(\"∞\");\n expect(prettyPrintWithMagnitudes(-Infinity)).toBe(\"-∞\");\n});\n\nexport function toFixedMax(num: number, maxDecimals: number): string {\n return num.toFixed(maxDecimals).replace(/\\.?0+$/, \"\");\n}\nundefined?.test(\"toFixedMax\", ({ expect }) => {\n expect(toFixedMax(1, 2)).toBe(\"1\");\n expect(toFixedMax(1.2, 2)).toBe(\"1.2\");\n expect(toFixedMax(1.23, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.234, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.0, 2)).toBe(\"1\");\n expect(toFixedMax(1.20, 2)).toBe(\"1.2\");\n expect(toFixedMax(0, 2)).toBe(\"0\");\n});\n\nexport function numberCompare(a: number, b: number): number {\n return Math.sign(a - b);\n}\nundefined?.test(\"numberCompare\", ({ expect }) => {\n expect(numberCompare(1, 2)).toBe(-1);\n expect(numberCompare(2, 1)).toBe(1);\n expect(numberCompare(1, 1)).toBe(0);\n expect(numberCompare(0, 0)).toBe(0);\n expect(numberCompare(-1, -2)).toBe(1);\n expect(numberCompare(-2, -1)).toBe(-1);\n expect(numberCompare(-1, 1)).toBe(-1);\n expect(numberCompare(1, -1)).toBe(1);\n});\n"],"mappings":";AAAA,IAAM,aAAa;AAAA,EACjB,CAAC,KAAO,GAAG;AAAA,EACX,CAAC,KAAO,GAAG;AAAA,EACX,CAAC,KAAO,IAAI;AAAA,EACZ,CAAC,KAAO,KAAK;AAAA,EACb,CAAC,KAAO,MAAM;AAChB;AAEO,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAChE,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,MAAM,EAAG,QAAO,MAAM,0BAA0B,CAAC,GAAG;AACxD,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAElC,MAAI,UAAU,WAAW,KAAK,CAAC;AAC/B,MAAI,aAAa;AACjB,aAAW,CAAC,YAAY,MAAM,KAAK,YAAY;AAC7C,QAAI,CAAC,WAAW,YAAY;AAC1B,gBAAU,WAAW,CAAC,UAAU,YAAY,CAAC;AAC7C,mBAAa;AAAA,IACf,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,SAAO,UAAU;AACnB;AA+BO,SAAS,WAAW,KAAa,aAA6B;AACnE,SAAO,IAAI,QAAQ,WAAW,EAAE,QAAQ,UAAU,EAAE;AACtD;AAWO,SAAS,cAAc,GAAW,GAAmB;AAC1D,SAAO,KAAK,KAAK,IAAI,CAAC;AACxB;","names":[]}
@@ -0,0 +1,230 @@
1
+ // src/utils/paginated-lists.tsx
2
+ import { range } from "./arrays.js";
3
+ import { StackAssertionError } from "./errors.js";
4
+ var PaginatedList = class _PaginatedList {
5
+ // Implementations
6
+ getFirstCursor() {
7
+ return this._getFirstCursor();
8
+ }
9
+ getLastCursor() {
10
+ return this._getLastCursor();
11
+ }
12
+ compare(orderBy, a, b) {
13
+ return this._compare(orderBy, a, b);
14
+ }
15
+ async nextOrPrev(type, options) {
16
+ let result = [];
17
+ let includesFirst = false;
18
+ let includesLast = false;
19
+ let cursor = options.cursor;
20
+ let limitRemaining = options.limit;
21
+ while (limitRemaining > 0 || type === "next" && includesLast || type === "prev" && includesFirst) {
22
+ const iterationRes = await this._nextOrPrev(type, {
23
+ cursor,
24
+ limit: options.limit,
25
+ limitPrecision: "approximate",
26
+ filter: options.filter,
27
+ orderBy: options.orderBy
28
+ });
29
+ result[type === "next" ? "push" : "unshift"](...iterationRes.items);
30
+ limitRemaining -= iterationRes.items.length;
31
+ includesFirst ||= iterationRes.isFirst;
32
+ includesLast ||= iterationRes.isLast;
33
+ cursor = iterationRes.cursor;
34
+ if (["approximate", "at-most"].includes(options.limitPrecision)) break;
35
+ }
36
+ for (let i = 1; i < result.length; i++) {
37
+ if (this._compare(options.orderBy, result[i].item, result[i - 1].item) < 0) {
38
+ throw new StackAssertionError("Paginated list result is not sorted; something is wrong with the implementation", {
39
+ i,
40
+ options,
41
+ result
42
+ });
43
+ }
44
+ }
45
+ if (["exact", "at-most"].includes(options.limitPrecision) && result.length > options.limit) {
46
+ if (type === "next") {
47
+ result = result.slice(0, options.limit);
48
+ includesLast = false;
49
+ if (options.limit > 0) cursor = result[result.length - 1].itemCursor;
50
+ } else {
51
+ result = result.slice(result.length - options.limit);
52
+ includesFirst = false;
53
+ if (options.limit > 0) cursor = result[0].itemCursor;
54
+ }
55
+ }
56
+ return { items: result, isFirst: includesFirst, isLast: includesLast, cursor };
57
+ }
58
+ async next({ after, ...rest }) {
59
+ return await this.nextOrPrev("next", {
60
+ ...rest,
61
+ cursor: after
62
+ });
63
+ }
64
+ async prev({ before, ...rest }) {
65
+ return await this.nextOrPrev("prev", {
66
+ ...rest,
67
+ cursor: before
68
+ });
69
+ }
70
+ // Utility methods below
71
+ flatMap(options) {
72
+ const that = this;
73
+ class FlatMapPaginatedList extends _PaginatedList {
74
+ _getFirstCursor() {
75
+ return options.newCursorFromOldCursor(that.getFirstCursor());
76
+ }
77
+ _getLastCursor() {
78
+ return options.newCursorFromOldCursor(that.getLastCursor());
79
+ }
80
+ _compare(orderBy, a, b) {
81
+ return options.compare(orderBy, a, b);
82
+ }
83
+ async _nextOrPrev(type, { limit, filter, orderBy, cursor }) {
84
+ const estimatedItems = options.estimateItemsToFetch({ limit, filter, orderBy });
85
+ const original = await that.nextOrPrev(type, {
86
+ limit: estimatedItems,
87
+ limitPrecision: "approximate",
88
+ cursor: options.oldCursorFromNewCursor(cursor),
89
+ filter: options.oldFilterFromNewFilter(filter),
90
+ orderBy: options.oldOrderByFromNewOrderBy(orderBy)
91
+ });
92
+ const mapped = original.items.flatMap((itemEntry) => options.itemMapper(
93
+ itemEntry,
94
+ filter,
95
+ orderBy
96
+ ));
97
+ return {
98
+ items: mapped,
99
+ isFirst: original.isFirst,
100
+ isLast: original.isLast,
101
+ cursor: options.newCursorFromOldCursor(original.cursor)
102
+ };
103
+ }
104
+ }
105
+ return new FlatMapPaginatedList();
106
+ }
107
+ map(options) {
108
+ return this.flatMap({
109
+ itemMapper: (itemEntry, filter, orderBy) => {
110
+ return [{ item: options.itemMapper(itemEntry.item), itemCursor: itemEntry.itemCursor }];
111
+ },
112
+ compare: (orderBy, a, b) => this.compare(options.oldOrderByFromNewOrderBy(orderBy), options.oldItemFromNewItem(a), options.oldItemFromNewItem(b)),
113
+ newCursorFromOldCursor: (cursor) => cursor,
114
+ oldCursorFromNewCursor: (cursor) => cursor,
115
+ oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),
116
+ oldOrderByFromNewOrderBy: (orderBy) => options.oldOrderByFromNewOrderBy(orderBy),
117
+ estimateItemsToFetch: (options2) => options2.limit
118
+ });
119
+ }
120
+ filter(options) {
121
+ return this.flatMap({
122
+ itemMapper: (itemEntry, filter, orderBy) => options.filter(itemEntry.item, filter) ? [itemEntry] : [],
123
+ compare: (orderBy, a, b) => this.compare(orderBy, a, b),
124
+ newCursorFromOldCursor: (cursor) => cursor,
125
+ oldCursorFromNewCursor: (cursor) => cursor,
126
+ oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),
127
+ oldOrderByFromNewOrderBy: (orderBy) => orderBy,
128
+ estimateItemsToFetch: (o) => options.estimateItemsToFetch(o)
129
+ });
130
+ }
131
+ addFilter(options) {
132
+ return this.filter({
133
+ filter: (item, filter) => options.filter(item, filter),
134
+ oldFilterFromNewFilter: (filter) => filter,
135
+ estimateItemsToFetch: (o) => options.estimateItemsToFetch(o)
136
+ });
137
+ }
138
+ static merge(...lists) {
139
+ class MergePaginatedList extends _PaginatedList {
140
+ _getFirstCursor() {
141
+ return JSON.stringify(lists.map((list) => list.getFirstCursor()));
142
+ }
143
+ _getLastCursor() {
144
+ return JSON.stringify(lists.map((list) => list.getLastCursor()));
145
+ }
146
+ _compare(orderBy, a, b) {
147
+ const listsResults = lists.map((list) => list.compare(orderBy, a, b));
148
+ if (!listsResults.every((result) => result === listsResults[0])) {
149
+ throw new StackAssertionError("Lists have different compare results; make sure that they use the same compare function", { lists, listsResults });
150
+ }
151
+ return listsResults[0];
152
+ }
153
+ async _nextOrPrev(type, { limit, filter, orderBy, cursor }) {
154
+ const cursors = JSON.parse(cursor);
155
+ const fetchedLists = await Promise.all(lists.map(async (list, i) => {
156
+ return await list.nextOrPrev(type, {
157
+ limit,
158
+ filter,
159
+ orderBy,
160
+ cursor: cursors[i],
161
+ limitPrecision: "at-least"
162
+ });
163
+ }));
164
+ const combinedItems = fetchedLists.flatMap((list, i) => list.items.map((itemEntry) => ({ itemEntry, listIndex: i })));
165
+ const sortedItems = [...combinedItems].sort((a, b) => this._compare(orderBy, a.itemEntry.item, b.itemEntry.item));
166
+ const lastCursorForEachList = sortedItems.reduce((acc, item) => {
167
+ acc[item.listIndex] = item.itemEntry.itemCursor;
168
+ return acc;
169
+ }, range(lists.length).map((i) => cursors[i]));
170
+ return {
171
+ items: sortedItems.map((item) => item.itemEntry),
172
+ isFirst: sortedItems.every((item) => item.listIndex === 0),
173
+ isLast: sortedItems.every((item) => item.listIndex === lists.length - 1),
174
+ cursor: JSON.stringify(lastCursorForEachList)
175
+ };
176
+ }
177
+ }
178
+ return new MergePaginatedList();
179
+ }
180
+ static empty() {
181
+ class EmptyPaginatedList extends _PaginatedList {
182
+ _getFirstCursor() {
183
+ return "first";
184
+ }
185
+ _getLastCursor() {
186
+ return "last";
187
+ }
188
+ _compare(orderBy, a, b) {
189
+ return 0;
190
+ }
191
+ async _nextOrPrev(type, options) {
192
+ return { items: [], isFirst: true, isLast: true, cursor: "first" };
193
+ }
194
+ }
195
+ return new EmptyPaginatedList();
196
+ }
197
+ };
198
+ var ArrayPaginatedList = class extends PaginatedList {
199
+ constructor(array) {
200
+ super();
201
+ this.array = array;
202
+ }
203
+ _getFirstCursor() {
204
+ return "0";
205
+ }
206
+ _getLastCursor() {
207
+ return `${this.array.length - 1}`;
208
+ }
209
+ _compare(orderBy, a, b) {
210
+ return orderBy(a, b);
211
+ }
212
+ async _nextOrPrev(type, options) {
213
+ const filteredArray = this.array.filter(options.filter);
214
+ const sortedArray = [...filteredArray].sort((a, b) => this._compare(options.orderBy, a, b));
215
+ const itemEntriesArray = sortedArray.map((item, index) => ({ item, itemCursor: `${index}` }));
216
+ const oldCursor = Number(options.cursor);
217
+ const newCursor = Math.max(0, Math.min(this.array.length - 1, oldCursor + (type === "next" ? 1 : -1) * options.limit));
218
+ return {
219
+ items: itemEntriesArray.slice(Math.min(oldCursor, newCursor), Math.max(oldCursor, newCursor)),
220
+ isFirst: oldCursor === 0 || newCursor === 0,
221
+ isLast: oldCursor === this.array.length - 1 || newCursor === this.array.length - 1,
222
+ cursor: `${newCursor}`
223
+ };
224
+ }
225
+ };
226
+ export {
227
+ ArrayPaginatedList,
228
+ PaginatedList
229
+ };
230
+ //# sourceMappingURL=paginated-lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/paginated-lists.tsx"],"sourcesContent":["import { range } from \"./arrays\";\nimport { StackAssertionError } from \"./errors\";\n\ntype QueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> =\n & {\n filter: Filter,\n orderBy: OrderBy,\n limit: number,\n /**\n * Whether the limit should be treated as an exact value, or an approximate value.\n *\n * If set to 'exact', less items will only be returned if the list item is the first or last item.\n *\n * If set to 'at-least' or 'approximate', the implementation may decide to return more items than the limit requested if doing so comes at no (or negligible) extra cost.\n *\n * If set to 'at-most' or 'approximate', the implementation may decide to return less items than the limit requested if requesting more items would come at a non-negligible extra cost. In this case, if limit > 0, the implementation must still make progress towards the end of the list and the returned cursor must be different from the one passed in.\n *\n * Defaults to 'exact'.\n */\n limitPrecision: 'exact' | 'at-least' | 'at-most' | 'approximate',\n }\n & ([Type] extends [never] ? unknown\n : [Type] extends ['next'] ? { after: Cursor }\n : [Type] extends ['prev'] ? { before: Cursor }\n : { cursor: Cursor });\n\ntype ImplQueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> = QueryOptions<Type, Cursor, Filter, OrderBy> & { limitPrecision: 'approximate' }\n\ntype QueryResult<Item, Cursor> = { items: { item: Item, itemCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\ntype ImplQueryResult<Item, Cursor> = { items: { item: Item, itemCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\nexport abstract class PaginatedList<\n Item,\n Cursor extends string,\n Filter extends unknown,\n OrderBy extends unknown,\n> {\n // Abstract methods\n\n protected abstract _getFirstCursor(): Cursor;\n protected abstract _getLastCursor(): Cursor;\n protected abstract _compare(orderBy: OrderBy, a: Item, b: Item): number;\n protected abstract _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<ImplQueryResult<Item, Cursor>>;\n\n // Implementations\n public getFirstCursor(): Cursor { return this._getFirstCursor(); }\n public getLastCursor(): Cursor { return this._getLastCursor(); }\n public compare(orderBy: OrderBy, a: Item, b: Item): number { return this._compare(orderBy, a, b); }\n\n async nextOrPrev(type: 'next' | 'prev', options: QueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n let result: { item: Item, itemCursor: Cursor }[] = [];\n let includesFirst = false;\n let includesLast = false;\n let cursor = options.cursor;\n let limitRemaining = options.limit;\n while (limitRemaining > 0 || (type === \"next\" && includesLast) || (type === \"prev\" && includesFirst)) {\n const iterationRes = await this._nextOrPrev(type, {\n cursor,\n limit: options.limit,\n limitPrecision: \"approximate\",\n filter: options.filter,\n orderBy: options.orderBy,\n });\n result[type === \"next\" ? \"push\" : \"unshift\"](...iterationRes.items);\n limitRemaining -= iterationRes.items.length;\n includesFirst ||= iterationRes.isFirst;\n includesLast ||= iterationRes.isLast;\n cursor = iterationRes.cursor;\n if ([\"approximate\", \"at-most\"].includes(options.limitPrecision)) break;\n }\n\n // Assert that the result is sorted\n for (let i = 1; i < result.length; i++) {\n if (this._compare(options.orderBy, result[i].item, result[i - 1].item) < 0) {\n throw new StackAssertionError(\"Paginated list result is not sorted; something is wrong with the implementation\", {\n i,\n options,\n result,\n });\n }\n }\n\n if ([\"exact\", \"at-most\"].includes(options.limitPrecision) && result.length > options.limit) {\n if (type === \"next\") {\n result = result.slice(0, options.limit);\n includesLast = false;\n if (options.limit > 0) cursor = result[result.length - 1].itemCursor;\n } else {\n result = result.slice(result.length - options.limit);\n includesFirst = false;\n if (options.limit > 0) cursor = result[0].itemCursor;\n }\n }\n return { items: result, isFirst: includesFirst, isLast: includesLast, cursor };\n }\n public async next({ after, ...rest }: QueryOptions<'next', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"next\", {\n ...rest,\n cursor: after,\n });\n }\n public async prev({ before, ...rest }: QueryOptions<'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"prev\", {\n ...rest,\n cursor: before,\n });\n }\n\n // Utility methods below\n\n flatMap<Item2, Cursor2 extends string, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (itemEntry: { item: Item, itemCursor: Cursor }, filter: Filter2, orderBy: OrderBy2) => { item: Item2, itemCursor: Cursor2 }[],\n compare: (orderBy: OrderBy2, a: Item2, b: Item2) => number,\n newCursorFromOldCursor: (cursor: Cursor) => Cursor2,\n oldCursorFromNewCursor: (cursor: Cursor2) => Cursor,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy2, limit: number }) => number,\n }): PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n const that = this;\n class FlatMapPaginatedList extends PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n override _getFirstCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getFirstCursor()); }\n override _getLastCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getLastCursor()); }\n\n override _compare(orderBy: OrderBy2, a: Item2, b: Item2): number {\n return options.compare(orderBy, a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', Cursor2, Filter2, OrderBy2>) {\n const estimatedItems = options.estimateItemsToFetch({ limit, filter, orderBy });\n const original = await that.nextOrPrev(type, {\n limit: estimatedItems,\n limitPrecision: \"approximate\",\n cursor: options.oldCursorFromNewCursor(cursor),\n filter: options.oldFilterFromNewFilter(filter),\n orderBy: options.oldOrderByFromNewOrderBy(orderBy),\n });\n const mapped = original.items.flatMap(itemEntry => options.itemMapper(\n itemEntry,\n filter,\n orderBy,\n ));\n return {\n items: mapped,\n isFirst: original.isFirst,\n isLast: original.isLast,\n cursor: options.newCursorFromOldCursor(original.cursor),\n };\n }\n }\n return new FlatMapPaginatedList();\n }\n\n map<Item2, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (item: Item) => Item2,\n oldItemFromNewItem: (item: Item2) => Item,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n }): PaginatedList<Item2, Cursor, Filter2, OrderBy2> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => {\n return [{ item: options.itemMapper(itemEntry.item), itemCursor: itemEntry.itemCursor }];\n },\n compare: (orderBy, a, b) => this.compare(options.oldOrderByFromNewOrderBy(orderBy), options.oldItemFromNewItem(a), options.oldItemFromNewItem(b)),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => options.oldOrderByFromNewOrderBy(orderBy),\n estimateItemsToFetch: (options) => options.limit,\n });\n }\n\n filter<Filter2 extends unknown>(options: {\n filter: (item: Item, filter: Filter2) => boolean,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter2, OrderBy> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => (options.filter(itemEntry.item, filter) ? [itemEntry] : []),\n compare: (orderBy, a, b) => this.compare(orderBy, a, b),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => orderBy,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n addFilter<AddedFilter extends unknown>(options: {\n filter: (item: Item, filter: Filter & AddedFilter) => boolean,\n estimateItemsToFetch: (options: { filter: Filter & AddedFilter, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter & AddedFilter, OrderBy> {\n return this.filter({\n filter: (item, filter) => options.filter(item, filter),\n oldFilterFromNewFilter: (filter) => filter,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n static merge<\n Item,\n Filter extends unknown,\n OrderBy extends unknown,\n >(\n ...lists: PaginatedList<Item, any, Filter, OrderBy>[]\n ): PaginatedList<Item, string, Filter, OrderBy> {\n class MergePaginatedList extends PaginatedList<Item, string, Filter, OrderBy> {\n override _getFirstCursor() { return JSON.stringify(lists.map(list => list.getFirstCursor())); }\n override _getLastCursor() { return JSON.stringify(lists.map(list => list.getLastCursor())); }\n override _compare(orderBy: OrderBy, a: Item, b: Item): number {\n const listsResults = lists.map(list => list.compare(orderBy, a, b));\n if (!listsResults.every(result => result === listsResults[0])) {\n throw new StackAssertionError(\"Lists have different compare results; make sure that they use the same compare function\", { lists, listsResults });\n }\n return listsResults[0];\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', \"first\" | \"last\" | `[${string}]`, Filter, OrderBy>) {\n const cursors = JSON.parse(cursor);\n const fetchedLists = await Promise.all(lists.map(async (list, i) => {\n return await list.nextOrPrev(type, {\n limit,\n filter,\n orderBy,\n cursor: cursors[i],\n limitPrecision: \"at-least\",\n });\n }));\n const combinedItems = fetchedLists.flatMap((list, i) => list.items.map((itemEntry) => ({ itemEntry, listIndex: i })));\n const sortedItems = [...combinedItems].sort((a, b) => this._compare(orderBy, a.itemEntry.item, b.itemEntry.item));\n const lastCursorForEachList = sortedItems.reduce((acc, item) => {\n acc[item.listIndex] = item.itemEntry.itemCursor;\n return acc;\n }, range(lists.length).map((i) => cursors[i]));\n return {\n items: sortedItems.map((item) => item.itemEntry),\n isFirst: sortedItems.every((item) => item.listIndex === 0),\n isLast: sortedItems.every((item) => item.listIndex === lists.length - 1),\n cursor: JSON.stringify(lastCursorForEachList),\n };\n }\n }\n return new MergePaginatedList();\n }\n\n static empty() {\n class EmptyPaginatedList extends PaginatedList<never, \"first\" | \"last\", any, any> {\n override _getFirstCursor() { return \"first\" as const; }\n override _getLastCursor() { return \"last\" as const; }\n override _compare(orderBy: any, a: any, b: any): number {\n return 0;\n }\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', string, any, any>) {\n return { items: [], isFirst: true, isLast: true, cursor: \"first\" as const };\n }\n }\n return new EmptyPaginatedList();\n }\n}\n\nexport class ArrayPaginatedList<Item> extends PaginatedList<Item, `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number> {\n constructor(private readonly array: Item[]) {\n super();\n }\n\n override _getFirstCursor() { return \"0\" as const; }\n override _getLastCursor() { return `${this.array.length - 1}` as const; }\n override _compare(orderBy: (a: Item, b: Item) => number, a: Item, b: Item): number {\n return orderBy(a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number>) {\n const filteredArray = this.array.filter(options.filter);\n const sortedArray = [...filteredArray].sort((a, b) => this._compare(options.orderBy, a, b));\n const itemEntriesArray = sortedArray.map((item, index) => ({ item, itemCursor: `${index}` as const }));\n const oldCursor = Number(options.cursor);\n const newCursor = Math.max(0, Math.min(this.array.length - 1, oldCursor + (type === \"next\" ? 1 : -1) * options.limit));\n return {\n items: itemEntriesArray.slice(Math.min(oldCursor, newCursor), Math.max(oldCursor, newCursor)),\n isFirst: oldCursor === 0 || newCursor === 0,\n isLast: oldCursor === this.array.length - 1 || newCursor === this.array.length - 1,\n cursor: `${newCursor}` as const,\n };\n }\n}\n"],"mappings":"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","names":["options"]}
@@ -148,7 +148,8 @@ var TimeoutError = class extends Error {
148
148
  this.name = "TimeoutError";
149
149
  }
150
150
  };
151
- async function timeout(promise, ms) {
151
+ async function timeout(promiseOrFunc, ms) {
152
+ const promise = typeof promiseOrFunc === "function" ? promiseOrFunc() : promiseOrFunc;
152
153
  return await Promise.race([
153
154
  promise.then((value) => Result.ok(value)),
154
155
  wait(ms).then(() => Result.error(new TimeoutError(ms)))