@rebasepro/client 0.24.0 → 0.24.1-canary.gcb918fc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -10
- package/dist/admin-contract.types.d.ts +21 -0
- package/dist/admin.d.ts +4 -0
- package/dist/auth.d.ts +26 -4
- package/dist/backups.d.ts +13 -2
- package/dist/collection.d.ts +37 -16
- package/dist/cron.d.ts +12 -5
- package/dist/exports-contract.types.d.ts +39 -0
- package/dist/functions.d.ts +2 -12
- package/dist/index.d.ts +36 -10
- package/dist/index.es.js +1033 -4301
- package/dist/index.es.js.map +1 -1
- package/dist/offline-QNQgsPOq.js +3307 -0
- package/dist/offline-QNQgsPOq.js.map +1 -0
- package/dist/offline-codec.d.ts +12 -0
- package/dist/offline-errors.d.ts +9 -0
- package/dist/offline-lazy.d.ts +58 -0
- package/dist/offline-store-Cm5sAX81.js +1837 -0
- package/dist/offline-store-Cm5sAX81.js.map +1 -0
- package/dist/offline-store.d.ts +37 -0
- package/dist/offline.d.ts +228 -16
- package/dist/query-contract.types.d.ts +53 -1
- package/dist/realtime-channel.d.ts +25 -0
- package/dist/reviver.d.ts +18 -0
- package/dist/storage-resumable-VO-cMpse.js +2 -0
- package/dist/storage-resumable.d.ts +1 -0
- package/dist/transport.d.ts +85 -5
- package/dist/websocket.d.ts +94 -11
- package/package.json +4 -4
- package/dist/query_builder.d.ts +0 -1
package/dist/offline.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RebaseApiError } from "./transport.js";
|
|
1
2
|
import { SDKCollectionClient } from "@rebasepro/types";
|
|
2
3
|
import { CollectionClient } from "./collection.js";
|
|
3
4
|
import { OfflineStore, PendingMutation } from "./offline-store.js";
|
|
@@ -81,7 +82,76 @@ export interface OfflineConfig {
|
|
|
81
82
|
* Network failures are not errors: those mutations stay queued.
|
|
82
83
|
*/
|
|
83
84
|
onSyncError?: (error: Error, mutation: PendingMutation) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Detect conflicts when a queued edit replays: an `update` made offline
|
|
87
|
+
* is sent with `If-Match`, the version of the row it was made against,
|
|
88
|
+
* and if someone changed the row on the server since, this decides what
|
|
89
|
+
* happens to the edit:
|
|
90
|
+
*
|
|
91
|
+
* - `"overwrite"`: send it again without the precondition. Last writer
|
|
92
|
+
* wins, as without this option.
|
|
93
|
+
* - `"discard"`: drop it. The row goes back to what it was before the
|
|
94
|
+
* edit, as for a rejection, but it is not reported to `onSyncError`
|
|
95
|
+
* (writes queued on top of it are, as `DEPENDENCY_REJECTED`), and the
|
|
96
|
+
* row is read again from the server.
|
|
97
|
+
*
|
|
98
|
+
* The edit's version is the one the row's last read reported: a
|
|
99
|
+
* `findById`, a `listenById` frame, or the answer to an earlier `update`.
|
|
100
|
+
* A row last seen in a `find()` or `listen()` list has no version, and an
|
|
101
|
+
* edit made on top of another queued write to the same row has none
|
|
102
|
+
* either; those replay without a precondition. A caller's own `ifMatch` is
|
|
103
|
+
* kept on a queued edit too.
|
|
104
|
+
*
|
|
105
|
+
* The queue waits while this runs, so a prompt can ask the user. Without
|
|
106
|
+
* it — the default — replay sends no precondition and the last writer
|
|
107
|
+
* wins, per field.
|
|
108
|
+
*/
|
|
109
|
+
onConflict?: (error: RebaseApiError, mutation: PendingMutation) => ConflictResolution | Promise<ConflictResolution>;
|
|
110
|
+
/**
|
|
111
|
+
* Remember, on this device, the last user who signed in, and open their
|
|
112
|
+
* local database when a reload cannot reach the server to restore the
|
|
113
|
+
* session. Off by default.
|
|
114
|
+
*
|
|
115
|
+
* This matters in cookie mode, where nothing about the session is stored
|
|
116
|
+
* on the device: without it, an offline reload has no user, so it shows
|
|
117
|
+
* the signed-out local database and queues the user's writes as the
|
|
118
|
+
* signed-out user's. With it, the reload shows the last user's rows, and
|
|
119
|
+
* writes made meanwhile go into their queue. That queue is not replayed
|
|
120
|
+
* until the session is confirmed — by a refresh once the connection is
|
|
121
|
+
* back, or a sign-in. If the server then refuses the session, or someone
|
|
122
|
+
* else signs in, the device switches users and the queue waits, kept, for
|
|
123
|
+
* its author.
|
|
124
|
+
*
|
|
125
|
+
* The trade-off is the reason it is opt-in: on a shared device, the
|
|
126
|
+
* previous user's cached rows can be read offline without a session.
|
|
127
|
+
* Turn it on for installs that belong to one person. The id is forgotten
|
|
128
|
+
* at sign-out, and when the server refuses the session; it is never used
|
|
129
|
+
* when the server answered that there is no session.
|
|
130
|
+
*
|
|
131
|
+
* {@link OfflineStatus.unverifiedUser} is `true` while the device shows a
|
|
132
|
+
* user whose session it has not confirmed.
|
|
133
|
+
*/
|
|
134
|
+
keepLastUser?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* How many queued creates replay as one request. Defaults to `1`: every
|
|
137
|
+
* queued write is its own request, in order. Raise it (up to 1 000) and a
|
|
138
|
+
* run of consecutive offline creates in one collection drains as one
|
|
139
|
+
* `createMany` — 500 rows typed offline go back in a handful of requests
|
|
140
|
+
* instead of 500 round trips.
|
|
141
|
+
*
|
|
142
|
+
* Only creates whose ids the SDK minted as strings are folded, and never
|
|
143
|
+
* one that references another row of the same run: those are the writes a
|
|
144
|
+
* lost answer cannot duplicate (the server refuses the id the second time,
|
|
145
|
+
* and the create is recognised as its own) and whose values need nothing
|
|
146
|
+
* from the server first. Anything else — and any run the server refuses or
|
|
147
|
+
* defers for any reason but a lost connection — replays one write at a
|
|
148
|
+
* time exactly as with the default, so each write still stands or falls
|
|
149
|
+
* on its own.
|
|
150
|
+
*/
|
|
151
|
+
replayBatchSize?: number;
|
|
84
152
|
}
|
|
153
|
+
/** What {@link OfflineConfig.onConflict} does with an edit the row has moved past. */
|
|
154
|
+
export type ConflictResolution = "overwrite" | "discard";
|
|
85
155
|
/** A snapshot of the engine's state, for a status indicator. */
|
|
86
156
|
export interface OfflineStatus {
|
|
87
157
|
/** False once a request has failed to reach the server, until one does. */
|
|
@@ -94,8 +164,23 @@ export interface OfflineStatus {
|
|
|
94
164
|
lastSyncedAt?: number;
|
|
95
165
|
/** The last replay rejection, if any. */
|
|
96
166
|
lastError?: string;
|
|
167
|
+
/**
|
|
168
|
+
* Whether the browser agreed to keep the offline database through storage
|
|
169
|
+
* pressure (`navigator.storage.persist()`), asked when the first write is
|
|
170
|
+
* queued. `false` means it may be evicted — on Safari, after seven days
|
|
171
|
+
* without a visit — and unsent writes with it; a UI can warn. Unset until
|
|
172
|
+
* asked, and where there is nothing to ask.
|
|
173
|
+
*/
|
|
174
|
+
persisted?: boolean;
|
|
175
|
+
/**
|
|
176
|
+
* `true` while the local data shown is the last signed-in user's, opened
|
|
177
|
+
* without a confirmed session ({@link OfflineConfig.keepLastUser}): their
|
|
178
|
+
* writes queue, and none replays until the session is confirmed. Unset
|
|
179
|
+
* otherwise.
|
|
180
|
+
*/
|
|
181
|
+
unverifiedUser?: boolean;
|
|
97
182
|
}
|
|
98
|
-
export type { LiveResult, ObserveOptions, RowSnapshotMeta } from "./collection.js";
|
|
183
|
+
export type { LiveResult, ObserveByIdOptions, ObserveOptions, RowSnapshotMeta } from "./collection.js";
|
|
99
184
|
/** Which queue {@link OfflineApi.pending} and {@link OfflineApi.clear} act on. */
|
|
100
185
|
export interface OfflineQueueOptions {
|
|
101
186
|
/**
|
|
@@ -145,15 +230,7 @@ export interface OfflineApi {
|
|
|
145
230
|
/** Subscribe to queue-size changes (for a "pending changes" badge). */
|
|
146
231
|
onQueueChange(listener: (count: number) => void): () => void;
|
|
147
232
|
}
|
|
148
|
-
|
|
149
|
-
* True when a read failed because there was neither network nor local data.
|
|
150
|
-
*
|
|
151
|
-
* The code is `OFFLINE`. It was `"offline"`, the one lowercase code in a
|
|
152
|
-
* surface whose every other code is `SCREAMING_SNAKE_CASE` — including the
|
|
153
|
-
* `NETWORK_ERROR` it sits next to — so `e.code === "OFFLINE"`, which is what
|
|
154
|
-
* anyone writes, never matched.
|
|
155
|
-
*/
|
|
156
|
-
export declare function isOfflineError(error: unknown): boolean;
|
|
233
|
+
export { isOfflineError } from "./offline-errors.js";
|
|
157
234
|
type AnyRow = Record<string, unknown>;
|
|
158
235
|
type InnerFactory = (slug: string) => SDKCollectionClient<AnyRow>;
|
|
159
236
|
export declare class OfflineManager {
|
|
@@ -162,10 +239,20 @@ export declare class OfflineManager {
|
|
|
162
239
|
private readonly maxCachedRows;
|
|
163
240
|
private readonly maxRetries;
|
|
164
241
|
private readonly onSyncError?;
|
|
242
|
+
private readonly onConflict?;
|
|
165
243
|
private readonly createInner;
|
|
166
244
|
private readonly inners;
|
|
167
245
|
private readonly connectivity;
|
|
246
|
+
private readonly keepLastUser;
|
|
247
|
+
private readonly replayBatchSize;
|
|
168
248
|
private scope;
|
|
249
|
+
/**
|
|
250
|
+
* The scope is the last signed-in user's, taken without a confirmed
|
|
251
|
+
* session ({@link resumeLastUser}). Nothing replays while it is set: the
|
|
252
|
+
* queue is sent under the credentials of whoever is signed in, and nobody
|
|
253
|
+
* is yet.
|
|
254
|
+
*/
|
|
255
|
+
private unverified;
|
|
169
256
|
/**
|
|
170
257
|
* Set while the manager does not know whose data it holds: from
|
|
171
258
|
* {@link holdScope} until the next {@link setScope}. `scope` is not
|
|
@@ -189,9 +276,21 @@ export declare class OfflineManager {
|
|
|
189
276
|
/** The local database: normalized rows and query snapshots per collection. */
|
|
190
277
|
private collections;
|
|
191
278
|
/** In-memory mirror of the current scope's queue, in replay order. */
|
|
192
|
-
private
|
|
279
|
+
private queueEntries;
|
|
280
|
+
private get queue();
|
|
281
|
+
private set queue(value);
|
|
193
282
|
/**
|
|
194
|
-
*
|
|
283
|
+
* Per collection, the row ids the queue writes to and the ones it creates
|
|
284
|
+
* — {@link hasPending} and {@link isLocallyCreated}, which answering a
|
|
285
|
+
* page asks once or twice per row. Each used to scan the whole queue: a
|
|
286
|
+
* 500-row page against 200 queued writes read 80,000 queue entries, on
|
|
287
|
+
* every emit. Built on first use after the queue changes; whatever changes
|
|
288
|
+
* it in place calls {@link queueChangedInPlace}.
|
|
289
|
+
*/
|
|
290
|
+
private pendingIndex?;
|
|
291
|
+
/**
|
|
292
|
+
* The mutations currently on the wire: one, or the members of a folded
|
|
293
|
+
* run (`replayBatchSize`).
|
|
195
294
|
*
|
|
196
295
|
* `flush` awaits `replay(op)` with `op` still at the head of `queue`, so for
|
|
197
296
|
* the whole duration of that request the in-flight op is also the queue's
|
|
@@ -205,10 +304,10 @@ export declare class OfflineManager {
|
|
|
205
304
|
* create. It is seeing it right now, so the row would be created and the
|
|
206
305
|
* delete never queued — an orphan row nothing will ever remove.
|
|
207
306
|
*
|
|
208
|
-
* Guarding on
|
|
209
|
-
*
|
|
307
|
+
* Guarding on ids rather than on a boolean is what keeps this correct
|
|
308
|
+
* when a folded run sends several at once, its last member the tail.
|
|
210
309
|
*/
|
|
211
|
-
private
|
|
310
|
+
private readonly inFlight;
|
|
212
311
|
private queueLoad?;
|
|
213
312
|
/** Serializes enqueues so concurrent writes keep the order the app made them. */
|
|
214
313
|
private enqueueChain;
|
|
@@ -219,6 +318,8 @@ export declare class OfflineManager {
|
|
|
219
318
|
private refreshPending;
|
|
220
319
|
private revCounter;
|
|
221
320
|
private disposed;
|
|
321
|
+
/** Whether {@link requestPersistence} has asked. */
|
|
322
|
+
private persistenceRequested;
|
|
222
323
|
private currentStatus;
|
|
223
324
|
private readonly channel?;
|
|
224
325
|
private readonly tabId;
|
|
@@ -230,7 +331,27 @@ export declare class OfflineManager {
|
|
|
230
331
|
* replay under the credentials that made them — so neither may ever leak
|
|
231
332
|
* across a sign-out/sign-in on a shared browser.
|
|
232
333
|
*/
|
|
233
|
-
setScope(uid: string | undefined
|
|
334
|
+
setScope(uid: string | undefined, options?: {
|
|
335
|
+
unverified?: boolean;
|
|
336
|
+
}): void;
|
|
337
|
+
/** Whether the manager was created with {@link OfflineConfig.keepLastUser}. */
|
|
338
|
+
get keepsLastUser(): boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Take the scope of the last user who signed in on this device, without a
|
|
341
|
+
* session — for a load whose session restore could not reach the server.
|
|
342
|
+
* See {@link OfflineConfig.keepLastUser}. With no user remembered (or the
|
|
343
|
+
* option off) this is the signed-out scope, as it would have been anyway.
|
|
344
|
+
*
|
|
345
|
+
* Only while the scope is still held: an auth event that settled it while
|
|
346
|
+
* the id was being read is newer news.
|
|
347
|
+
*/
|
|
348
|
+
resumeLastUser(): Promise<void>;
|
|
349
|
+
/**
|
|
350
|
+
* Keep the signed-in user's id for {@link resumeLastUser}, or forget it at
|
|
351
|
+
* sign-out. Forgetting does not depend on the option, so turning it off
|
|
352
|
+
* does not leave the last id behind for good.
|
|
353
|
+
*/
|
|
354
|
+
private rememberUser;
|
|
234
355
|
/**
|
|
235
356
|
* Forget whose data this is until the next {@link setScope}.
|
|
236
357
|
*
|
|
@@ -273,6 +394,21 @@ export declare class OfflineManager {
|
|
|
273
394
|
/** Release listeners, timers and the cross-tab channel (client.close()). */
|
|
274
395
|
dispose(): void;
|
|
275
396
|
wrap<M extends AnyRow>(slug: string, inner: CollectionClient<M>): CollectionClient<M>;
|
|
397
|
+
/**
|
|
398
|
+
* `client.data.<slug>.trash` on an offline client: every call goes to the
|
|
399
|
+
* server, and none is cached or queued.
|
|
400
|
+
*
|
|
401
|
+
* The local database holds live rows only — a trashed row read into it
|
|
402
|
+
* would show up in every live query that matches it — so it cannot answer
|
|
403
|
+
* a trash read, and a restore or a purge queued offline would act on a row
|
|
404
|
+
* this device has never seen. Each of them rejects with `OFFLINE` instead.
|
|
405
|
+
*
|
|
406
|
+
* What the server answers does reach the local database: a restored row
|
|
407
|
+
* is live again and joins it, and a purged row leaves it. A row with a
|
|
408
|
+
* write still queued is synced first, so a purge never overtakes the
|
|
409
|
+
* queued delete it follows; if the queue cannot drain, the call is refused.
|
|
410
|
+
*/
|
|
411
|
+
private wrapTrash;
|
|
276
412
|
private observe;
|
|
277
413
|
private observeById;
|
|
278
414
|
private observersFor;
|
|
@@ -325,6 +461,13 @@ export declare class OfflineManager {
|
|
|
325
461
|
* the difference. Reading one row first is enough to fix it.
|
|
326
462
|
*/
|
|
327
463
|
private mintOfflineId;
|
|
464
|
+
/**
|
|
465
|
+
* Keep the version a server row came with, or forget the one held when it
|
|
466
|
+
* came with none: a row from a list carries no version, and may be newer
|
|
467
|
+
* than the one held. Only with `onConflict`, the one reader of it.
|
|
468
|
+
*/
|
|
469
|
+
private noteETag;
|
|
470
|
+
private etagFor;
|
|
328
471
|
private rawLocalRow;
|
|
329
472
|
private localRow;
|
|
330
473
|
private setLocalRow;
|
|
@@ -372,9 +515,35 @@ export declare class OfflineManager {
|
|
|
372
515
|
*/
|
|
373
516
|
private scheduleRefresh;
|
|
374
517
|
private evictRows;
|
|
518
|
+
/**
|
|
519
|
+
* Tombstones are tiny but unbounded — every row the app ever asked for and
|
|
520
|
+
* did not find leaves one. Capped against the row budget, oldest first,
|
|
521
|
+
* whenever one is added: the cap used to sit behind the row cache's own
|
|
522
|
+
* early return, so it only applied once the rows were over theirs, and
|
|
523
|
+
* looking up missing ids grew the set without limit.
|
|
524
|
+
*/
|
|
525
|
+
private capTombstones;
|
|
375
526
|
private evictSnapshots;
|
|
376
527
|
private ensureQueueLoaded;
|
|
377
528
|
private enqueue;
|
|
529
|
+
/**
|
|
530
|
+
* Ask the browser to keep the offline database, once, when there is first
|
|
531
|
+
* something in it worth keeping. Without it IndexedDB is best-effort
|
|
532
|
+
* storage, evicted under pressure (and by Safari after seven days without
|
|
533
|
+
* a visit) with the unsent writes in it.
|
|
534
|
+
*/
|
|
535
|
+
private requestPersistence;
|
|
536
|
+
/** The queue's row ids for one collection — see {@link pendingIndex}. */
|
|
537
|
+
private pendingIds;
|
|
538
|
+
/**
|
|
539
|
+
* Did the queued writes to this row bring it into the query's filter? The
|
|
540
|
+
* row as it was before the first of them is that write's rollback; one
|
|
541
|
+
* that did not match then is in no server page of the query, so only
|
|
542
|
+
* this side can put it on screen.
|
|
543
|
+
*/
|
|
544
|
+
private movedIntoFilter;
|
|
545
|
+
/** The queue's entries were changed without replacing the array. */
|
|
546
|
+
private queueChangedInPlace;
|
|
378
547
|
private hasPending;
|
|
379
548
|
/** Is this row one the server has never been told about? */
|
|
380
549
|
private isLocallyCreated;
|
|
@@ -385,6 +554,38 @@ export declare class OfflineManager {
|
|
|
385
554
|
remaining: number;
|
|
386
555
|
}>;
|
|
387
556
|
private flush;
|
|
557
|
+
/**
|
|
558
|
+
* The creates at the head of the queue that may replay as one request —
|
|
559
|
+
* see {@link OfflineConfig.replayBatchSize}. Fewer than two means the head
|
|
560
|
+
* replays on its own.
|
|
561
|
+
*
|
|
562
|
+
* Consecutive, so the queue's order holds; one collection, since a bulk
|
|
563
|
+
* write is one collection's. Each is a create never sent (so its request
|
|
564
|
+
* is still its own to choose) with an id the SDK minted as a string — a
|
|
565
|
+
* lost answer then cannot duplicate it, because the server refuses that
|
|
566
|
+
* id the second time and the single replay recognises the row as its
|
|
567
|
+
* own. A create whose values name another member's minted id waits: the
|
|
568
|
+
* server may give that row a different id, which only a replay one at a
|
|
569
|
+
* time can carry over.
|
|
570
|
+
*/
|
|
571
|
+
private foldableRun;
|
|
572
|
+
/**
|
|
573
|
+
* Replay a {@link foldableRun} as one `createMany`.
|
|
574
|
+
*
|
|
575
|
+
* - `"done"`: every member is acknowledged, adopted and dropped.
|
|
576
|
+
* - `"offline"`: the connection failed; the members stay queued, as one
|
|
577
|
+
* write would.
|
|
578
|
+
* - `"unbatch"`: anything else — a refusal, a busy server. Nothing was
|
|
579
|
+
* written (a bulk write is one transaction), and the caller replays the
|
|
580
|
+
* members one at a time, where each refusal, retry budget and recovery
|
|
581
|
+
* applies to its own write.
|
|
582
|
+
*
|
|
583
|
+
* The key names the run by its first and last member and its length,
|
|
584
|
+
* which fixes its composition: a run re-sent after a lost answer is the
|
|
585
|
+
* same request under the same key, and is answered from the server's
|
|
586
|
+
* record of it.
|
|
587
|
+
*/
|
|
588
|
+
private replayRun;
|
|
388
589
|
private replay;
|
|
389
590
|
/**
|
|
390
591
|
* Send a queued write under its key.
|
|
@@ -428,6 +629,13 @@ export declare class OfflineManager {
|
|
|
428
629
|
* silently lose writes the server would have accepted.
|
|
429
630
|
*/
|
|
430
631
|
private rejectMutation;
|
|
632
|
+
/**
|
|
633
|
+
* An edit replayed with `If-Match` and refused with a 412: ask
|
|
634
|
+
* {@link OfflineConfig.onConflict}, and do what it says. A callback that
|
|
635
|
+
* throws, or none, is "overwrite" — what replay did before it knew of
|
|
636
|
+
* conflicts.
|
|
637
|
+
*/
|
|
638
|
+
private resolveConflict;
|
|
431
639
|
/**
|
|
432
640
|
* Put the separate edits a coalesced `update` was made of back in the
|
|
433
641
|
* queue, in its place and in their order, each with the row as it stood
|
|
@@ -455,6 +663,10 @@ export declare class OfflineManager {
|
|
|
455
663
|
private rowKey;
|
|
456
664
|
private absentKey;
|
|
457
665
|
private queueKey;
|
|
666
|
+
/** Queue a write in the store, its rows encoded as the cache's are. */
|
|
667
|
+
private persistMutation;
|
|
668
|
+
/** The store's queue under `prefix`, its rows revived. */
|
|
669
|
+
private readQueue;
|
|
458
670
|
private readCache;
|
|
459
671
|
private writeCache;
|
|
460
672
|
private deleteCache;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* generated SDK — where `M` is a concrete row type — the documented call was a
|
|
24
24
|
* compile error. Nothing in this repo noticed; a downstream application did.
|
|
25
25
|
*/
|
|
26
|
-
import type { FindParams, FindResult, SDKQueryBuilderInterface, WhereFilterOp } from "@rebasepro/types";
|
|
26
|
+
import type { AggregateParams, FindParams, FindResult, LogicalCondition, SDKCollectionClient, SDKQueryBuilderInterface, UpdateValues, WhereFilterOp } from "@rebasepro/types";
|
|
27
27
|
/**
|
|
28
28
|
* A row shaped the way a **generated** SDK shapes one: a type alias with a
|
|
29
29
|
* finite key set.
|
|
@@ -46,6 +46,10 @@ type ContractRow = {
|
|
|
46
46
|
tags: string[];
|
|
47
47
|
age: number;
|
|
48
48
|
deleted_at: string | null;
|
|
49
|
+
/** A `vector` property. */
|
|
50
|
+
embedding: number[];
|
|
51
|
+
/** An `enum` property, which codegen emits as a union of literals. */
|
|
52
|
+
status: "draft" | "published";
|
|
49
53
|
};
|
|
50
54
|
/** A to-many relation, which codegen emits as `Array<TargetRow>`. */
|
|
51
55
|
type TagRow = {
|
|
@@ -118,6 +122,21 @@ export declare const fluentRange: (qb: SDKQueryBuilderInterface<ContractRow>) =>
|
|
|
118
122
|
export declare const paramsArrayContains: FindParams<ContractRow>;
|
|
119
123
|
/** …and the array-of-tuples form the builder produces from two `.where()` calls. */
|
|
120
124
|
export declare const paramsRange: FindParams<ContractRow>;
|
|
125
|
+
/**
|
|
126
|
+
* `cond` cannot know the row on its own; the place the group is used can.
|
|
127
|
+
* `logical` and `.where(group)` check every condition in the tree against `M`,
|
|
128
|
+
* as the object-form `where` does, so a typo'd column is a compile error, not
|
|
129
|
+
* the server's 400.
|
|
130
|
+
*/
|
|
131
|
+
export declare const logicalOk: FindParams<ContractRow>;
|
|
132
|
+
export declare const logicalTypo: FindParams<ContractRow>;
|
|
133
|
+
export declare const logicalWrongValue: FindParams<ContractRow>;
|
|
134
|
+
export declare const logicalEnumTypo: FindParams<ContractRow>;
|
|
135
|
+
export declare const logicalArrayContainsList: FindParams<ContractRow>;
|
|
136
|
+
export declare const fluentLogicalTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
137
|
+
export declare const fluentLogicalOk: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
138
|
+
/** A group built at runtime keeps working where the row is untyped. */
|
|
139
|
+
export declare const untypedLogical: (qb: SDKQueryBuilderInterface, group: LogicalCondition) => SDKQueryBuilderInterface<Record<string, unknown>, import("@rebasepro/types").IncludeSpec>;
|
|
121
140
|
/**
|
|
122
141
|
* Sorting by relevance and then being unable to read it was the other half of
|
|
123
142
|
* the same bug — the e2e cast around it, which should have been the tell.
|
|
@@ -171,4 +190,37 @@ export declare const fluentAggregateSortTypo: (qb: SDKQueryBuilderInterface<Post
|
|
|
171
190
|
export declare const paramsRelationPath: FindParams<PostRow>;
|
|
172
191
|
export declare const paramsJsonPath: FindParams<ContractRow>;
|
|
173
192
|
export declare const paramsRelationAggregateSort: FindParams<PostRow>;
|
|
193
|
+
/**
|
|
194
|
+
* `FilterValues` typed the key and left the value `unknown`, so the shape the
|
|
195
|
+
* fluent form was fixed to refuse compiled in `find({ where })` — the form
|
|
196
|
+
* `listen`, `observe`, `count` and `iterate` all take.
|
|
197
|
+
*/
|
|
198
|
+
export declare const paramsArrayContainsWholeArray: FindParams<ContractRow>;
|
|
199
|
+
export declare const paramsWrongValueType: FindParams<ContractRow>;
|
|
200
|
+
/** A path that is not a column has nothing to check against, and stays open. */
|
|
201
|
+
export declare const paramsRelationPathTyped: FindParams<ContractRow>;
|
|
202
|
+
/** An untyped row keeps the object form exactly as permissive as it was. */
|
|
203
|
+
export declare const paramsUntyped: FindParams;
|
|
204
|
+
/** `fields` is a projection of columns; a typo was `UNKNOWN_FIELD` at runtime. */
|
|
205
|
+
export declare const fieldsColumn: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
206
|
+
export declare const fieldsTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
207
|
+
/** `vectorSearch` names a column. */
|
|
208
|
+
export declare const vectorTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
209
|
+
/** `sum` and `avg` add numbers up; `min`/`max` order anything. */
|
|
210
|
+
export declare const aggregateSum: AggregateParams<ContractRow>;
|
|
211
|
+
export declare const aggregateSumText: AggregateParams<ContractRow>;
|
|
212
|
+
/** A field operation fits its column: `$inc` a number, `$push` an array. */
|
|
213
|
+
export declare const updateOps: UpdateValues<Partial<ContractRow>>;
|
|
214
|
+
export declare const updateIncText: UpdateValues<Partial<ContractRow>>;
|
|
215
|
+
/** An untyped row keeps every operation on every key. */
|
|
216
|
+
export declare const updateUntyped: UpdateValues<Partial<Record<string, unknown>>>;
|
|
217
|
+
/**
|
|
218
|
+
* A typed client is still a client: a helper written against the wide
|
|
219
|
+
* `SDKCollectionClient` (an export, a paginator, a seeder) takes
|
|
220
|
+
* `rebase.data.posts`. The column checks above must narrow what a typed
|
|
221
|
+
* client's methods accept without making the typed client a different kind
|
|
222
|
+
* of thing — `NumericKeyOf` as a conditional over `keyof M` made tsc measure
|
|
223
|
+
* `AggregateParams` invariant, and this line stopped compiling.
|
|
224
|
+
*/
|
|
225
|
+
export declare const typedClientWidens: (posts: SDKCollectionClient<ContractRow>) => SDKCollectionClient;
|
|
174
226
|
export {};
|
|
@@ -103,6 +103,14 @@ export interface ChannelTransport {
|
|
|
103
103
|
* transport need not implement it.
|
|
104
104
|
*/
|
|
105
105
|
onConnectionLost?(handler: (error: RebaseApiError) => void): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the server agreed, for the current socket, to `name` (see the
|
|
108
|
+
* socket's `hello`). A transport without it is one whose server agreed to
|
|
109
|
+
* nothing.
|
|
110
|
+
*/
|
|
111
|
+
hasServerOptIn?(name: string): boolean;
|
|
112
|
+
/** The current socket's id on the server: the `senderId` of what it broadcasts. */
|
|
113
|
+
readonly socketClientId?: string;
|
|
106
114
|
}
|
|
107
115
|
export declare class RebaseRealtimeChannel {
|
|
108
116
|
readonly name: string;
|
|
@@ -303,6 +311,23 @@ export declare class RebaseRealtimeChannel {
|
|
|
303
311
|
private stopHeartbeat;
|
|
304
312
|
/** Fold an incoming frame into the roster and fan it out. */
|
|
305
313
|
private handle;
|
|
314
|
+
/**
|
|
315
|
+
* Whether a jump in the sequence means a missed message: only when the
|
|
316
|
+
* server numbers this client's own broadcasts for it (`channel_seq`), so
|
|
317
|
+
* its own messages leave no hole. Against a server that does not, a hole
|
|
318
|
+
* is as likely to be the client's own message, and the old behaviour —
|
|
319
|
+
* deliver, advance — stands.
|
|
320
|
+
*/
|
|
321
|
+
private watchesForGaps;
|
|
322
|
+
/**
|
|
323
|
+
* Hold `event` back and fetch what lies between it and {@link lastSeq},
|
|
324
|
+
* when it is past a hole. A frame can go missing between instances — a
|
|
325
|
+
* dropped bus connection, a bus that does not order — and the watermark
|
|
326
|
+
* would otherwise step over it for good.
|
|
327
|
+
*
|
|
328
|
+
* @returns Whether it was held back.
|
|
329
|
+
*/
|
|
330
|
+
private fillsGap;
|
|
306
331
|
/** Tell the app that retention dropped messages it never received. */
|
|
307
332
|
private reportHistoryGap;
|
|
308
333
|
private emitError;
|
package/dist/reviver.d.ts
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
1
|
export declare function rebaseReviver(_key: string, value: unknown): unknown;
|
|
2
|
+
/**
|
|
3
|
+
* `JSON.parse(text, rebaseReviver)`, without the reviver's cost where it has
|
|
4
|
+
* nothing to do.
|
|
5
|
+
*
|
|
6
|
+
* Handing `JSON.parse` a reviver takes V8 off its fast path and calls back for
|
|
7
|
+
* every key and every value, primitives included: about 7× a plain parse on a
|
|
8
|
+
* 1,000-row page, 10 ms of main thread per 600 KB. The reviver only ever
|
|
9
|
+
* changes objects carrying `__type`, and a REST page of flat rows has none. So
|
|
10
|
+
* this parses plainly and walks the result only when the text can hold one —
|
|
11
|
+
* and the walk visits objects, never primitives.
|
|
12
|
+
*
|
|
13
|
+
* Same result, instance for instance: the same values revived, bottom-up, as
|
|
14
|
+
* own data properties, and nothing deleted (the reviver never answers
|
|
15
|
+
* `undefined`).
|
|
16
|
+
*
|
|
17
|
+
* @throws SyntaxError exactly where `JSON.parse` does.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseRebaseJson<T = unknown>(text: string): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { uploadResumable } from "@rebasepro/common";
|
package/dist/transport.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface RebaseClientConfig {
|
|
|
19
19
|
* service key instead, which resolves to `{ uid: "service", roles: ["admin"] }`
|
|
20
20
|
* and **bypasses RLS**: there is no user to constrain those queries, so scope
|
|
21
21
|
* them explicitly.
|
|
22
|
+
*
|
|
23
|
+
* Read each time a request or the realtime socket needs a token, until
|
|
24
|
+
* `setToken()` or a sign-in replaces it — so it may be declared as a getter.
|
|
25
|
+
* A token getter installed with `setAuthTokenGetter()` comes first; this is
|
|
26
|
+
* what is sent when it answers nothing.
|
|
22
27
|
*/
|
|
23
28
|
token?: string;
|
|
24
29
|
/**
|
|
@@ -156,7 +161,25 @@ export type FindResponse<T> = TypesFindResponse<T extends Record<string, unknown
|
|
|
156
161
|
* serializes — `null` is a value, `undefined` is the absence of one).
|
|
157
162
|
*/
|
|
158
163
|
export declare function assertNoUndefinedFilterValues(where: Record<string, unknown>): void;
|
|
159
|
-
|
|
164
|
+
/**
|
|
165
|
+
* `?deleted=` — which soft-deleted rows a read covers: `"only"` the trashed
|
|
166
|
+
* ones, `"include"` live and trashed together. Absent, the server hides them.
|
|
167
|
+
*
|
|
168
|
+
* Not a {@link FindParams} field: `FindParams` is also what `listen()` and the
|
|
169
|
+
* offline cache take, and neither can answer for a trashed row — a
|
|
170
|
+
* subscription has no `withDeleted`, and the local database never holds one.
|
|
171
|
+
* Only the trash accessor (`client.data.<slug>.trash`) passes it.
|
|
172
|
+
*/
|
|
173
|
+
export type SoftDeletedRows = "include" | "only";
|
|
174
|
+
export declare function buildQueryString(params?: FindParams, deleted?: SoftDeletedRows): string;
|
|
175
|
+
/**
|
|
176
|
+
* The body of `POST /data/<collection>/query`: the parameters
|
|
177
|
+
* {@link buildQueryString} puts in the URL, spelled as the URL spells them —
|
|
178
|
+
* each value a string, and a parameter the URL repeats (a filter with two
|
|
179
|
+
* conditions on one field) a list of them. The server reads it back into the
|
|
180
|
+
* same parameters and runs the same listing.
|
|
181
|
+
*/
|
|
182
|
+
export declare function buildQueryBody(params?: FindParams, deleted?: SoftDeletedRows): Record<string, string | string[]>;
|
|
160
183
|
/**
|
|
161
184
|
* The query string for `GET /<collection>/aggregate`.
|
|
162
185
|
*
|
|
@@ -166,11 +189,12 @@ export declare function buildQueryString(params?: FindParams): string;
|
|
|
166
189
|
* by exactly the same code a `find()` uses, so "revenue by status, this month"
|
|
167
190
|
* narrows the same rows whichever call is asking.
|
|
168
191
|
*
|
|
169
|
-
* `
|
|
170
|
-
*
|
|
171
|
-
* and
|
|
192
|
+
* `include` and a cursor are deliberately not here: an aggregate has no
|
|
193
|
+
* relations to load and no row to continue after. `limit`, `offset` and
|
|
194
|
+
* `orderBy` are, and page and sort the *groups* — `orderBy` by a group column or
|
|
195
|
+
* an aggregate's alias, in the spelling a `find()` sends.
|
|
172
196
|
*/
|
|
173
|
-
export declare function buildAggregateQueryString(params: AggregateParams): string;
|
|
197
|
+
export declare function buildAggregateQueryString<M extends Record<string, unknown>>(params: AggregateParams<M>): string;
|
|
174
198
|
/**
|
|
175
199
|
* Response metadata a caller can ask for, filled in by `request`.
|
|
176
200
|
*
|
|
@@ -191,12 +215,40 @@ export interface ResponseMeta {
|
|
|
191
215
|
etag?: string;
|
|
192
216
|
/** The HTTP status, for a caller that has to tell 200 from 204. */
|
|
193
217
|
status?: number;
|
|
218
|
+
/**
|
|
219
|
+
* The response's headers, for a protocol that answers in them — a
|
|
220
|
+
* resumable upload's `Location` and `Upload-Offset`.
|
|
221
|
+
*/
|
|
222
|
+
headers?: {
|
|
223
|
+
get(name: string): string | null;
|
|
224
|
+
};
|
|
194
225
|
}
|
|
226
|
+
/** See {@link Transport.setTokenRefresher}. */
|
|
227
|
+
export type TokenRefresher = () => Promise<string | undefined> | undefined;
|
|
195
228
|
export interface Transport {
|
|
196
229
|
request: <T = unknown>(path: string, init?: RequestInit, meta?: ResponseMeta) => Promise<T>;
|
|
230
|
+
/**
|
|
231
|
+
* Like `request`, but the body of a success is left unread: the response
|
|
232
|
+
* comes back as it arrived, for a caller that wants text, a Blob or a
|
|
233
|
+
* stream. Auth, the refresh-and-resend and the error envelope are the same.
|
|
234
|
+
* Optional so a hand-written transport need not have it.
|
|
235
|
+
*/
|
|
236
|
+
requestRaw?: (path: string, init?: RequestInit, meta?: ResponseMeta) => Promise<Response>;
|
|
197
237
|
setToken: (newToken: string | null) => void;
|
|
198
238
|
setAuthTokenGetter: (getter: () => Promise<string | null>) => void;
|
|
199
239
|
setOnUnauthorized: (handler: () => Promise<boolean>) => void;
|
|
240
|
+
/**
|
|
241
|
+
* Install the hook asked, before each request that carries this
|
|
242
|
+
* transport's own token, whether that token is known to be spent. It
|
|
243
|
+
* answers `undefined` to send the token as held, or a promise for the one
|
|
244
|
+
* to send instead — the auth module's refresh, so a request made while a
|
|
245
|
+
* session is being restored, or after its token expired unnoticed, goes
|
|
246
|
+
* out once with a good token rather than with none or a dead one.
|
|
247
|
+
*
|
|
248
|
+
* Not consulted when an auth token getter is installed: the getter owns
|
|
249
|
+
* the token then. Optional so a hand-written transport need not have it.
|
|
250
|
+
*/
|
|
251
|
+
setTokenRefresher?: (refresher: TokenRefresher | null) => void;
|
|
200
252
|
readonly baseUrl: string;
|
|
201
253
|
readonly apiPath: string;
|
|
202
254
|
/** See {@link RebaseClientConfig.storageUrlOrigin}. Undefined = use `baseUrl`. */
|
|
@@ -205,4 +257,32 @@ export interface Transport {
|
|
|
205
257
|
getHeaders: (init?: RequestInit) => Record<string, string>;
|
|
206
258
|
resolveToken: () => Promise<string | null>;
|
|
207
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The base every request and every caller-built URL resolves against.
|
|
262
|
+
*
|
|
263
|
+
* `baseUrl` is optional because the common production shape is a Rebase
|
|
264
|
+
* backend serving its own SPA, where the API is simply the page's origin.
|
|
265
|
+
* Leaving it unset is therefore the *correct* configuration there — and the
|
|
266
|
+
* one that keeps working when a second hostname (a custom domain) points at
|
|
267
|
+
* the same app.
|
|
268
|
+
*
|
|
269
|
+
* When unset in a browser this resolves to the page origin rather than "".
|
|
270
|
+
* Requests behave identically either way, but the empty string is a trap for
|
|
271
|
+
* anything that builds a URL from `client.baseUrl`: `new URL("" + path)`
|
|
272
|
+
* throws, so apps "fixed" it by baking an absolute host into their bundle —
|
|
273
|
+
* which is exactly what breaks the day a custom domain is added, and which no
|
|
274
|
+
* amount of CORS configuration repairs, because a SameSite=Lax auth cookie is
|
|
275
|
+
* not sent cross-site either.
|
|
276
|
+
*/
|
|
277
|
+
/**
|
|
278
|
+
* The code an error carries when the response did not name one: a proxy's 413,
|
|
279
|
+
* a load balancer's 502, a crash page's 500 — anything without the server's
|
|
280
|
+
* envelope. Without it `e.code` was `undefined` and the documented
|
|
281
|
+
* `switch (e.code)` fell through to `default` on exactly the failures that are
|
|
282
|
+
* not Rebase's own. Only codes the backend itself answers with, so a switch
|
|
283
|
+
* written against the error reference catches both.
|
|
284
|
+
*
|
|
285
|
+
* @internal
|
|
286
|
+
*/
|
|
287
|
+
export declare function defaultCodeForStatus(status: number): string | undefined;
|
|
208
288
|
export declare function createTransport(config: RebaseClientConfig, environment?: TransportEnvironment): Transport;
|