lazypock 0.1.0

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.
@@ -0,0 +1,520 @@
1
+ /** Shape of a record returned from any collection. */
2
+ interface ApiRecord {
3
+ id: string;
4
+ collectionId: string;
5
+ collectionName: string;
6
+ created: string;
7
+ updated: string;
8
+ [key: string]: unknown;
9
+ }
10
+ /** Paginated list response matching PocketBase format. */
11
+ interface ListResult<T = ApiRecord> {
12
+ items: T[];
13
+ page: number;
14
+ perPage: number;
15
+ totalItems: number;
16
+ totalPages: number;
17
+ }
18
+ /** HTTP method supported by the client. */
19
+ type Method = "GET" | "POST" | "PATCH" | "DELETE";
20
+ interface RequestOptions {
21
+ /** Search/filter params */
22
+ params?: Record<string, string>;
23
+ /** Raw request headers to merge */
24
+ headers?: Record<string, string>;
25
+ /** Abort signal */
26
+ signal?: AbortSignal;
27
+ /** Custom fetch implementation (for RN or test mocking) */
28
+ fetch?: typeof globalThis.fetch;
29
+ }
30
+ declare class ApiError extends Error {
31
+ readonly data: unknown;
32
+ readonly status: number;
33
+ constructor(message: string, data: unknown, status: number);
34
+ }
35
+
36
+ /**
37
+ * Interface for pluggable persistence backends.
38
+ * Swap for `AsyncStorage` on React Native, `localStorage` on web, etc.
39
+ */
40
+ interface StorageAdapter {
41
+ /** Retrieve a stored value by key. */
42
+ get(key: string): string | null | Promise<string | null>;
43
+ /** Persist a key-value pair. */
44
+ set(key: string, value: string): void | Promise<void>;
45
+ /** Remove a stored value by key. */
46
+ remove(key: string): void | Promise<void>;
47
+ }
48
+ /** Shape of an authenticated user record (from auth collections). */
49
+ interface AuthModel {
50
+ id: string;
51
+ [key: string]: unknown;
52
+ }
53
+ /** Callback signature for auth state changes. */
54
+ type AuthListener = (model: AuthModel | null, token: string) => void;
55
+ declare class AuthStore {
56
+ private _token;
57
+ private _model;
58
+ private _tokenExpiresAt;
59
+ private _collectionName;
60
+ private listeners;
61
+ private storage;
62
+ /**
63
+ * Create an AuthStore with optional custom storage adapter.
64
+ * @param storage Persistence backend. Defaults to `memoryStorage` (localStorage fallback).
65
+ */
66
+ constructor(storage?: StorageAdapter);
67
+ /** The current JWT token string, or empty string if not authenticated. */
68
+ get token(): string;
69
+ /** The current authenticated user record, or null. */
70
+ get model(): AuthModel | null;
71
+ /** Whether a token exists (does not check expiry). */
72
+ get isValid(): boolean;
73
+ /**
74
+ * Whether the current token has expired (with a 30-second buffer).
75
+ * Returns false when no expiry has been recorded (e.g. superuser tokens).
76
+ */
77
+ get isExpired(): boolean;
78
+ /** The auth collection name used for automatic token refresh. */
79
+ get collectionName(): string | null;
80
+ /**
81
+ * Set the auth collection name (used internally by auto-refresh).
82
+ * @param name The collection name, or null for superuser tokens.
83
+ */
84
+ setCollectionName(name: string | null): void;
85
+ /**
86
+ * Load persisted auth state from storage.
87
+ * Should be called once at application startup.
88
+ */
89
+ init(): Promise<void>;
90
+ /**
91
+ * Update the current auth token and model, persist to storage, and notify listeners.
92
+ * @param token The JWT token string.
93
+ * @param model The authenticated user record, or null for superusers.
94
+ */
95
+ set(token: string, model: AuthModel | null): void;
96
+ /**
97
+ * Clear all auth state (token, model, expiry) and notify listeners.
98
+ */
99
+ clear(): void;
100
+ /**
101
+ * Register a listener for auth state changes.
102
+ * @param fn Callback invoked with (model, token) on every change.
103
+ * @returns An unsubscribe function.
104
+ */
105
+ onChange(fn: AuthListener): () => void;
106
+ private notify;
107
+ }
108
+
109
+ /**
110
+ * Low-level HTTP client wrapping `fetch` with automatic auth token injection.
111
+ * Only relies on `globalThis.fetch` — works in browser, React Native, and Node 18+.
112
+ */
113
+ declare class HttpClient {
114
+ private baseUrl;
115
+ private authStore;
116
+ private defaultFetch;
117
+ /**
118
+ * @param baseUrl The API base URL (e.g. `http://localhost:4000/api`). Trailing slash stripped.
119
+ * @param authStore The auth store providing the token for Authorization headers.
120
+ */
121
+ constructor(baseUrl: string, authStore: AuthStore);
122
+ private refreshAuth;
123
+ /**
124
+ * Make an HTTP request with automatic auth token injection and optional auto-refresh.
125
+ *
126
+ * @param method HTTP method.
127
+ * @param path URL path (appended to baseUrl).
128
+ * @param body JSON-serializable body, or FormData for file uploads.
129
+ * @param options Optional request options.
130
+ * @returns Parsed JSON response, or null for 204 No Content.
131
+ * @throws {ApiError} On non-2xx responses.
132
+ */
133
+ request<T = unknown>(method: Method, path: string, body?: unknown, options?: RequestOptions): Promise<T | null>;
134
+ /**
135
+ * HTTP GET.
136
+ * @param path URL path.
137
+ * @param options Optional request options.
138
+ */
139
+ get<T = unknown>(path: string, options?: RequestOptions): Promise<T | null>;
140
+ /**
141
+ * HTTP POST.
142
+ * @param path URL path.
143
+ * @param body Optional request body.
144
+ * @param options Optional request options.
145
+ */
146
+ post<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<T | null>;
147
+ /**
148
+ * HTTP PATCH.
149
+ * @param path URL path.
150
+ * @param body Optional request body.
151
+ * @param options Optional request options.
152
+ */
153
+ patch<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<T | null>;
154
+ /**
155
+ * HTTP DELETE.
156
+ * @param path URL path.
157
+ * @param options Optional request options.
158
+ */
159
+ delete<T = unknown>(path: string, options?: RequestOptions): Promise<T | null>;
160
+ }
161
+
162
+ /**
163
+ * Typed CRUD service for a single dynamic collection.
164
+ * Get an instance via {@link LazypockClient.collection}.
165
+ */
166
+ declare class CollectionService {
167
+ private http;
168
+ private collectionName;
169
+ private authStore?;
170
+ /** @internal */
171
+ constructor(http: HttpClient, collectionName: string, authStore?: AuthStore);
172
+ private encodeId;
173
+ /**
174
+ * List records with optional filter/sort/pagination.
175
+ * @param params Query parameters including `filter`, `sort`, `page`, `perPage`, `expand`.
176
+ * @param options Optional request options.
177
+ */
178
+ list<T = ApiRecord>(params?: Record<string, string>, options?: RequestOptions): Promise<ListResult<T> | null>;
179
+ /**
180
+ * Get a single record by ID.
181
+ * @param id Record ID.
182
+ * @param options Optional request options.
183
+ */
184
+ getOne<T = ApiRecord>(id: string, options?: RequestOptions): Promise<T | null>;
185
+ /**
186
+ * Create a new record.
187
+ * @param data Record fields.
188
+ * @param options Optional request options.
189
+ */
190
+ create<T = ApiRecord>(data: Record<string, unknown>, options?: RequestOptions): Promise<T | null>;
191
+ /**
192
+ * Update a record by ID.
193
+ * @param id Record ID.
194
+ * @param data Updated record fields.
195
+ * @param options Optional request options.
196
+ */
197
+ update<T = ApiRecord>(id: string, data: Record<string, unknown>, options?: RequestOptions): Promise<T | null>;
198
+ /**
199
+ * Delete a record by ID.
200
+ * @param id Record ID.
201
+ * @param options Optional request options.
202
+ */
203
+ delete(id: string, options?: RequestOptions): Promise<null>;
204
+ /**
205
+ * Get a list of expandable (relation) fields for this collection.
206
+ * Useful for constructing `expand` query parameters.
207
+ */
208
+ expandFields(options?: RequestOptions): Promise<{
209
+ field: string;
210
+ targetCollection: string;
211
+ }[] | null>;
212
+ /**
213
+ * Authenticate with email/password against this auth collection.
214
+ * Stores the returned token and user model in the auth store.
215
+ */
216
+ authWithPassword(identity: string, password: string, options?: RequestOptions): Promise<({
217
+ token: string;
218
+ record: ApiRecord;
219
+ } & Record<string, unknown>) | null>;
220
+ /**
221
+ * Refresh the auth token for the currently authenticated user.
222
+ * Updates the stored token and user model.
223
+ */
224
+ authRefresh(options?: RequestOptions): Promise<({
225
+ token: string;
226
+ record: ApiRecord;
227
+ } & Record<string, unknown>) | null>;
228
+ /**
229
+ * Get available auth methods for this collection.
230
+ */
231
+ authMethods(options?: RequestOptions): Promise<Record<string, unknown> | null>;
232
+ }
233
+
234
+ interface RealtimeEvent {
235
+ event: string;
236
+ topic: string;
237
+ payload: Record<string, unknown>;
238
+ }
239
+ type RealtimeConnectOpts = {
240
+ /** WebSocket URL (e.g. ws://localhost:4000/socket/websocket) */
241
+ url: string;
242
+ /** Auth token to pass as query param */
243
+ token?: string;
244
+ };
245
+ /**
246
+ * Derive a WebSocket URL from an HTTP base URL.
247
+ * http://localhost:4000/api → ws://localhost:4000/socket/websocket
248
+ */
249
+ declare function wsUrlFromBaseUrl(baseUrl: string): string;
250
+ /**
251
+ * Phoenix Channel client for real-time collection subscriptions.
252
+ *
253
+ * Connects via WebSocket and subscribes to collection topics.
254
+ * Includes automatic reconnection with exponential backoff.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * const rt = new RealtimeService();
259
+ * rt.connect({ url: wsUrlFromBaseUrl('http://localhost:4000/api') });
260
+ * rt.subscribe('collection:posts', (e) => console.log(e));
261
+ * ```
262
+ */
263
+ declare class RealtimeService {
264
+ private ws;
265
+ private refCounter;
266
+ private subscriptions;
267
+ private reconnectTimer;
268
+ private reconnectAttempt;
269
+ private maxReconnectDelay;
270
+ onReconnect?: () => void;
271
+ onDisconnect?: () => void;
272
+ onError?: (err: Event) => void;
273
+ private url;
274
+ private token;
275
+ connect(opts: RealtimeConnectOpts): void;
276
+ disconnect(): void;
277
+ /**
278
+ * Subscribe to a topic (e.g. "collection:posts" or "collection:posts:*").
279
+ * The backend Channel authorizes via listRule on join.
280
+ */
281
+ subscribe(topic: string, callback: (e: RealtimeEvent) => void): void;
282
+ /**
283
+ * Unsubscribe a specific callback from a topic.
284
+ */
285
+ unsubscribe(topic: string, callback?: (e: RealtimeEvent) => void): void;
286
+ private resubscribeAll;
287
+ private doConnect;
288
+ private handleMessage;
289
+ private joinTopic;
290
+ private nextRef;
291
+ private heartbeatInterval;
292
+ private startHeartbeat;
293
+ private stopHeartbeat;
294
+ private scheduleReconnect;
295
+ private clearReconnectTimer;
296
+ }
297
+
298
+ /** Response shape from the server file endpoints */
299
+ interface FileRecord {
300
+ id: string;
301
+ filename: string;
302
+ mimeType: string;
303
+ size: number;
304
+ url: string;
305
+ [key: string]: unknown;
306
+ }
307
+ /**
308
+ * Construct a file URL from the API base URL and file ID.
309
+ */
310
+ declare function getFileUrl(baseUrl: string, fileId: string): string;
311
+ /**
312
+ * Service for file upload, retrieval, and deletion.
313
+ * Access via {@link LazypockClient.files}.
314
+ */
315
+ declare class FilesService {
316
+ private http;
317
+ constructor(http: HttpClient);
318
+ /**
319
+ * Upload a file or blob.
320
+ *
321
+ * @param file The File or Blob to upload.
322
+ * @param filename Optional filename (required if `file` is a Blob without a name).
323
+ * @param options Optional request options (signal, custom fetch).
324
+ * @param meta Optional metadata: collectionName, recordId, fieldName for ownership tracking.
325
+ */
326
+ upload(file: File | Blob, filename?: string, options?: RequestOptions, meta?: {
327
+ collectionName?: string;
328
+ recordId?: string;
329
+ fieldName?: string;
330
+ }): Promise<FileRecord | null>;
331
+ /**
332
+ * Fetch file metadata including URL.
333
+ * @param fileId The file ID.
334
+ */
335
+ getUrl(fileId: string): Promise<string | null>;
336
+ /**
337
+ * Delete a file by ID.
338
+ * @param fileId The file ID.
339
+ * @param options Optional request options.
340
+ */
341
+ delete(fileId: string, options?: RequestOptions): Promise<null>;
342
+ }
343
+
344
+ /** Options for constructing a {@link LazypockClient}. */
345
+ interface LazypockClientOptions {
346
+ /** API base URL (e.g. 'http://localhost:4000/api') */
347
+ baseUrl: string;
348
+ /** Custom storage adapter (default: localStorage fallback) */
349
+ storage?: StorageAdapter;
350
+ /** Explicit auth store instance (for sharing across modules) */
351
+ authStore?: AuthStore;
352
+ /** Real-time service for Phoenix Channel WebSocket subscriptions */
353
+ realtime?: RealtimeService;
354
+ }
355
+ /**
356
+ * Lazypock API client.
357
+ *
358
+ * Provides methods for authentication, CRUD operations on dynamic collections,
359
+ * file management, and real-time subscriptions.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const client = new LazypockClient({ baseUrl: 'http://localhost:4000/api' });
364
+ * await client.login('admin@example.com', 'password');
365
+ * const posts = await client.collection('posts').list();
366
+ * ```
367
+ */
368
+ declare class LazypockClient {
369
+ readonly http: HttpClient;
370
+ readonly authStore: AuthStore;
371
+ readonly realtime: RealtimeService;
372
+ readonly files: FilesService;
373
+ private collectionCache;
374
+ /**
375
+ * Create a new Lazypock client.
376
+ * @param options Configuration options.
377
+ */
378
+ constructor(options: LazypockClientOptions);
379
+ /**
380
+ * Get or create a typed service for the given collection.
381
+ * Services are cached after first access.
382
+ *
383
+ * @param name The collection name.
384
+ * @returns A {@link CollectionService} instance.
385
+ */
386
+ collection(name: string): CollectionService;
387
+ /** Check whether any superuser exists (for login vs setup screen routing). */
388
+ checkSuperuser(): Promise<{
389
+ has_superuser: boolean;
390
+ } | null>;
391
+ /**
392
+ * Create the initial superuser account.
393
+ * Only works when no superuser exists yet.
394
+ * Stores the returned token in the auth store.
395
+ * @param email Superuser email.
396
+ * @param password Superuser password (min 8 chars).
397
+ */
398
+ setup(email: string, password: string): Promise<({
399
+ token: string;
400
+ } & Record<string, unknown>) | null>;
401
+ /**
402
+ * Authenticate as a superuser or auth collection user.
403
+ *
404
+ * When `collection` is provided, authenticates against
405
+ * `/{collection}/auth-with-password`. Otherwise logs in as superuser.
406
+ * Stores the returned token in the auth store.
407
+ *
408
+ * @param email User email or identity.
409
+ * @param password User password.
410
+ * @param collection Optional auth collection name.
411
+ */
412
+ login(email: string, password: string, collection?: string): Promise<({
413
+ token: string;
414
+ } & Record<string, unknown>) | null>;
415
+ /** Fetch the current superuser profile and refresh the auth model. */
416
+ me<T = ApiRecord>(options?: RequestOptions): Promise<T | null>;
417
+ /**
418
+ * Authenticate against an auth collection with email/password.
419
+ * Stores the returned token and user record in the auth store.
420
+ *
421
+ * @param collection The auth collection name.
422
+ * @param identity Email or username.
423
+ * @param password Password.
424
+ * @param options Optional request options.
425
+ */
426
+ authWithPassword(collection: string, identity: string, password: string, options?: RequestOptions): Promise<({
427
+ token: string;
428
+ record: ApiRecord;
429
+ } & Record<string, unknown>) | null>;
430
+ /**
431
+ * Refresh an auth collection token.
432
+ * Uses the currently stored auth token.
433
+ *
434
+ * @param collection The auth collection name.
435
+ * @param options Optional request options.
436
+ */
437
+ authRefresh(collection: string, options?: RequestOptions): Promise<({
438
+ token: string;
439
+ record: ApiRecord;
440
+ } & Record<string, unknown>) | null>;
441
+ /** Clear the current auth state and remove persisted tokens. */
442
+ logout(): void;
443
+ /** Ping the API health endpoint. */
444
+ health(options?: RequestOptions): Promise<Record<string, unknown> | null>;
445
+ /**
446
+ * List all collections (admin).
447
+ * @param q URL query string (e.g. `page=1&perPage=200`).
448
+ * @param options Optional request options.
449
+ */
450
+ listCollections(q?: string, options?: RequestOptions): Promise<ListResult<ApiRecord> | null>;
451
+ /**
452
+ * Get a single collection by ID or name.
453
+ * @param id Collection ID or name.
454
+ * @param options Optional request options.
455
+ */
456
+ getCollection(id: string, options?: RequestOptions): Promise<ApiRecord | null>;
457
+ /**
458
+ * Create a new collection (admin).
459
+ * @param data Collection definition (name, type, fields, options, rules, etc.).
460
+ * @param options Optional request options.
461
+ */
462
+ createCollection(data: Record<string, unknown>, options?: RequestOptions): Promise<ApiRecord | null>;
463
+ /**
464
+ * Update an existing collection (admin).
465
+ * @param id Collection ID or name.
466
+ * @param data Updated collection fields.
467
+ * @param options Optional request options.
468
+ */
469
+ updateCollection(id: string, data: Record<string, unknown>, options?: RequestOptions): Promise<ApiRecord | null>;
470
+ /**
471
+ * Delete a collection (admin).
472
+ * @param id Collection ID or name.
473
+ * @param options Optional request options.
474
+ */
475
+ deleteCollection(id: string, options?: RequestOptions): Promise<null>;
476
+ /**
477
+ * List records from a dynamic collection with optional filter/sort/pagination.
478
+ *
479
+ * @param coll Collection name.
480
+ * @param params Query parameters including:
481
+ * - `filter` — PocketBase filter syntax (e.g. `title~'hello' && published=true`)
482
+ * - `sort` — Comma-separated, `-` prefix for DESC (e.g. `-created,title`)
483
+ * - `page` — Page number (default: 1)
484
+ * - `perPage` — Items per page (default: 30, max: 200)
485
+ * - `expand` — Comma-separated relation fields (e.g. `author,category`)
486
+ * @param options Optional request options.
487
+ */
488
+ listRecords(coll: string, params?: Record<string, string>, options?: RequestOptions): Promise<ListResult<ApiRecord> | null>;
489
+ /**
490
+ * Get a single record by ID.
491
+ * @param coll Collection name.
492
+ * @param id Record ID.
493
+ * @param options Optional request options.
494
+ */
495
+ getRecord(coll: string, id: string, options?: RequestOptions): Promise<ApiRecord | null>;
496
+ /**
497
+ * Create a record in a dynamic collection.
498
+ * @param coll Collection name.
499
+ * @param data Record fields.
500
+ * @param options Optional request options.
501
+ */
502
+ createRecord(coll: string, data: Record<string, unknown>, options?: RequestOptions): Promise<ApiRecord | null>;
503
+ /**
504
+ * Update a record in a dynamic collection.
505
+ * @param coll Collection name.
506
+ * @param id Record ID.
507
+ * @param data Updated record fields.
508
+ * @param options Optional request options.
509
+ */
510
+ updateRecord(coll: string, id: string, data: Record<string, unknown>, options?: RequestOptions): Promise<ApiRecord | null>;
511
+ /**
512
+ * Delete a record from a dynamic collection.
513
+ * @param coll Collection name.
514
+ * @param id Record ID.
515
+ * @param options Optional request options.
516
+ */
517
+ deleteRecord(coll: string, id: string, options?: RequestOptions): Promise<null>;
518
+ }
519
+
520
+ export { ApiError, type ApiRecord, type AuthModel, AuthStore, type FileRecord, FilesService, LazypockClient, type LazypockClientOptions, type ListResult, RealtimeService, type RequestOptions, type StorageAdapter, getFileUrl, wsUrlFromBaseUrl };