@rebasepro/client 0.10.0 → 0.10.1-canary.18115ba

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rebasepro/client
2
2
 
3
- HTTP SDK client for the Rebase backend — typed CRUD, auth, storage, realtime WebSockets, admin, cron, and custom functions.
3
+ HTTP SDK client for the Rebase backend — typed CRUD, auth, storage, realtime WebSockets, offline / local-first sync, admin, cron, and custom functions.
4
4
 
5
5
  ## Installation
6
6
 
@@ -17,6 +17,7 @@ pnpm add @rebasepro/client
17
17
  - **Admin** — user CRUD for admins
18
18
  - **Storage** — file upload, download, delete, list
19
19
  - **Realtime** — WebSocket subscriptions for collection and snapshot changes
20
+ - **Offline / local-first sync** (opt-in) — a local row database, writes that apply instantly offline and replay when the connection returns, and live queries
20
21
  - **Cron** — list, trigger, and manage cron jobs
21
22
  - **Custom functions** — invoke server-side Hono route functions
22
23
  - **Type-safe data proxy** — `client.data.products` auto-maps to the `products` collection
@@ -41,6 +42,9 @@ pnpm add @rebasepro/client
41
42
  | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
42
43
  | `onUnauthorized` | `() => Promise<boolean>` | auto-refresh | Handler for 401 responses |
43
44
  | `websocketUrl` | `string` | derived from `baseUrl` | WebSocket URL for realtime |
45
+ | `realtime` | `boolean` | `true` | Open the WebSocket — `false` lets a one-shot script exit |
46
+ | `collections` | `Record<string, string>` | — | Maps accessor names to collection slugs |
47
+ | `offline` | `boolean \| OfflineConfig` | `false` | Local-first sync — see [the docs](https://rebase.pro/docs/sdk/offline) |
44
48
 
45
49
  ### Collection Client
46
50
 
@@ -61,6 +65,8 @@ pnpm add @rebasepro/client
61
65
  | `include(...rels)` | Include related snapshots — returns `QueryBuilder` |
62
66
  | `listen(params, onUpdate, onError?)` | Realtime subscription (requires WebSocket) |
63
67
  | `listenById(id, onUpdate, onError?)` | Realtime single-snapshot subscription |
68
+ | `observe(params, onResult, onError?, options?)` | Live query — local-first when `offline` is on, otherwise fetch + `listen` |
69
+ | `observeById(id, onResult, onError?, options?)` | Live query for a single row |
64
70
 
65
71
  ### Auth Module (`client.auth`)
66
72
 
@@ -116,6 +122,8 @@ pnpm add @rebasepro/client
116
122
  |---|---|
117
123
  | `RebaseApiError` | Error class with `status`, `message`, `code`, `details` |
118
124
  | `RebaseWebSocketClient` | WebSocket client for realtime subscriptions |
125
+ | `isOfflineError(error)` | True when a read failed with no network *and* nothing cached |
126
+ | `MemoryOfflineStore` | Reference `OfflineStore`; the IndexedDB one is wired automatically |
119
127
  | `createCookieStorage(options?)` | Cookie-based auth storage adapter |
120
128
  | `createMemoryStorage()` | In-memory auth storage adapter |
121
129
  | `QueryBuilder` | Fluent query builder (also re-exported from `@rebasepro/common`) |
@@ -1,6 +1,40 @@
1
1
  import { FindParams, Transport } from "./transport";
2
2
  import { RebaseWebSocketClient } from "./websocket";
3
- import { SDKCollectionClient } from "@rebasepro/types";
3
+ import { FindResult, SDKCollectionClient } from "@rebasepro/types";
4
+ /**
5
+ * A live query result: a normal {@link FindResult} plus what an interface
6
+ * needs to decide whether to show a "saving…" or "offline" affordance over it.
7
+ *
8
+ * The three flags are always `false` on a client without offline support —
9
+ * every result there came straight from the server.
10
+ */
11
+ export interface LiveResult<M extends Record<string, unknown>> extends FindResult<M> {
12
+ /** The data came from the local database, not from a completed request. */
13
+ fromCache: boolean;
14
+ /** At least one row here carries a write the server has not accepted yet. */
15
+ hasPendingWrites: boolean;
16
+ /**
17
+ * The local database may not hold every row the server would have
18
+ * returned, so treat this as a best effort rather than a complete answer.
19
+ */
20
+ partial: boolean;
21
+ /** The most recent revalidation failure, when the last one failed. */
22
+ error?: Error;
23
+ }
24
+ /** Snapshot metadata for a single observed row. */
25
+ export interface RowSnapshotMeta {
26
+ fromCache: boolean;
27
+ hasPendingWrites: boolean;
28
+ }
29
+ export interface ObserveOptions {
30
+ /**
31
+ * Keep the subscription live off the realtime socket, so changes made by
32
+ * other clients arrive without a refetch. On by default whenever realtime
33
+ * is enabled on the client; pass `false` for a one-shot read that still
34
+ * reports offline/pending metadata.
35
+ */
36
+ realtime?: boolean;
37
+ }
4
38
  /**
5
39
  * The concrete, HTTP-backed implementation of the public
6
40
  * {@link SDKCollectionClient} contract — flat rows (no Entity wrapper), plus
@@ -8,10 +42,28 @@ import { SDKCollectionClient } from "@rebasepro/types";
8
42
  *
9
43
  * This is what `createRebaseClient().data.<collection>` returns. It is not a
10
44
  * separate API from {@link SDKCollectionClient}; it only widens it with
11
- * `count()`. Program against {@link SDKCollectionClient} when you want a
12
- * transport-agnostic type.
45
+ * `count()` and the reactive `observe()` pair. Program against
46
+ * {@link SDKCollectionClient} when you want a transport-agnostic type.
13
47
  */
14
48
  export interface CollectionClient<M extends Record<string, unknown> = Record<string, unknown>, I = Partial<M>, U = Partial<M>> extends SDKCollectionClient<M, I, U> {
15
49
  count(params?: FindParams): Promise<number>;
50
+ /**
51
+ * Subscribe to a query's results.
52
+ *
53
+ * This is the reactive read primitive, and the one to reach for in a UI:
54
+ * unlike `find()` it keeps emitting. On a client with `offline` enabled it
55
+ * is local-first — the first emission comes from the local database, with
56
+ * no request in the way — and re-emits on every local write, every queued
57
+ * write reaching the server, and every rollback. With realtime enabled it
58
+ * also re-emits on changes made by other clients.
59
+ *
60
+ * Emissions are de-duplicated: a refresh that changes nothing does not
61
+ * call back.
62
+ *
63
+ * @returns An unsubscribe function.
64
+ */
65
+ observe(params: FindParams | undefined, onResult: (result: LiveResult<M>) => void, onError?: (error: Error) => void, options?: ObserveOptions): () => void;
66
+ /** {@link CollectionClient.observe} for a single row. */
67
+ observeById(id: string | number, onResult: (row: M | undefined, meta: RowSnapshotMeta) => void, onError?: (error: Error) => void, options?: ObserveOptions): () => void;
16
68
  }
17
69
  export declare function createCollectionClient<M extends Record<string, unknown> = Record<string, unknown>>(transport: Transport, slug: string, ws?: RebaseWebSocketClient): CollectionClient<M>;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ import { CollectionClient } from "./collection";
8
8
  import { createFunctionsClient } from "./functions";
9
9
  import { RebaseWebSocketClient } from "./websocket";
10
10
  import { RebaseRealtimeChannel, type ChannelOptions } from "./realtime-channel";
11
+ import { type OfflineApi, type OfflineConfig } from "./offline";
11
12
  import { InsertOf, RebaseClient, RebaseSdkData, RowOf, StorageSource, StorageSourceDefinition, StorageSourceRegistry, UpdateOf } from "@rebasepro/types";
12
13
  export { RebaseApiError } from "./transport";
13
14
  export { RebaseClientError } from "./errors";
@@ -30,6 +31,11 @@ export type { FunctionInvokeOptions, FunctionsClient } from "./functions";
30
31
  export { RebaseWebSocketClient } from "./websocket";
31
32
  export { RebaseRealtimeChannel } from "./realtime-channel";
32
33
  export type { PresenceState, PresenceDiff, BroadcastEvent, ChannelTransport, ChannelOptions, ChannelHistoryEntry, ChannelHistoryResult } from "./realtime-channel";
34
+ export type { OfflineApi, OfflineConfig, OfflineStatus } from "./offline";
35
+ export { isOfflineError } from "./offline";
36
+ export type { LiveResult, ObserveOptions, RowSnapshotMeta } from "./collection";
37
+ export type { OfflineStore, OfflineCacheEntry, OfflineCacheRecord, PendingMutation, MutationRollback } from "./offline-store";
38
+ export { MemoryOfflineStore } from "./offline-store";
33
39
  export interface CreateRebaseClientOptions extends RebaseClientConfig {
34
40
  auth?: CreateAuthOptions;
35
41
  admin?: CreateAdminOptions;
@@ -50,6 +56,21 @@ export interface CreateRebaseClientOptions extends RebaseClientConfig {
50
56
  * correct slugs via this map before falling back to automatic snake_casing.
51
57
  */
52
58
  collections?: Record<string, string>;
59
+ /**
60
+ * Local-first sync for the data layer.
61
+ *
62
+ * `true` enables it with defaults: reads populate a local row database and
63
+ * fall back to it (evaluating filters and sorts locally) when the network
64
+ * is gone, writes made offline apply immediately and replay in order when
65
+ * it returns, and `observe()` becomes a live query that emits from the
66
+ * local database first. A rejected write is rolled back. Pass an
67
+ * {@link OfflineConfig} to control the store, cache sizes, retry backoff,
68
+ * or rejection handling.
69
+ *
70
+ * Local rows and queued writes are partitioned per signed-in user, and
71
+ * shared across tabs. Off by default.
72
+ */
73
+ offline?: boolean | OfflineConfig;
53
74
  }
54
75
  type KebabToCamelCase<S extends string> = S extends `${infer T}-${infer U}` ? `${T}${Capitalize<KebabToCamelCase<U>>}` : S;
55
76
  type DBEntry<DB, S extends string> = KebabToCamelCase<S> extends keyof DB ? DB[KebabToCamelCase<S>] : unknown;
@@ -110,5 +131,7 @@ export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<Rebase
110
131
  call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
111
132
  collection: <M extends Record<string, unknown> = Record<string, unknown>>(slug: string) => CollectionClient<M>;
112
133
  data: TypedDataLayer<DB>;
134
+ /** Present only when the client was created with `offline` enabled. */
135
+ offline?: OfflineApi;
113
136
  };
114
137
  export declare function createRebaseClient<DB = Record<string, unknown>>(options: CreateRebaseClientOptions): CreateRebaseClientResult<DB>;