@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.
- package/CODE_OF_CONDUCT.md +45 -0
- package/CONTRIBUTING.md +68 -0
- package/LICENSE +201 -0
- package/README.md +39 -2
- package/SECURITY.md +33 -0
- package/bin/prisma-streams-server +2 -0
- package/package.json +29 -34
- package/src/app.ts +74 -0
- package/src/app_core.ts +1706 -0
- package/src/app_local.ts +46 -0
- package/src/backpressure.ts +66 -0
- package/src/bootstrap.ts +239 -0
- package/src/config.ts +251 -0
- package/src/db/db.ts +1386 -0
- package/src/db/schema.ts +625 -0
- package/src/expiry_sweeper.ts +44 -0
- package/src/hist.ts +169 -0
- package/src/index/binary_fuse.ts +379 -0
- package/src/index/indexer.ts +745 -0
- package/src/index/run_cache.ts +84 -0
- package/src/index/run_format.ts +213 -0
- package/src/ingest.ts +655 -0
- package/src/lens/lens.ts +501 -0
- package/src/manifest.ts +114 -0
- package/src/memory.ts +155 -0
- package/src/metrics.ts +161 -0
- package/src/metrics_emitter.ts +50 -0
- package/src/notifier.ts +64 -0
- package/src/objectstore/interface.ts +13 -0
- package/src/objectstore/mock_r2.ts +269 -0
- package/src/objectstore/null.ts +32 -0
- package/src/objectstore/r2.ts +128 -0
- package/src/offset.ts +70 -0
- package/src/reader.ts +454 -0
- package/src/runtime/hash.ts +156 -0
- package/src/runtime/hash_vendor/LICENSE.hash-wasm +38 -0
- package/src/runtime/hash_vendor/NOTICE.md +8 -0
- package/src/runtime/hash_vendor/xxhash3.umd.min.cjs +7 -0
- package/src/runtime/hash_vendor/xxhash32.umd.min.cjs +7 -0
- package/src/runtime/hash_vendor/xxhash64.umd.min.cjs +7 -0
- package/src/schema/lens_schema.ts +290 -0
- package/src/schema/proof.ts +547 -0
- package/src/schema/registry.ts +405 -0
- package/src/segment/cache.ts +179 -0
- package/src/segment/format.ts +331 -0
- package/src/segment/segmenter.ts +326 -0
- package/src/segment/segmenter_worker.ts +43 -0
- package/src/segment/segmenter_workers.ts +94 -0
- package/src/server.ts +326 -0
- package/src/sqlite/adapter.ts +164 -0
- package/src/stats.ts +205 -0
- package/src/touch/engine.ts +41 -0
- package/src/touch/interpreter_worker.ts +442 -0
- package/src/touch/live_keys.ts +118 -0
- package/src/touch/live_metrics.ts +827 -0
- package/src/touch/live_templates.ts +619 -0
- package/src/touch/manager.ts +1199 -0
- package/src/touch/spec.ts +456 -0
- package/src/touch/touch_journal.ts +671 -0
- package/src/touch/touch_key_id.ts +20 -0
- package/src/touch/worker_pool.ts +189 -0
- package/src/touch/worker_protocol.ts +56 -0
- package/src/types/proper-lockfile.d.ts +1 -0
- package/src/uploader.ts +317 -0
- package/src/util/base32_crockford.ts +81 -0
- package/src/util/bloom256.ts +67 -0
- package/src/util/cleanup.ts +22 -0
- package/src/util/crc32c.ts +29 -0
- package/src/util/ds_error.ts +15 -0
- package/src/util/duration.ts +17 -0
- package/src/util/endian.ts +53 -0
- package/src/util/json_pointer.ts +148 -0
- package/src/util/log.ts +25 -0
- package/src/util/lru.ts +45 -0
- package/src/util/retry.ts +35 -0
- package/src/util/siphash.ts +71 -0
- package/src/util/stream_paths.ts +31 -0
- package/src/util/time.ts +14 -0
- package/src/util/yield.ts +3 -0
- package/build/index.d.mts +0 -1
- package/build/index.d.ts +0 -1
- package/build/index.js +0 -0
- package/build/index.mjs +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { IndexRun } from "./run_format";
|
|
2
|
+
|
|
3
|
+
export type RunCacheStats = {
|
|
4
|
+
entries: number;
|
|
5
|
+
usedBytes: number;
|
|
6
|
+
maxBytes: number;
|
|
7
|
+
hits: number;
|
|
8
|
+
misses: number;
|
|
9
|
+
evictions: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class IndexRunCache {
|
|
13
|
+
private readonly maxBytes: number;
|
|
14
|
+
private readonly entries = new Map<string, { run: IndexRun; size: number }>();
|
|
15
|
+
private usedBytes = 0;
|
|
16
|
+
private hits = 0;
|
|
17
|
+
private misses = 0;
|
|
18
|
+
private evictions = 0;
|
|
19
|
+
|
|
20
|
+
constructor(maxBytes: number) {
|
|
21
|
+
this.maxBytes = maxBytes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(key: string): IndexRun | null {
|
|
25
|
+
const entry = this.entries.get(key);
|
|
26
|
+
if (!entry) {
|
|
27
|
+
this.misses += 1;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
this.hits += 1;
|
|
31
|
+
this.entries.delete(key);
|
|
32
|
+
this.entries.set(key, entry);
|
|
33
|
+
return entry.run;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
put(key: string, run: IndexRun, sizeHintBytes?: number): void {
|
|
37
|
+
if (this.maxBytes <= 0) return;
|
|
38
|
+
const size = Math.max(0, sizeHintBytes ?? estimateRunBytes(run));
|
|
39
|
+
if (size > this.maxBytes) return;
|
|
40
|
+
const existing = this.entries.get(key);
|
|
41
|
+
if (existing) {
|
|
42
|
+
this.usedBytes = Math.max(0, this.usedBytes - existing.size);
|
|
43
|
+
this.entries.delete(key);
|
|
44
|
+
}
|
|
45
|
+
while (this.usedBytes + size > this.maxBytes && this.entries.size > 0) {
|
|
46
|
+
const oldestKey = this.entries.keys().next().value as string;
|
|
47
|
+
const evicted = this.entries.get(oldestKey);
|
|
48
|
+
if (evicted) this.usedBytes = Math.max(0, this.usedBytes - evicted.size);
|
|
49
|
+
this.entries.delete(oldestKey);
|
|
50
|
+
this.evictions += 1;
|
|
51
|
+
}
|
|
52
|
+
this.entries.set(key, { run, size });
|
|
53
|
+
this.usedBytes += size;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
remove(key: string): void {
|
|
57
|
+
const entry = this.entries.get(key);
|
|
58
|
+
if (!entry) return;
|
|
59
|
+
this.usedBytes = Math.max(0, this.usedBytes - entry.size);
|
|
60
|
+
this.entries.delete(key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stats(): RunCacheStats {
|
|
64
|
+
return {
|
|
65
|
+
entries: this.entries.size,
|
|
66
|
+
usedBytes: this.usedBytes,
|
|
67
|
+
maxBytes: this.maxBytes,
|
|
68
|
+
hits: this.hits,
|
|
69
|
+
misses: this.misses,
|
|
70
|
+
evictions: this.evictions,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function estimateRunBytes(run: IndexRun): number {
|
|
76
|
+
let bytes = run.filterBytes ? run.filterBytes.byteLength : 0;
|
|
77
|
+
bytes += run.fingerprints.length * 8;
|
|
78
|
+
if (run.masks) bytes += run.masks.length * 2;
|
|
79
|
+
if (run.postings) {
|
|
80
|
+
for (const ps of run.postings) bytes += ps.length * 4;
|
|
81
|
+
}
|
|
82
|
+
// JS object overhead is significant; scale to avoid undercounting cache usage.
|
|
83
|
+
return bytes * 4;
|
|
84
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import { concatBytes, readU16BE, readU32BE, readU64BE, writeU16BE, writeU32BE, writeU64BE } from "../util/endian";
|
|
3
|
+
import { decodeBinaryFuseResult, encodeBinaryFuse, type BinaryFuseFilter } from "./binary_fuse";
|
|
4
|
+
import { dsError } from "../util/ds_error.ts";
|
|
5
|
+
|
|
6
|
+
export const INDEX_RUN_MAGIC = "IRN1";
|
|
7
|
+
export const INDEX_RUN_VERSION = 1;
|
|
8
|
+
export const RUN_TYPE_MASK16 = 0;
|
|
9
|
+
export const RUN_TYPE_POSTINGS = 1;
|
|
10
|
+
|
|
11
|
+
export type IndexRunMeta = {
|
|
12
|
+
runId: string;
|
|
13
|
+
level: number;
|
|
14
|
+
startSegment: number;
|
|
15
|
+
endSegment: number;
|
|
16
|
+
objectKey: string;
|
|
17
|
+
filterLen: number;
|
|
18
|
+
recordCount: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type IndexRun = {
|
|
22
|
+
meta: IndexRunMeta;
|
|
23
|
+
runType: number;
|
|
24
|
+
filterBytes: Uint8Array;
|
|
25
|
+
filter?: BinaryFuseFilter | null;
|
|
26
|
+
fingerprints: bigint[];
|
|
27
|
+
masks?: number[];
|
|
28
|
+
postings?: number[][];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type IndexRunFormatError = {
|
|
32
|
+
kind: "invalid_index_run";
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function invalidRun<T = never>(message: string): Result<T, IndexRunFormatError> {
|
|
37
|
+
return Result.err({ kind: "invalid_index_run", message });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function encodeIndexRun(run: IndexRun): Uint8Array {
|
|
41
|
+
const res = encodeIndexRunResult(run);
|
|
42
|
+
if (Result.isError(res)) throw dsError(res.error.message);
|
|
43
|
+
return res.value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function encodeIndexRunResult(run: IndexRun): Result<Uint8Array, IndexRunFormatError> {
|
|
47
|
+
const filterBytes =
|
|
48
|
+
run.filterBytes && run.filterBytes.byteLength > 0
|
|
49
|
+
? run.filterBytes
|
|
50
|
+
: run.filter
|
|
51
|
+
? encodeBinaryFuse(run.filter)
|
|
52
|
+
: new Uint8Array(0);
|
|
53
|
+
const recordCount = run.fingerprints.length;
|
|
54
|
+
let dataBytes: Uint8Array;
|
|
55
|
+
if (run.runType === RUN_TYPE_MASK16) {
|
|
56
|
+
if (!run.masks || run.masks.length !== recordCount) return invalidRun("mask run missing masks");
|
|
57
|
+
dataBytes = new Uint8Array(recordCount * 10);
|
|
58
|
+
for (let i = 0; i < recordCount; i++) {
|
|
59
|
+
const off = i * 10;
|
|
60
|
+
writeU64BE(dataBytes, off, run.fingerprints[i]);
|
|
61
|
+
writeU16BE(dataBytes, off + 8, run.masks[i]);
|
|
62
|
+
}
|
|
63
|
+
} else if (run.runType === RUN_TYPE_POSTINGS) {
|
|
64
|
+
if (!run.postings || run.postings.length !== recordCount) return invalidRun("postings run missing postings");
|
|
65
|
+
const chunks: Uint8Array[] = [];
|
|
66
|
+
for (let i = 0; i < recordCount; i++) {
|
|
67
|
+
const fp = run.fingerprints[i];
|
|
68
|
+
const hdr = new Uint8Array(8);
|
|
69
|
+
writeU64BE(hdr, 0, fp);
|
|
70
|
+
chunks.push(hdr);
|
|
71
|
+
const ps = run.postings[i];
|
|
72
|
+
chunks.push(encodeUVarint(ps.length));
|
|
73
|
+
for (const p of ps) chunks.push(encodeUVarint(p));
|
|
74
|
+
}
|
|
75
|
+
dataBytes = concatBytes(chunks);
|
|
76
|
+
} else {
|
|
77
|
+
return invalidRun(`unknown run type ${run.runType}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const header = new Uint8Array(36);
|
|
81
|
+
header[0] = INDEX_RUN_MAGIC.charCodeAt(0);
|
|
82
|
+
header[1] = INDEX_RUN_MAGIC.charCodeAt(1);
|
|
83
|
+
header[2] = INDEX_RUN_MAGIC.charCodeAt(2);
|
|
84
|
+
header[3] = INDEX_RUN_MAGIC.charCodeAt(3);
|
|
85
|
+
header[4] = INDEX_RUN_VERSION;
|
|
86
|
+
header[5] = run.runType & 0xff;
|
|
87
|
+
header[6] = run.meta.level & 0xff;
|
|
88
|
+
header[7] = 0;
|
|
89
|
+
writeU64BE(header, 8, BigInt(run.meta.startSegment));
|
|
90
|
+
writeU64BE(header, 16, BigInt(run.meta.endSegment));
|
|
91
|
+
writeU32BE(header, 24, recordCount);
|
|
92
|
+
writeU32BE(header, 28, filterBytes.byteLength);
|
|
93
|
+
writeU32BE(header, 32, dataBytes.byteLength);
|
|
94
|
+
|
|
95
|
+
return Result.ok(concatBytes([header, filterBytes, dataBytes]));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function decodeIndexRun(data: Uint8Array): IndexRun {
|
|
99
|
+
const res = decodeIndexRunResult(data);
|
|
100
|
+
if (Result.isError(res)) throw dsError(res.error.message);
|
|
101
|
+
return res.value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function decodeIndexRunResult(data: Uint8Array): Result<IndexRun, IndexRunFormatError> {
|
|
105
|
+
if (data.byteLength < 36) return invalidRun("run too short");
|
|
106
|
+
const magic = String.fromCharCode(data[0], data[1], data[2], data[3]);
|
|
107
|
+
if (magic !== INDEX_RUN_MAGIC) return invalidRun("invalid run magic");
|
|
108
|
+
if (data[4] !== INDEX_RUN_VERSION) return invalidRun("unsupported run version");
|
|
109
|
+
const runType = data[5];
|
|
110
|
+
const level = data[6];
|
|
111
|
+
const startSegment = Number(readU64BE(data, 8));
|
|
112
|
+
const endSegment = Number(readU64BE(data, 16));
|
|
113
|
+
const recordCount = readU32BE(data, 24);
|
|
114
|
+
const filterLen = readU32BE(data, 28);
|
|
115
|
+
const dataLen = readU32BE(data, 32);
|
|
116
|
+
const totalLen = 36 + filterLen + dataLen;
|
|
117
|
+
if (totalLen > data.byteLength) return invalidRun("run data truncated");
|
|
118
|
+
const filterBytes = data.slice(36, 36 + filterLen);
|
|
119
|
+
const filterRes = decodeBinaryFuseResult(filterBytes);
|
|
120
|
+
if (Result.isError(filterRes)) return invalidRun(filterRes.error.message);
|
|
121
|
+
const filter = filterRes.value;
|
|
122
|
+
const body = data.slice(36 + filterLen, totalLen);
|
|
123
|
+
|
|
124
|
+
const meta: IndexRunMeta = {
|
|
125
|
+
runId: "",
|
|
126
|
+
level,
|
|
127
|
+
startSegment,
|
|
128
|
+
endSegment,
|
|
129
|
+
objectKey: "",
|
|
130
|
+
filterLen,
|
|
131
|
+
recordCount,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const run: IndexRun = {
|
|
135
|
+
meta,
|
|
136
|
+
runType,
|
|
137
|
+
filterBytes,
|
|
138
|
+
filter,
|
|
139
|
+
fingerprints: [],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (runType === RUN_TYPE_MASK16) {
|
|
143
|
+
const needed = recordCount * 10;
|
|
144
|
+
if (body.byteLength < needed) return invalidRun("mask run truncated");
|
|
145
|
+
const fps: bigint[] = new Array(recordCount);
|
|
146
|
+
const masks: number[] = new Array(recordCount);
|
|
147
|
+
for (let i = 0; i < recordCount; i++) {
|
|
148
|
+
const off = i * 10;
|
|
149
|
+
fps[i] = readU64BE(body, off);
|
|
150
|
+
masks[i] = readU16BE(body, off + 8);
|
|
151
|
+
}
|
|
152
|
+
run.fingerprints = fps;
|
|
153
|
+
run.masks = masks;
|
|
154
|
+
return Result.ok(run);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (runType === RUN_TYPE_POSTINGS) {
|
|
158
|
+
const fps: bigint[] = [];
|
|
159
|
+
const postings: number[][] = [];
|
|
160
|
+
let pos = 0;
|
|
161
|
+
while (fps.length < recordCount && pos < body.byteLength) {
|
|
162
|
+
if (pos + 8 > body.byteLength) return invalidRun("postings run truncated (fp)");
|
|
163
|
+
const fp = readU64BE(body, pos);
|
|
164
|
+
pos += 8;
|
|
165
|
+
const countRes = decodeUVarint(body, pos);
|
|
166
|
+
if (!countRes) return invalidRun("postings run truncated (count)");
|
|
167
|
+
pos = countRes.next;
|
|
168
|
+
const count = countRes.value;
|
|
169
|
+
const ps: number[] = [];
|
|
170
|
+
for (let i = 0; i < count; i++) {
|
|
171
|
+
const vRes = decodeUVarint(body, pos);
|
|
172
|
+
if (!vRes) return invalidRun("postings run truncated (val)");
|
|
173
|
+
pos = vRes.next;
|
|
174
|
+
ps.push(vRes.value);
|
|
175
|
+
}
|
|
176
|
+
fps.push(fp);
|
|
177
|
+
postings.push(ps);
|
|
178
|
+
}
|
|
179
|
+
run.fingerprints = fps;
|
|
180
|
+
run.postings = postings;
|
|
181
|
+
return Result.ok(run);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return invalidRun(`unknown run type ${runType}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
type VarintResult = { value: number; next: number } | null;
|
|
188
|
+
|
|
189
|
+
function decodeUVarint(bytes: Uint8Array, offset: number): VarintResult {
|
|
190
|
+
let x = 0;
|
|
191
|
+
let s = 0;
|
|
192
|
+
for (let i = 0; i < 10 && offset + i < bytes.byteLength; i++) {
|
|
193
|
+
const b = bytes[offset + i];
|
|
194
|
+
if (b < 0x80) {
|
|
195
|
+
if (i === 9 && b > 1) return null;
|
|
196
|
+
return { value: x | (b << s), next: offset + i + 1 };
|
|
197
|
+
}
|
|
198
|
+
x |= (b & 0x7f) << s;
|
|
199
|
+
s += 7;
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function encodeUVarint(value: number): Uint8Array {
|
|
205
|
+
let v = value >>> 0;
|
|
206
|
+
const out: number[] = [];
|
|
207
|
+
while (v >= 0x80) {
|
|
208
|
+
out.push((v & 0x7f) | 0x80);
|
|
209
|
+
v >>>= 7;
|
|
210
|
+
}
|
|
211
|
+
out.push(v);
|
|
212
|
+
return new Uint8Array(out);
|
|
213
|
+
}
|