@spooky-sync/core 0.0.1-canary.33 → 0.0.1-canary.331

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 +84 -0
  2. package/README.md +56 -16
  3. package/dist/index.d.ts +2080 -142
  4. package/dist/index.js +16009 -2480
  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/otel/index.js +3 -3
  9. package/dist/pure.d.ts +373 -0
  10. package/dist/pure.js +24 -0
  11. package/dist/sqlite-open.js +392 -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 +477 -0
  18. package/dist/types.d.ts +1857 -38
  19. package/dist/worker-edges.js +480 -0
  20. package/dist/worker-impersonation.js +60 -0
  21. package/dist/worker-ref-tables.d.ts +48 -0
  22. package/dist/worker-types.d.ts +112 -0
  23. package/dist/worker-types.js +73 -0
  24. package/package.json +28 -7
  25. package/scripts/check-broker-bundle.mjs +33 -0
  26. package/scripts/check-worker-entries.mjs +173 -0
  27. package/skills/sp00ky-core/SKILL.md +32 -7
  28. package/skills/sp00ky-core/references/config.md +7 -1
  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 +203 -0
  32. package/src/boot/boot.saga.ts +146 -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 +80 -0
  36. package/src/boot/preload.saga.ts +40 -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 +195 -0
  40. package/src/client/bucket-handle.ts +352 -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 +74 -0
  46. package/src/client/services.ts +434 -0
  47. package/src/client/sp00ky-client.test.ts +341 -0
  48. package/src/client/sp00ky-client.ts +625 -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 +167 -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 +512 -0
  62. package/src/live/index.ts +1059 -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 +228 -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 +476 -22
  72. package/src/modules/crdt/acceptance.ts +75 -0
  73. package/src/modules/crdt/bytes.ts +50 -0
  74. package/src/modules/crdt/crdt-field.ts +125 -0
  75. package/src/modules/crdt/crdt.test.ts +469 -0
  76. package/src/modules/crdt/hash.ts +103 -0
  77. package/src/modules/crdt/index.ts +757 -0
  78. package/src/modules/crdt/loro-loader.ts +25 -0
  79. package/src/modules/crdt/mirror.ts +37 -0
  80. package/src/modules/devtools/flags.ts +349 -0
  81. package/src/modules/devtools/index.ts +746 -95
  82. package/src/modules/devtools/mutations.test.ts +92 -0
  83. package/src/modules/devtools/mutations.ts +160 -0
  84. package/src/modules/devtools/notify-throttle.test.ts +166 -0
  85. package/src/modules/devtools/state-shape.test.ts +263 -0
  86. package/src/modules/devtools/storage-info.test.ts +79 -0
  87. package/src/modules/devtools/storage-info.ts +168 -0
  88. package/src/modules/devtools/versions.test.ts +74 -0
  89. package/src/modules/devtools/versions.ts +110 -0
  90. package/src/modules/feature-flag/index.test.ts +298 -0
  91. package/src/modules/feature-flag/index.ts +316 -0
  92. package/src/modules/ref-tables.test.ts +93 -0
  93. package/src/modules/ref-tables.ts +91 -0
  94. package/src/modules/web-push/index.test.ts +824 -0
  95. package/src/modules/web-push/index.ts +1027 -0
  96. package/src/mutation/jobs.test.ts +25 -0
  97. package/src/mutation/jobs.ts +41 -0
  98. package/src/mutation/mutation-id.test.ts +37 -0
  99. package/src/mutation/mutation-id.ts +35 -0
  100. package/src/mutation/pending-writes.integration.test.ts +175 -0
  101. package/src/mutation/push.saga.test.ts +301 -0
  102. package/src/mutation/push.saga.ts +225 -0
  103. package/src/mutation/rows.test.ts +231 -0
  104. package/src/mutation/rows.ts +427 -0
  105. package/src/mutation/tray.saga.test.ts +63 -0
  106. package/src/mutation/tray.saga.ts +48 -0
  107. package/src/mutation/write.saga.test.ts +316 -0
  108. package/src/mutation/write.saga.ts +268 -0
  109. package/src/otel/index.ts +10 -7
  110. package/src/pure.ts +66 -0
  111. package/src/push/types.ts +174 -0
  112. package/src/query/anon-query-isolation.integration.test.ts +527 -0
  113. package/src/query/env.test.ts +23 -0
  114. package/src/query/env.ts +65 -0
  115. package/src/query/fetch.saga.test.ts +199 -0
  116. package/src/query/fetch.saga.ts +120 -0
  117. package/src/query/hash.test.ts +14 -0
  118. package/src/query/hash.ts +18 -0
  119. package/src/query/id-set-plan.test.ts +132 -0
  120. package/src/query/lifecycle.saga.test.ts +274 -0
  121. package/src/query/lifecycle.saga.ts +186 -0
  122. package/src/query/list-ref-live.integration.test.ts +203 -0
  123. package/src/query/materialize.saga.test.ts +115 -0
  124. package/src/query/materialize.saga.ts +84 -0
  125. package/src/query/materialize.test.ts +58 -0
  126. package/src/query/materialize.ts +57 -0
  127. package/src/query/membership.saga.test.ts +407 -0
  128. package/src/query/membership.saga.ts +282 -0
  129. package/src/query/membership.test.ts +118 -0
  130. package/src/query/membership.ts +201 -0
  131. package/src/query/register.saga.test.ts +311 -0
  132. package/src/query/register.saga.ts +235 -0
  133. package/src/query/render-set.test.ts +47 -0
  134. package/src/query/render-set.ts +71 -0
  135. package/src/query/sql.test.ts +118 -0
  136. package/src/query/sql.ts +157 -0
  137. package/src/query/window-query.test.ts +70 -0
  138. package/src/query/window-query.ts +208 -0
  139. package/src/services/blobs/blob-cache.test.ts +359 -0
  140. package/src/services/blobs/blob-cache.ts +603 -0
  141. package/src/services/blobs/blob-manifest.ts +227 -0
  142. package/src/services/blobs/blob-store.test.ts +77 -0
  143. package/src/services/blobs/blob-store.ts +359 -0
  144. package/src/services/blobs/blob.fixture.ts +90 -0
  145. package/src/services/blobs/index.ts +70 -0
  146. package/src/services/database/cache-engine.ts +209 -0
  147. package/src/services/database/connection-supervisor.test.ts +318 -0
  148. package/src/services/database/connection-supervisor.ts +422 -0
  149. package/src/services/database/database.query-timeout.test.ts +140 -0
  150. package/src/services/database/database.responses.test.ts +43 -0
  151. package/src/services/database/database.ts +156 -61
  152. package/src/services/database/engine-factory.ts +33 -0
  153. package/src/services/database/errors.ts +34 -0
  154. package/src/services/database/events/index.ts +2 -1
  155. package/src/services/database/index.ts +7 -0
  156. package/src/services/database/landed-body-clear.integration.test.ts +236 -0
  157. package/src/services/database/local-migrator.integration.test.ts +110 -0
  158. package/src/services/database/local-migrator.ts +61 -16
  159. package/src/services/database/local.test.ts +150 -0
  160. package/src/services/database/local.ts +508 -63
  161. package/src/services/database/plan-render.test.ts +159 -0
  162. package/src/services/database/plan-render.ts +108 -0
  163. package/src/services/database/relation-resolver.test.ts +484 -0
  164. package/src/services/database/relation-resolver.ts +0 -0
  165. package/src/services/database/remote.ts +285 -6
  166. package/src/services/database/sqlite-cache-engine.test.ts +628 -0
  167. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  168. package/src/services/database/sqlite-cache-engine.ts +1401 -0
  169. package/src/services/database/sqlite-column-reset.integration.test.ts +100 -0
  170. package/src/services/database/sqlite-column-reset.ts +91 -0
  171. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  172. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  173. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  174. package/src/services/database/sqlite-lock-verify.ts +45 -0
  175. package/src/services/database/sqlite-open.test.ts +150 -0
  176. package/src/services/database/sqlite-open.ts +164 -0
  177. package/src/services/database/sqlite-plan-sql.test.ts +164 -0
  178. package/src/services/database/sqlite-plan-sql.ts +196 -0
  179. package/src/services/database/sqlite-projection.test.ts +121 -0
  180. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  181. package/src/services/database/sqlite-select.test.ts +261 -0
  182. package/src/services/database/sqlite-select.ts +131 -0
  183. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  184. package/src/services/database/sqlite-transport.ts +224 -0
  185. package/src/services/database/sqlite-worker.ts +437 -0
  186. package/src/services/database/surql-translate.ts +417 -0
  187. package/src/services/database/surreal-cache-engine.ts +163 -0
  188. package/src/services/logger/index.test.ts +132 -0
  189. package/src/services/logger/index.ts +189 -7
  190. package/src/services/persistence/localstorage.ts +2 -2
  191. package/src/services/persistence/resilient.ts +10 -3
  192. package/src/services/persistence/surrealdb.ts +4 -4
  193. package/src/services/stream-processor/index.ts +806 -67
  194. package/src/services/stream-processor/permissions.test.ts +47 -0
  195. package/src/services/stream-processor/permissions.ts +53 -0
  196. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  197. package/src/services/stream-processor/stream-processor.prime.test.ts +234 -0
  198. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  199. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  200. package/src/services/stream-processor/wasm-types.ts +58 -2
  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 -395
  211. package/src/state/client-state.ts +208 -0
  212. package/src/state/lifecycle.test.ts +141 -0
  213. package/src/state/lifecycle.ts +99 -0
  214. package/src/state/reducers.test.ts +306 -0
  215. package/src/state/reducers.ts +531 -0
  216. package/src/state/selectors.test.ts +188 -0
  217. package/src/state/selectors.ts +258 -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 +178 -0
  224. package/src/sync/live.saga.ts +107 -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 +633 -10
  239. package/src/utils/blurhash.ts +90 -0
  240. package/src/utils/edges.ts +39 -0
  241. package/src/utils/error-classification.test.ts +54 -0
  242. package/src/utils/error-classification.ts +25 -0
  243. package/src/utils/index.ts +89 -12
  244. package/src/utils/parser.test.ts +67 -102
  245. package/src/utils/parser.ts +64 -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 +29 -18
  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 -879
  257. package/src/modules/sync/engine.ts +0 -173
  258. package/src/modules/sync/events/index.ts +0 -77
  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 -89
  262. package/src/modules/sync/queue/queue-up.ts +0 -223
  263. package/src/modules/sync/scheduler.ts +0 -84
  264. package/src/modules/sync/sync.ts +0 -421
  265. package/src/modules/sync/utils.test.ts +0 -311
  266. package/src/modules/sync/utils.ts +0 -167
package/src/pure.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `@spooky-sync/core/pure`: the side-effect-free builders of the sync protocol.
3
+ *
4
+ * Everything here is plain functions and constants over `surrealdb` value
5
+ * types: no wasm, no logger, no `window`/`document`/`localStorage`. Safe to
6
+ * import from a service worker, a dedicated worker or Node. The full client
7
+ * imports the same modules, so what this entry builds is byte for byte what
8
+ * `Sp00kyClient` sends.
9
+ */
10
+ import { queryHashInput, type QueryKeyInput } from './query/hash';
11
+ import { sha256Hex } from './utils/sha256';
12
+
13
+ // Statement builders: register + snapshot, heartbeat, bodies, views.
14
+ export * from './query/sql';
15
+ // Query keys.
16
+ export { queryHashInput, viewKeyInput, type QueryKeyInput } from './query/hash';
17
+ // `_00_list_ref` table naming and user-id sanitizing (mirrors ssp-protocol).
18
+ export {
19
+ ANON_LIST_REF_TABLE,
20
+ ANON_USER_ID,
21
+ DEFAULT_REF_MODE,
22
+ bucketIdForUser,
23
+ listRefTableFor,
24
+ sanitizeUserId,
25
+ type RefMode,
26
+ } from './modules/ref-tables';
27
+ // Membership snapshot parsing (the answer to the register read-back).
28
+ export {
29
+ FAILED,
30
+ dedupeRecordVersions,
31
+ metaFromRow,
32
+ snapshotFromSingle,
33
+ type Answer,
34
+ type ListRefSnapshot,
35
+ type QueryMetaRow,
36
+ } from './query/membership';
37
+ // LIVE edge decoding.
38
+ export { hashOfEdge, rowOfEdge } from './utils/edges';
39
+ // SurrealQL string helpers.
40
+ export {
41
+ surql,
42
+ type SealOptions,
43
+ type SealedQuery,
44
+ type SurqlHelper,
45
+ type TxQuery,
46
+ } from './utils/surql';
47
+ // Param typing the full client applies before registering.
48
+ export { parseQueryParams } from './utils/parser';
49
+ export { encodeRecordId, parseRecordIdString, parseDuration } from './utils/index';
50
+ // Unverified JWT claim reading (user id, access method, impersonation).
51
+ export {
52
+ decodeTokenClaims,
53
+ IMPERSONATION_ACCESS,
54
+ type TokenClaims,
55
+ } from './modules/auth/impersonation';
56
+ export { sha256Hex } from './utils/sha256';
57
+ // Web Push wire types.
58
+ export * from './push/types';
59
+
60
+ /**
61
+ * The remote `_00_query` key of a query for one session salt: what the full
62
+ * client names its view (`_00_query:<key>`).
63
+ */
64
+ export function queryKey(input: QueryKeyInput, sessionId: string | null): Promise<string> {
65
+ return sha256Hex(queryHashInput(input, sessionId));
66
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Web Push wire types shared by the page (`db.webPush`), the service worker
3
+ * bridge (`@spooky-sync/core/sw`) and the live feed. Pure: types plus a few
4
+ * dependency-free helpers, safe in any context.
5
+ *
6
+ * `PushPayload` mirrors `packages/push-core/src/config.rs` (`PushPayload`,
7
+ * `PayloadKind`, `Op`, `NotificationTemplate`). Keep the two in step.
8
+ */
9
+
10
+ /** Payload format version this build understands. */
11
+ export const PUSH_PAYLOAD_VERSION = 1;
12
+
13
+ export type PushPayloadKind = 'rule' | 'message';
14
+ export type PushOp = 'create' | 'update' | 'delete';
15
+ export type PushUrgency = 'very-low' | 'low' | 'normal' | 'high';
16
+
17
+ /** One button on a notification (`NotificationAction` plus a per-action `url`). */
18
+ export interface PushNotificationAction {
19
+ action: string;
20
+ title: string;
21
+ icon?: string;
22
+ /** Opened instead of the notification's `url` when this action is clicked. */
23
+ url?: string;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ /**
28
+ * What a content push shows. Every key other than `title` and `url` is passed
29
+ * to `showNotification` as is, unknown ones included (`vibrate`, `timestamp`,
30
+ * whatever a browser adds later).
31
+ */
32
+ export interface PushNotification {
33
+ title?: string;
34
+ body?: string;
35
+ icon?: string;
36
+ badge?: string;
37
+ image?: string;
38
+ lang?: string;
39
+ dir?: string;
40
+ /** Defaults to the push's `topic`. */
41
+ tag?: string;
42
+ /** Opened (or focused) on click. */
43
+ url?: string;
44
+ requireInteraction?: boolean;
45
+ renotify?: boolean;
46
+ silent?: boolean;
47
+ actions?: PushNotificationAction[];
48
+ /** Merged into the notification's `data` next to the bridge's own keys. */
49
+ data?: Record<string, unknown>;
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ /**
54
+ * The JSON every push carries (then RFC 8291 encrypted). `notification`
55
+ * present: a content push the service worker shows as is. Absent: a
56
+ * content-free nudge, the app renders from its own data.
57
+ */
58
+ export interface PushPayload {
59
+ v: number;
60
+ kind: PushPayloadKind;
61
+ /** Rule name (`kind: 'rule'`). */
62
+ rule?: string;
63
+ /** `_00_push_message` id (`kind: 'message'`). */
64
+ message?: string;
65
+ /** The row that fired the rule. */
66
+ table?: string;
67
+ id?: string;
68
+ op?: PushOp;
69
+ topic?: string;
70
+ notification?: PushNotification;
71
+ data?: Record<string, unknown>;
72
+ /** Epoch ms when the engine built it. */
73
+ ts: number;
74
+ }
75
+
76
+ /** Page -> service worker: the session to use for live rendering. */
77
+ export interface BridgeTokenMessage {
78
+ type: 'sp00ky:token';
79
+ token: string;
80
+ userId: string;
81
+ /** The SurrealDB endpoint (`wss://host/rpc`), not the push endpoint. */
82
+ endpoint: string;
83
+ namespace: string;
84
+ database: string;
85
+ /** VAPID public key (base64url) the page subscribed with, for `pushsubscriptionchange`. */
86
+ publicKey?: string;
87
+ /** This browser's push endpoint, when subscribed. */
88
+ pushEndpoint?: string;
89
+ }
90
+
91
+ /** Page -> service worker: forget the session and close every notification. */
92
+ export interface BridgeSignOutMessage {
93
+ type: 'sp00ky:signout';
94
+ }
95
+
96
+ /** Service worker -> page messages. */
97
+ export type WebPushClientMessage =
98
+ /** A push arrived while this page was visible, so nothing was shown. */
99
+ | { type: 'sp00ky:push'; payload: PushPayload }
100
+ /** A notification was clicked and the bridge was told to route in-app. */
101
+ | { type: 'sp00ky:navigate'; url: string; payload?: PushPayload; action?: string }
102
+ /** The browser rotated the push subscription; the page should `sync()`. */
103
+ | { type: 'sp00ky:subscriptionchange'; endpoint?: string };
104
+
105
+ /** `urlBase64ToUint8Array`: the VAPID key as `applicationServerKey` wants it. */
106
+ export function base64UrlToBytes(value: string): Uint8Array {
107
+ const padded =
108
+ value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (value.length % 4)) % 4);
109
+ const raw = typeof atob === 'function' ? atob(padded) : decodeBase64Fallback(padded);
110
+ const out = new Uint8Array(raw.length);
111
+ for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
112
+ return out;
113
+ }
114
+
115
+ export function bytesToBase64Url(bytes: ArrayBuffer | Uint8Array): string {
116
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
117
+ let raw = '';
118
+ for (let i = 0; i < view.length; i++) raw += String.fromCharCode(view[i]);
119
+ const b64 = typeof btoa === 'function' ? btoa(raw) : encodeBase64Fallback(raw);
120
+ return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
121
+ }
122
+
123
+ export function sameBytes(
124
+ a: ArrayBuffer | Uint8Array | null | undefined,
125
+ b: ArrayBuffer | Uint8Array | null | undefined
126
+ ): boolean {
127
+ if (!a || !b) return false;
128
+ const x = a instanceof Uint8Array ? a : new Uint8Array(a);
129
+ const y = b instanceof Uint8Array ? b : new Uint8Array(b);
130
+ if (x.length !== y.length) return false;
131
+ for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;
132
+ return true;
133
+ }
134
+
135
+ /** A payload this build can act on (`v` known, `kind` set). */
136
+ export function isPushPayload(value: unknown): value is PushPayload {
137
+ if (!value || typeof value !== 'object') return false;
138
+ const v = value as { v?: unknown; kind?: unknown };
139
+ return v.v === PUSH_PAYLOAD_VERSION && (v.kind === 'rule' || v.kind === 'message');
140
+ }
141
+
142
+ const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
143
+
144
+ function decodeBase64Fallback(input: string): string {
145
+ const clean = input.replace(/=+$/, '');
146
+ let out = '';
147
+ let buffer = 0;
148
+ let bits = 0;
149
+ for (const ch of clean) {
150
+ const idx = B64.indexOf(ch);
151
+ if (idx < 0) continue;
152
+ buffer = (buffer << 6) | idx;
153
+ bits += 6;
154
+ if (bits >= 8) {
155
+ bits -= 8;
156
+ out += String.fromCharCode((buffer >> bits) & 0xff);
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+
162
+ function encodeBase64Fallback(raw: string): string {
163
+ let out = '';
164
+ for (let i = 0; i < raw.length; i += 3) {
165
+ const a = raw.charCodeAt(i);
166
+ const b = i + 1 < raw.length ? raw.charCodeAt(i + 1) : 0;
167
+ const c = i + 2 < raw.length ? raw.charCodeAt(i + 2) : 0;
168
+ const n = (a << 16) | (b << 8) | c;
169
+ out += B64[(n >> 18) & 63] + B64[(n >> 12) & 63];
170
+ out += i + 1 < raw.length ? B64[(n >> 6) & 63] : '=';
171
+ out += i + 2 < raw.length ? B64[n & 63] : '=';
172
+ }
173
+ return out;
174
+ }