@lunora/client 0.0.0 → 1.0.0-alpha.2
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/LICENSE.md +105 -0
- package/README.md +111 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/auth/index.d.mts +20 -0
- package/dist/auth/index.d.ts +20 -0
- package/dist/auth/index.mjs +60 -0
- package/dist/index.d.mts +281 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.mjs +14 -0
- package/dist/packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs +4 -0
- package/dist/packem_shared/DEFAULT_MAX_BUFFER-BDkqO5PW.mjs +107 -0
- package/dist/packem_shared/LunoraClient-UiULzH_1.mjs +2165 -0
- package/dist/packem_shared/OfflineQueue-D5p_QgF_.mjs +127 -0
- package/dist/packem_shared/SKIP-vItZChkw.mjs +50 -0
- package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +26 -0
- package/dist/packem_shared/applyDelta-4jFGTPA3.mjs +61 -0
- package/dist/packem_shared/createAsyncStoragePersistence-1Z5BZ8RC.mjs +45 -0
- package/dist/packem_shared/createInMemoryBookmarkStorage-BoN7a7TH.mjs +11 -0
- package/dist/packem_shared/createInMemoryPersistence-CW82inU5.mjs +105 -0
- package/dist/packem_shared/createInMemoryQueryCache-B1PQ9Twl.mjs +138 -0
- package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +36 -0
- package/dist/packem_shared/createMutationRunner-BqsavzvG.mjs +21 -0
- package/dist/packem_shared/createReconnect-Di_-oHH7.mjs +22 -0
- package/dist/packem_shared/createServerClient-BjZc3gD8.mjs +11 -0
- package/dist/packem_shared/deserializePreloaded-C0eJTY_W.mjs +4 -0
- package/dist/packem_shared/getServerSession-8jXewqxd.mjs +13 -0
- package/dist/packem_shared/lunora-client.d-DGvyuJ_p.d.mts +1597 -0
- package/dist/packem_shared/lunora-client.d-DGvyuJ_p.d.ts +1597 -0
- package/dist/packem_shared/preload.d-BoDmFqSG.d.ts +20 -0
- package/dist/packem_shared/preload.d-dSaRMuhL.d.mts +20 -0
- package/dist/packem_shared/preloadQuery-lobFkD2Z.mjs +13 -0
- package/dist/pagination/index.d.mts +82 -0
- package/dist/pagination/index.d.ts +82 -0
- package/dist/pagination/index.mjs +61 -0
- package/dist/query/index.d.mts +62 -0
- package/dist/query/index.d.ts +62 -0
- package/dist/query/index.mjs +1 -0
- package/dist/ssr/index.d.mts +115 -0
- package/dist/ssr/index.d.ts +115 -0
- package/dist/ssr/index.mjs +4 -0
- package/package.json +53 -17
|
@@ -0,0 +1,1597 @@
|
|
|
1
|
+
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthSession } from '@lunora/runtime';
|
|
2
|
+
/** The registered function kinds a {@link FunctionReference} can describe. `stream` is a query that yields multiple frames over the WS. */
|
|
3
|
+
type FunctionKind = "action" | "mutation" | "query" | "stream";
|
|
4
|
+
/**
|
|
5
|
+
* Opaque reference to a registered function emitted by `@lunora/codegen`.
|
|
6
|
+
*
|
|
7
|
+
* At runtime it carries the `<file>:<function>` identifier in `__lunoraRef`.
|
|
8
|
+
* Generated declarations decorate this with phantom type parameters so the
|
|
9
|
+
* client can infer args / return values per call site.
|
|
10
|
+
*/
|
|
11
|
+
interface FunctionReference<Kind extends FunctionKind = FunctionKind, Args = unknown, Return = unknown> {
|
|
12
|
+
/**
|
|
13
|
+
* Phantom marker carrying the `Kind`/`Args`/`Return` type parameters for
|
|
14
|
+
* inference. Never present at runtime; declared as a covariant (output)
|
|
15
|
+
* position so a concrete reference stays assignable to a widened one.
|
|
16
|
+
*/
|
|
17
|
+
readonly __lunoraPhantom?: {
|
|
18
|
+
args: Args;
|
|
19
|
+
kind: Kind;
|
|
20
|
+
returns: Return;
|
|
21
|
+
};
|
|
22
|
+
readonly __lunoraRef: string;
|
|
23
|
+
}
|
|
24
|
+
/** Extract the args type from a {@link FunctionReference}. */
|
|
25
|
+
type ArgsOf<F> = F extends FunctionReference<infer _K, infer A, infer _R> ? A : never;
|
|
26
|
+
/** Extract the return type from a {@link FunctionReference}. */
|
|
27
|
+
type ReturnOf<F> = F extends FunctionReference<infer _K, infer _A, infer R> ? R : never;
|
|
28
|
+
type Unsubscribe = () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Serializable result of `preloadQuery`. Produced on the server during SSR,
|
|
31
|
+
* embedded in the rendered HTML, then handed to `usePreloadedQuery` on the
|
|
32
|
+
* client so the first render shows the server value with no loading flash
|
|
33
|
+
* before a live subscription attaches. Every field survives `JSON.stringify`.
|
|
34
|
+
*/
|
|
35
|
+
interface Preloaded<T = unknown> {
|
|
36
|
+
readonly __lunoraPreloaded: true;
|
|
37
|
+
readonly args: Record<string, unknown>;
|
|
38
|
+
readonly functionPath: string;
|
|
39
|
+
readonly shardKey?: string;
|
|
40
|
+
readonly value: T;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pluggable storage for the `x-d1-bookmark` value used to provide
|
|
44
|
+
* read-your-writes between a mutation and subsequent queries.
|
|
45
|
+
*/
|
|
46
|
+
interface BookmarkStorage {
|
|
47
|
+
get: () => string | null;
|
|
48
|
+
set: (value: string | null) => void;
|
|
49
|
+
}
|
|
50
|
+
interface ReconnectOptions {
|
|
51
|
+
initialDelayMs?: number;
|
|
52
|
+
jitter?: boolean;
|
|
53
|
+
maxDelayMs?: number;
|
|
54
|
+
}
|
|
55
|
+
/** Which durable-storage operation failed, passed to {@link OfflineQueueOptions.onPersistenceError}. */
|
|
56
|
+
type PersistenceOperation = "append" | "clear" | "load" | "remove";
|
|
57
|
+
/** Context handed to a persistence-error handler. */
|
|
58
|
+
interface PersistenceErrorContext {
|
|
59
|
+
readonly error: unknown;
|
|
60
|
+
/** The mutation id involved, when the failing op was scoped to one (`append`/`remove`). */
|
|
61
|
+
readonly mutationId?: string;
|
|
62
|
+
readonly operation: PersistenceOperation;
|
|
63
|
+
}
|
|
64
|
+
interface OfflineQueueOptions {
|
|
65
|
+
maxItems?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Invoked when a {@link PersistenceAdapter} call rejects (e.g. IndexedDB quota
|
|
68
|
+
* exceeded). Without a handler, failures are logged via `console.warn` so they
|
|
69
|
+
* are never fully silent. Note: a failed `append` means the write is queued in
|
|
70
|
+
* memory but NOT durable — it will not survive a reload.
|
|
71
|
+
*/
|
|
72
|
+
onPersistenceError?: (context: PersistenceErrorContext) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Queue mutations issued before a shard's first successful WebSocket
|
|
75
|
+
* connect (defaults to `false`). The standard behaviour (`LunoraClient`'s
|
|
76
|
+
* `mutation()`) queues only when the targeted shard has been connected at
|
|
77
|
+
* least once (`wasEverConnected`), so the registry / resubscribe handshake
|
|
78
|
+
* has run. Set this to `true` for offline-first apps that want to enqueue
|
|
79
|
+
* writes on the very first session before the WS is up.
|
|
80
|
+
*/
|
|
81
|
+
queueBeforeFirstConnect?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Serializable shape of an offline mutation, durably stored by a
|
|
85
|
+
* {@link PersistenceAdapter} so queued writes survive a reload/crash. The live
|
|
86
|
+
* `resolve`/`reject` callbacks of an in-flight `QueuedMutation` are *not*
|
|
87
|
+
* persisted — a restored mutation is replayed with no original awaiter.
|
|
88
|
+
*/
|
|
89
|
+
interface PersistedMutation {
|
|
90
|
+
args: Record<string, unknown>;
|
|
91
|
+
functionPath: string;
|
|
92
|
+
id: string;
|
|
93
|
+
/**
|
|
94
|
+
* Issuing identity fingerprint, persisted so a hydrated write replays only
|
|
95
|
+
* under the identity that queued it (`null` = queued while signed out).
|
|
96
|
+
* Absent on records written by older client versions, which replay under
|
|
97
|
+
* the ambient identity for back-compat.
|
|
98
|
+
*/
|
|
99
|
+
identity?: string | null;
|
|
100
|
+
shardKey?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Durable store for the offline mutation queue. The default client keeps the
|
|
104
|
+
* queue in memory; supplying an adapter (e.g. `createIndexedDbPersistence`)
|
|
105
|
+
* makes queued writes survive a page reload. Implementations must preserve FIFO
|
|
106
|
+
* (enqueue) order in `PersistenceAdapter.load`.
|
|
107
|
+
*
|
|
108
|
+
* Replay semantics are at-least-once: a mutation is removed only after the
|
|
109
|
+
* server confirms (or rejects) it, so a crash between commit and `remove` can
|
|
110
|
+
* replay it again on the next load.
|
|
111
|
+
*/
|
|
112
|
+
interface PersistenceAdapter {
|
|
113
|
+
/** Append a mutation to durable storage (called on enqueue). */
|
|
114
|
+
append: (mutation: PersistedMutation) => Promise<void>;
|
|
115
|
+
/** Drop every persisted mutation (e.g. on logout). */
|
|
116
|
+
clear: () => Promise<void>;
|
|
117
|
+
/** Load all persisted mutations in FIFO order — called once at startup. */
|
|
118
|
+
load: () => Promise<PersistedMutation[]>;
|
|
119
|
+
/** Remove a mutation by id once it has been replayed (resolved or rejected). */
|
|
120
|
+
remove: (id: string) => Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* One persisted query result in the durable read cache (Pillar 2). Keyed in the
|
|
124
|
+
* store by `shardKey + functionPath + argsKey`; the record carries everything
|
|
125
|
+
* needed to render offline on reload and to resume the live subscription.
|
|
126
|
+
*/
|
|
127
|
+
interface CachedQuery {
|
|
128
|
+
/**
|
|
129
|
+
* Issuing identity fingerprint (same shape the offline queue stamps). A
|
|
130
|
+
* cached value only hydrates when it matches the current identity, so a
|
|
131
|
+
* signed-out cache never leaks into a new session. `null` = cached while
|
|
132
|
+
* signed out.
|
|
133
|
+
*/
|
|
134
|
+
identity: string | null;
|
|
135
|
+
/**
|
|
136
|
+
* The `cursor` high-watermark this value reflects, replayed as `sinceSeq`
|
|
137
|
+
* on reconnect so the server can resume instead of re-snapshotting. Absent
|
|
138
|
+
* when the value predates CDC / no cursor was advertised.
|
|
139
|
+
*/
|
|
140
|
+
serverCursor?: number;
|
|
141
|
+
/**
|
|
142
|
+
* The CDC `epoch` the `serverCursor` belongs to, replayed as `sinceEpoch`
|
|
143
|
+
* on reconnect so the server only resumes when the client is still on the
|
|
144
|
+
* same changelog timeline. Absent when no epoch was advertised.
|
|
145
|
+
*/
|
|
146
|
+
serverEpoch?: string;
|
|
147
|
+
/** Wall-clock millis the value was written — drives LRU eviction. */
|
|
148
|
+
ts: number;
|
|
149
|
+
/** The full query result last seen from the server. */
|
|
150
|
+
value: unknown;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Durable store for the client read cache (Pillar 2): query results survive a
|
|
154
|
+
* reload so reads hydrate from disk and render immediately while the socket
|
|
155
|
+
* reconnects. Opt-in via {@link LunoraClientOptions.queryCache}; omit to keep
|
|
156
|
+
* reads in memory only (today's behaviour). Mirrors {@link PersistenceAdapter}'s
|
|
157
|
+
* shape over the same IndexedDB plumbing.
|
|
158
|
+
*/
|
|
159
|
+
interface QueryCacheAdapter {
|
|
160
|
+
/** Drop every cached query (e.g. on logout / identity change). */
|
|
161
|
+
clear: () => Promise<void>;
|
|
162
|
+
/** Load every cached query — called once at startup to hydrate reads. */
|
|
163
|
+
load: () => Promise<(CachedQuery & {
|
|
164
|
+
key: string;
|
|
165
|
+
})[]>;
|
|
166
|
+
/** Upsert one cached query by key (called when a subscription value advances). */
|
|
167
|
+
put: (key: string, entry: CachedQuery) => Promise<void>;
|
|
168
|
+
/** Remove one cached query by key. */
|
|
169
|
+
remove: (key: string) => Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
interface LunoraClientOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Base path the worker mounts better-auth at, used by the client's
|
|
174
|
+
* `getCurrentUser()` to reach the `get-session` route. Defaults to
|
|
175
|
+
* `/api/auth` (matching `@lunora/auth`'s `DEFAULT_AUTH_BASE_PATH`).
|
|
176
|
+
*/
|
|
177
|
+
authBasePath?: string;
|
|
178
|
+
bookmarkStorage?: BookmarkStorage;
|
|
179
|
+
/**
|
|
180
|
+
* Default app context sent in the `connect` envelope right after each socket
|
|
181
|
+
* opens, forwarded to the server's `onConnect`/`onDisconnect` lifecycle hooks
|
|
182
|
+
* as `event.context`. A per-shard context registered via
|
|
183
|
+
* `setConnectionContext` overrides this for that shard. Omit when no lifecycle
|
|
184
|
+
* hook needs connection context.
|
|
185
|
+
*/
|
|
186
|
+
connectionContext?: Record<string, unknown>;
|
|
187
|
+
fetch?: typeof fetch;
|
|
188
|
+
/**
|
|
189
|
+
* Interval (ms) between keepalive pings sent on each open subscription
|
|
190
|
+
* socket. The server answers them via the Durable Object's hibernation
|
|
191
|
+
* auto-response WITHOUT waking the DO, so an idle socket stays alive across
|
|
192
|
+
* hibernation without a billable wakeup. Defaults to 30000 (30s); set to
|
|
193
|
+
* `0` (or a negative value) to disable the heartbeat entirely.
|
|
194
|
+
*/
|
|
195
|
+
heartbeatIntervalMs?: number;
|
|
196
|
+
offlineQueue?: OfflineQueueOptions;
|
|
197
|
+
/** Durable store for the offline mutation queue; omit to keep it in memory. */
|
|
198
|
+
persistence?: PersistenceAdapter;
|
|
199
|
+
/**
|
|
200
|
+
* Durable store for the read cache (Pillar 2). When supplied, query results
|
|
201
|
+
* are persisted as their subscriptions advance and hydrated on construction
|
|
202
|
+
* so a reload renders cached data before the socket reconnects, then resumes
|
|
203
|
+
* the live subscription from the persisted cursor. Omit (or pass `false`) to
|
|
204
|
+
* keep reads in memory only — the default, unchanged behaviour.
|
|
205
|
+
*/
|
|
206
|
+
queryCache?: QueryCacheAdapter | false;
|
|
207
|
+
reconnect?: ReconnectOptions;
|
|
208
|
+
url: string;
|
|
209
|
+
WebSocket?: typeof WebSocket;
|
|
210
|
+
/**
|
|
211
|
+
* Token appended to the WebSocket URL as `?token=…`. The server matches it
|
|
212
|
+
* against `LUNORA_WS_BEARER` (to clear the upgrade gate) and/or
|
|
213
|
+
* `LUNORA_ADMIN_TOKEN` (to authorize `__lunora_admin__:*` subscriptions —
|
|
214
|
+
* what the studio sets it to). Browsers can't set headers on the
|
|
215
|
+
* `WebSocket` constructor, so the query parameter is the only channel; it
|
|
216
|
+
* ends up in server logs and history, so prefer a short-lived rotating
|
|
217
|
+
* token in production.
|
|
218
|
+
*/
|
|
219
|
+
wsToken?: string;
|
|
220
|
+
wsUrl?: string;
|
|
221
|
+
}
|
|
222
|
+
/** Wire envelope sent on `POST /_lunora/rpc`. */
|
|
223
|
+
interface RpcEnvelope {
|
|
224
|
+
args?: Record<string, unknown>;
|
|
225
|
+
functionPath: string;
|
|
226
|
+
shardKey?: string;
|
|
227
|
+
}
|
|
228
|
+
/** Wire response from the shard's `/rpc` endpoint (forwarded by the runtime). */
|
|
229
|
+
type RpcResponseBody = {
|
|
230
|
+
result: unknown;
|
|
231
|
+
} | {
|
|
232
|
+
error: {
|
|
233
|
+
code: string;
|
|
234
|
+
message: string;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
/** Subscription protocol — client → server. */
|
|
238
|
+
interface ClientSubscribeMessage {
|
|
239
|
+
id: string;
|
|
240
|
+
/**
|
|
241
|
+
* `sinceSeq` is the persisted `cursor` high-watermark the client last saw
|
|
242
|
+
* for this shard (Pillar 1b resume). Present only when a durable
|
|
243
|
+
* {@link QueryCacheAdapter} restored a cached value with a cursor; the
|
|
244
|
+
* server replies with a lightweight `resume` frame instead of a full
|
|
245
|
+
* snapshot when nothing the query reads changed since it. Absent on a
|
|
246
|
+
* first-time subscribe.
|
|
247
|
+
*/
|
|
248
|
+
query: {
|
|
249
|
+
args?: Record<string, unknown>;
|
|
250
|
+
functionPath?: string;
|
|
251
|
+
sinceEpoch?: string;
|
|
252
|
+
sinceSeq?: number;
|
|
253
|
+
table?: string;
|
|
254
|
+
};
|
|
255
|
+
type: "subscribe";
|
|
256
|
+
}
|
|
257
|
+
interface ClientUnsubscribeMessage {
|
|
258
|
+
id: string;
|
|
259
|
+
type: "unsubscribe";
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* One-shot control frame sent right after the socket opens. Registers the
|
|
263
|
+
* connection's app `context` (e.g. `{ roomId, sessionId }`) with the server and
|
|
264
|
+
* fires the `onConnect` lifecycle hooks; the same context is replayed to
|
|
265
|
+
* `onDisconnect` when the socket drops.
|
|
266
|
+
*/
|
|
267
|
+
interface ClientConnectMessage {
|
|
268
|
+
context?: Record<string, unknown>;
|
|
269
|
+
id: string;
|
|
270
|
+
type: "connect";
|
|
271
|
+
}
|
|
272
|
+
interface ClientAckMessage {
|
|
273
|
+
id: string;
|
|
274
|
+
type: "ack";
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Start a streaming query. The id namespaces a fresh stream and is echoed on
|
|
278
|
+
* every {@link ServerChunkMessage} the server pushes back. Cancel a running
|
|
279
|
+
* stream by sending a {@link ClientUnsubscribeMessage} with the same id —
|
|
280
|
+
* subscription and stream id-spaces share the cancel channel; the prefix
|
|
281
|
+
* (`sub_*` vs `stream_*`) keeps the local registries searchable.
|
|
282
|
+
*/
|
|
283
|
+
interface ClientStreamMessage {
|
|
284
|
+
id: string;
|
|
285
|
+
query: {
|
|
286
|
+
args?: Record<string, unknown>;
|
|
287
|
+
functionPath: string;
|
|
288
|
+
shardKey?: string;
|
|
289
|
+
};
|
|
290
|
+
type: "stream";
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Join or leave a whisper `topic` — an app-chosen ephemeral channel scoped to a
|
|
294
|
+
* shard. While joined, the client receives every {@link ServerWhisperMessage}
|
|
295
|
+
* other members broadcast to the topic.
|
|
296
|
+
*/
|
|
297
|
+
interface ClientWhisperSubscribeMessage {
|
|
298
|
+
topic: string;
|
|
299
|
+
type: "whisper_subscribe" | "whisper_unsubscribe";
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Broadcast ephemeral `data` to the topic's other members on the shard. The
|
|
303
|
+
* payload is relayed verbatim with no server-side persistence (no SQLite/CDC
|
|
304
|
+
* write) — for typing indicators, live cursors, presence pings. The sender does
|
|
305
|
+
* not receive its own whisper.
|
|
306
|
+
*/
|
|
307
|
+
interface ClientWhisperMessage {
|
|
308
|
+
data?: unknown;
|
|
309
|
+
topic: string;
|
|
310
|
+
type: "whisper";
|
|
311
|
+
}
|
|
312
|
+
type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
|
|
313
|
+
/** Subscription protocol — server → client. */
|
|
314
|
+
interface ServerDataMessage {
|
|
315
|
+
/**
|
|
316
|
+
* The `__cdc_log` high-watermark covered by this frame (Pillar 1b). The
|
|
317
|
+
* client persists it as the query's `serverCursor` and replays it as
|
|
318
|
+
* `sinceSeq` on the next reconnect. Absent on shards that never enabled CDC.
|
|
319
|
+
*/
|
|
320
|
+
cursor?: number;
|
|
321
|
+
data?: unknown;
|
|
322
|
+
delta?: unknown;
|
|
323
|
+
/** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
|
|
324
|
+
epoch?: string;
|
|
325
|
+
id: string;
|
|
326
|
+
type: "data" | "delta";
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Lightweight resume acknowledgement (Pillar 1b): the server determined that
|
|
330
|
+
* nothing the subscription reads changed since the client's `sinceSeq`, so it
|
|
331
|
+
* skips re-sending the snapshot. The client keeps its cached value and only
|
|
332
|
+
* advances `serverCursor` to `cursor`.
|
|
333
|
+
*/
|
|
334
|
+
interface ServerResumeMessage {
|
|
335
|
+
cursor?: number;
|
|
336
|
+
/** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
|
|
337
|
+
epoch?: string;
|
|
338
|
+
id: string;
|
|
339
|
+
type: "resume";
|
|
340
|
+
}
|
|
341
|
+
interface ServerErrorMessage {
|
|
342
|
+
error?: unknown;
|
|
343
|
+
id?: string;
|
|
344
|
+
message?: string;
|
|
345
|
+
type: "error";
|
|
346
|
+
}
|
|
347
|
+
interface ServerAckMessage {
|
|
348
|
+
id: string;
|
|
349
|
+
type: "ack";
|
|
350
|
+
}
|
|
351
|
+
interface ServerCompleteMessage {
|
|
352
|
+
id: string;
|
|
353
|
+
type: "complete";
|
|
354
|
+
}
|
|
355
|
+
/** One frame of a streaming query — `data` carries the user-yielded chunk. */
|
|
356
|
+
interface ServerChunkMessage {
|
|
357
|
+
data: unknown;
|
|
358
|
+
id: string;
|
|
359
|
+
type: "chunk";
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* An ephemeral whisper relayed from another member of `topic` on the same shard
|
|
363
|
+
* (AnyCable-style whispering). `data` is the sender's payload verbatim; `from`
|
|
364
|
+
* is the sender's verified user id when known (absent for an anonymous sender).
|
|
365
|
+
* Never persisted server-side.
|
|
366
|
+
*/
|
|
367
|
+
interface ServerWhisperMessage {
|
|
368
|
+
data: unknown;
|
|
369
|
+
from?: string;
|
|
370
|
+
topic: string;
|
|
371
|
+
type: "whisper";
|
|
372
|
+
}
|
|
373
|
+
type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerResumeMessage | ServerWhisperMessage;
|
|
374
|
+
/**
|
|
375
|
+
* The authenticated user as exposed client-side, mirroring better-auth's
|
|
376
|
+
* `user` row (the `user` field of the `get-session` response). Kept minimal
|
|
377
|
+
* and structural — only `id` is guaranteed; the rest are the common better-auth
|
|
378
|
+
* fields, and the index signature carries any plugin-contributed extras.
|
|
379
|
+
*/
|
|
380
|
+
interface User {
|
|
381
|
+
readonly createdAt?: NullableTimestamp;
|
|
382
|
+
readonly email?: null | string;
|
|
383
|
+
readonly emailVerified?: boolean | null;
|
|
384
|
+
readonly id: string;
|
|
385
|
+
readonly image?: null | string;
|
|
386
|
+
readonly name?: null | string;
|
|
387
|
+
readonly [key: string]: unknown;
|
|
388
|
+
readonly updatedAt?: NullableTimestamp;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* One pending scheduled function, as returned by the worker's
|
|
392
|
+
* `GET /_lunora/admin/scheduled` endpoint. Mirrors `@lunora/scheduler`'s
|
|
393
|
+
* `ScheduleRecord` structurally so the client carries no dependency on it.
|
|
394
|
+
*/
|
|
395
|
+
interface ScheduleRecord {
|
|
396
|
+
args: Record<string, unknown>;
|
|
397
|
+
/**
|
|
398
|
+
* Dispatch attempts already made. Absent (treated as 0) until the first
|
|
399
|
+
* failure; on a dead-letter record it is the exhausted count (> the retry
|
|
400
|
+
* budget). Surfaced so the studio can show how hard a job tried before it
|
|
401
|
+
* was parked.
|
|
402
|
+
*/
|
|
403
|
+
attempts?: number;
|
|
404
|
+
enqueuedAt: number;
|
|
405
|
+
functionPath: string;
|
|
406
|
+
id: string;
|
|
407
|
+
/** Logical workpool the job is routed to (concurrency-gated), when any. */
|
|
408
|
+
pool?: string;
|
|
409
|
+
scheduledFor: number;
|
|
410
|
+
shardKey?: string;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* One workpool's live backlog, as returned by the worker's
|
|
414
|
+
* `GET /_lunora/admin/scheduled/status` endpoint. Mirrors `@lunora/scheduler`'s
|
|
415
|
+
* `SchedulerPoolStatus` structurally so the client carries no dependency on it.
|
|
416
|
+
*/
|
|
417
|
+
interface SchedulerPoolStatus {
|
|
418
|
+
/** Jobs currently dispatched-but-not-yet-completed (the held concurrency slots). */
|
|
419
|
+
inFlight: number;
|
|
420
|
+
/** The pool's concurrency cap. */
|
|
421
|
+
maxConcurrency: number;
|
|
422
|
+
/** The logical workpool name. */
|
|
423
|
+
name: string;
|
|
424
|
+
/** Pending jobs routed to this pool but not yet dispatched. */
|
|
425
|
+
queued: number;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The app-level scheduler backlog, as returned by the worker's
|
|
429
|
+
* `GET /_lunora/admin/scheduled/status` endpoint. `pools` is the per-pool
|
|
430
|
+
* breakdown; `backlog` and `inFlight` are the app-wide sums of `queued` and
|
|
431
|
+
* `inFlight` across every pool — the headline numbers for the studio SLO
|
|
432
|
+
* view. Mirrors `@lunora/scheduler`'s `SchedulerStatus` structurally.
|
|
433
|
+
*/
|
|
434
|
+
interface SchedulerStatus {
|
|
435
|
+
/** Sum of every pool's `queued` count — the total pending backlog. */
|
|
436
|
+
backlog: number;
|
|
437
|
+
/** Sum of every pool's `inFlight` count — the total held concurrency slots. */
|
|
438
|
+
inFlight: number;
|
|
439
|
+
/** Per-pool backlog breakdown. */
|
|
440
|
+
pools: SchedulerPoolStatus[];
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* One shard's request volume, as returned by the worker's
|
|
444
|
+
* `POST /_lunora/admin/shard-traffic` endpoint. The cross-shard traffic feed
|
|
445
|
+
* the studio's `hot_shard` advisor lint consumes: `requests` is the shard's
|
|
446
|
+
* lifetime dispatch total, `shardKey` the DO id name (`""` for the root shard).
|
|
447
|
+
*/
|
|
448
|
+
interface ShardTrafficEntry {
|
|
449
|
+
requests: number;
|
|
450
|
+
shardKey: string;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The whole-shard-set traffic distribution returned by the worker's
|
|
454
|
+
* `POST /_lunora/admin/shard-traffic` endpoint. `shards` is one entry per live
|
|
455
|
+
* shard (a failed shard surfaces with `requests: 0`); `ok`/`failed` count the
|
|
456
|
+
* shards that returned vs. errored. Shaped to feed the advisor's `hot_shard`
|
|
457
|
+
* lint after the studio tags each entry with its sharded function `group`.
|
|
458
|
+
*/
|
|
459
|
+
interface ShardTrafficResult {
|
|
460
|
+
failed: number;
|
|
461
|
+
ok: number;
|
|
462
|
+
shards: ShardTrafficEntry[];
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* One object in the storage bucket, as returned by the worker's
|
|
466
|
+
* `GET /_lunora/admin/storage` endpoint. Mirrors `@lunora/storage`'s
|
|
467
|
+
* `R2ObjectLike` structurally.
|
|
468
|
+
*/
|
|
469
|
+
interface StorageObject {
|
|
470
|
+
customMetadata?: Record<string, string>;
|
|
471
|
+
etag: string;
|
|
472
|
+
httpMetadata?: {
|
|
473
|
+
contentType?: string;
|
|
474
|
+
};
|
|
475
|
+
key: string;
|
|
476
|
+
size: number;
|
|
477
|
+
/**
|
|
478
|
+
* When the object was stored. R2 emits a `Date`, which JSON-serializes to an
|
|
479
|
+
* ISO string over the wire; a mock may supply epoch ms — so consumers should
|
|
480
|
+
* normalise via `new Date(uploaded)`. Absent if the backend didn't report it.
|
|
481
|
+
*/
|
|
482
|
+
uploaded?: number | string;
|
|
483
|
+
}
|
|
484
|
+
/** One page of {@link StorageObject}s plus the cursor to fetch the next, if any. */
|
|
485
|
+
interface StorageListPage {
|
|
486
|
+
cursor?: string;
|
|
487
|
+
objects: StorageObject[];
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* One argument of a registered function, derived from its `v.*` validator by the
|
|
491
|
+
* worker. A compact signature shape — enough to render a function's API without
|
|
492
|
+
* the build-time codegen types.
|
|
493
|
+
*/
|
|
494
|
+
interface FunctionArgumentDescriptor {
|
|
495
|
+
/** Element validator kind for an `array` arg (one level), e.g. `string`. */
|
|
496
|
+
element?: string;
|
|
497
|
+
/** The (optional-unwrapped) validator kind, e.g. `string`, `id`, `object`. */
|
|
498
|
+
kind: string;
|
|
499
|
+
/** The argument name. */
|
|
500
|
+
name: string;
|
|
501
|
+
/** True when the arg is wrapped in `v.optional(...)`. */
|
|
502
|
+
optional: boolean;
|
|
503
|
+
/** Target table for an `id` arg (`v.id("table")`). */
|
|
504
|
+
table?: string;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* One registered function, as returned by the worker's
|
|
508
|
+
* `GET /_lunora/admin/functions` endpoint: its `<file>:<function>` path, which
|
|
509
|
+
* client method (`query` / `mutation` / `action`) invokes it, and its argument
|
|
510
|
+
* signature. `args` is absent on responses from an older worker.
|
|
511
|
+
*/
|
|
512
|
+
interface FunctionDescriptor {
|
|
513
|
+
args?: FunctionArgumentDescriptor[];
|
|
514
|
+
kind: "action" | "mutation" | "query";
|
|
515
|
+
path: string;
|
|
516
|
+
}
|
|
517
|
+
/** A `.global()` (D1-backed) table plus its row count, from `/_lunora/admin/global/tables`. */
|
|
518
|
+
interface GlobalTableInfo {
|
|
519
|
+
name: string;
|
|
520
|
+
rowCount: number;
|
|
521
|
+
}
|
|
522
|
+
/** A window of rows from one global table, from `/_lunora/admin/global/table`. */
|
|
523
|
+
interface GlobalTablePage {
|
|
524
|
+
columns: string[];
|
|
525
|
+
/** FK columns (local column → referenced table) for external tables with real `REFERENCES` constraints, from `PRAGMA foreign_key_list`. */
|
|
526
|
+
refs?: Record<string, string>;
|
|
527
|
+
rows: Record<string, unknown>[];
|
|
528
|
+
total: number;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* One equality constraint a facet-value click adds to the global browser's view
|
|
532
|
+
* (`column = value`). `value` is the raw stored scalar the facet returned, sent
|
|
533
|
+
* as-is and bound server-side, so it never injects SQL.
|
|
534
|
+
*/
|
|
535
|
+
interface GlobalFilterClause {
|
|
536
|
+
column: string;
|
|
537
|
+
value: unknown;
|
|
538
|
+
}
|
|
539
|
+
/** One distinct value of a faceted global column with its row count, from `/_lunora/admin/global/facet`. */
|
|
540
|
+
interface GlobalFacetValue {
|
|
541
|
+
count: number;
|
|
542
|
+
value: unknown;
|
|
543
|
+
}
|
|
544
|
+
/** Per-column distinct-value summary for the global browser, from `/_lunora/admin/global/facet`. */
|
|
545
|
+
interface GlobalFacetResult {
|
|
546
|
+
truncated: boolean;
|
|
547
|
+
values: GlobalFacetValue[];
|
|
548
|
+
}
|
|
549
|
+
/** A nullable timestamp field as better-auth serializes it: epoch-ms, ISO string, or null. */
|
|
550
|
+
type NullableTimestamp = null | number | string;
|
|
551
|
+
/** A workflow instance's lifecycle status. Mirrors Cloudflare's `InstanceStatus`. */
|
|
552
|
+
type WorkflowInstanceStatus = "complete" | "errored" | "paused" | "queued" | "running" | "terminated" | "unknown" | "waiting" | "waitingForPause";
|
|
553
|
+
/** The lifecycle mutations the status endpoint accepts. */
|
|
554
|
+
type WorkflowInstanceAction = "pause" | "resume" | "terminate";
|
|
555
|
+
/** One row of the workflow-instances list. */
|
|
556
|
+
interface WorkflowInstanceSummary {
|
|
557
|
+
createdOn?: string;
|
|
558
|
+
endedOn?: string;
|
|
559
|
+
id: string;
|
|
560
|
+
startedOn?: string;
|
|
561
|
+
status: WorkflowInstanceStatus;
|
|
562
|
+
}
|
|
563
|
+
/** One durable step of an instance's execution timeline. */
|
|
564
|
+
interface WorkflowStepDetail {
|
|
565
|
+
/** 1-based attempt count (`> 1` means the step retried). */
|
|
566
|
+
attempts?: number;
|
|
567
|
+
end?: string;
|
|
568
|
+
error?: unknown;
|
|
569
|
+
name: string;
|
|
570
|
+
output?: unknown;
|
|
571
|
+
start?: string;
|
|
572
|
+
success?: boolean;
|
|
573
|
+
/** `step` / `sleep` / `waitForEvent` / … (Cloudflare's step `type`). */
|
|
574
|
+
type?: string;
|
|
575
|
+
}
|
|
576
|
+
/** A workflow instance's full detail: summary plus params/output/error and the step timeline. */
|
|
577
|
+
interface WorkflowInstanceDetail extends WorkflowInstanceSummary {
|
|
578
|
+
error?: unknown;
|
|
579
|
+
output?: unknown;
|
|
580
|
+
params?: unknown;
|
|
581
|
+
steps: WorkflowStepDetail[];
|
|
582
|
+
}
|
|
583
|
+
/** A page of workflow instances. */
|
|
584
|
+
interface WorkflowInstancePage {
|
|
585
|
+
instances: WorkflowInstanceSummary[];
|
|
586
|
+
page: number;
|
|
587
|
+
perPage: number;
|
|
588
|
+
totalCount?: number;
|
|
589
|
+
}
|
|
590
|
+
type SubscriptionCallback = (data: unknown) => void;
|
|
591
|
+
/** A subscription-scoped error the server pushed for this subscription id. */
|
|
592
|
+
interface SubscriptionError {
|
|
593
|
+
code?: string;
|
|
594
|
+
message: string;
|
|
595
|
+
}
|
|
596
|
+
type SubscriptionErrorCallback = (error: SubscriptionError) => void;
|
|
597
|
+
interface SubscriptionState {
|
|
598
|
+
/** True once the server has acked the subscription on the current socket. */
|
|
599
|
+
acked: boolean;
|
|
600
|
+
readonly args: Record<string, unknown>;
|
|
601
|
+
/**
|
|
602
|
+
* Stable-stringified `args`, computed once at subscribe time. Cached so the
|
|
603
|
+
* optimistic-update fan-out can compare against a mutation's args key without
|
|
604
|
+
* re-serializing every subscription's args on every mutation.
|
|
605
|
+
*/
|
|
606
|
+
readonly argsKey: string;
|
|
607
|
+
readonly callbacks: Set<SubscriptionCallback>;
|
|
608
|
+
/** Notified when the server rejects this subscription (e.g. admin auth). */
|
|
609
|
+
readonly errorCallbacks: Set<SubscriptionErrorCallback>;
|
|
610
|
+
readonly fn: FunctionReference;
|
|
611
|
+
readonly id: string;
|
|
612
|
+
/** Last known value, used to short-circuit `useQuery`-style consumers. */
|
|
613
|
+
lastValue: unknown;
|
|
614
|
+
/**
|
|
615
|
+
* The `__cdc_log` high-watermark (`cursor`) the `lastValue` reflects,
|
|
616
|
+
* captured from the last `data`/`delta`/`resume` frame. Persisted to the
|
|
617
|
+
* durable read cache and replayed as `sinceSeq` on reconnect so the server
|
|
618
|
+
* can resume instead of re-snapshotting (Pillar 1b/2). Absent until the
|
|
619
|
+
* first cursor-stamped frame arrives.
|
|
620
|
+
*/
|
|
621
|
+
serverCursor?: number;
|
|
622
|
+
/**
|
|
623
|
+
* The CDC `epoch` token the `serverCursor` belongs to, captured from the
|
|
624
|
+
* same frame. Replayed as `sinceEpoch` on reconnect so the server resumes
|
|
625
|
+
* only when the client is still on the same changelog timeline — a reset or
|
|
626
|
+
* recycled shard advertises a new epoch, forcing a fresh snapshot. Absent
|
|
627
|
+
* until the first epoch-stamped frame arrives.
|
|
628
|
+
*/
|
|
629
|
+
serverEpoch?: string;
|
|
630
|
+
/**
|
|
631
|
+
* Monotonic counter incremented on every server-pushed delta or data.
|
|
632
|
+
* Used by optimistic-update rollback to detect whether the server has
|
|
633
|
+
* already moved past the value we'd otherwise restore.
|
|
634
|
+
*/
|
|
635
|
+
serverVersion: number;
|
|
636
|
+
readonly shardKey?: string;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Active subscription registry. The client keys subscriptions by
|
|
640
|
+
* `(functionPath, JSON.stringify(args), shardKey)` so duplicate calls share a
|
|
641
|
+
* single server-side registration.
|
|
642
|
+
*/
|
|
643
|
+
declare class SubscriptionRegistry {
|
|
644
|
+
static key(functionPath: string, args: Record<string, unknown>, shardKey?: string): string;
|
|
645
|
+
private readonly byKey;
|
|
646
|
+
private readonly byId;
|
|
647
|
+
get(key: string): SubscriptionState | undefined;
|
|
648
|
+
getById(id: string): SubscriptionState | undefined;
|
|
649
|
+
add(state: SubscriptionState): void;
|
|
650
|
+
remove(state: SubscriptionState): void;
|
|
651
|
+
all(): SubscriptionState[];
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Read/write handle over the client's live query cache, handed to a mutation's
|
|
655
|
+
* `withOptimisticUpdate` callback so a single mutation can optimistically patch
|
|
656
|
+
* many subscribed queries at once (Convex's `OptimisticLocalStore` model).
|
|
657
|
+
*
|
|
658
|
+
* `getQuery` reads the current value (server value or any still-pending
|
|
659
|
+
* optimistic override) of a subscribed query; `setQuery` writes an optimistic
|
|
660
|
+
* override on top. Every write is collected as a rollback closure so the whole
|
|
661
|
+
* batch unwinds atomically when the mutation settles or the server advances
|
|
662
|
+
* past it — the same per-subscription rollback machinery the legacy
|
|
663
|
+
* per-call `optimistic` transform uses, generalized to N queries.
|
|
664
|
+
*/
|
|
665
|
+
interface OptimisticLocalStore {
|
|
666
|
+
/**
|
|
667
|
+
* Every loaded subscription on `function_`, regardless of args, paired with
|
|
668
|
+
* the args it was subscribed under. Mirrors Convex's `getAllQueries` — handy
|
|
669
|
+
* when a write must patch every variant of a list query (all channels,
|
|
670
|
+
* all filters) without enumerating their args up front.
|
|
671
|
+
*/
|
|
672
|
+
getAllQueries: <F extends FunctionReference>(function_: F) => {
|
|
673
|
+
args: ArgsOf<F>;
|
|
674
|
+
value: ReturnOf<F> | undefined;
|
|
675
|
+
}[];
|
|
676
|
+
/**
|
|
677
|
+
* Current cached value for the subscribed `(function_, args)` query, or
|
|
678
|
+
* `undefined` when nothing is subscribed/loaded for it. Reflects any
|
|
679
|
+
* optimistic override already written in this batch.
|
|
680
|
+
*/
|
|
681
|
+
getQuery: <F extends FunctionReference>(function_: F, args: ArgsOf<F>) => ReturnOf<F> | undefined;
|
|
682
|
+
/**
|
|
683
|
+
* Write an optimistic override for the subscribed `(function_, args)`
|
|
684
|
+
* query. A no-op (returns without effect) when no subscription matches —
|
|
685
|
+
* mirroring Convex, where you only patch queries the page is watching.
|
|
686
|
+
*/
|
|
687
|
+
setQuery: <F extends FunctionReference>(function_: F, args: ArgsOf<F>, value: ReturnOf<F> | undefined) => void;
|
|
688
|
+
}
|
|
689
|
+
/** A mutation's multi-query optimistic update: read/write the cache via `localStore`. */
|
|
690
|
+
type OptimisticUpdate<Args> = (localStore: OptimisticLocalStore, args: Args) => void;
|
|
691
|
+
/**
|
|
692
|
+
* Build an {@link OptimisticLocalStore} bound to a subscription registry, the
|
|
693
|
+
* mutation's shard key, and the `writeOptimisticToState` primitive. Returns the
|
|
694
|
+
* store plus the ordered rollback closures every `setQuery` produced, so the
|
|
695
|
+
* caller can unwind the whole batch (LIFO) if the mutation later fails — and
|
|
696
|
+
* leave them in place to be GC'd alongside the subscription on success.
|
|
697
|
+
*/
|
|
698
|
+
declare const createLocalStore: (subscriptions: SubscriptionRegistry, shardKey: string | undefined, write: (state: SubscriptionState, next: unknown) => () => void, stableStringify: (value: unknown) => string) => {
|
|
699
|
+
rollbacks: (() => void)[];
|
|
700
|
+
store: OptimisticLocalStore;
|
|
701
|
+
};
|
|
702
|
+
/**
|
|
703
|
+
* Bounded async-iterator queue backing `LunoraClient.stream`.
|
|
704
|
+
*
|
|
705
|
+
* The server pushes one server `chunk` message per yielded value while the
|
|
706
|
+
* client iterates with `for await (const chunk of stream)`. A producer that
|
|
707
|
+
* outruns its consumer would otherwise OOM the page, so the buffer is bounded
|
|
708
|
+
* — exceeding {@link DEFAULT_MAX_BUFFER} surfaces a `STREAM_BACKPRESSURE`
|
|
709
|
+
* error to the iterator (and to the server-side cancel path).
|
|
710
|
+
*
|
|
711
|
+
* The queue is closed exactly once via {@link StreamHandle.complete} (success)
|
|
712
|
+
* or {@link StreamHandle.fail} (transport / server error). Subsequent calls
|
|
713
|
+
* are silent no-ops so a duplicate `complete` frame after a cancel doesn't
|
|
714
|
+
* crash the page.
|
|
715
|
+
*/
|
|
716
|
+
declare const DEFAULT_MAX_BUFFER = 1024;
|
|
717
|
+
interface StreamHandle<T = unknown> {
|
|
718
|
+
/** Mark the stream complete (no more chunks); resolves any pending consumer to `done:true`. */
|
|
719
|
+
readonly complete: () => void;
|
|
720
|
+
/** Surface an error to any pending consumer; subsequent pushes are dropped. */
|
|
721
|
+
readonly fail: (error: Error) => void;
|
|
722
|
+
/**
|
|
723
|
+
* Push one chunk. Silent no-op once the stream is `complete`, `fail`-ed,
|
|
724
|
+
* or `cancel`-ed. When the buffer is already at `maxBuffer`, the stream
|
|
725
|
+
* is failed with a `STREAM_BACKPRESSURE` error and the push is dropped —
|
|
726
|
+
* the producer never sees a thrown exception.
|
|
727
|
+
*/
|
|
728
|
+
readonly push: (value: T) => void;
|
|
729
|
+
}
|
|
730
|
+
interface StreamIterable<T> extends AsyncIterable<T> {
|
|
731
|
+
/** Cancel the stream from the consumer side: closes the iterator and notifies the registered canceller. */
|
|
732
|
+
cancel: () => void;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Build a stream handle paired with an async-iterable. The handle is the
|
|
736
|
+
* server-driven side (the WS dispatcher pushes chunks / completes / errors);
|
|
737
|
+
* the iterable is what the user awaits. `onCancel` is invoked exactly once
|
|
738
|
+
* when the consumer calls `.cancel()` (or `.return()`) so the client can
|
|
739
|
+
* send a `{type:"unsubscribe"}` frame to the server.
|
|
740
|
+
*/
|
|
741
|
+
declare const createStream: <T>(options: {
|
|
742
|
+
maxBuffer?: number;
|
|
743
|
+
onCancel: () => void;
|
|
744
|
+
}) => {
|
|
745
|
+
handle: StreamHandle<T>;
|
|
746
|
+
iterable: StreamIterable<T>;
|
|
747
|
+
};
|
|
748
|
+
/**
|
|
749
|
+
* Aggregate live-socket health across every shard connection, for a UI status
|
|
750
|
+
* indicator. `idle` = no socket opened yet; `connecting` = at least one socket
|
|
751
|
+
* is (re)connecting and none is open; `connected` = at least one socket is open;
|
|
752
|
+
* `offline` = sockets exist but all are down (between reconnect attempts).
|
|
753
|
+
*/
|
|
754
|
+
type ConnectionStatus = "connected" | "connecting" | "idle" | "offline";
|
|
755
|
+
/**
|
|
756
|
+
* Per-call options for {@link LunoraClient.mutation} — the optimistic-update
|
|
757
|
+
* machinery plus `shardKey`. Exported (at the end of this file) so the framework
|
|
758
|
+
* adapters (`@lunora/react`, `/solid`, `/svelte`, `/vue`) can type their
|
|
759
|
+
* `mutate(args, options?)` against one canonical definition instead of
|
|
760
|
+
* re-declaring it.
|
|
761
|
+
*/
|
|
762
|
+
interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unknown> {
|
|
763
|
+
optimistic?: (current: TCurrent | undefined) => TValue;
|
|
764
|
+
/**
|
|
765
|
+
* Convex-parity multi-query optimistic update. Receives an
|
|
766
|
+
* `OptimisticLocalStore` over the live subscription cache plus the
|
|
767
|
+
* mutation's args, so one mutation can patch many subscribed queries at
|
|
768
|
+
* once; every write is rolled back atomically if the mutation fails.
|
|
769
|
+
*/
|
|
770
|
+
optimisticUpdate?: OptimisticUpdate<TArgs>;
|
|
771
|
+
shardKey?: string;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Lunora browser/edge client. Talks RPC over HTTP and real-time deltas over
|
|
775
|
+
* a single multiplexed WebSocket.
|
|
776
|
+
*
|
|
777
|
+
* Reconnect, offline queueing, and optimistic updates are all handled here;
|
|
778
|
+
* see the package README for the wire protocol.
|
|
779
|
+
*/
|
|
780
|
+
declare class LunoraClient {
|
|
781
|
+
readonly url: string;
|
|
782
|
+
readonly wsUrl: string;
|
|
783
|
+
private wsToken;
|
|
784
|
+
/** Better-auth base path (trailing slash stripped) for the `get-session` lookup. */
|
|
785
|
+
private readonly authBasePath;
|
|
786
|
+
private readonly fetchImpl;
|
|
787
|
+
private readonly WebSocketImpl;
|
|
788
|
+
private readonly bookmark;
|
|
789
|
+
private readonly reconnectOptions;
|
|
790
|
+
/** Keepalive cadence (ms); `0` disables the heartbeat. See {@link LunoraClientOptions.heartbeatIntervalMs}. */
|
|
791
|
+
private readonly heartbeatIntervalMs;
|
|
792
|
+
private readonly offlineQueue;
|
|
793
|
+
private readonly onPersistenceError;
|
|
794
|
+
private readonly persistence;
|
|
795
|
+
/** Durable read cache (Pillar 2); `undefined` when `queryCache` is omitted or `false`. */
|
|
796
|
+
private readonly queryCache;
|
|
797
|
+
/**
|
|
798
|
+
* Values restored from the `queryCache` at construction, keyed by the
|
|
799
|
+
* read-cache key, awaiting the `subscribe()` that will consume them. A
|
|
800
|
+
* key is consumed (deleted) the first time its subscription is created, so
|
|
801
|
+
* the cache only ever seeds the initial value — live frames take over after.
|
|
802
|
+
*/
|
|
803
|
+
private readonly hydratedQueryCache;
|
|
804
|
+
/**
|
|
805
|
+
* Coalesced read-cache writes: the latest value per key, flushed to
|
|
806
|
+
* the `queryCache` on a short debounce so a burst of deltas persists once.
|
|
807
|
+
*/
|
|
808
|
+
private readonly pendingCacheWrites;
|
|
809
|
+
private cacheFlushTimer;
|
|
810
|
+
private readonly subscriptions;
|
|
811
|
+
/** One {@link ShardConnection} per shard key (keyed by `shardKey ?? ""`). */
|
|
812
|
+
private readonly connections;
|
|
813
|
+
/** Default `connect`-envelope context applied to a shard with no explicit override. */
|
|
814
|
+
private readonly defaultConnectionContext;
|
|
815
|
+
/**
|
|
816
|
+
* Per-shard `connect`-envelope context registered via `setConnectionContext`
|
|
817
|
+
* (keyed by `shardKey ?? ""`), overriding `defaultConnectionContext`. Sent
|
|
818
|
+
* on every socket open so it replays across reconnects, and forwarded to the
|
|
819
|
+
* server's `onConnect`/`onDisconnect` lifecycle hooks. This holds only the
|
|
820
|
+
* imperative (last-writer-wins) override; refcounted holders registered via
|
|
821
|
+
* `acquireConnectionContext` live in `connectionContextHolders` and take
|
|
822
|
+
* precedence — see `effectiveConnectionContext`.
|
|
823
|
+
*/
|
|
824
|
+
private readonly connectionContexts;
|
|
825
|
+
/**
|
|
826
|
+
* Per-shard stack of refcounted connection-context holders (keyed by
|
|
827
|
+
* `shardKey ?? ""`), registered via `acquireConnectionContext`. Each holder
|
|
828
|
+
* is an opaque token carrying its `context`; the most-recently acquired
|
|
829
|
+
* holder wins (last-writer-wins among live holders), and the context is only
|
|
830
|
+
* cleared for a shard once its last holder releases — so two concurrently
|
|
831
|
+
* mounted presence hooks on the same shard can't stomp each other's context
|
|
832
|
+
* on cleanup. A holder is identified by reference identity so a release
|
|
833
|
+
* removes exactly the right one regardless of stack position.
|
|
834
|
+
*/
|
|
835
|
+
private readonly connectionContextHolders;
|
|
836
|
+
private authToken;
|
|
837
|
+
/**
|
|
838
|
+
* Identity stamp recorded against each queued offline mutation, keyed by
|
|
839
|
+
* the queue-assigned mutation id. Captured at enqueue from the auth token
|
|
840
|
+
* in effect at the time, and re-checked at flush so a queued write can
|
|
841
|
+
* never replay under a different identity than the one that issued it.
|
|
842
|
+
* See `identityFingerprint` for the fingerprint shape.
|
|
843
|
+
*/
|
|
844
|
+
private readonly queuedIdentities;
|
|
845
|
+
private closed;
|
|
846
|
+
/** Subscribers to auth-token changes (see `onAuthTokenChange`). */
|
|
847
|
+
private readonly authTokenListeners;
|
|
848
|
+
/** Subscribers to aggregate connection-status changes (see `onConnectionStatus`). */
|
|
849
|
+
private readonly statusListeners;
|
|
850
|
+
/** Subscribers notified when the server drops a socket for an expired token (see `onTokenExpired`). */
|
|
851
|
+
private readonly tokenExpiredListeners;
|
|
852
|
+
/**
|
|
853
|
+
* Whisper-topic handlers, keyed by `connectionKey(shardKey)` → topic → set
|
|
854
|
+
* of callbacks. Membership doubles as the resubscribe set replayed on every
|
|
855
|
+
* (re)connect so a topic survives a socket bounce.
|
|
856
|
+
*/
|
|
857
|
+
private readonly whisperHandlers;
|
|
858
|
+
/** Last status broadcast, so we only notify listeners on an actual change. */
|
|
859
|
+
private lastStatus;
|
|
860
|
+
private nextSubId;
|
|
861
|
+
private nextStreamId;
|
|
862
|
+
/**
|
|
863
|
+
* In-flight client-side stream readers, keyed by the stream id sent on the
|
|
864
|
+
* wire. The handle drives the underlying iterator queue and `shardKey`
|
|
865
|
+
* tells us which socket to push the cancel frame onto when the consumer
|
|
866
|
+
* calls `.cancel()` or the iterator is garbage-collected.
|
|
867
|
+
*/
|
|
868
|
+
private readonly streams;
|
|
869
|
+
constructor(options: LunoraClientOptions);
|
|
870
|
+
/**
|
|
871
|
+
* Set (or clear) the bearer token sent on every HTTP RPC. Notifies any
|
|
872
|
+
* {@link onAuthTokenChange} listeners so React hooks like `useAuth` stay in
|
|
873
|
+
* sync across all mounted instances.
|
|
874
|
+
*
|
|
875
|
+
* Does NOT update the WebSocket auth — the WS token is fixed at upgrade
|
|
876
|
+
* time and lives in the URL. To refresh live WS auth, call
|
|
877
|
+
* {@link setWsToken} explicitly, which closes existing shard sockets to
|
|
878
|
+
* force a reconnect with the new credential.
|
|
879
|
+
*/
|
|
880
|
+
setAuthToken(token: string | null): void;
|
|
881
|
+
getAuthToken(): string | null;
|
|
882
|
+
/**
|
|
883
|
+
* Subscribe to auth-token changes. Returns an unsubscribe function. The
|
|
884
|
+
* listener is NOT invoked on registration — use {@link getAuthToken} for
|
|
885
|
+
* the current value.
|
|
886
|
+
*/
|
|
887
|
+
onAuthTokenChange(listener: (token: string | null) => void): Unsubscribe;
|
|
888
|
+
/**
|
|
889
|
+
* Fetch the currently authenticated user from better-auth's `get-session`
|
|
890
|
+
* endpoint, returning the `user` record or `null` when signed out. Sends
|
|
891
|
+
* the stored bearer token (if any) and `credentials: "include"` so a
|
|
892
|
+
* cookie-session is also honoured. A network/parse failure or a non-OK
|
|
893
|
+
* response resolves to `null` rather than throwing — callers treat "couldn't
|
|
894
|
+
* resolve identity" as "signed out".
|
|
895
|
+
*
|
|
896
|
+
* Framework-agnostic: pair it with {@link onAuthTokenChange} to refetch when
|
|
897
|
+
* the token changes (that's what `@lunora/react`'s `useAuth` does).
|
|
898
|
+
*/
|
|
899
|
+
getCurrentUser(): Promise<User | null>;
|
|
900
|
+
/**
|
|
901
|
+
* Replace the token appended to WS upgrade URLs as `?token=…` and close
|
|
902
|
+
* every open shard socket so the reconnect picks up the new value. Call
|
|
903
|
+
* this whenever the user's WS credential changes (rotating the admin token
|
|
904
|
+
* in the studio, switching workspaces, etc.). Bearer tokens for HTTP
|
|
905
|
+
* RPC are independent — see {@link setAuthToken}.
|
|
906
|
+
*/
|
|
907
|
+
setWsToken(token: string | undefined): void;
|
|
908
|
+
/**
|
|
909
|
+
* Register (or clear, with `undefined`) the app context sent in the `connect`
|
|
910
|
+
* envelope for a shard's socket, overriding the client-wide
|
|
911
|
+
* {@link LunoraClientOptions.connectionContext}. The server forwards it to the
|
|
912
|
+
* `onConnect`/`onDisconnect` lifecycle hooks as `event.context` — e.g.
|
|
913
|
+
* `@lunora/react`'s `usePresence` registers `{ roomId, sessionId }` so the
|
|
914
|
+
* presence row is removed the instant the socket drops, with no TTL lag.
|
|
915
|
+
*
|
|
916
|
+
* Stored per shard and replayed on every (re)connect. When a socket for the
|
|
917
|
+
* shard is already open, a fresh `connect` envelope is sent immediately so the
|
|
918
|
+
* server sees the new context without waiting for a reconnect.
|
|
919
|
+
*/
|
|
920
|
+
setConnectionContext(context: Record<string, unknown> | undefined, options?: {
|
|
921
|
+
shardKey?: string;
|
|
922
|
+
}): void;
|
|
923
|
+
/**
|
|
924
|
+
* Refcounted variant of {@link setConnectionContext}: register a connection
|
|
925
|
+
* `context` for a shard and get back a release function. Unlike the imperative
|
|
926
|
+
* setter, the context is only cleared once the *last* acquired holder releases
|
|
927
|
+
* it — so two components (e.g. two mounted `usePresence` hooks) on the same
|
|
928
|
+
* shard no longer clobber each other's context when one of them unmounts. The
|
|
929
|
+
* most-recently acquired live holder wins (last-writer-wins), and releasing
|
|
930
|
+
* the top holder falls back to the previous one rather than clearing.
|
|
931
|
+
*
|
|
932
|
+
* With a single holder the behaviour is identical to a
|
|
933
|
+
* `setConnectionContext(context)` / `setConnectionContext(undefined)` pair.
|
|
934
|
+
* Releasing more than once is a no-op (the holder is matched by reference, so
|
|
935
|
+
* a double release can't drop a different holder).
|
|
936
|
+
*/
|
|
937
|
+
acquireConnectionContext(context: Record<string, unknown>, options?: {
|
|
938
|
+
shardKey?: string;
|
|
939
|
+
}): Unsubscribe;
|
|
940
|
+
/**
|
|
941
|
+
* Join a whisper `topic` and receive every ephemeral message other members
|
|
942
|
+
* broadcast to it on the same shard (typing indicators, live cursors,
|
|
943
|
+
* presence pings). Whispers never touch the server's durable state — there's
|
|
944
|
+
* no query, no row, no CDC entry. Returns an unsubscribe function; the topic
|
|
945
|
+
* is left on the server once its last local handler unsubscribes.
|
|
946
|
+
*
|
|
947
|
+
* `handler` receives the raw `data` and the sender's verified `from` user id
|
|
948
|
+
* (omitted for an anonymous sender). The topic is scoped to `options.shardKey`
|
|
949
|
+
* (the default shard when omitted) — use the same shard you target with the
|
|
950
|
+
* matching queries/mutations so members land on the same Durable Object.
|
|
951
|
+
*
|
|
952
|
+
* Security: whisper topics are NOT access-controlled beyond the shard
|
|
953
|
+
* boundary — any client that can open a socket to the shard can join, read,
|
|
954
|
+
* and inject on any topic name. `from` is server-stamped and unforgeable, but
|
|
955
|
+
* do not put data on a whisper topic that some shard members shouldn't see,
|
|
956
|
+
* and don't trust a whisper's `data` as authorization. Use a query/mutation
|
|
957
|
+
* (with RLS) for anything privileged; whispers are for transient awareness.
|
|
958
|
+
*/
|
|
959
|
+
whisperSubscribe(topic: string, handler: (data: unknown, from?: string) => void, options?: {
|
|
960
|
+
shardKey?: string;
|
|
961
|
+
}): Unsubscribe;
|
|
962
|
+
/**
|
|
963
|
+
* Broadcast an ephemeral `data` payload to the other members of a whisper
|
|
964
|
+
* `topic` on `options.shardKey`'s shard. Fire-and-forget: the frame is
|
|
965
|
+
* dropped when the shard socket isn't open (whispers are transient, never
|
|
966
|
+
* queued), and the server silently drops it if the sender exceeds its
|
|
967
|
+
* whisper rate budget. The sender never receives its own whisper. Omitting
|
|
968
|
+
* `data` delivers JSON `null` to receivers (not `undefined`).
|
|
969
|
+
*/
|
|
970
|
+
whisper(topic: string, data?: unknown, options?: {
|
|
971
|
+
shardKey?: string;
|
|
972
|
+
}): void;
|
|
973
|
+
/**
|
|
974
|
+
* Subscribe to token-expiry events: invoked whenever the server drops a
|
|
975
|
+
* shard socket because the connection's credential lapsed (close code
|
|
976
|
+
* `4001`). The client already reconnects automatically (re-resolving
|
|
977
|
+
* identity from the cookie/token in effect); use this to refresh a
|
|
978
|
+
* short-lived token first — e.g. call {@link setWsToken} / {@link setAuthToken}
|
|
979
|
+
* with a freshly minted one. Returns an unsubscribe function.
|
|
980
|
+
*/
|
|
981
|
+
onTokenExpired(listener: () => void): Unsubscribe;
|
|
982
|
+
/**
|
|
983
|
+
* Current aggregate live-socket status across all shard connections. See
|
|
984
|
+
* {@link ConnectionStatus}.
|
|
985
|
+
*/
|
|
986
|
+
connectionStatus(): ConnectionStatus;
|
|
987
|
+
/**
|
|
988
|
+
* Subscribe to aggregate connection-status changes. Invokes `listener`
|
|
989
|
+
* immediately with the current status, then on every transition. Returns an
|
|
990
|
+
* unsubscribe function.
|
|
991
|
+
*/
|
|
992
|
+
onConnectionStatus(listener: (status: ConnectionStatus) => void): Unsubscribe;
|
|
993
|
+
query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: {
|
|
994
|
+
shardKey?: string;
|
|
995
|
+
}): Promise<ReturnOf<F>>;
|
|
996
|
+
/**
|
|
997
|
+
* Invoke a mutation. Errors propagate as rejections.
|
|
998
|
+
*
|
|
999
|
+
* Offline-queue semantics: a mutation is queued (and replayed on reconnect)
|
|
1000
|
+
* only when the targeted shard's socket was open at least once already
|
|
1001
|
+
* (`wasEverConnected`), so the registry / resubscribe handshake has run.
|
|
1002
|
+
* Mutations issued before the very first WS connect to a shard fail fast.
|
|
1003
|
+
* Opt into queueing-before-first-connect via
|
|
1004
|
+
* `OfflineQueueOptions.queueBeforeFirstConnect`.
|
|
1005
|
+
*/
|
|
1006
|
+
mutation<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>): Promise<ReturnOf<F>>;
|
|
1007
|
+
action<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: {
|
|
1008
|
+
shardKey?: string;
|
|
1009
|
+
}): Promise<ReturnOf<F>>;
|
|
1010
|
+
/**
|
|
1011
|
+
* Read the cross-shard request distribution for a `.shardBy(...)` table —
|
|
1012
|
+
* the feed the studio's `hot_shard` advisor lint consumes. Hits the
|
|
1013
|
+
* admin-gated `POST /_lunora/admin/shard-traffic` endpoint, which fans the
|
|
1014
|
+
* cheap per-shard `getMetrics` read out across every live shard and returns
|
|
1015
|
+
* each shard's `{ shardKey, requests }` total (a failed shard surfaces with
|
|
1016
|
+
* `requests: 0`). Requires the worker to be built with a `queryCoordinator`
|
|
1017
|
+
* and `adminToken`, and this client's auth token to match; defaults any
|
|
1018
|
+
* absent field so an older worker yields an empty-but-valid shape.
|
|
1019
|
+
*/
|
|
1020
|
+
shardTraffic(table: string): Promise<ShardTrafficResult>;
|
|
1021
|
+
/**
|
|
1022
|
+
* List the functions queued via `runAfter` / `runAt`, soonest-due last
|
|
1023
|
+
* (the worker returns them in storage order). Hits the admin-gated
|
|
1024
|
+
* `/_lunora/admin/scheduled` endpoint, so the worker must be built with a
|
|
1025
|
+
* `schedulerDO` namespace and `adminToken`, and this client's auth token
|
|
1026
|
+
* must match. Powers `@lunora/studio`'s scheduled-jobs panel.
|
|
1027
|
+
*/
|
|
1028
|
+
listScheduledJobs(): Promise<ScheduleRecord[]>;
|
|
1029
|
+
/**
|
|
1030
|
+
* Read the app-level workpool backlog that powers `@lunora/studio`'s SLO
|
|
1031
|
+
* view: per-pool `{ name, queued, inFlight, maxConcurrency }` plus the
|
|
1032
|
+
* app-wide `backlog` (total queued) and `inFlight` (total held slots) sums.
|
|
1033
|
+
* Hits the admin-gated `GET /_lunora/admin/scheduled/status` endpoint, so the
|
|
1034
|
+
* same preconditions as {@link listScheduledJobs} apply (a `schedulerDO`
|
|
1035
|
+
* namespace + `adminToken` on the worker and a matching auth token here).
|
|
1036
|
+
* Defaults any absent field so an older worker still yields a valid shape.
|
|
1037
|
+
*/
|
|
1038
|
+
schedulerStatus(): Promise<SchedulerStatus>;
|
|
1039
|
+
/** Cancel a pending scheduled job by id. Returns whether a job was removed. */
|
|
1040
|
+
cancelScheduledJob(id: string): Promise<{
|
|
1041
|
+
cancelled: boolean;
|
|
1042
|
+
}>;
|
|
1043
|
+
/**
|
|
1044
|
+
* List the dead-letter jobs: schedules that exhausted their retry budget
|
|
1045
|
+
* and were parked instead of dropped. These never appear in
|
|
1046
|
+
* {@link listScheduledJobs} (their live header is gone), so this is the only
|
|
1047
|
+
* way the studio surfaces a permanently-failed job. Hits the admin-gated
|
|
1048
|
+
* `GET /_lunora/admin/scheduled/dead`; same preconditions as
|
|
1049
|
+
* {@link listScheduledJobs}. Powers `@lunora/studio`'s dead-letter panel.
|
|
1050
|
+
*/
|
|
1051
|
+
listDeadJobs(): Promise<ScheduleRecord[]>;
|
|
1052
|
+
/**
|
|
1053
|
+
* Resurrect a dead-letter job by id: it re-enters the schedule with a fresh
|
|
1054
|
+
* retry budget and fires on the next drain. Returns whether a parked record
|
|
1055
|
+
* matched. Hits the admin-gated `POST /_lunora/admin/scheduled/dead/retry`.
|
|
1056
|
+
*/
|
|
1057
|
+
retryDeadJob(id: string): Promise<{
|
|
1058
|
+
retried: boolean;
|
|
1059
|
+
}>;
|
|
1060
|
+
/**
|
|
1061
|
+
* Permanently drop a dead-letter job by id (the operator has decided not to
|
|
1062
|
+
* recover it). Returns whether a parked record was removed. Hits the
|
|
1063
|
+
* admin-gated `POST /_lunora/admin/scheduled/dead/cancel`.
|
|
1064
|
+
*/
|
|
1065
|
+
removeDeadJob(id: string): Promise<{
|
|
1066
|
+
removed: boolean;
|
|
1067
|
+
}>;
|
|
1068
|
+
/**
|
|
1069
|
+
* List a workflow's instances via the admin Workflows proxy
|
|
1070
|
+
* (`/_lunora/admin/workflows/instances`) — the Cloudflare control-plane data
|
|
1071
|
+
* the `Workflow` binding can't expose. Requires the worker to be built with a
|
|
1072
|
+
* `workflowsClient` (Cloudflare account id + API token); otherwise the proxy
|
|
1073
|
+
* responds 501 and this rejects. `name` is the deployed workflow name.
|
|
1074
|
+
*/
|
|
1075
|
+
listWorkflowInstances(options: {
|
|
1076
|
+
name: string;
|
|
1077
|
+
page?: number;
|
|
1078
|
+
perPage?: number;
|
|
1079
|
+
status?: WorkflowInstanceStatus;
|
|
1080
|
+
}): Promise<WorkflowInstancePage>;
|
|
1081
|
+
/** Read one workflow instance with its step timeline (`/_lunora/admin/workflows/instance`). */
|
|
1082
|
+
getWorkflowInstance(options: {
|
|
1083
|
+
id: string;
|
|
1084
|
+
name: string;
|
|
1085
|
+
}): Promise<WorkflowInstanceDetail>;
|
|
1086
|
+
/** Pause / resume / terminate a workflow instance (`/_lunora/admin/workflows/status`). Needs an Edit-scoped Cloudflare token. */
|
|
1087
|
+
setWorkflowInstanceStatus(options: {
|
|
1088
|
+
action: WorkflowInstanceAction;
|
|
1089
|
+
id: string;
|
|
1090
|
+
name: string;
|
|
1091
|
+
}): Promise<{
|
|
1092
|
+
status: WorkflowInstanceStatus;
|
|
1093
|
+
}>;
|
|
1094
|
+
/**
|
|
1095
|
+
* Subscribe to the live scheduled-jobs list over the SchedulerDO's admin
|
|
1096
|
+
* WebSocket. `onJobs` fires with the full list on connect and on every
|
|
1097
|
+
* change (schedule / cancel / alarm-fire). Reconnects with the client's
|
|
1098
|
+
* configured backoff. Requires `wsToken` to be set to the admin token (the
|
|
1099
|
+
* browser can't send an `Authorization` header on a WS). Returns an
|
|
1100
|
+
* unsubscribe function that closes the socket and stops reconnecting.
|
|
1101
|
+
*/
|
|
1102
|
+
subscribeScheduledJobs(onJobs: (jobs: ScheduleRecord[]) => void): Unsubscribe;
|
|
1103
|
+
/**
|
|
1104
|
+
* List the registered public functions (queries / mutations / actions) with
|
|
1105
|
+
* their kinds. Hits the admin-gated `GET /_lunora/admin/functions` endpoint —
|
|
1106
|
+
* the worker must be built with a `functions` registry and `adminToken`, and
|
|
1107
|
+
* this client's auth token must match. Powers `@lunora/studio`'s function
|
|
1108
|
+
* runner auto-discovery.
|
|
1109
|
+
*/
|
|
1110
|
+
listFunctions(): Promise<FunctionDescriptor[]>;
|
|
1111
|
+
/**
|
|
1112
|
+
* List the code-defined cron triggers (the `cronJobs()` map injected on the
|
|
1113
|
+
* worker), each flattened to its firing `cron` expression. Hits the
|
|
1114
|
+
* admin-gated `GET /_lunora/admin/cron-jobs` endpoint — the worker must be
|
|
1115
|
+
* built with a `cronJobs` map and `adminToken`, and this client's auth token
|
|
1116
|
+
* must match. These are static (Cloudflare exposes no runtime cron
|
|
1117
|
+
* introspection), so the studio renders them read-only alongside the dynamic
|
|
1118
|
+
* scheduler jobs.
|
|
1119
|
+
*/
|
|
1120
|
+
getCronJobs(): Promise<CronJobInfo[]>;
|
|
1121
|
+
/**
|
|
1122
|
+
* Manually fire one code-defined cron job by name — the same dispatch the
|
|
1123
|
+
* scheduled trigger runs (dispatch the function, or start the durable
|
|
1124
|
+
* workflow), on demand. Hits the admin-gated `POST /_lunora/admin/cron-jobs/run`
|
|
1125
|
+
* endpoint; the worker must be built with a `cronJobs` map and `adminToken`,
|
|
1126
|
+
* and this client's auth token must match. Resolves when the job has run (a
|
|
1127
|
+
* function job's shard response is 2xx, or the workflow instance was created)
|
|
1128
|
+
* and rejects with the dispatch error otherwise.
|
|
1129
|
+
*/
|
|
1130
|
+
runCronJob(name: string): Promise<{
|
|
1131
|
+
name: string;
|
|
1132
|
+
ran: boolean;
|
|
1133
|
+
}>;
|
|
1134
|
+
/**
|
|
1135
|
+
* Fetch the generated OpenAPI 3.1 document. Hits the admin-gated
|
|
1136
|
+
* `GET /_lunora/admin/openapi` endpoint — the worker must be built with an
|
|
1137
|
+
* `openApiSpec` and `adminToken`, and this client's auth token must match.
|
|
1138
|
+
* Powers `@lunora/studio`'s API-reference (Scalar) view. When the worker has
|
|
1139
|
+
* no spec wired, the endpoint still resolves with an empty-but-valid OpenAPI
|
|
1140
|
+
* document (no `paths`), so callers can render a "not configured" state.
|
|
1141
|
+
*/
|
|
1142
|
+
fetchOpenApi(): Promise<Record<string, unknown>>;
|
|
1143
|
+
/**
|
|
1144
|
+
* Fetch the generated OpenRPC 1.x document. Hits the admin-gated
|
|
1145
|
+
* `GET /_lunora/admin/openrpc` endpoint — the worker must be built with an
|
|
1146
|
+
* `openRpcSpec` and `adminToken`, and this client's auth token must match.
|
|
1147
|
+
* OpenRPC is the RPC-native spec (a `methods` array over the JSON-RPC-shaped
|
|
1148
|
+
* `POST /_lunora/rpc` transport); it documents the RPC functions only.
|
|
1149
|
+
* Powers `@lunora/studio`'s OpenRPC API-reference view. When the worker has
|
|
1150
|
+
* no spec wired, the endpoint still resolves with an empty-but-valid OpenRPC
|
|
1151
|
+
* document (no `methods`), so callers can render a "not configured" state.
|
|
1152
|
+
*/
|
|
1153
|
+
fetchOpenRpc(): Promise<Record<string, unknown>>;
|
|
1154
|
+
/**
|
|
1155
|
+
* List objects in the storage bucket, optionally under a `prefix` and from a
|
|
1156
|
+
* pagination `cursor`. Hits the admin-gated `GET /_lunora/admin/storage`
|
|
1157
|
+
* endpoint — the worker must be built with a `storageList` function and
|
|
1158
|
+
* `adminToken`, and this client's auth token must match. Powers
|
|
1159
|
+
* `@lunora/studio`'s file browser.
|
|
1160
|
+
*/
|
|
1161
|
+
listStorageObjects(options?: {
|
|
1162
|
+
bucket?: string;
|
|
1163
|
+
cursor?: string;
|
|
1164
|
+
limit?: number;
|
|
1165
|
+
prefix?: string;
|
|
1166
|
+
}): Promise<StorageListPage>;
|
|
1167
|
+
/**
|
|
1168
|
+
* Delete one object from the storage bucket by key. Hits the admin-gated
|
|
1169
|
+
* `DELETE /_lunora/admin/storage?key=…` endpoint — the worker must be built
|
|
1170
|
+
* with a `storageDelete` function and `adminToken`. Powers the studio file
|
|
1171
|
+
* browser's per-row delete; resolves `{ deleted, key }`.
|
|
1172
|
+
*/
|
|
1173
|
+
deleteStorageObject(key: string, options?: {
|
|
1174
|
+
bucket?: string;
|
|
1175
|
+
}): Promise<{
|
|
1176
|
+
deleted: boolean;
|
|
1177
|
+
key: string;
|
|
1178
|
+
}>;
|
|
1179
|
+
/**
|
|
1180
|
+
* List the storage bucket names the worker exposes, for the studio file
|
|
1181
|
+
* browser's bucket picker. Hits the admin-gated
|
|
1182
|
+
* `GET /_lunora/admin/storage/buckets` endpoint — always resolves (an empty
|
|
1183
|
+
* array when the worker configures no `storageBuckets`, i.e. single-bucket).
|
|
1184
|
+
*/
|
|
1185
|
+
listStorageBuckets(): Promise<string[]>;
|
|
1186
|
+
/**
|
|
1187
|
+
* Upload one object to the storage bucket. Hits the admin-gated
|
|
1188
|
+
* `PUT /_lunora/admin/storage?key=…` endpoint with the raw body and an
|
|
1189
|
+
* optional `contentType` header — the worker must be built with a
|
|
1190
|
+
* `storageUpload` function and `adminToken`. Powers the studio file
|
|
1191
|
+
* browser's upload control; resolves `{ etag?, key }`.
|
|
1192
|
+
*/
|
|
1193
|
+
uploadStorageObject(options: {
|
|
1194
|
+
body: ArrayBuffer | Blob;
|
|
1195
|
+
bucket?: string;
|
|
1196
|
+
contentType?: string;
|
|
1197
|
+
key: string;
|
|
1198
|
+
}): Promise<{
|
|
1199
|
+
etag?: string;
|
|
1200
|
+
key: string;
|
|
1201
|
+
}>;
|
|
1202
|
+
/**
|
|
1203
|
+
* Build a (signed or public) URL for one object. Hits the admin-gated
|
|
1204
|
+
* `GET /_lunora/admin/storage/url?key=…` endpoint — the worker must be built
|
|
1205
|
+
* with a `storageSignedUrl` function and `adminToken`. Powers the studio
|
|
1206
|
+
* file browser's copy-URL action; resolves the URL string.
|
|
1207
|
+
*
|
|
1208
|
+
* `options.expiresInSeconds` requests a share-link lifetime, which is
|
|
1209
|
+
* validated/clamped server-side. The options object mirrors the worker's
|
|
1210
|
+
* `StorageSignedUrlFunction` options (a `password` / download-limit are noted
|
|
1211
|
+
* as future fields there).
|
|
1212
|
+
*/
|
|
1213
|
+
signedStorageUrl(key: string, options?: {
|
|
1214
|
+
bucket?: string;
|
|
1215
|
+
expiresInSeconds?: number;
|
|
1216
|
+
}): Promise<string>;
|
|
1217
|
+
/**
|
|
1218
|
+
* List the `.global()` (D1-backed) tables with their row counts. Hits the
|
|
1219
|
+
* admin-gated `GET /_lunora/admin/global/tables` endpoint — the worker must
|
|
1220
|
+
* be built with a `globalIntrospector` and `adminToken`. Powers the data
|
|
1221
|
+
* browser's global mode.
|
|
1222
|
+
*/
|
|
1223
|
+
listGlobalTables(): Promise<GlobalTableInfo[]>;
|
|
1224
|
+
/**
|
|
1225
|
+
* Read a page of rows from one `.global()` table. `filters` AND-narrows the
|
|
1226
|
+
* page to rows matching each `column = value` eq constraint — the drill-down a
|
|
1227
|
+
* facet-value click applies; the array is JSON-encoded into the `filters`
|
|
1228
|
+
* query param and the values are bound server-side.
|
|
1229
|
+
*/
|
|
1230
|
+
readGlobalTablePage(options: {
|
|
1231
|
+
filters?: GlobalFilterClause[];
|
|
1232
|
+
limit?: number;
|
|
1233
|
+
offset?: number;
|
|
1234
|
+
table: string;
|
|
1235
|
+
}): Promise<GlobalTablePage>;
|
|
1236
|
+
/**
|
|
1237
|
+
* Summarise the distinct values of one column in a `.global()` table over the
|
|
1238
|
+
* active view (the same eq `filters` the browser is previewing) — the global
|
|
1239
|
+
* twin of the shard browser's facet. Hits the admin-gated
|
|
1240
|
+
* `GET /_lunora/admin/global/facet` endpoint; `column` is validated + bound
|
|
1241
|
+
* server-side. Powers the global data browser's facet sidebar.
|
|
1242
|
+
*/
|
|
1243
|
+
facetGlobalColumn(options: {
|
|
1244
|
+
column: string;
|
|
1245
|
+
filters?: GlobalFilterClause[];
|
|
1246
|
+
limit?: number;
|
|
1247
|
+
table: string;
|
|
1248
|
+
}): Promise<GlobalFacetResult>;
|
|
1249
|
+
/**
|
|
1250
|
+
* List the schema's Vectorize indexes with their declared shape (table,
|
|
1251
|
+
* field, dimensions, metric, metadata) and live stats (vector count,
|
|
1252
|
+
* processing watermark) when the binding is reachable. Hits the admin-gated
|
|
1253
|
+
* `GET /_lunora/admin/vector/indexes` endpoint — the worker must be built
|
|
1254
|
+
* with a `vectorIntrospector` and `adminToken`. Powers the studio's vector
|
|
1255
|
+
* browser. Vectorize can't enumerate indexes at runtime, so this list comes
|
|
1256
|
+
* from the generated `LUNORA_VECTOR_INDEXES` registry.
|
|
1257
|
+
*/
|
|
1258
|
+
listVectorIndexes(): Promise<VectorIndexSummary[]>;
|
|
1259
|
+
/**
|
|
1260
|
+
* Run a nearest-neighbour similarity query against one vector index: the
|
|
1261
|
+
* worker embeds `text` via the index's embedder and returns the top matches.
|
|
1262
|
+
* Hits the admin-gated `POST /_lunora/admin/vector/query` endpoint. Throws
|
|
1263
|
+
* `VECTOR_QUERY_UNSUPPORTED` when the worker's introspector has no embedder
|
|
1264
|
+
* wired (the index lists read-only).
|
|
1265
|
+
*/
|
|
1266
|
+
queryVectorIndex(options: {
|
|
1267
|
+
name: string;
|
|
1268
|
+
text: string;
|
|
1269
|
+
topK?: number;
|
|
1270
|
+
}): Promise<VectorQueryMatch[]>;
|
|
1271
|
+
/**
|
|
1272
|
+
* List authenticated users, paged and optionally searched / filtered / sorted.
|
|
1273
|
+
* Hits the admin-gated `GET /_lunora/admin/auth/users` endpoint — the worker
|
|
1274
|
+
* must be built with an `authAdmin` and `adminToken`. Powers the studio's
|
|
1275
|
+
* users dashboard.
|
|
1276
|
+
*/
|
|
1277
|
+
listAuthUsers(options?: {
|
|
1278
|
+
filterField?: string;
|
|
1279
|
+
filterValue?: string;
|
|
1280
|
+
limit?: number;
|
|
1281
|
+
offset?: number;
|
|
1282
|
+
search?: string;
|
|
1283
|
+
searchField?: string;
|
|
1284
|
+
sortBy?: string;
|
|
1285
|
+
sortDirection?: "asc" | "desc";
|
|
1286
|
+
}): Promise<AuthPage<AuthUser>>;
|
|
1287
|
+
/**
|
|
1288
|
+
* Create a user. Hits the admin-gated `POST /_lunora/admin/auth/users/create`
|
|
1289
|
+
* endpoint (requires the worker's `authAdmin` to implement `createUser`).
|
|
1290
|
+
* `data` carries any app-defined `user.additionalFields`.
|
|
1291
|
+
*/
|
|
1292
|
+
createAuthUser(input: {
|
|
1293
|
+
data?: Record<string, unknown>;
|
|
1294
|
+
email: string;
|
|
1295
|
+
name: string;
|
|
1296
|
+
password?: string;
|
|
1297
|
+
role?: string | string[];
|
|
1298
|
+
}): Promise<AuthUser>;
|
|
1299
|
+
/** Set a user's role (string, or array joined comma-wise server-side). */
|
|
1300
|
+
setAuthUserRole(input: {
|
|
1301
|
+
role: string | string[];
|
|
1302
|
+
userId: string;
|
|
1303
|
+
}): Promise<AuthUser>;
|
|
1304
|
+
/** Ban a user. `expiresInSeconds` sets a temporary ban; omit it for a permanent one. Revokes the user's live sessions. */
|
|
1305
|
+
banAuthUser(input: {
|
|
1306
|
+
expiresInSeconds?: number;
|
|
1307
|
+
reason?: string;
|
|
1308
|
+
userId: string;
|
|
1309
|
+
}): Promise<AuthUser>;
|
|
1310
|
+
/** Lift a user's ban. */
|
|
1311
|
+
unbanAuthUser(input: {
|
|
1312
|
+
userId: string;
|
|
1313
|
+
}): Promise<AuthUser>;
|
|
1314
|
+
/** Set a user's password (admin override — no current-password challenge). */
|
|
1315
|
+
setAuthUserPassword(input: {
|
|
1316
|
+
newPassword: string;
|
|
1317
|
+
userId: string;
|
|
1318
|
+
}): Promise<void>;
|
|
1319
|
+
/** Permanently delete a user and revoke their sessions. */
|
|
1320
|
+
removeAuthUser(input: {
|
|
1321
|
+
userId: string;
|
|
1322
|
+
}): Promise<void>;
|
|
1323
|
+
/**
|
|
1324
|
+
* Mint an impersonation session for a user, returning its bearer `token`.
|
|
1325
|
+
* The caller is responsible for using the token (e.g. setting the session
|
|
1326
|
+
* cookie); the server performs no cookie round-trip.
|
|
1327
|
+
*/
|
|
1328
|
+
impersonateAuthUser(input: {
|
|
1329
|
+
userId: string;
|
|
1330
|
+
}): Promise<AuthImpersonation>;
|
|
1331
|
+
/** Revoke a single session by its id (force sign-out of one device). */
|
|
1332
|
+
revokeAuthSession(input: {
|
|
1333
|
+
sessionId: string;
|
|
1334
|
+
}): Promise<void>;
|
|
1335
|
+
/** Revoke every session for a user (force sign-out everywhere). */
|
|
1336
|
+
revokeAuthUserSessions(input: {
|
|
1337
|
+
userId: string;
|
|
1338
|
+
}): Promise<void>;
|
|
1339
|
+
/**
|
|
1340
|
+
* Report which auth dashboard surfaces are available — derived server-side
|
|
1341
|
+
* from the enabled better-auth plugins. The studio renders only the panels
|
|
1342
|
+
* whose capability is `true`.
|
|
1343
|
+
*/
|
|
1344
|
+
getAuthCapabilities(): Promise<AuthCapabilities>;
|
|
1345
|
+
/** Update a user's fields (name/email/app-defined `additionalFields`). */
|
|
1346
|
+
updateAuthUser(input: {
|
|
1347
|
+
data: Record<string, unknown>;
|
|
1348
|
+
userId: string;
|
|
1349
|
+
}): Promise<AuthUser>;
|
|
1350
|
+
/** List a user's linked accounts (credential / OAuth providers). Token material is stripped server-side. */
|
|
1351
|
+
listAuthAccounts(input: {
|
|
1352
|
+
userId: string;
|
|
1353
|
+
}): Promise<Record<string, unknown>[]>;
|
|
1354
|
+
/** Unlink a linked account from a user. */
|
|
1355
|
+
unlinkAuthAccount(input: {
|
|
1356
|
+
accountId: string;
|
|
1357
|
+
userId: string;
|
|
1358
|
+
}): Promise<void>;
|
|
1359
|
+
/** List a user's registered passkeys (requires the passkey plugin). */
|
|
1360
|
+
listAuthPasskeys(input: {
|
|
1361
|
+
userId: string;
|
|
1362
|
+
}): Promise<Record<string, unknown>[]>;
|
|
1363
|
+
/** Delete a passkey by id (requires the passkey plugin). */
|
|
1364
|
+
deleteAuthPasskey(input: {
|
|
1365
|
+
passkeyId: string;
|
|
1366
|
+
}): Promise<void>;
|
|
1367
|
+
/** Disable two-factor auth for a user (requires the two-factor plugin). */
|
|
1368
|
+
disableAuthTwoFactor(input: {
|
|
1369
|
+
userId: string;
|
|
1370
|
+
}): Promise<void>;
|
|
1371
|
+
/** List organizations, paged (requires the organization plugin). */
|
|
1372
|
+
listAuthOrganizations(options?: {
|
|
1373
|
+
limit?: number;
|
|
1374
|
+
offset?: number;
|
|
1375
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
1376
|
+
/** List the members of an organization (requires the organization plugin). */
|
|
1377
|
+
listAuthOrgMembers(input: {
|
|
1378
|
+
limit?: number;
|
|
1379
|
+
offset?: number;
|
|
1380
|
+
organizationId: string;
|
|
1381
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
1382
|
+
/** List an organization's pending invitations (requires the organization plugin). */
|
|
1383
|
+
listAuthOrgInvitations(input: {
|
|
1384
|
+
limit?: number;
|
|
1385
|
+
offset?: number;
|
|
1386
|
+
organizationId: string;
|
|
1387
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
1388
|
+
/** Remove a member from an organization. */
|
|
1389
|
+
removeAuthOrgMember(input: {
|
|
1390
|
+
memberId: string;
|
|
1391
|
+
}): Promise<void>;
|
|
1392
|
+
/** Cancel a pending organization invitation. */
|
|
1393
|
+
cancelAuthOrgInvitation(input: {
|
|
1394
|
+
invitationId: string;
|
|
1395
|
+
}): Promise<void>;
|
|
1396
|
+
/** List auth sessions, paged and optionally filtered to one user. */
|
|
1397
|
+
listAuthSessions(options?: {
|
|
1398
|
+
limit?: number;
|
|
1399
|
+
offset?: number;
|
|
1400
|
+
userId?: string;
|
|
1401
|
+
}): Promise<AuthPage<AuthSession>>;
|
|
1402
|
+
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
1403
|
+
onError?: SubscriptionErrorCallback;
|
|
1404
|
+
shardKey?: string;
|
|
1405
|
+
}): Unsubscribe;
|
|
1406
|
+
/**
|
|
1407
|
+
* Open a streaming query. The function reference must be a
|
|
1408
|
+
* `kind:"stream"` registration (built with `c.query.input(...).stream(...)`);
|
|
1409
|
+
* the type constraint catches accidental use of a query/mutation/action
|
|
1410
|
+
* reference at compile time. The returned iterable yields one element per
|
|
1411
|
+
* chunk frame the server pushes, terminating when the server sends
|
|
1412
|
+
* `complete` or the consumer calls `.cancel()`. Errors arrive as a
|
|
1413
|
+
* rejection on the next `next()`.
|
|
1414
|
+
*
|
|
1415
|
+
* Streams ride the same WS as subscriptions and share the unsubscribe
|
|
1416
|
+
* channel: cancelling sends `{type:"unsubscribe", id}` with the stream id,
|
|
1417
|
+
* which the DO recognises as an abort signal for the in-flight iterator.
|
|
1418
|
+
*
|
|
1419
|
+
* Stream-start frames buffered while the socket is (re)connecting are
|
|
1420
|
+
* capped at {@link MAX_PENDING_STREAMS} per connection — overflowing the
|
|
1421
|
+
* cap drops the oldest queued frame (and fails its consumer) so a stuck
|
|
1422
|
+
* reconnect can't OOM the page.
|
|
1423
|
+
*/
|
|
1424
|
+
stream<F extends FunctionReference<"stream">>(function_: F, args: ArgsOf<F>, options?: {
|
|
1425
|
+
maxBuffer?: number;
|
|
1426
|
+
shardKey?: string;
|
|
1427
|
+
}): StreamIterable<ReturnOf<F>>;
|
|
1428
|
+
close(): void;
|
|
1429
|
+
/**
|
|
1430
|
+
* Restore offline mutations persisted in a prior session and open a socket
|
|
1431
|
+
* for each shard they target so they flush once the WS reconnects. Failures
|
|
1432
|
+
* are swallowed — a broken durable store must not stop the client booting.
|
|
1433
|
+
*/
|
|
1434
|
+
private hydratePersistedQueue;
|
|
1435
|
+
/**
|
|
1436
|
+
* Load every cached query into {@link hydratedQueryCache} so the next
|
|
1437
|
+
* `subscribe()` for each key seeds its initial value off disk. A
|
|
1438
|
+
* subscription created before this resolves simply misses the cache (it
|
|
1439
|
+
* gets a live snapshot as before); the gate at seed time also drops any
|
|
1440
|
+
* entry whose stamped identity no longer matches the current one.
|
|
1441
|
+
*/
|
|
1442
|
+
private hydrateQueryCache;
|
|
1443
|
+
/**
|
|
1444
|
+
* Consume the hydrated read-cache entry for a key (if any), gated on
|
|
1445
|
+
* identity. The entry is removed whether or not it matches — the cache only
|
|
1446
|
+
* ever seeds a subscription's first value. A mismatch (the cache was written
|
|
1447
|
+
* under a different identity) yields `undefined` so a signed-out cache never
|
|
1448
|
+
* leaks into a new session.
|
|
1449
|
+
*/
|
|
1450
|
+
private takeHydratedCache;
|
|
1451
|
+
/**
|
|
1452
|
+
* Queue a coalesced read-cache write for a subscription's current value.
|
|
1453
|
+
* Latest-wins per key; flushed on a short debounce so a delta burst writes
|
|
1454
|
+
* once. No-op when the read cache is disabled or the value is undefined
|
|
1455
|
+
* (nothing to render offline).
|
|
1456
|
+
*/
|
|
1457
|
+
private persistQueryValue;
|
|
1458
|
+
/** Drain {@link pendingCacheWrites} to the durable store. */
|
|
1459
|
+
private flushQueryCacheWrites;
|
|
1460
|
+
/** Derive the aggregate status from the per-shard socket states. */
|
|
1461
|
+
private computeStatus;
|
|
1462
|
+
/** Recompute the aggregate status and notify listeners if it changed. */
|
|
1463
|
+
private emitConnectionStatus;
|
|
1464
|
+
/**
|
|
1465
|
+
* Apply an optimistic update to every subscription that matches the
|
|
1466
|
+
* mutation's function ref, shard key, and args, returning the rollback
|
|
1467
|
+
* callbacks to invoke if the mutation later fails. Scoping to the same
|
|
1468
|
+
* (fn, shardKey, args) keeps one user's mutation from clobbering another
|
|
1469
|
+
* subscriber's value on the same function (e.g. two users on different rooms).
|
|
1470
|
+
*/
|
|
1471
|
+
private applyOptimisticUpdates;
|
|
1472
|
+
/**
|
|
1473
|
+
* Run a Convex-parity `optimisticUpdate` callback against a localStore bound
|
|
1474
|
+
* to the live subscription registry, appending each `setQuery` write's
|
|
1475
|
+
* rollback to `optimisticRollbacks` (the same LIFO list the legacy path uses,
|
|
1476
|
+
* unwound on settle/error). A throwing callback unwinds its own partial
|
|
1477
|
+
* writes — LIFO over just the rollbacks it produced — and is swallowed, so a
|
|
1478
|
+
* buggy optimistic update can never fail the mutation or leave a partial
|
|
1479
|
+
* patch live, mirroring the legacy transform's throw handling.
|
|
1480
|
+
*/
|
|
1481
|
+
private applyOptimisticUpdate;
|
|
1482
|
+
private getConnection;
|
|
1483
|
+
private getOrCreateConnection;
|
|
1484
|
+
private wsUrlFor;
|
|
1485
|
+
/**
|
|
1486
|
+
* Build the outbound RPC headers: JSON content type, optional bearer auth,
|
|
1487
|
+
* the optional mutation-replay idempotency key, and the D1 read-your-writes
|
|
1488
|
+
* bookmark when the caller opted into `attachBookmark`. The mutation id
|
|
1489
|
+
* rides both the direct send and any offline-queue replay of the same write,
|
|
1490
|
+
* so a mutation the server already committed returns its cached result
|
|
1491
|
+
* instead of running twice.
|
|
1492
|
+
*/
|
|
1493
|
+
private rpcRequestHeaders;
|
|
1494
|
+
private rpc;
|
|
1495
|
+
/**
|
|
1496
|
+
* Authenticated request to a non-RPC admin endpoint (the scheduler list /
|
|
1497
|
+
* cancel routes). Attaches the bearer token, parses JSON, and surfaces the
|
|
1498
|
+
* worker's `{ error: { code, message } }` envelope as a coded `Error` —
|
|
1499
|
+
* mirroring {@link rpc} so callers see the same failure shape.
|
|
1500
|
+
*/
|
|
1501
|
+
private adminFetch;
|
|
1502
|
+
/**
|
|
1503
|
+
* Resolve the effective connection context for a shard: the most-recently
|
|
1504
|
+
* acquired refcounted holder ({@link acquireConnectionContext}) wins, falling
|
|
1505
|
+
* back to the imperative {@link setConnectionContext} override, then the
|
|
1506
|
+
* client-wide default. Returns `undefined` when none apply.
|
|
1507
|
+
*/
|
|
1508
|
+
private effectiveConnectionContext;
|
|
1509
|
+
/** Re-send the `connect` envelope for a shard whose effective context just changed (if its socket is open). */
|
|
1510
|
+
private refreshConnectionContext;
|
|
1511
|
+
/**
|
|
1512
|
+
* Send the one-shot `connect` envelope on an open shard socket. Always sent
|
|
1513
|
+
* once per socket open, so the server's `onConnect` hooks fire symmetrically
|
|
1514
|
+
* with `onDisconnect` (which the DO dispatches unconditionally at close for
|
|
1515
|
+
* every lifecycle-aware socket). The DO no-ops cheaply when no `onConnect`
|
|
1516
|
+
* hooks are registered, so the single frame costs nothing in the common case.
|
|
1517
|
+
*
|
|
1518
|
+
* The shard's registered context (or the client-wide default) rides along
|
|
1519
|
+
* when one is set — the DO records it on the attachment for replay to
|
|
1520
|
+
* `onDisconnect`. A socket with no registered context still announces itself;
|
|
1521
|
+
* the envelope simply omits `context`, which is optional on the wire.
|
|
1522
|
+
* Register a context — e.g. `setConnectionContext({})` — to attach app state
|
|
1523
|
+
* to the lifecycle dispatch.
|
|
1524
|
+
*/
|
|
1525
|
+
private sendConnectEnvelope;
|
|
1526
|
+
private ensureSocket;
|
|
1527
|
+
private handleDisconnect;
|
|
1528
|
+
/**
|
|
1529
|
+
* Begin the keepalive heartbeat on an open connection. Each tick sends a
|
|
1530
|
+
* {@link WS_KEEPALIVE_PING} text frame the server answers from its
|
|
1531
|
+
* hibernation auto-response without waking the DO. A no-op when the
|
|
1532
|
+
* heartbeat is disabled (an interval of zero or less); idempotent — any
|
|
1533
|
+
* existing timer is cleared first so a reconnect can't leak intervals.
|
|
1534
|
+
*/
|
|
1535
|
+
private startHeartbeat;
|
|
1536
|
+
/** Clear a connection's keepalive timer, if any. Safe to call repeatedly. */
|
|
1537
|
+
private stopHeartbeat;
|
|
1538
|
+
/** Mark every subscription bound to `shardKey` as needing a fresh ack. */
|
|
1539
|
+
private markShardPendingAck;
|
|
1540
|
+
private sendSubscribeIfOpen;
|
|
1541
|
+
private handleServerMessage;
|
|
1542
|
+
private handleErrorMessage;
|
|
1543
|
+
private handleDataMessage;
|
|
1544
|
+
/**
|
|
1545
|
+
* Handle a `resume` frame (Pillar 1b): the server proved nothing the
|
|
1546
|
+
* subscription reads changed since our `sinceSeq`, so the cached value is
|
|
1547
|
+
* still current. We keep `lastValue` as-is, mark the sub acked, and advance
|
|
1548
|
+
* the cursor (re-persisting so the next reconnect resumes from the newer
|
|
1549
|
+
* watermark). No callback fires — the value didn't change, and `subscribe()`
|
|
1550
|
+
* already replayed the cached value to every consumer synchronously.
|
|
1551
|
+
*/
|
|
1552
|
+
private handleResumeMessage;
|
|
1553
|
+
/**
|
|
1554
|
+
* Resolve the value to publish for a `data`/`delta` frame.
|
|
1555
|
+
*
|
|
1556
|
+
* A `data` frame is an authoritative snapshot (the server re-execution path)
|
|
1557
|
+
* and always replaces the cached value wholesale. A `delta` frame carrying a
|
|
1558
|
+
* structured `MutationDelta` (the `broadcastDelta` row-change path) is
|
|
1559
|
+
* merged incrementally into the cached list — preserving order, no dup/loss —
|
|
1560
|
+
* so each subscription (including every paginated page) updates by delta
|
|
1561
|
+
* rather than a full re-send. We fall back to full replacement when the
|
|
1562
|
+
* delta isn't a recognisable row change, when there's no cached value yet,
|
|
1563
|
+
* or when it can't be applied cleanly against the current cached shape.
|
|
1564
|
+
*/
|
|
1565
|
+
private resolveDataPayload;
|
|
1566
|
+
/** Route an inbound whisper to the topic's handlers on the originating shard. */
|
|
1567
|
+
private dispatchWhisper;
|
|
1568
|
+
/** Notify every {@link onTokenExpired} listener (best-effort, listener throws swallowed). */
|
|
1569
|
+
private notifyTokenExpired;
|
|
1570
|
+
private handleCompleteMessage;
|
|
1571
|
+
private unpersist;
|
|
1572
|
+
/**
|
|
1573
|
+
* Stable, non-reversible fingerprint of the current auth identity used to
|
|
1574
|
+
* stamp queued offline writes. `null` (signed out) is its own identity and
|
|
1575
|
+
* never matches a bearer-token fingerprint. The raw token is never stored;
|
|
1576
|
+
* a length-prefixed FNV-1a hash is enough to detect an identity *change*
|
|
1577
|
+
* without keeping the credential around in the queue map.
|
|
1578
|
+
*/
|
|
1579
|
+
private identityFingerprint;
|
|
1580
|
+
/**
|
|
1581
|
+
* Drain every in-memory offline write and reject it because the auth
|
|
1582
|
+
* identity changed. Durable entries are also dropped from persistence so a
|
|
1583
|
+
* later `hydrate` can't resurrect another user's writes. Stamps are cleared
|
|
1584
|
+
* alongside. Persisted entries restored without a live awaiter still get
|
|
1585
|
+
* unpersisted here.
|
|
1586
|
+
*/
|
|
1587
|
+
private rejectQueuedForIdentityChange;
|
|
1588
|
+
/**
|
|
1589
|
+
* Drop the durable read cache on an identity change so a cached value stamped
|
|
1590
|
+
* under the previous identity can never hydrate into a new session. Clears
|
|
1591
|
+
* the in-flight write batch and the not-yet-consumed hydrated entries too;
|
|
1592
|
+
* the durable `clear()` is best-effort.
|
|
1593
|
+
*/
|
|
1594
|
+
private clearQueryCacheForIdentityChange;
|
|
1595
|
+
private flushOfflineQueue;
|
|
1596
|
+
}
|
|
1597
|
+
export { ArgsOf as A, BookmarkStorage as B, CachedQuery as C, DEFAULT_MAX_BUFFER as D, StreamHandle as E, FunctionReference as F, GlobalFacetResult as G, StreamIterable as H, SubscriptionCallback as I, SubscriptionRegistry as J, SubscriptionState as K, LunoraClient as L, MutationCallOptions as M, WorkflowInstanceDetail as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, WorkflowInstancePage as T, User as U, WorkflowInstanceStatus as V, WorkflowInstanceAction as W, WorkflowInstanceSummary as X, WorkflowStepDetail as Y, createLocalStore as Z, createStream as _, Unsubscribe as a, SubscriptionErrorCallback as b, PersistenceAdapter as c, ReconnectOptions as d, ClientMessage as e, ConnectionStatus as f, FunctionArgumentDescriptor as g, FunctionDescriptor as h, GlobalFacetValue as i, GlobalFilterClause as j, GlobalTableInfo as k, GlobalTablePage as l, LunoraClientOptions as m, OptimisticLocalStore as n, OptimisticUpdate as o, PersistedMutation as p, RpcEnvelope as q, RpcResponseBody as r, ScheduleRecord as s, SchedulerPoolStatus as t, SchedulerStatus as u, ServerMessage as v, ShardTrafficEntry as w, ShardTrafficResult as x, StorageListPage as y, StorageObject as z };
|