@spooky-sync/core 0.0.1-canary.202 → 0.0.1-canary.204

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 CHANGED
@@ -696,6 +696,13 @@ declare class DataModule<S extends SchemaStructure> {
696
696
  private resolveMembership;
697
697
  private readonly settledWrites;
698
698
  private readonly settledDeletes;
699
+ private pendingIds;
700
+ private pendingIdsAt;
701
+ private pendingIdsInflight;
702
+ private static readonly PENDING_IDS_TTL_MS;
703
+ /** Drop the cached outbox ids. Cheap; call it on anything that could change
704
+ * `_00_pending_mutations`. */
705
+ private invalidatePendingIds;
699
706
  /**
700
707
  * Grace period for a settled write. Long enough to cover an SSP round trip
701
708
  * that is running slowly (seconds, not milliseconds, when the edge path is
@@ -841,6 +848,9 @@ declare class DataModule<S extends SchemaStructure> {
841
848
  writes: Set<string>;
842
849
  deletes: Set<string>;
843
850
  }>;
851
+ /** The uncached read. Also the reload path after an invalidation, so the ids
852
+ * still survive a reload exactly as before. */
853
+ private readPendingRecordIds;
844
854
  /** True while ≥1 live subscriber is watching this query (refcount guard). */
845
855
  hasSubscribers(hash: string): boolean;
846
856
  /**
@@ -1079,6 +1089,15 @@ interface Sp00kySyncOptions {
1079
1089
  * up-queue for the session. Defaults to 30000; `0` disables the timeout.
1080
1090
  */
1081
1091
  pushTimeoutMs?: number;
1092
+ /**
1093
+ * Max time a single down event (`register`/`sync`/`cleanup`) may take before
1094
+ * it is treated as a network failure and retried. The mirror of
1095
+ * {@link pushTimeoutMs} for the read side, which had no such guard: a
1096
+ * `fn::query::register` that never settled held its slot in the down drain,
1097
+ * and every later registration behind it, for the rest of the session.
1098
+ * Defaults to 30000; `0` disables the timeout.
1099
+ */
1100
+ downTimeoutMs?: number;
1082
1101
  /**
1083
1102
  * Transport supervisor. Sync reads its state to report `connection` in
1084
1103
  * {@link SyncHealth} so a UI can show "reconnecting…" the instant the socket
@@ -1148,6 +1167,7 @@ declare class Sp00kySync<S extends SchemaStructure> {
1148
1167
  private readonly degradeAfterFailures;
1149
1168
  /** Per-push RPC deadline; see {@link withPushTimeout}. */
1150
1169
  private readonly pushTimeoutMs;
1170
+ private readonly downTimeoutMs;
1151
1171
  private consecutiveSyncFailures;
1152
1172
  private syncHealthStatus;
1153
1173
  private lastSyncErrorKind;
@@ -1378,6 +1398,8 @@ declare class Sp00kySync<S extends SchemaStructure> {
1378
1398
  private handleMutationSettled;
1379
1399
  private handleRollback;
1380
1400
  private processDownEvent;
1401
+ private withDownTimeout;
1402
+ private runDownEvent;
1381
1403
  /**
1382
1404
  * Synchronizes a specific query by hash.
1383
1405
  * Compares local and remote version arrays and fetches differences.
@@ -1471,6 +1493,24 @@ declare class AuthService<S extends SchemaStructure> {
1471
1493
  */
1472
1494
  subscribe(cb: (userId: string | null) => void): () => void;
1473
1495
  private notifyListeners;
1496
+ /**
1497
+ * Restore a session from the locally cached JWT, with NO network.
1498
+ *
1499
+ * This is what makes a warm boot paint instantly and what makes an offline
1500
+ * boot possible at all: the token is in local storage, and it already carries
1501
+ * both the access method and the `$auth.id` record id. Everything the client
1502
+ * needs to route queries (`setCurrentUserId`) and to satisfy `$auth`-gated
1503
+ * permission predicates in the in-browser SSP (`setSessionAuth`) is therefore
1504
+ * available before a socket exists.
1505
+ *
1506
+ * The session is OPTIMISTIC: the token is unverified here. `check()` runs
1507
+ * afterwards in the background and downgrades to a real sign-out if the
1508
+ * server rejects it. Nothing is trusted that the server has not also seen -
1509
+ * the local store only ever holds rows the server previously sent.
1510
+ *
1511
+ * Returns the restored user id, or null when there is no usable token.
1512
+ */
1513
+ restoreSessionFromToken(): Promise<string | null>;
1474
1514
  /**
1475
1515
  * Check for existing session and validate
1476
1516
  */
@@ -2166,6 +2206,15 @@ declare class Sp00kyClient<S extends SchemaStructure> {
2166
2206
  private sync;
2167
2207
  private devTools;
2168
2208
  private crdtManager;
2209
+ /**
2210
+ * True once the LOCAL half of boot is done and the client can serve reads
2211
+ * from the local store. Distinct from being connected: `syncHealth` covers
2212
+ * reaching the server and `storageHealth` covers whether the local store is
2213
+ * durable, but neither says "usable". Consumers gate their first paint on
2214
+ * this, which is what makes a warm boot instant and an offline boot possible.
2215
+ */
2216
+ private localReady;
2217
+ private saltUserId;
2169
2218
  private featureFlags;
2170
2219
  private appReleases;
2171
2220
  private preloadedHashes;
@@ -2211,6 +2260,16 @@ declare class Sp00kyClient<S extends SchemaStructure> {
2211
2260
  */
2212
2261
  private setupCallbacks;
2213
2262
  init(): Promise<void>;
2263
+ /**
2264
+ * The network half of boot: connect, verify the restored session, and let the
2265
+ * sync engine catch up. Runs in the background after `init()` has already
2266
+ * resolved, so nothing here is on the paint path.
2267
+ *
2268
+ * Every step is best-effort. A failure leaves the client in exactly the state
2269
+ * a warm offline boot is in - local reads working, writes queued in the
2270
+ * outbox - and the connection supervisor keeps retrying underneath.
2271
+ */
2272
+ private initRemote;
2214
2273
  private bucketSwitchChain;
2215
2274
  private pendingBucketTarget;
2216
2275
  /**
@@ -2363,14 +2422,38 @@ declare class Sp00kyClient<S extends SchemaStructure> {
2363
2422
  [x: string]: /*elided*/any;
2364
2423
  }>;
2365
2424
  delete(table: string, id: string): Promise<void>;
2366
- useRemote<T>(fn: (client: Surreal) => Promise<T> | T): Promise<T>;
2367
2425
  /**
2368
- * Fetch SurrealDB's `session::id()` as a string. Used as a salt for
2369
- * query-id hashing so two sessions for the same user get distinct
2370
- * `_00_query` rows. Returns empty string if the query fails (we still
2371
- * boot, just without session scoping for IDs).
2426
+ * Whether the local store is initialized and reads can be served. See the
2427
+ * `localReady` field: this is deliberately independent of connectivity.
2372
2428
  */
2373
- private fetchSessionId;
2429
+ isLocalReady(): boolean;
2430
+ useRemote<T>(fn: (client: Surreal) => Promise<T> | T): Promise<T>;
2431
+ /**
2432
+ * Mint the salt used for query-id hashing, so two sessions registering the
2433
+ * same logical query get distinct `_00_query` rows.
2434
+ *
2435
+ * Generated LOCALLY, deliberately. This used to be `RETURN <string>session::id()`,
2436
+ * which cost a serial round trip on the critical boot path and resolved to
2437
+ * `''` offline. The value never needed to come from the server: the server
2438
+ * derives its own `clientId` inside `fn::query::register` and *ignores*
2439
+ * whatever the caller passed, and the permission rules that matter gate on
2440
+ * `auth_id = $auth.id` rather than the session (`_00_list_ref`). Session
2441
+ * scoping via `clientId = session::id()` was in fact removed upstream because
2442
+ * it broke a user with two tabs open. All this value has to be is unique per
2443
+ * browser session, which `randomUUID` gives us for free and offline.
2444
+ */
2445
+ /**
2446
+ * The current principal as the `"table:id"` string the in-browser SSP wants
2447
+ * for `$auth.id`, or null when signed out.
2448
+ *
2449
+ * Tolerates BOTH shapes `currentUser.id` can take, which is the point:
2450
+ * a session restored from the cached token carries a plain string (the JWT's
2451
+ * `ID` claim), while one verified by the server carries a RecordId. Passing
2452
+ * the former to `encodeRecordId` reads `.table` off a string and throws
2453
+ * during boot.
2454
+ */
2455
+ private sessionAuthId;
2456
+ private mintSessionSalt;
2374
2457
  }
2375
2458
  //#endregion
2376
2459
  //#region src/utils/semver.d.ts