@spooky-sync/core 0.0.1-canary.6 → 0.0.1-canary.61

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 (47) hide show
  1. package/dist/index.d.ts +103 -367
  2. package/dist/index.js +595 -289
  3. package/dist/otel/index.d.ts +21 -0
  4. package/dist/otel/index.js +86 -0
  5. package/dist/types.d.ts +361 -0
  6. package/package.json +35 -5
  7. package/skills/sp00ky-core/SKILL.md +258 -0
  8. package/skills/sp00ky-core/references/auth.md +98 -0
  9. package/skills/sp00ky-core/references/config.md +76 -0
  10. package/src/events/events.test.ts +2 -1
  11. package/src/index.ts +3 -2
  12. package/src/modules/auth/events/index.ts +2 -1
  13. package/src/modules/auth/index.ts +17 -20
  14. package/src/modules/cache/index.ts +23 -23
  15. package/src/modules/cache/types.ts +2 -2
  16. package/src/modules/crdt/crdt-field.ts +189 -0
  17. package/src/modules/crdt/index.ts +178 -0
  18. package/src/modules/data/index.ts +64 -43
  19. package/src/modules/devtools/index.ts +23 -20
  20. package/src/modules/sync/engine.ts +31 -21
  21. package/src/modules/sync/events/index.ts +3 -2
  22. package/src/modules/sync/queue/queue-down.ts +5 -4
  23. package/src/modules/sync/queue/queue-up.ts +14 -13
  24. package/src/modules/sync/scheduler.ts +2 -2
  25. package/src/modules/sync/sync.ts +49 -36
  26. package/src/modules/sync/utils.test.ts +2 -2
  27. package/src/modules/sync/utils.ts +15 -17
  28. package/src/otel/index.ts +127 -0
  29. package/src/services/database/database.ts +11 -11
  30. package/src/services/database/events/index.ts +2 -1
  31. package/src/services/database/local-migrator.ts +21 -21
  32. package/src/services/database/local.ts +16 -15
  33. package/src/services/database/remote.ts +13 -13
  34. package/src/services/logger/index.ts +6 -101
  35. package/src/services/persistence/localstorage.ts +2 -2
  36. package/src/services/persistence/resilient.ts +34 -0
  37. package/src/services/persistence/surrealdb.ts +9 -9
  38. package/src/services/stream-processor/index.ts +32 -31
  39. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  40. package/src/services/stream-processor/wasm-types.ts +2 -2
  41. package/src/{spooky.ts → sp00ky.ts} +67 -38
  42. package/src/types.ts +18 -11
  43. package/src/utils/index.ts +22 -10
  44. package/src/utils/parser.ts +3 -2
  45. package/src/utils/surql.ts +15 -15
  46. package/src/utils/withRetry.test.ts +1 -1
  47. package/tsdown.config.ts +1 -1
package/dist/index.js CHANGED
@@ -2,12 +2,8 @@ import { DateTime, Duration, RecordId, Surreal, Uuid, applyDiagnostics, createRe
2
2
  import { QueryBuilder, RecordId as RecordId$1 } from "@spooky-sync/query-builder";
3
3
  import { createWasmWorkerEngines } from "@surrealdb/wasm";
4
4
  import pino from "pino";
5
- import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
6
- import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
7
- import { resourceFromAttributes } from "@opentelemetry/resources";
8
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
9
- import { createContextKey } from "@opentelemetry/api";
10
- import init, { SpookyProcessor } from "@spooky-sync/ssp-wasm";
5
+ import init, { Sp00kyProcessor } from "@spooky-sync/ssp-wasm";
6
+ import { LoroDoc } from "loro-crdt";
11
7
 
12
8
  //#region src/utils/surql.ts
13
9
  const surql = {
@@ -33,7 +29,7 @@ const surql = {
33
29
  return `SELECT ${returnValues.join(",")} FROM ONLY $${idVar}`;
34
30
  },
35
31
  selectByFieldsAnd(table, whereVar, returnValues) {
36
- return `SELECT ${returnValues.map((returnValues) => typeof returnValues === "string" ? returnValues : `${returnValues.field} as ${returnValues.alias}`).join(",")} FROM ${table} WHERE ${whereVar.map((whereVar) => typeof whereVar === "string" ? `${whereVar} = $${whereVar}` : `${whereVar.field} = $${whereVar.variable}`).join(" AND ")}`;
32
+ return `SELECT ${returnValues.map((rv) => typeof rv === "string" ? rv : `${rv.field} as ${rv.alias}`).join(",")} FROM ${table} WHERE ${whereVar.map((wv) => typeof wv === "string" ? `${wv} = $${wv}` : `${wv.field} = $${wv.variable}`).join(" AND ")}`;
37
33
  },
38
34
  create(idVar, dataVar) {
39
35
  return `CREATE ONLY $${idVar} CONTENT $${dataVar}`;
@@ -48,7 +44,7 @@ const surql = {
48
44
  return `UPDATE ONLY $${idVar} MERGE $${dataVar}`;
49
45
  },
50
46
  updateSet(idVar, keyDataVar) {
51
- return `UPDATE $${idVar} SET ${keyDataVar.map((keyDataVar) => typeof keyDataVar === "string" ? `${keyDataVar} = $${keyDataVar}` : "statement" in keyDataVar ? keyDataVar.statement : `${keyDataVar.key} = $${keyDataVar.variable}`).join(", ")}`;
47
+ return `UPDATE $${idVar} SET ${keyDataVar.map((kdv) => typeof kdv === "string" ? `${kdv} = $${kdv}` : "statement" in kdv ? kdv.statement : `${kdv.key} = $${kdv.variable}`).join(", ")}`;
52
48
  },
53
49
  delete(idVar) {
54
50
  return `DELETE $${idVar}`;
@@ -76,7 +72,7 @@ const surql = {
76
72
  //#region src/utils/parser.ts
77
73
  function cleanRecord(tableSchema, record) {
78
74
  const cleaned = {};
79
- for (const [key, value] of Object.entries(record)) if (key === "id" || key in tableSchema) cleaned[key] = value;
75
+ for (const [key, value] of Object.entries(record)) if (key === "id" || key.startsWith("_00_") || key in tableSchema) cleaned[key] = value;
80
76
  return cleaned;
81
77
  }
82
78
  function parseParams(tableSchema, params) {
@@ -162,7 +158,7 @@ function parseDuration(duration) {
162
158
  if (typeof duration !== "string") return 6e5;
163
159
  const match = duration.match(/^(\d+)([smh])$/);
164
160
  if (!match) return 6e5;
165
- const val = parseInt(match[1], 10);
161
+ const val = Number.parseInt(match[1], 10);
166
162
  switch (match[2]) {
167
163
  case "s": return val * 1e3;
168
164
  case "h": return val * 36e5;
@@ -174,6 +170,13 @@ async function fileToUint8Array(file) {
174
170
  return new Uint8Array(buffer);
175
171
  }
176
172
  /**
173
+ * Convert plain text to simple HTML paragraphs.
174
+ * Useful for seeding a rich-text editor (e.g. TipTap/ProseMirror) with fallback content.
175
+ */
176
+ function textToHtml(text) {
177
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").split("\n").map((l) => `<p>${l || "<br>"}</p>`).join("");
178
+ }
179
+ /**
177
180
  * Helper for retrying DB operations with exponential backoff
178
181
  */
179
182
  async function withRetry(logger, operation, retries = 3, delayMs = 100) {
@@ -188,7 +191,7 @@ async function withRetry(logger, operation, retries = 3, delayMs = 100) {
188
191
  attempt: i + 1,
189
192
  retries,
190
193
  error: msg,
191
- Category: "spooky-client::utils::withRetry"
194
+ Category: "sp00ky-client::utils::withRetry"
192
195
  }, "Retrying DB operation");
193
196
  await new Promise((res) => setTimeout(res, delayMs * (i + 1)));
194
197
  continue;
@@ -221,7 +224,7 @@ var DataModule = class {
221
224
  this.logger = logger.child({ service: "DataModule" });
222
225
  }
223
226
  async init() {
224
- this.logger.info({ Category: "spooky-client::DataModule::init" }, "DataModule initialized");
227
+ this.logger.info({ Category: "sp00ky-client::DataModule::init" }, "DataModule initialized");
225
228
  }
226
229
  /**
227
230
  * Register a query and return its hash for subscriptions
@@ -233,27 +236,27 @@ var DataModule = class {
233
236
  });
234
237
  this.logger.debug({
235
238
  hash,
236
- Category: "spooky-client::DataModule::query"
239
+ Category: "sp00ky-client::DataModule::query"
237
240
  }, "Query Initialization: started");
238
- const recordId = new RecordId("_spooky_query", hash);
241
+ const recordId = new RecordId("_00_query", hash);
239
242
  if (this.activeQueries.has(hash)) {
240
243
  this.logger.debug({
241
244
  hash,
242
- Category: "spooky-client::DataModule::query"
245
+ Category: "sp00ky-client::DataModule::query"
243
246
  }, "Query Initialization: exists, returning");
244
247
  return hash;
245
248
  }
246
249
  if (this.pendingQueries.has(hash)) {
247
250
  this.logger.debug({
248
251
  hash,
249
- Category: "spooky-client::DataModule::query"
252
+ Category: "sp00ky-client::DataModule::query"
250
253
  }, "Query Initialization: pending, waiting for existing creation");
251
254
  await this.pendingQueries.get(hash);
252
255
  return hash;
253
256
  }
254
257
  this.logger.debug({
255
258
  hash,
256
- Category: "spooky-client::DataModule::query"
259
+ Category: "sp00ky-client::DataModule::query"
257
260
  }, "Query Initialization: not found, creating new query");
258
261
  const promise = this.createAndRegisterQuery(hash, recordId, surqlString, params, ttl, tableName);
259
262
  this.pendingQueries.set(hash, promise);
@@ -311,7 +314,7 @@ var DataModule = class {
311
314
  if (!queryState) {
312
315
  this.logger.warn({
313
316
  queryHash,
314
- Category: "spooky-client::DataModule::onStreamUpdate"
317
+ Category: "sp00ky-client::DataModule::onStreamUpdate"
315
318
  }, "Received update for unknown query. Skipping...");
316
319
  return;
317
320
  }
@@ -329,7 +332,7 @@ var DataModule = class {
329
332
  if (prevJson === newJson) {
330
333
  this.logger.debug({
331
334
  queryHash,
332
- Category: "spooky-client::DataModule::onStreamUpdate"
335
+ Category: "sp00ky-client::DataModule::onStreamUpdate"
333
336
  }, "Query records unchanged, skipping notification");
334
337
  return;
335
338
  }
@@ -339,13 +342,13 @@ var DataModule = class {
339
342
  this.logger.debug({
340
343
  queryHash,
341
344
  recordCount: records?.length,
342
- Category: "spooky-client::DataModule::onStreamUpdate"
345
+ Category: "sp00ky-client::DataModule::onStreamUpdate"
343
346
  }, "Query updated from stream");
344
347
  } catch (err) {
345
348
  this.logger.error({
346
349
  err,
347
350
  queryHash,
348
- Category: "spooky-client::DataModule::onStreamUpdate"
351
+ Category: "sp00ky-client::DataModule::onStreamUpdate"
349
352
  }, "Failed to fetch records for stream update");
350
353
  }
351
354
  }
@@ -372,7 +375,7 @@ var DataModule = class {
372
375
  if (!queryState) {
373
376
  this.logger.warn({
374
377
  id,
375
- Category: "spooky-client::DataModule::updateQueryLocalArray"
378
+ Category: "sp00ky-client::DataModule::updateQueryLocalArray"
376
379
  }, "Query to update local array not found");
377
380
  return;
378
381
  }
@@ -387,7 +390,7 @@ var DataModule = class {
387
390
  if (!queryState) {
388
391
  this.logger.warn({
389
392
  hash,
390
- Category: "spooky-client::DataModule::updateQueryRemoteArray"
393
+ Category: "sp00ky-client::DataModule::updateQueryRemoteArray"
391
394
  }, "Query to update remote array not found");
392
395
  return;
393
396
  }
@@ -431,6 +434,7 @@ var DataModule = class {
431
434
  max_retries: options?.max_retries ?? 3,
432
435
  retry_strategy: options?.retry_strategy ?? "linear"
433
436
  };
437
+ if (options?.timeout != null) record.timeout = options.timeout;
434
438
  if (options?.assignedTo) record.assigned_to = options.assignedTo;
435
439
  const recordId = `${tableName}:${generateId()}`;
436
440
  await this.create(recordId, record);
@@ -444,7 +448,7 @@ var DataModule = class {
444
448
  if (!tableSchema) throw new Error(`Table ${tableName} not found`);
445
449
  const rid = parseRecordIdString(id);
446
450
  const params = parseParams(tableSchema.columns, data);
447
- const mutationId = parseRecordIdString(`_spooky_pending_mutations:${Date.now()}`);
451
+ const mutationId = parseRecordIdString(`_00_pending_mutations:${Date.now()}`);
448
452
  const dataKeys = Object.keys(params).map((key) => ({
449
453
  key,
450
454
  variable: `data_${key}`
@@ -474,7 +478,7 @@ var DataModule = class {
474
478
  for (const callback of this.mutationCallbacks) callback([mutationEvent]);
475
479
  this.logger.debug({
476
480
  id,
477
- Category: "spooky-client::DataModule::create"
481
+ Category: "sp00ky-client::DataModule::create"
478
482
  }, "Record created");
479
483
  return target;
480
484
  }
@@ -487,10 +491,10 @@ var DataModule = class {
487
491
  if (!tableSchema) throw new Error(`Table ${tableName} not found`);
488
492
  const rid = parseRecordIdString(id);
489
493
  const params = parseParams(tableSchema.columns, data);
490
- const mutationId = parseRecordIdString(`_spooky_pending_mutations:${Date.now()}`);
494
+ const mutationId = parseRecordIdString(`_00_pending_mutations:${Date.now()}`);
491
495
  const [beforeRecord] = await withRetry(this.logger, () => this.local.query("SELECT * FROM ONLY $id", { id: rid }));
492
496
  const query = surql.seal(surql.tx([
493
- surql.updateSet("id", [{ statement: "spooky_rv += 1" }]),
497
+ surql.updateSet("id", [{ statement: "_00_rv += 1" }]),
494
498
  surql.let("updated", surql.updateMerge("id", "data")),
495
499
  surql.createMutation("update", "mid", "id", "data"),
496
500
  surql.returnObject([{
@@ -505,14 +509,14 @@ var DataModule = class {
505
509
  }));
506
510
  const updatedFields = { id: target.id };
507
511
  for (const key of Object.keys(data)) if (key in target) updatedFields[key] = target[key];
508
- if ("spooky_rv" in target) updatedFields.spooky_rv = target.spooky_rv;
512
+ if ("_00_rv" in target) updatedFields._00_rv = target._00_rv;
509
513
  this.replaceRecordInQueries(updatedFields);
510
514
  const parsedRecord = parseParams(tableSchema.columns, target);
511
515
  await this.cache.save({
512
516
  table,
513
517
  op: "UPDATE",
514
518
  record: parsedRecord,
515
- version: target.spooky_rv
519
+ version: target._00_rv
516
520
  }, true);
517
521
  const pushEventOptions = parseUpdateOptions(id, data, options);
518
522
  const mutationEvent = {
@@ -527,7 +531,7 @@ var DataModule = class {
527
531
  for (const callback of this.mutationCallbacks) callback([mutationEvent]);
528
532
  this.logger.debug({
529
533
  id,
530
- Category: "spooky-client::DataModule::update"
534
+ Category: "sp00ky-client::DataModule::update"
531
535
  }, "Record updated");
532
536
  return target;
533
537
  }
@@ -538,13 +542,16 @@ var DataModule = class {
538
542
  const tableName = extractTablePart(id);
539
543
  if (!this.schema.tables.find((t) => t.name === tableName)) throw new Error(`Table ${tableName} not found`);
540
544
  const rid = parseRecordIdString(id);
541
- const mutationId = parseRecordIdString(`_spooky_pending_mutations:${Date.now()}`);
545
+ const mutationId = parseRecordIdString(`_00_pending_mutations:${Date.now()}`);
546
+ const [beforeRecords] = await this.local.query("SELECT * FROM ONLY $id", { id: rid });
547
+ const beforeRecord = beforeRecords ?? {};
542
548
  const query = surql.seal(surql.tx([surql.delete("id"), surql.createMutation("delete", "mid", "id")]));
543
549
  await withRetry(this.logger, () => this.local.execute(query, {
544
550
  id: rid,
545
551
  mid: mutationId
546
552
  }));
547
- await this.cache.delete(table, id, true);
553
+ await this.cache.delete(table, id, true, beforeRecord);
554
+ for (const [queryHash, queryState] of this.activeQueries) if (queryState.config.tableName === tableName) await this.notifyQuerySynced(queryHash);
548
555
  const mutationEvent = {
549
556
  type: "delete",
550
557
  mutation_id: mutationId,
@@ -553,7 +560,7 @@ var DataModule = class {
553
560
  for (const callback of this.mutationCallbacks) callback([mutationEvent]);
554
561
  this.logger.debug({
555
562
  id,
556
- Category: "spooky-client::DataModule::delete"
563
+ Category: "sp00ky-client::DataModule::delete"
557
564
  }, "Record deleted");
558
565
  }
559
566
  /**
@@ -568,14 +575,14 @@ var DataModule = class {
568
575
  this.logger.info({
569
576
  id,
570
577
  tableName,
571
- Category: "spooky-client::DataModule::rollbackCreate"
578
+ Category: "sp00ky-client::DataModule::rollbackCreate"
572
579
  }, "Rolled back optimistic create");
573
580
  } catch (err) {
574
581
  this.logger.error({
575
582
  err,
576
583
  id,
577
584
  tableName,
578
- Category: "spooky-client::DataModule::rollbackCreate"
585
+ Category: "sp00ky-client::DataModule::rollbackCreate"
579
586
  }, "Failed to rollback create");
580
587
  }
581
588
  }
@@ -596,20 +603,20 @@ var DataModule = class {
596
603
  table: tableName,
597
604
  op: "UPDATE",
598
605
  record: parsedRecord,
599
- version: beforeRecord.spooky_rv || 1
606
+ version: beforeRecord._00_rv || 1
600
607
  }, true);
601
608
  await this.replaceRecordInQueries(beforeRecord);
602
609
  this.logger.info({
603
610
  id,
604
611
  tableName,
605
- Category: "spooky-client::DataModule::rollbackUpdate"
612
+ Category: "sp00ky-client::DataModule::rollbackUpdate"
606
613
  }, "Rolled back optimistic update");
607
614
  } catch (err) {
608
615
  this.logger.error({
609
616
  err,
610
617
  id,
611
618
  tableName,
612
- Category: "spooky-client::DataModule::rollbackUpdate"
619
+ Category: "sp00ky-client::DataModule::rollbackUpdate"
613
620
  }, "Failed to rollback update");
614
621
  }
615
622
  }
@@ -654,7 +661,7 @@ var DataModule = class {
654
661
  hash,
655
662
  tableName,
656
663
  recordCount: queryState.records.length,
657
- Category: "spooky-client::DataModule::query"
664
+ Category: "sp00ky-client::DataModule::query"
658
665
  }, "Query registered");
659
666
  return hash;
660
667
  }
@@ -689,7 +696,7 @@ var DataModule = class {
689
696
  } catch (err) {
690
697
  this.logger.warn({
691
698
  err,
692
- Category: "spooky-client::DataModule::createNewQuery"
699
+ Category: "sp00ky-client::DataModule::createNewQuery"
693
700
  }, "Failed to load initial cached records");
694
701
  }
695
702
  return {
@@ -712,7 +719,7 @@ var DataModule = class {
712
719
  queryState.ttlTimer = setTimeout(() => {
713
720
  this.logger.debug({
714
721
  id: encodeRecordId(queryState.config.id),
715
- Category: "spooky-client::DataModule::startTTLHeartbeat"
722
+ Category: "sp00ky-client::DataModule::startTTLHeartbeat"
716
723
  }, "TTL heartbeat");
717
724
  this.startTTLHeartbeat(queryState);
718
725
  }, heartbeatTime);
@@ -744,7 +751,7 @@ function parseUpdateOptions(id, data, options) {
744
751
  let pushEventOptions = {};
745
752
  if (options?.debounced) pushEventOptions = { debounced: {
746
753
  delay: options.debounced !== true ? options.debounced?.delay ?? 200 : 200,
747
- key: (options.debounced !== true ? options.debounced?.key ?? id : id) === "recordId_x_fields" ? `${id}::${Object.keys(data).sort().join("#")}` : id
754
+ key: (options.debounced !== true ? options.debounced?.key ?? id : id) === "recordId_x_fields" ? `${id}::${Object.keys(data).toSorted().join("#")}` : id
748
755
  } };
749
756
  return pushEventOptions;
750
757
  }
@@ -781,7 +788,7 @@ var AbstractDatabaseService = class {
781
788
  this.logger.debug({
782
789
  query,
783
790
  vars,
784
- Category: "spooky-client::Database::query"
791
+ Category: "sp00ky-client::Database::query"
785
792
  }, "Executing query");
786
793
  const result = await this.client.query(query, vars);
787
794
  const duration = performance.now() - startTime;
@@ -796,7 +803,7 @@ var AbstractDatabaseService = class {
796
803
  this.logger.trace({
797
804
  query,
798
805
  result,
799
- Category: "spooky-client::Database::query"
806
+ Category: "sp00ky-client::Database::query"
800
807
  }, "Query executed successfully");
801
808
  } catch (err) {
802
809
  const duration = performance.now() - startTime;
@@ -812,7 +819,7 @@ var AbstractDatabaseService = class {
812
819
  query,
813
820
  vars,
814
821
  err,
815
- Category: "spooky-client::Database::query"
822
+ Category: "sp00ky-client::Database::query"
816
823
  }, "Query execution failed");
817
824
  reject(err);
818
825
  }
@@ -824,7 +831,7 @@ var AbstractDatabaseService = class {
824
831
  return query.extract(raw);
825
832
  }
826
833
  async close() {
827
- this.logger.info({ Category: "spooky-client::Database::close" }, "Closing database connection");
834
+ this.logger.info({ Category: "sp00ky-client::Database::close" }, "Closing database connection");
828
835
  await this.client.close();
829
836
  }
830
837
  };
@@ -1004,7 +1011,7 @@ var LocalDatabaseService = class extends AbstractDatabaseService {
1004
1011
  type,
1005
1012
  phase,
1006
1013
  service: "surrealdb:local",
1007
- Category: "spooky-client::LocalDatabaseService::diagnostics"
1014
+ Category: "sp00ky-client::LocalDatabaseService::diagnostics"
1008
1015
  }, `Local SurrealDB diagnostics captured ${type}:${phase}`);
1009
1016
  })
1010
1017
  }), logger, events);
@@ -1018,30 +1025,30 @@ var LocalDatabaseService = class extends AbstractDatabaseService {
1018
1025
  this.logger.info({
1019
1026
  namespace,
1020
1027
  database,
1021
- Category: "spooky-client::LocalDatabaseService::connect"
1028
+ Category: "sp00ky-client::LocalDatabaseService::connect"
1022
1029
  }, "Connecting to local database");
1023
1030
  try {
1024
- const storeUrl = (this.getConfig().store ?? "memory") === "memory" ? "mem://" : "indxdb://spooky";
1031
+ const storeUrl = (this.getConfig().store ?? "memory") === "memory" ? "mem://" : "indxdb://sp00ky";
1025
1032
  this.logger.debug({
1026
1033
  storeUrl,
1027
- Category: "spooky-client::LocalDatabaseService::connect"
1034
+ Category: "sp00ky-client::LocalDatabaseService::connect"
1028
1035
  }, "[LocalDatabaseService] Calling client.connect");
1029
1036
  await this.client.connect(storeUrl, {});
1030
1037
  this.logger.debug({
1031
1038
  namespace,
1032
1039
  database,
1033
- Category: "spooky-client::LocalDatabaseService::connect"
1040
+ Category: "sp00ky-client::LocalDatabaseService::connect"
1034
1041
  }, "[LocalDatabaseService] client.connect returned. Calling client.use");
1035
1042
  await this.client.use({
1036
1043
  namespace,
1037
1044
  database
1038
1045
  });
1039
- this.logger.debug({ Category: "spooky-client::LocalDatabaseService::connect" }, "[LocalDatabaseService] client.use returned");
1040
- this.logger.info({ Category: "spooky-client::LocalDatabaseService::connect" }, "Connected to local database");
1046
+ this.logger.debug({ Category: "sp00ky-client::LocalDatabaseService::connect" }, "[LocalDatabaseService] client.use returned");
1047
+ this.logger.info({ Category: "sp00ky-client::LocalDatabaseService::connect" }, "Connected to local database");
1041
1048
  } catch (err) {
1042
1049
  this.logger.error({
1043
1050
  err,
1044
- Category: "spooky-client::LocalDatabaseService::connect"
1051
+ Category: "sp00ky-client::LocalDatabaseService::connect"
1045
1052
  }, "Failed to connect to local database");
1046
1053
  throw err;
1047
1054
  }
@@ -1062,7 +1069,7 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
1062
1069
  type,
1063
1070
  phase,
1064
1071
  service: "surrealdb:remote",
1065
- Category: "spooky-client::RemoteDatabaseService::diagnostics"
1072
+ Category: "sp00ky-client::RemoteDatabaseService::diagnostics"
1066
1073
  }, `Remote SurrealDB diagnostics captured ${type}:${phase}`);
1067
1074
  }) }), logger, events);
1068
1075
  this.config = config;
@@ -1077,7 +1084,7 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
1077
1084
  endpoint,
1078
1085
  namespace,
1079
1086
  database,
1080
- Category: "spooky-client::RemoteDatabaseService::connect"
1087
+ Category: "sp00ky-client::RemoteDatabaseService::connect"
1081
1088
  }, "Connecting to remote database");
1082
1089
  try {
1083
1090
  await this.client.connect(endpoint);
@@ -1086,18 +1093,18 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
1086
1093
  database
1087
1094
  });
1088
1095
  if (token) {
1089
- this.logger.debug({ Category: "spooky-client::RemoteDatabaseService::connect" }, "Authenticating with token");
1096
+ this.logger.debug({ Category: "sp00ky-client::RemoteDatabaseService::connect" }, "Authenticating with token");
1090
1097
  await this.client.authenticate(token);
1091
1098
  }
1092
- this.logger.info({ Category: "spooky-client::RemoteDatabaseService::connect" }, "Connected to remote database");
1099
+ this.logger.info({ Category: "sp00ky-client::RemoteDatabaseService::connect" }, "Connected to remote database");
1093
1100
  } catch (err) {
1094
1101
  this.logger.error({
1095
1102
  err,
1096
- Category: "spooky-client::RemoteDatabaseService::connect"
1103
+ Category: "sp00ky-client::RemoteDatabaseService::connect"
1097
1104
  }, "Failed to connect to remote database");
1098
1105
  throw err;
1099
1106
  }
1100
- } else this.logger.warn({ Category: "spooky-client::RemoteDatabaseService::connect" }, "No endpoint configured for remote database");
1107
+ } else this.logger.warn({ Category: "sp00ky-client::RemoteDatabaseService::connect" }, "No endpoint configured for remote database");
1101
1108
  }
1102
1109
  async signin(params) {
1103
1110
  return this.client.signin(params);
@@ -1115,70 +1122,14 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
1115
1122
 
1116
1123
  //#endregion
1117
1124
  //#region src/services/logger/index.ts
1118
- createContextKey("Category");
1119
- function mapLevelToSeverityNumber(level) {
1120
- switch (level) {
1121
- case "trace": return 1;
1122
- case "debug": return 5;
1123
- case "info": return 9;
1124
- case "warn": return 13;
1125
- case "error": return 17;
1126
- case "fatal": return 21;
1127
- default: return 9;
1128
- }
1129
- }
1130
- function createLogger(level = "info", otelEndpoint) {
1125
+ function createLogger(level = "info", transmit) {
1131
1126
  const browserConfig = {
1132
1127
  asObject: true,
1133
1128
  write: (o) => {
1134
1129
  console.log(JSON.stringify(o));
1135
1130
  }
1136
1131
  };
1137
- if (otelEndpoint) {
1138
- const loggerProvider = new LoggerProvider({
1139
- resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: "spooky-client" }),
1140
- processors: [new BatchLogRecordProcessor(new OTLPLogExporter({ url: otelEndpoint }))]
1141
- });
1142
- const otelLogger = {};
1143
- const getOtelLogger = (category) => {
1144
- if (!otelLogger[category]) otelLogger[category] = loggerProvider.getLogger(category);
1145
- return otelLogger[category];
1146
- };
1147
- browserConfig.transmit = {
1148
- level,
1149
- send: (levelLabel, logEvent) => {
1150
- try {
1151
- const messages = [...logEvent.messages];
1152
- const severityNumber = mapLevelToSeverityNumber(levelLabel);
1153
- let body = "";
1154
- const msg = messages.pop();
1155
- if (typeof msg === "string") body = msg;
1156
- else if (msg) body = JSON.stringify(msg);
1157
- let category = "spooky-client::unknown";
1158
- const attributes = {};
1159
- for (const msg of messages) if (typeof msg === "object") {
1160
- if (msg.Category) {
1161
- category = msg.Category;
1162
- delete msg.Category;
1163
- }
1164
- Object.assign(attributes, msg);
1165
- }
1166
- getOtelLogger(category).emit({
1167
- severityNumber,
1168
- severityText: levelLabel.toUpperCase(),
1169
- body,
1170
- attributes: {
1171
- ...logEvent.bindings[0],
1172
- ...attributes
1173
- },
1174
- timestamp: new Date(logEvent.ts)
1175
- });
1176
- } catch (e) {
1177
- console.warn("Failed to transmit log to OTEL endpoint", e);
1178
- }
1179
- }
1180
- };
1181
- }
1132
+ if (transmit) browserConfig.transmit = transmit;
1182
1133
  return pino({
1183
1134
  level,
1184
1135
  browser: browserConfig
@@ -1203,25 +1154,25 @@ var LocalMigrator = class {
1203
1154
  const hash = await sha1(schemaSurql);
1204
1155
  const { database } = this.localDb.getConfig();
1205
1156
  if (await this.isSchemaUpToDate(hash)) {
1206
- this.logger.info({ Category: "spooky-client::LocalMigrator::provision" }, "[Provisioning] Schema is up to date, skipping migration");
1157
+ this.logger.info({ Category: "sp00ky-client::LocalMigrator::provision" }, "[Provisioning] Schema is up to date, skipping migration");
1207
1158
  return;
1208
1159
  }
1209
1160
  await this.recreateDatabase(database);
1210
- const fullSchema = schemaSurql + "\n\n DEFINE TABLE IF NOT EXISTS _spooky_stream_processor_state SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _spooky_query SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _spooky_schema SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _spooky_pending_mutations SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n ";
1161
+ const fullSchema = schemaSurql + "\n\n DEFINE TABLE IF NOT EXISTS _00_stream_processor_state SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _00_query SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _00_schema SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n DEFINE TABLE IF NOT EXISTS _00_pending_mutations SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;\n ";
1211
1162
  const statements = this.splitStatements(fullSchema);
1212
1163
  for (let i = 0; i < statements.length; i++) {
1213
1164
  const statement = statements[i];
1214
1165
  const cleanStatement = statement.replace(/--.*/g, "").trim();
1215
1166
  if (cleanStatement.toUpperCase().startsWith("DEFINE INDEX")) {
1216
- this.logger.warn({ Category: "spooky-client::LocalMigrator::provision" }, `[Provisioning] Skipping index definition (WASM hang avoidance): ${cleanStatement.substring(0, 50)}...`);
1167
+ this.logger.warn({ Category: "sp00ky-client::LocalMigrator::provision" }, `[Provisioning] Skipping index definition (WASM hang avoidance): ${cleanStatement.substring(0, 50)}...`);
1217
1168
  continue;
1218
1169
  }
1219
1170
  try {
1220
- this.logger.info({ Category: "spooky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Executing: ${statement.substring(0, 50)}...`);
1171
+ this.logger.info({ Category: "sp00ky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Executing: ${statement.substring(0, 50)}...`);
1221
1172
  await this.localDb.query(statement);
1222
- this.logger.info({ Category: "spooky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Done`);
1173
+ this.logger.info({ Category: "sp00ky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Done`);
1223
1174
  } catch (e) {
1224
- this.logger.error({ Category: "spooky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Error executing statement: ${statement}`);
1175
+ this.logger.error({ Category: "sp00ky-client::LocalMigrator::provision" }, `[Provisioning] (${i + 1}/${statements.length}) Error executing statement: ${statement}`);
1225
1176
  throw e;
1226
1177
  }
1227
1178
  }
@@ -1229,22 +1180,22 @@ var LocalMigrator = class {
1229
1180
  }
1230
1181
  async isSchemaUpToDate(hash) {
1231
1182
  try {
1232
- const [lastSchemaRecord] = await this.localDb.query(`SELECT hash, created_at FROM ONLY _spooky_schema ORDER BY created_at DESC LIMIT 1;`);
1183
+ const [lastSchemaRecord] = await this.localDb.query(`SELECT hash, created_at FROM ONLY _00_schema ORDER BY created_at DESC LIMIT 1;`);
1233
1184
  return lastSchemaRecord?.hash === hash;
1234
- } catch (error) {
1185
+ } catch (_error) {
1235
1186
  return false;
1236
1187
  }
1237
1188
  }
1238
1189
  async recreateDatabase(database) {
1239
1190
  try {
1240
- await this.localDb.query(`DEFINE DATABASE _spooky_temp;`);
1241
- } catch (e) {}
1191
+ await this.localDb.query(`DEFINE DATABASE _00_temp;`);
1192
+ } catch (_e) {}
1242
1193
  try {
1243
1194
  await this.localDb.query(`
1244
- USE DB _spooky_temp;
1195
+ USE DB _00_temp;
1245
1196
  REMOVE DATABASE ${database};
1246
1197
  `);
1247
- } catch (e) {}
1198
+ } catch (_e) {}
1248
1199
  await this.localDb.query(`
1249
1200
  DEFINE DATABASE ${database};
1250
1201
  USE DB ${database};
@@ -1302,7 +1253,7 @@ var LocalMigrator = class {
1302
1253
  return statements;
1303
1254
  }
1304
1255
  async createHashRecord(hash) {
1305
- await this.localDb.query(`UPSERT _spooky_schema SET hash = $hash, created_at = time::now() WHERE hash = $hash;`, { hash });
1256
+ await this.localDb.query(`UPSERT _00_schema SET hash = $hash, created_at = time::now() WHERE hash = $hash;`, { hash });
1306
1257
  }
1307
1258
  };
1308
1259
 
@@ -1394,7 +1345,7 @@ var UpQueue = class {
1394
1345
  this.logger.error({
1395
1346
  error,
1396
1347
  event,
1397
- Category: "spooky-client::UpQueue::next"
1348
+ Category: "sp00ky-client::UpQueue::next"
1398
1349
  }, "Network error processing mutation, re-queuing");
1399
1350
  this.queue.unshift(event);
1400
1351
  throw error;
@@ -1402,7 +1353,7 @@ var UpQueue = class {
1402
1353
  this.logger.error({
1403
1354
  error,
1404
1355
  event,
1405
- Category: "spooky-client::UpQueue::next"
1356
+ Category: "sp00ky-client::UpQueue::next"
1406
1357
  }, "Application error processing mutation, rolling back");
1407
1358
  try {
1408
1359
  await this.removeEventFromDatabase(event.mutation_id);
@@ -1410,7 +1361,7 @@ var UpQueue = class {
1410
1361
  this.logger.error({
1411
1362
  error: removeError,
1412
1363
  event,
1413
- Category: "spooky-client::UpQueue::next"
1364
+ Category: "sp00ky-client::UpQueue::next"
1414
1365
  }, "Failed to remove rolled-back mutation from database");
1415
1366
  }
1416
1367
  if (onRollback) try {
@@ -1419,7 +1370,7 @@ var UpQueue = class {
1419
1370
  this.logger.error({
1420
1371
  error: rollbackError,
1421
1372
  event,
1422
- Category: "spooky-client::UpQueue::next"
1373
+ Category: "sp00ky-client::UpQueue::next"
1423
1374
  }, "Rollback handler failed");
1424
1375
  }
1425
1376
  this._events.addEvent({
@@ -1434,7 +1385,7 @@ var UpQueue = class {
1434
1385
  this.logger.error({
1435
1386
  error,
1436
1387
  event,
1437
- Category: "spooky-client::UpQueue::next"
1388
+ Category: "sp00ky-client::UpQueue::next"
1438
1389
  }, "Failed to remove mutation from database after successful processing");
1439
1390
  }
1440
1391
  this._events.addEvent({
@@ -1448,7 +1399,7 @@ var UpQueue = class {
1448
1399
  }
1449
1400
  async loadFromDatabase() {
1450
1401
  try {
1451
- const [records] = await this.local.query(`SELECT * FROM _spooky_pending_mutations ORDER BY created_at ASC`);
1402
+ const [records] = await this.local.query(`SELECT * FROM _00_pending_mutations ORDER BY created_at ASC`);
1452
1403
  this.queue = records.map((r) => {
1453
1404
  switch (r.mutationType) {
1454
1405
  case "create": return {
@@ -1474,7 +1425,7 @@ var UpQueue = class {
1474
1425
  this.logger.warn({
1475
1426
  mutationType: r.mutationType,
1476
1427
  record: r,
1477
- Category: "spooky-client::UpQueue::loadFromDatabase"
1428
+ Category: "sp00ky-client::UpQueue::loadFromDatabase"
1478
1429
  }, "Unknown mutation type");
1479
1430
  return null;
1480
1431
  }
@@ -1482,7 +1433,7 @@ var UpQueue = class {
1482
1433
  } catch (error) {
1483
1434
  this.logger.error({
1484
1435
  error,
1485
- Category: "spooky-client::UpQueue::loadFromDatabase"
1436
+ Category: "sp00ky-client::UpQueue::loadFromDatabase"
1486
1437
  }, "Failed to load pending mutations from database");
1487
1438
  }
1488
1439
  }
@@ -1523,7 +1474,7 @@ var DownQueue = class {
1523
1474
  this.logger.error({
1524
1475
  error,
1525
1476
  event,
1526
- Category: "spooky-client::DownQueue::next"
1477
+ Category: "sp00ky-client::DownQueue::next"
1527
1478
  }, "Failed to process query");
1528
1479
  this.queue.unshift(event);
1529
1480
  throw error;
@@ -1538,8 +1489,8 @@ var ArraySyncer = class {
1538
1489
  remoteArray;
1539
1490
  needsSort = false;
1540
1491
  constructor(localArray, remoteArray) {
1541
- this.remoteArray = remoteArray.sort((a, b) => a[0].localeCompare(b[0]));
1542
- this.localArray = localArray.sort((a, b) => a[0].localeCompare(b[0]));
1492
+ this.remoteArray = remoteArray.toSorted((a, b) => a[0].localeCompare(b[0]));
1493
+ this.localArray = localArray.toSorted((a, b) => a[0].localeCompare(b[0]));
1543
1494
  }
1544
1495
  /**
1545
1496
  * Inserts an item into the local array
@@ -1575,7 +1526,6 @@ var ArraySyncer = class {
1575
1526
  this.localArray.sort((a, b) => a[0].localeCompare(b[0]));
1576
1527
  this.needsSort = false;
1577
1528
  }
1578
- console.log("xxxx555", this.localArray, this.remoteArray);
1579
1529
  return diffRecordVersionArray(this.localArray, this.remoteArray);
1580
1530
  }
1581
1531
  };
@@ -1606,14 +1556,12 @@ function diffRecordVersionArray(local, remote) {
1606
1556
  };
1607
1557
  }
1608
1558
  function createDiffFromDbOp(op, recordId, version, versions) {
1609
- if (op !== "DELETE") {
1610
- const old = versions?.find((record) => record[0] === encodeRecordId(recordId));
1611
- if (old && old[1] >= version) return {
1612
- added: [],
1613
- updated: [],
1614
- removed: []
1615
- };
1616
- }
1559
+ const old = versions?.find((record) => record[0] === encodeRecordId(recordId));
1560
+ if (old && old[1] >= version) return {
1561
+ added: [],
1562
+ updated: [],
1563
+ removed: []
1564
+ };
1617
1565
  if (op === "CREATE") return {
1618
1566
  added: [{
1619
1567
  id: recordId,
@@ -1643,7 +1591,7 @@ function createDiffFromDbOp(op, recordId, version, versions) {
1643
1591
  * SyncEngine handles the core sync operations: fetching remote records,
1644
1592
  * caching them locally, and ingesting into DBSP.
1645
1593
  *
1646
- * This is extracted from SpookySync to separate "how to sync" from "when to sync".
1594
+ * This is extracted from Sp00kySync to separate "how to sync" from "when to sync".
1647
1595
  */
1648
1596
  var SyncEngine = class {
1649
1597
  logger;
@@ -1652,7 +1600,7 @@ var SyncEngine = class {
1652
1600
  this.remote = remote;
1653
1601
  this.cache = cache;
1654
1602
  this.schema = schema;
1655
- this.logger = logger.child({ service: "SpookySync:SyncEngine" });
1603
+ this.logger = logger.child({ service: "Sp00kySync:SyncEngine" });
1656
1604
  }
1657
1605
  /**
1658
1606
  * Sync missing/updated/removed records between local and remote.
@@ -1665,33 +1613,36 @@ var SyncEngine = class {
1665
1613
  added,
1666
1614
  updated,
1667
1615
  removed,
1668
- Category: "spooky-client::SyncEngine::syncRecords"
1616
+ Category: "sp00ky-client::SyncEngine::syncRecords"
1669
1617
  }, "SyncEngine.syncRecords diff");
1670
1618
  if (removed.length > 0) await this.handleRemovedRecords(removed);
1671
- const idsToFetch = [...added, ...updated].map((x) => x.id);
1619
+ const toFetch = [...added, ...updated];
1620
+ const idsToFetch = toFetch.map((x) => x.id);
1672
1621
  if (idsToFetch.length === 0) return;
1673
- const [remoteResults] = await this.remote.query("SELECT (SELECT * FROM ONLY <record>$parent.id) AS record, (SELECT version FROM ONLY _spooky_version WHERE record_id = <record>$parent.id)['version'] as spooky_rv FROM $idsToFetch", { idsToFetch });
1674
- console.log("remoteResults>", remoteResults);
1622
+ const versionMap = /* @__PURE__ */ new Map();
1623
+ for (const item of toFetch) versionMap.set(encodeRecordId(item.id), item.version);
1624
+ const [remoteResults] = await this.remote.query("SELECT * FROM $idsToFetch", { idsToFetch });
1675
1625
  const cacheBatch = [];
1676
- for (const { spooky_rv, record } of remoteResults) {
1626
+ for (const record of remoteResults) {
1677
1627
  if (!record?.id) {
1678
1628
  this.logger.warn({
1679
1629
  record,
1680
1630
  idsToFetch,
1681
- Category: "spooky-client::SyncEngine::syncRecords"
1682
- }, "Remote record has no id. Skipping record");
1631
+ Category: "sp00ky-client::SyncEngine::syncRecords"
1632
+ }, "Remote record has no id (possibly deleted). Skipping record");
1683
1633
  continue;
1684
1634
  }
1685
1635
  const fullId = encodeRecordId(record.id);
1686
1636
  const table = record.id.table.toString();
1687
1637
  const isAdded = added.some((item) => encodeRecordId(item.id) === fullId);
1638
+ const version = versionMap.get(fullId) ?? 0;
1688
1639
  const localVersion = this.cache.lookup(fullId);
1689
- if (localVersion && spooky_rv <= localVersion) {
1640
+ if (localVersion && version <= localVersion) {
1690
1641
  this.logger.info({
1691
1642
  recordId: fullId,
1692
- version: spooky_rv,
1643
+ version,
1693
1644
  localVersion,
1694
- Category: "spooky-client::SyncEngine::syncRecords"
1645
+ Category: "sp00ky-client::SyncEngine::syncRecords"
1695
1646
  }, "Local version is higher than remote version. Skipping record");
1696
1647
  continue;
1697
1648
  }
@@ -1701,7 +1652,7 @@ var SyncEngine = class {
1701
1652
  table,
1702
1653
  op: isAdded ? "CREATE" : "UPDATE",
1703
1654
  record: cleanedRecord,
1704
- version: spooky_rv
1655
+ version
1705
1656
  });
1706
1657
  }
1707
1658
  if (cacheBatch.length > 0) await this.cache.saveBatch(cacheBatch);
@@ -1713,21 +1664,21 @@ var SyncEngine = class {
1713
1664
  async handleRemovedRecords(removed) {
1714
1665
  this.logger.debug({
1715
1666
  removed: removed.map((r) => r.toString()),
1716
- Category: "spooky-client::SyncEngine::handleRemovedRecords"
1667
+ Category: "sp00ky-client::SyncEngine::handleRemovedRecords"
1717
1668
  }, "Checking removed records");
1718
1669
  let existingRemoteIds = /* @__PURE__ */ new Set();
1719
1670
  try {
1720
1671
  const [existingRemote] = await this.remote.query("SELECT id FROM $ids", { ids: removed });
1721
1672
  existingRemoteIds = new Set(existingRemote.map((r) => encodeRecordId(r.id)));
1722
1673
  } catch {
1723
- this.logger.debug({ Category: "spooky-client::SyncEngine::handleRemovedRecords" }, "Remote existence check failed, proceeding with deletion");
1674
+ this.logger.debug({ Category: "sp00ky-client::SyncEngine::handleRemovedRecords" }, "Remote existence check failed, proceeding with deletion");
1724
1675
  }
1725
1676
  for (const recordId of removed) {
1726
1677
  const recordIdStr = encodeRecordId(recordId);
1727
1678
  if (!existingRemoteIds.has(recordIdStr)) {
1728
1679
  this.logger.debug({
1729
1680
  recordId: recordIdStr,
1730
- Category: "spooky-client::SyncEngine::handleRemovedRecords"
1681
+ Category: "sp00ky-client::SyncEngine::handleRemovedRecords"
1731
1682
  }, "Deleting confirmed removed record");
1732
1683
  await this.cache.delete(recordId.table.name, recordIdStr);
1733
1684
  }
@@ -1806,12 +1757,12 @@ var SyncScheduler = class {
1806
1757
  //#endregion
1807
1758
  //#region src/modules/sync/sync.ts
1808
1759
  /**
1809
- * The main synchronization engine for Spooky.
1760
+ * The main synchronization engine for Sp00ky.
1810
1761
  * Handles the bidirectional synchronization between the local database and the remote backend.
1811
1762
  * Uses a queue-based architecture with 'up' (local to remote) and 'down' (remote to local) queues.
1812
1763
  * @template S The schema structure type.
1813
1764
  */
1814
- var SpookySync = class {
1765
+ var Sp00kySync = class {
1815
1766
  clientId = "";
1816
1767
  upQueue;
1817
1768
  downQueue;
@@ -1840,7 +1791,7 @@ var SpookySync = class {
1840
1791
  this.cache = cache;
1841
1792
  this.dataModule = dataModule;
1842
1793
  this.schema = schema;
1843
- this.logger = logger.child({ service: "SpookySync" });
1794
+ this.logger = logger.child({ service: "Sp00kySync" });
1844
1795
  this.upQueue = new UpQueue(this.local, this.logger);
1845
1796
  this.downQueue = new DownQueue(this.local, this.logger);
1846
1797
  this.syncEngine = new SyncEngine(this.remote, this.cache, this.schema, this.logger);
@@ -1853,7 +1804,7 @@ var SpookySync = class {
1853
1804
  * @throws Error if already initialized.
1854
1805
  */
1855
1806
  async init(clientId) {
1856
- if (this.isInit) throw new Error("SpookySync is already initialized");
1807
+ if (this.isInit) throw new Error("Sp00kySync is already initialized");
1857
1808
  this.clientId = clientId;
1858
1809
  this.isInit = true;
1859
1810
  await this.scheduler.init();
@@ -1865,29 +1816,37 @@ var SpookySync = class {
1865
1816
  async startRefLiveQueries() {
1866
1817
  this.logger.debug({
1867
1818
  clientId: this.clientId,
1868
- Category: "spooky-client::SpookySync::startRefLiveQueries"
1819
+ Category: "sp00ky-client::Sp00kySync::startRefLiveQueries"
1869
1820
  }, "Starting ref live queries");
1870
- const [queryUuid] = await this.remote.query("LIVE SELECT * FROM _spooky_list_ref");
1821
+ const [queryUuid] = await this.remote.query("LIVE SELECT * FROM _00_list_ref");
1871
1822
  (await this.remote.getClient().liveOf(queryUuid)).subscribe((message) => {
1872
1823
  this.logger.debug({
1873
1824
  message,
1874
- Category: "spooky-client::SpookySync::startRefLiveQueries"
1825
+ Category: "sp00ky-client::Sp00kySync::startRefLiveQueries"
1875
1826
  }, "Live update received");
1876
1827
  if (message.action === "KILLED") return;
1877
1828
  this.handleRemoteListRefChange(message.action, message.value.in, message.value.out, message.value.version).catch((err) => {
1878
1829
  this.logger.error({
1879
1830
  err,
1880
- Category: "spooky-client::SpookySync::startRefLiveQueries"
1831
+ Category: "sp00ky-client::Sp00kySync::startRefLiveQueries"
1881
1832
  }, "Error handling remote list ref change");
1882
1833
  });
1883
1834
  });
1884
1835
  }
1885
1836
  async handleRemoteListRefChange(action, queryId, recordId, version) {
1837
+ if (action === "DELETE") {
1838
+ this.logger.debug({
1839
+ queryId: queryId.toString(),
1840
+ recordId: recordId.toString(),
1841
+ Category: "sp00ky-client::Sp00kySync::handleRemoteListRefChange"
1842
+ }, "Ignoring DELETE on list_ref — should not happen");
1843
+ return;
1844
+ }
1886
1845
  const existing = this.dataModule.getQueryById(queryId);
1887
1846
  if (!existing) {
1888
1847
  this.logger.warn({
1889
1848
  queryId: queryId.toString(),
1890
- Category: "spooky-client::SpookySync::handleRemoteListRefChange"
1849
+ Category: "sp00ky-client::Sp00kySync::handleRemoteListRefChange"
1891
1850
  }, "Received remote update for unknown local query");
1892
1851
  return;
1893
1852
  }
@@ -1898,7 +1857,7 @@ var SpookySync = class {
1898
1857
  recordId,
1899
1858
  version,
1900
1859
  localArray,
1901
- Category: "spooky-client::SpookySync::handleRemoteListRefChange"
1860
+ Category: "sp00ky-client::Sp00kySync::handleRemoteListRefChange"
1902
1861
  }, "Live update is being processed");
1903
1862
  const diff = createDiffFromDbOp(action, recordId, version, localArray);
1904
1863
  await this.syncEngine.syncRecords(diff);
@@ -1913,11 +1872,10 @@ var SpookySync = class {
1913
1872
  async processUpEvent(event) {
1914
1873
  this.logger.debug({
1915
1874
  event,
1916
- Category: "spooky-client::SpookySync::processUpEvent"
1875
+ Category: "sp00ky-client::Sp00kySync::processUpEvent"
1917
1876
  }, "Processing up event");
1918
- console.log("xx1", event);
1919
1877
  switch (event.type) {
1920
- case "create":
1878
+ case "create": {
1921
1879
  const dataKeys = Object.keys(event.data).map((key) => ({
1922
1880
  key,
1923
1881
  variable: `data_${key}`
@@ -1929,6 +1887,7 @@ var SpookySync = class {
1929
1887
  ...prefixedParams
1930
1888
  });
1931
1889
  break;
1890
+ }
1932
1891
  case "update":
1933
1892
  await this.remote.query(`UPDATE $id MERGE $data`, {
1934
1893
  id: event.record_id,
@@ -1941,7 +1900,7 @@ var SpookySync = class {
1941
1900
  default:
1942
1901
  this.logger.error({
1943
1902
  event,
1944
- Category: "spooky-client::SpookySync::processUpEvent"
1903
+ Category: "sp00ky-client::Sp00kySync::processUpEvent"
1945
1904
  }, "processUpEvent unknown event type");
1946
1905
  return;
1947
1906
  }
@@ -1954,7 +1913,7 @@ var SpookySync = class {
1954
1913
  recordId,
1955
1914
  tableName,
1956
1915
  error: error.message,
1957
- Category: "spooky-client::SpookySync::handleRollback"
1916
+ Category: "sp00ky-client::Sp00kySync::handleRollback"
1958
1917
  }, "Rolling back failed mutation");
1959
1918
  switch (event.type) {
1960
1919
  case "create":
@@ -1964,13 +1923,13 @@ var SpookySync = class {
1964
1923
  if (event.beforeRecord) await this.dataModule.rollbackUpdate(event.record_id, tableName, event.beforeRecord);
1965
1924
  else this.logger.warn({
1966
1925
  recordId,
1967
- Category: "spooky-client::SpookySync::handleRollback"
1926
+ Category: "sp00ky-client::Sp00kySync::handleRollback"
1968
1927
  }, "Cannot rollback update: no beforeRecord available. Down-sync will reconcile.");
1969
1928
  break;
1970
1929
  case "delete":
1971
1930
  this.logger.warn({
1972
1931
  recordId,
1973
- Category: "spooky-client::SpookySync::handleRollback"
1932
+ Category: "sp00ky-client::Sp00kySync::handleRollback"
1974
1933
  }, "Delete rollback not implemented. Down-sync will reconcile.");
1975
1934
  break;
1976
1935
  }
@@ -1983,7 +1942,7 @@ var SpookySync = class {
1983
1942
  async processDownEvent(event) {
1984
1943
  this.logger.debug({
1985
1944
  event,
1986
- Category: "spooky-client::SpookySync::processDownEvent"
1945
+ Category: "sp00ky-client::Sp00kySync::processDownEvent"
1987
1946
  }, "Processing down event");
1988
1947
  switch (event.type) {
1989
1948
  case "register": return this.registerQuery(event.payload.hash);
@@ -2002,7 +1961,7 @@ var SpookySync = class {
2002
1961
  if (!queryState) {
2003
1962
  this.logger.warn({
2004
1963
  hash,
2005
- Category: "spooky-client::SpookySync::syncQuery"
1964
+ Category: "sp00ky-client::Sp00kySync::syncQuery"
2006
1965
  }, "Query not found");
2007
1966
  return;
2008
1967
  }
@@ -2021,7 +1980,7 @@ var SpookySync = class {
2021
1980
  try {
2022
1981
  this.logger.debug({
2023
1982
  queryHash,
2024
- Category: "spooky-client::SpookySync::registerQuery"
1983
+ Category: "sp00ky-client::Sp00kySync::registerQuery"
2025
1984
  }, "Register Query state");
2026
1985
  await this.createRemoteQuery(queryHash);
2027
1986
  await this.syncQuery(queryHash);
@@ -2029,7 +1988,7 @@ var SpookySync = class {
2029
1988
  } catch (e) {
2030
1989
  this.logger.error({
2031
1990
  err: e,
2032
- Category: "spooky-client::SpookySync::registerQuery"
1991
+ Category: "sp00ky-client::Sp00kySync::registerQuery"
2033
1992
  }, "registerQuery error");
2034
1993
  throw e;
2035
1994
  }
@@ -2039,7 +1998,7 @@ var SpookySync = class {
2039
1998
  if (!queryState) {
2040
1999
  this.logger.warn({
2041
2000
  queryHash,
2042
- Category: "spooky-client::SpookySync::createRemoteQuery"
2001
+ Category: "sp00ky-client::Sp00kySync::createRemoteQuery"
2043
2002
  }, "Query to register not found");
2044
2003
  throw new Error("Query to register not found");
2045
2004
  }
@@ -2050,17 +2009,17 @@ var SpookySync = class {
2050
2009
  params: queryState.config.params,
2051
2010
  ttl: queryState.config.ttl
2052
2011
  } });
2053
- const [items] = await this.remote.query(surql.selectByFieldsAnd("_spooky_list_ref", ["in"], ["out", "version"]), { in: queryState.config.id });
2012
+ const [items] = await this.remote.query(surql.selectByFieldsAnd("_00_list_ref", ["in"], ["out", "version"]), { in: queryState.config.id });
2054
2013
  this.logger.trace({
2055
2014
  queryId: encodeRecordId(queryState.config.id),
2056
2015
  items,
2057
- Category: "spooky-client::SpookySync::createRemoteQuery"
2016
+ Category: "sp00ky-client::Sp00kySync::createRemoteQuery"
2058
2017
  }, "Got query record version array from remote");
2059
2018
  const array = items.map((item) => [encodeRecordId(item.out), item.version]);
2060
2019
  this.logger.debug({
2061
2020
  queryId: encodeRecordId(queryState.config.id),
2062
2021
  array,
2063
- Category: "spooky-client::SpookySync::createRemoteQuery"
2022
+ Category: "sp00ky-client::Sp00kySync::createRemoteQuery"
2064
2023
  }, "createdRemoteQuery");
2065
2024
  if (array) await this.dataModule.updateQueryRemoteArray(queryHash, array);
2066
2025
  }
@@ -2069,7 +2028,7 @@ var SpookySync = class {
2069
2028
  if (!queryState) {
2070
2029
  this.logger.warn({
2071
2030
  queryHash,
2072
- Category: "spooky-client::SpookySync::heartbeatQuery"
2031
+ Category: "sp00ky-client::Sp00kySync::heartbeatQuery"
2073
2032
  }, "Query to register not found");
2074
2033
  throw new Error("Query to register not found");
2075
2034
  }
@@ -2080,7 +2039,7 @@ var SpookySync = class {
2080
2039
  if (!queryState) {
2081
2040
  this.logger.warn({
2082
2041
  queryHash,
2083
- Category: "spooky-client::SpookySync::cleanupQuery"
2042
+ Category: "sp00ky-client::Sp00kySync::cleanupQuery"
2084
2043
  }, "Query to register not found");
2085
2044
  throw new Error("Query to register not found");
2086
2045
  }
@@ -2112,7 +2071,7 @@ var DevToolsService = class {
2112
2071
  this.authService.eventSystem.subscribe(AuthEventTypes.AuthStateChanged, () => {
2113
2072
  this.notifyDevTools();
2114
2073
  });
2115
- this.logger.debug({ Category: "spooky-client::DevToolsService::init" }, "Service initialized");
2074
+ this.logger.debug({ Category: "sp00ky-client::DevToolsService::init" }, "Service initialized");
2116
2075
  }
2117
2076
  getActiveQueries() {
2118
2077
  const result = /* @__PURE__ */ new Map();
@@ -2138,7 +2097,7 @@ var DevToolsService = class {
2138
2097
  onQueryInitialized(payload) {
2139
2098
  this.logger.debug({
2140
2099
  payload,
2141
- Category: "spooky-client::DevToolsService::onQueryInitialized"
2100
+ Category: "sp00ky-client::DevToolsService::onQueryInitialized"
2142
2101
  }, "QueryInitialized");
2143
2102
  const queryHash = this.hashString(payload.queryId.toString());
2144
2103
  this.addEvent("QUERY_REQUEST_INIT", {
@@ -2151,7 +2110,7 @@ var DevToolsService = class {
2151
2110
  onQueryUpdated(payload) {
2152
2111
  this.logger.debug({
2153
2112
  id: payload.queryId?.toString(),
2154
- Category: "spooky-client::DevToolsService::onQueryUpdated"
2113
+ Category: "sp00ky-client::DevToolsService::onQueryUpdated"
2155
2114
  }, "QueryUpdated");
2156
2115
  const queryHash = this.hashString(payload.queryId.toString());
2157
2116
  this.addEvent("QUERY_UPDATED", {
@@ -2163,7 +2122,7 @@ var DevToolsService = class {
2163
2122
  onStreamUpdate(update) {
2164
2123
  this.logger.debug({
2165
2124
  update,
2166
- Category: "spooky-client::DevToolsService::onStreamUpdate"
2125
+ Category: "sp00ky-client::DevToolsService::onStreamUpdate"
2167
2126
  }, "StreamUpdate");
2168
2127
  this.addEvent("STREAM_UPDATE", { updates: [update] });
2169
2128
  this.notifyDevTools();
@@ -2218,8 +2177,8 @@ var DevToolsService = class {
2218
2177
  }
2219
2178
  notifyDevTools() {
2220
2179
  if (typeof window !== "undefined") window.postMessage({
2221
- type: "SPOOKY_STATE_CHANGED",
2222
- source: "spooky-devtools-page",
2180
+ type: "SP00KY_STATE_CHANGED",
2181
+ source: "sp00ky-devtools-page",
2223
2182
  state: this.getState()
2224
2183
  }, "*");
2225
2184
  }
@@ -2245,7 +2204,7 @@ var DevToolsService = class {
2245
2204
  }
2246
2205
  exposeToWindow() {
2247
2206
  if (typeof window !== "undefined") {
2248
- window.__SPOOKY__ = {
2207
+ window.__00__ = {
2249
2208
  version: this.version,
2250
2209
  getState: () => this.getState(),
2251
2210
  clearHistory: () => {
@@ -2266,7 +2225,7 @@ var DevToolsService = class {
2266
2225
  } catch (e) {
2267
2226
  this.logger.error({
2268
2227
  err: e,
2269
- Category: "spooky-client::DevToolsService::exposeToWindow"
2228
+ Category: "sp00ky-client::DevToolsService::exposeToWindow"
2270
2229
  }, "Failed to get table data");
2271
2230
  return [];
2272
2231
  }
@@ -2298,7 +2257,7 @@ var DevToolsService = class {
2298
2257
  this.logger.debug({
2299
2258
  query,
2300
2259
  target,
2301
- Category: "spooky-client::DevToolsService::runQuery"
2260
+ Category: "sp00ky-client::DevToolsService::runQuery"
2302
2261
  }, "Running query (START)");
2303
2262
  const service = target === "remote" ? this.remoteDatabaseService : this.databaseService;
2304
2263
  const startTime = Date.now();
@@ -2309,7 +2268,7 @@ var DevToolsService = class {
2309
2268
  time: queryTime,
2310
2269
  resultType: typeof result,
2311
2270
  isArray: Array.isArray(result),
2312
- Category: "spooky-client::DevToolsService::runQuery"
2271
+ Category: "sp00ky-client::DevToolsService::runQuery"
2313
2272
  }, "Database returned result");
2314
2273
  const serializeStart = Date.now();
2315
2274
  const serialized = this.serializeForDevTools(result);
@@ -2317,7 +2276,7 @@ var DevToolsService = class {
2317
2276
  this.logger.debug({
2318
2277
  serializeTime,
2319
2278
  serializedLength: JSON.stringify(serialized).length,
2320
- Category: "spooky-client::DevToolsService::runQuery"
2279
+ Category: "sp00ky-client::DevToolsService::runQuery"
2321
2280
  }, "Serialization complete");
2322
2281
  return {
2323
2282
  success: true,
@@ -2329,7 +2288,7 @@ var DevToolsService = class {
2329
2288
  err: e,
2330
2289
  query,
2331
2290
  target,
2332
- Category: "spooky-client::DevToolsService::runQuery"
2291
+ Category: "sp00ky-client::DevToolsService::runQuery"
2333
2292
  }, "Query execution failed");
2334
2293
  return {
2335
2294
  success: false,
@@ -2339,13 +2298,14 @@ var DevToolsService = class {
2339
2298
  }
2340
2299
  };
2341
2300
  window.postMessage({
2342
- type: "SPOOKY_DETECTED",
2343
- source: "spooky-devtools-page",
2301
+ type: "SP00KY_DETECTED",
2302
+ source: "sp00ky-devtools-page",
2344
2303
  data: {
2345
2304
  version: this.version,
2346
2305
  detected: true
2347
2306
  }
2348
2307
  }, "*");
2308
+ window.dispatchEvent(new CustomEvent("sp00ky:init"));
2349
2309
  }
2350
2310
  }
2351
2311
  };
@@ -2396,9 +2356,9 @@ var AuthService = class {
2396
2356
  async check(accessToken) {
2397
2357
  this.isLoading = true;
2398
2358
  try {
2399
- const token = accessToken || await this.persistenceClient.get("spooky_auth_token");
2359
+ const token = accessToken || await this.persistenceClient.get("sp00ky_auth_token");
2400
2360
  if (!token) {
2401
- this.logger.debug({ Category: "spooky-client::AuthService::check" }, "No token found in storage or arguments");
2361
+ this.logger.debug({ Category: "sp00ky-client::AuthService::check" }, "No token found in storage or arguments");
2402
2362
  this.isLoading = false;
2403
2363
  this.isAuthenticated = false;
2404
2364
  this.notifyListeners();
@@ -2411,22 +2371,22 @@ var AuthService = class {
2411
2371
  if (user && user.id) {
2412
2372
  this.logger.info({
2413
2373
  user,
2414
- Category: "spooky-client::AuthService::check"
2374
+ Category: "sp00ky-client::AuthService::check"
2415
2375
  }, "Auth check complete (via $auth.id)");
2416
2376
  await this.setSession(token, user);
2417
2377
  } else {
2418
- this.logger.warn({ Category: "spooky-client::AuthService::check" }, "$auth.id empty, attempting manual user fetch");
2378
+ this.logger.warn({ Category: "sp00ky-client::AuthService::check" }, "$auth.id empty, attempting manual user fetch");
2419
2379
  const manualResult = await this.remote.query("SELECT * FROM user WHERE id = $auth.id LIMIT 1");
2420
2380
  const manualItems = Array.isArray(manualResult) && Array.isArray(manualResult[0]) ? manualResult[0] : manualResult;
2421
2381
  const manualUser = Array.isArray(manualItems) ? manualItems[0] : manualItems;
2422
2382
  if (manualUser && manualUser.id) {
2423
2383
  this.logger.info({
2424
2384
  user: manualUser,
2425
- Category: "spooky-client::AuthService::check"
2385
+ Category: "sp00ky-client::AuthService::check"
2426
2386
  }, "Auth check complete (via manual fetch)");
2427
2387
  await this.setSession(token, manualUser);
2428
2388
  } else {
2429
- this.logger.warn({ Category: "spooky-client::AuthService::check" }, "Token valid but user not found via fallback");
2389
+ this.logger.warn({ Category: "sp00ky-client::AuthService::check" }, "Token valid but user not found via fallback");
2430
2390
  await this.signOut();
2431
2391
  }
2432
2392
  }
@@ -2434,7 +2394,7 @@ var AuthService = class {
2434
2394
  this.logger.error({
2435
2395
  error,
2436
2396
  stack: error.stack,
2437
- Category: "spooky-client::AuthService::check"
2397
+ Category: "sp00ky-client::AuthService::check"
2438
2398
  }, "Auth check failed");
2439
2399
  await this.signOut();
2440
2400
  } finally {
@@ -2448,17 +2408,17 @@ var AuthService = class {
2448
2408
  this.token = null;
2449
2409
  this.currentUser = null;
2450
2410
  this.isAuthenticated = false;
2451
- await this.persistenceClient.remove("spooky_auth_token");
2411
+ await this.persistenceClient.remove("sp00ky_auth_token");
2452
2412
  try {
2453
2413
  await this.remote.getClient().invalidate();
2454
- } catch (e) {}
2414
+ } catch (_e) {}
2455
2415
  this.notifyListeners();
2456
2416
  }
2457
2417
  async setSession(token, user) {
2458
2418
  this.token = token;
2459
2419
  this.currentUser = user;
2460
2420
  this.isAuthenticated = true;
2461
- await this.persistenceClient.set("spooky_auth_token", token);
2421
+ await this.persistenceClient.set("sp00ky_auth_token", token);
2462
2422
  this.notifyListeners();
2463
2423
  }
2464
2424
  async signUp(accessName, params) {
@@ -2470,13 +2430,13 @@ var AuthService = class {
2470
2430
  this.logger.info({
2471
2431
  accessName,
2472
2432
  runtimeParams,
2473
- Category: "spooky-client::AuthService::signUp"
2433
+ Category: "sp00ky-client::AuthService::signUp"
2474
2434
  }, "Attempting signup");
2475
2435
  const { access } = await this.remote.getClient().signup({
2476
2436
  access: accessName,
2477
2437
  variables: runtimeParams
2478
2438
  });
2479
- this.logger.info({ Category: "spooky-client::AuthService::signUp" }, "Signup successful, token received");
2439
+ this.logger.info({ Category: "sp00ky-client::AuthService::signUp" }, "Signup successful, token received");
2480
2440
  await this.check(access);
2481
2441
  }
2482
2442
  async signIn(accessName, params) {
@@ -2487,7 +2447,7 @@ var AuthService = class {
2487
2447
  if (missingParams.length > 0) throw new Error(`Missing required signin params for '${accessName}': ${missingParams.join(", ")}`);
2488
2448
  this.logger.info({
2489
2449
  accessName,
2490
- Category: "spooky-client::AuthService::signIn"
2450
+ Category: "sp00ky-client::AuthService::signIn"
2491
2451
  }, "Attempting signin");
2492
2452
  const { access } = await this.remote.getClient().signin({
2493
2453
  access: accessName,
@@ -2526,17 +2486,17 @@ var StreamProcessorService = class {
2526
2486
  */
2527
2487
  async init() {
2528
2488
  if (this.isInitialized) return;
2529
- this.logger.info({ Category: "spooky-client::StreamProcessorService::init" }, "Initializing WASM...");
2489
+ this.logger.info({ Category: "sp00ky-client::StreamProcessorService::init" }, "Initializing WASM...");
2530
2490
  try {
2531
2491
  await init();
2532
- this.processor = new SpookyProcessor();
2492
+ this.processor = new Sp00kyProcessor();
2533
2493
  await this.loadState();
2534
2494
  this.isInitialized = true;
2535
- this.logger.info({ Category: "spooky-client::StreamProcessorService::init" }, "Initialized successfully");
2495
+ this.logger.info({ Category: "sp00ky-client::StreamProcessorService::init" }, "Initialized successfully");
2536
2496
  } catch (e) {
2537
2497
  this.logger.error({
2538
2498
  error: e,
2539
- Category: "spooky-client::StreamProcessorService::init"
2499
+ Category: "sp00ky-client::StreamProcessorService::init"
2540
2500
  }, "Failed to initialize");
2541
2501
  throw e;
2542
2502
  }
@@ -2544,20 +2504,20 @@ var StreamProcessorService = class {
2544
2504
  async loadState() {
2545
2505
  if (!this.processor) return;
2546
2506
  try {
2547
- const result = await this.persistenceClient.get("_spooky_stream_processor_state");
2507
+ const result = await this.persistenceClient.get("_00_stream_processor_state");
2548
2508
  if (Array.isArray(result) && result.length > 0 && Array.isArray(result[0]) && result[0].length > 0 && result[0][0]?.state) {
2549
2509
  const state = result[0][0].state;
2550
2510
  this.logger.info({
2551
2511
  stateLength: state.length,
2552
- Category: "spooky-client::StreamProcessorService::loadState"
2512
+ Category: "sp00ky-client::StreamProcessorService::loadState"
2553
2513
  }, "Loading state from DB");
2554
2514
  if (typeof this.processor.load_state === "function") this.processor.load_state(state);
2555
- else this.logger.warn({ Category: "spooky-client::StreamProcessorService::loadState" }, "load_state method not found on processor");
2556
- } else this.logger.info({ Category: "spooky-client::StreamProcessorService::loadState" }, "No saved state found");
2515
+ else this.logger.warn({ Category: "sp00ky-client::StreamProcessorService::loadState" }, "load_state method not found on processor");
2516
+ } else this.logger.info({ Category: "sp00ky-client::StreamProcessorService::loadState" }, "No saved state found");
2557
2517
  } catch (e) {
2558
2518
  this.logger.error({
2559
2519
  error: e,
2560
- Category: "spooky-client::StreamProcessorService::loadState"
2520
+ Category: "sp00ky-client::StreamProcessorService::loadState"
2561
2521
  }, "Failed to load state");
2562
2522
  }
2563
2523
  }
@@ -2567,14 +2527,14 @@ var StreamProcessorService = class {
2567
2527
  if (typeof this.processor.save_state === "function") {
2568
2528
  const state = this.processor.save_state();
2569
2529
  if (state) {
2570
- await this.persistenceClient.set("_spooky_stream_processor_state", state);
2571
- this.logger.trace({ Category: "spooky-client::StreamProcessorService::saveState" }, "State saved");
2530
+ await this.persistenceClient.set("_00_stream_processor_state", state);
2531
+ this.logger.trace({ Category: "sp00ky-client::StreamProcessorService::saveState" }, "State saved");
2572
2532
  }
2573
2533
  }
2574
2534
  } catch (e) {
2575
2535
  this.logger.error({
2576
2536
  error: e,
2577
- Category: "spooky-client::StreamProcessorService::saveState"
2537
+ Category: "sp00ky-client::StreamProcessorService::saveState"
2578
2538
  }, "Failed to save state");
2579
2539
  }
2580
2540
  }
@@ -2588,10 +2548,10 @@ var StreamProcessorService = class {
2588
2548
  table,
2589
2549
  op,
2590
2550
  id,
2591
- Category: "spooky-client::StreamProcessorService::ingest"
2551
+ Category: "sp00ky-client::StreamProcessorService::ingest"
2592
2552
  }, "Ingesting into ssp");
2593
2553
  if (!this.processor) {
2594
- this.logger.warn({ Category: "spooky-client::StreamProcessorService::ingest" }, "Not initialized, skipping ingest");
2554
+ this.logger.warn({ Category: "sp00ky-client::StreamProcessorService::ingest" }, "Not initialized, skipping ingest");
2595
2555
  return [];
2596
2556
  }
2597
2557
  try {
@@ -2602,7 +2562,7 @@ var StreamProcessorService = class {
2602
2562
  op,
2603
2563
  id,
2604
2564
  rawUpdates: rawUpdates.length,
2605
- Category: "spooky-client::StreamProcessorService::ingest"
2565
+ Category: "sp00ky-client::StreamProcessorService::ingest"
2606
2566
  }, "Ingesting into ssp done");
2607
2567
  if (rawUpdates && Array.isArray(rawUpdates) && rawUpdates.length > 0) {
2608
2568
  const updates = rawUpdates.map((u) => ({
@@ -2617,7 +2577,7 @@ var StreamProcessorService = class {
2617
2577
  } catch (e) {
2618
2578
  this.logger.error({
2619
2579
  error: e,
2620
- Category: "spooky-client::StreamProcessorService::ingest"
2580
+ Category: "sp00ky-client::StreamProcessorService::ingest"
2621
2581
  }, "Ingesting into ssp failed");
2622
2582
  }
2623
2583
  return [];
@@ -2628,14 +2588,14 @@ var StreamProcessorService = class {
2628
2588
  */
2629
2589
  registerQueryPlan(queryPlan) {
2630
2590
  if (!this.processor) {
2631
- this.logger.warn({ Category: "spooky-client::StreamProcessorService::registerQueryPlan" }, "Not initialized, skipping registration");
2591
+ this.logger.warn({ Category: "sp00ky-client::StreamProcessorService::registerQueryPlan" }, "Not initialized, skipping registration");
2632
2592
  return;
2633
2593
  }
2634
2594
  this.logger.debug({
2635
2595
  queryHash: queryPlan.queryHash,
2636
2596
  surql: queryPlan.surql,
2637
2597
  params: queryPlan.params,
2638
- Category: "spooky-client::StreamProcessorService::registerQueryPlan"
2598
+ Category: "sp00ky-client::StreamProcessorService::registerQueryPlan"
2639
2599
  }, "Registering query plan");
2640
2600
  try {
2641
2601
  const normalizedParams = this.normalizeValue(queryPlan.params);
@@ -2649,7 +2609,7 @@ var StreamProcessorService = class {
2649
2609
  });
2650
2610
  this.logger.debug({
2651
2611
  initialUpdate,
2652
- Category: "spooky-client::StreamProcessorService::registerQueryPlan"
2612
+ Category: "sp00ky-client::StreamProcessorService::registerQueryPlan"
2653
2613
  }, "register_view result");
2654
2614
  if (!initialUpdate) throw new Error("Failed to register query plan");
2655
2615
  const update = {
@@ -2661,13 +2621,13 @@ var StreamProcessorService = class {
2661
2621
  queryHash: queryPlan.queryHash,
2662
2622
  surql: queryPlan.surql,
2663
2623
  params: queryPlan.params,
2664
- Category: "spooky-client::StreamProcessorService::registerQueryPlan"
2624
+ Category: "sp00ky-client::StreamProcessorService::registerQueryPlan"
2665
2625
  }, "Registered query plan");
2666
2626
  return update;
2667
2627
  } catch (e) {
2668
2628
  this.logger.error({
2669
2629
  error: e,
2670
- Category: "spooky-client::StreamProcessorService::registerQueryPlan"
2630
+ Category: "sp00ky-client::StreamProcessorService::registerQueryPlan"
2671
2631
  }, "Error registering query plan");
2672
2632
  throw e;
2673
2633
  }
@@ -2683,7 +2643,7 @@ var StreamProcessorService = class {
2683
2643
  } catch (e) {
2684
2644
  this.logger.error({
2685
2645
  error: e,
2686
- Category: "spooky-client::StreamProcessorService::unregisterQueryPlan"
2646
+ Category: "sp00ky-client::StreamProcessorService::unregisterQueryPlan"
2687
2647
  }, "Error unregistering query plan");
2688
2648
  }
2689
2649
  }
@@ -2698,7 +2658,7 @@ var StreamProcessorService = class {
2698
2658
  const result = value.toString();
2699
2659
  this.logger.trace({
2700
2660
  result,
2701
- Category: "spooky-client::StreamProcessorService::normalizeValue"
2661
+ Category: "sp00ky-client::StreamProcessorService::normalizeValue"
2702
2662
  }, "RecordId detected");
2703
2663
  return result;
2704
2664
  }
@@ -2741,7 +2701,7 @@ var CacheModule = class {
2741
2701
  this.logger.debug({
2742
2702
  queryHash: update.queryHash,
2743
2703
  arrayLength: update.localArray?.length,
2744
- Category: "spooky-client::CacheModule::onStreamUpdate"
2704
+ Category: "sp00ky-client::CacheModule::onStreamUpdate"
2745
2705
  }, "Stream update received");
2746
2706
  this.streamUpdateCallback(update);
2747
2707
  }
@@ -2764,7 +2724,7 @@ var CacheModule = class {
2764
2724
  if (records.length === 0) return;
2765
2725
  this.logger.debug({
2766
2726
  count: records.length,
2767
- Category: "spooky-client::CacheModule::saveBatch"
2727
+ Category: "sp00ky-client::CacheModule::saveBatch"
2768
2728
  }, "Saving record batch");
2769
2729
  try {
2770
2730
  const populatedRecords = records.map((record) => {
@@ -2773,7 +2733,7 @@ var CacheModule = class {
2773
2733
  ...record,
2774
2734
  record: {
2775
2735
  ...record.record,
2776
- spooky_rv: record.version
2736
+ _00_rv: record.version
2777
2737
  }
2778
2738
  };
2779
2739
  });
@@ -2798,13 +2758,13 @@ var CacheModule = class {
2798
2758
  }
2799
2759
  this.logger.debug({
2800
2760
  count: records.length,
2801
- Category: "spooky-client::CacheModule::saveBatch"
2761
+ Category: "sp00ky-client::CacheModule::saveBatch"
2802
2762
  }, "Batch saved successfully");
2803
2763
  } catch (err) {
2804
2764
  this.logger.error({
2805
2765
  err,
2806
2766
  count: records.length,
2807
- Category: "spooky-client::CacheModule::saveBatch"
2767
+ Category: "sp00ky-client::CacheModule::saveBatch"
2808
2768
  }, "Failed to save batch");
2809
2769
  throw err;
2810
2770
  }
@@ -2812,27 +2772,27 @@ var CacheModule = class {
2812
2772
  /**
2813
2773
  * Delete a record from local DB and ingest deletion into DBSP
2814
2774
  */
2815
- async delete(table, id, skipDbDelete = false) {
2775
+ async delete(table, id, skipDbDelete = false, recordData = {}) {
2816
2776
  this.logger.debug({
2817
2777
  table,
2818
2778
  id,
2819
- Category: "spooky-client::CacheModule::delete"
2779
+ Category: "sp00ky-client::CacheModule::delete"
2820
2780
  }, "Deleting record");
2821
2781
  try {
2822
2782
  if (!skipDbDelete) await this.local.query("DELETE $id", { id: parseRecordIdString(id) });
2823
2783
  delete this.versionLookups[id];
2824
- await this.streamProcessor.ingest(table, "DELETE", id, {});
2784
+ this.streamProcessor.ingest(table, "DELETE", id, recordData);
2825
2785
  this.logger.debug({
2826
2786
  table,
2827
2787
  id,
2828
- Category: "spooky-client::CacheModule::delete"
2788
+ Category: "sp00ky-client::CacheModule::delete"
2829
2789
  }, "Record deleted successfully");
2830
2790
  } catch (err) {
2831
2791
  this.logger.error({
2832
2792
  err,
2833
2793
  table,
2834
2794
  id,
2835
- Category: "spooky-client::CacheModule::delete"
2795
+ Category: "sp00ky-client::CacheModule::delete"
2836
2796
  }, "Failed to delete record");
2837
2797
  throw err;
2838
2798
  }
@@ -2845,7 +2805,7 @@ var CacheModule = class {
2845
2805
  this.logger.debug({
2846
2806
  queryHash: config.queryHash,
2847
2807
  surql: config.surql,
2848
- Category: "spooky-client::CacheModule::registerQuery"
2808
+ Category: "sp00ky-client::CacheModule::registerQuery"
2849
2809
  }, "Registering query");
2850
2810
  try {
2851
2811
  const update = this.streamProcessor.registerQueryPlan({
@@ -2862,14 +2822,14 @@ var CacheModule = class {
2862
2822
  this.logger.debug({
2863
2823
  queryHash: config.queryHash,
2864
2824
  arrayLength: update.localArray?.length,
2865
- Category: "spooky-client::CacheModule::registerQuery"
2825
+ Category: "sp00ky-client::CacheModule::registerQuery"
2866
2826
  }, "Query registered successfully");
2867
2827
  return { localArray: update.localArray };
2868
2828
  } catch (err) {
2869
2829
  this.logger.error({
2870
2830
  err,
2871
2831
  queryHash: config.queryHash,
2872
- Category: "spooky-client::CacheModule::registerQuery"
2832
+ Category: "sp00ky-client::CacheModule::registerQuery"
2873
2833
  }, "Failed to register query");
2874
2834
  throw err;
2875
2835
  }
@@ -2880,24 +2840,323 @@ var CacheModule = class {
2880
2840
  unregisterQuery(queryHash) {
2881
2841
  this.logger.debug({
2882
2842
  queryHash,
2883
- Category: "spooky-client::CacheModule::unregisterQuery"
2843
+ Category: "sp00ky-client::CacheModule::unregisterQuery"
2884
2844
  }, "Unregistering query");
2885
2845
  try {
2886
2846
  this.streamProcessor.unregisterQueryPlan(queryHash);
2887
2847
  this.logger.debug({
2888
2848
  queryHash,
2889
- Category: "spooky-client::CacheModule::unregisterQuery"
2849
+ Category: "sp00ky-client::CacheModule::unregisterQuery"
2890
2850
  }, "Query unregistered successfully");
2891
2851
  } catch (err) {
2892
2852
  this.logger.error({
2893
2853
  err,
2894
2854
  queryHash,
2895
- Category: "spooky-client::CacheModule::unregisterQuery"
2855
+ Category: "sp00ky-client::CacheModule::unregisterQuery"
2896
2856
  }, "Failed to unregister query");
2897
2857
  }
2898
2858
  }
2899
2859
  };
2900
2860
 
2861
+ //#endregion
2862
+ //#region src/modules/crdt/crdt-field.ts
2863
+ const CURSOR_COLORS = [
2864
+ "#3b82f6",
2865
+ "#ef4444",
2866
+ "#22c55e",
2867
+ "#f59e0b",
2868
+ "#8b5cf6",
2869
+ "#ec4899",
2870
+ "#14b8a6",
2871
+ "#f97316"
2872
+ ];
2873
+ function cursorColorFromName(name) {
2874
+ let hash = 0;
2875
+ for (let i = 0; i < name.length; i++) hash = (hash << 5) - hash + name.charCodeAt(i) | 0;
2876
+ return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];
2877
+ }
2878
+ var CrdtField = class {
2879
+ doc;
2880
+ pushTimer = null;
2881
+ remote = null;
2882
+ recordId = null;
2883
+ unsubscribe = null;
2884
+ lastPushTime = 0;
2885
+ lastCursorPushTime = 0;
2886
+ loadedFromCrdt = false;
2887
+ pushRetryCount = 0;
2888
+ logger;
2889
+ _onCursorUpdate = null;
2890
+ pendingCursorUpdate = null;
2891
+ /** Callback set by the editor to receive remote cursor updates.
2892
+ * Any cursor data that arrived before this callback was set will be replayed. */
2893
+ set onCursorUpdate(cb) {
2894
+ this._onCursorUpdate = cb;
2895
+ if (cb && this.pendingCursorUpdate) {
2896
+ try {
2897
+ cb(this.pendingCursorUpdate);
2898
+ } catch (e) {
2899
+ this.logger?.warn({
2900
+ error: e,
2901
+ Category: "sp00ky-client::CrdtField::onCursorUpdate"
2902
+ }, "Failed to replay pending cursor update");
2903
+ }
2904
+ this.pendingCursorUpdate = null;
2905
+ }
2906
+ }
2907
+ get onCursorUpdate() {
2908
+ return this._onCursorUpdate;
2909
+ }
2910
+ constructor(fieldName, initialState, logger) {
2911
+ this.fieldName = fieldName;
2912
+ this.logger = logger ?? null;
2913
+ this.doc = new LoroDoc();
2914
+ if (initialState) {
2915
+ this.doc.import(decodeBase64(initialState));
2916
+ this.loadedFromCrdt = true;
2917
+ }
2918
+ }
2919
+ getDoc() {
2920
+ return this.doc;
2921
+ }
2922
+ /** Whether the LoroDoc was loaded from saved CRDT state */
2923
+ hasContent() {
2924
+ return this.loadedFromCrdt;
2925
+ }
2926
+ startSync(remote, recordId) {
2927
+ this.remote = remote;
2928
+ this.recordId = recordId;
2929
+ this.unsubscribe = this.doc.subscribeLocalUpdates(() => {
2930
+ this.schedulePush();
2931
+ });
2932
+ }
2933
+ stopSync() {
2934
+ if (this.unsubscribe) {
2935
+ this.unsubscribe();
2936
+ this.unsubscribe = null;
2937
+ }
2938
+ if (this.pushTimer) {
2939
+ clearTimeout(this.pushTimer);
2940
+ this.pushTimer = null;
2941
+ }
2942
+ if (this.remote && this.recordId) this.pushToRemote();
2943
+ }
2944
+ importRemote(base64State) {
2945
+ if (Date.now() - this.lastPushTime < 500) return;
2946
+ try {
2947
+ this.doc.import(decodeBase64(base64State));
2948
+ } catch (e) {
2949
+ this.logger?.warn({
2950
+ error: e,
2951
+ Category: "sp00ky-client::CrdtField::importRemote"
2952
+ }, "Failed to import remote CRDT state");
2953
+ }
2954
+ }
2955
+ exportSnapshot() {
2956
+ return encodeBase64(this.doc.export({ mode: "snapshot" }));
2957
+ }
2958
+ /** Push cursor ephemeral state to _00_crdt as a "_cursor_<fieldName>" entry */
2959
+ async pushCursorState(encoded) {
2960
+ if (!this.remote || !this.recordId) return;
2961
+ this.lastCursorPushTime = Date.now();
2962
+ try {
2963
+ const state = encodeBase64(encoded);
2964
+ await this.remote.query(`INSERT INTO _00_crdt (record_id, field, state) VALUES ($rid, $field, $state)
2965
+ ON DUPLICATE KEY UPDATE state = $state`, {
2966
+ rid: parseRecordIdString(this.recordId),
2967
+ field: `_cursor_${this.fieldName}`,
2968
+ state
2969
+ });
2970
+ } catch (e) {
2971
+ this.logger?.warn({
2972
+ error: e,
2973
+ Category: "sp00ky-client::CrdtField::pushCursorState"
2974
+ }, "Failed to push cursor state");
2975
+ }
2976
+ }
2977
+ /** Import remote cursor state (called by CrdtManager from LIVE SELECT) */
2978
+ importRemoteCursor(base64State) {
2979
+ if (Date.now() - this.lastCursorPushTime < 300) return;
2980
+ try {
2981
+ const data = decodeBase64(base64State);
2982
+ if (this._onCursorUpdate) this._onCursorUpdate(data);
2983
+ else this.pendingCursorUpdate = data;
2984
+ } catch (e) {
2985
+ this.logger?.warn({
2986
+ error: e,
2987
+ Category: "sp00ky-client::CrdtField::importRemoteCursor"
2988
+ }, "Failed to apply remote cursor data");
2989
+ }
2990
+ }
2991
+ schedulePush() {
2992
+ if (this.pushTimer) clearTimeout(this.pushTimer);
2993
+ this.pushTimer = setTimeout(() => void this.pushToRemote(), 300);
2994
+ }
2995
+ async pushToRemote() {
2996
+ if (!this.remote || !this.recordId) return;
2997
+ this.lastPushTime = Date.now();
2998
+ try {
2999
+ await this.remote.query(`INSERT INTO _00_crdt (record_id, field, state) VALUES ($rid, $field, $state)
3000
+ ON DUPLICATE KEY UPDATE state = $state`, {
3001
+ rid: parseRecordIdString(this.recordId),
3002
+ field: this.fieldName,
3003
+ state: this.exportSnapshot()
3004
+ });
3005
+ this.pushRetryCount = 0;
3006
+ } catch (e) {
3007
+ this.logger?.warn({
3008
+ error: e,
3009
+ Category: "sp00ky-client::CrdtField::pushToRemote"
3010
+ }, "Failed to push CRDT state to remote");
3011
+ if (this.pushRetryCount < 2) {
3012
+ this.pushRetryCount++;
3013
+ this.schedulePush();
3014
+ }
3015
+ }
3016
+ }
3017
+ };
3018
+ function decodeBase64(b64) {
3019
+ if (typeof atob === "function") {
3020
+ const binary = atob(b64);
3021
+ const bytes = new Uint8Array(binary.length);
3022
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
3023
+ return bytes;
3024
+ }
3025
+ return new Uint8Array(Buffer.from(b64, "base64"));
3026
+ }
3027
+ function encodeBase64(bytes) {
3028
+ if (typeof btoa === "function") {
3029
+ let binary = "";
3030
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
3031
+ return btoa(binary);
3032
+ }
3033
+ return Buffer.from(bytes).toString("base64");
3034
+ }
3035
+
3036
+ //#endregion
3037
+ //#region src/modules/crdt/index.ts
3038
+ /**
3039
+ * CrdtManager manages active CrdtField instances and their sync channels.
3040
+ *
3041
+ * Each open record gets a LIVE SELECT on _00_crdt that delivers remote
3042
+ * changes in real time.
3043
+ */
3044
+ var CrdtManager = class {
3045
+ fields = /* @__PURE__ */ new Map();
3046
+ liveQueries = /* @__PURE__ */ new Map();
3047
+ logger;
3048
+ constructor(schema, remote, logger) {
3049
+ this.schema = schema;
3050
+ this.remote = remote;
3051
+ this.logger = logger.child({ service: "CrdtManager" });
3052
+ }
3053
+ /**
3054
+ * Open a CRDT field for collaborative editing.
3055
+ *
3056
+ * @param table - Table name
3057
+ * @param recordId - Full record ID (e.g., "thread:abc")
3058
+ * @param field - Field name (e.g., "title", "content")
3059
+ * @param fallbackText - Current plain text from the record, used to seed the
3060
+ * LoroDoc if no CRDT state exists yet (migration path)
3061
+ */
3062
+ async open(table, recordId, field, fallbackText) {
3063
+ const key = this.makeKey(table, recordId, field);
3064
+ let crdtField = this.fields.get(key);
3065
+ if (crdtField) return crdtField;
3066
+ let initialCrdtState;
3067
+ try {
3068
+ const [result] = await this.remote.query("SELECT VALUE state FROM _00_crdt WHERE record_id = $rid AND field = $field LIMIT 1", {
3069
+ rid: parseRecordIdString(recordId),
3070
+ field
3071
+ });
3072
+ if (result && result.length > 0 && result[0]) initialCrdtState = result[0];
3073
+ } catch (e) {
3074
+ this.logger.debug({
3075
+ error: e,
3076
+ Category: "sp00ky-client::CrdtManager::open"
3077
+ }, "No existing CRDT state found");
3078
+ }
3079
+ crdtField = new CrdtField(field, initialCrdtState, this.logger);
3080
+ crdtField.startSync(this.remote, recordId);
3081
+ this.fields.set(key, crdtField);
3082
+ this.logger.info({
3083
+ key,
3084
+ hasInitialState: !!initialCrdtState,
3085
+ hasFallback: !!fallbackText,
3086
+ Category: "sp00ky-client::CrdtManager::open"
3087
+ }, "CrdtField opened");
3088
+ await this.ensureLiveSelect(table, recordId);
3089
+ return crdtField;
3090
+ }
3091
+ close(table, recordId, field) {
3092
+ const key = this.makeKey(table, recordId, field);
3093
+ const crdtField = this.fields.get(key);
3094
+ if (crdtField) {
3095
+ crdtField.stopSync();
3096
+ this.fields.delete(key);
3097
+ }
3098
+ const prefix = `${table}:${recordId}:`;
3099
+ if (!Array.from(this.fields.keys()).some((k) => k !== key && k.startsWith(prefix))) this.killLiveSelect(recordId);
3100
+ this.logger.debug({
3101
+ key,
3102
+ Category: "sp00ky-client::CrdtManager::close"
3103
+ }, "CrdtField closed");
3104
+ }
3105
+ closeAll() {
3106
+ for (const [_, field] of this.fields) field.stopSync();
3107
+ this.fields.clear();
3108
+ for (const recordId of this.liveQueries.keys()) this.killLiveSelect(recordId);
3109
+ }
3110
+ async ensureLiveSelect(table, recordId) {
3111
+ if (this.liveQueries.has(recordId)) return;
3112
+ try {
3113
+ const [uuid] = await this.remote.query(`LIVE SELECT * FROM _00_crdt WHERE record_id = $rid`, { rid: parseRecordIdString(recordId) });
3114
+ this.liveQueries.set(recordId, {
3115
+ uuid,
3116
+ table
3117
+ });
3118
+ (await this.remote.getClient().liveOf(uuid)).subscribe((message) => {
3119
+ if (message.action === "KILLED") return;
3120
+ if (message.action === "CREATE" || message.action === "UPDATE") {
3121
+ const fieldName = message.value.field;
3122
+ const state = message.value.state;
3123
+ if (!fieldName || !state) return;
3124
+ if (fieldName.startsWith("_cursor_")) {
3125
+ const actualField = fieldName.slice(8);
3126
+ const key = this.makeKey(table, recordId, actualField);
3127
+ const crdtField = this.fields.get(key);
3128
+ if (crdtField) crdtField.importRemoteCursor(state);
3129
+ return;
3130
+ }
3131
+ const key = this.makeKey(table, recordId, fieldName);
3132
+ const crdtField = this.fields.get(key);
3133
+ if (crdtField) crdtField.importRemote(state);
3134
+ }
3135
+ });
3136
+ this.logger.info({
3137
+ recordId,
3138
+ Category: "sp00ky-client::CrdtManager::ensureLiveSelect"
3139
+ }, "LIVE SELECT on _00_crdt started");
3140
+ } catch (e) {
3141
+ this.logger.warn({
3142
+ error: e,
3143
+ recordId,
3144
+ Category: "sp00ky-client::CrdtManager::ensureLiveSelect"
3145
+ }, "Failed to start LIVE SELECT on _00_crdt");
3146
+ }
3147
+ }
3148
+ killLiveSelect(recordId) {
3149
+ const entry = this.liveQueries.get(recordId);
3150
+ if (entry) {
3151
+ this.remote.query("KILL $uuid", { uuid: entry.uuid }).catch(() => {});
3152
+ this.liveQueries.delete(recordId);
3153
+ }
3154
+ }
3155
+ makeKey(table, recordId, field) {
3156
+ return `${table}:${recordId}:${field}`;
3157
+ }
3158
+ };
3159
+
2901
3160
  //#endregion
2902
3161
  //#region src/services/persistence/localstorage.ts
2903
3162
  var LocalStoragePersistenceClient = class {
@@ -2930,7 +3189,7 @@ var SurrealDBPersistenceClient = class {
2930
3189
  }
2931
3190
  async set(key, val) {
2932
3191
  try {
2933
- const id = parseRecordIdString(`_spooky_kv:${key}`);
3192
+ const id = parseRecordIdString(`_00_kv:${key}`);
2934
3193
  await this.db.query(surql.seal(surql.upsert("id", "data")), {
2935
3194
  id,
2936
3195
  data: { val }
@@ -2938,40 +3197,69 @@ var SurrealDBPersistenceClient = class {
2938
3197
  } catch (error) {
2939
3198
  this.logger.error({
2940
3199
  error,
2941
- Category: "spooky-client::SurrealDBPersistenceClient::set"
3200
+ Category: "sp00ky-client::SurrealDBPersistenceClient::set"
2942
3201
  }, "Failed to set KV");
2943
3202
  throw error;
2944
3203
  }
2945
3204
  }
2946
3205
  async get(key) {
2947
3206
  try {
2948
- const id = parseRecordIdString(`_spooky_kv:${key}`);
3207
+ const id = parseRecordIdString(`_00_kv:${key}`);
2949
3208
  const [result] = await this.db.query(surql.seal(surql.selectById("id", ["val"])), { id });
2950
3209
  if (!result?.val) return null;
2951
3210
  return result.val;
2952
3211
  } catch (error) {
2953
3212
  this.logger.warn({
2954
3213
  error,
2955
- Category: "spooky-client::SurrealDBPersistenceClient::get"
3214
+ Category: "sp00ky-client::SurrealDBPersistenceClient::get"
2956
3215
  }, "Failed to get KV");
2957
3216
  return null;
2958
3217
  }
2959
3218
  }
2960
3219
  async remove(key) {
2961
3220
  try {
2962
- const id = parseRecordIdString(`_spooky_kv:${key}`);
3221
+ const id = parseRecordIdString(`_00_kv:${key}`);
2963
3222
  await this.db.query(surql.seal(surql.delete("id")), { id });
2964
3223
  } catch (err) {
2965
3224
  this.logger.info({
2966
3225
  err,
2967
- Category: "spooky-client::SurrealDBPersistenceClient::remove"
3226
+ Category: "sp00ky-client::SurrealDBPersistenceClient::remove"
2968
3227
  }, "Failed to delete KV");
2969
3228
  }
2970
3229
  }
2971
3230
  };
2972
3231
 
2973
3232
  //#endregion
2974
- //#region src/spooky.ts
3233
+ //#region src/services/persistence/resilient.ts
3234
+ var ResilientPersistenceClient = class {
3235
+ logger;
3236
+ constructor(inner, logger) {
3237
+ this.inner = inner;
3238
+ this.logger = logger.child({ service: "ResilientPersistenceClient" });
3239
+ }
3240
+ set(key, value) {
3241
+ return this.inner.set(key, value);
3242
+ }
3243
+ async get(key) {
3244
+ try {
3245
+ return await this.inner.get(key);
3246
+ } catch (e) {
3247
+ this.logger.warn({
3248
+ key,
3249
+ error: e,
3250
+ Category: "sp00ky-client::ResilientPersistenceClient::get"
3251
+ }, "Persistence read failed, dropping key");
3252
+ await this.inner.remove(key).catch(() => {});
3253
+ return null;
3254
+ }
3255
+ }
3256
+ remove(key) {
3257
+ return this.inner.remove(key);
3258
+ }
3259
+ };
3260
+
3261
+ //#endregion
3262
+ //#region src/sp00ky.ts
2975
3263
  var BucketHandle = class {
2976
3264
  constructor(bucketName, remote) {
2977
3265
  this.bucketName = bucketName;
@@ -3007,7 +3295,7 @@ var BucketHandle = class {
3007
3295
  return result;
3008
3296
  }
3009
3297
  };
3010
- var SpookyClient = class {
3298
+ var Sp00kyClient = class {
3011
3299
  local;
3012
3300
  remote;
3013
3301
  persistenceClient;
@@ -3016,6 +3304,7 @@ var SpookyClient = class {
3016
3304
  dataModule;
3017
3305
  sync;
3018
3306
  devTools;
3307
+ crdtManager;
3019
3308
  logger;
3020
3309
  auth;
3021
3310
  streamProcessor;
@@ -3033,28 +3322,30 @@ var SpookyClient = class {
3033
3322
  }
3034
3323
  constructor(config) {
3035
3324
  this.config = config;
3036
- const logger = createLogger(config.logLevel ?? "info", config.otelEndpoint);
3037
- this.logger = logger.child({ service: "SpookyClient" });
3325
+ const logger = createLogger(config.logLevel ?? "info", config.otelTransmit);
3326
+ this.logger = logger.child({ service: "Sp00kyClient" });
3038
3327
  this.logger.info({
3039
3328
  config: {
3040
3329
  ...config,
3041
3330
  schema: "[SchemaStructure]"
3042
3331
  },
3043
- Category: "spooky-client::SpookyClient::constructor"
3044
- }, "SpookyClient initialized");
3332
+ Category: "sp00ky-client::Sp00kyClient::constructor"
3333
+ }, "Sp00kyClient initialized");
3045
3334
  this.local = new LocalDatabaseService(this.config.database, logger);
3046
3335
  this.remote = new RemoteDatabaseService(this.config.database, logger);
3047
3336
  if (config.persistenceClient === "surrealdb") this.persistenceClient = new SurrealDBPersistenceClient(this.local, logger);
3048
3337
  else if (config.persistenceClient === "localstorage" || !config.persistenceClient) this.persistenceClient = new LocalStoragePersistenceClient(logger);
3049
3338
  else this.persistenceClient = config.persistenceClient;
3339
+ this.persistenceClient = new ResilientPersistenceClient(this.persistenceClient, logger);
3050
3340
  this.streamProcessor = new StreamProcessorService(new EventSystem(["stream_update"]), this.local, this.persistenceClient, logger);
3051
3341
  this.migrator = new LocalMigrator(this.local, logger);
3052
3342
  this.cache = new CacheModule(this.local, this.streamProcessor, (update) => {
3053
3343
  this.dataModule.onStreamUpdate(update);
3054
3344
  }, logger);
3345
+ this.crdtManager = new CrdtManager(this.config.schema, this.remote, logger);
3055
3346
  this.dataModule = new DataModule(this.cache, this.local, this.config.schema, logger, this.config.streamDebounceTime);
3056
3347
  this.auth = new AuthService(this.config.schema, this.remote, this.persistenceClient, logger);
3057
- this.sync = new SpookySync(this.local, this.remote, this.cache, this.dataModule, this.config.schema, this.logger);
3348
+ this.sync = new Sp00kySync(this.local, this.remote, this.cache, this.dataModule, this.config.schema, this.logger);
3058
3349
  this.devTools = new DevToolsService(this.local, this.remote, logger, this.config.schema, this.auth, this.dataModule);
3059
3350
  this.streamProcessor.addReceiver(this.devTools);
3060
3351
  this.setupCallbacks();
@@ -3078,44 +3369,59 @@ var SpookyClient = class {
3078
3369
  });
3079
3370
  }
3080
3371
  async init() {
3081
- this.logger.info({ Category: "spooky-client::SpookyClient::init" }, "SpookyClient initialization started");
3372
+ this.logger.info({ Category: "sp00ky-client::Sp00kyClient::init" }, "Sp00kyClient initialization started");
3082
3373
  try {
3083
3374
  const clientId = this.config.clientId ?? await this.loadOrGenerateClientId();
3084
3375
  this.persistClientId(clientId);
3085
3376
  this.logger.debug({
3086
3377
  clientId,
3087
- Category: "spooky-client::SpookyClient::init"
3378
+ Category: "sp00ky-client::Sp00kyClient::init"
3088
3379
  }, "Client ID loaded");
3089
3380
  await this.local.connect();
3090
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "Local database connected");
3381
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "Local database connected");
3091
3382
  await this.migrator.provision(this.config.schemaSurql);
3092
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "Schema provisioned");
3383
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "Schema provisioned");
3093
3384
  await this.remote.connect();
3094
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "Remote database connected");
3385
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "Remote database connected");
3095
3386
  await this.streamProcessor.init();
3096
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "StreamProcessor initialized");
3387
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "StreamProcessor initialized");
3097
3388
  await this.auth.init();
3098
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "Auth initialized");
3389
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "Auth initialized");
3099
3390
  await this.dataModule.init();
3100
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "DataModule initialized");
3391
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "DataModule initialized");
3101
3392
  await this.sync.init(clientId);
3102
- this.logger.debug({ Category: "spooky-client::SpookyClient::init" }, "Sync initialized");
3103
- this.logger.info({ Category: "spooky-client::SpookyClient::init" }, "SpookyClient initialization completed successfully");
3393
+ this.logger.debug({ Category: "sp00ky-client::Sp00kyClient::init" }, "Sync initialized");
3394
+ this.logger.info({ Category: "sp00ky-client::Sp00kyClient::init" }, "Sp00kyClient initialization completed successfully");
3104
3395
  } catch (e) {
3105
3396
  this.logger.error({
3106
3397
  error: e,
3107
- Category: "spooky-client::SpookyClient::init"
3108
- }, "SpookyClient initialization failed");
3398
+ Category: "sp00ky-client::Sp00kyClient::init"
3399
+ }, "Sp00kyClient initialization failed");
3109
3400
  throw e;
3110
3401
  }
3111
3402
  }
3112
3403
  async close() {
3404
+ this.crdtManager.closeAll();
3113
3405
  await this.local.close();
3114
3406
  await this.remote.close();
3115
3407
  }
3116
3408
  authenticate(token) {
3117
3409
  return this.remote.getClient().authenticate(token);
3118
3410
  }
3411
+ /**
3412
+ * Open a CRDT field for collaborative editing.
3413
+ * Returns a CrdtField with a LoroDoc that can be bound to any editor.
3414
+ * Also starts a LIVE SELECT on _00_crdt for real-time sync.
3415
+ */
3416
+ async openCrdtField(table, recordId, field, fallbackText) {
3417
+ return this.crdtManager.open(table, recordId, field, fallbackText);
3418
+ }
3419
+ /**
3420
+ * Close a CRDT field when editing is done.
3421
+ */
3422
+ closeCrdtField(table, recordId, field) {
3423
+ this.crdtManager.close(table, recordId, field);
3424
+ }
3119
3425
  deauthenticate() {
3120
3426
  return this.remote.getClient().invalidate();
3121
3427
  }
@@ -3159,16 +3465,16 @@ var SpookyClient = class {
3159
3465
  }
3160
3466
  persistClientId(id) {
3161
3467
  try {
3162
- this.persistenceClient.set("spooky_client_id", id);
3468
+ this.persistenceClient.set("sp00ky_client_id", id);
3163
3469
  } catch (e) {
3164
3470
  this.logger.warn({
3165
3471
  error: e,
3166
- Category: "spooky-client::SpookyClient::persistClientId"
3472
+ Category: "sp00ky-client::Sp00kyClient::persistClientId"
3167
3473
  }, "Failed to persist client ID");
3168
3474
  }
3169
3475
  }
3170
3476
  async loadOrGenerateClientId() {
3171
- const clientId = await this.persistenceClient.get("spooky_client_id");
3477
+ const clientId = await this.persistenceClient.get("sp00ky_client_id");
3172
3478
  if (clientId) return clientId;
3173
3479
  const newId = generateId();
3174
3480
  await this.persistClientId(newId);
@@ -3177,4 +3483,4 @@ var SpookyClient = class {
3177
3483
  };
3178
3484
 
3179
3485
  //#endregion
3180
- export { AuthEventTypes, AuthService, BucketHandle, SpookyClient, createAuthEventSystem, fileToUint8Array };
3486
+ export { AuthEventTypes, AuthService, BucketHandle, CURSOR_COLORS, CrdtField, CrdtManager, Sp00kyClient, createAuthEventSystem, cursorColorFromName, fileToUint8Array, textToHtml };