@taladb/react 0.10.2 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,13 +1,13 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { CollectionOptions, Document, TalaDB, OpenDBOptions, Collection, Filter, AggregatePipeline, RestSourceOptions, ReplicationSource, CoverageState } from 'taladb';
3
+ import { CollectionOptions, Document, TalaDB, OpenDBOptions, Collection, Filter, AggregatePipeline } from 'taladb';
4
4
 
5
5
  /**
6
6
  * Per-collection options (`schema`, `syncSchema`, `migrateDocument`, …), keyed by
7
7
  * collection name.
8
8
  *
9
9
  * Register them once on the provider and every hook below it — `useCollection`,
10
- * and therefore `useFind`, `useQuery` and `useMutation` — resolves a *configured*
10
+ * and therefore `useFind`, `useFindOne` and `useWrite` — resolves a *configured*
11
11
  * collection. Without this, those hooks call `db.collection(name)` with no
12
12
  * options, so a hook-driven write silently skips the strict `schema` validation
13
13
  * and the `_v` stamp that `db.collection(name, { … })` would have applied.
@@ -16,6 +16,16 @@ type CollectionRegistry = Record<string, CollectionOptions<any>>;
16
16
  /** Resolves the registered options for a collection. Stable across renders. */
17
17
  interface CollectionResolver {
18
18
  get<T extends Document>(name: string): CollectionOptions<T> | undefined;
19
+ /**
20
+ * Every registered collection name, or `[]` when the provider declared none.
21
+ *
22
+ * `get` alone cannot tell "this name is not registered" from "nothing is
23
+ * registered at all", and that distinction is the difference between an error
24
+ * and a warning for anything validating a name it inferred rather than was
25
+ * given — see `@taladb/react/query`'s collection resolution, which refuses to
26
+ * guess against a populated registry but must stay usable without one.
27
+ */
28
+ names(): string[];
19
29
  }
20
30
  declare function useCollectionOptions(): CollectionResolver;
21
31
  type SharedProps = {
@@ -92,7 +102,7 @@ declare function useTalaDB(): TalaDB;
92
102
  * The collection is opened **with its registered options** — the `schema`,
93
103
  * `syncSchema` and `migrateDocument` declared in the provider's `collections`
94
104
  * prop, or the `options` passed here (which win). That is what makes a write
95
- * through `useMutation` hard-fail on an invalid document and carry its `_v`
105
+ * through `useWrite` hard-fail on an invalid document and carry its `_v`
96
106
  * shape version, exactly as `db.collection(name, { … })` does. Without it the
97
107
  * hooks resolve a bare, unconfigured handle and silently skip validation.
98
108
  *
@@ -197,283 +207,7 @@ interface AggregateResult<R> {
197
207
  */
198
208
  declare function useAggregate<T extends Document, R extends Document = T>(collection: Collection<T>, pipeline: AggregatePipeline<T>): AggregateResult<R>;
199
209
 
200
- /** Resolved network configuration for one replicated slice. */
201
- interface ResolvedReplicationConfig {
202
- /** Base URL; `/push` and `/pull` are appended by {@link HttpSyncAdapter}. */
203
- endpoint: string;
204
- /**
205
- * Async (or sync) resolver for per-request headers — typically the
206
- * `Authorization` bearer. Called **once per pass, at send time**, so a token
207
- * that refreshed while a write sat in the local database is picked up when the
208
- * write finally flushes.
209
- */
210
- getAuth?: () => Promise<Record<string, string>> | Record<string, string>;
211
- /** `fetch` implementation. Defaults to the global `fetch`. */
212
- fetch?: typeof fetch;
213
- /** Override the `/push` and `/pull` sub-paths to match an existing API. */
214
- paths?: {
215
- push?: string;
216
- pull?: string;
217
- };
218
- }
219
-
220
- /** When the background hydration walk is allowed to start. */
221
- type HydrateMode =
222
- /** Immediately on mount. */
223
- 'eager'
224
- /** When the browser is idle (default). Keeps first paint responsive. */
225
- | 'idle'
226
- /** Never automatically — the app calls `hydrate()` itself. */
227
- | 'manual';
228
- interface ReplicateScope<RemoteRow = any, T extends Document = Document> extends Omit<RestSourceOptions<RemoteRow, T>, 'collection'> {
229
- /**
230
- * Provide a fully custom source instead of the REST defaults. When present,
231
- * every other field here is ignored.
232
- */
233
- source?: ReplicationSource<RemoteRow, T>;
234
- hydrate?: HydrateMode;
235
- /** Rows per bootstrap page. Default 500. */
236
- pageSize?: number;
237
- /** Re-check the origin for changes on this interval. `0` disables. */
238
- refreshMs?: number;
239
- /**
240
- * Fetch the current query directly when coverage isn't ready yet, so a cold
241
- * start paints immediately. Default `true`.
242
- *
243
- * Mandatory in practice for a Vite SPA or React Native, which have no server
244
- * render to paint behind while the replica fills.
245
- */
246
- bridge?: boolean;
247
- }
248
- /** One entry per local collection. */
249
- type ReplicateRegistry = Record<string, ReplicateScope<any, any>>;
250
-
251
- /** A slice to warm on first run — a collection, optionally on a specific endpoint. */
252
- type PrefetchSlice = {
253
- collection: string;
254
- endpoint?: string;
255
- };
256
- /** A prefetch entry: a collection name (shorthand) or a {@link PrefetchSlice}. */
257
- type PrefetchEntry = string | PrefetchSlice;
258
- /** `'once'` warms a slice only if it has never synced; `'always'` on every mount. */
259
- type PrefetchMode = 'once' | 'always';
260
- /**
261
- * Replication settings shared by `useQuery` / `useMutation`, supplied once by
262
- * `<ReplicationProvider>` and overridable per hook.
263
- *
264
- * The origin is *your* API — never a database credential. It authorizes the
265
- * session token from {@link ReplicationConfig.getAuth} and returns only that
266
- * user's slice, so the auth header doubles as the per-user scope.
267
- */
268
- interface ReplicationConfig {
269
- /** Base sync URL, e.g. `/api/sync`. `/push` and `/pull` are appended. */
270
- endpoint: string;
271
- /**
272
- * Per-request header resolver — typically `{ Authorization: 'Bearer …' }`.
273
- * Async so it can await a token refresh. Resolved at **send time**, once per
274
- * pass, so an offline write flushed later carries a current token.
275
- */
276
- getAuth?: () => Promise<Record<string, string>> | Record<string, string>;
277
- /** `fetch` implementation. Defaults to the global `fetch`. */
278
- fetch?: typeof fetch;
279
- /** Override the `/push` and `/pull` sub-paths to match an existing API. */
280
- paths?: {
281
- push?: string;
282
- pull?: string;
283
- };
284
- /**
285
- * Default background refresh interval (ms) for `useQuery`. A replication
286
- * *interval*, not a cache TTL — the local data is never evicted, only
287
- * refreshed. Omit or set `0` to disable polling by default; a hook can still
288
- * opt in per query. `30_000` matches the guide's own example cadence.
289
- */
290
- pollMs?: number;
291
- /**
292
- * Slices to warm into the local replica in the background on first run, so a
293
- * later `useQuery` for that collection reads local instead of waiting on the
294
- * network. Best-effort and non-blocking: deferred to browser idle, run in the
295
- * sync Worker on web, and silently skipped on failure. Each entry is a
296
- * collection name or a {@link PrefetchSlice}.
297
- */
298
- prefetch?: PrefetchEntry[];
299
- /** How prefetch decides to warm a slice. Default `'once'`. */
300
- prefetchMode?: PrefetchMode;
301
- /** Max concurrent prefetch pulls — keeps the active page from starving. Default `2`. */
302
- prefetchConcurrency?: number;
303
- }
304
- interface ReplicationProviderProps extends Partial<ReplicationConfig> {
305
- /**
306
- * Collections to replicate from a remote origin, keyed by local collection name.
307
- *
308
- * This is what makes `useQuery` a local read: once a collection is fully
309
- * hydrated for its scope, filtering, sorting and paging never touch the network.
310
- * See {@link ReplicateRegistry}.
311
- */
312
- replicate?: ReplicateRegistry;
313
- children: ReactNode;
314
- }
315
- /**
316
- * Supplies replication defaults (endpoint, auth, poll interval, prefetch) to the
317
- * `useQuery` / `useMutation` hooks below it. Compose it inside a
318
- * `<TalaDBProvider>`:
319
- *
320
- * ```tsx
321
- * <TalaDBProvider name="app.db" fallback={<Splash />}>
322
- * <ReplicationProvider
323
- * endpoint="/api/sync"
324
- * getAuth={async () => ({ Authorization: `Bearer ${await session.token()}` })}
325
- * pollMs={30_000}
326
- * prefetch={['products', 'categories']}
327
- * >
328
- * <App />
329
- * </ReplicationProvider>
330
- * </TalaDBProvider>
331
- * ```
332
- */
333
- declare function ReplicationProvider({ children, replicate, ...config }: ReplicationProviderProps): react_jsx_runtime.JSX.Element;
334
- /**
335
- * Read the nearest replication config, merged with per-hook overrides.
336
- * Non-throwing: `config` is `null` when no endpoint is resolvable (valid for
337
- * `source: 'local-only'`); the caller decides whether that's an error.
338
- */
339
- declare function useReplicationConfig(overrides?: Partial<ReplicationConfig>): {
340
- config: ResolvedReplicationConfig | null;
341
- pollMs: number;
342
- };
343
-
344
- interface Coverage {
345
- /** The raw state machine value. */
346
- status: CoverageState['status'];
347
- /**
348
- * Whether a purely local read is authorized.
349
- *
350
- * True **only** for `complete`. Notably *not* for `best-effort`, which means we
351
- * applied every row the origin gave us but the origin could not pin a snapshot —
352
- * so a row that shifted between pages during the walk may never have been seen,
353
- * and we cannot prove the replica is whole. Serving that as authoritative would
354
- * silently return incomplete results, which is worse than going to the network.
355
- */
356
- ready: boolean;
357
- /** Rows hydrated so far. */
358
- rows: number;
359
- /** Total rows in scope when supplied by the origin. */
360
- total?: number;
361
- /** 0–1 when the origin reported a total; otherwise undefined. */
362
- progress?: number;
363
- /** Present on `error`, `stale` and `best-effort`. */
364
- reason?: string;
365
- }
366
- /**
367
- * How much of a collection is local, and whether it can be trusted for a
368
- * network-free read.
369
- *
370
- * @example
371
- * const { ready, progress } = useCoverage('products')
372
- * if (!ready) return <ProgressBar value={progress} />
373
- */
374
- declare function useCoverage(collection: string): Coverage;
375
- /** Semantic alias for progress-oriented UIs. */
376
- declare const useHydrationProgress: typeof useCoverage;
377
-
378
- type ReadSource = 'local-first' | 'remote-first' | 'local-only';
379
- interface UseQueryOptions<T extends Document> extends Partial<Pick<ReplicationConfig, 'endpoint' | 'getAuth' | 'fetch' | 'paths' | 'pollMs'>> {
380
- /** The local collection to read. */
381
- collection: string;
382
- /** Mongo-style filter, applied **locally**. */
383
- filter?: Filter<T>;
384
- /** Sort, applied locally. `1` ascending, `-1` descending. */
385
- sort?: Partial<Record<keyof T & string, 1 | -1>>;
386
- /** 1-based page number. Requires `limit`. */
387
- page?: number;
388
- /** Rows per page. */
389
- limit?: number;
390
- /** Skip N rows. Ignored when `page` is set. */
391
- skip?: number;
392
- /** Skip the query entirely (e.g. while a route param is undefined). */
393
- enabled?: boolean;
394
- /** Legacy sync-contract read policy. Used when no full-replication scope exists. */
395
- source?: ReadSource;
396
- }
397
- interface QueryResult<T> {
398
- /** The current page. Reactive: re-renders as rows land. */
399
- data: T[];
400
- /** Total rows in the replicated scope when reported by the origin. */
401
- total?: number;
402
- /** True until the first local snapshot arrives. */
403
- loading: boolean;
404
- /** Most recent local read error. */
405
- error: unknown | null;
406
- /** Most recent cold-start bridge error. */
407
- fetchError: unknown | null;
408
- /** How much of the collection is local, and whether it is trustworthy. */
409
- coverage: Coverage;
410
- /**
411
- * A cold-start bridge fetch is in flight — we are serving the network because
412
- * the replica isn't complete yet.
413
- */
414
- fetching: boolean;
415
- /** Legacy sync-contract pull in progress. */
416
- syncing: boolean;
417
- /** Legacy sync-contract pull error. */
418
- syncError: unknown | null;
419
- /** Force a delta refresh from the origin. */
420
- refetch: () => Promise<void>;
421
- }
422
- /**
423
- * Read a page of a collection.
424
- *
425
- * ## The point
426
- *
427
- * Once the collection is **covered** — fully replicated for this scope — this hook
428
- * touches the network **zero times**. Filtering, sorting and paging are local
429
- * queries against the on-device database. Page 1 → page 2 → a new filter → page 47
430
- * → back to page 1: every one is a local read, instant and offline-capable.
431
- * Pagination stops being a network concern at all.
432
- *
433
- * That is the whole reason to put a real database on the device. A cache of API
434
- * pages could only ever answer the queries you already asked; a *covered replica*
435
- * answers queries nobody has asked yet.
436
- *
437
- * ## Before coverage lands
438
- *
439
- * On a cold start the replica is empty, and a SPA or React Native app has no server
440
- * render to paint behind. So the hook **bridges**: it fetches exactly the rows this
441
- * query needs and writes them into the same collection, under the same derived ids
442
- * the background walk will use. Those rows are not a cache entry to be reconciled
443
- * later — they are the replica, arriving early. When the walk reaches them it
444
- * overwrites them in place.
445
- *
446
- * @example
447
- * const { data, coverage } = useQuery<Product>({
448
- * collection: 'products',
449
- * filter: { category: 'kitchen', price: { $lt: 500 } },
450
- * sort: { price: 1 },
451
- * page: 2,
452
- * limit: 100,
453
- * })
454
- */
455
- declare function useQuery<T extends Document>(options: UseQueryOptions<T>): QueryResult<T>;
456
-
457
- /**
458
- * Several pages at once, from several collections. Index-aligned with `queries`.
459
- *
460
- * Hooks can't be called in a variable-length loop, so this manages its own
461
- * subscriptions rather than calling `useQuery` N times. Behaviour is otherwise
462
- * identical: once a collection is covered, its read is purely local.
463
- *
464
- * TalaDB is a document store with no cross-collection joins, so a page needing
465
- * several collections composes them in the component (or denormalises at the
466
- * origin). This is the hook for that.
467
- *
468
- * @example
469
- * const [products, categories] = useQueries([
470
- * { collection: 'products', filter: { category }, sort: { price: 1 }, page, limit: 50 },
471
- * { collection: 'categories' },
472
- * ])
473
- */
474
- declare function useQueries(queries: UseQueryOptions<Document>[]): QueryResult<Document>[];
475
-
476
- /** A single write intent against the local replica. Discriminated on `type`. */
210
+ /** A single write intent. Discriminated on `type`. */
477
211
  type WriteOp<T extends Document> = {
478
212
  type: 'insert';
479
213
  doc: Omit<T, '_id'>;
@@ -485,45 +219,42 @@ type WriteOp<T extends Document> = {
485
219
  type: 'delete';
486
220
  where: Filter<T>;
487
221
  };
488
- interface UseMutationOptions extends Partial<Pick<ReplicationConfig, 'endpoint' | 'getAuth' | 'fetch' | 'paths'>> {
222
+ interface UseWriteOptions {
489
223
  /** Collection the write targets. */
490
224
  collection: string;
491
- /**
492
- * Replication direction for the drain. `push` *(default)* sends the write;
493
- * read hooks reconcile the authoritative value on their next pull. `both`
494
- * also pulls the authoritative echo inline (heavier — replays the collection).
495
- */
496
- direction?: 'push' | 'both';
497
- /**
498
- * On mount, attempt to flush any local writes left unsent from a previous
499
- * (offline) session for this collection. Default `true`.
500
- */
501
- drainOnMount?: boolean;
502
225
  }
503
- interface MutationResult<T extends Document> {
226
+ interface WriteResult<T extends Document> {
504
227
  /** Fire-and-forget write. Errors surface on `error`, never thrown to render. */
505
- mutate: (op: WriteOp<T>) => void;
506
- /** Awaitable write. Resolves once the local write and drain settle; rejects on error. */
507
- mutateAsync: (op: WriteOp<T>) => Promise<void>;
508
- /** A write (local + drain) is in flight. */
228
+ write: (op: WriteOp<T>) => void;
229
+ /** Awaitable write. Resolves once the write commits; rejects on error. */
230
+ writeAsync: (op: WriteOp<T>) => Promise<void>;
231
+ /** A write is in flight. */
509
232
  pending: boolean;
510
- /** Most recent write/drain error. The local write is durable regardless. */
233
+ /** Most recent write error. */
511
234
  error: unknown | null;
512
235
  }
513
236
  /**
514
- * Local-first write hook. A mutation writes the local replica **first**
515
- * (immediate, durable, reactive — every `useQuery`/`useFind` on the collection
516
- * re-renders) and then replicates the change outward over the sync-contract with
517
- * bounded retry. The network step never rolls the local write back: it is
518
- * already committed, and a later drain still delivers it (write-behind).
237
+ * Write hook over a TalaDB collection.
238
+ *
239
+ * The write is local, immediate, durable, and reactive every `useFind` /
240
+ * `useFindOne` / `useAggregate` subscribed to the collection re-renders once it
241
+ * commits. There is no network step in this hook and no rollback to reason
242
+ * about: the database is on the device, and the write either committed or threw.
243
+ *
244
+ * Named `useWrite`, not `useMutation`, because it is exactly a local write and
245
+ * nothing more. The React Query-shaped name belongs to a hook that also owns a
246
+ * network round-trip; this one would only borrow the expectation and then fail
247
+ * to meet it.
519
248
  *
520
- * Write-authority is origin-authoritative by default the push sends the
521
- * change and the server is the arbiter; read hooks pull the authoritative value.
249
+ * If the application has enabled the change webhook (`openDB({ webhook })`),
250
+ * the resulting HTTP request is dispatched by the client after the commit,
251
+ * outside this hook and outside `pending` — a webhook is a notification, not
252
+ * part of the write's success.
522
253
  *
523
254
  * @example
524
- * const { mutate, pending } = useMutation<Order>({ collection: 'orders' })
525
- * mutate({ type: 'update', where: { _id }, set: { status: 'shipped' } })
255
+ * const { write, pending } = useWrite<Order>({ collection: 'orders' })
256
+ * write({ type: 'update', where: { _id }, set: { status: 'shipped' } })
526
257
  */
527
- declare function useMutation<T extends Document>(options: UseMutationOptions): MutationResult<T>;
258
+ declare function useWrite<T extends Document>(options: UseWriteOptions): WriteResult<T>;
528
259
 
529
- export { type AggregateResult, type CollectionRegistry, type CollectionResolver, type Coverage, type FindOneResult, type FindResult, type HydrateMode, type MutationResult, type PrefetchEntry, type PrefetchMode, type PrefetchSlice, type QueryResult, type ReplicateRegistry, type ReplicateScope, type ReplicationConfig, ReplicationProvider, type ReplicationProviderProps, TalaDBProvider, type TalaDBProviderProps, type UseMutationOptions, type UseQueryOptions, type WriteOp, useAggregate, useCollection, useCollectionOptions, useCoverage, useFind, useFindOne, useHydrationProgress, useMutation, useQueries, useQuery, useReplicationConfig, useTalaDB };
260
+ export { type AggregateResult, type CollectionRegistry, type CollectionResolver, type FindOneResult, type FindResult, TalaDBProvider, type TalaDBProviderProps, type UseWriteOptions, type WriteOp, type WriteResult, useAggregate, useCollection, useCollectionOptions, useFind, useFindOne, useTalaDB, useWrite };