@pylonsync/sync 0.3.291 → 0.3.293
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/ids.d.ts +14 -0
- package/dist/index.d.ts +949 -0
- package/dist/local-store.d.ts +186 -0
- package/dist/multi-tab-orchestrator.d.ts +141 -0
- package/dist/multi-tab.d.ts +70 -0
- package/dist/mutation-queue.d.ts +88 -0
- package/dist/op-queue.d.ts +18 -0
- package/dist/persistence.d.ts +114 -0
- package/dist/room-subscriptions.d.ts +113 -0
- package/dist/server-subscriptions.d.ts +26 -0
- package/dist/session-resolver.d.ts +68 -0
- package/dist/storage.d.ts +30 -0
- package/dist/subscription-coordinator.d.ts +99 -0
- package/dist/test-harness/env.d.ts +56 -0
- package/dist/test-harness/index.d.ts +5 -0
- package/dist/test-harness/server.d.ts +178 -0
- package/dist/test-harness/transport.d.ts +19 -0
- package/dist/transport.d.ts +89 -0
- package/dist/transports/index.d.ts +19 -0
- package/dist/transports/polling.d.ts +15 -0
- package/dist/transports/reconnect.d.ts +20 -0
- package/dist/transports/sse.d.ts +22 -0
- package/dist/transports/types.d.ts +97 -0
- package/dist/transports/websocket.d.ts +21 -0
- package/dist/types.d.ts +124 -0
- package/package.json +11 -5
- package/tsconfig.json +0 -7
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every caller of `pylonFetch` must supply. The SyncEngine
|
|
3
|
+
* passes its own config; the React free helpers pass a lightweight
|
|
4
|
+
* shim built from `getBaseUrl()` + `currentAuthToken()`.
|
|
5
|
+
*/
|
|
6
|
+
export interface TransportConfig {
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/** Static bearer token. Use `getToken` if the token can change. */
|
|
9
|
+
token?: string;
|
|
10
|
+
/** Lazy bearer-token resolver — called per request. Use this when
|
|
11
|
+
* the token can be rotated mid-session (refresh, session-changed).
|
|
12
|
+
* Returning null/undefined means "no auth header"; cookie-auth
|
|
13
|
+
* apps rely on `credentials: "include"` instead. */
|
|
14
|
+
getToken?: () => string | null | undefined;
|
|
15
|
+
/** Invoked with the value of the X-Pylon-Change-Seq response
|
|
16
|
+
* header when the server sets one. Returning a Promise pauses no
|
|
17
|
+
* one — the transport doesn't await it. */
|
|
18
|
+
onChangeSeq?: (seq: number) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Per-call init shape. Mirrors the standard `RequestInit` but
|
|
22
|
+
* normalizes the two body shapes (JSON-encode-this vs raw-pass-
|
|
23
|
+
* through) into separate fields so callers don't have to remember
|
|
24
|
+
* to set Content-Type.
|
|
25
|
+
*/
|
|
26
|
+
export interface PylonRequestInit {
|
|
27
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
28
|
+
/** When set, JSON-stringified into the body and Content-Type
|
|
29
|
+
* defaults to application/json. */
|
|
30
|
+
json?: unknown;
|
|
31
|
+
/** Raw body — passed through to fetch unchanged. Use this for file
|
|
32
|
+
* uploads (Blob, ArrayBuffer, FormData). */
|
|
33
|
+
body?: BodyInit;
|
|
34
|
+
/** Extra headers. Caller-supplied keys win against the transport's
|
|
35
|
+
* defaults. */
|
|
36
|
+
headers?: Record<string, string>;
|
|
37
|
+
/** Override the request's Accept header (useful for SSE streams). */
|
|
38
|
+
accept?: string;
|
|
39
|
+
/** AbortController signal for cancellation. */
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the base URL. Browser-side fallback to
|
|
44
|
+
* `window.location.origin` matches the SDK contract: when
|
|
45
|
+
* `configureClient` was called with no baseUrl, calls go to the
|
|
46
|
+
* current document's origin so dev + prod work without env
|
|
47
|
+
* gymnastics.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveBaseUrl(config: TransportConfig): string;
|
|
50
|
+
/**
|
|
51
|
+
* Assemble fetch URL + RequestInit. Exposed for streaming / raw-
|
|
52
|
+
* response callers (SSE, file upload) that need to handle the
|
|
53
|
+
* Response themselves but want the transport's auth + credentials +
|
|
54
|
+
* URL logic.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildRequest(config: TransportConfig, path: string, init?: PylonRequestInit): {
|
|
57
|
+
url: string;
|
|
58
|
+
init: RequestInit;
|
|
59
|
+
};
|
|
60
|
+
/** Error thrown by `pylonFetch` for non-2xx responses. Carries the
|
|
61
|
+
* status + structured error code + the parsed JSON body (if any). */
|
|
62
|
+
export declare class PylonHttpError extends Error {
|
|
63
|
+
status: number;
|
|
64
|
+
code?: string;
|
|
65
|
+
body?: unknown;
|
|
66
|
+
constructor(message: string, status: number, code?: string, body?: unknown);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The canonical client HTTP call. Returns the parsed JSON body on
|
|
70
|
+
* 2xx. Throws `PylonHttpError` on non-2xx. Empty 204 bodies parse
|
|
71
|
+
* as `null`.
|
|
72
|
+
*
|
|
73
|
+
* Surfaces `X-Pylon-Change-Seq` via `config.onChangeSeq` so the
|
|
74
|
+
* SyncEngine can fire a catch-up pull when a mutation's seq is
|
|
75
|
+
* ahead of its local cursor — matches the
|
|
76
|
+
* `requestWithChangeSync` shape but lifts the logic out of the
|
|
77
|
+
* engine so React free helpers (`callFn`, `apiRequest`) get it too.
|
|
78
|
+
*/
|
|
79
|
+
export declare function pylonFetch<T = unknown>(config: TransportConfig, path: string, init?: PylonRequestInit): Promise<T>;
|
|
80
|
+
/**
|
|
81
|
+
* Streaming variant — returns the raw `Response` so callers can read
|
|
82
|
+
* `.body` (SSE), `.blob()` (file download), etc. Bypasses JSON
|
|
83
|
+
* parsing but keeps the URL + auth + credentials logic.
|
|
84
|
+
*
|
|
85
|
+
* Caller is responsible for status checking and the
|
|
86
|
+
* X-Pylon-Change-Seq callback (we don't read the response without
|
|
87
|
+
* the caller's involvement).
|
|
88
|
+
*/
|
|
89
|
+
export declare function pylonFetchRaw(config: TransportConfig, path: string, init?: PylonRequestInit): Promise<Response>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Transport, TransportHost } from "./types";
|
|
2
|
+
export type TransportKind = "websocket" | "sse" | "poll";
|
|
3
|
+
export type { Transport, TransportHost } from "./types";
|
|
4
|
+
export { WebSocketTransport } from "./websocket";
|
|
5
|
+
export { SseTransport } from "./sse";
|
|
6
|
+
export { PollingTransport } from "./polling";
|
|
7
|
+
/** Build the right transport for a given kind. The host supplies all
|
|
8
|
+
* config + the inbound dispatch callbacks; the transport hides the
|
|
9
|
+
* underlying mechanism.
|
|
10
|
+
*
|
|
11
|
+
* Fallback rule: `transport: "sse"` in an environment without a
|
|
12
|
+
* native EventSource (Node, jsdom without a polyfill, old browsers)
|
|
13
|
+
* silently downgrades to polling. The pre-refactor `connectSse()`
|
|
14
|
+
* did this inside its constructor catch — we lift the decision to
|
|
15
|
+
* the factory so the engine never sees an SseTransport that can
|
|
16
|
+
* never connect. Clients that NEED SSE specifically can detect this
|
|
17
|
+
* by feature-checking `typeof EventSource` themselves before calling
|
|
18
|
+
* `init()`. */
|
|
19
|
+
export declare function createTransport(kind: TransportKind, host: TransportHost): Transport;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Transport, TransportHost } from "./types";
|
|
2
|
+
export declare class PollingTransport implements Transport {
|
|
3
|
+
private readonly host;
|
|
4
|
+
private timer;
|
|
5
|
+
constructor(host: TransportHost);
|
|
6
|
+
start(): void;
|
|
7
|
+
stop(): void;
|
|
8
|
+
send(_msg: unknown): void;
|
|
9
|
+
isOpen(): boolean;
|
|
10
|
+
/** No-op — polling doesn't have a backoff counter; it just retries
|
|
11
|
+
* every interval. A 429 on the in-flight push/pull will surface as
|
|
12
|
+
* a thrown error on the next tick but the loop continues at the
|
|
13
|
+
* same cadence. */
|
|
14
|
+
bumpReconnect(_by?: number): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Stateful backoff counter. Reset to 0 attempts after a successful
|
|
2
|
+
* stable connection window. Increment on every failed connect /
|
|
3
|
+
* unexpected close. */
|
|
4
|
+
export declare class ReconnectBackoff {
|
|
5
|
+
private attempts;
|
|
6
|
+
private baseDelayMs;
|
|
7
|
+
constructor(baseDelayMs?: number);
|
|
8
|
+
/** Record a failed connect / unexpected close. Returns the new attempt
|
|
9
|
+
* count so callers can use it for bookkeeping (e.g., the 429 case
|
|
10
|
+
* bumps by 3 to push the next delay further out). */
|
|
11
|
+
bump(by?: number): number;
|
|
12
|
+
/** Reset the counter — call this after the connection has been
|
|
13
|
+
* stable long enough that subsequent reconnects shouldn't carry
|
|
14
|
+
* the prior backoff over. */
|
|
15
|
+
reset(): void;
|
|
16
|
+
/** Compute the next delay in ms. Always returns at least 0; never
|
|
17
|
+
* negative. Full-jitter over [0, exp] where exp grows
|
|
18
|
+
* exponentially with the attempt count and caps at MAX_DELAY_MS. */
|
|
19
|
+
nextDelayMs(): number;
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Transport, TransportHost } from "./types";
|
|
2
|
+
export declare class SseTransport implements Transport {
|
|
3
|
+
private readonly host;
|
|
4
|
+
private es;
|
|
5
|
+
private reconnectTimer;
|
|
6
|
+
private readonly backoff;
|
|
7
|
+
constructor(host: TransportHost);
|
|
8
|
+
start(): void;
|
|
9
|
+
stop(): void;
|
|
10
|
+
/** SSE is one-way. The engine still calls send() for subscribe /
|
|
11
|
+
* presence / topic / ping, but those frames have nowhere to go.
|
|
12
|
+
* Silent no-op rather than throw: a no-op keeps app code identical
|
|
13
|
+
* across transports. */
|
|
14
|
+
send(_msg: unknown): void;
|
|
15
|
+
/** SSE is always "open enough" to receive once the EventSource is
|
|
16
|
+
* attached and not in CLOSED. We expose this for the engine's
|
|
17
|
+
* `connected` getter so UI status matches reality. */
|
|
18
|
+
isOpen(): boolean;
|
|
19
|
+
bumpReconnect(by?: number): void;
|
|
20
|
+
private scheduleReconnect;
|
|
21
|
+
private deriveSseUrl;
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ChangeEvent, SyncConnectionStatus } from "../types";
|
|
2
|
+
/** Real-time transport implementations all conform to this shape. The
|
|
3
|
+
* engine holds exactly one. The interface intentionally hides the
|
|
4
|
+
* underlying mechanism (WebSocket vs EventSource vs setInterval) so
|
|
5
|
+
* the engine can be told "go connect" without caring which one. */
|
|
6
|
+
export interface Transport {
|
|
7
|
+
/** Connect / start the transport. Idempotent — calling twice is a
|
|
8
|
+
* no-op for an already-running transport. */
|
|
9
|
+
start(): void;
|
|
10
|
+
/** Disconnect / stop the transport, clearing every timer and
|
|
11
|
+
* releasing the socket. Idempotent. After stop() the transport is
|
|
12
|
+
* re-startable. */
|
|
13
|
+
stop(): void;
|
|
14
|
+
/** Send a JSON message over the transport. No-op for non-bidirectional
|
|
15
|
+
* transports (SSE, polling) — the engine still sends subscribe
|
|
16
|
+
* frames + presence + topic + ping through this entry point, and
|
|
17
|
+
* those frames simply never reach the wire when the transport
|
|
18
|
+
* doesn't support uplink. */
|
|
19
|
+
send(msg: unknown): void;
|
|
20
|
+
/** True when the underlying connection is currently open for sending.
|
|
21
|
+
* Used by the engine's `connected` getter and by paths that need to
|
|
22
|
+
* short-circuit when no socket exists (e.g., avoid throwing on a
|
|
23
|
+
* send-without-WS). */
|
|
24
|
+
isOpen(): boolean;
|
|
25
|
+
/** Bump the reconnect backoff counter by N attempts. Called by the
|
|
26
|
+
* engine when it observes a hint that the NEXT reconnect should
|
|
27
|
+
* wait longer — typically a 429 on pull, which would otherwise
|
|
28
|
+
* drive a tight 429 / reconnect / 429 loop. Transports without a
|
|
29
|
+
* backoff (polling) implement this as a no-op. */
|
|
30
|
+
bumpReconnect(by?: number): void;
|
|
31
|
+
}
|
|
32
|
+
/** Engine-side surface the transport calls into. Transport is the
|
|
33
|
+
* thing that knows about sockets and timers; the host (engine) is the
|
|
34
|
+
* thing that knows what to DO with inbound frames and lifecycle
|
|
35
|
+
* events. Keeping this small + explicit makes the boundary clear:
|
|
36
|
+
* if a transport needs a new engine capability it gets added here,
|
|
37
|
+
* not via reaching back into the engine instance. */
|
|
38
|
+
export interface TransportHost {
|
|
39
|
+
/** Base URL of the Pylon HTTP API (e.g. `https://api.example.com`).
|
|
40
|
+
* Transports derive their own URLs from this (ws upgrade path, SSE
|
|
41
|
+
* endpoint, poll path). */
|
|
42
|
+
readonly baseUrl: string;
|
|
43
|
+
/** Optional explicit WS URL — overrides the derived one. */
|
|
44
|
+
readonly wsUrl?: string;
|
|
45
|
+
/** WS keepalive interval. Default is implementation-defined; the
|
|
46
|
+
* engine accepts `pingIntervalMs` from config to tune broadcast
|
|
47
|
+
* latency on single-threaded server fallbacks. */
|
|
48
|
+
readonly pingIntervalMs?: number;
|
|
49
|
+
/** Base delay for reconnect backoff (full-jitter). */
|
|
50
|
+
readonly reconnectDelayMs?: number;
|
|
51
|
+
/** Polling cadence (polling transport only). */
|
|
52
|
+
readonly pollIntervalMs?: number;
|
|
53
|
+
/** Current bearer token, or undefined when anonymous. The transport
|
|
54
|
+
* must call this fresh on each connect / reconnect so token rotation
|
|
55
|
+
* is picked up automatically. */
|
|
56
|
+
getToken(): string | undefined;
|
|
57
|
+
/** Is this tab the multi-tab leader. Followers don't open their own
|
|
58
|
+
* transport — they mirror the leader's broadcasts. Transports check
|
|
59
|
+
* this defensively at start() to avoid wasting socket budget. */
|
|
60
|
+
isLeader(): boolean;
|
|
61
|
+
/** Is the engine currently running. Set false on stop(); transports
|
|
62
|
+
* bail out of reconnect timers when this flips. */
|
|
63
|
+
isRunning(): boolean;
|
|
64
|
+
/** Inbound: a typed change event from the server's change log.
|
|
65
|
+
* Engine routes through the apply queue so order is preserved. */
|
|
66
|
+
onChangeEvent(ev: ChangeEvent): void;
|
|
67
|
+
/** Inbound: a typed JSON envelope (not a ChangeEvent). The transport
|
|
68
|
+
* doesn't try to interpret the envelope — engine has the dispatch
|
|
69
|
+
* table (session-changed, reactive-result, row-revoked, presence,
|
|
70
|
+
* pong, etc). */
|
|
71
|
+
onJsonMessage(msg: Record<string, unknown>): void;
|
|
72
|
+
/** Inbound: a binary frame. CRDT broadcast layer ships these for
|
|
73
|
+
* every Loro-mode write; the engine routes via its binaryHandlers
|
|
74
|
+
* + multi-tab forwarders. */
|
|
75
|
+
onBinaryFrame(bytes: Uint8Array): void;
|
|
76
|
+
/** Lifecycle: the transport just connected. Engine uses this to
|
|
77
|
+
* replay active subscriptions, fire a catch-up pull + reconcile,
|
|
78
|
+
* and flip the public connection status. */
|
|
79
|
+
onConnected(): void;
|
|
80
|
+
/** Lifecycle: the transport just disconnected. Engine flips status
|
|
81
|
+
* to `reconnecting` while the transport's own backoff timer is
|
|
82
|
+
* pending; or to `offline` when `isRunning()` is false. */
|
|
83
|
+
onDisconnected(): void;
|
|
84
|
+
/** Flip the engine's public `connectionStatus`. Transports drive
|
|
85
|
+
* this so the UI sees `connecting` / `connected` / `reconnecting`
|
|
86
|
+
* / `offline` at the right moments. */
|
|
87
|
+
setStatus(s: SyncConnectionStatus): void;
|
|
88
|
+
/** Polling transport only: one iteration of the push→pull cycle.
|
|
89
|
+
* Implementations of polling do nothing else; they just call this
|
|
90
|
+
* on a timer. */
|
|
91
|
+
performPollTick(): Promise<void>;
|
|
92
|
+
/** Reconnect-aware transports (WS, SSE) need to do a catch-up pull
|
|
93
|
+
* before the next connect attempt so they don't open a fresh socket
|
|
94
|
+
* and immediately discover their cursor is stale. The engine owns
|
|
95
|
+
* the pull queue + reconcile path; the transport invokes this. */
|
|
96
|
+
performReconnectPull(): Promise<void>;
|
|
97
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Transport, TransportHost } from "./types";
|
|
2
|
+
export declare class WebSocketTransport implements Transport {
|
|
3
|
+
private readonly host;
|
|
4
|
+
private ws;
|
|
5
|
+
private reconnectTimer;
|
|
6
|
+
private stableTimer;
|
|
7
|
+
private pingTimer;
|
|
8
|
+
private readonly backoff;
|
|
9
|
+
constructor(host: TransportHost);
|
|
10
|
+
start(): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
send(msg: unknown): void;
|
|
13
|
+
isOpen(): boolean;
|
|
14
|
+
/** Bump the reconnect counter explicitly. Used for the 429 RESYNC
|
|
15
|
+
* case where the engine wants the next delay to be 3 steps further
|
|
16
|
+
* out — protects the server from a 429-loop where a 429 on pull
|
|
17
|
+
* triggers onclose → reconnect → pull → 429 in a tight cycle. */
|
|
18
|
+
bumpReconnect(by?: number): void;
|
|
19
|
+
private scheduleReconnect;
|
|
20
|
+
private deriveWsUrl;
|
|
21
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export interface ChangeEvent {
|
|
2
|
+
seq: number;
|
|
3
|
+
entity: string;
|
|
4
|
+
row_id: string;
|
|
5
|
+
kind: "insert" | "update" | "delete";
|
|
6
|
+
data?: Record<string, unknown>;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncCursor {
|
|
10
|
+
last_seq: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PullResponse {
|
|
13
|
+
changes: ChangeEvent[];
|
|
14
|
+
cursor: SyncCursor;
|
|
15
|
+
has_more: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Server-resolved auth/session state. Shape mirrors what `/api/auth/me`
|
|
19
|
+
* returns (which is `AuthContext` from the Rust side, with camelCase
|
|
20
|
+
* normalization on the way out).
|
|
21
|
+
*
|
|
22
|
+
* `userId=null` means anonymous. `tenantId=null` means the user hasn't
|
|
23
|
+
* selected an org yet (or the backend is single-tenant).
|
|
24
|
+
*/
|
|
25
|
+
export interface ResolvedSession {
|
|
26
|
+
userId: string | null;
|
|
27
|
+
tenantId: string | null;
|
|
28
|
+
isAdmin: boolean;
|
|
29
|
+
roles: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Per-op result on /api/sync/push. Formal shape:
|
|
33
|
+
*
|
|
34
|
+
* { op_id, row_id, entity, kind, status, seq?, error? }
|
|
35
|
+
*
|
|
36
|
+
* Status semantics:
|
|
37
|
+
* - `applied` — first-time write committed at `seq`.
|
|
38
|
+
* - `replayed` — same op_id arrived again after a confirmed apply;
|
|
39
|
+
* `seq` is the cached value of the original write so
|
|
40
|
+
* the client can adopt it on its optimistic ghost
|
|
41
|
+
* without waiting for the WS rebroadcast.
|
|
42
|
+
* - `pending` — a concurrent push carrying this op_id is still
|
|
43
|
+
* in flight on the server. No `seq` yet; the
|
|
44
|
+
* client should retry after a short backoff.
|
|
45
|
+
* - `error` — the write was attempted and rejected; `error`
|
|
46
|
+
* carries the server's code + message.
|
|
47
|
+
*
|
|
48
|
+
* Legacy `deduped` is treated as `replayed` for compatibility — older
|
|
49
|
+
* servers (≤ 0.3.190) returned `deduped` for both InFlight and
|
|
50
|
+
* Replayed cases.
|
|
51
|
+
*/
|
|
52
|
+
export interface PushOpResult {
|
|
53
|
+
op_id?: string | null;
|
|
54
|
+
entity?: string;
|
|
55
|
+
row_id?: string;
|
|
56
|
+
kind?: "insert" | "update" | "delete";
|
|
57
|
+
status: "applied" | "replayed" | "pending" | "error" | "deduped";
|
|
58
|
+
seq?: number;
|
|
59
|
+
error?: {
|
|
60
|
+
code: string;
|
|
61
|
+
message: string;
|
|
62
|
+
} | string;
|
|
63
|
+
}
|
|
64
|
+
export interface PushResponse {
|
|
65
|
+
applied: number;
|
|
66
|
+
deduped: number;
|
|
67
|
+
errors: string[];
|
|
68
|
+
results?: PushOpResult[];
|
|
69
|
+
cursor: SyncCursor;
|
|
70
|
+
}
|
|
71
|
+
export interface ClientChange {
|
|
72
|
+
entity: string;
|
|
73
|
+
row_id: string;
|
|
74
|
+
kind: "insert" | "update" | "delete";
|
|
75
|
+
data?: Record<string, unknown>;
|
|
76
|
+
/**
|
|
77
|
+
* Client-minted idempotency key. The server tracks recently-seen op_ids
|
|
78
|
+
* and returns a no-op success for replays. Supply this on every retry of
|
|
79
|
+
* the same logical mutation — the `MutationQueue` does so automatically.
|
|
80
|
+
*/
|
|
81
|
+
op_id?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reactive subscription spec — what the server needs to replay a
|
|
85
|
+
* subscription if the client reconnects. Cached client-side so the
|
|
86
|
+
* `ws.onopen` reconnect sweep can re-register every active sub
|
|
87
|
+
* without the React hooks having to know about reconnect lifecycle.
|
|
88
|
+
*/
|
|
89
|
+
export interface ReactiveSpec {
|
|
90
|
+
fn_name: string;
|
|
91
|
+
args: unknown;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Push message routed to a reactive subscription handler. `result`
|
|
95
|
+
* fires on initial run + every time the server's re-run produces a
|
|
96
|
+
* value whose hash differs from the last push. `error` fires when
|
|
97
|
+
* the server can't execute the handler (function not registered,
|
|
98
|
+
* reactive runtime unavailable, runtime error in user code).
|
|
99
|
+
*/
|
|
100
|
+
export type ReactiveMessage = {
|
|
101
|
+
kind: "result";
|
|
102
|
+
result: unknown;
|
|
103
|
+
} | {
|
|
104
|
+
kind: "error";
|
|
105
|
+
code: string;
|
|
106
|
+
message: string;
|
|
107
|
+
};
|
|
108
|
+
export type Row = Record<string, unknown>;
|
|
109
|
+
export type TransportType = "websocket" | "sse" | "poll";
|
|
110
|
+
/**
|
|
111
|
+
* Coarse connection state for UI consumers.
|
|
112
|
+
*
|
|
113
|
+
* - `connecting` — engine is starting up; first WS handshake hasn't
|
|
114
|
+
* completed yet. Apps typically render their initial
|
|
115
|
+
* skeleton during this state.
|
|
116
|
+
* - `connected` — WS is open and we've stayed open long enough to
|
|
117
|
+
* consider it stable (5s on the wire). Live queries
|
|
118
|
+
* are receiving real-time updates.
|
|
119
|
+
* - `reconnecting` — WS dropped (network blip, autostop) and the
|
|
120
|
+
* engine is backing off + retrying.
|
|
121
|
+
* - `offline` — engine has been stopped via `engine.stop()` or
|
|
122
|
+
* was never started. No retries pending.
|
|
123
|
+
*/
|
|
124
|
+
export type SyncConnectionStatus = "connecting" | "connected" | "reconnecting" | "offline";
|
package/package.json
CHANGED
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.293",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "src/index.ts",
|
|
9
|
-
"types": "
|
|
8
|
+
"main": "./src/index.ts",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"check": "tsc -p tsconfig.json --noEmit"
|
|
11
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"build": "tsc -p tsconfig.build.json",
|
|
13
|
+
"prepack": "bun run build"
|
|
12
14
|
},
|
|
13
15
|
"devDependencies": {
|
|
14
16
|
"fake-indexeddb": "^6.2.5"
|
|
15
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"dist"
|
|
21
|
+
]
|
|
16
22
|
}
|