@rebasepro/types 0.17.3 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/call_context.d.ts +20 -0
- package/dist/controllers/client.d.ts +36 -4
- package/dist/controllers/data.d.ts +120 -10
- package/dist/errors.d.ts +83 -4
- package/dist/index.es.js +522 -160
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +2 -2
- package/dist/types/auth_adapter.d.ts +41 -6
- package/dist/types/backend.d.ts +48 -0
- package/dist/types/collections.d.ts +25 -1
- package/dist/types/cron.d.ts +34 -0
- package/dist/types/database_adapter.d.ts +39 -0
- package/dist/types/entity_callbacks.d.ts +14 -1
- package/dist/types/filter-operators.d.ts +24 -1
- package/dist/types/policy.d.ts +29 -1
- package/dist/types/properties.d.ts +216 -3
- package/dist/types/relations.d.ts +65 -7
- package/dist/types/resource_kinds.d.ts +173 -17
- package/dist/types/resources.d.ts +108 -7
- package/dist/types/rls-functions.d.ts +11 -0
- package/dist/types/storage_source.d.ts +12 -23
- package/package.json +24 -23
- package/src/call_context.ts +0 -120
- package/src/controllers/auth_state.ts +0 -24
- package/src/controllers/client.ts +0 -494
- package/src/controllers/collection_registry.ts +0 -62
- package/src/controllers/data.ts +0 -1012
- package/src/controllers/data_driver.ts +0 -576
- package/src/controllers/effective_role.ts +0 -4
- package/src/controllers/email.ts +0 -91
- package/src/controllers/index.ts +0 -11
- package/src/controllers/storage.ts +0 -252
- package/src/errors.ts +0 -119
- package/src/index.ts +0 -5
- package/src/types/admin_block.ts +0 -209
- package/src/types/api_keys.ts +0 -108
- package/src/types/auth_adapter.ts +0 -580
- package/src/types/backend.ts +0 -987
- package/src/types/backup.ts +0 -26
- package/src/types/channel_bus.ts +0 -202
- package/src/types/chips.ts +0 -34
- package/src/types/collection_contract.ts +0 -278
- package/src/types/collections.ts +0 -763
- package/src/types/component_ref.ts +0 -92
- package/src/types/cron.ts +0 -213
- package/src/types/data_source.ts +0 -357
- package/src/types/database_adapter.ts +0 -267
- package/src/types/entities.ts +0 -226
- package/src/types/entity_callbacks.ts +0 -229
- package/src/types/filter-operators.ts +0 -444
- package/src/types/history.ts +0 -66
- package/src/types/index.ts +0 -36
- package/src/types/indexes.ts +0 -180
- package/src/types/policy.ts +0 -328
- package/src/types/postgres_introspection.ts +0 -101
- package/src/types/project_manifest.ts +0 -598
- package/src/types/properties.ts +0 -1368
- package/src/types/relations.ts +0 -417
- package/src/types/resource_kinds.ts +0 -390
- package/src/types/resources.ts +0 -368
- package/src/types/rls-functions.ts +0 -98
- package/src/types/schema_editing.ts +0 -157
- package/src/types/schema_version.ts +0 -112
- package/src/types/search.ts +0 -247
- package/src/types/security_rules.ts +0 -344
- package/src/types/storage_authorize.ts +0 -77
- package/src/types/storage_source.ts +0 -248
- package/src/types/websockets.ts +0 -117
- package/src/users/index.ts +0 -2
- package/src/users/user.ts +0 -69
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
import { RebaseApiError } from "../errors";
|
|
2
|
-
import type { CollectionRegistryController } from "./collection_registry";
|
|
3
|
-
import type { EntityStatus, EntityValues } from "../types/entities";
|
|
4
|
-
import type { CollectionConfig, FilterValues } from "../types/collections";
|
|
5
|
-
import type { OrderByTuple } from "../types/filter-operators";
|
|
6
|
-
import type { RebaseCallContext } from "../call_context";
|
|
7
|
-
import type { LogicalCondition } from "./data";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @internal
|
|
12
|
-
*/
|
|
13
|
-
export interface FetchOneProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
14
|
-
path: string;
|
|
15
|
-
id: string | number;
|
|
16
|
-
databaseId?: string;
|
|
17
|
-
collection?: CollectionConfig<M>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @internal
|
|
22
|
-
*/
|
|
23
|
-
export type ListenOneProps<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
24
|
-
FetchOneProps<M>
|
|
25
|
-
& {
|
|
26
|
-
onUpdate: (row: Record<string, unknown> | null) => void,
|
|
27
|
-
onError?: (error: Error) => void,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Configuration for vector similarity search queries.
|
|
32
|
-
* Vector search applies an ORDER BY distance expression and optionally
|
|
33
|
-
* filters results by a distance threshold.
|
|
34
|
-
*/
|
|
35
|
-
export interface VectorSearchParams {
|
|
36
|
-
/** Property name containing the vector column */
|
|
37
|
-
property: string;
|
|
38
|
-
/** Query vector to compare against */
|
|
39
|
-
vector: number[];
|
|
40
|
-
/** Distance function (default: "cosine") */
|
|
41
|
-
distance?: "cosine" | "l2" | "inner_product";
|
|
42
|
-
/** Only return results within this distance threshold */
|
|
43
|
-
threshold?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── List pagination bounds ────────────────────────────────────────────────
|
|
47
|
-
//
|
|
48
|
-
// Client-driven list reads (REST `GET /<collection>` and the WebSocket
|
|
49
|
-
// `subscribe_collection` message) accept a client-supplied `limit`. Without
|
|
50
|
-
// bounds, an ABSENT limit streams the entire table into memory — a trivial
|
|
51
|
-
// OOM/DoS — and `limit=100000000` (or `limit=0`, historically an unlimited
|
|
52
|
-
// bypass) is honoured verbatim. `resolveClientListLimit` is the single shared
|
|
53
|
-
// enforcement point so every untrusted ingress behaves identically. Trusted
|
|
54
|
-
// server-side callers build fetch options directly and are intentionally NOT
|
|
55
|
-
// bounded here (migrations, admin exports, and CDC refetches may need the full
|
|
56
|
-
// set).
|
|
57
|
-
//
|
|
58
|
-
// A limit the platform will not serve is REFUSED, not quietly shrunk. Clamping
|
|
59
|
-
// answers a request for 100 000 rows with 1 000 of them, and a short page is
|
|
60
|
-
// indistinguishable from "that is all the data there is" — which is how a CSV
|
|
61
|
-
// export shipped 50 rows of a 100 000-row collection under a filename that read
|
|
62
|
-
// like the whole thing. `meta.total`/`meta.hasMore` make truncation *detectable*
|
|
63
|
-
// on the REST list response, but only for a caller who thinks to compare what it
|
|
64
|
-
// asked for against what it got, and the WebSocket `collection_update` frame
|
|
65
|
-
// carries neither — so signalling cannot be the answer on every surface and
|
|
66
|
-
// rejecting is. An ABSENT limit still defaults: naming no window is not the same
|
|
67
|
-
// as asking for one that cannot be served.
|
|
68
|
-
|
|
69
|
-
/** Rows returned for a plain / text-search list read when the client sends no `limit`. */
|
|
70
|
-
export const DEFAULT_LIST_LIMIT = 50;
|
|
71
|
-
/** Rows returned for a vector-search list read when the client sends no `limit`. */
|
|
72
|
-
export const DEFAULT_VECTOR_LIST_LIMIT = 10;
|
|
73
|
-
/** Largest `limit` a client may ask for on any surface. Above it, the read is refused. */
|
|
74
|
-
export const MAX_LIST_LIMIT = 1000;
|
|
75
|
-
|
|
76
|
-
/** Overridable bounds for {@link resolveClientListLimit}. */
|
|
77
|
-
export interface ListLimitBounds {
|
|
78
|
-
/** Default page size for plain and text-search reads. */
|
|
79
|
-
defaultLimit?: number;
|
|
80
|
-
/** Default page size for vector-search reads. */
|
|
81
|
-
vectorDefaultLimit?: number;
|
|
82
|
-
/** Largest limit a client may ask for. A larger one is rejected, not clamped. */
|
|
83
|
-
maxLimit?: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Thrown by {@link resolveClientListLimit} for a `limit` the platform will not
|
|
88
|
-
* serve. Carries an HTTP status so an ingress that speaks HTTP can forward it
|
|
89
|
-
* verbatim, and `maxLimit` so one can be built without re-deriving the ceiling.
|
|
90
|
-
*
|
|
91
|
-
* @group Errors
|
|
92
|
-
*/
|
|
93
|
-
export class ListLimitError extends RebaseApiError {
|
|
94
|
-
/** The ceiling that was exceeded — what the caller should page by instead. */
|
|
95
|
-
readonly maxLimit: number;
|
|
96
|
-
|
|
97
|
-
constructor(message: string, maxLimit: number) {
|
|
98
|
-
super(message, { status: 400, code: "INVALID_LIMIT" });
|
|
99
|
-
this.name = "ListLimitError";
|
|
100
|
-
this.maxLimit = maxLimit;
|
|
101
|
-
// Keeps `instanceof` working when this is compiled down for an older
|
|
102
|
-
// target, where extending a builtin otherwise loses the prototype.
|
|
103
|
-
Object.setPrototypeOf(this, ListLimitError.prototype);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Resolve a client-supplied list `limit` into a safe, always-defined value.
|
|
109
|
-
*
|
|
110
|
-
* - An absent / blank limit falls back to the mode default:
|
|
111
|
-
* `vectorDefaultLimit` for a vector search, otherwise `defaultLimit`.
|
|
112
|
-
* - A limit that is present must be an integer in `[1, maxLimit]`. Anything
|
|
113
|
-
* else — `0`, a negative, `1.5`, `abc`, `100000000` — throws
|
|
114
|
-
* {@link ListLimitError} rather than being coerced into range, because every
|
|
115
|
-
* coercion answers a question the caller did not ask with a page it cannot
|
|
116
|
-
* tell apart from the whole collection.
|
|
117
|
-
*
|
|
118
|
-
* The return is never `undefined` — no ingress that routes its client limit
|
|
119
|
-
* through this can produce an unbounded read.
|
|
120
|
-
*
|
|
121
|
-
* @throws {ListLimitError} when a present `limit` is not an integer in range.
|
|
122
|
-
*/
|
|
123
|
-
export function resolveClientListLimit(
|
|
124
|
-
rawLimit: number | string | null | undefined,
|
|
125
|
-
opts: ListLimitBounds & { vectorSearch?: boolean } = {}
|
|
126
|
-
): number {
|
|
127
|
-
const maxLimit = opts.maxLimit ?? MAX_LIST_LIMIT;
|
|
128
|
-
if (rawLimit != null && String(rawLimit).trim() !== "") {
|
|
129
|
-
// `Number`, not `parseInt`: `parseInt("50rows")` is 50, which silently
|
|
130
|
-
// reads a typo as a window the caller never wrote.
|
|
131
|
-
const parsed = typeof rawLimit === "number" ? rawLimit : Number(String(rawLimit).trim());
|
|
132
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
133
|
-
throw new ListLimitError(
|
|
134
|
-
`Invalid \`limit\`: ${String(rawLimit)}. Expected a whole number between 1 and ${maxLimit}.`,
|
|
135
|
-
maxLimit
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
if (parsed > maxLimit) {
|
|
139
|
-
throw new ListLimitError(
|
|
140
|
-
`\`limit\` ${parsed} is above the maximum of ${maxLimit}. Ask for at most ${maxLimit} rows ` +
|
|
141
|
-
"per read and page through the rest with `offset` — answering with a smaller page would be " +
|
|
142
|
-
"indistinguishable from there being no more rows.",
|
|
143
|
-
maxLimit
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
return parsed;
|
|
147
|
-
}
|
|
148
|
-
return opts.vectorSearch
|
|
149
|
-
? (opts.vectorDefaultLimit ?? DEFAULT_VECTOR_LIST_LIMIT)
|
|
150
|
-
: (opts.defaultLimit ?? DEFAULT_LIST_LIMIT);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @internal
|
|
155
|
-
*/
|
|
156
|
-
export interface FetchCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
157
|
-
path: string;
|
|
158
|
-
collection?: CollectionConfig<M>;
|
|
159
|
-
filter?: FilterValues<Extract<keyof M, string>>,
|
|
160
|
-
/**
|
|
161
|
-
* An `or(...)`/`and(...)` group, applied alongside `filter`.
|
|
162
|
-
*
|
|
163
|
-
* The REST layer parsed `?or=` into this and then had nowhere to put it, so
|
|
164
|
-
* the group was dropped and the read ran unfiltered — returning every row
|
|
165
|
-
* the caller's policies allowed rather than the ones they asked for.
|
|
166
|
-
*/
|
|
167
|
-
logical?: LogicalCondition;
|
|
168
|
-
limit?: number;
|
|
169
|
-
offset?: number;
|
|
170
|
-
startAfter?: unknown;
|
|
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[];
|
|
186
|
-
searchString?: string;
|
|
187
|
-
/** Ask each row which declared search field matched — populates `_matches`. */
|
|
188
|
-
searchExplain?: boolean;
|
|
189
|
-
/** Direction for the string form of `orderBy`. Ignored when `orderBy` is a list. */
|
|
190
|
-
order?: "desc" | "asc";
|
|
191
|
-
/** Vector similarity search configuration */
|
|
192
|
-
vectorSearch?: VectorSearchParams;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* @internal
|
|
197
|
-
*/
|
|
198
|
-
export type ListenCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
199
|
-
FetchCollectionProps<M> &
|
|
200
|
-
{
|
|
201
|
-
onUpdate: (rows: Record<string, unknown>[]) => void;
|
|
202
|
-
onError?: (error: Error) => void;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* @internal
|
|
207
|
-
*/
|
|
208
|
-
export interface SaveProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
209
|
-
path: string;
|
|
210
|
-
values: Partial<EntityValues<M>>;
|
|
211
|
-
id?: string | number; // can be empty for new entities
|
|
212
|
-
previousValues?: Partial<EntityValues<M>>;
|
|
213
|
-
collection?: CollectionConfig<M>;
|
|
214
|
-
status: EntityStatus;
|
|
215
|
-
/**
|
|
216
|
-
* Write the row with INSERT ... ON CONFLICT DO UPDATE on the primary key
|
|
217
|
-
* instead of choosing between insert and update up front.
|
|
218
|
-
*
|
|
219
|
-
* One statement, so it does not lose the race a read-then-write can, and it
|
|
220
|
-
* succeeds whether or not the row is already there — what a re-runnable
|
|
221
|
-
* import needs. Requires every primary key column to be present; without
|
|
222
|
-
* them there is no conflict target and the row is inserted normally.
|
|
223
|
-
*/
|
|
224
|
-
upsert?: boolean;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* @internal
|
|
229
|
-
*/
|
|
230
|
-
export interface SaveManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
231
|
-
path: string;
|
|
232
|
-
/**
|
|
233
|
-
* The rows to write. A row carrying its primary key updates (or, with
|
|
234
|
-
* `upsert`, inserts-or-updates) that row; one without inserts.
|
|
235
|
-
*/
|
|
236
|
-
rows: Partial<EntityValues<M>>[];
|
|
237
|
-
collection?: CollectionConfig<M>;
|
|
238
|
-
/** Apply every row as INSERT ... ON CONFLICT DO UPDATE. See {@link SaveProps.upsert}. */
|
|
239
|
-
upsert?: boolean;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @internal
|
|
244
|
-
*/
|
|
245
|
-
export interface UpdateManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
246
|
-
path: string;
|
|
247
|
-
/**
|
|
248
|
-
* The rows to update, each named by its address.
|
|
249
|
-
*
|
|
250
|
-
* Distinct from {@link SaveManyProps.rows}, which carries keys *inside* the
|
|
251
|
-
* values and is insert-shaped — `saveMany` passes `status: "new"` and no
|
|
252
|
-
* `id`, so it cannot express "update exactly this row". This can, and it is
|
|
253
|
-
* why bulk update is a separate driver method rather than a flag on that one.
|
|
254
|
-
*/
|
|
255
|
-
updates: { id: string | number; values: Partial<EntityValues<M>> }[];
|
|
256
|
-
collection?: CollectionConfig<M>;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* @internal
|
|
261
|
-
*/
|
|
262
|
-
export interface DeleteProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
263
|
-
row: { id: string | number; path: string; values?: Partial<EntityValues<M>> };
|
|
264
|
-
collection?: CollectionConfig<M>;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* @internal
|
|
269
|
-
*/
|
|
270
|
-
export interface DeleteManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
271
|
-
path: string;
|
|
272
|
-
ids: (string | number)[];
|
|
273
|
-
collection?: CollectionConfig<M>;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export type FilterCombinationValidProps = {
|
|
277
|
-
path: string;
|
|
278
|
-
databaseId?: string;
|
|
279
|
-
collection: CollectionConfig;
|
|
280
|
-
filterValues: FilterValues<string>;
|
|
281
|
-
sortBy?: [string, "asc" | "desc"];
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* The integration SPI for plugging a data backend into Rebase.
|
|
286
|
-
*
|
|
287
|
-
* Implement this interface to connect a custom backend (or use a built-in
|
|
288
|
-
* driver such as the Firestore one) and register it on
|
|
289
|
-
* `<Rebase dataSources>`. Rebase wraps drivers via `buildRebaseData` and
|
|
290
|
-
* routes collections to them by their `dataSource` key.
|
|
291
|
-
*
|
|
292
|
-
* For *consuming* data in application code, use `RebaseData` /
|
|
293
|
-
* `context.data` instead — this interface is only for providing it.
|
|
294
|
-
*
|
|
295
|
-
* @group Datasource
|
|
296
|
-
*/
|
|
297
|
-
export interface DataDriver {
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Key that identifies this driver
|
|
301
|
-
*/
|
|
302
|
-
key?: string;
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* If the driver has been initialised
|
|
306
|
-
*/
|
|
307
|
-
initialised?: boolean;
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Fetch data from a collection
|
|
311
|
-
* @param props
|
|
312
|
-
* @return Promise of flat rows
|
|
313
|
-
*/
|
|
314
|
-
fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]>;
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Listen to a collection in a given path. If you don't implement this method
|
|
318
|
-
* `fetchCollection` will be used instead, with no real time updates.
|
|
319
|
-
* @param props
|
|
320
|
-
* @return Function to cancel subscription
|
|
321
|
-
*/
|
|
322
|
-
listenCollection?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenCollectionProps<M>): () => void;
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Retrieve a single row given a path and a collection
|
|
326
|
-
* @param props
|
|
327
|
-
*/
|
|
328
|
-
fetchOne<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined>;
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Get realtime updates on one row.
|
|
332
|
-
* @param props
|
|
333
|
-
* @return Function to cancel subscription
|
|
334
|
-
*/
|
|
335
|
-
listenOne?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenOneProps<M>): () => void;
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Save a row to the specified path
|
|
339
|
-
* @param props
|
|
340
|
-
*/
|
|
341
|
-
save<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>>;
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Save many rows as one unit of work.
|
|
345
|
-
*
|
|
346
|
-
* Every row runs the same pipeline as {@link save} — callbacks, relations
|
|
347
|
-
* and row-level security all still apply — but they share a single
|
|
348
|
-
* transaction, so the batch either lands whole or not at all. That, and the
|
|
349
|
-
* single round trip, is what makes importing tens of thousands of rows
|
|
350
|
-
* viable without dropping to raw SQL.
|
|
351
|
-
*
|
|
352
|
-
* Optional: drivers that cannot do this leave it undefined and callers fall
|
|
353
|
-
* back to `save` per row.
|
|
354
|
-
*/
|
|
355
|
-
saveMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Update many rows in one transaction, each addressed by id.
|
|
359
|
-
*
|
|
360
|
-
* Optional for the same reason `saveMany` is: a driver that cannot make the
|
|
361
|
-
* batch atomic should not pretend to. The REST layer reports
|
|
362
|
-
* `BULK_UNSUPPORTED` rather than silently falling back to a loop of single
|
|
363
|
-
* writes, which would be neither atomic nor one round trip — the two things
|
|
364
|
-
* a caller reaches for a batch to get.
|
|
365
|
-
*/
|
|
366
|
-
updateMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: UpdateManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Delete the row `props.row` addresses.
|
|
370
|
-
*
|
|
371
|
-
* **Resolving means the row is gone because this call removed it.** A
|
|
372
|
-
* delete that matched nothing must reject with a not-found error
|
|
373
|
-
* (`ApiError.notFound`, `statusCode: 404`) rather than resolving quietly.
|
|
374
|
-
*
|
|
375
|
-
* The rule is here rather than in each driver because the two
|
|
376
|
-
* implementations answered differently and each had a test pinning its own
|
|
377
|
-
* habit: Postgres threw, Mongo logged a warning and resolved. Three things
|
|
378
|
-
* decide it in favour of rejecting.
|
|
379
|
-
*
|
|
380
|
-
* The REST layer already says 404 — `DELETE /api/data/<c>/<id>` reads the
|
|
381
|
-
* row before removing it — so a quiet resolve made the driver API disagree
|
|
382
|
-
* with the HTTP API about the same operation, and only in-process
|
|
383
|
-
* `rebase.data` callers could see the difference.
|
|
384
|
-
*
|
|
385
|
-
* A caller cannot tell "deleted" from "there was nothing there" without it,
|
|
386
|
-
* and those are different facts: one means the caller's model of the data
|
|
387
|
-
* was right, the other that it was stale. Silence hands back the wrong one
|
|
388
|
-
* and the caller carries on.
|
|
389
|
-
*
|
|
390
|
-
* And on a driver with row-level security, "matched nothing" is *also* how
|
|
391
|
-
* a policy refusal arrives — Postgres filters `DELETE` through `USING`
|
|
392
|
-
* rather than raising. A driver that resolves on zero rows therefore
|
|
393
|
-
* reports a refused delete as a completed one, which is the defect
|
|
394
|
-
* `explainZeroRowWrite` exists to prevent (see `write-denial.ts`).
|
|
395
|
-
*
|
|
396
|
-
* Conformance for both server drivers lives in
|
|
397
|
-
* `packages/server/test/contract/delete-contract.ts`, run by each driver's
|
|
398
|
-
* own suite against its own database. `packages/firebase`'s Firestore
|
|
399
|
-
* driver does not honour it: `deleteDoc` resolves for a missing document
|
|
400
|
-
* and reporting otherwise would cost a read on every delete. It runs in the
|
|
401
|
-
* browser against Firestore's own semantics rather than behind
|
|
402
|
-
* `rebase.data`, and that exception is stated here rather than left to be
|
|
403
|
-
* discovered.
|
|
404
|
-
*/
|
|
405
|
-
delete<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteProps<M>): Promise<void>;
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Delete all entities from a collection.
|
|
409
|
-
* @param path Collection path
|
|
410
|
-
*/
|
|
411
|
-
deleteAll?(path: string): Promise<void>;
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Delete many rows in one transaction, addressed by id.
|
|
415
|
-
*
|
|
416
|
-
* Ids rather than a filter, deliberately — see
|
|
417
|
-
* {@link SDKCollectionClient.deleteMany}. Optional, as `saveMany` is.
|
|
418
|
-
*/
|
|
419
|
-
deleteMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteManyProps<M>): Promise<void>;
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Check if the given property is unique in the given collection
|
|
423
|
-
* @param path Collection path
|
|
424
|
-
* @param name of the property
|
|
425
|
-
* @param value
|
|
426
|
-
* @param id
|
|
427
|
-
* @param collection
|
|
428
|
-
* @return `true` if there are no other fields besides the given entity
|
|
429
|
-
*/
|
|
430
|
-
checkUniqueField(
|
|
431
|
-
path: string,
|
|
432
|
-
name: string,
|
|
433
|
-
value: unknown,
|
|
434
|
-
id?: string | number,
|
|
435
|
-
collection?: CollectionConfig
|
|
436
|
-
): Promise<boolean>;
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Count the number of entities in a collection
|
|
440
|
-
*/
|
|
441
|
-
count?<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Check if the given filter combination is valid
|
|
445
|
-
* @param props
|
|
446
|
-
*/
|
|
447
|
-
isFilterCombinationValid?(props: Omit<FilterCombinationValidProps, "collection"> & {
|
|
448
|
-
databaseId?: string
|
|
449
|
-
}): boolean;
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Get the object to generate the current time in the driver
|
|
453
|
-
*/
|
|
454
|
-
currentTime?: () => unknown;
|
|
455
|
-
|
|
456
|
-
delegateToCMSModel?: (data: unknown) => unknown;
|
|
457
|
-
|
|
458
|
-
cmsToDelegateModel?: (data: unknown) => unknown;
|
|
459
|
-
|
|
460
|
-
initTextSearch?: (props: {
|
|
461
|
-
context: RebaseCallContext,
|
|
462
|
-
path: string,
|
|
463
|
-
databaseId?: string,
|
|
464
|
-
collection: CollectionConfig,
|
|
465
|
-
parentCollectionSlugs?: string[];
|
|
466
|
-
parentEntityIds?: string[];
|
|
467
|
-
}) => Promise<boolean>;
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Flag to indicate if the driver has requested the initialization of the text search index
|
|
471
|
-
*/
|
|
472
|
-
needsInitTextSearch?: boolean;
|
|
473
|
-
|
|
474
|
-
// ── REST fetch capabilities ─────────────────────────────────────────
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Optional REST-optimised fetch service. When present, the REST API
|
|
478
|
-
* generator uses these methods instead of the generic `fetchOne` /
|
|
479
|
-
* `fetchCollection` pipeline, enabling include-aware eager-loading.
|
|
480
|
-
*/
|
|
481
|
-
restFetchService?: RestFetchService;
|
|
482
|
-
|
|
483
|
-
// ── Admin capabilities ─────────────────────────────────────────────
|
|
484
|
-
//
|
|
485
|
-
// Admin operations are now modelled as capability-specific interfaces
|
|
486
|
-
// (SQLAdmin, DocumentAdmin, SchemaAdmin) in `@rebasepro/types/backend`.
|
|
487
|
-
//
|
|
488
|
-
// Drivers that support admin features should expose them here.
|
|
489
|
-
// Consumers should use the `isSQLAdmin()`, `isSchemaAdmin()` etc.
|
|
490
|
-
// type guards to safely narrow the type before calling methods.
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Return the admin capabilities of this driver.
|
|
494
|
-
* @see SQLAdmin
|
|
495
|
-
* @see DocumentAdmin
|
|
496
|
-
* @see SchemaAdmin
|
|
497
|
-
*/
|
|
498
|
-
admin?: import("../types/backend").DatabaseAdmin;
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* REST-optimised fetch service exposed by drivers that support
|
|
504
|
-
* eager-loading of relations via `include`.
|
|
505
|
-
*
|
|
506
|
-
* The methods return flattened rows — exactly the table's columns, under their
|
|
507
|
-
* own names and with the types the database returned — and included relations
|
|
508
|
-
* inlined as plain nested rows. This is the shape served to app developers
|
|
509
|
-
* through the REST API / SDK client.
|
|
510
|
-
*
|
|
511
|
-
* No synthesized `id`: identity is a primary key, which may be named anything
|
|
512
|
-
* and span several columns, so an address is derived by whoever needs one (see
|
|
513
|
-
* `buildCompositeId`) rather than written into the row on top of the data.
|
|
514
|
-
*
|
|
515
|
-
* @group DataDriver
|
|
516
|
-
*/
|
|
517
|
-
export interface RestFetchService {
|
|
518
|
-
/**
|
|
519
|
-
* Fetch a collection of flattened entities with optional relation includes.
|
|
520
|
-
*/
|
|
521
|
-
fetchCollectionForRest(
|
|
522
|
-
collectionPath: string,
|
|
523
|
-
options?: {
|
|
524
|
-
filter?: FilterValues<string>;
|
|
525
|
-
/** An `or(...)`/`and(...)` group, applied alongside `filter`. */
|
|
526
|
-
logical?: LogicalCondition;
|
|
527
|
-
/** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
|
|
528
|
-
orderBy?: string | OrderByTuple[];
|
|
529
|
-
order?: "desc" | "asc";
|
|
530
|
-
limit?: number;
|
|
531
|
-
offset?: number;
|
|
532
|
-
startAfter?: Record<string, unknown>;
|
|
533
|
-
searchString?: string;
|
|
534
|
-
/** Ask each row which declared search fields matched — populates `_matches`. */
|
|
535
|
-
searchExplain?: boolean;
|
|
536
|
-
databaseId?: string;
|
|
537
|
-
vectorSearch?: VectorSearchParams;
|
|
538
|
-
},
|
|
539
|
-
include?: string[]
|
|
540
|
-
): Promise<Record<string, unknown>[]>;
|
|
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
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Fetch a single flattened entity with optional relation includes.
|
|
569
|
-
*/
|
|
570
|
-
fetchOneForRest(
|
|
571
|
-
collectionPath: string,
|
|
572
|
-
id: string | number,
|
|
573
|
-
include?: string[],
|
|
574
|
-
databaseId?: string
|
|
575
|
-
): Promise<Record<string, unknown> | null>;
|
|
576
|
-
}
|
package/src/controllers/email.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email service types — portable interface shared by RebaseClient and server.
|
|
3
|
-
*
|
|
4
|
-
* The concrete SMTP implementation lives in `@rebasepro/server/email`.
|
|
5
|
-
* This file provides only the consumer-facing contract so that it can be
|
|
6
|
-
* referenced from `RebaseClient` without dragging in nodemailer.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Options for sending an email via the Rebase email service.
|
|
11
|
-
*/
|
|
12
|
-
export interface EmailSendOptions {
|
|
13
|
-
/** Recipient email address(es). */
|
|
14
|
-
to: string | string[];
|
|
15
|
-
/** Email subject line. */
|
|
16
|
-
subject: string;
|
|
17
|
-
/** HTML body content. */
|
|
18
|
-
html: string;
|
|
19
|
-
/** Optional plain-text fallback. */
|
|
20
|
-
text?: string;
|
|
21
|
-
/** Optional reply-to address. */
|
|
22
|
-
replyTo?: string;
|
|
23
|
-
/**
|
|
24
|
-
* Additional headers, verbatim.
|
|
25
|
-
*
|
|
26
|
-
* The reason this exists is that several things a real sender must do are
|
|
27
|
-
* only expressible as headers, and without a passthrough an application had
|
|
28
|
-
* to choose between not doing them and not using this interface:
|
|
29
|
-
*
|
|
30
|
-
* - `List-Unsubscribe` and `List-Unsubscribe-Post`, which give a mail client
|
|
31
|
-
* its own one-click opt-out. The large providers weigh their presence when
|
|
32
|
-
* deciding whether bulk mail reaches an inbox at all.
|
|
33
|
-
* - `In-Reply-To` and `References`, without which a reply is a new thread.
|
|
34
|
-
*
|
|
35
|
-
* **Values are validated, not escaped.** A value containing CR or LF is
|
|
36
|
-
* rejected rather than sanitised, because a newline in a header value ends
|
|
37
|
-
* the header and starts a new one — so a field built from user input is an
|
|
38
|
-
* injection point for `Bcc:` and anything else. Rejecting is the only safe
|
|
39
|
-
* response: silently stripping the newline would deliver a message the
|
|
40
|
-
* caller did not write, and neither would tell them.
|
|
41
|
-
*/
|
|
42
|
-
headers?: Record<string, string>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* What the provider reported about a message it accepted.
|
|
47
|
-
*
|
|
48
|
-
* Every field is optional because not every backend reports them: a custom
|
|
49
|
-
* `sendEmail` function that posts to an HTTP API may know nothing beyond "no
|
|
50
|
-
* error". An absent `messageId` therefore means "not reported", never "not
|
|
51
|
-
* sent" — the absence of an id is not a delivery failure, which is signalled by
|
|
52
|
-
* a thrown error.
|
|
53
|
-
*/
|
|
54
|
-
export interface EmailSendResult {
|
|
55
|
-
/**
|
|
56
|
-
* The message's RFC 5322 Message-ID, **without** angle brackets.
|
|
57
|
-
*
|
|
58
|
-
* Stripped because this is an identifier to store and compare — against a
|
|
59
|
-
* reply's `In-Reply-To`, most often — and a value that sometimes carries
|
|
60
|
-
* brackets and sometimes does not is a bug waiting in every comparison.
|
|
61
|
-
* Re-add them when writing it into a header: `<${messageId}>`.
|
|
62
|
-
*/
|
|
63
|
-
messageId?: string;
|
|
64
|
-
/** Recipients the provider accepted, when it says. */
|
|
65
|
-
accepted?: string[];
|
|
66
|
-
/** Recipients the provider refused, when it says. A non-empty list is not an error. */
|
|
67
|
-
rejected?: string[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Abstraction over an email delivery backend.
|
|
72
|
-
*
|
|
73
|
-
* Implementations may use SMTP, AWS SES, Resend, Postmark, or any other
|
|
74
|
-
* provider — consumers only interact through this interface.
|
|
75
|
-
*/
|
|
76
|
-
export interface EmailService {
|
|
77
|
-
/**
|
|
78
|
-
* Send a single email.
|
|
79
|
-
*
|
|
80
|
-
* Resolves with what the provider reported (see {@link EmailSendResult});
|
|
81
|
-
* throws on failure. It returned `void` before 0.17: an application that
|
|
82
|
-
* sent a message had no way to learn the id the server assigned it, so
|
|
83
|
-
* threading a reply back to the message that prompted it was impossible
|
|
84
|
-
* through this interface. Callers that do not care may still ignore it.
|
|
85
|
-
*/
|
|
86
|
-
send(options: EmailSendOptions): Promise<EmailSendResult>;
|
|
87
|
-
/** Returns `true` when the service has valid credentials / is ready to send. */
|
|
88
|
-
isConfigured(): boolean;
|
|
89
|
-
/** Verify connection/credentials with the email provider. */
|
|
90
|
-
verifyConnection?(): Promise<boolean>;
|
|
91
|
-
}
|
package/src/controllers/index.ts
DELETED