@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
@@ -0,0 +1,401 @@
1
+ import { evaluateQueryValue } from "./KernelValues.js";
2
+ import * as Schema from "effect/Schema";
3
+ import { compareValues, getConvexSize } from "convex/values";
4
+ //#region src/internal/Metering.ts
5
+ /**
6
+ * The binary's default ceilings (`crates/common/src/knobs.rs`).
7
+ */
8
+ const defaults = {
9
+ bytesRead: 1 << 24,
10
+ documentsRead: 32e3,
11
+ databaseQueries: 4096,
12
+ documentsWritten: 16e3,
13
+ bytesWritten: 1 << 24,
14
+ functionsScheduled: 1e3,
15
+ scheduledFunctionArgsBytes: 1 << 24,
16
+ filesWritten: 10,
17
+ fileWriteBytes: 1 << 24,
18
+ filesRead: 10,
19
+ fileReadBytes: 1 << 24
20
+ };
21
+ /**
22
+ * A nested call's ceilings: each one what the transaction has used plus the
23
+ * call's budget, never above the caller's (`TransactionLimits::from_budget`,
24
+ * execution_size.rs:97-160). An unset field leaves the caller's ceiling.
25
+ */
26
+ const fromBudget = (budget, used, ceiling) => Object.fromEntries(Object.keys(ceiling).map((metric) => [metric, Math.min(used[metric] + (budget[metric] ?? ceiling[metric]), ceiling[metric])]));
27
+ const overLimitHelp = "Consider using smaller limits in your queries, paginating your queries, or using indexed queries with a selective index range expressions.";
28
+ /**
29
+ * A function execution crossed one of its ceilings. `code` is the binary's
30
+ * short message and `message` its text, which the function receives as a
31
+ * catchable error.
32
+ */
33
+ var TransactionLimitExceeded = class extends Schema.TaggedError()("TransactionLimitExceeded", {
34
+ code: Schema.Literals([
35
+ "TooManyReads",
36
+ "TooManyDocumentsRead",
37
+ "TooManyBytesRead",
38
+ "TooManyWrites",
39
+ "TooManyBytesWritten",
40
+ "TooManyFunctionsScheduled",
41
+ "ScheduledFunctionsArgumentsTooLarge"
42
+ ]),
43
+ limit: Schema.Int,
44
+ message: Schema.String
45
+ }) {};
46
+ const byteUnits = [
47
+ [2 ** 30, "GiB"],
48
+ [1e9, "GB"],
49
+ [2 ** 20, "MiB"],
50
+ [1e6, "MB"],
51
+ [1024, "KiB"],
52
+ [1e3, "KB"]
53
+ ];
54
+ /**
55
+ * `common::fmt::format_bytes` (`crates/common/src/fmt.rs:29-47`).
56
+ */
57
+ const formatBytes = (n) => {
58
+ if (n === 0) return "0 bytes";
59
+ for (const [unit, name] of byteUnits) {
60
+ if (n < unit) continue;
61
+ if (n % unit === 0) return `${n / unit} ${name}`;
62
+ if (n * 10 % unit === 0) return `${Math.floor(n / unit)}.${Math.floor(n * 10 / unit) % 10} ${name}`;
63
+ }
64
+ return `${n} bytes`;
65
+ };
66
+ const compareBounds = (a, b) => {
67
+ const shared = Math.min(a.key.length, b.key.length);
68
+ for (let i = 0; i < shared; i++) {
69
+ const cmp = compareValues(a.key[i], b.key[i]);
70
+ if (cmp !== 0) return cmp;
71
+ }
72
+ if (a.key.length === b.key.length) return a.side - b.side;
73
+ return a.key.length < b.key.length ? a.side : -b.side;
74
+ };
75
+ const maxBound = (a, b) => compareBounds(a, b) >= 0 ? a : b;
76
+ const minBound = (a, b) => compareBounds(a, b) <= 0 ? a : b;
77
+ /**
78
+ * The one key `key`, as a read or write of a document by ID records it.
79
+ */
80
+ const point = (key) => ({
81
+ start: {
82
+ key,
83
+ side: -1
84
+ },
85
+ end: {
86
+ key,
87
+ side: 1
88
+ }
89
+ });
90
+ /**
91
+ * Every key of an index.
92
+ */
93
+ const everything = point([]);
94
+ /**
95
+ * The part of an index range a query has examined: from where it starts, past
96
+ * the cursor `after`, through `through` in its order. `null` when it has
97
+ * examined nothing.
98
+ */
99
+ const examined = (range, order, after, through) => {
100
+ const equal = [];
101
+ let start;
102
+ let end;
103
+ for (const expression of range) {
104
+ const value = evaluateQueryValue(expression.value);
105
+ switch (expression.type) {
106
+ case "Eq":
107
+ equal.push(value);
108
+ break;
109
+ case "Gt":
110
+ case "Gte":
111
+ start = {
112
+ key: [...equal, value],
113
+ side: expression.type === "Gt" ? 1 : -1
114
+ };
115
+ break;
116
+ case "Lt":
117
+ case "Lte": end = {
118
+ key: [...equal, value],
119
+ side: expression.type === "Lt" ? -1 : 1
120
+ };
121
+ }
122
+ }
123
+ start ??= {
124
+ key: equal,
125
+ side: -1
126
+ };
127
+ end ??= {
128
+ key: equal,
129
+ side: 1
130
+ };
131
+ if (after !== null) {
132
+ if (order === "asc") start = maxBound(start, {
133
+ key: after,
134
+ side: 1
135
+ });
136
+ else end = minBound(end, {
137
+ key: after,
138
+ side: -1
139
+ });
140
+ }
141
+ if (through !== "end") {
142
+ if (order === "asc") end = minBound(end, {
143
+ key: through,
144
+ side: 1
145
+ });
146
+ else start = maxBound(start, {
147
+ key: through,
148
+ side: -1
149
+ });
150
+ }
151
+ return compareBounds(start, end) < 0 ? {
152
+ start,
153
+ end
154
+ } : null;
155
+ };
156
+ /**
157
+ * The intervals read from one index, overlapping and adjacent ones merged
158
+ * (`IntervalSet::add`, `crates/common/src/interval/interval_set.rs:150-230`).
159
+ * They stay sorted and disjoint, so an add touches only its neighbours.
160
+ */
161
+ var IntervalSet = class {
162
+ intervals = [];
163
+ get size() {
164
+ return this.intervals.length;
165
+ }
166
+ add(interval) {
167
+ const intervals = this.intervals;
168
+ let low = 0;
169
+ let high = intervals.length;
170
+ while (low < high) {
171
+ const middle = low + high >> 1;
172
+ if (compareBounds(intervals[middle].end, interval.start) < 0) low = middle + 1;
173
+ else high = middle;
174
+ }
175
+ let last = low;
176
+ let merged = interval;
177
+ while (last < intervals.length && compareBounds(intervals[last].start, interval.end) <= 0) {
178
+ const other = intervals[last];
179
+ merged = {
180
+ start: minBound(merged.start, other.start),
181
+ end: maxBound(merged.end, other.end)
182
+ };
183
+ last++;
184
+ }
185
+ intervals.splice(low, last - low, merged);
186
+ }
187
+ };
188
+ /**
189
+ * The counters every frame of one attempt shares: reads and schedules stay when
190
+ * a nested mutation fails, as the binary keeps them outside its subtransaction
191
+ * token (`crates/database/src/transaction.rs:418-454`).
192
+ */
193
+ var Shared = class Shared {
194
+ indexes = /* @__PURE__ */ new Map();
195
+ intervals = 0;
196
+ documentsRead = 0;
197
+ bytesRead = 0;
198
+ functionsScheduled = 0;
199
+ scheduledFunctionArgsBytes = 0;
200
+ filesRead = 0;
201
+ fileReadBytes = 0;
202
+ /**
203
+ * A stale-snapshot query's counters: the scheduling and file-read counters
204
+ * carried over, reads and intervals from zero (`clone_for_snapshot_query`,
205
+ * `crates/database/src/transaction.rs:1460-1485`).
206
+ */
207
+ forStaleSnapshot() {
208
+ const copy = new Shared();
209
+ copy.functionsScheduled = this.functionsScheduled;
210
+ copy.scheduledFunctionArgsBytes = this.scheduledFunctionArgsBytes;
211
+ return copy;
212
+ }
213
+ };
214
+ /**
215
+ * `a - b`, or 0 when `b` is larger: Rust's `usize::saturating_sub`.
216
+ */
217
+ const saturatingSub = (a, b) => a > b ? a - b : 0;
218
+ /**
219
+ * One frame's share of an attempt's accounting. The outermost frame belongs to
220
+ * the top-level function and each nested mutation gets a child. A child's
221
+ * writes count toward its caller's once it merges and are forgotten when it
222
+ * fails, as the binary's nested write set rolls its sizes back
223
+ * (`crates/database/src/writes.rs:96-136`).
224
+ */
225
+ var Meter = class Meter {
226
+ shared;
227
+ parent;
228
+ documentsWritten = 0;
229
+ bytesWritten = 0;
230
+ filesWritten = 0;
231
+ fileWriteBytes = 0;
232
+ constructor(shared = new Shared(), parent) {
233
+ this.shared = shared;
234
+ this.parent = parent;
235
+ }
236
+ child() {
237
+ return new Meter(this.shared, this);
238
+ }
239
+ /**
240
+ * The meter of a query run on a stale snapshot. The binary runs it on a clone
241
+ * of the transaction that keeps the scheduling counters and starts reads and
242
+ * writes from zero, then drops the clone, so nothing it counts reaches this
243
+ * meter (`clone_for_snapshot_query`,
244
+ * `crates/database/src/transaction.rs:1460-1485`; `run_udf`,
245
+ * `crates/isolate/src/environment/udf/async_syscall.rs:695-776`).
246
+ */
247
+ forStaleSnapshot() {
248
+ return new Meter(this.shared.forStaleSnapshot());
249
+ }
250
+ /**
251
+ * The child's writes become its caller's.
252
+ */
253
+ merge() {
254
+ if (this.parent === void 0) throw new Error("The outermost meter of an attempt has no caller.");
255
+ this.parent.documentsWritten += this.documentsWritten;
256
+ this.parent.bytesWritten += this.bytesWritten;
257
+ }
258
+ chain() {
259
+ return this.parent === void 0 ? [this] : [...this.parent.chain(), this];
260
+ }
261
+ /**
262
+ * How much of each limit the execution has used.
263
+ */
264
+ used() {
265
+ const frames = this.chain();
266
+ const sum = (of) => frames.reduce((total, meter) => total + of(meter), 0);
267
+ const shared = this.shared;
268
+ return {
269
+ bytesRead: shared.bytesRead,
270
+ documentsRead: shared.documentsRead,
271
+ databaseQueries: shared.intervals,
272
+ documentsWritten: sum((meter) => meter.documentsWritten),
273
+ bytesWritten: sum((meter) => meter.bytesWritten),
274
+ functionsScheduled: shared.functionsScheduled,
275
+ scheduledFunctionArgsBytes: shared.scheduledFunctionArgsBytes,
276
+ filesWritten: sum((meter) => meter.filesWritten),
277
+ fileWriteBytes: sum((meter) => meter.fileWriteBytes),
278
+ filesRead: shared.filesRead,
279
+ fileReadBytes: shared.fileReadBytes
280
+ };
281
+ }
282
+ /**
283
+ * What `getTransactionMetrics` answers (`tx_metrics`,
284
+ * `crates/isolate/src/environment/udf/async_syscall.rs:947-993`).
285
+ */
286
+ metrics(limits) {
287
+ const used = this.used();
288
+ return Object.fromEntries(Object.keys(limits).map((metric) => [metric, {
289
+ used: used[metric],
290
+ remaining: limits[metric] - used[metric]
291
+ }]));
292
+ }
293
+ set(index) {
294
+ const set = this.shared.indexes.get(index) ?? new IntervalSet();
295
+ this.shared.indexes.set(index, set);
296
+ return set;
297
+ }
298
+ /**
299
+ * A read the function made of `interval` in `index`, counted as the binary
300
+ * counts it: the total drops by the index's interval count before the read,
301
+ * saturating at zero, and rises by its count after
302
+ * (`record_indexed_directly`, `crates/database/src/reads.rs:447-480`). The
303
+ * index's count includes the intervals of IDs this execution inserted, which
304
+ * joined it without counting (`deriveInterval`), so a read that covers them
305
+ * can move the total by more than the read itself. The binary starts the
306
+ * total at the system-table intervals it reads to run the function, which the
307
+ * kernel does not simulate. While neither total saturates they differ by
308
+ * exactly that share. A read that saturates only the kernel's lower total
309
+ * narrows the gap, and it never widens or turns negative (see the
310
+ * `SystemTableReadIntervals` boundary).
311
+ */
312
+ readInterval(index, interval, limits) {
313
+ const set = this.set(index);
314
+ const before = set.size;
315
+ set.add(interval);
316
+ const shared = this.shared;
317
+ shared.intervals = saturatingSub(shared.intervals, before) + set.size;
318
+ if (!Number.isSafeInteger(shared.intervals) || shared.intervals < 0) throw new Error(`The kernel counted ${shared.intervals} intervals read, which is no count.`);
319
+ return shared.intervals > limits.databaseQueries ? new TransactionLimitExceeded({
320
+ code: "TooManyReads",
321
+ limit: limits.databaseQueries,
322
+ message: `Too many reads in a single function execution (limit: ${limits.databaseQueries}). ${overLimitHelp}`
323
+ }) : void 0;
324
+ }
325
+ /**
326
+ * A read the binary takes for a write rather than for the function: a new
327
+ * document's ID (`register_new_id`, writes.rs:426-441). It joins the index's
328
+ * intervals and leaves the total as it was (`record_indexed_derived` calls
329
+ * `_record_indexed` and ignores its counts, reads.rs:333-344), so a later
330
+ * read of that index starts from a count that includes it.
331
+ */
332
+ deriveInterval(index, interval) {
333
+ this.set(index).add(interval);
334
+ }
335
+ /**
336
+ * A document the function read. A system table's document counts toward no
337
+ * limit (`record_read_document`, reads.rs:365-445).
338
+ */
339
+ readDocument(document, system, limits) {
340
+ if (system) return void 0;
341
+ const shared = this.shared;
342
+ shared.documentsRead += 1;
343
+ shared.bytesRead += getConvexSize(document);
344
+ if (shared.documentsRead > limits.documentsRead) return new TransactionLimitExceeded({
345
+ code: "TooManyDocumentsRead",
346
+ limit: limits.documentsRead,
347
+ message: `Too many documents read in a single function execution (limit: ${limits.documentsRead}). ${overLimitHelp}`
348
+ });
349
+ if (shared.bytesRead > limits.bytesRead) return new TransactionLimitExceeded({
350
+ code: "TooManyBytesRead",
351
+ limit: limits.bytesRead,
352
+ message: `Too many bytes read in a single function execution (limit: ${limits.bytesRead} bytes). ${overLimitHelp}`
353
+ });
354
+ }
355
+ /**
356
+ * A write to a user table: the new document, or `null` for a delete. Each
357
+ * write counts, even to a document this execution already wrote
358
+ * (`Writes::update`, writes.rs:241-310).
359
+ */
360
+ write(document, limits) {
361
+ this.documentsWritten += 1;
362
+ this.bytesWritten += document === null ? 0 : getConvexSize(document);
363
+ const used = this.used();
364
+ if (used.documentsWritten > limits.documentsWritten) return new TransactionLimitExceeded({
365
+ code: "TooManyWrites",
366
+ limit: limits.documentsWritten,
367
+ message: `Too many writes in a single function execution (limit: ${limits.documentsWritten})`
368
+ });
369
+ if (used.bytesWritten > limits.bytesWritten) return new TransactionLimitExceeded({
370
+ code: "TooManyBytesWritten",
371
+ limit: limits.bytesWritten,
372
+ message: `Too many bytes written in a single function execution (limit: ${formatBytes(limits.bytesWritten)})`
373
+ });
374
+ }
375
+ /**
376
+ * A function scheduled with `args`. The binary checks before it counts, so a
377
+ * refused schedule counts nothing (`check_scheduling_limits`,
378
+ * `crates/model/src/scheduled_jobs/mod.rs:162-196`). Its arguments are sized
379
+ * as the one-element array the scheduler stores.
380
+ */
381
+ schedule(args, limits) {
382
+ const shared = this.shared;
383
+ const size = getConvexSize([args]);
384
+ if (shared.functionsScheduled >= limits.functionsScheduled) return new TransactionLimitExceeded({
385
+ code: "TooManyFunctionsScheduled",
386
+ limit: limits.functionsScheduled,
387
+ message: `Too many functions scheduled by this mutation (limit: ${limits.functionsScheduled})`
388
+ });
389
+ if (shared.scheduledFunctionArgsBytes + size > limits.scheduledFunctionArgsBytes) return new TransactionLimitExceeded({
390
+ code: "ScheduledFunctionsArgumentsTooLarge",
391
+ limit: limits.scheduledFunctionArgsBytes,
392
+ message: `Too large total size of the arguments of scheduled functions from this mutation (limit: ${limits.scheduledFunctionArgsBytes} bytes)`
393
+ });
394
+ shared.functionsScheduled += 1;
395
+ shared.scheduledFunctionArgsBytes += size;
396
+ }
397
+ };
398
+ //#endregion
399
+ export { Meter, TransactionLimitExceeded, defaults, everything, examined, formatBytes, fromBudget, point };
400
+
401
+ //# sourceMappingURL=Metering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Metering.js","names":[],"sources":["../../src/internal/Metering.ts"],"sourcesContent":["// The limits, counters and messages are convex-backend's, read at the commit\n// the fork's local backend runs (`precompiled-2026-10-28-cccbade`). Paths below\n// are in that repository.\n\nimport type { TransactionMetrics } from \"convex/server\";\nimport { compareValues, getConvexSize, type Value } from \"convex/values\";\nimport * as Schema from \"effect/Schema\";\nimport type { SerializedRangeExpression } from \"./KernelValues.js\";\nimport { evaluateQueryValue } from \"./KernelValues.js\";\n\n/**\n * The ceilings one function execution runs under, the binary's\n * `TransactionLimits` (`crates/database/src/execution_size.rs:62-95`).\n */\nexport type Limits = { readonly [K in keyof TransactionMetrics]: number };\n\n/**\n * The binary's default ceilings (`crates/common/src/knobs.rs`).\n */\nexport const defaults: Limits = {\n // `TRANSACTION_MAX_READ_SIZE_BYTES`, knobs.rs:645-647.\n bytesRead: 1 << 24,\n // `TRANSACTION_MAX_READ_SIZE_ROWS`, knobs.rs:641-642.\n documentsRead: 32000,\n // `TRANSACTION_MAX_READ_SET_INTERVALS`, knobs.rs:657-658.\n databaseQueries: 4096,\n // `TRANSACTION_MAX_NUM_USER_WRITES`, knobs.rs:406-407.\n documentsWritten: 16000,\n // `TRANSACTION_MAX_USER_WRITE_SIZE_BYTES`, knobs.rs:410-412.\n bytesWritten: 1 << 24,\n // `TRANSACTION_MAX_NUM_SCHEDULED`, knobs.rs:514-515.\n functionsScheduled: 1000,\n // `TRANSACTION_MAX_SCHEDULED_TOTAL_ARGUMENT_SIZE_BYTES`, knobs.rs:530-536.\n scheduledFunctionArgsBytes: 1 << 24,\n // `TRANSACTION_MAX_NUM_FILES_WRITTEN`, knobs.rs:539-540.\n filesWritten: 10,\n // `TRANSACTION_MAX_FILE_WRITE_SIZE_BYTES`, knobs.rs:544-546.\n fileWriteBytes: 1 << 24,\n // `TRANSACTION_MAX_NUM_FILES_READ`, knobs.rs:549-550.\n filesRead: 10,\n // `TRANSACTION_MAX_FILE_READ_SIZE_BYTES`, knobs.rs:554-556.\n fileReadBytes: 1 << 24,\n};\n\n/**\n * What a nested call may use on top of what its transaction has used, as\n * `ctx.runQuery` and `ctx.runMutation` take it in `transactionLimits`.\n */\nexport type Budget = Partial<Limits>;\n\n/**\n * A nested call's ceilings: each one what the transaction has used plus the\n * call's budget, never above the caller's (`TransactionLimits::from_budget`,\n * execution_size.rs:97-160). An unset field leaves the caller's ceiling.\n */\nexport const fromBudget = (\n budget: Budget,\n used: Limits,\n ceiling: Limits,\n): Limits =>\n Object.fromEntries(\n (Object.keys(ceiling) as Array<keyof Limits>).map((metric) => [\n metric,\n Math.min(\n used[metric] + (budget[metric] ?? ceiling[metric]),\n ceiling[metric],\n ),\n ]),\n ) as unknown as Limits;\n\nconst overLimitHelp =\n \"Consider using smaller limits in your queries, paginating your queries, or using indexed queries with a selective index range expressions.\";\n\n/**\n * A function execution crossed one of its ceilings. `code` is the binary's\n * short message and `message` its text, which the function receives as a\n * catchable error.\n */\nexport class TransactionLimitExceeded extends Schema.TaggedError<TransactionLimitExceeded>()(\n \"TransactionLimitExceeded\",\n {\n code: Schema.Literals([\n \"TooManyReads\",\n \"TooManyDocumentsRead\",\n \"TooManyBytesRead\",\n \"TooManyWrites\",\n \"TooManyBytesWritten\",\n \"TooManyFunctionsScheduled\",\n \"ScheduledFunctionsArgumentsTooLarge\",\n ]),\n limit: Schema.Int,\n message: Schema.String,\n },\n) {}\n\nconst byteUnits: ReadonlyArray<readonly [number, string]> = [\n [2 ** 30, \"GiB\"],\n [1_000_000_000, \"GB\"],\n [2 ** 20, \"MiB\"],\n [1_000_000, \"MB\"],\n [2 ** 10, \"KiB\"],\n [1_000, \"KB\"],\n];\n\n/**\n * `common::fmt::format_bytes` (`crates/common/src/fmt.rs:29-47`).\n */\nexport const formatBytes = (n: number): string => {\n if (n === 0) return \"0 bytes\";\n for (const [unit, name] of byteUnits) {\n if (n < unit) continue;\n if (n % unit === 0) return `${n / unit} ${name}`;\n if ((n * 10) % unit === 0) {\n return `${Math.floor(n / unit)}.${Math.floor((n * 10) / unit) % 10} ${name}`;\n }\n }\n return `${n} bytes`;\n};\n\n/**\n * A position in an index's key space: just before, or just after, every key\n * that starts with `key`.\n */\ninterface Bound {\n readonly key: ReadonlyArray<Value | undefined>;\n readonly side: -1 | 1;\n}\n\n/**\n * The keys from `start` up to `end`.\n */\nexport interface KeyInterval {\n readonly start: Bound;\n readonly end: Bound;\n}\n\nconst compareBounds = (a: Bound, b: Bound): number => {\n const shared = Math.min(a.key.length, b.key.length);\n for (let i = 0; i < shared; i++) {\n const cmp = compareValues(a.key[i] as Value, b.key[i] as Value);\n if (cmp !== 0) return cmp;\n }\n if (a.key.length === b.key.length) return a.side - b.side;\n return a.key.length < b.key.length ? a.side : -b.side;\n};\n\nconst maxBound = (a: Bound, b: Bound) => (compareBounds(a, b) >= 0 ? a : b);\nconst minBound = (a: Bound, b: Bound) => (compareBounds(a, b) <= 0 ? a : b);\n\n/**\n * The one key `key`, as a read or write of a document by ID records it.\n */\nexport const point = (key: ReadonlyArray<Value | undefined>): KeyInterval => ({\n start: { key, side: -1 },\n end: { key, side: 1 },\n});\n\n/**\n * Every key of an index.\n */\nexport const everything: KeyInterval = point([]);\n\n/**\n * The part of an index range a query has examined: from where it starts, past\n * the cursor `after`, through `through` in its order. `null` when it has\n * examined nothing.\n */\nexport const examined = (\n range: ReadonlyArray<SerializedRangeExpression>,\n order: \"asc\" | \"desc\",\n after: ReadonlyArray<Value | undefined> | null,\n through: ReadonlyArray<Value | undefined> | \"end\",\n): KeyInterval | null => {\n const equal: Array<Value | undefined> = [];\n let start: Bound | undefined;\n let end: Bound | undefined;\n for (const expression of range) {\n const value = evaluateQueryValue(expression.value);\n switch (expression.type) {\n case \"Eq\":\n equal.push(value);\n break;\n case \"Gt\":\n case \"Gte\":\n start = {\n key: [...equal, value],\n side: expression.type === \"Gt\" ? 1 : -1,\n };\n break;\n case \"Lt\":\n case \"Lte\":\n end = {\n key: [...equal, value],\n side: expression.type === \"Lt\" ? -1 : 1,\n };\n break;\n }\n }\n start ??= { key: equal, side: -1 };\n end ??= { key: equal, side: 1 };\n if (after !== null) {\n if (order === \"asc\") start = maxBound(start, { key: after, side: 1 });\n else end = minBound(end, { key: after, side: -1 });\n }\n if (through !== \"end\") {\n if (order === \"asc\") end = minBound(end, { key: through, side: 1 });\n else start = maxBound(start, { key: through, side: -1 });\n }\n return compareBounds(start, end) < 0 ? { start, end } : null;\n};\n\n/**\n * The intervals read from one index, overlapping and adjacent ones merged\n * (`IntervalSet::add`, `crates/common/src/interval/interval_set.rs:150-230`).\n * They stay sorted and disjoint, so an add touches only its neighbours.\n */\nclass IntervalSet {\n private readonly intervals: Array<KeyInterval> = [];\n\n get size(): number {\n return this.intervals.length;\n }\n\n add(interval: KeyInterval) {\n const intervals = this.intervals;\n // The first interval that ends at or after the new one's start.\n let low = 0;\n let high = intervals.length;\n while (low < high) {\n const middle = (low + high) >> 1;\n if (compareBounds(intervals[middle]!.end, interval.start) < 0) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n let last = low;\n let merged = interval;\n while (\n last < intervals.length &&\n compareBounds(intervals[last]!.start, interval.end) <= 0\n ) {\n const other = intervals[last]!;\n merged = {\n start: minBound(merged.start, other.start),\n end: maxBound(merged.end, other.end),\n };\n last++;\n }\n intervals.splice(low, last - low, merged);\n }\n}\n\n/**\n * The counters every frame of one attempt shares: reads and schedules stay when\n * a nested mutation fails, as the binary keeps them outside its subtransaction\n * token (`crates/database/src/transaction.rs:418-454`).\n */\nclass Shared {\n readonly indexes = new Map<string, IntervalSet>();\n intervals = 0;\n documentsRead = 0;\n bytesRead = 0;\n functionsScheduled = 0;\n scheduledFunctionArgsBytes = 0;\n // File storage counters stay at zero until the kernel stores files.\n readonly filesRead = 0;\n readonly fileReadBytes = 0;\n\n /**\n * A stale-snapshot query's counters: the scheduling and file-read counters\n * carried over, reads and intervals from zero (`clone_for_snapshot_query`,\n * `crates/database/src/transaction.rs:1460-1485`).\n */\n forStaleSnapshot(): Shared {\n const copy = new Shared();\n copy.functionsScheduled = this.functionsScheduled;\n copy.scheduledFunctionArgsBytes = this.scheduledFunctionArgsBytes;\n return copy;\n }\n}\n\n/**\n * `a - b`, or 0 when `b` is larger: Rust's `usize::saturating_sub`.\n */\nconst saturatingSub = (a: number, b: number) => (a > b ? a - b : 0);\n\n/**\n * One frame's share of an attempt's accounting. The outermost frame belongs to\n * the top-level function and each nested mutation gets a child. A child's\n * writes count toward its caller's once it merges and are forgotten when it\n * fails, as the binary's nested write set rolls its sizes back\n * (`crates/database/src/writes.rs:96-136`).\n */\nexport class Meter {\n private documentsWritten = 0;\n private bytesWritten = 0;\n // File storage counters stay at zero until the kernel stores files.\n private readonly filesWritten = 0;\n private readonly fileWriteBytes = 0;\n\n constructor(\n private readonly shared: Shared = new Shared(),\n private readonly parent?: Meter,\n ) {}\n\n child(): Meter {\n return new Meter(this.shared, this);\n }\n\n /**\n * The meter of a query run on a stale snapshot. The binary runs it on a clone\n * of the transaction that keeps the scheduling counters and starts reads and\n * writes from zero, then drops the clone, so nothing it counts reaches this\n * meter (`clone_for_snapshot_query`,\n * `crates/database/src/transaction.rs:1460-1485`; `run_udf`,\n * `crates/isolate/src/environment/udf/async_syscall.rs:695-776`).\n */\n forStaleSnapshot(): Meter {\n return new Meter(this.shared.forStaleSnapshot());\n }\n\n /**\n * The child's writes become its caller's.\n */\n merge() {\n if (this.parent === undefined) {\n throw new Error(\"The outermost meter of an attempt has no caller.\");\n }\n this.parent.documentsWritten += this.documentsWritten;\n this.parent.bytesWritten += this.bytesWritten;\n }\n\n private chain(): Array<Meter> {\n return this.parent === undefined ? [this] : [...this.parent.chain(), this];\n }\n\n /**\n * How much of each limit the execution has used.\n */\n used(): Limits {\n const frames = this.chain();\n const sum = (of: (meter: Meter) => number) =>\n frames.reduce((total, meter) => total + of(meter), 0);\n const shared = this.shared;\n return {\n bytesRead: shared.bytesRead,\n documentsRead: shared.documentsRead,\n databaseQueries: shared.intervals,\n documentsWritten: sum((meter) => meter.documentsWritten),\n bytesWritten: sum((meter) => meter.bytesWritten),\n functionsScheduled: shared.functionsScheduled,\n scheduledFunctionArgsBytes: shared.scheduledFunctionArgsBytes,\n filesWritten: sum((meter) => meter.filesWritten),\n fileWriteBytes: sum((meter) => meter.fileWriteBytes),\n filesRead: shared.filesRead,\n fileReadBytes: shared.fileReadBytes,\n };\n }\n\n /**\n * What `getTransactionMetrics` answers (`tx_metrics`,\n * `crates/isolate/src/environment/udf/async_syscall.rs:947-993`).\n */\n metrics(limits: Limits): TransactionMetrics {\n const used = this.used();\n return Object.fromEntries(\n (Object.keys(limits) as Array<keyof Limits>).map((metric) => [\n metric,\n { used: used[metric], remaining: limits[metric] - used[metric] },\n ]),\n ) as unknown as TransactionMetrics;\n }\n\n private set(index: string): IntervalSet {\n const set = this.shared.indexes.get(index) ?? new IntervalSet();\n this.shared.indexes.set(index, set);\n return set;\n }\n\n /**\n * A read the function made of `interval` in `index`, counted as the binary\n * counts it: the total drops by the index's interval count before the read,\n * saturating at zero, and rises by its count after\n * (`record_indexed_directly`, `crates/database/src/reads.rs:447-480`). The\n * index's count includes the intervals of IDs this execution inserted, which\n * joined it without counting (`deriveInterval`), so a read that covers them\n * can move the total by more than the read itself. The binary starts the\n * total at the system-table intervals it reads to run the function, which the\n * kernel does not simulate. While neither total saturates they differ by\n * exactly that share. A read that saturates only the kernel's lower total\n * narrows the gap, and it never widens or turns negative (see the\n * `SystemTableReadIntervals` boundary).\n */\n readInterval(\n index: string,\n interval: KeyInterval,\n limits: Limits,\n ): TransactionLimitExceeded | undefined {\n const set = this.set(index);\n const before = set.size;\n set.add(interval);\n const shared = this.shared;\n shared.intervals = saturatingSub(shared.intervals, before) + set.size;\n // Both terms are counts, so the total never goes below zero.\n if (!Number.isSafeInteger(shared.intervals) || shared.intervals < 0) {\n throw new Error(\n `The kernel counted ${shared.intervals} intervals read, which is no count.`,\n );\n }\n return shared.intervals > limits.databaseQueries\n ? new TransactionLimitExceeded({\n code: \"TooManyReads\",\n limit: limits.databaseQueries,\n message: `Too many reads in a single function execution (limit: ${limits.databaseQueries}). ${overLimitHelp}`,\n })\n : undefined;\n }\n\n /**\n * A read the binary takes for a write rather than for the function: a new\n * document's ID (`register_new_id`, writes.rs:426-441). It joins the index's\n * intervals and leaves the total as it was (`record_indexed_derived` calls\n * `_record_indexed` and ignores its counts, reads.rs:333-344), so a later\n * read of that index starts from a count that includes it.\n */\n deriveInterval(index: string, interval: KeyInterval) {\n this.set(index).add(interval);\n }\n\n /**\n * A document the function read. A system table's document counts toward no\n * limit (`record_read_document`, reads.rs:365-445).\n */\n readDocument(\n document: Value,\n system: boolean,\n limits: Limits,\n ): TransactionLimitExceeded | undefined {\n if (system) return undefined;\n const shared = this.shared;\n shared.documentsRead += 1;\n shared.bytesRead += getConvexSize(document);\n if (shared.documentsRead > limits.documentsRead) {\n return new TransactionLimitExceeded({\n code: \"TooManyDocumentsRead\",\n limit: limits.documentsRead,\n message: `Too many documents read in a single function execution (limit: ${limits.documentsRead}). ${overLimitHelp}`,\n });\n }\n if (shared.bytesRead > limits.bytesRead) {\n return new TransactionLimitExceeded({\n code: \"TooManyBytesRead\",\n limit: limits.bytesRead,\n message: `Too many bytes read in a single function execution (limit: ${limits.bytesRead} bytes). ${overLimitHelp}`,\n });\n }\n return undefined;\n }\n\n /**\n * A write to a user table: the new document, or `null` for a delete. Each\n * write counts, even to a document this execution already wrote\n * (`Writes::update`, writes.rs:241-310).\n */\n write(\n document: Value | null,\n limits: Limits,\n ): TransactionLimitExceeded | undefined {\n this.documentsWritten += 1;\n this.bytesWritten += document === null ? 0 : getConvexSize(document);\n const used = this.used();\n if (used.documentsWritten > limits.documentsWritten) {\n return new TransactionLimitExceeded({\n code: \"TooManyWrites\",\n limit: limits.documentsWritten,\n message: `Too many writes in a single function execution (limit: ${limits.documentsWritten})`,\n });\n }\n if (used.bytesWritten > limits.bytesWritten) {\n return new TransactionLimitExceeded({\n code: \"TooManyBytesWritten\",\n limit: limits.bytesWritten,\n message: `Too many bytes written in a single function execution (limit: ${formatBytes(limits.bytesWritten)})`,\n });\n }\n return undefined;\n }\n\n /**\n * A function scheduled with `args`. The binary checks before it counts, so a\n * refused schedule counts nothing (`check_scheduling_limits`,\n * `crates/model/src/scheduled_jobs/mod.rs:162-196`). Its arguments are sized\n * as the one-element array the scheduler stores.\n */\n schedule(args: Value, limits: Limits): TransactionLimitExceeded | undefined {\n const shared = this.shared;\n const size = getConvexSize([args]);\n if (shared.functionsScheduled >= limits.functionsScheduled) {\n return new TransactionLimitExceeded({\n code: \"TooManyFunctionsScheduled\",\n limit: limits.functionsScheduled,\n message: `Too many functions scheduled by this mutation (limit: ${limits.functionsScheduled})`,\n });\n }\n if (\n shared.scheduledFunctionArgsBytes + size >\n limits.scheduledFunctionArgsBytes\n ) {\n return new TransactionLimitExceeded({\n code: \"ScheduledFunctionsArgumentsTooLarge\",\n limit: limits.scheduledFunctionArgsBytes,\n message: `Too large total size of the arguments of scheduled functions from this mutation (limit: ${limits.scheduledFunctionArgsBytes} bytes)`,\n });\n }\n shared.functionsScheduled += 1;\n shared.scheduledFunctionArgsBytes += size;\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import type { UserIdentity } from "convex/server";
2
+ import * as Execution from "./Execution.js";
3
+ import * as Kernel from "./Kernel.js";
4
+ /**
5
+ * One run of a subscribed query, and the identity version its subscriber had
6
+ * when it ran.
7
+ */
8
+ export interface Run extends Execution.QueryRun {
9
+ readonly identityVersion: number;
10
+ }
11
+ /**
12
+ * Why a run no longer shows what its query answers now.
13
+ */
14
+ export type Invalidation = {
15
+ readonly kind: "commit";
16
+ readonly change: Kernel.Change;
17
+ } | {
18
+ readonly kind: "identity";
19
+ readonly from: number;
20
+ readonly to: number;
21
+ };
22
+ /**
23
+ * Why `run` is stale, if it is: a commit since its snapshot changed something
24
+ * it read, as an attempt's read set conflicts, or it read its subscriber's
25
+ * identity and that identity has changed. The clock never makes a run stale.
26
+ * The binary does not rerun a query because time passed, so a run keeps the
27
+ * time it read until something else invalidates it.
28
+ */
29
+ export declare const invalidation: (run: Run, state: Kernel.State, identityVersion: number) => Invalidation | undefined;
30
+ /**
31
+ * Whether two outcomes show a client the same thing: equal values, failures
32
+ * with errors of the same name, message and data, or failures that threw equal
33
+ * bare values.
34
+ */
35
+ export declare const sameOutcome: (a: Execution.Outcome, b: Execution.Outcome) => boolean;
36
+ /**
37
+ * A subscription's value changed when a refresh reran it.
38
+ */
39
+ export interface Transition {
40
+ readonly subscription: string;
41
+ readonly from: Execution.Outcome;
42
+ readonly to: Execution.Outcome;
43
+ readonly because: Invalidation;
44
+ }
45
+ /**
46
+ * A subscription whose value differs from what a full rerun of its query
47
+ * answers.
48
+ */
49
+ export interface Disagreement {
50
+ readonly subscription: string;
51
+ readonly incremental: Execution.Outcome;
52
+ readonly reference: Execution.Outcome;
53
+ }
54
+ export interface Subscription {
55
+ readonly id: string;
56
+ /**
57
+ * The latest run: its value and the dependencies it took.
58
+ */
59
+ readonly current: () => Run;
60
+ readonly unsubscribe: () => void;
61
+ }
62
+ /**
63
+ * One client's query set. Its subscriptions share one caller, whose identity
64
+ * `authenticate` replaces, bumping the identity version.
65
+ */
66
+ export declare class Subscriber {
67
+ private readonly registry;
68
+ private who;
69
+ private version;
70
+ constructor(registry: Registry, who: Execution.Caller);
71
+ get caller(): Execution.Caller;
72
+ get identityVersion(): number;
73
+ subscribe(reference: unknown, args?: unknown): Promise<Subscription>;
74
+ /**
75
+ * Replace the caller's identity, or sign it out with `null`. Each
76
+ * subscription that read the identity is stale until the next refresh.
77
+ */
78
+ authenticate(identity: Partial<UserIdentity> | null): void;
79
+ }
80
+ /**
81
+ * Every live subscription on one world. A commit or an identity change makes a
82
+ * subscription stale. `refresh` reruns the stale ones, and `reference` reruns
83
+ * every one from scratch to check that none was missed.
84
+ */
85
+ export declare class Registry {
86
+ private readonly world;
87
+ private readonly live;
88
+ private nextId;
89
+ constructor(world: Execution.World);
90
+ subscriber(caller: Execution.Caller): Subscriber;
91
+ subscribe(subscriber: Subscriber, reference: unknown, args: unknown): Promise<Subscription>;
92
+ /**
93
+ * Rerun each stale subscription at the latest commit and the kernel's time
94
+ * now, oldest first, until none is stale. Lists each one whose value
95
+ * changed.
96
+ */
97
+ refresh(): Promise<ReadonlyArray<Transition>>;
98
+ /**
99
+ * Rerun every subscription's query from scratch, at the latest commit and
100
+ * under its subscriber's identity now, and list each whose value differs. A
101
+ * paginated run keeps its page end.
102
+ */
103
+ reference(): Promise<ReadonlyArray<Disagreement>>;
104
+ /**
105
+ * A full rerun of `live`'s query. A run that read the time first reruns at
106
+ * that time, since the clock alone never invalidates it. When that rerun's
107
+ * own reads show the run was owed a rerun, it reruns again at the kernel's
108
+ * time now, as `refresh` would. The rerun was owed when a commit since the
109
+ * run changed something the rerun read, or the rerun read an identity that
110
+ * has changed since. The check reads neither the run's recorded reads nor
111
+ * `invalidation`, so a dependency `refresh` missed still disagrees.
112
+ */
113
+ private rerun;
114
+ private run;
115
+ }
116
+ //# sourceMappingURL=Subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Subscriptions.d.ts","sourceRoot":"","sources":["../../src/internal/Subscriptions.ts"],"names":[],"mappings":"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"}