@gaganref/confect-test 10.3.1-fork.92 → 10.3.1-fork.93
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 +26 -0
- package/dist/TestConfect.d.ts.map +1 -1
- package/dist/TestConfect.js +9 -1
- package/dist/TestConfect.js.map +1 -1
- package/dist/internal/ConvexTest.d.ts +26 -1
- package/dist/internal/ConvexTest.d.ts.map +1 -1
- package/dist/internal/ConvexTest.js +55 -27
- package/dist/internal/ConvexTest.js.map +1 -1
- package/dist/internal/Execution.d.ts +138 -1
- package/dist/internal/Execution.d.ts.map +1 -1
- package/dist/internal/Execution.js +595 -182
- package/dist/internal/Execution.js.map +1 -1
- package/dist/internal/KernelRefusal.d.ts +134 -0
- package/dist/internal/KernelRefusal.d.ts.map +1 -1
- package/dist/internal/KernelRefusal.js +134 -1
- package/dist/internal/KernelRefusal.js.map +1 -1
- package/dist/internal/Redaction.d.ts +142 -0
- package/dist/internal/Redaction.d.ts.map +1 -0
- package/dist/internal/Redaction.js +448 -0
- package/dist/internal/Redaction.js.map +1 -0
- package/dist/internal/Snapshot.d.ts +298 -0
- package/dist/internal/Snapshot.d.ts.map +1 -0
- package/dist/internal/Snapshot.js +532 -0
- package/dist/internal/Snapshot.js.map +1 -0
- package/dist/internal/Subscriptions.d.ts +13 -0
- package/dist/internal/Subscriptions.d.ts.map +1 -1
- package/dist/internal/Subscriptions.js +27 -3
- package/dist/internal/Subscriptions.js.map +1 -1
- package/package.json +3 -3
- package/src/TestConfect.ts +63 -4
- package/src/internal/ConvexTest.ts +135 -38
- package/src/internal/Execution.ts +886 -287
- package/src/internal/KernelRefusal.ts +160 -0
- package/src/internal/Redaction.ts +730 -0
- package/src/internal/Snapshot.ts +945 -0
- package/src/internal/Subscriptions.ts +34 -4
|
@@ -5,12 +5,15 @@ import { apply, conflict, empty, wallClock } from "./Kernel.js";
|
|
|
5
5
|
import { defaults, everything, examined, fromBudget, point } from "./Metering.js";
|
|
6
6
|
import { KernelAttempt } from "./Attempt.js";
|
|
7
7
|
import { bind, current as current$1, fresh, unbound } from "./Invocation.js";
|
|
8
|
-
import { ClosedAttempt, CommitTsNotIncreasing, DrainBudgetExceeded, OptimisticConcurrencyControlFailure, RegisteredAfterOpen, StaleAttempt, UnknownStandingSource, requiresBinary, rowPending } from "./KernelRefusal.js";
|
|
8
|
+
import { ClosedAttempt, CommitTsNotIncreasing, DrainBudgetExceeded, OptimisticConcurrencyControlFailure, RegisteredAfterOpen, SnapshotComponentMismatch, SnapshotJobInconsistent, SnapshotSubscribed, SnapshotWorldBusy, SnapshotWorldRunning, SnapshotWorldUnresolved, StaleAttempt, UnknownStandingSource, requiresBinary, rowPending } from "./KernelRefusal.js";
|
|
9
9
|
import { exportSchema, paginate, stream, validateDocument } from "./KernelQuery.js";
|
|
10
|
+
import { Secrets, failure, keep, mark, markValue, marker, none, sensitiveAt, unmark } from "./Redaction.js";
|
|
10
11
|
import { functions, pending, row, stateOf, table } from "./Scheduling.js";
|
|
12
|
+
import { check, contract, decode, encode } from "./Snapshot.js";
|
|
11
13
|
import { actionGeneric, getFunctionAddress, mutationGeneric, queryGeneric } from "convex/server";
|
|
12
14
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
15
|
import { CommitTsPlaceholder, compareValues, convexToJson, jsonToConvex } from "convex/values";
|
|
16
|
+
import * as Result from "effect/Result";
|
|
14
17
|
//#region src/internal/Execution.ts
|
|
15
18
|
const ROOT = "";
|
|
16
19
|
const TYPICAL_CONVEX_ID_LENGTH = 32;
|
|
@@ -98,6 +101,23 @@ const pinnedTime = () => {
|
|
|
98
101
|
* `finishAllScheduledFunctions` defaults to.
|
|
99
102
|
*/
|
|
100
103
|
const defaultBudget = 100;
|
|
104
|
+
const unresolvedWorld = (name, reason) => new SnapshotWorldUnresolved({
|
|
105
|
+
name,
|
|
106
|
+
reason,
|
|
107
|
+
message: reason === "missing" ? `A snapshot job runs under the test world "${name}", which no world passed to restore is named. Pass that world.` : `Two different test worlds are named "${name}", so a snapshot cannot tell their jobs apart. Name each world uniquely.`
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* `shape` placed at `at`, or no placement when there is no shape.
|
|
111
|
+
*/
|
|
112
|
+
const placed = (shape, at = []) => shape === void 0 ? [] : [{
|
|
113
|
+
at,
|
|
114
|
+
shape
|
|
115
|
+
}];
|
|
116
|
+
const running = (registry, count, what) => new SnapshotWorldRunning({
|
|
117
|
+
registry,
|
|
118
|
+
count,
|
|
119
|
+
message: `The test backend is running ${count} ${what}, whose effects a snapshot would split. Await them first.`
|
|
120
|
+
});
|
|
101
121
|
const current = () => {
|
|
102
122
|
const execution = executions.getStore();
|
|
103
123
|
if (execution === void 0) throw new Error("A Convex syscall ran outside any function the test kernel started.");
|
|
@@ -191,6 +211,7 @@ const kindCheck = {
|
|
|
191
211
|
*/
|
|
192
212
|
var World = class {
|
|
193
213
|
runtime;
|
|
214
|
+
redaction;
|
|
194
215
|
components = /* @__PURE__ */ new Map();
|
|
195
216
|
state = empty;
|
|
196
217
|
opened;
|
|
@@ -206,8 +227,31 @@ var World = class {
|
|
|
206
227
|
*/
|
|
207
228
|
floor = Number.NEGATIVE_INFINITY;
|
|
208
229
|
targets = /* @__PURE__ */ new Map();
|
|
209
|
-
|
|
230
|
+
/**
|
|
231
|
+
* The raw secrets this world has seen under a secret schema node. Replay
|
|
232
|
+
* state, kept in memory and never written to an artifact.
|
|
233
|
+
*/
|
|
234
|
+
secrets = new Secrets();
|
|
235
|
+
/**
|
|
236
|
+
* The live registries `Snapshot.contract.live` names, each a count: calls
|
|
237
|
+
* from the test, scheduler walks, function executions (nested and detached
|
|
238
|
+
* ones included) and registered subscribers. Held attempts are `holding`.
|
|
239
|
+
*/
|
|
240
|
+
live = {
|
|
241
|
+
running: 0,
|
|
242
|
+
walks: 0,
|
|
243
|
+
functions: 0,
|
|
244
|
+
subscribers: 0
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* The latest identity version a subscriber's `authenticate` reached. Each
|
|
248
|
+
* subscriber starts at the epoch, and each identity change advances it, so a
|
|
249
|
+
* restored world never hands out a version its source already used.
|
|
250
|
+
*/
|
|
251
|
+
identityEpoch = 0;
|
|
252
|
+
constructor(schema, modules, runtime = wallClock(), redaction = none) {
|
|
210
253
|
this.runtime = runtime;
|
|
254
|
+
this.redaction = redaction;
|
|
211
255
|
installConvexGlobal();
|
|
212
256
|
install(pinnedTime, () => executions.getStore() !== void 0);
|
|
213
257
|
this.components.set(ROOT, makeComponent(ROOT, schema, modules));
|
|
@@ -273,66 +317,74 @@ var World = class {
|
|
|
273
317
|
};
|
|
274
318
|
}
|
|
275
319
|
query(caller, reference, args) {
|
|
276
|
-
return this.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
320
|
+
return this.during(() => {
|
|
321
|
+
return this.open().then(() => {
|
|
322
|
+
const from = {
|
|
323
|
+
caller,
|
|
324
|
+
request: newRequest(caller)
|
|
325
|
+
};
|
|
326
|
+
return typeof reference === "function" ? this.runQuery({
|
|
327
|
+
componentPath: ROOT,
|
|
328
|
+
udfPath: "inline"
|
|
329
|
+
}, {
|
|
330
|
+
handler: reference,
|
|
331
|
+
args: { type: "any" },
|
|
332
|
+
returns: null
|
|
333
|
+
}, args ?? {}, from) : this.queryAt(pathOfReference(reference, ROOT), args, from);
|
|
334
|
+
});
|
|
289
335
|
});
|
|
290
336
|
}
|
|
291
337
|
mutation(caller, reference, args) {
|
|
292
|
-
return this.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
338
|
+
return this.during(() => {
|
|
339
|
+
return this.open().then(() => {
|
|
340
|
+
const from = {
|
|
341
|
+
caller,
|
|
342
|
+
request: newRequest(caller)
|
|
343
|
+
};
|
|
344
|
+
return typeof reference === "function" ? this.runMutation({
|
|
345
|
+
componentPath: ROOT,
|
|
346
|
+
udfPath: "inline"
|
|
347
|
+
}, {
|
|
348
|
+
handler: reference,
|
|
349
|
+
args: { type: "any" },
|
|
350
|
+
returns: null
|
|
351
|
+
}, args ?? {}, from) : this.mutationAt(pathOfReference(reference, ROOT), args, from);
|
|
352
|
+
});
|
|
305
353
|
});
|
|
306
354
|
}
|
|
307
355
|
action(caller, reference, args) {
|
|
308
|
-
return this.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
356
|
+
return this.during(() => {
|
|
357
|
+
return this.open().then(() => {
|
|
358
|
+
const from = {
|
|
359
|
+
caller,
|
|
360
|
+
request: newRequest(caller)
|
|
361
|
+
};
|
|
362
|
+
return typeof reference === "function" ? this.runAction({
|
|
363
|
+
componentPath: ROOT,
|
|
364
|
+
udfPath: "inline"
|
|
365
|
+
}, {
|
|
366
|
+
handler: reference,
|
|
367
|
+
args: { type: "any" },
|
|
368
|
+
returns: null
|
|
369
|
+
}, args ?? {}, from) : this.actionAt(pathOfReference(reference, ROOT), args, from);
|
|
370
|
+
});
|
|
321
371
|
});
|
|
322
372
|
}
|
|
323
373
|
run(caller, componentPath, handler) {
|
|
324
|
-
return this.
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
374
|
+
return this.during(() => {
|
|
375
|
+
return this.open().then(() => {
|
|
376
|
+
this.component(componentPath);
|
|
377
|
+
return this.runMutation({
|
|
378
|
+
componentPath,
|
|
379
|
+
udfPath: "custom"
|
|
380
|
+
}, {
|
|
381
|
+
handler,
|
|
382
|
+
args: { type: "any" },
|
|
383
|
+
returns: null
|
|
384
|
+
}, {}, {
|
|
385
|
+
caller,
|
|
386
|
+
request: newRequest(caller)
|
|
387
|
+
});
|
|
336
388
|
});
|
|
337
389
|
});
|
|
338
390
|
}
|
|
@@ -340,22 +392,27 @@ var World = class {
|
|
|
340
392
|
* Run a mutation to its commit point and hold it as attempt 1.
|
|
341
393
|
*/
|
|
342
394
|
prepare(caller, reference, args) {
|
|
343
|
-
return this.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
395
|
+
return this.during(() => {
|
|
396
|
+
return this.open().then(() => {
|
|
397
|
+
const functionPath = pathOfReference(reference, ROOT);
|
|
398
|
+
const resolved = this.resolve(functionPath, "mutation");
|
|
399
|
+
this.keepArgs(functionPath, args);
|
|
400
|
+
validateFunctionArgs(resolved.args, args ?? {}, functionPath, false);
|
|
401
|
+
return this.prepareMutation(functionPath, resolved, args, {
|
|
402
|
+
caller,
|
|
403
|
+
request: newRequest(caller)
|
|
404
|
+
}, 1, current$1()).then((held) => held.view);
|
|
405
|
+
});
|
|
351
406
|
});
|
|
352
407
|
}
|
|
353
408
|
apply(id) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
409
|
+
return this.during(() => {
|
|
410
|
+
const held = this.holding.get(id);
|
|
411
|
+
return held === void 0 ? Promise.reject(new StaleAttempt({
|
|
412
|
+
id,
|
|
413
|
+
message: `The test kernel holds no attempt "${id}". It was already applied, or never prepared.`
|
|
414
|
+
})) : this.applyHeld(held);
|
|
415
|
+
});
|
|
359
416
|
}
|
|
360
417
|
/**
|
|
361
418
|
* The held attempts, oldest first.
|
|
@@ -370,37 +427,226 @@ var World = class {
|
|
|
370
427
|
* read where an earlier run's ended.
|
|
371
428
|
*/
|
|
372
429
|
subscribed(caller, reference, args, pinned) {
|
|
373
|
-
return this.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
return this.during(() => {
|
|
431
|
+
return this.open().then(() => {
|
|
432
|
+
const functionPath = pathOfReference(reference, ROOT);
|
|
433
|
+
const resolved = this.resolve(functionPath, "query");
|
|
434
|
+
this.keepArgs(functionPath, args);
|
|
435
|
+
validateFunctionArgs(resolved.args, args ?? {}, functionPath, false);
|
|
436
|
+
const top = {
|
|
437
|
+
attempt: new KernelAttempt(this.state),
|
|
438
|
+
pagination: {
|
|
439
|
+
calls: 0,
|
|
440
|
+
journal: pinned.journal,
|
|
441
|
+
end: null
|
|
442
|
+
},
|
|
443
|
+
startTime: pinned.time
|
|
444
|
+
};
|
|
445
|
+
const run = (outcome) => ({
|
|
446
|
+
outcome,
|
|
447
|
+
snapshot: top.attempt.snapshot.version,
|
|
448
|
+
reads: top.attempt.reads,
|
|
449
|
+
observedIdentity: top.attempt.observedIdentity,
|
|
450
|
+
time: top.attempt.observedTime,
|
|
451
|
+
journal: pinned.journal ?? top.pagination.end
|
|
452
|
+
});
|
|
453
|
+
return this.runQuery(functionPath, resolved, args, {
|
|
454
|
+
caller,
|
|
455
|
+
request: newRequest(caller)
|
|
456
|
+
}, false, top).then((value) => run({
|
|
457
|
+
_tag: "returned",
|
|
458
|
+
value
|
|
459
|
+
}), (error) => run({
|
|
460
|
+
_tag: "failed",
|
|
461
|
+
error
|
|
462
|
+
}));
|
|
393
463
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Run `call`, a call from the test, counted as running until it settles.
|
|
468
|
+
*/
|
|
469
|
+
during(call) {
|
|
470
|
+
return this.track("running", call);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Run `work` counted in the live registry `registry` until it settles.
|
|
474
|
+
*/
|
|
475
|
+
track(registry, work) {
|
|
476
|
+
this.live[registry]++;
|
|
477
|
+
const settle = () => {
|
|
478
|
+
this.live[registry]--;
|
|
479
|
+
};
|
|
480
|
+
try {
|
|
481
|
+
return work().finally(settle);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
settle();
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* How many entries each live registry holds now.
|
|
489
|
+
*/
|
|
490
|
+
liveCounts() {
|
|
491
|
+
return {
|
|
492
|
+
running: this.live.running,
|
|
493
|
+
walks: this.live.walks,
|
|
494
|
+
functions: this.live.functions,
|
|
495
|
+
held: this.holding.size,
|
|
496
|
+
subscribers: this.live.subscribers
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Count a subscriber the shim's registry created. Subscribers live as long as
|
|
501
|
+
* their test, so the count never falls.
|
|
502
|
+
*/
|
|
503
|
+
registerSubscriber() {
|
|
504
|
+
this.live.subscribers++;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* The identity version a new subscriber starts at.
|
|
508
|
+
*/
|
|
509
|
+
currentIdentityEpoch() {
|
|
510
|
+
return this.identityEpoch;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* The next identity version, for a subscriber whose identity changed.
|
|
514
|
+
*/
|
|
515
|
+
nextIdentityEpoch() {
|
|
516
|
+
return ++this.identityEpoch;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* The committed state as snapshot bytes: every component's rows and the
|
|
520
|
+
* version that wrote each, the write log, the jobs and their targets, the
|
|
521
|
+
* clock's floor and the identity counters. Each job's world is recorded by
|
|
522
|
+
* name. Two exports of the same state are the same bytes. A world running a
|
|
523
|
+
* call or holding attempts refuses, since a snapshot carries neither.
|
|
524
|
+
*/
|
|
525
|
+
exportSnapshot() {
|
|
526
|
+
this.refuseBusy(0, "export");
|
|
527
|
+
const names = /* @__PURE__ */ new Map();
|
|
528
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
529
|
+
for (const byTable of this.state.tables.values()) for (const id of byTable.get("_scheduled_functions")?.keys() ?? []) {
|
|
530
|
+
const target = this.targets.get(id);
|
|
531
|
+
if (target === void 0) continue;
|
|
532
|
+
const world = target.world;
|
|
533
|
+
if (world !== void 0) {
|
|
534
|
+
if ((names.get(world.name) ?? world) !== world) throw unresolvedWorld(world.name, "ambiguous");
|
|
535
|
+
names.set(world.name, world);
|
|
536
|
+
}
|
|
537
|
+
jobs.set(id, {
|
|
538
|
+
functionPath: target.functionPath,
|
|
539
|
+
standing: target.standing,
|
|
540
|
+
world: world?.name ?? null
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
return encode({
|
|
544
|
+
components: [...this.components.keys()],
|
|
545
|
+
state: this.state,
|
|
546
|
+
floor: this.floor,
|
|
547
|
+
identity: {
|
|
548
|
+
nextDocumentId: this.nextDocId,
|
|
549
|
+
lastCreationTime: this.lastCreationTime,
|
|
550
|
+
nextAttemptId: this.nextAttemptId,
|
|
551
|
+
identityEpoch: this.identityEpoch
|
|
552
|
+
},
|
|
553
|
+
jobs
|
|
554
|
+
}, (componentPath, table, json) => mark(json, this.documentPlacements(componentPath, table, json), this.secrets));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Replace this world's state with a snapshot's. The world must register the
|
|
558
|
+
* snapshot's components, run nothing, and have committed nothing and hold
|
|
559
|
+
* nothing. Each job's world resolves by name among `worlds`. Bytes whose
|
|
560
|
+
* digest does not match, and state the kernel could not have reached, refuse
|
|
561
|
+
* before anything changes.
|
|
562
|
+
*/
|
|
563
|
+
restoreSnapshot(bytes, options = {}) {
|
|
564
|
+
return this.open().then(() => this.restore(bytes, options.worlds ?? [], options.secrets ?? new Secrets()));
|
|
565
|
+
}
|
|
566
|
+
restore(bytes, worlds, secrets) {
|
|
567
|
+
this.refuseBusy(this.state.version, "restore");
|
|
568
|
+
const decoded = decode(bytes, (json) => unmark(json, secrets));
|
|
569
|
+
if (Result.isFailure(decoded)) throw decoded.failure;
|
|
570
|
+
const image = decoded.success;
|
|
571
|
+
const checked = check(image, table);
|
|
572
|
+
if (Result.isFailure(checked)) throw checked.failure;
|
|
573
|
+
const registered = [...this.components.keys()].sort();
|
|
574
|
+
const snapshot = [...image.components].sort();
|
|
575
|
+
if (registered.join("\0") !== snapshot.join("\0")) throw new SnapshotComponentMismatch({
|
|
576
|
+
snapshot,
|
|
577
|
+
registered,
|
|
578
|
+
message: `The snapshot holds components ${snapshot.map((p) => `"${p}"`).join(", ")}, but this test backend registers ${registered.map((p) => `"${p}"`).join(", ")}. Register the same components before restoring.`
|
|
579
|
+
});
|
|
580
|
+
for (const [id, { functionPath }] of image.jobs) if (!this.exports(functionPath)) throw new SnapshotJobInconsistent({
|
|
581
|
+
id,
|
|
582
|
+
reason: "function",
|
|
583
|
+
message: `The snapshot's job ${id} targets ${functionPath.udfPath} in "${functionPath.componentPath}", which that component does not export.`
|
|
584
|
+
});
|
|
585
|
+
const byName = /* @__PURE__ */ new Map();
|
|
586
|
+
for (const world of worlds) {
|
|
587
|
+
if ((byName.get(world.name) ?? world) !== world) throw unresolvedWorld(world.name, "ambiguous");
|
|
588
|
+
byName.set(world.name, world);
|
|
589
|
+
}
|
|
590
|
+
const targets = Array.from(image.jobs, ([id, { functionPath, standing, world }]) => {
|
|
591
|
+
const resolved = world === null ? void 0 : byName.get(world);
|
|
592
|
+
if (world !== null && resolved === void 0) throw unresolvedWorld(world, "missing");
|
|
593
|
+
return [id, {
|
|
594
|
+
functionPath,
|
|
595
|
+
standing,
|
|
596
|
+
world: resolved
|
|
597
|
+
}];
|
|
598
|
+
});
|
|
599
|
+
this.state = image.state;
|
|
600
|
+
this.floor = image.floor;
|
|
601
|
+
this.nextDocId = image.identity.nextDocumentId;
|
|
602
|
+
this.lastCreationTime = image.identity.lastCreationTime;
|
|
603
|
+
this.nextAttemptId = image.identity.nextAttemptId;
|
|
604
|
+
this.identityEpoch = image.identity.identityEpoch;
|
|
605
|
+
this.targets.clear();
|
|
606
|
+
for (const [id, target] of targets) this.targets.set(id, target);
|
|
607
|
+
this.secrets = secrets.copy();
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Whether `functionPath`'s component exports a Convex function there.
|
|
611
|
+
*/
|
|
612
|
+
exports({ componentPath, udfPath }) {
|
|
613
|
+
const [modulePath, exportName = "default"] = udfPath.split(":");
|
|
614
|
+
const func = this.components.get(componentPath)?.loaded.get(modulePath)?.[exportName];
|
|
615
|
+
return func?.isQuery === true || func?.isMutation === true || func?.isAction === true;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Refuse a snapshot while any live registry `operation` waits on is
|
|
619
|
+
* non-empty, or, restoring, once `version` shows the world committed
|
|
620
|
+
* something of its own.
|
|
621
|
+
*/
|
|
622
|
+
refuseBusy(version, operation) {
|
|
623
|
+
const counts = this.liveCounts();
|
|
624
|
+
const refusals = {
|
|
625
|
+
running: (count) => running("running", count, "calls from the test"),
|
|
626
|
+
walks: (count) => running("walks", count, "scheduler walks"),
|
|
627
|
+
functions: (count) => running("functions", count, "functions, nested and detached ones included"),
|
|
628
|
+
held: () => {
|
|
629
|
+
const held = [...this.holding.keys()];
|
|
630
|
+
return new SnapshotWorldBusy({
|
|
631
|
+
version,
|
|
632
|
+
held,
|
|
633
|
+
message: `The test backend holds attempts ${held.join(", ")}, which a snapshot does not carry. Apply them first.`
|
|
634
|
+
});
|
|
635
|
+
},
|
|
636
|
+
subscribers: (count) => new SnapshotSubscribed({
|
|
637
|
+
subscribers: count,
|
|
638
|
+
message: `The test backend has ${count} subscribers, whose identity versions a restore would rewind. Restore into a test backend no client has subscribed to.`
|
|
639
|
+
})
|
|
640
|
+
};
|
|
641
|
+
for (const key of Object.keys(refusals)) {
|
|
642
|
+
const { blocks } = contract.live[key];
|
|
643
|
+
if (operation === "export" && blocks === "restore") continue;
|
|
644
|
+
if (counts[key] > 0) throw refusals[key](counts[key]);
|
|
645
|
+
}
|
|
646
|
+
if (operation === "restore" && version !== 0) throw new SnapshotWorldBusy({
|
|
647
|
+
version,
|
|
648
|
+
held: [],
|
|
649
|
+
message: `The test backend has committed ${version} versions of its own. Restore a snapshot into a fresh test backend.`
|
|
404
650
|
});
|
|
405
651
|
}
|
|
406
652
|
/**
|
|
@@ -422,9 +668,11 @@ var World = class {
|
|
|
422
668
|
* crosses no future deadline.
|
|
423
669
|
*/
|
|
424
670
|
advance(ms, budget = defaultBudget) {
|
|
425
|
-
return this.
|
|
426
|
-
|
|
427
|
-
|
|
671
|
+
return this.track("walks", () => {
|
|
672
|
+
return this.open().then(() => {
|
|
673
|
+
const until = this.now() + ms;
|
|
674
|
+
return this.walk((pending) => pending.filter((job) => job.scheduledTime <= until), budget).then(() => this.raiseFloor(until));
|
|
675
|
+
});
|
|
428
676
|
});
|
|
429
677
|
}
|
|
430
678
|
/**
|
|
@@ -432,7 +680,9 @@ var World = class {
|
|
|
432
680
|
* standing jobs are pending. Every job run counts against `budget`.
|
|
433
681
|
*/
|
|
434
682
|
drain(budget = defaultBudget) {
|
|
435
|
-
return this.
|
|
683
|
+
return this.track("walks", () => {
|
|
684
|
+
return this.open().then(() => this.walk((pending) => pending.filter((job) => !job.standing), budget));
|
|
685
|
+
});
|
|
436
686
|
}
|
|
437
687
|
/**
|
|
438
688
|
* Run the first of `select(pending)` until it is empty, moving the clock to
|
|
@@ -533,6 +783,7 @@ var World = class {
|
|
|
533
783
|
const target = functionPathOf(args, execution.componentPath);
|
|
534
784
|
this.component(target.componentPath);
|
|
535
785
|
const jobArgs = jsonToConvex(args.args);
|
|
786
|
+
this.keepArgs(target, jobArgs);
|
|
536
787
|
if (hasPendingValues(jobArgs)) throw new Error(`Invalid arguments for "${target.udfPath}": a scheduled function cannot take an unresolved placeholder value.`);
|
|
537
788
|
refuse(attempt.meter.schedule(jobArgs, execution.limits));
|
|
538
789
|
const jobsIndex = indexKey(execution.componentPath, table, "by_id");
|
|
@@ -570,16 +821,19 @@ var World = class {
|
|
|
570
821
|
}
|
|
571
822
|
queryAt(functionPath, args, from, snapshotOnly = false) {
|
|
572
823
|
const resolved = this.resolve(functionPath, "query");
|
|
824
|
+
this.keepArgs(functionPath, args);
|
|
573
825
|
validateFunctionArgs(resolved.args, args ?? {}, functionPath, from.parent?.attempt !== void 0);
|
|
574
826
|
return this.runQuery(functionPath, resolved, args, from, snapshotOnly);
|
|
575
827
|
}
|
|
576
828
|
mutationAt(functionPath, args, from) {
|
|
577
829
|
const resolved = this.resolve(functionPath, "mutation");
|
|
830
|
+
this.keepArgs(functionPath, args);
|
|
578
831
|
validateFunctionArgs(resolved.args, args ?? {}, functionPath, from.parent?.attempt !== void 0);
|
|
579
832
|
return this.runMutation(functionPath, resolved, args, from);
|
|
580
833
|
}
|
|
581
834
|
actionAt(functionPath, args, from) {
|
|
582
835
|
const resolved = this.resolve(functionPath, "action");
|
|
836
|
+
this.keepArgs(functionPath, args);
|
|
583
837
|
validateFunctionArgs(resolved.args, args ?? {}, functionPath, false);
|
|
584
838
|
return this.runAction(functionPath, resolved, args, from);
|
|
585
839
|
}
|
|
@@ -602,25 +856,28 @@ var World = class {
|
|
|
602
856
|
};
|
|
603
857
|
}
|
|
604
858
|
runQuery(functionPath, resolved, args, from, snapshotOnly = false, top) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
859
|
+
return this.track("functions", () => {
|
|
860
|
+
const parentAttempt = from.parent?.attempt;
|
|
861
|
+
const isNested = parentAttempt !== void 0;
|
|
862
|
+
const attempt = top?.attempt ?? (parentAttempt === void 0 ? new KernelAttempt(this.state) : snapshotOnly ? parentAttempt.staleSnapshot() : parentAttempt);
|
|
863
|
+
const execution = this.execution(functionPath, "query", attempt, from, top?.pagination ?? (isNested && from.parent?.pagination || {
|
|
864
|
+
calls: 0,
|
|
865
|
+
journal: null,
|
|
866
|
+
end: null
|
|
867
|
+
}), top?.startTime);
|
|
868
|
+
const q = queryGeneric({ handler: (ctx, a) => resolved.handler({
|
|
869
|
+
...ctx,
|
|
870
|
+
auth: queryAuth(execution)
|
|
871
|
+
}, a) });
|
|
872
|
+
return enter(execution, () => q.invokeQuery(JSON.stringify(convexToJson([parseArgs(args)])))).then((raw) => settled(execution, raw)).then((raw) => {
|
|
873
|
+
const result = jsonToConvex(JSON.parse(raw));
|
|
874
|
+
if (!isNested && hasPendingValues(result)) throw new Error(`Function ${functionPath.udfPath} return value invalid: queries cannot return an unresolved placeholder value.`);
|
|
875
|
+
this.keepReturns(functionPath, result);
|
|
876
|
+
validateReturnValue(resolved.returns, result, "query", functionPath);
|
|
877
|
+
return result;
|
|
878
|
+
}).catch((error) => {
|
|
879
|
+
throw deserializeConvexErrorData(error);
|
|
880
|
+
});
|
|
624
881
|
});
|
|
625
882
|
}
|
|
626
883
|
invokeMutation(execution, resolved, args) {
|
|
@@ -630,28 +887,32 @@ var World = class {
|
|
|
630
887
|
}, a) });
|
|
631
888
|
return enter(execution, () => m.invokeMutation(JSON.stringify(convexToJson([parseArgs(args)])))).then((raw) => settled(execution, raw)).then((raw) => {
|
|
632
889
|
const result = jsonToConvex(JSON.parse(raw));
|
|
633
|
-
|
|
890
|
+
const functionPath = {
|
|
634
891
|
componentPath: execution.componentPath,
|
|
635
892
|
udfPath: execution.udfPath
|
|
636
|
-
}
|
|
893
|
+
};
|
|
894
|
+
this.keepReturns(functionPath, result);
|
|
895
|
+
validateReturnValue(resolved.returns, result, "mutation", functionPath);
|
|
637
896
|
return result;
|
|
638
897
|
});
|
|
639
898
|
}
|
|
640
899
|
runMutation(functionPath, resolved, args, from) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
frame.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
900
|
+
return this.track("functions", () => {
|
|
901
|
+
const parentAttempt = from.parent?.attempt;
|
|
902
|
+
if (parentAttempt !== void 0) {
|
|
903
|
+
const frame = parentAttempt.child();
|
|
904
|
+
const execution = this.execution(functionPath, "mutation", frame, from, void 0);
|
|
905
|
+
return this.invokeMutation(execution, resolved, args).then((result) => frame.drained().then(() => {
|
|
906
|
+
frame.close();
|
|
907
|
+
frame.merge();
|
|
908
|
+
return result;
|
|
909
|
+
}), (error) => {
|
|
910
|
+
frame.close();
|
|
911
|
+
throw deserializeConvexErrorData(error);
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
return this.prepareMutation(functionPath, resolved, args, from, 1, current$1()).then((held) => this.publish(held));
|
|
915
|
+
});
|
|
655
916
|
}
|
|
656
917
|
/**
|
|
657
918
|
* Run one attempt of a top-level mutation against the latest commit, then
|
|
@@ -661,34 +922,37 @@ var World = class {
|
|
|
661
922
|
* number.
|
|
662
923
|
*/
|
|
663
924
|
prepareMutation(functionPath, resolved, args, from, number, bound) {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
925
|
+
return this.track("functions", () => {
|
|
926
|
+
const attempt = new KernelAttempt(this.state, number);
|
|
927
|
+
const execution = this.execution(functionPath, "mutation", attempt, from, void 0);
|
|
928
|
+
const run = () => this.invokeMutation(execution, resolved, args);
|
|
929
|
+
return (bound === void 0 ? run() : bind({
|
|
930
|
+
...bound,
|
|
931
|
+
attempt: number
|
|
932
|
+
}, run)).then((value) => attempt.drained().then(() => ({
|
|
933
|
+
_tag: "returned",
|
|
934
|
+
value
|
|
935
|
+
})), (error) => ({
|
|
936
|
+
_tag: "failed",
|
|
937
|
+
error: deserializeConvexErrorData(error)
|
|
938
|
+
})).then((outcome) => {
|
|
939
|
+
attempt.close();
|
|
940
|
+
const held = {
|
|
941
|
+
view: {
|
|
942
|
+
id: `attempt-${this.nextAttemptId++}`,
|
|
943
|
+
functionPath,
|
|
944
|
+
request: from.request.requestId,
|
|
945
|
+
number,
|
|
946
|
+
snapshot: attempt.snapshot.version,
|
|
947
|
+
reads: attempt.reads,
|
|
948
|
+
outcome
|
|
949
|
+
},
|
|
950
|
+
attempt,
|
|
951
|
+
again: (next) => this.prepareMutation(functionPath, resolved, args, from, next, bound)
|
|
952
|
+
};
|
|
953
|
+
this.holding.set(held.view.id, held);
|
|
954
|
+
return held;
|
|
955
|
+
});
|
|
692
956
|
});
|
|
693
957
|
}
|
|
694
958
|
/**
|
|
@@ -753,28 +1017,174 @@ var World = class {
|
|
|
753
1017
|
});
|
|
754
1018
|
}
|
|
755
1019
|
runAction(functionPath, resolved, args, from) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1020
|
+
return this.track("functions", () => {
|
|
1021
|
+
const execution = this.execution(functionPath, "action", void 0, from, void 0);
|
|
1022
|
+
const inner = {
|
|
1023
|
+
request: from.request,
|
|
1024
|
+
parent: execution
|
|
1025
|
+
};
|
|
1026
|
+
const a = actionGeneric({ handler: (ctx, innerArgs) => resolved.handler({
|
|
1027
|
+
...ctx,
|
|
1028
|
+
runQuery: (reference, queryArgs) => this.queryAt(pathOfReference(reference, functionPath.componentPath), queryArgs, inner),
|
|
1029
|
+
runMutation: (reference, mutationArgs) => this.mutationAt(pathOfReference(reference, functionPath.componentPath), mutationArgs, inner),
|
|
1030
|
+
runAction: (reference, actionArgs) => this.actionAt(pathOfReference(reference, functionPath.componentPath), actionArgs, inner),
|
|
1031
|
+
auth: queryAuth(execution)
|
|
1032
|
+
}, innerArgs) });
|
|
1033
|
+
const run = () => enter(execution, () => a.invokeAction(from.request.requestId, JSON.stringify(convexToJson([parseArgs(args)]))));
|
|
1034
|
+
const bound = from.parent === void 0 ? void 0 : current$1();
|
|
1035
|
+
return (bound === void 0 ? run() : bind(fresh(bound.world, functionPath.componentPath, from.request), run)).then((raw) => {
|
|
1036
|
+
const result = jsonToConvex(JSON.parse(raw));
|
|
1037
|
+
this.keepReturns(functionPath, result);
|
|
1038
|
+
validateReturnValue(resolved.returns, result, "action", functionPath);
|
|
1039
|
+
return result;
|
|
1040
|
+
}).catch((error) => {
|
|
1041
|
+
throw deserializeConvexErrorData(error);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
keepArgs(functionPath, args) {
|
|
1046
|
+
keep(args ?? {}, placed(this.redaction.args(functionPath)), this.secrets);
|
|
1047
|
+
}
|
|
1048
|
+
keepReturns(functionPath, result) {
|
|
1049
|
+
keep(result, placed(this.redaction.returns(functionPath)), this.secrets);
|
|
1050
|
+
}
|
|
1051
|
+
keepRow(componentPath, table, value) {
|
|
1052
|
+
keep(value, placed(this.redaction.table(componentPath, table)), this.secrets);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Where a stored document holds secrets: its table's shape at its root, and
|
|
1056
|
+
* for a job's row, its target's argument shape at `args.0`.
|
|
1057
|
+
*/
|
|
1058
|
+
documentPlacements(componentPath, table$1, doc) {
|
|
1059
|
+
const own = placed(this.redaction.table(componentPath, table$1));
|
|
1060
|
+
if (table$1 !== "_scheduled_functions") return own;
|
|
1061
|
+
const target = this.targets.get(doc._id)?.functionPath ?? {
|
|
1062
|
+
componentPath,
|
|
1063
|
+
udfPath: String(doc.name)
|
|
760
1064
|
};
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1065
|
+
return own.concat(placed(this.redaction.args(target), ["args", "0"]));
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* A copy of the raw secrets this world has kept, for a restore of its
|
|
1069
|
+
* snapshots. It lives in memory only.
|
|
1070
|
+
*/
|
|
1071
|
+
keptSecrets() {
|
|
1072
|
+
return this.secrets.copy();
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* The failure report for `thrown`, whatever was thrown, with every kept
|
|
1076
|
+
* secret marked. `thrown` itself is never changed.
|
|
1077
|
+
*/
|
|
1078
|
+
redactFailure(thrown) {
|
|
1079
|
+
return failure(thrown, this.secrets);
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* `outcome` as an artifact writes it: a value marked by the shape of what
|
|
1083
|
+
* `functionPath` returns, a failure as its report.
|
|
1084
|
+
*/
|
|
1085
|
+
redactOutcome(outcome, functionPath) {
|
|
1086
|
+
return outcome._tag === "returned" ? {
|
|
1087
|
+
_tag: "returned",
|
|
1088
|
+
value: markValue(outcome.value, placed(functionPath === void 0 ? void 0 : this.redaction.returns(functionPath)), this.secrets)
|
|
1089
|
+
} : {
|
|
1090
|
+
_tag: "failed",
|
|
1091
|
+
error: this.redactFailure(outcome.error)
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* A changed document as an artifact writes it, marked by its table's shape.
|
|
1096
|
+
*/
|
|
1097
|
+
redactChange(change) {
|
|
1098
|
+
const document = (doc) => doc === null ? null : markValue(doc, this.documentPlacements(change.componentPath, tableNameFromId(doc._id) ?? "", doc), this.secrets);
|
|
1099
|
+
return {
|
|
1100
|
+
...change,
|
|
1101
|
+
before: document(change.before),
|
|
1102
|
+
after: document(change.after)
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* The function a subscription's or call's reference names, if it names one.
|
|
1107
|
+
*/
|
|
1108
|
+
functionPathOf(reference) {
|
|
1109
|
+
try {
|
|
1110
|
+
return typeof reference === "function" ? void 0 : pathOfReference(reference, ROOT);
|
|
1111
|
+
} catch {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Read records as an artifact writes them. A key or range bound on a field
|
|
1117
|
+
* its table's shape holds secret is the marker, and so is any string holding
|
|
1118
|
+
* a kept secret.
|
|
1119
|
+
*/
|
|
1120
|
+
projectReads(reads) {
|
|
1121
|
+
return reads.map((read) => {
|
|
1122
|
+
if (read.kind === "document") return read;
|
|
1123
|
+
const shape = this.redaction.table(read.componentPath, read.table);
|
|
1124
|
+
const sensitive = (fieldPath) => shape !== void 0 && sensitiveAt(shape, fieldPath.split("."));
|
|
1125
|
+
const value = (fieldPath, v) => sensitive(fieldPath) ? marker : markValue(v, [], this.secrets);
|
|
1126
|
+
const key = (k) => k === null ? null : k.map((v, i) => v === void 0 ? void 0 : value(read.fields[i] ?? "", v));
|
|
1127
|
+
return {
|
|
1128
|
+
...read,
|
|
1129
|
+
range: read.range.map((bound) => ({
|
|
1130
|
+
...bound,
|
|
1131
|
+
value: sensitive(bound.fieldPath) ? marker : convexToJson(markValue(jsonToConvex(bound.value), [], this.secrets))
|
|
1132
|
+
})),
|
|
1133
|
+
after: key(read.after),
|
|
1134
|
+
through: read.through === "end" || read.through === null ? read.through : key(read.through)
|
|
1135
|
+
};
|
|
776
1136
|
});
|
|
777
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* A held attempt as an artifact writes it: its read records projected and its
|
|
1140
|
+
* outcome marked.
|
|
1141
|
+
*/
|
|
1142
|
+
projectHeld(view) {
|
|
1143
|
+
return {
|
|
1144
|
+
...view,
|
|
1145
|
+
reads: this.projectReads(view.reads),
|
|
1146
|
+
outcome: this.redactOutcome(view.outcome, view.functionPath)
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* What `apply` answered as an artifact writes it: an applied result marked by
|
|
1151
|
+
* the shape of what `functionPath` returns, a conflict's next attempt
|
|
1152
|
+
* projected, a refusal as its report.
|
|
1153
|
+
*/
|
|
1154
|
+
projectApplied(applied, functionPath) {
|
|
1155
|
+
switch (applied.kind) {
|
|
1156
|
+
case "applied": {
|
|
1157
|
+
const outcome = this.redactOutcome({
|
|
1158
|
+
_tag: "returned",
|
|
1159
|
+
value: applied.result
|
|
1160
|
+
}, functionPath);
|
|
1161
|
+
return outcome._tag === "returned" ? {
|
|
1162
|
+
...applied,
|
|
1163
|
+
result: outcome.value
|
|
1164
|
+
} : applied;
|
|
1165
|
+
}
|
|
1166
|
+
case "conflict": return {
|
|
1167
|
+
...applied,
|
|
1168
|
+
next: this.projectHeld(applied.next)
|
|
1169
|
+
};
|
|
1170
|
+
case "refused": return {
|
|
1171
|
+
...applied,
|
|
1172
|
+
error: this.redactFailure(applied.error)
|
|
1173
|
+
};
|
|
1174
|
+
case "exhausted": return {
|
|
1175
|
+
...applied,
|
|
1176
|
+
error: this.redactFailure(applied.error)
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* A function's arguments as an artifact writes them, marked by the shape of
|
|
1182
|
+
* its arguments. A command log of provider calls (#137's `commands()`) writes
|
|
1183
|
+
* each command's input through this.
|
|
1184
|
+
*/
|
|
1185
|
+
projectArguments(functionPath, args) {
|
|
1186
|
+
return markValue(args, placed(this.redaction.args(functionPath)), this.secrets);
|
|
1187
|
+
}
|
|
778
1188
|
schema(componentPath) {
|
|
779
1189
|
return this.component(componentPath).schema;
|
|
780
1190
|
}
|
|
@@ -990,6 +1400,7 @@ var World = class {
|
|
|
990
1400
|
}
|
|
991
1401
|
insert(execution, table, value) {
|
|
992
1402
|
const attempt = transaction(execution);
|
|
1403
|
+
this.keepRow(execution.componentPath, table, value);
|
|
993
1404
|
validateDocument(this.schema(execution.componentPath), table, value);
|
|
994
1405
|
const _id = this.generateId(table);
|
|
995
1406
|
attempt.meter.deriveInterval(indexKey(execution.componentPath, table, "by_id"), point([_id]));
|
|
@@ -1030,6 +1441,7 @@ var World = class {
|
|
|
1030
1441
|
...fields,
|
|
1031
1442
|
...this.fields(document, value)
|
|
1032
1443
|
};
|
|
1444
|
+
this.keepRow(execution.componentPath, tableNameFromId(_id), merged);
|
|
1033
1445
|
const next = {
|
|
1034
1446
|
_id,
|
|
1035
1447
|
_creationTime,
|
|
@@ -1042,6 +1454,7 @@ var World = class {
|
|
|
1042
1454
|
replace(execution, tableName, id, value) {
|
|
1043
1455
|
const document = this.existing(execution, tableName, id, "replace", value);
|
|
1044
1456
|
const fields = this.fields(document, value);
|
|
1457
|
+
this.keepRow(execution.componentPath, tableNameFromId(document._id), fields);
|
|
1045
1458
|
const next = {
|
|
1046
1459
|
...fields,
|
|
1047
1460
|
_id: document._id,
|