@spooky-sync/core 0.0.1-canary.38 → 0.0.1-canary.381

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 (266) hide show
  1. package/AGENTS.md +91 -0
  2. package/README.md +59 -16
  3. package/dist/index.d.ts +2238 -144
  4. package/dist/index.js +18149 -2475
  5. package/dist/live.d.ts +149 -0
  6. package/dist/live.js +739 -0
  7. package/dist/otel/index.d.ts +1 -1
  8. package/dist/pure.d.ts +401 -0
  9. package/dist/pure.js +24 -0
  10. package/dist/sqlite-open.js +392 -0
  11. package/dist/sqlite-worker.d.ts +1 -0
  12. package/dist/sqlite-worker.js +439 -0
  13. package/dist/sw.d.ts +207 -0
  14. package/dist/sw.js +393 -0
  15. package/dist/tabs-broker-worker.d.ts +8 -0
  16. package/dist/tabs-broker-worker.js +477 -0
  17. package/dist/types.d.ts +2082 -39
  18. package/dist/worker-edges.js +498 -0
  19. package/dist/worker-impersonation.js +60 -0
  20. package/dist/worker-ref-tables.d.ts +48 -0
  21. package/dist/worker-types.d.ts +112 -0
  22. package/dist/worker-types.js +73 -0
  23. package/package.json +28 -7
  24. package/scripts/check-broker-bundle.mjs +33 -0
  25. package/scripts/check-worker-entries.mjs +173 -0
  26. package/skills/sp00ky-core/SKILL.md +42 -8
  27. package/skills/sp00ky-core/references/config.md +8 -2
  28. package/src/boot/auth-flip.saga.test.ts +174 -0
  29. package/src/boot/auth-flip.saga.ts +77 -0
  30. package/src/boot/boot.saga.test.ts +203 -0
  31. package/src/boot/boot.saga.ts +146 -0
  32. package/src/boot/bucket-switch.saga.test.ts +191 -0
  33. package/src/boot/bucket-switch.saga.ts +151 -0
  34. package/src/boot/preload.saga.test.ts +96 -0
  35. package/src/boot/preload.saga.ts +45 -0
  36. package/src/bucket-blurhash.test.ts +148 -0
  37. package/src/build-globals.d.ts +12 -0
  38. package/src/client/bucket-handle.test.ts +195 -0
  39. package/src/client/bucket-handle.ts +352 -0
  40. package/src/client/router.test.ts +59 -0
  41. package/src/client/router.ts +104 -0
  42. package/src/client/runtime.test.ts +319 -0
  43. package/src/client/runtime.ts +307 -0
  44. package/src/client/services.test.ts +74 -0
  45. package/src/client/services.ts +437 -0
  46. package/src/client/sp00ky-client.test.ts +428 -0
  47. package/src/client/sp00ky-client.ts +737 -0
  48. package/src/events/index.ts +3 -0
  49. package/src/index.ts +86 -1
  50. package/src/kernel/constants.test.ts +19 -0
  51. package/src/kernel/constants.ts +94 -0
  52. package/src/kernel/effects.test.ts +40 -0
  53. package/src/kernel/effects.ts +171 -0
  54. package/src/kernel/events.ts +60 -0
  55. package/src/kernel/interpreter.test.ts +116 -0
  56. package/src/kernel/interpreter.ts +148 -0
  57. package/src/kernel/saga.test.ts +89 -0
  58. package/src/kernel/saga.ts +73 -0
  59. package/src/live/index.test.ts +512 -0
  60. package/src/live/index.ts +1059 -0
  61. package/src/modules/app-release/index.test.ts +125 -0
  62. package/src/modules/app-release/index.ts +209 -0
  63. package/src/modules/auth/auth.local-first.test.ts +228 -0
  64. package/src/modules/auth/auth.session-recheck.test.ts +140 -0
  65. package/src/modules/auth/events/index.ts +7 -1
  66. package/src/modules/auth/impersonation-banner.ts +443 -0
  67. package/src/modules/auth/impersonation.integration.test.ts +209 -0
  68. package/src/modules/auth/impersonation.test.ts +377 -0
  69. package/src/modules/auth/impersonation.ts +179 -0
  70. package/src/modules/auth/index.ts +538 -14
  71. package/src/modules/channel/channel.test.ts +752 -0
  72. package/src/modules/channel/index.ts +1153 -0
  73. package/src/modules/channel/webrtc.ts +472 -0
  74. package/src/modules/crdt/acceptance.ts +75 -0
  75. package/src/modules/crdt/bytes.ts +50 -0
  76. package/src/modules/crdt/crdt-field.ts +125 -0
  77. package/src/modules/crdt/crdt.test.ts +546 -0
  78. package/src/modules/crdt/hash.ts +103 -0
  79. package/src/modules/crdt/index.ts +821 -0
  80. package/src/modules/crdt/loro-loader.ts +25 -0
  81. package/src/modules/crdt/mirror.ts +37 -0
  82. package/src/modules/devtools/flags.ts +349 -0
  83. package/src/modules/devtools/index.ts +746 -93
  84. package/src/modules/devtools/mutations.test.ts +92 -0
  85. package/src/modules/devtools/mutations.ts +160 -0
  86. package/src/modules/devtools/notify-throttle.test.ts +166 -0
  87. package/src/modules/devtools/state-shape.test.ts +263 -0
  88. package/src/modules/devtools/storage-info.test.ts +79 -0
  89. package/src/modules/devtools/storage-info.ts +168 -0
  90. package/src/modules/devtools/versions.test.ts +74 -0
  91. package/src/modules/devtools/versions.ts +110 -0
  92. package/src/modules/feature-flag/index.test.ts +298 -0
  93. package/src/modules/feature-flag/index.ts +316 -0
  94. package/src/modules/ref-tables.test.ts +93 -0
  95. package/src/modules/ref-tables.ts +91 -0
  96. package/src/modules/web-push/index.test.ts +824 -0
  97. package/src/modules/web-push/index.ts +1027 -0
  98. package/src/mutation/ingest-outage.integration.test.ts +400 -0
  99. package/src/mutation/jobs.test.ts +25 -0
  100. package/src/mutation/jobs.ts +41 -0
  101. package/src/mutation/mutation-id.test.ts +37 -0
  102. package/src/mutation/mutation-id.ts +35 -0
  103. package/src/mutation/pending-writes.integration.test.ts +175 -0
  104. package/src/mutation/push.saga.test.ts +361 -0
  105. package/src/mutation/push.saga.ts +245 -0
  106. package/src/mutation/rows.test.ts +278 -0
  107. package/src/mutation/rows.ts +515 -0
  108. package/src/mutation/tray.saga.test.ts +63 -0
  109. package/src/mutation/tray.saga.ts +48 -0
  110. package/src/mutation/write.saga.test.ts +436 -0
  111. package/src/mutation/write.saga.ts +384 -0
  112. package/src/pure.ts +66 -0
  113. package/src/push/types.ts +174 -0
  114. package/src/query/anon-query-isolation.integration.test.ts +531 -0
  115. package/src/query/env.test.ts +23 -0
  116. package/src/query/env.ts +65 -0
  117. package/src/query/fetch.saga.test.ts +199 -0
  118. package/src/query/fetch.saga.ts +120 -0
  119. package/src/query/hash.test.ts +14 -0
  120. package/src/query/hash.ts +18 -0
  121. package/src/query/id-set-plan.test.ts +132 -0
  122. package/src/query/lifecycle.saga.test.ts +274 -0
  123. package/src/query/lifecycle.saga.ts +186 -0
  124. package/src/query/list-ref-live.integration.test.ts +203 -0
  125. package/src/query/materialize.saga.test.ts +200 -0
  126. package/src/query/materialize.saga.ts +102 -0
  127. package/src/query/materialize.test.ts +58 -0
  128. package/src/query/materialize.ts +57 -0
  129. package/src/query/membership.saga.test.ts +514 -0
  130. package/src/query/membership.saga.ts +339 -0
  131. package/src/query/membership.test.ts +151 -0
  132. package/src/query/membership.ts +234 -0
  133. package/src/query/register.saga.test.ts +369 -0
  134. package/src/query/register.saga.ts +293 -0
  135. package/src/query/render-set.test.ts +47 -0
  136. package/src/query/render-set.ts +71 -0
  137. package/src/query/session-guard.integration.test.ts +304 -0
  138. package/src/query/sql.test.ts +118 -0
  139. package/src/query/sql.ts +174 -0
  140. package/src/query/window-query.test.ts +70 -0
  141. package/src/query/window-query.ts +208 -0
  142. package/src/services/blobs/blob-cache.test.ts +359 -0
  143. package/src/services/blobs/blob-cache.ts +603 -0
  144. package/src/services/blobs/blob-manifest.ts +227 -0
  145. package/src/services/blobs/blob-store.test.ts +77 -0
  146. package/src/services/blobs/blob-store.ts +359 -0
  147. package/src/services/blobs/blob.fixture.ts +90 -0
  148. package/src/services/blobs/index.ts +70 -0
  149. package/src/services/database/cache-engine.ts +209 -0
  150. package/src/services/database/connection-supervisor.test.ts +318 -0
  151. package/src/services/database/connection-supervisor.ts +422 -0
  152. package/src/services/database/database.query-timeout.test.ts +140 -0
  153. package/src/services/database/database.responses.test.ts +43 -0
  154. package/src/services/database/database.ts +151 -56
  155. package/src/services/database/engine-factory.ts +33 -0
  156. package/src/services/database/errors.ts +34 -0
  157. package/src/services/database/index.ts +7 -0
  158. package/src/services/database/landed-body-clear.integration.test.ts +236 -0
  159. package/src/services/database/local-migrator.integration.test.ts +110 -0
  160. package/src/services/database/local-migrator.ts +54 -9
  161. package/src/services/database/local.test.ts +150 -0
  162. package/src/services/database/local.ts +503 -59
  163. package/src/services/database/plan-render.test.ts +159 -0
  164. package/src/services/database/plan-render.ts +108 -0
  165. package/src/services/database/relation-resolver.test.ts +484 -0
  166. package/src/services/database/relation-resolver.ts +0 -0
  167. package/src/services/database/remote.ts +283 -4
  168. package/src/services/database/sqlite-cache-engine.test.ts +628 -0
  169. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  170. package/src/services/database/sqlite-cache-engine.ts +1401 -0
  171. package/src/services/database/sqlite-column-reset.integration.test.ts +100 -0
  172. package/src/services/database/sqlite-column-reset.ts +91 -0
  173. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  174. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  175. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  176. package/src/services/database/sqlite-lock-verify.ts +45 -0
  177. package/src/services/database/sqlite-open.test.ts +150 -0
  178. package/src/services/database/sqlite-open.ts +164 -0
  179. package/src/services/database/sqlite-plan-sql.test.ts +164 -0
  180. package/src/services/database/sqlite-plan-sql.ts +196 -0
  181. package/src/services/database/sqlite-projection.test.ts +121 -0
  182. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  183. package/src/services/database/sqlite-select.test.ts +261 -0
  184. package/src/services/database/sqlite-select.ts +131 -0
  185. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  186. package/src/services/database/sqlite-transport.ts +224 -0
  187. package/src/services/database/sqlite-worker.ts +437 -0
  188. package/src/services/database/surql-translate.ts +417 -0
  189. package/src/services/database/surreal-cache-engine.ts +163 -0
  190. package/src/services/logger/index.test.ts +132 -0
  191. package/src/services/logger/index.ts +187 -6
  192. package/src/services/persistence/resilient.ts +8 -1
  193. package/src/services/persistence/surrealdb.ts +2 -2
  194. package/src/services/stream-processor/index.ts +801 -63
  195. package/src/services/stream-processor/permissions.test.ts +47 -0
  196. package/src/services/stream-processor/permissions.ts +53 -0
  197. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  198. package/src/services/stream-processor/stream-processor.prime.test.ts +234 -0
  199. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  200. package/src/services/stream-processor/wasm-types.ts +57 -1
  201. package/src/services/tabs/broker-client.ts +284 -0
  202. package/src/services/tabs/broker.test.ts +381 -0
  203. package/src/services/tabs/coordinator.test.ts +386 -0
  204. package/src/services/tabs/coordinator.ts +646 -0
  205. package/src/services/tabs/fake-ports.fixture.ts +112 -0
  206. package/src/services/tabs/leader-locks.ts +75 -0
  207. package/src/services/tabs/protocol.ts +262 -0
  208. package/src/services/tabs/support.ts +36 -0
  209. package/src/services/tabs/tabs-broker-worker.ts +652 -0
  210. package/src/sp00ky.ts +5 -394
  211. package/src/state/client-state.ts +231 -0
  212. package/src/state/lifecycle.test.ts +184 -0
  213. package/src/state/lifecycle.ts +127 -0
  214. package/src/state/reducers.test.ts +337 -0
  215. package/src/state/reducers.ts +571 -0
  216. package/src/state/selectors.test.ts +203 -0
  217. package/src/state/selectors.ts +264 -0
  218. package/src/sw/index.test.ts +582 -0
  219. package/src/sw/index.ts +607 -0
  220. package/src/sw/store.ts +96 -0
  221. package/src/sync/connection.saga.test.ts +97 -0
  222. package/src/sync/connection.saga.ts +89 -0
  223. package/src/sync/live.saga.test.ts +229 -0
  224. package/src/sync/live.saga.ts +116 -0
  225. package/src/sync/policy.health.test.ts +56 -0
  226. package/src/sync/policy.test.ts +663 -0
  227. package/src/sync/policy.ts +515 -0
  228. package/src/sync/poll.saga.test.ts +51 -0
  229. package/src/sync/poll.saga.ts +36 -0
  230. package/src/sync/tabs.saga.test.ts +78 -0
  231. package/src/sync/tabs.saga.ts +96 -0
  232. package/src/testing/adapters.ts +137 -0
  233. package/src/testing/build.ts +63 -0
  234. package/src/testing/fake-services.ts +63 -0
  235. package/src/testing/run-pure.test.ts +92 -0
  236. package/src/testing/run-pure.ts +128 -0
  237. package/src/testing/services.ts +17 -0
  238. package/src/types.ts +694 -7
  239. package/src/utils/blurhash.ts +90 -0
  240. package/src/utils/edges.ts +57 -0
  241. package/src/utils/error-classification.test.ts +116 -0
  242. package/src/utils/error-classification.ts +74 -0
  243. package/src/utils/index.ts +83 -6
  244. package/src/utils/parser.test.ts +67 -102
  245. package/src/utils/parser.ts +63 -2
  246. package/src/utils/semver.test.ts +32 -0
  247. package/src/utils/semver.ts +30 -0
  248. package/src/utils/sha256.ts +15 -0
  249. package/src/utils/surql.ts +22 -11
  250. package/src/utils/utils.test.ts +3 -2
  251. package/tsdown.config.ts +117 -3
  252. package/vitest.config.ts +48 -0
  253. package/src/modules/cache/index.ts +0 -241
  254. package/src/modules/cache/types.ts +0 -19
  255. package/src/modules/data/data.test.ts +0 -58
  256. package/src/modules/data/index.ts +0 -882
  257. package/src/modules/sync/engine.ts +0 -173
  258. package/src/modules/sync/events/index.ts +0 -78
  259. package/src/modules/sync/index.ts +0 -3
  260. package/src/modules/sync/queue/index.ts +0 -2
  261. package/src/modules/sync/queue/queue-down.ts +0 -90
  262. package/src/modules/sync/queue/queue-up.ts +0 -224
  263. package/src/modules/sync/scheduler.ts +0 -84
  264. package/src/modules/sync/sync.ts +0 -422
  265. package/src/modules/sync/utils.test.ts +0 -311
  266. package/src/modules/sync/utils.ts +0 -169
@@ -1,4 +1,4 @@
1
- import { c as PinoTransmit } from "../types.js";
1
+ import { h as PinoTransmit } from "../types.js";
2
2
  import { Level } from "pino";
3
3
 
4
4
  //#region src/otel/index.d.ts
package/dist/pure.d.ts ADDED
@@ -0,0 +1,401 @@
1
+ import { a as bucketIdForUser, i as RefMode, n as ANON_USER_ID, o as listRefTableFor, r as DEFAULT_REF_MODE, s as sanitizeUserId, t as ANON_LIST_REF_TABLE } from "./worker-ref-tables.js";
2
+ import { a as PushNotificationAction, c as PushPayloadKind, d as base64UrlToBytes, f as bytesToBase64Url, i as PushNotification, l as PushUrgency, m as sameBytes, n as BridgeTokenMessage, o as PushOp, p as isPushPayload, r as PUSH_PAYLOAD_VERSION, s as PushPayload, t as BridgeSignOutMessage, u as WebPushClientMessage } from "./worker-types.js";
3
+ import { Duration, RecordId } from "surrealdb";
4
+ import { ColumnSchema, RecordId as RecordId$1, SchemaStructure } from "@spooky-sync/query-builder";
5
+ import "pino";
6
+
7
+ //#region src/query/hash.d.ts
8
+ /**
9
+ * Hash inputs for the two query keys. Kept byte-identical to the previous
10
+ * implementation (`DataModule.calculateHash` / `calculateMembershipKey`):
11
+ * the salted key names the remote `_00_query` row, the unsalted one names the
12
+ * durable `_00_view` row, and both are `sha256(JSON.stringify(input))`.
13
+ */
14
+ interface QueryKeyInput {
15
+ surql: string;
16
+ params: Record<string, unknown>;
17
+ }
18
+ /** Session-salted: two tabs of one user must not share a remote view. */
19
+ declare const queryHashInput: (input: QueryKeyInput, sessionId: string | null) => string;
20
+ /** Session-independent: survives reload and bucket switch. */
21
+ declare const viewKeyInput: (input: QueryKeyInput) => string;
22
+ //#endregion
23
+ //#region src/modules/auth/impersonation.d.ts
24
+ /**
25
+ * Admin impersonation, client side.
26
+ *
27
+ * The server does the enforcing (see `impersonation_remote.surql` in the CLI):
28
+ * an admin calls `fn::_00_impersonate::start`, receives a token for the
29
+ * `_00_impersonate` access method, and authenticates with it, so `$auth.id` is
30
+ * the target user. Everything here is state and timers around that:
31
+ *
32
+ * - the admin's own token is kept under {@link ORIGIN_TOKEN_KEY} so Stop (and a
33
+ * reload in between) can return to it;
34
+ * - the impersonation state is derived from the token's claims, never stored
35
+ * separately, so it cannot disagree with the session actually in use;
36
+ * - the token is re-signed before it expires, and the session is polled so a
37
+ * stop or revocation made elsewhere reaches an open WebSocket quickly.
38
+ */
39
+ /** Access method impersonation tokens are issued for. */
40
+ declare const IMPERSONATION_ACCESS = "_00_impersonate";
41
+ /** Persistence key holding the admin's own token while impersonating. */
42
+
43
+ interface TokenClaims {
44
+ access: string | null;
45
+ userId: string | null;
46
+ exp: number | null;
47
+ impersonation: {
48
+ session: string;
49
+ admin: string;
50
+ } | null;
51
+ }
52
+ /**
53
+ * Read the claims of a SurrealDB record-access JWT WITHOUT verifying it. The
54
+ * server still enforces the token on every request; this is only so the client
55
+ * can act on what it already holds before a round trip completes.
56
+ *
57
+ * Returns nulls on any malformed input.
58
+ */
59
+ declare function decodeTokenClaims(token: string): TokenClaims;
60
+ /** The impersonation a token represents, or null for a regular session. */
61
+ //#endregion
62
+ //#region src/utils/surql.d.ts
63
+ interface TxQuery {
64
+ readonly __brand: 'TxQuery';
65
+ readonly sql: string;
66
+ readonly statementCount: number;
67
+ }
68
+ interface SealOptions {
69
+ /** 0-based index of the inner statement to extract. Default: last statement. */
70
+ resultIndex?: number;
71
+ }
72
+ interface SealedQuery<T = void> {
73
+ readonly sql: string;
74
+ readonly extract: (results: unknown[]) => T;
75
+ }
76
+ interface SurqlHelper {
77
+ seal(query: string): string;
78
+ seal<T = void>(query: TxQuery, options?: SealOptions): SealedQuery<T>;
79
+ tx(queries: string[]): TxQuery;
80
+ selectById(idVar: string, returnValues: string[]): string;
81
+ selectByFieldsAnd(table: string, whereVar: ({
82
+ field: string;
83
+ variable: string;
84
+ } | string)[], returnValues: ({
85
+ field: string;
86
+ alias: string;
87
+ } | string)[]): string;
88
+ create(idVar: string, dataVar: string): string;
89
+ createSet(idVar: string, keyDataVars: ({
90
+ key: string;
91
+ variable: string;
92
+ } | {
93
+ statement: string;
94
+ } | string)[]): string;
95
+ upsert(idVar: string, dataVar: string): string;
96
+ upsertMerge(idVar: string, dataVar: string): string;
97
+ updateMerge(idVar: string, dataVar: string): string;
98
+ updateSet(idVar: string, keyDataVar: ({
99
+ key: string;
100
+ variable: string;
101
+ } | {
102
+ statement: string;
103
+ } | string)[]): string;
104
+ delete(idVar: string): string;
105
+ let(name: string, query: string): string;
106
+ createMutation(t: MutationEventType, mutationIdVar: string, recordIdVar: string, dataVar?: string, beforeRecordVar?: string): string;
107
+ returnObject(entries: {
108
+ key: string;
109
+ variable: string;
110
+ }[]): string;
111
+ }
112
+ declare const surql: SurqlHelper;
113
+ //#endregion
114
+ //#region src/types.d.ts
115
+
116
+ /**
117
+ * Supported Time-To-Live (TTL) values for cached queries.
118
+ * Format: number + unit (m=minutes, h=hours, d=days).
119
+ */
120
+ type QueryTimeToLive = '1m' | '5m' | '10m' | '15m' | '20m' | '25m' | '30m' | '1h' | '2h' | '3h' | '4h' | '5h' | '6h' | '7h' | '8h' | '9h' | '10h' | '11h' | '12h' | '1d';
121
+ /**
122
+ * Refresh behavior for `preload` when the data is already cached locally (warm).
123
+ * The FIRST load (cold) always fetches + blocks regardless.
124
+ * - `onUse` (default): do nothing when warm — the data freshens on use, when the
125
+ * real `useQuery` mounts and registers its live view. No network on load.
126
+ * - `background`: return instantly, but kick a one-time silent refetch.
127
+ * - `stale`: like `background`, but only if the cached copy is older than
128
+ * `staleTime`.
129
+ */
130
+
131
+ /**
132
+ * One changed row delivered inline on a LIVE notification, when the
133
+ * subscription was opened with the body joined on (see
134
+ * {@link Sp00kyConfig.liveInlineBodies}). `version` is the edge's `version`,
135
+ * i.e. the same number the membership read would have reported for this row.
136
+ */
137
+ interface InlineRow {
138
+ id: string;
139
+ version: number;
140
+ record: Record<string, unknown>;
141
+ }
142
+ /**
143
+ * A row that joined a query's view, or moved to a newer version, as one LIVE
144
+ * notification on `_00_list_ref` reports it: the edge's `out` and `version`.
145
+ * The client commits it to the query's membership straight away instead of
146
+ * re-reading the edges (see `sync/live.saga.ts`).
147
+ */
148
+
149
+ type RecordVersionArray = Array<[string, number]>;
150
+ /**
151
+ * Represents the difference between two record version sets.
152
+ * Used for synchronizing local and remote states.
153
+ */
154
+
155
+ /**
156
+ * What the server's `_00_query` row says about a view, read alongside its
157
+ * `_00_list_ref` edges (registration, the poll, a targeted re-read).
158
+ *
159
+ * - `present: false`: the row is not there (or not readable). The SSP no
160
+ * longer has this view: a TTL sweep, an SSP reset, a scheduler wipe. An
161
+ * empty edge set read at the same time is NOT "no rows", it is "no view".
162
+ * - `state: 'materializing'`: the SSP has (re)computed the set and its edges
163
+ * are in flight; an empty read is "not landed yet".
164
+ * - `state: 'ready'`: the edges are committed (flipped in the same
165
+ * transaction), so `rowCount === 0` with no edges is a real empty result.
166
+ * - `state: null`: a server that predates the marker; only `rowCount` can be
167
+ * used, with its `> 0` = flush window / `0` = empty ambiguity.
168
+ */
169
+ interface ServerViewMeta {
170
+ present: boolean;
171
+ /** `_00_query.rowCount`; `null` when unreadable. */
172
+ rowCount: number | null;
173
+ state: 'materializing' | 'ready' | null;
174
+ }
175
+ /** Outcome of handing a server id-set to `DataModule.updateQueryRemoteArray`. */
176
+
177
+ type MutationEventType = 'create' | 'update' | 'delete';
178
+ /**
179
+ * Represents a mutation event (create, update, delete) to be synchronized.
180
+ */
181
+ //#endregion
182
+ //#region src/kernel/effects.d.ts
183
+ type Vars = Record<string, unknown>;
184
+ /** What the in-browser SSP needs to build a local view. */
185
+ //#endregion
186
+ //#region src/query/sql.d.ts
187
+ /** Every SurrealQL statement the query side sends, as pure builders. */
188
+ declare const VIEW_TABLE = "_00_view";
189
+ declare const LEGACY_VIEW_TABLE = "_00_window";
190
+ declare const viewRecordId: (viewKey: string) => RecordId<string>;
191
+ /**
192
+ * The durable `_00_view` row: the confirmed membership plus the subquery
193
+ * children, which the GC must keep and a cold scan may paint as much as the
194
+ * members.
195
+ */
196
+ declare const viewRow: (ids: RecordVersionArray, children: RecordVersionArray, confirmed: boolean, now: number) => Record<string, unknown>;
197
+ /** Every `_00_view` row: the durable view index and the GC read it. */
198
+ declare const readViewRows: () => string;
199
+ /** Copy every legacy `_00_window` row into `_00_view` once. */
200
+ declare const readLegacyViewRows: () => string;
201
+ declare const countViewRows: () => string;
202
+ declare const listRefSelect: (table: string) => string;
203
+ declare const subqueryListRefSelect: (table: string) => string;
204
+ declare const queryRowCountSelect: () => string;
205
+ declare const listRefBatchSelect: (table: string) => string;
206
+ declare const queryRowCountBatchSelect: () => string;
207
+ /** The single-query read: edges, meta, subquery children in one request. */
208
+ declare const singleSnapshotSelect: (table: string) => string;
209
+ /** The many-query read: all edges + all metas in one request. */
210
+ declare const batchSnapshotSelect: (table: string) => string;
211
+ /**
212
+ * The session's one LIVE subscription on its `_00_list_ref` table.
213
+ *
214
+ * `inlineBodies` adds `FETCH out`, which resolves the edge's target into the
215
+ * notification so the body arrives with the doorbell.
216
+ *
217
+ * On the anon table it is filtered to this client's own edges. Every
218
+ * signed-out session shares that table, so unfiltered each one received (and
219
+ * the server fetched a body for) every anonymous edge change of every other
220
+ * session, and the client then dropped all but its own. An edge carries the
221
+ * `session::id()` of whichever socket last registered its view, which is a
222
+ * socket of this client but not necessarily the current one: a view stays
223
+ * registered across a reconnect, and a reload keeps its salt and its views,
224
+ * so their older edges still name an earlier socket. `sessionIds` are those
225
+ * earlier sockets. The current one is matched by `session::id()` itself,
226
+ * which SurrealDB evaluates in the subscriber's own session, so the filter
227
+ * holds before the client has learned its new id.
228
+ *
229
+ * The cast is load-bearing: `session::id()` is a uuid, `clientId` a string,
230
+ * and without it nothing matches. Per-user tables need no filter.
231
+ */
232
+ declare function listRefLive(table: string, opts: {
233
+ inlineBodies: boolean;
234
+ sessionIds: ReadonlyArray<string>;
235
+ }): {
236
+ sql: string;
237
+ vars?: Vars;
238
+ };
239
+ interface RegisterPayload {
240
+ id: RecordId<string>;
241
+ surql: string;
242
+ params: Record<string, unknown>;
243
+ ttl: string;
244
+ /**
245
+ * The registering page's secret. `fn::query::register` forwards only its
246
+ * hash, and the SSP lets a registration onto an existing anonymous view
247
+ * only with the hash that view was registered with: every signed-out
248
+ * visitor is `anon`, and anyone can read an anonymous view's id. Kept out
249
+ * of `params`, which name the computation the SSP shares between views.
250
+ */
251
+ owner?: string;
252
+ }
253
+ /** Register + read back edges/meta/children in ONE request. */
254
+ declare const registerSelect: (table: string) => string;
255
+ declare const registerVars: (payload: RegisterPayload) => Vars;
256
+ /** One heartbeat statement per view id; result index i answers id i. */
257
+ declare function heartbeatBatch(ids: ReadonlyArray<RecordId<string>>): {
258
+ sql: string;
259
+ vars: Vars;
260
+ };
261
+ /**
262
+ * Hand back view ids this page no longer answers to. Per id,
263
+ * `fn::query::unsubscribe` drops this socket's entry and deletes the row when
264
+ * nobody else watches it; `fn::query::leave` then drops the entries of this
265
+ * page's earlier sockets (a reconnect re-registered under a new session id)
266
+ * and pulls the expiry in when none remain. Result index 2i answers the
267
+ * unsubscribe of id i, 2i + 1 its leave.
268
+ */
269
+ declare function releaseBatch(ids: ReadonlyArray<RecordId<string>>, sessionIds: ReadonlyArray<string>): {
270
+ sql: string;
271
+ vars: Vars;
272
+ };
273
+ /** A heartbeat answer whose statement matched no row: the view was reclaimed. */
274
+ declare const heartbeatRowGone: (result: unknown) => boolean;
275
+ /**
276
+ * The pagehide release: one bare statement for `beaconSql`, which has no params
277
+ * channel, so ids are inlined. Only shapes the client minted itself get in, a
278
+ * `_00_query:<sha256>` id and a UUID session id, so nothing else is ever
279
+ * interpolated. `null` when there is nothing the server could act on: with no
280
+ * session ids it could not tell this page's entries from anyone else's.
281
+ */
282
+ declare function leaveViewsBeacon(ids: ReadonlyArray<unknown>, sessionIds: ReadonlyArray<string>): string | null;
283
+ declare const bodySelect: () => string;
284
+ /** Local transaction that MERGEs many fetched bodies. */
285
+ declare function upsertBodiesTx(records: ReadonlyArray<{
286
+ id: unknown;
287
+ content: Record<string, unknown>;
288
+ }>): {
289
+ query: SealedQuery<unknown>;
290
+ vars: Vars;
291
+ };
292
+ //#endregion
293
+ //#region src/query/membership.d.ts
294
+ /** The `{ rowCount, state }` object the row-count selects return per row. */
295
+ interface QueryMetaRow {
296
+ id?: RecordId<string>;
297
+ rowCount?: number | null;
298
+ state?: string | null;
299
+ }
300
+ declare function metaFromRow(row: QueryMetaRow | null | undefined): ServerViewMeta;
301
+ /** Collapse duplicate `(id, version)` pairs to one per id, keeping the highest version. */
302
+ declare function dedupeRecordVersions(pairs: RecordVersionArray): RecordVersionArray;
303
+ /**
304
+ * A statement that did not answer (an ERR, or no result at all). Kept apart
305
+ * from a `null` result, which is the server saying "no such row": reading a
306
+ * failed statement as a missing row turned every timeout under load into a
307
+ * lost view and re-registered it, over and over (whitepawn, 2026-09-16).
308
+ */
309
+ declare const FAILED: unique symbol;
310
+ type Answer<T> = T | null | typeof FAILED;
311
+ interface ListRefSnapshot {
312
+ primary: RecordVersionArray;
313
+ subquery: RecordVersionArray;
314
+ meta: ServerViewMeta;
315
+ }
316
+ /**
317
+ * Fold the single-query statement batch (edges, meta, children) into a
318
+ * snapshot. `null` when any of the three did not answer: half an answer is
319
+ * not one, and a missing meta would read as a lost view.
320
+ */
321
+ declare function snapshotFromSingle(items: Answer<Array<{
322
+ out: RecordId<string>;
323
+ version: number;
324
+ }>>, metaRow: Answer<QueryMetaRow>, children: Answer<Array<{
325
+ out: RecordId<string>;
326
+ version: number;
327
+ }>>): ListRefSnapshot | null;
328
+ /**
329
+ * Fold the many-query statement batch into one snapshot per hash. Every hash
330
+ * in `hashById` gets a snapshot; a query whose row did not come back reads
331
+ * `present: false`. Empty when either statement did not answer.
332
+ */
333
+ //#endregion
334
+ //#region src/utils/edges.d.ts
335
+ /**
336
+ * Reading `_00_list_ref` edges as a LIVE notification delivers them. Pure, so
337
+ * both the full client and the wasm-free live feed (`@spooky-sync/core/live`)
338
+ * decode the same wire shape the same way.
339
+ */
340
+ /** The hash a `_00_list_ref` edge belongs to: the id part of its `in` (`_00_query:<hash>`). */
341
+ declare function hashOfEdge(value: unknown): string | null;
342
+ /**
343
+ * The row an edge notification carries, when the subscription joined it on.
344
+ *
345
+ * Without `FETCH out` the edge's `out` is a record id and there is nothing to
346
+ * land, so this returns null and the caller falls back to fetching the body.
347
+ * It also returns null for a row the session may not read (the join yields
348
+ * `out: null`) and for an edge whose target has been deleted.
349
+ */
350
+ declare function rowOfEdge(value: unknown): InlineRow | null;
351
+ /**
352
+ * The membership change a `CREATE` or `UPDATE` notification reports: the
353
+ * edge's target joined the view (or its version moved). Null for anything a
354
+ * re-read must settle instead: a `DELETE` (a row leaving, or every edge of a
355
+ * view being rewritten), a subquery child (`parent` set), or an edge missing
356
+ * its target or version. With `FETCH out` the target is the joined row, whose
357
+ * `id` is the record id.
358
+ */
359
+
360
+ //#endregion
361
+ //#region src/utils/parser.d.ts
362
+ /**
363
+ * Parse a QUERY's params. Unlike a record's fields, a param name is not always a
364
+ * column: an `_or` branch binds under a synthetic `white__or0` (see
365
+ * `orParamName` in @spooky-sync/query-builder), and the surql that references it
366
+ * is already written. So resolve the column through the synthetic name, and keep
367
+ * a param we cannot type rather than dropping it.
368
+ *
369
+ * Dropping was the bug: `parseParams` kept only column-named params, so every
370
+ * `_or` query registered with `$or0`/`$or1` unbound and matched no rows at all,
371
+ * silently (rowCount 0, errorCount 0).
372
+ */
373
+ declare function parseQueryParams(tableSchema: Record<string, ColumnSchema>, params: Record<string, any>): Record<string, any>;
374
+ //#endregion
375
+ //#region src/utils/index.d.ts
376
+ declare const encodeRecordId: (recordId: RecordId<string>) => string;
377
+ declare const parseRecordIdString: (id: string) => RecordId<string>;
378
+ /**
379
+ * Parse duration string or Duration object to milliseconds
380
+ */
381
+ declare function parseDuration(duration: QueryTimeToLive | Duration): number;
382
+ //#endregion
383
+ //#region src/utils/sha256.d.ts
384
+ /**
385
+ * Lowercase hex SHA-256 of a UTF-8 string, through WebCrypto.
386
+ *
387
+ * Pure and dependency free so it can live in `@spooky-sync/core/pure` and run
388
+ * in a service worker, a dedicated worker or Node (`globalThis.crypto` is
389
+ * there on Node 19+). Query keys are `sha256Hex(JSON.stringify(..))`, see
390
+ * `query/hash.ts`.
391
+ */
392
+ declare function sha256Hex(input: string): Promise<string>;
393
+ //#endregion
394
+ //#region src/pure.d.ts
395
+ /**
396
+ * The remote `_00_query` key of a query for one session salt: what the full
397
+ * client names its view (`_00_query:<key>`).
398
+ */
399
+ declare function queryKey(input: QueryKeyInput, sessionId: string | null): Promise<string>;
400
+ //#endregion
401
+ export { ANON_LIST_REF_TABLE, ANON_USER_ID, type Answer, BridgeSignOutMessage, BridgeTokenMessage, DEFAULT_REF_MODE, FAILED, IMPERSONATION_ACCESS, LEGACY_VIEW_TABLE, type ListRefSnapshot, PUSH_PAYLOAD_VERSION, PushNotification, PushNotificationAction, PushOp, PushPayload, PushPayloadKind, PushUrgency, type QueryKeyInput, type QueryMetaRow, type RefMode, RegisterPayload, type SealOptions, type SealedQuery, type SurqlHelper, type TokenClaims, type TxQuery, VIEW_TABLE, WebPushClientMessage, base64UrlToBytes, batchSnapshotSelect, bodySelect, bucketIdForUser, bytesToBase64Url, countViewRows, decodeTokenClaims, dedupeRecordVersions, encodeRecordId, hashOfEdge, heartbeatBatch, heartbeatRowGone, isPushPayload, leaveViewsBeacon, listRefBatchSelect, listRefLive, listRefSelect, listRefTableFor, metaFromRow, parseDuration, parseQueryParams, parseRecordIdString, queryHashInput, queryKey, queryRowCountBatchSelect, queryRowCountSelect, readLegacyViewRows, readViewRows, registerSelect, registerVars, releaseBatch, rowOfEdge, sameBytes, sanitizeUserId, sha256Hex, singleSnapshotSelect, snapshotFromSingle, subqueryListRefSelect, surql, upsertBodiesTx, viewKeyInput, viewRecordId, viewRow };
package/dist/pure.js ADDED
@@ -0,0 +1,24 @@
1
+ import { A as releaseBatch, B as listRefTableFor, C as listRefSelect, D as readViewRows, E as readLegacyViewRows, F as viewRow, G as viewKeyInput, H as surql, I as ANON_LIST_REF_TABLE, L as ANON_USER_ID, M as subqueryListRefSelect, N as upsertBodiesTx, O as registerSelect, P as viewRecordId, R as DEFAULT_REF_MODE, S as listRefLive, T as queryRowCountSelect, U as sha256Hex, V as sanitizeUserId, W as queryHashInput, _ as countViewRows, a as metaFromRow, b as leaveViewsBeacon, f as parseQueryParams, g as bodySelect, h as batchSnapshotSelect, i as dedupeRecordVersions, j as singleSnapshotSelect, k as registerVars, l as parseDuration, m as VIEW_TABLE, n as rowOfEdge, o as snapshotFromSingle, p as LEGACY_VIEW_TABLE, r as FAILED, s as encodeRecordId, t as hashOfEdge, u as parseRecordIdString, v as heartbeatBatch, w as queryRowCountBatchSelect, x as listRefBatchSelect, y as heartbeatRowGone, z as bucketIdForUser } from "./worker-edges.js";
2
+ import { n as decodeTokenClaims, t as IMPERSONATION_ACCESS } from "./worker-impersonation.js";
3
+ import { a as sameBytes, i as isPushPayload, n as base64UrlToBytes, r as bytesToBase64Url, t as PUSH_PAYLOAD_VERSION } from "./worker-types.js";
4
+
5
+ //#region src/pure.ts
6
+ /**
7
+ * `@spooky-sync/core/pure`: the side-effect-free builders of the sync protocol.
8
+ *
9
+ * Everything here is plain functions and constants over `surrealdb` value
10
+ * types: no wasm, no logger, no `window`/`document`/`localStorage`. Safe to
11
+ * import from a service worker, a dedicated worker or Node. The full client
12
+ * imports the same modules, so what this entry builds is byte for byte what
13
+ * `Sp00kyClient` sends.
14
+ */
15
+ /**
16
+ * The remote `_00_query` key of a query for one session salt: what the full
17
+ * client names its view (`_00_query:<key>`).
18
+ */
19
+ function queryKey(input, sessionId) {
20
+ return sha256Hex(queryHashInput(input, sessionId));
21
+ }
22
+
23
+ //#endregion
24
+ export { ANON_LIST_REF_TABLE, ANON_USER_ID, DEFAULT_REF_MODE, FAILED, IMPERSONATION_ACCESS, LEGACY_VIEW_TABLE, PUSH_PAYLOAD_VERSION, VIEW_TABLE, base64UrlToBytes, batchSnapshotSelect, bodySelect, bucketIdForUser, bytesToBase64Url, countViewRows, decodeTokenClaims, dedupeRecordVersions, encodeRecordId, hashOfEdge, heartbeatBatch, heartbeatRowGone, isPushPayload, leaveViewsBeacon, listRefBatchSelect, listRefLive, listRefSelect, listRefTableFor, metaFromRow, parseDuration, parseQueryParams, parseRecordIdString, queryHashInput, queryKey, queryRowCountBatchSelect, queryRowCountSelect, readLegacyViewRows, readViewRows, registerSelect, registerVars, releaseBatch, rowOfEdge, sameBytes, sanitizeUserId, sha256Hex, singleSnapshotSelect, snapshotFromSingle, subqueryListRefSelect, surql, upsertBodiesTx, viewKeyInput, viewRecordId, viewRow };