@spooky-sync/core 0.0.1-canary.240 → 0.0.1-canary.241
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 +34 -15
- package/README.md +56 -16
- package/dist/index.d.ts +1136 -1846
- package/dist/index.js +10168 -11267
- package/dist/types.d.ts +22 -79
- package/package.json +8 -4
- package/skills/sp00ky-core/SKILL.md +18 -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 +151 -0
- package/src/boot/boot.saga.ts +128 -0
- package/src/boot/bucket-switch.saga.test.ts +158 -0
- package/src/boot/bucket-switch.saga.ts +115 -0
- package/src/boot/preload.saga.test.ts +51 -0
- package/src/boot/preload.saga.ts +31 -0
- package/src/client/bucket-handle.test.ts +117 -0
- package/src/client/bucket-handle.ts +227 -0
- package/src/client/router.test.ts +56 -0
- package/src/client/router.ts +94 -0
- package/src/client/runtime.test.ts +192 -0
- package/src/client/runtime.ts +296 -0
- package/src/client/services.ts +349 -0
- package/src/client/sp00ky-client.test.ts +241 -0
- package/src/client/sp00ky-client.ts +482 -0
- package/src/index.ts +1 -0
- package/src/kernel/constants.test.ts +19 -0
- package/src/kernel/constants.ts +54 -0
- package/src/kernel/effects.test.ts +40 -0
- package/src/kernel/effects.ts +159 -0
- package/src/kernel/events.ts +55 -0
- package/src/kernel/interpreter.test.ts +116 -0
- package/src/kernel/interpreter.ts +143 -0
- package/src/kernel/saga.test.ts +89 -0
- package/src/kernel/saga.ts +73 -0
- package/src/modules/app-release/index.ts +13 -5
- package/src/modules/devtools/index.ts +10 -3
- package/src/modules/devtools/state-shape.test.ts +4 -1
- package/src/modules/feature-flag/index.ts +13 -5
- package/src/mutation/jobs.test.ts +25 -0
- package/src/mutation/jobs.ts +41 -0
- package/src/{modules/data → mutation}/mutation-id.test.ts +12 -0
- package/src/mutation/push.saga.test.ts +285 -0
- package/src/mutation/push.saga.ts +215 -0
- package/src/mutation/rows.test.ts +201 -0
- package/src/mutation/rows.ts +377 -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 +163 -0
- package/src/mutation/write.saga.ts +166 -0
- package/src/query/env.test.ts +23 -0
- package/src/query/env.ts +56 -0
- package/src/query/fetch.saga.test.ts +156 -0
- package/src/query/fetch.saga.ts +109 -0
- package/src/query/hash.test.ts +14 -0
- package/src/query/hash.ts +18 -0
- package/src/{modules/data → query}/id-set-plan.test.ts +12 -2
- package/src/query/lifecycle.saga.test.ts +122 -0
- package/src/query/lifecycle.saga.ts +101 -0
- package/src/query/materialize.saga.test.ts +82 -0
- package/src/query/materialize.saga.ts +75 -0
- package/src/query/materialize.test.ts +50 -0
- package/src/query/materialize.ts +44 -0
- package/src/query/membership.saga.test.ts +246 -0
- package/src/query/membership.saga.ts +208 -0
- package/src/query/membership.test.ts +118 -0
- package/src/query/membership.ts +158 -0
- package/src/query/register.saga.test.ts +251 -0
- package/src/query/register.saga.ts +190 -0
- package/src/query/render-set.test.ts +36 -0
- package/src/query/render-set.ts +60 -0
- package/src/query/sql.test.ts +62 -0
- package/src/query/sql.ts +79 -0
- package/src/{modules/data → query}/window-query.test.ts +18 -0
- package/src/{modules/data → query}/window-query.ts +2 -1
- package/src/services/database/connection-supervisor.test.ts +29 -0
- package/src/services/database/connection-supervisor.ts +7 -0
- package/src/services/database/database.responses.test.ts +43 -0
- package/src/services/database/database.ts +25 -10
- package/src/services/database/local-migrator.ts +2 -2
- package/src/services/database/plan-render.test.ts +1 -1
- package/src/services/database/sqlite-cache-engine.test.ts +4 -3
- package/src/services/database/sqlite-cache-engine.ts +2 -11
- package/src/services/database/sqlite-devtools-queries.integration.test.ts +1 -1
- package/src/services/tabs/protocol.ts +7 -3
- package/src/sp00ky.ts +5 -1854
- package/src/state/client-state.ts +184 -0
- package/src/state/lifecycle.test.ts +118 -0
- package/src/state/lifecycle.ts +86 -0
- package/src/state/reducers.test.ts +280 -0
- package/src/state/reducers.ts +476 -0
- package/src/state/selectors.test.ts +149 -0
- package/src/state/selectors.ts +184 -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 +69 -0
- package/src/sync/live.saga.ts +49 -0
- package/src/sync/policy.health.test.ts +56 -0
- package/src/{modules/sync/utils.test.ts → sync/policy.test.ts} +31 -3
- package/src/{modules/sync/utils.ts → sync/policy.ts} +67 -5
- package/src/sync/poll.saga.test.ts +51 -0
- package/src/sync/poll.saga.ts +36 -0
- package/src/sync/tabs.saga.test.ts +74 -0
- package/src/sync/tabs.saga.ts +92 -0
- package/src/testing/adapters.ts +133 -0
- package/src/testing/build.ts +61 -0
- package/src/testing/fake-services.ts +34 -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 +8 -7
- package/src/utils/utils.test.ts +2 -1
- package/vitest.config.ts +48 -0
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -476
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/boot/auth-flip.saga.ts.html +0 -271
- package/coverage/lcov-report/src/boot/boot.saga.ts.html +0 -469
- package/coverage/lcov-report/src/boot/bucket-switch.saga.ts.html +0 -430
- package/coverage/lcov-report/src/boot/index.html +0 -161
- package/coverage/lcov-report/src/boot/preload.saga.ts.html +0 -178
- package/coverage/lcov-report/src/client/bucket-handle.ts.html +0 -766
- package/coverage/lcov-report/src/client/index.html +0 -161
- package/coverage/lcov-report/src/client/router.ts.html +0 -367
- package/coverage/lcov-report/src/client/runtime.ts.html +0 -940
- package/coverage/lcov-report/src/client/sp00ky-client.ts.html +0 -1531
- package/coverage/lcov-report/src/events/index.html +0 -116
- package/coverage/lcov-report/src/events/index.ts.html +0 -877
- package/coverage/lcov-report/src/index.html +0 -146
- package/coverage/lcov-report/src/index.ts.html +0 -202
- package/coverage/lcov-report/src/kernel/constants.ts.html +0 -247
- package/coverage/lcov-report/src/kernel/effects.ts.html +0 -562
- package/coverage/lcov-report/src/kernel/events.ts.html +0 -250
- package/coverage/lcov-report/src/kernel/index.html +0 -176
- package/coverage/lcov-report/src/kernel/interpreter.ts.html +0 -514
- package/coverage/lcov-report/src/kernel/saga.ts.html +0 -304
- package/coverage/lcov-report/src/modules/app-release/index.html +0 -116
- package/coverage/lcov-report/src/modules/app-release/index.ts.html +0 -712
- package/coverage/lcov-report/src/modules/auth/events/index.html +0 -116
- package/coverage/lcov-report/src/modules/auth/events/index.ts.html +0 -142
- package/coverage/lcov-report/src/modules/auth/index.html +0 -116
- package/coverage/lcov-report/src/modules/auth/index.ts.html +0 -1225
- package/coverage/lcov-report/src/modules/crdt/crdt-field.ts.html +0 -967
- package/coverage/lcov-report/src/modules/crdt/index.html +0 -146
- package/coverage/lcov-report/src/modules/crdt/index.ts.html +0 -1474
- package/coverage/lcov-report/src/modules/crdt/loro-loader.ts.html +0 -160
- package/coverage/lcov-report/src/modules/devtools/flags.ts.html +0 -1132
- package/coverage/lcov-report/src/modules/devtools/index.html +0 -161
- package/coverage/lcov-report/src/modules/devtools/index.ts.html +0 -2413
- package/coverage/lcov-report/src/modules/devtools/storage-info.ts.html +0 -589
- package/coverage/lcov-report/src/modules/devtools/versions.ts.html +0 -415
- package/coverage/lcov-report/src/modules/feature-flag/index.html +0 -116
- package/coverage/lcov-report/src/modules/feature-flag/index.ts.html +0 -1033
- package/coverage/lcov-report/src/modules/index.html +0 -116
- package/coverage/lcov-report/src/modules/ref-tables.ts.html +0 -349
- package/coverage/lcov-report/src/mutation/index.html +0 -191
- package/coverage/lcov-report/src/mutation/jobs.ts.html +0 -208
- package/coverage/lcov-report/src/mutation/mutation-id.ts.html +0 -190
- package/coverage/lcov-report/src/mutation/push.saga.ts.html +0 -730
- package/coverage/lcov-report/src/mutation/rows.ts.html +0 -1216
- package/coverage/lcov-report/src/mutation/tray.saga.ts.html +0 -229
- package/coverage/lcov-report/src/mutation/write.saga.ts.html +0 -583
- package/coverage/lcov-report/src/otel/index.html +0 -116
- package/coverage/lcov-report/src/otel/index.ts.html +0 -466
- package/coverage/lcov-report/src/query/env.ts.html +0 -253
- package/coverage/lcov-report/src/query/fetch.saga.ts.html +0 -412
- package/coverage/lcov-report/src/query/hash.ts.html +0 -139
- package/coverage/lcov-report/src/query/index.html +0 -281
- package/coverage/lcov-report/src/query/lifecycle.saga.ts.html +0 -388
- package/coverage/lcov-report/src/query/materialize.saga.ts.html +0 -310
- package/coverage/lcov-report/src/query/materialize.ts.html +0 -217
- package/coverage/lcov-report/src/query/membership.saga.ts.html +0 -709
- package/coverage/lcov-report/src/query/membership.ts.html +0 -559
- package/coverage/lcov-report/src/query/register.saga.ts.html +0 -655
- package/coverage/lcov-report/src/query/render-set.ts.html +0 -265
- package/coverage/lcov-report/src/query/sql.ts.html +0 -322
- package/coverage/lcov-report/src/query/window-query.ts.html +0 -670
- package/coverage/lcov-report/src/services/blobs/blob-cache.ts.html +0 -1894
- package/coverage/lcov-report/src/services/blobs/blob-manifest.ts.html +0 -766
- package/coverage/lcov-report/src/services/blobs/blob-store.ts.html +0 -1162
- package/coverage/lcov-report/src/services/blobs/index.html +0 -161
- package/coverage/lcov-report/src/services/blobs/index.ts.html +0 -295
- package/coverage/lcov-report/src/services/database/cache-engine.ts.html +0 -664
- package/coverage/lcov-report/src/services/database/connection-supervisor.ts.html +0 -1330
- package/coverage/lcov-report/src/services/database/database.ts.html +0 -658
- package/coverage/lcov-report/src/services/database/engine-factory.ts.html +0 -184
- package/coverage/lcov-report/src/services/database/errors.ts.html +0 -187
- package/coverage/lcov-report/src/services/database/events/index.html +0 -116
- package/coverage/lcov-report/src/services/database/events/index.ts.html +0 -184
- package/coverage/lcov-report/src/services/database/index.html +0 -386
- package/coverage/lcov-report/src/services/database/index.ts.html +0 -121
- package/coverage/lcov-report/src/services/database/local-migrator.ts.html +0 -703
- package/coverage/lcov-report/src/services/database/local.ts.html +0 -1633
- package/coverage/lcov-report/src/services/database/plan-render.ts.html +0 -409
- package/coverage/lcov-report/src/services/database/relation-resolver.ts.html +0 -682
- package/coverage/lcov-report/src/services/database/remote.ts.html +0 -1036
- package/coverage/lcov-report/src/services/database/sqlite-cache-engine.ts.html +0 -4132
- package/coverage/lcov-report/src/services/database/sqlite-lock-verify.ts.html +0 -220
- package/coverage/lcov-report/src/services/database/sqlite-open.ts.html +0 -577
- package/coverage/lcov-report/src/services/database/sqlite-plan-sql.ts.html +0 -499
- package/coverage/lcov-report/src/services/database/sqlite-select.ts.html +0 -478
- package/coverage/lcov-report/src/services/database/sqlite-transport.ts.html +0 -757
- package/coverage/lcov-report/src/services/database/surql-translate.ts.html +0 -1333
- package/coverage/lcov-report/src/services/database/surreal-cache-engine.ts.html +0 -568
- package/coverage/lcov-report/src/services/logger/index.html +0 -116
- package/coverage/lcov-report/src/services/logger/index.ts.html +0 -154
- package/coverage/lcov-report/src/services/persistence/index.html +0 -146
- package/coverage/lcov-report/src/services/persistence/localstorage.ts.html +0 -163
- package/coverage/lcov-report/src/services/persistence/resilient.ts.html +0 -208
- package/coverage/lcov-report/src/services/persistence/surrealdb.ts.html +0 -271
- package/coverage/lcov-report/src/services/stream-processor/index.html +0 -146
- package/coverage/lcov-report/src/services/stream-processor/index.ts.html +0 -3313
- package/coverage/lcov-report/src/services/stream-processor/permissions.ts.html +0 -244
- package/coverage/lcov-report/src/services/stream-processor/wasm-types.ts.html +0 -346
- package/coverage/lcov-report/src/services/tabs/broker-client.ts.html +0 -934
- package/coverage/lcov-report/src/services/tabs/coordinator.ts.html +0 -1984
- package/coverage/lcov-report/src/services/tabs/index.html +0 -191
- package/coverage/lcov-report/src/services/tabs/leader-locks.ts.html +0 -310
- package/coverage/lcov-report/src/services/tabs/protocol.ts.html +0 -871
- package/coverage/lcov-report/src/services/tabs/support.ts.html +0 -193
- package/coverage/lcov-report/src/services/tabs/tabs-broker-worker.ts.html +0 -2005
- package/coverage/lcov-report/src/sp00ky.ts.html +0 -100
- package/coverage/lcov-report/src/state/client-state.ts.html +0 -637
- package/coverage/lcov-report/src/state/index.html +0 -161
- package/coverage/lcov-report/src/state/lifecycle.ts.html +0 -343
- package/coverage/lcov-report/src/state/reducers.ts.html +0 -1471
- package/coverage/lcov-report/src/state/selectors.ts.html +0 -637
- package/coverage/lcov-report/src/sync/connection.saga.ts.html +0 -343
- package/coverage/lcov-report/src/sync/index.html +0 -176
- package/coverage/lcov-report/src/sync/live.saga.ts.html +0 -232
- package/coverage/lcov-report/src/sync/policy.ts.html +0 -1606
- package/coverage/lcov-report/src/sync/poll.saga.ts.html +0 -193
- package/coverage/lcov-report/src/sync/tabs.saga.ts.html +0 -361
- package/coverage/lcov-report/src/types.ts.html +0 -2419
- package/coverage/lcov-report/src/utils/blurhash.ts.html +0 -355
- package/coverage/lcov-report/src/utils/error-classification.ts.html +0 -244
- package/coverage/lcov-report/src/utils/index.html +0 -191
- package/coverage/lcov-report/src/utils/index.ts.html +0 -820
- package/coverage/lcov-report/src/utils/parser.ts.html +0 -313
- package/coverage/lcov-report/src/utils/semver.ts.html +0 -175
- package/coverage/lcov-report/src/utils/surql.ts.html +0 -667
- package/coverage/lcov.info +0 -19079
- package/src/modules/cache/cache.relay.test.ts +0 -95
- package/src/modules/cache/index.ts +0 -361
- package/src/modules/cache/types.ts +0 -19
- package/src/modules/data/data.activity.test.ts +0 -109
- package/src/modules/data/data.authority.test.ts +0 -122
- package/src/modules/data/data.heartbeat-wake.test.ts +0 -115
- package/src/modules/data/data.hydration.test.ts +0 -142
- package/src/modules/data/data.membership.test.ts +0 -623
- package/src/modules/data/data.notify-table.test.ts +0 -41
- package/src/modules/data/data.pending-ids.test.ts +0 -199
- package/src/modules/data/data.rebind.test.ts +0 -170
- package/src/modules/data/data.rematerialize.test.ts +0 -114
- package/src/modules/data/data.run.test.ts +0 -113
- package/src/modules/data/data.settled-writes.test.ts +0 -235
- package/src/modules/data/data.status.test.ts +0 -249
- package/src/modules/data/data.test.ts +0 -58
- package/src/modules/data/index.ts +0 -2745
- package/src/modules/sync/engine.ts +0 -250
- package/src/modules/sync/events/index.ts +0 -84
- package/src/modules/sync/index.ts +0 -3
- package/src/modules/sync/queue/index.ts +0 -2
- package/src/modules/sync/queue/queue-down.test.ts +0 -228
- package/src/modules/sync/queue/queue-down.ts +0 -184
- package/src/modules/sync/queue/queue-up.forwarded.test.ts +0 -164
- package/src/modules/sync/queue/queue-up.ts +0 -407
- package/src/modules/sync/scheduler.pause.test.ts +0 -109
- package/src/modules/sync/scheduler.retry.test.ts +0 -237
- package/src/modules/sync/scheduler.ts +0 -286
- package/src/modules/sync/sync.cleanup.test.ts +0 -116
- package/src/modules/sync/sync.health.test.ts +0 -167
- package/src/modules/sync/sync.heartbeat.test.ts +0 -80
- package/src/modules/sync/sync.list-ref-meta.test.ts +0 -193
- package/src/modules/sync/sync.live-removal.test.ts +0 -289
- package/src/modules/sync/sync.reconnect.test.ts +0 -145
- package/src/modules/sync/sync.rematerialize-after-fetch.test.ts +0 -93
- package/src/modules/sync/sync.settled-poll.test.ts +0 -58
- package/src/modules/sync/sync.subquery.test.ts +0 -82
- package/src/modules/sync/sync.tabs.test.ts +0 -249
- package/src/modules/sync/sync.ts +0 -2626
- package/src/sp00ky.auth-order.test.ts +0 -112
- package/src/sp00ky.init-query.test.ts +0 -183
- package/src/sp00ky.local-first.test.ts +0 -60
- /package/src/{modules/data → mutation}/mutation-id.ts +0 -0
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { RecordId } from 'surrealdb';
|
|
3
|
-
import { DataModule } from './index';
|
|
4
|
-
import type { QueryState } from '../../types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A query's AUTHORITY is whether server membership is known for it. It is what
|
|
8
|
-
* the bindings' `isAuthoritative()` mirrors, and with `hasData` it is what
|
|
9
|
-
* separates "cold, still waiting for the server" from "the server answered and
|
|
10
|
-
* the answer is empty". `setMembershipKnown` is the one writer; it fans out to
|
|
11
|
-
* `subscribeAuthority` listeners and the DevTools hook only on a change.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
function makeLogger(): any {
|
|
15
|
-
const noop = () => {};
|
|
16
|
-
const logger: any = { debug: noop, info: noop, warn: noop, error: noop, trace: noop };
|
|
17
|
-
logger.child = () => logger;
|
|
18
|
-
return logger;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeDataModule(): DataModule<any> {
|
|
22
|
-
return new DataModule(
|
|
23
|
-
{ unregisterQuery: vi.fn() } as any,
|
|
24
|
-
{} as any,
|
|
25
|
-
{ tables: [] } as any,
|
|
26
|
-
makeLogger(),
|
|
27
|
-
100
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeQueryState(hash: string, membershipKnown?: boolean): QueryState {
|
|
32
|
-
return {
|
|
33
|
-
config: {
|
|
34
|
-
id: new RecordId('_00_query', hash),
|
|
35
|
-
surql: 'SELECT * FROM user',
|
|
36
|
-
params: {},
|
|
37
|
-
localArray: [],
|
|
38
|
-
remoteArray: [],
|
|
39
|
-
membershipKnown,
|
|
40
|
-
ttl: '10m',
|
|
41
|
-
lastActiveAt: new Date(),
|
|
42
|
-
tableName: 'user',
|
|
43
|
-
},
|
|
44
|
-
records: [],
|
|
45
|
-
ttlTimer: null,
|
|
46
|
-
ttlDurationMs: 0,
|
|
47
|
-
updateCount: 0,
|
|
48
|
-
lastUpdatedAt: null,
|
|
49
|
-
materializationSamples: [],
|
|
50
|
-
lastIngestLatencyMs: null,
|
|
51
|
-
errorCount: 0,
|
|
52
|
-
status: 'idle',
|
|
53
|
-
phaseSamples: {},
|
|
54
|
-
phaseLast: {},
|
|
55
|
-
registrationTimings: { parseMs: null, planMs: null, snapshotMs: null, wallMs: null },
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe('DataModule query authority', () => {
|
|
60
|
-
const hash = 'h1';
|
|
61
|
-
|
|
62
|
-
it('reports the current value immediately, false for an unknown query', () => {
|
|
63
|
-
const dm = makeDataModule();
|
|
64
|
-
const unknown = vi.fn();
|
|
65
|
-
dm.subscribeAuthority('nope', unknown, { immediate: true });
|
|
66
|
-
expect(unknown).toHaveBeenCalledWith(false);
|
|
67
|
-
|
|
68
|
-
(dm as any).activeQueries.set(hash, makeQueryState(hash, true));
|
|
69
|
-
const known = vi.fn();
|
|
70
|
-
dm.subscribeAuthority(hash, known, { immediate: true });
|
|
71
|
-
expect(known).toHaveBeenCalledWith(true);
|
|
72
|
-
expect(dm.isAuthoritative(hash)).toBe(true);
|
|
73
|
-
expect(dm.isAuthoritative('nope')).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('emits once per transition through the single writer', () => {
|
|
77
|
-
const dm = makeDataModule();
|
|
78
|
-
(dm as any).activeQueries.set(hash, makeQueryState(hash));
|
|
79
|
-
const cb = vi.fn();
|
|
80
|
-
const hook = vi.fn();
|
|
81
|
-
dm.onQueryAuthorityChange = hook;
|
|
82
|
-
dm.subscribeAuthority(hash, cb);
|
|
83
|
-
|
|
84
|
-
(dm as any).setMembershipKnown(hash, true);
|
|
85
|
-
(dm as any).setMembershipKnown(hash, true);
|
|
86
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
87
|
-
expect(cb).toHaveBeenLastCalledWith(true);
|
|
88
|
-
expect(hook).toHaveBeenCalledWith(hash, true);
|
|
89
|
-
|
|
90
|
-
(dm as any).setMembershipKnown(hash, false);
|
|
91
|
-
expect(cb).toHaveBeenCalledTimes(2);
|
|
92
|
-
expect(cb).toHaveBeenLastCalledWith(false);
|
|
93
|
-
expect(dm.isAuthoritative(hash)).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('is a no-op for an unknown query', () => {
|
|
97
|
-
const dm = makeDataModule();
|
|
98
|
-
const hook = vi.fn();
|
|
99
|
-
dm.onQueryAuthorityChange = hook;
|
|
100
|
-
(dm as any).setMembershipKnown('nope', true);
|
|
101
|
-
expect(hook).not.toHaveBeenCalled();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('stops delivering after unsubscribe', () => {
|
|
105
|
-
const dm = makeDataModule();
|
|
106
|
-
(dm as any).activeQueries.set(hash, makeQueryState(hash));
|
|
107
|
-
const cb = vi.fn();
|
|
108
|
-
const off = dm.subscribeAuthority(hash, cb);
|
|
109
|
-
off();
|
|
110
|
-
(dm as any).setMembershipKnown(hash, true);
|
|
111
|
-
expect(cb).not.toHaveBeenCalled();
|
|
112
|
-
expect((dm as any).authoritySubscriptions.has(hash)).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('drops the authority subscribers with the query on finalizeDeregister', () => {
|
|
116
|
-
const dm = makeDataModule();
|
|
117
|
-
(dm as any).activeQueries.set(hash, makeQueryState(hash));
|
|
118
|
-
dm.subscribeAuthority(hash, vi.fn());
|
|
119
|
-
dm.finalizeDeregister(hash);
|
|
120
|
-
expect((dm as any).authoritySubscriptions.has(hash)).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { RecordId } from 'surrealdb';
|
|
3
|
-
import { DataModule } from './index';
|
|
4
|
-
import type { QueryState } from '../../types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The TTL heartbeat is the only thing keeping a view alive on the server, and
|
|
8
|
-
* browsers freeze timers in hidden tabs. `heartbeatOnWake` re-asserts liveness
|
|
9
|
-
* for every WATCHED query the moment the tab is back (visibilitychange /
|
|
10
|
-
* online) and re-anchors its timer. There is no client-side expiry anywhere:
|
|
11
|
-
* the TTL belongs to the server.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
function makeLogger(): any {
|
|
15
|
-
const noop = () => {};
|
|
16
|
-
const logger: any = { debug: noop, info: noop, warn: noop, error: noop, trace: noop };
|
|
17
|
-
logger.child = () => logger;
|
|
18
|
-
return logger;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeDataModule(): DataModule<any> {
|
|
22
|
-
return new DataModule({} as any, {} as any, { tables: [] } as any, makeLogger(), 100);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TTL_MS = 600_000;
|
|
26
|
-
|
|
27
|
-
function makeQueryState(hash: string): QueryState {
|
|
28
|
-
return {
|
|
29
|
-
config: {
|
|
30
|
-
id: new RecordId('_00_query', hash),
|
|
31
|
-
surql: 'SELECT * FROM user',
|
|
32
|
-
params: {},
|
|
33
|
-
localArray: [],
|
|
34
|
-
remoteArray: [['user:a', 1]],
|
|
35
|
-
membershipKnown: true,
|
|
36
|
-
ttl: '10m',
|
|
37
|
-
lastActiveAt: new Date(),
|
|
38
|
-
tableName: 'user',
|
|
39
|
-
},
|
|
40
|
-
records: [{ id: 'user:a' }],
|
|
41
|
-
ttlTimer: null,
|
|
42
|
-
ttlDurationMs: TTL_MS,
|
|
43
|
-
updateCount: 0,
|
|
44
|
-
lastUpdatedAt: null,
|
|
45
|
-
materializationSamples: [],
|
|
46
|
-
lastIngestLatencyMs: null,
|
|
47
|
-
errorCount: 0,
|
|
48
|
-
status: 'idle',
|
|
49
|
-
phaseSamples: {},
|
|
50
|
-
phaseLast: {},
|
|
51
|
-
registrationTimings: { parseMs: null, planMs: null, snapshotMs: null, wallMs: null },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function setup() {
|
|
56
|
-
const dm = makeDataModule();
|
|
57
|
-
const onHeartbeat = vi.fn();
|
|
58
|
-
dm.onHeartbeat = onHeartbeat;
|
|
59
|
-
const watched = makeQueryState('watched');
|
|
60
|
-
const idle = makeQueryState('idle');
|
|
61
|
-
(dm as any).activeQueries.set('watched', watched);
|
|
62
|
-
(dm as any).activeQueries.set('idle', idle);
|
|
63
|
-
(dm as any).subscriptions.set('watched', new Set([() => {}]));
|
|
64
|
-
(dm as any).startTTLHeartbeat(watched, 'watched');
|
|
65
|
-
(dm as any).startTTLHeartbeat(idle, 'idle');
|
|
66
|
-
return { dm, onHeartbeat, watched, idle };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
describe('DataModule heartbeat on wake', () => {
|
|
70
|
-
beforeEach(() => vi.useFakeTimers());
|
|
71
|
-
afterEach(() => vi.useRealTimers());
|
|
72
|
-
|
|
73
|
-
it('beats every watched query now and re-anchors its timer', async () => {
|
|
74
|
-
const { dm, onHeartbeat, watched } = setup();
|
|
75
|
-
await vi.advanceTimersByTimeAsync(200_000);
|
|
76
|
-
|
|
77
|
-
expect(dm.heartbeatOnWake()).toBe(1);
|
|
78
|
-
expect(onHeartbeat).toHaveBeenCalledTimes(1);
|
|
79
|
-
expect(onHeartbeat).toHaveBeenCalledWith('watched');
|
|
80
|
-
expect(watched.ttlTimer).not.toBeNull();
|
|
81
|
-
|
|
82
|
-
// The regular beat would have been due at 300s; it is now due 300s after
|
|
83
|
-
// the wake beat instead.
|
|
84
|
-
await vi.advanceTimersByTimeAsync(TTL_MS / 2 - 1_000);
|
|
85
|
-
expect(onHeartbeat).toHaveBeenCalledTimes(1);
|
|
86
|
-
await vi.advanceTimersByTimeAsync(2_000);
|
|
87
|
-
expect(onHeartbeat).toHaveBeenCalledTimes(2);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('skips queries nobody is watching', () => {
|
|
91
|
-
const { dm, onHeartbeat } = setup();
|
|
92
|
-
dm.heartbeatOnWake();
|
|
93
|
-
expect(onHeartbeat).not.toHaveBeenCalledWith('idle');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('does not beat the same query twice within the gap', async () => {
|
|
97
|
-
const { dm, onHeartbeat } = setup();
|
|
98
|
-
expect(dm.heartbeatOnWake()).toBe(1);
|
|
99
|
-
expect(dm.heartbeatOnWake()).toBe(0);
|
|
100
|
-
await vi.advanceTimersByTimeAsync(31_000);
|
|
101
|
-
expect(dm.heartbeatOnWake()).toBe(1);
|
|
102
|
-
expect(onHeartbeat).toHaveBeenCalledTimes(2);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('never expires local query state on its own', async () => {
|
|
106
|
-
const { dm, watched, idle } = setup();
|
|
107
|
-
// Well past the TTL, with no server in the picture.
|
|
108
|
-
await vi.advanceTimersByTimeAsync(TTL_MS * 3);
|
|
109
|
-
expect((dm as any).activeQueries.get('watched')).toBe(watched);
|
|
110
|
-
expect((dm as any).activeQueries.get('idle')).toBe(idle);
|
|
111
|
-
expect(watched.records).toEqual([{ id: 'user:a' }]);
|
|
112
|
-
expect(watched.config.remoteArray).toEqual([['user:a', 1]]);
|
|
113
|
-
expect(watched.config.membershipKnown).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { RecordId } from 'surrealdb';
|
|
3
|
-
import { DataModule } from './index';
|
|
4
|
-
import type { QueryState } from '../../types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Tests for instant-hydrate's DataModule half: `applyHydration` — run-once,
|
|
8
|
-
* remoteArray priming, subscriber notify, and the epoch guard added when the
|
|
9
|
-
* hydrate fetch moved off the paint path into a background chain.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
function makeLogger(): any {
|
|
13
|
-
const noop = () => {};
|
|
14
|
-
const logger: any = { debug: noop, info: noop, warn: noop, error: noop, trace: noop };
|
|
15
|
-
logger.child = () => logger;
|
|
16
|
-
return logger;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function makeQueryState(hash: string): QueryState {
|
|
20
|
-
return {
|
|
21
|
-
config: {
|
|
22
|
-
id: new RecordId('_00_query', hash),
|
|
23
|
-
surql: 'SELECT * FROM user',
|
|
24
|
-
params: {},
|
|
25
|
-
localArray: [],
|
|
26
|
-
remoteArray: [],
|
|
27
|
-
ttl: '10m',
|
|
28
|
-
lastActiveAt: new Date(),
|
|
29
|
-
tableName: 'user',
|
|
30
|
-
},
|
|
31
|
-
records: [],
|
|
32
|
-
ttlTimer: null,
|
|
33
|
-
ttlDurationMs: 0,
|
|
34
|
-
updateCount: 0,
|
|
35
|
-
lastUpdatedAt: null,
|
|
36
|
-
materializationSamples: [],
|
|
37
|
-
lastIngestLatencyMs: null,
|
|
38
|
-
errorCount: 0,
|
|
39
|
-
status: 'fetching',
|
|
40
|
-
phaseSamples: {},
|
|
41
|
-
phaseLast: {},
|
|
42
|
-
registrationTimings: { parseMs: null, planMs: null, snapshotMs: null, wallMs: null },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const schema = { tables: [{ name: 'user', columns: {} }] } as any;
|
|
47
|
-
|
|
48
|
-
function makeRow(id: string, rv = 1) {
|
|
49
|
-
return { id: new RecordId('user', id), name: id, _00_rv: rv };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
describe('DataModule.applyHydration', () => {
|
|
53
|
-
const hash = 'h1';
|
|
54
|
-
|
|
55
|
-
function setup({ epochFlipsOnSave = false } = {}) {
|
|
56
|
-
let epoch = 1;
|
|
57
|
-
const saved: any[] = [];
|
|
58
|
-
const cache: any = {
|
|
59
|
-
saveBatch: async (batch: any[]) => {
|
|
60
|
-
saved.push(...batch);
|
|
61
|
-
if (epochFlipsOnSave) epoch = 2;
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
const local: any = {
|
|
65
|
-
get epoch() {
|
|
66
|
-
return epoch;
|
|
67
|
-
},
|
|
68
|
-
query: async () => [[makeRow('a')]],
|
|
69
|
-
};
|
|
70
|
-
const dm = new DataModule(cache, local, schema, makeLogger(), 100);
|
|
71
|
-
const state = makeQueryState(hash);
|
|
72
|
-
(dm as any).activeQueries.set(hash, state);
|
|
73
|
-
return { dm, state, saved };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
it('primes remoteArray, materializes and notifies subscribers', async () => {
|
|
77
|
-
const { dm, state, saved } = setup();
|
|
78
|
-
const emissions: any[] = [];
|
|
79
|
-
dm.subscribe(hash, (records) => emissions.push(records));
|
|
80
|
-
|
|
81
|
-
await dm.applyHydration(hash, [makeRow('a', 3)]);
|
|
82
|
-
|
|
83
|
-
expect(state.hydrated).toBe(true);
|
|
84
|
-
expect(state.config.remoteArray).toEqual([['user:a', 3]]);
|
|
85
|
-
expect(saved.length).toBe(1);
|
|
86
|
-
expect(emissions.length).toBe(1);
|
|
87
|
-
expect(dm.isCold(hash)).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('runs once even when the remote returns nothing (hydrated flag)', async () => {
|
|
91
|
-
const { dm, state, saved } = setup();
|
|
92
|
-
await dm.applyHydration(hash, []);
|
|
93
|
-
expect(state.hydrated).toBe(true);
|
|
94
|
-
expect(saved).toEqual([]);
|
|
95
|
-
expect(dm.isCold(hash)).toBe(false); // hydrated → no longer cold
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('is a no-op for an unknown query', async () => {
|
|
99
|
-
const { dm, saved } = setup();
|
|
100
|
-
await dm.applyHydration('nope', [makeRow('a')]);
|
|
101
|
-
expect(saved).toEqual([]);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('bails before mutating state when the bucket epoch moves mid-persist', async () => {
|
|
105
|
-
const { dm, state } = setup({ epochFlipsOnSave: true });
|
|
106
|
-
const emissions: any[] = [];
|
|
107
|
-
dm.subscribe(hash, (records) => emissions.push(records));
|
|
108
|
-
|
|
109
|
-
await dm.applyHydration(hash, [makeRow('a', 3)]);
|
|
110
|
-
|
|
111
|
-
// Rows fetched under the previous auth context must not prime the new
|
|
112
|
-
// bucket's query state — the rebind's re-registration refills it.
|
|
113
|
-
expect(state.config.remoteArray).toEqual([]);
|
|
114
|
-
expect(state.records).toEqual([]);
|
|
115
|
-
expect(emissions).toEqual([]);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('DataModule.getPreloadMarker', () => {
|
|
120
|
-
function makeDm(getById: (table: string, id: unknown) => Promise<any>) {
|
|
121
|
-
const local: any = { getById };
|
|
122
|
-
return new DataModule({} as any, local, schema, makeLogger(), 100);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
it('returns the marker fields for a real row', async () => {
|
|
126
|
-
const dm = makeDm(async () => ({ fetchedAt: 123, rowCount: 5 }));
|
|
127
|
-
expect(await dm.getPreloadMarker('h')).toEqual({ fetchedAt: 123, rowCount: 5 });
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('returns null on a miss and on a non-object echo (SurrealDB string quirk)', async () => {
|
|
131
|
-
expect(await makeDm(async () => null).getPreloadMarker('h')).toBeNull();
|
|
132
|
-
// `FROM ONLY <string>` used to echo the id string back — must not read as warm.
|
|
133
|
-
expect(await makeDm(async () => 'h' as any).getPreloadMarker('h')).toBeNull();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('returns null when the read throws', async () => {
|
|
137
|
-
const dm = makeDm(async () => {
|
|
138
|
-
throw new Error('boom');
|
|
139
|
-
});
|
|
140
|
-
expect(await dm.getPreloadMarker('h')).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
});
|