@lunora/client 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
Files changed (37) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +2 -0
  3. package/__assets__/package-og.svg +1 -1
  4. package/dist/auth/index.d.mts +1 -1
  5. package/dist/auth/index.d.ts +1 -1
  6. package/dist/index.d.mts +144 -24
  7. package/dist/index.d.ts +144 -24
  8. package/dist/index.mjs +10 -9
  9. package/dist/packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs +33 -0
  10. package/dist/packem_shared/{DEFAULT_MAX_BUFFER-BDkqO5PW.mjs → DEFAULT_MAX_BUFFER-7hFnzNk9.mjs} +6 -3
  11. package/dist/packem_shared/{LunoraClient-UiULzH_1.mjs → LunoraClient-kXpHNyaE.mjs} +1562 -267
  12. package/dist/packem_shared/OfflineQueue-GGYJRmhF.mjs +1 -0
  13. package/dist/packem_shared/SubscriptionRegistry-DjGKZsqq.mjs +1 -0
  14. package/dist/packem_shared/{applyDelta-4jFGTPA3.mjs → applyDelta-CRKZ1PBt.mjs} +21 -1
  15. package/dist/packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs +79 -0
  16. package/dist/packem_shared/{createInMemoryQueryCache-B1PQ9Twl.mjs → createInMemoryQueryCache-DiaGZkA2.mjs} +25 -51
  17. package/dist/packem_shared/createLocalStore-jRoqmazl.mjs +2 -0
  18. package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +31 -0
  19. package/dist/packem_shared/{createServerClient-BjZc3gD8.mjs → createServerClient-DF-3mLmb.mjs} +1 -1
  20. package/dist/packem_shared/idb-utility-DrSVX43Q.mjs +48 -0
  21. package/dist/packem_shared/local-store-BveBeFEo.mjs +106 -0
  22. package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.mts → lunora-client.d-BYkEjCEJ.d.mts} +1001 -58
  23. package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.ts → lunora-client.d-BYkEjCEJ.d.ts} +1001 -58
  24. package/dist/packem_shared/{OfflineQueue-D5p_QgF_.mjs → offline-queue-B9vfdSqp.mjs} +49 -6
  25. package/dist/packem_shared/{preload.d-BoDmFqSG.d.ts → preload.d-B-vyHnml.d.ts} +1 -1
  26. package/dist/packem_shared/{preload.d-dSaRMuhL.d.mts → preload.d-DrfuisCE.d.mts} +1 -1
  27. package/dist/packem_shared/subscription-BjynOXCU.mjs +68 -0
  28. package/dist/query/index.d.mts +2 -2
  29. package/dist/query/index.d.ts +2 -2
  30. package/dist/ssr/index.d.mts +3 -3
  31. package/dist/ssr/index.d.ts +3 -3
  32. package/dist/ssr/index.mjs +1 -1
  33. package/package.json +5 -2
  34. package/dist/packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs +0 -4
  35. package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +0 -26
  36. package/dist/packem_shared/createInMemoryPersistence-CW82inU5.mjs +0 -105
  37. package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +0 -36
@@ -1,4 +1,4 @@
1
- import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-DGvyuJ_p.mjs";
1
+ import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-BYkEjCEJ.mjs";
2
2
  import '@lunora/runtime';
3
3
  interface IdentityStore {
4
4
  /** Read the current resolved user, or `null` when signed out / not yet resolved. */
@@ -1,4 +1,4 @@
1
- import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-DGvyuJ_p.js";
1
+ import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-BYkEjCEJ.js";
2
2
  import '@lunora/runtime';
3
3
  interface IdentityStore {
4
4
  /** Read the current resolved user, or `null` when signed out / not yet resolved. */
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-DGvyuJ_p.mjs";
2
- export { type C as CachedQuery, type e as ClientMessage, type f as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type g as FunctionArgumentDescriptor, type h as FunctionDescriptor, type G as GlobalFacetResult, type i as GlobalFacetValue, type j as GlobalFilterClause, type k as GlobalTableInfo, type l as GlobalTablePage, L as LunoraClient, type m as LunoraClientOptions, type n as OptimisticLocalStore, type o as OptimisticUpdate, type p as PersistedMutation, type P as Preloaded, type q as RpcEnvelope, type r as RpcResponseBody, type s as ScheduleRecord, type t as SchedulerPoolStatus, type u as SchedulerStatus, type v as ServerMessage, type w as ShardTrafficEntry, type x as ShardTrafficResult, type y as StorageListPage, type z as StorageObject, type E as StreamHandle, type H as StreamIterable, type I as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, J as SubscriptionRegistry, type K as SubscriptionState, type a as Unsubscribe, type U as User, type W as WorkflowInstanceAction, type N as WorkflowInstanceDetail, type T as WorkflowInstancePage, type V as WorkflowInstanceStatus, type X as WorkflowInstanceSummary, type Y as WorkflowStepDetail, Z as createLocalStore, _ as createStream } from "./packem_shared/lunora-client.d-DGvyuJ_p.mjs";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-dSaRMuhL.mjs";
4
- export type { AuthCapabilities, AuthImpersonation, AuthPage, AuthSession, AuthUser, CronJobInfo, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
1
+ import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-BYkEjCEJ.mjs";
2
+ export { type e as BatchSlot, C as CONFLICT_ERROR_CODE, type f as CachedQuery, type g as ClientMessage, type h as ClientShapeSubscribeMessage, type i as ClientShapeUnsubscribeMessage, type j as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type k as FunctionArgumentDescriptor, type l as FunctionDescriptor, type G as GlobalFacetResult, type m as GlobalFacetValue, type n as GlobalFilterClause, type o as GlobalTableInfo, type p as GlobalTablePage, L as LunoraClient, type q as LunoraClientError, type r as LunoraClientOptions, type s as LunoraErrorCode, type t as MutationSettledEvent, type u as OptimisticLocalStore, type v as OptimisticUpdate, type w as OutboxMutation, type x as OutboxSink, type y as PersistedMutation, type P as Preloaded, type z as RowOp, type E as RpcEnvelope, type H as RpcResponseBody, type I as ScheduleRecord, type J as SchedulerPoolStatus, type K as SchedulerStatus, type N as ServerMessage, type T as ServerPokeEndMessage, type V as ServerPokePartMessage, type W as ServerPokeStartMessage, type X as ShardTrafficEntry, type Y as ShardTrafficResult, type Z as StorageListPage, type _ as StorageObject, type $ as StreamHandle, type a0 as StreamIterable, type a1 as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, a2 as SubscriptionRegistry, type a3 as SubscriptionState, type a4 as SyncWatermark, type a as Unsubscribe, type U as User, type a5 as WorkflowInstanceAction, type a6 as WorkflowInstanceDetail, type a7 as WorkflowInstancePage, type a8 as WorkflowInstanceStatus, type a9 as WorkflowInstanceSummary, type aa as WorkflowStepDetail, ab as createLocalStore, ac as createStream, ad as getErrorCode, ae as getRetryAfterMs, af as isConflictError, ag as isForbiddenError, ah as isRateLimitedError, ai as isUnauthorizedError } from "./packem_shared/lunora-client.d-BYkEjCEJ.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-DrfuisCE.mjs";
4
+ export type { AuthCapabilities, AuthConfigInfo, AuthImpersonation, AuthPage, AuthSession, AuthUser, AuthUserFieldSpec, CronJobInfo, KvKeyEntry, KvKeyListResult, KvNamespaceSummary, KvValueResult, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
5
5
  /**
6
6
  * The slice of React Native's `AsyncStorage` (or any async key/value store —
7
7
  * Expo `SecureStore`, a wrapped `localForage`, an in-memory map in tests) this
@@ -95,17 +95,6 @@ declare const isMutationDelta: (value: unknown) => value is MutationDelta;
95
95
  * when an `insert`/`update` delta carries no `row` to splice in.
96
96
  */
97
97
  declare const applyDelta: (current: unknown, delta: MutationDelta) => undefined | unknown[];
98
- /** Error code the server uses for optimistic-concurrency conflicts (HTTP 409). */
99
- declare const CONFLICT_ERROR_CODE = "CONFLICT";
100
- /**
101
- * Whether an unknown rejection is an optimistic-concurrency conflict — the
102
- * server lost a write race and the caller should refetch and retry (or surface
103
- * the conflict). Structural check on the `code` property the client attaches
104
- * when decoding the worker's `{ error: { code, message } }` envelope.
105
- */
106
- declare const isConflictError: (error: unknown) => error is Error & {
107
- code: "CONFLICT";
108
- };
109
98
  /** The single transport method a mutation runner needs — narrowed so adapters can test against a stub. */
110
99
  interface MutationCapableClient<F extends FunctionReference> {
111
100
  mutation: (function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
@@ -136,6 +125,59 @@ interface MutationRunnerSinks<R> {
136
125
  * pass straight through to `client.mutation`.
137
126
  */
138
127
  declare const createMutationRunner: <F extends FunctionReference>(client: MutationCapableClient<F>, function_: F, sinks: MutationRunnerSinks<ReturnOf<F>>) => ((args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>);
128
+ /**
129
+ * The structural surface of a TanStack `Transaction` a bound custom mutator
130
+ * returns — its `isPersisted.promise` resolves once the write is persisted and
131
+ * rejects on failure. Typed structurally so the framework adapters need not
132
+ * depend on `@tanstack/db` or `@lunora/db` (the handle is created app-side by
133
+ * `bindMutators`).
134
+ */
135
+ interface MutatorTransaction {
136
+ isPersisted: {
137
+ promise: Promise<unknown>;
138
+ };
139
+ }
140
+ /**
141
+ * A bound custom-mutator handle produced by `bindMutators(client, ctx, mutators)`
142
+ * in `@lunora/db`. Calling it applies the optimistic overlay to the local
143
+ * collections and pushes the authoritative server write; it returns the TanStack
144
+ * transaction whose `isPersisted` promise tracks completion.
145
+ */
146
+ type MutatorHandle<TArgs> = (args: TArgs) => MutatorTransaction;
147
+ /**
148
+ * Reactive sinks an adapter binds to its own primitive's setters (a React
149
+ * `useState`, a Solid signal, a Vue ref, a Svelte store). The runner pushes into
150
+ * them; how they store the value is the adapter's concern.
151
+ */
152
+ interface MutatorRunnerSinks {
153
+ /** Receives the normalized {@link Error} when an invocation rejects, or `undefined` on success / reset. */
154
+ setError: (error: Error | undefined) => void;
155
+ /** Receives `true` while at least one invocation is in flight (ref-counted across overlapping calls), else `false`. */
156
+ setPending: (pending: boolean) => void;
157
+ }
158
+ /**
159
+ * Build the framework-neutral `mutate` / `reset` pair of an adapter's
160
+ * custom-mutator hook (`useMutator` / `createMutator` / `mutator`).
161
+ *
162
+ * Owns the orchestration every adapter otherwise copy-pastes: ref-counts
163
+ * overlapping invocations into `setPending` (so it only clears once the last
164
+ * settles), awaits the bound handle's `isPersisted` promise, normalizes a thrown
165
+ * non-`Error`, and routes failure to `setError` (clearing it on success) before
166
+ * re-throwing. Each adapter (`@lunora/react`, `/solid`, `/svelte`, `/vue`) binds
167
+ * the two sinks to its own reactive setters, so this logic lives in exactly one
168
+ * place. The optimistic overlay + server push are owned by the bound handle.
169
+ *
170
+ * `error` tracks the LATEST invocation, not the last to settle: overlapping
171
+ * calls can resolve out of order, so an earlier call that finishes later must
172
+ * not clobber a newer call's outcome. Each invocation takes a monotonic token
173
+ * and only writes `setError` while it is still the most recent one — otherwise
174
+ * `error`/`isError` could surface a stale success or failure (the documented
175
+ * "latest invocation's error" contract every adapter advertises).
176
+ */
177
+ declare const createMutatorRunner: <TArgs>(handle: MutatorHandle<TArgs>, sinks: MutatorRunnerSinks) => {
178
+ mutate: (args: TArgs) => Promise<void>;
179
+ reset: () => void;
180
+ };
139
181
  interface QueuedMutation<T = unknown> {
140
182
  readonly args: Record<string, unknown>;
141
183
  readonly functionPath: string;
@@ -146,12 +188,57 @@ interface QueuedMutation<T = unknown> {
146
188
  * signed out). Absent on hydrated legacy records, which replay ambiently.
147
189
  */
148
190
  readonly identity?: string | null;
191
+ /**
192
+ * `true` when a live caller is still awaiting this write's `mutation()`
193
+ * Promise; `false`/absent for a write restored from durable storage after a
194
+ * reload (its original awaiter is gone). Carried so terminal-verdict
195
+ * observers can distinguish "the caller already saw this" from "nothing else
196
+ * will report this". Maps to the public `MutationSettledEvent.hadAwaiter`.
197
+ */
198
+ liveAwaiter?: boolean;
199
+ /**
200
+ * Invoked on a successful replay with the server's echoed commit CDC cursor,
201
+ * so a live per-call optimistic layer drops gaplessly once a frame reaches it.
202
+ * Absent on hydrated records (the optimistic write lived in a prior session).
203
+ */
204
+ readonly onCommit?: (commitCursor: number | undefined) => void;
149
205
  /** Rejects if the mutation can no longer be replayed. */
150
206
  readonly reject: (error: unknown) => void;
151
207
  /** Resolves once the mutation has been replayed against the server. */
152
208
  readonly resolve: (value: T) => void;
153
209
  readonly shardKey?: string;
154
210
  }
211
+ /**
212
+ * Invoked when the queue itself discards an entry on overflow (capacity
213
+ * eviction), so the client can surface the dropped write on its
214
+ * terminal-verdict observer even when the entry has no live awaiter (a hydrated
215
+ * record). The `error` carries the `OFFLINE_QUEUE_OVERFLOW` code.
216
+ */
217
+ type EvictHandler = (entry: QueuedMutation, error: Error & {
218
+ code?: string;
219
+ }) => void;
220
+ /** Injected dependencies for {@link OfflineQueue} (kept off the user-facing {@link OfflineQueueOptions}). */
221
+ interface OfflineQueueDeps {
222
+ /** Invoked when an entry is discarded on capacity overflow (carries `OFFLINE_QUEUE_OVERFLOW`). */
223
+ onEvict?: EvictHandler;
224
+ /** Invoked with the new depth after any size change (drives the client's pending-sync count). */
225
+ onSizeChange?: (size: number) => void;
226
+ /** Durable store; when present, writes are mirrored and restored across reloads. */
227
+ persistence?: PersistenceAdapter;
228
+ /** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
229
+ version?: string;
230
+ }
231
+ /**
232
+ * A process-unique id, used both per-mutation and as the fallback `clientId`. It
233
+ * MUST be globally unique: the server scopes a custom mutator's replay watermark
234
+ * by `(verifiedIdentity, clientId)`, and an anonymous push has no verified
235
+ * identity — so two anonymous clients that collide on `clientId` would share one
236
+ * watermark namespace, letting one stall/suppress the other's ordered mutations.
237
+ * `crypto.randomUUID` covers every modern runtime; the fallback still mixes
238
+ * crypto-quality (or `Math.random`) entropy with the timestamp + counter so it
239
+ * can't collide across two clients started in the same millisecond.
240
+ */
241
+
155
242
  /**
156
243
  * Bounded FIFO queue. Mutations issued while the client is offline are
157
244
  * enqueued and replayed in the order they were submitted once the WS
@@ -170,8 +257,12 @@ declare class OfflineQueue {
170
257
  private readonly maxItems;
171
258
  private readonly onPersistenceError;
172
259
  private readonly persistence;
260
+ private readonly onEvict;
261
+ private readonly onSizeChange;
262
+ /** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
263
+ private readonly version;
173
264
  private readonly items;
174
- constructor(options?: OfflineQueueOptions, persistence?: PersistenceAdapter);
265
+ constructor(options?: OfflineQueueOptions, deps?: OfflineQueueDeps);
175
266
  get size(): number;
176
267
  enqueue<T>(entry: QueuedMutation<T>): void;
177
268
  /**
@@ -198,6 +289,8 @@ declare class OfflineQueue {
198
289
  */
199
290
  requeue(items: QueuedMutation[]): void;
200
291
  clear(): void;
292
+ /** Notify the size observer (the client's pending-sync count) after any change. */
293
+ private notifySize;
201
294
  }
202
295
  /**
203
296
  * In-memory {@link PersistenceAdapter}. Doesn't survive a reload — it exists so
@@ -207,7 +300,7 @@ declare class OfflineQueue {
207
300
  */
208
301
  declare const createInMemoryPersistence: () => PersistenceAdapter;
209
302
  interface IndexedDbPersistenceOptions {
210
- /** Database name; defaults to `"lunora"`. */
303
+ /** Database name; defaults to `"lunora-outbox"` (its own DB, separate from the read cache). */
211
304
  databaseName?: string;
212
305
  /** Injectable `IDBFactory` (e.g. `fake-indexeddb` in tests); defaults to the global `indexedDB`. */
213
306
  indexedDB?: IDBFactory;
@@ -225,6 +318,24 @@ interface IndexedDbPersistenceOptions {
225
318
  */
226
319
  declare const createIndexedDbPersistence: (options?: IndexedDbPersistenceOptions) => PersistenceAdapter;
227
320
  /**
321
+ * Resolve the effective offline-queue persistence from the user option,
322
+ * defaulting to a durable IndexedDB store when the environment supports one.
323
+ *
324
+ * An explicit adapter is used as-is; `false` opts out (the caller keeps an
325
+ * in-memory queue, lost on reload); `undefined` (the default) auto-probes
326
+ * IndexedDB when the global is present (browsers) and `autoProbe` is set,
327
+ * otherwise `undefined` — so SSR/Node/React-Native keep today's in-memory
328
+ * behaviour and only environments that can persist do.
329
+ *
330
+ * `autoProbe` is `false` when the `@lunora/db` outbox is wired: that sink is the
331
+ * single durable write path, so the built-in queue must stay in memory rather
332
+ * than persist a second, never-flushed copy. An explicit adapter is still
333
+ * honoured (the caller asked for it); only the implicit default is suppressed.
334
+ *
335
+ * The IndexedDB adapter opens its connection lazily, so constructing it here is
336
+ * cheap and never throws (the `indexedDB` global is verified present first).
337
+ */
338
+ /**
228
339
  * Compose the read-cache key for a subscription. Mirrors how
229
340
  * `SubscriptionRegistry` keys live subscriptions so a hydrated value lines up
230
341
  * with the subscription that will consume it. `shardKey` defaults to `""` (the
@@ -242,7 +353,7 @@ declare const createInMemoryQueryCache: (options?: {
242
353
  maxEntries?: number;
243
354
  }) => QueryCacheAdapter;
244
355
  interface IndexedDbQueryCacheOptions {
245
- /** Database name; defaults to `"lunora"` (shared with the offline-mutation store). */
356
+ /** Database name; defaults to `"lunora-query-cache"` (its own DB, separate from the offline outbox). */
246
357
  databaseName?: string;
247
358
  /** Injectable `IDBFactory` (e.g. `fake-indexeddb` in tests); defaults to the global `indexedDB`. */
248
359
  indexedDB?: IDBFactory;
@@ -257,14 +368,23 @@ interface IndexedDbQueryCacheOptions {
257
368
  * LRU eviction. The store handle is opened lazily and cached, so repeated ops
258
369
  * reuse one connection.
259
370
  *
260
- * The store lives in the same `lunora` database as the offline-mutation queue
261
- * (bumped to schema v2). Opening it upgrades a v1 database in place, adding the
262
- * `query-cache` store without touching `offline-mutations`. Throws eagerly if no
263
- * `IDBFactory` is available — callers in non-browser environments should use
264
- * {@link createInMemoryQueryCache}.
371
+ * The store lives in its own `lunora-query-cache` database deliberately
372
+ * separate from the offline-mutation outbox's `lunora-outbox` database so the two
373
+ * independently-toggleable adapters never share (and drift on) a schema version.
374
+ * Throws eagerly if no `IDBFactory` is available — callers in non-browser
375
+ * environments should use {@link createInMemoryQueryCache}.
265
376
  */
266
377
  declare const createIndexedDbQueryCache: (options?: IndexedDbQueryCacheOptions) => QueryCacheAdapter;
267
378
  /**
379
+ * Resolve the effective read-cache from the user option, defaulting to a durable
380
+ * IndexedDB store when the environment supports one. Same tri-state semantics as
381
+ * `resolvePersistenceAdapter` in `./persistence`:
382
+ *
383
+ * - an explicit adapter is used as-is;
384
+ * - `false` opts out — reads stay in memory only;
385
+ * - `undefined` (the default) auto-probes IndexedDB (browsers), else `undefined`.
386
+ */
387
+ /**
268
388
  * Exponential backoff calculator with optional jitter.
269
389
  *
270
390
  * `next()` doubles the delay each call up to `maxDelayMs`. When `jitter` is
@@ -278,4 +398,4 @@ interface ReconnectCalculator {
278
398
  reset: () => void;
279
399
  }
280
400
  declare const createReconnect: (options?: ReconnectOptions, random?: () => number) => ReconnectCalculator;
281
- export { type ArgsOf, type AsyncStorageLike, type AsyncStoragePersistenceOptions, type BookmarkStorage, CONFLICT_ERROR_CODE, type FunctionReference, type IndexedDbPersistenceOptions, type IndexedDbQueryCacheOptions, type MutationCallOptions, type MutationDelta, type MutationRunnerSinks, OfflineQueue, type OfflineQueueOptions, type PersistenceAdapter, type QueryCacheAdapter, type QueuedMutation, type ReconnectCalculator, type ReconnectOptions, type ReturnOf, applyDelta, createAsyncStoragePersistence, createInMemoryBookmarkStorage, createInMemoryPersistence, createInMemoryQueryCache, createIndexedDbPersistence, createIndexedDbQueryCache, createMutationRunner, createReconnect, isConflictError, isMutationDelta, queryCacheKey };
401
+ export { type ArgsOf, type AsyncStorageLike, type AsyncStoragePersistenceOptions, type BookmarkStorage, type FunctionReference, type IndexedDbPersistenceOptions, type IndexedDbQueryCacheOptions, type MutationCallOptions, type MutationDelta, type MutationRunnerSinks, type MutatorHandle, type MutatorRunnerSinks, type MutatorTransaction, OfflineQueue, type OfflineQueueOptions, type PersistenceAdapter, type QueryCacheAdapter, type QueuedMutation, type ReconnectCalculator, type ReconnectOptions, type ReturnOf, applyDelta, createAsyncStoragePersistence, createInMemoryBookmarkStorage, createInMemoryPersistence, createInMemoryQueryCache, createIndexedDbPersistence, createIndexedDbQueryCache, createMutationRunner, createMutatorRunner, createReconnect, isMutationDelta, queryCacheKey };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-DGvyuJ_p.js";
2
- export { type C as CachedQuery, type e as ClientMessage, type f as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type g as FunctionArgumentDescriptor, type h as FunctionDescriptor, type G as GlobalFacetResult, type i as GlobalFacetValue, type j as GlobalFilterClause, type k as GlobalTableInfo, type l as GlobalTablePage, L as LunoraClient, type m as LunoraClientOptions, type n as OptimisticLocalStore, type o as OptimisticUpdate, type p as PersistedMutation, type P as Preloaded, type q as RpcEnvelope, type r as RpcResponseBody, type s as ScheduleRecord, type t as SchedulerPoolStatus, type u as SchedulerStatus, type v as ServerMessage, type w as ShardTrafficEntry, type x as ShardTrafficResult, type y as StorageListPage, type z as StorageObject, type E as StreamHandle, type H as StreamIterable, type I as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, J as SubscriptionRegistry, type K as SubscriptionState, type a as Unsubscribe, type U as User, type W as WorkflowInstanceAction, type N as WorkflowInstanceDetail, type T as WorkflowInstancePage, type V as WorkflowInstanceStatus, type X as WorkflowInstanceSummary, type Y as WorkflowStepDetail, Z as createLocalStore, _ as createStream } from "./packem_shared/lunora-client.d-DGvyuJ_p.js";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-BoDmFqSG.js";
4
- export type { AuthCapabilities, AuthImpersonation, AuthPage, AuthSession, AuthUser, CronJobInfo, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
1
+ import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-BYkEjCEJ.js";
2
+ export { type e as BatchSlot, C as CONFLICT_ERROR_CODE, type f as CachedQuery, type g as ClientMessage, type h as ClientShapeSubscribeMessage, type i as ClientShapeUnsubscribeMessage, type j as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type k as FunctionArgumentDescriptor, type l as FunctionDescriptor, type G as GlobalFacetResult, type m as GlobalFacetValue, type n as GlobalFilterClause, type o as GlobalTableInfo, type p as GlobalTablePage, L as LunoraClient, type q as LunoraClientError, type r as LunoraClientOptions, type s as LunoraErrorCode, type t as MutationSettledEvent, type u as OptimisticLocalStore, type v as OptimisticUpdate, type w as OutboxMutation, type x as OutboxSink, type y as PersistedMutation, type P as Preloaded, type z as RowOp, type E as RpcEnvelope, type H as RpcResponseBody, type I as ScheduleRecord, type J as SchedulerPoolStatus, type K as SchedulerStatus, type N as ServerMessage, type T as ServerPokeEndMessage, type V as ServerPokePartMessage, type W as ServerPokeStartMessage, type X as ShardTrafficEntry, type Y as ShardTrafficResult, type Z as StorageListPage, type _ as StorageObject, type $ as StreamHandle, type a0 as StreamIterable, type a1 as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, a2 as SubscriptionRegistry, type a3 as SubscriptionState, type a4 as SyncWatermark, type a as Unsubscribe, type U as User, type a5 as WorkflowInstanceAction, type a6 as WorkflowInstanceDetail, type a7 as WorkflowInstancePage, type a8 as WorkflowInstanceStatus, type a9 as WorkflowInstanceSummary, type aa as WorkflowStepDetail, ab as createLocalStore, ac as createStream, ad as getErrorCode, ae as getRetryAfterMs, af as isConflictError, ag as isForbiddenError, ah as isRateLimitedError, ai as isUnauthorizedError } from "./packem_shared/lunora-client.d-BYkEjCEJ.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-B-vyHnml.js";
4
+ export type { AuthCapabilities, AuthConfigInfo, AuthImpersonation, AuthPage, AuthSession, AuthUser, AuthUserFieldSpec, CronJobInfo, KvKeyEntry, KvKeyListResult, KvNamespaceSummary, KvValueResult, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
5
5
  /**
6
6
  * The slice of React Native's `AsyncStorage` (or any async key/value store —
7
7
  * Expo `SecureStore`, a wrapped `localForage`, an in-memory map in tests) this
@@ -95,17 +95,6 @@ declare const isMutationDelta: (value: unknown) => value is MutationDelta;
95
95
  * when an `insert`/`update` delta carries no `row` to splice in.
96
96
  */
97
97
  declare const applyDelta: (current: unknown, delta: MutationDelta) => undefined | unknown[];
98
- /** Error code the server uses for optimistic-concurrency conflicts (HTTP 409). */
99
- declare const CONFLICT_ERROR_CODE = "CONFLICT";
100
- /**
101
- * Whether an unknown rejection is an optimistic-concurrency conflict — the
102
- * server lost a write race and the caller should refetch and retry (or surface
103
- * the conflict). Structural check on the `code` property the client attaches
104
- * when decoding the worker's `{ error: { code, message } }` envelope.
105
- */
106
- declare const isConflictError: (error: unknown) => error is Error & {
107
- code: "CONFLICT";
108
- };
109
98
  /** The single transport method a mutation runner needs — narrowed so adapters can test against a stub. */
110
99
  interface MutationCapableClient<F extends FunctionReference> {
111
100
  mutation: (function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
@@ -136,6 +125,59 @@ interface MutationRunnerSinks<R> {
136
125
  * pass straight through to `client.mutation`.
137
126
  */
138
127
  declare const createMutationRunner: <F extends FunctionReference>(client: MutationCapableClient<F>, function_: F, sinks: MutationRunnerSinks<ReturnOf<F>>) => ((args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>);
128
+ /**
129
+ * The structural surface of a TanStack `Transaction` a bound custom mutator
130
+ * returns — its `isPersisted.promise` resolves once the write is persisted and
131
+ * rejects on failure. Typed structurally so the framework adapters need not
132
+ * depend on `@tanstack/db` or `@lunora/db` (the handle is created app-side by
133
+ * `bindMutators`).
134
+ */
135
+ interface MutatorTransaction {
136
+ isPersisted: {
137
+ promise: Promise<unknown>;
138
+ };
139
+ }
140
+ /**
141
+ * A bound custom-mutator handle produced by `bindMutators(client, ctx, mutators)`
142
+ * in `@lunora/db`. Calling it applies the optimistic overlay to the local
143
+ * collections and pushes the authoritative server write; it returns the TanStack
144
+ * transaction whose `isPersisted` promise tracks completion.
145
+ */
146
+ type MutatorHandle<TArgs> = (args: TArgs) => MutatorTransaction;
147
+ /**
148
+ * Reactive sinks an adapter binds to its own primitive's setters (a React
149
+ * `useState`, a Solid signal, a Vue ref, a Svelte store). The runner pushes into
150
+ * them; how they store the value is the adapter's concern.
151
+ */
152
+ interface MutatorRunnerSinks {
153
+ /** Receives the normalized {@link Error} when an invocation rejects, or `undefined` on success / reset. */
154
+ setError: (error: Error | undefined) => void;
155
+ /** Receives `true` while at least one invocation is in flight (ref-counted across overlapping calls), else `false`. */
156
+ setPending: (pending: boolean) => void;
157
+ }
158
+ /**
159
+ * Build the framework-neutral `mutate` / `reset` pair of an adapter's
160
+ * custom-mutator hook (`useMutator` / `createMutator` / `mutator`).
161
+ *
162
+ * Owns the orchestration every adapter otherwise copy-pastes: ref-counts
163
+ * overlapping invocations into `setPending` (so it only clears once the last
164
+ * settles), awaits the bound handle's `isPersisted` promise, normalizes a thrown
165
+ * non-`Error`, and routes failure to `setError` (clearing it on success) before
166
+ * re-throwing. Each adapter (`@lunora/react`, `/solid`, `/svelte`, `/vue`) binds
167
+ * the two sinks to its own reactive setters, so this logic lives in exactly one
168
+ * place. The optimistic overlay + server push are owned by the bound handle.
169
+ *
170
+ * `error` tracks the LATEST invocation, not the last to settle: overlapping
171
+ * calls can resolve out of order, so an earlier call that finishes later must
172
+ * not clobber a newer call's outcome. Each invocation takes a monotonic token
173
+ * and only writes `setError` while it is still the most recent one — otherwise
174
+ * `error`/`isError` could surface a stale success or failure (the documented
175
+ * "latest invocation's error" contract every adapter advertises).
176
+ */
177
+ declare const createMutatorRunner: <TArgs>(handle: MutatorHandle<TArgs>, sinks: MutatorRunnerSinks) => {
178
+ mutate: (args: TArgs) => Promise<void>;
179
+ reset: () => void;
180
+ };
139
181
  interface QueuedMutation<T = unknown> {
140
182
  readonly args: Record<string, unknown>;
141
183
  readonly functionPath: string;
@@ -146,12 +188,57 @@ interface QueuedMutation<T = unknown> {
146
188
  * signed out). Absent on hydrated legacy records, which replay ambiently.
147
189
  */
148
190
  readonly identity?: string | null;
191
+ /**
192
+ * `true` when a live caller is still awaiting this write's `mutation()`
193
+ * Promise; `false`/absent for a write restored from durable storage after a
194
+ * reload (its original awaiter is gone). Carried so terminal-verdict
195
+ * observers can distinguish "the caller already saw this" from "nothing else
196
+ * will report this". Maps to the public `MutationSettledEvent.hadAwaiter`.
197
+ */
198
+ liveAwaiter?: boolean;
199
+ /**
200
+ * Invoked on a successful replay with the server's echoed commit CDC cursor,
201
+ * so a live per-call optimistic layer drops gaplessly once a frame reaches it.
202
+ * Absent on hydrated records (the optimistic write lived in a prior session).
203
+ */
204
+ readonly onCommit?: (commitCursor: number | undefined) => void;
149
205
  /** Rejects if the mutation can no longer be replayed. */
150
206
  readonly reject: (error: unknown) => void;
151
207
  /** Resolves once the mutation has been replayed against the server. */
152
208
  readonly resolve: (value: T) => void;
153
209
  readonly shardKey?: string;
154
210
  }
211
+ /**
212
+ * Invoked when the queue itself discards an entry on overflow (capacity
213
+ * eviction), so the client can surface the dropped write on its
214
+ * terminal-verdict observer even when the entry has no live awaiter (a hydrated
215
+ * record). The `error` carries the `OFFLINE_QUEUE_OVERFLOW` code.
216
+ */
217
+ type EvictHandler = (entry: QueuedMutation, error: Error & {
218
+ code?: string;
219
+ }) => void;
220
+ /** Injected dependencies for {@link OfflineQueue} (kept off the user-facing {@link OfflineQueueOptions}). */
221
+ interface OfflineQueueDeps {
222
+ /** Invoked when an entry is discarded on capacity overflow (carries `OFFLINE_QUEUE_OVERFLOW`). */
223
+ onEvict?: EvictHandler;
224
+ /** Invoked with the new depth after any size change (drives the client's pending-sync count). */
225
+ onSizeChange?: (size: number) => void;
226
+ /** Durable store; when present, writes are mirrored and restored across reloads. */
227
+ persistence?: PersistenceAdapter;
228
+ /** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
229
+ version?: string;
230
+ }
231
+ /**
232
+ * A process-unique id, used both per-mutation and as the fallback `clientId`. It
233
+ * MUST be globally unique: the server scopes a custom mutator's replay watermark
234
+ * by `(verifiedIdentity, clientId)`, and an anonymous push has no verified
235
+ * identity — so two anonymous clients that collide on `clientId` would share one
236
+ * watermark namespace, letting one stall/suppress the other's ordered mutations.
237
+ * `crypto.randomUUID` covers every modern runtime; the fallback still mixes
238
+ * crypto-quality (or `Math.random`) entropy with the timestamp + counter so it
239
+ * can't collide across two clients started in the same millisecond.
240
+ */
241
+
155
242
  /**
156
243
  * Bounded FIFO queue. Mutations issued while the client is offline are
157
244
  * enqueued and replayed in the order they were submitted once the WS
@@ -170,8 +257,12 @@ declare class OfflineQueue {
170
257
  private readonly maxItems;
171
258
  private readonly onPersistenceError;
172
259
  private readonly persistence;
260
+ private readonly onEvict;
261
+ private readonly onSizeChange;
262
+ /** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
263
+ private readonly version;
173
264
  private readonly items;
174
- constructor(options?: OfflineQueueOptions, persistence?: PersistenceAdapter);
265
+ constructor(options?: OfflineQueueOptions, deps?: OfflineQueueDeps);
175
266
  get size(): number;
176
267
  enqueue<T>(entry: QueuedMutation<T>): void;
177
268
  /**
@@ -198,6 +289,8 @@ declare class OfflineQueue {
198
289
  */
199
290
  requeue(items: QueuedMutation[]): void;
200
291
  clear(): void;
292
+ /** Notify the size observer (the client's pending-sync count) after any change. */
293
+ private notifySize;
201
294
  }
202
295
  /**
203
296
  * In-memory {@link PersistenceAdapter}. Doesn't survive a reload — it exists so
@@ -207,7 +300,7 @@ declare class OfflineQueue {
207
300
  */
208
301
  declare const createInMemoryPersistence: () => PersistenceAdapter;
209
302
  interface IndexedDbPersistenceOptions {
210
- /** Database name; defaults to `"lunora"`. */
303
+ /** Database name; defaults to `"lunora-outbox"` (its own DB, separate from the read cache). */
211
304
  databaseName?: string;
212
305
  /** Injectable `IDBFactory` (e.g. `fake-indexeddb` in tests); defaults to the global `indexedDB`. */
213
306
  indexedDB?: IDBFactory;
@@ -225,6 +318,24 @@ interface IndexedDbPersistenceOptions {
225
318
  */
226
319
  declare const createIndexedDbPersistence: (options?: IndexedDbPersistenceOptions) => PersistenceAdapter;
227
320
  /**
321
+ * Resolve the effective offline-queue persistence from the user option,
322
+ * defaulting to a durable IndexedDB store when the environment supports one.
323
+ *
324
+ * An explicit adapter is used as-is; `false` opts out (the caller keeps an
325
+ * in-memory queue, lost on reload); `undefined` (the default) auto-probes
326
+ * IndexedDB when the global is present (browsers) and `autoProbe` is set,
327
+ * otherwise `undefined` — so SSR/Node/React-Native keep today's in-memory
328
+ * behaviour and only environments that can persist do.
329
+ *
330
+ * `autoProbe` is `false` when the `@lunora/db` outbox is wired: that sink is the
331
+ * single durable write path, so the built-in queue must stay in memory rather
332
+ * than persist a second, never-flushed copy. An explicit adapter is still
333
+ * honoured (the caller asked for it); only the implicit default is suppressed.
334
+ *
335
+ * The IndexedDB adapter opens its connection lazily, so constructing it here is
336
+ * cheap and never throws (the `indexedDB` global is verified present first).
337
+ */
338
+ /**
228
339
  * Compose the read-cache key for a subscription. Mirrors how
229
340
  * `SubscriptionRegistry` keys live subscriptions so a hydrated value lines up
230
341
  * with the subscription that will consume it. `shardKey` defaults to `""` (the
@@ -242,7 +353,7 @@ declare const createInMemoryQueryCache: (options?: {
242
353
  maxEntries?: number;
243
354
  }) => QueryCacheAdapter;
244
355
  interface IndexedDbQueryCacheOptions {
245
- /** Database name; defaults to `"lunora"` (shared with the offline-mutation store). */
356
+ /** Database name; defaults to `"lunora-query-cache"` (its own DB, separate from the offline outbox). */
246
357
  databaseName?: string;
247
358
  /** Injectable `IDBFactory` (e.g. `fake-indexeddb` in tests); defaults to the global `indexedDB`. */
248
359
  indexedDB?: IDBFactory;
@@ -257,14 +368,23 @@ interface IndexedDbQueryCacheOptions {
257
368
  * LRU eviction. The store handle is opened lazily and cached, so repeated ops
258
369
  * reuse one connection.
259
370
  *
260
- * The store lives in the same `lunora` database as the offline-mutation queue
261
- * (bumped to schema v2). Opening it upgrades a v1 database in place, adding the
262
- * `query-cache` store without touching `offline-mutations`. Throws eagerly if no
263
- * `IDBFactory` is available — callers in non-browser environments should use
264
- * {@link createInMemoryQueryCache}.
371
+ * The store lives in its own `lunora-query-cache` database deliberately
372
+ * separate from the offline-mutation outbox's `lunora-outbox` database so the two
373
+ * independently-toggleable adapters never share (and drift on) a schema version.
374
+ * Throws eagerly if no `IDBFactory` is available — callers in non-browser
375
+ * environments should use {@link createInMemoryQueryCache}.
265
376
  */
266
377
  declare const createIndexedDbQueryCache: (options?: IndexedDbQueryCacheOptions) => QueryCacheAdapter;
267
378
  /**
379
+ * Resolve the effective read-cache from the user option, defaulting to a durable
380
+ * IndexedDB store when the environment supports one. Same tri-state semantics as
381
+ * `resolvePersistenceAdapter` in `./persistence`:
382
+ *
383
+ * - an explicit adapter is used as-is;
384
+ * - `false` opts out — reads stay in memory only;
385
+ * - `undefined` (the default) auto-probes IndexedDB (browsers), else `undefined`.
386
+ */
387
+ /**
268
388
  * Exponential backoff calculator with optional jitter.
269
389
  *
270
390
  * `next()` doubles the delay each call up to `maxDelayMs`. When `jitter` is
@@ -278,4 +398,4 @@ interface ReconnectCalculator {
278
398
  reset: () => void;
279
399
  }
280
400
  declare const createReconnect: (options?: ReconnectOptions, random?: () => number) => ReconnectCalculator;
281
- export { type ArgsOf, type AsyncStorageLike, type AsyncStoragePersistenceOptions, type BookmarkStorage, CONFLICT_ERROR_CODE, type FunctionReference, type IndexedDbPersistenceOptions, type IndexedDbQueryCacheOptions, type MutationCallOptions, type MutationDelta, type MutationRunnerSinks, OfflineQueue, type OfflineQueueOptions, type PersistenceAdapter, type QueryCacheAdapter, type QueuedMutation, type ReconnectCalculator, type ReconnectOptions, type ReturnOf, applyDelta, createAsyncStoragePersistence, createInMemoryBookmarkStorage, createInMemoryPersistence, createInMemoryQueryCache, createIndexedDbPersistence, createIndexedDbQueryCache, createMutationRunner, createReconnect, isConflictError, isMutationDelta, queryCacheKey };
401
+ export { type ArgsOf, type AsyncStorageLike, type AsyncStoragePersistenceOptions, type BookmarkStorage, type FunctionReference, type IndexedDbPersistenceOptions, type IndexedDbQueryCacheOptions, type MutationCallOptions, type MutationDelta, type MutationRunnerSinks, type MutatorHandle, type MutatorRunnerSinks, type MutatorTransaction, OfflineQueue, type OfflineQueueOptions, type PersistenceAdapter, type QueryCacheAdapter, type QueuedMutation, type ReconnectCalculator, type ReconnectOptions, type ReturnOf, applyDelta, createAsyncStoragePersistence, createInMemoryBookmarkStorage, createInMemoryPersistence, createInMemoryQueryCache, createIndexedDbPersistence, createIndexedDbQueryCache, createMutationRunner, createMutatorRunner, createReconnect, isMutationDelta, queryCacheKey };
package/dist/index.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  export { createAsyncStoragePersistence } from './packem_shared/createAsyncStoragePersistence-1Z5BZ8RC.mjs';
2
2
  export { default as createInMemoryBookmarkStorage } from './packem_shared/createInMemoryBookmarkStorage-BoN7a7TH.mjs';
3
- export { applyDelta, isMutationDelta } from './packem_shared/applyDelta-4jFGTPA3.mjs';
4
- export { CONFLICT_ERROR_CODE, isConflictError } from './packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs';
5
- export { createLocalStore } from './packem_shared/createLocalStore-DSUfoLqY.mjs';
6
- export { LunoraClient } from './packem_shared/LunoraClient-UiULzH_1.mjs';
3
+ export { applyDelta, isMutationDelta } from './packem_shared/applyDelta-CRKZ1PBt.mjs';
4
+ export { CONFLICT_ERROR_CODE, getErrorCode, getRetryAfterMs, isConflictError, isForbiddenError, isRateLimitedError, isUnauthorizedError } from './packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs';
5
+ export { c as createLocalStore } from './packem_shared/local-store-BveBeFEo.mjs';
6
+ export { LunoraClient } from './packem_shared/LunoraClient-kXpHNyaE.mjs';
7
7
  export { createMutationRunner } from './packem_shared/createMutationRunner-BqsavzvG.mjs';
8
- export { OfflineQueue } from './packem_shared/OfflineQueue-D5p_QgF_.mjs';
9
- export { createInMemoryPersistence, createIndexedDbPersistence } from './packem_shared/createInMemoryPersistence-CW82inU5.mjs';
8
+ export { createMutatorRunner } from './packem_shared/createMutatorRunner-BETvCd0p.mjs';
9
+ export { O as OfflineQueue } from './packem_shared/offline-queue-B9vfdSqp.mjs';
10
+ export { createInMemoryPersistence, createIndexedDbPersistence } from './packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs';
10
11
  export { preloadQuery, preloadedQueryResult } from './packem_shared/preloadQuery-lobFkD2Z.mjs';
11
- export { createInMemoryQueryCache, createIndexedDbQueryCache, queryCacheKey } from './packem_shared/createInMemoryQueryCache-B1PQ9Twl.mjs';
12
+ export { createInMemoryQueryCache, createIndexedDbQueryCache, queryCacheKey } from './packem_shared/createInMemoryQueryCache-DiaGZkA2.mjs';
12
13
  export { createReconnect } from './packem_shared/createReconnect-Di_-oHH7.mjs';
13
- export { DEFAULT_MAX_BUFFER, createStream } from './packem_shared/DEFAULT_MAX_BUFFER-BDkqO5PW.mjs';
14
- export { SubscriptionRegistry } from './packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs';
14
+ export { DEFAULT_MAX_BUFFER, createStream } from './packem_shared/DEFAULT_MAX_BUFFER-7hFnzNk9.mjs';
15
+ export { S as SubscriptionRegistry } from './packem_shared/subscription-BjynOXCU.mjs';
@@ -0,0 +1,33 @@
1
+ const LUNORA_ERROR_CODES = [
2
+ "BAD_REQUEST",
3
+ "CONFLICT",
4
+ "COUNT_RLS_UNSUPPORTED",
5
+ "FORBIDDEN",
6
+ "INTERNAL_SERVER_ERROR",
7
+ "MASK_UNSUPPORTED",
8
+ "NOT_FOUND",
9
+ "NOT_IMPLEMENTED",
10
+ "RELATION_PREDICATE_UNSUPPORTED",
11
+ "TOO_MANY_REQUESTS",
12
+ "UNAUTHORIZED",
13
+ "UNPROCESSABLE"
14
+ ];
15
+ const KNOWN_ERROR_CODES = new Set(LUNORA_ERROR_CODES);
16
+ const CONFLICT_ERROR_CODE = "CONFLICT";
17
+ const isConflictError = (error) => error instanceof Error && error.code === CONFLICT_ERROR_CODE;
18
+ const isForbiddenError = (error) => error instanceof Error && error.code === "FORBIDDEN";
19
+ const isUnauthorizedError = (error) => error instanceof Error && error.code === "UNAUTHORIZED";
20
+ const isRateLimitedError = (error) => error instanceof Error && error.code === "TOO_MANY_REQUESTS";
21
+ const getErrorCode = (error) => {
22
+ if (!(error instanceof Error)) {
23
+ return void 0;
24
+ }
25
+ const { code } = error;
26
+ return typeof code === "string" && KNOWN_ERROR_CODES.has(code) ? code : void 0;
27
+ };
28
+ const getRetryAfterMs = (error) => {
29
+ const value = error?.data?.retryAfterMs;
30
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
31
+ };
32
+
33
+ export { CONFLICT_ERROR_CODE, getErrorCode, getRetryAfterMs, isConflictError, isForbiddenError, isRateLimitedError, isUnauthorizedError };
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const DEFAULT_MAX_BUFFER = 1024;
2
4
  const createStream = (options) => {
3
5
  const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
@@ -51,9 +53,10 @@ const createStream = (options) => {
51
53
  }
52
54
  if (buffer.length >= maxBuffer) {
53
55
  handle.fail(
54
- Object.assign(new Error(`stream buffer overflow (max=${maxBuffer.toString()}); the consumer cannot keep up with the producer`), {
55
- code: "STREAM_BACKPRESSURE"
56
- })
56
+ new LunoraError(
57
+ "STREAM_BACKPRESSURE",
58
+ `stream buffer overflow (max=${maxBuffer.toString()}); the consumer cannot keep up with the producer`
59
+ )
57
60
  );
58
61
  return;
59
62
  }