@tungthedev/streams-server 0.2.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 (183) hide show
  1. package/CODE_OF_CONDUCT.md +45 -0
  2. package/CONTRIBUTING.md +76 -0
  3. package/LICENSE +201 -0
  4. package/README.md +58 -0
  5. package/SECURITY.md +42 -0
  6. package/bin/prisma-streams-server +2 -0
  7. package/package.json +46 -0
  8. package/src/app.ts +583 -0
  9. package/src/app_core.ts +3144 -0
  10. package/src/app_local.ts +206 -0
  11. package/src/auth.ts +124 -0
  12. package/src/auto_tune.ts +69 -0
  13. package/src/backpressure.ts +66 -0
  14. package/src/bootstrap.ts +613 -0
  15. package/src/compute/demo_entry.ts +415 -0
  16. package/src/compute/demo_site.ts +1242 -0
  17. package/src/compute/entry.ts +19 -0
  18. package/src/compute/package_entry.ts +4 -0
  19. package/src/compute/virtual-modules.d.ts +15 -0
  20. package/src/compute/worker_module_url.ts +9 -0
  21. package/src/concurrency_gate.ts +108 -0
  22. package/src/config.ts +402 -0
  23. package/src/db/bootstrap_store.ts +9 -0
  24. package/src/db/db.ts +2424 -0
  25. package/src/db/schema.ts +925 -0
  26. package/src/db/sqlite_manifest_snapshot.ts +81 -0
  27. package/src/db/sqlite_touch_store.ts +491 -0
  28. package/src/db/sqlite_wal_store.ts +472 -0
  29. package/src/details/full_mode_details.ts +568 -0
  30. package/src/expiry_sweeper.ts +47 -0
  31. package/src/foreground_activity.ts +55 -0
  32. package/src/hist.ts +169 -0
  33. package/src/index/binary_fuse.ts +379 -0
  34. package/src/index/indexer.ts +947 -0
  35. package/src/index/lexicon_file_cache.ts +261 -0
  36. package/src/index/lexicon_format.ts +93 -0
  37. package/src/index/lexicon_indexer.ts +863 -0
  38. package/src/index/run_cache.ts +84 -0
  39. package/src/index/run_format.ts +213 -0
  40. package/src/index/schedule.ts +28 -0
  41. package/src/index/secondary_indexer.ts +901 -0
  42. package/src/index/secondary_schema.ts +105 -0
  43. package/src/ingest.ts +309 -0
  44. package/src/lens/lens.ts +501 -0
  45. package/src/manifest.ts +249 -0
  46. package/src/memory.ts +334 -0
  47. package/src/metrics.ts +147 -0
  48. package/src/metrics_emitter.ts +83 -0
  49. package/src/notifier.ts +180 -0
  50. package/src/objectstore/accounting.ts +151 -0
  51. package/src/objectstore/interface.ts +13 -0
  52. package/src/objectstore/mock_r2.ts +269 -0
  53. package/src/objectstore/null.ts +32 -0
  54. package/src/objectstore/r2.ts +318 -0
  55. package/src/observe/pairing.ts +61 -0
  56. package/src/observe/request.ts +772 -0
  57. package/src/offset.ts +70 -0
  58. package/src/postgres/bootstrap.ts +269 -0
  59. package/src/postgres/companions.ts +197 -0
  60. package/src/postgres/control_restore.ts +109 -0
  61. package/src/postgres/details.ts +189 -0
  62. package/src/postgres/lexicon_index.ts +260 -0
  63. package/src/postgres/routing_index.ts +189 -0
  64. package/src/postgres/rows.ts +132 -0
  65. package/src/postgres/schema.ts +355 -0
  66. package/src/postgres/secondary_index.ts +238 -0
  67. package/src/postgres/segments.ts +900 -0
  68. package/src/postgres/stats.ts +103 -0
  69. package/src/postgres/store.ts +947 -0
  70. package/src/postgres/touch.ts +591 -0
  71. package/src/postgres/types.ts +32 -0
  72. package/src/profiles/evlog/schema.ts +234 -0
  73. package/src/profiles/evlog.ts +473 -0
  74. package/src/profiles/generic.ts +51 -0
  75. package/src/profiles/index.ts +237 -0
  76. package/src/profiles/metrics/block_format.ts +109 -0
  77. package/src/profiles/metrics/normalize.ts +366 -0
  78. package/src/profiles/metrics/schema.ts +319 -0
  79. package/src/profiles/metrics.ts +83 -0
  80. package/src/profiles/otelTraces/normalize.ts +955 -0
  81. package/src/profiles/otelTraces/otlp.ts +1002 -0
  82. package/src/profiles/otelTraces/schema.ts +408 -0
  83. package/src/profiles/otelTraces.ts +390 -0
  84. package/src/profiles/profile.ts +284 -0
  85. package/src/profiles/stateProtocol/change_event_conformance.typecheck.ts +35 -0
  86. package/src/profiles/stateProtocol/changes.ts +24 -0
  87. package/src/profiles/stateProtocol/ingest.ts +115 -0
  88. package/src/profiles/stateProtocol/routes.ts +511 -0
  89. package/src/profiles/stateProtocol/types.ts +6 -0
  90. package/src/profiles/stateProtocol/validation.ts +51 -0
  91. package/src/profiles/stateProtocol.ts +107 -0
  92. package/src/read_filter.ts +468 -0
  93. package/src/reader.ts +2986 -0
  94. package/src/runtime/hash.ts +156 -0
  95. package/src/runtime/hash_vendor/LICENSE.hash-wasm +38 -0
  96. package/src/runtime/hash_vendor/NOTICE.md +8 -0
  97. package/src/runtime/hash_vendor/xxhash3.umd.min.cjs +7 -0
  98. package/src/runtime/hash_vendor/xxhash32.umd.min.cjs +7 -0
  99. package/src/runtime/hash_vendor/xxhash64.umd.min.cjs +7 -0
  100. package/src/runtime/host_runtime.ts +5 -0
  101. package/src/runtime_memory.ts +200 -0
  102. package/src/runtime_memory_sampler.ts +237 -0
  103. package/src/schema/lens_schema.ts +290 -0
  104. package/src/schema/proof.ts +547 -0
  105. package/src/schema/read_json.ts +51 -0
  106. package/src/schema/registry.ts +966 -0
  107. package/src/search/agg_format.ts +638 -0
  108. package/src/search/aggregate.ts +409 -0
  109. package/src/search/binary/codec.ts +162 -0
  110. package/src/search/binary/docset.ts +67 -0
  111. package/src/search/binary/restart_strings.ts +181 -0
  112. package/src/search/binary/varint.ts +34 -0
  113. package/src/search/bitset.ts +19 -0
  114. package/src/search/col_format.ts +382 -0
  115. package/src/search/col_runtime.ts +59 -0
  116. package/src/search/column_encoding.ts +43 -0
  117. package/src/search/companion_file_cache.ts +319 -0
  118. package/src/search/companion_format.ts +327 -0
  119. package/src/search/companion_manager.ts +1305 -0
  120. package/src/search/companion_plan.ts +229 -0
  121. package/src/search/exact_format.ts +281 -0
  122. package/src/search/exact_runtime.ts +55 -0
  123. package/src/search/fts_format.ts +423 -0
  124. package/src/search/fts_runtime.ts +333 -0
  125. package/src/search/query.ts +875 -0
  126. package/src/search/schema.ts +245 -0
  127. package/src/segment/cache.ts +270 -0
  128. package/src/segment/cached_segment.ts +89 -0
  129. package/src/segment/format.ts +403 -0
  130. package/src/segment/segmenter.ts +412 -0
  131. package/src/segment/segmenter_worker.ts +72 -0
  132. package/src/segment/segmenter_workers.ts +130 -0
  133. package/src/server.ts +264 -0
  134. package/src/server_auto_tune.ts +158 -0
  135. package/src/sqlite/adapter.ts +335 -0
  136. package/src/sqlite/runtime_stats.ts +163 -0
  137. package/src/stats.ts +205 -0
  138. package/src/store/append.ts +50 -0
  139. package/src/store/bootstrap_restore_store.ts +71 -0
  140. package/src/store/capabilities.ts +86 -0
  141. package/src/store/full_mode_details_store.ts +71 -0
  142. package/src/store/index_store.ts +104 -0
  143. package/src/store/profile_touch_store.ts +1 -0
  144. package/src/store/rows.ts +144 -0
  145. package/src/store/schema_profile_store.ts +73 -0
  146. package/src/store/schema_publication.ts +6 -0
  147. package/src/store/segment_manifest_store.ts +129 -0
  148. package/src/store/segment_read_store.ts +22 -0
  149. package/src/store/stats_accounting_store.ts +83 -0
  150. package/src/store/touch_store.ts +98 -0
  151. package/src/store/wal_store.ts +21 -0
  152. package/src/stream_size_reconciler.ts +100 -0
  153. package/src/touch/canonical_change.ts +7 -0
  154. package/src/touch/live_keys.ts +158 -0
  155. package/src/touch/live_metrics.ts +841 -0
  156. package/src/touch/live_templates.ts +449 -0
  157. package/src/touch/manager.ts +1292 -0
  158. package/src/touch/process_batch.ts +576 -0
  159. package/src/touch/processor_worker.ts +85 -0
  160. package/src/touch/spec.ts +459 -0
  161. package/src/touch/touch_journal.ts +771 -0
  162. package/src/touch/touch_key_id.ts +20 -0
  163. package/src/touch/worker_pool.ts +191 -0
  164. package/src/touch/worker_protocol.ts +57 -0
  165. package/src/types/proper-lockfile.d.ts +1 -0
  166. package/src/uploader.ts +358 -0
  167. package/src/util/base32_crockford.ts +81 -0
  168. package/src/util/bloom256.ts +67 -0
  169. package/src/util/byte_lru.ts +73 -0
  170. package/src/util/cleanup.ts +22 -0
  171. package/src/util/crc32c.ts +29 -0
  172. package/src/util/ds_error.ts +15 -0
  173. package/src/util/duration.ts +17 -0
  174. package/src/util/endian.ts +53 -0
  175. package/src/util/json_pointer.ts +148 -0
  176. package/src/util/log.ts +25 -0
  177. package/src/util/lru.ts +53 -0
  178. package/src/util/retry.ts +35 -0
  179. package/src/util/siphash.ts +71 -0
  180. package/src/util/stream_paths.ts +50 -0
  181. package/src/util/time.ts +14 -0
  182. package/src/util/yield.ts +3 -0
  183. package/src/util/zstd.ts +24 -0
@@ -0,0 +1,403 @@
1
+ import { Result } from "better-result";
2
+ import { Bloom256 } from "../util/bloom256";
3
+ import { crc32c } from "../util/crc32c";
4
+ import { concatBytes, readU32BE, readU64BE, writeU32BE, writeU64BE } from "../util/endian";
5
+ import { dsError } from "../util/ds_error.ts";
6
+ import { zstdCompressSync, zstdDecompressSync } from "../util/zstd";
7
+
8
+ export type SegmentRecord = {
9
+ appendNs: bigint;
10
+ routingKey: Uint8Array; // UTF8 bytes (may be empty)
11
+ payload: Uint8Array;
12
+ };
13
+
14
+ export type DecodedBlock = {
15
+ recordCount: number;
16
+ firstAppendNs: bigint;
17
+ lastAppendNs: bigint;
18
+ bloom: Uint8Array; // 32 bytes
19
+ records: SegmentRecord[];
20
+ };
21
+
22
+ export type BlockIndexEntry = {
23
+ blockOffset: number;
24
+ firstOffset: bigint;
25
+ recordCount: number;
26
+ compressedLen: number;
27
+ firstAppendNs: bigint;
28
+ lastAppendNs: bigint;
29
+ };
30
+
31
+ export type SegmentFooter = {
32
+ version: number;
33
+ blocks: BlockIndexEntry[];
34
+ };
35
+
36
+ export type ParsedFooter = {
37
+ footer: SegmentFooter | null;
38
+ footerStart: number;
39
+ };
40
+
41
+ export type BlockHeader = {
42
+ uncompressedLen: number;
43
+ compressedLen: number;
44
+ recordCount: number;
45
+ bloom: Uint8Array;
46
+ firstAppendNs: bigint;
47
+ lastAppendNs: bigint;
48
+ crc32c: number;
49
+ };
50
+
51
+ export type SegmentFormatError = {
52
+ kind: "invalid_segment_format";
53
+ message: string;
54
+ };
55
+
56
+ export type IterateBlockEntry = {
57
+ blockOffset: number;
58
+ blockBytes: Uint8Array;
59
+ decoded: DecodedBlock;
60
+ };
61
+
62
+ export type IterateBlockRecordEntry = {
63
+ blockOffset: number;
64
+ recordIndex: number;
65
+ appendNs: bigint;
66
+ routingKey: Uint8Array;
67
+ payload: Uint8Array;
68
+ };
69
+
70
+ function invalidSegment<T = never>(message: string): Result<T, SegmentFormatError> {
71
+ return Result.err({ kind: "invalid_segment_format", message });
72
+ }
73
+
74
+ export const DSB3_HEADER_BYTES = 68;
75
+
76
+ const FOOTER_MAGIC = "DSF1";
77
+ const FOOTER_VERSION = 1;
78
+ const FOOTER_ENTRY_BYTES = 40; // 8+8+4+4+8+8
79
+ const FOOTER_TRAILER_BYTES = 8; // u32 len + 4-byte magic
80
+
81
+ export function encodeRecord(rec: SegmentRecord): Uint8Array {
82
+ const keyLen = rec.routingKey.byteLength;
83
+ const dataLen = rec.payload.byteLength;
84
+ const out = new Uint8Array(8 + 4 + keyLen + 4 + dataLen);
85
+ writeU64BE(out, 0, rec.appendNs);
86
+ writeU32BE(out, 8, keyLen);
87
+ out.set(rec.routingKey, 12);
88
+ writeU32BE(out, 12 + keyLen, dataLen);
89
+ out.set(rec.payload, 16 + keyLen);
90
+ return out;
91
+ }
92
+
93
+ export function encodeBlock(records: SegmentRecord[]): Uint8Array {
94
+ const res = encodeBlockResult(records);
95
+ if (Result.isError(res)) throw dsError(res.error.message);
96
+ return res.value;
97
+ }
98
+
99
+ export function encodeBlockResult(records: SegmentRecord[]): Result<Uint8Array, SegmentFormatError> {
100
+ if (records.length === 0) return invalidSegment("empty block");
101
+
102
+ const bloom = new Bloom256();
103
+ for (const r of records) bloom.add(r.routingKey);
104
+
105
+ const recBytes = records.map(encodeRecord);
106
+ const uncompressed = concatBytes(recBytes);
107
+ const compressed = new Uint8Array(zstdCompressSync(uncompressed));
108
+ const crc = crc32c(compressed);
109
+
110
+ const header = new Uint8Array(DSB3_HEADER_BYTES);
111
+ header[0] = "D".charCodeAt(0);
112
+ header[1] = "S".charCodeAt(0);
113
+ header[2] = "B".charCodeAt(0);
114
+ header[3] = "3".charCodeAt(0);
115
+ writeU32BE(header, 4, uncompressed.byteLength);
116
+ writeU32BE(header, 8, compressed.byteLength);
117
+ writeU32BE(header, 12, records.length);
118
+ header.set(bloom.toBytes(), 16);
119
+
120
+ const firstTs = records[0].appendNs;
121
+ const lastTs = records[records.length - 1].appendNs;
122
+ writeU64BE(header, 48, firstTs);
123
+ writeU64BE(header, 56, lastTs);
124
+ writeU32BE(header, 64, crc);
125
+
126
+ return Result.ok(concatBytes([header, compressed]));
127
+ }
128
+
129
+ export function decodeBlock(blockBytes: Uint8Array): DecodedBlock {
130
+ const res = decodeBlockResult(blockBytes);
131
+ if (Result.isError(res)) throw dsError(res.error.message);
132
+ return res.value;
133
+ }
134
+
135
+ export function decodeBlockResult(blockBytes: Uint8Array): Result<DecodedBlock, SegmentFormatError> {
136
+ const headerRes = parseBlockHeaderResult(blockBytes);
137
+ if (Result.isError(headerRes)) return headerRes;
138
+ const header = headerRes.value;
139
+ const uncompressedRes = decompressBlockPayloadResult(blockBytes, header);
140
+ if (Result.isError(uncompressedRes)) return uncompressedRes;
141
+ const uncompressed = uncompressedRes.value;
142
+
143
+ const records: SegmentRecord[] = [];
144
+ let off = 0;
145
+ for (let i = 0; i < header.recordCount; i++) {
146
+ if (off + 8 + 4 > uncompressed.byteLength) return invalidSegment("truncated record");
147
+ const appendNs = readU64BE(uncompressed, off);
148
+ off += 8;
149
+ const keyLen = readU32BE(uncompressed, off);
150
+ off += 4;
151
+ if (off + keyLen + 4 > uncompressed.byteLength) return invalidSegment("truncated key");
152
+ const routingKey = uncompressed.subarray(off, off + keyLen);
153
+ off += keyLen;
154
+ const dataLen = readU32BE(uncompressed, off);
155
+ off += 4;
156
+ if (off + dataLen > uncompressed.byteLength) return invalidSegment("truncated payload");
157
+ const payload = uncompressed.subarray(off, off + dataLen);
158
+ off += dataLen;
159
+ records.push({ appendNs, routingKey, payload });
160
+ }
161
+
162
+ return Result.ok({
163
+ recordCount: header.recordCount,
164
+ firstAppendNs: header.firstAppendNs,
165
+ lastAppendNs: header.lastAppendNs,
166
+ bloom: header.bloom.slice(),
167
+ records,
168
+ });
169
+ }
170
+
171
+ export function encodeFooter(entries: BlockIndexEntry[]): Uint8Array {
172
+ const footerLen = 12 + entries.length * FOOTER_ENTRY_BYTES;
173
+ const footer = new Uint8Array(footerLen + FOOTER_TRAILER_BYTES);
174
+ footer[0] = FOOTER_MAGIC.charCodeAt(0);
175
+ footer[1] = FOOTER_MAGIC.charCodeAt(1);
176
+ footer[2] = FOOTER_MAGIC.charCodeAt(2);
177
+ footer[3] = FOOTER_MAGIC.charCodeAt(3);
178
+ writeU32BE(footer, 4, FOOTER_VERSION);
179
+ writeU32BE(footer, 8, entries.length);
180
+
181
+ let off = 12;
182
+ for (const e of entries) {
183
+ writeU64BE(footer, off, BigInt(e.blockOffset));
184
+ off += 8;
185
+ writeU64BE(footer, off, e.firstOffset);
186
+ off += 8;
187
+ writeU32BE(footer, off, e.recordCount);
188
+ off += 4;
189
+ writeU32BE(footer, off, e.compressedLen);
190
+ off += 4;
191
+ writeU64BE(footer, off, e.firstAppendNs);
192
+ off += 8;
193
+ writeU64BE(footer, off, e.lastAppendNs);
194
+ off += 8;
195
+ }
196
+
197
+ // Trailer: footer length + magic
198
+ writeU32BE(footer, footerLen, footerLen);
199
+ footer[footerLen + 4] = FOOTER_MAGIC.charCodeAt(0);
200
+ footer[footerLen + 5] = FOOTER_MAGIC.charCodeAt(1);
201
+ footer[footerLen + 6] = FOOTER_MAGIC.charCodeAt(2);
202
+ footer[footerLen + 7] = FOOTER_MAGIC.charCodeAt(3);
203
+
204
+ return footer;
205
+ }
206
+
207
+ export function parseFooter(segmentBytes: Uint8Array): ParsedFooter | null {
208
+ if (segmentBytes.byteLength < FOOTER_TRAILER_BYTES) return null;
209
+ const tail = segmentBytes.slice(segmentBytes.byteLength - 4);
210
+ const tailMagic = String.fromCharCode(tail[0], tail[1], tail[2], tail[3]);
211
+ if (tailMagic !== FOOTER_MAGIC) return null;
212
+
213
+ const footerLen = readU32BE(segmentBytes, segmentBytes.byteLength - 8);
214
+ if (footerLen <= 0 || footerLen + FOOTER_TRAILER_BYTES > segmentBytes.byteLength) return null;
215
+
216
+ const footerStart = segmentBytes.byteLength - FOOTER_TRAILER_BYTES - footerLen;
217
+ if (footerStart < 0) return null;
218
+ const footer = segmentBytes.slice(footerStart, footerStart + footerLen);
219
+ const parsed = parseFooterBytes(footer);
220
+ return { footer: parsed, footerStart };
221
+ }
222
+
223
+ export function* iterateBlocksResult(
224
+ segmentBytes: Uint8Array
225
+ ): Generator<Result<IterateBlockEntry, SegmentFormatError>, void, void> {
226
+ const parsed = parseFooter(segmentBytes);
227
+ const limit = parsed ? parsed.footerStart : segmentBytes.byteLength;
228
+ let off = 0;
229
+ while (off < limit) {
230
+ if (off + DSB3_HEADER_BYTES > limit) {
231
+ yield invalidSegment("truncated segment (block header)");
232
+ return;
233
+ }
234
+ const header = segmentBytes.slice(off, off + DSB3_HEADER_BYTES);
235
+ const compressedLen = readU32BE(header, 8);
236
+ const totalLen = DSB3_HEADER_BYTES + compressedLen;
237
+ if (off + totalLen > limit) {
238
+ yield invalidSegment("truncated segment (block payload)");
239
+ return;
240
+ }
241
+ const blockBytes = segmentBytes.slice(off, off + totalLen);
242
+ const decodedRes = decodeBlockResult(blockBytes);
243
+ if (Result.isError(decodedRes)) {
244
+ yield decodedRes;
245
+ return;
246
+ }
247
+ yield Result.ok({ blockOffset: off, blockBytes, decoded: decodedRes.value });
248
+ off += totalLen;
249
+ }
250
+ }
251
+
252
+ export function* iterateBlockRecordsResult(
253
+ segmentBytes: Uint8Array
254
+ ): Generator<Result<IterateBlockRecordEntry, SegmentFormatError>, void, void> {
255
+ const parsed = parseFooter(segmentBytes);
256
+ const limit = parsed ? parsed.footerStart : segmentBytes.byteLength;
257
+ let off = 0;
258
+ while (off < limit) {
259
+ if (off + DSB3_HEADER_BYTES > limit) {
260
+ yield invalidSegment("truncated segment (block header)");
261
+ return;
262
+ }
263
+ const headerRes = parseBlockHeaderResult(segmentBytes.subarray(off, off + DSB3_HEADER_BYTES));
264
+ if (Result.isError(headerRes)) {
265
+ yield headerRes;
266
+ return;
267
+ }
268
+ const header = headerRes.value;
269
+ const totalLen = DSB3_HEADER_BYTES + header.compressedLen;
270
+ if (off + totalLen > limit) {
271
+ yield invalidSegment("truncated segment (block payload)");
272
+ return;
273
+ }
274
+ const blockBytes = segmentBytes.subarray(off, off + totalLen);
275
+ const uncompressedRes = decompressBlockPayloadResult(blockBytes, header);
276
+ if (Result.isError(uncompressedRes)) {
277
+ yield uncompressedRes;
278
+ return;
279
+ }
280
+ const uncompressed = uncompressedRes.value;
281
+ let recOff = 0;
282
+ for (let recordIndex = 0; recordIndex < header.recordCount; recordIndex++) {
283
+ if (recOff + 8 + 4 > uncompressed.byteLength) {
284
+ yield invalidSegment("truncated record");
285
+ return;
286
+ }
287
+ const appendNs = readU64BE(uncompressed, recOff);
288
+ recOff += 8;
289
+ const keyLen = readU32BE(uncompressed, recOff);
290
+ recOff += 4;
291
+ if (recOff + keyLen + 4 > uncompressed.byteLength) {
292
+ yield invalidSegment("truncated key");
293
+ return;
294
+ }
295
+ const routingKey = uncompressed.subarray(recOff, recOff + keyLen);
296
+ recOff += keyLen;
297
+ const dataLen = readU32BE(uncompressed, recOff);
298
+ recOff += 4;
299
+ if (recOff + dataLen > uncompressed.byteLength) {
300
+ yield invalidSegment("truncated payload");
301
+ return;
302
+ }
303
+ const payload = uncompressed.subarray(recOff, recOff + dataLen);
304
+ recOff += dataLen;
305
+ yield Result.ok({ blockOffset: off, recordIndex, appendNs, routingKey, payload });
306
+ }
307
+ off += totalLen;
308
+ }
309
+ }
310
+
311
+ export function* iterateBlocks(segmentBytes: Uint8Array): Generator<IterateBlockEntry, void, void> {
312
+ for (const itemRes of iterateBlocksResult(segmentBytes)) {
313
+ if (Result.isError(itemRes)) throw dsError(itemRes.error.message);
314
+ yield itemRes.value;
315
+ }
316
+ }
317
+
318
+ export function* iterateBlockRecords(segmentBytes: Uint8Array): Generator<IterateBlockRecordEntry, void, void> {
319
+ for (const itemRes of iterateBlockRecordsResult(segmentBytes)) {
320
+ if (Result.isError(itemRes)) throw dsError(itemRes.error.message);
321
+ yield itemRes.value;
322
+ }
323
+ }
324
+
325
+ export function parseFooterBytes(footer: Uint8Array): SegmentFooter | null {
326
+ if (footer.byteLength < 12) return null;
327
+ const magic = String.fromCharCode(footer[0], footer[1], footer[2], footer[3]);
328
+ if (magic !== FOOTER_MAGIC) return null;
329
+ const version = readU32BE(footer, 4);
330
+ const blockCount = readU32BE(footer, 8);
331
+ const expectedLen = 12 + blockCount * FOOTER_ENTRY_BYTES;
332
+ if (footer.byteLength !== expectedLen) return null;
333
+ const blocks: BlockIndexEntry[] = [];
334
+ let off = 12;
335
+ for (let i = 0; i < blockCount; i++) {
336
+ const blockOffset = Number(readU64BE(footer, off));
337
+ off += 8;
338
+ const firstOffset = readU64BE(footer, off);
339
+ off += 8;
340
+ const recordCount = readU32BE(footer, off);
341
+ off += 4;
342
+ const compressedLen = readU32BE(footer, off);
343
+ off += 4;
344
+ const firstAppendNs = readU64BE(footer, off);
345
+ off += 8;
346
+ const lastAppendNs = readU64BE(footer, off);
347
+ off += 8;
348
+ blocks.push({ blockOffset, firstOffset, recordCount, compressedLen, firstAppendNs, lastAppendNs });
349
+ }
350
+ return { version, blocks };
351
+ }
352
+
353
+ export function parseBlockHeader(header: Uint8Array): BlockHeader {
354
+ const res = parseBlockHeaderResult(header);
355
+ if (Result.isError(res)) throw dsError(res.error.message);
356
+ return res.value;
357
+ }
358
+
359
+ export function parseBlockHeaderResult(header: Uint8Array): Result<BlockHeader, SegmentFormatError> {
360
+ if (header.byteLength < DSB3_HEADER_BYTES) return invalidSegment("block header too small");
361
+ if (
362
+ header[0] !== "D".charCodeAt(0) ||
363
+ header[1] !== "S".charCodeAt(0) ||
364
+ header[2] !== "B".charCodeAt(0) ||
365
+ header[3] !== "3".charCodeAt(0)
366
+ ) {
367
+ return invalidSegment("bad block magic");
368
+ }
369
+ const uncompressedLen = readU32BE(header, 4);
370
+ const compressedLen = readU32BE(header, 8);
371
+ const recordCount = readU32BE(header, 12);
372
+ const bloom = header.slice(16, 48);
373
+ const firstAppendNs = readU64BE(header, 48);
374
+ const lastAppendNs = readU64BE(header, 56);
375
+ const crc32cVal = readU32BE(header, 64);
376
+ return Result.ok({
377
+ uncompressedLen,
378
+ compressedLen,
379
+ recordCount,
380
+ bloom,
381
+ firstAppendNs,
382
+ lastAppendNs,
383
+ crc32c: crc32cVal,
384
+ });
385
+ }
386
+
387
+ function decompressBlockPayloadResult(blockBytes: Uint8Array, header: BlockHeader): Result<Uint8Array, SegmentFormatError> {
388
+ const payload = blockBytes.subarray(DSB3_HEADER_BYTES, DSB3_HEADER_BYTES + header.compressedLen);
389
+ if (payload.byteLength !== header.compressedLen) return invalidSegment("truncated block");
390
+ const actualCrc = crc32c(payload);
391
+ if (actualCrc !== header.crc32c) return invalidSegment("crc mismatch");
392
+
393
+ let uncompressed: Uint8Array;
394
+ try {
395
+ uncompressed = new Uint8Array(zstdDecompressSync(payload));
396
+ } catch (e: any) {
397
+ return invalidSegment(String(e?.message ?? e));
398
+ }
399
+ if (uncompressed.byteLength !== header.uncompressedLen) {
400
+ return invalidSegment(`bad uncompressed len: got=${uncompressed.byteLength} expected=${header.uncompressedLen}`);
401
+ }
402
+ return Result.ok(uncompressed);
403
+ }