@rebasepro/types 0.19.1 → 0.19.2-canary.g08eed46
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/controllers/data.d.ts +515 -14
- package/dist/controllers/data_driver.d.ts +166 -4
- package/dist/controllers/storage.d.ts +30 -0
- package/dist/index.es.js +163 -3
- package/dist/index.es.js.map +1 -1
- package/dist/types/collections.d.ts +59 -0
- package/dist/types/filter-operators.d.ts +37 -6
- package/dist/types/index.d.ts +1 -0
- package/dist/types/policy.d.ts +39 -1
- package/dist/types/properties.d.ts +205 -8
- package/dist/types/relations.d.ts +71 -0
- package/dist/types/schema_editing.d.ts +6 -0
- package/dist/types/tenancy.d.ts +147 -0
- package/dist/types/websockets.d.ts +37 -0
- package/dist/users/user.d.ts +21 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { VectorSearchParams } from "./data_driver.js";
|
|
2
2
|
import type { ComputedSortField, SearchMatch } from "../types/search.js";
|
|
3
3
|
import { Entity, EntityValues } from "../types/entities.js";
|
|
4
|
-
import { WhereFilterOp, FieldPath, NonColumnFieldPath, FilterValues, OrderBySpec, RelationAggregateSort } from "../types/filter-operators.js";
|
|
4
|
+
import { WhereFilterOp, FieldPath, NonColumnFieldPath, FilterValues, NullsPlacement, OrderBySpec, RelationAggregateSort } from "../types/filter-operators.js";
|
|
5
5
|
/**
|
|
6
6
|
* The element type of an array column, and the column's own type otherwise.
|
|
7
7
|
*
|
|
@@ -54,8 +54,24 @@ export type WhereElementOf<T> = ElementOf<T> | IdOf<ElementOf<T>>;
|
|
|
54
54
|
* permissive as it was.
|
|
55
55
|
*/
|
|
56
56
|
export type WhereValueFor<Op extends WhereFilterOp, T> = Op extends "array-contains" ? WhereElementOf<T> : Op extends "in" | "not-in" | "array-contains-any" ? readonly WhereElementOf<T>[] | WhereElementOf<T> | null : Op extends "like" | "ilike" | "not-like" | "not-ilike" ? string : Op extends "is-null" | "is-not-null" ? null | undefined : T | null;
|
|
57
|
+
/**
|
|
58
|
+
* A group of conditions combined with `and`, `or`, or negated with `not`.
|
|
59
|
+
*
|
|
60
|
+
* ## `not`
|
|
61
|
+
*
|
|
62
|
+
* `not` negates the **conjunction** of its `conditions`: `not(a)` is `NOT a`,
|
|
63
|
+
* and `not(a, b)` is `NOT (a AND b)`. One rule, stated here and applied
|
|
64
|
+
* identically by the wire codec (`or(...)`/`and(...)`/`not(...)` in
|
|
65
|
+
* `@rebasepro/common`), the REST `?not=` parameter and every driver compiler,
|
|
66
|
+
* so a negation means the same thing whichever end writes it.
|
|
67
|
+
*
|
|
68
|
+
* Negation is not expressible by inverting the operators inside the group: SQL
|
|
69
|
+
* three-valued logic makes `NOT (a AND b)` and `(NOT a) OR (NOT b)` differ the
|
|
70
|
+
* moment a NULL is involved, and only one of them is what the caller wrote. It
|
|
71
|
+
* compiles to a real `NOT (...)`.
|
|
72
|
+
*/
|
|
57
73
|
export interface LogicalCondition {
|
|
58
|
-
type: "and" | "or";
|
|
74
|
+
type: "and" | "or" | "not";
|
|
59
75
|
conditions: (FilterCondition | LogicalCondition)[];
|
|
60
76
|
}
|
|
61
77
|
export interface FilterCondition {
|
|
@@ -63,6 +79,76 @@ export interface FilterCondition {
|
|
|
63
79
|
operator: WhereFilterOp;
|
|
64
80
|
value: unknown;
|
|
65
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* How one relation is loaded by {@link FindParams.include}.
|
|
84
|
+
*
|
|
85
|
+
* `true` loads the relation whole. The object form narrows it — the same four
|
|
86
|
+
* knobs a top-level query has, applied to the rows *inside* one relation — and
|
|
87
|
+
* `include` nests, so a query can ask for "each post's five newest published
|
|
88
|
+
* comments, each with its author" in one request.
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* include: {
|
|
92
|
+
* comments: {
|
|
93
|
+
* limit: 5,
|
|
94
|
+
* where: { published: ["==", true] },
|
|
95
|
+
* orderBy: ["created_at", "desc"],
|
|
96
|
+
* include: { author: true }
|
|
97
|
+
* }
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* Nesting is bounded at {@link MAX_INCLUDE_DEPTH} hops. Each hop is another
|
|
102
|
+
* batched query, and the bound is what stops one request from walking a
|
|
103
|
+
* self-referencing relation forever.
|
|
104
|
+
*
|
|
105
|
+
* @group Data
|
|
106
|
+
*/
|
|
107
|
+
export interface IncludeOptions {
|
|
108
|
+
/** Rows to load per parent row. Applied per parent, not across the page. */
|
|
109
|
+
limit?: number;
|
|
110
|
+
/** Filter the related rows, in the same dialect as {@link FindParams.where}. */
|
|
111
|
+
where?: FilterValues<string>;
|
|
112
|
+
/** An `and`/`or`/`not` group over the related rows. */
|
|
113
|
+
logical?: LogicalCondition;
|
|
114
|
+
/**
|
|
115
|
+
* Sort the related rows — the tuple form, or the `field:direction[:nulls]`
|
|
116
|
+
* shorthand the REST `?orderBy=` parameter uses.
|
|
117
|
+
*
|
|
118
|
+
* The string is accepted because this whole object travels over a query
|
|
119
|
+
* string, where a tuple is three characters of JSON heavier for no gain.
|
|
120
|
+
*/
|
|
121
|
+
orderBy?: OrderBySpec<string> | string;
|
|
122
|
+
/** Columns of the *related* row to return. `id` is always included. */
|
|
123
|
+
fields?: string[];
|
|
124
|
+
/** Relations of the related row to load in turn. */
|
|
125
|
+
include?: IncludeSpec;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The relations a read loads, as a list of (possibly dotted) names or as a
|
|
129
|
+
* tree.
|
|
130
|
+
*
|
|
131
|
+
* - `["author", "comments.author"]` — a dotted path is the same thing as the
|
|
132
|
+
* nested object form, spelled flat. It is what the REST `?include=` parameter
|
|
133
|
+
* carries, and the two forms compile to the same request.
|
|
134
|
+
* - `["*"]` — every relation, one hop deep. The admin panel's shape.
|
|
135
|
+
* - `{ comments: { limit: 5, include: { author: true } } }` — the parametrised
|
|
136
|
+
* form.
|
|
137
|
+
*
|
|
138
|
+
* A name that is not a relation of the collection is a **400
|
|
139
|
+
* `UNKNOWN_RELATION`**, not a silent omission: a read that quietly drops an
|
|
140
|
+
* `include` answers 200 with the field missing, which is indistinguishable from
|
|
141
|
+
* a row that genuinely has no related row.
|
|
142
|
+
*
|
|
143
|
+
* @group Data
|
|
144
|
+
*/
|
|
145
|
+
export type IncludeSpec = string[] | Record<string, true | IncludeOptions>;
|
|
146
|
+
/**
|
|
147
|
+
* Hops an {@link IncludeSpec} may nest. `comments.author` is two.
|
|
148
|
+
*
|
|
149
|
+
* @group Data
|
|
150
|
+
*/
|
|
151
|
+
export declare const MAX_INCLUDE_DEPTH = 3;
|
|
66
152
|
/**
|
|
67
153
|
* Parameters for querying a collection.
|
|
68
154
|
*
|
|
@@ -145,13 +231,59 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
|
|
|
145
231
|
*/
|
|
146
232
|
orderBy?: OrderBySpec<FieldPath<M> | ComputedSortField>;
|
|
147
233
|
/**
|
|
148
|
-
* Relations to
|
|
234
|
+
* Relations to load into the response — see {@link IncludeSpec}.
|
|
235
|
+
*
|
|
236
|
+
* Not checked against `M` here: a relation name comes from the collection's
|
|
237
|
+
* `relations`, not from its columns, so nothing in a *hand-written* row type
|
|
238
|
+
* can validate one. A **generated** `Database` narrows this to the
|
|
239
|
+
* collection's actual relation keys, recursively — see `rebase codegen`.
|
|
240
|
+
*
|
|
241
|
+
* An unknown name is a 400 `UNKNOWN_RELATION`. It used to be ignored.
|
|
242
|
+
*/
|
|
243
|
+
include?: IncludeSpec;
|
|
244
|
+
/**
|
|
245
|
+
* Columns to return, instead of all of them.
|
|
246
|
+
*
|
|
247
|
+
* A real column projection: only these columns are read from the database,
|
|
248
|
+
* so a query that needs two fields of a wide row does not pay for the rest.
|
|
249
|
+
* `excludeFromApi` still applies — naming such a column here does not
|
|
250
|
+
* un-hide it — and the primary key is always returned, because a row that
|
|
251
|
+
* cannot be addressed cannot be updated, deleted or paged past.
|
|
252
|
+
*
|
|
253
|
+
* A relation named in {@link FindParams.include} is loaded regardless of
|
|
254
|
+
* whether it appears here; use {@link IncludeOptions.fields} to narrow the
|
|
255
|
+
* columns *within* an included relation.
|
|
256
|
+
*/
|
|
257
|
+
fields?: string[];
|
|
258
|
+
/**
|
|
259
|
+
* Collapse rows that are identical over the columns being returned.
|
|
260
|
+
*
|
|
261
|
+
* `SELECT DISTINCT` over the projection — so it is only meaningful
|
|
262
|
+
* alongside {@link FindParams.fields}, and with the primary key in the
|
|
263
|
+
* projection (which it always is) every row is already distinct. Pair it
|
|
264
|
+
* with `fields` naming the columns you actually want the distinct values of.
|
|
149
265
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* in a generated row type can validate one.
|
|
266
|
+
* `meta.total` counts distinct rows too, so a distinct listing's `hasMore`
|
|
267
|
+
* describes the set it is paging.
|
|
153
268
|
*/
|
|
154
|
-
|
|
269
|
+
distinct?: boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Continue from where a previous page ended — keyset ("seek") pagination.
|
|
272
|
+
*
|
|
273
|
+
* The value is the opaque `meta.nextCursor` of the previous response. It
|
|
274
|
+
* encodes the sort keys the query was ordered by and the last row's values
|
|
275
|
+
* for them, so a page picks up strictly after the last row served rather
|
|
276
|
+
* than at a row *count* that concurrent writes have already moved.
|
|
277
|
+
*
|
|
278
|
+
* It has to describe the same query: `after` alongside a different
|
|
279
|
+
* `orderBy` is a 400 `CURSOR_ORDER_MISMATCH` rather than a page of rows
|
|
280
|
+
* seeked in an order nobody asked for. Mutually exclusive with `offset` and
|
|
281
|
+
* `page` for the same reason.
|
|
282
|
+
*
|
|
283
|
+
* Multi-key sorts and nullable keys both work — the comparison is built
|
|
284
|
+
* over every key, in order, with the NULL placement the sort declared.
|
|
285
|
+
*/
|
|
286
|
+
after?: string;
|
|
155
287
|
/**
|
|
156
288
|
* Text search string, AND-ed with `where`/`logical`. This is the value
|
|
157
289
|
* behind the query builder's `.search()` method.
|
|
@@ -279,6 +411,7 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
|
|
|
279
411
|
*/
|
|
280
412
|
createMany?(data: Partial<EntityValues<M>>[], options?: {
|
|
281
413
|
upsert?: boolean;
|
|
414
|
+
onConflict?: readonly string[];
|
|
282
415
|
}): Promise<Entity<M>[]>;
|
|
283
416
|
/**
|
|
284
417
|
* Update an existing record by ID.
|
|
@@ -324,6 +457,13 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
|
|
|
324
457
|
* one place the two halves of this API are not interchangeable.
|
|
325
458
|
*/
|
|
326
459
|
count?(params?: FindParams<M>): Promise<number>;
|
|
460
|
+
/**
|
|
461
|
+
* {@link SDKCollectionClient.aggregate}. Optional here for the same reason
|
|
462
|
+
* `count` is: not every data source can compute one, and the SDK wraps an
|
|
463
|
+
* absent implementation in a stub that says so rather than returning a
|
|
464
|
+
* number nothing counted.
|
|
465
|
+
*/
|
|
466
|
+
aggregate?(params: AggregateParams<M>): Promise<AggregateRow[]>;
|
|
327
467
|
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): QueryBuilderInterface<M>;
|
|
328
468
|
where(logicalCondition: LogicalCondition): QueryBuilderInterface<M>;
|
|
329
469
|
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
|
|
@@ -356,6 +496,20 @@ export interface PaginationMeta {
|
|
|
356
496
|
limit: number;
|
|
357
497
|
offset: number;
|
|
358
498
|
hasMore: boolean;
|
|
499
|
+
/**
|
|
500
|
+
* The opaque cursor that continues this listing — pass it back as
|
|
501
|
+
* {@link FindParams.after}.
|
|
502
|
+
*
|
|
503
|
+
* Present whenever there is a next page to describe (`hasMore` is true and
|
|
504
|
+
* the page returned at least one row). Absent on the last page, and absent
|
|
505
|
+
* on a query no cursor can describe (relevance ordering, whose scores are
|
|
506
|
+
* computed per query and are not comparable between two of them).
|
|
507
|
+
*
|
|
508
|
+
* Opaque on purpose: it encodes the sort keys *and* the last row's values
|
|
509
|
+
* for them, and a client that parsed it would be depending on an encoding
|
|
510
|
+
* that exists to be changed.
|
|
511
|
+
*/
|
|
512
|
+
nextCursor?: string;
|
|
359
513
|
}
|
|
360
514
|
/**
|
|
361
515
|
* Paginated response from a collection query (SDK-facing).
|
|
@@ -416,6 +570,66 @@ export type QueryComputedFields = {
|
|
|
416
570
|
*/
|
|
417
571
|
_distance?: number;
|
|
418
572
|
};
|
|
573
|
+
/**
|
|
574
|
+
* One aggregate a query asks for.
|
|
575
|
+
*
|
|
576
|
+
* `count` alone counts rows; every other function names a column, and `count`
|
|
577
|
+
* with a column counts its non-NULL values.
|
|
578
|
+
*
|
|
579
|
+
* The result key is derived rather than chosen: `sum(total)` comes back as
|
|
580
|
+
* `sum_total` and a bare `count()` as `count`. Letting a caller name it would
|
|
581
|
+
* mean checking their name is not also a `groupBy` field — a rule nobody would
|
|
582
|
+
* guess, and a silently overwritten value if it went unchecked.
|
|
583
|
+
*
|
|
584
|
+
* @group Data
|
|
585
|
+
*/
|
|
586
|
+
export type AggregateSelect<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
587
|
+
fn: "count";
|
|
588
|
+
field?: Extract<keyof M, string>;
|
|
589
|
+
} | {
|
|
590
|
+
fn: "sum" | "avg" | "min" | "max";
|
|
591
|
+
field: Extract<keyof M, string>;
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* One row of an aggregate result: the `groupBy` columns, plus one key per
|
|
595
|
+
* {@link AggregateSelect} under its derived alias.
|
|
596
|
+
*
|
|
597
|
+
* `count`, `sum` and `avg` arrive as numbers — Postgres returns bigint and
|
|
598
|
+
* numeric as strings, and they are parsed once at the driver rather than by
|
|
599
|
+
* every caller. `min`/`max` keep the column's own type.
|
|
600
|
+
*
|
|
601
|
+
* @group Data
|
|
602
|
+
*/
|
|
603
|
+
export type AggregateRow = Record<string, unknown>;
|
|
604
|
+
/**
|
|
605
|
+
* What {@link SDKCollectionClient.aggregate} takes: the same narrowing a
|
|
606
|
+
* `find()` takes, minus the parts of it that describe a *page* of rows.
|
|
607
|
+
*
|
|
608
|
+
* `limit` survives and means what it means on the REST route — a bound on the
|
|
609
|
+
* number of **groups**, because grouping by a high-cardinality column is a whole
|
|
610
|
+
* table's worth of rows in one response. It is ignored when there is no
|
|
611
|
+
* `groupBy`, since an ungrouped aggregate is one row.
|
|
612
|
+
*
|
|
613
|
+
* `orderBy`, `include`, `after` and the rest are absent on purpose: an
|
|
614
|
+
* aggregate has no rows to sort, no relations to load and no page to continue.
|
|
615
|
+
* They were silently ignored on the REST route; here they do not typecheck.
|
|
616
|
+
*
|
|
617
|
+
* @group Data
|
|
618
|
+
*/
|
|
619
|
+
export interface AggregateParams<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
620
|
+
/** The aggregates to compute. At least one. */
|
|
621
|
+
select: AggregateSelect<M>[];
|
|
622
|
+
/** Columns to group by. Omit for a single row over everything that matches. */
|
|
623
|
+
groupBy?: Extract<keyof M, string>[];
|
|
624
|
+
/** Filter conditions, as {@link FindParams.where}. */
|
|
625
|
+
where?: FilterValues<FieldPath<M>>;
|
|
626
|
+
/** An `and`/`or`/`not` group, AND-ed with `where`. */
|
|
627
|
+
logical?: LogicalCondition;
|
|
628
|
+
/** Text search, AND-ed with the filters. */
|
|
629
|
+
searchString?: string;
|
|
630
|
+
/** Most groups to return. Ignored without `groupBy`. */
|
|
631
|
+
limit?: number;
|
|
632
|
+
}
|
|
419
633
|
/**
|
|
420
634
|
* Which column an iteration seeks on, for keyset ("seek") pagination.
|
|
421
635
|
*
|
|
@@ -524,7 +738,7 @@ export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Re
|
|
|
524
738
|
* Sort by a column, a relation or JSON path, `_score`, or an aggregate over
|
|
525
739
|
* a to-many relation — the same key set {@link FindParams.orderBy} takes.
|
|
526
740
|
*/
|
|
527
|
-
orderBy(column: FieldPath<M> | ComputedSortField | RelationAggregateSort, direction?: "asc" | "desc"): this;
|
|
741
|
+
orderBy(column: FieldPath<M> | ComputedSortField | RelationAggregateSort, direction?: "asc" | "desc", nulls?: NullsPlacement): this;
|
|
528
742
|
limit(count: number): this;
|
|
529
743
|
offset(count: number): this;
|
|
530
744
|
search(searchString: string, options?: {
|
|
@@ -543,8 +757,29 @@ export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Re
|
|
|
543
757
|
distance?: "cosine" | "l2" | "inner_product";
|
|
544
758
|
threshold?: number;
|
|
545
759
|
}): this;
|
|
546
|
-
|
|
760
|
+
/**
|
|
761
|
+
* Load relations — names, dotted paths (`"comments.author"`), or the
|
|
762
|
+
* parametrised tree. Repeated calls merge rather than replace.
|
|
763
|
+
*/
|
|
764
|
+
include(...relations: (string | IncludeSpec)[]): this;
|
|
765
|
+
/**
|
|
766
|
+
* Return only these columns. A real projection: the columns are what is
|
|
767
|
+
* read from the database, not what survives a trim of the response.
|
|
768
|
+
*/
|
|
769
|
+
fields(...columns: (FieldPath<M> | string)[]): this;
|
|
770
|
+
/** `SELECT DISTINCT` over the projection — see {@link FindParams.distinct}. */
|
|
771
|
+
distinct(enabled?: boolean): this;
|
|
772
|
+
/** Continue after a previous page's `meta.nextCursor`. */
|
|
773
|
+
after(cursor: string): this;
|
|
547
774
|
find(): Promise<FindResult<M>>;
|
|
775
|
+
/**
|
|
776
|
+
* Aggregate the rows this query matches instead of returning them.
|
|
777
|
+
*
|
|
778
|
+
* The builder's `where`/`logical`/`search` narrow which rows are
|
|
779
|
+
* aggregated; its `orderBy`, `include` and window do not apply and are
|
|
780
|
+
* ignored, exactly as they are on the REST route.
|
|
781
|
+
*/
|
|
782
|
+
aggregate(params: Omit<AggregateParams<M>, "where" | "logical" | "searchString">): Promise<AggregateRow[]>;
|
|
548
783
|
/**
|
|
549
784
|
* Page through everything this query matches, one row at a time.
|
|
550
785
|
*
|
|
@@ -591,6 +826,173 @@ export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Re
|
|
|
591
826
|
*
|
|
592
827
|
* @group Data
|
|
593
828
|
*/
|
|
829
|
+
/**
|
|
830
|
+
* A change expressed as an operation on the column's current value, rather than
|
|
831
|
+
* as the value to store.
|
|
832
|
+
*
|
|
833
|
+
* `{ views: 5 }` says what the number becomes; `{ views: { $inc: 1 } }` says
|
|
834
|
+
* what happens to it. The difference is the read the caller no longer has to
|
|
835
|
+
* make — and the race that read opens. Two requests that each read `4`, add one
|
|
836
|
+
* and write `5` lose an increment between them; `SET views = views + 1` cannot,
|
|
837
|
+
* because the arithmetic happens inside the statement holding the row lock.
|
|
838
|
+
*
|
|
839
|
+
* Exactly one operator per field. `{ views: { $inc: 1, $push: "x" } }` is
|
|
840
|
+
* refused rather than applied in an order the caller cannot see.
|
|
841
|
+
*
|
|
842
|
+
* @group Data
|
|
843
|
+
*/
|
|
844
|
+
/**
|
|
845
|
+
* The operator names, as a value.
|
|
846
|
+
*
|
|
847
|
+
* A runtime list beside the type because three layers have to *recognise* an
|
|
848
|
+
* operation, not just accept one: the REST validator, the driver that compiles
|
|
849
|
+
* it, and the offline queue that must refuse to apply one locally. Three copies
|
|
850
|
+
* of four strings is three chances for one of them to miss an operator added to
|
|
851
|
+
* the other two, and the failure is silent in the worst direction — an
|
|
852
|
+
* unrecognised marker is written to the column as a JSON document.
|
|
853
|
+
*
|
|
854
|
+
* @group Data
|
|
855
|
+
*/
|
|
856
|
+
export declare const FIELD_OPERATORS: readonly ["$inc", "$push", "$pull", "$merge"];
|
|
857
|
+
/**
|
|
858
|
+
* The key of a {@link BatchRef}. Declared here, beside the field operators,
|
|
859
|
+
* because the two share one namespace: a `$`-prefixed key in a write payload is
|
|
860
|
+
* a marker, and every reader of that namespace has to know all of it.
|
|
861
|
+
*
|
|
862
|
+
* @group Data
|
|
863
|
+
*/
|
|
864
|
+
export declare const BATCH_REF_KEY = "$ref";
|
|
865
|
+
/**
|
|
866
|
+
* Whether a value is *trying* to be a field operation — including a misspelled
|
|
867
|
+
* one, which is the case worth catching.
|
|
868
|
+
*
|
|
869
|
+
* Any `$`-prefixed key counts, because `{ $increment: 1 }` written to a number
|
|
870
|
+
* column as a JSON document is the failure this exists to prevent. No collection
|
|
871
|
+
* can declare a column whose value legitimately has a key beginning with `$`: a
|
|
872
|
+
* `map` property's sub-keys are declared, and `$` is not valid in the
|
|
873
|
+
* identifiers the DDL generators emit.
|
|
874
|
+
*
|
|
875
|
+
* The one exception is `{ $ref: … }`, the batch's backward reference. It stands
|
|
876
|
+
* where a *value* goes and is resolved to one before the row is written, so it
|
|
877
|
+
* is not an operation on a column — reading it as a misspelled operator refused
|
|
878
|
+
* every `$ref` in a batch with "unknown field operator '$ref'".
|
|
879
|
+
*
|
|
880
|
+
* @group Data
|
|
881
|
+
*/
|
|
882
|
+
export declare function isFieldOperation(value: unknown): boolean;
|
|
883
|
+
/** True when any value in a write payload is (or is attempting to be) one. @group Data */
|
|
884
|
+
export declare function hasFieldOperation(values: Record<string, unknown> | undefined): boolean;
|
|
885
|
+
export type FieldOperation =
|
|
886
|
+
/** Add to a `number` column; negative to subtract. `SET col = col + n`. */
|
|
887
|
+
{
|
|
888
|
+
$inc: number;
|
|
889
|
+
}
|
|
890
|
+
/** Append one value, or each of an array of values, to an `array` column. */
|
|
891
|
+
| {
|
|
892
|
+
$push: unknown;
|
|
893
|
+
}
|
|
894
|
+
/** Remove every occurrence of a value from an `array` column. */
|
|
895
|
+
| {
|
|
896
|
+
$pull: unknown;
|
|
897
|
+
}
|
|
898
|
+
/** Shallow-merge an object into a `map` column. `SET col = col || …::jsonb`. */
|
|
899
|
+
| {
|
|
900
|
+
$merge: Record<string, unknown>;
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* The payload {@link SDKCollectionClient.update} accepts: plain values, field
|
|
904
|
+
* operations, or both in one body.
|
|
905
|
+
*
|
|
906
|
+
* @group Data
|
|
907
|
+
*/
|
|
908
|
+
export type UpdateValues<U> = {
|
|
909
|
+
[K in keyof U]?: U[K] | FieldOperation;
|
|
910
|
+
};
|
|
911
|
+
/**
|
|
912
|
+
* Where an upsert looks for the row it might be replacing.
|
|
913
|
+
*
|
|
914
|
+
* The columns must carry a uniqueness guarantee the database can use as an
|
|
915
|
+
* `ON CONFLICT` target — the primary key, a property with
|
|
916
|
+
* `validation.unique`, or the columns of a declared `unique` index. Anything
|
|
917
|
+
* else is refused with a 400 rather than sent to Postgres, which would answer
|
|
918
|
+
* `there is no unique or exclusion constraint matching the ON CONFLICT
|
|
919
|
+
* specification` from inside a transaction that has already done work.
|
|
920
|
+
*
|
|
921
|
+
* @group Data
|
|
922
|
+
*/
|
|
923
|
+
export interface UpsertOptions extends WriteOptions {
|
|
924
|
+
/** Column names forming the conflict target. Defaults to the primary key. */
|
|
925
|
+
onConflict?: readonly string[];
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* A placeholder standing for a value only the server will know: the id of a row
|
|
929
|
+
* an earlier operation in the same batch creates.
|
|
930
|
+
*
|
|
931
|
+
* `{ "$ref": "order.id" }` reads the field `id` off the result of the operation
|
|
932
|
+
* that named itself `ref: "order"`. Without it a batch cannot express the one
|
|
933
|
+
* thing a cross-collection batch exists for — writing a parent and its children
|
|
934
|
+
* together — because the child's foreign key is not knowable until the parent
|
|
935
|
+
* has been inserted, and splitting the two into separate requests is exactly
|
|
936
|
+
* the non-atomic sequence the batch replaces.
|
|
937
|
+
*
|
|
938
|
+
* Only backward references resolve. `ref` names must be unique within a batch,
|
|
939
|
+
* and an operation may not reference itself or anything after it.
|
|
940
|
+
*
|
|
941
|
+
* @group Data
|
|
942
|
+
*/
|
|
943
|
+
export interface BatchRef {
|
|
944
|
+
/** `<ref name>.<field>`, e.g. `order.id`. */
|
|
945
|
+
$ref: string;
|
|
946
|
+
}
|
|
947
|
+
/** One entry of a batch request. @group Data */
|
|
948
|
+
export type BatchOperation<DB = Record<string, unknown>> = {
|
|
949
|
+
[K in Extract<keyof DB, string>]: {
|
|
950
|
+
op: "create";
|
|
951
|
+
collection: K;
|
|
952
|
+
values: {
|
|
953
|
+
[F in keyof InsertOf<DB[K]>]?: InsertOf<DB[K]>[F] | BatchRef;
|
|
954
|
+
} & Record<string, unknown>;
|
|
955
|
+
/** Name this row so a later operation can reference its columns. */
|
|
956
|
+
ref?: string;
|
|
957
|
+
} | {
|
|
958
|
+
op: "upsert";
|
|
959
|
+
collection: K;
|
|
960
|
+
values: {
|
|
961
|
+
[F in keyof InsertOf<DB[K]>]?: InsertOf<DB[K]>[F] | BatchRef;
|
|
962
|
+
} & Record<string, unknown>;
|
|
963
|
+
/** See {@link UpsertOptions.onConflict}. Defaults to the primary key. */
|
|
964
|
+
onConflict?: readonly string[];
|
|
965
|
+
ref?: string;
|
|
966
|
+
} | {
|
|
967
|
+
op: "update";
|
|
968
|
+
collection: K;
|
|
969
|
+
id: string | number | BatchRef;
|
|
970
|
+
values: {
|
|
971
|
+
[F in keyof UpdateOf<DB[K]>]?: UpdateOf<DB[K]>[F] | FieldOperation | BatchRef;
|
|
972
|
+
} & Record<string, unknown>;
|
|
973
|
+
ref?: string;
|
|
974
|
+
} | {
|
|
975
|
+
op: "delete";
|
|
976
|
+
collection: K;
|
|
977
|
+
id: string | number | BatchRef;
|
|
978
|
+
ref?: string;
|
|
979
|
+
};
|
|
980
|
+
}[Extract<keyof DB, string>];
|
|
981
|
+
/**
|
|
982
|
+
* What `POST /api/data/_batch` answers with.
|
|
983
|
+
*
|
|
984
|
+
* `data` is aligned to `operations`: the written row for a create, upsert or
|
|
985
|
+
* update, and `null` for a delete — so an index into one is an index into the
|
|
986
|
+
* other, whatever the batch mixed.
|
|
987
|
+
*
|
|
988
|
+
* @group Data
|
|
989
|
+
*/
|
|
990
|
+
export interface BatchResult<R = Record<string, unknown>> {
|
|
991
|
+
data: (R | null)[];
|
|
992
|
+
meta: {
|
|
993
|
+
operations: number;
|
|
994
|
+
};
|
|
995
|
+
}
|
|
594
996
|
/**
|
|
595
997
|
* Per-request options for a write.
|
|
596
998
|
* @group Data
|
|
@@ -621,6 +1023,37 @@ export interface WriteOptions {
|
|
|
621
1023
|
* header rather than refusing the write.
|
|
622
1024
|
*/
|
|
623
1025
|
idempotencyKey?: string;
|
|
1026
|
+
/**
|
|
1027
|
+
* Whether the server should send the written row back.
|
|
1028
|
+
*
|
|
1029
|
+
* `false` sends `Prefer: return=minimal`, and the write answers `204 No
|
|
1030
|
+
* Content` — `200` carrying the ids only, for a batch. The row is the
|
|
1031
|
+
* default because it carries what the server decided: a serial id, an
|
|
1032
|
+
* `autoValue` timestamp, whatever `beforeSave` rewrote. A caller that
|
|
1033
|
+
* needs none of that is paying for a full row serialisation and, on
|
|
1034
|
+
* Postgres, a read-back per written row.
|
|
1035
|
+
*
|
|
1036
|
+
* Reach for it on imports and fire-and-forget writes. The method resolves
|
|
1037
|
+
* to `undefined` (or `[]`) when it is set, so a caller cannot accidentally
|
|
1038
|
+
* use a row the server never sent.
|
|
1039
|
+
*/
|
|
1040
|
+
returning?: boolean;
|
|
1041
|
+
/**
|
|
1042
|
+
* The version of the row this write was made against, so it is refused if
|
|
1043
|
+
* the row has moved on.
|
|
1044
|
+
*
|
|
1045
|
+
* The `ETag` from the read that produced the row — `etagOf(row)` on a row
|
|
1046
|
+
* from `findById`, or the `ETag` response header. A mismatch answers `412`
|
|
1047
|
+
* rather than writing, which is the difference between "update the row I
|
|
1048
|
+
* read" and "overwrite whatever is there now". Without it a read, an edit
|
|
1049
|
+
* and a write is last-writer-wins over everything the write did not send,
|
|
1050
|
+
* and the loser is told nothing.
|
|
1051
|
+
*
|
|
1052
|
+
* `"*"` asserts only that the row exists.
|
|
1053
|
+
*
|
|
1054
|
+
* Honoured on `update` and `delete`.
|
|
1055
|
+
*/
|
|
1056
|
+
ifMatch?: string;
|
|
624
1057
|
}
|
|
625
1058
|
export interface SDKCollectionClient<M extends Record<string, unknown> = Record<string, unknown>, I = Partial<M>, U = Partial<M>> {
|
|
626
1059
|
/**
|
|
@@ -787,6 +1220,7 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
|
|
|
787
1220
|
*/
|
|
788
1221
|
createMany(data: I[], options?: {
|
|
789
1222
|
upsert?: boolean;
|
|
1223
|
+
onConflict?: readonly string[];
|
|
790
1224
|
} & WriteOptions): Promise<M[]>;
|
|
791
1225
|
/**
|
|
792
1226
|
* Update an existing record by ID.
|
|
@@ -802,7 +1236,37 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
|
|
|
802
1236
|
* that retry from a second deliberate edit — which on a `PATCH` that
|
|
803
1237
|
* increments or appends is a second edit applied.
|
|
804
1238
|
*/
|
|
805
|
-
update(id: string | number, data: U
|
|
1239
|
+
update(id: string | number, data: U | UpdateValues<U>, options?: WriteOptions): Promise<M>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Insert the row, or replace the one already occupying its key.
|
|
1242
|
+
*
|
|
1243
|
+
* `INSERT ... ON CONFLICT DO UPDATE`, in one statement — so unlike a
|
|
1244
|
+
* `findById` followed by `create`-or-`update` it cannot lose the race
|
|
1245
|
+
* between the two, and unlike `create` it does not fail when the row is
|
|
1246
|
+
* already there. That is what makes a re-runnable import idempotent
|
|
1247
|
+
* without a key.
|
|
1248
|
+
*
|
|
1249
|
+
* The conflict target defaults to the primary key. Pass `onConflict` to
|
|
1250
|
+
* upsert on a natural key instead — `["email"]`, `["tenant_id", "slug"]` —
|
|
1251
|
+
* and the columns must carry a uniqueness guarantee the database can use:
|
|
1252
|
+
* a property with `validation.unique`, or the columns of a declared
|
|
1253
|
+
* `unique` index. Anything else is a 400 rather than a Postgres error
|
|
1254
|
+
* raised half-way through a transaction.
|
|
1255
|
+
*
|
|
1256
|
+
* The `on_create` timestamp of a row that already existed is left alone: a
|
|
1257
|
+
* conflict means the row's creation is a fact about the past, and a nightly
|
|
1258
|
+
* re-import that reset `createdAt` on everything it touched would take
|
|
1259
|
+
* every "new this week" query with it.
|
|
1260
|
+
*
|
|
1261
|
+
* @example
|
|
1262
|
+
* ```ts
|
|
1263
|
+
* await client.data.users.upsert(
|
|
1264
|
+
* { email: "a@b.c", name: "Ada" },
|
|
1265
|
+
* { onConflict: ["email"] }
|
|
1266
|
+
* );
|
|
1267
|
+
* ```
|
|
1268
|
+
*/
|
|
1269
|
+
upsert(data: I, options?: UpsertOptions): Promise<M>;
|
|
806
1270
|
/**
|
|
807
1271
|
* Update many records in a single request and a single transaction.
|
|
808
1272
|
*
|
|
@@ -843,13 +1307,19 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
|
|
|
843
1307
|
*/
|
|
844
1308
|
updateMany(updates: {
|
|
845
1309
|
id: string | number;
|
|
846
|
-
data: U
|
|
1310
|
+
data: U | UpdateValues<U>;
|
|
847
1311
|
}[], options?: WriteOptions): Promise<M[]>;
|
|
848
1312
|
/**
|
|
849
1313
|
* Delete a record by ID.
|
|
850
1314
|
* @throws {RebaseApiError} with status 404 when the record does not exist.
|
|
1315
|
+
*
|
|
1316
|
+
* Takes {@link WriteOptions} like every other write. It did not, so the one
|
|
1317
|
+
* mutation that cannot be made safe by repeating it — a delete replayed
|
|
1318
|
+
* after the row is gone answers 404, which an offline queue reads as a
|
|
1319
|
+
* permanent failure — was also the one that could not carry an
|
|
1320
|
+
* `idempotencyKey`.
|
|
851
1321
|
*/
|
|
852
|
-
delete(id: string | number): Promise<void>;
|
|
1322
|
+
delete(id: string | number, options?: WriteOptions): Promise<void>;
|
|
853
1323
|
/**
|
|
854
1324
|
* Delete many records in a single request and a single transaction.
|
|
855
1325
|
*
|
|
@@ -907,11 +1377,36 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
|
|
|
907
1377
|
* it does instead.
|
|
908
1378
|
*/
|
|
909
1379
|
count(params?: FindParams<M>): Promise<number>;
|
|
1380
|
+
/**
|
|
1381
|
+
* `count`/`sum`/`avg`/`min`/`max` over the matching rows, optionally
|
|
1382
|
+
* grouped — the SDK half of `GET /<collection>/aggregate`.
|
|
1383
|
+
*
|
|
1384
|
+
* The whole point is not to fetch rows in order to reduce them: "revenue by
|
|
1385
|
+
* status" over a million orders is one query and one row per status here,
|
|
1386
|
+
* and a `findAll()` plus a loop everywhere else — which is wrong under a
|
|
1387
|
+
* `limit` and unaffordable without one. It runs through the same
|
|
1388
|
+
* request-scoped handle as every other read, so RLS applies to the rows
|
|
1389
|
+
* being aggregated.
|
|
1390
|
+
*
|
|
1391
|
+
* ```ts
|
|
1392
|
+
* const rows = await rebase.data.orders.aggregate({
|
|
1393
|
+
* select: [{ fn: "sum", field: "total" }, { fn: "count" }],
|
|
1394
|
+
* groupBy: ["status"],
|
|
1395
|
+
* where: { created_at: [">=", startOfMonth] }
|
|
1396
|
+
* });
|
|
1397
|
+
* // [{ status: "paid", sum_total: 41822.5, count: 317 }, …]
|
|
1398
|
+
* ```
|
|
1399
|
+
*
|
|
1400
|
+
* Always present; a backend whose driver cannot aggregate answers 501
|
|
1401
|
+
* naming the capability rather than an empty result set, which would read
|
|
1402
|
+
* as "nothing matched".
|
|
1403
|
+
*/
|
|
1404
|
+
aggregate(params: AggregateParams<M>): Promise<AggregateRow[]>;
|
|
910
1405
|
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): SDKQueryBuilderInterface<M>;
|
|
911
1406
|
/** A relation path (`author.name`) or a JSON path (`metadata->>tier`). */
|
|
912
1407
|
where(column: NonColumnFieldPath, operator: WhereFilterOp, value: unknown): SDKQueryBuilderInterface<M>;
|
|
913
1408
|
where(logicalCondition: LogicalCondition): SDKQueryBuilderInterface<M>;
|
|
914
|
-
orderBy(column: FieldPath<M> | ComputedSortField | RelationAggregateSort, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
|
|
1409
|
+
orderBy(column: FieldPath<M> | ComputedSortField | RelationAggregateSort, direction?: "asc" | "desc", nulls?: NullsPlacement): SDKQueryBuilderInterface<M>;
|
|
915
1410
|
limit(count: number): SDKQueryBuilderInterface<M>;
|
|
916
1411
|
offset(count: number): SDKQueryBuilderInterface<M>;
|
|
917
1412
|
search(searchString: string, options?: {
|
|
@@ -926,7 +1421,13 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
|
|
|
926
1421
|
distance?: "cosine" | "l2" | "inner_product";
|
|
927
1422
|
threshold?: number;
|
|
928
1423
|
}): SDKQueryBuilderInterface<M>;
|
|
929
|
-
include(...relations: string[]): SDKQueryBuilderInterface<M>;
|
|
1424
|
+
include(...relations: (string | IncludeSpec)[]): SDKQueryBuilderInterface<M>;
|
|
1425
|
+
/** {@link SDKQueryBuilderInterface.fields} */
|
|
1426
|
+
fields(...columns: (FieldPath<M> | string)[]): SDKQueryBuilderInterface<M>;
|
|
1427
|
+
/** {@link SDKQueryBuilderInterface.distinct} */
|
|
1428
|
+
distinct(enabled?: boolean): SDKQueryBuilderInterface<M>;
|
|
1429
|
+
/** {@link SDKQueryBuilderInterface.after} */
|
|
1430
|
+
after(cursor: string): SDKQueryBuilderInterface<M>;
|
|
930
1431
|
}
|
|
931
1432
|
/**
|
|
932
1433
|
* The unified data access object for the **admin panel** (Entity-shaped).
|