@prisma/streams-server 0.0.1 → 0.1.1

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 (83) hide show
  1. package/CODE_OF_CONDUCT.md +45 -0
  2. package/CONTRIBUTING.md +68 -0
  3. package/LICENSE +201 -0
  4. package/README.md +39 -2
  5. package/SECURITY.md +33 -0
  6. package/bin/prisma-streams-server +2 -0
  7. package/package.json +29 -34
  8. package/src/app.ts +74 -0
  9. package/src/app_core.ts +1706 -0
  10. package/src/app_local.ts +46 -0
  11. package/src/backpressure.ts +66 -0
  12. package/src/bootstrap.ts +239 -0
  13. package/src/config.ts +251 -0
  14. package/src/db/db.ts +1386 -0
  15. package/src/db/schema.ts +625 -0
  16. package/src/expiry_sweeper.ts +44 -0
  17. package/src/hist.ts +169 -0
  18. package/src/index/binary_fuse.ts +379 -0
  19. package/src/index/indexer.ts +745 -0
  20. package/src/index/run_cache.ts +84 -0
  21. package/src/index/run_format.ts +213 -0
  22. package/src/ingest.ts +655 -0
  23. package/src/lens/lens.ts +501 -0
  24. package/src/manifest.ts +114 -0
  25. package/src/memory.ts +155 -0
  26. package/src/metrics.ts +161 -0
  27. package/src/metrics_emitter.ts +50 -0
  28. package/src/notifier.ts +64 -0
  29. package/src/objectstore/interface.ts +13 -0
  30. package/src/objectstore/mock_r2.ts +269 -0
  31. package/src/objectstore/null.ts +32 -0
  32. package/src/objectstore/r2.ts +128 -0
  33. package/src/offset.ts +70 -0
  34. package/src/reader.ts +454 -0
  35. package/src/runtime/hash.ts +156 -0
  36. package/src/runtime/hash_vendor/LICENSE.hash-wasm +38 -0
  37. package/src/runtime/hash_vendor/NOTICE.md +8 -0
  38. package/src/runtime/hash_vendor/xxhash3.umd.min.cjs +7 -0
  39. package/src/runtime/hash_vendor/xxhash32.umd.min.cjs +7 -0
  40. package/src/runtime/hash_vendor/xxhash64.umd.min.cjs +7 -0
  41. package/src/schema/lens_schema.ts +290 -0
  42. package/src/schema/proof.ts +547 -0
  43. package/src/schema/registry.ts +405 -0
  44. package/src/segment/cache.ts +179 -0
  45. package/src/segment/format.ts +331 -0
  46. package/src/segment/segmenter.ts +326 -0
  47. package/src/segment/segmenter_worker.ts +43 -0
  48. package/src/segment/segmenter_workers.ts +94 -0
  49. package/src/server.ts +326 -0
  50. package/src/sqlite/adapter.ts +164 -0
  51. package/src/stats.ts +205 -0
  52. package/src/touch/engine.ts +41 -0
  53. package/src/touch/interpreter_worker.ts +442 -0
  54. package/src/touch/live_keys.ts +118 -0
  55. package/src/touch/live_metrics.ts +827 -0
  56. package/src/touch/live_templates.ts +619 -0
  57. package/src/touch/manager.ts +1199 -0
  58. package/src/touch/spec.ts +456 -0
  59. package/src/touch/touch_journal.ts +671 -0
  60. package/src/touch/touch_key_id.ts +20 -0
  61. package/src/touch/worker_pool.ts +189 -0
  62. package/src/touch/worker_protocol.ts +56 -0
  63. package/src/types/proper-lockfile.d.ts +1 -0
  64. package/src/uploader.ts +317 -0
  65. package/src/util/base32_crockford.ts +81 -0
  66. package/src/util/bloom256.ts +67 -0
  67. package/src/util/cleanup.ts +22 -0
  68. package/src/util/crc32c.ts +29 -0
  69. package/src/util/ds_error.ts +15 -0
  70. package/src/util/duration.ts +17 -0
  71. package/src/util/endian.ts +53 -0
  72. package/src/util/json_pointer.ts +148 -0
  73. package/src/util/log.ts +25 -0
  74. package/src/util/lru.ts +45 -0
  75. package/src/util/retry.ts +35 -0
  76. package/src/util/siphash.ts +71 -0
  77. package/src/util/stream_paths.ts +31 -0
  78. package/src/util/time.ts +14 -0
  79. package/src/util/yield.ts +3 -0
  80. package/build/index.d.mts +0 -1
  81. package/build/index.d.ts +0 -1
  82. package/build/index.js +0 -0
  83. package/build/index.mjs +0 -1
@@ -0,0 +1,547 @@
1
+ import { Result } from "better-result";
2
+ import type { Lens, LensOp } from "../lens/lens";
3
+ import { parseJsonPointerResult } from "../util/json_pointer";
4
+ import { dsError } from "../util/ds_error.ts";
5
+
6
+ type Schema = any;
7
+ export type LensProofError = {
8
+ kind: "invalid_lens_proof" | "invalid_lens_defaults";
9
+ message: string;
10
+ };
11
+
12
+ type PathInfo = {
13
+ schema: Schema;
14
+ required: boolean;
15
+ exists: boolean;
16
+ explicit: boolean;
17
+ };
18
+
19
+ function invalidLensProof<T = never>(message: string): Result<T, LensProofError> {
20
+ return Result.err({ kind: "invalid_lens_proof", message });
21
+ }
22
+
23
+ function invalidLensDefaults<T = never>(message: string): Result<T, LensProofError> {
24
+ return Result.err({ kind: "invalid_lens_defaults", message });
25
+ }
26
+
27
+ function parsePointerResult(
28
+ ptr: string,
29
+ kind: LensProofError["kind"]
30
+ ): Result<string[], LensProofError> {
31
+ const res = parseJsonPointerResult(ptr);
32
+ if (Result.isError(res)) {
33
+ return Result.err({ kind, message: res.error.message });
34
+ }
35
+ return Result.ok(res.value);
36
+ }
37
+
38
+ function isObjectSchema(schema: Schema): boolean {
39
+ if (schema === true) return true;
40
+ if (schema === false || schema == null) return false;
41
+ const t = schema.type;
42
+ if (t === undefined) return true;
43
+ if (Array.isArray(t)) return t.includes("object");
44
+ return t === "object";
45
+ }
46
+
47
+ function isArraySchema(schema: Schema): boolean {
48
+ if (schema === true) return true;
49
+ if (schema === false || schema == null) return false;
50
+ const t = schema.type;
51
+ if (t === undefined) return true;
52
+ if (Array.isArray(t)) return t.includes("array");
53
+ return t === "array";
54
+ }
55
+
56
+ function getTypeSet(schema: Schema): Set<string> | null {
57
+ if (schema === true) return null;
58
+ if (schema === false || schema == null) return new Set();
59
+ if (schema.const !== undefined) return new Set([inferType(schema.const)]);
60
+ if (schema.enum) return new Set(schema.enum.map((v: any) => inferType(v)));
61
+ const t = schema.type;
62
+ if (t === undefined) return null;
63
+ if (Array.isArray(t)) return new Set(t);
64
+ return new Set([t]);
65
+ }
66
+
67
+ function inferType(value: any): string {
68
+ if (value === null) return "null";
69
+ if (Array.isArray(value)) return "array";
70
+ switch (typeof value) {
71
+ case "string":
72
+ return "string";
73
+ case "number":
74
+ return Number.isInteger(value) ? "integer" : "number";
75
+ case "boolean":
76
+ return "boolean";
77
+ case "object":
78
+ return "object";
79
+ default:
80
+ return "unknown";
81
+ }
82
+ }
83
+
84
+ function typeSubset(src: string, dest: string): boolean {
85
+ if (src === dest) return true;
86
+ if (src === "integer" && dest === "number") return true;
87
+ return false;
88
+ }
89
+
90
+ function isSchemaCompatible(src: Schema, dest: Schema): boolean {
91
+ if (dest === true) return true;
92
+ if (dest === false || dest == null) return false;
93
+
94
+ if (src && src.const !== undefined) {
95
+ return valueConformsToSchema(src.const, dest);
96
+ }
97
+ if (src && Array.isArray(src.enum)) {
98
+ return src.enum.every((v: any) => valueConformsToSchema(v, dest));
99
+ }
100
+
101
+ const srcTypes = getTypeSet(src);
102
+ const destTypes = getTypeSet(dest);
103
+ if (srcTypes && destTypes) {
104
+ for (const t of srcTypes) {
105
+ let ok = false;
106
+ for (const d of destTypes) {
107
+ if (typeSubset(t, d)) {
108
+ ok = true;
109
+ break;
110
+ }
111
+ }
112
+ if (!ok) return false;
113
+ }
114
+ return true;
115
+ }
116
+
117
+ // If dest is narrowly constrained but src is broad, we cannot prove.
118
+ if (dest.const !== undefined || Array.isArray(dest.enum)) return false;
119
+ return true;
120
+ }
121
+
122
+ function valueConformsToSchema(value: any, schema: Schema): boolean {
123
+ if (schema === true) return true;
124
+ if (schema === false || schema == null) return false;
125
+ if (schema.const !== undefined) return deepEqual(value, schema.const);
126
+ if (Array.isArray(schema.enum)) return schema.enum.some((v: any) => deepEqual(v, value));
127
+ const t = getTypeSet(schema);
128
+ if (t) {
129
+ const vt = inferType(value);
130
+ for (const allowed of t) {
131
+ if (typeSubset(vt, allowed)) return true;
132
+ }
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ function deepEqual(a: any, b: any): boolean {
139
+ return JSON.stringify(a) === JSON.stringify(b);
140
+ }
141
+
142
+ function getPropertySchema(schema: Schema, prop: string, requireExplicit: boolean): PathInfo {
143
+ if (!schema || schema === false) return { schema: schema, required: false, exists: false, explicit: false };
144
+ if (!isObjectSchema(schema)) return { schema, required: false, exists: false, explicit: false };
145
+ const props = schema.properties ?? {};
146
+ const required = Array.isArray(schema.required) && schema.required.includes(prop);
147
+ if (Object.prototype.hasOwnProperty.call(props, prop)) {
148
+ return { schema: props[prop], required, exists: true, explicit: true };
149
+ }
150
+ if (requireExplicit) return { schema: schema, required, exists: false, explicit: false };
151
+ if (schema.additionalProperties === false) return { schema: schema, required, exists: false, explicit: false };
152
+ if (schema.additionalProperties && schema.additionalProperties !== true) {
153
+ return { schema: schema.additionalProperties, required, exists: true, explicit: false };
154
+ }
155
+ return { schema: true, required, exists: true, explicit: false };
156
+ }
157
+
158
+ function getItemsSchema(schema: Schema): Schema {
159
+ if (!schema || schema === false) return schema;
160
+ if (!isArraySchema(schema)) return schema;
161
+ if (schema.items !== undefined) return schema.items;
162
+ return true;
163
+ }
164
+
165
+ function getPathInfo(schema: Schema, segments: string[], requireExplicit: boolean): PathInfo {
166
+ if (segments.length === 0) return { schema, required: true, exists: true, explicit: true };
167
+ let cur = schema;
168
+ let required = true;
169
+ for (let i = 0; i < segments.length; i++) {
170
+ const seg = segments[i];
171
+ if (/^[0-9]+$/.test(seg)) {
172
+ if (!isArraySchema(cur)) return { schema: cur, required: false, exists: false, explicit: false };
173
+ cur = getItemsSchema(cur);
174
+ continue;
175
+ }
176
+ const info = getPropertySchema(cur, seg, requireExplicit);
177
+ if (!info.exists) return info;
178
+ required = required && info.required;
179
+ cur = info.schema;
180
+ }
181
+ return { schema: cur, required, exists: true, explicit: true };
182
+ }
183
+
184
+ function ensureParentRequiredResult(
185
+ schema: Schema,
186
+ segments: string[],
187
+ requireExplicit: boolean,
188
+ kind: LensProofError["kind"]
189
+ ): Result<void, LensProofError> {
190
+ if (segments.length === 0) return Result.ok(undefined);
191
+ let cur = schema;
192
+ for (let i = 0; i < segments.length - 1; i++) {
193
+ const seg = segments[i];
194
+ const info = getPropertySchema(cur, seg, requireExplicit);
195
+ if (!info.exists || !info.required) {
196
+ return Result.err({ kind, message: "parent path not required" });
197
+ }
198
+ if (!isObjectSchema(info.schema)) {
199
+ return Result.err({ kind, message: "parent path not object" });
200
+ }
201
+ cur = info.schema;
202
+ }
203
+ return Result.ok(undefined);
204
+ }
205
+
206
+ function isPathRequired(schema: Schema, segments: string[]): boolean {
207
+ const info = getPathInfo(schema, segments, false);
208
+ return info.exists && info.required;
209
+ }
210
+
211
+ function deriveDefault(schema: Schema): any | undefined {
212
+ if (schema === false || schema == null) return undefined;
213
+ if (schema.default !== undefined) return schema.default;
214
+ const types = getTypeSet(schema);
215
+ if (types) {
216
+ if (types.has("object")) return {};
217
+ if (types.has("array")) return [];
218
+ }
219
+ return undefined;
220
+ }
221
+
222
+ function ensureEnumOrConstResult(kind: LensProofError["kind"], schema: Schema): Result<void, LensProofError> {
223
+ if (schema && (schema.const !== undefined || Array.isArray(schema.enum))) return Result.ok(undefined);
224
+ return Result.err({ kind, message: "schema must be enum/const for non-total transform" });
225
+ }
226
+
227
+ function samePointer(a: string[], b: string[]): boolean {
228
+ if (a.length !== b.length) return false;
229
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
230
+ return true;
231
+ }
232
+
233
+ function hasWrapAfterResult(ops: LensOp[], pathSeg: string[]): Result<boolean, LensProofError> {
234
+ for (const op of ops) {
235
+ if (op.op !== "wrap") continue;
236
+ const wrapPathRes = parsePointerResult(op.path, "invalid_lens_proof");
237
+ if (Result.isError(wrapPathRes)) return wrapPathRes;
238
+ if (samePointer(wrapPathRes.value, pathSeg)) return Result.ok(true);
239
+ }
240
+ return Result.ok(false);
241
+ }
242
+
243
+ function findRenameToResult(priorOps: LensOp[], pathSeg: string[]): Result<LensOp | null, LensProofError> {
244
+ for (let i = priorOps.length - 1; i >= 0; i--) {
245
+ const op = priorOps[i];
246
+ if (op.op !== "rename") continue;
247
+ const toRes = parsePointerResult(op.to, "invalid_lens_proof");
248
+ if (Result.isError(toRes)) return toRes;
249
+ if (samePointer(toRes.value, pathSeg)) return Result.ok(op);
250
+ }
251
+ return Result.ok(null);
252
+ }
253
+
254
+ function validateOpResult(
255
+ oldSchema: Schema,
256
+ newSchema: Schema,
257
+ op: LensOp,
258
+ priorOps: LensOp[],
259
+ remainingOps: LensOp[]
260
+ ): Result<void, LensProofError> {
261
+ switch (op.op) {
262
+ case "rename": {
263
+ const fromSegRes = parsePointerResult(op.from, "invalid_lens_proof");
264
+ if (Result.isError(fromSegRes)) return fromSegRes;
265
+ const toSegRes = parsePointerResult(op.to, "invalid_lens_proof");
266
+ if (Result.isError(toSegRes)) return toSegRes;
267
+ const fromSeg = fromSegRes.value;
268
+ const toSeg = toSegRes.value;
269
+ const src = getPathInfo(oldSchema, fromSeg, false);
270
+ if (!src.exists || !src.required) return invalidLensProof("rename source not required");
271
+ const parentRes = ensureParentRequiredResult(oldSchema, toSeg, false, "invalid_lens_proof");
272
+ if (Result.isError(parentRes)) return parentRes;
273
+ const dest = getPathInfo(newSchema, toSeg, false);
274
+ if (!dest.exists) return invalidLensProof("rename dest missing in new schema");
275
+ if (!isSchemaCompatible(src.schema, dest.schema)) {
276
+ if (isArraySchema(dest.schema)) {
277
+ const hasWrapRes = hasWrapAfterResult(remainingOps, toSeg);
278
+ if (Result.isError(hasWrapRes)) return hasWrapRes;
279
+ if (hasWrapRes.value) {
280
+ const items = getItemsSchema(dest.schema);
281
+ if (!isSchemaCompatible(src.schema, items)) {
282
+ return invalidLensProof("rename schema incompatible");
283
+ }
284
+ return Result.ok(undefined);
285
+ }
286
+ }
287
+ return invalidLensProof("rename schema incompatible");
288
+ }
289
+ return Result.ok(undefined);
290
+ }
291
+ case "copy": {
292
+ const fromSegRes = parsePointerResult(op.from, "invalid_lens_proof");
293
+ if (Result.isError(fromSegRes)) return fromSegRes;
294
+ const toSegRes = parsePointerResult(op.to, "invalid_lens_proof");
295
+ if (Result.isError(toSegRes)) return toSegRes;
296
+ const fromSeg = fromSegRes.value;
297
+ const toSeg = toSegRes.value;
298
+ const src = getPathInfo(oldSchema, fromSeg, false);
299
+ if (!src.exists || !src.required) return invalidLensProof("copy source not required");
300
+ const parentRes = ensureParentRequiredResult(oldSchema, toSeg, false, "invalid_lens_proof");
301
+ if (Result.isError(parentRes)) return parentRes;
302
+ const dest = getPathInfo(newSchema, toSeg, false);
303
+ if (!dest.exists) return invalidLensProof("copy dest missing in new schema");
304
+ if (!isSchemaCompatible(src.schema, dest.schema)) return invalidLensProof("copy schema incompatible");
305
+ return Result.ok(undefined);
306
+ }
307
+ case "add": {
308
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
309
+ if (Result.isError(pathSegRes)) return pathSegRes;
310
+ const pathSeg = pathSegRes.value;
311
+ const parentRes = ensureParentRequiredResult(oldSchema, pathSeg, true, "invalid_lens_proof");
312
+ if (Result.isError(parentRes)) return parentRes;
313
+ const dest = getPathInfo(newSchema, pathSeg, true);
314
+ if (!dest.exists) return invalidLensProof("add dest missing in new schema");
315
+ const def = op.default ?? deriveDefault(dest.schema);
316
+ if (def === undefined) return invalidLensProof("add missing default");
317
+ if (!valueConformsToSchema(def, dest.schema)) return invalidLensProof("add default invalid");
318
+ return Result.ok(undefined);
319
+ }
320
+ case "remove": {
321
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
322
+ if (Result.isError(pathSegRes)) return pathSegRes;
323
+ const pathSeg = pathSegRes.value;
324
+ const src = getPathInfo(oldSchema, pathSeg, true);
325
+ if (!src.exists || !src.required) return invalidLensProof("remove path not required");
326
+ if (isPathRequired(newSchema, pathSeg)) return invalidLensProof("remove path still required in new schema");
327
+ return Result.ok(undefined);
328
+ }
329
+ case "hoist": {
330
+ const hostSegRes = parsePointerResult(op.host, "invalid_lens_proof");
331
+ if (Result.isError(hostSegRes)) return hostSegRes;
332
+ const toSegRes = parsePointerResult(op.to, "invalid_lens_proof");
333
+ if (Result.isError(toSegRes)) return toSegRes;
334
+ const hostSeg = hostSegRes.value;
335
+ const toSeg = toSegRes.value;
336
+ const host = getPathInfo(oldSchema, hostSeg, true);
337
+ if (!host.exists || !host.required || !isObjectSchema(host.schema)) return invalidLensProof("hoist host invalid");
338
+ const child = getPropertySchema(host.schema, op.name, true);
339
+ if (!child.exists || !child.required) return invalidLensProof("hoist name missing/optional");
340
+ const parentRes = ensureParentRequiredResult(oldSchema, toSeg, true, "invalid_lens_proof");
341
+ if (Result.isError(parentRes)) return parentRes;
342
+ const dest = getPathInfo(newSchema, toSeg, true);
343
+ if (!dest.exists) return invalidLensProof("hoist dest missing in new schema");
344
+ if (!isSchemaCompatible(child.schema, dest.schema)) return invalidLensProof("hoist schema incompatible");
345
+ if (op.removeFromHost !== false) {
346
+ const hostNew = getPathInfo(newSchema, hostSeg, true);
347
+ if (hostNew.exists && hostNew.required && isPathRequired(hostNew.schema, [op.name])) {
348
+ return invalidLensProof("hoist removed field still required");
349
+ }
350
+ }
351
+ return Result.ok(undefined);
352
+ }
353
+ case "plunge": {
354
+ const fromSegRes = parsePointerResult(op.from, "invalid_lens_proof");
355
+ if (Result.isError(fromSegRes)) return fromSegRes;
356
+ const hostSegRes = parsePointerResult(op.host, "invalid_lens_proof");
357
+ if (Result.isError(hostSegRes)) return hostSegRes;
358
+ const fromSeg = fromSegRes.value;
359
+ const hostSeg = hostSegRes.value;
360
+ const src = getPathInfo(oldSchema, fromSeg, true);
361
+ if (!src.exists || !src.required) return invalidLensProof("plunge source not required");
362
+ if (op.createHost !== false) {
363
+ const parentRes = ensureParentRequiredResult(oldSchema, hostSeg, true, "invalid_lens_proof");
364
+ if (Result.isError(parentRes)) return parentRes;
365
+ } else {
366
+ const host = getPathInfo(oldSchema, hostSeg, true);
367
+ if (!host.exists || !host.required || !isObjectSchema(host.schema)) return invalidLensProof("plunge host invalid");
368
+ }
369
+ const hostNew = getPathInfo(newSchema, hostSeg, true);
370
+ if (!hostNew.exists || !isObjectSchema(hostNew.schema)) return invalidLensProof("plunge host missing in new schema");
371
+ const child = getPropertySchema(hostNew.schema, op.name, true);
372
+ if (!child.exists) return invalidLensProof("plunge name missing in new schema");
373
+ if (!isSchemaCompatible(src.schema, child.schema)) return invalidLensProof("plunge schema incompatible");
374
+ if (op.removeFromSource !== false && isPathRequired(newSchema, fromSeg)) {
375
+ return invalidLensProof("plunge removed field still required");
376
+ }
377
+ return Result.ok(undefined);
378
+ }
379
+ case "wrap": {
380
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
381
+ if (Result.isError(pathSegRes)) return pathSegRes;
382
+ const pathSeg = pathSegRes.value;
383
+ let src = getPathInfo(oldSchema, pathSeg, true);
384
+ if (!src.exists || !src.required) {
385
+ const renameRes = findRenameToResult(priorOps, pathSeg);
386
+ if (Result.isError(renameRes)) return renameRes;
387
+ if (renameRes.value) {
388
+ const fromRes = parsePointerResult((renameRes.value as any).from, "invalid_lens_proof");
389
+ if (Result.isError(fromRes)) return fromRes;
390
+ src = getPathInfo(oldSchema, fromRes.value, true);
391
+ }
392
+ }
393
+ if (!src.exists || !src.required) return invalidLensProof("wrap path not required");
394
+ const dest = getPathInfo(newSchema, pathSeg, true);
395
+ if (!dest.exists || !isArraySchema(dest.schema)) return invalidLensProof("wrap dest not array");
396
+ const items = getItemsSchema(dest.schema);
397
+ if (!isSchemaCompatible(src.schema, items)) return invalidLensProof("wrap items incompatible");
398
+ return Result.ok(undefined);
399
+ }
400
+ case "head": {
401
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
402
+ if (Result.isError(pathSegRes)) return pathSegRes;
403
+ const pathSeg = pathSegRes.value;
404
+ const src = getPathInfo(oldSchema, pathSeg, true);
405
+ if (!src.exists || !src.required || !isArraySchema(src.schema)) return invalidLensProof("head path not required array");
406
+ const hasEnum = src.schema && Array.isArray(src.schema.enum);
407
+ if (hasEnum) {
408
+ const ok = src.schema.enum.every((v: any) => Array.isArray(v) && v.length > 0);
409
+ if (!ok) return invalidLensProof("head requires enum non-empty");
410
+ }
411
+ const minItems = src.schema?.minItems;
412
+ if (!hasEnum && (minItems === undefined || minItems < 1)) {
413
+ return invalidLensProof("head requires minItems");
414
+ }
415
+ if (minItems !== undefined && minItems < 1) return invalidLensProof("head requires non-empty array");
416
+ const dest = getPathInfo(newSchema, pathSeg, true);
417
+ if (!dest.exists) return invalidLensProof("head dest missing");
418
+ const items = getItemsSchema(src.schema);
419
+ if (!isSchemaCompatible(items, dest.schema)) return invalidLensProof("head schema incompatible");
420
+ return Result.ok(undefined);
421
+ }
422
+ case "convert": {
423
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
424
+ if (Result.isError(pathSegRes)) return pathSegRes;
425
+ const pathSeg = pathSegRes.value;
426
+ const src = getPathInfo(oldSchema, pathSeg, true);
427
+ if (!src.exists || !src.required) return invalidLensProof("convert path not required");
428
+ if ((op.forward as any).map && !(op.forward as any).default) {
429
+ const enumRes = ensureEnumOrConstResult("invalid_lens_proof", src.schema);
430
+ if (Result.isError(enumRes)) return enumRes;
431
+ }
432
+ const builtin = (op.forward as any).builtin as string | undefined;
433
+ if (builtin === "string_to_int" || builtin === "rfc3339_to_unix_millis") {
434
+ const enumRes = ensureEnumOrConstResult("invalid_lens_proof", src.schema);
435
+ if (Result.isError(enumRes)) return enumRes;
436
+ }
437
+ const dest = getPathInfo(newSchema, pathSeg, true);
438
+ if (!dest.exists) return invalidLensProof("convert dest missing");
439
+ const outType = { type: op.toType };
440
+ if (!isSchemaCompatible(outType, dest.schema)) return invalidLensProof("convert dest incompatible");
441
+ return Result.ok(undefined);
442
+ }
443
+ case "in": {
444
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
445
+ if (Result.isError(pathSegRes)) return pathSegRes;
446
+ const pathSeg = pathSegRes.value;
447
+ const src = getPathInfo(oldSchema, pathSeg, true);
448
+ const dest = getPathInfo(newSchema, pathSeg, true);
449
+ if (!src.exists || !src.required || !isObjectSchema(src.schema)) return invalidLensProof("in path invalid");
450
+ if (!dest.exists || !isObjectSchema(dest.schema)) return invalidLensProof("in dest invalid");
451
+ return validateOpsResult(src.schema, dest.schema, op.ops);
452
+ }
453
+ case "map": {
454
+ const pathSegRes = parsePointerResult(op.path, "invalid_lens_proof");
455
+ if (Result.isError(pathSegRes)) return pathSegRes;
456
+ const pathSeg = pathSegRes.value;
457
+ const src = getPathInfo(oldSchema, pathSeg, true);
458
+ const dest = getPathInfo(newSchema, pathSeg, true);
459
+ if (!src.exists || !src.required || !isArraySchema(src.schema)) return invalidLensProof("map path invalid");
460
+ if (!dest.exists || !isArraySchema(dest.schema)) return invalidLensProof("map dest invalid");
461
+ const srcItems = getItemsSchema(src.schema);
462
+ const destItems = getItemsSchema(dest.schema);
463
+ return validateOpsResult(srcItems, destItems, op.ops);
464
+ }
465
+ default:
466
+ return invalidLensProof(`unknown op: ${(op as any).op}`);
467
+ }
468
+ }
469
+
470
+ function validateOpsResult(oldSchema: Schema, newSchema: Schema, ops: LensOp[]): Result<void, LensProofError> {
471
+ for (let i = 0; i < ops.length; i++) {
472
+ const res = validateOpResult(oldSchema, newSchema, ops[i], ops.slice(0, i), ops.slice(i + 1));
473
+ if (Result.isError(res)) return res;
474
+ }
475
+ return Result.ok(undefined);
476
+ }
477
+
478
+ export function validateLensAgainstSchemas(oldSchema: Schema, newSchema: Schema, lens: { ops: LensOp[] }): void {
479
+ const res = validateLensAgainstSchemasResult(oldSchema, newSchema, lens);
480
+ if (Result.isError(res)) throw dsError(res.error.message);
481
+ }
482
+
483
+ export function validateLensAgainstSchemasResult(
484
+ oldSchema: Schema,
485
+ newSchema: Schema,
486
+ lens: { ops: LensOp[] }
487
+ ): Result<void, LensProofError> {
488
+ return validateOpsResult(oldSchema, newSchema, lens.ops);
489
+ }
490
+
491
+ function fillOpsDefaultsResult(ops: LensOp[], newSchema: Schema): Result<void, LensProofError> {
492
+ for (const op of ops) {
493
+ if (op.op === "add") {
494
+ if (op.default === undefined) {
495
+ const pathRes = parsePointerResult(op.path, "invalid_lens_defaults");
496
+ if (Result.isError(pathRes)) return pathRes;
497
+ const info = getPathInfo(newSchema, pathRes.value, true);
498
+ if (!info.exists) return invalidLensDefaults("add dest missing in new schema");
499
+ const def = deriveDefault(info.schema);
500
+ if (def === undefined) return invalidLensDefaults("add missing default");
501
+ op.default = def;
502
+ }
503
+ continue;
504
+ }
505
+ if (op.op === "in") {
506
+ const pathRes = parsePointerResult(op.path, "invalid_lens_defaults");
507
+ if (Result.isError(pathRes)) return pathRes;
508
+ const info = getPathInfo(newSchema, pathRes.value, true);
509
+ if (!info.exists) return invalidLensDefaults("in dest missing in new schema");
510
+ const nestedRes = fillOpsDefaultsResult(op.ops, info.schema);
511
+ if (Result.isError(nestedRes)) return nestedRes;
512
+ continue;
513
+ }
514
+ if (op.op === "map") {
515
+ const pathRes = parsePointerResult(op.path, "invalid_lens_defaults");
516
+ if (Result.isError(pathRes)) return pathRes;
517
+ const info = getPathInfo(newSchema, pathRes.value, true);
518
+ if (!info.exists) return invalidLensDefaults("map dest missing in new schema");
519
+ const items = getItemsSchema(info.schema);
520
+ const nestedRes = fillOpsDefaultsResult(op.ops, items);
521
+ if (Result.isError(nestedRes)) return nestedRes;
522
+ }
523
+ }
524
+ return Result.ok(undefined);
525
+ }
526
+
527
+ function cloneLensForDefaultsResult(lens: Lens): Result<Lens, LensProofError> {
528
+ try {
529
+ return Result.ok(JSON.parse(JSON.stringify(lens)) as Lens);
530
+ } catch (e: any) {
531
+ return invalidLensDefaults(String(e?.message ?? e));
532
+ }
533
+ }
534
+
535
+ export function fillLensDefaults(lens: Lens, newSchema: Schema): Lens {
536
+ const res = fillLensDefaultsResult(lens, newSchema);
537
+ if (Result.isError(res)) throw dsError(res.error.message);
538
+ return res.value;
539
+ }
540
+
541
+ export function fillLensDefaultsResult(lens: Lens, newSchema: Schema): Result<Lens, LensProofError> {
542
+ const copyRes = cloneLensForDefaultsResult(lens);
543
+ if (Result.isError(copyRes)) return copyRes;
544
+ const fillRes = fillOpsDefaultsResult(copyRes.value.ops, newSchema);
545
+ if (Result.isError(fillRes)) return fillRes;
546
+ return Result.ok(copyRes.value);
547
+ }