@gaganref/confect-test 10.3.1-fork.91 → 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.
@@ -1,6 +1,7 @@
1
1
  import type { Attempt as AttemptNumber } from "./Invocation.js";
2
2
  import * as Kernel from "./Kernel.js";
3
3
  import type { DocumentId, StoredDocument } from "./KernelValues.js";
4
+ import * as Metering from "./Metering.js";
4
5
  import type * as ReadSet from "./ReadSet.js";
5
6
  /**
6
7
  * One frame of an attempt at a query or mutation: the committed state the
@@ -28,12 +29,18 @@ export declare class KernelAttempt {
28
29
  private readonly overlay;
29
30
  private readonly life;
30
31
  private readonly allReads;
32
+ /**
33
+ * The attempt's transaction-limit accounting. A nested mutation's frame
34
+ * meters into its own child, so its writes merge or drop with it while its
35
+ * reads stay.
36
+ */
37
+ readonly meter: Metering.Meter;
31
38
  /**
32
39
  * Whether the attempt asked for its caller's identity, and the time it read.
33
40
  * Every frame of the attempt shares them, as it shares the reads.
34
41
  */
35
42
  private readonly observed;
36
- constructor(snapshot: Kernel.State, number?: AttemptNumber, parent?: KernelAttempt | undefined);
43
+ constructor(snapshot: Kernel.State, number?: AttemptNumber, parent?: KernelAttempt | undefined, meter?: Metering.Meter);
37
44
  get reads(): ReadonlyArray<ReadSet.Read>;
38
45
  /**
39
46
  * Whether the function asked for its caller's identity.
@@ -74,6 +81,12 @@ export declare class KernelAttempt {
74
81
  */
75
82
  scan(componentPath: string, tableName: string): Array<StoredDocument>;
76
83
  write(componentPath: string, id: DocumentId, doc: StoredDocument | null): void;
84
+ /**
85
+ * A query run on this attempt's snapshot rather than its pending writes. It
86
+ * reads and meters on its own, and its meter starts with this attempt's
87
+ * scheduling counters, as the binary's snapshot clone does.
88
+ */
89
+ staleSnapshot(): KernelAttempt;
77
90
  /**
78
91
  * A nested mutation begins, in a frame of its own. Dropping a failed one is
79
92
  * forgetting it.
@@ -1 +1 @@
1
- {"version":3,"file":"Attempt.d.ts","sourceRoot":"","sources":["../../src/internal/Attempt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,KAAK,KAAK,OAAO,MAAM,cAAc,CAAC;AAiB7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAa;IAWtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAZ1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgD;IACrE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IAEtE,YACW,QAAQ,EAAE,MAAM,CAAC,KAAK,EACtB,MAAM,GAAE,aAAiB,EACjB,MAAM,CAAC,EAAE,aAAa,YAAA,EAIxC;IAED,IAAI,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAEvC;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,eAAe,SAEd;IAED,WAAW,CAAC,EAAE,EAAE,MAAM,QAErB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,QAEjD;IAED;;;OAGG;IACH,YAAY,CACV,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,GAC1C,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,GAAG,KAAK,KAAK,IAAI,CAMxC;IAED;;OAEG;IACH,KAAK,IAAI,MAAM,IAAI,CAOlB;IAED;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAOvB;IAED,KAAK,SAEJ;IAED;;OAEG;IACH,OAAO,CAAC,QAAQ;IAMhB,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAMhE;IAED;;;OAGG;IACH,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,CAapE;IAED,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,QAItE;IAED;;;OAGG;IACH,KAAK,IAAI,aAAa,CAErB;IAED;;;OAGG;IACH,KAAK,SASJ;IAED;;OAEG;IACH,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAI5B;CACF"}
1
+ {"version":3,"file":"Attempt.d.ts","sourceRoot":"","sources":["../../src/internal/Attempt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,KAAK,OAAO,MAAM,cAAc,CAAC;AAiB7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAa;IAiBtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAlB1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgD;IACrE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC/B;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IAEtE,YACW,QAAQ,EAAE,MAAM,CAAC,KAAK,EACtB,MAAM,GAAE,aAAiB,EACjB,MAAM,CAAC,EAAE,aAAa,YAAA,EACvC,KAAK,CAAC,EAAE,QAAQ,CAAC,KAAK,EAKvB;IAED,IAAI,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAEvC;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,eAAe,SAEd;IAED,WAAW,CAAC,EAAE,EAAE,MAAM,QAErB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,QAEjD;IAED;;;OAGG;IACH,YAAY,CACV,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,GAC1C,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,GAAG,KAAK,KAAK,IAAI,CAMxC;IAED;;OAEG;IACH,KAAK,IAAI,MAAM,IAAI,CAOlB;IAED;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAOvB;IAED,KAAK,SAEJ;IAED;;OAEG;IACH,OAAO,CAAC,QAAQ;IAMhB,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAMhE;IAED;;;OAGG;IACH,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,CAapE;IAED,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,QAItE;IAED;;;;OAIG;IACH,aAAa,IAAI,aAAa,CAO7B;IAED;;;OAGG;IACH,KAAK,IAAI,aAAa,CAErB;IAED;;;OAGG;IACH,KAAK,SAUJ;IAED;;OAEG;IACH,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAI5B;CACF"}
@@ -1,5 +1,6 @@
1
1
  import { tableNameFromId } from "./KernelValues.js";
2
2
  import { get, rows } from "./Kernel.js";
3
+ import { Meter } from "./Metering.js";
3
4
  import { setImmediate } from "node:timers/promises";
4
5
  //#region src/internal/Attempt.ts
5
6
  /**
@@ -33,15 +34,22 @@ var KernelAttempt = class KernelAttempt {
33
34
  };
34
35
  allReads;
35
36
  /**
37
+ * The attempt's transaction-limit accounting. A nested mutation's frame
38
+ * meters into its own child, so its writes merge or drop with it while its
39
+ * reads stay.
40
+ */
41
+ meter;
42
+ /**
36
43
  * Whether the attempt asked for its caller's identity, and the time it read.
37
44
  * Every frame of the attempt shares them, as it shares the reads.
38
45
  */
39
46
  observed;
40
- constructor(snapshot, number = 1, parent) {
47
+ constructor(snapshot, number = 1, parent, meter) {
41
48
  this.snapshot = snapshot;
42
49
  this.number = number;
43
50
  this.parent = parent;
44
51
  this.allReads = parent?.allReads ?? [];
52
+ this.meter = meter ?? parent?.meter.child() ?? new Meter();
45
53
  this.observed = parent?.observed ?? {
46
54
  identity: false,
47
55
  time: null
@@ -149,6 +157,14 @@ var KernelAttempt = class KernelAttempt {
149
157
  writes.set(id, doc);
150
158
  }
151
159
  /**
160
+ * A query run on this attempt's snapshot rather than its pending writes. It
161
+ * reads and meters on its own, and its meter starts with this attempt's
162
+ * scheduling counters, as the binary's snapshot clone does.
163
+ */
164
+ staleSnapshot() {
165
+ return new KernelAttempt(this.snapshot, this.number, void 0, this.meter.forStaleSnapshot());
166
+ }
167
+ /**
152
168
  * A nested mutation begins, in a frame of its own. Dropping a failed one is
153
169
  * forgetting it.
154
170
  */
@@ -163,6 +179,7 @@ var KernelAttempt = class KernelAttempt {
163
179
  if (this.parent === void 0) throw new Error("The outermost frame of an attempt has no caller.");
164
180
  if (this.life.open) throw new Error("A nested mutation's frame merges only once closed.");
165
181
  for (const [componentPath, writes] of this.overlay) for (const [id, doc] of writes) this.parent.write(componentPath, id, doc);
182
+ this.meter.merge();
166
183
  }
167
184
  /**
168
185
  * The outermost frame's writes, for `Kernel.apply`.
@@ -1 +1 @@
1
- {"version":3,"file":"Attempt.js","names":["Kernel.get","Kernel.rows"],"sources":["../../src/internal/Attempt.ts"],"sourcesContent":["// oxlint-disable-next-line effecttsgo/node-builtin-import, no-restricted-imports -- An attempt's drain waits one real turn of the event loop, which is no deadline. Fake timers in a test replace the global `setImmediate`, not this one.\nimport { setImmediate } from \"node:timers/promises\";\nimport type { Attempt as AttemptNumber } from \"./Invocation.js\";\nimport * as Kernel from \"./Kernel.js\";\nimport type { DocumentId, StoredDocument } from \"./KernelValues.js\";\nimport { tableNameFromId } from \"./KernelValues.js\";\nimport type * as ReadSet from \"./ReadSet.js\";\n\n/**\n * Pending writes, keyed by component path, then document ID. `null` deletes.\n */\ntype Overlay = Map<string, Map<DocumentId, StoredDocument | null>>;\n\n/**\n * How long one frame takes syscalls: those it has running, those it has\n * started, and whether it still takes them.\n */\ninterface Life {\n running: number;\n started: number;\n open: boolean;\n}\n\n/**\n * One frame of an attempt at a query or mutation: the committed state the\n * attempt started from, its caller's frame and its own pending writes. The\n * outermost frame belongs to the top-level function. Each nested mutation opens\n * a child frame, which merges into its caller's frame when it returns and is\n * dropped when it fails. Reads belong to the whole attempt and are never\n * dropped: a failed nested mutation's reads stay in the attempt.\n *\n * Each frame has its own lifetime. A frame takes reads and writes until it is\n * closed. A mutation's frame closes once its function has returned and no\n * syscall it started is still running, so a write a detached fiber makes after\n * the handler settled still lands in it, as on the binary. A nested mutation's\n * frame closes before it merges into its caller's, so no write reaches it after\n * its writes became its caller's.\n *\n * A frame belongs to one invocation, which alone writes to it, so a caller's\n * write made while its nested mutation is in flight stays the caller's.\n * `Kernel.apply` publishes the outermost frame's writes.\n */\nexport class KernelAttempt {\n private readonly overlay: Overlay = new Map();\n private readonly life: Life = { running: 0, started: 0, open: true };\n private readonly allReads: Array<ReadSet.Read>;\n /**\n * Whether the attempt asked for its caller's identity, and the time it read.\n * Every frame of the attempt shares them, as it shares the reads.\n */\n private readonly observed: { identity: boolean; time: number | null };\n\n constructor(\n readonly snapshot: Kernel.State,\n readonly number: AttemptNumber = 1,\n private readonly parent?: KernelAttempt,\n ) {\n this.allReads = parent?.allReads ?? [];\n this.observed = parent?.observed ?? { identity: false, time: null };\n }\n\n get reads(): ReadonlyArray<ReadSet.Read> {\n return this.allReads;\n }\n\n /**\n * Whether the function asked for its caller's identity.\n */\n get observedIdentity(): boolean {\n return this.observed.identity;\n }\n\n /**\n * The pinned time the function read, or `null` when it never read the time.\n */\n get observedTime(): number | null {\n return this.observed.time;\n }\n\n observeIdentity() {\n this.observed.identity = true;\n }\n\n observeTime(at: number) {\n this.observed.time = at;\n }\n\n get closed(): boolean {\n return !this.life.open;\n }\n\n readDocument(componentPath: string, id: DocumentId) {\n this.allReads.push({ kind: \"document\", componentPath, id });\n }\n\n /**\n * Record a read of an interval. The returned function advances how far the\n * query examined it.\n */\n readInterval(\n read: Omit<ReadSet.IntervalRead, \"through\">,\n ): (through: ReadSet.Key | \"end\") => void {\n const interval: ReadSet.IntervalRead = { ...read, through: null };\n this.allReads.push(interval);\n return (through) => {\n interval.through = through;\n };\n }\n\n /**\n * A syscall began. `end` runs when it settles.\n */\n begin(): () => void {\n const life = this.life;\n life.running++;\n life.started++;\n return () => {\n life.running--;\n };\n }\n\n /**\n * Resolves once a whole turn of the event loop passes with no syscall of this\n * frame running or started. A nested call is a syscall of its caller's frame\n * until it has drained.\n */\n drained(): Promise<void> {\n const before = this.life.started;\n return setImmediate().then(() =>\n this.life.running === 0 && this.life.started === before\n ? undefined\n : this.drained(),\n );\n }\n\n close() {\n this.life.open = false;\n }\n\n /**\n * The pending writes this frame sees, from the outermost frame to this one.\n */\n private overlays(): Array<Overlay> {\n return this.parent === undefined\n ? [this.overlay]\n : [...this.parent.overlays(), this.overlay];\n }\n\n get(componentPath: string, id: DocumentId): StoredDocument | null {\n for (const overlay of this.overlays().toReversed()) {\n const write = overlay.get(componentPath)?.get(id);\n if (write !== undefined) return write;\n }\n return Kernel.get(this.snapshot, componentPath, id)?.doc ?? null;\n }\n\n /**\n * The table as this frame sees it, committed rows under every pending write\n * from the outermost frame to this one.\n */\n scan(componentPath: string, tableName: string): Array<StoredDocument> {\n const ids = new Set<DocumentId>();\n for (const row of Kernel.rows(this.snapshot, componentPath, tableName))\n ids.add(row.doc._id);\n for (const overlay of this.overlays())\n for (const id of overlay.get(componentPath)?.keys() ?? [])\n if (tableNameFromId(id) === tableName) ids.add(id);\n const docs: Array<StoredDocument> = [];\n for (const id of ids) {\n const doc = this.get(componentPath, id);\n if (doc !== null) docs.push(doc);\n }\n return docs;\n }\n\n write(componentPath: string, id: DocumentId, doc: StoredDocument | null) {\n const writes = this.overlay.get(componentPath) ?? new Map();\n this.overlay.set(componentPath, writes);\n writes.set(id, doc);\n }\n\n /**\n * A nested mutation begins, in a frame of its own. Dropping a failed one is\n * forgetting it.\n */\n child(): KernelAttempt {\n return new KernelAttempt(this.snapshot, this.number, this);\n }\n\n /**\n * A nested mutation returned and its frame closed. Its writes become its\n * caller's.\n */\n merge() {\n if (this.parent === undefined) {\n throw new Error(\"The outermost frame of an attempt has no caller.\");\n }\n if (this.life.open) {\n throw new Error(\"A nested mutation's frame merges only once closed.\");\n }\n for (const [componentPath, writes] of this.overlay)\n for (const [id, doc] of writes) this.parent.write(componentPath, id, doc);\n }\n\n /**\n * The outermost frame's writes, for `Kernel.apply`.\n */\n writes(): Array<Kernel.Write> {\n return Array.from(this.overlay, ([componentPath, writes]) =>\n Array.from(writes, ([id, doc]) => ({ componentPath, id, doc })),\n ).flat();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Attempt.js","names":["Metering.Meter","Kernel.get","Kernel.rows"],"sources":["../../src/internal/Attempt.ts"],"sourcesContent":["// oxlint-disable-next-line effecttsgo/node-builtin-import, no-restricted-imports -- An attempt's drain waits one real turn of the event loop, which is no deadline. Fake timers in a test replace the global `setImmediate`, not this one.\nimport { setImmediate } from \"node:timers/promises\";\nimport type { Attempt as AttemptNumber } from \"./Invocation.js\";\nimport * as Kernel from \"./Kernel.js\";\nimport type { DocumentId, StoredDocument } from \"./KernelValues.js\";\nimport { tableNameFromId } from \"./KernelValues.js\";\nimport * as Metering from \"./Metering.js\";\nimport type * as ReadSet from \"./ReadSet.js\";\n\n/**\n * Pending writes, keyed by component path, then document ID. `null` deletes.\n */\ntype Overlay = Map<string, Map<DocumentId, StoredDocument | null>>;\n\n/**\n * How long one frame takes syscalls: those it has running, those it has\n * started, and whether it still takes them.\n */\ninterface Life {\n running: number;\n started: number;\n open: boolean;\n}\n\n/**\n * One frame of an attempt at a query or mutation: the committed state the\n * attempt started from, its caller's frame and its own pending writes. The\n * outermost frame belongs to the top-level function. Each nested mutation opens\n * a child frame, which merges into its caller's frame when it returns and is\n * dropped when it fails. Reads belong to the whole attempt and are never\n * dropped: a failed nested mutation's reads stay in the attempt.\n *\n * Each frame has its own lifetime. A frame takes reads and writes until it is\n * closed. A mutation's frame closes once its function has returned and no\n * syscall it started is still running, so a write a detached fiber makes after\n * the handler settled still lands in it, as on the binary. A nested mutation's\n * frame closes before it merges into its caller's, so no write reaches it after\n * its writes became its caller's.\n *\n * A frame belongs to one invocation, which alone writes to it, so a caller's\n * write made while its nested mutation is in flight stays the caller's.\n * `Kernel.apply` publishes the outermost frame's writes.\n */\nexport class KernelAttempt {\n private readonly overlay: Overlay = new Map();\n private readonly life: Life = { running: 0, started: 0, open: true };\n private readonly allReads: Array<ReadSet.Read>;\n /**\n * The attempt's transaction-limit accounting. A nested mutation's frame\n * meters into its own child, so its writes merge or drop with it while its\n * reads stay.\n */\n readonly meter: Metering.Meter;\n /**\n * Whether the attempt asked for its caller's identity, and the time it read.\n * Every frame of the attempt shares them, as it shares the reads.\n */\n private readonly observed: { identity: boolean; time: number | null };\n\n constructor(\n readonly snapshot: Kernel.State,\n readonly number: AttemptNumber = 1,\n private readonly parent?: KernelAttempt,\n meter?: Metering.Meter,\n ) {\n this.allReads = parent?.allReads ?? [];\n this.meter = meter ?? parent?.meter.child() ?? new Metering.Meter();\n this.observed = parent?.observed ?? { identity: false, time: null };\n }\n\n get reads(): ReadonlyArray<ReadSet.Read> {\n return this.allReads;\n }\n\n /**\n * Whether the function asked for its caller's identity.\n */\n get observedIdentity(): boolean {\n return this.observed.identity;\n }\n\n /**\n * The pinned time the function read, or `null` when it never read the time.\n */\n get observedTime(): number | null {\n return this.observed.time;\n }\n\n observeIdentity() {\n this.observed.identity = true;\n }\n\n observeTime(at: number) {\n this.observed.time = at;\n }\n\n get closed(): boolean {\n return !this.life.open;\n }\n\n readDocument(componentPath: string, id: DocumentId) {\n this.allReads.push({ kind: \"document\", componentPath, id });\n }\n\n /**\n * Record a read of an interval. The returned function advances how far the\n * query examined it.\n */\n readInterval(\n read: Omit<ReadSet.IntervalRead, \"through\">,\n ): (through: ReadSet.Key | \"end\") => void {\n const interval: ReadSet.IntervalRead = { ...read, through: null };\n this.allReads.push(interval);\n return (through) => {\n interval.through = through;\n };\n }\n\n /**\n * A syscall began. `end` runs when it settles.\n */\n begin(): () => void {\n const life = this.life;\n life.running++;\n life.started++;\n return () => {\n life.running--;\n };\n }\n\n /**\n * Resolves once a whole turn of the event loop passes with no syscall of this\n * frame running or started. A nested call is a syscall of its caller's frame\n * until it has drained.\n */\n drained(): Promise<void> {\n const before = this.life.started;\n return setImmediate().then(() =>\n this.life.running === 0 && this.life.started === before\n ? undefined\n : this.drained(),\n );\n }\n\n close() {\n this.life.open = false;\n }\n\n /**\n * The pending writes this frame sees, from the outermost frame to this one.\n */\n private overlays(): Array<Overlay> {\n return this.parent === undefined\n ? [this.overlay]\n : [...this.parent.overlays(), this.overlay];\n }\n\n get(componentPath: string, id: DocumentId): StoredDocument | null {\n for (const overlay of this.overlays().toReversed()) {\n const write = overlay.get(componentPath)?.get(id);\n if (write !== undefined) return write;\n }\n return Kernel.get(this.snapshot, componentPath, id)?.doc ?? null;\n }\n\n /**\n * The table as this frame sees it, committed rows under every pending write\n * from the outermost frame to this one.\n */\n scan(componentPath: string, tableName: string): Array<StoredDocument> {\n const ids = new Set<DocumentId>();\n for (const row of Kernel.rows(this.snapshot, componentPath, tableName))\n ids.add(row.doc._id);\n for (const overlay of this.overlays())\n for (const id of overlay.get(componentPath)?.keys() ?? [])\n if (tableNameFromId(id) === tableName) ids.add(id);\n const docs: Array<StoredDocument> = [];\n for (const id of ids) {\n const doc = this.get(componentPath, id);\n if (doc !== null) docs.push(doc);\n }\n return docs;\n }\n\n write(componentPath: string, id: DocumentId, doc: StoredDocument | null) {\n const writes = this.overlay.get(componentPath) ?? new Map();\n this.overlay.set(componentPath, writes);\n writes.set(id, doc);\n }\n\n /**\n * A query run on this attempt's snapshot rather than its pending writes. It\n * reads and meters on its own, and its meter starts with this attempt's\n * scheduling counters, as the binary's snapshot clone does.\n */\n staleSnapshot(): KernelAttempt {\n return new KernelAttempt(\n this.snapshot,\n this.number,\n undefined,\n this.meter.forStaleSnapshot(),\n );\n }\n\n /**\n * A nested mutation begins, in a frame of its own. Dropping a failed one is\n * forgetting it.\n */\n child(): KernelAttempt {\n return new KernelAttempt(this.snapshot, this.number, this);\n }\n\n /**\n * A nested mutation returned and its frame closed. Its writes become its\n * caller's.\n */\n merge() {\n if (this.parent === undefined) {\n throw new Error(\"The outermost frame of an attempt has no caller.\");\n }\n if (this.life.open) {\n throw new Error(\"A nested mutation's frame merges only once closed.\");\n }\n for (const [componentPath, writes] of this.overlay)\n for (const [id, doc] of writes) this.parent.write(componentPath, id, doc);\n this.meter.merge();\n }\n\n /**\n * The outermost frame's writes, for `Kernel.apply`.\n */\n writes(): Array<Kernel.Write> {\n return Array.from(this.overlay, ([componentPath, writes]) =>\n Array.from(writes, ([id, doc]) => ({ componentPath, id, doc })),\n ).flat();\n }\n}\n"],"mappings":"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"}
@@ -239,6 +239,19 @@ export declare class World {
239
239
  private runAction;
240
240
  private schema;
241
241
  private view;
242
+ /**
243
+ * Read one document by ID, as the binary's `get_inner` does for a get and for
244
+ * the document a patch, replace, delete or cancel changes: it reads the ID's
245
+ * interval of the table's `by_id` index, then charges the document if there
246
+ * is one (`crates/database/src/transaction.rs:1188-1250`).
247
+ */
248
+ private readById;
249
+ /**
250
+ * Stage a write to a user table, counted against the execution's limits
251
+ * first. A write over a limit is not staged (`Writes::update`,
252
+ * `crates/database/src/writes.rs:241-310`).
253
+ */
254
+ private stage;
242
255
  private generateId;
243
256
  syscall(op: string, jsonArgs: string): string;
244
257
  /**
@@ -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,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;
@@ -533,7 +534,12 @@ var World = class {
533
534
  this.component(target.componentPath);
534
535
  const jobArgs = jsonToConvex(args.args);
535
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));
536
541
  const id = this.generateId(table);
542
+ attempt.meter.deriveInterval(jobsIndex, point([id]));
537
543
  attempt.write(execution.componentPath, id, row(id, this.creationTime(execution.startTime), target.udfPath, jobArgs, args.ts * 1e3));
538
544
  this.targets.set(id, {
539
545
  functionPath: target,
@@ -548,10 +554,10 @@ var World = class {
548
554
  /**
549
555
  * Cancel a job that has not finished. A finished job stays as it ended.
550
556
  */
551
- cancelJob(attempt, componentPath, id) {
557
+ cancelJob(attempt, execution, id) {
558
+ const { componentPath } = execution;
552
559
  validateId(table, id);
553
- attempt.readDocument(componentPath, id);
554
- const doc = attempt.get(componentPath, id);
560
+ const doc = this.readById(attempt, execution, id);
555
561
  if (doc === null) throw new Error(`Patch on non-existent document with ID "${id}"`);
556
562
  const { kind } = stateOf(doc);
557
563
  if (kind === "pending" || kind === "inProgress") attempt.write(componentPath, id, {
@@ -579,6 +585,8 @@ var World = class {
579
585
  }
580
586
  execution(functionPath, udfType, attempt, from, pagination, startTime) {
581
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);
582
590
  return {
583
591
  world: this,
584
592
  componentPath: functionPath.componentPath,
@@ -589,13 +597,14 @@ var World = class {
589
597
  request: from.request,
590
598
  startTime: startTime ?? (from.parent !== void 0 && from.parent.udfType !== "action" ? from.parent.startTime : this.now()),
591
599
  pagination,
592
- turn: new Turn()
600
+ turn: new Turn(),
601
+ limits
593
602
  };
594
603
  }
595
604
  runQuery(functionPath, resolved, args, from, snapshotOnly = false, top) {
596
605
  const parentAttempt = from.parent?.attempt;
597
606
  const isNested = parentAttempt !== void 0;
598
- const attempt = top?.attempt ?? (parentAttempt === void 0 ? new KernelAttempt(this.state) : snapshotOnly ? new KernelAttempt(parentAttempt.snapshot) : parentAttempt);
607
+ const attempt = top?.attempt ?? (parentAttempt === void 0 ? new KernelAttempt(this.state) : snapshotOnly ? parentAttempt.staleSnapshot() : parentAttempt);
599
608
  const execution = this.execution(functionPath, "query", attempt, from, top?.pagination ?? (isNested && from.parent?.pagination || {
600
609
  calls: 0,
601
610
  journal: null,
@@ -771,16 +780,50 @@ var World = class {
771
780
  }
772
781
  view(execution) {
773
782
  const attempt = transaction(execution);
783
+ const { componentPath, limits } = execution;
774
784
  return {
775
- schema: this.schema(execution.componentPath),
776
- scan: (tableName) => attempt.scan(execution.componentPath, tableName),
777
- read: (interval, after) => attempt.readInterval({
778
- ...interval,
779
- componentPath: execution.componentPath,
780
- after
781
- })
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))
782
800
  };
783
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
+ }
784
827
  generateId(table) {
785
828
  return String(this.nextDocId++).padStart(TYPICAL_CONVEX_ID_LENGTH - table.length, "0") + table;
786
829
  }
@@ -869,6 +912,7 @@ var World = class {
869
912
  return JSON.stringify({});
870
913
  case "1.0/count": {
871
914
  const attempt = transaction(execution);
915
+ refuse(attempt.meter.readInterval(indexKey(execution.componentPath, args.table, "by_id"), everything, execution.limits));
872
916
  attempt.readInterval({
873
917
  kind: "interval",
874
918
  componentPath: execution.componentPath,
@@ -903,10 +947,11 @@ var World = class {
903
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)));
904
948
  }
905
949
  case "1.0/runUdf": {
906
- const { udfType, args: udfArgs } = args;
950
+ const { udfType, args: udfArgs, transactionLimits } = args;
907
951
  const from = {
908
952
  request: execution.request,
909
- parent: execution
953
+ parent: execution,
954
+ budget: budgetOf(transactionLimits)
910
955
  };
911
956
  const functionPath = functionPathOf(args, execution.componentPath);
912
957
  const value = jsonToConvex(udfArgs);
@@ -921,15 +966,17 @@ var World = class {
921
966
  case "1.0/schedule": return JSON.stringify(convexToJson(this.scheduleJob(transaction(execution), execution, args)));
922
967
  case "1.0/actions/schedule": return JSON.stringify(convexToJson(this.commitNow((attempt) => this.scheduleJob(attempt, execution, args))));
923
968
  case "1.0/cancel_job":
924
- this.cancelJob(transaction(execution), execution.componentPath, args.id);
969
+ this.cancelJob(transaction(execution), execution, args.id);
925
970
  return JSON.stringify({});
926
971
  case "1.0/actions/cancel_job":
927
- this.commitNow((attempt) => this.cancelJob(attempt, execution.componentPath, args.id));
972
+ this.commitNow((attempt) => this.cancelJob(attempt, execution, args.id));
928
973
  return JSON.stringify({});
929
974
  case "1.0/storageDelete":
930
975
  case "1.0/storageGetUrl":
931
976
  case "1.0/storageGenerateUploadUrl": throw rowPending("storage", 6);
932
- 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));
933
980
  case "1.0/actions/vectorSearch": throw requiresBinary("vectorSearch");
934
981
  default: throw new Error(`The test kernel does not support async syscall: "${op}"`);
935
982
  }
@@ -939,15 +986,14 @@ var World = class {
939
986
  }
940
987
  get(execution, tableName, id) {
941
988
  validateId(tableName, id);
942
- const attempt = transaction(execution);
943
- attempt.readDocument(execution.componentPath, id);
944
- return attempt.get(execution.componentPath, id);
989
+ return this.readById(transaction(execution), execution, id);
945
990
  }
946
991
  insert(execution, table, value) {
947
992
  const attempt = transaction(execution);
948
993
  validateDocument(this.schema(execution.componentPath), table, value);
949
994
  const _id = this.generateId(table);
950
- attempt.write(execution.componentPath, _id, {
995
+ attempt.meter.deriveInterval(indexKey(execution.componentPath, table, "by_id"), point([_id]));
996
+ this.stage(execution, _id, {
951
997
  ...value,
952
998
  _id,
953
999
  _creationTime: this.creationTime(execution.startTime)
@@ -966,9 +1012,7 @@ var World = class {
966
1012
  existing(execution, tableName, id, write, value) {
967
1013
  validateId(tableName, id);
968
1014
  if (value !== void 0 && typeof value !== "object") throw new Error(`Invalid argument \`value\` in \`db.${write}\`, expected object but got '${typeof value}': ${value}`);
969
- const attempt = transaction(execution);
970
- attempt.readDocument(execution.componentPath, id);
971
- const document = attempt.get(execution.componentPath, id);
1015
+ const document = this.readById(transaction(execution), execution, id);
972
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}"`);
973
1017
  return document;
974
1018
  }
@@ -986,27 +1030,87 @@ var World = class {
986
1030
  ...fields,
987
1031
  ...this.fields(document, value)
988
1032
  };
989
- validateDocument(this.schema(execution.componentPath), tableNameFromId(_id), merged);
990
- transaction(execution).write(execution.componentPath, _id, {
1033
+ const next = {
991
1034
  _id,
992
1035
  _creationTime,
993
1036
  ...merged
994
- });
1037
+ };
1038
+ if (sameDocument(document, next)) return;
1039
+ validateDocument(this.schema(execution.componentPath), tableNameFromId(_id), merged);
1040
+ this.stage(execution, _id, next);
995
1041
  }
996
1042
  replace(execution, tableName, id, value) {
997
1043
  const document = this.existing(execution, tableName, id, "replace", value);
998
1044
  const fields = this.fields(document, value);
999
- validateDocument(this.schema(execution.componentPath), tableNameFromId(document._id), fields);
1000
- transaction(execution).write(execution.componentPath, document._id, {
1045
+ const next = {
1001
1046
  ...fields,
1002
1047
  _id: document._id,
1003
1048
  _creationTime: document._creationTime
1004
- });
1049
+ };
1050
+ if (sameDocument(document, next)) return;
1051
+ validateDocument(this.schema(execution.componentPath), tableNameFromId(document._id), fields);
1052
+ this.stage(execution, document._id, next);
1005
1053
  }
1006
1054
  remove(execution, tableName, id) {
1007
1055
  const document = this.existing(execution, tableName, id, "delete", void 0);
1008
- 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;
1009
1112
  }
1113
+ return budget;
1010
1114
  };
1011
1115
  const closed = (execution, operation) => new ClosedAttempt({
1012
1116
  operation,