@gaganref/confect-test 10.3.1-fork.9 → 10.3.1-fork.90

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/TestConfect.d.ts +37 -11
  2. package/dist/TestConfect.d.ts.map +1 -1
  3. package/dist/TestConfect.js +22 -7
  4. package/dist/TestConfect.js.map +1 -1
  5. package/dist/internal/Attempt.d.ts +77 -0
  6. package/dist/internal/Attempt.d.ts.map +1 -0
  7. package/dist/internal/Attempt.js +154 -0
  8. package/dist/internal/Attempt.js.map +1 -0
  9. package/dist/internal/Clock.d.ts +29 -0
  10. package/dist/internal/Clock.d.ts.map +1 -0
  11. package/dist/internal/Clock.js +66 -0
  12. package/dist/internal/Clock.js.map +1 -0
  13. package/dist/internal/ConvexTest.d.ts +66 -0
  14. package/dist/internal/ConvexTest.d.ts.map +1 -0
  15. package/dist/internal/ConvexTest.js +62 -0
  16. package/dist/internal/ConvexTest.js.map +1 -0
  17. package/dist/internal/Execution.d.ts +247 -0
  18. package/dist/internal/Execution.d.ts.map +1 -0
  19. package/dist/internal/Execution.js +1024 -0
  20. package/dist/internal/Execution.js.map +1 -0
  21. package/dist/internal/Globals.d.ts +11 -0
  22. package/dist/internal/Globals.d.ts.map +1 -0
  23. package/dist/internal/Globals.js +88 -0
  24. package/dist/internal/Globals.js.map +1 -0
  25. package/dist/internal/Invocation.d.ts +68 -0
  26. package/dist/internal/Invocation.d.ts.map +1 -0
  27. package/dist/internal/Invocation.js +158 -0
  28. package/dist/internal/Invocation.js.map +1 -0
  29. package/dist/internal/Kernel.d.ts +95 -0
  30. package/dist/internal/Kernel.d.ts.map +1 -0
  31. package/dist/internal/Kernel.js +91 -0
  32. package/dist/internal/Kernel.js.map +1 -0
  33. package/dist/internal/KernelQuery.d.ts +55 -0
  34. package/dist/internal/KernelQuery.d.ts.map +1 -0
  35. package/dist/internal/KernelQuery.js +205 -0
  36. package/dist/internal/KernelQuery.js.map +1 -0
  37. package/dist/internal/KernelRefusal.d.ts +102 -0
  38. package/dist/internal/KernelRefusal.d.ts.map +1 -0
  39. package/dist/internal/KernelRefusal.js +94 -0
  40. package/dist/internal/KernelRefusal.js.map +1 -0
  41. package/dist/internal/KernelValues.d.ts +138 -0
  42. package/dist/internal/KernelValues.d.ts.map +1 -0
  43. package/dist/internal/KernelValues.js +281 -0
  44. package/dist/internal/KernelValues.js.map +1 -0
  45. package/dist/internal/MountedHttp.d.ts +1 -1
  46. package/dist/internal/MountedHttp.d.ts.map +1 -1
  47. package/dist/internal/MountedHttp.js +3 -3
  48. package/dist/internal/MountedHttp.js.map +1 -1
  49. package/dist/internal/ReadSet.d.ts +55 -0
  50. package/dist/internal/ReadSet.d.ts.map +1 -0
  51. package/dist/internal/ReadSet.js +29 -0
  52. package/dist/internal/ReadSet.js.map +1 -0
  53. package/dist/internal/Scheduling.d.ts +71 -0
  54. package/dist/internal/Scheduling.d.ts.map +1 -0
  55. package/dist/internal/Scheduling.js +60 -0
  56. package/dist/internal/Scheduling.js.map +1 -0
  57. package/package.json +5 -5
  58. package/src/TestConfect.ts +84 -19
  59. package/src/internal/Attempt.ts +185 -0
  60. package/src/internal/Clock.ts +95 -0
  61. package/src/internal/ConvexTest.ts +163 -0
  62. package/src/internal/Execution.ts +1795 -0
  63. package/src/internal/Globals.ts +113 -0
  64. package/src/internal/Invocation.ts +316 -0
  65. package/src/internal/Kernel.ts +190 -0
  66. package/src/internal/KernelQuery.ts +405 -0
  67. package/src/internal/KernelRefusal.ts +112 -0
  68. package/src/internal/KernelValues.ts +765 -0
  69. package/src/internal/MountedHttp.ts +13 -6
  70. package/src/internal/ReadSet.ts +108 -0
  71. package/src/internal/Scheduling.ts +126 -0
@@ -1 +1 @@
1
- {"version":3,"file":"MountedHttp.js","names":[],"sources":["../../src/internal/MountedHttp.ts"],"sourcesContent":["/* oxlint-disable effecttsgo/async-function -- This convex-test adapter uses Promise callbacks and temporarily overrides the real process environment consumed by HTTP module construction. */\nimport type { TestConvexForDataModel } from \"convex-test\";\nimport type {\n GenericDataModel,\n HttpRouter,\n FunctionReference,\n RoutableMethod,\n PublicHttpAction,\n} from \"convex/server\";\nimport { actionGeneric } from \"convex/server\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport type { HttpMount, RegisterComponent } from \"../TestConfect.js\";\n\ndeclare const process: { env: Record<string, string | undefined> };\n\nclass InvalidHttpMount extends Schema.TaggedError<InvalidHttpMount>()(\n \"InvalidHttpMount\",\n {\n componentPath: Schema.String,\n httpPrefix: Schema.String,\n message: Schema.String,\n },\n) {}\n\nclass ComponentHttpError extends Schema.TaggedError<ComponentHttpError>()(\n \"ComponentHttpError\",\n { componentPath: Schema.String, message: Schema.String },\n) {}\n\nclass InvalidFetchPath extends Schema.TaggedError<InvalidFetchPath>()(\n \"InvalidFetchPath\",\n { path: Schema.String, message: Schema.String },\n) {}\n\ntype NormalizedMount = {\n readonly componentPath: string;\n readonly prefix: string;\n};\n\nconst normalizeMounts = (mounts: ReadonlyArray<HttpMount>) => {\n const normalized: Array<NormalizedMount> = [];\n for (const mount of mounts) {\n if (!mount.httpPrefix.startsWith(\"/\") || /[?#]/.test(mount.httpPrefix)) {\n return Result.fail(\n new InvalidHttpMount({\n ...mount,\n message:\n \"httpPrefix must be an absolute path without a query or fragment.\",\n }),\n );\n }\n const prefix = mount.httpPrefix.replace(/\\/+$/, \"\");\n if (normalized.some((previous) => previous.prefix === prefix)) {\n return Result.fail(\n new InvalidHttpMount({\n ...mount,\n message: \"Each httpPrefix must select one component installation.\",\n }),\n );\n }\n normalized.push({ componentPath: mount.componentPath, prefix });\n }\n return Result.succeed(\n normalized.sort((a, b) => b.prefix.length - a.prefix.length),\n );\n};\n\ntype HttpRequest = {\n siteUrl: string;\n url: string;\n pathname: string;\n method: string;\n headers: Array<[string, string]>;\n body: ArrayBuffer | null;\n};\n\ntype HttpResponse = {\n status: number;\n statusText: string;\n headers: Array<[string, string]>;\n body: ArrayBuffer;\n};\n\n// Convex omits this runtime entry point from its published declarations.\nconst isInvokable = (\n action: PublicHttpAction,\n): action is PublicHttpAction & {\n invokeHttpAction(request: Request): Promise<Response>;\n} =>\n \"invokeHttpAction\" in action && typeof action.invokeHttpAction === \"function\";\n\nconst bridgeModule = \"__confect_test_http\";\nconst defaultSiteUrl = \"https://some.convex.site\";\n\nexport const make = (\n mounts: ReadonlyArray<HttpMount>,\n registerComponent: RegisterComponent,\n) => {\n const normalized = normalizeMounts(mounts);\n if (Result.isFailure(normalized)) throw normalized.failure;\n const registered = new Set<string>();\n let tail = Promise.resolve();\n\n const register: RegisterComponent = (componentPath, schema, modules) => {\n if (\n !normalized.success.some((mount) => mount.componentPath === componentPath)\n ) {\n registerComponent(componentPath, schema, modules);\n return;\n }\n const generated = Object.keys(modules).find((key) =>\n key.includes(\"_generated\"),\n );\n const root = generated?.split(\"_generated\", 2)[0];\n const moduleLoaders = Object.fromEntries(\n Object.entries(modules).map(([path, load]) => [\n path.replace(/\\.[^.]+$/, \"\"),\n load,\n ]),\n );\n const loadHttp =\n root === undefined ? undefined : moduleLoaders[`${root}http`];\n if (root === undefined || loadHttp === undefined) {\n throw new ComponentHttpError({\n componentPath,\n message:\n \"Mounted components need an HTTP module and Convex _generated modules.\",\n });\n }\n const bridgePath = `${root}${bridgeModule}.ts`;\n if (`${root}${bridgeModule}` in moduleLoaders) {\n throw new ComponentHttpError({\n componentPath,\n message: `${bridgeModule} is reserved for mounted HTTP tests.`,\n });\n }\n const routers = new Map<string, Promise<HttpRouter>>();\n const invoke = actionGeneric({\n handler: async (_ctx, args: HttpRequest): Promise<HttpResponse> => {\n const previousSiteUrl = process.env.CONVEX_SITE_URL;\n try {\n process.env.CONVEX_SITE_URL = args.siteUrl;\n let router = routers.get(args.siteUrl);\n if (router === undefined) {\n router = loadHttp().then(\n (module: { default: HttpRouter }) => module.default,\n );\n routers.set(args.siteUrl, router);\n }\n const route = (await router).lookup(\n args.pathname,\n args.method as RoutableMethod,\n );\n let response: Response;\n if (route === null) {\n response = new Response(\n `No HttpAction routed for ${args.pathname}`,\n { status: 404 },\n );\n } else {\n const action = route[0];\n if (!isInvokable(action)) {\n throw new ComponentHttpError({\n componentPath,\n message:\n \"This Convex version does not expose invokeHttpAction for HTTP tests.\",\n });\n }\n // Convex selects on the relative path and passes the full URL to the handler.\n response = await action.invokeHttpAction(\n new Request(args.url, {\n method: args.method,\n headers: args.headers,\n ...(args.body === null ? {} : { body: args.body }),\n }),\n );\n }\n return {\n status: response.status,\n statusText: response.statusText,\n headers: [...response.headers],\n body: await response.arrayBuffer(),\n };\n } finally {\n if (previousSiteUrl === undefined) delete process.env.CONVEX_SITE_URL;\n else process.env.CONVEX_SITE_URL = previousSiteUrl;\n }\n },\n });\n registerComponent(componentPath, schema, {\n ...modules,\n [bridgePath]: () => Promise.resolve({ invoke }),\n });\n registered.add(componentPath);\n };\n\n const fetch = <DataModel extends GenericDataModel>(\n accessor: TestConvexForDataModel<DataModel>,\n path: string,\n init?: RequestInit,\n ): Promise<Response> => {\n if (normalized.success.length === 0) return accessor.fetch(path, init);\n const run = async () => {\n if (!path.startsWith(\"/\")) {\n throw new InvalidFetchPath({\n path,\n message: 'Path given to fetch must start with a \"/\".',\n });\n }\n const pathname = new URL(`${defaultSiteUrl}${path}`).pathname;\n const mount = normalized.success.find(\n ({ prefix }) =>\n pathname === prefix || pathname.startsWith(`${prefix}/`),\n );\n if (mount === undefined) return accessor.fetch(path, init);\n if (!registered.has(mount.componentPath)) {\n throw new ComponentHttpError({\n componentPath: mount.componentPath,\n message: \"Register the mounted component before calling fetch.\",\n });\n }\n const appSiteUrl = (\n process.env.CONVEX_SITE_URL || defaultSiteUrl\n ).replace(/\\/$/, \"\");\n const request = new Request(new URL(`${appSiteUrl}${path}`), init);\n // convex-test encodes exact component paths in test-only function handles.\n const reference =\n `function://${mount.componentPath};${bridgeModule}:invoke` as unknown as FunctionReference<\n \"action\",\n \"public\",\n HttpRequest,\n HttpResponse\n >;\n const response = await accessor.action(reference, {\n siteUrl: `${appSiteUrl}${mount.prefix}`,\n url: request.url,\n pathname: pathname.slice(mount.prefix.length) || \"/\",\n method: request.method,\n headers: [...request.headers],\n body:\n request.method === \"GET\" || request.method === \"HEAD\"\n ? null\n : await request.arrayBuffer(),\n });\n return new Response(\n request.method === \"HEAD\" || [204, 205, 304].includes(response.status)\n ? null\n : response.body,\n {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n },\n );\n };\n const result = tail.then(run);\n tail = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n return { register, fetch };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"MountedHttp.js","names":[],"sources":["../../src/internal/MountedHttp.ts"],"sourcesContent":["/* oxlint-disable effecttsgo/async-function -- This convex-test adapter uses Promise callbacks and temporarily overrides the real process environment consumed by HTTP module construction. */\nimport type { TestConvexForDataModel } from \"convex-test\";\nimport type {\n GenericDataModel,\n HttpRouter,\n FunctionReference,\n RoutableMethod,\n PublicHttpAction,\n} from \"convex/server\";\nimport { actionGeneric } from \"convex/server\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport type { HttpMount, RegisterComponent } from \"../TestConfect.js\";\n\ndeclare const process: { env: Record<string, string | undefined> };\n\nclass InvalidHttpMount extends Schema.TaggedError<InvalidHttpMount>()(\n \"InvalidHttpMount\",\n {\n componentPath: Schema.String,\n httpPrefix: Schema.String,\n message: Schema.String,\n },\n) {}\n\nclass ComponentHttpError extends Schema.TaggedError<ComponentHttpError>()(\n \"ComponentHttpError\",\n { componentPath: Schema.String, message: Schema.String },\n) {}\n\nclass InvalidFetchPath extends Schema.TaggedError<InvalidFetchPath>()(\n \"InvalidFetchPath\",\n { path: Schema.String, message: Schema.String },\n) {}\n\ntype NormalizedMount = {\n readonly componentPath: string;\n readonly prefix: string;\n};\n\nconst normalizeMounts = (mounts: ReadonlyArray<HttpMount>) => {\n const normalized: Array<NormalizedMount> = [];\n for (const mount of mounts) {\n if (!mount.httpPrefix.startsWith(\"/\") || /[?#]/.test(mount.httpPrefix)) {\n return Result.fail(\n new InvalidHttpMount({\n ...mount,\n message:\n \"httpPrefix must be an absolute path without a query or fragment.\",\n }),\n );\n }\n const prefix = mount.httpPrefix.replace(/\\/+$/, \"\");\n if (normalized.some((previous) => previous.prefix === prefix)) {\n return Result.fail(\n new InvalidHttpMount({\n ...mount,\n message: \"Each httpPrefix must select one component installation.\",\n }),\n );\n }\n normalized.push({ componentPath: mount.componentPath, prefix });\n }\n return Result.succeed(\n normalized.sort((a, b) => b.prefix.length - a.prefix.length),\n );\n};\n\ntype HttpRequest = {\n siteUrl: string;\n url: string;\n pathname: string;\n method: string;\n headers: Array<[string, string]>;\n body: ArrayBuffer | null;\n};\n\ntype HttpResponse = {\n status: number;\n statusText: string;\n headers: Array<[string, string]>;\n body: ArrayBuffer;\n};\n\n// Convex omits this runtime entry point from its published declarations.\nconst isInvokable = (\n action: PublicHttpAction,\n): action is PublicHttpAction & {\n invokeHttpAction(request: Request): Promise<Response>;\n} =>\n \"invokeHttpAction\" in action && typeof action.invokeHttpAction === \"function\";\n\nconst bridgeModule = \"__confect_test_http\";\nconst defaultSiteUrl = \"https://some.convex.site\";\n\nexport const make = (\n mounts: ReadonlyArray<HttpMount>,\n registerComponent: RegisterComponent,\n) => {\n const normalized = normalizeMounts(mounts);\n if (Result.isFailure(normalized)) throw normalized.failure;\n const registered = new Set<string>();\n let tail = Promise.resolve();\n\n const register: RegisterComponent = (\n componentPath,\n schema,\n modules,\n options,\n ) => {\n if (\n !normalized.success.some((mount) => mount.componentPath === componentPath)\n ) {\n registerComponent(componentPath, schema, modules, options);\n return;\n }\n const generated = Object.keys(modules).find((key) =>\n key.includes(\"_generated\"),\n );\n const root = generated?.split(\"_generated\", 2)[0];\n const moduleLoaders = Object.fromEntries(\n Object.entries(modules).map(([path, load]) => [\n path.replace(/\\.[^.]+$/, \"\"),\n load,\n ]),\n );\n const loadHttp =\n root === undefined ? undefined : moduleLoaders[`${root}http`];\n if (root === undefined || loadHttp === undefined) {\n throw new ComponentHttpError({\n componentPath,\n message:\n \"Mounted components need an HTTP module and Convex _generated modules.\",\n });\n }\n const bridgePath = `${root}${bridgeModule}.ts`;\n if (`${root}${bridgeModule}` in moduleLoaders) {\n throw new ComponentHttpError({\n componentPath,\n message: `${bridgeModule} is reserved for mounted HTTP tests.`,\n });\n }\n const routers = new Map<string, Promise<HttpRouter>>();\n const invoke = actionGeneric({\n handler: async (_ctx, args: HttpRequest): Promise<HttpResponse> => {\n const previousSiteUrl = process.env.CONVEX_SITE_URL;\n try {\n process.env.CONVEX_SITE_URL = args.siteUrl;\n let router = routers.get(args.siteUrl);\n if (router === undefined) {\n router = loadHttp().then(\n (module: { default: HttpRouter }) => module.default,\n );\n routers.set(args.siteUrl, router);\n }\n const route = (await router).lookup(\n args.pathname,\n args.method as RoutableMethod,\n );\n let response: Response;\n if (route === null) {\n response = new Response(\n `No HttpAction routed for ${args.pathname}`,\n { status: 404 },\n );\n } else {\n const action = route[0];\n if (!isInvokable(action)) {\n throw new ComponentHttpError({\n componentPath,\n message:\n \"This Convex version does not expose invokeHttpAction for HTTP tests.\",\n });\n }\n // Convex selects on the relative path and passes the full URL to the handler.\n response = await action.invokeHttpAction(\n new Request(args.url, {\n method: args.method,\n headers: args.headers,\n ...(args.body === null ? {} : { body: args.body }),\n }),\n );\n }\n return {\n status: response.status,\n statusText: response.statusText,\n headers: [...response.headers],\n body: await response.arrayBuffer(),\n };\n } finally {\n if (previousSiteUrl === undefined) delete process.env.CONVEX_SITE_URL;\n else process.env.CONVEX_SITE_URL = previousSiteUrl;\n }\n },\n });\n registerComponent(\n componentPath,\n schema,\n { ...modules, [bridgePath]: () => Promise.resolve({ invoke }) },\n options,\n );\n registered.add(componentPath);\n };\n\n const fetch = <DataModel extends GenericDataModel>(\n accessor: TestConvexForDataModel<DataModel>,\n path: string,\n init?: RequestInit,\n ): Promise<Response> => {\n if (normalized.success.length === 0) return accessor.fetch(path, init);\n const run = async () => {\n if (!path.startsWith(\"/\")) {\n throw new InvalidFetchPath({\n path,\n message: 'Path given to fetch must start with a \"/\".',\n });\n }\n const pathname = new URL(`${defaultSiteUrl}${path}`).pathname;\n const mount = normalized.success.find(\n ({ prefix }) =>\n pathname === prefix || pathname.startsWith(`${prefix}/`),\n );\n if (mount === undefined) return accessor.fetch(path, init);\n if (!registered.has(mount.componentPath)) {\n throw new ComponentHttpError({\n componentPath: mount.componentPath,\n message: \"Register the mounted component before calling fetch.\",\n });\n }\n const appSiteUrl = (\n process.env.CONVEX_SITE_URL || defaultSiteUrl\n ).replace(/\\/$/, \"\");\n const request = new Request(new URL(`${appSiteUrl}${path}`), init);\n // convex-test encodes exact component paths in test-only function handles.\n const reference =\n `function://${mount.componentPath};${bridgeModule}:invoke` as unknown as FunctionReference<\n \"action\",\n \"public\",\n HttpRequest,\n HttpResponse\n >;\n const response = await accessor.action(reference, {\n siteUrl: `${appSiteUrl}${mount.prefix}`,\n url: request.url,\n pathname: pathname.slice(mount.prefix.length) || \"/\",\n method: request.method,\n headers: [...request.headers],\n body:\n request.method === \"GET\" || request.method === \"HEAD\"\n ? null\n : await request.arrayBuffer(),\n });\n return new Response(\n request.method === \"HEAD\" || [204, 205, 304].includes(response.status)\n ? null\n : response.body,\n {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n },\n );\n };\n const result = tail.then(run);\n tail = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n return { register, fetch };\n};\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type Value } from "convex/values";
2
+ import type { DocumentId, SerializedRangeExpression, StoredDocument } from "./KernelValues.js";
3
+ /**
4
+ * A row's position in an interval's order: its value at each of the interval's
5
+ * fields.
6
+ */
7
+ export type Key = ReadonlyArray<Value | undefined>;
8
+ /**
9
+ * A read of one document by ID. It records the document's absence as well as
10
+ * its presence, so a commit that creates the document invalidates it.
11
+ */
12
+ export interface DocumentRead {
13
+ readonly kind: "document";
14
+ readonly componentPath: string;
15
+ readonly id: DocumentId;
16
+ }
17
+ /**
18
+ * A read of an ordered interval of one table.
19
+ *
20
+ * `fields` is the sort key: an index's fields, then the tie-breakers
21
+ * `_creationTime` and `_id`. `range` is the index range expression, whose `Gt`
22
+ * and `Lt` bounds are exclusive and `Gte` and `Lte` inclusive. `after` is an
23
+ * exclusive start in `order`, a page's cursor. `through` is how far the query
24
+ * examined, inclusive: nothing yet, up to a key, or to the end of the range. An
25
+ * empty result reads to the end, so it still covers an insert into the range.
26
+ *
27
+ * Only the attempt that owns the read advances `through`.
28
+ */
29
+ export interface IntervalRead {
30
+ readonly kind: "interval";
31
+ readonly componentPath: string;
32
+ readonly table: string;
33
+ readonly fields: ReadonlyArray<string>;
34
+ readonly order: "asc" | "desc";
35
+ readonly range: ReadonlyArray<SerializedRangeExpression>;
36
+ readonly after: Key | null;
37
+ through: Key | "end" | null;
38
+ }
39
+ export type Read = DocumentRead | IntervalRead;
40
+ /**
41
+ * Where `doc` sorts against `key` in the interval's order.
42
+ */
43
+ export declare const compareToKey: (fields: ReadonlyArray<string>, order: "asc" | "desc", doc: StoredDocument, key: Key) => number;
44
+ export declare const keyOf: (fields: ReadonlyArray<string>, doc: StoredDocument) => Key;
45
+ /**
46
+ * Whether a commit's change to one document invalidates `read`: it wrote the
47
+ * document read by ID, or the document entered or left the interval.
48
+ */
49
+ export declare const covers: (read: Read, change: {
50
+ readonly componentPath: string;
51
+ readonly id: DocumentId;
52
+ readonly before: StoredDocument | null;
53
+ readonly after: StoredDocument | null;
54
+ }) => boolean;
55
+ //# sourceMappingURL=ReadSet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadSet.d.ts","sourceRoot":"","sources":["../../src/internal/ReadSet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,cAAc,EACf,MAAM,mBAAmB,CAAC;AAO3B;;;GAGG;AACH,MAAM,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,yBAAyB,CAAC,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,MAAM,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,YAAY,WACf,aAAa,CAAC,MAAM,CAAC,SACtB,KAAK,GAAG,MAAM,OAChB,cAAc,OACd,GAAG,KACP,MAUF,CAAC;AAEF,eAAO,MAAM,KAAK,WACR,aAAa,CAAC,MAAM,CAAC,OACxB,cAAc,KAClB,GAA2D,CAAC;AAU/D;;;GAGG;AACH,eAAO,MAAM,MAAM,SACX,IAAI,UACF;IACN,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC,KACA,OASF,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { evaluateFieldPath, evaluateRangeFilter, tableNameFromId } from "./KernelValues.js";
2
+ import { compareValues } from "convex/values";
3
+ //#region src/internal/ReadSet.ts
4
+ /**
5
+ * Where `doc` sorts against `key` in the interval's order.
6
+ */
7
+ const compareToKey = (fields, order, doc, key) => {
8
+ const direction = order === "asc" ? 1 : -1;
9
+ for (let i = 0; i < fields.length; i++) {
10
+ const cmp = compareValues(evaluateFieldPath(fields[i], doc), key[i]);
11
+ if (cmp !== 0) return cmp * direction;
12
+ }
13
+ return 0;
14
+ };
15
+ const keyOf = (fields, doc) => fields.map((field) => evaluateFieldPath(field, doc));
16
+ const inInterval = (read, doc) => read.through !== null && read.range.every((filter) => evaluateRangeFilter(doc, filter)) && (read.after === null || compareToKey(read.fields, read.order, doc, read.after) > 0) && (read.through === "end" || compareToKey(read.fields, read.order, doc, read.through) <= 0);
17
+ /**
18
+ * Whether a commit's change to one document invalidates `read`: it wrote the
19
+ * document read by ID, or the document entered or left the interval.
20
+ */
21
+ const covers = (read, change) => {
22
+ if (read.componentPath !== change.componentPath) return false;
23
+ if (read.kind === "document") return read.id === change.id;
24
+ return tableNameFromId(change.id) === read.table && [change.before, change.after].some((doc) => doc !== null && inInterval(read, doc));
25
+ };
26
+ //#endregion
27
+ export { compareToKey, covers, keyOf };
28
+
29
+ //# sourceMappingURL=ReadSet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadSet.js","names":[],"sources":["../../src/internal/ReadSet.ts"],"sourcesContent":["import { compareValues, type Value } from \"convex/values\";\nimport type {\n DocumentId,\n SerializedRangeExpression,\n StoredDocument,\n} from \"./KernelValues.js\";\nimport {\n evaluateFieldPath,\n evaluateRangeFilter,\n tableNameFromId,\n} from \"./KernelValues.js\";\n\n/**\n * A row's position in an interval's order: its value at each of the interval's\n * fields.\n */\nexport type Key = ReadonlyArray<Value | undefined>;\n\n/**\n * A read of one document by ID. It records the document's absence as well as\n * its presence, so a commit that creates the document invalidates it.\n */\nexport interface DocumentRead {\n readonly kind: \"document\";\n readonly componentPath: string;\n readonly id: DocumentId;\n}\n\n/**\n * A read of an ordered interval of one table.\n *\n * `fields` is the sort key: an index's fields, then the tie-breakers\n * `_creationTime` and `_id`. `range` is the index range expression, whose `Gt`\n * and `Lt` bounds are exclusive and `Gte` and `Lte` inclusive. `after` is an\n * exclusive start in `order`, a page's cursor. `through` is how far the query\n * examined, inclusive: nothing yet, up to a key, or to the end of the range. An\n * empty result reads to the end, so it still covers an insert into the range.\n *\n * Only the attempt that owns the read advances `through`.\n */\nexport interface IntervalRead {\n readonly kind: \"interval\";\n readonly componentPath: string;\n readonly table: string;\n readonly fields: ReadonlyArray<string>;\n readonly order: \"asc\" | \"desc\";\n readonly range: ReadonlyArray<SerializedRangeExpression>;\n readonly after: Key | null;\n through: Key | \"end\" | null;\n}\n\nexport type Read = DocumentRead | IntervalRead;\n\n/**\n * Where `doc` sorts against `key` in the interval's order.\n */\nexport const compareToKey = (\n fields: ReadonlyArray<string>,\n order: \"asc\" | \"desc\",\n doc: StoredDocument,\n key: Key,\n): number => {\n const direction = order === \"asc\" ? 1 : -1;\n for (let i = 0; i < fields.length; i++) {\n const cmp = compareValues(\n evaluateFieldPath(fields[i]!, doc),\n key[i] as Value,\n );\n if (cmp !== 0) return cmp * direction;\n }\n return 0;\n};\n\nexport const keyOf = (\n fields: ReadonlyArray<string>,\n doc: StoredDocument,\n): Key => fields.map((field) => evaluateFieldPath(field, doc));\n\nconst inInterval = (read: IntervalRead, doc: StoredDocument): boolean =>\n read.through !== null &&\n read.range.every((filter) => evaluateRangeFilter(doc, filter)) &&\n (read.after === null ||\n compareToKey(read.fields, read.order, doc, read.after) > 0) &&\n (read.through === \"end\" ||\n compareToKey(read.fields, read.order, doc, read.through) <= 0);\n\n/**\n * Whether a commit's change to one document invalidates `read`: it wrote the\n * document read by ID, or the document entered or left the interval.\n */\nexport const covers = (\n read: Read,\n change: {\n readonly componentPath: string;\n readonly id: DocumentId;\n readonly before: StoredDocument | null;\n readonly after: StoredDocument | null;\n },\n): boolean => {\n if (read.componentPath !== change.componentPath) return false;\n if (read.kind === \"document\") return read.id === change.id;\n return (\n tableNameFromId(change.id) === read.table &&\n [change.before, change.after].some(\n (doc) => doc !== null && inInterval(read, doc),\n )\n );\n};\n"],"mappings":";;;;;;AAwDA,MAAa,gBACX,QACA,OACA,KACA,QACW;CACX,MAAM,YAAY,UAAU,QAAQ,IAAI;CACxC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,MAAM,cACV,kBAAkB,OAAO,IAAK,GAAG,GACjC,IAAI,EACN;EACA,IAAI,QAAQ,GAAG,OAAO,MAAM;CAC9B;CACA,OAAO;AACT;AAEA,MAAa,SACX,QACA,QACQ,OAAO,KAAK,UAAU,kBAAkB,OAAO,GAAG,CAAC;AAE7D,MAAM,cAAc,MAAoB,QACtC,KAAK,YAAY,QACjB,KAAK,MAAM,OAAO,WAAW,oBAAoB,KAAK,MAAM,CAAC,MAC5D,KAAK,UAAU,QACd,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,KAAK,KAAK,IAAI,OAC1D,KAAK,YAAY,SAChB,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,KAAK,OAAO,KAAK;;;;;AAMhE,MAAa,UACX,MACA,WAMY;CACZ,IAAI,KAAK,kBAAkB,OAAO,eAAe,OAAO;CACxD,IAAI,KAAK,SAAS,YAAY,OAAO,KAAK,OAAO,OAAO;CACxD,OACE,gBAAgB,OAAO,EAAE,MAAM,KAAK,SACpC,CAAC,OAAO,QAAQ,OAAO,KAAK,CAAC,CAAC,MAC3B,QAAQ,QAAQ,QAAQ,WAAW,MAAM,GAAG,CAC/C;AAEJ"}
@@ -0,0 +1,71 @@
1
+ import type { Value } from "convex/values";
2
+ import type * as Invocation from "./Invocation.js";
3
+ import type * as Kernel from "./Kernel.js";
4
+ import type { DocumentId, FunctionPath, StoredDocument } from "./KernelValues.js";
5
+ /**
6
+ * Convex's system table of scheduled functions. Each component has its own.
7
+ */
8
+ export declare const table = "_scheduled_functions";
9
+ export type JobState = {
10
+ readonly kind: "pending";
11
+ } | {
12
+ readonly kind: "inProgress";
13
+ } | {
14
+ readonly kind: "success";
15
+ } | {
16
+ readonly kind: "failed";
17
+ readonly error: string;
18
+ } | {
19
+ readonly kind: "canceled";
20
+ };
21
+ /**
22
+ * What the kernel keeps beside a job's row: the function it calls, which a
23
+ * handle may place in another component, whether a standing source owns the
24
+ * job, and the test world whose invocation scheduled it. The row holds only
25
+ * what Convex's system table holds.
26
+ */
27
+ export interface Target {
28
+ readonly functionPath: FunctionPath;
29
+ readonly standing: boolean;
30
+ /**
31
+ * The job runs as a fresh invocation of this world, with its services, or
32
+ * unbound when no invocation was bound as it was scheduled.
33
+ */
34
+ readonly world: Invocation.World | undefined;
35
+ }
36
+ /**
37
+ * A pending job as a driver sees it.
38
+ */
39
+ export interface Job {
40
+ readonly id: DocumentId;
41
+ readonly componentPath: string;
42
+ readonly name: string;
43
+ readonly scheduledTime: number;
44
+ /**
45
+ * A standing job is never drain work. Time reaches it through `advance`.
46
+ */
47
+ readonly standing: boolean;
48
+ }
49
+ /**
50
+ * A pending job with what running it needs.
51
+ */
52
+ export interface Due extends Job {
53
+ readonly target: FunctionPath;
54
+ readonly args: Value;
55
+ readonly world: Invocation.World | undefined;
56
+ }
57
+ export declare const row: (id: DocumentId, creationTime: number, name: string, args: Value, scheduledTime: number) => StoredDocument;
58
+ export declare const stateOf: (doc: StoredDocument) => JobState;
59
+ /**
60
+ * Every committed pending job of every component, earliest deadline first. Jobs
61
+ * that share a deadline run in the order they were scheduled.
62
+ */
63
+ export declare const pending: (state: Kernel.State, targets: ReadonlyMap<DocumentId, Target>) => Array<Due>;
64
+ /**
65
+ * The distinct functions of `jobs`, for a refusal that names what is left.
66
+ */
67
+ export declare const functions: (jobs: ReadonlyArray<Job>) => Array<{
68
+ readonly componentPath: string;
69
+ readonly name: string;
70
+ }>;
71
+ //# sourceMappingURL=Scheduling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scheduling.d.ts","sourceRoot":"","sources":["../../src/internal/Scheduling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,KAAK,UAAU,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,eAAO,MAAM,KAAK,yBAAyB,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAElC;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,GAAI,SAAQ,GAAG;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;CAC9C;AAED,eAAO,MAAM,GAAG,OACV,UAAU,gBACA,MAAM,QACd,MAAM,QACN,KAAK,iBACI,MAAM,KACpB,cAOD,CAAC;AAEH,eAAO,MAAM,OAAO,QAAS,cAAc,KAAG,QACpB,CAAC;AAK3B;;;GAGG;AACH,eAAO,MAAM,OAAO,UACX,MAAM,CAAC,KAAK,WACV,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KACvC,KAAK,CAAC,GAAG,CAwBX,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,SACd,aAAa,CAAC,GAAG,CAAC,KACvB,KAAK,CAAC;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMjE,CAAC"}
@@ -0,0 +1,60 @@
1
+ //#region src/internal/Scheduling.ts
2
+ /**
3
+ * Convex's system table of scheduled functions. Each component has its own.
4
+ */
5
+ const table = "_scheduled_functions";
6
+ const row = (id, creationTime, name, args, scheduledTime) => ({
7
+ _id: id,
8
+ _creationTime: creationTime,
9
+ name,
10
+ args: [args],
11
+ scheduledTime,
12
+ state: { kind: "pending" }
13
+ });
14
+ const stateOf = (doc) => doc["state"];
15
+ const byDeadline = (a, b) => a.scheduledTime - b.scheduledTime || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
16
+ /**
17
+ * Every committed pending job of every component, earliest deadline first. Jobs
18
+ * that share a deadline run in the order they were scheduled.
19
+ */
20
+ const pending = (state, targets) => {
21
+ const due = [];
22
+ for (const [componentPath, tables] of state.tables) for (const { doc } of tables.get("_scheduled_functions")?.values() ?? []) {
23
+ if (stateOf(doc).kind !== "pending") continue;
24
+ const name = doc["name"];
25
+ const target = targets.get(doc._id) ?? {
26
+ functionPath: {
27
+ componentPath,
28
+ udfPath: name
29
+ },
30
+ standing: false,
31
+ world: void 0
32
+ };
33
+ due.push({
34
+ id: doc._id,
35
+ componentPath,
36
+ name,
37
+ scheduledTime: doc["scheduledTime"],
38
+ standing: target.standing,
39
+ target: target.functionPath,
40
+ args: doc["args"][0],
41
+ world: target.world
42
+ });
43
+ }
44
+ return due.sort(byDeadline);
45
+ };
46
+ /**
47
+ * The distinct functions of `jobs`, for a refusal that names what is left.
48
+ */
49
+ const functions = (jobs) => {
50
+ const seen = /* @__PURE__ */ new Map();
51
+ for (const { componentPath, name } of jobs) seen.set(`${componentPath}\u0000${name}`, {
52
+ componentPath,
53
+ name
54
+ });
55
+ return [...seen.values()];
56
+ };
57
+ //#endregion
58
+ export { functions, pending, row, stateOf, table };
59
+
60
+ //# sourceMappingURL=Scheduling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scheduling.js","names":[],"sources":["../../src/internal/Scheduling.ts"],"sourcesContent":["import type { Value } from \"convex/values\";\nimport type * as Invocation from \"./Invocation.js\";\nimport type * as Kernel from \"./Kernel.js\";\nimport type {\n DocumentId,\n FunctionPath,\n StoredDocument,\n} from \"./KernelValues.js\";\n\n/**\n * Convex's system table of scheduled functions. Each component has its own.\n */\nexport const table = \"_scheduled_functions\";\n\nexport type JobState =\n | { readonly kind: \"pending\" }\n | { readonly kind: \"inProgress\" }\n | { readonly kind: \"success\" }\n | { readonly kind: \"failed\"; readonly error: string }\n | { readonly kind: \"canceled\" };\n\n/**\n * What the kernel keeps beside a job's row: the function it calls, which a\n * handle may place in another component, whether a standing source owns the\n * job, and the test world whose invocation scheduled it. The row holds only\n * what Convex's system table holds.\n */\nexport interface Target {\n readonly functionPath: FunctionPath;\n readonly standing: boolean;\n /**\n * The job runs as a fresh invocation of this world, with its services, or\n * unbound when no invocation was bound as it was scheduled.\n */\n readonly world: Invocation.World | undefined;\n}\n\n/**\n * A pending job as a driver sees it.\n */\nexport interface Job {\n readonly id: DocumentId;\n readonly componentPath: string;\n readonly name: string;\n readonly scheduledTime: number;\n /**\n * A standing job is never drain work. Time reaches it through `advance`.\n */\n readonly standing: boolean;\n}\n\n/**\n * A pending job with what running it needs.\n */\nexport interface Due extends Job {\n readonly target: FunctionPath;\n readonly args: Value;\n readonly world: Invocation.World | undefined;\n}\n\nexport const row = (\n id: DocumentId,\n creationTime: number,\n name: string,\n args: Value,\n scheduledTime: number,\n): StoredDocument => ({\n _id: id,\n _creationTime: creationTime,\n name,\n args: [args],\n scheduledTime,\n state: { kind: \"pending\" },\n});\n\nexport const stateOf = (doc: StoredDocument): JobState =>\n doc[\"state\"] as JobState;\n\nconst byDeadline = (a: Due, b: Due) =>\n a.scheduledTime - b.scheduledTime || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n\n/**\n * Every committed pending job of every component, earliest deadline first. Jobs\n * that share a deadline run in the order they were scheduled.\n */\nexport const pending = (\n state: Kernel.State,\n targets: ReadonlyMap<DocumentId, Target>,\n): Array<Due> => {\n const due: Array<Due> = [];\n for (const [componentPath, tables] of state.tables) {\n for (const { doc } of tables.get(table)?.values() ?? []) {\n if (stateOf(doc).kind !== \"pending\") continue;\n const name = doc[\"name\"] as string;\n const target = targets.get(doc._id) ?? {\n functionPath: { componentPath, udfPath: name },\n standing: false,\n world: undefined,\n };\n due.push({\n id: doc._id,\n componentPath,\n name,\n scheduledTime: doc[\"scheduledTime\"] as number,\n standing: target.standing,\n target: target.functionPath,\n args: (doc[\"args\"] as ReadonlyArray<Value>)[0]!,\n world: target.world,\n });\n }\n }\n return due.sort(byDeadline);\n};\n\n/**\n * The distinct functions of `jobs`, for a refusal that names what is left.\n */\nexport const functions = (\n jobs: ReadonlyArray<Job>,\n): Array<{ readonly componentPath: string; readonly name: string }> => {\n const seen = new Map<string, { componentPath: string; name: string }>();\n for (const { componentPath, name } of jobs) {\n seen.set(`${componentPath}\\u0000${name}`, { componentPath, name });\n }\n return [...seen.values()];\n};\n"],"mappings":";;;;AAYA,MAAa,QAAQ;AAgDrB,MAAa,OACX,IACA,cACA,MACA,MACA,mBACoB;CACpB,KAAK;CACL,eAAe;CACf;CACA,MAAM,CAAC,IAAI;CACX;CACA,OAAO,EAAE,MAAM,UAAU;AAC3B;AAEA,MAAa,WAAW,QACtB,IAAI;AAEN,MAAM,cAAc,GAAQ,MAC1B,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;;;;;AAM7E,MAAa,WACX,OACA,YACe;CACf,MAAM,MAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,eAAe,WAAW,MAAM,QAC1C,KAAK,MAAM,EAAE,SAAS,OAAO,IAAA,sBAAS,CAAC,EAAE,OAAO,KAAK,CAAC,GAAG;EACvD,IAAI,QAAQ,GAAG,CAAC,CAAC,SAAS,WAAW;EACrC,MAAM,OAAO,IAAI;EACjB,MAAM,SAAS,QAAQ,IAAI,IAAI,GAAG,KAAK;GACrC,cAAc;IAAE;IAAe,SAAS;GAAK;GAC7C,UAAU;GACV,OAAO,KAAA;EACT;EACA,IAAI,KAAK;GACP,IAAI,IAAI;GACR;GACA;GACA,eAAe,IAAI;GACnB,UAAU,OAAO;GACjB,QAAQ,OAAO;GACf,MAAO,IAAI,OAAO,CAA0B;GAC5C,OAAO,OAAO;EAChB,CAAC;CACH;CAEF,OAAO,IAAI,KAAK,UAAU;AAC5B;;;;AAKA,MAAa,aACX,SACqE;CACrE,MAAM,uBAAO,IAAI,IAAqD;CACtE,KAAK,MAAM,EAAE,eAAe,UAAU,MACpC,KAAK,IAAI,GAAG,cAAc,QAAQ,QAAQ;EAAE;EAAe;CAAK,CAAC;CAEnE,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B"}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@gaganref/confect-test",
3
- "version": "10.3.1-fork.9",
3
+ "version": "10.3.1-fork.90",
4
4
  "peerDependencies": {
5
- "@confect/core": "npm:@gaganref/confect-core@10.3.1-fork.9",
6
- "@confect/server": "npm:@gaganref/confect-server@10.3.1-fork.9",
5
+ "@confect/core": "npm:@gaganref/confect-core@10.3.1-fork.90",
6
+ "@confect/server": "npm:@gaganref/confect-server@10.3.1-fork.90",
7
7
  "convex": "^1.32.0",
8
- "convex-test": ">=0.0.50 <0.1.0",
9
- "effect": "^4.0.0"
8
+ "convex-test": ">=0.0.65 <0.1.0",
9
+ "effect": ">=4.0.0 <4.1.0"
10
10
  },
11
11
  "description": "Utilities for testing Confect apps without a live Convex backend",
12
12
  "author": "RJ Dellecese",
@@ -1,4 +1,4 @@
1
- import { App, Ref } from "@confect/core";
1
+ import { App, type Mount, Ref } from "@confect/core";
2
2
  import type { DatabaseSchema, DataModel } from "@confect/server";
3
3
  import { RegisteredConvexFunction } from "@confect/server";
4
4
  import type {
@@ -7,7 +7,6 @@ import type {
7
7
  } from "convex-test";
8
8
  import { convexTest } from "convex-test";
9
9
  import type {
10
- GenericDataModel,
11
10
  GenericMutationCtx,
12
11
  GenericSchema,
13
12
  SchemaDefinition,
@@ -18,13 +17,33 @@ import * as Context from "effect/Context";
18
17
  import * as Effect from "effect/Effect";
19
18
  import * as Layer from "effect/Layer";
20
19
  import * as Schema from "effect/Schema";
20
+ import { convexTest as kernelTest } from "./internal/ConvexTest.js";
21
21
  import * as MountedHttp from "./internal/MountedHttp.js";
22
22
 
23
+ declare const process: { env: Record<string, string | undefined> };
24
+
25
+ /**
26
+ * How a kit registers a component beyond its schema and modules.
27
+ */
28
+ export type RegisterOptions = {
29
+ /**
30
+ * The component's functions, as `module:export`, that schedule their own next
31
+ * run, such as the crons component's `public:rescheduler`. The test kernel
32
+ * never treats a job that runs one, or that one schedules, as work
33
+ * `finishAllScheduledFunctions` must finish. convex-test ignores it.
34
+ */
35
+ readonly standing?: ReadonlyArray<string>;
36
+ };
37
+
23
38
  /**
24
39
  * Register a component's Convex schema and modules at its installation path.
25
40
  */
26
- export type RegisterComponent =
27
- TestConvexForDataModelAndIdentity<GenericDataModel>["registerComponent"];
41
+ export type RegisterComponent = (
42
+ componentPath: string,
43
+ schema: SchemaDefinition<GenericSchema, boolean>,
44
+ modules: Record<string, () => Promise<any>>,
45
+ options?: RegisterOptions,
46
+ ) => void;
28
47
 
29
48
  /**
30
49
  * An installed component path and the HTTP prefix exposed by its host.
@@ -34,7 +53,22 @@ export type HttpMount = {
34
53
  readonly httpPrefix: string;
35
54
  };
36
55
 
37
- export type LayerOptions = {
56
+ /**
57
+ * The in-process backend a test layer runs on.
58
+ */
59
+ export type Target = "convex-test" | "kernel";
60
+
61
+ export type TargetOptions = {
62
+ /**
63
+ * `"kernel"` runs every function and component the layer registers on
64
+ * Confect's test kernel, which runs scheduled functions on its own clock and
65
+ * reads `registerComponent`'s `standing`. Defaults to `"convex-test"`. The
66
+ * option goes once the kernel replaces convex-test.
67
+ */
68
+ readonly target?: Target;
69
+ };
70
+
71
+ export type LayerOptions = TargetOptions & {
38
72
  /**
39
73
  * Register component kits synchronously before the test service is available.
40
74
  */
@@ -101,6 +135,7 @@ export type TestConfect<ConfectSchema extends DatabaseSchema.AnyWithProps> = {
101
135
  componentPath: string,
102
136
  schema: SchemaDefinition<GenericSchema, boolean>,
103
137
  modules: Record<string, () => Promise<any>>,
138
+ options?: RegisterOptions,
104
139
  ) => Effect.Effect<void>;
105
140
  withIdentity: (
106
141
  userIdentity: Partial<UserIdentity>,
@@ -256,9 +291,9 @@ class TestConfectImpl<
256
291
 
257
292
  readonly registerComponent: TestConfect<ConfectSchema>["registerComponent"] =
258
293
  Effect.fn("TestConfect.registerComponent")(
259
- (componentPath, schema, modules) =>
294
+ (componentPath, schema, modules, options) =>
260
295
  Effect.sync(() =>
261
- this.mountedHttp.register(componentPath, schema, modules),
296
+ this.mountedHttp.register(componentPath, schema, modules, options),
262
297
  ),
263
298
  );
264
299
 
@@ -302,8 +337,8 @@ class TestConfectImpl<
302
337
 
303
338
  /**
304
339
  * How a test registers one installed component: its own schema and modules, or
305
- * the `register(t, name)` a component package exports from `<package>/test`,
306
- * which also registers the package's own children.
340
+ * the `register(t, name)` a component package exports from `<package>/testing`
341
+ * or `<package>/test`, which also registers the package's own children.
307
342
  */
308
343
  export type ComponentKit =
309
344
  | {
@@ -317,6 +352,11 @@ export type ComponentKit =
317
352
  ): unknown;
318
353
  };
319
354
 
355
+ class InvalidComponentKit extends Schema.TaggedError<InvalidComponentKit>()(
356
+ "InvalidComponentKit",
357
+ { componentPath: Schema.String, message: Schema.String },
358
+ ) {}
359
+
320
360
  const registerKit = (
321
361
  registerComponent: RegisterComponent,
322
362
  name: string,
@@ -324,8 +364,14 @@ const registerKit = (
324
364
  ) => {
325
365
  if ("register" in kit) {
326
366
  kit.register({ registerComponent }, name);
327
- } else {
367
+ } else if ("schema" in kit && "modules" in kit) {
328
368
  registerComponent(name, kit.schema, kit.modules);
369
+ } else {
370
+ // Codegen accepts a kit whose exports it cannot list, such as CommonJS.
371
+ throw new InvalidComponentKit({
372
+ componentPath: name,
373
+ message: `The test kit for ${JSON.stringify(name)} must export register(t, name), or schema and modules.`,
374
+ });
329
375
  }
330
376
  };
331
377
 
@@ -350,9 +396,12 @@ export type DeclaredApp<DatabaseSchema_ extends DatabaseSchema.AnyWithProps> =
350
396
  /**
351
397
  * Build a fresh test backend from a declaration and its generated
352
398
  * `confect/_generated/testing` registry, without restating component mounts.
399
+ * Pass `{ target: "kernel" }` to run it on Confect's test kernel instead of
400
+ * convex-test.
353
401
  */
354
402
  export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
355
403
  app: DeclaredApp<DatabaseSchema_>,
404
+ options?: TargetOptions,
356
405
  ): Layer.Layer<TestConfect<DatabaseSchema_>>;
357
406
  export function layer<
358
407
  A extends App.Any,
@@ -362,11 +411,12 @@ export function layer<
362
411
  registry: Registry<DatabaseSchema_> & {
363
412
  readonly components: Readonly<
364
413
  Record<
365
- keyof NoInfer<A>["mounts"],
414
+ Mount.NameOf<NoInfer<A>["mounts"][number]>,
366
415
  Registry<DatabaseSchema_>["components"][string]
367
416
  >
368
417
  >;
369
418
  },
419
+ options?: TargetOptions,
370
420
  ): Layer.Layer<TestConfect<DatabaseSchema_>>;
371
421
  export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
372
422
  databaseSchema: DatabaseSchema_,
@@ -376,20 +426,22 @@ export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
376
426
  ): Layer.Layer<TestConfect<DatabaseSchema_>>;
377
427
  export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
378
428
  appOrSchema: App.Any | DeclaredApp<DatabaseSchema_> | DatabaseSchema_,
379
- registryOrSchema?:
429
+ second?:
380
430
  | Registry<DatabaseSchema_>
381
- | SchemaDefinition<GenericSchema, true>,
382
- modules?: Record<string, () => Promise<any>>,
431
+ | SchemaDefinition<GenericSchema, true>
432
+ | TargetOptions,
433
+ third?: Record<string, () => Promise<any>> | TargetOptions,
383
434
  options: LayerOptions = {},
384
435
  ): Layer.Layer<TestConfect<DatabaseSchema_>> {
385
436
  if (App.isApp(appOrSchema)) {
386
- const registry = registryOrSchema as Registry<DatabaseSchema_>;
437
+ const registry = second as Registry<DatabaseSchema_>;
387
438
  const mounts = App.mounts(appOrSchema);
388
439
  return schemaLayer(
389
440
  registry.schema,
390
441
  registry.convexSchema,
391
442
  registry.modules,
392
443
  {
444
+ ...targetOf(third as TargetOptions | undefined),
393
445
  httpMounts: mounts.flatMap(({ name, httpPrefix }) =>
394
446
  httpPrefix === undefined ? [] : [{ componentPath: name, httpPrefix }],
395
447
  ),
@@ -411,6 +463,7 @@ export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
411
463
  if ("httpMounts" in appOrSchema) {
412
464
  const app = appOrSchema as DeclaredApp<DatabaseSchema_>;
413
465
  return schemaLayer(app.schema, app.convexSchema, app.modules, {
466
+ ...targetOf(second as TargetOptions | undefined),
414
467
  httpMounts: app.httpMounts,
415
468
  register: (registerComponent) => {
416
469
  for (const [name, component] of Object.entries(app.components)) {
@@ -421,12 +474,15 @@ export function layer<DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
421
474
  }
422
475
  return schemaLayer(
423
476
  appOrSchema as DatabaseSchema_,
424
- registryOrSchema as SchemaDefinition<GenericSchema, true>,
425
- modules!,
477
+ second as SchemaDefinition<GenericSchema, true>,
478
+ third as Record<string, () => Promise<any>>,
426
479
  options,
427
480
  );
428
481
  }
429
482
 
483
+ const targetOf = (options: TargetOptions | undefined): TargetOptions =>
484
+ options?.target === undefined ? {} : { target: options.target };
485
+
430
486
  const schemaLayer = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
431
487
  databaseSchema: DatabaseSchema_,
432
488
  convexSchemaDefinition: SchemaDefinition<GenericSchema, true>,
@@ -434,9 +490,18 @@ const schemaLayer = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
434
490
  options: LayerOptions = {},
435
491
  ): Layer.Layer<TestConfect<DatabaseSchema_>> =>
436
492
  Layer.sync(TestConfect<DatabaseSchema_>(), () => {
437
- const testConvex = convexTest(convexSchemaDefinition, modules);
493
+ process.env.CONVEX_CLOUD_URL ??= "https://some-deployment.convex.cloud";
494
+ process.env.CONVEX_SITE_URL ??= "https://some.convex.site";
495
+ const testConvex = (options.target === "kernel" ? kernelTest : convexTest)(
496
+ convexSchemaDefinition,
497
+ modules,
498
+ );
499
+ // convex-test takes three arguments and ignores the options. The kernel
500
+ // reads `standing`.
501
+ const registerComponent =
502
+ testConvex.registerComponent as unknown as RegisterComponent;
438
503
  const mountedHttp = MountedHttp.make(options.httpMounts ?? [], (...args) =>
439
- testConvex.registerComponent(...args),
504
+ registerComponent(...args),
440
505
  );
441
506
  options.register?.(mountedHttp.register);
442
507
  return new TestConfectImpl(databaseSchema, testConvex as any, mountedHttp);