@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.
- package/dist/TestConfect.d.ts +37 -11
- package/dist/TestConfect.d.ts.map +1 -1
- package/dist/TestConfect.js +22 -7
- package/dist/TestConfect.js.map +1 -1
- package/dist/internal/Attempt.d.ts +77 -0
- package/dist/internal/Attempt.d.ts.map +1 -0
- package/dist/internal/Attempt.js +154 -0
- package/dist/internal/Attempt.js.map +1 -0
- package/dist/internal/Clock.d.ts +29 -0
- package/dist/internal/Clock.d.ts.map +1 -0
- package/dist/internal/Clock.js +66 -0
- package/dist/internal/Clock.js.map +1 -0
- package/dist/internal/ConvexTest.d.ts +66 -0
- package/dist/internal/ConvexTest.d.ts.map +1 -0
- package/dist/internal/ConvexTest.js +62 -0
- package/dist/internal/ConvexTest.js.map +1 -0
- package/dist/internal/Execution.d.ts +247 -0
- package/dist/internal/Execution.d.ts.map +1 -0
- package/dist/internal/Execution.js +1024 -0
- package/dist/internal/Execution.js.map +1 -0
- package/dist/internal/Globals.d.ts +11 -0
- package/dist/internal/Globals.d.ts.map +1 -0
- package/dist/internal/Globals.js +88 -0
- package/dist/internal/Globals.js.map +1 -0
- package/dist/internal/Invocation.d.ts +68 -0
- package/dist/internal/Invocation.d.ts.map +1 -0
- package/dist/internal/Invocation.js +158 -0
- package/dist/internal/Invocation.js.map +1 -0
- package/dist/internal/Kernel.d.ts +95 -0
- package/dist/internal/Kernel.d.ts.map +1 -0
- package/dist/internal/Kernel.js +91 -0
- package/dist/internal/Kernel.js.map +1 -0
- package/dist/internal/KernelQuery.d.ts +55 -0
- package/dist/internal/KernelQuery.d.ts.map +1 -0
- package/dist/internal/KernelQuery.js +205 -0
- package/dist/internal/KernelQuery.js.map +1 -0
- package/dist/internal/KernelRefusal.d.ts +102 -0
- package/dist/internal/KernelRefusal.d.ts.map +1 -0
- package/dist/internal/KernelRefusal.js +94 -0
- package/dist/internal/KernelRefusal.js.map +1 -0
- package/dist/internal/KernelValues.d.ts +138 -0
- package/dist/internal/KernelValues.d.ts.map +1 -0
- package/dist/internal/KernelValues.js +281 -0
- package/dist/internal/KernelValues.js.map +1 -0
- package/dist/internal/MountedHttp.d.ts +1 -1
- package/dist/internal/MountedHttp.d.ts.map +1 -1
- package/dist/internal/MountedHttp.js +3 -3
- package/dist/internal/MountedHttp.js.map +1 -1
- package/dist/internal/ReadSet.d.ts +55 -0
- package/dist/internal/ReadSet.d.ts.map +1 -0
- package/dist/internal/ReadSet.js +29 -0
- package/dist/internal/ReadSet.js.map +1 -0
- package/dist/internal/Scheduling.d.ts +71 -0
- package/dist/internal/Scheduling.d.ts.map +1 -0
- package/dist/internal/Scheduling.js +60 -0
- package/dist/internal/Scheduling.js.map +1 -0
- package/package.json +5 -5
- package/src/TestConfect.ts +84 -19
- package/src/internal/Attempt.ts +185 -0
- package/src/internal/Clock.ts +95 -0
- package/src/internal/ConvexTest.ts +163 -0
- package/src/internal/Execution.ts +1795 -0
- package/src/internal/Globals.ts +113 -0
- package/src/internal/Invocation.ts +316 -0
- package/src/internal/Kernel.ts +190 -0
- package/src/internal/KernelQuery.ts +405 -0
- package/src/internal/KernelRefusal.ts +112 -0
- package/src/internal/KernelValues.ts +765 -0
- package/src/internal/MountedHttp.ts +13 -6
- package/src/internal/ReadSet.ts +108 -0
- 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.
|
|
3
|
+
"version": "10.3.1-fork.90",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@confect/core": "npm:@gaganref/confect-core@10.3.1-fork.
|
|
6
|
-
"@confect/server": "npm:@gaganref/confect-server@10.3.1-fork.
|
|
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.
|
|
9
|
-
"effect": "
|
|
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",
|
package/src/TestConfect.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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>/
|
|
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
|
-
|
|
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
|
-
|
|
429
|
+
second?:
|
|
380
430
|
| Registry<DatabaseSchema_>
|
|
381
|
-
| SchemaDefinition<GenericSchema, true
|
|
382
|
-
|
|
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 =
|
|
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
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
504
|
+
registerComponent(...args),
|
|
440
505
|
);
|
|
441
506
|
options.register?.(mountedHttp.register);
|
|
442
507
|
return new TestConfectImpl(databaseSchema, testConvex as any, mountedHttp);
|