@spooky-sync/core 0.0.1-canary.340 → 0.0.1-canary.341
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -4
- package/package.json +3 -3
- package/src/boot/preload.saga.test.ts +18 -2
- package/src/boot/preload.saga.ts +6 -1
- package/src/client/services.ts +1 -0
- package/src/kernel/effects.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -1236,6 +1236,8 @@ interface ServiceCalls {
|
|
|
1236
1236
|
'auth.currentUser': () => Record<string, unknown> | null;
|
|
1237
1237
|
'auth.consumeEndedImpersonation': () => string | null;
|
|
1238
1238
|
'remote.connect': () => Promise<void>;
|
|
1239
|
+
/** The platform reports no network at all (a browser's `navigator.onLine === false`). */
|
|
1240
|
+
'net.offline': () => boolean;
|
|
1239
1241
|
'remote.releaseViews': (ids: unknown[]) => void;
|
|
1240
1242
|
'supervisor.start': () => void;
|
|
1241
1243
|
'tabs.start': (bucketId: string) => Promise<'solo' | 'leader' | 'follower'>;
|
package/dist/index.js
CHANGED
|
@@ -2286,8 +2286,8 @@ function trimHistory(history, cap) {
|
|
|
2286
2286
|
|
|
2287
2287
|
//#endregion
|
|
2288
2288
|
//#region src/modules/devtools/index.ts
|
|
2289
|
-
const CORE_VERSION = "0.0.1-canary.
|
|
2290
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
2289
|
+
const CORE_VERSION = "0.0.1-canary.341";
|
|
2290
|
+
const WASM_VERSION = "0.0.1-canary.341";
|
|
2291
2291
|
const SURREAL_VERSION = "3.1.0";
|
|
2292
2292
|
var DevToolsService = class DevToolsService {
|
|
2293
2293
|
version = CORE_VERSION;
|
|
@@ -6327,7 +6327,11 @@ var PreloadFailedError = class extends Error {
|
|
|
6327
6327
|
* A session that cannot be sent membership (signed out, without anonymous
|
|
6328
6328
|
* live queries) does not wait either: a view with rows would never settle,
|
|
6329
6329
|
* and an app that gates its first paint on a preload showed its loading
|
|
6330
|
-
* screen forever, signed-out landing page included.
|
|
6330
|
+
* screen forever, signed-out landing page included. Nor does an offline
|
|
6331
|
+
* device: the membership it waits for cannot arrive before the network does,
|
|
6332
|
+
* and an app reloaded offline before its preload ever settled stayed on its
|
|
6333
|
+
* loading screen. It paints from what is local instead, and the query fills in
|
|
6334
|
+
* once the registration lands.
|
|
6331
6335
|
*/
|
|
6332
6336
|
function* preload(env, input) {
|
|
6333
6337
|
const hash = yield* registerLocal(env, input);
|
|
@@ -6336,6 +6340,10 @@ function* preload(env, input) {
|
|
|
6336
6340
|
hash,
|
|
6337
6341
|
waited: false
|
|
6338
6342
|
};
|
|
6343
|
+
if (yield fx.service("net.offline")) return {
|
|
6344
|
+
hash,
|
|
6345
|
+
waited: false
|
|
6346
|
+
};
|
|
6339
6347
|
yield fx.state.wait((s) => settled(s, hash) || settleFailed(s, hash));
|
|
6340
6348
|
if (yield fx.state.read((s) => settleFailed(s, hash))) throw new PreloadFailedError(hash);
|
|
6341
6349
|
return {
|
|
@@ -15889,7 +15897,7 @@ function createTabsCoordinator(config, s, host) {
|
|
|
15889
15897
|
return new TabsCoordinator({
|
|
15890
15898
|
tabId: s.tabId,
|
|
15891
15899
|
fingerprint: computeTabsFingerprint({
|
|
15892
|
-
coreVersion: "0.0.1-canary.
|
|
15900
|
+
coreVersion: "0.0.1-canary.341",
|
|
15893
15901
|
schemaHash: hash53(config.schemaSurql),
|
|
15894
15902
|
endpoint: config.database.endpoint ?? "",
|
|
15895
15903
|
namespace: config.database.namespace,
|
|
@@ -16054,6 +16062,7 @@ function createAdapters(config, s, host, lateModules) {
|
|
|
16054
16062
|
if (sql) s.remote.beaconSql(sql);
|
|
16055
16063
|
},
|
|
16056
16064
|
"supervisor.start": () => s.connectionSupervisor.start(),
|
|
16065
|
+
"net.offline": () => typeof navigator !== "undefined" && navigator.onLine === false,
|
|
16057
16066
|
"tabs.start": (bucketId) => {
|
|
16058
16067
|
if (!s.tabs) throw new Error("shared tabs unavailable");
|
|
16059
16068
|
return s.tabs.start(bucketId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/core",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.341",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@spooky-sync/query-builder": "0.0.1-canary.
|
|
79
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
78
|
+
"@spooky-sync/query-builder": "0.0.1-canary.341",
|
|
79
|
+
"@spooky-sync/ssp-wasm": "0.0.1-canary.341",
|
|
80
80
|
"@sqlite.org/sqlite-wasm": "3.53.0-build1",
|
|
81
81
|
"@surrealdb/wasm": "^3.1.0",
|
|
82
82
|
"@surrealdb/wasm-native": "^3.3.4",
|
|
@@ -9,6 +9,11 @@ import { preload, PreloadFailedError } from './preload.saga';
|
|
|
9
9
|
const env = defaultEnv({ tables: [{ name: 'thing', columns: {} }] } as any);
|
|
10
10
|
const input = { tableName: 'thing', surql: 'SELECT * FROM thing', params: {}, ttl: '10m' as const };
|
|
11
11
|
const sspOk = () => ({ localArray: [], timings: { parseMs: 0, planMs: 0, snapshotMs: 0, wallMs: 0 } });
|
|
12
|
+
/** The `service` handler for an online device: `net.offline` answers false. */
|
|
13
|
+
const online = (offline = false) => (e: any) => {
|
|
14
|
+
if (e.name === 'net.offline') return offline;
|
|
15
|
+
throw new Error(`unexpected service ${e.name}`);
|
|
16
|
+
};
|
|
12
17
|
|
|
13
18
|
describe('preload', () => {
|
|
14
19
|
it('resolved before: returns without waiting and with no remote effect', async () => {
|
|
@@ -31,7 +36,7 @@ describe('preload', () => {
|
|
|
31
36
|
};
|
|
32
37
|
const out = await runPure(preload(env, input), {
|
|
33
38
|
state: signedIn,
|
|
34
|
-
handlers: { 'local.getById': () => null, 'ssp.register': sspOk, 'state.wait': (_e, ctx) => settleIt(ctx) },
|
|
39
|
+
handlers: { 'local.getById': () => null, 'ssp.register': sspOk, service: online(), 'state.wait': (_e, ctx) => settleIt(ctx) },
|
|
35
40
|
});
|
|
36
41
|
expect(out.result).toEqual({ hash, waited: true });
|
|
37
42
|
const failed = runPure(preload(env, input), {
|
|
@@ -39,13 +44,14 @@ describe('preload', () => {
|
|
|
39
44
|
handlers: {
|
|
40
45
|
'local.getById': () => null,
|
|
41
46
|
'ssp.register': sspOk,
|
|
47
|
+
service: online(),
|
|
42
48
|
'state.wait': (_e, ctx) => void (ctx.state = R.applyLifecycle(hash, { type: 'remote-failed' })(ctx.state)),
|
|
43
49
|
},
|
|
44
50
|
});
|
|
45
51
|
await expect(failed).rejects.toBeInstanceOf(PreloadFailedError);
|
|
46
52
|
const gone = runPure(preload(env, input), {
|
|
47
53
|
state: signedIn,
|
|
48
|
-
handlers: { 'local.getById': () => null, 'ssp.register': sspOk, 'state.wait': (_e, ctx) => void (ctx.state = R.removeQuery(hash)(ctx.state)) },
|
|
54
|
+
handlers: { 'local.getById': () => null, 'ssp.register': sspOk, service: online(), 'state.wait': (_e, ctx) => void (ctx.state = R.removeQuery(hash)(ctx.state)) },
|
|
49
55
|
});
|
|
50
56
|
await expect(gone).rejects.toThrow(/could not settle/);
|
|
51
57
|
const already = buildState([buildEntry({ def: { hash }, lifecycle: { phase: 'cached' } })]);
|
|
@@ -67,6 +73,7 @@ describe('preload', () => {
|
|
|
67
73
|
handlers: {
|
|
68
74
|
'local.getById': () => null,
|
|
69
75
|
'ssp.register': sspOk,
|
|
76
|
+
service: online(),
|
|
70
77
|
'state.wait': (_e, ctx) =>
|
|
71
78
|
void (ctx.state = R.compose(
|
|
72
79
|
R.commitMembership(hash, [['thing:1', 1]], true),
|
|
@@ -77,4 +84,13 @@ describe('preload', () => {
|
|
|
77
84
|
});
|
|
78
85
|
expect(anon.result).toEqual({ hash, waited: true });
|
|
79
86
|
});
|
|
87
|
+
it('offline: registers but never waits, so a reload without a network paints from what is local', async () => {
|
|
88
|
+
const signedIn = buildState([], R.setIdentity({ userId: 'user:alice' }));
|
|
89
|
+
const out = await runPure(preload(env, input), {
|
|
90
|
+
state: signedIn,
|
|
91
|
+
handlers: { 'local.getById': () => null, 'ssp.register': sspOk, service: online(true) },
|
|
92
|
+
});
|
|
93
|
+
expect(out.result.waited).toBe(false);
|
|
94
|
+
expect(out.log.filter((e) => e.kind === 'state.wait')).toHaveLength(0);
|
|
95
|
+
});
|
|
80
96
|
});
|
package/src/boot/preload.saga.ts
CHANGED
|
@@ -24,7 +24,11 @@ export class PreloadFailedError extends Error {
|
|
|
24
24
|
* A session that cannot be sent membership (signed out, without anonymous
|
|
25
25
|
* live queries) does not wait either: a view with rows would never settle,
|
|
26
26
|
* and an app that gates its first paint on a preload showed its loading
|
|
27
|
-
* screen forever, signed-out landing page included.
|
|
27
|
+
* screen forever, signed-out landing page included. Nor does an offline
|
|
28
|
+
* device: the membership it waits for cannot arrive before the network does,
|
|
29
|
+
* and an app reloaded offline before its preload ever settled stayed on its
|
|
30
|
+
* loading screen. It paints from what is local instead, and the query fills in
|
|
31
|
+
* once the registration lands.
|
|
28
32
|
*/
|
|
29
33
|
export function* preload(env: SagaEnv, input: RegisterInput): Saga<{ hash: QueryHash; waited: boolean }> {
|
|
30
34
|
const hash = yield* registerLocal(env, input);
|
|
@@ -33,6 +37,7 @@ export function* preload(env: SagaEnv, input: RegisterInput): Saga<{ hash: Query
|
|
|
33
37
|
boolean,
|
|
34
38
|
];
|
|
35
39
|
if (phase !== 'cold' || !reachable) return { hash, waited: false };
|
|
40
|
+
if ((yield fx.service('net.offline')) as boolean) return { hash, waited: false };
|
|
36
41
|
yield fx.state.wait((s) => settled(s, hash) || settleFailed(s, hash));
|
|
37
42
|
const failed = (yield fx.state.read((s) => settleFailed(s, hash))) as boolean;
|
|
38
43
|
if (failed) throw new PreloadFailedError(hash);
|
package/src/client/services.ts
CHANGED
|
@@ -331,6 +331,7 @@ export function createAdapters<S extends SchemaStructure>(config: Sp00kyConfig<S
|
|
|
331
331
|
if (sql) s.remote.beaconSql(sql);
|
|
332
332
|
},
|
|
333
333
|
'supervisor.start': () => s.connectionSupervisor.start(),
|
|
334
|
+
'net.offline': () => typeof navigator !== 'undefined' && navigator.onLine === false,
|
|
334
335
|
'tabs.start': (bucketId) => {
|
|
335
336
|
if (!s.tabs) throw new Error('shared tabs unavailable');
|
|
336
337
|
return s.tabs.start(bucketId);
|
package/src/kernel/effects.ts
CHANGED
|
@@ -100,6 +100,8 @@ export interface ServiceCalls {
|
|
|
100
100
|
'auth.currentUser': () => Record<string, unknown> | null;
|
|
101
101
|
'auth.consumeEndedImpersonation': () => string | null;
|
|
102
102
|
'remote.connect': () => Promise<void>;
|
|
103
|
+
/** The platform reports no network at all (a browser's `navigator.onLine === false`). */
|
|
104
|
+
'net.offline': () => boolean;
|
|
103
105
|
'remote.releaseViews': (ids: unknown[]) => void;
|
|
104
106
|
'supervisor.start': () => void;
|
|
105
107
|
'tabs.start': (bucketId: string) => Promise<'solo' | 'leader' | 'follower'>;
|