@spooky-sync/core 0.0.1-canary.13 → 0.0.1-canary.131

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 (90) hide show
  1. package/AGENTS.md +56 -0
  2. package/dist/index.d.ts +1171 -372
  3. package/dist/index.js +5726 -1277
  4. package/dist/otel/index.d.ts +21 -0
  5. package/dist/otel/index.js +86 -0
  6. package/dist/sqlite-worker.d.ts +1 -0
  7. package/dist/sqlite-worker.js +107 -0
  8. package/dist/types.d.ts +746 -0
  9. package/package.json +39 -8
  10. package/skills/sp00ky-core/SKILL.md +258 -0
  11. package/skills/sp00ky-core/references/auth.md +98 -0
  12. package/skills/sp00ky-core/references/config.md +76 -0
  13. package/src/build-globals.d.ts +12 -0
  14. package/src/events/events.test.ts +2 -1
  15. package/src/events/index.ts +3 -0
  16. package/src/index.ts +9 -2
  17. package/src/modules/auth/events/index.ts +2 -1
  18. package/src/modules/auth/index.ts +59 -20
  19. package/src/modules/cache/index.ts +77 -32
  20. package/src/modules/cache/types.ts +2 -2
  21. package/src/modules/crdt/crdt-field.ts +288 -0
  22. package/src/modules/crdt/crdt-hydration.test.ts +206 -0
  23. package/src/modules/crdt/index.ts +357 -0
  24. package/src/modules/data/data.hydration.test.ts +150 -0
  25. package/src/modules/data/data.rebind.test.ts +147 -0
  26. package/src/modules/data/data.recurring.test.ts +137 -0
  27. package/src/modules/data/data.status.test.ts +249 -0
  28. package/src/modules/data/index.ts +1234 -127
  29. package/src/modules/data/window-query.test.ts +52 -0
  30. package/src/modules/data/window-query.ts +154 -0
  31. package/src/modules/devtools/index.ts +191 -30
  32. package/src/modules/devtools/versions.test.ts +74 -0
  33. package/src/modules/devtools/versions.ts +81 -0
  34. package/src/modules/feature-flag/index.test.ts +120 -0
  35. package/src/modules/feature-flag/index.ts +209 -0
  36. package/src/modules/ref-tables.test.ts +91 -0
  37. package/src/modules/ref-tables.ts +88 -0
  38. package/src/modules/sync/engine.ts +101 -37
  39. package/src/modules/sync/events/index.ts +9 -2
  40. package/src/modules/sync/queue/queue-down.ts +12 -5
  41. package/src/modules/sync/queue/queue-up.ts +29 -14
  42. package/src/modules/sync/scheduler.pause.test.ts +109 -0
  43. package/src/modules/sync/scheduler.ts +73 -7
  44. package/src/modules/sync/sync.health.test.ts +149 -0
  45. package/src/modules/sync/sync.subquery.test.ts +82 -0
  46. package/src/modules/sync/sync.ts +1017 -62
  47. package/src/modules/sync/utils.test.ts +269 -2
  48. package/src/modules/sync/utils.ts +182 -17
  49. package/src/otel/index.ts +127 -0
  50. package/src/services/database/cache-engine.ts +143 -0
  51. package/src/services/database/database.ts +11 -11
  52. package/src/services/database/engine-factory.ts +32 -0
  53. package/src/services/database/events/index.ts +2 -1
  54. package/src/services/database/index.ts +6 -0
  55. package/src/services/database/local-migrator.ts +28 -27
  56. package/src/services/database/local.test.ts +64 -0
  57. package/src/services/database/local.ts +452 -66
  58. package/src/services/database/plan-render.test.ts +133 -0
  59. package/src/services/database/plan-render.ts +100 -0
  60. package/src/services/database/relation-resolver.test.ts +413 -0
  61. package/src/services/database/relation-resolver.ts +0 -0
  62. package/src/services/database/remote.ts +13 -13
  63. package/src/services/database/sqlite-cache-engine.test.ts +85 -0
  64. package/src/services/database/sqlite-cache-engine.ts +699 -0
  65. package/src/services/database/sqlite-worker.ts +116 -0
  66. package/src/services/database/surql-translate.ts +291 -0
  67. package/src/services/database/surreal-cache-engine.ts +139 -0
  68. package/src/services/logger/index.ts +6 -101
  69. package/src/services/persistence/localstorage.ts +2 -2
  70. package/src/services/persistence/resilient.ts +41 -0
  71. package/src/services/persistence/surrealdb.ts +10 -10
  72. package/src/services/stream-processor/index.ts +295 -38
  73. package/src/services/stream-processor/permissions.test.ts +47 -0
  74. package/src/services/stream-processor/permissions.ts +53 -0
  75. package/src/services/stream-processor/stream-processor.batch.test.ts +136 -0
  76. package/src/services/stream-processor/stream-processor.reset.test.ts +104 -0
  77. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  78. package/src/services/stream-processor/wasm-types.ts +18 -2
  79. package/src/sp00ky.auth-order.test.ts +92 -0
  80. package/src/sp00ky.init-query.test.ts +185 -0
  81. package/src/sp00ky.ts +1016 -0
  82. package/src/types.ts +258 -15
  83. package/src/utils/error-classification.test.ts +44 -0
  84. package/src/utils/error-classification.ts +7 -0
  85. package/src/utils/index.ts +35 -13
  86. package/src/utils/parser.ts +3 -2
  87. package/src/utils/surql.ts +24 -15
  88. package/src/utils/withRetry.test.ts +1 -1
  89. package/tsdown.config.ts +77 -1
  90. package/src/spooky.ts +0 -392
@@ -0,0 +1,21 @@
1
+ import { u as PinoTransmit } from "../types.js";
2
+ import { Level } from "pino";
3
+
4
+ //#region src/otel/index.d.ts
5
+
6
+ /**
7
+ * Creates a pino browser transmit object that forwards logs to an OpenTelemetry collector.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { createOtelTransmit } from '@spooky-sync/core/otel';
12
+ *
13
+ * new Sp00kyClient({
14
+ * // ...
15
+ * otelTransmit: createOtelTransmit('http://localhost:4318/v1/logs'),
16
+ * });
17
+ * ```
18
+ */
19
+ declare function createOtelTransmit(endpoint: string, level?: Level): PinoTransmit;
20
+ //#endregion
21
+ export { createOtelTransmit };
@@ -0,0 +1,86 @@
1
+ //#region src/otel/index.ts
2
+ function mapLevelToSeverityNumber(level) {
3
+ switch (level) {
4
+ case "trace": return 1;
5
+ case "debug": return 5;
6
+ case "info": return 9;
7
+ case "warn": return 13;
8
+ case "error": return 17;
9
+ case "fatal": return 21;
10
+ default: return 9;
11
+ }
12
+ }
13
+ async function loadOtelModules(otelEndpoint) {
14
+ const [{ LoggerProvider, BatchLogRecordProcessor }, { OTLPLogExporter }, { resourceFromAttributes }, { ATTR_SERVICE_NAME }] = await Promise.all([
15
+ import("@opentelemetry/sdk-logs"),
16
+ import("@opentelemetry/exporter-logs-otlp-proto"),
17
+ import("@opentelemetry/resources"),
18
+ import("@opentelemetry/semantic-conventions")
19
+ ]);
20
+ const loggerProvider = new LoggerProvider({
21
+ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: "sp00ky-client" }),
22
+ processors: [new BatchLogRecordProcessor(new OTLPLogExporter({ url: otelEndpoint }))]
23
+ });
24
+ const otelLoggerCache = {};
25
+ return (category) => {
26
+ if (!otelLoggerCache[category]) otelLoggerCache[category] = loggerProvider.getLogger(category);
27
+ return otelLoggerCache[category];
28
+ };
29
+ }
30
+ /**
31
+ * Creates a pino browser transmit object that forwards logs to an OpenTelemetry collector.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { createOtelTransmit } from '@spooky-sync/core/otel';
36
+ *
37
+ * new Sp00kyClient({
38
+ * // ...
39
+ * otelTransmit: createOtelTransmit('http://localhost:4318/v1/logs'),
40
+ * });
41
+ * ```
42
+ */
43
+ function createOtelTransmit(endpoint, level = "info") {
44
+ const otelReady = loadOtelModules(endpoint);
45
+ return {
46
+ level,
47
+ send: (levelLabel, logEvent) => {
48
+ otelReady.then((getOtelLogger) => {
49
+ try {
50
+ const messages = [...logEvent.messages];
51
+ const severityNumber = mapLevelToSeverityNumber(levelLabel);
52
+ let body = "";
53
+ const lastMsg = messages.pop();
54
+ if (typeof lastMsg === "string") body = lastMsg;
55
+ else if (lastMsg) body = JSON.stringify(lastMsg);
56
+ let category = "sp00ky-client::unknown";
57
+ const attributes = {};
58
+ for (const msg of messages) if (typeof msg === "object") {
59
+ if (msg.Category) {
60
+ category = msg.Category;
61
+ delete msg.Category;
62
+ }
63
+ Object.assign(attributes, msg);
64
+ }
65
+ getOtelLogger(category).emit({
66
+ severityNumber,
67
+ severityText: levelLabel.toUpperCase(),
68
+ body,
69
+ attributes: {
70
+ ...logEvent.bindings[0],
71
+ ...attributes
72
+ },
73
+ timestamp: new Date(logEvent.ts)
74
+ });
75
+ } catch (e) {
76
+ console.warn("Failed to transmit log to OTEL endpoint", e);
77
+ }
78
+ }).catch((e) => {
79
+ console.warn("Failed to load OpenTelemetry modules", e);
80
+ });
81
+ }
82
+ };
83
+ }
84
+
85
+ //#endregion
86
+ export { createOtelTransmit };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
2
+
3
+ //#region src/services/database/sqlite-worker.ts
4
+ /**
5
+ * Dedicated Web Worker that owns the SQLite-WASM handle. All DB access is
6
+ * funnelled through here (the main thread never touches the wasm module), which
7
+ * is also what the OPFS VFS requires — file access must happen off the main
8
+ * thread. Persistence uses the **OPFS SAHPool VFS**: durable, and (unlike the
9
+ * classic OPFS VFS) it does NOT require COOP/COEP cross-origin isolation
10
+ * headers, so host apps embedding the client need no server changes. Falls back
11
+ * to an in-memory DB when OPFS is unavailable.
12
+ *
13
+ * Message protocol (request/response keyed by `id`):
14
+ * { id, type: 'open', payload: { dbName, useOpfs } }
15
+ * { id, type: 'exec', payload: { sql, bind } } -> { id, ok, rows }
16
+ * { id, type: 'run', payload: { sql, bind } } -> { id, ok }
17
+ * { id, type: 'batch', payload: [{ sql, bind }] } (atomic BEGIN/COMMIT)
18
+ * { id, type: 'close' }
19
+ */
20
+ let db = null;
21
+ async function open(dbName, useOpfs) {
22
+ const sqlite3 = await sqlite3InitModule();
23
+ let persisted = false;
24
+ if (useOpfs && sqlite3.installOpfsSAHPoolVfs) try {
25
+ db = new (await (sqlite3.installOpfsSAHPoolVfs({ name: `sp00ky-${dbName}` }))).OpfsSAHPoolDb(`/${dbName}.sqlite3`);
26
+ persisted = true;
27
+ } catch {}
28
+ if (!db) db = new sqlite3.oo1.DB(":memory:", "c");
29
+ try {
30
+ db.exec({ sql: "PRAGMA busy_timeout = 5000; PRAGMA cache_size = -32000;" });
31
+ } catch {}
32
+ return { persisted };
33
+ }
34
+ function exec(sql, bind) {
35
+ if (!db) throw new Error("sqlite: DB not open");
36
+ return db.exec({
37
+ sql,
38
+ bind,
39
+ rowMode: "object",
40
+ returnValue: "resultRows"
41
+ });
42
+ }
43
+ function run(sql, bind) {
44
+ if (!db) throw new Error("sqlite: DB not open");
45
+ db.exec({
46
+ sql,
47
+ bind
48
+ });
49
+ }
50
+ function batch(stmts) {
51
+ if (!db) throw new Error("sqlite: DB not open");
52
+ db.exec({ sql: "BEGIN" });
53
+ try {
54
+ for (const s of stmts) db.exec({
55
+ sql: s.sql,
56
+ bind: s.bind
57
+ });
58
+ db.exec({ sql: "COMMIT" });
59
+ } catch (e) {
60
+ try {
61
+ db.exec({ sql: "ROLLBACK" });
62
+ } catch {}
63
+ throw e;
64
+ }
65
+ }
66
+ self.onmessage = async (ev) => {
67
+ const { id, type, payload } = ev.data ?? {};
68
+ try {
69
+ let result;
70
+ switch (type) {
71
+ case "open":
72
+ result = await open(payload.dbName, payload.useOpfs);
73
+ break;
74
+ case "exec":
75
+ result = { rows: exec(payload.sql, payload.bind) };
76
+ break;
77
+ case "run":
78
+ run(payload.sql, payload.bind);
79
+ result = {};
80
+ break;
81
+ case "batch":
82
+ batch(payload);
83
+ result = {};
84
+ break;
85
+ case "close":
86
+ db?.close();
87
+ db = null;
88
+ result = {};
89
+ break;
90
+ default: throw new Error(`sqlite worker: unknown message ${type}`);
91
+ }
92
+ self.postMessage({
93
+ id,
94
+ ok: true,
95
+ ...result
96
+ });
97
+ } catch (err) {
98
+ self.postMessage({
99
+ id,
100
+ ok: false,
101
+ error: err instanceof Error ? err.message : String(err)
102
+ });
103
+ }
104
+ };
105
+
106
+ //#endregion
107
+ export { };