@spooky-sync/core 0.0.1-canary.325 → 0.0.1-canary.326

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2188,8 +2188,8 @@ function trimHistory(history, cap) {
2188
2188
 
2189
2189
  //#endregion
2190
2190
  //#region src/modules/devtools/index.ts
2191
- const CORE_VERSION = "0.0.1-canary.325";
2192
- const WASM_VERSION = "0.0.1-canary.325";
2191
+ const CORE_VERSION = "0.0.1-canary.326";
2192
+ const WASM_VERSION = "0.0.1-canary.326";
2193
2193
  const SURREAL_VERSION = "3.0.3";
2194
2194
  var DevToolsService = class DevToolsService {
2195
2195
  version = CORE_VERSION;
@@ -3548,6 +3548,22 @@ const compose = (...reducers) => (s) => reducers.reduce((acc, r) => r(acc), s);
3548
3548
  //#endregion
3549
3549
  //#region src/state/selectors.ts
3550
3550
  const activeHashes = (s) => [...s.queries.keys()];
3551
+ /**
3552
+ * The status subscribers see. `fetching` while a fetch cycle is in flight,
3553
+ * and also while a live query is not yet `settled`: the server's membership
3554
+ * is in but its rows are still downloading or not yet shown. Registration
3555
+ * ends its cycle as soon as membership is applied and the body fetch starts
3556
+ * its own a moment later, so the fetch count alone read `idle` in between,
3557
+ * and a binding that settles on "membership known and idle" told the app a
3558
+ * row did not exist while it was on its way (a by-id screen showed "not
3559
+ * found" for a moment, or for as long as the bodies took).
3560
+ */
3561
+ const queryStatus = (s, hash) => {
3562
+ const e = s.queries.get(hash);
3563
+ if (!e) return void 0;
3564
+ if (deriveStatus(e.lifecycle) === "fetching") return "fetching";
3565
+ return e.lifecycle.phase === "live" && !settled(s, hash) ? "fetching" : "idle";
3566
+ };
3551
3567
  /** The optimistic overlay, derived from the outbox (pending AND acked items). */
3552
3568
  function overlay(s) {
3553
3569
  const writes = /* @__PURE__ */ new Set();
@@ -7396,9 +7412,8 @@ var Runtime = class {
7396
7412
  });
7397
7413
  }
7398
7414
  for (const [hash, subs] of this.statusSubs) {
7399
- const entry = next.queries.get(hash);
7400
- if (!entry) continue;
7401
- const status = deriveStatus(entry.lifecycle);
7415
+ if (!next.queries.get(hash)) continue;
7416
+ const status = queryStatus(next, hash) ?? "idle";
7402
7417
  if (this.lastStatus.get(hash) === status) continue;
7403
7418
  this.lastStatus.set(hash, status);
7404
7419
  for (const cb of subs) this.safely(() => cb(status));
@@ -7502,9 +7517,8 @@ var Runtime = class {
7502
7517
  const set = this.statusSubs.get(hash) ?? /* @__PURE__ */ new Set();
7503
7518
  set.add(cb);
7504
7519
  this.statusSubs.set(hash, set);
7505
- const entry = this.stateValue.queries.get(hash);
7506
- if (entry) {
7507
- const status = deriveStatus(entry.lifecycle);
7520
+ if (this.stateValue.queries.get(hash)) {
7521
+ const status = queryStatus(this.stateValue, hash) ?? "idle";
7508
7522
  this.lastStatus.set(hash, status);
7509
7523
  if (options.immediate) this.safely(() => cb(status));
7510
7524
  }
@@ -15261,7 +15275,7 @@ function createTabsCoordinator(config, s, host) {
15261
15275
  return new TabsCoordinator({
15262
15276
  tabId: s.tabId,
15263
15277
  fingerprint: computeTabsFingerprint({
15264
- coreVersion: "0.0.1-canary.325",
15278
+ coreVersion: "0.0.1-canary.326",
15265
15279
  schemaHash: hash53(config.schemaSurql),
15266
15280
  endpoint: config.database.endpoint ?? "",
15267
15281
  namespace: config.database.namespace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/core",
3
- "version": "0.0.1-canary.325",
3
+ "version": "0.0.1-canary.326",
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.325",
79
- "@spooky-sync/ssp-wasm": "0.0.1-canary.325",
78
+ "@spooky-sync/query-builder": "0.0.1-canary.326",
79
+ "@spooky-sync/ssp-wasm": "0.0.1-canary.326",
80
80
  "@sqlite.org/sqlite-wasm": "3.53.0-build1",
81
81
  "@surrealdb/wasm": "^3.0.3",
82
82
  "blurhash": "^2.0.5",
@@ -7,9 +7,9 @@ import type { Lane, Saga } from '../kernel/saga';
7
7
  import { runSaga } from '../kernel/saga';
8
8
  import type { ClientState } from '../state/client-state';
9
9
  import { emptyState } from '../state/client-state';
10
- import { deriveStatus, isAuthoritative } from '../state/lifecycle';
10
+ import { isAuthoritative } from '../state/lifecycle';
11
11
  import * as R from '../state/reducers';
12
- import { fetchingQueryCount, pendingMutationCount } from '../state/selectors';
12
+ import { fetchingQueryCount, pendingMutationCount, queryStatus } from '../state/selectors';
13
13
  import type { SagaEnv } from '../query/env';
14
14
  import { route } from './router';
15
15
 
@@ -152,7 +152,7 @@ export class Runtime {
152
152
  for (const [hash, subs] of this.statusSubs) {
153
153
  const entry = next.queries.get(hash);
154
154
  if (!entry) continue;
155
- const status = deriveStatus(entry.lifecycle);
155
+ const status = queryStatus(next, hash) ?? 'idle';
156
156
  if (this.lastStatus.get(hash) === status) continue;
157
157
  this.lastStatus.set(hash, status);
158
158
  for (const cb of subs) this.safely(() => cb(status));
@@ -255,7 +255,7 @@ export class Runtime {
255
255
  this.statusSubs.set(hash, set);
256
256
  const entry = this.stateValue.queries.get(hash);
257
257
  if (entry) {
258
- const status = deriveStatus(entry.lifecycle);
258
+ const status = queryStatus(this.stateValue, hash) ?? 'idle';
259
259
  this.lastStatus.set(hash, status);
260
260
  if (options.immediate) this.safely(() => cb(status));
261
261
  }
@@ -77,6 +77,19 @@ describe('needed / planFetch / settled', () => {
77
77
  expect(S.settleFailed(s, 'a')).toBe(false);
78
78
  expect(S.settleFailed(R.applyLifecycle('a', { type: 'remote-failed' })(s), 'a')).toBe(true);
79
79
  });
80
+ it('queryStatus stays fetching until a live query is settled', () => {
81
+ const base = e('a', { lifecycle: { ...live, notified: true }, remoteArray: [['thing:1', 1]] });
82
+ const s = { ...buildState([base], R.setVersions([['thing:1', 1]])), dirty: new Set<string>() };
83
+ expect(S.queryStatus(s, 'a')).toBe('idle');
84
+ // Membership in, body still downloading (registration already ended its cycle).
85
+ expect(S.queryStatus(R.setVersions([['thing:1', 0]])(s), 'a')).toBe('fetching');
86
+ // Body landed, not yet shown to subscribers.
87
+ expect(S.queryStatus(R.markDirty(['a'])(s), 'a')).toBe('fetching');
88
+ // A cold query with no fetch in flight has nothing to wait for.
89
+ expect(S.queryStatus(buildState([e('c', { lifecycle: { phase: 'cold' } })]), 'c')).toBe('idle');
90
+ expect(S.queryStatus(R.applyLifecycle('a', { type: 'fetch-begin' })(s), 'a')).toBe('fetching');
91
+ expect(S.queryStatus(s, 'missing')).toBeUndefined();
92
+ });
80
93
  it('fetchingQueryCount counts entries with fetch depth', () => {
81
94
  const s = buildState([e('a', { lifecycle: { fetchDepth: 2 } }), e('b')]);
82
95
  expect(S.fetchingQueryCount(s)).toBe(1);
@@ -12,9 +12,21 @@ export const activeHashes = (s: ClientState): QueryHash[] => [...s.queries.keys(
12
12
  export const hashesForTable = (s: ClientState, table: string): QueryHash[] =>
13
13
  [...s.queries].filter(([, e]) => e.def.tableName === table).map(([h]) => h);
14
14
 
15
+ /**
16
+ * The status subscribers see. `fetching` while a fetch cycle is in flight,
17
+ * and also while a live query is not yet `settled`: the server's membership
18
+ * is in but its rows are still downloading or not yet shown. Registration
19
+ * ends its cycle as soon as membership is applied and the body fetch starts
20
+ * its own a moment later, so the fetch count alone read `idle` in between,
21
+ * and a binding that settles on "membership known and idle" told the app a
22
+ * row did not exist while it was on its way (a by-id screen showed "not
23
+ * found" for a moment, or for as long as the bodies took).
24
+ */
15
25
  export const queryStatus = (s: ClientState, hash: QueryHash): QueryStatus | undefined => {
16
26
  const e = s.queries.get(hash);
17
- return e ? deriveStatus(e.lifecycle) : undefined;
27
+ if (!e) return undefined;
28
+ if (deriveStatus(e.lifecycle) === 'fetching') return 'fetching';
29
+ return e.lifecycle.phase === 'live' && !settled(s, hash) ? 'fetching' : 'idle';
18
30
  };
19
31
 
20
32
  export interface Overlay {