@gaganref/confect-test 10.3.1-fork.88 → 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.
Files changed (46) hide show
  1. package/dist/internal/Attempt.d.ts +77 -0
  2. package/dist/internal/Attempt.d.ts.map +1 -0
  3. package/dist/internal/Attempt.js +154 -0
  4. package/dist/internal/Attempt.js.map +1 -0
  5. package/dist/internal/ConvexTest.d.ts +34 -0
  6. package/dist/internal/ConvexTest.d.ts.map +1 -0
  7. package/dist/internal/ConvexTest.js +59 -0
  8. package/dist/internal/ConvexTest.js.map +1 -0
  9. package/dist/internal/Execution.d.ts +167 -0
  10. package/dist/internal/Execution.d.ts.map +1 -0
  11. package/dist/internal/Execution.js +790 -0
  12. package/dist/internal/Execution.js.map +1 -0
  13. package/dist/internal/Globals.d.ts +11 -0
  14. package/dist/internal/Globals.d.ts.map +1 -0
  15. package/dist/internal/Globals.js +76 -0
  16. package/dist/internal/Globals.js.map +1 -0
  17. package/dist/internal/Kernel.d.ts +93 -0
  18. package/dist/internal/Kernel.d.ts.map +1 -0
  19. package/dist/internal/Kernel.js +91 -0
  20. package/dist/internal/Kernel.js.map +1 -0
  21. package/dist/internal/KernelQuery.d.ts +55 -0
  22. package/dist/internal/KernelQuery.d.ts.map +1 -0
  23. package/dist/internal/KernelQuery.js +205 -0
  24. package/dist/internal/KernelQuery.js.map +1 -0
  25. package/dist/internal/KernelRefusal.d.ts +77 -0
  26. package/dist/internal/KernelRefusal.d.ts.map +1 -0
  27. package/dist/internal/KernelRefusal.js +73 -0
  28. package/dist/internal/KernelRefusal.js.map +1 -0
  29. package/dist/internal/KernelValues.d.ts +136 -0
  30. package/dist/internal/KernelValues.d.ts.map +1 -0
  31. package/dist/internal/KernelValues.js +266 -0
  32. package/dist/internal/KernelValues.js.map +1 -0
  33. package/dist/internal/ReadSet.d.ts +55 -0
  34. package/dist/internal/ReadSet.d.ts.map +1 -0
  35. package/dist/internal/ReadSet.js +29 -0
  36. package/dist/internal/ReadSet.js.map +1 -0
  37. package/package.json +3 -3
  38. package/src/internal/Attempt.ts +185 -0
  39. package/src/internal/ConvexTest.ts +118 -0
  40. package/src/internal/Execution.ts +1430 -0
  41. package/src/internal/Globals.ts +93 -0
  42. package/src/internal/Kernel.ts +190 -0
  43. package/src/internal/KernelQuery.ts +405 -0
  44. package/src/internal/KernelRefusal.ts +88 -0
  45. package/src/internal/KernelValues.ts +725 -0
  46. package/src/internal/ReadSet.ts +108 -0
@@ -0,0 +1,266 @@
1
+ import { CommitTsPlaceholder, compareValues, convexToJson, jsonToConvex } from "convex/values";
2
+ //#region src/internal/KernelValues.ts
3
+ function tableNameFromId(id) {
4
+ const match = id.match(/^[0-9]+/);
5
+ if (!match || match[0].length === id.length) return null;
6
+ return id.slice(match[0].length);
7
+ }
8
+ function deserializeConvexErrorData(thrown) {
9
+ if (typeof thrown === "object" && thrown !== null && Symbol.for("ConvexError") in thrown && typeof thrown.data === "string") try {
10
+ thrown.data = jsonToConvex(JSON.parse(thrown.data));
11
+ } catch {}
12
+ return thrown;
13
+ }
14
+ function isSimpleObject(value) {
15
+ const isObject = typeof value === "object";
16
+ const prototype = Object.getPrototypeOf(value);
17
+ const isSimple = prototype === null || prototype === Object.prototype || prototype?.constructor?.name === "Object";
18
+ return isObject && isSimple;
19
+ }
20
+ const MAX_COMMIT_TS = 2n ** 63n - 1n;
21
+ function hasPendingValues(value) {
22
+ if (value instanceof CommitTsPlaceholder) return true;
23
+ if (Array.isArray(value)) return value.some(hasPendingValues);
24
+ if (value !== null && value !== void 0 && isSimpleObject(value)) return Object.values(value).some(hasPendingValues);
25
+ return false;
26
+ }
27
+ function resolvePendingValues(value) {
28
+ return substituteCommitTs(value, MAX_COMMIT_TS);
29
+ }
30
+ function resolveCommittedValues(value, commitTs) {
31
+ return substituteCommitTs(value, commitTs);
32
+ }
33
+ function substituteCommitTs(value, commitTs) {
34
+ if (!hasPendingValues(value)) return value;
35
+ if (value instanceof CommitTsPlaceholder) return commitTs;
36
+ if (Array.isArray(value)) return value.map((v) => substituteCommitTs(v, commitTs));
37
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, substituteCommitTs(v, commitTs)]));
38
+ }
39
+ function evaluateFieldPath(fieldPath, document) {
40
+ const pathParts = fieldPath.split(".");
41
+ let result = document;
42
+ for (const p of pathParts) result = result !== void 0 && result !== null && isSimpleObject(result) ? result[p] : void 0;
43
+ return result === void 0 ? void 0 : resolvePendingValues(result);
44
+ }
45
+ function evaluateFilter(document, filter) {
46
+ if (filter.$eq !== void 0) return compareValues(evaluateFilter(document, filter.$eq[0]), evaluateFilter(document, filter.$eq[1])) === 0;
47
+ if (filter.$neq !== void 0) return compareValues(evaluateFilter(document, filter.$neq[0]), evaluateFilter(document, filter.$neq[1])) !== 0;
48
+ if (filter.$and !== void 0) return filter.$and.every((child) => evaluateFilter(document, child));
49
+ if (filter.$or !== void 0) return filter.$or.some((child) => evaluateFilter(document, child));
50
+ if (filter.$not !== void 0) return !evaluateFilter(document, filter.$not);
51
+ if (filter.$gt !== void 0) return evaluateFilter(document, filter.$gt[0]) > evaluateFilter(document, filter.$gt[1]);
52
+ if (filter.$gte !== void 0) return evaluateFilter(document, filter.$gte[0]) >= evaluateFilter(document, filter.$gte[1]);
53
+ if (filter.$lt !== void 0) return evaluateFilter(document, filter.$lt[0]) < evaluateFilter(document, filter.$lt[1]);
54
+ if (filter.$lte !== void 0) return evaluateFilter(document, filter.$lte[0]) <= evaluateFilter(document, filter.$lte[1]);
55
+ if (filter.$add !== void 0) return evaluateFilter(document, filter.$add[0]) + evaluateFilter(document, filter.$add[1]);
56
+ if (filter.$sub !== void 0) return evaluateFilter(document, filter.$sub[0]) - evaluateFilter(document, filter.$sub[1]);
57
+ if (filter.$mul !== void 0) return evaluateFilter(document, filter.$mul[0]) * evaluateFilter(document, filter.$mul[1]);
58
+ if (filter.$div !== void 0) return evaluateFilter(document, filter.$div[0]) / evaluateFilter(document, filter.$div[1]);
59
+ if (filter.$mod !== void 0) return evaluateFilter(document, filter.$mod[0]) % evaluateFilter(document, filter.$mod[1]);
60
+ if (filter.$field !== void 0) return evaluateFieldPath(filter.$field, document);
61
+ if (filter.$literal !== void 0) return evaluateQueryValue(filter.$literal);
62
+ throw new Error(`not implemented: ${JSON.stringify(filter)}`);
63
+ }
64
+ function evaluateRangeFilter(document, expr) {
65
+ const result = evaluateFieldPath(expr.fieldPath, document);
66
+ const value = evaluateQueryValue(expr.value);
67
+ switch (expr.type) {
68
+ case "Eq": return compareValues(result, value) === 0;
69
+ case "Gt": return compareValues(result, value) > 0;
70
+ case "Gte": return compareValues(result, value) >= 0;
71
+ case "Lt": return compareValues(result, value) < 0;
72
+ case "Lte": return compareValues(result, value) <= 0;
73
+ }
74
+ }
75
+ function evaluateValue(value) {
76
+ if (typeof value === "object" && value !== null && "$undefined" in value) return;
77
+ return jsonToConvex(value);
78
+ }
79
+ function evaluateQueryValue(value) {
80
+ const evaluatedValue = evaluateValue(value);
81
+ return evaluatedValue === void 0 ? void 0 : resolvePendingValues(evaluatedValue);
82
+ }
83
+ function validateIndexRangeExpression(source, fields) {
84
+ let fieldIndex = 0;
85
+ let state = "eq";
86
+ for (const [filterIndex, filter] of source.range.entries()) {
87
+ if (state === "done") throw new Error(`Incorrect operator used in \`withIndex\`, cannot chain more operators after both \`.gt\` and \`.lt\` were already used, got \`${printIndexOperator(filter)}\`.`);
88
+ const filterType = filter.type == "Gt" || filter.type == "Gte" ? "gt" : filter.type == "Lt" || filter.type == "Lte" ? "lt" : "eq";
89
+ switch (`${state}|${filterType}`) {
90
+ case "eq|eq":
91
+ case "eq|gt":
92
+ case "eq|lt":
93
+ if (filter.fieldPath === fields[fieldIndex]) {
94
+ fieldIndex += 1;
95
+ state = filterType;
96
+ continue;
97
+ }
98
+ throw new Error(`Incorrect field used in \`withIndex\`, expected "${fields[fieldIndex]}", got "${filter.fieldPath}"`);
99
+ case "lt|gt":
100
+ case "gt|lt":
101
+ if (fieldIndex > 0 && filter.fieldPath === fields[fieldIndex - 1]) {
102
+ state = "done";
103
+ continue;
104
+ }
105
+ throw new Error(`Incorrect field used in \`withIndex\`, \`.gt\` and \`.lt\` must operate on the same field, expected "${fields[fieldIndex - 1]}", got "${filter.fieldPath}"`);
106
+ default: throw new Error(`Incorrect operator used in \`withIndex\`, cannot chain \`.${filter.type.toLowerCase()}()\` after \`.${source.range[filterIndex - 1].type.toLowerCase()}()\``);
107
+ }
108
+ }
109
+ }
110
+ function printIndexOperator(filter) {
111
+ return `.${filter.type.toLowerCase()}(${filter.fieldPath}, ${JSON.stringify(filter.value)})`;
112
+ }
113
+ function validateFieldNames(validator) {
114
+ if (validator.type === "object") Object.keys(validator.value).forEach((fieldName) => {
115
+ if (!isValidIdentifier(fieldName)) throw new Error(`Field names must be valid identifiers, got "${fieldName}"`);
116
+ });
117
+ if (validator.type === "union") validator.value.forEach(validateFieldNames);
118
+ }
119
+ function isValidIdentifier(name) {
120
+ return /(^_(id|creationTime)$)|^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
121
+ }
122
+ function validateValidator(validator, value) {
123
+ switch (validator.type) {
124
+ case "null":
125
+ if (value !== null) throw new Error(`Validator error: Expected \`null\`, got \`${value}\``);
126
+ return;
127
+ case "number":
128
+ if (typeof value !== "number") throw new Error(`Validator error: Expected \`number\`, got \`${value}\``);
129
+ return;
130
+ case "bigint":
131
+ if (typeof value !== "bigint") throw new Error(`Validator error: Expected \`bigint\`, got \`${value}\``);
132
+ return;
133
+ case "commitTs":
134
+ if (typeof value !== "bigint" && !(value instanceof CommitTsPlaceholder)) throw new Error(`Validator error: Expected \`v.commitTs()\`, got \`${value}\``);
135
+ return;
136
+ case "boolean":
137
+ if (typeof value !== "boolean") throw new Error(`Validator error: Expected \`boolean\`, got \`${value}\``);
138
+ return;
139
+ case "string":
140
+ if (typeof value !== "string") throw new Error(`Validator error: Expected \`string\`, got \`${value}\``);
141
+ return;
142
+ case "bytes":
143
+ if (!(value instanceof ArrayBuffer)) throw new Error(`Validator error: Expected \`ArrayBuffer\`, got \`${value}\``);
144
+ return;
145
+ case "any": return;
146
+ case "literal":
147
+ if (value !== validator.value) throw new Error(`Validator error: Expected \`${validator.value}\`, got \`${value}\``);
148
+ return;
149
+ case "id":
150
+ if (typeof value !== "string") throw new Error(`Validator error: Expected \`string\`, got \`${value}\``);
151
+ if (tableNameFromId(value) !== validator.tableName) throw new Error(`Validator error: Expected ID for table "${validator.tableName}", got \`${value}\``);
152
+ return;
153
+ case "array":
154
+ if (!Array.isArray(value)) throw new Error(`Validator error: Expected \`Array\`, got \`${value}\``);
155
+ for (const v of value) validateValidator(validator.value, v);
156
+ return;
157
+ case "union": {
158
+ let isValid = false;
159
+ for (const v of validator.value) try {
160
+ validateValidator(v, value);
161
+ isValid = true;
162
+ break;
163
+ } catch {}
164
+ if (!isValid) throw new Error(`Validator error: Expected one of ${validator.value.map((v) => v.type).join(", ")}, got \`${JSON.stringify(convexToJson(value))}\``);
165
+ return;
166
+ }
167
+ case "object":
168
+ if (typeof value !== "object") throw new Error(`Validator error: Expected \`object\`, got \`${value}\``);
169
+ if (!isSimpleObject(value)) throw new Error(`Validator error: Expected a plain old JavaScript \`object\`, got \`${value}\``);
170
+ for (const [k, { fieldType, optional }] of Object.entries(validator.value)) if (value[k] === void 0) {
171
+ if (!optional) throw new Error(`Validator error: Missing required field \`${k}\` in object`);
172
+ } else validateValidator(fieldType, value[k]);
173
+ for (const k of Object.keys(value)) if (validator.value[k] === void 0) throw new Error(`Validator error: Unexpected field \`${k}\` in object`);
174
+ return;
175
+ case "record":
176
+ if (value === null || typeof value !== "object" || !isSimpleObject(value)) throw new Error(`Validator error: Expected a plain old JavaScript \`object\` for a record, got \`${value}\``);
177
+ for (const [k, fieldValue] of Object.entries(value)) {
178
+ validateValidator(validator.keys, k);
179
+ if (fieldValue !== void 0) validateValidator(validator.values.fieldType, fieldValue);
180
+ }
181
+ return;
182
+ default: throw new Error(`The test kernel cannot check a validator of type \`${validator.type}\`.`);
183
+ }
184
+ }
185
+ const JWT_CLAIM_BY_IDENTITY_ATTRIBUTE = {
186
+ subject: "sub",
187
+ issuer: "iss",
188
+ name: "name",
189
+ givenName: "given_name",
190
+ familyName: "family_name",
191
+ nickname: "nickname",
192
+ preferredUsername: "preferred_username",
193
+ profileUrl: "profile",
194
+ pictureUrl: "picture",
195
+ email: "email",
196
+ emailVerified: "email_verified",
197
+ gender: "gender",
198
+ birthday: "birthdate",
199
+ timezone: "zoneinfo",
200
+ language: "locale",
201
+ phoneNumber: "phone_number",
202
+ phoneNumberVerified: "phone_number_verified",
203
+ address: "address",
204
+ updatedAt: "updated_at"
205
+ };
206
+ function base64UrlEncode(value) {
207
+ return base64UrlEncodeBytes(new TextEncoder().encode(value));
208
+ }
209
+ function base64UrlEncodeBytes(bytes) {
210
+ let binary = "";
211
+ for (const byte of bytes) binary += String.fromCharCode(byte);
212
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
213
+ }
214
+ function createTestJwt(identity) {
215
+ const claims = Object.create(null);
216
+ for (const [attribute, value] of Object.entries(identity)) {
217
+ if (value === void 0 || attribute === "tokenIdentifier") continue;
218
+ const claimName = Object.hasOwn(JWT_CLAIM_BY_IDENTITY_ATTRIBUTE, attribute) ? JWT_CLAIM_BY_IDENTITY_ATTRIBUTE[attribute] : attribute;
219
+ claims[claimName] = value;
220
+ }
221
+ claims.iss = identity.issuer;
222
+ claims.sub = identity.subject;
223
+ return `${base64UrlEncode(JSON.stringify({
224
+ alg: "none",
225
+ typ: "JWT"
226
+ }))}.${base64UrlEncode(JSON.stringify(claims))}.`;
227
+ }
228
+ function parseArgs(args) {
229
+ if (args === void 0) return {};
230
+ if (!isSimpleObject(args)) throw new Error(`The arguments to a Convex function must be an object. Received: ${args}`);
231
+ return args;
232
+ }
233
+ function validateFunctionArgs(argsValidator, args, functionPath, allowPendingValues) {
234
+ if (!allowPendingValues && hasPendingValues(args)) throw new Error(`Invalid arguments for "${functionPath.udfPath}": an unresolved placeholder value can only be passed to a query or mutation called from a mutation`);
235
+ validateValidator(argsValidator, resolvePendingValues(args));
236
+ }
237
+ function validateReturnValue(returnsValidator, result, functionType, functionPath) {
238
+ if (returnsValidator !== null) try {
239
+ validateValidator(returnsValidator, resolvePendingValues(result));
240
+ } catch (e) {
241
+ throw new Error(`Return value validation failed for ${functionType} "${functionPath.udfPath}":\n${e.message}`, { cause: e });
242
+ }
243
+ }
244
+ function createFunctionHandle(functionPath) {
245
+ return `function://${functionPath.componentPath};${functionPath.udfPath}`;
246
+ }
247
+ function parseFunctionHandle(handle) {
248
+ const [componentPath, udfPath] = handle.split("function://")[1].split(";");
249
+ return {
250
+ componentPath,
251
+ udfPath
252
+ };
253
+ }
254
+ function simpleHash(string) {
255
+ let hash = 0;
256
+ for (let i = 0; i < string.length; i++) {
257
+ const char = string.charCodeAt(i);
258
+ hash = (hash << 5) - hash + char;
259
+ hash = hash & hash;
260
+ }
261
+ return hash;
262
+ }
263
+ //#endregion
264
+ export { JWT_CLAIM_BY_IDENTITY_ATTRIBUTE, MAX_COMMIT_TS, base64UrlEncode, base64UrlEncodeBytes, createFunctionHandle, createTestJwt, deserializeConvexErrorData, evaluateFieldPath, evaluateFilter, evaluateQueryValue, evaluateRangeFilter, evaluateValue, hasPendingValues, isSimpleObject, isValidIdentifier, parseArgs, parseFunctionHandle, printIndexOperator, resolveCommittedValues, resolvePendingValues, simpleHash, substituteCommitTs, tableNameFromId, validateFieldNames, validateFunctionArgs, validateIndexRangeExpression, validateReturnValue, validateValidator };
265
+
266
+ //# sourceMappingURL=KernelValues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KernelValues.js","names":["unknown"],"sources":["../../src/internal/KernelValues.ts"],"sourcesContent":["// Derived from convex-test 0.0.65 (index.ts), Copyright Convex, Inc.,\n// licensed under the Apache License, Version 2.0\n// (https://www.apache.org/licenses/LICENSE-2.0). The value, filter, range,\n// validator and argument rules are kept as convex-test states them, so the\n// kernel answers a function the way convex-test does. The storage, attempts\n// and execution around them are the kernel's own.\n\nimport type { GenericDocument } from \"convex/server\";\nimport {\n CommitTsPlaceholder,\n type GenericId,\n type JSONValue,\n type Value,\n compareValues,\n convexToJson,\n jsonToConvex,\n} from \"convex/values\";\n\n/**\n * A function address inside one test backend: its component and its\n * `module:export` path.\n */\nexport type FunctionPath = {\n readonly componentPath: string;\n readonly udfPath: string;\n};\n\nexport type FilterJson =\n | { $eq: [FilterJson, FilterJson] }\n | { $field: string }\n | JSONValue;\n\nexport type QueryOperator = { filter: FilterJson } | { limit: number };\nexport type Source =\n | { type: \"FullTableScan\"; tableName: string; order: \"asc\" | \"desc\" | null }\n | {\n type: \"IndexRange\";\n indexName: string;\n range: ReadonlyArray<SerializedRangeExpression>;\n order: \"asc\" | \"desc\" | null;\n }\n | {\n type: \"Search\";\n indexName: string;\n filters: ReadonlyArray<SerializedSearchFilter>;\n };\n\nexport type SerializedQuery = {\n source: Source;\n operators: Array<QueryOperator>;\n};\n\nexport type SerializedRangeExpression = {\n type: \"Eq\" | \"Gt\" | \"Gte\" | \"Lt\" | \"Lte\";\n fieldPath: string;\n value: JSONValue;\n};\n\nexport type SerializedSearchFilter =\n | {\n type: \"Search\";\n fieldPath: string;\n value: string;\n }\n | {\n type: \"Eq\";\n fieldPath: string;\n value: JSONValue;\n };\n\nexport type StoredDocument = GenericDocument & {\n _id: DocumentId;\n _creationTime: number;\n};\n\nexport type Index = {\n indexDescriptor: string;\n fields: string[];\n};\n\nexport type TableName = string;\n\nexport type DocumentId = GenericId<TableName>;\n\nexport function tableNameFromId(id: string) {\n const match = id.match(/^[0-9]+/);\n if (!match || match[0].length === id.length) {\n return null;\n }\n return id.slice(match[0].length);\n}\nexport function deserializeConvexErrorData(thrown: unknown) {\n if (\n typeof thrown === \"object\" &&\n thrown !== null &&\n Symbol.for(\"ConvexError\") in thrown &&\n typeof (thrown as { data: unknown }).data === \"string\"\n ) {\n try {\n (thrown as { data: unknown }).data = jsonToConvex(\n JSON.parse((thrown as { data: string }).data),\n );\n } catch {\n // Data wasn't a serialized JSON string — leave it untouched.\n }\n }\n return thrown;\n}\n\nexport function isSimpleObject(value: unknown) {\n const isObject = typeof value === \"object\";\n const prototype = Object.getPrototypeOf(value);\n const isSimple =\n prototype === null ||\n prototype === Object.prototype ||\n // Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous\n // conditions but are still simple objects.\n prototype?.constructor?.name === \"Object\";\n return isObject && isSimple;\n}\n\n// Commit timestamp placeholders are resolved to `i64::MAX` before commit.\nexport const MAX_COMMIT_TS = 2n ** 63n - 1n;\n\nexport function hasPendingValues(value: Value): boolean {\n if (value instanceof CommitTsPlaceholder) {\n return true;\n }\n if (Array.isArray(value)) {\n return value.some(hasPendingValues);\n }\n if (value !== null && value !== undefined && isSimpleObject(value)) {\n return Object.values(value).some(hasPendingValues);\n }\n return false;\n}\n\n// The value as the transaction sees it before committing.\nexport function resolvePendingValues(value: Value): Value {\n return substituteCommitTs(value, MAX_COMMIT_TS);\n}\n\n// The value as it is committed.\nexport function resolveCommittedValues<T extends Value>(\n value: T,\n commitTs: bigint,\n): T {\n return substituteCommitTs(value, commitTs);\n}\n\nexport function substituteCommitTs<T extends Value>(\n value: T,\n commitTs: bigint,\n): T {\n if (!hasPendingValues(value)) {\n return value;\n }\n if (value instanceof CommitTsPlaceholder) {\n return commitTs as T;\n }\n if (Array.isArray(value)) {\n return value.map((v) => substituteCommitTs(v, commitTs)) as T;\n }\n return Object.fromEntries(\n Object.entries(value as object).map(([k, v]) => [\n k,\n substituteCommitTs(v, commitTs),\n ]),\n ) as T;\n}\n\nexport function evaluateFieldPath(fieldPath: string, document: any) {\n const pathParts = fieldPath.split(\".\");\n let result: Value | undefined = document;\n for (const p of pathParts) {\n result =\n result !== undefined && result !== null && isSimpleObject(result)\n ? (result as any)[p]\n : undefined;\n }\n return result === undefined ? undefined : resolvePendingValues(result);\n}\n\nexport function evaluateFilter(\n document: GenericDocument,\n filter: any,\n): Value | undefined {\n if (filter.$eq !== undefined) {\n return (\n compareValues(\n evaluateFilter(document, filter.$eq[0]),\n evaluateFilter(document, filter.$eq[1]),\n ) === 0\n );\n }\n if (filter.$neq !== undefined) {\n return (\n compareValues(\n evaluateFilter(document, filter.$neq[0]),\n evaluateFilter(document, filter.$neq[1]),\n ) !== 0\n );\n }\n if (filter.$and !== undefined) {\n return filter.$and.every((child: any) => evaluateFilter(document, child));\n }\n if (filter.$or !== undefined) {\n return filter.$or.some((child: any) => evaluateFilter(document, child));\n }\n if (filter.$not !== undefined) {\n return !evaluateFilter(document, filter.$not);\n }\n if (filter.$gt !== undefined) {\n return (\n evaluateFilter(document, filter.$gt[0])! >\n evaluateFilter(document, filter.$gt[1])!\n );\n }\n if (filter.$gte !== undefined) {\n return (\n evaluateFilter(document, filter.$gte[0])! >=\n evaluateFilter(document, filter.$gte[1])!\n );\n }\n if (filter.$lt !== undefined) {\n return (\n evaluateFilter(document, filter.$lt[0])! <\n evaluateFilter(document, filter.$lt[1])!\n );\n }\n if (filter.$lte !== undefined) {\n return (\n evaluateFilter(document, filter.$lte[0])! <=\n evaluateFilter(document, filter.$lte[1])!\n );\n }\n if (filter.$add !== undefined) {\n return (\n (evaluateFilter(document, filter.$add[0]) as number) +\n (evaluateFilter(document, filter.$add[1]) as number)\n );\n }\n if (filter.$sub !== undefined) {\n return (\n (evaluateFilter(document, filter.$sub[0]) as number) -\n (evaluateFilter(document, filter.$sub[1]) as number)\n );\n }\n if (filter.$mul !== undefined) {\n return (\n (evaluateFilter(document, filter.$mul[0]) as number) *\n (evaluateFilter(document, filter.$mul[1]) as number)\n );\n }\n if (filter.$div !== undefined) {\n return (\n (evaluateFilter(document, filter.$div[0]) as number) /\n (evaluateFilter(document, filter.$div[1]) as number)\n );\n }\n if (filter.$mod !== undefined) {\n return (\n (evaluateFilter(document, filter.$mod[0]) as number) %\n (evaluateFilter(document, filter.$mod[1]) as number)\n );\n }\n if (filter.$field !== undefined) {\n return evaluateFieldPath(filter.$field, document);\n }\n if (filter.$literal !== undefined) {\n return evaluateQueryValue(filter.$literal);\n }\n throw new Error(`not implemented: ${JSON.stringify(filter)}`);\n}\n\nexport function evaluateRangeFilter(\n document: GenericDocument,\n expr: SerializedRangeExpression,\n) {\n const result = evaluateFieldPath(expr.fieldPath, document);\n const value = evaluateQueryValue(expr.value);\n switch (expr.type) {\n case \"Eq\":\n return compareValues(result, value) === 0;\n case \"Gt\":\n return compareValues(result, value) > 0;\n case \"Gte\":\n return compareValues(result, value) >= 0;\n case \"Lt\":\n return compareValues(result, value) < 0;\n case \"Lte\":\n return compareValues(result, value) <= 0;\n }\n}\n\n// A value used as a written field.\nexport function evaluateValue(value: JSONValue) {\n if (typeof value === \"object\" && value !== null && \"$undefined\" in value) {\n return undefined;\n }\n return jsonToConvex(value);\n}\n\n// A value used as a query bound or literal.\nexport function evaluateQueryValue(value: JSONValue) {\n const evaluatedValue = evaluateValue(value);\n return evaluatedValue === undefined\n ? undefined\n : resolvePendingValues(evaluatedValue);\n}\n\n// An index range expression is always a chained list of:\n//\n// 0 or more equality expressions defined with .eq.\n// [Optionally] A lower bound expression defined with .gt or .gte.\n// [Optionally] An upper bound expression defined with .lt or .lte.\nexport function validateIndexRangeExpression(\n source: Source & { type: \"IndexRange\" },\n fields: string[],\n) {\n let fieldIndex = 0;\n let state: \"eq\" | \"gt\" | \"lt\" | \"done\" = \"eq\";\n for (const [filterIndex, filter] of source.range.entries()) {\n if (state === \"done\") {\n throw new Error(\n `Incorrect operator used in \\`withIndex\\`, cannot chain ` +\n `more operators after both \\`.gt\\` and \\`.lt\\` were already used, ` +\n `got \\`${printIndexOperator(filter)}\\`.`,\n );\n }\n\n const filterType: \"eq\" | \"gt\" | \"lt\" =\n filter.type == \"Gt\" || filter.type == \"Gte\"\n ? \"gt\"\n : filter.type == \"Lt\" || filter.type == \"Lte\"\n ? \"lt\"\n : \"eq\";\n\n switch (`${state}|${filterType}`) {\n // Allow to operate on the current indexed field\n case \"eq|eq\":\n case \"eq|gt\":\n case \"eq|lt\":\n if (filter.fieldPath === fields[fieldIndex]) {\n fieldIndex += 1;\n state = filterType;\n continue;\n }\n throw new Error(\n `Incorrect field used in \\`withIndex\\`, ` +\n `expected \"${fields[fieldIndex]}\", got \"${filter.fieldPath}\"`,\n );\n\n // Allow to operate on the previous field (gt and lt must operate on same field)\n case \"lt|gt\":\n case \"gt|lt\":\n if (fieldIndex > 0 && filter.fieldPath === fields[fieldIndex - 1]) {\n state = \"done\";\n continue;\n }\n throw new Error(\n `Incorrect field used in \\`withIndex\\`, ` +\n `\\`.gt\\` and \\`.lt\\` must operate on the same field, ` +\n `expected \"${fields[fieldIndex - 1]}\", got \"${filter.fieldPath}\"`,\n );\n\n default:\n throw new Error(\n `Incorrect operator used in \\`withIndex\\`, ` +\n `cannot chain \\`.${filter.type.toLowerCase()}()\\` ` +\n `after \\`.${source.range[filterIndex - 1].type.toLowerCase()}()\\``,\n );\n }\n }\n}\n\nexport function printIndexOperator(filter: SerializedRangeExpression) {\n return `.${filter.type.toLowerCase()}(${filter.fieldPath}, ${JSON.stringify(filter.value)})`;\n}\n\nexport function validateFieldNames(validator: ValidatorJSON) {\n if (validator.type === \"object\") {\n Object.keys(validator.value).forEach((fieldName) => {\n if (!isValidIdentifier(fieldName)) {\n throw new Error(\n `Field names must be valid identifiers, got \"${fieldName}\"`,\n );\n }\n });\n }\n\n if (validator.type === \"union\") {\n validator.value.forEach(validateFieldNames);\n }\n}\n\nexport function isValidIdentifier(name: string) {\n return /(^_(id|creationTime)$)|^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);\n}\n\nexport type ObjectFieldType = { fieldType: ValidatorJSON; optional: boolean };\n\nexport type ValidatorJSON =\n | {\n type: \"null\";\n }\n | { type: \"number\" }\n | { type: \"bigint\" }\n | { type: \"commitTs\" }\n | { type: \"boolean\" }\n | { type: \"string\" }\n | { type: \"bytes\" }\n | { type: \"any\" }\n | {\n type: \"literal\";\n value: JSONValue;\n }\n | { type: \"id\"; tableName: string }\n | { type: \"array\"; value: ValidatorJSON }\n | { type: \"object\"; value: Record<string, ObjectFieldType> }\n | { type: \"record\"; keys: ValidatorJSON; values: ObjectFieldType }\n | { type: \"union\"; value: ValidatorJSON[] };\n\nexport function validateValidator(validator: ValidatorJSON, value: any) {\n switch (validator.type) {\n case \"null\": {\n if (value !== null) {\n throw new Error(`Validator error: Expected \\`null\\`, got \\`${value}\\``);\n }\n return;\n }\n case \"number\": {\n if (typeof value !== \"number\") {\n throw new Error(\n `Validator error: Expected \\`number\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"bigint\": {\n if (typeof value !== \"bigint\") {\n throw new Error(\n `Validator error: Expected \\`bigint\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"commitTs\": {\n if (\n typeof value !== \"bigint\" &&\n !(value instanceof CommitTsPlaceholder)\n ) {\n throw new Error(\n `Validator error: Expected \\`v.commitTs()\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"boolean\": {\n if (typeof value !== \"boolean\") {\n throw new Error(\n `Validator error: Expected \\`boolean\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"string\": {\n if (typeof value !== \"string\") {\n throw new Error(\n `Validator error: Expected \\`string\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"bytes\": {\n if (!(value instanceof ArrayBuffer)) {\n throw new Error(\n `Validator error: Expected \\`ArrayBuffer\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"any\": {\n return;\n }\n case \"literal\": {\n if (value !== validator.value) {\n throw new Error(\n `Validator error: Expected \\`${\n validator.value as any\n }\\`, got \\`${value}\\``,\n );\n }\n return;\n }\n case \"id\": {\n if (typeof value !== \"string\") {\n throw new Error(\n `Validator error: Expected \\`string\\`, got \\`${value}\\``,\n );\n }\n if (tableNameFromId(value) !== validator.tableName) {\n throw new Error(\n `Validator error: Expected ID for table \"${validator.tableName}\", got \\`${value}\\``,\n );\n }\n return;\n }\n case \"array\": {\n if (!Array.isArray(value)) {\n throw new Error(\n `Validator error: Expected \\`Array\\`, got \\`${value}\\``,\n );\n }\n for (const v of value) {\n validateValidator(validator.value, v);\n }\n return;\n }\n case \"union\": {\n let isValid = false;\n for (const v of validator.value) {\n try {\n validateValidator(v, value);\n isValid = true;\n break;\n } catch {\n // Ignore\n }\n }\n if (!isValid) {\n throw new Error(\n `Validator error: Expected one of ${validator.value.map((v) => v.type).join(\", \")}, got \\`${JSON.stringify(convexToJson(value))}\\``,\n );\n }\n return;\n }\n case \"object\": {\n if (typeof value !== \"object\") {\n throw new Error(\n `Validator error: Expected \\`object\\`, got \\`${value}\\``,\n );\n }\n if (!isSimpleObject(value)) {\n throw new Error(\n `Validator error: Expected a plain old JavaScript \\`object\\`, got \\`${value}\\``,\n );\n }\n for (const [k, { fieldType, optional }] of Object.entries(\n validator.value,\n )) {\n if (value[k] === undefined) {\n if (!optional) {\n throw new Error(\n `Validator error: Missing required field \\`${k}\\` in object`,\n );\n }\n } else {\n validateValidator(fieldType, value[k]);\n }\n }\n for (const k of Object.keys(value)) {\n if (validator.value[k] === undefined) {\n throw new Error(\n `Validator error: Unexpected field \\`${k}\\` in object`,\n );\n }\n }\n return;\n }\n case \"record\": {\n if (\n value === null ||\n typeof value !== \"object\" ||\n !isSimpleObject(value)\n ) {\n throw new Error(\n `Validator error: Expected a plain old JavaScript \\`object\\` for a record, got \\`${value}\\``,\n );\n }\n for (const [k, fieldValue] of Object.entries(value)) {\n validateValidator(validator.keys, k);\n if (fieldValue !== undefined) {\n validateValidator(validator.values.fieldType, fieldValue);\n }\n }\n return;\n }\n default: {\n const unknown: never = validator;\n throw new Error(\n `The test kernel cannot check a validator of type \\`${(unknown as { type: string }).type}\\`.`,\n );\n }\n }\n}\n\nexport const JWT_CLAIM_BY_IDENTITY_ATTRIBUTE: Record<string, string> = {\n subject: \"sub\",\n issuer: \"iss\",\n name: \"name\",\n givenName: \"given_name\",\n familyName: \"family_name\",\n nickname: \"nickname\",\n preferredUsername: \"preferred_username\",\n profileUrl: \"profile\",\n pictureUrl: \"picture\",\n email: \"email\",\n emailVerified: \"email_verified\",\n gender: \"gender\",\n birthday: \"birthdate\",\n timezone: \"zoneinfo\",\n language: \"locale\",\n phoneNumber: \"phone_number\",\n phoneNumberVerified: \"phone_number_verified\",\n address: \"address\",\n updatedAt: \"updated_at\",\n};\n\nexport function base64UrlEncode(value: string) {\n return base64UrlEncodeBytes(new TextEncoder().encode(value));\n}\n\nexport function base64UrlEncodeBytes(bytes: Uint8Array) {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n// Builds the token `withIdentity` pretends the request was authenticated with.\n// Real tokens are signed by an identity provider; this one is unsecured\n// (`\"alg\": \"none\"`, empty signature).\nexport function createTestJwt(identity: Record<string, any>) {\n const claims: Record<string, any> = Object.create(null);\n for (const [attribute, value] of Object.entries(identity)) {\n if (value === undefined || attribute === \"tokenIdentifier\") {\n continue;\n }\n // Custom claims are used as is, known attributes use their claim name.\n const claimName = Object.hasOwn(JWT_CLAIM_BY_IDENTITY_ATTRIBUTE, attribute)\n ? JWT_CLAIM_BY_IDENTITY_ATTRIBUTE[attribute]\n : attribute;\n claims[claimName] = value;\n }\n // The identity provider always determines who the user is.\n claims.iss = identity.issuer;\n claims.sub = identity.subject;\n const header = base64UrlEncode(JSON.stringify({ alg: \"none\", typ: \"JWT\" }));\n const payload = base64UrlEncode(JSON.stringify(claims));\n return `${header}.${payload}.`;\n}\n\n// Limits the backend puts on tokens from `ctx.auth.signJwt`.\nexport function parseArgs(\n args: Record<string, Value> | undefined,\n): Record<string, Value> {\n if (args === undefined) {\n return {};\n }\n if (!isSimpleObject(args)) {\n throw new Error(\n `The arguments to a Convex function must be an object. Received: ${\n args as any\n }`,\n );\n }\n return args;\n}\n\nexport function validateFunctionArgs(\n argsValidator: ValidatorJSON,\n args: any,\n functionPath: FunctionPath,\n allowPendingValues: boolean,\n) {\n if (!allowPendingValues && hasPendingValues(args)) {\n throw new Error(\n `Invalid arguments for \"${functionPath.udfPath}\": an unresolved ` +\n `placeholder value can only be passed to a query or mutation ` +\n `called from a mutation`,\n );\n }\n validateValidator(argsValidator, resolvePendingValues(args));\n}\n\nexport function validateReturnValue(\n returnsValidator: ValidatorJSON | null,\n result: any,\n functionType: \"query\" | \"mutation\" | \"action\",\n functionPath: FunctionPath,\n) {\n if (returnsValidator !== null) {\n try {\n validateValidator(returnsValidator, resolvePendingValues(result));\n } catch (e) {\n throw new Error(\n `Return value validation failed for ${functionType} \"${functionPath.udfPath}\":\\n${(e as Error).message}`,\n { cause: e },\n );\n }\n }\n}\n\nexport function createFunctionHandle(functionPath: FunctionPath) {\n return `function://${functionPath.componentPath};${functionPath.udfPath}`;\n}\n\nexport function parseFunctionHandle(handle: string) {\n const [componentPath, udfPath] = handle.split(\"function://\")[1].split(\";\");\n return { componentPath, udfPath };\n}\nexport function simpleHash(string: string) {\n let hash = 0;\n for (let i = 0; i < string.length; i++) {\n const char = string.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32bit integer\n }\n return hash;\n}\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type Value } from "convex/values";
2
+ import type { DocumentId, SerializedRangeExpression, StoredDocument } from "./KernelValues.js";
3
+ /**
4
+ * A row's position in an interval's order: its value at each of the interval's
5
+ * fields.
6
+ */
7
+ export type Key = ReadonlyArray<Value | undefined>;
8
+ /**
9
+ * A read of one document by ID. It records the document's absence as well as
10
+ * its presence, so a commit that creates the document invalidates it.
11
+ */
12
+ export interface DocumentRead {
13
+ readonly kind: "document";
14
+ readonly componentPath: string;
15
+ readonly id: DocumentId;
16
+ }
17
+ /**
18
+ * A read of an ordered interval of one table.
19
+ *
20
+ * `fields` is the sort key: an index's fields, then the tie-breakers
21
+ * `_creationTime` and `_id`. `range` is the index range expression, whose `Gt`
22
+ * and `Lt` bounds are exclusive and `Gte` and `Lte` inclusive. `after` is an
23
+ * exclusive start in `order`, a page's cursor. `through` is how far the query
24
+ * examined, inclusive: nothing yet, up to a key, or to the end of the range. An
25
+ * empty result reads to the end, so it still covers an insert into the range.
26
+ *
27
+ * Only the attempt that owns the read advances `through`.
28
+ */
29
+ export interface IntervalRead {
30
+ readonly kind: "interval";
31
+ readonly componentPath: string;
32
+ readonly table: string;
33
+ readonly fields: ReadonlyArray<string>;
34
+ readonly order: "asc" | "desc";
35
+ readonly range: ReadonlyArray<SerializedRangeExpression>;
36
+ readonly after: Key | null;
37
+ through: Key | "end" | null;
38
+ }
39
+ export type Read = DocumentRead | IntervalRead;
40
+ /**
41
+ * Where `doc` sorts against `key` in the interval's order.
42
+ */
43
+ export declare const compareToKey: (fields: ReadonlyArray<string>, order: "asc" | "desc", doc: StoredDocument, key: Key) => number;
44
+ export declare const keyOf: (fields: ReadonlyArray<string>, doc: StoredDocument) => Key;
45
+ /**
46
+ * Whether a commit's change to one document invalidates `read`: it wrote the
47
+ * document read by ID, or the document entered or left the interval.
48
+ */
49
+ export declare const covers: (read: Read, change: {
50
+ readonly componentPath: string;
51
+ readonly id: DocumentId;
52
+ readonly before: StoredDocument | null;
53
+ readonly after: StoredDocument | null;
54
+ }) => boolean;
55
+ //# sourceMappingURL=ReadSet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadSet.d.ts","sourceRoot":"","sources":["../../src/internal/ReadSet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,cAAc,EACf,MAAM,mBAAmB,CAAC;AAO3B;;;GAGG;AACH,MAAM,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,yBAAyB,CAAC,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,MAAM,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,YAAY,WACf,aAAa,CAAC,MAAM,CAAC,SACtB,KAAK,GAAG,MAAM,OAChB,cAAc,OACd,GAAG,KACP,MAUF,CAAC;AAEF,eAAO,MAAM,KAAK,WACR,aAAa,CAAC,MAAM,CAAC,OACxB,cAAc,KAClB,GAA2D,CAAC;AAU/D;;;GAGG;AACH,eAAO,MAAM,MAAM,SACX,IAAI,UACF;IACN,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC,KACA,OASF,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { evaluateFieldPath, evaluateRangeFilter, tableNameFromId } from "./KernelValues.js";
2
+ import { compareValues } from "convex/values";
3
+ //#region src/internal/ReadSet.ts
4
+ /**
5
+ * Where `doc` sorts against `key` in the interval's order.
6
+ */
7
+ const compareToKey = (fields, order, doc, key) => {
8
+ const direction = order === "asc" ? 1 : -1;
9
+ for (let i = 0; i < fields.length; i++) {
10
+ const cmp = compareValues(evaluateFieldPath(fields[i], doc), key[i]);
11
+ if (cmp !== 0) return cmp * direction;
12
+ }
13
+ return 0;
14
+ };
15
+ const keyOf = (fields, doc) => fields.map((field) => evaluateFieldPath(field, doc));
16
+ const inInterval = (read, doc) => read.through !== null && read.range.every((filter) => evaluateRangeFilter(doc, filter)) && (read.after === null || compareToKey(read.fields, read.order, doc, read.after) > 0) && (read.through === "end" || compareToKey(read.fields, read.order, doc, read.through) <= 0);
17
+ /**
18
+ * Whether a commit's change to one document invalidates `read`: it wrote the
19
+ * document read by ID, or the document entered or left the interval.
20
+ */
21
+ const covers = (read, change) => {
22
+ if (read.componentPath !== change.componentPath) return false;
23
+ if (read.kind === "document") return read.id === change.id;
24
+ return tableNameFromId(change.id) === read.table && [change.before, change.after].some((doc) => doc !== null && inInterval(read, doc));
25
+ };
26
+ //#endregion
27
+ export { compareToKey, covers, keyOf };
28
+
29
+ //# sourceMappingURL=ReadSet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadSet.js","names":[],"sources":["../../src/internal/ReadSet.ts"],"sourcesContent":["import { compareValues, type Value } from \"convex/values\";\nimport type {\n DocumentId,\n SerializedRangeExpression,\n StoredDocument,\n} from \"./KernelValues.js\";\nimport {\n evaluateFieldPath,\n evaluateRangeFilter,\n tableNameFromId,\n} from \"./KernelValues.js\";\n\n/**\n * A row's position in an interval's order: its value at each of the interval's\n * fields.\n */\nexport type Key = ReadonlyArray<Value | undefined>;\n\n/**\n * A read of one document by ID. It records the document's absence as well as\n * its presence, so a commit that creates the document invalidates it.\n */\nexport interface DocumentRead {\n readonly kind: \"document\";\n readonly componentPath: string;\n readonly id: DocumentId;\n}\n\n/**\n * A read of an ordered interval of one table.\n *\n * `fields` is the sort key: an index's fields, then the tie-breakers\n * `_creationTime` and `_id`. `range` is the index range expression, whose `Gt`\n * and `Lt` bounds are exclusive and `Gte` and `Lte` inclusive. `after` is an\n * exclusive start in `order`, a page's cursor. `through` is how far the query\n * examined, inclusive: nothing yet, up to a key, or to the end of the range. An\n * empty result reads to the end, so it still covers an insert into the range.\n *\n * Only the attempt that owns the read advances `through`.\n */\nexport interface IntervalRead {\n readonly kind: \"interval\";\n readonly componentPath: string;\n readonly table: string;\n readonly fields: ReadonlyArray<string>;\n readonly order: \"asc\" | \"desc\";\n readonly range: ReadonlyArray<SerializedRangeExpression>;\n readonly after: Key | null;\n through: Key | \"end\" | null;\n}\n\nexport type Read = DocumentRead | IntervalRead;\n\n/**\n * Where `doc` sorts against `key` in the interval's order.\n */\nexport const compareToKey = (\n fields: ReadonlyArray<string>,\n order: \"asc\" | \"desc\",\n doc: StoredDocument,\n key: Key,\n): number => {\n const direction = order === \"asc\" ? 1 : -1;\n for (let i = 0; i < fields.length; i++) {\n const cmp = compareValues(\n evaluateFieldPath(fields[i]!, doc),\n key[i] as Value,\n );\n if (cmp !== 0) return cmp * direction;\n }\n return 0;\n};\n\nexport const keyOf = (\n fields: ReadonlyArray<string>,\n doc: StoredDocument,\n): Key => fields.map((field) => evaluateFieldPath(field, doc));\n\nconst inInterval = (read: IntervalRead, doc: StoredDocument): boolean =>\n read.through !== null &&\n read.range.every((filter) => evaluateRangeFilter(doc, filter)) &&\n (read.after === null ||\n compareToKey(read.fields, read.order, doc, read.after) > 0) &&\n (read.through === \"end\" ||\n compareToKey(read.fields, read.order, doc, read.through) <= 0);\n\n/**\n * Whether a commit's change to one document invalidates `read`: it wrote the\n * document read by ID, or the document entered or left the interval.\n */\nexport const covers = (\n read: Read,\n change: {\n readonly componentPath: string;\n readonly id: DocumentId;\n readonly before: StoredDocument | null;\n readonly after: StoredDocument | null;\n },\n): boolean => {\n if (read.componentPath !== change.componentPath) return false;\n if (read.kind === \"document\") return read.id === change.id;\n return (\n tableNameFromId(change.id) === read.table &&\n [change.before, change.after].some(\n (doc) => doc !== null && inInterval(read, doc),\n )\n );\n};\n"],"mappings":";;;;;;AAwDA,MAAa,gBACX,QACA,OACA,KACA,QACW;CACX,MAAM,YAAY,UAAU,QAAQ,IAAI;CACxC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,MAAM,cACV,kBAAkB,OAAO,IAAK,GAAG,GACjC,IAAI,EACN;EACA,IAAI,QAAQ,GAAG,OAAO,MAAM;CAC9B;CACA,OAAO;AACT;AAEA,MAAa,SACX,QACA,QACQ,OAAO,KAAK,UAAU,kBAAkB,OAAO,GAAG,CAAC;AAE7D,MAAM,cAAc,MAAoB,QACtC,KAAK,YAAY,QACjB,KAAK,MAAM,OAAO,WAAW,oBAAoB,KAAK,MAAM,CAAC,MAC5D,KAAK,UAAU,QACd,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,KAAK,KAAK,IAAI,OAC1D,KAAK,YAAY,SAChB,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,KAAK,OAAO,KAAK;;;;;AAMhE,MAAa,UACX,MACA,WAMY;CACZ,IAAI,KAAK,kBAAkB,OAAO,eAAe,OAAO;CACxD,IAAI,KAAK,SAAS,YAAY,OAAO,KAAK,OAAO,OAAO;CACxD,OACE,gBAAgB,OAAO,EAAE,MAAM,KAAK,SACpC,CAAC,OAAO,QAAQ,OAAO,KAAK,CAAC,CAAC,MAC3B,QAAQ,QAAQ,QAAQ,WAAW,MAAM,GAAG,CAC/C;AAEJ"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@gaganref/confect-test",
3
- "version": "10.3.1-fork.88",
3
+ "version": "10.3.1-fork.89",
4
4
  "peerDependencies": {
5
- "@confect/core": "npm:@gaganref/confect-core@10.3.1-fork.88",
6
- "@confect/server": "npm:@gaganref/confect-server@10.3.1-fork.88",
5
+ "@confect/core": "npm:@gaganref/confect-core@10.3.1-fork.89",
6
+ "@confect/server": "npm:@gaganref/confect-server@10.3.1-fork.89",
7
7
  "convex": "^1.32.0",
8
8
  "convex-test": ">=0.0.65 <0.1.0",
9
9
  "effect": ">=4.0.0 <4.1.0"
@@ -0,0 +1,185 @@
1
+ // oxlint-disable-next-line effecttsgo/node-builtin-import -- The drain waits a real turn of the event loop. Fake timers in a test replace the global `setImmediate`, not this one.
2
+ import { setImmediate } from "node:timers/promises";
3
+ import type { Attempt as AttemptNumber } from "./Invocation.js";
4
+ import * as Kernel from "./Kernel.js";
5
+ import type { DocumentId, StoredDocument } from "./KernelValues.js";
6
+ import { tableNameFromId } from "./KernelValues.js";
7
+ import type * as ReadSet from "./ReadSet.js";
8
+
9
+ /**
10
+ * Pending writes, keyed by component path, then document ID. `null` deletes.
11
+ */
12
+ type Overlay = Map<string, Map<DocumentId, StoredDocument | null>>;
13
+
14
+ /**
15
+ * How long one frame takes syscalls: those it has running, those it has
16
+ * started, and whether it still takes them.
17
+ */
18
+ interface Life {
19
+ running: number;
20
+ started: number;
21
+ open: boolean;
22
+ }
23
+
24
+ /**
25
+ * One frame of an attempt at a query or mutation: the committed state the
26
+ * attempt started from, its caller's frame and its own pending writes. The
27
+ * outermost frame belongs to the top-level function. Each nested mutation opens
28
+ * a child frame, which merges into its caller's frame when it returns and is
29
+ * dropped when it fails. Reads belong to the whole attempt and are never
30
+ * dropped: a failed nested mutation's reads stay in the attempt.
31
+ *
32
+ * Each frame has its own lifetime. A frame takes reads and writes until it is
33
+ * closed. A mutation's frame closes once its function has returned and no
34
+ * syscall it started is still running, so a write a detached fiber makes after
35
+ * the handler settled still lands in it, as on the binary. A nested mutation's
36
+ * frame closes before it merges into its caller's, so no write reaches it after
37
+ * its writes became its caller's.
38
+ *
39
+ * A frame belongs to one invocation, which alone writes to it, so a caller's
40
+ * write made while its nested mutation is in flight stays the caller's.
41
+ * `Kernel.apply` publishes the outermost frame's writes.
42
+ */
43
+ export class KernelAttempt {
44
+ private readonly overlay: Overlay = new Map();
45
+ private readonly life: Life = { running: 0, started: 0, open: true };
46
+ private readonly allReads: Array<ReadSet.Read>;
47
+
48
+ constructor(
49
+ readonly snapshot: Kernel.State,
50
+ readonly number: AttemptNumber = 1,
51
+ private readonly parent?: KernelAttempt,
52
+ ) {
53
+ this.allReads = parent?.allReads ?? [];
54
+ }
55
+
56
+ get reads(): ReadonlyArray<ReadSet.Read> {
57
+ return this.allReads;
58
+ }
59
+
60
+ get closed(): boolean {
61
+ return !this.life.open;
62
+ }
63
+
64
+ readDocument(componentPath: string, id: DocumentId) {
65
+ this.allReads.push({ kind: "document", componentPath, id });
66
+ }
67
+
68
+ /**
69
+ * Record a read of an interval. The returned function advances how far the
70
+ * query examined it.
71
+ */
72
+ readInterval(
73
+ read: Omit<ReadSet.IntervalRead, "through">,
74
+ ): (through: ReadSet.Key | "end") => void {
75
+ const interval: ReadSet.IntervalRead = { ...read, through: null };
76
+ this.allReads.push(interval);
77
+ return (through) => {
78
+ interval.through = through;
79
+ };
80
+ }
81
+
82
+ /**
83
+ * A syscall began. `end` runs when it settles.
84
+ */
85
+ begin(): () => void {
86
+ const life = this.life;
87
+ life.running++;
88
+ life.started++;
89
+ return () => {
90
+ life.running--;
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Resolves once a whole turn of the event loop passes with no syscall of this
96
+ * frame running or started. A nested call is a syscall of its caller's frame
97
+ * until it has drained.
98
+ */
99
+ drained(): Promise<void> {
100
+ const before = this.life.started;
101
+ return setImmediate().then(() =>
102
+ this.life.running === 0 && this.life.started === before
103
+ ? undefined
104
+ : this.drained(),
105
+ );
106
+ }
107
+
108
+ close() {
109
+ this.life.open = false;
110
+ }
111
+
112
+ /**
113
+ * The pending writes this frame sees, from the outermost frame to this one.
114
+ */
115
+ private overlays(): Array<Overlay> {
116
+ return this.parent === undefined
117
+ ? [this.overlay]
118
+ : [...this.parent.overlays(), this.overlay];
119
+ }
120
+
121
+ get(componentPath: string, id: DocumentId): StoredDocument | null {
122
+ for (const overlay of this.overlays().toReversed()) {
123
+ const write = overlay.get(componentPath)?.get(id);
124
+ if (write !== undefined) return write;
125
+ }
126
+ return Kernel.get(this.snapshot, componentPath, id)?.doc ?? null;
127
+ }
128
+
129
+ /**
130
+ * The table as this frame sees it, committed rows under every pending write
131
+ * from the outermost frame to this one.
132
+ */
133
+ scan(componentPath: string, tableName: string): Array<StoredDocument> {
134
+ const ids = new Set<DocumentId>();
135
+ for (const row of Kernel.rows(this.snapshot, componentPath, tableName))
136
+ ids.add(row.doc._id);
137
+ for (const overlay of this.overlays())
138
+ for (const id of overlay.get(componentPath)?.keys() ?? [])
139
+ if (tableNameFromId(id) === tableName) ids.add(id);
140
+ const docs: Array<StoredDocument> = [];
141
+ for (const id of ids) {
142
+ const doc = this.get(componentPath, id);
143
+ if (doc !== null) docs.push(doc);
144
+ }
145
+ return docs;
146
+ }
147
+
148
+ write(componentPath: string, id: DocumentId, doc: StoredDocument | null) {
149
+ const writes = this.overlay.get(componentPath) ?? new Map();
150
+ this.overlay.set(componentPath, writes);
151
+ writes.set(id, doc);
152
+ }
153
+
154
+ /**
155
+ * A nested mutation begins, in a frame of its own. Dropping a failed one is
156
+ * forgetting it.
157
+ */
158
+ child(): KernelAttempt {
159
+ return new KernelAttempt(this.snapshot, this.number, this);
160
+ }
161
+
162
+ /**
163
+ * A nested mutation returned and its frame closed. Its writes become its
164
+ * caller's.
165
+ */
166
+ merge() {
167
+ if (this.parent === undefined) {
168
+ throw new Error("The outermost frame of an attempt has no caller.");
169
+ }
170
+ if (this.life.open) {
171
+ throw new Error("A nested mutation's frame merges only once closed.");
172
+ }
173
+ for (const [componentPath, writes] of this.overlay)
174
+ for (const [id, doc] of writes) this.parent.write(componentPath, id, doc);
175
+ }
176
+
177
+ /**
178
+ * The outermost frame's writes, for `Kernel.apply`.
179
+ */
180
+ writes(): Array<Kernel.Write> {
181
+ return Array.from(this.overlay, ([componentPath, writes]) =>
182
+ Array.from(writes, ([id, doc]) => ({ componentPath, id, doc })),
183
+ ).flat();
184
+ }
185
+ }