@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,19 @@
1
+ function hasFlag(argv: string[], flag: string): boolean {
2
+ return argv.includes(flag) || argv.some((arg) => arg.startsWith(`${flag}=`));
3
+ }
4
+
5
+ export function ensureComputeArgv(argv: string[], env: NodeJS.ProcessEnv = process.env): string[] {
6
+ const next = [...argv];
7
+ if (!hasFlag(next, "--object-store")) {
8
+ next.push("--object-store", "r2");
9
+ }
10
+ if (env.DS_MEMORY_LIMIT_MB != null && !hasFlag(next, "--auto-tune")) {
11
+ next.push("--auto-tune");
12
+ }
13
+ return next;
14
+ }
15
+
16
+ if (import.meta.main) {
17
+ process.argv = ensureComputeArgv(process.argv);
18
+ await import("../server");
19
+ }
@@ -0,0 +1,4 @@
1
+ import { ensureComputeArgv } from "./entry";
2
+
3
+ process.argv = ensureComputeArgv(process.argv);
4
+ await import("../server");
@@ -0,0 +1,15 @@
1
+ declare module "virtual:prebuilt-studio-assets" {
2
+ const mod: {
3
+ appScript: string;
4
+ appStyles: string;
5
+ builtAssets: Map<
6
+ string,
7
+ {
8
+ bytes: Uint8Array;
9
+ contentType: string;
10
+ }
11
+ >;
12
+ };
13
+
14
+ export = mod;
15
+ }
@@ -0,0 +1,9 @@
1
+ export function resolveWorkerModuleUrl(
2
+ currentModuleUrl: string,
3
+ sourceRelativePath: string,
4
+ builtRelativePath = sourceRelativePath.endsWith(".ts")
5
+ ? `${sourceRelativePath.slice(0, -3)}.js`
6
+ : sourceRelativePath
7
+ ): URL {
8
+ return new URL(currentModuleUrl.endsWith(".js") ? builtRelativePath : sourceRelativePath, currentModuleUrl);
9
+ }
@@ -0,0 +1,108 @@
1
+ export type GateRelease = () => void;
2
+
3
+ type Waiter = {
4
+ resolve: (release: GateRelease) => void;
5
+ reject: (error: unknown) => void;
6
+ signal: AbortSignal | null;
7
+ onAbort: (() => void) | null;
8
+ };
9
+
10
+ function abortError(): Error {
11
+ const err = new Error("operation aborted");
12
+ err.name = "AbortError";
13
+ return err;
14
+ }
15
+
16
+ export class ConcurrencyGate {
17
+ private limit: number;
18
+ private active = 0;
19
+ private readonly waiters: Waiter[] = [];
20
+
21
+ constructor(limit: number) {
22
+ this.limit = Math.max(1, Math.floor(limit));
23
+ }
24
+
25
+ getLimit(): number {
26
+ return this.limit;
27
+ }
28
+
29
+ getActive(): number {
30
+ return this.active;
31
+ }
32
+
33
+ getQueued(): number {
34
+ return this.waiters.length;
35
+ }
36
+
37
+ setLimit(nextLimit: number): void {
38
+ this.limit = Math.max(1, Math.floor(nextLimit));
39
+ this.drain();
40
+ }
41
+
42
+ async acquire(signal?: AbortSignal | null): Promise<GateRelease> {
43
+ if (signal?.aborted) throw abortError();
44
+ if (this.active < this.limit) {
45
+ this.active += 1;
46
+ return this.releaseFactory();
47
+ }
48
+ return await new Promise<GateRelease>((resolve, reject) => {
49
+ const waiter: Waiter = {
50
+ resolve,
51
+ reject,
52
+ signal: signal ?? null,
53
+ onAbort: null,
54
+ };
55
+ if (signal) {
56
+ waiter.onAbort = () => {
57
+ this.removeWaiter(waiter);
58
+ reject(abortError());
59
+ };
60
+ signal.addEventListener("abort", waiter.onAbort, { once: true });
61
+ }
62
+ this.waiters.push(waiter);
63
+ });
64
+ }
65
+
66
+ async run<T>(fn: () => Promise<T>, signal?: AbortSignal | null): Promise<T> {
67
+ const release = await this.acquire(signal);
68
+ try {
69
+ return await fn();
70
+ } finally {
71
+ release();
72
+ }
73
+ }
74
+
75
+ private releaseFactory(): GateRelease {
76
+ let released = false;
77
+ return () => {
78
+ if (released) return;
79
+ released = true;
80
+ this.active = Math.max(0, this.active - 1);
81
+ this.drain();
82
+ };
83
+ }
84
+
85
+ private removeWaiter(waiter: Waiter): void {
86
+ const idx = this.waiters.indexOf(waiter);
87
+ if (idx >= 0) this.waiters.splice(idx, 1);
88
+ if (waiter.signal && waiter.onAbort) {
89
+ waiter.signal.removeEventListener("abort", waiter.onAbort);
90
+ waiter.onAbort = null;
91
+ }
92
+ }
93
+
94
+ private drain(): void {
95
+ while (this.active < this.limit && this.waiters.length > 0) {
96
+ const waiter = this.waiters.shift()!;
97
+ if (waiter.signal?.aborted) {
98
+ if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener("abort", waiter.onAbort);
99
+ waiter.reject(abortError());
100
+ continue;
101
+ }
102
+ if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener("abort", waiter.onAbort);
103
+ waiter.onAbort = null;
104
+ this.active += 1;
105
+ waiter.resolve(this.releaseFactory());
106
+ }
107
+ }
108
+ }
package/src/config.ts ADDED
@@ -0,0 +1,402 @@
1
+ import { dsError } from "./util/ds_error.ts";
2
+ export type Config = {
3
+ autoTuneRequestedMemoryMb: number | null;
4
+ autoTunePresetMb: number | null;
5
+ autoTuneEffectiveMemoryLimitMb: number | null;
6
+ storage: "sqlite" | "postgres";
7
+ postgresMode: "wal" | "full";
8
+ postgresUrl: string | null;
9
+ host: string;
10
+ rootDir: string;
11
+ dbPath: string;
12
+ segmentMaxBytes: number;
13
+ blockMaxBytes: number;
14
+ segmentTargetRows: number;
15
+ segmentMaxIntervalMs: number;
16
+ segmentCheckIntervalMs: number;
17
+ segmenterWorkers: number;
18
+ uploadIntervalMs: number;
19
+ uploadConcurrency: number;
20
+ segmentCacheMaxBytes: number;
21
+ segmentFooterCacheEntries: number;
22
+ indexRunCacheMaxBytes: number;
23
+ indexRunMemoryCacheBytes: number;
24
+ lexiconIndexCacheMaxBytes: number;
25
+ lexiconMappedCacheEntries: number;
26
+ indexL0SpanSegments: number;
27
+ indexBuildConcurrency: number;
28
+ indexCheckIntervalMs: number;
29
+ searchCompanionBuildBatchSegments: number;
30
+ searchCompanionYieldBlocks: number;
31
+ searchCompanionFileCacheMaxBytes: number;
32
+ searchCompanionFileCacheMaxAgeMs: number;
33
+ searchCompanionMappedCacheEntries: number;
34
+ searchCompanionTocCacheBytes: number;
35
+ searchCompanionSectionCacheBytes: number;
36
+ searchWalOverlayQuietPeriodMs: number;
37
+ searchWalOverlayMaxBytes: number;
38
+ indexCompactionFanout: number;
39
+ indexMaxLevel: number;
40
+ indexCompactionConcurrency: number;
41
+ indexRetireGenWindow: number;
42
+ indexRetireMinMs: number;
43
+ readMaxBytes: number;
44
+ readMaxRecords: number;
45
+ appendMaxBodyBytes: number;
46
+ ingestFlushIntervalMs: number;
47
+ ingestMaxBatchRequests: number;
48
+ ingestMaxBatchBytes: number;
49
+ ingestMaxQueueRequests: number;
50
+ ingestMaxQueueBytes: number;
51
+ ingestConcurrency: number;
52
+ ingestBusyTimeoutMs: number;
53
+ localBacklogMaxBytes: number;
54
+ memoryLimitBytes: number;
55
+ sqliteCacheBytes: number;
56
+ workerSqliteCacheBytes: number;
57
+ readConcurrency: number;
58
+ searchConcurrency: number;
59
+ asyncIndexConcurrency: number;
60
+ heapSnapshotPath: string | null;
61
+ memorySamplerPath: string | null;
62
+ memorySamplerIntervalMs: number;
63
+ objectStoreTimeoutMs: number;
64
+ objectStoreRetries: number;
65
+ objectStoreBaseDelayMs: number;
66
+ objectStoreMaxDelayMs: number;
67
+ expirySweepIntervalMs: number;
68
+ expirySweepBatchLimit: number;
69
+ metricsFlushIntervalMs: number;
70
+ touchWorkers: number;
71
+ touchCheckIntervalMs: number;
72
+ touchMaxBatchRows: number;
73
+ touchMaxBatchBytes: number;
74
+ otlpTracesStream: string | null;
75
+ otlpAutoCreate: boolean;
76
+ port: number;
77
+ };
78
+
79
+ const KNOWN_DS_ENVS = new Set<string>([
80
+ "DS_STORAGE",
81
+ "DS_POSTGRES_MODE",
82
+ "DS_POSTGRES_URL",
83
+ "DS_TEST_POSTGRES_URL",
84
+ "DS_ROOT",
85
+ "DS_HOST",
86
+ "DS_DB_PATH",
87
+ "DS_SEGMENT_MAX_BYTES",
88
+ "DS_BLOCK_MAX_BYTES",
89
+ "DS_SEGMENT_TARGET_ROWS",
90
+ "DS_SEGMENT_MAX_INTERVAL_MS",
91
+ "DS_SEGMENT_CHECK_MS",
92
+ "DS_SEGMENTER_WORKERS",
93
+ "DS_UPLOAD_CHECK_MS",
94
+ "DS_UPLOAD_CONCURRENCY",
95
+ "DS_BASE_WAL_GC_CHUNK_OFFSETS",
96
+ "DS_BASE_WAL_GC_INTERVAL_MS",
97
+ "DS_SEGMENT_CACHE_MAX_BYTES",
98
+ "DS_SEGMENT_FOOTER_CACHE_ENTRIES",
99
+ "DS_INDEX_RUN_CACHE_MAX_BYTES",
100
+ "DS_INDEX_RUN_MEM_CACHE_BYTES",
101
+ "DS_LEXICON_INDEX_CACHE_MAX_BYTES",
102
+ "DS_LEXICON_MMAP_CACHE_ENTRIES",
103
+ "DS_INDEX_L0_SPAN",
104
+ "DS_INDEX_BUILD_CONCURRENCY",
105
+ "DS_INDEX_CHECK_MS",
106
+ "DS_SEARCH_COMPANION_BATCH_SEGMENTS",
107
+ "DS_SEARCH_COMPANION_YIELD_BLOCKS",
108
+ "DS_SEARCH_COMPANION_FILE_CACHE_MAX_BYTES",
109
+ "DS_SEARCH_COMPANION_FILE_CACHE_MAX_AGE_MS",
110
+ "DS_SEARCH_COMPANION_MMAP_CACHE_ENTRIES",
111
+ "DS_SEARCH_COMPANION_TOC_CACHE_BYTES",
112
+ "DS_SEARCH_COMPANION_SECTION_CACHE_BYTES",
113
+ "DS_SEARCH_WAL_OVERLAY_QUIET_MS",
114
+ "DS_SEARCH_WAL_OVERLAY_MAX_BYTES",
115
+ "DS_INDEX_COMPACTION_FANOUT",
116
+ "DS_INDEX_MAX_LEVEL",
117
+ "DS_INDEX_COMPACT_CONCURRENCY",
118
+ "DS_INDEX_RETIRE_GEN_WINDOW",
119
+ "DS_INDEX_RETIRE_MIN_MS",
120
+ "DS_READ_MAX_BYTES",
121
+ "DS_READ_MAX_RECORDS",
122
+ "DS_APPEND_MAX_BODY_BYTES",
123
+ "DS_INGEST_FLUSH_MS",
124
+ "DS_INGEST_MAX_BATCH_REQS",
125
+ "DS_INGEST_MAX_BATCH_BYTES",
126
+ "DS_INGEST_MAX_QUEUE_REQS",
127
+ "DS_INGEST_MAX_QUEUE_BYTES",
128
+ "DS_INGEST_CONCURRENCY",
129
+ "DS_INGEST_BUSY_MS",
130
+ "DS_LOCAL_BACKLOG_MAX_BYTES",
131
+ "DS_MEMORY_LIMIT_BYTES",
132
+ "DS_MEMORY_LIMIT_MB",
133
+ "DS_SQLITE_CACHE_BYTES",
134
+ "DS_SQLITE_CACHE_MB",
135
+ "DS_WORKER_SQLITE_CACHE_BYTES",
136
+ "DS_WORKER_SQLITE_CACHE_MB",
137
+ "DS_READ_CONCURRENCY",
138
+ "DS_SEARCH_CONCURRENCY",
139
+ "DS_ASYNC_INDEX_CONCURRENCY",
140
+ "DS_HEAP_SNAPSHOT_PATH",
141
+ "DS_MEMORY_SAMPLER_PATH",
142
+ "DS_MEMORY_SAMPLER_INTERVAL_MS",
143
+ "DS_OBJECTSTORE_TIMEOUT_MS",
144
+ "DS_OBJECTSTORE_RETRIES",
145
+ "DS_OBJECTSTORE_RETRY_BASE_MS",
146
+ "DS_OBJECTSTORE_RETRY_MAX_MS",
147
+ "DS_LOCAL_DATA_ROOT",
148
+ "DS_EXPIRY_SWEEP_MS",
149
+ "DS_EXPIRY_SWEEP_LIMIT",
150
+ "DS_METRICS_FLUSH_MS",
151
+ "DS_TOUCH_WORKERS",
152
+ "DS_TOUCH_CHECK_MS",
153
+ "DS_TOUCH_MAX_BATCH_ROWS",
154
+ "DS_TOUCH_MAX_BATCH_BYTES",
155
+ "DS_OTLP_TRACES_STREAM",
156
+ "DS_OTLP_AUTO_CREATE",
157
+ "DS_AUTO_TUNE_REQUESTED_MB",
158
+ "DS_AUTO_TUNE_PRESET_MB",
159
+ "DS_AUTO_TUNE_EFFECTIVE_MEMORY_LIMIT_MB",
160
+ "DS_STATS_INTERVAL_MS",
161
+ "DS_BACKPRESSURE_BUDGET_MS",
162
+ "DS_MOCK_R2_MAX_INMEM_BYTES",
163
+ "DS_MOCK_R2_MAX_INMEM_MB",
164
+ "DS_MOCK_R2_SPILL_DIR",
165
+ "DS_MOCK_R2_PUT_DELAY_MS",
166
+ "DS_MOCK_R2_GET_DELAY_MS",
167
+ "DS_MOCK_R2_HEAD_DELAY_MS",
168
+ "DS_MOCK_R2_LIST_DELAY_MS",
169
+ "DS_BENCH_URL",
170
+ "DS_BENCH_DURATION_MS",
171
+ "DS_BENCH_INTERVAL_MS",
172
+ "DS_BENCH_PAYLOAD_BYTES",
173
+ "DS_BENCH_CONCURRENCY",
174
+ "DS_BENCH_REQUEST_TIMEOUT_MS",
175
+ "DS_BENCH_DRAIN_TIMEOUT_MS",
176
+ "DS_BENCH_PAUSE_BACKGROUND",
177
+ "DS_BENCH_YIELD_EVERY",
178
+ "DS_BENCH_DEBUG",
179
+ "DS_BENCH_SCENARIOS",
180
+ "DS_MEMORY_STRESS_LIMITS_MB",
181
+ "DS_MEMORY_STRESS_STATS_MS",
182
+ "DS_MEMORY_STRESS_PORT_BASE",
183
+ "DS_RK_EVENTS_MAX",
184
+ "DS_RK_EVENTS_STEP",
185
+ "DS_RK_PAYLOAD_BYTES",
186
+ "DS_RK_APPEND_BATCH",
187
+ "DS_RK_KEYS",
188
+ "DS_RK_HOT_KEYS",
189
+ "DS_RK_HOT_PCT",
190
+ "DS_RK_PAYLOAD_POOL",
191
+ "DS_RK_READ_ENTRIES",
192
+ "DS_RK_WARM_READS",
193
+ "DS_RK_SEGMENT_BYTES",
194
+ "DS_RK_BLOCK_BYTES",
195
+ "DS_RK_SEED",
196
+ "DS_RK_R2_GET_DELAY_MS",
197
+ "DS_LARGE_INDEX_FILTER",
198
+ "DS_LARGE_INDEX_FILTER_TOTAL_BYTES",
199
+ "DS_LARGE_INDEX_FILTER_PAYLOAD_BYTES",
200
+ "DS_LARGE_INDEX_FILTER_BATCH_ROWS",
201
+ "DS_LARGE_INDEX_FILTER_SEGMENT_BYTES",
202
+ "DS_LARGE_INDEX_FILTER_INDEX_SPAN",
203
+ "DS_LARGE_INDEX_FILTER_TIMEOUT_MS",
204
+ "DS_LARGE_INDEX_FILTER_R2_MAX_INMEM_BYTES",
205
+ ]);
206
+
207
+ let warnedUnknownEnv = false;
208
+
209
+ function warnUnknownEnv(): void {
210
+ if (warnedUnknownEnv) return;
211
+ warnedUnknownEnv = true;
212
+ const unknown: string[] = [];
213
+ for (const key of Object.keys(process.env)) {
214
+ if (!key.startsWith("DS_")) continue;
215
+ if (KNOWN_DS_ENVS.has(key)) continue;
216
+ unknown.push(key);
217
+ }
218
+ if (unknown.length > 0) {
219
+ unknown.sort();
220
+ console.warn(`[config] unknown DS_* environment variables: ${unknown.join(", ")}`);
221
+ }
222
+ }
223
+
224
+ function envNum(name: string, def: number): number {
225
+ const v = process.env[name];
226
+ if (!v) return def;
227
+ const n = Number(v);
228
+ if (!Number.isFinite(n)) throw dsError(`invalid ${name}: ${v}`);
229
+ return n;
230
+ }
231
+
232
+ function envBool(name: string, def: boolean): boolean {
233
+ const v = process.env[name];
234
+ if (v == null || v === "") return def;
235
+ const normalized = v.trim().toLowerCase();
236
+ if (normalized === "1" || normalized === "true" || normalized === "yes") return true;
237
+ if (normalized === "0" || normalized === "false" || normalized === "no") return false;
238
+ throw dsError(`invalid ${name}: ${v}`);
239
+ }
240
+
241
+ function envBytes(name: string): number | null {
242
+ const v = process.env[name];
243
+ if (!v) return null;
244
+ const n = Number(v);
245
+ if (!Number.isFinite(n)) throw dsError(`invalid ${name}: ${v}`);
246
+ return Math.max(0, Math.floor(n));
247
+ }
248
+
249
+ function clampBytes(value: number, min: number, max: number): number {
250
+ if (!Number.isFinite(value)) return min;
251
+ if (value < min) return min;
252
+ if (value > max) return max;
253
+ return value;
254
+ }
255
+
256
+ function parseStorageMode(): "sqlite" | "postgres" {
257
+ const raw = process.env.DS_STORAGE?.trim().toLowerCase();
258
+ if (raw == null || raw === "" || raw === "sqlite") return "sqlite";
259
+ if (raw === "postgres") return "postgres";
260
+ throw dsError(`invalid DS_STORAGE: ${process.env.DS_STORAGE}`);
261
+ }
262
+
263
+ function parsePostgresMode(): "wal" | "full" {
264
+ const raw = process.env.DS_POSTGRES_MODE?.trim().toLowerCase();
265
+ if (raw == null || raw === "" || raw === "wal") return "wal";
266
+ if (raw === "full") return "full";
267
+ throw dsError(`invalid DS_POSTGRES_MODE: ${process.env.DS_POSTGRES_MODE}`);
268
+ }
269
+
270
+ export function loadConfig(): Config {
271
+ warnUnknownEnv();
272
+ const storage = parseStorageMode();
273
+ const postgresMode = storage === "postgres" ? parsePostgresMode() : "wal";
274
+ const postgresUrl = process.env.DS_POSTGRES_URL?.trim() || null;
275
+ if (storage === "postgres" && postgresUrl == null) throw dsError("DS_POSTGRES_URL is required when DS_STORAGE=postgres");
276
+ const rootDir = process.env.DS_ROOT ?? "./ds-data";
277
+ const host = process.env.DS_HOST?.trim() || "127.0.0.1";
278
+ const autoTuneRequestedMemoryMb = envBytes("DS_AUTO_TUNE_REQUESTED_MB");
279
+ const autoTunePresetMb = envBytes("DS_AUTO_TUNE_PRESET_MB");
280
+ const autoTuneEffectiveMemoryLimitMb = envBytes("DS_AUTO_TUNE_EFFECTIVE_MEMORY_LIMIT_MB");
281
+ const bytesOverride = envBytes("DS_MEMORY_LIMIT_BYTES");
282
+ const mbOverride = envBytes("DS_MEMORY_LIMIT_MB");
283
+ const memoryLimitBytes = bytesOverride ?? (mbOverride != null ? mbOverride * 1024 * 1024 : 0);
284
+ const backlogOverride = envBytes("DS_LOCAL_BACKLOG_MAX_BYTES");
285
+ const sqliteCacheBytesOverride = envBytes("DS_SQLITE_CACHE_BYTES");
286
+ const sqliteCacheMbOverride = envBytes("DS_SQLITE_CACHE_MB");
287
+ const workerSqliteCacheBytesOverride = envBytes("DS_WORKER_SQLITE_CACHE_BYTES");
288
+ const workerSqliteCacheMbOverride = envBytes("DS_WORKER_SQLITE_CACHE_MB");
289
+ const indexMemOverride = envBytes("DS_INDEX_RUN_MEM_CACHE_BYTES");
290
+ const indexDiskOverride = envBytes("DS_INDEX_RUN_CACHE_MAX_BYTES");
291
+ const lexiconDiskOverride = envBytes("DS_LEXICON_INDEX_CACHE_MAX_BYTES");
292
+ const localBacklogMaxBytes = backlogOverride ?? 10 * 1024 * 1024 * 1024;
293
+ const sqliteCacheBytes =
294
+ sqliteCacheBytesOverride ??
295
+ (sqliteCacheMbOverride != null
296
+ ? sqliteCacheMbOverride * 1024 * 1024
297
+ : memoryLimitBytes > 0
298
+ ? Math.floor(memoryLimitBytes * 0.25)
299
+ : 0);
300
+ const workerSqliteCacheBytes =
301
+ workerSqliteCacheBytesOverride ??
302
+ (workerSqliteCacheMbOverride != null
303
+ ? workerSqliteCacheMbOverride * 1024 * 1024
304
+ : sqliteCacheBytes > 0
305
+ ? clampBytes(Math.floor(sqliteCacheBytes / 8), 8 * 1024 * 1024, 32 * 1024 * 1024)
306
+ : 0);
307
+ const tunedIndexMem =
308
+ indexMemOverride ??
309
+ (memoryLimitBytes > 0
310
+ ? clampBytes(Math.floor(memoryLimitBytes * 0.05), 8 * 1024 * 1024, 128 * 1024 * 1024)
311
+ : 64 * 1024 * 1024);
312
+ const companionSectionCacheBytes =
313
+ envBytes("DS_SEARCH_COMPANION_SECTION_CACHE_BYTES") ??
314
+ (memoryLimitBytes > 0
315
+ ? clampBytes(Math.floor(memoryLimitBytes * 0.02), 8 * 1024 * 1024, 128 * 1024 * 1024)
316
+ : 32 * 1024 * 1024);
317
+ const companionFileCacheBytes =
318
+ envBytes("DS_SEARCH_COMPANION_FILE_CACHE_MAX_BYTES") ??
319
+ clampBytes(Math.max(512 * 1024 * 1024, Math.floor(localBacklogMaxBytes * 0.1)), 256 * 1024 * 1024, 4 * 1024 * 1024 * 1024);
320
+ const segmentMaxBytes = envNum("DS_SEGMENT_MAX_BYTES", 16 * 1024 * 1024);
321
+ const searchWalOverlayMaxBytes = envBytes("DS_SEARCH_WAL_OVERLAY_MAX_BYTES") ?? segmentMaxBytes;
322
+ return {
323
+ autoTuneRequestedMemoryMb,
324
+ autoTunePresetMb,
325
+ autoTuneEffectiveMemoryLimitMb,
326
+ storage,
327
+ postgresMode,
328
+ postgresUrl,
329
+ host,
330
+ rootDir,
331
+ dbPath: process.env.DS_DB_PATH ?? `${rootDir}/wal.sqlite`,
332
+ segmentMaxBytes,
333
+ blockMaxBytes: envNum("DS_BLOCK_MAX_BYTES", 256 * 1024),
334
+ segmentTargetRows: envNum("DS_SEGMENT_TARGET_ROWS", 100_000),
335
+ segmentMaxIntervalMs: envNum("DS_SEGMENT_MAX_INTERVAL_MS", 0),
336
+ segmentCheckIntervalMs: envNum("DS_SEGMENT_CHECK_MS", 250),
337
+ segmenterWorkers: envNum("DS_SEGMENTER_WORKERS", 0),
338
+ uploadIntervalMs: envNum("DS_UPLOAD_CHECK_MS", 250),
339
+ uploadConcurrency: envNum("DS_UPLOAD_CONCURRENCY", 4),
340
+ segmentCacheMaxBytes: envNum("DS_SEGMENT_CACHE_MAX_BYTES", 256 * 1024 * 1024),
341
+ segmentFooterCacheEntries: envNum("DS_SEGMENT_FOOTER_CACHE_ENTRIES", 2048),
342
+ indexRunCacheMaxBytes: indexDiskOverride ?? 256 * 1024 * 1024,
343
+ indexRunMemoryCacheBytes: tunedIndexMem,
344
+ lexiconIndexCacheMaxBytes:
345
+ lexiconDiskOverride ??
346
+ (memoryLimitBytes > 0
347
+ ? clampBytes(Math.floor(memoryLimitBytes * 0.03), 8 * 1024 * 1024, 256 * 1024 * 1024)
348
+ : 64 * 1024 * 1024),
349
+ lexiconMappedCacheEntries: envNum("DS_LEXICON_MMAP_CACHE_ENTRIES", 64),
350
+ indexL0SpanSegments: envNum("DS_INDEX_L0_SPAN", 16),
351
+ indexBuildConcurrency: envNum("DS_INDEX_BUILD_CONCURRENCY", 4),
352
+ indexCheckIntervalMs: envNum("DS_INDEX_CHECK_MS", 1000),
353
+ searchCompanionBuildBatchSegments: envNum("DS_SEARCH_COMPANION_BATCH_SEGMENTS", 4),
354
+ searchCompanionYieldBlocks: envNum("DS_SEARCH_COMPANION_YIELD_BLOCKS", 4),
355
+ searchCompanionFileCacheMaxBytes: companionFileCacheBytes,
356
+ searchCompanionFileCacheMaxAgeMs: envNum("DS_SEARCH_COMPANION_FILE_CACHE_MAX_AGE_MS", 24 * 60 * 60 * 1000),
357
+ searchCompanionMappedCacheEntries: envNum("DS_SEARCH_COMPANION_MMAP_CACHE_ENTRIES", 64),
358
+ searchCompanionTocCacheBytes: envNum("DS_SEARCH_COMPANION_TOC_CACHE_BYTES", 1 * 1024 * 1024),
359
+ searchCompanionSectionCacheBytes: companionSectionCacheBytes,
360
+ searchWalOverlayQuietPeriodMs: envNum("DS_SEARCH_WAL_OVERLAY_QUIET_MS", 5_000),
361
+ searchWalOverlayMaxBytes,
362
+ indexCompactionFanout: envNum("DS_INDEX_COMPACTION_FANOUT", 16),
363
+ indexMaxLevel: envNum("DS_INDEX_MAX_LEVEL", 4),
364
+ indexCompactionConcurrency: envNum("DS_INDEX_COMPACT_CONCURRENCY", 4),
365
+ indexRetireGenWindow: envNum("DS_INDEX_RETIRE_GEN_WINDOW", 2),
366
+ indexRetireMinMs: envNum("DS_INDEX_RETIRE_MIN_MS", 5 * 60 * 1000),
367
+ readMaxBytes: envNum("DS_READ_MAX_BYTES", 1 * 1024 * 1024),
368
+ readMaxRecords: envNum("DS_READ_MAX_RECORDS", 1000),
369
+ appendMaxBodyBytes: envNum("DS_APPEND_MAX_BODY_BYTES", 10 * 1024 * 1024),
370
+ ingestFlushIntervalMs: envNum("DS_INGEST_FLUSH_MS", 10),
371
+ ingestMaxBatchRequests: envNum("DS_INGEST_MAX_BATCH_REQS", 200),
372
+ ingestMaxBatchBytes: envNum("DS_INGEST_MAX_BATCH_BYTES", 8 * 1024 * 1024),
373
+ ingestMaxQueueRequests: envNum("DS_INGEST_MAX_QUEUE_REQS", 50_000),
374
+ ingestMaxQueueBytes: envNum("DS_INGEST_MAX_QUEUE_BYTES", 64 * 1024 * 1024),
375
+ ingestConcurrency: envNum("DS_INGEST_CONCURRENCY", 2),
376
+ ingestBusyTimeoutMs: envNum("DS_INGEST_BUSY_MS", 5000),
377
+ localBacklogMaxBytes,
378
+ memoryLimitBytes,
379
+ sqliteCacheBytes,
380
+ workerSqliteCacheBytes,
381
+ readConcurrency: envNum("DS_READ_CONCURRENCY", 4),
382
+ searchConcurrency: envNum("DS_SEARCH_CONCURRENCY", 2),
383
+ asyncIndexConcurrency: envNum("DS_ASYNC_INDEX_CONCURRENCY", 1),
384
+ heapSnapshotPath: process.env.DS_HEAP_SNAPSHOT_PATH?.trim() || null,
385
+ memorySamplerPath: process.env.DS_MEMORY_SAMPLER_PATH?.trim() || null,
386
+ memorySamplerIntervalMs: envNum("DS_MEMORY_SAMPLER_INTERVAL_MS", 1_000),
387
+ objectStoreTimeoutMs: envNum("DS_OBJECTSTORE_TIMEOUT_MS", 5000),
388
+ objectStoreRetries: envNum("DS_OBJECTSTORE_RETRIES", 3),
389
+ objectStoreBaseDelayMs: envNum("DS_OBJECTSTORE_RETRY_BASE_MS", 50),
390
+ objectStoreMaxDelayMs: envNum("DS_OBJECTSTORE_RETRY_MAX_MS", 2000),
391
+ expirySweepIntervalMs: envNum("DS_EXPIRY_SWEEP_MS", 60_000),
392
+ expirySweepBatchLimit: envNum("DS_EXPIRY_SWEEP_LIMIT", 100),
393
+ metricsFlushIntervalMs: envNum("DS_METRICS_FLUSH_MS", 10_000),
394
+ touchWorkers: envNum("DS_TOUCH_WORKERS", 1),
395
+ touchCheckIntervalMs: envNum("DS_TOUCH_CHECK_MS", 250),
396
+ touchMaxBatchRows: envNum("DS_TOUCH_MAX_BATCH_ROWS", 500),
397
+ touchMaxBatchBytes: envNum("DS_TOUCH_MAX_BATCH_BYTES", 4 * 1024 * 1024),
398
+ otlpTracesStream: process.env.DS_OTLP_TRACES_STREAM?.trim() || null,
399
+ otlpAutoCreate: envBool("DS_OTLP_AUTO_CREATE", false),
400
+ port: envNum("PORT", 8080),
401
+ };
402
+ }
@@ -0,0 +1,9 @@
1
+ import { SqliteDurableStore } from "./db";
2
+ import type { BootstrapRestoreStore } from "../store/bootstrap_restore_store";
3
+
4
+ export function createSqliteBootstrapRestoreStore(
5
+ dbPath: string,
6
+ opts: { cacheBytes?: number } = {}
7
+ ): BootstrapRestoreStore {
8
+ return new SqliteDurableStore(dbPath, opts);
9
+ }