@rebasepro/types 0.14.0 → 0.14.1-canary.g932d14c

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.
@@ -1,4 +1,5 @@
1
1
  import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
2
+ import type { OrderByTuple } from "./filter-operators";
2
3
  import type { LogicalCondition } from "../controllers/data";
3
4
  import type { AuthAdapter } from "./auth_adapter";
4
5
  import type { HistoryConfig } from "../controllers/client";
@@ -34,7 +35,8 @@ export interface QueryFilter {
34
35
  */
35
36
  export interface FetchCollectionOptions<M extends Record<string, unknown> = Record<string, unknown>> {
36
37
  filter?: FilterValues<Extract<keyof M, string>>;
37
- orderBy?: string;
38
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
39
+ orderBy?: string | OrderByTuple[];
38
40
  order?: "desc" | "asc";
39
41
  limit?: number;
40
42
  offset?: number;
@@ -48,7 +50,8 @@ export interface FetchCollectionOptions<M extends Record<string, unknown> = Reco
48
50
  */
49
51
  export interface SearchOptions<M extends Record<string, unknown> = Record<string, unknown>> {
50
52
  filter?: FilterValues<Extract<keyof M, string>>;
51
- orderBy?: string;
53
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
54
+ orderBy?: string | OrderByTuple[];
52
55
  order?: "desc" | "asc";
53
56
  limit?: number;
54
57
  databaseId?: string;
@@ -156,7 +159,25 @@ export interface CollectionSubscriptionConfig {
156
159
  clientId: string;
157
160
  path: string;
158
161
  filter?: unknown;
159
- orderBy?: string;
162
+ /**
163
+ * An `or(...)`/`and(...)` group, applied alongside `filter`.
164
+ *
165
+ * Declared here because a subscription is a query, and every field a query
166
+ * has this one needs too. It was missing, so the type-checked boundary
167
+ * dropped it: the client sent the group, nothing rejected it, and the
168
+ * subscription re-fetched with the group gone — pushing every row the
169
+ * caller's policies allowed rather than the ones they asked for. The same
170
+ * defect `FetchCollectionProps.logical` documents, one layer up.
171
+ */
172
+ logical?: LogicalCondition;
173
+ /**
174
+ * Where the subscription's page starts. Missing for the same reason, with
175
+ * a quieter symptom: a subscriber watching page two was pushed page one,
176
+ * and a `collection_update` frame carries no window for it to notice with.
177
+ */
178
+ offset?: number;
179
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
180
+ orderBy?: string | OrderByTuple[];
160
181
  order?: "desc" | "asc";
161
182
  limit?: number;
162
183
  startAfter?: unknown;
@@ -617,8 +638,14 @@ export interface BackendBootstrapper {
617
638
  * enum types, and never drop, narrow or rewrite anything. This runs
618
639
  * unattended against live customer data with nobody reading a diff, so the
619
640
  * destructive half stays a deliberate migration.
641
+ *
642
+ * `driverResult` is optional: this runs before `initializeDriver`, and only
643
+ * the bundle path has a pre-init stand-in to pass. An adapter built by an
644
+ * application already holds its own connection and MUST use it when this is
645
+ * `undefined` — dereferencing it unconditionally works for managed tenants
646
+ * and breaks every app that builds its own adapter.
620
647
  */
621
- ensureCollectionSchema?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
648
+ ensureCollectionSchema?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
622
649
  applied: number;
623
650
  }>;
624
651
  /**
@@ -637,7 +664,7 @@ export interface BackendBootstrapper {
637
664
  * Runs after auth initialization, because the generated policies call the
638
665
  * `auth.*` helper functions and `CREATE POLICY` validates they exist.
639
666
  */
640
- ensureCollectionPolicies?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
667
+ ensureCollectionPolicies?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
641
668
  applied: number;
642
669
  }>;
643
670
  /**
@@ -84,8 +84,16 @@ export interface DatabaseAdapter {
84
84
  * into a `BackendBootstrapper`: the runtime calls it through the bootstrapper
85
85
  * at boot, and a wrapper that silently omits it leaves a managed tenant
86
86
  * 500ing every data route with no create step ever having run.
87
+ *
88
+ * `driverResult` is optional because this runs BEFORE `initializeDriver`, so
89
+ * there may be no result to pass. The bundle path can supply a pre-init
90
+ * stand-in because the coordinator opened the connection itself; an app that
91
+ * constructed this adapter never handed the framework a connection handle,
92
+ * so it passes `undefined` and the adapter MUST fall back to the connection
93
+ * it was constructed with. An adapter that dereferences `driverResult`
94
+ * unconditionally works for managed tenants and breaks every self-built one.
87
95
  */
88
- ensureCollectionSchema?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
96
+ ensureCollectionSchema?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
89
97
  applied: number;
90
98
  }>;
91
99
  /**
@@ -98,7 +106,7 @@ export interface DatabaseAdapter {
98
106
  * policies, tables exist but every user-context read is denied (a public
99
107
  * collection answers 401).
100
108
  */
101
- ensureCollectionPolicies?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
109
+ ensureCollectionPolicies?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
102
110
  applied: number;
103
111
  }>;
104
112
  /**
@@ -147,4 +155,25 @@ export interface DatabaseAdapterInitConfig {
147
155
  baas?: {
148
156
  unprotectedTables?: "exclude" | "serve";
149
157
  };
158
+ /**
159
+ * What the runtime's boot-time table provisioning did, in this process,
160
+ * before this driver was initialized.
161
+ *
162
+ * A driver that checks for missing tables cannot otherwise tell "the create
163
+ * step ran and this table still is not here" from "no create step ran at
164
+ * all" — and those need opposite advice. The Postgres driver's drift warning
165
+ * assumed the first, told operators to redeploy with REBASE_MIGRATE_ON_BOOT
166
+ * unset, and pointed at driver version skew; for an app whose boot path had
167
+ * no provisioning step, all of that was unactionable and one investigation
168
+ * chased a driver that was perfectly current.
169
+ *
170
+ * Absent when the caller predates this field: treat that as "unknown" and
171
+ * fall back to generic guidance rather than asserting either case.
172
+ */
173
+ schemaProvisioning?: {
174
+ /** Whether the table-creation hook actually ran this boot. */
175
+ attempted: boolean;
176
+ /** Why it did not, when it did not — safe to print verbatim. */
177
+ reason?: string;
178
+ };
150
179
  }
@@ -44,12 +44,28 @@
44
44
  * boundary, handled by `serializeOrderBy` / `deserializeOrderBy` in
45
45
  * `@rebasepro/common`.
46
46
  *
47
- * Design note: the natural extension for multi-column sort is
48
- * `OrderByTuple[]` — not implemented yet (server consumes only the first).
49
- *
50
47
  * @group Models
51
48
  */
52
49
  export type OrderByTuple<Key extends string = string> = [Key, "asc" | "desc"];
50
+ /**
51
+ * One sort key, or several applied in order of significance.
52
+ *
53
+ * ```ts
54
+ * orderBy: ["created_at", "desc"] // one key
55
+ * orderBy: [["roles", "asc"], ["created_at", "desc"]] // roles, then newest first
56
+ * ```
57
+ *
58
+ * The two forms are told apart by whether the first element is itself an
59
+ * array, so a single tuple never needs wrapping and every existing caller
60
+ * keeps working unchanged. `normalizeOrderBy` in `@rebasepro/common` collapses
61
+ * both to the list form, which is what every layer below the call site speaks.
62
+ *
63
+ * Ties on the last key are broken by the row id, so a multi-key sort is a
64
+ * total order and pages over it neither repeat nor skip rows.
65
+ *
66
+ * @group Models
67
+ */
68
+ export type OrderBySpec<Key extends string = string> = OrderByTuple<Key> | OrderByTuple<Key>[];
53
69
  /**
54
70
  * Canonical filter operators supported across all database backends.
55
71
  * Each DB driver translates these to its native query format.
@@ -133,8 +149,9 @@ export interface FilterPreset<Key extends string = string> {
133
149
  filterValues: FilterValues<Key>;
134
150
  /**
135
151
  * Optional sort override to apply alongside the filter values.
152
+ * One key, or several in order of significance.
136
153
  */
137
- sort?: OrderByTuple<Key>;
154
+ sort?: OrderBySpec<Key>;
138
155
  }
139
156
  /**
140
157
  * PostgREST short-code operators. Wire format only — these never appear
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
3
  "type": "module",
4
- "version": "0.14.0",
4
+ "version": "0.14.1-canary.g932d14c",
5
5
  "description": "Rebase type definitions — shared interfaces and controller types",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -1,7 +1,7 @@
1
1
  import type { VectorSearchParams } from "./data_driver";
2
2
  import type { ComputedSortField, SearchMatch } from "../types/search";
3
3
  import { Entity, EntityValues } from "../types/entities";
4
- import { WhereFilterOp, FieldPath, FilterValues, OrderByTuple } from "../types/filter-operators";
4
+ import { WhereFilterOp, FieldPath, FilterValues, OrderBySpec } from "../types/filter-operators";
5
5
 
6
6
  /**
7
7
  * Operator-blind filter value: whatever the column holds, a list of it, or null.
@@ -160,10 +160,13 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
160
160
  */
161
161
  logical?: LogicalCondition;
162
162
  /**
163
- * Sort order as a `[field, direction]` tuple.
163
+ * Sort order as a `[field, direction]` tuple, or a list of them applied in
164
+ * order of significance — the second key breaks ties on the first, and so on.
165
+ *
164
166
  * @example orderBy: ["created_at", "desc"]
167
+ * @example orderBy: [["roles", "asc"], ["created_at", "desc"]]
165
168
  */
166
- orderBy?: OrderByTuple<FieldPath<M> | ComputedSortField>;
169
+ orderBy?: OrderBySpec<FieldPath<M> | ComputedSortField>;
167
170
  /**
168
171
  * Relations to include in the response.
169
172
  *
@@ -2,6 +2,7 @@ import { RebaseApiError } from "../errors";
2
2
  import type { CollectionRegistryController } from "./collection_registry";
3
3
  import type { EntityStatus, EntityValues } from "../types/entities";
4
4
  import type { CollectionConfig, FilterValues } from "../types/collections";
5
+ import type { OrderByTuple } from "../types/filter-operators";
5
6
  import type { RebaseCallContext } from "../call_context";
6
7
  import type { LogicalCondition } from "./data";
7
8
 
@@ -167,10 +168,25 @@ export interface FetchCollectionProps<M extends Record<string, unknown> = Record
167
168
  limit?: number;
168
169
  offset?: number;
169
170
  startAfter?: unknown;
170
- orderBy?: string;
171
+ /**
172
+ * The sort, in either of two spellings:
173
+ *
174
+ * - a field name, whose direction is the separate `order` below — the
175
+ * original single-column contract, which every existing driver reads;
176
+ * - a list of `[field, direction]` tuples applied in order of significance,
177
+ * which carries a multi-column sort and ignores `order` entirely.
178
+ *
179
+ * `normalizeDriverOrderBy` in `@rebasepro/common` collapses the pair to the
180
+ * list form. A driver that has not been taught the list form should read it
181
+ * through that helper rather than assume a string: handed an array, `String()`
182
+ * would produce a field name like `roles,asc` and the sort would 400 (or,
183
+ * with unknown-field warnings on, silently vanish).
184
+ */
185
+ orderBy?: string | OrderByTuple[];
171
186
  searchString?: string;
172
187
  /** Ask each row which declared search field matched — populates `_matches`. */
173
188
  searchExplain?: boolean;
189
+ /** Direction for the string form of `orderBy`. Ignored when `orderBy` is a list. */
174
190
  order?: "desc" | "asc";
175
191
  /** Vector similarity search configuration */
176
192
  vectorSearch?: VectorSearchParams;
@@ -508,7 +524,8 @@ export interface RestFetchService {
508
524
  filter?: FilterValues<string>;
509
525
  /** An `or(...)`/`and(...)` group, applied alongside `filter`. */
510
526
  logical?: LogicalCondition;
511
- orderBy?: string;
527
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
528
+ orderBy?: string | OrderByTuple[];
512
529
  order?: "desc" | "asc";
513
530
  limit?: number;
514
531
  offset?: number;
@@ -522,6 +539,31 @@ export interface RestFetchService {
522
539
  include?: string[]
523
540
  ): Promise<Record<string, unknown>[]>;
524
541
 
542
+ /**
543
+ * `count`/`sum`/`avg`/`min`/`max` over the rows a filter selects,
544
+ * optionally grouped.
545
+ *
546
+ * Optional, and the REST route answers 501 where a driver does not
547
+ * implement it — an aggregate is not a thing to approximate, and an empty
548
+ * result set would read as "nothing matched".
549
+ *
550
+ * Any implementation **must apply the same row-level authorization as a
551
+ * read**. An aggregate is an efficient way to learn about rows you cannot
552
+ * select, and `count(*)` over a table whose policies would return nothing
553
+ * has to be zero.
554
+ */
555
+ aggregate?(
556
+ collectionPath: string,
557
+ options: {
558
+ aggregates: { fn: "count" | "sum" | "avg" | "min" | "max"; field?: string; alias: string }[];
559
+ groupBy?: string[];
560
+ filter?: FilterValues<string>;
561
+ logical?: LogicalCondition;
562
+ searchString?: string;
563
+ limit?: number;
564
+ }
565
+ ): Promise<Record<string, unknown>[]>;
566
+
525
567
  /**
526
568
  * Fetch a single flattened entity with optional relation includes.
527
569
  */
@@ -1,4 +1,5 @@
1
1
  import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
2
+ import type { OrderByTuple } from "./filter-operators";
2
3
  import type { LogicalCondition } from "../controllers/data";
3
4
  import type { AuthAdapter } from "./auth_adapter";
4
5
  import type { HistoryConfig } from "../controllers/client";
@@ -47,7 +48,8 @@ export interface QueryFilter {
47
48
  */
48
49
  export interface FetchCollectionOptions<M extends Record<string, unknown> = Record<string, unknown>> {
49
50
  filter?: FilterValues<Extract<keyof M, string>>;
50
- orderBy?: string;
51
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
52
+ orderBy?: string | OrderByTuple[];
51
53
  order?: "desc" | "asc";
52
54
  limit?: number;
53
55
  offset?: number;
@@ -62,7 +64,8 @@ export interface FetchCollectionOptions<M extends Record<string, unknown> = Reco
62
64
  */
63
65
  export interface SearchOptions<M extends Record<string, unknown> = Record<string, unknown>> {
64
66
  filter?: FilterValues<Extract<keyof M, string>>;
65
- orderBy?: string;
67
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
68
+ orderBy?: string | OrderByTuple[];
66
69
  order?: "desc" | "asc";
67
70
  limit?: number;
68
71
  databaseId?: string;
@@ -237,7 +240,25 @@ export interface CollectionSubscriptionConfig {
237
240
  clientId: string;
238
241
  path: string;
239
242
  filter?: unknown;
240
- orderBy?: string;
243
+ /**
244
+ * An `or(...)`/`and(...)` group, applied alongside `filter`.
245
+ *
246
+ * Declared here because a subscription is a query, and every field a query
247
+ * has this one needs too. It was missing, so the type-checked boundary
248
+ * dropped it: the client sent the group, nothing rejected it, and the
249
+ * subscription re-fetched with the group gone — pushing every row the
250
+ * caller's policies allowed rather than the ones they asked for. The same
251
+ * defect `FetchCollectionProps.logical` documents, one layer up.
252
+ */
253
+ logical?: LogicalCondition;
254
+ /**
255
+ * Where the subscription's page starts. Missing for the same reason, with
256
+ * a quieter symptom: a subscriber watching page two was pushed page one,
257
+ * and a `collection_update` frame carries no window for it to notice with.
258
+ */
259
+ offset?: number;
260
+ /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
261
+ orderBy?: string | OrderByTuple[];
241
262
  order?: "desc" | "asc";
242
263
  limit?: number;
243
264
  startAfter?: unknown;
@@ -801,10 +822,16 @@ export interface BackendBootstrapper {
801
822
  * enum types, and never drop, narrow or rewrite anything. This runs
802
823
  * unattended against live customer data with nobody reading a diff, so the
803
824
  * destructive half stays a deliberate migration.
825
+ *
826
+ * `driverResult` is optional: this runs before `initializeDriver`, and only
827
+ * the bundle path has a pre-init stand-in to pass. An adapter built by an
828
+ * application already holds its own connection and MUST use it when this is
829
+ * `undefined` — dereferencing it unconditionally works for managed tenants
830
+ * and breaks every app that builds its own adapter.
804
831
  */
805
832
  ensureCollectionSchema?(
806
833
  collections: unknown[],
807
- driverResult: InitializedDriver,
834
+ driverResult?: InitializedDriver,
808
835
  log?: (message: string) => void
809
836
  ): Promise<{ applied: number }>;
810
837
 
@@ -826,7 +853,7 @@ export interface BackendBootstrapper {
826
853
  */
827
854
  ensureCollectionPolicies?(
828
855
  collections: unknown[],
829
- driverResult: InitializedDriver,
856
+ driverResult?: InitializedDriver,
830
857
  log?: (message: string) => void
831
858
  ): Promise<{ applied: number }>;
832
859
 
@@ -108,10 +108,18 @@ export interface DatabaseAdapter {
108
108
  * into a `BackendBootstrapper`: the runtime calls it through the bootstrapper
109
109
  * at boot, and a wrapper that silently omits it leaves a managed tenant
110
110
  * 500ing every data route with no create step ever having run.
111
+ *
112
+ * `driverResult` is optional because this runs BEFORE `initializeDriver`, so
113
+ * there may be no result to pass. The bundle path can supply a pre-init
114
+ * stand-in because the coordinator opened the connection itself; an app that
115
+ * constructed this adapter never handed the framework a connection handle,
116
+ * so it passes `undefined` and the adapter MUST fall back to the connection
117
+ * it was constructed with. An adapter that dereferences `driverResult`
118
+ * unconditionally works for managed tenants and breaks every self-built one.
111
119
  */
112
120
  ensureCollectionSchema?(
113
121
  collections: unknown[],
114
- driverResult: InitializedDriver,
122
+ driverResult?: InitializedDriver,
115
123
  log?: (message: string) => void,
116
124
  ): Promise<{ applied: number }>;
117
125
 
@@ -127,7 +135,7 @@ export interface DatabaseAdapter {
127
135
  */
128
136
  ensureCollectionPolicies?(
129
137
  collections: unknown[],
130
- driverResult: InitializedDriver,
138
+ driverResult?: InitializedDriver,
131
139
  log?: (message: string) => void,
132
140
  ): Promise<{ applied: number }>;
133
141
 
@@ -178,4 +186,25 @@ export interface DatabaseAdapterInitConfig {
178
186
  * Drivers that introspect should honour `unprotectedTables`.
179
187
  */
180
188
  baas?: { unprotectedTables?: "exclude" | "serve" };
189
+ /**
190
+ * What the runtime's boot-time table provisioning did, in this process,
191
+ * before this driver was initialized.
192
+ *
193
+ * A driver that checks for missing tables cannot otherwise tell "the create
194
+ * step ran and this table still is not here" from "no create step ran at
195
+ * all" — and those need opposite advice. The Postgres driver's drift warning
196
+ * assumed the first, told operators to redeploy with REBASE_MIGRATE_ON_BOOT
197
+ * unset, and pointed at driver version skew; for an app whose boot path had
198
+ * no provisioning step, all of that was unactionable and one investigation
199
+ * chased a driver that was perfectly current.
200
+ *
201
+ * Absent when the caller predates this field: treat that as "unknown" and
202
+ * fall back to generic guidance rather than asserting either case.
203
+ */
204
+ schemaProvisioning?: {
205
+ /** Whether the table-creation hook actually ran this boot. */
206
+ attempted: boolean;
207
+ /** Why it did not, when it did not — safe to print verbatim. */
208
+ reason?: string;
209
+ };
181
210
  }
@@ -45,13 +45,30 @@
45
45
  * boundary, handled by `serializeOrderBy` / `deserializeOrderBy` in
46
46
  * `@rebasepro/common`.
47
47
  *
48
- * Design note: the natural extension for multi-column sort is
49
- * `OrderByTuple[]` — not implemented yet (server consumes only the first).
50
- *
51
48
  * @group Models
52
49
  */
53
50
  export type OrderByTuple<Key extends string = string> = [Key, "asc" | "desc"];
54
51
 
52
+ /**
53
+ * One sort key, or several applied in order of significance.
54
+ *
55
+ * ```ts
56
+ * orderBy: ["created_at", "desc"] // one key
57
+ * orderBy: [["roles", "asc"], ["created_at", "desc"]] // roles, then newest first
58
+ * ```
59
+ *
60
+ * The two forms are told apart by whether the first element is itself an
61
+ * array, so a single tuple never needs wrapping and every existing caller
62
+ * keeps working unchanged. `normalizeOrderBy` in `@rebasepro/common` collapses
63
+ * both to the list form, which is what every layer below the call site speaks.
64
+ *
65
+ * Ties on the last key are broken by the row id, so a multi-key sort is a
66
+ * total order and pages over it neither repeat nor skip rows.
67
+ *
68
+ * @group Models
69
+ */
70
+ export type OrderBySpec<Key extends string = string> = OrderByTuple<Key> | OrderByTuple<Key>[];
71
+
55
72
  /**
56
73
  * Canonical filter operators supported across all database backends.
57
74
  * Each DB driver translates these to its native query format.
@@ -161,8 +178,9 @@ export interface FilterPreset<Key extends string = string> {
161
178
 
162
179
  /**
163
180
  * Optional sort override to apply alongside the filter values.
181
+ * One key, or several in order of significance.
164
182
  */
165
- sort?: OrderByTuple<Key>;
183
+ sort?: OrderBySpec<Key>;
166
184
  }
167
185
 
168
186
  /**