@gaganref/confect-test 10.3.1-fork.90 → 10.3.1-fork.92
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/internal/Attempt.d.ts +29 -1
- package/dist/internal/Attempt.d.ts.map +1 -1
- package/dist/internal/Attempt.js +45 -1
- package/dist/internal/Attempt.js.map +1 -1
- package/dist/internal/ConvexTest.d.ts +23 -2
- package/dist/internal/ConvexTest.d.ts.map +1 -1
- package/dist/internal/ConvexTest.js +11 -5
- package/dist/internal/ConvexTest.js.map +1 -1
- package/dist/internal/Execution.d.ts +43 -0
- package/dist/internal/Execution.d.ts.map +1 -1
- package/dist/internal/Execution.js +200 -41
- package/dist/internal/Execution.js.map +1 -1
- package/dist/internal/Invocation.d.ts.map +1 -1
- package/dist/internal/Invocation.js.map +1 -1
- package/dist/internal/KernelQuery.d.ts +8 -3
- package/dist/internal/KernelQuery.d.ts.map +1 -1
- package/dist/internal/KernelQuery.js +10 -3
- package/dist/internal/KernelQuery.js.map +1 -1
- package/dist/internal/Metering.d.ts +186 -0
- package/dist/internal/Metering.d.ts.map +1 -0
- package/dist/internal/Metering.js +401 -0
- package/dist/internal/Metering.js.map +1 -0
- package/dist/internal/Subscriptions.d.ts +116 -0
- package/dist/internal/Subscriptions.d.ts.map +1 -0
- package/dist/internal/Subscriptions.js +186 -0
- package/dist/internal/Subscriptions.js.map +1 -0
- package/package.json +3 -3
- package/src/internal/Attempt.ts +52 -0
- package/src/internal/ConvexTest.ts +33 -5
- package/src/internal/Execution.ts +375 -54
- package/src/internal/Invocation.ts +2 -1
- package/src/internal/KernelQuery.ts +17 -6
- package/src/internal/Metering.ts +520 -0
- package/src/internal/Subscriptions.ts +302 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { own } from "./Globals.js";
|
|
2
2
|
import { install } from "./Clock.js";
|
|
3
|
-
import { createFunctionHandle, createTestJwt, deserializeConvexErrorData, evaluateValue, hasPendingValues, parseArgs, parseFunctionHandle, resolveCommittedValues, simpleHash, tableNameFromId, validateFunctionArgs, validateReturnValue } from "./KernelValues.js";
|
|
3
|
+
import { createFunctionHandle, createTestJwt, deserializeConvexErrorData, evaluateValue, hasPendingValues, isSimpleObject, parseArgs, parseFunctionHandle, resolveCommittedValues, simpleHash, tableNameFromId, validateFunctionArgs, validateReturnValue } from "./KernelValues.js";
|
|
4
4
|
import { apply, conflict, empty, wallClock } from "./Kernel.js";
|
|
5
|
+
import { defaults, everything, examined, fromBudget, point } from "./Metering.js";
|
|
5
6
|
import { KernelAttempt } from "./Attempt.js";
|
|
6
7
|
import { bind, current as current$1, fresh, unbound } from "./Invocation.js";
|
|
7
8
|
import { ClosedAttempt, CommitTsNotIncreasing, DrainBudgetExceeded, OptimisticConcurrencyControlFailure, RegisteredAfterOpen, StaleAttempt, UnknownStandingSource, requiresBinary, rowPending } from "./KernelRefusal.js";
|
|
@@ -9,7 +10,7 @@ import { exportSchema, paginate, stream, validateDocument } from "./KernelQuery.
|
|
|
9
10
|
import { functions, pending, row, stateOf, table } from "./Scheduling.js";
|
|
10
11
|
import { actionGeneric, getFunctionAddress, mutationGeneric, queryGeneric } from "convex/server";
|
|
11
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
|
-
import { convexToJson, jsonToConvex } from "convex/values";
|
|
13
|
+
import { CommitTsPlaceholder, compareValues, convexToJson, jsonToConvex } from "convex/values";
|
|
13
14
|
//#region src/internal/Execution.ts
|
|
14
15
|
const ROOT = "";
|
|
15
16
|
const TYPICAL_CONVEX_ID_LENGTH = 32;
|
|
@@ -82,12 +83,15 @@ const enter = (execution, run) => {
|
|
|
82
83
|
const kernelBrand = Symbol.for("@confect/test/Kernel");
|
|
83
84
|
/**
|
|
84
85
|
* 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
|
+
* start, or the clock as it runs in an action. Outside one, none. A query or
|
|
87
|
+
* mutation that reads it takes a dependency on that time.
|
|
86
88
|
*/
|
|
87
89
|
const pinnedTime = () => {
|
|
88
90
|
const execution = executions.getStore();
|
|
89
91
|
if (execution === void 0) return void 0;
|
|
90
|
-
|
|
92
|
+
if (execution.udfType === "action") return execution.world.now();
|
|
93
|
+
execution.attempt?.observeTime(execution.startTime);
|
|
94
|
+
return execution.startTime;
|
|
91
95
|
};
|
|
92
96
|
/**
|
|
93
97
|
* How many jobs one drain or advance runs before it gives up, as convex-test's
|
|
@@ -360,6 +364,46 @@ var World = class {
|
|
|
360
364
|
return Array.from(this.holding.values(), (held) => held.view);
|
|
361
365
|
}
|
|
362
366
|
/**
|
|
367
|
+
* Run a query at the latest commit as a subscription runs it, under `caller`,
|
|
368
|
+
* and return its answer with every dependency it took. `time` pins the time
|
|
369
|
+
* it reads, the kernel's time now by default. `journal` ends its paginated
|
|
370
|
+
* read where an earlier run's ended.
|
|
371
|
+
*/
|
|
372
|
+
subscribed(caller, reference, args, pinned) {
|
|
373
|
+
return this.open().then(() => {
|
|
374
|
+
const functionPath = pathOfReference(reference, ROOT);
|
|
375
|
+
const resolved = this.resolve(functionPath, "query");
|
|
376
|
+
validateFunctionArgs(resolved.args, args ?? {}, functionPath, false);
|
|
377
|
+
const top = {
|
|
378
|
+
attempt: new KernelAttempt(this.state),
|
|
379
|
+
pagination: {
|
|
380
|
+
calls: 0,
|
|
381
|
+
journal: pinned.journal,
|
|
382
|
+
end: null
|
|
383
|
+
},
|
|
384
|
+
startTime: pinned.time
|
|
385
|
+
};
|
|
386
|
+
const run = (outcome) => ({
|
|
387
|
+
outcome,
|
|
388
|
+
snapshot: top.attempt.snapshot.version,
|
|
389
|
+
reads: top.attempt.reads,
|
|
390
|
+
observedIdentity: top.attempt.observedIdentity,
|
|
391
|
+
time: top.attempt.observedTime,
|
|
392
|
+
journal: pinned.journal ?? top.pagination.end
|
|
393
|
+
});
|
|
394
|
+
return this.runQuery(functionPath, resolved, args, {
|
|
395
|
+
caller,
|
|
396
|
+
request: newRequest(caller)
|
|
397
|
+
}, false, top).then((value) => run({
|
|
398
|
+
_tag: "returned",
|
|
399
|
+
value
|
|
400
|
+
}), (error) => run({
|
|
401
|
+
_tag: "failed",
|
|
402
|
+
error
|
|
403
|
+
}));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
363
407
|
* The committed pending jobs, earliest deadline first.
|
|
364
408
|
*/
|
|
365
409
|
jobs() {
|
|
@@ -490,7 +534,12 @@ var World = class {
|
|
|
490
534
|
this.component(target.componentPath);
|
|
491
535
|
const jobArgs = jsonToConvex(args.args);
|
|
492
536
|
if (hasPendingValues(jobArgs)) throw new Error(`Invalid arguments for "${target.udfPath}": a scheduled function cannot take an unresolved placeholder value.`);
|
|
537
|
+
refuse(attempt.meter.schedule(jobArgs, execution.limits));
|
|
538
|
+
const jobsIndex = indexKey(execution.componentPath, table, "by_id");
|
|
539
|
+
const parentJob = execution.request.scheduledFunctionId;
|
|
540
|
+
if (parentJob !== null) refuse(attempt.meter.readInterval(jobsIndex, point([parentJob]), execution.limits));
|
|
493
541
|
const id = this.generateId(table);
|
|
542
|
+
attempt.meter.deriveInterval(jobsIndex, point([id]));
|
|
494
543
|
attempt.write(execution.componentPath, id, row(id, this.creationTime(execution.startTime), target.udfPath, jobArgs, args.ts * 1e3));
|
|
495
544
|
this.targets.set(id, {
|
|
496
545
|
functionPath: target,
|
|
@@ -505,10 +554,10 @@ var World = class {
|
|
|
505
554
|
/**
|
|
506
555
|
* Cancel a job that has not finished. A finished job stays as it ended.
|
|
507
556
|
*/
|
|
508
|
-
cancelJob(attempt,
|
|
557
|
+
cancelJob(attempt, execution, id) {
|
|
558
|
+
const { componentPath } = execution;
|
|
509
559
|
validateId(table, id);
|
|
510
|
-
|
|
511
|
-
const doc = attempt.get(componentPath, id);
|
|
560
|
+
const doc = this.readById(attempt, execution, id);
|
|
512
561
|
if (doc === null) throw new Error(`Patch on non-existent document with ID "${id}"`);
|
|
513
562
|
const { kind } = stateOf(doc);
|
|
514
563
|
if (kind === "pending" || kind === "inProgress") attempt.write(componentPath, id, {
|
|
@@ -534,8 +583,10 @@ var World = class {
|
|
|
534
583
|
validateFunctionArgs(resolved.args, args ?? {}, functionPath, false);
|
|
535
584
|
return this.runAction(functionPath, resolved, args, from);
|
|
536
585
|
}
|
|
537
|
-
execution(functionPath, udfType, attempt, from, pagination) {
|
|
586
|
+
execution(functionPath, udfType, attempt, from, pagination, startTime) {
|
|
538
587
|
const identity = functionPath.componentPath !== ROOT ? null : from.parent !== void 0 ? from.parent.identity : from.caller.identity;
|
|
588
|
+
const inherited = attempt === void 0 || from.parent?.attempt === void 0 ? defaults : from.parent.limits;
|
|
589
|
+
const limits = attempt === void 0 || from.budget === void 0 ? inherited : fromBudget(from.budget, attempt.meter.used(), inherited);
|
|
539
590
|
return {
|
|
540
591
|
world: this,
|
|
541
592
|
componentPath: functionPath.componentPath,
|
|
@@ -544,16 +595,21 @@ var World = class {
|
|
|
544
595
|
attempt,
|
|
545
596
|
identity,
|
|
546
597
|
request: from.request,
|
|
547
|
-
startTime: from.parent !== void 0 && from.parent.udfType !== "action" ? from.parent.startTime : this.now(),
|
|
598
|
+
startTime: startTime ?? (from.parent !== void 0 && from.parent.udfType !== "action" ? from.parent.startTime : this.now()),
|
|
548
599
|
pagination,
|
|
549
|
-
turn: new Turn()
|
|
600
|
+
turn: new Turn(),
|
|
601
|
+
limits
|
|
550
602
|
};
|
|
551
603
|
}
|
|
552
|
-
runQuery(functionPath, resolved, args, from, snapshotOnly = false) {
|
|
604
|
+
runQuery(functionPath, resolved, args, from, snapshotOnly = false, top) {
|
|
553
605
|
const parentAttempt = from.parent?.attempt;
|
|
554
606
|
const isNested = parentAttempt !== void 0;
|
|
555
|
-
const attempt = parentAttempt === void 0 ? new KernelAttempt(this.state) : snapshotOnly ?
|
|
556
|
-
const execution = this.execution(functionPath, "query", attempt, from, isNested && from.parent?.pagination || {
|
|
607
|
+
const attempt = top?.attempt ?? (parentAttempt === void 0 ? new KernelAttempt(this.state) : snapshotOnly ? parentAttempt.staleSnapshot() : parentAttempt);
|
|
608
|
+
const execution = this.execution(functionPath, "query", attempt, from, top?.pagination ?? (isNested && from.parent?.pagination || {
|
|
609
|
+
calls: 0,
|
|
610
|
+
journal: null,
|
|
611
|
+
end: null
|
|
612
|
+
}), top?.startTime);
|
|
557
613
|
const q = queryGeneric({ handler: (ctx, a) => resolved.handler({
|
|
558
614
|
...ctx,
|
|
559
615
|
auth: queryAuth(execution)
|
|
@@ -724,16 +780,50 @@ var World = class {
|
|
|
724
780
|
}
|
|
725
781
|
view(execution) {
|
|
726
782
|
const attempt = transaction(execution);
|
|
783
|
+
const { componentPath, limits } = execution;
|
|
727
784
|
return {
|
|
728
|
-
schema: this.schema(
|
|
729
|
-
scan: (tableName) => attempt.scan(
|
|
730
|
-
read: (interval, after) =>
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
785
|
+
schema: this.schema(componentPath),
|
|
786
|
+
scan: (tableName) => attempt.scan(componentPath, tableName),
|
|
787
|
+
read: ({ index, ...interval }, after) => {
|
|
788
|
+
const advance = attempt.readInterval({
|
|
789
|
+
...interval,
|
|
790
|
+
componentPath,
|
|
791
|
+
after
|
|
792
|
+
});
|
|
793
|
+
return (through) => {
|
|
794
|
+
advance(through);
|
|
795
|
+
const examined$1 = examined(interval.range, interval.order, after, through);
|
|
796
|
+
if (examined$1 !== null) refuse(attempt.meter.readInterval(indexKey(componentPath, interval.table, index), examined$1, limits));
|
|
797
|
+
};
|
|
798
|
+
},
|
|
799
|
+
examine: (table, doc) => refuse(attempt.meter.readDocument(doc, isSystemTable(table), limits))
|
|
735
800
|
};
|
|
736
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* Read one document by ID, as the binary's `get_inner` does for a get and for
|
|
804
|
+
* the document a patch, replace, delete or cancel changes: it reads the ID's
|
|
805
|
+
* interval of the table's `by_id` index, then charges the document if there
|
|
806
|
+
* is one (`crates/database/src/transaction.rs:1188-1250`).
|
|
807
|
+
*/
|
|
808
|
+
readById(attempt, execution, id) {
|
|
809
|
+
const { componentPath, limits } = execution;
|
|
810
|
+
const table = tableNameFromId(id);
|
|
811
|
+
attempt.readDocument(componentPath, id);
|
|
812
|
+
refuse(attempt.meter.readInterval(indexKey(componentPath, table, "by_id"), point([id]), limits));
|
|
813
|
+
const doc = attempt.get(componentPath, id);
|
|
814
|
+
if (doc !== null) refuse(attempt.meter.readDocument(doc, isSystemTable(table), limits));
|
|
815
|
+
return doc;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Stage a write to a user table, counted against the execution's limits
|
|
819
|
+
* first. A write over a limit is not staged (`Writes::update`,
|
|
820
|
+
* `crates/database/src/writes.rs:241-310`).
|
|
821
|
+
*/
|
|
822
|
+
stage(execution, id, doc) {
|
|
823
|
+
const attempt = transaction(execution);
|
|
824
|
+
refuse(attempt.meter.write(doc, execution.limits));
|
|
825
|
+
attempt.write(execution.componentPath, id, doc);
|
|
826
|
+
}
|
|
737
827
|
generateId(table) {
|
|
738
828
|
return String(this.nextDocId++).padStart(TYPICAL_CONVEX_ID_LENGTH - table.length, "0") + table;
|
|
739
829
|
}
|
|
@@ -803,7 +893,12 @@ var World = class {
|
|
|
803
893
|
pagination.calls += 1;
|
|
804
894
|
if (pagination.calls > 1) throw new Error("Only a single paginated query (`.paginate()`) is allowed per query, including nested query calls and component calls. This is a Convex limit: https://docs.convex.dev/database/pagination");
|
|
805
895
|
}
|
|
806
|
-
|
|
896
|
+
const page = paginate(this.view(execution), {
|
|
897
|
+
...args,
|
|
898
|
+
endCursor: args.endCursor ?? pagination?.journal ?? null
|
|
899
|
+
});
|
|
900
|
+
if (pagination) pagination.end = page.continueCursor;
|
|
901
|
+
return JSON.stringify(convexToJson(page));
|
|
807
902
|
}
|
|
808
903
|
case "1.0/insert": return JSON.stringify({ _id: this.insert(execution, args.table, jsonToConvex(args.value)) });
|
|
809
904
|
case "1.0/shallowMerge":
|
|
@@ -817,6 +912,7 @@ var World = class {
|
|
|
817
912
|
return JSON.stringify({});
|
|
818
913
|
case "1.0/count": {
|
|
819
914
|
const attempt = transaction(execution);
|
|
915
|
+
refuse(attempt.meter.readInterval(indexKey(execution.componentPath, args.table, "by_id"), everything, execution.limits));
|
|
820
916
|
attempt.readInterval({
|
|
821
917
|
kind: "interval",
|
|
822
918
|
componentPath: execution.componentPath,
|
|
@@ -851,10 +947,11 @@ var World = class {
|
|
|
851
947
|
return (op === "1.0/actions/query" ? this.queryAt(functionPath, args.args, from) : op === "1.0/actions/mutation" ? this.mutationAt(functionPath, args.args, from) : this.actionAt(functionPath, args.args, from)).then((value) => JSON.stringify(convexToJson(value)));
|
|
852
948
|
}
|
|
853
949
|
case "1.0/runUdf": {
|
|
854
|
-
const { udfType, args: udfArgs } = args;
|
|
950
|
+
const { udfType, args: udfArgs, transactionLimits } = args;
|
|
855
951
|
const from = {
|
|
856
952
|
request: execution.request,
|
|
857
|
-
parent: execution
|
|
953
|
+
parent: execution,
|
|
954
|
+
budget: budgetOf(transactionLimits)
|
|
858
955
|
};
|
|
859
956
|
const functionPath = functionPathOf(args, execution.componentPath);
|
|
860
957
|
const value = jsonToConvex(udfArgs);
|
|
@@ -869,15 +966,17 @@ var World = class {
|
|
|
869
966
|
case "1.0/schedule": return JSON.stringify(convexToJson(this.scheduleJob(transaction(execution), execution, args)));
|
|
870
967
|
case "1.0/actions/schedule": return JSON.stringify(convexToJson(this.commitNow((attempt) => this.scheduleJob(attempt, execution, args))));
|
|
871
968
|
case "1.0/cancel_job":
|
|
872
|
-
this.cancelJob(transaction(execution), execution
|
|
969
|
+
this.cancelJob(transaction(execution), execution, args.id);
|
|
873
970
|
return JSON.stringify({});
|
|
874
971
|
case "1.0/actions/cancel_job":
|
|
875
|
-
this.commitNow((attempt) => this.cancelJob(attempt, execution
|
|
972
|
+
this.commitNow((attempt) => this.cancelJob(attempt, execution, args.id));
|
|
876
973
|
return JSON.stringify({});
|
|
877
974
|
case "1.0/storageDelete":
|
|
878
975
|
case "1.0/storageGetUrl":
|
|
879
976
|
case "1.0/storageGenerateUploadUrl": throw rowPending("storage", 6);
|
|
880
|
-
case "1.0/getTransactionMetrics":
|
|
977
|
+
case "1.0/getTransactionMetrics":
|
|
978
|
+
if (execution.attempt === void 0) throw new Error(`Unknown async operation ${op}`);
|
|
979
|
+
return JSON.stringify(execution.attempt.meter.metrics(execution.limits));
|
|
881
980
|
case "1.0/actions/vectorSearch": throw requiresBinary("vectorSearch");
|
|
882
981
|
default: throw new Error(`The test kernel does not support async syscall: "${op}"`);
|
|
883
982
|
}
|
|
@@ -887,15 +986,14 @@ var World = class {
|
|
|
887
986
|
}
|
|
888
987
|
get(execution, tableName, id) {
|
|
889
988
|
validateId(tableName, id);
|
|
890
|
-
|
|
891
|
-
attempt.readDocument(execution.componentPath, id);
|
|
892
|
-
return attempt.get(execution.componentPath, id);
|
|
989
|
+
return this.readById(transaction(execution), execution, id);
|
|
893
990
|
}
|
|
894
991
|
insert(execution, table, value) {
|
|
895
992
|
const attempt = transaction(execution);
|
|
896
993
|
validateDocument(this.schema(execution.componentPath), table, value);
|
|
897
994
|
const _id = this.generateId(table);
|
|
898
|
-
attempt.
|
|
995
|
+
attempt.meter.deriveInterval(indexKey(execution.componentPath, table, "by_id"), point([_id]));
|
|
996
|
+
this.stage(execution, _id, {
|
|
899
997
|
...value,
|
|
900
998
|
_id,
|
|
901
999
|
_creationTime: this.creationTime(execution.startTime)
|
|
@@ -914,9 +1012,7 @@ var World = class {
|
|
|
914
1012
|
existing(execution, tableName, id, write, value) {
|
|
915
1013
|
validateId(tableName, id);
|
|
916
1014
|
if (value !== void 0 && typeof value !== "object") throw new Error(`Invalid argument \`value\` in \`db.${write}\`, expected object but got '${typeof value}': ${value}`);
|
|
917
|
-
const
|
|
918
|
-
attempt.readDocument(execution.componentPath, id);
|
|
919
|
-
const document = attempt.get(execution.componentPath, id);
|
|
1015
|
+
const document = this.readById(transaction(execution), execution, id);
|
|
920
1016
|
if (document === null) throw new Error(write === "delete" ? "Delete on non-existent doc" : `${write === "patch" ? "Patch" : "Replace"} on non-existent document with ID "${id}"`);
|
|
921
1017
|
return document;
|
|
922
1018
|
}
|
|
@@ -934,27 +1030,87 @@ var World = class {
|
|
|
934
1030
|
...fields,
|
|
935
1031
|
...this.fields(document, value)
|
|
936
1032
|
};
|
|
937
|
-
|
|
938
|
-
transaction(execution).write(execution.componentPath, _id, {
|
|
1033
|
+
const next = {
|
|
939
1034
|
_id,
|
|
940
1035
|
_creationTime,
|
|
941
1036
|
...merged
|
|
942
|
-
}
|
|
1037
|
+
};
|
|
1038
|
+
if (sameDocument(document, next)) return;
|
|
1039
|
+
validateDocument(this.schema(execution.componentPath), tableNameFromId(_id), merged);
|
|
1040
|
+
this.stage(execution, _id, next);
|
|
943
1041
|
}
|
|
944
1042
|
replace(execution, tableName, id, value) {
|
|
945
1043
|
const document = this.existing(execution, tableName, id, "replace", value);
|
|
946
1044
|
const fields = this.fields(document, value);
|
|
947
|
-
|
|
948
|
-
transaction(execution).write(execution.componentPath, document._id, {
|
|
1045
|
+
const next = {
|
|
949
1046
|
...fields,
|
|
950
1047
|
_id: document._id,
|
|
951
1048
|
_creationTime: document._creationTime
|
|
952
|
-
}
|
|
1049
|
+
};
|
|
1050
|
+
if (sameDocument(document, next)) return;
|
|
1051
|
+
validateDocument(this.schema(execution.componentPath), tableNameFromId(document._id), fields);
|
|
1052
|
+
this.stage(execution, document._id, next);
|
|
953
1053
|
}
|
|
954
1054
|
remove(execution, tableName, id) {
|
|
955
1055
|
const document = this.existing(execution, tableName, id, "delete", void 0);
|
|
956
|
-
|
|
1056
|
+
this.stage(execution, document._id, null);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
const refuse = (error) => {
|
|
1060
|
+
if (error !== void 0) throw error;
|
|
1061
|
+
};
|
|
1062
|
+
/**
|
|
1063
|
+
* One index of one table in one component, as the binary keys a read set.
|
|
1064
|
+
*/
|
|
1065
|
+
const indexKey = (componentPath, table, index) => `${componentPath}\u0000${table}\u0000${index}`;
|
|
1066
|
+
const isSystemTable = (table) => table.startsWith("_");
|
|
1067
|
+
const isFieldMap = (value) => typeof value === "object" && value !== null && isSimpleObject(value);
|
|
1068
|
+
/**
|
|
1069
|
+
* Whether two stored values are the same value, an unresolved commit timestamp
|
|
1070
|
+
* included. The binary compares pending values structurally, so a pending
|
|
1071
|
+
* timestamp equals only another pending timestamp, at any depth (`PendingValue`
|
|
1072
|
+
* derives `PartialEq`, `crates/value/src/pending.rs:56-75`, as
|
|
1073
|
+
* `PendingDocument` does, `crates/common/src/document.rs:624-632`). Concrete
|
|
1074
|
+
* values compare by `ConvexValue`'s total order
|
|
1075
|
+
* (`crates/value/src/sorting.rs:468-472`), which `compareValues` follows.
|
|
1076
|
+
* `compareValues` alone ranks a pending timestamp as the largest int64, so it
|
|
1077
|
+
* cannot tell the two apart. An object field set to `undefined` is absent.
|
|
1078
|
+
*/
|
|
1079
|
+
const sameValue = (a, b) => {
|
|
1080
|
+
const aPending = a instanceof CommitTsPlaceholder;
|
|
1081
|
+
const bPending = b instanceof CommitTsPlaceholder;
|
|
1082
|
+
if (aPending || bPending) return aPending && bPending;
|
|
1083
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((value, i) => sameValue(value, b[i]));
|
|
1084
|
+
if (isFieldMap(a) || isFieldMap(b)) {
|
|
1085
|
+
if (!isFieldMap(a) || !isFieldMap(b)) return false;
|
|
1086
|
+
const fields = (value) => Object.entries(value).filter(([, field]) => field !== void 0);
|
|
1087
|
+
const aFields = fields(a);
|
|
1088
|
+
const bFields = new Map(fields(b));
|
|
1089
|
+
return aFields.length === bFields.size && aFields.every(([key, field]) => bFields.has(key) && sameValue(field, bFields.get(key)));
|
|
1090
|
+
}
|
|
1091
|
+
return compareValues(a, b) === 0;
|
|
1092
|
+
};
|
|
1093
|
+
/**
|
|
1094
|
+
* Whether a patch or replace leaves a document as it was. The binary then
|
|
1095
|
+
* writes nothing (`patch_inner` and `replace_inner`,
|
|
1096
|
+
* `crates/database/src/transaction.rs:713-790`).
|
|
1097
|
+
*/
|
|
1098
|
+
const sameDocument = (before, after) => sameValue(before, after);
|
|
1099
|
+
/**
|
|
1100
|
+
* A nested call's `transactionLimits`. The binary reads each field as an
|
|
1101
|
+
* unsigned integer and ignores fields it does not know (`TransactionLimits`,
|
|
1102
|
+
* `crates/database/src/execution_size.rs:62-95`).
|
|
1103
|
+
*/
|
|
1104
|
+
const budgetOf = (limits) => {
|
|
1105
|
+
if (limits === void 0 || limits === null) return void 0;
|
|
1106
|
+
const budget = {};
|
|
1107
|
+
for (const metric of Object.keys(defaults)) {
|
|
1108
|
+
const value = limits[metric];
|
|
1109
|
+
if (value === void 0) continue;
|
|
1110
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`Invalid arguments for \`runUdf\`: transactionLimits.${metric} must be a non-negative integer, got ${JSON.stringify(value)}`);
|
|
1111
|
+
budget[metric] = value;
|
|
957
1112
|
}
|
|
1113
|
+
return budget;
|
|
958
1114
|
};
|
|
959
1115
|
const closed = (execution, operation) => new ClosedAttempt({
|
|
960
1116
|
operation,
|
|
@@ -964,7 +1120,10 @@ const transaction = (execution) => {
|
|
|
964
1120
|
if (execution.attempt === void 0) throw new Error("The database is only available in queries and mutations. An action reads and writes through `ctx.runQuery` and `ctx.runMutation`.");
|
|
965
1121
|
return execution.attempt;
|
|
966
1122
|
};
|
|
967
|
-
const queryAuth = (execution) => ({ getUserIdentity: () =>
|
|
1123
|
+
const queryAuth = (execution) => ({ getUserIdentity: () => {
|
|
1124
|
+
execution.attempt?.observeIdentity();
|
|
1125
|
+
return Promise.resolve(execution.identity);
|
|
1126
|
+
} });
|
|
968
1127
|
const mutationAuth = (execution) => ({
|
|
969
1128
|
...queryAuth(execution),
|
|
970
1129
|
signJwt: () => Promise.reject(rowPending("JWT signing", 6))
|