@ichibase/client 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,742 @@
1
+ /**
2
+ * Configuration shared by every service package. All fields are
3
+ * optional — when running inside an ichibase Edge Function, sane
4
+ * defaults are read from env (ICHIBASE_PROJECT_URL +
5
+ * ICHIBASE_SERVICE_KEY / ICHIBASE_ANON_KEY).
6
+ */
7
+ interface IchibaseConfig {
8
+ /** Base URL like `https://abc.ichibase.net`. Defaults to ICHIBASE_PROJECT_URL. */
9
+ url?: string;
10
+ /** API key: ich_pub_<jwt> (anon) or ich_admin_<jwt> (service). Defaults to ICHIBASE_SERVICE_KEY then ICHIBASE_ANON_KEY. */
11
+ key?: string;
12
+ /** Optional fetch override (testing). Defaults to globalThis.fetch. */
13
+ fetch?: typeof fetch;
14
+ }
15
+ interface IchibaseError {
16
+ code: string;
17
+ detail?: string;
18
+ /** HTTP status. */
19
+ status: number;
20
+ }
21
+ type Result<T> = {
22
+ data: T;
23
+ error: null;
24
+ } | {
25
+ data: null;
26
+ error: IchibaseError;
27
+ };
28
+
29
+ type Method = 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'HEAD';
30
+ interface BuilderState {
31
+ base: string;
32
+ key: string;
33
+ table: string;
34
+ fetchFn: typeof fetch;
35
+ /** Encoded "col=op.val" pieces. */
36
+ filters: string[];
37
+ body?: unknown;
38
+ method?: Method;
39
+ returnRepresentation?: boolean;
40
+ /** PostgREST "object+json" Accept — enforce exactly-one. */
41
+ acceptSingle?: boolean;
42
+ /** Return first row of an array or null; no server-side enforcement. */
43
+ acceptMaybeSingle?: boolean;
44
+ /** Return CSV text instead of JSON. */
45
+ acceptCsv?: boolean;
46
+ /** Count mode for Prefer header. Also makes the result a {rows,count}. */
47
+ countMode?: 'exact' | 'planned' | 'estimated';
48
+ /** Upsert resolution. */
49
+ upsertResolution?: 'merge-duplicates' | 'ignore-duplicates';
50
+ /** Columns for on_conflict on upsert. */
51
+ onConflict?: string;
52
+ /** HTTP Range header values (server returns 206 + Content-Range). */
53
+ rangeFrom?: number;
54
+ rangeTo?: number;
55
+ /** Non-public schema name (Accept-Profile / Content-Profile header). */
56
+ schemaName?: string;
57
+ /** Free-form headers the caller wants to override. */
58
+ extraHeaders?: Record<string, string>;
59
+ }
60
+ /** What `.count()` resolves to. */
61
+ interface CountedResult<T> {
62
+ rows: T[];
63
+ count: number;
64
+ }
65
+ /** PostgREST filter operators usable with `not()` / `filter()` escape hatches. */
66
+ type FilterOp = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'match' | 'imatch' | 'in' | 'is' | 'isdistinct' | 'fts' | 'plfts' | 'phfts' | 'wfts' | 'cs' | 'cd' | 'ov' | 'sl' | 'sr' | 'nxr' | 'nxl' | 'adj';
67
+ declare class QueryBuilder<T, R = T[]> implements PromiseLike<Result<R>> {
68
+ private state;
69
+ constructor(state: BuilderState);
70
+ eq(col: string, val: unknown): this;
71
+ neq(col: string, val: unknown): this;
72
+ gt(col: string, val: unknown): this;
73
+ gte(col: string, val: unknown): this;
74
+ lt(col: string, val: unknown): this;
75
+ lte(col: string, val: unknown): this;
76
+ like(col: string, pattern: string): this;
77
+ ilike(col: string, pattern: string): this;
78
+ /** Full-text search (PostgreSQL @@). `mode` picks the parser. */
79
+ fts(col: string, query: string, opts?: {
80
+ config?: string;
81
+ type?: 'plain' | 'phrase' | 'websearch';
82
+ }): this;
83
+ in(col: string, values: unknown[]): this;
84
+ /** is.null, is.true, is.false, is.unknown */
85
+ is(col: string, val: null | boolean | 'unknown'): this;
86
+ /** array/jsonb `@>` — col contains the given values. */
87
+ contains(col: string, val: unknown): this;
88
+ /** `<@` — col is contained by the given values. */
89
+ containedBy(col: string, val: unknown): this;
90
+ /** `&&` — arrays/ranges overlap. */
91
+ overlaps(col: string, val: unknown): this;
92
+ /** Range strictly left of (`<<`). */
93
+ rangeLt(col: string, val: string): this;
94
+ /** Range strictly right of (`>>`). */
95
+ rangeGt(col: string, val: string): this;
96
+ /** Range does not extend to the right of (`&<`). */
97
+ rangeLte(col: string, val: string): this;
98
+ /** Range does not extend to the left of (`&>`). */
99
+ rangeGte(col: string, val: string): this;
100
+ /** Adjacent ranges (`-|-`). */
101
+ rangeAdjacent(col: string, val: string): this;
102
+ /** Negate a filter: `not(col, 'gt', 18)` → `col=not.gt.18`. */
103
+ not(col: string, op: FilterOp, val: unknown): this;
104
+ /**
105
+ * Logical OR. Pass a comma-separated filter string in PostgREST syntax:
106
+ * .or('age.gt.18,status.eq.active')
107
+ * .or('and(status.eq.paid,total.gt.100),user_id.eq.42')
108
+ */
109
+ or(filters: string): this;
110
+ /** Logical AND group (rarely needed — multiple chained filters are already ANDed). */
111
+ and(filters: string): this;
112
+ /** Multiple eq() in one call: `.match({ status: 'paid', user_id: 42 })`. */
113
+ match(query: Record<string, unknown>): this;
114
+ /** Escape hatch — any column/op/value combo PostgREST supports. */
115
+ filter(col: string, op: FilterOp, val: unknown): this;
116
+ order(col: string, opts?: {
117
+ ascending?: boolean;
118
+ nullsFirst?: boolean;
119
+ }): this;
120
+ limit(n: number): this;
121
+ offset(n: number): this;
122
+ /**
123
+ * HTTP Range pagination — `from` and `to` are inclusive 0-based.
124
+ * Server returns 206 + Content-Range: from-to/total.
125
+ * Pairs naturally with `.count()`.
126
+ */
127
+ range(from: number, to: number): this;
128
+ /**
129
+ * Pick columns and embed related resources:
130
+ * .select('id, total, customer:profiles(name, email)')
131
+ * Defaults to '*'.
132
+ */
133
+ select(cols?: string): this;
134
+ /** Target a non-public schema for this call. */
135
+ schema(name: string): this;
136
+ insert(rows: Record<string, unknown> | Record<string, unknown>[]): this;
137
+ /**
138
+ * INSERT ... ON CONFLICT ... DO UPDATE / DO NOTHING.
139
+ * .upsert([row1, row2], { onConflict: 'email' })
140
+ * .upsert(row, { onConflict: 'id', ignoreDuplicates: true })
141
+ */
142
+ upsert(rows: Record<string, unknown> | Record<string, unknown>[], opts?: {
143
+ onConflict?: string;
144
+ ignoreDuplicates?: boolean;
145
+ }): this;
146
+ update(values: Record<string, unknown>): this;
147
+ delete(): this;
148
+ /** Mark write op to return the affected row(s). */
149
+ returning(): this;
150
+ /**
151
+ * Expect exactly one row — server returns 406 otherwise.
152
+ * After this, the result data is T (not T[]).
153
+ */
154
+ single(): QueryBuilder<T, T>;
155
+ /**
156
+ * Expect zero or one row — never errors on shape.
157
+ * After this, the result data is T | null.
158
+ */
159
+ maybeSingle(): QueryBuilder<T, T | null>;
160
+ /** Return CSV text instead of JSON rows. */
161
+ csv(): QueryBuilder<T, string>;
162
+ /**
163
+ * Include the total row count in the result. The resolved value
164
+ * becomes `{ rows: T[]; count: number }` — count is the size of
165
+ * the FULL result set ignoring limit/offset/range.
166
+ * `mode='exact'` is accurate (slow on big tables);
167
+ * `'planned'` uses the planner estimate;
168
+ * `'estimated'` uses planner then exact-counts only if small.
169
+ */
170
+ count(mode?: 'exact' | 'planned' | 'estimated'): QueryBuilder<T, CountedResult<T>>;
171
+ /** Add an arbitrary header to the request (e.g. `Prefer: missing=null`). */
172
+ setHeader(name: string, value: string): this;
173
+ /** HEAD request — no body returned. Pair with `.count()` to fetch just a total. */
174
+ head(): this;
175
+ then<TR1 = Result<R>, TR2 = never>(onFulfilled?: ((value: Result<R>) => TR1 | PromiseLike<TR1>) | null, onRejected?: ((reason: unknown) => TR2 | PromiseLike<TR2>) | null): PromiseLike<TR1 | TR2>;
176
+ private appendFilter;
177
+ private formatArrayOrJson;
178
+ private buildHeaders;
179
+ private execute;
180
+ }
181
+ declare class Postgrest {
182
+ private readonly url;
183
+ private readonly key;
184
+ private readonly fetchFn;
185
+ constructor(url: string, key: string, fetchFn: typeof fetch);
186
+ /** Start a query against a table or view. */
187
+ from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
188
+ /**
189
+ * Call a stored procedure (PostgREST RPC). Returns whatever the
190
+ * function returns — set the generic to type it. Pass `count:'exact'`
191
+ * if you want a total in the same envelope.
192
+ */
193
+ rpc<T = unknown>(fnName: string, args?: Record<string, unknown>, opts?: {
194
+ schema?: string;
195
+ count?: 'exact' | 'planned' | 'estimated';
196
+ head?: boolean;
197
+ }): Promise<Result<T>>;
198
+ /** Return a new Postgrest authenticated as a specific end-user (RLS applies). */
199
+ asUser(accessToken: string): Postgrest;
200
+ }
201
+
202
+ /** Per-collection client. */
203
+ declare class MongoCollection {
204
+ private base;
205
+ private key;
206
+ private collection;
207
+ private fetchFn;
208
+ private userToken?;
209
+ constructor(base: string, key: string, collection: string, fetchFn: typeof fetch, userToken?: string | undefined);
210
+ private op;
211
+ private rt;
212
+ find(filter?: Record<string, unknown>, opts?: {
213
+ projection?: Record<string, 0 | 1>;
214
+ sort?: Record<string, 1 | -1>;
215
+ limit?: number;
216
+ skip?: number;
217
+ }): Promise<Result<Record<string, unknown>[]>>;
218
+ findOne(filter?: Record<string, unknown>): Promise<Result<Record<string, unknown> | null>>;
219
+ insertOne(doc: Record<string, unknown>, opts?: {
220
+ realtime?: boolean;
221
+ }): Promise<Result<{
222
+ insertedId: string;
223
+ }>>;
224
+ insertMany(docs: Record<string, unknown>[], opts?: {
225
+ realtime?: boolean;
226
+ }): Promise<Result<{
227
+ insertedIds: string[];
228
+ }>>;
229
+ updateOne(filter: Record<string, unknown>, update: Record<string, unknown>, opts?: {
230
+ upsert?: boolean;
231
+ realtime?: boolean;
232
+ }): Promise<Result<{
233
+ matched: number;
234
+ modified: number;
235
+ upsertedId?: string;
236
+ }>>;
237
+ updateMany(filter: Record<string, unknown>, update: Record<string, unknown>, opts?: {
238
+ realtime?: boolean;
239
+ }): Promise<Result<{
240
+ matched: number;
241
+ modified: number;
242
+ }>>;
243
+ deleteOne(filter: Record<string, unknown>, opts?: {
244
+ realtime?: boolean;
245
+ }): Promise<Result<{
246
+ deleted: number;
247
+ }>>;
248
+ deleteMany(filter: Record<string, unknown>, opts?: {
249
+ realtime?: boolean;
250
+ }): Promise<Result<{
251
+ deleted: number;
252
+ }>>;
253
+ count(filter?: Record<string, unknown>): Promise<Result<{
254
+ count: number;
255
+ }>>;
256
+ aggregate(pipeline: Record<string, unknown>[]): Promise<Result<Record<string, unknown>[]>>;
257
+ /**
258
+ * Atomic find-and-update. Returns the matched document (post-update
259
+ * by default; pass `returnDocument: 'before'` for the pre-update
260
+ * snapshot). Honours upsert; on upsert with no match the returned
261
+ * doc is null.
262
+ *
263
+ * await users.findOneAndUpdate(
264
+ * { _id: 1 },
265
+ * { $inc: { visits: 1 } },
266
+ * { returnDocument: 'after' },
267
+ * );
268
+ */
269
+ findOneAndUpdate(filter: Record<string, unknown>, update: Record<string, unknown>, opts?: {
270
+ projection?: Record<string, 0 | 1>;
271
+ sort?: Record<string, 1 | -1>;
272
+ upsert?: boolean;
273
+ /** Default 'after'. Picks which snapshot of the doc to return. */
274
+ returnDocument?: 'before' | 'after';
275
+ realtime?: boolean;
276
+ }): Promise<Result<{
277
+ doc: Record<string, unknown> | null;
278
+ }>>;
279
+ /** Atomic find-and-delete. Returns the deleted document or null. */
280
+ findOneAndDelete(filter: Record<string, unknown>, opts?: {
281
+ projection?: Record<string, 0 | 1>;
282
+ sort?: Record<string, 1 | -1>;
283
+ realtime?: boolean;
284
+ }): Promise<Result<{
285
+ doc: Record<string, unknown> | null;
286
+ }>>;
287
+ /**
288
+ * Replace a single matched document with a full replacement.
289
+ * Unlike updateOne, the body is the new document — no $set / $inc.
290
+ * Keys starting with `$` are rejected.
291
+ */
292
+ replaceOne(filter: Record<string, unknown>, replacement: Record<string, unknown>, opts?: {
293
+ upsert?: boolean;
294
+ realtime?: boolean;
295
+ }): Promise<Result<{
296
+ matched: number;
297
+ modified: number;
298
+ upserted: number;
299
+ upserted_id?: unknown;
300
+ }>>;
301
+ /**
302
+ * Batched write — multiple ops in one HTTP round trip. Each op is
303
+ * one of insertOne / updateOne / updateMany / replaceOne /
304
+ * deleteOne / deleteMany. The whole batch shares one policy check.
305
+ *
306
+ * await users.bulkWrite([
307
+ * { op: 'insertOne', doc: { name: 'a' } },
308
+ * { op: 'updateOne', filter: { _id: 1 }, update: { $set: { name: 'b' } } },
309
+ * { op: 'deleteOne', filter: { _id: 2 } },
310
+ * ]);
311
+ *
312
+ * Set `ordered: true` to stop on first error; default is unordered
313
+ * (continue past errors). Subject to your plan's mongo_max_docs cap.
314
+ */
315
+ bulkWrite(ops: BulkWriteOp[], opts?: {
316
+ ordered?: boolean;
317
+ realtime?: boolean;
318
+ }): Promise<Result<{
319
+ inserted: number;
320
+ matched: number;
321
+ modified: number;
322
+ deleted: number;
323
+ upserted: number;
324
+ upserted_ids: Record<string, unknown>;
325
+ }>>;
326
+ /**
327
+ * Distinct values of a field across docs matching filter.
328
+ *
329
+ * await events.distinct('category', { tenant_id: 'acme' });
330
+ * // → { data: { values: ['ui', 'api', 'cron'], truncated: false } }
331
+ */
332
+ distinct(field: string, filter?: Record<string, unknown>): Promise<Result<{
333
+ values: unknown[];
334
+ truncated: boolean;
335
+ }>>;
336
+ }
337
+ type BulkWriteOp = {
338
+ op: 'insertOne';
339
+ doc: Record<string, unknown>;
340
+ } | {
341
+ op: 'updateOne';
342
+ filter: Record<string, unknown>;
343
+ update: Record<string, unknown>;
344
+ upsert?: boolean;
345
+ } | {
346
+ op: 'updateMany';
347
+ filter: Record<string, unknown>;
348
+ update: Record<string, unknown>;
349
+ upsert?: boolean;
350
+ } | {
351
+ op: 'replaceOne';
352
+ filter: Record<string, unknown>;
353
+ replacement: Record<string, unknown>;
354
+ upsert?: boolean;
355
+ } | {
356
+ op: 'deleteOne';
357
+ filter: Record<string, unknown>;
358
+ } | {
359
+ op: 'deleteMany';
360
+ filter: Record<string, unknown>;
361
+ };
362
+ /** Top-level mongo client. Use `.collection(name)`. */
363
+ declare class Mongo {
364
+ private readonly base;
365
+ private readonly key;
366
+ private readonly fetchFn;
367
+ private readonly userToken?;
368
+ constructor(base: string, key: string, fetchFn: typeof fetch, userToken?: string | undefined);
369
+ /**
370
+ * Return a Mongo client that acts AS the signed-in end user: their JWT is
371
+ * sent as `Authorization: Bearer`, so your `_mongo_policy` and realtime rules
372
+ * see the real `$auth.uid` / role. The project key still gates anon vs
373
+ * service_role. Pass the access token you got from ichibase auth.
374
+ */
375
+ asUser(token: string): Mongo;
376
+ collection(name: string): MongoCollection;
377
+ }
378
+
379
+ interface InvokeOptions {
380
+ /** HTTP method. Defaults to POST. */
381
+ method?: string;
382
+ /**
383
+ * Request body. A string / Blob / FormData / ArrayBuffer / typed array is
384
+ * sent as-is; anything else is JSON-encoded (with Content-Type json).
385
+ */
386
+ body?: unknown;
387
+ /** Extra headers (don't set Authorization here — sign in instead). */
388
+ headers?: Record<string, string>;
389
+ /** Extra path appended after the function name, e.g. '/items/42'. */
390
+ path?: string;
391
+ }
392
+ declare class Functions {
393
+ private base;
394
+ private key;
395
+ private fetchFn;
396
+ private userToken?;
397
+ constructor(base: string, key: string, // project (anon) key — sent as the `apikey` header
398
+ fetchFn: typeof fetch, userToken?: string | undefined);
399
+ /** Return a Functions client that calls AS a specific end user. */
400
+ asUser(accessToken: string): Functions;
401
+ /**
402
+ * Invoke an Edge Function by name.
403
+ *
404
+ * const { data, error } = await ichi.functions.invoke('hello', { body: { name: 'world' } });
405
+ */
406
+ invoke<T = unknown>(name: string, opts?: InvokeOptions): Promise<Result<T>>;
407
+ }
408
+
409
+ interface SignupResult {
410
+ user_id: string;
411
+ email: string;
412
+ needs_verification: boolean;
413
+ }
414
+ interface LoginResult {
415
+ access_token: string;
416
+ refresh_token: string;
417
+ expires_in: number;
418
+ user: {
419
+ id: string;
420
+ email: string;
421
+ };
422
+ }
423
+ interface RefreshResult {
424
+ access_token: string;
425
+ refresh_token: string;
426
+ expires_in: number;
427
+ }
428
+ interface UserProfile {
429
+ id: string;
430
+ email: string;
431
+ verified_at: string | null;
432
+ }
433
+ /** Auth client. */
434
+ declare class Auth {
435
+ private base;
436
+ private key;
437
+ private fetchFn;
438
+ constructor(base: string, key: string, fetchFn: typeof fetch);
439
+ private call;
440
+ signup(input: {
441
+ email: string;
442
+ password: string;
443
+ }): Promise<Result<SignupResult>>;
444
+ login(input: {
445
+ email: string;
446
+ password: string;
447
+ }): Promise<Result<LoginResult>>;
448
+ refresh(refresh_token: string): Promise<Result<RefreshResult>>;
449
+ /** Get the user identified by the given access token (or the SDK's key if none given). */
450
+ getUser(accessToken?: string): Promise<Result<UserProfile>>;
451
+ logout(refresh_token: string, accessToken: string): Promise<Result<unknown>>;
452
+ logoutAll(accessToken: string): Promise<Result<unknown>>;
453
+ requestPasswordReset(email: string): Promise<Result<{
454
+ sent: boolean;
455
+ }>>;
456
+ confirmPasswordReset(token: string, new_password: string): Promise<Result<{
457
+ reset: boolean;
458
+ }>>;
459
+ verifyEmail(token: string): Promise<Result<{
460
+ verified: boolean;
461
+ }>>;
462
+ verifyEmailOtp(email: string, code: string): Promise<Result<{
463
+ verified: boolean;
464
+ }>>;
465
+ resendVerification(email: string): Promise<Result<{
466
+ sent: boolean;
467
+ }>>;
468
+ /**
469
+ * Update the user's metadata JSONB. **Full replacement** — to merge,
470
+ * read `getUser()` first and pass the merged object.
471
+ *
472
+ * await auth.updateUser(accessToken, { metadata: { theme: 'dark', tz: 'UTC' } });
473
+ *
474
+ * Server enforces an 8 KB cap on the encoded metadata.
475
+ */
476
+ updateUser(accessToken: string, patch: {
477
+ metadata: Record<string, unknown>;
478
+ }): Promise<Result<UpdatedUser>>;
479
+ /**
480
+ * Change the user's password. Requires the current password — does
481
+ * NOT use a reset token (that's `confirmPasswordReset`). On success,
482
+ * existing access tokens KEEP working (short TTL); call `refresh()`
483
+ * after to mint a new pair if you want a fresh access token.
484
+ *
485
+ * Returns `{ changed: true, hint: '...' }`.
486
+ */
487
+ changePassword(accessToken: string, currentPassword: string, newPassword: string): Promise<Result<{
488
+ changed: boolean;
489
+ hint: string;
490
+ }>>;
491
+ /**
492
+ * List the user's active refresh-token sessions (one row per
493
+ * device / login). Returns up to 100, ordered by most-recently-used.
494
+ * Each row includes the user-agent + IP captured at login so the
495
+ * user can identify devices.
496
+ */
497
+ listSessions(accessToken: string): Promise<Result<{
498
+ sessions: SessionInfo[];
499
+ }>>;
500
+ /**
501
+ * Revoke a single session by its id. Idempotent — revoking a
502
+ * session that's already revoked returns `{ revoked: true }`.
503
+ * The session must belong to the bearer user (404 otherwise).
504
+ */
505
+ revokeSession(accessToken: string, sessionId: string): Promise<Result<{
506
+ revoked: boolean;
507
+ }>>;
508
+ }
509
+ interface UpdatedUser {
510
+ id: string;
511
+ email: string;
512
+ email_verified: boolean;
513
+ created_at: string;
514
+ metadata: Record<string, unknown>;
515
+ }
516
+ interface SessionInfo {
517
+ id: string;
518
+ user_agent: string | null;
519
+ ip: string | null;
520
+ issued_at: string;
521
+ last_used_at: string;
522
+ expires_at: string;
523
+ }
524
+
525
+ type ChangeEvent = 'INSERT' | 'UPDATE' | 'DELETE';
526
+ type MongoChangeEvent = 'insert' | 'update' | 'delete';
527
+ /** A row/document change frame delivered to a postgres/mongo subscriber. */
528
+ interface ChangeMessage {
529
+ type: 'change';
530
+ event: ChangeEvent | MongoChangeEvent;
531
+ table?: string;
532
+ collection?: string;
533
+ /** The new row/document (absent on DELETE for some engines). */
534
+ record?: Record<string, unknown>;
535
+ /** The previous row, when the engine captures it (UPDATE/DELETE). */
536
+ old?: Record<string, unknown>;
537
+ }
538
+ /** A broadcast message on a channel. */
539
+ interface BroadcastMessage {
540
+ type: 'broadcast';
541
+ channel: string;
542
+ event?: string;
543
+ payload: unknown;
544
+ /** user id of the sender (empty for anonymous). */
545
+ from?: string;
546
+ }
547
+ /** Presence snapshot / diff. */
548
+ interface PresenceMessage {
549
+ type: 'presence_state' | 'presence_diff';
550
+ channel?: string;
551
+ presences?: Record<string, {
552
+ uid: string;
553
+ state?: unknown;
554
+ }>;
555
+ joins?: Record<string, {
556
+ uid: string;
557
+ state?: unknown;
558
+ }>;
559
+ leaves?: Record<string, {
560
+ uid: string;
561
+ state?: unknown;
562
+ }>;
563
+ }
564
+ type RealtimeMessage = ChangeMessage | BroadcastMessage | PresenceMessage;
565
+ interface PostgresSubscribeOptions {
566
+ kind: 'postgres';
567
+ /** `public.orders` or just `orders` (defaults to the public schema). */
568
+ table: string;
569
+ events?: ChangeEvent[];
570
+ /** Optional client-side narrowing (rule grammar). */
571
+ filter?: unknown;
572
+ }
573
+ interface MongoSubscribeOptions {
574
+ kind: 'mongo';
575
+ collection: string;
576
+ events?: MongoChangeEvent[];
577
+ filter?: unknown;
578
+ }
579
+ interface BroadcastSubscribeOptions {
580
+ kind: 'broadcast';
581
+ channel: string;
582
+ /** Also track presence on this channel. */
583
+ presence?: boolean;
584
+ /** Initial presence state to publish on join. */
585
+ state?: Record<string, unknown>;
586
+ }
587
+ type SubscribeOptions = PostgresSubscribeOptions | MongoSubscribeOptions | BroadcastSubscribeOptions;
588
+ /** Handle to one live subscription. */
589
+ interface Subscription {
590
+ /** Stop this subscription. */
591
+ unsubscribe(): void;
592
+ /** Publish to the channel (broadcast subscriptions only). */
593
+ send(event: string, payload: unknown): void;
594
+ /** Update this connection's presence state (broadcast + presence only). */
595
+ track(state: Record<string, unknown>): void;
596
+ }
597
+ interface RealtimeOptions {
598
+ /** Base project URL, e.g. https://abc.ichibase.net. */
599
+ url: string;
600
+ /** Returns the current bearer (user access token, or the anon key). */
601
+ getToken: () => string | undefined;
602
+ /** Override the WebSocket constructor (testing / non-global envs). */
603
+ WebSocketImpl?: typeof WebSocket;
604
+ }
605
+ declare class RealtimeClient {
606
+ private url;
607
+ private getToken;
608
+ private WS;
609
+ private ws;
610
+ private subs;
611
+ private refSeq;
612
+ private connecting;
613
+ private closedByUser;
614
+ private reconnectAttempts;
615
+ private heartbeat;
616
+ private outbox;
617
+ constructor(opts: RealtimeOptions);
618
+ /** Subscribe to postgres/mongo changes or a broadcast channel. */
619
+ subscribe(opts: SubscribeOptions, handler: (msg: RealtimeMessage) => void): Subscription;
620
+ /** Close the socket and drop all subscriptions. */
621
+ disconnect(): void;
622
+ private isOpen;
623
+ private ensureConnected;
624
+ private scheduleReconnect;
625
+ private startHeartbeat;
626
+ private stopHeartbeat;
627
+ private sendSubscribe;
628
+ private send;
629
+ private onFrame;
630
+ }
631
+
632
+ interface SessionStorage {
633
+ getItem(key: string): string | null | Promise<string | null>;
634
+ setItem(key: string, value: string): void | Promise<void>;
635
+ removeItem(key: string): void | Promise<void>;
636
+ }
637
+ /** In-memory adapter (default) — session is lost on reload. */
638
+ declare class MemoryStorage implements SessionStorage {
639
+ private m;
640
+ getItem(key: string): string | null;
641
+ setItem(key: string, value: string): void;
642
+ removeItem(key: string): void;
643
+ }
644
+
645
+ interface Session {
646
+ access_token: string;
647
+ refresh_token: string;
648
+ /** Epoch seconds when the access token expires (best-effort, from expires_in). */
649
+ expires_at?: number;
650
+ user?: {
651
+ id: string;
652
+ email: string;
653
+ };
654
+ }
655
+ type AuthEvent = 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED';
656
+ interface ClientOptions {
657
+ /** Custom fetch (SSR, testing). Defaults to globalThis.fetch. */
658
+ fetch?: typeof fetch;
659
+ /** Where to persist the session. Browsers pass `localStorage`. */
660
+ storage?: SessionStorage;
661
+ /** Key under which the session is persisted. */
662
+ storageKey?: string;
663
+ /** WebSocket constructor for realtime (non-global envs). */
664
+ WebSocketImpl?: typeof WebSocket;
665
+ }
666
+ /** Auth surface with session management layered over the stateless Auth client. */
667
+ declare class SessionAuth {
668
+ private inner;
669
+ private client;
670
+ constructor(inner: Auth, client: IchibaseClient);
671
+ signup(input: {
672
+ email: string;
673
+ password: string;
674
+ }): Promise<Result<SignupResult>>;
675
+ login(input: {
676
+ email: string;
677
+ password: string;
678
+ }): Promise<Result<LoginResult>>;
679
+ refresh(): Promise<Result<RefreshResult>>;
680
+ /** Current signed-in user (from the live access token), or null. */
681
+ getUser(): Promise<UserProfile | null>;
682
+ logout(): Promise<void>;
683
+ requestPasswordReset(email: string): Promise<Result<{
684
+ sent: boolean;
685
+ }>>;
686
+ confirmPasswordReset(token: string, newPassword: string): Promise<Result<{
687
+ reset: boolean;
688
+ }>>;
689
+ verifyEmail(token: string): Promise<Result<{
690
+ verified: boolean;
691
+ }>>;
692
+ verifyEmailOtp(email: string, code: string): Promise<Result<{
693
+ verified: boolean;
694
+ }>>;
695
+ resendVerification(email: string): Promise<Result<{
696
+ sent: boolean;
697
+ }>>;
698
+ /** Hydrate the session from the storage adapter (call once at startup for async adapters). */
699
+ loadSession(): Promise<Session | null>;
700
+ /** Set the session directly (e.g. from your own SSR cookie). */
701
+ setSession(session: Session | null): Promise<void>;
702
+ }
703
+ declare class IchibaseClient {
704
+ readonly url: string;
705
+ /** Auth surface with session management. */
706
+ readonly auth: SessionAuth;
707
+ private anonKey;
708
+ private fetchFn;
709
+ private sessionStore;
710
+ private storageKey;
711
+ private session;
712
+ private listeners;
713
+ private _realtime;
714
+ private wsImpl?;
715
+ constructor(url: string, anonKey: string, opts?: ClientOptions);
716
+ /** The bearer to send on data-plane calls: the user token if signed in, else the anon key. */
717
+ private bearer;
718
+ private cfg;
719
+ /** Start a PostgREST query against a table or view. */
720
+ from<T = Record<string, unknown>>(table: string): QueryBuilder<T, T[]>;
721
+ /** Call a Postgres stored procedure (RPC). */
722
+ rpc<T = unknown>(fn: string, args?: Record<string, unknown>, opts?: {
723
+ schema?: string;
724
+ count?: 'exact' | 'planned' | 'estimated';
725
+ head?: boolean;
726
+ }): Promise<Result<T>>;
727
+ /** Mongo data client (apikey = anon; user token attached when signed in). */
728
+ get mongo(): Mongo;
729
+ /** Invoke your deployed Edge Functions: `ichi.functions.invoke('name', { body })`. */
730
+ get functions(): Functions;
731
+ get realtime(): RealtimeClient;
732
+ getSession(): Session | null;
733
+ /** Subscribe to auth state changes. Returns an unsubscribe fn. */
734
+ onAuthStateChange(cb: (event: AuthEvent, session: Session | null) => void): () => void;
735
+ /** @internal */
736
+ _setSession(session: Session | null, event: AuthEvent): Promise<void>;
737
+ /** @internal */
738
+ _loadSession(): Promise<Session | null>;
739
+ }
740
+ declare function createClient(url: string, anonKey: string, opts?: ClientOptions): IchibaseClient;
741
+
742
+ export { Auth, type AuthEvent, type BroadcastMessage, type BroadcastSubscribeOptions, type ChangeEvent, type ChangeMessage, type ClientOptions, type CountedResult, type FilterOp, Functions, IchibaseClient, type IchibaseConfig, type IchibaseError, type InvokeOptions, type LoginResult, MemoryStorage, Mongo, type MongoChangeEvent, MongoCollection, type MongoSubscribeOptions, type PostgresSubscribeOptions, Postgrest, type PresenceMessage, QueryBuilder, RealtimeClient, type RealtimeMessage, type RefreshResult, type Result, type Session, type SessionInfo, type SessionStorage, type SignupResult, type SubscribeOptions, type Subscription, type UpdatedUser, type UserProfile, createClient };