@hanzo/base 0.2.0 → 0.2.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.
@@ -1,368 +1,12 @@
1
- /**
2
- * State version tracking (Convex-inspired).
3
- *
4
- * Each query result is tagged with a StateVersion so the client can
5
- * detect ordering, replay transitions, and drive optimistic rollbacks.
6
- */
7
- interface StateVersion {
8
- /** Monotonically increasing counter bumped on every query-set change. */
9
- querySet: number;
10
- /** Server timestamp (microseconds since epoch) of the latest known event. */
11
- ts: bigint;
12
- /** Identity hash -- changes when the authenticated user changes. */
13
- identity: number;
14
- }
15
- type Modification = {
16
- type: 'QueryUpdated';
17
- collection: string;
18
- record: BaseRecord;
19
- } | {
20
- type: 'QueryRemoved';
21
- collection: string;
22
- id: string;
23
- } | {
24
- type: 'QueryFailed';
25
- collection: string;
26
- error: string;
27
- };
28
- interface Transition {
29
- startVersion: StateVersion;
30
- endVersion: StateVersion;
31
- modifications: Modification[];
32
- }
33
- /** Minimal record shape that every Base record satisfies. */
34
- interface BaseRecord {
35
- id: string;
36
- collectionId?: string;
37
- collectionName?: string;
38
- created?: string;
39
- updated?: string;
40
- [key: string]: unknown;
41
- }
42
- declare class VersionTracker {
43
- private _version;
44
- private _history;
45
- private readonly _maxHistory;
46
- constructor(maxHistory?: number);
47
- get current(): Readonly<StateVersion>;
48
- get history(): readonly Transition[];
49
- /**
50
- * Advance the version and record a transition.
51
- * Returns the new version.
52
- */
53
- advance(modifications: Modification[], serverTs?: bigint): StateVersion;
54
- /** Update identity hash (e.g. on auth change). */
55
- setIdentity(identity: number): void;
56
- /** Update the high-water timestamp without bumping querySet. */
57
- updateTimestamp(ts: bigint): void;
58
- /** Simple FNV-1a-like hash for identity derivation. */
59
- static hashIdentity(token: string): number;
60
- }
61
-
62
- interface QueryKey {
63
- collection: string;
64
- filter: string;
65
- }
66
- type StoreCallback = (records: BaseRecord[]) => void;
67
- declare class QueryStore {
68
- private readonly _slots;
69
- private readonly _optimistic;
70
- private readonly _version;
71
- get version(): Readonly<StateVersion>;
72
- /** Return cached effective (server+optimistic) result or undefined. */
73
- getQuery(collection: string, filter?: string): BaseRecord[] | undefined;
74
- /** Overwrite the server-truth cache for a query and notify. */
75
- setQuery(collection: string, filter: string, data: BaseRecord[]): void;
76
- /** Apply an optimistic create/update. Returns a mutationId for rollback. */
77
- optimisticSet(collection: string, record: BaseRecord): string;
78
- /** Apply an optimistic delete. */
79
- optimisticDelete(collection: string, id: string): string;
80
- /** Remove a single optimistic mutation and re-derive. */
81
- rollbackOptimistic(mutationId: string): void;
82
- /** Drop all optimistic entries for a collection. */
83
- clearOptimistic(collection: string): void;
84
- /**
85
- * Apply a realtime SSE event from the server.
86
- * `action` is one of "create", "update", "delete".
87
- */
88
- applyServerUpdate(collection: string, action: 'create' | 'update' | 'delete', record: BaseRecord): void;
89
- /** Subscribe to effective-state changes for a query. Returns unsubscribe. */
90
- subscribe(collection: string, filter: string, callback: StoreCallback): () => void;
91
- private _notify;
92
- private _notifyCollection;
93
- }
1
+ import { B as BaseClient } from '../client-Ckpi5rr9.js';
2
+ export { A as AuthChangeCallback, a as AuthResponse, b as AuthStore, c as BaseClientError, d as BaseRecord, C as ClientConfig, e as ClientResponseError, f as ClientResponseErrorData, g as CollectionService, h as ConnectionCallback, i as ConnectionState, F as FileOptions, j as FileService, L as ListOptions, k as ListResult, M as MemoryAuthStore, l as Modification, O as OAuth2Options, Q as QueryKey, m as QueryStore, R as RealtimeCallback, n as RealtimeEvent, o as RealtimeService, p as RecordFullListOptions, q as RecordQueryOptions, S as StateVersion, r as StoreCallback, T as Transition, V as VersionTracker } from '../client-Ckpi5rr9.js';
3
+ export { AsyncAuthStore, BaseAuthStore, CollectionField, CollectionModel, CookieSerializeOptions, LocalAuthStore, RecordModel, cookieParse, cookieSerialize, getTokenPayload, isTokenExpired, normalizeUnknownQueryParams } from '../compat/index.js';
94
4
 
95
5
  /**
96
- * RealtimeService -- SSE-based subscription manager.
6
+ * @hanzo/base -- Core entry point.
97
7
  *
98
- * Features:
99
- * - Query deduplication via hash(collection+topic) with reference counting
100
- * - Auto-reconnect with exponential backoff
101
- * - Max observed timestamp tracking
102
- * - Connection state notifications
103
- */
104
-
105
- type ConnectionState = 'disconnected' | 'connecting' | 'connected';
106
- interface RealtimeEvent {
107
- action: 'create' | 'update' | 'delete';
108
- record: BaseRecord;
109
- }
110
- type RealtimeCallback = (event: RealtimeEvent) => void;
111
- type ConnectionCallback = (state: ConnectionState) => void;
112
- declare class RealtimeService {
113
- private readonly _baseUrl;
114
- private readonly _getToken;
115
- private _eventSource;
116
- private _state;
117
- private _connectionListeners;
118
- /** Dedup map: hash -> Subscription. */
119
- private _subscriptions;
120
- /** SSE client id assigned by the server on connect. */
121
- private _clientId;
122
- /** Reconnect state. */
123
- private _reconnectAttempts;
124
- private _reconnectTimer;
125
- private _maxReconnectDelay;
126
- private _baseReconnectDelay;
127
- /** High-water mark of observed server timestamps. */
128
- private _maxTimestamp;
129
- /** Set to true when disconnect() is called explicitly. */
130
- private _intentionalDisconnect;
131
- constructor(baseUrl: string, getToken: () => string);
132
- get state(): ConnectionState;
133
- get maxTimestamp(): bigint;
134
- /**
135
- * Subscribe to realtime events for a collection topic.
136
- *
137
- * Topic is usually "*" (all changes) or a record id.
138
- * Returns an unsubscribe function.
139
- */
140
- subscribe(collection: string, topic: string, callback: RealtimeCallback): () => void;
141
- /** Remove all subscribers for a topic, or all topics if none specified. */
142
- unsubscribe(topic?: string): void;
143
- /** Register a connection-state listener. Returns unsubscribe. */
144
- onConnectionChange(callback: ConnectionCallback): () => void;
145
- /** Explicitly disconnect. */
146
- disconnect(): void;
147
- private _connect;
148
- private _handleMessage;
149
- /** Submit current subscription set to the server via POST. */
150
- private _submitSubscriptions;
151
- private _scheduleReconnect;
152
- private _clearReconnect;
153
- private _setState;
154
- private _hash;
155
- }
156
-
157
- /**
158
- * CollectionService -- typed CRUD + auth + realtime for a single collection.
159
- *
160
- * API-compatible with PocketBase JS SDK's RecordService, extended with
161
- * reactive features (subscribe/unsubscribe, optimistic writes).
162
- */
163
-
164
- interface ListResult$1<T = BaseRecord> {
165
- page: number;
166
- perPage: number;
167
- totalItems: number;
168
- totalPages: number;
169
- items: T[];
170
- }
171
- interface RecordQueryOptions {
172
- filter?: string;
173
- sort?: string;
174
- expand?: string;
175
- fields?: string;
176
- headers?: Record<string, string>;
177
- /** Extra query params merged into the URL. */
178
- query?: Record<string, string>;
179
- /** Request-scoped AbortSignal. */
180
- signal?: AbortSignal;
181
- }
182
- interface RecordFullListOptions extends RecordQueryOptions {
183
- /** Batch size for pagination (default 200). */
184
- batch?: number;
185
- }
186
- interface FileOptions {
187
- thumb?: string;
188
- token?: string;
189
- }
190
- interface AuthResponse<T = BaseRecord> {
191
- token: string;
192
- record: T;
193
- }
194
- interface OAuth2Options {
195
- provider: string;
196
- code: string;
197
- codeVerifier: string;
198
- redirectUrl: string;
199
- createData?: Record<string, unknown>;
200
- }
201
- declare class CollectionService {
202
- readonly collectionIdOrName: string;
203
- private readonly _baseUrl;
204
- private readonly _getToken;
205
- private readonly _setAuth;
206
- private readonly _store;
207
- private readonly _realtime;
208
- constructor(collectionIdOrName: string, baseUrl: string, getToken: () => string, setAuth: (token: string, record: BaseRecord) => void, store: QueryStore, realtime: RealtimeService);
209
- getList<T extends BaseRecord = BaseRecord>(page?: number, perPage?: number, options?: RecordQueryOptions): Promise<ListResult$1<T>>;
210
- getFullList<T extends BaseRecord = BaseRecord>(options?: RecordFullListOptions): Promise<T[]>;
211
- getOne<T extends BaseRecord = BaseRecord>(id: string, options?: RecordQueryOptions): Promise<T>;
212
- getFirstListItem<T extends BaseRecord = BaseRecord>(filter: string, options?: RecordQueryOptions): Promise<T>;
213
- create<T extends BaseRecord = BaseRecord>(data: Record<string, unknown> | FormData, options?: RecordQueryOptions): Promise<T>;
214
- update<T extends BaseRecord = BaseRecord>(id: string, data: Record<string, unknown> | FormData, options?: RecordQueryOptions): Promise<T>;
215
- delete(id: string, options?: RecordQueryOptions): Promise<boolean>;
216
- /**
217
- * Subscribe to realtime events for this collection.
218
- * `topic` is "*" for all changes or a specific record id.
219
- */
220
- subscribe(topic: string, callback: RealtimeCallback): () => void;
221
- /** Unsubscribe from a specific topic or all topics for this collection. */
222
- unsubscribe(topic?: string): void;
223
- authWithPassword<T extends BaseRecord = BaseRecord>(identity: string, password: string, options?: RecordQueryOptions): Promise<AuthResponse<T>>;
224
- authWithOAuth2<T extends BaseRecord = BaseRecord>(oauthOptions: OAuth2Options, options?: RecordQueryOptions): Promise<AuthResponse<T>>;
225
- requestVerification(email: string, options?: RecordQueryOptions): Promise<boolean>;
226
- confirmVerification(token: string, options?: RecordQueryOptions): Promise<boolean>;
227
- requestPasswordReset(email: string, options?: RecordQueryOptions): Promise<boolean>;
228
- confirmPasswordReset(token: string, password: string, passwordConfirm: string, options?: RecordQueryOptions): Promise<boolean>;
229
- private _collectionPath;
230
- private _applyOptions;
231
- private _request;
232
- }
233
- interface ClientResponseErrorData {
234
- url: string;
235
- status: number;
236
- data: Record<string, unknown>;
237
- }
238
- declare class ClientResponseError extends Error {
239
- url: string;
240
- status: number;
241
- data: Record<string, unknown>;
242
- isAbort: boolean;
243
- constructor(errorData: ClientResponseErrorData);
244
- toJSON(): ClientResponseErrorData;
245
- }
246
-
247
- interface AuthStore {
248
- token: string;
249
- record: BaseRecord | null;
250
- onChange(callback: (token: string, record: BaseRecord | null) => void): () => void;
251
- save(token: string, record: BaseRecord | null): void;
252
- clear(): void;
253
- readonly isValid: boolean;
254
- }
255
- type AuthChangeCallback = (token: string, record: BaseRecord | null) => void;
256
- /**
257
- * Default in-memory auth store.
258
- * Validates JWT exp claim without external dependencies.
8
+ * Exposes the full Base client surface natively. No upstream package
9
+ * dependency every type and helper the SDK needs lives here.
259
10
  */
260
- declare class MemoryAuthStore implements AuthStore {
261
- private _token;
262
- private _record;
263
- private _listeners;
264
- get token(): string;
265
- get record(): BaseRecord | null;
266
- get isValid(): boolean;
267
- save(token: string, record: BaseRecord | null): void;
268
- clear(): void;
269
- onChange(callback: AuthChangeCallback): () => void;
270
- private _notify;
271
- }
272
- declare class FileService {
273
- private readonly _baseUrl;
274
- constructor(baseUrl: string);
275
- /**
276
- * Build a full URL to a record file.
277
- * Compatible with PocketBase's pb.files.getURL().
278
- */
279
- getURL(record: BaseRecord, filename: string, options?: FileOptions): string;
280
- }
281
- interface ClientConfig {
282
- /** Base URL of the Hanzo Base instance (e.g. "https://myapp.hanzo.ai"). */
283
- url: string;
284
- /** Optional external auth store. Defaults to in-memory store. */
285
- authStore?: AuthStore;
286
- }
287
- interface ListOptions {
288
- filter?: string;
289
- sort?: string;
290
- expand?: string;
291
- fields?: string;
292
- page?: number;
293
- perPage?: number;
294
- }
295
- interface ListResult<T = BaseRecord> {
296
- page: number;
297
- perPage: number;
298
- totalItems: number;
299
- totalPages: number;
300
- items: T[];
301
- }
302
- declare class BaseClient {
303
- readonly url: string;
304
- readonly authStore: AuthStore;
305
- readonly store: QueryStore;
306
- readonly realtime: RealtimeService;
307
- readonly files: FileService;
308
- private readonly _versionTracker;
309
- private readonly _collections;
310
- /**
311
- * Create a BaseClient.
312
- *
313
- * Accepts either a config object or a plain URL string for convenience:
314
- * new BaseClient('https://myapp.hanzo.ai')
315
- * new BaseClient({ url: 'https://myapp.hanzo.ai' })
316
- */
317
- constructor(configOrUrl: ClientConfig | string);
318
- /** Get or create a CollectionService for the given name/id. */
319
- collection(nameOrId: string): CollectionService;
320
- /** Current state version from the QueryStore's internal tracker. */
321
- get version(): Readonly<StateVersion>;
322
- private _headers;
323
- private _request;
324
- list(collection: string, options?: ListOptions): Promise<ListResult>;
325
- getOne(collection: string, id: string, options?: Pick<ListOptions, 'expand' | 'fields'>): Promise<BaseRecord>;
326
- create(collection: string, data: Record<string, unknown>): Promise<BaseRecord>;
327
- update(collection: string, id: string, data: Record<string, unknown>): Promise<BaseRecord>;
328
- delete(collection: string, id: string): Promise<void>;
329
- signInWithPassword(collection: string, identity: string, password: string): Promise<{
330
- token: string;
331
- record: BaseRecord;
332
- }>;
333
- signUp(collection: string, data: Record<string, unknown>): Promise<BaseRecord>;
334
- refreshAuth(collection: string): Promise<{
335
- token: string;
336
- record: BaseRecord;
337
- }>;
338
- signOut(): void;
339
- /**
340
- * Send a raw request to the Base API.
341
- * Convenience for endpoints not covered by CollectionService.
342
- */
343
- send<T = unknown>(path: string, options?: {
344
- method?: string;
345
- headers?: Record<string, string>;
346
- body?: string | FormData;
347
- query?: Record<string, string>;
348
- signal?: AbortSignal;
349
- }): Promise<T>;
350
- health(): Promise<{
351
- code: number;
352
- message: string;
353
- }>;
354
- /**
355
- * Subscribe to realtime events for a collection topic.
356
- * Also wires events into the QueryStore automatically.
357
- */
358
- subscribeAndSync(collection: string, topic?: string, callback?: (e: RealtimeEvent) => void): () => void;
359
- /** Disconnect realtime and clear caches. */
360
- disconnect(): void;
361
- }
362
- declare class BaseClientError extends Error {
363
- readonly status: number;
364
- readonly detail: unknown;
365
- constructor(status: number, detail: unknown);
366
- }
367
11
 
368
- export { type AuthChangeCallback, type AuthResponse, type AuthStore, BaseClient, BaseClientError, type BaseRecord, type ClientConfig, ClientResponseError, type ClientResponseErrorData, CollectionService, type ConnectionCallback, type ConnectionState, type FileOptions, FileService, type ListOptions, type ListResult, MemoryAuthStore, type Modification, type OAuth2Options, type QueryKey, QueryStore, type RealtimeCallback, type RealtimeEvent, RealtimeService, type RecordFullListOptions, type RecordQueryOptions, type StateVersion, type StoreCallback, type Transition, VersionTracker };
12
+ export { BaseClient, BaseClient as default };
@@ -1,3 +1,4 @@
1
- export { BaseClient, BaseClientError, ClientResponseError, CollectionService, FileService, MemoryAuthStore, QueryStore, RealtimeService, VersionTracker } from '../chunk-LBAV5X5P.js';
1
+ export { AsyncAuthStore, LocalAuthStore, cookieParse, cookieSerialize, core_default as default, getTokenPayload, isTokenExpired, normalizeUnknownQueryParams } from '../chunk-DC6LDHAD.js';
2
+ export { BaseClient, BaseClientError, ClientResponseError, CollectionService, FileService, MemoryAuthStore, QueryStore, RealtimeService, VersionTracker } from '../chunk-3WOGNODW.js';
2
3
  //# sourceMappingURL=index.js.map
3
4
  //# sourceMappingURL=index.js.map
@@ -342,7 +342,7 @@ declare class CRDTSync {
342
342
  /**
343
343
  * Connect to the CRDT sync endpoint and start syncing a document.
344
344
  *
345
- * @param wsUrl - WebSocket URL, e.g. "wss://myapp.hanzo.ai/api/crdt"
345
+ * @param wsUrl - WebSocket URL, e.g. "wss://myapp.hanzo.ai/v1/crdt"
346
346
  * @param document - The CRDTDocument to sync.
347
347
  * @param token - Optional auth token.
348
348
  */
@@ -1,3 +1,3 @@
1
- export { CRDTCounter, CRDTDocument, CRDTRegister, CRDTSet, CRDTSync, CRDTText, HybridLogicalClock, compareHLC, makeOpId, makePositionId } from '../chunk-5NHFZRMO.js';
1
+ export { CRDTCounter, CRDTDocument, CRDTRegister, CRDTSet, CRDTSync, CRDTText, HybridLogicalClock, compareHLC, makeOpId, makePositionId } from '../chunk-FTBTJJQY.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { AuthStore, BaseClient, BaseRecord, ListOptions, ConnectionState, RealtimeEvent } from '../core/index.js';
4
- export { BaseClientError, ClientConfig, ListResult, StateVersion } from '../core/index.js';
3
+ import { b as AuthStore, B as BaseClient, d as BaseRecord, L as ListOptions, i as ConnectionState, n as RealtimeEvent } from '../client-Ckpi5rr9.js';
4
+ export { c as BaseClientError, C as ClientConfig, k as ListResult, S as StateVersion } from '../client-Ckpi5rr9.js';
5
5
  import { CRDTDocument, CRDTText, CRDTCounter, CRDTSet, CRDTRegister, CRDTSync, SyncState, PeerState } from '../crdt/index.js';
6
6
 
7
7
  interface BaseProviderProps {
@@ -13,7 +13,7 @@ interface BaseProviderProps {
13
13
  client?: BaseClient;
14
14
  children: ReactNode;
15
15
  }
16
- declare function BaseProvider({ url, authStore, client: externalClient, children }: BaseProviderProps): react_jsx_runtime.JSX.Element;
16
+ declare function BaseProvider({ url, authStore, client: externalClient, children }: BaseProviderProps): react.JSX.Element;
17
17
  /**
18
18
  * Get the BaseClient from the nearest BaseProvider.
19
19
  * Throws if used outside a provider.
@@ -1,6 +1,6 @@
1
- import { BaseClient } from '../chunk-LBAV5X5P.js';
2
- export { BaseClient, BaseClientError } from '../chunk-LBAV5X5P.js';
3
- import { CRDTDocument, CRDTSync } from '../chunk-5NHFZRMO.js';
1
+ import { BaseClient } from '../chunk-3WOGNODW.js';
2
+ export { BaseClient, BaseClientError } from '../chunk-3WOGNODW.js';
3
+ import { CRDTDocument, CRDTSync } from '../chunk-FTBTJJQY.js';
4
4
  import { createContext, useRef, useEffect, useContext, useState, useCallback } from 'react';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
@@ -273,9 +273,9 @@ function useCRDTCounter(doc, field) {
273
273
  function deriveCrdtWsUrl(httpUrl) {
274
274
  const url = httpUrl.replace(/\/$/, "");
275
275
  if (url.startsWith("https://")) {
276
- return url.replace("https://", "wss://") + "/api/crdt";
276
+ return url.replace("https://", "wss://") + "/v1/crdt";
277
277
  }
278
- return url.replace("http://", "ws://") + "/api/crdt";
278
+ return url.replace("http://", "ws://") + "/v1/crdt";
279
279
  }
280
280
 
281
281
  export { BaseProvider, useAuth, useBase, useCRDT, useCRDTCounter, useCRDTText, useConnectionState, useMutation, usePresence, useQuery, useRealtime };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/context.tsx","../../src/react/hooks.ts"],"names":["useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * BaseProvider -- React context providing the BaseClient instance.\n *\n * Usage:\n * <BaseProvider url=\"https://myapp.hanzo.ai\">\n * <App />\n * </BaseProvider>\n *\n * // or with an existing client:\n * <BaseProvider client={myClient}>\n * <App />\n * </BaseProvider>\n */\n\nimport { createContext, useContext, useRef, useEffect, type ReactNode } from 'react'\nimport { BaseClient, type AuthStore, type ClientConfig } from '../core/client.js'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst BaseContext = createContext<BaseClient | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider props\n// ---------------------------------------------------------------------------\n\nexport interface BaseProviderProps {\n /** Base URL for automatic client creation. */\n url?: string\n /** Optional auth store override. */\n authStore?: AuthStore\n /** Pre-created BaseClient (takes precedence over url). */\n client?: BaseClient\n children: ReactNode\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport function BaseProvider({ url, authStore, client: externalClient, children }: BaseProviderProps) {\n // Use a ref to hold the client so it survives re-renders without\n // creating a new client on every render.\n const clientRef = useRef<BaseClient | null>(null)\n\n if (externalClient) {\n clientRef.current = externalClient\n } else if (!clientRef.current && url) {\n const config: ClientConfig = { url }\n if (authStore) config.authStore = authStore\n clientRef.current = new BaseClient(config)\n }\n\n const client = clientRef.current\n if (!client) {\n throw new Error('BaseProvider: provide either `url` or `client` prop')\n }\n\n // Cleanup: disconnect realtime on unmount.\n useEffect(() => {\n return () => {\n // Only disconnect if we own the client (created from url).\n if (!externalClient) {\n client.disconnect()\n }\n }\n }, [client, externalClient])\n\n return <BaseContext value={client}>{children}</BaseContext>\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Get the BaseClient from the nearest BaseProvider.\n * Throws if used outside a provider.\n */\nexport function useBase(): BaseClient {\n const client = useContext(BaseContext)\n if (!client) {\n throw new Error('useBase: must be used within a <BaseProvider>')\n }\n return client\n}\n","/**\n * React hooks for Hanzo Base.\n *\n * All hooks use the BaseClient from the nearest BaseProvider context.\n * They manage subscriptions, cleanup, and state transitions correctly\n * with React 19 patterns (useEffect, useState, useCallback, useRef).\n */\n\nimport {\n useState,\n useEffect,\n useCallback,\n useRef,\n} from 'react'\n\nimport { useBase } from './context.js'\nimport type { BaseRecord } from '../core/state.js'\nimport type { ListOptions, AuthStore } from '../core/client.js'\nimport type { RealtimeEvent, ConnectionState } from '../core/realtime.js'\nimport type { CRDTDocument } from '../crdt/document.js'\nimport type { CRDTText } from '../crdt/text.js'\nimport type { CRDTCounter } from '../crdt/counter.js'\nimport type { CRDTSet } from '../crdt/set.js'\nimport type { CRDTRegister } from '../crdt/register.js'\nimport type { PeerState, SyncState } from '../crdt/sync.js'\nimport { CRDTSync } from '../crdt/sync.js'\nimport { CRDTDocument as CRDTDocumentImpl } from '../crdt/document.js'\n\n// ---------------------------------------------------------------------------\n// useQuery -- reactive collection query with SSE subscription\n// ---------------------------------------------------------------------------\n\nexport interface UseQueryOptions extends ListOptions {\n /** Whether to auto-subscribe to realtime updates. Default: true. */\n realtime?: boolean\n /** Whether to run the query immediately. Default: true. */\n enabled?: boolean\n}\n\nexport interface UseQueryResult<T = BaseRecord> {\n data: T[]\n isLoading: boolean\n error: Error | null\n /** Manually refetch the query. */\n refetch: () => Promise<void>\n}\n\n/**\n * Reactive query hook.\n *\n * Fetches records from a collection and subscribes to realtime updates.\n * Results are cached in the QueryStore for deduplication across components.\n */\nexport function useQuery<T extends BaseRecord = BaseRecord>(\n collection: string,\n options?: UseQueryOptions,\n): UseQueryResult<T> {\n const client = useBase()\n const [data, setData] = useState<T[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n // Stabilize options reference.\n const filter = options?.filter ?? ''\n const sort = options?.sort\n const expand = options?.expand\n const fields = options?.fields\n const page = options?.page\n const perPage = options?.perPage\n const realtimeEnabled = options?.realtime !== false\n const enabled = options?.enabled !== false\n\n const fetchData = useCallback(async () => {\n if (!enabled) return\n setIsLoading(true)\n setError(null)\n try {\n const result = await client.list(collection, {\n filter, sort, expand, fields, page, perPage,\n })\n setData(result.items as T[])\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)))\n } finally {\n setIsLoading(false)\n }\n }, [client, collection, filter, sort, expand, fields, page, perPage, enabled])\n\n // Initial fetch.\n useEffect(() => {\n fetchData()\n }, [fetchData])\n\n // Subscribe to QueryStore for optimistic + server updates.\n useEffect(() => {\n if (!enabled) return\n\n const unsubscribe = client.store.subscribe(collection, filter, (records) => {\n setData(records as T[])\n })\n\n return unsubscribe\n }, [client, collection, filter, enabled])\n\n // SSE realtime subscription.\n useEffect(() => {\n if (!realtimeEnabled || !enabled) return\n\n const unsubscribe = client.subscribeAndSync(collection, '*')\n return unsubscribe\n }, [client, collection, realtimeEnabled, enabled])\n\n return { data, isLoading, error, refetch: fetchData }\n}\n\n// ---------------------------------------------------------------------------\n// useMutation -- mutation with optimistic update support\n// ---------------------------------------------------------------------------\n\nexport type MutationAction = 'create' | 'update' | 'delete'\n\nexport interface MutateOptions {\n /** If true, apply an optimistic update to the QueryStore before the server responds. */\n optimistic?: boolean\n}\n\nexport interface UseMutationResult {\n mutate: (data: Record<string, unknown>, options?: MutateOptions) => Promise<BaseRecord | void>\n isLoading: boolean\n error: Error | null\n}\n\n/**\n * Mutation hook.\n *\n * Performs a create, update, or delete mutation against a collection.\n * Supports optimistic updates that are automatically rolled back on failure.\n */\nexport function useMutation(\n collection: string,\n action: MutationAction,\n): UseMutationResult {\n const client = useBase()\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n const mutate = useCallback(\n async (data: Record<string, unknown>, opts?: MutateOptions): Promise<BaseRecord | void> => {\n setIsLoading(true)\n setError(null)\n\n let optimisticId: string | undefined\n\n try {\n // Optimistic update.\n if (opts?.optimistic && action !== 'delete') {\n const tempRecord: BaseRecord = {\n id: data.id as string ?? `_temp_${Date.now()}`,\n ...data,\n }\n optimisticId = client.store.optimisticSet(collection, tempRecord)\n } else if (opts?.optimistic && action === 'delete' && data.id) {\n optimisticId = client.store.optimisticDelete(collection, data.id as string)\n }\n\n // Server request.\n let result: BaseRecord | void = undefined\n switch (action) {\n case 'create':\n result = await client.create(collection, data)\n break\n case 'update': {\n const id = data.id as string\n if (!id) throw new Error('useMutation: update requires data.id')\n const { id: _id, ...rest } = data\n result = await client.update(collection, id, rest)\n break\n }\n case 'delete': {\n const id = data.id as string\n if (!id) throw new Error('useMutation: delete requires data.id')\n await client.delete(collection, id)\n break\n }\n }\n\n // On success, remove optimistic entry (server truth takes over via applyServerUpdate).\n if (optimisticId) {\n client.store.rollbackOptimistic(optimisticId)\n }\n\n return result\n } catch (err) {\n // Rollback optimistic on failure.\n if (optimisticId) {\n client.store.rollbackOptimistic(optimisticId)\n }\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n throw e\n } finally {\n setIsLoading(false)\n }\n },\n [client, collection, action],\n )\n\n return { mutate, isLoading, error }\n}\n\n// ---------------------------------------------------------------------------\n// useAuth -- auth state\n// ---------------------------------------------------------------------------\n\nexport interface UseAuthResult {\n user: BaseRecord | null\n token: string\n isValid: boolean\n signIn: (identity: string, password: string) => Promise<void>\n signUp: (data: Record<string, unknown>) => Promise<BaseRecord>\n signOut: () => void\n /** Subscribe to auth changes. Returns unsubscribe. */\n onChange: AuthStore['onChange']\n}\n\n/**\n * Auth state hook.\n *\n * Returns the current auth state and provides sign-in, sign-up, and\n * sign-out methods. Re-renders when auth state changes.\n *\n * @param collection - Auth collection name (default: \"users\").\n */\nexport function useAuth(collection = 'users'): UseAuthResult {\n const client = useBase()\n const [user, setUser] = useState<BaseRecord | null>(client.authStore.record)\n const [token, setToken] = useState(client.authStore.token)\n\n useEffect(() => {\n const unsubscribe = client.authStore.onChange((newToken, newRecord) => {\n setToken(newToken)\n setUser(newRecord)\n })\n return unsubscribe\n }, [client])\n\n const signIn = useCallback(\n async (identity: string, password: string) => {\n await client.signInWithPassword(collection, identity, password)\n },\n [client, collection],\n )\n\n const signUp = useCallback(\n async (data: Record<string, unknown>) => {\n return client.signUp(collection, data)\n },\n [client, collection],\n )\n\n const signOut = useCallback(() => {\n client.signOut()\n }, [client])\n\n return {\n user,\n token,\n isValid: client.authStore.isValid,\n signIn,\n signUp,\n signOut,\n onChange: client.authStore.onChange.bind(client.authStore),\n }\n}\n\n// ---------------------------------------------------------------------------\n// useRealtime -- low-level SSE subscription\n// ---------------------------------------------------------------------------\n\n/**\n * Low-level realtime subscription hook.\n *\n * Subscribes to SSE events for a collection topic on mount and\n * unsubscribes on unmount.\n */\nexport function useRealtime(\n collection: string,\n topic: string,\n callback: (event: RealtimeEvent) => void,\n): void {\n const client = useBase()\n const callbackRef = useRef(callback)\n callbackRef.current = callback\n\n useEffect(() => {\n const unsubscribe = client.realtime.subscribe(collection, topic, (event) => {\n callbackRef.current(event)\n })\n return unsubscribe\n }, [client, collection, topic])\n}\n\n// ---------------------------------------------------------------------------\n// useConnectionState -- realtime connection state\n// ---------------------------------------------------------------------------\n\nexport function useConnectionState(): ConnectionState {\n const client = useBase()\n const [state, setState] = useState<ConnectionState>(client.realtime.state)\n\n useEffect(() => {\n const unsubscribe = client.realtime.onConnectionChange((s) => {\n setState(s)\n })\n return unsubscribe\n }, [client])\n\n return state\n}\n\n// ---------------------------------------------------------------------------\n// usePresence -- presence tracking via CRDT sync\n// ---------------------------------------------------------------------------\n\nexport interface UsePresenceResult {\n /** Map of peer siteId to their state. */\n peers: Map<string, PeerState>\n /** Our own presence metadata. */\n myState: Record<string, unknown>\n /** Update our presence metadata. */\n updateState: (meta: Record<string, unknown>) => void\n}\n\n/**\n * Presence tracking hook.\n *\n * Requires a CRDTSync instance (usually obtained from useCRDT).\n * Tracks peers connected to the same document.\n */\nexport function usePresence(sync: CRDTSync): UsePresenceResult {\n const [peers, setPeers] = useState<Map<string, PeerState>>(new Map())\n const [myState, setMyState] = useState<Record<string, unknown>>({})\n\n useEffect(() => {\n const unsubscribe = sync.onPeersChange((p) => {\n setPeers(new Map(p))\n })\n return unsubscribe\n }, [sync])\n\n const updateState = useCallback(\n (meta: Record<string, unknown>) => {\n setMyState(meta)\n sync.updatePresence(meta)\n },\n [sync],\n )\n\n return { peers, myState, updateState }\n}\n\n// ---------------------------------------------------------------------------\n// useCRDT -- CRDT document hook with auto-sync\n// ---------------------------------------------------------------------------\n\nexport interface UseCRDTResult {\n /** The CRDTDocument instance. */\n doc: CRDTDocument\n /** Convenience accessor for text fields. */\n text: (field: string) => CRDTText\n /** Convenience accessor for counter fields. */\n counter: (field: string) => CRDTCounter\n /** Convenience accessor for set fields. */\n set: <T = unknown>(field: string) => CRDTSet<T>\n /** Convenience accessor for register fields. */\n register: <T = unknown>(field: string) => CRDTRegister<T>\n /** The CRDTSync instance for advanced usage (presence, state listeners). */\n sync: CRDTSync\n /** Current sync state. */\n syncState: SyncState\n}\n\n/**\n * CRDT document hook.\n *\n * Creates a CRDTDocument and CRDTSync, connects to the server via\n * WebSocket, and auto-syncs all local operations.\n *\n * @param documentId - Unique document identifier.\n * @param wsUrl - WebSocket URL for the CRDT sync endpoint. If not provided,\n * derives it from the BaseClient URL (http->ws, https->wss).\n */\nexport function useCRDT(documentId: string, wsUrl?: string): UseCRDTResult {\n const client = useBase()\n\n // Stable refs for document and sync -- created once per documentId.\n const docRef = useRef<CRDTDocument | null>(null)\n const syncRef = useRef<CRDTSync | null>(null)\n const [syncState, setSyncState] = useState<SyncState>('disconnected')\n\n // Create document and sync on first render or documentId change.\n if (!docRef.current || docRef.current.id !== documentId) {\n docRef.current = new CRDTDocumentImpl(documentId)\n }\n if (!syncRef.current) {\n syncRef.current = new CRDTSync()\n }\n\n const doc = docRef.current\n const sync = syncRef.current\n\n // Connect on mount, disconnect on unmount.\n useEffect(() => {\n const url = wsUrl ?? deriveCrdtWsUrl(client.url)\n const token = client.authStore.token || undefined\n\n sync.connect(url, doc, token)\n\n const unsubState = sync.onStateChange((s) => {\n setSyncState(s)\n })\n\n return () => {\n unsubState()\n sync.disconnect()\n }\n }, [doc, sync, client, wsUrl])\n\n // Convenience accessors that delegate to the document.\n const text = useCallback((field: string) => doc.getText(field), [doc])\n const counter = useCallback((field: string) => doc.getCounter(field), [doc])\n const set = useCallback(<T = unknown>(field: string) => doc.getSet<T>(field), [doc])\n const register = useCallback(<T = unknown>(field: string) => doc.getRegister<T>(field), [doc])\n\n return { doc, text, counter, set, register, sync, syncState }\n}\n\n// ---------------------------------------------------------------------------\n// useCRDTText -- subscribe to a CRDT text field's content\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a specific CRDTText field's current string value.\n * Re-renders whenever the text changes (local or remote).\n */\nexport function useCRDTText(doc: CRDTDocument, field: string): string {\n const textCrdt = doc.getText(field)\n const [value, setValue] = useState(() => textCrdt.toString())\n\n useEffect(() => {\n const unsubscribe = textCrdt.onChange((text) => {\n setValue(text)\n })\n return unsubscribe\n }, [textCrdt])\n\n return value\n}\n\n// ---------------------------------------------------------------------------\n// useCRDTCounter -- subscribe to a CRDT counter field\n// ---------------------------------------------------------------------------\n\nexport function useCRDTCounter(doc: CRDTDocument, field: string): number {\n const counterCrdt = doc.getCounter(field)\n const [value, setValue] = useState(() => counterCrdt.value)\n\n useEffect(() => {\n const unsubscribe = counterCrdt.onChange((v) => {\n setValue(v)\n })\n return unsubscribe\n }, [counterCrdt])\n\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Derive WebSocket URL from HTTP URL: https://x -> wss://x/api/crdt */\nfunction deriveCrdtWsUrl(httpUrl: string): string {\n const url = httpUrl.replace(/\\/$/, '')\n if (url.startsWith('https://')) {\n return url.replace('https://', 'wss://') + '/api/crdt'\n }\n return url.replace('http://', 'ws://') + '/api/crdt'\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/context.tsx","../../src/react/hooks.ts"],"names":["useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * BaseProvider -- React context providing the BaseClient instance.\n *\n * Usage:\n * <BaseProvider url=\"https://myapp.hanzo.ai\">\n * <App />\n * </BaseProvider>\n *\n * // or with an existing client:\n * <BaseProvider client={myClient}>\n * <App />\n * </BaseProvider>\n */\n\nimport { createContext, useContext, useRef, useEffect, type ReactNode } from 'react'\nimport { BaseClient, type AuthStore, type ClientConfig } from '../core/client.js'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst BaseContext = createContext<BaseClient | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider props\n// ---------------------------------------------------------------------------\n\nexport interface BaseProviderProps {\n /** Base URL for automatic client creation. */\n url?: string\n /** Optional auth store override. */\n authStore?: AuthStore\n /** Pre-created BaseClient (takes precedence over url). */\n client?: BaseClient\n children: ReactNode\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport function BaseProvider({ url, authStore, client: externalClient, children }: BaseProviderProps) {\n // Use a ref to hold the client so it survives re-renders without\n // creating a new client on every render.\n const clientRef = useRef<BaseClient | null>(null)\n\n if (externalClient) {\n clientRef.current = externalClient\n } else if (!clientRef.current && url) {\n const config: ClientConfig = { url }\n if (authStore) config.authStore = authStore\n clientRef.current = new BaseClient(config)\n }\n\n const client = clientRef.current\n if (!client) {\n throw new Error('BaseProvider: provide either `url` or `client` prop')\n }\n\n // Cleanup: disconnect realtime on unmount.\n useEffect(() => {\n return () => {\n // Only disconnect if we own the client (created from url).\n if (!externalClient) {\n client.disconnect()\n }\n }\n }, [client, externalClient])\n\n return <BaseContext value={client}>{children}</BaseContext>\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Get the BaseClient from the nearest BaseProvider.\n * Throws if used outside a provider.\n */\nexport function useBase(): BaseClient {\n const client = useContext(BaseContext)\n if (!client) {\n throw new Error('useBase: must be used within a <BaseProvider>')\n }\n return client\n}\n","/**\n * React hooks for Hanzo Base.\n *\n * All hooks use the BaseClient from the nearest BaseProvider context.\n * They manage subscriptions, cleanup, and state transitions correctly\n * with React 19 patterns (useEffect, useState, useCallback, useRef).\n */\n\nimport {\n useState,\n useEffect,\n useCallback,\n useRef,\n} from 'react'\n\nimport { useBase } from './context.js'\nimport type { BaseRecord } from '../core/state.js'\nimport type { ListOptions, AuthStore } from '../core/client.js'\nimport type { RealtimeEvent, ConnectionState } from '../core/realtime.js'\nimport type { CRDTDocument } from '../crdt/document.js'\nimport type { CRDTText } from '../crdt/text.js'\nimport type { CRDTCounter } from '../crdt/counter.js'\nimport type { CRDTSet } from '../crdt/set.js'\nimport type { CRDTRegister } from '../crdt/register.js'\nimport type { PeerState, SyncState } from '../crdt/sync.js'\nimport { CRDTSync } from '../crdt/sync.js'\nimport { CRDTDocument as CRDTDocumentImpl } from '../crdt/document.js'\n\n// ---------------------------------------------------------------------------\n// useQuery -- reactive collection query with SSE subscription\n// ---------------------------------------------------------------------------\n\nexport interface UseQueryOptions extends ListOptions {\n /** Whether to auto-subscribe to realtime updates. Default: true. */\n realtime?: boolean\n /** Whether to run the query immediately. Default: true. */\n enabled?: boolean\n}\n\nexport interface UseQueryResult<T = BaseRecord> {\n data: T[]\n isLoading: boolean\n error: Error | null\n /** Manually refetch the query. */\n refetch: () => Promise<void>\n}\n\n/**\n * Reactive query hook.\n *\n * Fetches records from a collection and subscribes to realtime updates.\n * Results are cached in the QueryStore for deduplication across components.\n */\nexport function useQuery<T extends BaseRecord = BaseRecord>(\n collection: string,\n options?: UseQueryOptions,\n): UseQueryResult<T> {\n const client = useBase()\n const [data, setData] = useState<T[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n // Stabilize options reference.\n const filter = options?.filter ?? ''\n const sort = options?.sort\n const expand = options?.expand\n const fields = options?.fields\n const page = options?.page\n const perPage = options?.perPage\n const realtimeEnabled = options?.realtime !== false\n const enabled = options?.enabled !== false\n\n const fetchData = useCallback(async () => {\n if (!enabled) return\n setIsLoading(true)\n setError(null)\n try {\n const result = await client.list(collection, {\n filter, sort, expand, fields, page, perPage,\n })\n setData(result.items as T[])\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)))\n } finally {\n setIsLoading(false)\n }\n }, [client, collection, filter, sort, expand, fields, page, perPage, enabled])\n\n // Initial fetch.\n useEffect(() => {\n fetchData()\n }, [fetchData])\n\n // Subscribe to QueryStore for optimistic + server updates.\n useEffect(() => {\n if (!enabled) return\n\n const unsubscribe = client.store.subscribe(collection, filter, (records) => {\n setData(records as T[])\n })\n\n return unsubscribe\n }, [client, collection, filter, enabled])\n\n // SSE realtime subscription.\n useEffect(() => {\n if (!realtimeEnabled || !enabled) return\n\n const unsubscribe = client.subscribeAndSync(collection, '*')\n return unsubscribe\n }, [client, collection, realtimeEnabled, enabled])\n\n return { data, isLoading, error, refetch: fetchData }\n}\n\n// ---------------------------------------------------------------------------\n// useMutation -- mutation with optimistic update support\n// ---------------------------------------------------------------------------\n\nexport type MutationAction = 'create' | 'update' | 'delete'\n\nexport interface MutateOptions {\n /** If true, apply an optimistic update to the QueryStore before the server responds. */\n optimistic?: boolean\n}\n\nexport interface UseMutationResult {\n mutate: (data: Record<string, unknown>, options?: MutateOptions) => Promise<BaseRecord | void>\n isLoading: boolean\n error: Error | null\n}\n\n/**\n * Mutation hook.\n *\n * Performs a create, update, or delete mutation against a collection.\n * Supports optimistic updates that are automatically rolled back on failure.\n */\nexport function useMutation(\n collection: string,\n action: MutationAction,\n): UseMutationResult {\n const client = useBase()\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n const mutate = useCallback(\n async (data: Record<string, unknown>, opts?: MutateOptions): Promise<BaseRecord | void> => {\n setIsLoading(true)\n setError(null)\n\n let optimisticId: string | undefined\n\n try {\n // Optimistic update.\n if (opts?.optimistic && action !== 'delete') {\n const tempRecord: BaseRecord = {\n id: data.id as string ?? `_temp_${Date.now()}`,\n ...data,\n }\n optimisticId = client.store.optimisticSet(collection, tempRecord)\n } else if (opts?.optimistic && action === 'delete' && data.id) {\n optimisticId = client.store.optimisticDelete(collection, data.id as string)\n }\n\n // Server request.\n let result: BaseRecord | void = undefined\n switch (action) {\n case 'create':\n result = await client.create(collection, data)\n break\n case 'update': {\n const id = data.id as string\n if (!id) throw new Error('useMutation: update requires data.id')\n const { id: _id, ...rest } = data\n result = await client.update(collection, id, rest)\n break\n }\n case 'delete': {\n const id = data.id as string\n if (!id) throw new Error('useMutation: delete requires data.id')\n await client.delete(collection, id)\n break\n }\n }\n\n // On success, remove optimistic entry (server truth takes over via applyServerUpdate).\n if (optimisticId) {\n client.store.rollbackOptimistic(optimisticId)\n }\n\n return result\n } catch (err) {\n // Rollback optimistic on failure.\n if (optimisticId) {\n client.store.rollbackOptimistic(optimisticId)\n }\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n throw e\n } finally {\n setIsLoading(false)\n }\n },\n [client, collection, action],\n )\n\n return { mutate, isLoading, error }\n}\n\n// ---------------------------------------------------------------------------\n// useAuth -- auth state\n// ---------------------------------------------------------------------------\n\nexport interface UseAuthResult {\n user: BaseRecord | null\n token: string\n isValid: boolean\n signIn: (identity: string, password: string) => Promise<void>\n signUp: (data: Record<string, unknown>) => Promise<BaseRecord>\n signOut: () => void\n /** Subscribe to auth changes. Returns unsubscribe. */\n onChange: AuthStore['onChange']\n}\n\n/**\n * Auth state hook.\n *\n * Returns the current auth state and provides sign-in, sign-up, and\n * sign-out methods. Re-renders when auth state changes.\n *\n * @param collection - Auth collection name (default: \"users\").\n */\nexport function useAuth(collection = 'users'): UseAuthResult {\n const client = useBase()\n const [user, setUser] = useState<BaseRecord | null>(client.authStore.record)\n const [token, setToken] = useState(client.authStore.token)\n\n useEffect(() => {\n const unsubscribe = client.authStore.onChange((newToken, newRecord) => {\n setToken(newToken)\n setUser(newRecord)\n })\n return unsubscribe\n }, [client])\n\n const signIn = useCallback(\n async (identity: string, password: string) => {\n await client.signInWithPassword(collection, identity, password)\n },\n [client, collection],\n )\n\n const signUp = useCallback(\n async (data: Record<string, unknown>) => {\n return client.signUp(collection, data)\n },\n [client, collection],\n )\n\n const signOut = useCallback(() => {\n client.signOut()\n }, [client])\n\n return {\n user,\n token,\n isValid: client.authStore.isValid,\n signIn,\n signUp,\n signOut,\n onChange: client.authStore.onChange.bind(client.authStore),\n }\n}\n\n// ---------------------------------------------------------------------------\n// useRealtime -- low-level SSE subscription\n// ---------------------------------------------------------------------------\n\n/**\n * Low-level realtime subscription hook.\n *\n * Subscribes to SSE events for a collection topic on mount and\n * unsubscribes on unmount.\n */\nexport function useRealtime(\n collection: string,\n topic: string,\n callback: (event: RealtimeEvent) => void,\n): void {\n const client = useBase()\n const callbackRef = useRef(callback)\n callbackRef.current = callback\n\n useEffect(() => {\n const unsubscribe = client.realtime.subscribe(collection, topic, (event) => {\n callbackRef.current(event)\n })\n return unsubscribe\n }, [client, collection, topic])\n}\n\n// ---------------------------------------------------------------------------\n// useConnectionState -- realtime connection state\n// ---------------------------------------------------------------------------\n\nexport function useConnectionState(): ConnectionState {\n const client = useBase()\n const [state, setState] = useState<ConnectionState>(client.realtime.state)\n\n useEffect(() => {\n const unsubscribe = client.realtime.onConnectionChange((s) => {\n setState(s)\n })\n return unsubscribe\n }, [client])\n\n return state\n}\n\n// ---------------------------------------------------------------------------\n// usePresence -- presence tracking via CRDT sync\n// ---------------------------------------------------------------------------\n\nexport interface UsePresenceResult {\n /** Map of peer siteId to their state. */\n peers: Map<string, PeerState>\n /** Our own presence metadata. */\n myState: Record<string, unknown>\n /** Update our presence metadata. */\n updateState: (meta: Record<string, unknown>) => void\n}\n\n/**\n * Presence tracking hook.\n *\n * Requires a CRDTSync instance (usually obtained from useCRDT).\n * Tracks peers connected to the same document.\n */\nexport function usePresence(sync: CRDTSync): UsePresenceResult {\n const [peers, setPeers] = useState<Map<string, PeerState>>(new Map())\n const [myState, setMyState] = useState<Record<string, unknown>>({})\n\n useEffect(() => {\n const unsubscribe = sync.onPeersChange((p) => {\n setPeers(new Map(p))\n })\n return unsubscribe\n }, [sync])\n\n const updateState = useCallback(\n (meta: Record<string, unknown>) => {\n setMyState(meta)\n sync.updatePresence(meta)\n },\n [sync],\n )\n\n return { peers, myState, updateState }\n}\n\n// ---------------------------------------------------------------------------\n// useCRDT -- CRDT document hook with auto-sync\n// ---------------------------------------------------------------------------\n\nexport interface UseCRDTResult {\n /** The CRDTDocument instance. */\n doc: CRDTDocument\n /** Convenience accessor for text fields. */\n text: (field: string) => CRDTText\n /** Convenience accessor for counter fields. */\n counter: (field: string) => CRDTCounter\n /** Convenience accessor for set fields. */\n set: <T = unknown>(field: string) => CRDTSet<T>\n /** Convenience accessor for register fields. */\n register: <T = unknown>(field: string) => CRDTRegister<T>\n /** The CRDTSync instance for advanced usage (presence, state listeners). */\n sync: CRDTSync\n /** Current sync state. */\n syncState: SyncState\n}\n\n/**\n * CRDT document hook.\n *\n * Creates a CRDTDocument and CRDTSync, connects to the server via\n * WebSocket, and auto-syncs all local operations.\n *\n * @param documentId - Unique document identifier.\n * @param wsUrl - WebSocket URL for the CRDT sync endpoint. If not provided,\n * derives it from the BaseClient URL (http->ws, https->wss).\n */\nexport function useCRDT(documentId: string, wsUrl?: string): UseCRDTResult {\n const client = useBase()\n\n // Stable refs for document and sync -- created once per documentId.\n const docRef = useRef<CRDTDocument | null>(null)\n const syncRef = useRef<CRDTSync | null>(null)\n const [syncState, setSyncState] = useState<SyncState>('disconnected')\n\n // Create document and sync on first render or documentId change.\n if (!docRef.current || docRef.current.id !== documentId) {\n docRef.current = new CRDTDocumentImpl(documentId)\n }\n if (!syncRef.current) {\n syncRef.current = new CRDTSync()\n }\n\n const doc = docRef.current\n const sync = syncRef.current\n\n // Connect on mount, disconnect on unmount.\n useEffect(() => {\n const url = wsUrl ?? deriveCrdtWsUrl(client.url)\n const token = client.authStore.token || undefined\n\n sync.connect(url, doc, token)\n\n const unsubState = sync.onStateChange((s) => {\n setSyncState(s)\n })\n\n return () => {\n unsubState()\n sync.disconnect()\n }\n }, [doc, sync, client, wsUrl])\n\n // Convenience accessors that delegate to the document.\n const text = useCallback((field: string) => doc.getText(field), [doc])\n const counter = useCallback((field: string) => doc.getCounter(field), [doc])\n const set = useCallback(<T = unknown>(field: string) => doc.getSet<T>(field), [doc])\n const register = useCallback(<T = unknown>(field: string) => doc.getRegister<T>(field), [doc])\n\n return { doc, text, counter, set, register, sync, syncState }\n}\n\n// ---------------------------------------------------------------------------\n// useCRDTText -- subscribe to a CRDT text field's content\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a specific CRDTText field's current string value.\n * Re-renders whenever the text changes (local or remote).\n */\nexport function useCRDTText(doc: CRDTDocument, field: string): string {\n const textCrdt = doc.getText(field)\n const [value, setValue] = useState(() => textCrdt.toString())\n\n useEffect(() => {\n const unsubscribe = textCrdt.onChange((text) => {\n setValue(text)\n })\n return unsubscribe\n }, [textCrdt])\n\n return value\n}\n\n// ---------------------------------------------------------------------------\n// useCRDTCounter -- subscribe to a CRDT counter field\n// ---------------------------------------------------------------------------\n\nexport function useCRDTCounter(doc: CRDTDocument, field: string): number {\n const counterCrdt = doc.getCounter(field)\n const [value, setValue] = useState(() => counterCrdt.value)\n\n useEffect(() => {\n const unsubscribe = counterCrdt.onChange((v) => {\n setValue(v)\n })\n return unsubscribe\n }, [counterCrdt])\n\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Derive WebSocket URL from HTTP URL: https://x -> wss://x/v1/crdt */\nfunction deriveCrdtWsUrl(httpUrl: string): string {\n const url = httpUrl.replace(/\\/$/, '')\n if (url.startsWith('https://')) {\n return url.replace('https://', 'wss://') + '/v1/crdt'\n }\n return url.replace('http://', 'ws://') + '/v1/crdt'\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hanzo/base",
3
- "version": "0.2.0",
4
- "description": "Hanzo Base SDK - Reactive queries, CRDT, realtime, and PocketBase-compatible client",
3
+ "version": "0.2.1",
4
+ "description": "Hanzo Base SDK - Reactive queries, CRDT, realtime, and Base-compatible client",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
7
7
  "module": "./dist/core/index.js",
@@ -35,9 +35,7 @@
35
35
  "clean": "rm -rf dist",
36
36
  "prepublishOnly": "npm run build"
37
37
  },
38
- "dependencies": {
39
- "pocketbase": "^0.26.5"
40
- },
38
+ "dependencies": {},
41
39
  "devDependencies": {
42
40
  "@types/react": "^19.0.0",
43
41
  "react": "^19.0.0",
@@ -66,6 +64,6 @@
66
64
  "realtime",
67
65
  "reactive",
68
66
  "crdt",
69
- "pocketbase"
67
+ "hanzo-base"
70
68
  ]
71
69
  }