@spooky-sync/core 0.0.1-canary.32 → 0.0.1-canary.320

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 (259) hide show
  1. package/AGENTS.md +81 -0
  2. package/README.md +56 -16
  3. package/dist/index.d.ts +2034 -137
  4. package/dist/index.js +15137 -2434
  5. package/dist/live.d.ts +141 -0
  6. package/dist/live.js +709 -0
  7. package/dist/otel/index.d.ts +1 -1
  8. package/dist/otel/index.js +3 -3
  9. package/dist/pure.d.ts +337 -0
  10. package/dist/pure.js +24 -0
  11. package/dist/sqlite-open.js +379 -0
  12. package/dist/sqlite-worker.d.ts +1 -0
  13. package/dist/sqlite-worker.js +439 -0
  14. package/dist/sw.d.ts +207 -0
  15. package/dist/sw.js +393 -0
  16. package/dist/tabs-broker-worker.d.ts +8 -0
  17. package/dist/tabs-broker-worker.js +472 -0
  18. package/dist/types.d.ts +1811 -38
  19. package/dist/worker-edges.js +439 -0
  20. package/dist/worker-impersonation.js +60 -0
  21. package/dist/worker-ref-tables.d.ts +46 -0
  22. package/dist/worker-types.d.ts +112 -0
  23. package/dist/worker-types.js +73 -0
  24. package/package.json +27 -7
  25. package/scripts/check-broker-bundle.mjs +33 -0
  26. package/scripts/check-worker-entries.mjs +172 -0
  27. package/skills/sp00ky-core/SKILL.md +31 -6
  28. package/skills/sp00ky-core/references/config.md +6 -0
  29. package/src/boot/auth-flip.saga.test.ts +135 -0
  30. package/src/boot/auth-flip.saga.ts +77 -0
  31. package/src/boot/boot.saga.test.ts +180 -0
  32. package/src/boot/boot.saga.ts +142 -0
  33. package/src/boot/bucket-switch.saga.test.ts +187 -0
  34. package/src/boot/bucket-switch.saga.ts +133 -0
  35. package/src/boot/preload.saga.test.ts +51 -0
  36. package/src/boot/preload.saga.ts +31 -0
  37. package/src/bucket-blurhash.test.ts +148 -0
  38. package/src/build-globals.d.ts +12 -0
  39. package/src/client/bucket-handle.test.ts +117 -0
  40. package/src/client/bucket-handle.ts +227 -0
  41. package/src/client/router.test.ts +58 -0
  42. package/src/client/router.ts +100 -0
  43. package/src/client/runtime.test.ts +195 -0
  44. package/src/client/runtime.ts +296 -0
  45. package/src/client/services.test.ts +51 -0
  46. package/src/client/services.ts +404 -0
  47. package/src/client/sp00ky-client.test.ts +303 -0
  48. package/src/client/sp00ky-client.ts +586 -0
  49. package/src/events/events.test.ts +2 -1
  50. package/src/events/index.ts +3 -0
  51. package/src/index.ts +69 -1
  52. package/src/kernel/constants.test.ts +19 -0
  53. package/src/kernel/constants.ts +80 -0
  54. package/src/kernel/effects.test.ts +40 -0
  55. package/src/kernel/effects.ts +163 -0
  56. package/src/kernel/events.ts +57 -0
  57. package/src/kernel/interpreter.test.ts +116 -0
  58. package/src/kernel/interpreter.ts +148 -0
  59. package/src/kernel/saga.test.ts +89 -0
  60. package/src/kernel/saga.ts +73 -0
  61. package/src/live/index.test.ts +478 -0
  62. package/src/live/index.ts +1013 -0
  63. package/src/modules/app-release/index.test.ts +125 -0
  64. package/src/modules/app-release/index.ts +209 -0
  65. package/src/modules/auth/auth.local-first.test.ts +169 -0
  66. package/src/modules/auth/events/index.ts +9 -2
  67. package/src/modules/auth/impersonation-banner.ts +443 -0
  68. package/src/modules/auth/impersonation.integration.test.ts +205 -0
  69. package/src/modules/auth/impersonation.test.ts +377 -0
  70. package/src/modules/auth/impersonation.ts +179 -0
  71. package/src/modules/auth/index.ts +466 -19
  72. package/src/modules/crdt/crdt-field.ts +294 -0
  73. package/src/modules/crdt/crdt-hydration.test.ts +210 -0
  74. package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
  75. package/src/modules/crdt/index.ts +463 -0
  76. package/src/modules/crdt/loro-loader.ts +25 -0
  77. package/src/modules/devtools/flags.ts +349 -0
  78. package/src/modules/devtools/index.ts +746 -95
  79. package/src/modules/devtools/mutations.test.ts +92 -0
  80. package/src/modules/devtools/mutations.ts +160 -0
  81. package/src/modules/devtools/notify-throttle.test.ts +166 -0
  82. package/src/modules/devtools/state-shape.test.ts +263 -0
  83. package/src/modules/devtools/storage-info.test.ts +79 -0
  84. package/src/modules/devtools/storage-info.ts +168 -0
  85. package/src/modules/devtools/versions.test.ts +74 -0
  86. package/src/modules/devtools/versions.ts +110 -0
  87. package/src/modules/feature-flag/index.test.ts +298 -0
  88. package/src/modules/feature-flag/index.ts +316 -0
  89. package/src/modules/ref-tables.test.ts +91 -0
  90. package/src/modules/ref-tables.ts +88 -0
  91. package/src/modules/web-push/index.test.ts +824 -0
  92. package/src/modules/web-push/index.ts +1027 -0
  93. package/src/mutation/jobs.test.ts +25 -0
  94. package/src/mutation/jobs.ts +41 -0
  95. package/src/mutation/mutation-id.test.ts +37 -0
  96. package/src/mutation/mutation-id.ts +35 -0
  97. package/src/mutation/pending-writes.integration.test.ts +175 -0
  98. package/src/mutation/push.saga.test.ts +285 -0
  99. package/src/mutation/push.saga.ts +217 -0
  100. package/src/mutation/rows.test.ts +231 -0
  101. package/src/mutation/rows.ts +427 -0
  102. package/src/mutation/tray.saga.test.ts +63 -0
  103. package/src/mutation/tray.saga.ts +48 -0
  104. package/src/mutation/write.saga.test.ts +316 -0
  105. package/src/mutation/write.saga.ts +268 -0
  106. package/src/otel/index.ts +10 -7
  107. package/src/pure.ts +65 -0
  108. package/src/push/types.ts +174 -0
  109. package/src/query/env.test.ts +23 -0
  110. package/src/query/env.ts +56 -0
  111. package/src/query/fetch.saga.test.ts +199 -0
  112. package/src/query/fetch.saga.ts +120 -0
  113. package/src/query/hash.test.ts +14 -0
  114. package/src/query/hash.ts +18 -0
  115. package/src/query/id-set-plan.test.ts +132 -0
  116. package/src/query/lifecycle.saga.test.ts +274 -0
  117. package/src/query/lifecycle.saga.ts +159 -0
  118. package/src/query/materialize.saga.test.ts +115 -0
  119. package/src/query/materialize.saga.ts +84 -0
  120. package/src/query/materialize.test.ts +58 -0
  121. package/src/query/materialize.ts +57 -0
  122. package/src/query/membership.saga.test.ts +350 -0
  123. package/src/query/membership.saga.ts +278 -0
  124. package/src/query/membership.test.ts +118 -0
  125. package/src/query/membership.ts +201 -0
  126. package/src/query/register.saga.test.ts +311 -0
  127. package/src/query/register.saga.ts +229 -0
  128. package/src/query/render-set.test.ts +47 -0
  129. package/src/query/render-set.ts +71 -0
  130. package/src/query/sql.test.ts +81 -0
  131. package/src/query/sql.ts +112 -0
  132. package/src/query/window-query.test.ts +70 -0
  133. package/src/query/window-query.ts +195 -0
  134. package/src/services/blobs/blob-cache.test.ts +359 -0
  135. package/src/services/blobs/blob-cache.ts +603 -0
  136. package/src/services/blobs/blob-manifest.ts +227 -0
  137. package/src/services/blobs/blob-store.test.ts +77 -0
  138. package/src/services/blobs/blob-store.ts +359 -0
  139. package/src/services/blobs/blob.fixture.ts +90 -0
  140. package/src/services/blobs/index.ts +70 -0
  141. package/src/services/database/cache-engine.ts +199 -0
  142. package/src/services/database/connection-supervisor.test.ts +318 -0
  143. package/src/services/database/connection-supervisor.ts +422 -0
  144. package/src/services/database/database.query-timeout.test.ts +140 -0
  145. package/src/services/database/database.responses.test.ts +43 -0
  146. package/src/services/database/database.ts +146 -61
  147. package/src/services/database/engine-factory.ts +33 -0
  148. package/src/services/database/errors.ts +34 -0
  149. package/src/services/database/events/index.ts +2 -1
  150. package/src/services/database/index.ts +7 -0
  151. package/src/services/database/landed-body-clear.integration.test.ts +236 -0
  152. package/src/services/database/local-migrator.integration.test.ts +110 -0
  153. package/src/services/database/local-migrator.ts +59 -16
  154. package/src/services/database/local.test.ts +64 -0
  155. package/src/services/database/local.ts +487 -63
  156. package/src/services/database/plan-render.test.ts +159 -0
  157. package/src/services/database/plan-render.ts +108 -0
  158. package/src/services/database/relation-resolver.test.ts +484 -0
  159. package/src/services/database/relation-resolver.ts +0 -0
  160. package/src/services/database/remote.ts +255 -6
  161. package/src/services/database/sqlite-cache-engine.test.ts +617 -0
  162. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  163. package/src/services/database/sqlite-cache-engine.ts +1372 -0
  164. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  165. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  166. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  167. package/src/services/database/sqlite-lock-verify.ts +45 -0
  168. package/src/services/database/sqlite-open.test.ts +150 -0
  169. package/src/services/database/sqlite-open.ts +164 -0
  170. package/src/services/database/sqlite-plan-sql.test.ts +145 -0
  171. package/src/services/database/sqlite-plan-sql.ts +182 -0
  172. package/src/services/database/sqlite-projection.test.ts +121 -0
  173. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  174. package/src/services/database/sqlite-select.test.ts +261 -0
  175. package/src/services/database/sqlite-select.ts +131 -0
  176. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  177. package/src/services/database/sqlite-transport.ts +224 -0
  178. package/src/services/database/sqlite-worker.ts +437 -0
  179. package/src/services/database/surql-translate.ts +416 -0
  180. package/src/services/database/surreal-cache-engine.ts +161 -0
  181. package/src/services/logger/index.test.ts +132 -0
  182. package/src/services/logger/index.ts +189 -7
  183. package/src/services/persistence/localstorage.ts +2 -2
  184. package/src/services/persistence/resilient.ts +10 -3
  185. package/src/services/persistence/surrealdb.ts +4 -4
  186. package/src/services/stream-processor/index.ts +783 -68
  187. package/src/services/stream-processor/permissions.test.ts +47 -0
  188. package/src/services/stream-processor/permissions.ts +53 -0
  189. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  190. package/src/services/stream-processor/stream-processor.prime.test.ts +198 -0
  191. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  192. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  193. package/src/services/stream-processor/wasm-types.ts +58 -2
  194. package/src/services/tabs/broker-client.ts +283 -0
  195. package/src/services/tabs/broker.test.ts +327 -0
  196. package/src/services/tabs/coordinator.test.ts +386 -0
  197. package/src/services/tabs/coordinator.ts +638 -0
  198. package/src/services/tabs/fake-ports.fixture.ts +112 -0
  199. package/src/services/tabs/leader-locks.ts +75 -0
  200. package/src/services/tabs/protocol.ts +262 -0
  201. package/src/services/tabs/support.ts +36 -0
  202. package/src/services/tabs/tabs-broker-worker.ts +640 -0
  203. package/src/sp00ky.ts +5 -395
  204. package/src/state/client-state.ts +200 -0
  205. package/src/state/lifecycle.test.ts +141 -0
  206. package/src/state/lifecycle.ts +99 -0
  207. package/src/state/reducers.test.ts +304 -0
  208. package/src/state/reducers.ts +526 -0
  209. package/src/state/selectors.test.ts +175 -0
  210. package/src/state/selectors.ts +222 -0
  211. package/src/sw/index.test.ts +582 -0
  212. package/src/sw/index.ts +607 -0
  213. package/src/sw/store.ts +96 -0
  214. package/src/sync/connection.saga.test.ts +86 -0
  215. package/src/sync/connection.saga.ts +85 -0
  216. package/src/sync/live.saga.test.ts +178 -0
  217. package/src/sync/live.saga.ts +107 -0
  218. package/src/sync/policy.health.test.ts +56 -0
  219. package/src/sync/policy.test.ts +659 -0
  220. package/src/sync/policy.ts +502 -0
  221. package/src/sync/poll.saga.test.ts +51 -0
  222. package/src/sync/poll.saga.ts +36 -0
  223. package/src/sync/tabs.saga.test.ts +78 -0
  224. package/src/sync/tabs.saga.ts +96 -0
  225. package/src/testing/adapters.ts +133 -0
  226. package/src/testing/build.ts +63 -0
  227. package/src/testing/fake-services.ts +48 -0
  228. package/src/testing/run-pure.test.ts +92 -0
  229. package/src/testing/run-pure.ts +128 -0
  230. package/src/testing/services.ts +17 -0
  231. package/src/types.ts +624 -10
  232. package/src/utils/blurhash.ts +90 -0
  233. package/src/utils/edges.ts +39 -0
  234. package/src/utils/error-classification.test.ts +54 -0
  235. package/src/utils/error-classification.ts +25 -0
  236. package/src/utils/index.ts +78 -12
  237. package/src/utils/parser.test.ts +67 -102
  238. package/src/utils/parser.ts +64 -2
  239. package/src/utils/semver.test.ts +32 -0
  240. package/src/utils/semver.ts +30 -0
  241. package/src/utils/sha256.ts +15 -0
  242. package/src/utils/surql.ts +30 -18
  243. package/src/utils/utils.test.ts +3 -2
  244. package/tsdown.config.ts +117 -3
  245. package/vitest.config.ts +48 -0
  246. package/src/modules/cache/index.ts +0 -241
  247. package/src/modules/cache/types.ts +0 -19
  248. package/src/modules/data/data.test.ts +0 -58
  249. package/src/modules/data/index.ts +0 -879
  250. package/src/modules/sync/engine.ts +0 -173
  251. package/src/modules/sync/events/index.ts +0 -77
  252. package/src/modules/sync/index.ts +0 -3
  253. package/src/modules/sync/queue/index.ts +0 -2
  254. package/src/modules/sync/queue/queue-down.ts +0 -89
  255. package/src/modules/sync/queue/queue-up.ts +0 -223
  256. package/src/modules/sync/scheduler.ts +0 -84
  257. package/src/modules/sync/sync.ts +0 -421
  258. package/src/modules/sync/utils.test.ts +0 -311
  259. package/src/modules/sync/utils.ts +0 -167
@@ -0,0 +1,439 @@
1
+ import { DateTime, Duration, RecordId } from "surrealdb";
2
+ import { RecordId as RecordId$1, baseFieldOfParam, cyrb53 } from "@spooky-sync/query-builder";
3
+
4
+ //#region src/query/hash.ts
5
+ /** Session-salted: two tabs of one user must not share a remote view. */
6
+ const queryHashInput = (input, sessionId) => JSON.stringify({
7
+ surql: input.surql,
8
+ params: input.params,
9
+ sessionId
10
+ });
11
+ /** Session-independent: survives reload and bucket switch. */
12
+ const viewKeyInput = (input) => JSON.stringify({
13
+ surql: input.surql,
14
+ params: input.params
15
+ });
16
+
17
+ //#endregion
18
+ //#region src/utils/sha256.ts
19
+ /**
20
+ * Lowercase hex SHA-256 of a UTF-8 string, through WebCrypto.
21
+ *
22
+ * Pure and dependency free so it can live in `@spooky-sync/core/pure` and run
23
+ * in a service worker, a dedicated worker or Node (`globalThis.crypto` is
24
+ * there on Node 19+). Query keys are `sha256Hex(JSON.stringify(..))`, see
25
+ * `query/hash.ts`.
26
+ */
27
+ async function sha256Hex(input) {
28
+ const bytes = new TextEncoder().encode(input);
29
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
30
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
31
+ }
32
+
33
+ //#endregion
34
+ //#region src/utils/surql.ts
35
+ const surql = {
36
+ seal(query, options) {
37
+ if (typeof query === "string") return `${query};`;
38
+ const txQuery = query;
39
+ const idx = options?.resultIndex ?? txQuery.statementCount - 1;
40
+ return {
41
+ sql: `${txQuery.sql};`,
42
+ extract(results) {
43
+ return results[idx + 1];
44
+ }
45
+ };
46
+ },
47
+ tx(queries) {
48
+ return {
49
+ __brand: "TxQuery",
50
+ sql: `BEGIN TRANSACTION;\n${queries.join(";")};\nCOMMIT TRANSACTION`,
51
+ statementCount: queries.length
52
+ };
53
+ },
54
+ selectById(idVar, returnValues) {
55
+ return `SELECT ${returnValues.join(",")} FROM ONLY $${idVar}`;
56
+ },
57
+ selectByFieldsAnd(table, whereVar, returnValues) {
58
+ 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 ")}`;
59
+ },
60
+ create(idVar, dataVar) {
61
+ return `CREATE ONLY $${idVar} CONTENT $${dataVar}`;
62
+ },
63
+ createSet(idVar, keyDataVars) {
64
+ return `CREATE ONLY $${idVar} SET ${keyDataVars.map((keyDataVar) => typeof keyDataVar === "string" ? `${keyDataVar} = $${keyDataVar}` : "statement" in keyDataVar ? keyDataVar.statement : `${keyDataVar.key} = $${keyDataVar.variable}`).join(", ")}`;
65
+ },
66
+ upsert(idVar, dataVar) {
67
+ return `UPSERT ONLY $${idVar} REPLACE $${dataVar}`;
68
+ },
69
+ upsertMerge(idVar, dataVar) {
70
+ return `UPSERT ONLY $${idVar} MERGE $${dataVar}`;
71
+ },
72
+ updateMerge(idVar, dataVar) {
73
+ return `UPDATE ONLY $${idVar} MERGE $${dataVar}`;
74
+ },
75
+ updateSet(idVar, keyDataVar) {
76
+ return `UPDATE $${idVar} SET ${keyDataVar.map((kdv) => typeof kdv === "string" ? `${kdv} = $${kdv}` : "statement" in kdv ? kdv.statement : `${kdv.key} = $${kdv.variable}`).join(", ")}`;
77
+ },
78
+ delete(idVar) {
79
+ return `DELETE $${idVar}`;
80
+ },
81
+ let(name, query) {
82
+ return `LET $${name} = (${query})`;
83
+ },
84
+ createMutation(t, mutationIdVar, recordIdVar, dataVar, beforeRecordVar) {
85
+ switch (t) {
86
+ case "create": return dataVar ? `CREATE ONLY $${mutationIdVar} SET mutationType = 'create', recordId = $${recordIdVar}, data = $${dataVar}` : `CREATE ONLY $${mutationIdVar} SET mutationType = 'create', recordId = $${recordIdVar}`;
87
+ case "update": {
88
+ let stmt = `CREATE ONLY $${mutationIdVar} SET mutationType = 'update', recordId = $${recordIdVar}, data = $${dataVar}`;
89
+ if (beforeRecordVar) stmt += `, beforeRecord = $${beforeRecordVar}`;
90
+ return stmt;
91
+ }
92
+ case "delete": return `CREATE ONLY $${mutationIdVar} SET mutationType = 'delete', recordId = $${recordIdVar}`;
93
+ }
94
+ },
95
+ returnObject(entries) {
96
+ return `RETURN {${entries.map(({ key, variable }) => `${key}: $${variable}`).join(",")}}`;
97
+ }
98
+ };
99
+
100
+ //#endregion
101
+ //#region src/query/sql.ts
102
+ /** Every SurrealQL statement the query side sends, as pure builders. */
103
+ const VIEW_TABLE = "_00_view";
104
+ const LEGACY_VIEW_TABLE = "_00_window";
105
+ const viewRecordId = (viewKey) => new RecordId(VIEW_TABLE, viewKey);
106
+ /**
107
+ * The durable `_00_view` row: the confirmed membership plus the subquery
108
+ * children, which the GC must keep and a cold scan may paint as much as the
109
+ * members.
110
+ */
111
+ const viewRow = (ids, children, confirmed, now) => ({
112
+ ids,
113
+ children,
114
+ confirmed,
115
+ updatedAt: now
116
+ });
117
+ /** Every `_00_view` row: the durable view index and the GC read it. */
118
+ const readViewRows = () => `SELECT * FROM ${VIEW_TABLE}`;
119
+ /** Copy every legacy `_00_window` row into `_00_view` once. */
120
+ const readLegacyViewRows = () => `SELECT * FROM ${LEGACY_VIEW_TABLE}`;
121
+ const countViewRows = () => `SELECT count() FROM ${VIEW_TABLE} GROUP ALL`;
122
+ const listRefSelect = (table) => `SELECT out, version FROM ${table} WHERE in = $in AND parent IS NONE`;
123
+ const subqueryListRefSelect = (table) => `SELECT out, version FROM ${table} WHERE in = $in AND parent IS NOT NONE`;
124
+ const queryRowCountSelect = () => "SELECT VALUE { rowCount: rowCount, state: state } FROM ONLY $in";
125
+ const listRefBatchSelect = (table) => `SELECT in, out, version, parent FROM ${table} WHERE in IN $ins`;
126
+ const queryRowCountBatchSelect = () => "SELECT VALUE { id: id, rowCount: rowCount, state: state } FROM $ins";
127
+ /** The single-query read: edges, meta, subquery children in one request. */
128
+ const singleSnapshotSelect = (table) => `${listRefSelect(table)};\n${queryRowCountSelect()};\n${subqueryListRefSelect(table)}`;
129
+ /** The many-query read: all edges + all metas in one request. */
130
+ const batchSnapshotSelect = (table) => `${listRefBatchSelect(table)};\n${queryRowCountBatchSelect()}`;
131
+ /** Register + read back edges/meta/children in ONE request. */
132
+ const registerSelect = (table) => `fn::query::register($config);\n${singleSnapshotSelect(table)}`;
133
+ const registerVars = (payload) => ({
134
+ config: payload,
135
+ in: payload.id
136
+ });
137
+ /** One heartbeat statement per view id; result index i answers id i. */
138
+ function heartbeatBatch(ids) {
139
+ const vars = {};
140
+ return {
141
+ sql: ids.map((id, i) => {
142
+ vars[`id${i}`] = id;
143
+ return `fn::query::heartbeat($id${i})`;
144
+ }).join(";\n"),
145
+ vars
146
+ };
147
+ }
148
+ /** A heartbeat answer whose statement matched no row: the view was reclaimed. */
149
+ const heartbeatRowGone = (result) => Array.isArray(result) && result.length === 0;
150
+ const QUERY_ID = /^_00_query:(?:[0-9a-f]{64}|⟨[0-9a-f]{64}⟩)$/;
151
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
152
+ /**
153
+ * The pagehide release: one bare statement for `beaconSql`, which has no params
154
+ * channel, so ids are inlined. Only shapes the client minted itself get in, a
155
+ * `_00_query:<sha256>` id and a UUID session id, so nothing else is ever
156
+ * interpolated. `null` when there is nothing the server could act on: with no
157
+ * session ids it could not tell this page's entries from anyone else's.
158
+ */
159
+ function leaveViewsBeacon(ids, sessionIds) {
160
+ const list = ids.map((id) => String(id)).filter((id) => QUERY_ID.test(id));
161
+ const sids = sessionIds.filter((sid) => SESSION_ID.test(sid));
162
+ if (list.length === 0 || sids.length === 0) return null;
163
+ const sidList = sids.map((sid) => `'${sid}'`).join(", ");
164
+ return `FOR $id IN [${list.join(", ")}] { LET $_left = fn::query::leave($id, [${sidList}]); };`;
165
+ }
166
+ const bodySelect = () => "SELECT * FROM $ids";
167
+ /** Local transaction that MERGEs many fetched bodies. */
168
+ function upsertBodiesTx(records) {
169
+ const vars = {};
170
+ const stmts = records.map((r, i) => {
171
+ vars[`id${i}`] = r.id;
172
+ vars[`content${i}`] = r.content;
173
+ return surql.upsertMerge(`id${i}`, `content${i}`);
174
+ });
175
+ return {
176
+ query: surql.seal(surql.tx(stmts)),
177
+ vars
178
+ };
179
+ }
180
+
181
+ //#endregion
182
+ //#region src/modules/ref-tables.ts
183
+ /**
184
+ * Sentinel user id for unauthenticated clients when anonymous live queries are
185
+ * enabled. Mirrors `ssp_protocol::ANON_AUTH_ID`. It carries no `user:` prefix
186
+ * so it can never collide with a real user id (those arrive as `user:<id>`);
187
+ * both sides resolve it to the shared `_00_list_ref_anon` table.
188
+ */
189
+ const ANON_USER_ID = "anon";
190
+ /**
191
+ * Default ref-storage mode for this client build. Mirrors the SSP's
192
+ * default (`RefMode::Dedicated`) so cross-session sync works out of the
193
+ * box.
194
+ */
195
+ const DEFAULT_REF_MODE = "dedicated";
196
+ /**
197
+ * Sanitize a user record id (e.g. `"user:abc"`) into the segment that
198
+ * goes into a dedicated table name (e.g. `"abc"`). Returns `null` if
199
+ * the id is missing the `user:` prefix or contains characters that
200
+ * aren't valid in a SurrealDB table identifier — the server-side
201
+ * `ssp_protocol::sanitize_user_id` uses the same predicate.
202
+ *
203
+ * Accepts both string ids (`"user:abc"`) and SurrealDB `RecordId`
204
+ * objects (which only stringify cleanly via `.toString()`), since
205
+ * `AuthService` passes the record-id object as-is to its subscribers.
206
+ */
207
+ function sanitizeUserId(userId) {
208
+ if (userId === null || userId === void 0) return null;
209
+ const asString = typeof userId === "string" ? userId : typeof userId.toString === "function" ? userId.toString() : null;
210
+ if (!asString) return null;
211
+ const raw = asString.startsWith("user:") ? asString.slice(5) : asString;
212
+ if (raw.length === 0) return null;
213
+ if (!/^[A-Za-z0-9_]+$/.test(raw)) return null;
214
+ return raw;
215
+ }
216
+ /**
217
+ * Resolve the LOCAL storage bucket id for a user. Every user gets their own
218
+ * IndexedDB-backed local store (`indxdb://sp00ky-<bucketId>`) so cached rows,
219
+ * query state, and the mutation outbox never leak across accounts on a shared
220
+ * device. Signed-out sessions share the `anon` bucket.
221
+ *
222
+ * An id that fails sanitization still gets a DETERMINISTIC per-user bucket
223
+ * (cyrb53 hex of the raw id) — falling back to `anon` here would put an
224
+ * authenticated user in the shared bucket and recreate the cross-user leak.
225
+ */
226
+ function bucketIdForUser(userId) {
227
+ if (userId === null || userId === void 0 || userId === ANON_USER_ID) return ANON_USER_ID;
228
+ const uid = sanitizeUserId(userId);
229
+ if (uid) return uid;
230
+ return `u${cyrb53(String(userId)).toString(16)}`;
231
+ }
232
+ /**
233
+ * Returns the `_00_list_ref` table name for `(mode, userId)`. Falls
234
+ * back to the global `_00_list_ref` when sanitization fails or in
235
+ * single mode.
236
+ */
237
+ function listRefTableFor(mode, userId) {
238
+ if (userId === ANON_USER_ID) return "_00_list_ref_anon";
239
+ if (mode === "single") return "_00_list_ref";
240
+ const uid = sanitizeUserId(userId);
241
+ return uid ? `_00_list_ref_user_${uid}` : "_00_list_ref";
242
+ }
243
+
244
+ //#endregion
245
+ //#region src/utils/parser.ts
246
+ /**
247
+ * Parse a QUERY's params. Unlike a record's fields, a param name is not always a
248
+ * column: an `_or` branch binds under a synthetic `white__or0` (see
249
+ * `orParamName` in @spooky-sync/query-builder), and the surql that references it
250
+ * is already written. So resolve the column through the synthetic name, and keep
251
+ * a param we cannot type rather than dropping it.
252
+ *
253
+ * Dropping was the bug: `parseParams` kept only column-named params, so every
254
+ * `_or` query registered with `$or0`/`$or1` unbound and matched no rows at all,
255
+ * silently (rowCount 0, errorCount 0).
256
+ */
257
+ function parseQueryParams(tableSchema, params) {
258
+ const parsedParams = {};
259
+ for (const [key, value] of Object.entries(params)) {
260
+ if (value === void 0) continue;
261
+ const column = tableSchema[key] ?? tableSchema[baseFieldOfParam(key)];
262
+ parsedParams[key] = column ? parseValue(key, column, value) : value;
263
+ }
264
+ return parsedParams;
265
+ }
266
+ function parseValue(name, column, value) {
267
+ if (column.recordId) {
268
+ if (value instanceof RecordId$1) return value;
269
+ if (typeof value === "string") return parseRecordIdString(value);
270
+ throw new Error(`Invalid value for ${name}: ${value}`);
271
+ }
272
+ if (column.dateTime) {
273
+ if (value instanceof Date) return value;
274
+ if (value instanceof DateTime) return value.toDate();
275
+ if (typeof value === "number" || typeof value === "string") return new Date(value);
276
+ throw new Error(`Invalid value for ${name}: ${value}`);
277
+ }
278
+ return value;
279
+ }
280
+
281
+ //#endregion
282
+ //#region src/utils/index.ts
283
+ const encodeRecordId = (recordId) => {
284
+ return `${recordId.table.name}:${recordId.id}`;
285
+ };
286
+ const parseRecordIdString = (id) => {
287
+ const [table, ...idParts] = id.split(":");
288
+ return new RecordId(table, idParts.join(":"));
289
+ };
290
+ /**
291
+ * Read the millisecond count off a surrealdb `Duration`. Different `surrealdb`
292
+ * releases expose it under `milliseconds` (current) or the older private
293
+ * `_milliseconds` field, so read whichever is set; returns 0 when neither is.
294
+ */
295
+ function durationMillis(duration) {
296
+ const d = duration;
297
+ return Number(d.milliseconds || d._milliseconds || 0);
298
+ }
299
+ /**
300
+ * Parse duration string or Duration object to milliseconds
301
+ */
302
+ function parseDuration(duration) {
303
+ if (duration instanceof Duration) {
304
+ const ms = durationMillis(duration);
305
+ if (ms) return ms;
306
+ const str = duration.toString();
307
+ if (str !== "[object Object]") return parseDuration(str);
308
+ return 6e5;
309
+ }
310
+ if (typeof duration === "bigint") return Number(duration);
311
+ if (typeof duration !== "string") return 6e5;
312
+ const match = duration.match(/^(\d+)([smh])$/);
313
+ if (!match) return 6e5;
314
+ const val = Number.parseInt(match[1], 10);
315
+ switch (match[2]) {
316
+ case "s": return val * 1e3;
317
+ case "h": return val * 36e5;
318
+ default: return val * 6e4;
319
+ }
320
+ }
321
+ /**
322
+ * Reject after `timeoutMs` if `promise` hasn't settled.
323
+ *
324
+ * Exists because a WebSocket RPC has no deadline of its own: the SurrealDB SDK
325
+ * parks each call in a pending map and only rejects it when the socket reports
326
+ * a close. On a half-open socket (peer gone, no `close` event, `readyState`
327
+ * still OPEN) the call never settles at all, which wedges anything serialized
328
+ * behind it.
329
+ *
330
+ * `message` MUST contain "timed out" so `classifySyncError` treats the
331
+ * rejection as `network` — that's what makes the sync queues retry the
332
+ * operation instead of rolling the mutation back as an application error.
333
+ *
334
+ * Non-positive `timeoutMs` returns `promise` unchanged. The underlying promise
335
+ * is NOT cancelled (nothing can cancel an in-flight RPC); its eventual
336
+ * settlement is absorbed, so attach no expectations to it after a timeout.
337
+ */
338
+ function withTimeout(promise, timeoutMs, message) {
339
+ if (!(timeoutMs > 0)) return promise;
340
+ return new Promise((resolve, reject) => {
341
+ const timer = setTimeout(() => reject(typeof message === "function" ? message() : new Error(message)), timeoutMs);
342
+ promise.then((value) => {
343
+ clearTimeout(timer);
344
+ resolve(value);
345
+ }, (err) => {
346
+ clearTimeout(timer);
347
+ reject(err);
348
+ });
349
+ });
350
+ }
351
+
352
+ //#endregion
353
+ //#region src/query/membership.ts
354
+ function metaFromRow(row) {
355
+ if (!row || typeof row !== "object") return {
356
+ present: false,
357
+ rowCount: null,
358
+ state: null
359
+ };
360
+ const state = row.state === "materializing" || row.state === "ready" ? row.state : null;
361
+ return {
362
+ present: true,
363
+ rowCount: typeof row.rowCount === "number" ? row.rowCount : null,
364
+ state
365
+ };
366
+ }
367
+ /** Collapse duplicate `(id, version)` pairs to one per id, keeping the highest version. */
368
+ function dedupeRecordVersions(pairs) {
369
+ if (pairs.length < 2) return pairs;
370
+ const best = /* @__PURE__ */ new Map();
371
+ for (const [id, version] of pairs) {
372
+ const prev = best.get(id);
373
+ if (prev === void 0 || version > prev) best.set(id, version);
374
+ }
375
+ if (best.size === pairs.length) return pairs;
376
+ return Array.from(best.entries());
377
+ }
378
+ /**
379
+ * A statement that did not answer (an ERR, or no result at all). Kept apart
380
+ * from a `null` result, which is the server saying "no such row": reading a
381
+ * failed statement as a missing row turned every timeout under load into a
382
+ * lost view and re-registered it, over and over (whitepawn, 2026-09-16).
383
+ */
384
+ const FAILED = Symbol("statement failed");
385
+ const toPairs = (rows) => dedupeRecordVersions(Array.isArray(rows) ? rows.map((r) => [encodeRecordId(r.out), r.version]) : []);
386
+ /**
387
+ * Fold the single-query statement batch (edges, meta, children) into a
388
+ * snapshot. `null` when any of the three did not answer: half an answer is
389
+ * not one, and a missing meta would read as a lost view.
390
+ */
391
+ function snapshotFromSingle(items, metaRow, children) {
392
+ if (!Array.isArray(items) || metaRow === FAILED || children === FAILED) return null;
393
+ return {
394
+ primary: toPairs(items),
395
+ subquery: toPairs(children),
396
+ meta: metaFromRow(metaRow)
397
+ };
398
+ }
399
+
400
+ //#endregion
401
+ //#region src/utils/edges.ts
402
+ /**
403
+ * Reading `_00_list_ref` edges as a LIVE notification delivers them. Pure, so
404
+ * both the full client and the wasm-free live feed (`@spooky-sync/core/live`)
405
+ * decode the same wire shape the same way.
406
+ */
407
+ /** The hash a `_00_list_ref` edge belongs to: the id part of its `in` (`_00_query:<hash>`). */
408
+ function hashOfEdge(value) {
409
+ const inId = value?.in;
410
+ if (!inId) return null;
411
+ const str = inId instanceof RecordId ? String(inId.id) : String(inId).replace(/^_00_query:/, "");
412
+ return str.length > 0 ? str : null;
413
+ }
414
+ /**
415
+ * The row an edge notification carries, when the subscription joined it on.
416
+ *
417
+ * Without `FETCH out` the edge's `out` is a record id and there is nothing to
418
+ * land, so this returns null and the caller falls back to fetching the body.
419
+ * It also returns null for a row the session may not read (the join yields
420
+ * `out: null`) and for an edge whose target has been deleted.
421
+ */
422
+ function rowOfEdge(value) {
423
+ const edge = value;
424
+ const out = edge?.out;
425
+ if (!out || typeof out !== "object" || Array.isArray(out) || out instanceof RecordId) return null;
426
+ const rid = out.id;
427
+ if (!(rid instanceof RecordId)) return null;
428
+ const id = encodeRecordId(rid);
429
+ const version = typeof edge?.version === "number" ? edge.version : null;
430
+ if (version === null) return null;
431
+ return {
432
+ id,
433
+ version,
434
+ record: out
435
+ };
436
+ }
437
+
438
+ //#endregion
439
+ export { readViewRows as A, queryHashInput as B, heartbeatRowGone as C, queryRowCountBatchSelect as D, listRefSelect as E, upsertBodiesTx as F, viewRecordId as I, viewRow as L, registerVars as M, singleSnapshotSelect as N, queryRowCountSelect as O, subqueryListRefSelect as P, surql as R, heartbeatBatch as S, listRefBatchSelect as T, viewKeyInput as V, LEGACY_VIEW_TABLE as _, metaFromRow as a, bodySelect as b, parseDuration as c, parseQueryParams as d, ANON_USER_ID as f, sanitizeUserId as g, listRefTableFor as h, dedupeRecordVersions as i, registerSelect as j, readLegacyViewRows as k, parseRecordIdString as l, bucketIdForUser as m, rowOfEdge as n, snapshotFromSingle as o, DEFAULT_REF_MODE as p, FAILED as r, encodeRecordId as s, hashOfEdge as t, withTimeout as u, VIEW_TABLE as v, leaveViewsBeacon as w, countViewRows as x, batchSnapshotSelect as y, sha256Hex as z };
@@ -0,0 +1,60 @@
1
+ //#region src/modules/auth/impersonation.ts
2
+ /**
3
+ * Admin impersonation, client side.
4
+ *
5
+ * The server does the enforcing (see `impersonation_remote.surql` in the CLI):
6
+ * an admin calls `fn::_00_impersonate::start`, receives a token for the
7
+ * `_00_impersonate` access method, and authenticates with it, so `$auth.id` is
8
+ * the target user. Everything here is state and timers around that:
9
+ *
10
+ * - the admin's own token is kept under {@link ORIGIN_TOKEN_KEY} so Stop (and a
11
+ * reload in between) can return to it;
12
+ * - the impersonation state is derived from the token's claims, never stored
13
+ * separately, so it cannot disagree with the session actually in use;
14
+ * - the token is re-signed before it expires, and the session is polled so a
15
+ * stop or revocation made elsewhere reaches an open WebSocket quickly.
16
+ */
17
+ /** Access method impersonation tokens are issued for. */
18
+ const IMPERSONATION_ACCESS = "_00_impersonate";
19
+ /**
20
+ * Read the claims of a SurrealDB record-access JWT WITHOUT verifying it. The
21
+ * server still enforces the token on every request; this is only so the client
22
+ * can act on what it already holds before a round trip completes.
23
+ *
24
+ * Returns nulls on any malformed input.
25
+ */
26
+ function decodeTokenClaims(token) {
27
+ const none = {
28
+ access: null,
29
+ userId: null,
30
+ exp: null,
31
+ impersonation: null
32
+ };
33
+ try {
34
+ const payload = token.split(".")[1];
35
+ if (!payload) return none;
36
+ let b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
37
+ b64 += "=".repeat((4 - b64.length % 4) % 4);
38
+ const json = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("binary");
39
+ const claims = JSON.parse(json);
40
+ const ac = claims.AC ?? claims.ac;
41
+ const id = claims.ID ?? claims.id;
42
+ const access = typeof ac === "string" ? ac : null;
43
+ const session = claims.spky_imp;
44
+ const admin = claims.spky_admin;
45
+ return {
46
+ access,
47
+ userId: typeof id === "string" ? id : null,
48
+ exp: typeof claims.exp === "number" ? claims.exp : null,
49
+ impersonation: access === IMPERSONATION_ACCESS && typeof session === "string" && typeof admin === "string" ? {
50
+ session,
51
+ admin
52
+ } : null
53
+ };
54
+ } catch {
55
+ return none;
56
+ }
57
+ }
58
+
59
+ //#endregion
60
+ export { decodeTokenClaims as n, IMPERSONATION_ACCESS as t };
@@ -0,0 +1,46 @@
1
+ //#region src/modules/ref-tables.d.ts
2
+ type RefMode = 'single' | 'dedicated';
3
+ /**
4
+ * Sentinel user id for unauthenticated clients when anonymous live queries are
5
+ * enabled. Mirrors `ssp_protocol::ANON_AUTH_ID`. It carries no `user:` prefix
6
+ * so it can never collide with a real user id (those arrive as `user:<id>`);
7
+ * both sides resolve it to the shared `_00_list_ref_anon` table.
8
+ */
9
+ declare const ANON_USER_ID = "anon";
10
+ /**
11
+ * Default ref-storage mode for this client build. Mirrors the SSP's
12
+ * default (`RefMode::Dedicated`) so cross-session sync works out of the
13
+ * box.
14
+ */
15
+ declare const DEFAULT_REF_MODE: RefMode;
16
+ /**
17
+ * Sanitize a user record id (e.g. `"user:abc"`) into the segment that
18
+ * goes into a dedicated table name (e.g. `"abc"`). Returns `null` if
19
+ * the id is missing the `user:` prefix or contains characters that
20
+ * aren't valid in a SurrealDB table identifier — the server-side
21
+ * `ssp_protocol::sanitize_user_id` uses the same predicate.
22
+ *
23
+ * Accepts both string ids (`"user:abc"`) and SurrealDB `RecordId`
24
+ * objects (which only stringify cleanly via `.toString()`), since
25
+ * `AuthService` passes the record-id object as-is to its subscribers.
26
+ */
27
+ declare function sanitizeUserId(userId: unknown): string | null;
28
+ /**
29
+ * Resolve the LOCAL storage bucket id for a user. Every user gets their own
30
+ * IndexedDB-backed local store (`indxdb://sp00ky-<bucketId>`) so cached rows,
31
+ * query state, and the mutation outbox never leak across accounts on a shared
32
+ * device. Signed-out sessions share the `anon` bucket.
33
+ *
34
+ * An id that fails sanitization still gets a DETERMINISTIC per-user bucket
35
+ * (cyrb53 hex of the raw id) — falling back to `anon` here would put an
36
+ * authenticated user in the shared bucket and recreate the cross-user leak.
37
+ */
38
+ declare function bucketIdForUser(userId: unknown): string;
39
+ /**
40
+ * Returns the `_00_list_ref` table name for `(mode, userId)`. Falls
41
+ * back to the global `_00_list_ref` when sanitization fails or in
42
+ * single mode.
43
+ */
44
+ declare function listRefTableFor(mode: RefMode, userId: unknown): string;
45
+ //#endregion
46
+ export { listRefTableFor as a, bucketIdForUser as i, DEFAULT_REF_MODE as n, sanitizeUserId as o, RefMode as r, ANON_USER_ID as t };
@@ -0,0 +1,112 @@
1
+ //#region src/push/types.d.ts
2
+ /**
3
+ * Web Push wire types shared by the page (`db.webPush`), the service worker
4
+ * bridge (`@spooky-sync/core/sw`) and the live feed. Pure: types plus a few
5
+ * dependency-free helpers, safe in any context.
6
+ *
7
+ * `PushPayload` mirrors `packages/push-core/src/config.rs` (`PushPayload`,
8
+ * `PayloadKind`, `Op`, `NotificationTemplate`). Keep the two in step.
9
+ */
10
+ /** Payload format version this build understands. */
11
+ declare const PUSH_PAYLOAD_VERSION = 1;
12
+ type PushPayloadKind = 'rule' | 'message';
13
+ type PushOp = 'create' | 'update' | 'delete';
14
+ type PushUrgency = 'very-low' | 'low' | 'normal' | 'high';
15
+ /** One button on a notification (`NotificationAction` plus a per-action `url`). */
16
+ interface PushNotificationAction {
17
+ action: string;
18
+ title: string;
19
+ icon?: string;
20
+ /** Opened instead of the notification's `url` when this action is clicked. */
21
+ url?: string;
22
+ [key: string]: unknown;
23
+ }
24
+ /**
25
+ * What a content push shows. Every key other than `title` and `url` is passed
26
+ * to `showNotification` as is, unknown ones included (`vibrate`, `timestamp`,
27
+ * whatever a browser adds later).
28
+ */
29
+ interface PushNotification {
30
+ title?: string;
31
+ body?: string;
32
+ icon?: string;
33
+ badge?: string;
34
+ image?: string;
35
+ lang?: string;
36
+ dir?: string;
37
+ /** Defaults to the push's `topic`. */
38
+ tag?: string;
39
+ /** Opened (or focused) on click. */
40
+ url?: string;
41
+ requireInteraction?: boolean;
42
+ renotify?: boolean;
43
+ silent?: boolean;
44
+ actions?: PushNotificationAction[];
45
+ /** Merged into the notification's `data` next to the bridge's own keys. */
46
+ data?: Record<string, unknown>;
47
+ [key: string]: unknown;
48
+ }
49
+ /**
50
+ * The JSON every push carries (then RFC 8291 encrypted). `notification`
51
+ * present: a content push the service worker shows as is. Absent: a
52
+ * content-free nudge, the app renders from its own data.
53
+ */
54
+ interface PushPayload {
55
+ v: number;
56
+ kind: PushPayloadKind;
57
+ /** Rule name (`kind: 'rule'`). */
58
+ rule?: string;
59
+ /** `_00_push_message` id (`kind: 'message'`). */
60
+ message?: string;
61
+ /** The row that fired the rule. */
62
+ table?: string;
63
+ id?: string;
64
+ op?: PushOp;
65
+ topic?: string;
66
+ notification?: PushNotification;
67
+ data?: Record<string, unknown>;
68
+ /** Epoch ms when the engine built it. */
69
+ ts: number;
70
+ }
71
+ /** Page -> service worker: the session to use for live rendering. */
72
+ interface BridgeTokenMessage {
73
+ type: 'sp00ky:token';
74
+ token: string;
75
+ userId: string;
76
+ /** The SurrealDB endpoint (`wss://host/rpc`), not the push endpoint. */
77
+ endpoint: string;
78
+ namespace: string;
79
+ database: string;
80
+ /** VAPID public key (base64url) the page subscribed with, for `pushsubscriptionchange`. */
81
+ publicKey?: string;
82
+ /** This browser's push endpoint, when subscribed. */
83
+ pushEndpoint?: string;
84
+ }
85
+ /** Page -> service worker: forget the session and close every notification. */
86
+ interface BridgeSignOutMessage {
87
+ type: 'sp00ky:signout';
88
+ }
89
+ /** Service worker -> page messages. */
90
+ type WebPushClientMessage = /** A push arrived while this page was visible, so nothing was shown. */
91
+ {
92
+ type: 'sp00ky:push';
93
+ payload: PushPayload;
94
+ }
95
+ /** A notification was clicked and the bridge was told to route in-app. */ | {
96
+ type: 'sp00ky:navigate';
97
+ url: string;
98
+ payload?: PushPayload;
99
+ action?: string;
100
+ }
101
+ /** The browser rotated the push subscription; the page should `sync()`. */ | {
102
+ type: 'sp00ky:subscriptionchange';
103
+ endpoint?: string;
104
+ };
105
+ /** `urlBase64ToUint8Array`: the VAPID key as `applicationServerKey` wants it. */
106
+ declare function base64UrlToBytes(value: string): Uint8Array;
107
+ declare function bytesToBase64Url(bytes: ArrayBuffer | Uint8Array): string;
108
+ declare function sameBytes(a: ArrayBuffer | Uint8Array | null | undefined, b: ArrayBuffer | Uint8Array | null | undefined): boolean;
109
+ /** A payload this build can act on (`v` known, `kind` set). */
110
+ declare function isPushPayload(value: unknown): value is PushPayload;
111
+ //#endregion
112
+ export { PushNotificationAction as a, PushPayloadKind as c, base64UrlToBytes as d, bytesToBase64Url as f, PushNotification as i, PushUrgency as l, sameBytes as m, BridgeTokenMessage as n, PushOp as o, isPushPayload as p, PUSH_PAYLOAD_VERSION as r, PushPayload as s, BridgeSignOutMessage as t, WebPushClientMessage as u };