@gaganref/confect-test 10.3.1-fork.89 → 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 +33 -7
- package/dist/TestConfect.d.ts.map +1 -1
- package/dist/TestConfect.js +11 -6
- package/dist/TestConfect.js.map +1 -1
- package/dist/internal/Attempt.js.map +1 -1
- 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 +33 -1
- package/dist/internal/ConvexTest.d.ts.map +1 -1
- package/dist/internal/ConvexTest.js +8 -5
- package/dist/internal/ConvexTest.js.map +1 -1
- package/dist/internal/Execution.d.ts +85 -5
- package/dist/internal/Execution.d.ts.map +1 -1
- package/dist/internal/Execution.js +285 -51
- package/dist/internal/Execution.js.map +1 -1
- package/dist/internal/Globals.d.ts.map +1 -1
- package/dist/internal/Globals.js +12 -0
- package/dist/internal/Globals.js.map +1 -1
- package/dist/internal/Invocation.d.ts +11 -0
- package/dist/internal/Invocation.d.ts.map +1 -1
- package/dist/internal/Invocation.js +34 -21
- package/dist/internal/Invocation.js.map +1 -1
- package/dist/internal/Kernel.d.ts +3 -1
- package/dist/internal/Kernel.d.ts.map +1 -1
- package/dist/internal/Kernel.js +2 -2
- package/dist/internal/Kernel.js.map +1 -1
- package/dist/internal/KernelRefusal.d.ts +25 -0
- package/dist/internal/KernelRefusal.d.ts.map +1 -1
- package/dist/internal/KernelRefusal.js +22 -1
- package/dist/internal/KernelRefusal.js.map +1 -1
- package/dist/internal/KernelValues.d.ts +6 -4
- package/dist/internal/KernelValues.d.ts.map +1 -1
- package/dist/internal/KernelValues.js +20 -5
- package/dist/internal/KernelValues.js.map +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/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 +3 -3
- package/src/TestConfect.ts +62 -15
- package/src/internal/Attempt.ts +1 -1
- package/src/internal/Clock.ts +95 -0
- package/src/internal/ConvexTest.ts +51 -6
- package/src/internal/Execution.ts +415 -50
- package/src/internal/Globals.ts +20 -0
- package/src/internal/Invocation.ts +38 -13
- package/src/internal/Kernel.ts +5 -5
- package/src/internal/KernelRefusal.ts +24 -0
- package/src/internal/KernelValues.ts +44 -4
- package/src/internal/MountedHttp.ts +13 -6
- package/src/internal/Scheduling.ts +126 -0
|
@@ -5,16 +5,22 @@ import * as Kernel from "./Kernel.js";
|
|
|
5
5
|
import * as KernelQuery from "./KernelQuery.js";
|
|
6
6
|
import * as KernelRefusal from "./KernelRefusal.js";
|
|
7
7
|
import type * as ReadSet from "./ReadSet.js";
|
|
8
|
+
import * as Scheduling from "./Scheduling.js";
|
|
8
9
|
type Modules = Record<string, () => Promise<any>>;
|
|
9
10
|
/**
|
|
10
|
-
* One registered component: its schema
|
|
11
|
-
*
|
|
11
|
+
* One registered component: its schema, its function modules, each loaded once
|
|
12
|
+
* when the world opens, and its standing sources.
|
|
12
13
|
*/
|
|
13
14
|
interface Component {
|
|
14
15
|
readonly path: string;
|
|
15
16
|
readonly schema: KernelQuery.KernelSchema | null;
|
|
16
17
|
readonly loaders: ReadonlyMap<string, () => Promise<any>>;
|
|
17
18
|
readonly loaded: Map<string, any>;
|
|
19
|
+
/**
|
|
20
|
+
* The functions, as `module:export`, that schedule their own next run. A job
|
|
21
|
+
* that runs one, or that one schedules, is never drain work.
|
|
22
|
+
*/
|
|
23
|
+
readonly standing: ReadonlySet<string>;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
26
|
* Who is calling: the identity and the request attributes a test accessor set.
|
|
@@ -83,13 +89,28 @@ export declare class World {
|
|
|
83
89
|
private readonly queries;
|
|
84
90
|
private readonly holding;
|
|
85
91
|
private nextAttemptId;
|
|
92
|
+
/**
|
|
93
|
+
* The latest deadline time has been advanced to. The clock never reads
|
|
94
|
+
* earlier.
|
|
95
|
+
*/
|
|
96
|
+
private floor;
|
|
97
|
+
private readonly targets;
|
|
86
98
|
constructor(schema: SchemaDefinition<GenericSchema, boolean> | null | undefined, modules: Modules, runtime?: Kernel.KernelRuntime);
|
|
87
|
-
register(componentPath: string, schema: SchemaDefinition<GenericSchema, boolean>, modules: Modules): void;
|
|
99
|
+
register(componentPath: string, schema: SchemaDefinition<GenericSchema, boolean>, modules: Modules, standing?: ReadonlyArray<string>): void;
|
|
88
100
|
/**
|
|
89
101
|
* Load every registered module of every component, once, before any function
|
|
90
|
-
* runs.
|
|
102
|
+
* runs, then check that each standing source names an exported function.
|
|
91
103
|
*/
|
|
92
104
|
open(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Refuse a standing source no module of its component exports.
|
|
107
|
+
*/
|
|
108
|
+
private checkStanding;
|
|
109
|
+
/**
|
|
110
|
+
* The kernel's time: the runtime's, never earlier than the latest deadline
|
|
111
|
+
* time was advanced to.
|
|
112
|
+
*/
|
|
113
|
+
now(): number;
|
|
93
114
|
private component;
|
|
94
115
|
private resolve;
|
|
95
116
|
query(caller: Caller, reference: any, args: any): Promise<any>;
|
|
@@ -105,6 +126,59 @@ export declare class World {
|
|
|
105
126
|
* The held attempts, oldest first.
|
|
106
127
|
*/
|
|
107
128
|
held(): ReadonlyArray<HeldAttempt>;
|
|
129
|
+
/**
|
|
130
|
+
* The committed pending jobs, earliest deadline first.
|
|
131
|
+
*/
|
|
132
|
+
jobs(): ReadonlyArray<Scheduling.Job>;
|
|
133
|
+
/**
|
|
134
|
+
* Move the clock forward `ms`, running every job whose deadline falls on the
|
|
135
|
+
* way, in deadline order, each starting at its deadline. Jobs those jobs
|
|
136
|
+
* schedule inside the window run too. `advance(0)` runs what is due now and
|
|
137
|
+
* crosses no future deadline.
|
|
138
|
+
*/
|
|
139
|
+
advance(ms: number, budget?: number): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Run jobs, earliest deadline first and each at its deadline, until only
|
|
142
|
+
* standing jobs are pending. Every job run counts against `budget`.
|
|
143
|
+
*/
|
|
144
|
+
drain(budget?: number): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Run the first of `select(pending)` until it is empty, moving the clock to
|
|
147
|
+
* each job's deadline as it starts.
|
|
148
|
+
*/
|
|
149
|
+
private walk;
|
|
150
|
+
private raiseFloor;
|
|
151
|
+
/**
|
|
152
|
+
* Run one pending job as its own request, moving its row through in progress
|
|
153
|
+
* to success or failed. `walk` picks it from the committed pending jobs and
|
|
154
|
+
* starts it in the same turn, so no cancel can land in between. The job runs
|
|
155
|
+
* as a fresh invocation of the world that scheduled it, whatever invocation
|
|
156
|
+
* the drain itself runs under.
|
|
157
|
+
*/
|
|
158
|
+
private runJob;
|
|
159
|
+
/**
|
|
160
|
+
* Write through a fresh attempt and publish it at once, as the scheduler does
|
|
161
|
+
* for an action's schedule and a job's state. Nothing runs between the
|
|
162
|
+
* snapshot and the commit, so no other commit can conflict with it.
|
|
163
|
+
*/
|
|
164
|
+
private commitNow;
|
|
165
|
+
/**
|
|
166
|
+
* Publish `writes` as the next commit, at the runtime's timestamp for it. No
|
|
167
|
+
* writes publish nothing and ask the runtime for no timestamp, which answers
|
|
168
|
+
* the latest commit's.
|
|
169
|
+
*/
|
|
170
|
+
private publishWrites;
|
|
171
|
+
/**
|
|
172
|
+
* Write a pending job into `attempt`, so the job exists only once that
|
|
173
|
+
* attempt publishes. A job that a standing function schedules, or that runs
|
|
174
|
+
* one, is standing.
|
|
175
|
+
*/
|
|
176
|
+
private scheduleJob;
|
|
177
|
+
/**
|
|
178
|
+
* Cancel a job that has not finished. A finished job stays as it ended.
|
|
179
|
+
*/
|
|
180
|
+
private cancelJob;
|
|
181
|
+
private isStanding;
|
|
108
182
|
private queryAt;
|
|
109
183
|
private mutationAt;
|
|
110
184
|
private actionAt;
|
|
@@ -153,6 +227,11 @@ export declare class World {
|
|
|
153
227
|
jsSyscall(op: string): Promise<never>;
|
|
154
228
|
private get;
|
|
155
229
|
private insert;
|
|
230
|
+
/**
|
|
231
|
+
* A new document's creation time: `at`, or just past the latest one, so no
|
|
232
|
+
* two documents share one.
|
|
233
|
+
*/
|
|
234
|
+
private creationTime;
|
|
156
235
|
private existing;
|
|
157
236
|
private fields;
|
|
158
237
|
private patch;
|
|
@@ -160,7 +239,8 @@ export declare class World {
|
|
|
160
239
|
private remove;
|
|
161
240
|
}
|
|
162
241
|
/**
|
|
163
|
-
* Whether `globalThis.Convex` is the kernel's,
|
|
242
|
+
* Whether `globalThis.Convex` is the kernel's, and no other backend installed
|
|
243
|
+
* one behind it.
|
|
164
244
|
*/
|
|
165
245
|
export declare const ownsConvexGlobal: () => boolean;
|
|
166
246
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Execution.d.ts","sourceRoot":"","sources":["../../src/internal/Execution.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAA8B,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Execution.d.ts","sourceRoot":"","sources":["../../src/internal/Execution.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { own } from "./Globals.js";
|
|
2
|
+
import { install } from "./Clock.js";
|
|
2
3
|
import { createFunctionHandle, createTestJwt, deserializeConvexErrorData, evaluateValue, hasPendingValues, parseArgs, parseFunctionHandle, resolveCommittedValues, simpleHash, tableNameFromId, validateFunctionArgs, validateReturnValue } from "./KernelValues.js";
|
|
3
4
|
import { apply, conflict, empty, wallClock } from "./Kernel.js";
|
|
4
5
|
import { KernelAttempt } from "./Attempt.js";
|
|
5
|
-
import { bind, current as current$1 } from "./Invocation.js";
|
|
6
|
-
import { ClosedAttempt, CommitTsNotIncreasing, OptimisticConcurrencyControlFailure, RegisteredAfterOpen, StaleAttempt, requiresBinary, rowPending } from "./KernelRefusal.js";
|
|
6
|
+
import { bind, current as current$1, fresh, unbound } from "./Invocation.js";
|
|
7
|
+
import { ClosedAttempt, CommitTsNotIncreasing, DrainBudgetExceeded, OptimisticConcurrencyControlFailure, RegisteredAfterOpen, StaleAttempt, UnknownStandingSource, requiresBinary, rowPending } from "./KernelRefusal.js";
|
|
7
8
|
import { exportSchema, paginate, stream, validateDocument } from "./KernelQuery.js";
|
|
9
|
+
import { functions, pending, row, stateOf, table } from "./Scheduling.js";
|
|
8
10
|
import { actionGeneric, getFunctionAddress, mutationGeneric, queryGeneric } from "convex/server";
|
|
9
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
12
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
@@ -73,8 +75,25 @@ const executions = new AsyncLocalStorage();
|
|
|
73
75
|
* Run a function's code as `execution`, with its own copy of the globals a test
|
|
74
76
|
* invocation pins.
|
|
75
77
|
*/
|
|
76
|
-
const enter = (execution, run) =>
|
|
78
|
+
const enter = (execution, run) => {
|
|
79
|
+
installConvexGlobal();
|
|
80
|
+
return own(() => executions.run(execution, run));
|
|
81
|
+
};
|
|
77
82
|
const kernelBrand = Symbol.for("@confect/test/Kernel");
|
|
83
|
+
/**
|
|
84
|
+
* The time `Date` answers inside a kernel invocation: a query's or mutation's
|
|
85
|
+
* start, or the clock as it runs in an action. Outside one, none.
|
|
86
|
+
*/
|
|
87
|
+
const pinnedTime = () => {
|
|
88
|
+
const execution = executions.getStore();
|
|
89
|
+
if (execution === void 0) return void 0;
|
|
90
|
+
return execution.udfType === "action" ? execution.world.now() : execution.startTime;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* How many jobs one drain or advance runs before it gives up, as convex-test's
|
|
94
|
+
* `finishAllScheduledFunctions` defaults to.
|
|
95
|
+
*/
|
|
96
|
+
const defaultBudget = 100;
|
|
78
97
|
const current = () => {
|
|
79
98
|
const execution = executions.getStore();
|
|
80
99
|
if (execution === void 0) throw new Error("A Convex syscall ran outside any function the test kernel started.");
|
|
@@ -88,12 +107,20 @@ const newRequest = (caller) => ({
|
|
|
88
107
|
scheduledFunctionId: null,
|
|
89
108
|
authToken: caller.token
|
|
90
109
|
});
|
|
110
|
+
/**
|
|
111
|
+
* A scheduled call is a request of its own: no caller's attributes, and its
|
|
112
|
+
* job's ID.
|
|
113
|
+
*/
|
|
114
|
+
const jobRequest = (jobId) => ({
|
|
115
|
+
...newRequest(anonymous),
|
|
116
|
+
scheduledFunctionId: jobId
|
|
117
|
+
});
|
|
91
118
|
const findModulesRoot = (paths) => {
|
|
92
119
|
const generated = paths.find((path) => path.includes("_generated"));
|
|
93
120
|
if (generated === void 0) throw new Error("Could not find the \"_generated\" directory, make sure to run `npx convex dev` or `npx convex codegen`. Make sure your `import.meta.glob` includes the files in the \"_generated\" directory");
|
|
94
121
|
return generated.split("_generated", 2)[0];
|
|
95
122
|
};
|
|
96
|
-
const makeComponent = (path, schema, modules) => {
|
|
123
|
+
const makeComponent = (path, schema, modules, standing = []) => {
|
|
97
124
|
const root = findModulesRoot(Object.keys(modules));
|
|
98
125
|
const relative = Object.entries(modules).filter(([key]) => key.startsWith(root)).map(([key, load]) => [key.slice(root.length), load]);
|
|
99
126
|
const nested = relative.map(([file]) => file).filter((file) => file.endsWith("/convex.config.ts")).map((file) => file.slice(0, -16));
|
|
@@ -101,7 +128,8 @@ const makeComponent = (path, schema, modules) => {
|
|
|
101
128
|
path,
|
|
102
129
|
schema: exportSchema(schema),
|
|
103
130
|
loaders: new Map(relative.filter(([file]) => isFunctionModule(file) && !nested.some((dir) => file.startsWith(dir))).map(([file, load]) => [file.replace(/\.[^.]+$/, ""), load])),
|
|
104
|
-
loaded: /* @__PURE__ */ new Map()
|
|
131
|
+
loaded: /* @__PURE__ */ new Map(),
|
|
132
|
+
standing: new Set(standing)
|
|
105
133
|
};
|
|
106
134
|
};
|
|
107
135
|
const entryExtensions = [
|
|
@@ -168,26 +196,54 @@ var World = class {
|
|
|
168
196
|
queries = /* @__PURE__ */ new Map();
|
|
169
197
|
holding = /* @__PURE__ */ new Map();
|
|
170
198
|
nextAttemptId = 1;
|
|
199
|
+
/**
|
|
200
|
+
* The latest deadline time has been advanced to. The clock never reads
|
|
201
|
+
* earlier.
|
|
202
|
+
*/
|
|
203
|
+
floor = Number.NEGATIVE_INFINITY;
|
|
204
|
+
targets = /* @__PURE__ */ new Map();
|
|
171
205
|
constructor(schema, modules, runtime = wallClock()) {
|
|
172
206
|
this.runtime = runtime;
|
|
173
207
|
installConvexGlobal();
|
|
208
|
+
install(pinnedTime, () => executions.getStore() !== void 0);
|
|
174
209
|
this.components.set(ROOT, makeComponent(ROOT, schema, modules));
|
|
175
210
|
}
|
|
176
|
-
register(componentPath, schema, modules) {
|
|
211
|
+
register(componentPath, schema, modules, standing = []) {
|
|
177
212
|
if (this.opened !== void 0) throw new RegisteredAfterOpen({
|
|
178
213
|
componentPath,
|
|
179
214
|
message: `Component "${componentPath}" was registered after the test backend opened. Register every component before the first call.`
|
|
180
215
|
});
|
|
181
|
-
this.components.set(componentPath, makeComponent(componentPath, schema, modules));
|
|
216
|
+
this.components.set(componentPath, makeComponent(componentPath, schema, modules, standing));
|
|
182
217
|
}
|
|
183
218
|
/**
|
|
184
219
|
* Load every registered module of every component, once, before any function
|
|
185
|
-
* runs.
|
|
220
|
+
* runs, then check that each standing source names an exported function.
|
|
186
221
|
*/
|
|
187
222
|
open() {
|
|
188
|
-
this.opened ??= Promise.all(Array.from(this.components.values(), (component) => Promise.all(Array.from(component.loaders, ([path, load]) => load().then((module) => component.loaded.set(path, module)))))).then(() =>
|
|
223
|
+
this.opened ??= Promise.all(Array.from(this.components.values(), (component) => Promise.all(Array.from(component.loaders, ([path, load]) => load().then((module) => component.loaded.set(path, module)))))).then(() => this.checkStanding());
|
|
189
224
|
return this.opened;
|
|
190
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Refuse a standing source no module of its component exports.
|
|
228
|
+
*/
|
|
229
|
+
checkStanding() {
|
|
230
|
+
for (const component of this.components.values()) for (const name of component.standing) {
|
|
231
|
+
const [modulePath, exportName = "default"] = name.split(":");
|
|
232
|
+
const func = component.loaded.get(modulePath)?.[exportName];
|
|
233
|
+
if (func?.isMutation !== true && func?.isAction !== true) throw new UnknownStandingSource({
|
|
234
|
+
componentPath: component.path,
|
|
235
|
+
name,
|
|
236
|
+
message: `Component "${component.path}" declares "${name}" standing, but exports no mutation or action by that name. Name the self-rescheduling function as module:export.`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The kernel's time: the runtime's, never earlier than the latest deadline
|
|
242
|
+
* time was advanced to.
|
|
243
|
+
*/
|
|
244
|
+
now() {
|
|
245
|
+
return Math.max(this.runtime.now(), this.floor);
|
|
246
|
+
}
|
|
191
247
|
component(componentPath) {
|
|
192
248
|
const component = this.components.get(componentPath);
|
|
193
249
|
if (component === void 0) throw unregistered(this, componentPath);
|
|
@@ -303,6 +359,166 @@ var World = class {
|
|
|
303
359
|
held() {
|
|
304
360
|
return Array.from(this.holding.values(), (held) => held.view);
|
|
305
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* The committed pending jobs, earliest deadline first.
|
|
364
|
+
*/
|
|
365
|
+
jobs() {
|
|
366
|
+
return pending(this.state, this.targets).map(({ id, componentPath, name, scheduledTime, standing }) => ({
|
|
367
|
+
id,
|
|
368
|
+
componentPath,
|
|
369
|
+
name,
|
|
370
|
+
scheduledTime,
|
|
371
|
+
standing
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Move the clock forward `ms`, running every job whose deadline falls on the
|
|
376
|
+
* way, in deadline order, each starting at its deadline. Jobs those jobs
|
|
377
|
+
* schedule inside the window run too. `advance(0)` runs what is due now and
|
|
378
|
+
* crosses no future deadline.
|
|
379
|
+
*/
|
|
380
|
+
advance(ms, budget = defaultBudget) {
|
|
381
|
+
return this.open().then(() => {
|
|
382
|
+
const until = this.now() + ms;
|
|
383
|
+
return this.walk((pending) => pending.filter((job) => job.scheduledTime <= until), budget).then(() => this.raiseFloor(until));
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Run jobs, earliest deadline first and each at its deadline, until only
|
|
388
|
+
* standing jobs are pending. Every job run counts against `budget`.
|
|
389
|
+
*/
|
|
390
|
+
drain(budget = defaultBudget) {
|
|
391
|
+
return this.open().then(() => this.walk((pending) => pending.filter((job) => !job.standing), budget));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Run the first of `select(pending)` until it is empty, moving the clock to
|
|
395
|
+
* each job's deadline as it starts.
|
|
396
|
+
*/
|
|
397
|
+
walk(select, budget) {
|
|
398
|
+
const step = (ran) => {
|
|
399
|
+
const left = select(pending(this.state, this.targets));
|
|
400
|
+
const next = left[0];
|
|
401
|
+
if (next === void 0) return Promise.resolve();
|
|
402
|
+
if (ran === budget) {
|
|
403
|
+
const pending = functions(left);
|
|
404
|
+
return Promise.reject(new DrainBudgetExceeded({
|
|
405
|
+
budget,
|
|
406
|
+
pending,
|
|
407
|
+
message: `Scheduled functions ran ${budget} jobs and work remains: ${pending.map(({ componentPath, name }) => componentPath === ROOT ? name : `${name} in "${componentPath}"`).join(", ")}. A function that schedules its own next run never lets the drain end, so its kit declares it with registerComponent's \`standing\` option.`
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
this.raiseFloor(next.scheduledTime);
|
|
411
|
+
return this.runJob(next).then(() => step(ran + 1));
|
|
412
|
+
};
|
|
413
|
+
return step(0);
|
|
414
|
+
}
|
|
415
|
+
raiseFloor(to) {
|
|
416
|
+
this.floor = Math.max(this.floor, to);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Run one pending job as its own request, moving its row through in progress
|
|
420
|
+
* to success or failed. `walk` picks it from the committed pending jobs and
|
|
421
|
+
* starts it in the same turn, so no cancel can land in between. The job runs
|
|
422
|
+
* as a fresh invocation of the world that scheduled it, whatever invocation
|
|
423
|
+
* the drain itself runs under.
|
|
424
|
+
*/
|
|
425
|
+
runJob(job) {
|
|
426
|
+
this.commitNow((attempt) => {
|
|
427
|
+
const doc = attempt.get(job.componentPath, job.id);
|
|
428
|
+
attempt.write(job.componentPath, job.id, {
|
|
429
|
+
...doc,
|
|
430
|
+
state: { kind: "inProgress" }
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
const request = jobRequest(job.id);
|
|
434
|
+
const from = {
|
|
435
|
+
caller: anonymous,
|
|
436
|
+
request
|
|
437
|
+
};
|
|
438
|
+
const run = () => Promise.resolve().then(() => {
|
|
439
|
+
const { func } = this.resolve(job.target, "any");
|
|
440
|
+
return func.isQuery ? this.queryAt(job.target, job.args, from) : func.isMutation ? this.mutationAt(job.target, job.args, from) : this.actionAt(job.target, job.args, from);
|
|
441
|
+
});
|
|
442
|
+
return (job.world === void 0 ? unbound(run) : bind(fresh(job.world, job.target.componentPath, request), run)).then(() => ({ kind: "success" }), (error) => ({
|
|
443
|
+
kind: "failed",
|
|
444
|
+
error: error instanceof Error ? error.message : String(error)
|
|
445
|
+
})).then((state) => this.commitNow((attempt) => {
|
|
446
|
+
const doc = attempt.get(job.componentPath, job.id);
|
|
447
|
+
if (doc === null || stateOf(doc).kind !== "inProgress") return;
|
|
448
|
+
attempt.write(job.componentPath, job.id, {
|
|
449
|
+
...doc,
|
|
450
|
+
state,
|
|
451
|
+
completedTime: this.now()
|
|
452
|
+
});
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Write through a fresh attempt and publish it at once, as the scheduler does
|
|
457
|
+
* for an action's schedule and a job's state. Nothing runs between the
|
|
458
|
+
* snapshot and the commit, so no other commit can conflict with it.
|
|
459
|
+
*/
|
|
460
|
+
commitNow(write) {
|
|
461
|
+
const attempt = new KernelAttempt(this.state);
|
|
462
|
+
const result = write(attempt);
|
|
463
|
+
this.publishWrites(attempt.writes());
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Publish `writes` as the next commit, at the runtime's timestamp for it. No
|
|
468
|
+
* writes publish nothing and ask the runtime for no timestamp, which answers
|
|
469
|
+
* the latest commit's.
|
|
470
|
+
*/
|
|
471
|
+
publishWrites(writes) {
|
|
472
|
+
if (writes.length === 0) return this.state.commitTs;
|
|
473
|
+
const commitTs = this.runtime.commitTs(this.state.version + 1);
|
|
474
|
+
if (commitTs <= this.state.commitTs) throw new CommitTsNotIncreasing({
|
|
475
|
+
version: this.state.version + 1,
|
|
476
|
+
previous: this.state.commitTs,
|
|
477
|
+
commitTs,
|
|
478
|
+
message: `The runtime answered commit timestamp ${commitTs} for version ${this.state.version + 1}, which is not greater than the previous commit's ${this.state.commitTs}.`
|
|
479
|
+
});
|
|
480
|
+
this.state = apply(this.state, writes, commitTs);
|
|
481
|
+
return commitTs;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Write a pending job into `attempt`, so the job exists only once that
|
|
485
|
+
* attempt publishes. A job that a standing function schedules, or that runs
|
|
486
|
+
* one, is standing.
|
|
487
|
+
*/
|
|
488
|
+
scheduleJob(attempt, execution, args) {
|
|
489
|
+
const target = functionPathOf(args, execution.componentPath);
|
|
490
|
+
this.component(target.componentPath);
|
|
491
|
+
const jobArgs = jsonToConvex(args.args);
|
|
492
|
+
if (hasPendingValues(jobArgs)) throw new Error(`Invalid arguments for "${target.udfPath}": a scheduled function cannot take an unresolved placeholder value.`);
|
|
493
|
+
const id = this.generateId(table);
|
|
494
|
+
attempt.write(execution.componentPath, id, row(id, this.creationTime(execution.startTime), target.udfPath, jobArgs, args.ts * 1e3));
|
|
495
|
+
this.targets.set(id, {
|
|
496
|
+
functionPath: target,
|
|
497
|
+
standing: this.isStanding({
|
|
498
|
+
componentPath: execution.componentPath,
|
|
499
|
+
udfPath: execution.udfPath
|
|
500
|
+
}) || this.isStanding(target),
|
|
501
|
+
world: current$1()?.world
|
|
502
|
+
});
|
|
503
|
+
return id;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Cancel a job that has not finished. A finished job stays as it ended.
|
|
507
|
+
*/
|
|
508
|
+
cancelJob(attempt, componentPath, id) {
|
|
509
|
+
validateId(table, id);
|
|
510
|
+
attempt.readDocument(componentPath, id);
|
|
511
|
+
const doc = attempt.get(componentPath, id);
|
|
512
|
+
if (doc === null) throw new Error(`Patch on non-existent document with ID "${id}"`);
|
|
513
|
+
const { kind } = stateOf(doc);
|
|
514
|
+
if (kind === "pending" || kind === "inProgress") attempt.write(componentPath, id, {
|
|
515
|
+
...doc,
|
|
516
|
+
state: { kind: "canceled" }
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
isStanding({ componentPath, udfPath }) {
|
|
520
|
+
return this.components.get(componentPath)?.standing.has(udfPath) ?? false;
|
|
521
|
+
}
|
|
306
522
|
queryAt(functionPath, args, from, snapshotOnly = false) {
|
|
307
523
|
const resolved = this.resolve(functionPath, "query");
|
|
308
524
|
validateFunctionArgs(resolved.args, args ?? {}, functionPath, from.parent?.attempt !== void 0);
|
|
@@ -328,6 +544,7 @@ var World = class {
|
|
|
328
544
|
attempt,
|
|
329
545
|
identity,
|
|
330
546
|
request: from.request,
|
|
547
|
+
startTime: from.parent !== void 0 && from.parent.udfType !== "action" ? from.parent.startTime : this.now(),
|
|
331
548
|
pagination,
|
|
332
549
|
turn: new Turn()
|
|
333
550
|
};
|
|
@@ -455,14 +672,12 @@ var World = class {
|
|
|
455
672
|
next: next.view
|
|
456
673
|
}));
|
|
457
674
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}));
|
|
465
|
-
this.state = apply(this.state, writes, commitTs);
|
|
675
|
+
let commitTs;
|
|
676
|
+
try {
|
|
677
|
+
commitTs = this.publishWrites(writes);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
return Promise.reject(error);
|
|
680
|
+
}
|
|
466
681
|
return Promise.resolve({
|
|
467
682
|
kind: "applied",
|
|
468
683
|
version: this.state.version,
|
|
@@ -496,21 +711,7 @@ var World = class {
|
|
|
496
711
|
}, innerArgs) });
|
|
497
712
|
const run = () => enter(execution, () => a.invokeAction(from.request.requestId, JSON.stringify(convexToJson([parseArgs(args)]))));
|
|
498
713
|
const bound = from.parent === void 0 ? void 0 : current$1();
|
|
499
|
-
|
|
500
|
-
return (bound === void 0 ? run() : bind({
|
|
501
|
-
world: bound.world,
|
|
502
|
-
componentPath: functionPath.componentPath,
|
|
503
|
-
attempt: 1,
|
|
504
|
-
request: {
|
|
505
|
-
ip,
|
|
506
|
-
userAgent,
|
|
507
|
-
requestId,
|
|
508
|
-
scheduledFunctionId
|
|
509
|
-
},
|
|
510
|
-
services: bound.world.services,
|
|
511
|
-
startedAt: bound.world.clock(),
|
|
512
|
-
random: bound.world.random
|
|
513
|
-
}, run)).then((raw) => {
|
|
714
|
+
return (bound === void 0 ? run() : bind(fresh(bound.world, functionPath.componentPath, from.request), run)).then((raw) => {
|
|
514
715
|
const result = jsonToConvex(JSON.parse(raw));
|
|
515
716
|
validateReturnValue(resolved.returns, result, "action", functionPath);
|
|
516
717
|
return result;
|
|
@@ -665,10 +866,14 @@ var World = class {
|
|
|
665
866
|
return JSON.stringify(createFunctionHandle(functionPath));
|
|
666
867
|
}
|
|
667
868
|
case "1.0/auditLog": return JSON.stringify(null);
|
|
668
|
-
case "1.0/schedule":
|
|
669
|
-
case "1.0/actions/schedule":
|
|
869
|
+
case "1.0/schedule": return JSON.stringify(convexToJson(this.scheduleJob(transaction(execution), execution, args)));
|
|
870
|
+
case "1.0/actions/schedule": return JSON.stringify(convexToJson(this.commitNow((attempt) => this.scheduleJob(attempt, execution, args))));
|
|
670
871
|
case "1.0/cancel_job":
|
|
671
|
-
|
|
872
|
+
this.cancelJob(transaction(execution), execution.componentPath, args.id);
|
|
873
|
+
return JSON.stringify({});
|
|
874
|
+
case "1.0/actions/cancel_job":
|
|
875
|
+
this.commitNow((attempt) => this.cancelJob(attempt, execution.componentPath, args.id));
|
|
876
|
+
return JSON.stringify({});
|
|
672
877
|
case "1.0/storageDelete":
|
|
673
878
|
case "1.0/storageGetUrl":
|
|
674
879
|
case "1.0/storageGenerateUploadUrl": throw rowPending("storage", 6);
|
|
@@ -690,16 +895,22 @@ var World = class {
|
|
|
690
895
|
const attempt = transaction(execution);
|
|
691
896
|
validateDocument(this.schema(execution.componentPath), table, value);
|
|
692
897
|
const _id = this.generateId(table);
|
|
693
|
-
const now = this.runtime.now();
|
|
694
|
-
const _creationTime = now <= this.lastCreationTime ? this.lastCreationTime + .001 : now;
|
|
695
|
-
this.lastCreationTime = _creationTime;
|
|
696
898
|
attempt.write(execution.componentPath, _id, {
|
|
697
899
|
...value,
|
|
698
900
|
_id,
|
|
699
|
-
_creationTime
|
|
901
|
+
_creationTime: this.creationTime(execution.startTime)
|
|
700
902
|
});
|
|
701
903
|
return _id;
|
|
702
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* A new document's creation time: `at`, or just past the latest one, so no
|
|
907
|
+
* two documents share one.
|
|
908
|
+
*/
|
|
909
|
+
creationTime(at) {
|
|
910
|
+
const time = at <= this.lastCreationTime ? this.lastCreationTime + .001 : at;
|
|
911
|
+
this.lastCreationTime = time;
|
|
912
|
+
return time;
|
|
913
|
+
}
|
|
703
914
|
existing(execution, tableName, id, write, value) {
|
|
704
915
|
validateId(tableName, id);
|
|
705
916
|
if (value !== void 0 && typeof value !== "object") throw new Error(`Invalid argument \`value\` in \`db.${write}\`, expected object but got '${typeof value}': ${value}`);
|
|
@@ -767,23 +978,46 @@ function validateId(expectedTableName, id) {
|
|
|
767
978
|
if (actualTableName !== expectedTableName) throw new Error(`Invalid argument \`id\`, expected ID in table '${expectedTableName}' but got ID in table '${actualTableName}'`);
|
|
768
979
|
}
|
|
769
980
|
/**
|
|
770
|
-
*
|
|
771
|
-
*
|
|
981
|
+
* The backend another library installed as `globalThis.Convex`, which answers
|
|
982
|
+
* every syscall made outside a kernel function.
|
|
983
|
+
*/
|
|
984
|
+
let forwarded;
|
|
985
|
+
const backend = () => executions.getStore()?.world ?? forwarded ?? current().world;
|
|
986
|
+
const dispatcher = {
|
|
987
|
+
[kernelBrand]: true,
|
|
988
|
+
syscall: (op, args) => backend().syscall(op, args),
|
|
989
|
+
asyncSyscall: (op, args) => backend().asyncSyscall(op, args),
|
|
990
|
+
jsSyscall: (op, args) => backend().jsSyscall(op, args)
|
|
991
|
+
};
|
|
992
|
+
/**
|
|
993
|
+
* Convex's server library reaches the backend through `globalThis.Convex`, one
|
|
994
|
+
* global for the whole test runtime. convex-test 0.0.65 assigns its own the
|
|
995
|
+
* first time it builds a world (its `dist/index.js:1936-1951`), whenever that
|
|
996
|
+
* is. The kernel keeps the global an accessor that always reads its dispatcher,
|
|
997
|
+
* and takes an assignment, or the global it found, as the backend for syscalls
|
|
998
|
+
* made outside a kernel function. So a kernel world and a convex-test world
|
|
999
|
+
* stay live together, whichever was built first, and no world restores the
|
|
1000
|
+
* global when it ends. A test that redefines the global removes the accessor,
|
|
1001
|
+
* so every kernel function installs it again over what the test left.
|
|
772
1002
|
*/
|
|
773
1003
|
const installConvexGlobal = () => {
|
|
774
|
-
const
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1004
|
+
const found = globalThis.Convex;
|
|
1005
|
+
if (found?.[kernelBrand] === true) return;
|
|
1006
|
+
forwarded = found;
|
|
1007
|
+
Object.defineProperty(globalThis, "Convex", {
|
|
1008
|
+
get: () => dispatcher,
|
|
1009
|
+
set: (value) => {
|
|
1010
|
+
if (value?.[kernelBrand] !== true) forwarded = value;
|
|
1011
|
+
},
|
|
1012
|
+
configurable: true,
|
|
1013
|
+
enumerable: true
|
|
1014
|
+
});
|
|
782
1015
|
};
|
|
783
1016
|
/**
|
|
784
|
-
* Whether `globalThis.Convex` is the kernel's,
|
|
1017
|
+
* Whether `globalThis.Convex` is the kernel's, and no other backend installed
|
|
1018
|
+
* one behind it.
|
|
785
1019
|
*/
|
|
786
|
-
const ownsConvexGlobal = () => globalThis.Convex?.[kernelBrand] === true;
|
|
1020
|
+
const ownsConvexGlobal = () => globalThis.Convex?.[kernelBrand] === true && forwarded === void 0;
|
|
787
1021
|
//#endregion
|
|
788
1022
|
export { World, anonymous, ownsConvexGlobal, withIdentity };
|
|
789
1023
|
|