@rebasepro/client 0.22.0 → 0.24.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.
- package/dist/admin.d.ts +20 -13
- package/dist/api-keys.d.ts +29 -11
- package/dist/auth.d.ts +65 -14
- package/dist/backups.d.ts +2 -6
- package/dist/collection.d.ts +4 -4
- package/dist/cron.d.ts +2 -4
- package/dist/index.d.ts +37 -12
- package/dist/index.es.js +1814 -333
- package/dist/index.es.js.map +1 -1
- package/dist/offline-connectivity.d.ts +14 -0
- package/dist/offline-query.d.ts +9 -5
- package/dist/offline-store.d.ts +30 -0
- package/dist/offline.d.ts +117 -3
- package/dist/query-contract.types.d.ts +23 -23
- package/dist/realtime-channel.d.ts +35 -0
- package/dist/sdk_query_builder.d.ts +2 -2
- package/dist/sql-script.d.ts +10 -0
- package/dist/transport.d.ts +32 -2
- package/dist/websocket.d.ts +144 -17
- package/package.json +4 -4
|
@@ -30,6 +30,14 @@ export declare function isNetworkError(error: unknown): boolean;
|
|
|
30
30
|
* server's own message says, and exactly what this SDK used not to do.
|
|
31
31
|
*/
|
|
32
32
|
export declare function isIdempotencyInProgressError(error: unknown): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Is this key held for a different request than the one just sent with it?
|
|
35
|
+
*
|
|
36
|
+
* For a create the queue already sent once, that is an answer rather than a
|
|
37
|
+
* failure: the key is live only because the first attempt got through and
|
|
38
|
+
* committed, and only its response went missing.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isIdempotencyKeyReusedError(error: unknown): boolean;
|
|
33
41
|
/** Is this failure worth another attempt later? */
|
|
34
42
|
export declare function isRetryableError(error: unknown): boolean;
|
|
35
43
|
/**
|
|
@@ -82,6 +90,12 @@ export declare class ConnectivityMonitor {
|
|
|
82
90
|
private readonly clearTimer;
|
|
83
91
|
/** Called when the backoff window expires, to drive an automatic retry. */
|
|
84
92
|
onRetryDue?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* Called when the browser says the connection is back. Its own hook rather
|
|
95
|
+
* than {@link onRetryDue}: a client with automatic retries off sets no retry
|
|
96
|
+
* callback, and the `online` event has to wake it all the same.
|
|
97
|
+
*/
|
|
98
|
+
onOnline?: () => void;
|
|
85
99
|
private readonly handleOnline;
|
|
86
100
|
private readonly handleOffline;
|
|
87
101
|
constructor(options?: ConnectivityOptions);
|
package/dist/offline-query.d.ts
CHANGED
|
@@ -15,7 +15,11 @@ export { DEFAULT_LIST_LIMIT as DEFAULT_PAGE_SIZE } from "@rebasepro/types";
|
|
|
15
15
|
* which is how NULL propagates through a comparison.
|
|
16
16
|
*/
|
|
17
17
|
export declare function compareValues(a: unknown, b: unknown): number | undefined;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Equality with a string filter value allowed to name a number, a boolean or
|
|
20
|
+
* an instant, but never across NULL. Two strings are equal only when they are
|
|
21
|
+
* the same string, as text is in Postgres.
|
|
22
|
+
*/
|
|
19
23
|
export declare function looseEquals(a: unknown, b: unknown): boolean;
|
|
20
24
|
/** Evaluate one canonical operator against one row value. */
|
|
21
25
|
export declare function matchesOperator(rowValue: unknown, op: WhereFilterOp, filterValue: unknown): boolean;
|
|
@@ -72,14 +76,14 @@ export declare function isExactlyEvaluable(params?: FindParams): boolean;
|
|
|
72
76
|
*
|
|
73
77
|
* Asked of the rows rather than of the query, because unlike a filter this one
|
|
74
78
|
* *is* decidable from the data in hand: {@link compareValues} reaches the
|
|
75
|
-
* collator only when
|
|
76
|
-
*
|
|
79
|
+
* collator only when neither operand is a number, and `toComparable` has
|
|
80
|
+
* already turned dates and relations into numbers and ids by then. If every
|
|
77
81
|
* value on the sort column normalises to a number, the collator is unreachable
|
|
78
82
|
* and the local order is the server's order.
|
|
79
83
|
*
|
|
80
84
|
* A text column is therefore refused — see {@link isExactlyEvaluable} for why
|
|
81
|
-
* the two cannot be made to agree —
|
|
82
|
-
*
|
|
85
|
+
* the two cannot be made to agree — including one holding only digits, which
|
|
86
|
+
* Postgres orders as text ("10", "100", "9").
|
|
83
87
|
*
|
|
84
88
|
* Nulls are fine either way: they are ordered by an explicit rule (last
|
|
85
89
|
* ascending, first descending) that matches Postgres and never reaches the
|
package/dist/offline-store.d.ts
CHANGED
|
@@ -67,7 +67,37 @@ export interface PendingMutation {
|
|
|
67
67
|
generatedId?: boolean;
|
|
68
68
|
/** The payload: a row for create/update, an array of rows for createMany. */
|
|
69
69
|
data?: Record<string, unknown> | Record<string, unknown>[];
|
|
70
|
+
/**
|
|
71
|
+
* For an `update` that consecutive edits were coalesced into: each edit,
|
|
72
|
+
* in the order the app made them. `data` is their merge, and is what is
|
|
73
|
+
* sent. Kept so a refusal of the merge over one field can be told apart
|
|
74
|
+
* from a refusal of the rest: the merge is split back into these and each
|
|
75
|
+
* replayed on its own, so only the edit the server refuses is undone.
|
|
76
|
+
*/
|
|
77
|
+
parts?: Record<string, unknown>[];
|
|
70
78
|
upsert?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The request as it was already sent once, for a write tried online
|
|
81
|
+
* that failed on the network before it was queued. Nothing says whether
|
|
82
|
+
* it reached the server — only the answer may have been lost — so replay
|
|
83
|
+
* keeps its key. For a create, sent with the rows as queued (their minted
|
|
84
|
+
* ids, which later offline writes may reference), the server either takes
|
|
85
|
+
* them, or, having committed the first attempt, refuses the key as held
|
|
86
|
+
* for a different request, and this request is sent again for its stored
|
|
87
|
+
* answer. Replaying under a fresh key wrote the rows a second time.
|
|
88
|
+
*
|
|
89
|
+
* An `update` or `delete` carries only the key: its queued request is the
|
|
90
|
+
* one it sent, since nothing is merged into an op that has `sent`.
|
|
91
|
+
*/
|
|
92
|
+
sent?: {
|
|
93
|
+
idempotencyKey: string;
|
|
94
|
+
/** `create`: the row it was called with; `createMany`: the rows. */
|
|
95
|
+
data?: Record<string, unknown> | Record<string, unknown>[];
|
|
96
|
+
/** `create`: the `id` argument it was called with. */
|
|
97
|
+
id?: string | number;
|
|
98
|
+
/** `createMany`: the conflict target the batch was sent with. */
|
|
99
|
+
onConflict?: readonly string[];
|
|
100
|
+
};
|
|
71
101
|
queuedAt: number;
|
|
72
102
|
/** How many times replay has been attempted (diagnostics for a stuck queue). */
|
|
73
103
|
attempts?: number;
|
package/dist/offline.d.ts
CHANGED
|
@@ -96,6 +96,24 @@ export interface OfflineStatus {
|
|
|
96
96
|
lastError?: string;
|
|
97
97
|
}
|
|
98
98
|
export type { LiveResult, ObserveOptions, RowSnapshotMeta } from "./collection.js";
|
|
99
|
+
/** Which queue {@link OfflineApi.pending} and {@link OfflineApi.clear} act on. */
|
|
100
|
+
export interface OfflineQueueOptions {
|
|
101
|
+
/**
|
|
102
|
+
* The writes queued while nobody was signed in, instead of the current
|
|
103
|
+
* user's.
|
|
104
|
+
*
|
|
105
|
+
* While someone is signed in these are held back: they were not made by
|
|
106
|
+
* that user, so they are never replayed under their credentials, and never
|
|
107
|
+
* moved into their queue. `pending({ orphaned: true })` lists them so the
|
|
108
|
+
* app can decide — re-issue them as the signed-in user, or discard them
|
|
109
|
+
* with `clear({ orphaned: true })`. Left alone, they replay the next time
|
|
110
|
+
* nobody is signed in.
|
|
111
|
+
*
|
|
112
|
+
* With nobody signed in they are the current queue, and are not orphaned:
|
|
113
|
+
* `pending({ orphaned: true })` lists nothing.
|
|
114
|
+
*/
|
|
115
|
+
orphaned?: boolean;
|
|
116
|
+
}
|
|
99
117
|
/** What `client.offline` exposes to the app. */
|
|
100
118
|
export interface OfflineApi {
|
|
101
119
|
/** Replay the queue now. Resolves with what was flushed and what remains. */
|
|
@@ -103,8 +121,12 @@ export interface OfflineApi {
|
|
|
103
121
|
flushed: number;
|
|
104
122
|
remaining: number;
|
|
105
123
|
}>;
|
|
106
|
-
/**
|
|
107
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The queued mutations for the current user, oldest first — or, with
|
|
126
|
+
* `{ orphaned: true }`, the ones queued while nobody was signed in (see
|
|
127
|
+
* {@link OfflineQueueOptions.orphaned}).
|
|
128
|
+
*/
|
|
129
|
+
pending(options?: OfflineQueueOptions): Promise<PendingMutation[]>;
|
|
108
130
|
/** The current engine state — connectivity, queue depth, last sync. */
|
|
109
131
|
status(): OfflineStatus;
|
|
110
132
|
/** Subscribe to {@link OfflineStatus} changes (for a sync indicator). */
|
|
@@ -114,8 +136,12 @@ export interface OfflineApi {
|
|
|
114
136
|
* Destructive: queued writes are lost, not replayed. For "discard my
|
|
115
137
|
* offline changes" flows, not for sign-out (scoping already isolates
|
|
116
138
|
* users).
|
|
139
|
+
*
|
|
140
|
+
* With `{ orphaned: true }`, drop the writes queued while nobody was
|
|
141
|
+
* signed in, and the local rows they were applied to, instead — the
|
|
142
|
+
* current user's are left alone.
|
|
117
143
|
*/
|
|
118
|
-
clear(): Promise<void>;
|
|
144
|
+
clear(options?: OfflineQueueOptions): Promise<void>;
|
|
119
145
|
/** Subscribe to queue-size changes (for a "pending changes" badge). */
|
|
120
146
|
onQueueChange(listener: (count: number) => void): () => void;
|
|
121
147
|
}
|
|
@@ -140,6 +166,26 @@ export declare class OfflineManager {
|
|
|
140
166
|
private readonly inners;
|
|
141
167
|
private readonly connectivity;
|
|
142
168
|
private scope;
|
|
169
|
+
/**
|
|
170
|
+
* Set while the manager does not know whose data it holds: from
|
|
171
|
+
* {@link holdScope} until the next {@link setScope}. `scope` is not
|
|
172
|
+
* consulted meanwhile — no operation gets a {@link ScopeTicket}, and
|
|
173
|
+
* nothing reads or writes the store.
|
|
174
|
+
*/
|
|
175
|
+
private scopeHold?;
|
|
176
|
+
/**
|
|
177
|
+
* Bumped by every change of user, and by `clear()`.
|
|
178
|
+
*
|
|
179
|
+
* The load paths always re-checked the scope after their awaits; the write
|
|
180
|
+
* paths did not. A `find` sent for user A that answered after B had signed
|
|
181
|
+
* in wrote A's RLS-filtered rows into B's local database, under B's keys,
|
|
182
|
+
* and showed them to B's observers; a replay acknowledged after the switch
|
|
183
|
+
* dequeued a key under B's scope that did not exist, so A's write stayed
|
|
184
|
+
* queued and was sent again when A came back. Every write now carries the
|
|
185
|
+
* {@link ScopeTicket} its operation started under, and one that is stale
|
|
186
|
+
* touches nothing that belongs to the new user.
|
|
187
|
+
*/
|
|
188
|
+
private scopeEpoch;
|
|
143
189
|
/** The local database: normalized rows and query snapshots per collection. */
|
|
144
190
|
private collections;
|
|
145
191
|
/** In-memory mirror of the current scope's queue, in replay order. */
|
|
@@ -185,6 +231,35 @@ export declare class OfflineManager {
|
|
|
185
231
|
* across a sign-out/sign-in on a shared browser.
|
|
186
232
|
*/
|
|
187
233
|
setScope(uid: string | undefined): void;
|
|
234
|
+
/**
|
|
235
|
+
* Forget whose data this is until the next {@link setScope}.
|
|
236
|
+
*
|
|
237
|
+
* For the time before the client knows who is signed in — a session being
|
|
238
|
+
* restored on load. Taking that time for "signed out" would put the user's
|
|
239
|
+
* own writes in the signed-out queue, which no sign-in replays, and show
|
|
240
|
+
* them the signed-out local database instead of theirs.
|
|
241
|
+
*
|
|
242
|
+
* Meanwhile the manager has no scope at all. Every operation — a read, a
|
|
243
|
+
* write, a replay, `pending()`, `clear()` — waits for one and then runs
|
|
244
|
+
* for whoever it turns out to be, and nothing reads or writes the store.
|
|
245
|
+
* A realtime frame arriving in between is handed on as the server sent it
|
|
246
|
+
* and kept nowhere.
|
|
247
|
+
*/
|
|
248
|
+
holdScope(): void;
|
|
249
|
+
/** Let go of the current user's rows and queue: in memory, and on screen. */
|
|
250
|
+
private leaveScope;
|
|
251
|
+
/** Settles once the manager knows whose data it holds — see {@link holdScope}. */
|
|
252
|
+
private scopeKnown;
|
|
253
|
+
/**
|
|
254
|
+
* The user an operation is running for — see {@link scopeEpoch} — or
|
|
255
|
+
* nothing while that is not known ({@link holdScope}). An operation takes
|
|
256
|
+
* it as `this.ticket() ?? await this.nextTicket()`, before its first await.
|
|
257
|
+
*/
|
|
258
|
+
private ticket;
|
|
259
|
+
/** The ticket of whoever the held scope turns out to be. */
|
|
260
|
+
private nextTicket;
|
|
261
|
+
/** Whether an operation's user is still the signed-in one. */
|
|
262
|
+
private isCurrent;
|
|
188
263
|
/**
|
|
189
264
|
* Throw away every local row, for a scope change or an explicit clear.
|
|
190
265
|
*
|
|
@@ -221,6 +296,19 @@ export declare class OfflineManager {
|
|
|
221
296
|
* query the server has never answered here.
|
|
222
297
|
*/
|
|
223
298
|
private answer;
|
|
299
|
+
/**
|
|
300
|
+
* Answer a projected query (`fields`, `distinct`) from the server's own
|
|
301
|
+
* answer to it.
|
|
302
|
+
*
|
|
303
|
+
* The server's rows are the answer. The local database contributes only
|
|
304
|
+
* what it knows better: a row with unsynced writes shows them, narrowed to
|
|
305
|
+
* the columns the server sent, or drops out if it was deleted here or
|
|
306
|
+
* edited out of a filter this side can evaluate; and rows created here
|
|
307
|
+
* join the first page when there is somewhere to put them. A distinct
|
|
308
|
+
* query, or an order on a column the projection does not carry, leaves
|
|
309
|
+
* them out, and the result says it is partial.
|
|
310
|
+
*/
|
|
311
|
+
private projectedAnswer;
|
|
224
312
|
private localFind;
|
|
225
313
|
/**
|
|
226
314
|
* A temporary id of the type this collection's ids actually have.
|
|
@@ -256,6 +344,12 @@ export declare class OfflineManager {
|
|
|
256
344
|
* Rows that came back unchanged keep their identity and revision, so a
|
|
257
345
|
* refetch that changed nothing does not re-render every live query that
|
|
258
346
|
* touches them — or rewrite them all to disk.
|
|
347
|
+
*
|
|
348
|
+
* A `projection` (see {@link isProjection}) only refreshes the columns it
|
|
349
|
+
* carries on rows already held, and never creates one. It is not a row:
|
|
350
|
+
* stored as one, it replaced the full row every other query reads and
|
|
351
|
+
* wrote the narrowed copy to disk, so a list lost its columns because a
|
|
352
|
+
* dropdown elsewhere asked for titles.
|
|
259
353
|
*/
|
|
260
354
|
private ingest;
|
|
261
355
|
/**
|
|
@@ -292,6 +386,17 @@ export declare class OfflineManager {
|
|
|
292
386
|
}>;
|
|
293
387
|
private flush;
|
|
294
388
|
private replay;
|
|
389
|
+
/**
|
|
390
|
+
* Send a queued write under its key.
|
|
391
|
+
*
|
|
392
|
+
* A write the queue sent once already, before it was queued, carries that
|
|
393
|
+
* attempt's key and is sent under it again, as queued. If the server holds
|
|
394
|
+
* the key for a different request, the difference is the ids minted here
|
|
395
|
+
* — so the first attempt committed and only its answer was lost, and
|
|
396
|
+
* sending that request again returns the stored answer instead of a
|
|
397
|
+
* second copy of the rows.
|
|
398
|
+
*/
|
|
399
|
+
private sendQueuedWrite;
|
|
295
400
|
/**
|
|
296
401
|
* Take the server's version of a row the client created offline.
|
|
297
402
|
*
|
|
@@ -323,6 +428,15 @@ export declare class OfflineManager {
|
|
|
323
428
|
* silently lose writes the server would have accepted.
|
|
324
429
|
*/
|
|
325
430
|
private rejectMutation;
|
|
431
|
+
/**
|
|
432
|
+
* Put the separate edits a coalesced `update` was made of back in the
|
|
433
|
+
* queue, in its place and in their order, each with the row as it stood
|
|
434
|
+
* before it as its rollback — so a refusal undoes that edit alone.
|
|
435
|
+
*
|
|
436
|
+
* Their ids extend the merged op's, which sorts them after it and before
|
|
437
|
+
* whatever was queued next, so the queue's order survives a reload.
|
|
438
|
+
*/
|
|
439
|
+
private splitCoalesced;
|
|
326
440
|
/** Every row id a mutation writes to. */
|
|
327
441
|
private idsOf;
|
|
328
442
|
private drop;
|
|
@@ -67,11 +67,11 @@ export declare const orderByColumn: FindParams<ContractRow>;
|
|
|
67
67
|
* turning every typo into an unsorted 200 in production.
|
|
68
68
|
*/
|
|
69
69
|
export declare const orderByTypo: FindParams<ContractRow>;
|
|
70
|
-
export declare const fluentScore: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
71
|
-
export declare const fluentColumn: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
72
|
-
export declare const fluentTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
70
|
+
export declare const fluentScore: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
71
|
+
export declare const fluentColumn: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
72
|
+
export declare const fluentTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
73
73
|
/** Vector search must be reachable from the builder, and chain. */
|
|
74
|
-
export declare const fluentVector: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
74
|
+
export declare const fluentVector: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
75
75
|
/**
|
|
76
76
|
* `array-contains` takes an **element** of the column, not the column.
|
|
77
77
|
*
|
|
@@ -82,38 +82,38 @@ export declare const fluentVector: (qb: SDKQueryBuilderInterface<ContractRow>) =
|
|
|
82
82
|
* did compile, `["featured"]`, builds `@> ARRAY[$1]` with the whole array bound
|
|
83
83
|
* as the single element and matches nothing, forever, with no error anywhere.
|
|
84
84
|
*/
|
|
85
|
-
export declare const fluentArrayContains: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
86
|
-
export declare const fluentArrayContainsWrapped: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
85
|
+
export declare const fluentArrayContains: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
86
|
+
export declare const fluentArrayContainsWrapped: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
87
87
|
/**
|
|
88
88
|
* Same defect, and the case the relation compiler was specifically built for:
|
|
89
89
|
* a to-many relation is emitted as `Array<TargetRow>`, and the compiler answers
|
|
90
90
|
* `array-contains` on it by comparing **ids**. So the id must be accepted even
|
|
91
91
|
* though it is not the element type.
|
|
92
92
|
*/
|
|
93
|
-
export declare const fluentRelationContains: (qb: SDKQueryBuilderInterface<PostRow>, tagId: string) => SDKQueryBuilderInterface<PostRow>;
|
|
94
|
-
export declare const fluentRelationIn: (qb: SDKQueryBuilderInterface<PostRow>, tagIds: string[]) => SDKQueryBuilderInterface<PostRow>;
|
|
95
|
-
export declare const fluentRelationTypo: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow>;
|
|
93
|
+
export declare const fluentRelationContains: (qb: SDKQueryBuilderInterface<PostRow>, tagId: string) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
94
|
+
export declare const fluentRelationIn: (qb: SDKQueryBuilderInterface<PostRow>, tagIds: string[]) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
95
|
+
export declare const fluentRelationTypo: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
96
96
|
/** The list operators take a list of elements — or one, read as a one-element list. */
|
|
97
|
-
export declare const fluentInList: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
98
|
-
export declare const fluentInScalar: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
99
|
-
export declare const fluentInNested: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
97
|
+
export declare const fluentInList: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
98
|
+
export declare const fluentInScalar: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
99
|
+
export declare const fluentInNested: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
100
100
|
/** A comparison takes one value. `eq(column, ["a","b"])` is not a query anyone meant. */
|
|
101
|
-
export declare const fluentEqArray: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
101
|
+
export declare const fluentEqArray: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
102
102
|
/**
|
|
103
103
|
* A pattern is a string on every column type. The driver casts, so refusing
|
|
104
104
|
* `"%3%"` on a numeric column was the type being stricter than the runtime.
|
|
105
105
|
*/
|
|
106
|
-
export declare const fluentLikeOnNumber: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
106
|
+
export declare const fluentLikeOnNumber: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
107
107
|
/** The null operators ignore their value; `null` is the conventional spelling. */
|
|
108
|
-
export declare const fluentIsNull: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
108
|
+
export declare const fluentIsNull: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
109
109
|
/**
|
|
110
110
|
* A caller holding an unnarrowed operator — a dynamic filter UI — must keep
|
|
111
111
|
* compiling. `WhereValueFor` distributes over the operator, so this is the
|
|
112
112
|
* union of every branch rather than a `never`.
|
|
113
113
|
*/
|
|
114
|
-
export declare const fluentDynamicOp: (qb: SDKQueryBuilderInterface<ContractRow>, op: WhereFilterOp) => SDKQueryBuilderInterface<ContractRow>;
|
|
114
|
+
export declare const fluentDynamicOp: (qb: SDKQueryBuilderInterface<ContractRow>, op: WhereFilterOp) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
115
115
|
/** Two conditions on one column: the shape the Mongo compiler used to drop. */
|
|
116
|
-
export declare const fluentRange: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
116
|
+
export declare const fluentRange: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
117
117
|
/** The object form must accept exactly what the fluent form accepts. */
|
|
118
118
|
export declare const paramsArrayContains: FindParams<ContractRow>;
|
|
119
119
|
/** …and the array-of-tuples form the builder produces from two `.where()` calls. */
|
|
@@ -150,11 +150,11 @@ export declare const rowStaysIndexable: (result: FindResult<ContractRow>) => Rec
|
|
|
150
150
|
* which is to say: on every project that took the trouble to be typed.
|
|
151
151
|
*/
|
|
152
152
|
/** 1. A relation path. `find({ where: { "author.name": … } })` always compiled. */
|
|
153
|
-
export declare const fluentRelationPath: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow>;
|
|
153
|
+
export declare const fluentRelationPath: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
154
154
|
/** 2. A JSON path into a `jsonb` column — `?metadata->>tier=eq.gold` on the wire. */
|
|
155
|
-
export declare const fluentJsonPath: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
155
|
+
export declare const fluentJsonPath: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
156
156
|
/** 3. An aggregate over a to-many relation, which `FindParams.orderBy` takes. */
|
|
157
|
-
export declare const fluentRelationAggregateSort: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow>;
|
|
157
|
+
export declare const fluentRelationAggregateSort: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
158
158
|
/** 4. Paging past the `limit` ceiling, which the client can do and the builder could not. */
|
|
159
159
|
export declare const fluentIterate: (qb: SDKQueryBuilderInterface<ContractRow>) => AsyncIterableIterator<ContractRow>;
|
|
160
160
|
export declare const fluentFindAll: (qb: SDKQueryBuilderInterface<ContractRow>) => Promise<ContractRow[]>;
|
|
@@ -164,9 +164,9 @@ export declare const fluentFindAll: (qb: SDKQueryBuilderInterface<ContractRow>)
|
|
|
164
164
|
* `unknown` — turning every one of the assertions above into a silent pass.
|
|
165
165
|
* These are the same refusals as before.
|
|
166
166
|
*/
|
|
167
|
-
export declare const fluentPathTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
168
|
-
export declare const fluentPathDoesNotLoosenValues: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow>;
|
|
169
|
-
export declare const fluentAggregateSortTypo: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow>;
|
|
167
|
+
export declare const fluentPathTypo: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
168
|
+
export declare const fluentPathDoesNotLoosenValues: (qb: SDKQueryBuilderInterface<ContractRow>) => SDKQueryBuilderInterface<ContractRow, import("@rebasepro/types").IncludeSpec>;
|
|
169
|
+
export declare const fluentAggregateSortTypo: (qb: SDKQueryBuilderInterface<PostRow>) => SDKQueryBuilderInterface<PostRow, import("@rebasepro/types").IncludeSpec>;
|
|
170
170
|
/** And the object form still takes all four, unchanged. */
|
|
171
171
|
export declare const paramsRelationPath: FindParams<PostRow>;
|
|
172
172
|
export declare const paramsJsonPath: FindParams<ContractRow>;
|
|
@@ -98,6 +98,11 @@ export interface ChannelTransport {
|
|
|
98
98
|
sendMessage(message: Record<string, unknown>): Promise<unknown>;
|
|
99
99
|
onChannelMessage(channel: string, handler: (message: ChannelMessage) => void): () => void;
|
|
100
100
|
onReconnect(handler: () => void): () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Notified once per outage that outlasted a blip. Optional so a test
|
|
103
|
+
* transport need not implement it.
|
|
104
|
+
*/
|
|
105
|
+
onConnectionLost?(handler: (error: RebaseApiError) => void): () => void;
|
|
101
106
|
}
|
|
102
107
|
export declare class RebaseRealtimeChannel {
|
|
103
108
|
readonly name: string;
|
|
@@ -133,6 +138,27 @@ export declare class RebaseRealtimeChannel {
|
|
|
133
138
|
*/
|
|
134
139
|
private pendingLive;
|
|
135
140
|
private catchUpInFlight;
|
|
141
|
+
/**
|
|
142
|
+
* Whether {@link lastSeq} is a position this client reached, rather than
|
|
143
|
+
* the zero of a handle that has not caught up yet.
|
|
144
|
+
*
|
|
145
|
+
* A client joining for the first time asks for whatever is retained, so
|
|
146
|
+
* history pruned before it ever joined is not something it missed. Only a
|
|
147
|
+
* client with a position can have a hole in what it saw.
|
|
148
|
+
*/
|
|
149
|
+
private positioned;
|
|
150
|
+
/** The page size the catch-up in flight asked for, reused for its later pages. */
|
|
151
|
+
private catchUpLimit;
|
|
152
|
+
/**
|
|
153
|
+
* `latestSeq` of an empty page that left this client behind, while a
|
|
154
|
+
* second request confirms it.
|
|
155
|
+
*
|
|
156
|
+
* The server reads the retained messages and then the cursor, so a message
|
|
157
|
+
* committed between the two shows up in `latestSeq` and not in the page.
|
|
158
|
+
* The second request returns it if it exists; only a second empty page
|
|
159
|
+
* means the messages up to this seq are gone.
|
|
160
|
+
*/
|
|
161
|
+
private emptyTailSeq;
|
|
136
162
|
/**
|
|
137
163
|
* Deadline for a catch-up response.
|
|
138
164
|
*
|
|
@@ -234,6 +260,12 @@ export declare class RebaseRealtimeChannel {
|
|
|
234
260
|
* retention cannot be served; `CHANNEL_BUS_PAYLOAD_TOO_LARGE` when a
|
|
235
261
|
* broadcast on an ephemeral channel is too big to cross the bus.
|
|
236
262
|
*
|
|
263
|
+
* `CHANNEL_HISTORY_GAP` is raised by the client: a catch-up found that the
|
|
264
|
+
* server no longer retains messages this client never received, so its
|
|
265
|
+
* state has a hole a replay cannot fill. `details` is `{ from, to }`, the
|
|
266
|
+
* inclusive range of sequence numbers missed. Resync the channel's state
|
|
267
|
+
* from its source of truth.
|
|
268
|
+
*
|
|
237
269
|
* These used to be dropped on the floor — there is no promise to reject on
|
|
238
270
|
* a fire-and-forget frame, so a forbidden broadcast looked exactly like a
|
|
239
271
|
* delivered one. With no handler attached they are logged as a warning,
|
|
@@ -271,6 +303,9 @@ export declare class RebaseRealtimeChannel {
|
|
|
271
303
|
private stopHeartbeat;
|
|
272
304
|
/** Fold an incoming frame into the roster and fan it out. */
|
|
273
305
|
private handle;
|
|
306
|
+
/** Tell the app that retention dropped messages it never received. */
|
|
307
|
+
private reportHistoryGap;
|
|
308
|
+
private emitError;
|
|
274
309
|
/** Deliver everything held back during a catch-up, in sequence order. */
|
|
275
310
|
private flushPendingLive;
|
|
276
311
|
private deliver;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FindResult, LogicalCondition, PageWalkOptions, RelationAggregateSort, SDKCollectionClient, SDKQueryBuilderInterface, WhereFilterOp, WhereValueFor, type AggregateParams, type
|
|
1
|
+
import { FindResult, LogicalCondition, PageWalkOptions, RelationAggregateSort, SDKCollectionClient, SDKQueryBuilderInterface, WhereFilterOp, WhereValueFor, type AggregateParams, type AggregateResult, type ComputedSortField, type FieldPath, type IncludeSpec, type NonColumnFieldPath, type NullsPlacement } from "@rebasepro/types";
|
|
2
2
|
/**
|
|
3
3
|
* SDK Query Builder — returns flat rows (`FindResult<M>`) instead of
|
|
4
4
|
* Entity-wrapped results (`FindResponse<M>`).
|
|
@@ -173,7 +173,7 @@ export declare class SDKQueryBuilder<M extends Record<string, unknown> = Record<
|
|
|
173
173
|
* // [{ status: "paid", sum_total: 41822.5 }, …]
|
|
174
174
|
* ```
|
|
175
175
|
*/
|
|
176
|
-
aggregate(params: Omit<AggregateParams<M>, "where" | "logical" | "searchString">): Promise<
|
|
176
|
+
aggregate(params: Omit<AggregateParams<M>, "where" | "logical" | "searchString">): Promise<AggregateResult>;
|
|
177
177
|
/**
|
|
178
178
|
* Page through everything this query matches, one row at a time.
|
|
179
179
|
*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SqlScriptResult } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* Read an `EXECUTE_SQL_SUCCESS` payload for a script run as a
|
|
4
|
+
* {@link SqlScriptResult}, checking every field rather than trusting the frame.
|
|
5
|
+
*
|
|
6
|
+
* A server from before scripts existed answers `{ result }` alone, with parsed
|
|
7
|
+
* values. Its rows are kept, as text, and no column is said to come from
|
|
8
|
+
* anywhere — the console edits no cell of a result it cannot trace.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readSqlScriptResult(payload: unknown): SqlScriptResult;
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AggregateParams, FindParams as TypesFindParams, FindResponse as TypesFindResponse } from "@rebasepro/types";
|
|
1
|
+
import { AggregateParams, FindParams as TypesFindParams, FindResponse as TypesFindResponse, type IncludeSpec } from "@rebasepro/types";
|
|
2
2
|
export { RebaseApiError } from "@rebasepro/types";
|
|
3
3
|
export type { RebaseErrorInit } from "@rebasepro/types";
|
|
4
4
|
export interface RebaseClientConfig {
|
|
@@ -95,6 +95,19 @@ export interface RebaseClientConfig {
|
|
|
95
95
|
* is not settable here; it always comes from the token.
|
|
96
96
|
*/
|
|
97
97
|
headers?: Record<string, string>;
|
|
98
|
+
/**
|
|
99
|
+
* Run every request as this user, named by uid: sent as
|
|
100
|
+
* `x-rebase-impersonate` on each HTTP request, and with the realtime
|
|
101
|
+
* socket's sign-in.
|
|
102
|
+
*
|
|
103
|
+
* For checking what one user can see and do against the real row-level
|
|
104
|
+
* security, as Studio's API explorer and JS editor do. Only an
|
|
105
|
+
* administrator's own session may — the server refuses it for anyone
|
|
106
|
+
* else, and always for an API key or the service key. The data API,
|
|
107
|
+
* custom functions and realtime run as the named user; every other route
|
|
108
|
+
* refuses the request rather than answer as the administrator.
|
|
109
|
+
*/
|
|
110
|
+
impersonate?: string;
|
|
98
111
|
}
|
|
99
112
|
/**
|
|
100
113
|
* Facts about the surrounding client that the transport cannot read off its own
|
|
@@ -124,8 +137,25 @@ export declare const ANONYMOUS_SERVER_CLIENT_WARNING: string;
|
|
|
124
137
|
* `orderBy` went back to accepting any column name — the alias, not the
|
|
125
138
|
* definition, was where the typing was lost.
|
|
126
139
|
*/
|
|
127
|
-
export type FindParams<M extends Record<string, unknown> = Record<string, unknown
|
|
140
|
+
export type FindParams<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> = TypesFindParams<M, Inc>;
|
|
128
141
|
export type FindResponse<T> = TypesFindResponse<T extends Record<string, unknown> ? T : Record<string, unknown>>;
|
|
142
|
+
/**
|
|
143
|
+
* Refuse a filter whose *value* is missing.
|
|
144
|
+
*
|
|
145
|
+
* `where: { status: ["==", undefined] }` used to serialize to the literal
|
|
146
|
+
* string, so `status=eq.undefined` went out on the wire and the server dutifully
|
|
147
|
+
* looked for rows whose status is the four-letter word "undefined". The caller
|
|
148
|
+
* saw an empty page, not an error — the classic shape of a variable that was
|
|
149
|
+
* never set.
|
|
150
|
+
*
|
|
151
|
+
* Dropping the condition instead would be worse than sending it: the query
|
|
152
|
+
* would come back *unfiltered*, which for an ownership or tenant filter means
|
|
153
|
+
* returning rows the caller never asked to see. So this is a hard error, and
|
|
154
|
+
* both correct spellings are named in the message: omit the key to skip the
|
|
155
|
+
* filter, or use `["is-null", null]` to match SQL NULL (which still
|
|
156
|
+
* serializes — `null` is a value, `undefined` is the absence of one).
|
|
157
|
+
*/
|
|
158
|
+
export declare function assertNoUndefinedFilterValues(where: Record<string, unknown>): void;
|
|
129
159
|
export declare function buildQueryString(params?: FindParams): string;
|
|
130
160
|
/**
|
|
131
161
|
* The query string for `GET /<collection>/aggregate`.
|