@spooky-sync/core 0.0.1-canary.283 → 0.0.1-canary.287
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 +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +306 -53
- package/dist/live.js +1 -1
- package/dist/pure.d.ts +9 -2
- package/dist/pure.js +2 -2
- package/dist/sqlite-open.js +28 -1
- package/dist/sqlite-worker.js +1 -1
- package/dist/worker-edges.js +10 -2
- package/package.json +3 -3
- package/skills/sp00ky-core/SKILL.md +9 -6
- package/src/boot/boot.saga.test.ts +9 -2
- package/src/boot/boot.saga.ts +4 -1
- package/src/boot/bucket-switch.saga.ts +2 -0
- package/src/client/runtime.test.ts +4 -1
- package/src/client/sp00ky-client.test.ts +8 -1
- package/src/kernel/constants.ts +15 -2
- package/src/query/fetch.saga.test.ts +44 -1
- package/src/query/fetch.saga.ts +7 -2
- package/src/query/lifecycle.saga.test.ts +169 -17
- package/src/query/lifecycle.saga.ts +86 -28
- package/src/query/materialize.saga.test.ts +36 -3
- package/src/query/materialize.saga.ts +15 -6
- package/src/query/materialize.test.ts +9 -1
- package/src/query/materialize.ts +14 -1
- package/src/query/membership.saga.test.ts +30 -2
- package/src/query/membership.saga.ts +18 -2
- package/src/query/membership.ts +30 -0
- package/src/query/render-set.test.ts +12 -1
- package/src/query/render-set.ts +11 -0
- package/src/query/sql.test.ts +2 -1
- package/src/query/sql.ts +15 -1
- package/src/services/database/landed-body-clear.integration.test.ts +236 -0
- package/src/services/database/sqlite-cache-engine.ts +9 -5
- package/src/services/database/sqlite-plan-sql.test.ts +35 -0
- package/src/services/database/sqlite-plan-sql.ts +26 -0
- package/src/state/client-state.ts +7 -0
- package/src/state/reducers.test.ts +19 -0
- package/src/state/reducers.ts +41 -3
- package/src/state/selectors.test.ts +18 -0
- package/src/state/selectors.ts +32 -0
- package/src/sync/tabs.saga.test.ts +4 -0
- package/src/sync/tabs.saga.ts +3 -0
- package/src/utils/parser.test.ts +37 -1
- package/src/utils/parser.ts +32 -0
package/AGENTS.md
CHANGED
|
@@ -55,6 +55,7 @@ re-materialized from state, never patched in place.
|
|
|
55
55
|
- **Raw remote calls are gated by the query allowlist.** When the generated schema carries `policy.queryAllowlist: 'warn' | 'enforce'` (from `sync.queryAllowlist` in `sp00ky.yml`), `remoteQuery`, `useRemote` and `queryRaw` throw unless the client config sets `allowRawRemote: true`; builder queries are unaffected. A registration the SSP refuses as not allowlisted (403 `not_allowlisted`) is marked `remote: 'failed'` after one attempt with an error-level log carrying the surql and a `spky generate` hint; the view stays cold (`membershipKnown` false), it is never read as confirmed empty.
|
|
56
56
|
- **`@parent` columns are auto-populated.** Defined in the `.surql` schema as `record<user>` with the `-- @parent` annotation; never write them yourself — they're set from the auth context server-side.
|
|
57
57
|
- **`db.preload` is a registered query.** Resolved before on this device (a `_00_view` row exists) it returns at once; never resolved it waits until the server's membership and every body are local. It holds no subscriber, so it is evicted a ttl after registration unless a view mounts the same query. `refresh`/`staleTime` are ignored; pass `signal` to abort a cold wait.
|
|
58
|
+
- **A never-resolved query paints only vouched rows.** Before the server answers (`cold`), the local predicate scan shows a cached body only if some `_00_view` row names it (as a member or subquery child) or a write for it is in flight. A body no view names is an orphan (deleted upstream while nothing watched it): it stays hidden, and the GC deletes it 30 s after boot and then hourly. A `_00_view` row no query has resolved for 14 days is retired by the same sweep, so that query paints cold again.
|
|
58
59
|
- **A rejected write is not lost.** Application errors roll the local row back (create/update/delete) and move the mutation to `_00_failed_mutations`; the tray API above lists, retries (as a fresh write) or discards it. Network errors keep the row queued.
|
|
59
60
|
|
|
60
61
|
## Module map (under `src/`)
|
package/dist/index.d.ts
CHANGED
|
@@ -1311,6 +1311,12 @@ interface ClientState {
|
|
|
1311
1311
|
readonly membershipReread: ReadonlyMap<QueryHash, number>;
|
|
1312
1312
|
/** Local body versions (`_00_rv`) by encoded record id. */
|
|
1313
1313
|
readonly versions: ReadonlyMap<string, number>;
|
|
1314
|
+
/**
|
|
1315
|
+
* What each durable `_00_view` row vouches for, by view key: its members
|
|
1316
|
+
* and subquery children. A body no row names is an orphan: it never paints
|
|
1317
|
+
* before the server answers and the GC deletes it.
|
|
1318
|
+
*/
|
|
1319
|
+
readonly views: ReadonlyMap<string, ReadonlyArray<string>>;
|
|
1314
1320
|
readonly outbox: ReadonlyArray<OutboxItem>;
|
|
1315
1321
|
readonly pendingWrites: ReadonlyMap<string, PendingWrite>;
|
|
1316
1322
|
readonly failedCount: number;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as renderOrderSql, c as
|
|
1
|
+
import { a as renderOrderSql, c as serializeMergeRow, d as stableKey, i as projectedDataSql, l as serializeRow, o as renderWhereSql, r as project, s as reviveRow, t as PROMOTION_OPEN_OPTIONS, u as resolveRelations } from "./sqlite-open.js";
|
|
2
2
|
import { QueryBuilder, RecordId, baseFieldOfParam, cyrb53 } from "@spooky-sync/query-builder";
|
|
3
3
|
import { DateTime, Duration, RecordId as RecordId$1, Surreal, Uuid, applyDiagnostics, createRemoteEngines } from "surrealdb";
|
|
4
4
|
import pino from "pino";
|
|
@@ -448,6 +448,34 @@ function cleanRecord(tableSchema, record) {
|
|
|
448
448
|
return cleaned;
|
|
449
449
|
}
|
|
450
450
|
/**
|
|
451
|
+
* The OPTIONAL schema columns a full server row does not carry, each mapped to
|
|
452
|
+
* `undefined`, for spreading into a body that is landed with MERGE.
|
|
453
|
+
*
|
|
454
|
+
* The server omits an optional field that is unset (`UPDATE t UNSET club`
|
|
455
|
+
* leaves no `club` key in `SELECT *` or the SSP body), and MERGE keeps every
|
|
456
|
+
* key the patch does not mention, so without this the local store held on to
|
|
457
|
+
* a cleared value forever. `undefined` is the one representation both engines
|
|
458
|
+
* read as "remove": the SurrealDB SDK encodes it as NONE, which MERGE removes
|
|
459
|
+
* (NULL would be a type error on an `option<record<...>>` field), and the
|
|
460
|
+
* SQLite engine turns it into the `null` member that `json_patch` removes (see
|
|
461
|
+
* `serializeMergeRow`).
|
|
462
|
+
*
|
|
463
|
+
* Required columns are left alone. A SCHEMAFULL server cannot hold NONE in
|
|
464
|
+
* one, so its absence means the row was not readable in full (a field
|
|
465
|
+
* permission), not that it was cleared, and NONE in a typed local store is a
|
|
466
|
+
* coercion error that would fail the whole chunk's transaction. `id` and the
|
|
467
|
+
* local-only `_00_*` bookkeeping are never touched.
|
|
468
|
+
*/
|
|
469
|
+
function absentOptionalColumns(tableSchema, record) {
|
|
470
|
+
const cleared = {};
|
|
471
|
+
if (!tableSchema) return cleared;
|
|
472
|
+
for (const [key, column] of Object.entries(tableSchema)) {
|
|
473
|
+
if (key === "id" || key.startsWith("_00_") || column?.optional !== true) continue;
|
|
474
|
+
if (record[key] === void 0) cleared[key] = void 0;
|
|
475
|
+
}
|
|
476
|
+
return cleared;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
451
479
|
* Parse a RECORD's fields against the table schema. Anything the schema does not
|
|
452
480
|
* know is dropped, which is what keeps a stray field out of a write.
|
|
453
481
|
*/
|
|
@@ -1724,8 +1752,8 @@ function selfAllowlistedVariant(flag, userId) {
|
|
|
1724
1752
|
|
|
1725
1753
|
//#endregion
|
|
1726
1754
|
//#region src/modules/devtools/index.ts
|
|
1727
|
-
const CORE_VERSION = "0.0.1-canary.
|
|
1728
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
1755
|
+
const CORE_VERSION = "0.0.1-canary.287";
|
|
1756
|
+
const WASM_VERSION = "0.0.1-canary.287";
|
|
1729
1757
|
const SURREAL_VERSION = "3.0.3";
|
|
1730
1758
|
var DevToolsService = class DevToolsService {
|
|
1731
1759
|
eventsHistory = [];
|
|
@@ -2410,8 +2438,21 @@ const AUTH_READY_RETRY_MS = 500;
|
|
|
2410
2438
|
const AUTH_READY_MAX_ATTEMPTS = 10;
|
|
2411
2439
|
/** Heartbeat at this fraction of the shortest ttl in state. */
|
|
2412
2440
|
const TTL_HEARTBEAT_FRACTION = .5;
|
|
2413
|
-
/**
|
|
2414
|
-
|
|
2441
|
+
/**
|
|
2442
|
+
* Orphan body garbage collection: the first sweep waits this long after boot
|
|
2443
|
+
* (off the first paint and the first sync round), then repeats on the interval.
|
|
2444
|
+
*/
|
|
2445
|
+
const GC_BOOT_DELAY_MS = 3e4;
|
|
2446
|
+
const GC_INTERVAL_MS = 3600 * 1e3;
|
|
2447
|
+
/** Ids deleted per GC step. */
|
|
2448
|
+
const GC_CHUNK = 200;
|
|
2449
|
+
/**
|
|
2450
|
+
* A `_00_view` row no query has resolved for this long stops vouching for its
|
|
2451
|
+
* ids (the sweep deletes it, so its bodies can be collected). Each session
|
|
2452
|
+
* that resolves a query rewrites its row, so this only retires query shapes
|
|
2453
|
+
* the app no longer runs.
|
|
2454
|
+
*/
|
|
2455
|
+
const VIEW_RETENTION_MS = 336 * 60 * 60 * 1e3;
|
|
2415
2456
|
/** Rolling telemetry sample window per query. */
|
|
2416
2457
|
const TELEMETRY_SAMPLE_WINDOW = 100;
|
|
2417
2458
|
function backoffMs(attempt, base = RETRY_BASE_MS, max = RETRY_MAX_MS) {
|
|
@@ -2484,6 +2525,32 @@ const setServerState = (hash, serverState) => (s) => withEntry(s, hash, (e) => e
|
|
|
2484
2525
|
* set names: membership has caught up with the write, the overlay's job is
|
|
2485
2526
|
* done (invariant I7).
|
|
2486
2527
|
*/
|
|
2528
|
+
/** What a `_00_view` row holding these arrays vouches for. */
|
|
2529
|
+
const vouchedIds = (members, children) => [...new Set([...members.map(([id]) => id), ...children.map(([id]) => id)])];
|
|
2530
|
+
const withView = (s, viewKey, ids) => {
|
|
2531
|
+
const views = new Map(s.views);
|
|
2532
|
+
views.set(viewKey, ids);
|
|
2533
|
+
return {
|
|
2534
|
+
...s,
|
|
2535
|
+
views
|
|
2536
|
+
};
|
|
2537
|
+
};
|
|
2538
|
+
/**
|
|
2539
|
+
* Replace the durable view index with a fresh `_00_view` read (boot, bucket
|
|
2540
|
+
* switch, GC). An entry of a query in state is kept: its commits update the
|
|
2541
|
+
* index as they write the row, so it is never older than the read.
|
|
2542
|
+
*/
|
|
2543
|
+
const reloadViews = (fromStore) => (s) => {
|
|
2544
|
+
const views = new Map(fromStore);
|
|
2545
|
+
for (const e of s.queries.values()) {
|
|
2546
|
+
const held = s.views.get(e.def.viewKey);
|
|
2547
|
+
if (held) views.set(e.def.viewKey, held);
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
...s,
|
|
2551
|
+
views
|
|
2552
|
+
};
|
|
2553
|
+
};
|
|
2487
2554
|
const commitMembership = (hash, remoteArray, present) => (s) => {
|
|
2488
2555
|
const withArray = withEntry(s, hash, (e) => ({
|
|
2489
2556
|
...e,
|
|
@@ -2494,10 +2561,11 @@ const commitMembership = (hash, remoteArray, present) => (s) => {
|
|
|
2494
2561
|
})
|
|
2495
2562
|
}));
|
|
2496
2563
|
if (withArray === s) return s;
|
|
2564
|
+
const entry = withArray.queries.get(hash);
|
|
2497
2565
|
const named = new Set(remoteArray.map(([id]) => id));
|
|
2498
2566
|
const outbox = withArray.outbox.filter((item) => !(item.status === "acked" && named.has(item.recordId)));
|
|
2499
2567
|
return {
|
|
2500
|
-
...withArray,
|
|
2568
|
+
...withView(withArray, entry.def.viewKey, vouchedIds(remoteArray, entry.subqueryRemoteArray)),
|
|
2501
2569
|
outbox,
|
|
2502
2570
|
dirty: addAll(withArray.dirty, [hash])
|
|
2503
2571
|
};
|
|
@@ -2512,10 +2580,19 @@ const setLocalArray = (hash, localArray) => (s) => {
|
|
|
2512
2580
|
dirty: addAll(next.dirty, [hash])
|
|
2513
2581
|
};
|
|
2514
2582
|
};
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2583
|
+
/**
|
|
2584
|
+
* Replace a query's subquery child set. A query past `cold` has a `_00_view`
|
|
2585
|
+
* row, which records the children too (see `applySubqueryChildren`).
|
|
2586
|
+
*/
|
|
2587
|
+
const setSubqueryRemoteArray = (hash, subqueryRemoteArray) => (s) => {
|
|
2588
|
+
const next = withEntry(s, hash, (e) => ({
|
|
2589
|
+
...e,
|
|
2590
|
+
subqueryRemoteArray
|
|
2591
|
+
}));
|
|
2592
|
+
const entry = next.queries.get(hash);
|
|
2593
|
+
if (!entry || entry.lifecycle.phase === "cold") return next;
|
|
2594
|
+
return withView(next, entry.def.viewKey, vouchedIds(entry.remoteArray, subqueryRemoteArray));
|
|
2595
|
+
};
|
|
2519
2596
|
const setRecords = (hash, records, changed, materializeMs) => (s) => {
|
|
2520
2597
|
const next = withEntry(s, hash, (e) => {
|
|
2521
2598
|
const prev = e.telemetry.phaseSamples.localFetch ?? [];
|
|
@@ -2816,6 +2893,7 @@ const rebindQuery = (hash, next) => (s) => {
|
|
|
2816
2893
|
const clearBucketState = () => (s) => ({
|
|
2817
2894
|
...s,
|
|
2818
2895
|
versions: /* @__PURE__ */ new Map(),
|
|
2896
|
+
views: /* @__PURE__ */ new Map(),
|
|
2819
2897
|
outbox: [],
|
|
2820
2898
|
pendingWrites: /* @__PURE__ */ new Map(),
|
|
2821
2899
|
dirty: /* @__PURE__ */ new Set(),
|
|
@@ -2875,6 +2953,35 @@ function overlay(s) {
|
|
|
2875
2953
|
deletes
|
|
2876
2954
|
};
|
|
2877
2955
|
}
|
|
2956
|
+
const vouchedCache = /* @__PURE__ */ new WeakMap();
|
|
2957
|
+
/**
|
|
2958
|
+
* Every id some durable `_00_view` row vouches for. Memoized on the index,
|
|
2959
|
+
* which only changes when a view row does.
|
|
2960
|
+
*/
|
|
2961
|
+
function vouched(s) {
|
|
2962
|
+
const hit = vouchedCache.get(s.views);
|
|
2963
|
+
if (hit) return hit;
|
|
2964
|
+
const out = /* @__PURE__ */ new Set();
|
|
2965
|
+
for (const ids of s.views.values()) for (const id of ids) out.add(id);
|
|
2966
|
+
vouchedCache.set(s.views, out);
|
|
2967
|
+
return out;
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Bodies the GC must keep: `_00_` bookkeeping, anything a durable view
|
|
2971
|
+
* vouches for, anything a query in memory names (members or children), and
|
|
2972
|
+
* anything a write is still in flight for.
|
|
2973
|
+
*/
|
|
2974
|
+
function retained(s) {
|
|
2975
|
+
const held = /* @__PURE__ */ new Set();
|
|
2976
|
+
for (const e of s.queries.values()) {
|
|
2977
|
+
for (const [id] of e.remoteArray) held.add(id);
|
|
2978
|
+
for (const [id] of e.subqueryRemoteArray) held.add(id);
|
|
2979
|
+
}
|
|
2980
|
+
for (const item of s.outbox) held.add(item.recordId);
|
|
2981
|
+
for (const write of s.pendingWrites.values()) held.add(write.recordId);
|
|
2982
|
+
const known = vouched(s);
|
|
2983
|
+
return (id) => id.startsWith("_00_") || known.has(id) || held.has(id);
|
|
2984
|
+
}
|
|
2878
2985
|
const pendingDeleteIds = (s) => new Set(s.outbox.filter((i) => i.type === "delete").map((i) => i.recordId));
|
|
2879
2986
|
const hasAckedWrites = (s) => s.outbox.some((i) => i.status === "acked");
|
|
2880
2987
|
const pendingMutationCount = (s) => s.outbox.filter((i) => i.status === "pending").length;
|
|
@@ -3288,6 +3395,7 @@ function emptyState(init) {
|
|
|
3288
3395
|
registering: /* @__PURE__ */ new Set(),
|
|
3289
3396
|
membershipReread: /* @__PURE__ */ new Map(),
|
|
3290
3397
|
versions: /* @__PURE__ */ new Map(),
|
|
3398
|
+
views: /* @__PURE__ */ new Map(),
|
|
3291
3399
|
outbox: [],
|
|
3292
3400
|
pendingWrites: /* @__PURE__ */ new Map(),
|
|
3293
3401
|
failedCount: 0,
|
|
@@ -3334,6 +3442,24 @@ function parseViewRow(row) {
|
|
|
3334
3442
|
confirmed: row.confirmed === true
|
|
3335
3443
|
};
|
|
3336
3444
|
}
|
|
3445
|
+
const pairIds = (pairs) => Array.isArray(pairs) ? pairs.filter((p) => Array.isArray(p) && typeof p[0] === "string").map((p) => p[0]) : [];
|
|
3446
|
+
/** `SELECT * FROM _00_view` rows, keyed by view key; junk rows are skipped. */
|
|
3447
|
+
function parseViewIndex(rows) {
|
|
3448
|
+
if (!Array.isArray(rows)) return [];
|
|
3449
|
+
const out = [];
|
|
3450
|
+
for (const row of rows) {
|
|
3451
|
+
if (!row || typeof row !== "object" || !Array.isArray(row.ids)) continue;
|
|
3452
|
+
const id = row.id;
|
|
3453
|
+
const key = typeof id === "string" ? id.slice(id.indexOf(":") + 1) : String(id?.id ?? "");
|
|
3454
|
+
if (!key) continue;
|
|
3455
|
+
out.push({
|
|
3456
|
+
key,
|
|
3457
|
+
ids: [...new Set([...pairIds(row.ids), ...pairIds(row.children)])],
|
|
3458
|
+
updatedAt: typeof row.updatedAt === "number" ? row.updatedAt : 0
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
return out;
|
|
3462
|
+
}
|
|
3337
3463
|
/**
|
|
3338
3464
|
* "Resolved before": the server answered this query on this device at some
|
|
3339
3465
|
* point. An unconfirmed empty row cannot be told apart from one written before
|
|
@@ -3606,11 +3732,19 @@ const selfHealDelayMs = (attempt) => Math.min(SELF_HEAL_MAX_MS, SELF_HEAL_BASE_M
|
|
|
3606
3732
|
const VIEW_TABLE = "_00_view";
|
|
3607
3733
|
const LEGACY_VIEW_TABLE = "_00_window";
|
|
3608
3734
|
const viewRecordId = (viewKey) => new RecordId$1(VIEW_TABLE, viewKey);
|
|
3609
|
-
|
|
3735
|
+
/**
|
|
3736
|
+
* The durable `_00_view` row: the confirmed membership plus the subquery
|
|
3737
|
+
* children, which the GC must keep and a cold scan may paint as much as the
|
|
3738
|
+
* members.
|
|
3739
|
+
*/
|
|
3740
|
+
const viewRow = (ids, children, confirmed, now) => ({
|
|
3610
3741
|
ids,
|
|
3742
|
+
children,
|
|
3611
3743
|
confirmed,
|
|
3612
3744
|
updatedAt: now
|
|
3613
3745
|
});
|
|
3746
|
+
/** Every `_00_view` row: the durable view index and the GC read it. */
|
|
3747
|
+
const readViewRows = () => `SELECT * FROM ${VIEW_TABLE}`;
|
|
3614
3748
|
/** Copy every legacy `_00_window` row into `_00_view` once. */
|
|
3615
3749
|
const readLegacyViewRows = () => `SELECT * FROM ${LEGACY_VIEW_TABLE}`;
|
|
3616
3750
|
const countViewRows = () => `SELECT count() FROM ${VIEW_TABLE} GROUP ALL`;
|
|
@@ -3719,7 +3853,7 @@ function* applyMembership(hash, remoteArray, meta, verifiedRemoval) {
|
|
|
3719
3853
|
});
|
|
3720
3854
|
const now = yield fx.now();
|
|
3721
3855
|
try {
|
|
3722
|
-
yield fx.local.upsert(VIEW_TABLE, viewRecordId(entry.def.viewKey), viewRow(remoteArray, true, now), "replace");
|
|
3856
|
+
yield fx.local.upsert(VIEW_TABLE, viewRecordId(entry.def.viewKey), viewRow(remoteArray, entry.subqueryRemoteArray, true, now), "replace");
|
|
3723
3857
|
} catch (err) {
|
|
3724
3858
|
yield fx.emit({
|
|
3725
3859
|
type: "log",
|
|
@@ -3749,11 +3883,28 @@ function* recoverLostView(hash) {
|
|
|
3749
3883
|
hash
|
|
3750
3884
|
});
|
|
3751
3885
|
}
|
|
3752
|
-
/**
|
|
3886
|
+
/**
|
|
3887
|
+
* Replace the subquery child set; bodies follow through the fetch plan. A
|
|
3888
|
+
* query with a `_00_view` row (past `cold`) records the children there too,
|
|
3889
|
+
* so the GC keeps their bodies and a cold scan may paint them.
|
|
3890
|
+
*/
|
|
3753
3891
|
function* applySubqueryChildren(hash, children) {
|
|
3754
3892
|
const entry = yield fx.state.read((s) => s.queries.get(hash));
|
|
3755
3893
|
if (!entry || recordVersionArraysEqual(entry.subqueryRemoteArray, children)) return;
|
|
3756
3894
|
yield fx.state.update(setSubqueryRemoteArray(hash, children));
|
|
3895
|
+
if (entry.lifecycle.phase !== "cold") try {
|
|
3896
|
+
yield fx.local.upsert(VIEW_TABLE, viewRecordId(entry.def.viewKey), { children }, "merge");
|
|
3897
|
+
} catch (err) {
|
|
3898
|
+
yield fx.emit({
|
|
3899
|
+
type: "log",
|
|
3900
|
+
level: "debug",
|
|
3901
|
+
message: "view row write failed",
|
|
3902
|
+
data: {
|
|
3903
|
+
hash,
|
|
3904
|
+
err
|
|
3905
|
+
}
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3757
3908
|
yield fx.dispatch({ type: "FetchRows" });
|
|
3758
3909
|
}
|
|
3759
3910
|
const ok = (r) => r.ok;
|
|
@@ -4183,42 +4334,65 @@ function* ackPrune() {
|
|
|
4183
4334
|
yield fx.state.update(outboxPruneAcked(now, ACK_GRACE_MS));
|
|
4184
4335
|
if (yield fx.state.read((s) => s.outbox.some((i) => i.status === "acked"))) yield fx.timer.set("ack-prune", ACK_GRACE_MS, { type: "AckPrune" });
|
|
4185
4336
|
}
|
|
4337
|
+
/** Every `_00_view` row, parsed. Throws when the read fails. */
|
|
4338
|
+
function* readViews() {
|
|
4339
|
+
const res = yield fx.local.query(readViewRows());
|
|
4340
|
+
return parseViewIndex(Array.isArray(res) ? res[0] : void 0);
|
|
4341
|
+
}
|
|
4342
|
+
const indexOf = (rows) => new Map(rows.map((r) => [r.key, r.ids]));
|
|
4343
|
+
/**
|
|
4344
|
+
* Load the durable view index (boot, bucket switch). A failed read leaves it
|
|
4345
|
+
* empty, which fails closed: cold scans paint nothing until the server answers.
|
|
4346
|
+
*/
|
|
4347
|
+
function* loadViews() {
|
|
4348
|
+
try {
|
|
4349
|
+
yield fx.state.update(reloadViews(indexOf(yield* readViews())));
|
|
4350
|
+
} catch (error) {
|
|
4351
|
+
yield fx.emit({
|
|
4352
|
+
type: "log",
|
|
4353
|
+
level: "warn",
|
|
4354
|
+
message: "view index read failed",
|
|
4355
|
+
data: { error }
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
const tableOf$1 = (id) => id.slice(0, id.indexOf(":"));
|
|
4186
4360
|
/**
|
|
4187
|
-
*
|
|
4188
|
-
*
|
|
4361
|
+
* Orphan collection, `GC_BOOT_DELAY_MS` after boot and then every
|
|
4362
|
+
* `GC_INTERVAL_MS`. Re-reads every `_00_view` row into the index, retires the
|
|
4363
|
+
* rows no query has resolved for `VIEW_RETENTION_MS`, then deletes the bodies
|
|
4364
|
+
* nothing retains (see `retained`) from the store and the circuit, a chunk at
|
|
4365
|
+
* a time. Only a leader or solo tab deletes, and a leader relays the deletes
|
|
4366
|
+
* so followers drop the rows too; a follower only refreshes its index.
|
|
4189
4367
|
*/
|
|
4190
4368
|
function* gcTick() {
|
|
4369
|
+
yield fx.state.wait((s) => s.primed);
|
|
4191
4370
|
try {
|
|
4192
|
-
const
|
|
4193
|
-
const
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
const
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
const
|
|
4204
|
-
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
yield fx.state.update(deleteVersions(done));
|
|
4210
|
-
if (done.length > 0) yield fx.ssp.ingest(done.map((id) => ({
|
|
4211
|
-
table: id.slice(0, id.indexOf(":")),
|
|
4212
|
-
op: "DELETE",
|
|
4213
|
-
id,
|
|
4214
|
-
record: {}
|
|
4215
|
-
})));
|
|
4216
|
-
}
|
|
4371
|
+
const rows = yield* readViews();
|
|
4372
|
+
const [role, bucket, held] = yield fx.state.read((s) => [
|
|
4373
|
+
s.tabRole,
|
|
4374
|
+
s.bucketId,
|
|
4375
|
+
new Set([...s.queries.values()].map((e) => e.def.viewKey))
|
|
4376
|
+
]);
|
|
4377
|
+
const now = yield fx.now();
|
|
4378
|
+
const expired = role === "follower" ? [] : rows.filter((r) => !held.has(r.key) && now - r.updatedAt >= VIEW_RETENTION_MS);
|
|
4379
|
+
const retired = /* @__PURE__ */ new Set();
|
|
4380
|
+
for (let i = 0; i < expired.length; i += GC_CHUNK) {
|
|
4381
|
+
const chunk = expired.slice(i, i + GC_CHUNK);
|
|
4382
|
+
const settled = yield fx.all(chunk.map((r) => fx.local.delete(VIEW_TABLE, viewRecordId(r.key))));
|
|
4383
|
+
chunk.forEach((r, j) => settled[j].ok && retired.add(r.key));
|
|
4384
|
+
}
|
|
4385
|
+
yield fx.state.update(reloadViews(indexOf(rows.filter((r) => !retired.has(r.key)))));
|
|
4386
|
+
if (role === "follower") return;
|
|
4387
|
+
const removed = yield* collectOrphans(bucket, role === "leader");
|
|
4217
4388
|
yield fx.emit({
|
|
4218
4389
|
type: "log",
|
|
4219
4390
|
level: "info",
|
|
4220
4391
|
message: "orphan gc done",
|
|
4221
|
-
data: {
|
|
4392
|
+
data: {
|
|
4393
|
+
removed,
|
|
4394
|
+
retiredViews: retired.size
|
|
4395
|
+
}
|
|
4222
4396
|
});
|
|
4223
4397
|
} catch (error) {
|
|
4224
4398
|
yield fx.emit({
|
|
@@ -4227,8 +4401,50 @@ function* gcTick() {
|
|
|
4227
4401
|
message: "orphan gc failed",
|
|
4228
4402
|
data: { error }
|
|
4229
4403
|
});
|
|
4404
|
+
} finally {
|
|
4405
|
+
yield fx.timer.set("gc", GC_INTERVAL_MS, { type: "GcTick" });
|
|
4230
4406
|
}
|
|
4231
|
-
|
|
4407
|
+
}
|
|
4408
|
+
/**
|
|
4409
|
+
* Delete every body nothing retains, `GC_CHUNK` at a time. Each chunk is
|
|
4410
|
+
* re-checked against fresh state (a membership committed meanwhile may name
|
|
4411
|
+
* an id again) and the sweep stops if the bucket moved under it.
|
|
4412
|
+
*/
|
|
4413
|
+
function* collectOrphans(bucket, relay) {
|
|
4414
|
+
const candidates = yield fx.state.read((s) => {
|
|
4415
|
+
const keep = retained(s);
|
|
4416
|
+
return [...s.versions.keys()].filter((id) => !keep(id));
|
|
4417
|
+
});
|
|
4418
|
+
let removed = 0;
|
|
4419
|
+
for (let i = 0; i < candidates.length; i += GC_CHUNK) {
|
|
4420
|
+
const chunk = yield fx.state.read((s) => {
|
|
4421
|
+
if (!s.primed || s.bucketId !== bucket) return null;
|
|
4422
|
+
const keep = retained(s);
|
|
4423
|
+
return candidates.slice(i, i + GC_CHUNK).filter((id) => s.versions.has(id) && !keep(id));
|
|
4424
|
+
});
|
|
4425
|
+
if (chunk === null) break;
|
|
4426
|
+
const settled = yield fx.all(chunk.map((id) => fx.local.delete(tableOf$1(id), id)));
|
|
4427
|
+
const done = chunk.filter((_, j) => settled[j].ok);
|
|
4428
|
+
if (done.length === 0) continue;
|
|
4429
|
+
const records = done.map((id) => ({
|
|
4430
|
+
table: tableOf$1(id),
|
|
4431
|
+
op: "DELETE",
|
|
4432
|
+
id,
|
|
4433
|
+
record: {}
|
|
4434
|
+
}));
|
|
4435
|
+
yield fx.state.update(deleteVersions(done));
|
|
4436
|
+
yield fx.ssp.ingest(records);
|
|
4437
|
+
if (relay) yield fx.emit({
|
|
4438
|
+
type: "tabs:broadcast",
|
|
4439
|
+
message: {
|
|
4440
|
+
type: "ingest",
|
|
4441
|
+
records
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
removed += done.length;
|
|
4445
|
+
}
|
|
4446
|
+
if (removed > 0) yield fx.dispatch({ type: "FetchRows" });
|
|
4447
|
+
return removed;
|
|
4232
4448
|
}
|
|
4233
4449
|
|
|
4234
4450
|
//#endregion
|
|
@@ -5187,6 +5403,7 @@ function* boot(env, opts) {
|
|
|
5187
5403
|
yield fx.state.update(setIdentity({ bucketId: bucket }));
|
|
5188
5404
|
if (yield fx.service("local.usesSurqlSchema")) yield fx.service("migrator.provision");
|
|
5189
5405
|
yield* migrateWindowToView();
|
|
5406
|
+
yield* loadViews();
|
|
5190
5407
|
yield fx.dispatch({ type: "WarmBlobs" });
|
|
5191
5408
|
yield fx.service("ssp.init");
|
|
5192
5409
|
yield fx.service("ssp.setPermissions");
|
|
@@ -5206,7 +5423,7 @@ function* boot(env, opts) {
|
|
|
5206
5423
|
yield fx.service("features.init");
|
|
5207
5424
|
yield fx.service("releases.init");
|
|
5208
5425
|
yield fx.dispatch({ type: "LifecycleTick" });
|
|
5209
|
-
yield fx.
|
|
5426
|
+
yield fx.timer.set("gc", GC_BOOT_DELAY_MS, { type: "GcTick" });
|
|
5210
5427
|
yield fx.state.update(setIdentity({ localReady: true }));
|
|
5211
5428
|
yield fx.dispatch({ type: "StartRemote" });
|
|
5212
5429
|
}
|
|
@@ -5485,6 +5702,7 @@ function* landChunk(env, requested, rows, versions, state, epoch) {
|
|
|
5485
5702
|
bodies.push({
|
|
5486
5703
|
id: rid,
|
|
5487
5704
|
content: {
|
|
5705
|
+
...absentOptionalColumns(columns, row),
|
|
5488
5706
|
...content,
|
|
5489
5707
|
_00_rv: version
|
|
5490
5708
|
}
|
|
@@ -5707,6 +5925,17 @@ function rowsFromResult(effect, result) {
|
|
|
5707
5925
|
const first = Array.isArray(result) ? result[0] : void 0;
|
|
5708
5926
|
return Array.isArray(first) ? first : [];
|
|
5709
5927
|
}
|
|
5928
|
+
/**
|
|
5929
|
+
* A row's encoded record id: a string on the SQLite engine, a `RecordId` on
|
|
5930
|
+
* the SurrealDB one. `null` for a row that carries none (a projection
|
|
5931
|
+
* without `id`, a `SELECT VALUE`).
|
|
5932
|
+
*/
|
|
5933
|
+
function rowKey(row) {
|
|
5934
|
+
const id = row !== null && typeof row === "object" ? row.id : void 0;
|
|
5935
|
+
if (typeof id === "string") return id;
|
|
5936
|
+
if (id !== null && typeof id === "object" && "table" in id && "id" in id) return encodeRecordId(id);
|
|
5937
|
+
return null;
|
|
5938
|
+
}
|
|
5710
5939
|
/** Cheap structural equality for the "did the rows change" check. */
|
|
5711
5940
|
function rowsEqual(a, b) {
|
|
5712
5941
|
if (a === b) return true;
|
|
@@ -5728,6 +5957,16 @@ function resolveMembership(input) {
|
|
|
5728
5957
|
return null;
|
|
5729
5958
|
}
|
|
5730
5959
|
/**
|
|
5960
|
+
* Whether a body may paint before the server has answered (`cold`). One no
|
|
5961
|
+
* durable view vouches for and no optimistic write touches is an orphan:
|
|
5962
|
+
* deleted upstream while no view watched it, or dropped from every view since.
|
|
5963
|
+
* The predicate scan would otherwise resurrect it until the server answers,
|
|
5964
|
+
* and indefinitely offline.
|
|
5965
|
+
*/
|
|
5966
|
+
function paintsCold(id, vouched, overlay) {
|
|
5967
|
+
return id.startsWith("_00_") || vouched.has(id) || overlay.writes.has(id);
|
|
5968
|
+
}
|
|
5969
|
+
/**
|
|
5731
5970
|
* render = (membership ∪ (writes ∩ localView)) − deletes
|
|
5732
5971
|
*
|
|
5733
5972
|
* `writes`/`deletes` are the outbox overlay (pending and acked items). The
|
|
@@ -5755,21 +5994,32 @@ function buildRenderIds(membership, localView, overlay, opts) {
|
|
|
5755
5994
|
|
|
5756
5995
|
//#endregion
|
|
5757
5996
|
//#region src/query/materialize.saga.ts
|
|
5997
|
+
const NONE = /* @__PURE__ */ new Set();
|
|
5758
5998
|
/**
|
|
5759
5999
|
* Render one query from state: membership (or a cold scan), the outbox
|
|
5760
6000
|
* overlay, one local read, then notify subscribers if the rows changed. The
|
|
5761
6001
|
* only writer of `records`. Runs on the `mat:<hash>` dedupe lane whenever the
|
|
5762
|
-
* query is dirty.
|
|
6002
|
+
* query is dirty. Before the server has answered (`cold`) only bodies some
|
|
6003
|
+
* durable view vouches for paint (see `paintsCold`).
|
|
5763
6004
|
*/
|
|
5764
6005
|
function* materialize(hash) {
|
|
5765
|
-
const [entry, ov] = yield fx.state.read((s) =>
|
|
6006
|
+
const [entry, ov, known] = yield fx.state.read((s) => {
|
|
6007
|
+
const e = s.queries.get(hash);
|
|
6008
|
+
return [
|
|
6009
|
+
e,
|
|
6010
|
+
overlay(s),
|
|
6011
|
+
e?.lifecycle.phase === "cold" ? vouched(s) : NONE
|
|
6012
|
+
];
|
|
6013
|
+
});
|
|
5766
6014
|
if (!entry) return;
|
|
5767
6015
|
const t0 = yield fx.now();
|
|
5768
6016
|
const isWindow = isWindowed(entry.def.surql);
|
|
6017
|
+
const paints = (id) => id === null || paintsCold(id, known, ov);
|
|
6018
|
+
const coldWindow = entry.lifecycle.phase === "cold" && isWindow;
|
|
5769
6019
|
const membership = resolveMembership({
|
|
5770
6020
|
phase: entry.lifecycle.phase,
|
|
5771
6021
|
remoteArray: entry.remoteArray,
|
|
5772
|
-
localArray: entry.localArray,
|
|
6022
|
+
localArray: coldWindow ? entry.localArray.filter(([id]) => paints(id)) : entry.localArray,
|
|
5773
6023
|
isWindow
|
|
5774
6024
|
});
|
|
5775
6025
|
const ids = membership ? buildRenderIds(membership, entry.localArray, ov, {
|
|
@@ -5780,6 +6030,7 @@ function* materialize(hash) {
|
|
|
5780
6030
|
let rows;
|
|
5781
6031
|
try {
|
|
5782
6032
|
rows = rowsFromResult(effect, yield effect);
|
|
6033
|
+
if (ids === null) rows = rows.filter((row) => paints(rowKey(row)));
|
|
5783
6034
|
} catch (error) {
|
|
5784
6035
|
yield fx.state.update(compose(recordError(hash), clearDirty(hash)));
|
|
5785
6036
|
yield fx.emit({
|
|
@@ -6089,6 +6340,7 @@ function* tabMessage(env, raw) {
|
|
|
6089
6340
|
});
|
|
6090
6341
|
}
|
|
6091
6342
|
yield fx.state.update(compose(setVersions(msg.records.filter((r) => r.op !== "DELETE").map((r) => [r.id, typeof r.record?._00_rv === "number" ? r.record._00_rv : 0])), deleteVersions(msg.records.filter((r) => r.op === "DELETE").map((r) => r.id)), ...[...new Set(msg.records.map((r) => r.table))].map((t) => markTableDirty(t))));
|
|
6343
|
+
if (msg.records.some((r) => r.op === "DELETE")) yield fx.dispatch({ type: "FetchRows" });
|
|
6092
6344
|
return;
|
|
6093
6345
|
case "membership-dirty":
|
|
6094
6346
|
if (Array.isArray(msg.hashes)) yield* liveChange(env, msg.hashes);
|
|
@@ -6207,6 +6459,7 @@ function* bucketSwitch(env, target, release = null, opts = { clearBlobsOnSignOut
|
|
|
6207
6459
|
data: { error }
|
|
6208
6460
|
});
|
|
6209
6461
|
}
|
|
6462
|
+
yield* loadViews();
|
|
6210
6463
|
yield* rebindQueries();
|
|
6211
6464
|
yield* loadOutbox(env);
|
|
6212
6465
|
yield fx.dispatch({ type: "EnsureRegistered" });
|
|
@@ -9310,7 +9563,7 @@ var SqliteCacheEngine = class {
|
|
|
9310
9563
|
await this.ensureTable(table);
|
|
9311
9564
|
const key = stableKey(id);
|
|
9312
9565
|
if (mode === "merge") {
|
|
9313
|
-
const
|
|
9566
|
+
const { insert, patch } = serializeMergeRow({
|
|
9314
9567
|
...data,
|
|
9315
9568
|
id: key
|
|
9316
9569
|
});
|
|
@@ -9318,8 +9571,8 @@ var SqliteCacheEngine = class {
|
|
|
9318
9571
|
sql: `INSERT INTO "${table}"(id, data) VALUES(?, ?) ON CONFLICT(id) DO UPDATE SET data = json_patch(data, ?)`,
|
|
9319
9572
|
bind: [
|
|
9320
9573
|
key,
|
|
9321
|
-
|
|
9322
|
-
|
|
9574
|
+
insert,
|
|
9575
|
+
patch
|
|
9323
9576
|
]
|
|
9324
9577
|
});
|
|
9325
9578
|
return;
|
|
@@ -9496,7 +9749,7 @@ var SqliteCacheEngine = class {
|
|
|
9496
9749
|
const key = stableKey(op.id);
|
|
9497
9750
|
ensure(t);
|
|
9498
9751
|
if (op.mode === "merge") {
|
|
9499
|
-
const
|
|
9752
|
+
const { insert, patch } = serializeMergeRow({
|
|
9500
9753
|
...op.data,
|
|
9501
9754
|
id: key
|
|
9502
9755
|
});
|
|
@@ -9504,8 +9757,8 @@ var SqliteCacheEngine = class {
|
|
|
9504
9757
|
sql: `INSERT INTO "${t}"(id, data) VALUES(?, ?) ON CONFLICT(id) DO UPDATE SET data = json_patch(data, ?)`,
|
|
9505
9758
|
bind: [
|
|
9506
9759
|
key,
|
|
9507
|
-
|
|
9508
|
-
|
|
9760
|
+
insert,
|
|
9761
|
+
patch
|
|
9509
9762
|
]
|
|
9510
9763
|
});
|
|
9511
9764
|
} else stmts.push({
|
|
@@ -13758,7 +14011,7 @@ function createTabsCoordinator(config, s, host) {
|
|
|
13758
14011
|
return new TabsCoordinator({
|
|
13759
14012
|
tabId: s.tabId,
|
|
13760
14013
|
fingerprint: computeTabsFingerprint({
|
|
13761
|
-
coreVersion: "0.0.1-canary.
|
|
14014
|
+
coreVersion: "0.0.1-canary.287",
|
|
13762
14015
|
schemaHash: hash53(config.schemaSurql),
|
|
13763
14016
|
endpoint: config.database.endpoint ?? "",
|
|
13764
14017
|
namespace: config.database.namespace,
|
package/dist/live.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as registerSelect, C as heartbeatRowGone, M as singleSnapshotSelect, R as sha256Hex, S as heartbeatBatch, a as metaFromRow, b as bodySelect, c as parseDuration, d as parseQueryParams, f as ANON_USER_ID, h as listRefTableFor, j as registerVars, l as parseRecordIdString, n as rowOfEdge, p as DEFAULT_REF_MODE, s as encodeRecordId, t as hashOfEdge, u as withTimeout, z as queryHashInput } from "./worker-edges.js";
|
|
2
2
|
import { n as decodeTokenClaims } from "./worker-impersonation.js";
|
|
3
3
|
import { RecordId, Surreal } from "surrealdb";
|
|
4
4
|
|
package/dist/pure.d.ts
CHANGED
|
@@ -181,7 +181,14 @@ type Vars = Record<string, unknown>;
|
|
|
181
181
|
declare const VIEW_TABLE = "_00_view";
|
|
182
182
|
declare const LEGACY_VIEW_TABLE = "_00_window";
|
|
183
183
|
declare const viewRecordId: (viewKey: string) => RecordId<string>;
|
|
184
|
-
|
|
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;
|
|
185
192
|
/** Copy every legacy `_00_window` row into `_00_view` once. */
|
|
186
193
|
declare const readLegacyViewRows: () => string;
|
|
187
194
|
declare const countViewRows: () => string;
|
|
@@ -319,4 +326,4 @@ declare function sha256Hex(input: string): Promise<string>;
|
|
|
319
326
|
*/
|
|
320
327
|
declare function queryKey(input: QueryKeyInput, sessionId: string | null): Promise<string>;
|
|
321
328
|
//#endregion
|
|
322
|
-
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, listRefBatchSelect, listRefSelect, listRefTableFor, metaFromRow, parseDuration, parseQueryParams, parseRecordIdString, queryHashInput, queryKey, queryRowCountBatchSelect, queryRowCountSelect, readLegacyViewRows, registerSelect, registerVars, rowOfEdge, sameBytes, sanitizeUserId, sha256Hex, singleSnapshotSelect, snapshotFromSingle, subqueryListRefSelect, surql, upsertBodiesTx, viewKeyInput, viewRecordId, viewRow };
|
|
329
|
+
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, 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 };
|
package/dist/pure.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as registerSelect, B as viewKeyInput, C as heartbeatRowGone, D as queryRowCountSelect, E as queryRowCountBatchSelect, F as viewRecordId, I as viewRow, L as surql, M as singleSnapshotSelect, N as subqueryListRefSelect, O as readLegacyViewRows, P as upsertBodiesTx, R as sha256Hex, S as heartbeatBatch, T as listRefSelect, _ as LEGACY_VIEW_TABLE, a as metaFromRow, b as bodySelect, c as parseDuration, d as parseQueryParams, f as ANON_USER_ID, g as sanitizeUserId, h as listRefTableFor, i as dedupeRecordVersions, j as registerVars, k as readViewRows, l as parseRecordIdString, m as bucketIdForUser, n as rowOfEdge, o as snapshotFromSingle, p as DEFAULT_REF_MODE, r as FAILED, s as encodeRecordId, t as hashOfEdge, v as VIEW_TABLE, w as listRefBatchSelect, x as countViewRows, y as batchSnapshotSelect, z as queryHashInput } 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, listRefBatchSelect, listRefSelect, listRefTableFor, metaFromRow, parseDuration, parseQueryParams, parseRecordIdString, queryHashInput, queryKey, queryRowCountBatchSelect, queryRowCountSelect, readLegacyViewRows, registerSelect, registerVars, rowOfEdge, sameBytes, sanitizeUserId, sha256Hex, singleSnapshotSelect, snapshotFromSingle, subqueryListRefSelect, surql, upsertBodiesTx, viewKeyInput, viewRecordId, viewRow };
|
|
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, 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 };
|