@spooky-sync/core 0.0.1-canary.320 → 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/dist/index.js +97 -31
- package/dist/live.d.ts +1 -1
- package/dist/live.js +28 -4
- package/dist/pure.d.ts +30 -2
- package/dist/pure.js +2 -2
- package/dist/types.d.ts +10 -0
- package/dist/worker-edges.js +98 -67
- package/dist/worker-ref-tables.d.ts +3 -1
- package/package.json +3 -3
- package/src/client/services.test.ts +11 -2
- package/src/client/services.ts +25 -11
- package/src/live/index.test.ts +24 -1
- package/src/live/index.ts +29 -3
- package/src/modules/ref-tables.test.ts +3 -1
- package/src/modules/ref-tables.ts +4 -1
- package/src/pure.ts +1 -0
- package/src/query/anon-query-isolation.integration.test.ts +337 -0
- package/src/query/list-ref-live.integration.test.ts +203 -0
- package/src/query/membership.saga.test.ts +5 -5
- package/src/query/sql.test.ts +31 -0
- package/src/query/sql.ts +40 -3
- package/src/services/database/remote.ts +20 -2
- package/src/sync/connection.saga.test.ts +13 -2
- package/src/sync/connection.saga.ts +17 -13
- package/src/sync/policy.test.ts +13 -9
- package/src/sync/policy.ts +16 -3
package/dist/index.js
CHANGED
|
@@ -2178,8 +2178,8 @@ function trimHistory(history, cap) {
|
|
|
2178
2178
|
|
|
2179
2179
|
//#endregion
|
|
2180
2180
|
//#region src/modules/devtools/index.ts
|
|
2181
|
-
const CORE_VERSION = "0.0.1-canary.
|
|
2182
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
2181
|
+
const CORE_VERSION = "0.0.1-canary.321";
|
|
2182
|
+
const WASM_VERSION = "0.0.1-canary.321";
|
|
2183
2183
|
const SURREAL_VERSION = "3.0.3";
|
|
2184
2184
|
var DevToolsService = class DevToolsService {
|
|
2185
2185
|
version = CORE_VERSION;
|
|
@@ -3726,6 +3726,8 @@ function phaseTimings(e) {
|
|
|
3726
3726
|
* both sides resolve it to the shared `_00_list_ref_anon` table.
|
|
3727
3727
|
*/
|
|
3728
3728
|
const ANON_USER_ID = "anon";
|
|
3729
|
+
/** The one `_00_list_ref` table every signed-out session shares. */
|
|
3730
|
+
const ANON_LIST_REF_TABLE = "_00_list_ref_anon";
|
|
3729
3731
|
/**
|
|
3730
3732
|
* Default ref-storage mode for this client build. Mirrors the SSP's
|
|
3731
3733
|
* default (`RefMode::Dedicated`) so cross-session sync works out of the
|
|
@@ -3774,7 +3776,7 @@ function bucketIdForUser(userId) {
|
|
|
3774
3776
|
* single mode.
|
|
3775
3777
|
*/
|
|
3776
3778
|
function listRefTableFor(mode, userId) {
|
|
3777
|
-
if (userId === ANON_USER_ID) return
|
|
3779
|
+
if (userId === ANON_USER_ID) return ANON_LIST_REF_TABLE;
|
|
3778
3780
|
if (mode === "single") return "_00_list_ref";
|
|
3779
3781
|
const uid = sanitizeUserId(userId);
|
|
3780
3782
|
return uid ? `_00_list_ref_user_${uid}` : "_00_list_ref";
|
|
@@ -4350,15 +4352,44 @@ const readViewRows = () => `SELECT * FROM ${VIEW_TABLE}`;
|
|
|
4350
4352
|
/** Copy every legacy `_00_window` row into `_00_view` once. */
|
|
4351
4353
|
const readLegacyViewRows = () => `SELECT * FROM ${LEGACY_VIEW_TABLE}`;
|
|
4352
4354
|
const countViewRows = () => `SELECT count() FROM ${VIEW_TABLE} GROUP ALL`;
|
|
4353
|
-
const listRefSelect = (table) => `SELECT out, version FROM ${table} WHERE
|
|
4354
|
-
const subqueryListRefSelect = (table) => `SELECT out, version FROM ${table} WHERE
|
|
4355
|
+
const listRefSelect = (table) => `SELECT out, version FROM $in->${table} WHERE parent IS NONE`;
|
|
4356
|
+
const subqueryListRefSelect = (table) => `SELECT out, version FROM $in->${table} WHERE parent IS NOT NONE`;
|
|
4355
4357
|
const queryRowCountSelect = () => "SELECT VALUE { rowCount: rowCount, state: state } FROM ONLY $in";
|
|
4356
|
-
const listRefBatchSelect = (table) => `SELECT in, out, version, parent FROM ${table}
|
|
4358
|
+
const listRefBatchSelect = (table) => `SELECT in, out, version, parent FROM $ins->${table}`;
|
|
4357
4359
|
const queryRowCountBatchSelect = () => "SELECT VALUE { id: id, rowCount: rowCount, state: state } FROM $ins";
|
|
4358
4360
|
/** The single-query read: edges, meta, subquery children in one request. */
|
|
4359
4361
|
const singleSnapshotSelect = (table) => `${listRefSelect(table)};\n${queryRowCountSelect()};\n${subqueryListRefSelect(table)}`;
|
|
4360
4362
|
/** The many-query read: all edges + all metas in one request. */
|
|
4361
4363
|
const batchSnapshotSelect = (table) => `${listRefBatchSelect(table)};\n${queryRowCountBatchSelect()}`;
|
|
4364
|
+
/**
|
|
4365
|
+
* The session's one LIVE subscription on its `_00_list_ref` table.
|
|
4366
|
+
*
|
|
4367
|
+
* `inlineBodies` adds `FETCH out`, which resolves the edge's target into the
|
|
4368
|
+
* notification so the body arrives with the doorbell.
|
|
4369
|
+
*
|
|
4370
|
+
* On the anon table it is filtered to this client's own edges. Every
|
|
4371
|
+
* signed-out session shares that table, so unfiltered each one received (and
|
|
4372
|
+
* the server fetched a body for) every anonymous edge change of every other
|
|
4373
|
+
* session, and the client then dropped all but its own. An edge carries the
|
|
4374
|
+
* `session::id()` of whichever socket last registered its view, which is a
|
|
4375
|
+
* socket of this client but not necessarily the current one: a view stays
|
|
4376
|
+
* registered across a reconnect, and a reload keeps its salt and its views,
|
|
4377
|
+
* so their older edges still name an earlier socket. `sessionIds` are those
|
|
4378
|
+
* earlier sockets. The current one is matched by `session::id()` itself,
|
|
4379
|
+
* which SurrealDB evaluates in the subscriber's own session, so the filter
|
|
4380
|
+
* holds before the client has learned its new id.
|
|
4381
|
+
*
|
|
4382
|
+
* The cast is load-bearing: `session::id()` is a uuid, `clientId` a string,
|
|
4383
|
+
* and without it nothing matches. Per-user tables need no filter.
|
|
4384
|
+
*/
|
|
4385
|
+
function listRefLive(table, opts) {
|
|
4386
|
+
const join = opts.inlineBodies ? " FETCH out" : "";
|
|
4387
|
+
if (table !== ANON_LIST_REF_TABLE) return { sql: `LIVE SELECT * FROM ${table}${join}` };
|
|
4388
|
+
return {
|
|
4389
|
+
sql: `LIVE SELECT * FROM ${table} WHERE clientId = <string>session::id() OR clientId IN $sids${join}`,
|
|
4390
|
+
vars: { sids: [...opts.sessionIds] }
|
|
4391
|
+
};
|
|
4392
|
+
}
|
|
4362
4393
|
/** Register + read back edges/meta/children in ONE request. */
|
|
4363
4394
|
const registerSelect = (table) => `fn::query::register($config);\n${singleSnapshotSelect(table)}`;
|
|
4364
4395
|
const registerVars = (payload) => ({
|
|
@@ -6657,9 +6688,9 @@ function* landInlineRows(env, rows) {
|
|
|
6657
6688
|
//#region src/sync/connection.saga.ts
|
|
6658
6689
|
/**
|
|
6659
6690
|
* Transport state changes. A drop invalidates the live query and arms a
|
|
6660
|
-
* resubscribe
|
|
6661
|
-
*
|
|
6662
|
-
* `$auth.id` gate
|
|
6691
|
+
* resubscribe. The next `connected` restarts LIVE and drains the outbox;
|
|
6692
|
+
* outside the burst cooldown it also drops every remote registration so
|
|
6693
|
+
* `ensureRegistered` rebuilds them behind the `$auth.id` gate.
|
|
6663
6694
|
*/
|
|
6664
6695
|
function* connectionChanged(env, connection) {
|
|
6665
6696
|
const state = yield fx.state.read((s) => s);
|
|
@@ -6672,18 +6703,17 @@ function* connectionChanged(env, connection) {
|
|
|
6672
6703
|
if (connection !== "connected" || !state.sync.needsResubscribe) return;
|
|
6673
6704
|
const now = yield fx.now();
|
|
6674
6705
|
const last = state.sync.lastReconnectRefetchAt;
|
|
6675
|
-
if (last !== null && now - last < RECONNECT_REFETCH_COOLDOWN_MS) {
|
|
6676
|
-
|
|
6677
|
-
|
|
6706
|
+
if (last !== null && now - last < RECONNECT_REFETCH_COOLDOWN_MS) yield fx.state.update(patchSync({ needsResubscribe: false }));
|
|
6707
|
+
else {
|
|
6708
|
+
yield fx.state.update(compose(patchSync({
|
|
6709
|
+
needsResubscribe: false,
|
|
6710
|
+
lastReconnectRefetchAt: now
|
|
6711
|
+
}), ...[...state.queries.values()].filter((e) => e.lifecycle.remote !== "unregistered").map((e) => applyLifecycle(e.def.hash, { type: "remote-dropped" }))));
|
|
6712
|
+
yield fx.dispatch({
|
|
6713
|
+
type: "EnsureRegistered",
|
|
6714
|
+
requireAuth: true
|
|
6715
|
+
});
|
|
6678
6716
|
}
|
|
6679
|
-
yield fx.state.update(compose(patchSync({
|
|
6680
|
-
needsResubscribe: false,
|
|
6681
|
-
lastReconnectRefetchAt: now
|
|
6682
|
-
}), ...[...state.queries.values()].filter((e) => e.lifecycle.remote !== "unregistered").map((e) => applyLifecycle(e.def.hash, { type: "remote-dropped" }))));
|
|
6683
|
-
yield fx.dispatch({
|
|
6684
|
-
type: "EnsureRegistered",
|
|
6685
|
-
requireAuth: true
|
|
6686
|
-
});
|
|
6687
6717
|
yield fx.dispatch({ type: "LiveStart" });
|
|
6688
6718
|
yield fx.dispatch({ type: "Drain" });
|
|
6689
6719
|
}
|
|
@@ -8213,8 +8243,12 @@ function resolveReconnectConfig(input) {
|
|
|
8213
8243
|
*/
|
|
8214
8244
|
const REMOTE_MAX_CONCURRENT_QUERIES = 6;
|
|
8215
8245
|
/**
|
|
8216
|
-
* Session ids a page remembers for its pagehide release
|
|
8217
|
-
*
|
|
8246
|
+
* Session ids a page remembers for its pagehide release and its anonymous LIVE
|
|
8247
|
+
* filter. The release only needs the sessions of the last ttl; this many
|
|
8248
|
+
* reconnects inside one ttl is a broken network. The LIVE filter would like
|
|
8249
|
+
* every session that stamped an edge still held, but the SSP restamps an edge
|
|
8250
|
+
* whenever its row changes, so an id that falls off costs at most the LIVE
|
|
8251
|
+
* removal of a row untouched since, which the membership poll still catches.
|
|
8218
8252
|
*/
|
|
8219
8253
|
const MAX_SESSION_IDS = 16;
|
|
8220
8254
|
var RemoteDatabaseService = class extends AbstractDatabaseService {
|
|
@@ -8256,6 +8290,10 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
|
|
|
8256
8290
|
* whose own session id matches none of them, so it names these instead. Kept
|
|
8257
8291
|
* for the page, not the socket: a view registered before a reconnect still
|
|
8258
8292
|
* carries the old id until its next heartbeat.
|
|
8293
|
+
*
|
|
8294
|
+
* The anonymous LIVE filters on the same ids (see `listRefLive`): its edges
|
|
8295
|
+
* name the socket that registered their view, which may be an earlier one.
|
|
8296
|
+
* A reload hands its ids to the next page with the salt.
|
|
8259
8297
|
*/
|
|
8260
8298
|
sessionIdsSeen = [];
|
|
8261
8299
|
constructor(config, logger) {
|
|
@@ -8281,6 +8319,14 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
|
|
|
8281
8319
|
return this.sessionIdsSeen;
|
|
8282
8320
|
}
|
|
8283
8321
|
/**
|
|
8322
|
+
* Take on the session ids of the page this one reloaded from. It kept that
|
|
8323
|
+
* page's salt, so the views it re-registers still carry edges stamped by
|
|
8324
|
+
* those sockets. Older than anything this page has seen, so they go first.
|
|
8325
|
+
*/
|
|
8326
|
+
adoptSessionIds(ids) {
|
|
8327
|
+
this.sessionIdsSeen = [...ids.filter((sid) => typeof sid === "string" && sid !== "" && !this.sessionIdsSeen.includes(sid)), ...this.sessionIdsSeen].slice(-MAX_SESSION_IDS);
|
|
8328
|
+
}
|
|
8329
|
+
/**
|
|
8284
8330
|
* Ask the socket that just connected for its session id. Straight to the
|
|
8285
8331
|
* SDK: it needs no namespace or identity, so it need not wait behind an
|
|
8286
8332
|
* in-flight connect or take a slot from the query queue.
|
|
@@ -14707,17 +14753,29 @@ function takeReloadSalt(authId) {
|
|
|
14707
14753
|
if (raw === null) return null;
|
|
14708
14754
|
sessionStorage.removeItem(RELOAD_SALT_KEY);
|
|
14709
14755
|
const left = JSON.parse(raw);
|
|
14710
|
-
|
|
14756
|
+
if (typeof left.salt !== "string" || left.salt === "" || (left.authId ?? null) !== authId) return null;
|
|
14757
|
+
const sessionIds = Array.isArray(left.sessionIds) ? left.sessionIds.filter((sid) => typeof sid === "string") : [];
|
|
14758
|
+
return {
|
|
14759
|
+
salt: left.salt,
|
|
14760
|
+
sessionIds
|
|
14761
|
+
};
|
|
14711
14762
|
} catch {
|
|
14712
14763
|
return null;
|
|
14713
14764
|
}
|
|
14714
14765
|
}
|
|
14715
|
-
/**
|
|
14716
|
-
|
|
14766
|
+
/**
|
|
14767
|
+
* Leave this page's salt for the next page in this tab; see {@link takeReloadSalt}.
|
|
14768
|
+
*
|
|
14769
|
+
* The session ids of this page's sockets go with it. The views the next page
|
|
14770
|
+
* re-registers keep the edges this page's sockets stamped, and the anonymous
|
|
14771
|
+
* LIVE only hears edges stamped by a session it knows (see `listRefLive`).
|
|
14772
|
+
*/
|
|
14773
|
+
function leaveReloadSalt(salt, authId, sessionIds = []) {
|
|
14717
14774
|
try {
|
|
14718
14775
|
if (typeof sessionStorage !== "undefined") sessionStorage.setItem(RELOAD_SALT_KEY, JSON.stringify({
|
|
14719
14776
|
salt,
|
|
14720
|
-
authId
|
|
14777
|
+
authId,
|
|
14778
|
+
sessionIds
|
|
14721
14779
|
}));
|
|
14722
14780
|
} catch {}
|
|
14723
14781
|
}
|
|
@@ -14782,7 +14840,7 @@ function createTabsCoordinator(config, s, host) {
|
|
|
14782
14840
|
return new TabsCoordinator({
|
|
14783
14841
|
tabId: s.tabId,
|
|
14784
14842
|
fingerprint: computeTabsFingerprint({
|
|
14785
|
-
coreVersion: "0.0.1-canary.
|
|
14843
|
+
coreVersion: "0.0.1-canary.321",
|
|
14786
14844
|
schemaHash: hash53(config.schemaSurql),
|
|
14787
14845
|
endpoint: config.database.endpoint ?? "",
|
|
14788
14846
|
namespace: config.database.namespace,
|
|
@@ -14886,8 +14944,13 @@ function createAdapters(config, s, host, lateModules) {
|
|
|
14886
14944
|
const services = {
|
|
14887
14945
|
"hint.read": readBootBucketHint,
|
|
14888
14946
|
"hint.write": writeBootBucketHint,
|
|
14889
|
-
"salt.take":
|
|
14890
|
-
|
|
14947
|
+
"salt.take": (authId) => {
|
|
14948
|
+
const left = takeReloadSalt(authId);
|
|
14949
|
+
if (!left) return null;
|
|
14950
|
+
s.remote.adoptSessionIds(left.sessionIds);
|
|
14951
|
+
return left.salt;
|
|
14952
|
+
},
|
|
14953
|
+
"salt.leave": (salt, authId) => leaveReloadSalt(salt, authId, s.remote.sessionIds()),
|
|
14891
14954
|
"local.connect": (bucketId) => s.local.connect(bucketId),
|
|
14892
14955
|
"local.switchStore": (bucketId) => s.local.switchStore(bucketId),
|
|
14893
14956
|
"local.beginSwitch": () => s.local.beginSwitch(),
|
|
@@ -14983,8 +15046,11 @@ function createAdapters(config, s, host, lateModules) {
|
|
|
14983
15046
|
remote: {
|
|
14984
15047
|
queryResponses: (sql, vars) => s.remote.queryResponses(sql, vars),
|
|
14985
15048
|
live: async (table, onChange) => {
|
|
14986
|
-
const
|
|
14987
|
-
|
|
15049
|
+
const stmt = listRefLive(table, {
|
|
15050
|
+
inlineBodies: config.liveInlineBodies === true,
|
|
15051
|
+
sessionIds: s.remote.sessionIds()
|
|
15052
|
+
});
|
|
15053
|
+
const [uuid] = await s.remote.query(stmt.sql, stmt.vars);
|
|
14988
15054
|
(await s.remote.getClient().liveOf(uuid)).subscribe((message) => {
|
|
14989
15055
|
if (message.action === "KILLED") return;
|
|
14990
15056
|
const hash = hashOfEdge(message.value);
|
package/dist/live.d.ts
CHANGED
package/dist/live.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as registerSelect, F as ANON_USER_ID, H as queryHashInput, I as DEFAULT_REF_MODE, O as registerVars, R as listRefTableFor, V as sha256Hex, _ as heartbeatBatch, a as metaFromRow, c as parseDuration, d as parseQueryParams, h as bodySelect, k as singleSnapshotSelect, l as parseRecordIdString, n as rowOfEdge, s as encodeRecordId, t as hashOfEdge, u as withTimeout, v as heartbeatRowGone, x as listRefLive } from "./worker-edges.js";
|
|
2
2
|
import { n as decodeTokenClaims } from "./worker-impersonation.js";
|
|
3
3
|
import { RecordId, Surreal } from "surrealdb";
|
|
4
4
|
|
|
@@ -18,7 +18,8 @@ import { RecordId, Surreal } from "surrealdb";
|
|
|
18
18
|
* params, ttl }`, key = sha256(JSON.stringify({ surql, params, sessionId })));
|
|
19
19
|
* - bodies with `SELECT * FROM $ids`;
|
|
20
20
|
* - one `LIVE SELECT * FROM <the user's _00_list_ref table> FETCH out` per
|
|
21
|
-
* feed
|
|
21
|
+
* feed (on the shared anon table, filtered to the feed's own edges; see
|
|
22
|
+
* `listRefLive`), so a changed row usually arrives inline with its edge; an edge whose
|
|
22
23
|
* `out` is a bare id (a server that ignores FETCH) costs a body read, one
|
|
23
24
|
* whose `out` is null (unreadable or deleted row) a membership re-read;
|
|
24
25
|
* - `fn::query::heartbeat` at half the ttl, re-registering a reclaimed view;
|
|
@@ -45,6 +46,7 @@ const FLUSH_MS = 10;
|
|
|
45
46
|
const FETCH_BATCH_MS = 15;
|
|
46
47
|
const REREAD_MS = 50;
|
|
47
48
|
const RELEASE_TIMEOUT_MS = 2e3;
|
|
49
|
+
const MAX_SOCKETS = 16;
|
|
48
50
|
function deferred() {
|
|
49
51
|
let resolve;
|
|
50
52
|
let reject;
|
|
@@ -184,6 +186,8 @@ var Feed = class {
|
|
|
184
186
|
liveStarting = null;
|
|
185
187
|
subs = /* @__PURE__ */ new Map();
|
|
186
188
|
sessionId;
|
|
189
|
+
/** `session::id()` of every socket this feed has had, oldest first. */
|
|
190
|
+
sockets = [];
|
|
187
191
|
ttl;
|
|
188
192
|
timeoutMs;
|
|
189
193
|
isClosed = false;
|
|
@@ -273,9 +277,23 @@ var Feed = class {
|
|
|
273
277
|
}));
|
|
274
278
|
}
|
|
275
279
|
this.db = db;
|
|
280
|
+
this.noteSession();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Remember the `session::id()` of the socket now open. Views are registered
|
|
284
|
+
* once and not again after a reconnect, so their edges keep naming the socket
|
|
285
|
+
* that registered them; the anonymous LIVE has to keep hearing those.
|
|
286
|
+
*/
|
|
287
|
+
async noteSession() {
|
|
288
|
+
try {
|
|
289
|
+
const [sid] = await this.q("RETURN <string>session::id()");
|
|
290
|
+
if (typeof sid !== "string" || this.sockets.includes(sid)) return;
|
|
291
|
+
this.sockets = [...this.sockets, sid].slice(-MAX_SOCKETS);
|
|
292
|
+
} catch {}
|
|
276
293
|
}
|
|
277
294
|
/** After a reconnect: LIVE again, then re-read every view (events in the gap are lost). */
|
|
278
295
|
async recover() {
|
|
296
|
+
this.noteSession();
|
|
279
297
|
try {
|
|
280
298
|
await this.ensureLive();
|
|
281
299
|
for (const sub of this.subs.values()) if (sub.loaded) this.scheduleReread(sub);
|
|
@@ -310,12 +328,18 @@ var Feed = class {
|
|
|
310
328
|
if (this.live || this.isClosed) return false;
|
|
311
329
|
if (!this.liveStarting) this.liveStarting = (async () => {
|
|
312
330
|
try {
|
|
331
|
+
const live = (inlineBodies) => listRefLive(this.table, {
|
|
332
|
+
inlineBodies,
|
|
333
|
+
sessionIds: this.sockets
|
|
334
|
+
});
|
|
313
335
|
let id;
|
|
314
336
|
try {
|
|
315
|
-
|
|
337
|
+
const { sql, vars } = live(true);
|
|
338
|
+
[id] = await this.q(sql, vars);
|
|
316
339
|
} catch (error) {
|
|
317
340
|
this.report(error);
|
|
318
|
-
|
|
341
|
+
const { sql, vars } = live(false);
|
|
342
|
+
[id] = await this.q(sql, vars);
|
|
319
343
|
}
|
|
320
344
|
const stream = await this.db.liveOf(id);
|
|
321
345
|
const off = stream.subscribe((message) => this.onLive(message));
|
package/dist/pure.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
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
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
3
|
import { Duration, RecordId } from "surrealdb";
|
|
4
4
|
import { ColumnSchema, RecordId as RecordId$1, SchemaStructure } from "@spooky-sync/query-builder";
|
|
@@ -201,6 +201,34 @@ declare const queryRowCountBatchSelect: () => string;
|
|
|
201
201
|
declare const singleSnapshotSelect: (table: string) => string;
|
|
202
202
|
/** The many-query read: all edges + all metas in one request. */
|
|
203
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
|
+
};
|
|
204
232
|
interface RegisterPayload {
|
|
205
233
|
id: RecordId<string>;
|
|
206
234
|
surql: string;
|
|
@@ -334,4 +362,4 @@ declare function sha256Hex(input: string): Promise<string>;
|
|
|
334
362
|
*/
|
|
335
363
|
declare function queryKey(input: QueryKeyInput, sessionId: string | null): Promise<string>;
|
|
336
364
|
//#endregion
|
|
337
|
-
export { 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, 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 };
|
|
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
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
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
2
|
import { n as decodeTokenClaims, t as IMPERSONATION_ACCESS } from "./worker-impersonation.js";
|
|
3
3
|
import { a as sameBytes, i as isPushPayload, n as base64UrlToBytes, r as bytesToBase64Url, t as PUSH_PAYLOAD_VERSION } from "./worker-types.js";
|
|
4
4
|
|
|
@@ -21,4 +21,4 @@ function queryKey(input, sessionId) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
export { 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, 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 };
|
|
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 };
|
package/dist/types.d.ts
CHANGED
|
@@ -668,11 +668,21 @@ declare class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
668
668
|
* whose own session id matches none of them, so it names these instead. Kept
|
|
669
669
|
* for the page, not the socket: a view registered before a reconnect still
|
|
670
670
|
* carries the old id until its next heartbeat.
|
|
671
|
+
*
|
|
672
|
+
* The anonymous LIVE filters on the same ids (see `listRefLive`): its edges
|
|
673
|
+
* name the socket that registered their view, which may be an earlier one.
|
|
674
|
+
* A reload hands its ids to the next page with the salt.
|
|
671
675
|
*/
|
|
672
676
|
private sessionIdsSeen;
|
|
673
677
|
constructor(config: Sp00kyConfig<any>['database'], logger: Logger$1);
|
|
674
678
|
/** See {@link sessionIdsSeen}. */
|
|
675
679
|
sessionIds(): readonly string[];
|
|
680
|
+
/**
|
|
681
|
+
* Take on the session ids of the page this one reloaded from. It kept that
|
|
682
|
+
* page's salt, so the views it re-registers still carry edges stamped by
|
|
683
|
+
* those sockets. Older than anything this page has seen, so they go first.
|
|
684
|
+
*/
|
|
685
|
+
adoptSessionIds(ids: ReadonlyArray<string>): void;
|
|
676
686
|
/**
|
|
677
687
|
* Ask the socket that just connected for its session id. Straight to the
|
|
678
688
|
* SDK: it needs no namespace or identity, so it need not wait behind an
|
package/dist/worker-edges.js
CHANGED
|
@@ -97,6 +97,71 @@ const surql = {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/modules/ref-tables.ts
|
|
102
|
+
/**
|
|
103
|
+
* Sentinel user id for unauthenticated clients when anonymous live queries are
|
|
104
|
+
* enabled. Mirrors `ssp_protocol::ANON_AUTH_ID`. It carries no `user:` prefix
|
|
105
|
+
* so it can never collide with a real user id (those arrive as `user:<id>`);
|
|
106
|
+
* both sides resolve it to the shared `_00_list_ref_anon` table.
|
|
107
|
+
*/
|
|
108
|
+
const ANON_USER_ID = "anon";
|
|
109
|
+
/** The one `_00_list_ref` table every signed-out session shares. */
|
|
110
|
+
const ANON_LIST_REF_TABLE = "_00_list_ref_anon";
|
|
111
|
+
/**
|
|
112
|
+
* Default ref-storage mode for this client build. Mirrors the SSP's
|
|
113
|
+
* default (`RefMode::Dedicated`) so cross-session sync works out of the
|
|
114
|
+
* box.
|
|
115
|
+
*/
|
|
116
|
+
const DEFAULT_REF_MODE = "dedicated";
|
|
117
|
+
/**
|
|
118
|
+
* Sanitize a user record id (e.g. `"user:abc"`) into the segment that
|
|
119
|
+
* goes into a dedicated table name (e.g. `"abc"`). Returns `null` if
|
|
120
|
+
* the id is missing the `user:` prefix or contains characters that
|
|
121
|
+
* aren't valid in a SurrealDB table identifier — the server-side
|
|
122
|
+
* `ssp_protocol::sanitize_user_id` uses the same predicate.
|
|
123
|
+
*
|
|
124
|
+
* Accepts both string ids (`"user:abc"`) and SurrealDB `RecordId`
|
|
125
|
+
* objects (which only stringify cleanly via `.toString()`), since
|
|
126
|
+
* `AuthService` passes the record-id object as-is to its subscribers.
|
|
127
|
+
*/
|
|
128
|
+
function sanitizeUserId(userId) {
|
|
129
|
+
if (userId === null || userId === void 0) return null;
|
|
130
|
+
const asString = typeof userId === "string" ? userId : typeof userId.toString === "function" ? userId.toString() : null;
|
|
131
|
+
if (!asString) return null;
|
|
132
|
+
const raw = asString.startsWith("user:") ? asString.slice(5) : asString;
|
|
133
|
+
if (raw.length === 0) return null;
|
|
134
|
+
if (!/^[A-Za-z0-9_]+$/.test(raw)) return null;
|
|
135
|
+
return raw;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the LOCAL storage bucket id for a user. Every user gets their own
|
|
139
|
+
* IndexedDB-backed local store (`indxdb://sp00ky-<bucketId>`) so cached rows,
|
|
140
|
+
* query state, and the mutation outbox never leak across accounts on a shared
|
|
141
|
+
* device. Signed-out sessions share the `anon` bucket.
|
|
142
|
+
*
|
|
143
|
+
* An id that fails sanitization still gets a DETERMINISTIC per-user bucket
|
|
144
|
+
* (cyrb53 hex of the raw id) — falling back to `anon` here would put an
|
|
145
|
+
* authenticated user in the shared bucket and recreate the cross-user leak.
|
|
146
|
+
*/
|
|
147
|
+
function bucketIdForUser(userId) {
|
|
148
|
+
if (userId === null || userId === void 0 || userId === ANON_USER_ID) return ANON_USER_ID;
|
|
149
|
+
const uid = sanitizeUserId(userId);
|
|
150
|
+
if (uid) return uid;
|
|
151
|
+
return `u${cyrb53(String(userId)).toString(16)}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns the `_00_list_ref` table name for `(mode, userId)`. Falls
|
|
155
|
+
* back to the global `_00_list_ref` when sanitization fails or in
|
|
156
|
+
* single mode.
|
|
157
|
+
*/
|
|
158
|
+
function listRefTableFor(mode, userId) {
|
|
159
|
+
if (userId === ANON_USER_ID) return ANON_LIST_REF_TABLE;
|
|
160
|
+
if (mode === "single") return "_00_list_ref";
|
|
161
|
+
const uid = sanitizeUserId(userId);
|
|
162
|
+
return uid ? `_00_list_ref_user_${uid}` : "_00_list_ref";
|
|
163
|
+
}
|
|
164
|
+
|
|
100
165
|
//#endregion
|
|
101
166
|
//#region src/query/sql.ts
|
|
102
167
|
/** Every SurrealQL statement the query side sends, as pure builders. */
|
|
@@ -119,15 +184,44 @@ const readViewRows = () => `SELECT * FROM ${VIEW_TABLE}`;
|
|
|
119
184
|
/** Copy every legacy `_00_window` row into `_00_view` once. */
|
|
120
185
|
const readLegacyViewRows = () => `SELECT * FROM ${LEGACY_VIEW_TABLE}`;
|
|
121
186
|
const countViewRows = () => `SELECT count() FROM ${VIEW_TABLE} GROUP ALL`;
|
|
122
|
-
const listRefSelect = (table) => `SELECT out, version FROM ${table} WHERE
|
|
123
|
-
const subqueryListRefSelect = (table) => `SELECT out, version FROM ${table} WHERE
|
|
187
|
+
const listRefSelect = (table) => `SELECT out, version FROM $in->${table} WHERE parent IS NONE`;
|
|
188
|
+
const subqueryListRefSelect = (table) => `SELECT out, version FROM $in->${table} WHERE parent IS NOT NONE`;
|
|
124
189
|
const queryRowCountSelect = () => "SELECT VALUE { rowCount: rowCount, state: state } FROM ONLY $in";
|
|
125
|
-
const listRefBatchSelect = (table) => `SELECT in, out, version, parent FROM ${table}
|
|
190
|
+
const listRefBatchSelect = (table) => `SELECT in, out, version, parent FROM $ins->${table}`;
|
|
126
191
|
const queryRowCountBatchSelect = () => "SELECT VALUE { id: id, rowCount: rowCount, state: state } FROM $ins";
|
|
127
192
|
/** The single-query read: edges, meta, subquery children in one request. */
|
|
128
193
|
const singleSnapshotSelect = (table) => `${listRefSelect(table)};\n${queryRowCountSelect()};\n${subqueryListRefSelect(table)}`;
|
|
129
194
|
/** The many-query read: all edges + all metas in one request. */
|
|
130
195
|
const batchSnapshotSelect = (table) => `${listRefBatchSelect(table)};\n${queryRowCountBatchSelect()}`;
|
|
196
|
+
/**
|
|
197
|
+
* The session's one LIVE subscription on its `_00_list_ref` table.
|
|
198
|
+
*
|
|
199
|
+
* `inlineBodies` adds `FETCH out`, which resolves the edge's target into the
|
|
200
|
+
* notification so the body arrives with the doorbell.
|
|
201
|
+
*
|
|
202
|
+
* On the anon table it is filtered to this client's own edges. Every
|
|
203
|
+
* signed-out session shares that table, so unfiltered each one received (and
|
|
204
|
+
* the server fetched a body for) every anonymous edge change of every other
|
|
205
|
+
* session, and the client then dropped all but its own. An edge carries the
|
|
206
|
+
* `session::id()` of whichever socket last registered its view, which is a
|
|
207
|
+
* socket of this client but not necessarily the current one: a view stays
|
|
208
|
+
* registered across a reconnect, and a reload keeps its salt and its views,
|
|
209
|
+
* so their older edges still name an earlier socket. `sessionIds` are those
|
|
210
|
+
* earlier sockets. The current one is matched by `session::id()` itself,
|
|
211
|
+
* which SurrealDB evaluates in the subscriber's own session, so the filter
|
|
212
|
+
* holds before the client has learned its new id.
|
|
213
|
+
*
|
|
214
|
+
* The cast is load-bearing: `session::id()` is a uuid, `clientId` a string,
|
|
215
|
+
* and without it nothing matches. Per-user tables need no filter.
|
|
216
|
+
*/
|
|
217
|
+
function listRefLive(table, opts) {
|
|
218
|
+
const join = opts.inlineBodies ? " FETCH out" : "";
|
|
219
|
+
if (table !== ANON_LIST_REF_TABLE) return { sql: `LIVE SELECT * FROM ${table}${join}` };
|
|
220
|
+
return {
|
|
221
|
+
sql: `LIVE SELECT * FROM ${table} WHERE clientId = <string>session::id() OR clientId IN $sids${join}`,
|
|
222
|
+
vars: { sids: [...opts.sessionIds] }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
131
225
|
/** Register + read back edges/meta/children in ONE request. */
|
|
132
226
|
const registerSelect = (table) => `fn::query::register($config);\n${singleSnapshotSelect(table)}`;
|
|
133
227
|
const registerVars = (payload) => ({
|
|
@@ -178,69 +272,6 @@ function upsertBodiesTx(records) {
|
|
|
178
272
|
};
|
|
179
273
|
}
|
|
180
274
|
|
|
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
275
|
//#endregion
|
|
245
276
|
//#region src/utils/parser.ts
|
|
246
277
|
/**
|
|
@@ -436,4 +467,4 @@ function rowOfEdge(value) {
|
|
|
436
467
|
}
|
|
437
468
|
|
|
438
469
|
//#endregion
|
|
439
|
-
export {
|
|
470
|
+
export { subqueryListRefSelect as A, surql as B, queryRowCountBatchSelect as C, registerSelect as D, readViewRows as E, ANON_USER_ID as F, queryHashInput as H, DEFAULT_REF_MODE as I, bucketIdForUser as L, viewRecordId as M, viewRow as N, registerVars as O, ANON_LIST_REF_TABLE as P, listRefTableFor as R, listRefSelect as S, readLegacyViewRows as T, viewKeyInput as U, sha256Hex as V, heartbeatBatch as _, metaFromRow as a, listRefBatchSelect as b, parseDuration as c, parseQueryParams as d, LEGACY_VIEW_TABLE as f, countViewRows as g, bodySelect as h, dedupeRecordVersions as i, upsertBodiesTx as j, singleSnapshotSelect as k, parseRecordIdString as l, batchSnapshotSelect as m, rowOfEdge as n, snapshotFromSingle as o, VIEW_TABLE as p, FAILED as r, encodeRecordId as s, hashOfEdge as t, withTimeout as u, heartbeatRowGone as v, queryRowCountSelect as w, listRefLive as x, leaveViewsBeacon as y, sanitizeUserId as z };
|
|
@@ -7,6 +7,8 @@ type RefMode = 'single' | 'dedicated';
|
|
|
7
7
|
* both sides resolve it to the shared `_00_list_ref_anon` table.
|
|
8
8
|
*/
|
|
9
9
|
declare const ANON_USER_ID = "anon";
|
|
10
|
+
/** The one `_00_list_ref` table every signed-out session shares. */
|
|
11
|
+
declare const ANON_LIST_REF_TABLE = "_00_list_ref_anon";
|
|
10
12
|
/**
|
|
11
13
|
* Default ref-storage mode for this client build. Mirrors the SSP's
|
|
12
14
|
* default (`RefMode::Dedicated`) so cross-session sync works out of the
|
|
@@ -43,4 +45,4 @@ declare function bucketIdForUser(userId: unknown): string;
|
|
|
43
45
|
*/
|
|
44
46
|
declare function listRefTableFor(mode: RefMode, userId: unknown): string;
|
|
45
47
|
//#endregion
|
|
46
|
-
export {
|
|
48
|
+
export { bucketIdForUser as a, RefMode as i, ANON_USER_ID as n, listRefTableFor as o, DEFAULT_REF_MODE as r, sanitizeUserId as s, ANON_LIST_REF_TABLE as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/core",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.321",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@spooky-sync/query-builder": "0.0.1-canary.
|
|
79
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
78
|
+
"@spooky-sync/query-builder": "0.0.1-canary.321",
|
|
79
|
+
"@spooky-sync/ssp-wasm": "0.0.1-canary.321",
|
|
80
80
|
"@sqlite.org/sqlite-wasm": "3.53.0-build1",
|
|
81
81
|
"@surrealdb/wasm": "^3.0.3",
|
|
82
82
|
"blurhash": "^2.0.5",
|