@gaganref/confect-test 10.3.1-fork.87 → 10.3.1-fork.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/Attempt.d.ts +77 -0
- package/dist/internal/Attempt.d.ts.map +1 -0
- package/dist/internal/Attempt.js +154 -0
- package/dist/internal/Attempt.js.map +1 -0
- package/dist/internal/ConvexTest.d.ts +34 -0
- package/dist/internal/ConvexTest.d.ts.map +1 -0
- package/dist/internal/ConvexTest.js +59 -0
- package/dist/internal/ConvexTest.js.map +1 -0
- package/dist/internal/Execution.d.ts +167 -0
- package/dist/internal/Execution.d.ts.map +1 -0
- package/dist/internal/Execution.js +790 -0
- package/dist/internal/Execution.js.map +1 -0
- package/dist/internal/Globals.d.ts +11 -0
- package/dist/internal/Globals.d.ts.map +1 -0
- package/dist/internal/Globals.js +76 -0
- package/dist/internal/Globals.js.map +1 -0
- package/dist/internal/Invocation.d.ts +57 -0
- package/dist/internal/Invocation.d.ts.map +1 -0
- package/dist/internal/Invocation.js +145 -0
- package/dist/internal/Invocation.js.map +1 -0
- package/dist/internal/Kernel.d.ts +93 -0
- package/dist/internal/Kernel.d.ts.map +1 -0
- package/dist/internal/Kernel.js +91 -0
- package/dist/internal/Kernel.js.map +1 -0
- package/dist/internal/KernelQuery.d.ts +55 -0
- package/dist/internal/KernelQuery.d.ts.map +1 -0
- package/dist/internal/KernelQuery.js +205 -0
- package/dist/internal/KernelQuery.js.map +1 -0
- package/dist/internal/KernelRefusal.d.ts +77 -0
- package/dist/internal/KernelRefusal.d.ts.map +1 -0
- package/dist/internal/KernelRefusal.js +73 -0
- package/dist/internal/KernelRefusal.js.map +1 -0
- package/dist/internal/KernelValues.d.ts +136 -0
- package/dist/internal/KernelValues.d.ts.map +1 -0
- package/dist/internal/KernelValues.js +266 -0
- package/dist/internal/KernelValues.js.map +1 -0
- package/dist/internal/ReadSet.d.ts +55 -0
- package/dist/internal/ReadSet.d.ts.map +1 -0
- package/dist/internal/ReadSet.js +29 -0
- package/dist/internal/ReadSet.js.map +1 -0
- package/package.json +3 -3
- package/src/internal/Attempt.ts +185 -0
- package/src/internal/ConvexTest.ts +118 -0
- package/src/internal/Execution.ts +1430 -0
- package/src/internal/Globals.ts +93 -0
- package/src/internal/Invocation.ts +291 -0
- package/src/internal/Kernel.ts +190 -0
- package/src/internal/KernelQuery.ts +405 -0
- package/src/internal/KernelRefusal.ts +88 -0
- package/src/internal/KernelValues.ts +725 -0
- package/src/internal/ReadSet.ts +108 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// Derived from convex-test 0.0.65 (index.ts, `DatabaseFake`), Copyright Convex,
|
|
2
|
+
// Inc., licensed under the Apache License, Version 2.0
|
|
3
|
+
// (https://www.apache.org/licenses/LICENSE-2.0). The ordering, index range and
|
|
4
|
+
// pagination rules are convex-test's. They read a table through the attempt
|
|
5
|
+
// that runs the query instead of convex-test's database, and record the
|
|
6
|
+
// interval each query examined.
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
GenericDocument,
|
|
10
|
+
GenericSchema,
|
|
11
|
+
SchemaDefinition,
|
|
12
|
+
} from "convex/server";
|
|
13
|
+
import {
|
|
14
|
+
compareValues,
|
|
15
|
+
convexToJson,
|
|
16
|
+
getDocumentSize,
|
|
17
|
+
jsonToConvex,
|
|
18
|
+
} from "convex/values";
|
|
19
|
+
import * as KernelRefusal from "./KernelRefusal.js";
|
|
20
|
+
import * as ReadSet from "./ReadSet.js";
|
|
21
|
+
import type {
|
|
22
|
+
FilterJson,
|
|
23
|
+
Index,
|
|
24
|
+
SerializedQuery,
|
|
25
|
+
SerializedRangeExpression,
|
|
26
|
+
StoredDocument,
|
|
27
|
+
ValidatorJSON,
|
|
28
|
+
} from "./KernelValues.js";
|
|
29
|
+
import {
|
|
30
|
+
evaluateFieldPath,
|
|
31
|
+
evaluateFilter,
|
|
32
|
+
evaluateRangeFilter,
|
|
33
|
+
isValidIdentifier,
|
|
34
|
+
resolvePendingValues,
|
|
35
|
+
validateFieldNames,
|
|
36
|
+
validateIndexRangeExpression,
|
|
37
|
+
validateValidator,
|
|
38
|
+
} from "./KernelValues.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A component's tables as its schema declares them.
|
|
42
|
+
*/
|
|
43
|
+
export interface KernelSchema {
|
|
44
|
+
readonly schemaValidation: boolean;
|
|
45
|
+
readonly tables: ReadonlyMap<
|
|
46
|
+
string,
|
|
47
|
+
{
|
|
48
|
+
readonly indexes: ReadonlyArray<Index>;
|
|
49
|
+
readonly documentType: ValidatorJSON;
|
|
50
|
+
}
|
|
51
|
+
>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const exportSchema = (
|
|
55
|
+
schema: SchemaDefinition<GenericSchema, boolean> | null | undefined,
|
|
56
|
+
): KernelSchema | null => {
|
|
57
|
+
if (schema === null || schema === undefined) return null;
|
|
58
|
+
const exported: KernelSchema = {
|
|
59
|
+
schemaValidation: (schema as unknown as { schemaValidation: boolean })
|
|
60
|
+
.schemaValidation,
|
|
61
|
+
tables: new Map(
|
|
62
|
+
Object.entries(schema.tables).map(([name, table]) => [
|
|
63
|
+
name,
|
|
64
|
+
(table as unknown as { export: () => any }).export(),
|
|
65
|
+
]),
|
|
66
|
+
),
|
|
67
|
+
};
|
|
68
|
+
exported.tables.forEach((table, tableName) => {
|
|
69
|
+
if (!isValidIdentifier(tableName)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Table names must be valid identifiers, got "${tableName}"`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
validateFieldNames(table.documentType);
|
|
75
|
+
table.indexes.forEach(({ indexDescriptor }) => {
|
|
76
|
+
if (!isValidIdentifier(indexDescriptor)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Index names must be valid identifiers, got "${indexDescriptor}"`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
return exported;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check a document against its table's declared type, as the backend does
|
|
88
|
+
* before a write.
|
|
89
|
+
*/
|
|
90
|
+
export const validateDocument = (
|
|
91
|
+
schema: KernelSchema | null,
|
|
92
|
+
tableName: string,
|
|
93
|
+
doc: Record<string, unknown>,
|
|
94
|
+
) => {
|
|
95
|
+
if (schema === null || !schema.schemaValidation) return;
|
|
96
|
+
const validator = schema.tables.get(tableName)?.documentType;
|
|
97
|
+
if (validator === undefined) return;
|
|
98
|
+
validateValidator(validator, resolvePendingValues(doc as GenericDocument));
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* An interval a query reads, before it says where it starts and how far it got.
|
|
103
|
+
*/
|
|
104
|
+
export type Interval = Omit<
|
|
105
|
+
ReadSet.IntervalRead,
|
|
106
|
+
"componentPath" | "after" | "through"
|
|
107
|
+
>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* What a query reads: one table of one component, as the running attempt sees
|
|
111
|
+
* it. `read` records an interval in the attempt's read set and returns how to
|
|
112
|
+
* advance it.
|
|
113
|
+
*/
|
|
114
|
+
export interface TableView {
|
|
115
|
+
readonly schema: KernelSchema | null;
|
|
116
|
+
readonly scan: (tableName: string) => ReadonlyArray<StoredDocument>;
|
|
117
|
+
readonly read: (
|
|
118
|
+
interval: Interval,
|
|
119
|
+
after: ReadSet.Key | null,
|
|
120
|
+
) => (through: ReadSet.Key | "end") => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface ResolvedSource {
|
|
124
|
+
readonly sortedDocs: ReadonlyArray<GenericDocument>;
|
|
125
|
+
readonly filterFn: (doc: GenericDocument) => boolean;
|
|
126
|
+
readonly limit: number | null;
|
|
127
|
+
readonly order: "asc" | "desc";
|
|
128
|
+
readonly interval: Interval;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const resolveSource = (
|
|
132
|
+
view: TableView,
|
|
133
|
+
query: SerializedQuery,
|
|
134
|
+
): ResolvedSource => {
|
|
135
|
+
const source = query.source;
|
|
136
|
+
let docs: Array<GenericDocument>;
|
|
137
|
+
let fieldPathsToSortBy: Array<string>;
|
|
138
|
+
let order: "asc" | "desc";
|
|
139
|
+
let table: string;
|
|
140
|
+
let range: ReadonlyArray<SerializedRangeExpression> = [];
|
|
141
|
+
switch (source.type) {
|
|
142
|
+
case "FullTableScan": {
|
|
143
|
+
table = source.tableName;
|
|
144
|
+
docs = [...view.scan(source.tableName)];
|
|
145
|
+
order = source.order ?? "asc";
|
|
146
|
+
fieldPathsToSortBy = ["_creationTime"];
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "IndexRange": {
|
|
150
|
+
const [tableName, indexName] = source.indexName.split(".") as [
|
|
151
|
+
string,
|
|
152
|
+
string,
|
|
153
|
+
];
|
|
154
|
+
table = tableName;
|
|
155
|
+
range = source.range;
|
|
156
|
+
order = source.order ?? "asc";
|
|
157
|
+
let fields: Array<string>;
|
|
158
|
+
if (indexName === "by_creation_time") {
|
|
159
|
+
fields = ["_creationTime", "_id"];
|
|
160
|
+
} else if (indexName === "by_id") {
|
|
161
|
+
fields = ["_id"];
|
|
162
|
+
} else {
|
|
163
|
+
const index = view.schema?.tables
|
|
164
|
+
.get(tableName)
|
|
165
|
+
?.indexes.find(
|
|
166
|
+
({ indexDescriptor }) => indexDescriptor === indexName,
|
|
167
|
+
);
|
|
168
|
+
if (index === undefined) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Cannot use index "${indexName}" for table "${tableName}" because ` +
|
|
171
|
+
`it is not declared in the schema.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
fields = index.fields.concat(["_creationTime", "_id"]);
|
|
175
|
+
}
|
|
176
|
+
fieldPathsToSortBy = fields;
|
|
177
|
+
validateIndexRangeExpression(source, fields);
|
|
178
|
+
docs = view
|
|
179
|
+
.scan(tableName)
|
|
180
|
+
.filter((doc) =>
|
|
181
|
+
source.range.every((filter) => evaluateRangeFilter(doc, filter)),
|
|
182
|
+
);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case "Search": {
|
|
186
|
+
throw KernelRefusal.requiresBinary("textSearch");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const filters = query.operators
|
|
191
|
+
.filter(
|
|
192
|
+
(operator): operator is { filter: FilterJson } => "filter" in operator,
|
|
193
|
+
)
|
|
194
|
+
.map((operator) => operator.filter);
|
|
195
|
+
const limit =
|
|
196
|
+
query.operators.find(
|
|
197
|
+
(operator): operator is { limit: number } => "limit" in operator,
|
|
198
|
+
) ?? null;
|
|
199
|
+
|
|
200
|
+
const orderMultiplier = order === "asc" ? 1 : -1;
|
|
201
|
+
docs.sort((a, b) => {
|
|
202
|
+
for (const fp of fieldPathsToSortBy) {
|
|
203
|
+
const v = compareValues(
|
|
204
|
+
evaluateFieldPath(fp, a),
|
|
205
|
+
evaluateFieldPath(fp, b),
|
|
206
|
+
);
|
|
207
|
+
if (v !== 0) return v * orderMultiplier;
|
|
208
|
+
}
|
|
209
|
+
return 0;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
sortedDocs: docs,
|
|
214
|
+
filterFn: (doc) => filters.every((f) => evaluateFilter(doc, f)),
|
|
215
|
+
limit: limit?.limit ?? null,
|
|
216
|
+
order,
|
|
217
|
+
interval: {
|
|
218
|
+
kind: "interval",
|
|
219
|
+
table,
|
|
220
|
+
fields: fieldPathsToSortBy.includes("_id")
|
|
221
|
+
? fieldPathsToSortBy
|
|
222
|
+
: [...fieldPathsToSortBy, "_id"],
|
|
223
|
+
order,
|
|
224
|
+
range,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A query's documents in order. The source is read when the query starts, as a
|
|
231
|
+
* snapshot, and consumed lazily. The read set holds the prefix the stream has
|
|
232
|
+
* examined, filtered documents included, or the whole range once it ends.
|
|
233
|
+
*/
|
|
234
|
+
export const stream = (
|
|
235
|
+
view: TableView,
|
|
236
|
+
query: SerializedQuery,
|
|
237
|
+
): Iterator<GenericDocument> => {
|
|
238
|
+
const { sortedDocs, filterFn, limit, interval } = resolveSource(view, query);
|
|
239
|
+
const examined = view.read(interval, null);
|
|
240
|
+
return (function* () {
|
|
241
|
+
let returned = 0;
|
|
242
|
+
for (const doc of sortedDocs) {
|
|
243
|
+
if (limit !== null && returned >= limit) return;
|
|
244
|
+
examined(ReadSet.keyOf(interval.fields, doc as StoredDocument));
|
|
245
|
+
if (filterFn(doc)) {
|
|
246
|
+
returned++;
|
|
247
|
+
yield doc;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
examined("end");
|
|
251
|
+
})();
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const encodeCursor = (
|
|
255
|
+
doc: GenericDocument,
|
|
256
|
+
fieldPaths: ReadonlyArray<string>,
|
|
257
|
+
): string =>
|
|
258
|
+
JSON.stringify(
|
|
259
|
+
fieldPaths.map((fp) => {
|
|
260
|
+
const k = evaluateFieldPath(fp, doc);
|
|
261
|
+
return k === undefined ? null : JSON.stringify(convexToJson(k));
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const parseCursor = (c: string | null | undefined) =>
|
|
266
|
+
c
|
|
267
|
+
? (JSON.parse(c) as Array<string | null>).map((k) =>
|
|
268
|
+
k === null ? undefined : jsonToConvex(JSON.parse(k)),
|
|
269
|
+
)
|
|
270
|
+
: null;
|
|
271
|
+
|
|
272
|
+
const compareToCursor = (
|
|
273
|
+
doc: GenericDocument,
|
|
274
|
+
cursorKeys: ReadonlyArray<unknown>,
|
|
275
|
+
fieldPaths: ReadonlyArray<string>,
|
|
276
|
+
order: "asc" | "desc",
|
|
277
|
+
): number => {
|
|
278
|
+
const orderMultiplier = order === "asc" ? 1 : -1;
|
|
279
|
+
for (let i = 0; i < fieldPaths.length; i++) {
|
|
280
|
+
const cmp = compareValues(
|
|
281
|
+
evaluateFieldPath(fieldPaths[i]!, doc),
|
|
282
|
+
cursorKeys[i] as any,
|
|
283
|
+
);
|
|
284
|
+
if (cmp !== 0) return cmp * orderMultiplier;
|
|
285
|
+
}
|
|
286
|
+
return 0;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export interface PageRequest {
|
|
290
|
+
readonly query: SerializedQuery;
|
|
291
|
+
readonly cursor: string | null;
|
|
292
|
+
readonly endCursor?: string | null;
|
|
293
|
+
readonly pageSize: number;
|
|
294
|
+
readonly maximumRowsRead?: number | null;
|
|
295
|
+
readonly maximumBytesRead?: number | null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const paginate = (
|
|
299
|
+
view: TableView,
|
|
300
|
+
{
|
|
301
|
+
query,
|
|
302
|
+
cursor,
|
|
303
|
+
endCursor,
|
|
304
|
+
pageSize,
|
|
305
|
+
maximumRowsRead,
|
|
306
|
+
maximumBytesRead,
|
|
307
|
+
}: PageRequest,
|
|
308
|
+
) => {
|
|
309
|
+
const { sortedDocs, filterFn, order, interval } = resolveSource(view, query);
|
|
310
|
+
// A page from the terminal cursor reads the empty interval past the end, so
|
|
311
|
+
// it depends on nothing.
|
|
312
|
+
if (cursor === "_end_cursor") {
|
|
313
|
+
return {
|
|
314
|
+
page: [],
|
|
315
|
+
isDone: true,
|
|
316
|
+
continueCursor: "_end_cursor",
|
|
317
|
+
splitCursor: null,
|
|
318
|
+
pageStatus: null,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const cursorFields = interval.fields;
|
|
322
|
+
const cursorKeys = parseCursor(cursor);
|
|
323
|
+
const hasEndBoundary = !!endCursor && endCursor !== "_end_cursor";
|
|
324
|
+
const endCursorKeys = hasEndBoundary ? parseCursor(endCursor) : null;
|
|
325
|
+
const examined = view.read(interval, cursorKeys);
|
|
326
|
+
let reachedEnd = true;
|
|
327
|
+
|
|
328
|
+
const page: Array<GenericDocument> = [];
|
|
329
|
+
let isInPage = cursor === null;
|
|
330
|
+
let continueCursor = "_end_cursor";
|
|
331
|
+
let splitCursor: string | null = null;
|
|
332
|
+
let pageStatus: "SplitRecommended" | "SplitRequired" | null = null;
|
|
333
|
+
let rowsRead = 0;
|
|
334
|
+
let bytesRead = 0;
|
|
335
|
+
const readDocCursors: Array<string> = [];
|
|
336
|
+
|
|
337
|
+
for (let i = 0; i < sortedDocs.length; i++) {
|
|
338
|
+
const doc = sortedDocs[i]!;
|
|
339
|
+
const isLastDoc = i === sortedDocs.length - 1;
|
|
340
|
+
if (!isInPage) {
|
|
341
|
+
if (cursorKeys) {
|
|
342
|
+
if (compareToCursor(doc, cursorKeys, cursorFields, order) > 0) {
|
|
343
|
+
isInPage = true;
|
|
344
|
+
} else {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (hasEndBoundary && endCursorKeys) {
|
|
350
|
+
const cmp = compareToCursor(doc, endCursorKeys, cursorFields, order);
|
|
351
|
+
if (cmp >= 0) {
|
|
352
|
+
reachedEnd = false;
|
|
353
|
+
examined(endCursorKeys);
|
|
354
|
+
if (cmp === 0) {
|
|
355
|
+
rowsRead += 1;
|
|
356
|
+
bytesRead += getDocumentSize(doc);
|
|
357
|
+
readDocCursors.push(encodeCursor(doc, cursorFields));
|
|
358
|
+
if (filterFn(doc)) page.push(doc);
|
|
359
|
+
continueCursor = encodeCursor(doc, cursorFields);
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
rowsRead += 1;
|
|
365
|
+
bytesRead += getDocumentSize(doc);
|
|
366
|
+
readDocCursors.push(encodeCursor(doc, cursorFields));
|
|
367
|
+
examined(ReadSet.keyOf(cursorFields, doc as StoredDocument));
|
|
368
|
+
const hitLimit =
|
|
369
|
+
(!!maximumRowsRead && rowsRead >= maximumRowsRead) ||
|
|
370
|
+
(!!maximumBytesRead && bytesRead >= maximumBytesRead);
|
|
371
|
+
if (filterFn(doc)) page.push(doc);
|
|
372
|
+
if (hitLimit) {
|
|
373
|
+
reachedEnd = false;
|
|
374
|
+
pageStatus = "SplitRequired";
|
|
375
|
+
continueCursor = encodeCursor(doc, cursorFields);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (!endCursor && page.length >= pageSize) {
|
|
379
|
+
reachedEnd = isLastDoc;
|
|
380
|
+
if (!isLastDoc) continueCursor = encodeCursor(doc, cursorFields);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// A page bounded by an end cursor examined the range only up to it.
|
|
385
|
+
if (reachedEnd) examined(endCursorKeys ?? "end");
|
|
386
|
+
|
|
387
|
+
if (pageStatus === "SplitRequired" && readDocCursors.length >= 2) {
|
|
388
|
+
splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;
|
|
389
|
+
} else if (
|
|
390
|
+
pageStatus === null &&
|
|
391
|
+
rowsRead > pageSize + 1 &&
|
|
392
|
+
readDocCursors.length >= 2
|
|
393
|
+
) {
|
|
394
|
+
pageStatus = "SplitRecommended";
|
|
395
|
+
splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
page,
|
|
400
|
+
isDone: continueCursor === "_end_cursor",
|
|
401
|
+
continueCursor,
|
|
402
|
+
splitCursor,
|
|
403
|
+
pageStatus,
|
|
404
|
+
};
|
|
405
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The kernel never answers this operation in process. Only the binary does.
|
|
5
|
+
*/
|
|
6
|
+
export class RequiresBinary extends Schema.TaggedError<RequiresBinary>()(
|
|
7
|
+
"RequiresBinary",
|
|
8
|
+
{
|
|
9
|
+
operation: Schema.Literals(["textSearch", "vectorSearch"]),
|
|
10
|
+
message: Schema.String,
|
|
11
|
+
},
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A later kernel row supplies this operation. Until then the kernel refuses it
|
|
16
|
+
* rather than answer with convex-test's behaviour.
|
|
17
|
+
*/
|
|
18
|
+
export class KernelRowPending extends Schema.TaggedError<KernelRowPending>()(
|
|
19
|
+
"KernelRowPending",
|
|
20
|
+
{
|
|
21
|
+
operation: Schema.String,
|
|
22
|
+
row: Schema.Int,
|
|
23
|
+
message: Schema.String,
|
|
24
|
+
},
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A component was registered after the test backend opened, so its modules
|
|
29
|
+
* would load after a function had already run.
|
|
30
|
+
*/
|
|
31
|
+
export class RegisteredAfterOpen extends Schema.TaggedError<RegisteredAfterOpen>()(
|
|
32
|
+
"RegisteredAfterOpen",
|
|
33
|
+
{ componentPath: Schema.String, message: Schema.String },
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Every attempt of a mutation read data a commit changed before it could
|
|
38
|
+
* publish. The kernel gives up on attempt 5.
|
|
39
|
+
*/
|
|
40
|
+
export class OptimisticConcurrencyControlFailure extends Schema.TaggedError<OptimisticConcurrencyControlFailure>()(
|
|
41
|
+
"OptimisticConcurrencyControlFailure",
|
|
42
|
+
{ attempts: Schema.Int, message: Schema.String },
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A syscall reached a mutation's attempt after the mutation returned and its
|
|
47
|
+
* syscalls drained, so the attempt was already held or published.
|
|
48
|
+
*/
|
|
49
|
+
export class ClosedAttempt extends Schema.TaggedError<ClosedAttempt>()(
|
|
50
|
+
"ClosedAttempt",
|
|
51
|
+
{ operation: Schema.String, message: Schema.String },
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `apply` named an attempt the kernel does not hold: it was already applied, or
|
|
56
|
+
* never prepared.
|
|
57
|
+
*/
|
|
58
|
+
export class StaleAttempt extends Schema.TaggedError<StaleAttempt>()(
|
|
59
|
+
"StaleAttempt",
|
|
60
|
+
{ id: Schema.String, message: Schema.String },
|
|
61
|
+
) {}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The runtime answered a commit timestamp no greater than the previous
|
|
65
|
+
* commit's.
|
|
66
|
+
*/
|
|
67
|
+
export class CommitTsNotIncreasing extends Schema.TaggedError<CommitTsNotIncreasing>()(
|
|
68
|
+
"CommitTsNotIncreasing",
|
|
69
|
+
{
|
|
70
|
+
version: Schema.Int,
|
|
71
|
+
previous: Schema.BigInt,
|
|
72
|
+
commitTs: Schema.BigInt,
|
|
73
|
+
message: Schema.String,
|
|
74
|
+
},
|
|
75
|
+
) {}
|
|
76
|
+
|
|
77
|
+
export const requiresBinary = (operation: RequiresBinary["operation"]) =>
|
|
78
|
+
new RequiresBinary({
|
|
79
|
+
operation,
|
|
80
|
+
message: `The test kernel does not simulate ${operation}. Run this case against the Convex binary.`,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const rowPending = (operation: string, row: number) =>
|
|
84
|
+
new KernelRowPending({
|
|
85
|
+
operation,
|
|
86
|
+
row,
|
|
87
|
+
message: `The test kernel supplies ${operation} in build-order row ${row}.`,
|
|
88
|
+
});
|