@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.
Files changed (56) hide show
  1. package/dist/TestConfect.d.ts +33 -7
  2. package/dist/TestConfect.d.ts.map +1 -1
  3. package/dist/TestConfect.js +11 -6
  4. package/dist/TestConfect.js.map +1 -1
  5. package/dist/internal/Attempt.js.map +1 -1
  6. package/dist/internal/Clock.d.ts +29 -0
  7. package/dist/internal/Clock.d.ts.map +1 -0
  8. package/dist/internal/Clock.js +66 -0
  9. package/dist/internal/Clock.js.map +1 -0
  10. package/dist/internal/ConvexTest.d.ts +33 -1
  11. package/dist/internal/ConvexTest.d.ts.map +1 -1
  12. package/dist/internal/ConvexTest.js +8 -5
  13. package/dist/internal/ConvexTest.js.map +1 -1
  14. package/dist/internal/Execution.d.ts +85 -5
  15. package/dist/internal/Execution.d.ts.map +1 -1
  16. package/dist/internal/Execution.js +285 -51
  17. package/dist/internal/Execution.js.map +1 -1
  18. package/dist/internal/Globals.d.ts.map +1 -1
  19. package/dist/internal/Globals.js +12 -0
  20. package/dist/internal/Globals.js.map +1 -1
  21. package/dist/internal/Invocation.d.ts +11 -0
  22. package/dist/internal/Invocation.d.ts.map +1 -1
  23. package/dist/internal/Invocation.js +34 -21
  24. package/dist/internal/Invocation.js.map +1 -1
  25. package/dist/internal/Kernel.d.ts +3 -1
  26. package/dist/internal/Kernel.d.ts.map +1 -1
  27. package/dist/internal/Kernel.js +2 -2
  28. package/dist/internal/Kernel.js.map +1 -1
  29. package/dist/internal/KernelRefusal.d.ts +25 -0
  30. package/dist/internal/KernelRefusal.d.ts.map +1 -1
  31. package/dist/internal/KernelRefusal.js +22 -1
  32. package/dist/internal/KernelRefusal.js.map +1 -1
  33. package/dist/internal/KernelValues.d.ts +6 -4
  34. package/dist/internal/KernelValues.d.ts.map +1 -1
  35. package/dist/internal/KernelValues.js +20 -5
  36. package/dist/internal/KernelValues.js.map +1 -1
  37. package/dist/internal/MountedHttp.d.ts.map +1 -1
  38. package/dist/internal/MountedHttp.js +3 -3
  39. package/dist/internal/MountedHttp.js.map +1 -1
  40. package/dist/internal/Scheduling.d.ts +71 -0
  41. package/dist/internal/Scheduling.d.ts.map +1 -0
  42. package/dist/internal/Scheduling.js +60 -0
  43. package/dist/internal/Scheduling.js.map +1 -0
  44. package/package.json +3 -3
  45. package/src/TestConfect.ts +62 -15
  46. package/src/internal/Attempt.ts +1 -1
  47. package/src/internal/Clock.ts +95 -0
  48. package/src/internal/ConvexTest.ts +51 -6
  49. package/src/internal/Execution.ts +415 -50
  50. package/src/internal/Globals.ts +20 -0
  51. package/src/internal/Invocation.ts +38 -13
  52. package/src/internal/Kernel.ts +5 -5
  53. package/src/internal/KernelRefusal.ts +24 -0
  54. package/src/internal/KernelValues.ts +44 -4
  55. package/src/internal/MountedHttp.ts +13 -6
  56. 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 and its function modules, each loaded
11
- * once when the world opens.
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, so no other backend answered.
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":"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"}
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) => own(() => executions.run(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(() => void 0);
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
- const commitTs = this.runtime.commitTs(this.state.version + 1);
459
- if (commitTs <= this.state.commitTs) return Promise.reject(new CommitTsNotIncreasing({
460
- version: this.state.version + 1,
461
- previous: this.state.commitTs,
462
- commitTs,
463
- 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}.`
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
- const { ip, userAgent, requestId, scheduledFunctionId } = from.request;
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
- case "1.0/actions/cancel_job": throw rowPending("the scheduler", 5);
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
- * Convex's server library reaches the backend through `globalThis.Convex`. The
771
- * kernel answers each syscall for the function that made it.
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 global = globalThis;
775
- if (global.Convex?.[kernelBrand] === true) return;
776
- global.Convex = {
777
- [kernelBrand]: true,
778
- syscall: (op, args) => current().world.syscall(op, args),
779
- asyncSyscall: (op, args) => current().world.asyncSyscall(op, args),
780
- jsSyscall: (op) => current().world.jsSyscall(op)
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, so no other backend answered.
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