@prisma/streams-server 0.1.1 → 0.1.3

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 (91) hide show
  1. package/CONTRIBUTING.md +8 -0
  2. package/package.json +2 -1
  3. package/src/app.ts +290 -17
  4. package/src/app_core.ts +1833 -698
  5. package/src/app_local.ts +144 -4
  6. package/src/auto_tune.ts +62 -0
  7. package/src/bootstrap.ts +159 -1
  8. package/src/concurrency_gate.ts +108 -0
  9. package/src/config.ts +116 -14
  10. package/src/db/db.ts +1201 -131
  11. package/src/db/schema.ts +308 -8
  12. package/src/foreground_activity.ts +55 -0
  13. package/src/index/indexer.ts +254 -124
  14. package/src/index/lexicon_file_cache.ts +261 -0
  15. package/src/index/lexicon_format.ts +93 -0
  16. package/src/index/lexicon_indexer.ts +789 -0
  17. package/src/index/secondary_indexer.ts +824 -0
  18. package/src/index/secondary_schema.ts +105 -0
  19. package/src/ingest.ts +10 -12
  20. package/src/manifest.ts +143 -8
  21. package/src/memory.ts +183 -8
  22. package/src/metrics.ts +15 -29
  23. package/src/metrics_emitter.ts +26 -3
  24. package/src/notifier.ts +121 -5
  25. package/src/objectstore/accounting.ts +92 -0
  26. package/src/objectstore/mock_r2.ts +1 -1
  27. package/src/objectstore/r2.ts +17 -1
  28. package/src/profiles/evlog/schema.ts +234 -0
  29. package/src/profiles/evlog.ts +299 -0
  30. package/src/profiles/generic.ts +47 -0
  31. package/src/profiles/index.ts +205 -0
  32. package/src/profiles/metrics/block_format.ts +109 -0
  33. package/src/profiles/metrics/normalize.ts +366 -0
  34. package/src/profiles/metrics/schema.ts +319 -0
  35. package/src/profiles/metrics.ts +85 -0
  36. package/src/profiles/profile.ts +225 -0
  37. package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
  38. package/src/profiles/stateProtocol/routes.ts +389 -0
  39. package/src/profiles/stateProtocol/types.ts +6 -0
  40. package/src/profiles/stateProtocol/validation.ts +51 -0
  41. package/src/profiles/stateProtocol.ts +100 -0
  42. package/src/read_filter.ts +468 -0
  43. package/src/reader.ts +2151 -164
  44. package/src/runtime/host_runtime.ts +5 -0
  45. package/src/runtime_memory.ts +200 -0
  46. package/src/runtime_memory_sampler.ts +235 -0
  47. package/src/schema/read_json.ts +43 -0
  48. package/src/schema/registry.ts +563 -59
  49. package/src/search/agg_format.ts +638 -0
  50. package/src/search/aggregate.ts +389 -0
  51. package/src/search/binary/codec.ts +162 -0
  52. package/src/search/binary/docset.ts +67 -0
  53. package/src/search/binary/restart_strings.ts +181 -0
  54. package/src/search/binary/varint.ts +34 -0
  55. package/src/search/bitset.ts +19 -0
  56. package/src/search/col_format.ts +382 -0
  57. package/src/search/col_runtime.ts +59 -0
  58. package/src/search/column_encoding.ts +43 -0
  59. package/src/search/companion_file_cache.ts +319 -0
  60. package/src/search/companion_format.ts +313 -0
  61. package/src/search/companion_manager.ts +1086 -0
  62. package/src/search/companion_plan.ts +218 -0
  63. package/src/search/fts_format.ts +423 -0
  64. package/src/search/fts_runtime.ts +333 -0
  65. package/src/search/query.ts +875 -0
  66. package/src/search/schema.ts +245 -0
  67. package/src/segment/cache.ts +93 -2
  68. package/src/segment/cached_segment.ts +89 -0
  69. package/src/segment/format.ts +108 -36
  70. package/src/segment/segmenter.ts +79 -5
  71. package/src/segment/segmenter_worker.ts +35 -6
  72. package/src/segment/segmenter_workers.ts +42 -12
  73. package/src/server.ts +150 -36
  74. package/src/sqlite/adapter.ts +185 -14
  75. package/src/sqlite/runtime_stats.ts +163 -0
  76. package/src/stats.ts +3 -3
  77. package/src/stream_size_reconciler.ts +100 -0
  78. package/src/touch/canonical_change.ts +7 -0
  79. package/src/touch/live_metrics.ts +94 -64
  80. package/src/touch/live_templates.ts +15 -1
  81. package/src/touch/manager.ts +166 -88
  82. package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
  83. package/src/touch/spec.ts +95 -92
  84. package/src/touch/touch_journal.ts +4 -0
  85. package/src/touch/worker_pool.ts +8 -14
  86. package/src/touch/worker_protocol.ts +3 -3
  87. package/src/uploader.ts +77 -6
  88. package/src/util/bloom256.ts +2 -2
  89. package/src/util/byte_lru.ts +73 -0
  90. package/src/util/lru.ts +8 -0
  91. package/src/util/stream_paths.ts +19 -0
package/src/db/schema.ts CHANGED
@@ -9,7 +9,7 @@ import { dsError } from "../util/ds_error.ts";
9
9
  * - local metadata store (streams/segments/manifests/schemas)
10
10
  */
11
11
 
12
- export const SCHEMA_VERSION = 11;
12
+ export const SCHEMA_VERSION = 24;
13
13
 
14
14
  export const DEFAULT_PRAGMAS_SQL = `
15
15
  PRAGMA journal_mode = WAL;
@@ -26,6 +26,7 @@ CREATE TABLE IF NOT EXISTS streams (
26
26
  updated_at_ms INTEGER NOT NULL,
27
27
 
28
28
  content_type TEXT NOT NULL,
29
+ profile TEXT NULL,
29
30
  stream_seq TEXT NULL,
30
31
  closed INTEGER NOT NULL DEFAULT 0,
31
32
  closed_producer_id TEXT NULL,
@@ -42,6 +43,11 @@ CREATE TABLE IF NOT EXISTS streams (
42
43
  pending_rows INTEGER NOT NULL,
43
44
  pending_bytes INTEGER NOT NULL,
44
45
 
46
+ -- Logical payload bytes ever appended to this stream and still part of its
47
+ -- visible history on this node. This is the constant-time source for
48
+ -- management endpoints such as /_details.
49
+ logical_size_bytes INTEGER NOT NULL DEFAULT 0,
50
+
45
51
  -- Logical size of retained rows in the wal table for this stream (payload-only bytes).
46
52
  -- This is explicitly tracked because SQLite file size is high-water and does not shrink
47
53
  -- deterministically after DELETE-based GC/retention trimming.
@@ -73,7 +79,6 @@ CREATE TABLE IF NOT EXISTS wal (
73
79
  );
74
80
 
75
81
  CREATE UNIQUE INDEX IF NOT EXISTS wal_stream_offset_uniq ON wal(stream, offset);
76
- CREATE INDEX IF NOT EXISTS wal_stream_offset_idx ON wal(stream, offset);
77
82
  CREATE INDEX IF NOT EXISTS wal_ts_idx ON wal(ts_ms);
78
83
 
79
84
  CREATE TABLE IF NOT EXISTS segments (
@@ -84,6 +89,7 @@ CREATE TABLE IF NOT EXISTS segments (
84
89
  end_offset INTEGER NOT NULL,
85
90
  block_count INTEGER NOT NULL,
86
91
  last_append_ms INTEGER NOT NULL,
92
+ payload_bytes INTEGER NOT NULL DEFAULT 0,
87
93
  size_bytes INTEGER NOT NULL,
88
94
  local_path TEXT NOT NULL,
89
95
  created_at_ms INTEGER NOT NULL,
@@ -108,12 +114,20 @@ CREATE TABLE IF NOT EXISTS manifests (
108
114
  generation INTEGER NOT NULL,
109
115
  uploaded_generation INTEGER NOT NULL,
110
116
  last_uploaded_at_ms INTEGER NULL,
111
- last_uploaded_etag TEXT NULL
117
+ last_uploaded_etag TEXT NULL,
118
+ last_uploaded_size_bytes INTEGER NULL
112
119
  );
113
120
 
114
121
  CREATE TABLE IF NOT EXISTS schemas (
115
122
  stream TEXT PRIMARY KEY,
116
123
  schema_json TEXT NOT NULL,
124
+ updated_at_ms INTEGER NOT NULL,
125
+ uploaded_size_bytes INTEGER NOT NULL DEFAULT 0
126
+ );
127
+
128
+ CREATE TABLE IF NOT EXISTS stream_profiles (
129
+ stream TEXT PRIMARY KEY,
130
+ profile_json TEXT NOT NULL,
117
131
  updated_at_ms INTEGER NOT NULL
118
132
  );
119
133
 
@@ -126,9 +140,9 @@ CREATE TABLE IF NOT EXISTS producer_state (
126
140
  PRIMARY KEY (stream, producer_id)
127
141
  );
128
142
 
129
- CREATE TABLE IF NOT EXISTS stream_interpreters (
143
+ CREATE TABLE IF NOT EXISTS stream_touch_state (
130
144
  stream TEXT PRIMARY KEY,
131
- interpreted_through INTEGER NOT NULL,
145
+ processed_through INTEGER NOT NULL,
132
146
  updated_at_ms INTEGER NOT NULL
133
147
  );
134
148
 
@@ -170,6 +184,7 @@ CREATE TABLE IF NOT EXISTS index_runs (
170
184
  start_segment INTEGER NOT NULL,
171
185
  end_segment INTEGER NOT NULL,
172
186
  object_key TEXT NOT NULL,
187
+ size_bytes INTEGER NOT NULL DEFAULT 0,
173
188
  filter_len INTEGER NOT NULL,
174
189
  record_count INTEGER NOT NULL,
175
190
  retired_gen INTEGER NULL,
@@ -179,6 +194,107 @@ CREATE TABLE IF NOT EXISTS index_runs (
179
194
  CREATE INDEX IF NOT EXISTS index_runs_stream_idx ON index_runs(stream, level, start_segment);
180
195
  `;
181
196
 
197
+ const CREATE_SECONDARY_INDEX_TABLES_SQL = `
198
+ CREATE TABLE IF NOT EXISTS secondary_index_state (
199
+ stream TEXT NOT NULL,
200
+ index_name TEXT NOT NULL,
201
+ index_secret BLOB NOT NULL,
202
+ config_hash TEXT NOT NULL,
203
+ indexed_through INTEGER NOT NULL,
204
+ updated_at_ms INTEGER NOT NULL,
205
+ PRIMARY KEY (stream, index_name)
206
+ );
207
+
208
+ CREATE TABLE IF NOT EXISTS secondary_index_runs (
209
+ run_id TEXT PRIMARY KEY,
210
+ stream TEXT NOT NULL,
211
+ index_name TEXT NOT NULL,
212
+ level INTEGER NOT NULL,
213
+ start_segment INTEGER NOT NULL,
214
+ end_segment INTEGER NOT NULL,
215
+ object_key TEXT NOT NULL,
216
+ size_bytes INTEGER NOT NULL DEFAULT 0,
217
+ filter_len INTEGER NOT NULL,
218
+ record_count INTEGER NOT NULL,
219
+ retired_gen INTEGER NULL,
220
+ retired_at_ms INTEGER NULL
221
+ );
222
+
223
+ CREATE INDEX IF NOT EXISTS secondary_index_runs_stream_idx
224
+ ON secondary_index_runs(stream, index_name, level, start_segment);
225
+ `;
226
+
227
+ const CREATE_LEXICON_INDEX_TABLES_SQL = `
228
+ CREATE TABLE IF NOT EXISTS lexicon_index_state (
229
+ stream TEXT NOT NULL,
230
+ source_kind TEXT NOT NULL,
231
+ source_name TEXT NOT NULL,
232
+ indexed_through INTEGER NOT NULL,
233
+ updated_at_ms INTEGER NOT NULL,
234
+ PRIMARY KEY (stream, source_kind, source_name)
235
+ );
236
+
237
+ CREATE TABLE IF NOT EXISTS lexicon_index_runs (
238
+ run_id TEXT PRIMARY KEY,
239
+ stream TEXT NOT NULL,
240
+ source_kind TEXT NOT NULL,
241
+ source_name TEXT NOT NULL,
242
+ level INTEGER NOT NULL,
243
+ start_segment INTEGER NOT NULL,
244
+ end_segment INTEGER NOT NULL,
245
+ object_key TEXT NOT NULL,
246
+ size_bytes INTEGER NOT NULL DEFAULT 0,
247
+ record_count INTEGER NOT NULL,
248
+ retired_gen INTEGER NULL,
249
+ retired_at_ms INTEGER NULL
250
+ );
251
+
252
+ CREATE INDEX IF NOT EXISTS lexicon_index_runs_stream_idx
253
+ ON lexicon_index_runs(stream, source_kind, source_name, level, start_segment);
254
+ `;
255
+
256
+ const CREATE_SEARCH_COMPANION_TABLES_SQL = `
257
+ CREATE TABLE IF NOT EXISTS search_companion_plans (
258
+ stream TEXT PRIMARY KEY,
259
+ generation INTEGER NOT NULL,
260
+ plan_hash TEXT NOT NULL,
261
+ plan_json TEXT NOT NULL,
262
+ updated_at_ms INTEGER NOT NULL
263
+ );
264
+
265
+ CREATE TABLE IF NOT EXISTS search_segment_companions (
266
+ stream TEXT NOT NULL,
267
+ segment_index INTEGER NOT NULL,
268
+ object_key TEXT NOT NULL,
269
+ plan_generation INTEGER NOT NULL,
270
+ sections_json TEXT NOT NULL,
271
+ section_sizes_json TEXT NOT NULL DEFAULT '{}',
272
+ size_bytes INTEGER NOT NULL DEFAULT 0,
273
+ primary_timestamp_min_ms INTEGER NULL,
274
+ primary_timestamp_max_ms INTEGER NULL,
275
+ updated_at_ms INTEGER NOT NULL,
276
+ PRIMARY KEY (stream, segment_index)
277
+ );
278
+
279
+ CREATE INDEX IF NOT EXISTS search_segment_companions_stream_plan_idx
280
+ ON search_segment_companions(stream, plan_generation, segment_index);
281
+ `;
282
+
283
+ const CREATE_OBJECTSTORE_REQUEST_TABLES_SQL = `
284
+ CREATE TABLE IF NOT EXISTS objectstore_request_counts (
285
+ stream_hash TEXT NOT NULL,
286
+ artifact TEXT NOT NULL,
287
+ op TEXT NOT NULL,
288
+ count INTEGER NOT NULL DEFAULT 0,
289
+ bytes INTEGER NOT NULL DEFAULT 0,
290
+ updated_at_ms INTEGER NOT NULL,
291
+ PRIMARY KEY (stream_hash, artifact, op)
292
+ );
293
+
294
+ CREATE INDEX IF NOT EXISTS objectstore_request_counts_stream_hash_idx
295
+ ON objectstore_request_counts(stream_hash, updated_at_ms);
296
+ `;
297
+
182
298
  const CREATE_TABLES_V4_SUFFIX_SQL = (suffix: string): string => `
183
299
  CREATE TABLE streams_${suffix} (
184
300
  stream TEXT PRIMARY KEY,
@@ -201,6 +317,7 @@ CREATE TABLE streams_${suffix} (
201
317
 
202
318
  pending_rows INTEGER NOT NULL,
203
319
  pending_bytes INTEGER NOT NULL,
320
+ logical_size_bytes INTEGER NOT NULL DEFAULT 0,
204
321
 
205
322
  last_append_ms INTEGER NOT NULL,
206
323
  last_segment_cut_ms INTEGER NOT NULL,
@@ -263,7 +380,6 @@ CREATE TABLE producer_state_${suffix} (
263
380
 
264
381
  const CREATE_INDEXES_V4_SQL = `
265
382
  CREATE UNIQUE INDEX IF NOT EXISTS wal_stream_offset_uniq ON wal(stream, offset);
266
- CREATE INDEX IF NOT EXISTS wal_stream_offset_idx ON wal(stream, offset);
267
383
  CREATE INDEX IF NOT EXISTS wal_ts_idx ON wal(ts_ms);
268
384
 
269
385
  CREATE INDEX IF NOT EXISTS streams_pending_bytes_idx ON streams(pending_bytes);
@@ -297,6 +413,10 @@ export function initSchema(db: SqliteDatabase, opts: { skipMigrations?: boolean
297
413
  if (version0 == null) {
298
414
  db.exec(CREATE_TABLES_V4_SQL);
299
415
  db.exec(CREATE_INDEX_TABLES_SQL);
416
+ db.exec(CREATE_SECONDARY_INDEX_TABLES_SQL);
417
+ db.exec(CREATE_LEXICON_INDEX_TABLES_SQL);
418
+ db.exec(CREATE_SEARCH_COMPANION_TABLES_SQL);
419
+ db.exec(CREATE_OBJECTSTORE_REQUEST_TABLES_SQL);
300
420
  db.query("INSERT INTO schema_version(version) VALUES (?);").run(SCHEMA_VERSION);
301
421
  return;
302
422
  }
@@ -325,6 +445,32 @@ export function initSchema(db: SqliteDatabase, opts: { skipMigrations?: boolean
325
445
  migrateV9ToV10(db);
326
446
  } else if (version === 10) {
327
447
  migrateV10ToV11(db);
448
+ } else if (version === 11) {
449
+ migrateV11ToV12(db);
450
+ } else if (version === 12) {
451
+ migrateV12ToV13(db);
452
+ } else if (version === 13) {
453
+ migrateV13ToV14(db);
454
+ } else if (version === 14) {
455
+ migrateV14ToV15(db);
456
+ } else if (version === 15) {
457
+ migrateV15ToV16(db);
458
+ } else if (version === 16) {
459
+ migrateV16ToV17(db);
460
+ } else if (version === 17) {
461
+ migrateV17ToV18(db);
462
+ } else if (version === 18) {
463
+ migrateV18ToV19(db);
464
+ } else if (version === 19) {
465
+ migrateV19ToV20(db);
466
+ } else if (version === 20) {
467
+ migrateV20ToV21(db);
468
+ } else if (version === 21) {
469
+ migrateV21ToV22(db);
470
+ } else if (version === 22) {
471
+ migrateV22ToV23(db);
472
+ } else if (version === 23) {
473
+ migrateV23ToV24(db);
328
474
  } else {
329
475
  throw dsError(`unexpected schema version: ${version} (expected ${SCHEMA_VERSION})`);
330
476
  }
@@ -541,9 +687,9 @@ function migrateV5ToV6(db: SqliteDatabase): void {
541
687
  function migrateV6ToV7(db: SqliteDatabase): void {
542
688
  const tx = db.transaction(() => {
543
689
  db.exec(`
544
- CREATE TABLE IF NOT EXISTS stream_interpreters (
690
+ CREATE TABLE IF NOT EXISTS stream_touch_state (
545
691
  stream TEXT PRIMARY KEY,
546
- interpreted_through INTEGER NOT NULL,
692
+ processed_through INTEGER NOT NULL,
547
693
  updated_at_ms INTEGER NOT NULL
548
694
  );
549
695
  `);
@@ -619,7 +765,161 @@ function migrateV9ToV10(db: SqliteDatabase): void {
619
765
  function migrateV10ToV11(db: SqliteDatabase): void {
620
766
  const tx = db.transaction(() => {
621
767
  db.exec(`DROP INDEX IF EXISTS wal_touch_stream_rk_offset_idx;`);
768
+ db.exec(`UPDATE schema_version SET version = 11;`);
769
+ });
770
+ tx();
771
+ }
772
+
773
+ function migrateV11ToV12(db: SqliteDatabase): void {
774
+ const tx = db.transaction(() => {
775
+ db.exec(`ALTER TABLE streams ADD COLUMN profile TEXT NULL;`);
776
+ db.exec(`UPDATE schema_version SET version = 12;`);
777
+ });
778
+ tx();
779
+ }
780
+
781
+ function migrateV12ToV13(db: SqliteDatabase): void {
782
+ const tx = db.transaction(() => {
783
+ db.exec(`
784
+ CREATE TABLE IF NOT EXISTS stream_profiles (
785
+ stream TEXT PRIMARY KEY,
786
+ profile_json TEXT NOT NULL,
787
+ updated_at_ms INTEGER NOT NULL
788
+ );
789
+ `);
790
+ db.exec(`UPDATE schema_version SET version = 13;`);
791
+ });
792
+ tx();
793
+ }
794
+
795
+ function migrateV13ToV14(db: SqliteDatabase): void {
796
+ const tx = db.transaction(() => {
797
+ db.exec(`
798
+ CREATE TABLE IF NOT EXISTS stream_touch_state (
799
+ stream TEXT PRIMARY KEY,
800
+ processed_through INTEGER NOT NULL,
801
+ updated_at_ms INTEGER NOT NULL
802
+ );
803
+ `);
804
+
805
+ const hasLegacy = !!db
806
+ .query(`SELECT name FROM sqlite_master WHERE type='table' AND name='stream_interpreters' LIMIT 1;`)
807
+ .get();
808
+ if (hasLegacy) {
809
+ db.exec(`
810
+ INSERT OR REPLACE INTO stream_touch_state(stream, processed_through, updated_at_ms)
811
+ SELECT stream, interpreted_through, updated_at_ms
812
+ FROM stream_interpreters;
813
+ `);
814
+ db.exec(`DROP TABLE stream_interpreters;`);
815
+ }
816
+
622
817
  db.exec(`UPDATE schema_version SET version = ${SCHEMA_VERSION};`);
623
818
  });
624
819
  tx();
625
820
  }
821
+
822
+ function migrateV14ToV15(db: SqliteDatabase): void {
823
+ const tx = db.transaction(() => {
824
+ db.exec(CREATE_SECONDARY_INDEX_TABLES_SQL);
825
+ db.exec(`UPDATE schema_version SET version = 15;`);
826
+ });
827
+ tx();
828
+ }
829
+
830
+ function migrateV15ToV16(db: SqliteDatabase): void {
831
+ const tx = db.transaction(() => {
832
+ db.exec(`UPDATE schema_version SET version = 16;`);
833
+ });
834
+ tx();
835
+ }
836
+
837
+ function migrateV16ToV17(db: SqliteDatabase): void {
838
+ const tx = db.transaction(() => {
839
+ db.exec(`ALTER TABLE streams ADD COLUMN logical_size_bytes INTEGER NOT NULL DEFAULT 0;`);
840
+
841
+ // Streams that still live entirely in the retained WAL can be backfilled
842
+ // cheaply here. Streams with published segments are repaired asynchronously
843
+ // at runtime from segment objects if this value is still missing.
844
+ db.exec(`
845
+ UPDATE streams
846
+ SET logical_size_bytes = wal_bytes
847
+ WHERE next_offset = wal_rows;
848
+ `);
849
+
850
+ db.exec(`UPDATE schema_version SET version = 17;`);
851
+ });
852
+ tx();
853
+ }
854
+
855
+ function migrateV17ToV18(db: SqliteDatabase): void {
856
+ const tx = db.transaction(() => {
857
+ db.exec(CREATE_SEARCH_COMPANION_TABLES_SQL);
858
+ db.exec(`UPDATE schema_version SET version = 18;`);
859
+ });
860
+ tx();
861
+ }
862
+
863
+ function migrateV18ToV19(db: SqliteDatabase): void {
864
+ const tx = db.transaction(() => {
865
+ db.exec(`ALTER TABLE secondary_index_state ADD COLUMN config_hash TEXT NOT NULL DEFAULT '';`);
866
+ db.exec(`DROP INDEX IF EXISTS search_family_segments_stream_idx;`);
867
+ db.exec(`DROP TABLE IF EXISTS search_family_segments;`);
868
+ db.exec(`DROP TABLE IF EXISTS search_family_state;`);
869
+ db.exec(`UPDATE schema_version SET version = 19;`);
870
+ });
871
+ tx();
872
+ }
873
+
874
+ function migrateV19ToV20(db: SqliteDatabase): void {
875
+ const tx = db.transaction(() => {
876
+ db.exec(`ALTER TABLE manifests ADD COLUMN last_uploaded_size_bytes INTEGER NULL;`);
877
+ db.exec(`ALTER TABLE schemas ADD COLUMN uploaded_size_bytes INTEGER NOT NULL DEFAULT 0;`);
878
+ db.exec(`ALTER TABLE index_runs ADD COLUMN size_bytes INTEGER NOT NULL DEFAULT 0;`);
879
+ db.exec(`ALTER TABLE secondary_index_runs ADD COLUMN size_bytes INTEGER NOT NULL DEFAULT 0;`);
880
+ db.exec(`ALTER TABLE search_segment_companions ADD COLUMN section_sizes_json TEXT NOT NULL DEFAULT '{}';`);
881
+ db.exec(`ALTER TABLE search_segment_companions ADD COLUMN size_bytes INTEGER NOT NULL DEFAULT 0;`);
882
+ db.exec(CREATE_OBJECTSTORE_REQUEST_TABLES_SQL);
883
+ db.exec(`UPDATE schema_version SET version = 20;`);
884
+ });
885
+ tx();
886
+ }
887
+
888
+ function migrateV20ToV21(db: SqliteDatabase): void {
889
+ const tx = db.transaction(() => {
890
+ db.exec(`ALTER TABLE search_segment_companions ADD COLUMN primary_timestamp_min_ms INTEGER NULL;`);
891
+ db.exec(`ALTER TABLE search_segment_companions ADD COLUMN primary_timestamp_max_ms INTEGER NULL;`);
892
+ db.exec(`UPDATE schema_version SET version = 21;`);
893
+ });
894
+ tx();
895
+ }
896
+
897
+ function migrateV21ToV22(db: SqliteDatabase): void {
898
+ const tx = db.transaction(() => {
899
+ db.exec(CREATE_LEXICON_INDEX_TABLES_SQL);
900
+ db.exec(`UPDATE schema_version SET version = 22;`);
901
+ });
902
+ tx();
903
+ }
904
+
905
+ function migrateV22ToV23(db: SqliteDatabase): void {
906
+ const tx = db.transaction(() => {
907
+ db.exec(`DROP INDEX IF EXISTS wal_stream_offset_idx;`);
908
+ db.exec(`UPDATE schema_version SET version = 23;`);
909
+ });
910
+ tx();
911
+ }
912
+
913
+ function migrateV23ToV24(db: SqliteDatabase): void {
914
+ const tx = db.transaction(() => {
915
+ const hasPayloadBytes = db
916
+ .query(`PRAGMA table_info(segments);`)
917
+ .all()
918
+ .some((row: any) => String(row.name) === "payload_bytes");
919
+ if (!hasPayloadBytes) {
920
+ db.exec(`ALTER TABLE segments ADD COLUMN payload_bytes INTEGER NOT NULL DEFAULT 0;`);
921
+ }
922
+ db.exec(`UPDATE schema_version SET version = 24;`);
923
+ });
924
+ tx();
925
+ }
@@ -0,0 +1,55 @@
1
+ import { yieldToEventLoop } from "./util/yield";
2
+
3
+ export class ForegroundActivityTracker {
4
+ private active = 0;
5
+ private lastActivityAt = 0;
6
+ private readonly idleResolvers = new Set<() => void>();
7
+
8
+ enter(): () => void {
9
+ this.lastActivityAt = Date.now();
10
+ this.active += 1;
11
+ let released = false;
12
+ return () => {
13
+ if (released) return;
14
+ released = true;
15
+ this.lastActivityAt = Date.now();
16
+ this.active = Math.max(0, this.active - 1);
17
+ if (this.active !== 0) return;
18
+ const resolvers = Array.from(this.idleResolvers);
19
+ this.idleResolvers.clear();
20
+ for (const resolve of resolvers) resolve();
21
+ };
22
+ }
23
+
24
+ isActive(): boolean {
25
+ return this.active > 0;
26
+ }
27
+
28
+ getActive(): number {
29
+ return this.active;
30
+ }
31
+
32
+ wasActiveWithin(windowMs: number): boolean {
33
+ return Date.now() - this.lastActivityAt <= windowMs;
34
+ }
35
+
36
+ async yieldBackgroundWork(maxPauseMs = 5): Promise<void> {
37
+ if (this.active === 0) {
38
+ await yieldToEventLoop();
39
+ return;
40
+ }
41
+ let idleResolve!: () => void;
42
+ const idlePromise = new Promise<void>((resolve) => {
43
+ idleResolve = resolve;
44
+ this.idleResolvers.add(resolve);
45
+ });
46
+ try {
47
+ await Promise.race([
48
+ idlePromise,
49
+ new Promise<void>((resolve) => setTimeout(resolve, maxPauseMs)),
50
+ ]);
51
+ } finally {
52
+ this.idleResolvers.delete(idleResolve);
53
+ }
54
+ }
55
+ }