@prisma/streams-server 0.0.1 → 0.1.0

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 (85) 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 +1983 -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 +1440 -0
  15. package/src/db/schema.ts +619 -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 +459 -0
  54. package/src/touch/live_keys.ts +118 -0
  55. package/src/touch/live_metrics.ts +858 -0
  56. package/src/touch/live_templates.ts +619 -0
  57. package/src/touch/manager.ts +1341 -0
  58. package/src/touch/naming.ts +13 -0
  59. package/src/touch/routing_key_notifier.ts +275 -0
  60. package/src/touch/spec.ts +526 -0
  61. package/src/touch/touch_journal.ts +671 -0
  62. package/src/touch/touch_key_id.ts +20 -0
  63. package/src/touch/worker_pool.ts +189 -0
  64. package/src/touch/worker_protocol.ts +58 -0
  65. package/src/types/proper-lockfile.d.ts +1 -0
  66. package/src/uploader.ts +317 -0
  67. package/src/util/base32_crockford.ts +81 -0
  68. package/src/util/bloom256.ts +67 -0
  69. package/src/util/cleanup.ts +22 -0
  70. package/src/util/crc32c.ts +29 -0
  71. package/src/util/ds_error.ts +15 -0
  72. package/src/util/duration.ts +17 -0
  73. package/src/util/endian.ts +53 -0
  74. package/src/util/json_pointer.ts +148 -0
  75. package/src/util/log.ts +25 -0
  76. package/src/util/lru.ts +45 -0
  77. package/src/util/retry.ts +35 -0
  78. package/src/util/siphash.ts +71 -0
  79. package/src/util/stream_paths.ts +31 -0
  80. package/src/util/time.ts +14 -0
  81. package/src/util/yield.ts +3 -0
  82. package/build/index.d.mts +0 -1
  83. package/build/index.d.ts +0 -1
  84. package/build/index.js +0 -0
  85. package/build/index.mjs +0 -1
@@ -0,0 +1,501 @@
1
+ import { Result } from "better-result";
2
+ import { parseJsonPointerResult } from "../util/json_pointer";
3
+ import { dsError } from "../util/ds_error.ts";
4
+
5
+ export type LensTransformMap = { map: Record<string, any>; default?: any };
6
+ export type LensTransformBuiltin = { builtin: string };
7
+ export type LensTransform = LensTransformMap | LensTransformBuiltin;
8
+
9
+ export type LensOp =
10
+ | { op: "rename"; from: string; to: string }
11
+ | { op: "copy"; from: string; to: string }
12
+ | { op: "add"; path: string; schema: any; default?: any }
13
+ | { op: "remove"; path: string; schema: any; default?: any }
14
+ | { op: "hoist"; host: string; name: string; to: string; removeFromHost?: boolean }
15
+ | { op: "plunge"; from: string; host: string; name: string; createHost?: boolean; removeFromSource?: boolean }
16
+ | { op: "wrap"; path: string; mode: "singleton"; reverseMode?: "first" }
17
+ | { op: "head"; path: string; reverseMode?: "singleton" }
18
+ | { op: "convert"; path: string; fromType: JsonTypeName; toType: JsonTypeName; forward: LensTransform; backward: LensTransform }
19
+ | { op: "in"; path: string; ops: LensOp[] }
20
+ | { op: "map"; path: string; ops: LensOp[] };
21
+
22
+ export type Lens = {
23
+ apiVersion: "durable.lens/v1";
24
+ schema: string;
25
+ from: number;
26
+ to: number;
27
+ description?: string;
28
+ ops: LensOp[];
29
+ };
30
+
31
+ export type JsonTypeName = "string" | "number" | "integer" | "boolean" | "null" | "object" | "array";
32
+
33
+ type CompiledOp =
34
+ | { op: "rename"; from: string[]; to: string[] }
35
+ | { op: "copy"; from: string[]; to: string[] }
36
+ | { op: "add"; path: string[]; default?: any }
37
+ | { op: "remove"; path: string[] }
38
+ | { op: "hoist"; host: string[]; name: string; to: string[]; removeFromHost: boolean }
39
+ | { op: "plunge"; from: string[]; host: string[]; name: string; createHost: boolean; removeFromSource: boolean }
40
+ | { op: "wrap"; path: string[] }
41
+ | { op: "head"; path: string[] }
42
+ | { op: "convert"; path: string[]; fromType: JsonTypeName; toType: JsonTypeName; forward: LensTransform }
43
+ | { op: "in"; path: string[]; ops: CompiledOp[] }
44
+ | { op: "map"; path: string[]; ops: CompiledOp[] };
45
+
46
+ export type CompiledLens = {
47
+ schema: string;
48
+ from: number;
49
+ to: number;
50
+ ops: CompiledOp[];
51
+ };
52
+
53
+ export type LensCompileError = { kind: "invalid_lens"; message: string };
54
+ export type LensApplyError = { kind: "invalid_lens_ops"; message: string };
55
+
56
+ function invalidLensApply<T = never>(message: string): Result<T, LensApplyError> {
57
+ return Result.err({ kind: "invalid_lens_ops", message });
58
+ }
59
+
60
+ function resolveSegments(doc: any, segments: string[]): { parent: any; key: string | null; value: any; exists: boolean } {
61
+ if (segments.length === 0) return { parent: null, key: null, value: doc, exists: true };
62
+ let cur: any = doc;
63
+ for (let i = 0; i < segments.length - 1; i++) {
64
+ cur = getChild(cur, segments[i]);
65
+ if (cur === undefined) return { parent: null, key: null, value: undefined, exists: false };
66
+ }
67
+ const key = segments[segments.length - 1];
68
+ const value = cur === undefined ? undefined : getChild(cur, key);
69
+ return { parent: cur, key, value, exists: value !== undefined };
70
+ }
71
+
72
+ function getChild(container: any, seg: string): any {
73
+ if (Array.isArray(container)) {
74
+ const idx = Number(seg);
75
+ if (!Number.isInteger(idx)) return undefined;
76
+ return container[idx];
77
+ }
78
+ if (container && typeof container === "object") return (container as any)[seg];
79
+ return undefined;
80
+ }
81
+
82
+ function setChildResult(container: any, seg: string, value: any): Result<void, LensApplyError> {
83
+ if (Array.isArray(container)) {
84
+ const idx = Number(seg);
85
+ if (!Number.isInteger(idx) || idx < 0 || idx >= container.length) return invalidLensApply("array index out of bounds");
86
+ container[idx] = value;
87
+ return Result.ok(undefined);
88
+ }
89
+ if (container && typeof container === "object") {
90
+ (container as any)[seg] = value;
91
+ return Result.ok(undefined);
92
+ }
93
+ return invalidLensApply("invalid parent");
94
+ }
95
+
96
+ function deleteChildResult(container: any, seg: string): Result<void, LensApplyError> {
97
+ if (Array.isArray(container)) {
98
+ const idx = Number(seg);
99
+ if (!Number.isInteger(idx) || idx < 0 || idx >= container.length) return invalidLensApply("array index out of bounds");
100
+ container.splice(idx, 1);
101
+ return Result.ok(undefined);
102
+ }
103
+ if (container && typeof container === "object") {
104
+ delete (container as any)[seg];
105
+ return Result.ok(undefined);
106
+ }
107
+ return invalidLensApply("invalid parent");
108
+ }
109
+
110
+ function setAtResult(doc: any, segments: string[], value: any, opts?: { createParents?: boolean }): Result<any, LensApplyError> {
111
+ if (segments.length === 0) return Result.ok(value);
112
+ let cur: any = doc;
113
+ for (let i = 0; i < segments.length - 1; i++) {
114
+ const seg = segments[i];
115
+ let next = getChild(cur, seg);
116
+ if (next === undefined) {
117
+ if (!opts?.createParents) return invalidLensApply("missing parent");
118
+ next = {};
119
+ const setRes = setChildResult(cur, seg, next);
120
+ if (Result.isError(setRes)) return setRes;
121
+ }
122
+ cur = next;
123
+ }
124
+ const setLeafRes = setChildResult(cur, segments[segments.length - 1], value);
125
+ if (Result.isError(setLeafRes)) return setLeafRes;
126
+ return Result.ok(doc);
127
+ }
128
+
129
+ function deleteAtResult(doc: any, segments: string[]): Result<any, LensApplyError> {
130
+ if (segments.length === 0) return invalidLensApply("cannot delete document root");
131
+ let cur: any = doc;
132
+ for (let i = 0; i < segments.length - 1; i++) {
133
+ cur = getChild(cur, segments[i]);
134
+ if (cur === undefined) return invalidLensApply("missing parent");
135
+ }
136
+ const deleteRes = deleteChildResult(cur, segments[segments.length - 1]);
137
+ if (Result.isError(deleteRes)) return deleteRes;
138
+ return Result.ok(doc);
139
+ }
140
+
141
+ function coerceTypeNameResult(value: any): Result<JsonTypeName, LensApplyError> {
142
+ if (value === null) return Result.ok("null");
143
+ if (Array.isArray(value)) return Result.ok("array");
144
+ switch (typeof value) {
145
+ case "string":
146
+ return Result.ok("string");
147
+ case "number":
148
+ return Result.ok(Number.isInteger(value) ? "integer" : "number");
149
+ case "boolean":
150
+ return Result.ok("boolean");
151
+ case "object":
152
+ return Result.ok("object");
153
+ default:
154
+ return invalidLensApply("invalid json type");
155
+ }
156
+ }
157
+
158
+ function ensureTypeResult(value: any, expected: JsonTypeName): Result<void, LensApplyError> {
159
+ const actualRes = coerceTypeNameResult(value);
160
+ if (Result.isError(actualRes)) return actualRes;
161
+ const actual = actualRes.value;
162
+ if (expected === "number" && (actual === "number" || actual === "integer")) return Result.ok(undefined);
163
+ if (actual !== expected) return invalidLensApply(`type mismatch: expected ${expected}, got ${actual}`);
164
+ return Result.ok(undefined);
165
+ }
166
+
167
+ function applyTransformResult(transform: LensTransform, value: any): Result<any, LensApplyError> {
168
+ if ((transform as any).builtin) {
169
+ const name = (transform as LensTransformBuiltin).builtin;
170
+ return applyBuiltinResult(name, value);
171
+ }
172
+ const t = transform as LensTransformMap;
173
+ const key = String(value);
174
+ if (Object.prototype.hasOwnProperty.call(t.map, key)) return Result.ok(t.map[key]);
175
+ if (Object.prototype.hasOwnProperty.call(t, "default")) return Result.ok((t as any).default);
176
+ return invalidLensApply("convert map missing key and default");
177
+ }
178
+
179
+ function applyBuiltinResult(name: string, value: any): Result<any, LensApplyError> {
180
+ switch (name) {
181
+ case "lowercase":
182
+ if (typeof value !== "string") return invalidLensApply("builtin lowercase expects string");
183
+ return Result.ok(value.toLowerCase());
184
+ case "uppercase":
185
+ if (typeof value !== "string") return invalidLensApply("builtin uppercase expects string");
186
+ return Result.ok(value.toUpperCase());
187
+ case "string_to_int": {
188
+ if (typeof value !== "string") return invalidLensApply("builtin string_to_int expects string");
189
+ const n = Number.parseInt(value, 10);
190
+ if (!Number.isFinite(n)) return invalidLensApply("builtin string_to_int invalid");
191
+ return Result.ok(n);
192
+ }
193
+ case "int_to_string":
194
+ if (typeof value !== "number" || !Number.isInteger(value)) return invalidLensApply("builtin int_to_string expects integer");
195
+ return Result.ok(String(value));
196
+ case "rfc3339_to_unix_millis": {
197
+ if (typeof value !== "string") return invalidLensApply("builtin rfc3339_to_unix_millis expects string");
198
+ const ms = new Date(value).getTime();
199
+ if (Number.isNaN(ms)) return invalidLensApply("builtin rfc3339_to_unix_millis invalid");
200
+ return Result.ok(ms);
201
+ }
202
+ case "unix_millis_to_rfc3339":
203
+ if (typeof value !== "number" || !Number.isFinite(value)) return invalidLensApply("builtin unix_millis_to_rfc3339 expects number");
204
+ return Result.ok(new Date(value).toISOString());
205
+ default:
206
+ return invalidLensApply(`unknown builtin: ${name}`);
207
+ }
208
+ }
209
+
210
+ export function compileLens(lens: Lens): CompiledLens {
211
+ const res = compileLensResult(lens);
212
+ if (Result.isError(res)) throw dsError(res.error.message);
213
+ return res.value;
214
+ }
215
+
216
+ export function compileLensResult(lens: Lens): Result<CompiledLens, LensCompileError> {
217
+ const opsRes = compileOpsResult(lens.ops);
218
+ if (Result.isError(opsRes)) return opsRes;
219
+ return Result.ok({
220
+ schema: lens.schema,
221
+ from: lens.from,
222
+ to: lens.to,
223
+ ops: opsRes.value,
224
+ });
225
+ }
226
+
227
+ function invalidLens<T = never>(message: string): Result<T, LensCompileError> {
228
+ return Result.err({ kind: "invalid_lens", message });
229
+ }
230
+
231
+ function parsePointer(pointer: string, field: string): Result<string[], LensCompileError> {
232
+ const res = parseJsonPointerResult(pointer);
233
+ if (Result.isError(res)) return invalidLens(`${field} ${res.error.message}`);
234
+ return Result.ok(res.value);
235
+ }
236
+
237
+ function compileOpsResult(ops: LensOp[]): Result<CompiledOp[], LensCompileError> {
238
+ const compiled: CompiledOp[] = [];
239
+ for (const op of ops) {
240
+ switch (op.op) {
241
+ case "rename": {
242
+ const fromRes = parsePointer(op.from, "rename.from:");
243
+ if (Result.isError(fromRes)) return fromRes;
244
+ const toRes = parsePointer(op.to, "rename.to:");
245
+ if (Result.isError(toRes)) return toRes;
246
+ compiled.push({ op: "rename", from: fromRes.value, to: toRes.value });
247
+ break;
248
+ }
249
+ case "copy": {
250
+ const fromRes = parsePointer(op.from, "copy.from:");
251
+ if (Result.isError(fromRes)) return fromRes;
252
+ const toRes = parsePointer(op.to, "copy.to:");
253
+ if (Result.isError(toRes)) return toRes;
254
+ compiled.push({ op: "copy", from: fromRes.value, to: toRes.value });
255
+ break;
256
+ }
257
+ case "add": {
258
+ const pathRes = parsePointer(op.path, "add.path:");
259
+ if (Result.isError(pathRes)) return pathRes;
260
+ compiled.push({ op: "add", path: pathRes.value, default: op.default });
261
+ break;
262
+ }
263
+ case "remove": {
264
+ const pathRes = parsePointer(op.path, "remove.path:");
265
+ if (Result.isError(pathRes)) return pathRes;
266
+ compiled.push({ op: "remove", path: pathRes.value });
267
+ break;
268
+ }
269
+ case "hoist": {
270
+ const hostRes = parsePointer(op.host, "hoist.host:");
271
+ if (Result.isError(hostRes)) return hostRes;
272
+ const toRes = parsePointer(op.to, "hoist.to:");
273
+ if (Result.isError(toRes)) return toRes;
274
+ compiled.push({
275
+ op: "hoist",
276
+ host: hostRes.value,
277
+ name: op.name,
278
+ to: toRes.value,
279
+ removeFromHost: op.removeFromHost !== false,
280
+ });
281
+ break;
282
+ }
283
+ case "plunge": {
284
+ const fromRes = parsePointer(op.from, "plunge.from:");
285
+ if (Result.isError(fromRes)) return fromRes;
286
+ const hostRes = parsePointer(op.host, "plunge.host:");
287
+ if (Result.isError(hostRes)) return hostRes;
288
+ compiled.push({
289
+ op: "plunge",
290
+ from: fromRes.value,
291
+ host: hostRes.value,
292
+ name: op.name,
293
+ createHost: op.createHost !== false,
294
+ removeFromSource: op.removeFromSource !== false,
295
+ });
296
+ break;
297
+ }
298
+ case "wrap": {
299
+ const pathRes = parsePointer(op.path, "wrap.path:");
300
+ if (Result.isError(pathRes)) return pathRes;
301
+ compiled.push({ op: "wrap", path: pathRes.value });
302
+ break;
303
+ }
304
+ case "head": {
305
+ const pathRes = parsePointer(op.path, "head.path:");
306
+ if (Result.isError(pathRes)) return pathRes;
307
+ compiled.push({ op: "head", path: pathRes.value });
308
+ break;
309
+ }
310
+ case "convert": {
311
+ const pathRes = parsePointer(op.path, "convert.path:");
312
+ if (Result.isError(pathRes)) return pathRes;
313
+ compiled.push({
314
+ op: "convert",
315
+ path: pathRes.value,
316
+ fromType: op.fromType,
317
+ toType: op.toType,
318
+ forward: op.forward,
319
+ });
320
+ break;
321
+ }
322
+ case "in": {
323
+ const pathRes = parsePointer(op.path, "in.path:");
324
+ if (Result.isError(pathRes)) return pathRes;
325
+ const nestedRes = compileOpsResult(op.ops);
326
+ if (Result.isError(nestedRes)) return nestedRes;
327
+ compiled.push({ op: "in", path: pathRes.value, ops: nestedRes.value });
328
+ break;
329
+ }
330
+ case "map": {
331
+ const pathRes = parsePointer(op.path, "map.path:");
332
+ if (Result.isError(pathRes)) return pathRes;
333
+ const nestedRes = compileOpsResult(op.ops);
334
+ if (Result.isError(nestedRes)) return nestedRes;
335
+ compiled.push({ op: "map", path: pathRes.value, ops: nestedRes.value });
336
+ break;
337
+ }
338
+ default: {
339
+ return invalidLens(`unknown op: ${(op as any).op}`);
340
+ }
341
+ }
342
+ }
343
+ return Result.ok(compiled);
344
+ }
345
+
346
+ export function applyCompiledLens(lens: CompiledLens, doc: any): any {
347
+ const res = applyCompiledLensResult(lens, doc);
348
+ if (Result.isError(res)) throw dsError(res.error.message);
349
+ return res.value;
350
+ }
351
+
352
+ export function applyLensChain(lenses: CompiledLens[], doc: any): any {
353
+ const res = applyLensChainResult(lenses, doc);
354
+ if (Result.isError(res)) throw dsError(res.error.message);
355
+ return res.value;
356
+ }
357
+
358
+ function applyCompiledLensResult(lens: CompiledLens, doc: any): Result<any, LensApplyError> {
359
+ return applyOpsResult(doc, lens.ops);
360
+ }
361
+
362
+ export function applyLensChainResult(lenses: CompiledLens[], doc: any): Result<any, LensApplyError> {
363
+ let cur = doc;
364
+ for (const l of lenses) {
365
+ const stepRes = applyCompiledLensResult(l, cur);
366
+ if (Result.isError(stepRes)) return stepRes;
367
+ cur = stepRes.value;
368
+ }
369
+ return Result.ok(cur);
370
+ }
371
+
372
+ function applyOpsResult(doc: any, ops: CompiledOp[]): Result<any, LensApplyError> {
373
+ let root = doc;
374
+ for (const op of ops) {
375
+ switch (op.op) {
376
+ case "rename": {
377
+ const src = resolveSegments(root, op.from);
378
+ if (!src.exists) return invalidLensApply("rename missing source");
379
+ const setRes = setAtResult(root, op.to, src.value);
380
+ if (Result.isError(setRes)) return setRes;
381
+ const deleteRes = deleteAtResult(setRes.value, op.from);
382
+ if (Result.isError(deleteRes)) return deleteRes;
383
+ root = deleteRes.value;
384
+ break;
385
+ }
386
+ case "copy": {
387
+ const src = resolveSegments(root, op.from);
388
+ if (!src.exists) return invalidLensApply("copy missing source");
389
+ const setRes = setAtResult(root, op.to, src.value);
390
+ if (Result.isError(setRes)) return setRes;
391
+ root = setRes.value;
392
+ break;
393
+ }
394
+ case "add": {
395
+ const setRes = setAtResult(root, op.path, op.default);
396
+ if (Result.isError(setRes)) return setRes;
397
+ root = setRes.value;
398
+ break;
399
+ }
400
+ case "remove": {
401
+ const dst = resolveSegments(root, op.path);
402
+ if (!dst.exists) return invalidLensApply("remove missing path");
403
+ const deleteRes = deleteAtResult(root, op.path);
404
+ if (Result.isError(deleteRes)) return deleteRes;
405
+ root = deleteRes.value;
406
+ break;
407
+ }
408
+ case "hoist": {
409
+ const host = resolveSegments(root, op.host);
410
+ if (!host.exists || !host.value || typeof host.value !== "object" || Array.isArray(host.value)) {
411
+ return invalidLensApply("hoist host missing or not object");
412
+ }
413
+ if (!(op.name in host.value)) return invalidLensApply("hoist missing name");
414
+ const value = (host.value as any)[op.name];
415
+ const setRes = setAtResult(root, op.to, value);
416
+ if (Result.isError(setRes)) return setRes;
417
+ root = setRes.value;
418
+ if (op.removeFromHost) delete (host.value as any)[op.name];
419
+ break;
420
+ }
421
+ case "plunge": {
422
+ const src = resolveSegments(root, op.from);
423
+ if (!src.exists) return invalidLensApply("plunge missing source");
424
+ let host = resolveSegments(root, op.host);
425
+ if (!host.exists) {
426
+ if (!op.createHost) return invalidLensApply("plunge host missing");
427
+ const setHostRes = setAtResult(root, op.host, {}, { createParents: true });
428
+ if (Result.isError(setHostRes)) return setHostRes;
429
+ root = setHostRes.value;
430
+ host = resolveSegments(root, op.host);
431
+ }
432
+ if (!host.value || typeof host.value !== "object" || Array.isArray(host.value)) {
433
+ return invalidLensApply("plunge host not object");
434
+ }
435
+ (host.value as any)[op.name] = src.value;
436
+ if (op.removeFromSource) {
437
+ const deleteRes = deleteAtResult(root, op.from);
438
+ if (Result.isError(deleteRes)) return deleteRes;
439
+ root = deleteRes.value;
440
+ }
441
+ break;
442
+ }
443
+ case "wrap": {
444
+ const dst = resolveSegments(root, op.path);
445
+ if (!dst.exists) return invalidLensApply("wrap missing path");
446
+ const setRes = setAtResult(root, op.path, [dst.value]);
447
+ if (Result.isError(setRes)) return setRes;
448
+ root = setRes.value;
449
+ break;
450
+ }
451
+ case "head": {
452
+ const dst = resolveSegments(root, op.path);
453
+ if (!dst.exists) return invalidLensApply("head missing path");
454
+ if (!Array.isArray(dst.value) || dst.value.length === 0) return invalidLensApply("head expects non-empty array");
455
+ const setRes = setAtResult(root, op.path, dst.value[0]);
456
+ if (Result.isError(setRes)) return setRes;
457
+ root = setRes.value;
458
+ break;
459
+ }
460
+ case "convert": {
461
+ const dst = resolveSegments(root, op.path);
462
+ if (!dst.exists) return invalidLensApply("convert missing path");
463
+ const ensureFromRes = ensureTypeResult(dst.value, op.fromType);
464
+ if (Result.isError(ensureFromRes)) return ensureFromRes;
465
+ const outRes = applyTransformResult(op.forward, dst.value);
466
+ if (Result.isError(outRes)) return outRes;
467
+ const ensureToRes = ensureTypeResult(outRes.value, op.toType);
468
+ if (Result.isError(ensureToRes)) return ensureToRes;
469
+ const setRes = setAtResult(root, op.path, outRes.value);
470
+ if (Result.isError(setRes)) return setRes;
471
+ root = setRes.value;
472
+ break;
473
+ }
474
+ case "in": {
475
+ const dst = resolveSegments(root, op.path);
476
+ if (!dst.exists) return invalidLensApply("in missing path");
477
+ if (!dst.value || typeof dst.value !== "object" || Array.isArray(dst.value)) return invalidLensApply("in expects object");
478
+ const nestedRes = applyOpsResult(dst.value, op.ops);
479
+ if (Result.isError(nestedRes)) return nestedRes;
480
+ break;
481
+ }
482
+ case "map": {
483
+ const dst = resolveSegments(root, op.path);
484
+ if (!dst.exists) return invalidLensApply("map missing path");
485
+ if (!Array.isArray(dst.value)) return invalidLensApply("map expects array");
486
+ for (const item of dst.value) {
487
+ const nestedRes = applyOpsResult(item, op.ops);
488
+ if (Result.isError(nestedRes)) return nestedRes;
489
+ }
490
+ break;
491
+ }
492
+ default:
493
+ return invalidLensApply(`unknown op: ${(op as any).op}`);
494
+ }
495
+ }
496
+ return Result.ok(root);
497
+ }
498
+
499
+ export function lensFromJson(raw: any): Lens {
500
+ return raw as Lens;
501
+ }
@@ -0,0 +1,114 @@
1
+ import { zstdCompressSync } from "node:zlib";
2
+ import { Result } from "better-result";
3
+ import type { IndexRunRow, IndexStateRow, SegmentMetaRow, StreamRow } from "./db/db";
4
+ import { encodeOffsetResult } from "./offset";
5
+ import { dsError } from "./util/ds_error.ts";
6
+
7
+ function b64(bytes: Uint8Array): string {
8
+ return Buffer.from(bytes).toString("base64");
9
+ }
10
+
11
+ function compressB64(bytes: Uint8Array): string {
12
+ return b64(new Uint8Array(zstdCompressSync(bytes)));
13
+ }
14
+
15
+ export type ManifestJson = Record<string, any>;
16
+ export type ManifestBuildError = { kind: "invalid_manifest"; message: string };
17
+
18
+ function invalidManifest<T = never>(message: string): Result<T, ManifestBuildError> {
19
+ return Result.err({ kind: "invalid_manifest", message });
20
+ }
21
+
22
+ type BuildManifestArgs = {
23
+ streamName: string;
24
+ streamRow: StreamRow;
25
+ segmentMeta: SegmentMetaRow;
26
+ uploadedPrefixCount: number;
27
+ generation: number;
28
+ indexState?: IndexStateRow | null;
29
+ indexRuns?: IndexRunRow[];
30
+ retiredRuns?: IndexRunRow[];
31
+ };
32
+
33
+ export function buildManifestResult(args: BuildManifestArgs): Result<ManifestJson, ManifestBuildError> {
34
+ const { streamName, streamRow, segmentMeta, uploadedPrefixCount, generation, indexState, indexRuns, retiredRuns } = args;
35
+
36
+ const createdAt = new Date(Number(streamRow.created_at_ms)).toISOString();
37
+ const expiresAt = streamRow.expires_at_ms == null ? null : new Date(Number(streamRow.expires_at_ms)).toISOString();
38
+
39
+ const nextOffset = streamRow.next_offset;
40
+ const nextOffsetNum = Number(nextOffset);
41
+ const nextOffsetEncodedRes = encodeOffsetResult(streamRow.epoch, nextOffset);
42
+ if (Result.isError(nextOffsetEncodedRes)) return invalidManifest(nextOffsetEncodedRes.error.message);
43
+ const nextOffsetEncoded = nextOffsetEncodedRes.value;
44
+
45
+ const maxCount = Math.max(0, segmentMeta.segment_count);
46
+ const prefix = Math.max(0, Math.min(uploadedPrefixCount, maxCount));
47
+ const offBytes = segmentMeta.segment_offsets.subarray(0, prefix * 8);
48
+ const blockBytes = segmentMeta.segment_blocks.subarray(0, prefix * 4);
49
+ const lastTsBytes = segmentMeta.segment_last_ts.subarray(0, prefix * 8);
50
+
51
+ const segOffsetsB64 = compressB64(offBytes);
52
+ const segBlocksB64 = compressB64(blockBytes);
53
+ const segLastTsB64 = compressB64(lastTsBytes);
54
+
55
+ const activeRuns =
56
+ indexRuns?.map((r) => ({
57
+ run_id: r.run_id,
58
+ level: r.level,
59
+ start_segment: r.start_segment,
60
+ end_segment: r.end_segment,
61
+ object_key: r.object_key,
62
+ filter_len: r.filter_len,
63
+ record_count: r.record_count,
64
+ })) ?? [];
65
+ const retired = retiredRuns?.map((r) => ({
66
+ run_id: r.run_id,
67
+ level: r.level,
68
+ start_segment: r.start_segment,
69
+ end_segment: r.end_segment,
70
+ object_key: r.object_key,
71
+ filter_len: r.filter_len,
72
+ record_count: r.record_count,
73
+ retired_gen: r.retired_gen ?? undefined,
74
+ retired_at_unix: r.retired_at_ms != null ? Number(r.retired_at_ms / 1000n) : undefined,
75
+ })) ?? [];
76
+ const indexSecret = indexState?.index_secret ? b64(indexState.index_secret) : "";
77
+ const indexedThrough = indexState?.indexed_through ?? 0;
78
+
79
+ return Result.ok({
80
+ name: streamName,
81
+ created_at: createdAt,
82
+ expires_at: expiresAt,
83
+ content_type: streamRow.content_type,
84
+ stream_seq: streamRow.stream_seq ?? null,
85
+ closed: streamRow.closed,
86
+ closed_producer_id: streamRow.closed_producer_id ?? null,
87
+ closed_producer_epoch: streamRow.closed_producer_epoch ?? null,
88
+ closed_producer_seq: streamRow.closed_producer_seq ?? null,
89
+ ttl_seconds: streamRow.ttl_seconds ?? null,
90
+ stream_flags: streamRow.stream_flags,
91
+ generation,
92
+ epoch: streamRow.epoch,
93
+ next_offset: nextOffsetNum,
94
+ next_offset_encoded: nextOffsetEncoded,
95
+ segment_count: prefix,
96
+ uploaded_through: prefix,
97
+ active_file_offset: nextOffsetNum,
98
+ last_committed_ts: Number(streamRow.last_append_ms * 1_000_000n),
99
+ zstd_dict: "",
100
+ segment_offsets: segOffsetsB64,
101
+ segment_blocks: segBlocksB64,
102
+ segment_last_ts: segLastTsB64,
103
+ indexed_through: indexedThrough,
104
+ index_secret: indexSecret,
105
+ active_runs: activeRuns,
106
+ retired_runs: retired,
107
+ });
108
+ }
109
+
110
+ export function buildManifest(args: BuildManifestArgs): ManifestJson {
111
+ const res = buildManifestResult(args);
112
+ if (Result.isError(res)) throw dsError(res.error.message);
113
+ return res.value;
114
+ }