@spooky-sync/core 0.0.1-canary.330 → 0.0.1-canary.332

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 CHANGED
@@ -35,10 +35,10 @@ re-materialized from state, never patched in place.
35
35
 
36
36
  - `Sp00kyClient<S>` — main class. Methods: `init()`, `create(id, payload)`, `update(table, id, payload, options?)`, `delete(table, id)`, `query(table, opts?)`, `preload(query, { signal? })`, `run(backend, route, payload)`, `bucket(name)`, `remoteQuery(sql, vars?)`, `useRemote(fn)`, `authenticate(token)`, `auth.signOut()`. Admin impersonation (`impersonation.enabled` in sp00ky.yml, admins only, server-enforced): `auth.impersonate(targetId, reason)`, `auth.stopImpersonating()`, `auth.impersonation`, `auth.subscribeImpersonation(cb)`, `auth.searchImpersonationTargets(search)`; while active the client injects a warning bar with a Stop button and shifts the page down onto a hazard-striped layer. `impersonationBanner` in the client config restyles it (`theme`), replaces it (`mode: 'custom'` + `client.acknowledgeImpersonationBanner()`, else it falls back after ~2.5s) or turns it off (`mode: 'none'`). See `/docs/client/impersonation`. Traffic indicators: `pendingMutationCount` / `subscribeToPendingMutations(cb)` (outbox depth), `fetchingQueryCount` / `subscribeToFetchActivity(cb)` (queries a user waits on; a query that already shows rows and is only re-registering, as after a reload, is not counted). Failed-writes tray: `failedMutationCount`, `subscribeToFailedMutations(cb)`, `listFailedMutations()`, `retryFailedMutation(id)`, `discardFailedMutation(id)`.
37
37
  - `FailedMutation` — a row of the `_00_failed_mutations` tray (`mutationType`, `recordId`, `data`, `beforeRecord`, `error`, `revert`).
38
- - `BucketHandle` — file storage handle (`put`, `get`, `delete`, `exists`), plus the cache-aware `read`, `url`, `pin`/`unpin`, `evict`, `prefetch`. `get` is always remote; `read`/`url` go through the blob cache.
38
+ - `BucketHandle` — file storage handle (`put`, `get`, `delete`, `exists`), plus the cache-aware `read`, `url`, `pin`/`unpin`, `evict`, `prefetch`. `get` is always remote; `read`/`url` go through the blob cache. A bucket generated with `backend: 'r2'` (`BACKEND "r2"`) sends its bytes to the SSP over HTTP (`<files.endpoint>/bucket/<name>/<path>`, bearer = the session token from `RemoteDatabaseService.getAuthToken()`) instead of `f"bucket:/path"` statements; the SSP checks `fn::_00_bucket::<name>` as the user first.
39
39
  - `services/blobs/` — durable cache for bucket bytes: OPFS holds the files, `_00_blob` holds a manifest that reconcile rebuilds from disk (so a wiped local store costs metadata, not the offline cache). Nothing expires on a timer; eviction is LRU under a byte budget only, skipping pinned and on-screen entries. Config: `blobCache` in `types.ts`.
40
40
  - `AuthService` — token management, sign-in/sign-out events. `subscribe(cb, { immediate? })` calls `cb` at once with the current user unless `immediate: false`, then on every change.
41
- - `CrdtManager`, `CrdtField`, `cursorColorFromName`, `CURSOR_COLORS`: collaborative `@crdt` fields on Loro. Client methods: `openCrdtField(table, recordId, field, draft?)` -> `Promise<CrdtField>` (every open of one field in a tab shares one `LoroDoc`; pair each with a close), `closeCrdtField(table, recordId, field)` -> `Promise<void>` (resolves once unsent edits are in the outbox; offline, once they are saved on the device, never waiting on the network), `createCrdtDraft(table, field)` -> a detached `CrdtField` for a record not created yet (`db.create` first, then pass it to `openCrdtField` as `draft`). `CrdtField`: `getDoc()`, `hasContent()`, `pushCursorState(bytes)`, `onCursorUpdate = cb` (a setter). Config: `crdt: true` preloads Loro, `crdtDebounceMs` (default 300, max wait 2 s). Design: `docs/crdt-v2.md` at the repo root.
41
+ - `CrdtManager`, `CrdtField`, `cursorColorFromName`, `CURSOR_COLORS`: collaborative `@crdt` fields on Loro. Client methods: `openCrdtField(table, recordId, field, draft?)` -> `Promise<CrdtField>` (every open of one field in a tab shares one `LoroDoc`; pair each with a close), `closeCrdtField(table, recordId, field)` -> `Promise<void>` (resolves once unsent edits are in the outbox; offline, once they are saved on the device, never waiting on the network), `createCrdtDraft(table, field)` -> a detached `CrdtField` for a record not created yet (`db.create` first, then pass it to `openCrdtField` as `draft`). `CrdtField`: `getDoc()`, `hasContent()`, `pushCursorState(bytes)`, `onCursorUpdate = cb` (a setter). Config: `crdt: true` preloads Loro, `crdtDebounceMs` (the first edit after a quiet spell waits this long, default 50), `crdtSendIntervalMs` (shortest gap between two sends of one field while typing, default 200). Design: `docs/crdt-v2.md` at the repo root.
42
42
  - Types: `Sp00kyConfig` (client-solid wraps it as `SyncedDbConfig`), `QueryTimeToLive`, `PersistenceClient`, `StoreType`, `UpdateOptions`, `RunOptions`, `PreloadOptions`.
43
43
  - Subpath: `@spooky-sync/core/otel` — `createOtelTransmit(endpoint)` for piping pino logs to OpenTelemetry.
44
44
  - `client.webPush` (`WebPushModule`, `db.webPush` in the Solid bindings) - Web Push on the page: `support()`, `info()`, `subscribe(opts)` (call from a click), `unsubscribe(opts)`, `sync()`, `isSubscribed()`, `update(opts)`, `devices()`, `notify(msg)`, `cancel(id)`, `test()`, `bridge()`, `onMessage(cb)`. Server calls are `fn::push::*`; typed `WebPushError`; config `webPush` in `types.ts`. Sign-out runs through `AuthService.onBeforeSignOut`.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as WebPushSubscribeOptions, A as RunOptions, At as Logger$1, B as SyncHealthStatus, Bt as SESSION_POLL_MS, C as QueryTimeToLive, Ct as SignOutHook, D as RecordVersionArray, Dt as createAuthEventSystem, E as ReconnectConfig, Et as AuthEventTypes, F as StorageHealth, Ft as IMPERSONATION_ACCESS, G as WebPushConfig, Gt as sameImpersonation, H as UpdateOptions, Ht as decodeTokenClaims, I as StorageHealthStatus, It as ImpersonationInfo, J as WebPushErrorCode, K as WebPushDevice, L as StoreType, Lt as ImpersonationTimerHost, M as Sp00kyConfig, Mt as SealedQuery, N as Sp00kyQueryResult, Nt as EventDefinition, O as RecordVersionDiff, Ot as RemoteDatabaseService, P as Sp00kyQueryResultPromise, Pt as EventSystem, Q as WebPushModule, R as SyncHealth, Rt as ImpersonationTimers, S as QueryStatusCallback, St as SignOutContext, T as QueryUpdateCallback, Tt as AuthEventTypeMap, U as PUSH_ENDPOINT_KEY, Ut as impersonationFromToken, V as TimingPhase, Vt as TokenClaims, W as PushDeviceKind, Wt as renewDelayMs, X as WebPushMessage, Y as WebPushInfo, Z as WebPushMessageInput, _ as QueryConfig, _t as isPushPayload, a as Level, at as WebPushUpdateOptions, b as QueryState, bt as ImpersonationCandidate, c as MutationCallback, ct as BridgeTokenMessage, d as PersistenceClient, dt as PushNotificationAction, et as WebPushSupport, f as PhaseStat, ft as PushOp, g as QueryAuthorityCallback, gt as WebPushClientMessage, h as PreloadRefresh, ht as PushUrgency, i as InlineRow, it as WebPushUnsupportedReason, j as ServerViewMeta, jt as LocalStore, k as RegistrationTimings, kt as LogTap, l as MutationEvent, lt as PUSH_PAYLOAD_VERSION, m as PreloadOptions, mt as PushPayloadKind, n as DebounceOptions, nt as WebPushSyncStatus, o as MATERIALIZATION_SAMPLE_WINDOW, ot as detectWebPushSupport, p as PinoTransmit, pt as PushPayload, q as WebPushError, r as EventSubscriptionOptions, rt as WebPushUnsubscribeOptions, s as MembershipOutcome, st as BridgeSignOutMessage, t as ConnectionState, tt as WebPushSyncOptions, u as MutationEventType, ut as PushNotification, v as QueryConfigRecord, vt as ActiveImpersonation, w as QueryTimings, wt as AuthEventSystem, x as QueryStatus, xt as SIGN_OUT_HOOK_TIMEOUT_MS, y as QueryHash, yt as AuthService, z as SyncHealthConfig, zt as ORIGIN_TOKEN_KEY } from "./types.js";
1
+ import { $ as WebPushModule, A as RegistrationTimings, At as LogTap, B as SyncHealthConfig, Bt as ORIGIN_TOKEN_KEY, C as QueryStatusCallback, Ct as SignOutContext, D as ReconnectConfig, Dt as AuthEventTypes, E as QueryUpdateCallback, Et as AuthEventTypeMap, F as Sp00kyQueryResultPromise, Ft as EventSystem, G as PushDeviceKind, Gt as renewDelayMs, H as TimingPhase, Ht as TokenClaims, I as StorageHealth, It as IMPERSONATION_ACCESS, J as WebPushError, K as WebPushConfig, Kt as sameImpersonation, L as StorageHealthStatus, Lt as ImpersonationInfo, M as ServerViewMeta, Mt as LocalStore, N as Sp00kyConfig, Nt as SealedQuery, O as RecordVersionArray, Ot as createAuthEventSystem, P as Sp00kyQueryResult, Pt as EventDefinition, Q as WebPushMessageInput, R as StoreType, Rt as ImpersonationTimerHost, S as QueryStatus, St as SIGN_OUT_HOOK_TIMEOUT_MS, T as QueryTimings, Tt as AuthEventSystem, U as UpdateOptions, Ut as decodeTokenClaims, V as SyncHealthStatus, Vt as SESSION_POLL_MS, W as PUSH_ENDPOINT_KEY, Wt as impersonationFromToken, X as WebPushInfo, Y as WebPushErrorCode, Z as WebPushMessage, _ as QueryAuthorityCallback, _t as WebPushClientMessage, a as InlineRow, at as WebPushUnsupportedReason, b as QueryHash, bt as AuthService, c as MembershipOutcome, ct as BridgeSignOutMessage, d as MutationEventType, dt as PushNotification, et as WebPushSubscribeOptions, f as PersistenceClient, ft as PushNotificationAction, g as PreloadRefresh, gt as PushUrgency, h as PreloadOptions, ht as PushPayloadKind, i as EventSubscriptionOptions, it as WebPushUnsubscribeOptions, j as RunOptions, jt as Logger$1, k as RecordVersionDiff, kt as RemoteDatabaseService, l as MutationCallback, lt as BridgeTokenMessage, m as PinoTransmit, mt as PushPayload, n as DebounceOptions, nt as WebPushSyncOptions, o as Level, ot as WebPushUpdateOptions, p as PhaseStat, pt as PushOp, q as WebPushDevice, r as EdgeDelta, rt as WebPushSyncStatus, s as MATERIALIZATION_SAMPLE_WINDOW, st as detectWebPushSupport, t as ConnectionState, tt as WebPushSupport, u as MutationEvent, ut as PUSH_PAYLOAD_VERSION, v as QueryConfig, vt as isPushPayload, w as QueryTimeToLive, wt as SignOutHook, x as QueryState, xt as ImpersonationCandidate, y as QueryConfigRecord, yt as ActiveImpersonation, z as SyncHealth, zt as ImpersonationTimers } from "./types.js";
2
2
  import { BackendNames, BackendRoutes, BucketNames, FinalQuery, GetTable, QueryBuilder, QueryOptions, QueryPlan, RecordId, RoutePayload, SchemaStructure, TableModel, TableNames } from "@spooky-sync/query-builder";
3
3
  import * as surrealdb0 from "surrealdb";
4
4
  import { Duration, RecordId as RecordId$1, Surreal } from "surrealdb";
@@ -608,7 +608,10 @@ declare class CrdtManager {
608
608
  private schema;
609
609
  private local;
610
610
  private remote;
611
+ /** First edit after a quiet spell: wait this long to batch the burst. */
611
612
  private debounceMs;
613
+ /** Shortest gap between two sends of one field, while edits keep coming. */
614
+ private sendIntervalMs;
612
615
  private sessions;
613
616
  private opening;
614
617
  /** Closes still saving their field's local state, by session key. */
@@ -620,7 +623,9 @@ declare class CrdtManager {
620
623
  private claim;
621
624
  private claimRetry;
622
625
  private connectionUnsubscribes;
623
- constructor(schema: SchemaStructure, local: LocalStore, remote: RemoteDatabaseService, logger: Logger$1, debounceMs?: number);
626
+ constructor(schema: SchemaStructure, local: LocalStore, remote: RemoteDatabaseService, logger: Logger$1, /** First edit after a quiet spell: wait this long to batch the burst. */
627
+ debounceMs?: number, /** Shortest gap between two sends of one field, while edits keep coming. */
628
+ sendIntervalMs?: number);
624
629
  /** Hand over the runtime ports. Called once by the client facade. */
625
630
  attach(ports: CrdtPorts): void;
626
631
  /**
@@ -680,7 +685,16 @@ declare class CrdtManager {
680
685
  * be merged away; that is logged.
681
686
  */
682
687
  private verify;
688
+ /**
689
+ * Arm the send. Edits go out on a steady cadence while they keep coming:
690
+ * the first one after a quiet spell waits `debounceMs` (one row for the
691
+ * burst), later ones at least `sendIntervalMs` after the previous send. An
692
+ * armed timer is never pushed back, so continuous typing cannot hold the
693
+ * edits; it is only brought forward (`delay`, e.g. 0 on reconnect).
694
+ */
683
695
  private scheduleFlush;
696
+ /** Send what is unsent. A call while a send runs queues one more after it,
697
+ * so edits made during that send are not left waiting for the next edit. */
684
698
  private flush;
685
699
  /** Send every own peer's ops that are not in the outbox yet, one row per
686
700
  * peer. Only the peer's own ops travel (`updates-in-range`): a plain
@@ -961,6 +975,7 @@ type RuntimeEvent = {
961
975
  type: 'LiveChange';
962
976
  hashes: QueryHash[];
963
977
  rows?: InlineRow[];
978
+ deltas?: EdgeDelta[];
964
979
  } | {
965
980
  type: 'TabRole';
966
981
  role: 'solo' | 'leader' | 'follower';
@@ -1442,7 +1457,7 @@ interface Adapters {
1442
1457
  * changed query hashes, plus the changed rows themselves when the
1443
1458
  * subscription was opened with the body joined on (`liveInlineBodies`).
1444
1459
  */
1445
- live(table: string, onChange: (hashes: string[], rows?: InlineRow[]) => void): Promise<string>;
1460
+ live(table: string, onChange: (hashes: string[], rows?: InlineRow[], deltas?: EdgeDelta[]) => void): Promise<string>;
1446
1461
  kill(uuid: string): Promise<void>;
1447
1462
  };
1448
1463
  ssp: {
@@ -1985,13 +2000,21 @@ interface BucketHandleSettings {
1985
2000
  logger?: {
1986
2001
  warn: (obj: unknown, msg?: string) => void;
1987
2002
  };
2003
+ /** The bucket's backend as generated (`'r2'`: files live with the SSP). */
2004
+ backend?: string;
2005
+ /** {@link Sp00kyConfig.files}: where `r2` bucket files are read and written. */
2006
+ files?: {
2007
+ endpoint?: string;
2008
+ };
1988
2009
  }
2010
+ /** A handle's settings from the client config and the bucket's generated schema. */
2011
+
1989
2012
  declare class BucketHandle {
1990
2013
  private bucketName;
1991
- private remote;
1992
2014
  /** Absent on the raw handle the cache itself reads through. */
1993
2015
  private blobs?;
1994
2016
  private settings?;
2017
+ private io;
1995
2018
  constructor(bucketName: string, remote: RemoteDatabaseService, /** Absent on the raw handle the cache itself reads through. */
1996
2019
  blobs?: (BlobCache | null) | undefined, settings?: BucketHandleSettings | undefined);
1997
2020
  /** Effective blurhash setting: per-call option > client config > default ON. */
@@ -2189,4 +2212,4 @@ declare class LocalOpTimeoutError extends Error {
2189
2212
  * select on a throttled tab is seconds, not tens of seconds. */
2190
2213
  declare const DEFAULT_LOCAL_OP_TIMEOUT_MS = 30000;
2191
2214
  //#endregion
2192
- export { ActiveImpersonation, AppReleaseHandle, AppReleaseModule, type AppReleaseOptions, type AppReleaseSnapshot, AuthEventSystem, AuthEventTypeMap, AuthEventTypes, AuthService, BLURHASH_IMAGE_EXTENSIONS, type BlobCacheStats, type BlobEntry, type BlobKey, type BlobReadOptions, type BlobUrlLease, type BlurhashEncodeOptions, type BlurhashSetting, type BridgeSignOutMessage, type BridgeTokenMessage, BucketHandle, BucketPutOptions, BucketPutResult, CURSOR_COLORS, ConnectionState, CrdtField, CrdtManager, DEFAULT_LOCAL_OP_TIMEOUT_MS, DebounceOptions, EventSubscriptionOptions, type FailedMutationRow as FailedMutation, FeatureFlagHandle, FeatureFlagModule, type FeatureFlagOptions, type FeatureFlagOverride, type FeatureFlagSnapshot, IMPERSONATION_ACCESS, ImpersonationCandidate, ImpersonationInfo, ImpersonationTimerHost, ImpersonationTimers, InlineRow, Level, LocalOpTimeoutError, MATERIALIZATION_SAMPLE_WINDOW, MembershipOutcome, MutationCallback, MutationEvent, MutationEventType, ORIGIN_TOKEN_KEY, PUSH_ENDPOINT_KEY, PUSH_PAYLOAD_VERSION, PersistenceClient, PhaseStat, PinoTransmit, PreloadOptions, PreloadRefresh, type PushDeviceKind, type PushNotification, type PushNotificationAction, type PushOp, type PushPayload, type PushPayloadKind, type PushUrgency, QueryAuthorityCallback, QueryConfig, QueryConfigRecord, QueryHash, QueryState, QueryStatus, QueryStatusCallback, QueryTimeToLive, QueryTimings, QueryUpdateCallback, ReconnectConfig, RecordVersionArray, RecordVersionDiff, RegistrationTimings, RunOptions, SESSION_POLL_MS, SIGN_OUT_HOOK_TIMEOUT_MS, ServerViewMeta, SignOutContext, SignOutHook, Sp00kyClient, Sp00kyClientDeps, Sp00kyConfig, Sp00kyQueryResult, Sp00kyQueryResultPromise, StorageHealth, StorageHealthStatus, StoreType, SyncHealth, SyncHealthConfig, SyncHealthStatus, TimingPhase, TokenClaims, UpdateOptions, type WebPushClientMessage, type WebPushConfig, type WebPushDevice, WebPushError, type WebPushErrorCode, type WebPushInfo, type WebPushMessage, type WebPushMessageInput, WebPushModule, type WebPushSubscribeOptions, type WebPushSupport, type WebPushSyncOptions, type WebPushSyncStatus, type WebPushUnsubscribeOptions, type WebPushUnsupportedReason, type WebPushUpdateOptions, blurhashSidecarPath, bucketContentToBlob, createAuthEventSystem, cursorColorFromName, decode as decodeBlurhash, decodeTokenClaims, detectWebPushSupport, encode as encodeBlurhash, encodeImageToBlurhash, fileToUint8Array, impersonationFromToken, isBlurhashValid, isImagePath, isPushPayload, renewDelayMs, sameImpersonation, semverGt, textToHtml };
2215
+ export { ActiveImpersonation, AppReleaseHandle, AppReleaseModule, type AppReleaseOptions, type AppReleaseSnapshot, AuthEventSystem, AuthEventTypeMap, AuthEventTypes, AuthService, BLURHASH_IMAGE_EXTENSIONS, type BlobCacheStats, type BlobEntry, type BlobKey, type BlobReadOptions, type BlobUrlLease, type BlurhashEncodeOptions, type BlurhashSetting, type BridgeSignOutMessage, type BridgeTokenMessage, BucketHandle, BucketPutOptions, BucketPutResult, CURSOR_COLORS, ConnectionState, CrdtField, CrdtManager, DEFAULT_LOCAL_OP_TIMEOUT_MS, DebounceOptions, EdgeDelta, EventSubscriptionOptions, type FailedMutationRow as FailedMutation, FeatureFlagHandle, FeatureFlagModule, type FeatureFlagOptions, type FeatureFlagOverride, type FeatureFlagSnapshot, IMPERSONATION_ACCESS, ImpersonationCandidate, ImpersonationInfo, ImpersonationTimerHost, ImpersonationTimers, InlineRow, Level, LocalOpTimeoutError, MATERIALIZATION_SAMPLE_WINDOW, MembershipOutcome, MutationCallback, MutationEvent, MutationEventType, ORIGIN_TOKEN_KEY, PUSH_ENDPOINT_KEY, PUSH_PAYLOAD_VERSION, PersistenceClient, PhaseStat, PinoTransmit, PreloadOptions, PreloadRefresh, type PushDeviceKind, type PushNotification, type PushNotificationAction, type PushOp, type PushPayload, type PushPayloadKind, type PushUrgency, QueryAuthorityCallback, QueryConfig, QueryConfigRecord, QueryHash, QueryState, QueryStatus, QueryStatusCallback, QueryTimeToLive, QueryTimings, QueryUpdateCallback, ReconnectConfig, RecordVersionArray, RecordVersionDiff, RegistrationTimings, RunOptions, SESSION_POLL_MS, SIGN_OUT_HOOK_TIMEOUT_MS, ServerViewMeta, SignOutContext, SignOutHook, Sp00kyClient, Sp00kyClientDeps, Sp00kyConfig, Sp00kyQueryResult, Sp00kyQueryResultPromise, StorageHealth, StorageHealthStatus, StoreType, SyncHealth, SyncHealthConfig, SyncHealthStatus, TimingPhase, TokenClaims, UpdateOptions, type WebPushClientMessage, type WebPushConfig, type WebPushDevice, WebPushError, type WebPushErrorCode, type WebPushInfo, type WebPushMessage, type WebPushMessageInput, WebPushModule, type WebPushSubscribeOptions, type WebPushSupport, type WebPushSyncOptions, type WebPushSyncStatus, type WebPushUnsubscribeOptions, type WebPushUnsupportedReason, type WebPushUpdateOptions, blurhashSidecarPath, bucketContentToBlob, createAuthEventSystem, cursorColorFromName, decode as decodeBlurhash, decodeTokenClaims, detectWebPushSupport, encode as encodeBlurhash, encodeImageToBlurhash, fileToUint8Array, impersonationFromToken, isBlurhashValid, isImagePath, isPushPayload, renewDelayMs, sameImpersonation, semverGt, textToHtml };