@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.
Files changed (34) hide show
  1. package/dist/internal/Attempt.d.ts +29 -1
  2. package/dist/internal/Attempt.d.ts.map +1 -1
  3. package/dist/internal/Attempt.js +45 -1
  4. package/dist/internal/Attempt.js.map +1 -1
  5. package/dist/internal/ConvexTest.d.ts +23 -2
  6. package/dist/internal/ConvexTest.d.ts.map +1 -1
  7. package/dist/internal/ConvexTest.js +11 -5
  8. package/dist/internal/ConvexTest.js.map +1 -1
  9. package/dist/internal/Execution.d.ts +43 -0
  10. package/dist/internal/Execution.d.ts.map +1 -1
  11. package/dist/internal/Execution.js +200 -41
  12. package/dist/internal/Execution.js.map +1 -1
  13. package/dist/internal/Invocation.d.ts.map +1 -1
  14. package/dist/internal/Invocation.js.map +1 -1
  15. package/dist/internal/KernelQuery.d.ts +8 -3
  16. package/dist/internal/KernelQuery.d.ts.map +1 -1
  17. package/dist/internal/KernelQuery.js +10 -3
  18. package/dist/internal/KernelQuery.js.map +1 -1
  19. package/dist/internal/Metering.d.ts +186 -0
  20. package/dist/internal/Metering.d.ts.map +1 -0
  21. package/dist/internal/Metering.js +401 -0
  22. package/dist/internal/Metering.js.map +1 -0
  23. package/dist/internal/Subscriptions.d.ts +116 -0
  24. package/dist/internal/Subscriptions.d.ts.map +1 -0
  25. package/dist/internal/Subscriptions.js +186 -0
  26. package/dist/internal/Subscriptions.js.map +1 -0
  27. package/package.json +3 -3
  28. package/src/internal/Attempt.ts +52 -0
  29. package/src/internal/ConvexTest.ts +33 -5
  30. package/src/internal/Execution.ts +375 -54
  31. package/src/internal/Invocation.ts +2 -1
  32. package/src/internal/KernelQuery.ts +17 -6
  33. package/src/internal/Metering.ts +520 -0
  34. 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
- return execution.udfType === "action" ? execution.world.now() : execution.startTime;
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, componentPath, id) {
557
+ cancelJob(attempt, execution, id) {
558
+ const { componentPath } = execution;
509
559
  validateId(table, id);
510
- attempt.readDocument(componentPath, id);
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 ? new KernelAttempt(parentAttempt.snapshot) : parentAttempt;
556
- const execution = this.execution(functionPath, "query", attempt, from, isNested && from.parent?.pagination || { calls: 0 });
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(execution.componentPath),
729
- scan: (tableName) => attempt.scan(execution.componentPath, tableName),
730
- read: (interval, after) => attempt.readInterval({
731
- ...interval,
732
- componentPath: execution.componentPath,
733
- after
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
- return JSON.stringify(convexToJson(paginate(this.view(execution), args)));
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.componentPath, args.id);
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.componentPath, args.id));
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": throw rowPending("transaction limits", 6);
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
- const attempt = transaction(execution);
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.write(execution.componentPath, _id, {
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 attempt = transaction(execution);
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
- validateDocument(this.schema(execution.componentPath), tableNameFromId(_id), merged);
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
- validateDocument(this.schema(execution.componentPath), tableNameFromId(document._id), fields);
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
- transaction(execution).write(execution.componentPath, document._id, null);
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: () => Promise.resolve(execution.identity) });
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))