@spooky-sync/core 0.0.1-canary.29 → 0.0.1-canary.291
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 +2026 -137
- package/dist/index.js +14941 -2433
- package/dist/live.d.ts +141 -0
- package/dist/live.js +709 -0
- package/dist/otel/index.d.ts +1 -1
- package/dist/otel/index.js +3 -3
- package/dist/pure.d.ts +329 -0
- package/dist/pure.js +24 -0
- package/dist/sqlite-open.js +358 -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 +1765 -38
- package/dist/worker-edges.js +423 -0
- package/dist/worker-impersonation.js +60 -0
- package/dist/worker-ref-tables.d.ts +46 -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 +82 -0
- package/src/boot/auth-flip.saga.ts +62 -0
- package/src/boot/boot.saga.test.ts +158 -0
- package/src/boot/boot.saga.ts +131 -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.ts +370 -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 +161 -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 +478 -0
- package/src/live/index.ts +1013 -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 +141 -0
- package/src/modules/auth/events/index.ts +9 -2
- package/src/modules/auth/impersonation-banner.ts +443 -0
- package/src/modules/auth/impersonation.test.ts +355 -0
- package/src/modules/auth/impersonation.ts +179 -0
- package/src/modules/auth/index.ts +386 -17
- 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 +750 -96
- 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 +251 -0
- package/src/modules/feature-flag/index.ts +316 -0
- package/src/modules/ref-tables.test.ts +91 -0
- package/src/modules/ref-tables.ts +88 -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 +65 -0
- package/src/push/types.ts +174 -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/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 +293 -0
- package/src/query/register.saga.ts +226 -0
- package/src/query/render-set.test.ts +47 -0
- package/src/query/render-set.ts +71 -0
- package/src/query/sql.test.ts +63 -0
- package/src/query/sql.ts +93 -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 +467 -0
- package/src/services/database/relation-resolver.ts +0 -0
- package/src/services/database/remote.ts +213 -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 +139 -0
- package/src/services/database/sqlite-plan-sql.ts +179 -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 +246 -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 +779 -67
- 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 +118 -0
- package/src/state/lifecycle.ts +86 -0
- package/src/state/reducers.test.ts +304 -0
- package/src/state/reducers.ts +526 -0
- package/src/state/selectors.test.ts +167 -0
- package/src/state/selectors.ts +216 -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 +86 -0
- package/src/sync/connection.saga.ts +85 -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 +659 -0
- package/src/sync/policy.ts +502 -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 +75 -9
- 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 +2 -1
- 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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { PhaseStat, QueryHash, QueryState, QueryStatus, QueryTimings, RecordVersionArray } from '../types';
|
|
2
|
+
import type { ClientState, QueryEntry } from './client-state';
|
|
3
|
+
import { deriveStatus, hasServerMembership, isAuthoritative } from './lifecycle';
|
|
4
|
+
import { FETCH_CHUNK } from '../kernel/constants';
|
|
5
|
+
|
|
6
|
+
/** Pure reads over `ClientState`. Sagas use them through `state.read`. */
|
|
7
|
+
|
|
8
|
+
export const queryByHash = (s: ClientState, hash: QueryHash): QueryEntry | undefined => s.queries.get(hash);
|
|
9
|
+
|
|
10
|
+
export const activeHashes = (s: ClientState): QueryHash[] => [...s.queries.keys()];
|
|
11
|
+
|
|
12
|
+
export const hashesForTable = (s: ClientState, table: string): QueryHash[] =>
|
|
13
|
+
[...s.queries].filter(([, e]) => e.def.tableName === table).map(([h]) => h);
|
|
14
|
+
|
|
15
|
+
export const queryStatus = (s: ClientState, hash: QueryHash): QueryStatus | undefined => {
|
|
16
|
+
const e = s.queries.get(hash);
|
|
17
|
+
return e ? deriveStatus(e.lifecycle) : undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface Overlay {
|
|
21
|
+
/** Ids with an unsynced or just-acked create/update. */
|
|
22
|
+
readonly writes: ReadonlySet<string>;
|
|
23
|
+
/** Ids with an unsynced or just-acked delete. */
|
|
24
|
+
readonly deletes: ReadonlySet<string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The optimistic overlay, derived from the outbox (pending AND acked items). */
|
|
28
|
+
export function overlay(s: ClientState): Overlay {
|
|
29
|
+
const writes = new Set<string>();
|
|
30
|
+
const deletes = new Set<string>();
|
|
31
|
+
for (const item of s.outbox) {
|
|
32
|
+
if (item.type === 'delete') deletes.add(item.recordId);
|
|
33
|
+
else writes.add(item.recordId);
|
|
34
|
+
}
|
|
35
|
+
return { writes, deletes };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const vouchedCache = new WeakMap<ClientState['views'], ReadonlySet<string>>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Every id some durable `_00_view` row vouches for. Memoized on the index,
|
|
42
|
+
* which only changes when a view row does.
|
|
43
|
+
*/
|
|
44
|
+
export function vouched(s: ClientState): ReadonlySet<string> {
|
|
45
|
+
const hit = vouchedCache.get(s.views);
|
|
46
|
+
if (hit) return hit;
|
|
47
|
+
const out = new Set<string>();
|
|
48
|
+
for (const ids of s.views.values()) for (const id of ids) out.add(id);
|
|
49
|
+
vouchedCache.set(s.views, out);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bodies the GC must keep: `_00_` bookkeeping, anything a durable view
|
|
55
|
+
* vouches for, anything a query in memory names (members or children), and
|
|
56
|
+
* anything a write is still in flight for.
|
|
57
|
+
*/
|
|
58
|
+
export function retained(s: ClientState): (id: string) => boolean {
|
|
59
|
+
const held = new Set<string>();
|
|
60
|
+
for (const e of s.queries.values()) {
|
|
61
|
+
for (const [id] of e.remoteArray) held.add(id);
|
|
62
|
+
for (const [id] of e.subqueryRemoteArray) held.add(id);
|
|
63
|
+
}
|
|
64
|
+
for (const item of s.outbox) held.add(item.recordId);
|
|
65
|
+
for (const write of s.pendingWrites.values()) held.add(write.recordId);
|
|
66
|
+
const known = vouched(s);
|
|
67
|
+
return (id) => id.startsWith('_00_') || known.has(id) || held.has(id);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const pendingDeleteIds = (s: ClientState): ReadonlySet<string> =>
|
|
71
|
+
new Set(s.outbox.filter((i) => i.type === 'delete').map((i) => i.recordId));
|
|
72
|
+
|
|
73
|
+
export const hasAckedWrites = (s: ClientState): boolean => s.outbox.some((i) => i.status === 'acked');
|
|
74
|
+
|
|
75
|
+
export const pendingMutationCount = (s: ClientState): number =>
|
|
76
|
+
s.outbox.filter((i) => i.status === 'pending').length;
|
|
77
|
+
|
|
78
|
+
export const fetchingQueryCount = (s: ClientState): number =>
|
|
79
|
+
[...s.queries.values()].filter((e) => e.lifecycle.fetchDepth > 0).length;
|
|
80
|
+
|
|
81
|
+
/** Membership entries whose body is missing or older locally. */
|
|
82
|
+
export function needed(s: ClientState, hash: QueryHash): RecordVersionArray {
|
|
83
|
+
const e = s.queries.get(hash);
|
|
84
|
+
if (!e || !hasServerMembership(e.lifecycle)) return [];
|
|
85
|
+
const deletes = pendingDeleteIds(s);
|
|
86
|
+
return e.remoteArray.filter(([id, v]) => !deletes.has(id) && (s.versions.get(id) ?? -1) < v);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Subquery child bodies missing or stale locally (never part of `settled`). */
|
|
90
|
+
export function neededChildren(s: ClientState, hash: QueryHash): RecordVersionArray {
|
|
91
|
+
const e = s.queries.get(hash);
|
|
92
|
+
if (!e || e.subqueryRemoteArray.length === 0) return [];
|
|
93
|
+
return e.subqueryRemoteArray.filter(([id, v]) => (s.versions.get(id) ?? -1) < v);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface FetchPlan {
|
|
97
|
+
/** Queries whose primary membership needs bodies (they flip to `fetching`). */
|
|
98
|
+
readonly hashes: QueryHash[];
|
|
99
|
+
readonly chunks: string[][];
|
|
100
|
+
/** Highest requested version per id, across every query naming it. */
|
|
101
|
+
readonly versions: ReadonlyMap<string, number>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Cross-query, deduped, chunked list of ids to pull from the server. */
|
|
105
|
+
export function planFetch(s: ClientState, chunkSize = FETCH_CHUNK): FetchPlan {
|
|
106
|
+
const versions = new Map<string, number>();
|
|
107
|
+
const hashes: QueryHash[] = [];
|
|
108
|
+
const add = (pairs: RecordVersionArray) => {
|
|
109
|
+
for (const [id, v] of pairs) versions.set(id, Math.max(v, versions.get(id) ?? -1));
|
|
110
|
+
};
|
|
111
|
+
for (const hash of s.queries.keys()) {
|
|
112
|
+
const missing = needed(s, hash);
|
|
113
|
+
if (missing.length > 0) hashes.push(hash);
|
|
114
|
+
add(missing);
|
|
115
|
+
add(neededChildren(s, hash));
|
|
116
|
+
}
|
|
117
|
+
const all = [...versions.keys()];
|
|
118
|
+
const chunks: string[][] = [];
|
|
119
|
+
for (let i = 0; i < all.length; i += chunkSize) chunks.push(all.slice(i, i + chunkSize));
|
|
120
|
+
return { hashes, chunks, versions };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* "This query's rows are authoritative and complete": server membership
|
|
125
|
+
* accepted, every body present at its version, nothing waiting to be
|
|
126
|
+
* re-rendered, subscribers told at least once.
|
|
127
|
+
*/
|
|
128
|
+
export function settled(s: ClientState, hash: QueryHash): boolean {
|
|
129
|
+
const e = s.queries.get(hash);
|
|
130
|
+
if (!e || e.lifecycle.phase !== 'live') return false;
|
|
131
|
+
return needed(s, hash).length === 0 && !s.dirty.has(hash) && e.lifecycle.notified;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const settleFailed = (s: ClientState, hash: QueryHash): boolean => {
|
|
135
|
+
const e = s.queries.get(hash);
|
|
136
|
+
return !e || e.lifecycle.remote === 'failed';
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const desiredRegistrations = (s: ClientState): QueryHash[] =>
|
|
140
|
+
[...s.queries].filter(([, e]) => e.lifecycle.remote === 'unregistered').map(([h]) => h);
|
|
141
|
+
|
|
142
|
+
export const evictable = (s: ClientState, now: number): QueryHash[] =>
|
|
143
|
+
[...s.queries]
|
|
144
|
+
.filter(
|
|
145
|
+
([, e]) => e.subscribers === 0 && e.lastSubscriberLeftAt !== null && now - e.lastSubscriberLeftAt >= e.def.ttlMs
|
|
146
|
+
)
|
|
147
|
+
.map(([h]) => h);
|
|
148
|
+
|
|
149
|
+
export const shortestTtlMs = (s: ClientState): number | null => {
|
|
150
|
+
let min: number | null = null;
|
|
151
|
+
for (const e of s.queries.values()) min = min === null ? e.def.ttlMs : Math.min(min, e.def.ttlMs);
|
|
152
|
+
return min;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Legacy `QueryState` shape for DevTools and the public type surface. */
|
|
156
|
+
export function toQueryState(e: QueryEntry): QueryState {
|
|
157
|
+
const l = e.lifecycle;
|
|
158
|
+
return {
|
|
159
|
+
config: {
|
|
160
|
+
id: e.def.id,
|
|
161
|
+
surql: e.def.surql,
|
|
162
|
+
plan: e.def.plan,
|
|
163
|
+
params: { ...e.def.params },
|
|
164
|
+
localArray: [...e.localArray],
|
|
165
|
+
remoteArray: [...e.remoteArray],
|
|
166
|
+
subqueryRemoteArray: e.subqueryRemoteArray.length ? [...e.subqueryRemoteArray] : undefined,
|
|
167
|
+
membershipKnown: isAuthoritative(l),
|
|
168
|
+
membershipKey: e.def.viewKey,
|
|
169
|
+
ttl: e.def.ttl,
|
|
170
|
+
lastActiveAt: new Date(e.lastHeartbeatAt ?? e.def.createdAt),
|
|
171
|
+
tableName: e.def.tableName,
|
|
172
|
+
},
|
|
173
|
+
records: [...e.records] as Record<string, any>[],
|
|
174
|
+
serverMembership: hasServerMembership(l),
|
|
175
|
+
viewLost: l.phase === 'view-lost',
|
|
176
|
+
serverState: e.serverState,
|
|
177
|
+
syncNotified: l.notified,
|
|
178
|
+
ttlTimer: null,
|
|
179
|
+
ttlDurationMs: e.def.ttlMs,
|
|
180
|
+
lastHeartbeatAt: e.lastHeartbeatAt ?? undefined,
|
|
181
|
+
updateCount: e.telemetry.updateCount,
|
|
182
|
+
lastUpdatedAt: e.telemetry.lastUpdatedAt,
|
|
183
|
+
materializationSamples: [...e.telemetry.materializationSamples],
|
|
184
|
+
lastIngestLatencyMs: e.telemetry.lastIngestLatencyMs,
|
|
185
|
+
errorCount: e.telemetry.errorCount,
|
|
186
|
+
status: deriveStatus(l),
|
|
187
|
+
phaseSamples: Object.fromEntries(Object.entries(e.telemetry.phaseSamples).map(([k, v]) => [k, [...v]])),
|
|
188
|
+
phaseLast: { ...e.telemetry.phaseLast },
|
|
189
|
+
registrationTimings: e.telemetry.registrationTimings,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function phaseStatOf(samples: ReadonlyArray<number>, lastMs: number | null): PhaseStat {
|
|
194
|
+
if (samples.length === 0) return { lastMs, p50: null, p90: null, p99: null, count: 0 };
|
|
195
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
196
|
+
const pick = (q: number) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]!;
|
|
197
|
+
return { lastMs, p50: pick(0.5), p90: pick(0.9), p99: pick(0.99), count: samples.length };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Per-query processing-time breakdown for DevTools and the MCP. */
|
|
201
|
+
export function phaseTimings(e: QueryEntry): QueryTimings {
|
|
202
|
+
const t = e.telemetry;
|
|
203
|
+
const stat = (phase: string) => phaseStatOf(t.phaseSamples[phase] ?? [], t.phaseLast[phase] ?? null);
|
|
204
|
+
return {
|
|
205
|
+
ssp: phaseStatOf(t.materializationSamples, t.lastIngestLatencyMs),
|
|
206
|
+
sspStoreApply: stat('sspStoreApply'),
|
|
207
|
+
sspCircuitStep: stat('sspCircuitStep'),
|
|
208
|
+
sspTransform: stat('sspTransform'),
|
|
209
|
+
localFetch: stat('localFetch'),
|
|
210
|
+
remoteFetch: stat('remoteFetch'),
|
|
211
|
+
frontend: stat('frontend'),
|
|
212
|
+
registration: t.registrationTimings,
|
|
213
|
+
updateCount: t.updateCount,
|
|
214
|
+
errorCount: t.errorCount,
|
|
215
|
+
};
|
|
216
|
+
}
|