@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 +2 -2
- package/dist/index.d.ts +28 -5
- package/dist/index.js +285 -64
- package/dist/otel/index.d.ts +1 -1
- package/dist/pure.d.ts +16 -0
- package/dist/types.d.ts +42 -11
- package/package.json +3 -3
- package/src/client/bucket-handle.test.ts +78 -0
- package/src/client/bucket-handle.ts +156 -31
- package/src/client/router.ts +1 -1
- package/src/client/services.ts +6 -5
- package/src/client/sp00ky-client.ts +2 -2
- package/src/kernel/events.ts +2 -2
- package/src/kernel/interpreter.ts +3 -3
- package/src/modules/crdt/crdt.test.ts +52 -6
- package/src/modules/crdt/index.ts +59 -23
- package/src/query/membership.saga.test.ts +73 -1
- package/src/query/membership.saga.ts +39 -5
- package/src/query/membership.test.ts +33 -0
- package/src/query/membership.ts +33 -0
- package/src/services/database/remote.ts +6 -0
- package/src/sync/live.saga.test.ts +51 -0
- package/src/sync/live.saga.ts +17 -8
- package/src/types.ts +37 -10
- package/src/utils/edges.ts +19 -1
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
|
|
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
|
|
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,
|
|
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 };
|