@spooky-sync/core 0.0.1-canary.32 → 0.0.1-canary.321
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.
- package/AGENTS.md +81 -0
- package/README.md +56 -16
- package/dist/index.d.ts +2034 -137
- package/dist/index.js +15236 -2467
- package/dist/live.d.ts +141 -0
- package/dist/live.js +733 -0
- package/dist/otel/index.d.ts +1 -1
- package/dist/otel/index.js +3 -3
- package/dist/pure.d.ts +365 -0
- package/dist/pure.js +24 -0
- package/dist/sqlite-open.js +379 -0
- package/dist/sqlite-worker.d.ts +1 -0
- package/dist/sqlite-worker.js +439 -0
- package/dist/sw.d.ts +207 -0
- package/dist/sw.js +393 -0
- package/dist/tabs-broker-worker.d.ts +8 -0
- package/dist/tabs-broker-worker.js +472 -0
- package/dist/types.d.ts +1821 -38
- package/dist/worker-edges.js +470 -0
- package/dist/worker-impersonation.js +60 -0
- package/dist/worker-ref-tables.d.ts +48 -0
- package/dist/worker-types.d.ts +112 -0
- package/dist/worker-types.js +73 -0
- package/package.json +27 -7
- package/scripts/check-broker-bundle.mjs +33 -0
- package/scripts/check-worker-entries.mjs +172 -0
- package/skills/sp00ky-core/SKILL.md +31 -6
- package/skills/sp00ky-core/references/config.md +6 -0
- package/src/boot/auth-flip.saga.test.ts +135 -0
- package/src/boot/auth-flip.saga.ts +77 -0
- package/src/boot/boot.saga.test.ts +180 -0
- package/src/boot/boot.saga.ts +142 -0
- package/src/boot/bucket-switch.saga.test.ts +187 -0
- package/src/boot/bucket-switch.saga.ts +133 -0
- package/src/boot/preload.saga.test.ts +51 -0
- package/src/boot/preload.saga.ts +31 -0
- package/src/bucket-blurhash.test.ts +148 -0
- package/src/build-globals.d.ts +12 -0
- package/src/client/bucket-handle.test.ts +117 -0
- package/src/client/bucket-handle.ts +227 -0
- package/src/client/router.test.ts +58 -0
- package/src/client/router.ts +100 -0
- package/src/client/runtime.test.ts +195 -0
- package/src/client/runtime.ts +296 -0
- package/src/client/services.test.ts +60 -0
- package/src/client/services.ts +418 -0
- package/src/client/sp00ky-client.test.ts +303 -0
- package/src/client/sp00ky-client.ts +586 -0
- package/src/events/events.test.ts +2 -1
- package/src/events/index.ts +3 -0
- package/src/index.ts +69 -1
- package/src/kernel/constants.test.ts +19 -0
- package/src/kernel/constants.ts +80 -0
- package/src/kernel/effects.test.ts +40 -0
- package/src/kernel/effects.ts +163 -0
- package/src/kernel/events.ts +57 -0
- package/src/kernel/interpreter.test.ts +116 -0
- package/src/kernel/interpreter.ts +148 -0
- package/src/kernel/saga.test.ts +89 -0
- package/src/kernel/saga.ts +73 -0
- package/src/live/index.test.ts +501 -0
- package/src/live/index.ts +1039 -0
- package/src/modules/app-release/index.test.ts +125 -0
- package/src/modules/app-release/index.ts +209 -0
- package/src/modules/auth/auth.local-first.test.ts +169 -0
- package/src/modules/auth/events/index.ts +9 -2
- package/src/modules/auth/impersonation-banner.ts +443 -0
- package/src/modules/auth/impersonation.integration.test.ts +205 -0
- package/src/modules/auth/impersonation.test.ts +377 -0
- package/src/modules/auth/impersonation.ts +179 -0
- package/src/modules/auth/index.ts +466 -19
- package/src/modules/crdt/crdt-field.ts +294 -0
- package/src/modules/crdt/crdt-hydration.test.ts +210 -0
- package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
- package/src/modules/crdt/index.ts +463 -0
- package/src/modules/crdt/loro-loader.ts +25 -0
- package/src/modules/devtools/flags.ts +349 -0
- package/src/modules/devtools/index.ts +746 -95
- package/src/modules/devtools/mutations.test.ts +92 -0
- package/src/modules/devtools/mutations.ts +160 -0
- package/src/modules/devtools/notify-throttle.test.ts +166 -0
- package/src/modules/devtools/state-shape.test.ts +263 -0
- package/src/modules/devtools/storage-info.test.ts +79 -0
- package/src/modules/devtools/storage-info.ts +168 -0
- package/src/modules/devtools/versions.test.ts +74 -0
- package/src/modules/devtools/versions.ts +110 -0
- package/src/modules/feature-flag/index.test.ts +298 -0
- package/src/modules/feature-flag/index.ts +316 -0
- package/src/modules/ref-tables.test.ts +93 -0
- package/src/modules/ref-tables.ts +91 -0
- package/src/modules/web-push/index.test.ts +824 -0
- package/src/modules/web-push/index.ts +1027 -0
- package/src/mutation/jobs.test.ts +25 -0
- package/src/mutation/jobs.ts +41 -0
- package/src/mutation/mutation-id.test.ts +37 -0
- package/src/mutation/mutation-id.ts +35 -0
- package/src/mutation/pending-writes.integration.test.ts +175 -0
- package/src/mutation/push.saga.test.ts +285 -0
- package/src/mutation/push.saga.ts +217 -0
- package/src/mutation/rows.test.ts +231 -0
- package/src/mutation/rows.ts +427 -0
- package/src/mutation/tray.saga.test.ts +63 -0
- package/src/mutation/tray.saga.ts +48 -0
- package/src/mutation/write.saga.test.ts +316 -0
- package/src/mutation/write.saga.ts +268 -0
- package/src/otel/index.ts +10 -7
- package/src/pure.ts +66 -0
- package/src/push/types.ts +174 -0
- package/src/query/anon-query-isolation.integration.test.ts +337 -0
- package/src/query/env.test.ts +23 -0
- package/src/query/env.ts +56 -0
- package/src/query/fetch.saga.test.ts +199 -0
- package/src/query/fetch.saga.ts +120 -0
- package/src/query/hash.test.ts +14 -0
- package/src/query/hash.ts +18 -0
- package/src/query/id-set-plan.test.ts +132 -0
- package/src/query/lifecycle.saga.test.ts +274 -0
- package/src/query/lifecycle.saga.ts +159 -0
- package/src/query/list-ref-live.integration.test.ts +203 -0
- package/src/query/materialize.saga.test.ts +115 -0
- package/src/query/materialize.saga.ts +84 -0
- package/src/query/materialize.test.ts +58 -0
- package/src/query/materialize.ts +57 -0
- package/src/query/membership.saga.test.ts +350 -0
- package/src/query/membership.saga.ts +278 -0
- package/src/query/membership.test.ts +118 -0
- package/src/query/membership.ts +201 -0
- package/src/query/register.saga.test.ts +311 -0
- package/src/query/register.saga.ts +229 -0
- package/src/query/render-set.test.ts +47 -0
- package/src/query/render-set.ts +71 -0
- package/src/query/sql.test.ts +112 -0
- package/src/query/sql.ts +149 -0
- package/src/query/window-query.test.ts +70 -0
- package/src/query/window-query.ts +195 -0
- package/src/services/blobs/blob-cache.test.ts +359 -0
- package/src/services/blobs/blob-cache.ts +603 -0
- package/src/services/blobs/blob-manifest.ts +227 -0
- package/src/services/blobs/blob-store.test.ts +77 -0
- package/src/services/blobs/blob-store.ts +359 -0
- package/src/services/blobs/blob.fixture.ts +90 -0
- package/src/services/blobs/index.ts +70 -0
- package/src/services/database/cache-engine.ts +199 -0
- package/src/services/database/connection-supervisor.test.ts +318 -0
- package/src/services/database/connection-supervisor.ts +422 -0
- package/src/services/database/database.query-timeout.test.ts +140 -0
- package/src/services/database/database.responses.test.ts +43 -0
- package/src/services/database/database.ts +146 -61
- package/src/services/database/engine-factory.ts +33 -0
- package/src/services/database/errors.ts +34 -0
- package/src/services/database/events/index.ts +2 -1
- package/src/services/database/index.ts +7 -0
- package/src/services/database/landed-body-clear.integration.test.ts +236 -0
- package/src/services/database/local-migrator.integration.test.ts +110 -0
- package/src/services/database/local-migrator.ts +59 -16
- package/src/services/database/local.test.ts +64 -0
- package/src/services/database/local.ts +487 -63
- package/src/services/database/plan-render.test.ts +159 -0
- package/src/services/database/plan-render.ts +108 -0
- package/src/services/database/relation-resolver.test.ts +484 -0
- package/src/services/database/relation-resolver.ts +0 -0
- package/src/services/database/remote.ts +273 -6
- package/src/services/database/sqlite-cache-engine.test.ts +617 -0
- package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
- package/src/services/database/sqlite-cache-engine.ts +1372 -0
- package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
- package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
- package/src/services/database/sqlite-lock-verify.test.ts +33 -0
- package/src/services/database/sqlite-lock-verify.ts +45 -0
- package/src/services/database/sqlite-open.test.ts +150 -0
- package/src/services/database/sqlite-open.ts +164 -0
- package/src/services/database/sqlite-plan-sql.test.ts +145 -0
- package/src/services/database/sqlite-plan-sql.ts +182 -0
- package/src/services/database/sqlite-projection.test.ts +121 -0
- package/src/services/database/sqlite-select.integration.test.ts +185 -0
- package/src/services/database/sqlite-select.test.ts +261 -0
- package/src/services/database/sqlite-select.ts +131 -0
- package/src/services/database/sqlite-transport.fixture.ts +30 -0
- package/src/services/database/sqlite-transport.ts +224 -0
- package/src/services/database/sqlite-worker.ts +437 -0
- package/src/services/database/surql-translate.ts +416 -0
- package/src/services/database/surreal-cache-engine.ts +161 -0
- package/src/services/logger/index.test.ts +132 -0
- package/src/services/logger/index.ts +189 -7
- package/src/services/persistence/localstorage.ts +2 -2
- package/src/services/persistence/resilient.ts +10 -3
- package/src/services/persistence/surrealdb.ts +4 -4
- package/src/services/stream-processor/index.ts +783 -68
- package/src/services/stream-processor/permissions.test.ts +47 -0
- package/src/services/stream-processor/permissions.ts +53 -0
- package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
- package/src/services/stream-processor/stream-processor.prime.test.ts +198 -0
- package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
- package/src/services/stream-processor/stream-processor.test.ts +1 -1
- package/src/services/stream-processor/wasm-types.ts +58 -2
- package/src/services/tabs/broker-client.ts +283 -0
- package/src/services/tabs/broker.test.ts +327 -0
- package/src/services/tabs/coordinator.test.ts +386 -0
- package/src/services/tabs/coordinator.ts +638 -0
- package/src/services/tabs/fake-ports.fixture.ts +112 -0
- package/src/services/tabs/leader-locks.ts +75 -0
- package/src/services/tabs/protocol.ts +262 -0
- package/src/services/tabs/support.ts +36 -0
- package/src/services/tabs/tabs-broker-worker.ts +640 -0
- package/src/sp00ky.ts +5 -395
- package/src/state/client-state.ts +200 -0
- package/src/state/lifecycle.test.ts +141 -0
- package/src/state/lifecycle.ts +99 -0
- package/src/state/reducers.test.ts +304 -0
- package/src/state/reducers.ts +526 -0
- package/src/state/selectors.test.ts +175 -0
- package/src/state/selectors.ts +222 -0
- package/src/sw/index.test.ts +582 -0
- package/src/sw/index.ts +607 -0
- package/src/sw/store.ts +96 -0
- package/src/sync/connection.saga.test.ts +97 -0
- package/src/sync/connection.saga.ts +89 -0
- package/src/sync/live.saga.test.ts +178 -0
- package/src/sync/live.saga.ts +107 -0
- package/src/sync/policy.health.test.ts +56 -0
- package/src/sync/policy.test.ts +663 -0
- package/src/sync/policy.ts +515 -0
- package/src/sync/poll.saga.test.ts +51 -0
- package/src/sync/poll.saga.ts +36 -0
- package/src/sync/tabs.saga.test.ts +78 -0
- package/src/sync/tabs.saga.ts +96 -0
- package/src/testing/adapters.ts +133 -0
- package/src/testing/build.ts +63 -0
- package/src/testing/fake-services.ts +48 -0
- package/src/testing/run-pure.test.ts +92 -0
- package/src/testing/run-pure.ts +128 -0
- package/src/testing/services.ts +17 -0
- package/src/types.ts +624 -10
- package/src/utils/blurhash.ts +90 -0
- package/src/utils/edges.ts +39 -0
- package/src/utils/error-classification.test.ts +54 -0
- package/src/utils/error-classification.ts +25 -0
- package/src/utils/index.ts +78 -12
- package/src/utils/parser.test.ts +67 -102
- package/src/utils/parser.ts +64 -2
- package/src/utils/semver.test.ts +32 -0
- package/src/utils/semver.ts +30 -0
- package/src/utils/sha256.ts +15 -0
- package/src/utils/surql.ts +30 -18
- package/src/utils/utils.test.ts +3 -2
- package/tsdown.config.ts +117 -3
- package/vitest.config.ts +48 -0
- package/src/modules/cache/index.ts +0 -241
- package/src/modules/cache/types.ts +0 -19
- package/src/modules/data/data.test.ts +0 -58
- package/src/modules/data/index.ts +0 -879
- package/src/modules/sync/engine.ts +0 -173
- package/src/modules/sync/events/index.ts +0 -77
- package/src/modules/sync/index.ts +0 -3
- package/src/modules/sync/queue/index.ts +0 -2
- package/src/modules/sync/queue/queue-down.ts +0 -89
- package/src/modules/sync/queue/queue-up.ts +0 -223
- package/src/modules/sync/scheduler.ts +0 -84
- package/src/modules/sync/sync.ts +0 -421
- package/src/modules/sync/utils.test.ts +0 -311
- package/src/modules/sync/utils.ts +0 -167
package/dist/otel/index.d.ts
CHANGED
package/dist/otel/index.js
CHANGED
|
@@ -50,9 +50,9 @@ function createOtelTransmit(endpoint, level = "info") {
|
|
|
50
50
|
const messages = [...logEvent.messages];
|
|
51
51
|
const severityNumber = mapLevelToSeverityNumber(levelLabel);
|
|
52
52
|
let body = "";
|
|
53
|
-
const
|
|
54
|
-
if (typeof
|
|
55
|
-
else if (
|
|
53
|
+
const lastMsg = messages.pop();
|
|
54
|
+
if (typeof lastMsg === "string") body = lastMsg;
|
|
55
|
+
else if (lastMsg) body = JSON.stringify(lastMsg);
|
|
56
56
|
let category = "sp00ky-client::unknown";
|
|
57
57
|
const attributes = {};
|
|
58
58
|
for (const msg of messages) if (typeof msg === "object") {
|
package/dist/pure.d.ts
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
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
|
+
type RecordVersionArray = Array<[string, number]>;
|
|
143
|
+
/**
|
|
144
|
+
* Represents the difference between two record version sets.
|
|
145
|
+
* Used for synchronizing local and remote states.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* What the server's `_00_query` row says about a view, read alongside its
|
|
150
|
+
* `_00_list_ref` edges (registration, the poll, a targeted re-read).
|
|
151
|
+
*
|
|
152
|
+
* - `present: false`: the row is not there (or not readable). The SSP no
|
|
153
|
+
* longer has this view: a TTL sweep, an SSP reset, a scheduler wipe. An
|
|
154
|
+
* empty edge set read at the same time is NOT "no rows", it is "no view".
|
|
155
|
+
* - `state: 'materializing'`: the SSP has (re)computed the set and its edges
|
|
156
|
+
* are in flight; an empty read is "not landed yet".
|
|
157
|
+
* - `state: 'ready'`: the edges are committed (flipped in the same
|
|
158
|
+
* transaction), so `rowCount === 0` with no edges is a real empty result.
|
|
159
|
+
* - `state: null`: a server that predates the marker; only `rowCount` can be
|
|
160
|
+
* used, with its `> 0` = flush window / `0` = empty ambiguity.
|
|
161
|
+
*/
|
|
162
|
+
interface ServerViewMeta {
|
|
163
|
+
present: boolean;
|
|
164
|
+
/** `_00_query.rowCount`; `null` when unreadable. */
|
|
165
|
+
rowCount: number | null;
|
|
166
|
+
state: 'materializing' | 'ready' | null;
|
|
167
|
+
}
|
|
168
|
+
/** Outcome of handing a server id-set to `DataModule.updateQueryRemoteArray`. */
|
|
169
|
+
|
|
170
|
+
type MutationEventType = 'create' | 'update' | 'delete';
|
|
171
|
+
/**
|
|
172
|
+
* Represents a mutation event (create, update, delete) to be synchronized.
|
|
173
|
+
*/
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/kernel/effects.d.ts
|
|
176
|
+
type Vars = Record<string, unknown>;
|
|
177
|
+
/** What the in-browser SSP needs to build a local view. */
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/query/sql.d.ts
|
|
180
|
+
/** Every SurrealQL statement the query side sends, as pure builders. */
|
|
181
|
+
declare const VIEW_TABLE = "_00_view";
|
|
182
|
+
declare const LEGACY_VIEW_TABLE = "_00_window";
|
|
183
|
+
declare const viewRecordId: (viewKey: string) => RecordId<string>;
|
|
184
|
+
/**
|
|
185
|
+
* The durable `_00_view` row: the confirmed membership plus the subquery
|
|
186
|
+
* children, which the GC must keep and a cold scan may paint as much as the
|
|
187
|
+
* members.
|
|
188
|
+
*/
|
|
189
|
+
declare const viewRow: (ids: RecordVersionArray, children: RecordVersionArray, confirmed: boolean, now: number) => Record<string, unknown>;
|
|
190
|
+
/** Every `_00_view` row: the durable view index and the GC read it. */
|
|
191
|
+
declare const readViewRows: () => string;
|
|
192
|
+
/** Copy every legacy `_00_window` row into `_00_view` once. */
|
|
193
|
+
declare const readLegacyViewRows: () => string;
|
|
194
|
+
declare const countViewRows: () => string;
|
|
195
|
+
declare const listRefSelect: (table: string) => string;
|
|
196
|
+
declare const subqueryListRefSelect: (table: string) => string;
|
|
197
|
+
declare const queryRowCountSelect: () => string;
|
|
198
|
+
declare const listRefBatchSelect: (table: string) => string;
|
|
199
|
+
declare const queryRowCountBatchSelect: () => string;
|
|
200
|
+
/** The single-query read: edges, meta, subquery children in one request. */
|
|
201
|
+
declare const singleSnapshotSelect: (table: string) => string;
|
|
202
|
+
/** The many-query read: all edges + all metas in one request. */
|
|
203
|
+
declare const batchSnapshotSelect: (table: string) => string;
|
|
204
|
+
/**
|
|
205
|
+
* The session's one LIVE subscription on its `_00_list_ref` table.
|
|
206
|
+
*
|
|
207
|
+
* `inlineBodies` adds `FETCH out`, which resolves the edge's target into the
|
|
208
|
+
* notification so the body arrives with the doorbell.
|
|
209
|
+
*
|
|
210
|
+
* On the anon table it is filtered to this client's own edges. Every
|
|
211
|
+
* signed-out session shares that table, so unfiltered each one received (and
|
|
212
|
+
* the server fetched a body for) every anonymous edge change of every other
|
|
213
|
+
* session, and the client then dropped all but its own. An edge carries the
|
|
214
|
+
* `session::id()` of whichever socket last registered its view, which is a
|
|
215
|
+
* socket of this client but not necessarily the current one: a view stays
|
|
216
|
+
* registered across a reconnect, and a reload keeps its salt and its views,
|
|
217
|
+
* so their older edges still name an earlier socket. `sessionIds` are those
|
|
218
|
+
* earlier sockets. The current one is matched by `session::id()` itself,
|
|
219
|
+
* which SurrealDB evaluates in the subscriber's own session, so the filter
|
|
220
|
+
* holds before the client has learned its new id.
|
|
221
|
+
*
|
|
222
|
+
* The cast is load-bearing: `session::id()` is a uuid, `clientId` a string,
|
|
223
|
+
* and without it nothing matches. Per-user tables need no filter.
|
|
224
|
+
*/
|
|
225
|
+
declare function listRefLive(table: string, opts: {
|
|
226
|
+
inlineBodies: boolean;
|
|
227
|
+
sessionIds: ReadonlyArray<string>;
|
|
228
|
+
}): {
|
|
229
|
+
sql: string;
|
|
230
|
+
vars?: Vars;
|
|
231
|
+
};
|
|
232
|
+
interface RegisterPayload {
|
|
233
|
+
id: RecordId<string>;
|
|
234
|
+
surql: string;
|
|
235
|
+
params: Record<string, unknown>;
|
|
236
|
+
ttl: string;
|
|
237
|
+
}
|
|
238
|
+
/** Register + read back edges/meta/children in ONE request. */
|
|
239
|
+
declare const registerSelect: (table: string) => string;
|
|
240
|
+
declare const registerVars: (payload: RegisterPayload) => Vars;
|
|
241
|
+
/** One heartbeat statement per view id; result index i answers id i. */
|
|
242
|
+
declare function heartbeatBatch(ids: ReadonlyArray<RecordId<string>>): {
|
|
243
|
+
sql: string;
|
|
244
|
+
vars: Vars;
|
|
245
|
+
};
|
|
246
|
+
/** A heartbeat answer whose statement matched no row: the view was reclaimed. */
|
|
247
|
+
declare const heartbeatRowGone: (result: unknown) => boolean;
|
|
248
|
+
/**
|
|
249
|
+
* The pagehide release: one bare statement for `beaconSql`, which has no params
|
|
250
|
+
* channel, so ids are inlined. Only shapes the client minted itself get in, a
|
|
251
|
+
* `_00_query:<sha256>` id and a UUID session id, so nothing else is ever
|
|
252
|
+
* interpolated. `null` when there is nothing the server could act on: with no
|
|
253
|
+
* session ids it could not tell this page's entries from anyone else's.
|
|
254
|
+
*/
|
|
255
|
+
declare function leaveViewsBeacon(ids: ReadonlyArray<unknown>, sessionIds: ReadonlyArray<string>): string | null;
|
|
256
|
+
declare const bodySelect: () => string;
|
|
257
|
+
/** Local transaction that MERGEs many fetched bodies. */
|
|
258
|
+
declare function upsertBodiesTx(records: ReadonlyArray<{
|
|
259
|
+
id: unknown;
|
|
260
|
+
content: Record<string, unknown>;
|
|
261
|
+
}>): {
|
|
262
|
+
query: SealedQuery<unknown>;
|
|
263
|
+
vars: Vars;
|
|
264
|
+
};
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/query/membership.d.ts
|
|
267
|
+
/** The `{ rowCount, state }` object the row-count selects return per row. */
|
|
268
|
+
interface QueryMetaRow {
|
|
269
|
+
id?: RecordId<string>;
|
|
270
|
+
rowCount?: number | null;
|
|
271
|
+
state?: string | null;
|
|
272
|
+
}
|
|
273
|
+
declare function metaFromRow(row: QueryMetaRow | null | undefined): ServerViewMeta;
|
|
274
|
+
/** Collapse duplicate `(id, version)` pairs to one per id, keeping the highest version. */
|
|
275
|
+
declare function dedupeRecordVersions(pairs: RecordVersionArray): RecordVersionArray;
|
|
276
|
+
/**
|
|
277
|
+
* A statement that did not answer (an ERR, or no result at all). Kept apart
|
|
278
|
+
* from a `null` result, which is the server saying "no such row": reading a
|
|
279
|
+
* failed statement as a missing row turned every timeout under load into a
|
|
280
|
+
* lost view and re-registered it, over and over (whitepawn, 2026-09-16).
|
|
281
|
+
*/
|
|
282
|
+
declare const FAILED: unique symbol;
|
|
283
|
+
type Answer<T> = T | null | typeof FAILED;
|
|
284
|
+
interface ListRefSnapshot {
|
|
285
|
+
primary: RecordVersionArray;
|
|
286
|
+
subquery: RecordVersionArray;
|
|
287
|
+
meta: ServerViewMeta;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Fold the single-query statement batch (edges, meta, children) into a
|
|
291
|
+
* snapshot. `null` when any of the three did not answer: half an answer is
|
|
292
|
+
* not one, and a missing meta would read as a lost view.
|
|
293
|
+
*/
|
|
294
|
+
declare function snapshotFromSingle(items: Answer<Array<{
|
|
295
|
+
out: RecordId<string>;
|
|
296
|
+
version: number;
|
|
297
|
+
}>>, metaRow: Answer<QueryMetaRow>, children: Answer<Array<{
|
|
298
|
+
out: RecordId<string>;
|
|
299
|
+
version: number;
|
|
300
|
+
}>>): ListRefSnapshot | null;
|
|
301
|
+
/**
|
|
302
|
+
* Fold the many-query statement batch into one snapshot per hash. Every hash
|
|
303
|
+
* in `hashById` gets a snapshot; a query whose row did not come back reads
|
|
304
|
+
* `present: false`. Empty when either statement did not answer.
|
|
305
|
+
*/
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/utils/edges.d.ts
|
|
308
|
+
/**
|
|
309
|
+
* Reading `_00_list_ref` edges as a LIVE notification delivers them. Pure, so
|
|
310
|
+
* both the full client and the wasm-free live feed (`@spooky-sync/core/live`)
|
|
311
|
+
* decode the same wire shape the same way.
|
|
312
|
+
*/
|
|
313
|
+
/** The hash a `_00_list_ref` edge belongs to: the id part of its `in` (`_00_query:<hash>`). */
|
|
314
|
+
declare function hashOfEdge(value: unknown): string | null;
|
|
315
|
+
/**
|
|
316
|
+
* The row an edge notification carries, when the subscription joined it on.
|
|
317
|
+
*
|
|
318
|
+
* Without `FETCH out` the edge's `out` is a record id and there is nothing to
|
|
319
|
+
* land, so this returns null and the caller falls back to fetching the body.
|
|
320
|
+
* It also returns null for a row the session may not read (the join yields
|
|
321
|
+
* `out: null`) and for an edge whose target has been deleted.
|
|
322
|
+
*/
|
|
323
|
+
declare function rowOfEdge(value: unknown): InlineRow | null;
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/utils/parser.d.ts
|
|
326
|
+
/**
|
|
327
|
+
* Parse a QUERY's params. Unlike a record's fields, a param name is not always a
|
|
328
|
+
* column: an `_or` branch binds under a synthetic `white__or0` (see
|
|
329
|
+
* `orParamName` in @spooky-sync/query-builder), and the surql that references it
|
|
330
|
+
* is already written. So resolve the column through the synthetic name, and keep
|
|
331
|
+
* a param we cannot type rather than dropping it.
|
|
332
|
+
*
|
|
333
|
+
* Dropping was the bug: `parseParams` kept only column-named params, so every
|
|
334
|
+
* `_or` query registered with `$or0`/`$or1` unbound and matched no rows at all,
|
|
335
|
+
* silently (rowCount 0, errorCount 0).
|
|
336
|
+
*/
|
|
337
|
+
declare function parseQueryParams(tableSchema: Record<string, ColumnSchema>, params: Record<string, any>): Record<string, any>;
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/utils/index.d.ts
|
|
340
|
+
declare const encodeRecordId: (recordId: RecordId<string>) => string;
|
|
341
|
+
declare const parseRecordIdString: (id: string) => RecordId<string>;
|
|
342
|
+
/**
|
|
343
|
+
* Parse duration string or Duration object to milliseconds
|
|
344
|
+
*/
|
|
345
|
+
declare function parseDuration(duration: QueryTimeToLive | Duration): number;
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/utils/sha256.d.ts
|
|
348
|
+
/**
|
|
349
|
+
* Lowercase hex SHA-256 of a UTF-8 string, through WebCrypto.
|
|
350
|
+
*
|
|
351
|
+
* Pure and dependency free so it can live in `@spooky-sync/core/pure` and run
|
|
352
|
+
* in a service worker, a dedicated worker or Node (`globalThis.crypto` is
|
|
353
|
+
* there on Node 19+). Query keys are `sha256Hex(JSON.stringify(..))`, see
|
|
354
|
+
* `query/hash.ts`.
|
|
355
|
+
*/
|
|
356
|
+
declare function sha256Hex(input: string): Promise<string>;
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/pure.d.ts
|
|
359
|
+
/**
|
|
360
|
+
* The remote `_00_query` key of a query for one session salt: what the full
|
|
361
|
+
* client names its view (`_00_query:<key>`).
|
|
362
|
+
*/
|
|
363
|
+
declare function queryKey(input: QueryKeyInput, sessionId: string | null): Promise<string>;
|
|
364
|
+
//#endregion
|
|
365
|
+
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, 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 subqueryListRefSelect, B as surql, C as queryRowCountBatchSelect, D as registerSelect, E as readViewRows, F as ANON_USER_ID, H as queryHashInput, I as DEFAULT_REF_MODE, L as bucketIdForUser, M as viewRecordId, N as viewRow, O as registerVars, P as ANON_LIST_REF_TABLE, R as listRefTableFor, S as listRefSelect, T as readLegacyViewRows, U as viewKeyInput, V as sha256Hex, _ as heartbeatBatch, a as metaFromRow, b as listRefBatchSelect, c as parseDuration, d as parseQueryParams, f as LEGACY_VIEW_TABLE, g as countViewRows, h as bodySelect, i as dedupeRecordVersions, j as upsertBodiesTx, k as singleSnapshotSelect, l as parseRecordIdString, m as batchSnapshotSelect, n as rowOfEdge, o as snapshotFromSingle, p as VIEW_TABLE, r as FAILED, s as encodeRecordId, t as hashOfEdge, v as heartbeatRowGone, w as queryRowCountSelect, x as listRefLive, y as leaveViewsBeacon, z as sanitizeUserId } 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, rowOfEdge, sameBytes, sanitizeUserId, sha256Hex, singleSnapshotSelect, snapshotFromSingle, subqueryListRefSelect, surql, upsertBodiesTx, viewKeyInput, viewRecordId, viewRow };
|