@rebasepro/types 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/client.d.ts +48 -17
- package/dist/controllers/collection_registry.d.ts +17 -0
- package/dist/controllers/data.d.ts +161 -49
- package/dist/controllers/storage.d.ts +132 -13
- package/dist/errors.d.ts +15 -1
- package/dist/index.es.js +1094 -37
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +1 -1
- package/dist/types/api_keys.d.ts +68 -36
- package/dist/types/auth_adapter.d.ts +65 -1
- package/dist/types/backend.d.ts +177 -6
- package/dist/types/backup.d.ts +83 -0
- package/dist/types/collection_patch.d.ts +74 -0
- package/dist/types/collections.d.ts +23 -0
- package/dist/types/cron.d.ts +39 -3
- package/dist/types/database_adapter.d.ts +2 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/project_manifest.d.ts +65 -1
- package/dist/types/properties.d.ts +11 -0
- package/dist/types/schema_editing.d.ts +39 -13
- package/dist/types/scopes.d.ts +272 -0
- package/dist/types/storage_authorize.d.ts +5 -1
- package/dist/users/user.d.ts +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** One change to a collection. `path` is a list of object keys. */
|
|
2
|
+
export type CollectionPatchOp = {
|
|
3
|
+
op: "set";
|
|
4
|
+
path: string[];
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
op: "remove";
|
|
8
|
+
path: string[];
|
|
9
|
+
};
|
|
10
|
+
/** @group Models */
|
|
11
|
+
export type CollectionPatch = CollectionPatchOp[];
|
|
12
|
+
/**
|
|
13
|
+
* Keys the runtime adds to a collection that must never be written into its
|
|
14
|
+
* source. `resolvedRelation` is the registry's resolved form of a relation; a
|
|
15
|
+
* file holding one does not type-check (`target` is missing from it).
|
|
16
|
+
*/
|
|
17
|
+
export declare const RUNTIME_ONLY_COLLECTION_KEYS: readonly string[];
|
|
18
|
+
/** A JSON value as JSON would carry it: functions, `undefined` and symbols dropped. */
|
|
19
|
+
export declare function toCollectionJson(value: unknown): unknown;
|
|
20
|
+
/**
|
|
21
|
+
* The operations that take `before` to `after`, both JSON values.
|
|
22
|
+
*
|
|
23
|
+
* Objects are compared key by key; anything else that differs — a primitive,
|
|
24
|
+
* an array, a change of kind — is set whole.
|
|
25
|
+
*/
|
|
26
|
+
export declare function diffJsonValues(before: unknown, after: unknown, path?: string[]): CollectionPatch;
|
|
27
|
+
/**
|
|
28
|
+
* The patch, with every flat presentation key moved into its `admin` block.
|
|
29
|
+
*
|
|
30
|
+
* The panel edits a flat view model — `icon` beside `name`, a property's
|
|
31
|
+
* `readOnly` beside its `type` — while the file keeps them under `admin`. The
|
|
32
|
+
* difference is taken on the flat model, because that is where a cleared field
|
|
33
|
+
* shows up as a removed key (the `admin` copy the model was loaded with still
|
|
34
|
+
* holds the old value), and moved here.
|
|
35
|
+
*
|
|
36
|
+
* The flat value wins over the `admin` copy, as in {@link nestAdminKeysOf}:
|
|
37
|
+
* operations that were already under `admin` are ordered first, so the flat
|
|
38
|
+
* one — what the form just wrote — is applied last.
|
|
39
|
+
*/
|
|
40
|
+
export declare function nestCollectionPatchPaths(patch: CollectionPatch): CollectionPatch;
|
|
41
|
+
/**
|
|
42
|
+
* The patch that takes the collection the editor loaded to the one it is
|
|
43
|
+
* saving, in the authoring shape. Empty when nothing changed.
|
|
44
|
+
*
|
|
45
|
+
* Both sides go through JSON first, so whatever JSON cannot carry — a
|
|
46
|
+
* function, a value it drops — is equally absent from both and never appears
|
|
47
|
+
* as a change.
|
|
48
|
+
*/
|
|
49
|
+
export declare function diffCollections(loaded: unknown, saving: unknown): CollectionPatch;
|
|
50
|
+
/**
|
|
51
|
+
* Why a patch may not be applied, or an empty list.
|
|
52
|
+
*
|
|
53
|
+
* Checked by every door that accepts one: a path is a list of non-empty object
|
|
54
|
+
* keys that cannot reach a prototype, and no runtime-only key may be written.
|
|
55
|
+
*/
|
|
56
|
+
export declare function collectionPatchProblems(patch: unknown): string[];
|
|
57
|
+
/**
|
|
58
|
+
* The first runtime-only key (see {@link RUNTIME_ONLY_COLLECTION_KEYS}) found
|
|
59
|
+
* anywhere in a value, or `undefined`. For the doors that take a whole
|
|
60
|
+
* collection rather than a patch.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runtimeOnlyKeyIn(value: unknown): string | undefined;
|
|
63
|
+
/** A patch every door may apply: {@link collectionPatchProblems} found nothing. */
|
|
64
|
+
export declare function isCollectionPatch(patch: unknown): patch is CollectionPatch;
|
|
65
|
+
/**
|
|
66
|
+
* `target` with the patch applied, copying only what it changes.
|
|
67
|
+
*
|
|
68
|
+
* Everything the patch does not reach is shared with `target` by reference —
|
|
69
|
+
* functions, thunks and all — which is what lets a planner see a relation's
|
|
70
|
+
* `target()` on the patched collection exactly as it was. Intermediate objects
|
|
71
|
+
* a `set` needs are created; a `remove` of a key that is not there is nothing
|
|
72
|
+
* to do.
|
|
73
|
+
*/
|
|
74
|
+
export declare function applyCollectionPatch<T>(target: T, patch: CollectionPatch): T;
|
|
@@ -4,6 +4,7 @@ import type { User } from "../users/index.js";
|
|
|
4
4
|
import type { EmailSendResult } from "../controllers/email.js";
|
|
5
5
|
import type { Relation } from "./relations.js";
|
|
6
6
|
import type { SecurityRule } from "./security_rules.js";
|
|
7
|
+
import type { RoleDefinition, ScopeDefinition } from "./scopes.js";
|
|
7
8
|
import type { SearchConfig } from "./search.js";
|
|
8
9
|
import type { CollectionIndex } from "./indexes.js";
|
|
9
10
|
import type { CollectionTenantConfig } from "./tenancy.js";
|
|
@@ -614,6 +615,28 @@ export type InferCollectionConfigType<S extends CollectionConfig> = S extends Co
|
|
|
614
615
|
export interface AuthCollectionConfig {
|
|
615
616
|
/** Set to true to mark this collection as the authentication collection. */
|
|
616
617
|
enabled: boolean;
|
|
618
|
+
/**
|
|
619
|
+
* The app's roles and the admin-plane scopes each holds.
|
|
620
|
+
*
|
|
621
|
+
* `admin` is built in — it holds every scope — and cannot be declared. A
|
|
622
|
+
* role used only by RLS policies needs no entry here. See
|
|
623
|
+
* {@link RoleDefinition}.
|
|
624
|
+
*/
|
|
625
|
+
roles?: Record<string, RoleDefinition>;
|
|
626
|
+
/**
|
|
627
|
+
* Scopes the app defines for its own operations, named `resource:action`
|
|
628
|
+
* like the built-in ones and checked with `requireScope` in a function.
|
|
629
|
+
* Every signed-in person holds them; they exist so a key can be narrowed
|
|
630
|
+
* to one action. See {@link ScopeDefinition}.
|
|
631
|
+
*/
|
|
632
|
+
scopes?: Record<string, ScopeDefinition>;
|
|
633
|
+
/**
|
|
634
|
+
* Let every signed-in account mint personal API keys at
|
|
635
|
+
* `/api/auth/keys`. A personal key acts as its owner and can never hold
|
|
636
|
+
* a scope its owner does not. Off by default: each one is a long-lived
|
|
637
|
+
* credential for the account, and an app opts into handing those out.
|
|
638
|
+
*/
|
|
639
|
+
personalKeys?: boolean;
|
|
617
640
|
/**
|
|
618
641
|
* Called when an admin creates a user, through the collection REST API or
|
|
619
642
|
* `POST /admin/users`.
|
package/dist/types/cron.d.ts
CHANGED
|
@@ -94,7 +94,11 @@ export interface CronJobContext {
|
|
|
94
94
|
jobId: string;
|
|
95
95
|
/** The current scheduled tick timestamp. */
|
|
96
96
|
scheduledAt: Date;
|
|
97
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* A simple logger scoped to this job run. Each line is kept with the
|
|
99
|
+
* run's log entry and also written to the server log at `info`, with a
|
|
100
|
+
* `cron` field holding the job ID.
|
|
101
|
+
*/
|
|
98
102
|
log: (...args: unknown[]) => void;
|
|
99
103
|
/**
|
|
100
104
|
* Aborted when the run exceeds `timeoutSeconds`, or when the server
|
|
@@ -179,11 +183,43 @@ export interface CronJobStatus {
|
|
|
179
183
|
lastDurationMs?: number;
|
|
180
184
|
/** Error message from the last failed run. */
|
|
181
185
|
lastError?: string;
|
|
182
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Executions in the run history: every process's, read from `cron_logs`
|
|
188
|
+
* where runs are persisted, and this process's own otherwise.
|
|
189
|
+
*/
|
|
183
190
|
totalRuns: number;
|
|
184
|
-
/**
|
|
191
|
+
/** How many of {@link totalRuns} failed. */
|
|
185
192
|
totalFailures: number;
|
|
186
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* A job the scheduler refused, and why.
|
|
196
|
+
*
|
|
197
|
+
* It is not a `CronJobStatus`: it has no state, no next run and no counters,
|
|
198
|
+
* because it was never registered. Reporting it as a job with `state: "error"`
|
|
199
|
+
* would be a lie in the other direction — nothing is going to run it.
|
|
200
|
+
*/
|
|
201
|
+
export interface RejectedCronJob {
|
|
202
|
+
id: string;
|
|
203
|
+
name: string;
|
|
204
|
+
schedule: string;
|
|
205
|
+
/** Why it was refused — most often `Expected 5 fields, got 6`. */
|
|
206
|
+
reason: string;
|
|
207
|
+
}
|
|
208
|
+
/** What the cron listing (`GET /admin/cron`) answers. */
|
|
209
|
+
export interface CronJobListing {
|
|
210
|
+
/** The registered jobs. */
|
|
211
|
+
jobs: CronJobStatus[];
|
|
212
|
+
/**
|
|
213
|
+
* Cron files that did not become jobs: the files that failed to load,
|
|
214
|
+
* plus the jobs in {@link rejected}. Absent when there are none. None of
|
|
215
|
+
* them is scheduled.
|
|
216
|
+
*/
|
|
217
|
+
skipped?: number;
|
|
218
|
+
/** The jobs the scheduler refused, by name, with the reason. */
|
|
219
|
+
rejected?: RejectedCronJob[];
|
|
220
|
+
/** {@link skipped} and {@link rejected}, in a sentence. */
|
|
221
|
+
note?: string;
|
|
222
|
+
}
|
|
187
223
|
export type CronLogLevel = "info" | "error" | "warn";
|
|
188
224
|
/**
|
|
189
225
|
* A single execution log entry stored in the in-memory ring buffer.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { DataDriver } from "../controllers/data_driver.js";
|
|
23
23
|
import type { CollectionConfig } from "./collections.js";
|
|
24
|
-
import type { CollectionRegistryInterface, DatabaseAdmin, InitializedDriver, RealtimeProvider,
|
|
24
|
+
import type { CollectionRegistryInterface, DatabaseAdmin, InitializedDriver, RealtimeProvider, RealtimeSocketOptions, BootstrappedAuth } from "./backend.js";
|
|
25
25
|
import type { HistoryConfig } from "../controllers/client.js";
|
|
26
26
|
/**
|
|
27
27
|
* A `DatabaseAdapter` provides data persistence for Rebase.
|
|
@@ -78,7 +78,7 @@ export interface DatabaseAdapter {
|
|
|
78
78
|
* socket's frames: it serves the same rows, and without them a frame had
|
|
79
79
|
* neither. Forwarded for the same reason `adapter` is.
|
|
80
80
|
*/
|
|
81
|
-
initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: import("./auth_adapter.js").AuthAdapter, limits?:
|
|
81
|
+
initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: import("./auth_adapter.js").AuthAdapter, limits?: RealtimeSocketOptions): Promise<void> | void;
|
|
82
82
|
/**
|
|
83
83
|
* Ask the database whether it is there, before boot's first real query. See
|
|
84
84
|
* `BackendBootstrapper.verifyConnection` for the contract.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./entity_callbacks.js";
|
|
|
15
15
|
export * from "./websockets.js";
|
|
16
16
|
export * from "./backend.js";
|
|
17
17
|
export * from "./schema_editing.js";
|
|
18
|
+
export * from "./collection_patch.js";
|
|
18
19
|
export * from "./channel_bus.js";
|
|
19
20
|
export * from "./data_source.js";
|
|
20
21
|
export * from "./resources.js";
|
|
@@ -27,6 +28,7 @@ export * from "./component_ref.js";
|
|
|
27
28
|
export * from "./auth_adapter.js";
|
|
28
29
|
export * from "./database_adapter.js";
|
|
29
30
|
export * from "./api_keys.js";
|
|
31
|
+
export * from "./scopes.js";
|
|
30
32
|
export * from "./history.js";
|
|
31
33
|
export * from "./postgres_introspection.js";
|
|
32
34
|
export * from "./project_manifest.js";
|
|
@@ -132,6 +132,20 @@ export interface RebaseStaticAppConfig {
|
|
|
132
132
|
* loads and every asset 404s: a blank page with no server error. `rebase
|
|
133
133
|
* build` passes it as `REBASE_APP_BASE` and asserts the emitted HTML honours
|
|
134
134
|
* it. Changing this value requires rebuilding the app.
|
|
135
|
+
*
|
|
136
|
+
* **Or a full URL, to give the app a hostname of its own:**
|
|
137
|
+
* `"https://admin.example.com"`, or `"https://admin.example.com/cms"` for a
|
|
138
|
+
* path under it. The app then answers only on that hostname, and every app
|
|
139
|
+
* without one keeps answering on all the others. The URL form is split by
|
|
140
|
+
* {@link parseAppAddress}: the hostname travels as the bundle's
|
|
141
|
+
* {@link RebaseBundleStatic.host}, and the path part is what the rules above
|
|
142
|
+
* (and `REBASE_APP_BASE`) apply to. HTTPS only, with no port, query or
|
|
143
|
+
* fragment — a hostname is served over TLS by the platform, not chosen per
|
|
144
|
+
* request.
|
|
145
|
+
*
|
|
146
|
+
* On Rebase Cloud the deploy registers the hostname on the project. It is
|
|
147
|
+
* live once its DNS records verify, which the deploy prints when they don't
|
|
148
|
+
* yet.
|
|
135
149
|
*/
|
|
136
150
|
path?: string;
|
|
137
151
|
/**
|
|
@@ -191,6 +205,42 @@ export declare const RESERVED_BACKEND_PREFIXES: readonly ["/api", "/health", "/h
|
|
|
191
205
|
* that is looser admits paths that will not.
|
|
192
206
|
*/
|
|
193
207
|
export declare function reservedPrefixFor(path: string): string | undefined;
|
|
208
|
+
/**
|
|
209
|
+
* Where a static app answers: on one hostname or all of them, at a path.
|
|
210
|
+
*
|
|
211
|
+
* `host` absent is every hostname the project is served on.
|
|
212
|
+
*/
|
|
213
|
+
export interface AppAddress {
|
|
214
|
+
host?: string;
|
|
215
|
+
path: string;
|
|
216
|
+
}
|
|
217
|
+
export type AppAddressResult = {
|
|
218
|
+
ok: true;
|
|
219
|
+
address: AppAddress;
|
|
220
|
+
} | {
|
|
221
|
+
ok: false;
|
|
222
|
+
reason: string;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Split a static app's declared `path` into the hostname it claims and the path
|
|
226
|
+
* under it.
|
|
227
|
+
*
|
|
228
|
+
* `"/admin"` is `{ path: "/admin" }`, returned as written: the path rules
|
|
229
|
+
* (leading slash, no `..`, reserved prefixes) belong to the caller, which
|
|
230
|
+
* applies them to the path part of either form. `"https://admin.example.com/cms"`
|
|
231
|
+
* is `{ host: "admin.example.com", path: "/cms" }`.
|
|
232
|
+
*
|
|
233
|
+
* Shared because three parties must read the same value the same way: the CLI
|
|
234
|
+
* while `rebase.json` is edited, the control plane at deploy intake (a bundle can
|
|
235
|
+
* come from any CLI, or none), and the runtime choosing which app answers.
|
|
236
|
+
*/
|
|
237
|
+
export declare function parseAppAddress(value: string): AppAddressResult;
|
|
238
|
+
/**
|
|
239
|
+
* Whether `host` is a public DNS name an app can be given: lowercase labels of
|
|
240
|
+
* letters, digits and hyphens, at least two of them, not an IP address and not
|
|
241
|
+
* `localhost`. Internationalised names arrive here already in punycode.
|
|
242
|
+
*/
|
|
243
|
+
export declare function isValidAppHost(host: string): boolean;
|
|
194
244
|
/**
|
|
195
245
|
* One declared storage source, as authored in `rebase.json`.
|
|
196
246
|
*
|
|
@@ -375,8 +425,22 @@ export interface RebaseBundleEntrypoints {
|
|
|
375
425
|
}
|
|
376
426
|
/** One built static app inside a bundle. */
|
|
377
427
|
export interface RebaseBundleStatic {
|
|
378
|
-
/** Public base path, e.g. `/` or `/admin`. */
|
|
428
|
+
/** Public base path, e.g. `/` or `/admin`. Always a path, never a URL. */
|
|
379
429
|
path: string;
|
|
430
|
+
/**
|
|
431
|
+
* The one hostname this app answers on, lowercase, e.g. `admin.example.com`.
|
|
432
|
+
*
|
|
433
|
+
* Absent means every hostname the project is served on, which is what every
|
|
434
|
+
* app was before this field existed. Present, the app answers only for
|
|
435
|
+
* requests whose `Host` is exactly this — the path part of its address is
|
|
436
|
+
* still `path`. At an equal `path`, an app naming the request's hostname
|
|
437
|
+
* wins over one that does not.
|
|
438
|
+
*
|
|
439
|
+
* A runtime that predates this field ignores it and would mount two apps at
|
|
440
|
+
* the same `path`, so a bundle carrying it must only ever reach a runtime
|
|
441
|
+
* that reads it. The control plane enforces that at intake.
|
|
442
|
+
*/
|
|
443
|
+
host?: string;
|
|
380
444
|
/** Bundle-relative directory holding the built assets. */
|
|
381
445
|
dir: string;
|
|
382
446
|
/** Serve `index.html` for unmatched paths under `path`. */
|
|
@@ -435,6 +435,13 @@ export interface StringProperty extends BaseProperty {
|
|
|
435
435
|
* Drizzle append: `.primaryKey()`
|
|
436
436
|
* UI behavior: Field value cannot be changed after creation.
|
|
437
437
|
*
|
|
438
|
+
* Marking several properties declares one composite key: the table gets a
|
|
439
|
+
* single `PRIMARY KEY (a, b)` constraint (Drizzle: `primaryKey({ columns })`)
|
|
440
|
+
* and a row is addressed by all of its key values, joined in declaration
|
|
441
|
+
* order — `1:::en_US`. A foreign key cannot point at a composite key, so a
|
|
442
|
+
* relation or reference into such a collection is refused by the schema
|
|
443
|
+
* generators.
|
|
444
|
+
*
|
|
438
445
|
* You can set this to `"manual"` for a user-defined ID, or specify a generation strategy:
|
|
439
446
|
* 'uuid' -> Drizzle `.defaultRandom()` (Postgres gen_random_uuid())
|
|
440
447
|
* Or any other string to act as a raw SQL default expression, written either
|
|
@@ -565,6 +572,10 @@ export interface NumberProperty extends BaseProperty {
|
|
|
565
572
|
* Drizzle append: `.primaryKey()`
|
|
566
573
|
* UI behavior: Field value cannot be changed after creation.
|
|
567
574
|
*
|
|
575
|
+
* Marking several properties declares one composite key — see
|
|
576
|
+
* {@link StringProperty.isId}. A column whose value the database does not
|
|
577
|
+
* generate, such as a foreign key that is part of the key, takes `true`.
|
|
578
|
+
*
|
|
568
579
|
* You can set this to `"manual"` for a user-defined ID, or specify a generation strategy:
|
|
569
580
|
* 'increment' -> PostgreSQL `INTEGER GENERATED BY DEFAULT AS IDENTITY`.
|
|
570
581
|
* Or any other string to act as a raw SQL default expression, bare or in
|
|
@@ -24,7 +24,19 @@
|
|
|
24
24
|
* changing a type, moving a primary key.
|
|
25
25
|
*/
|
|
26
26
|
export type SchemaChangeVerdict = "safe" | "diverges" | "needs-migration";
|
|
27
|
-
export type SchemaChangeKind = "add-collection" | "remove-collection" | "add-property" | "remove-property" | "change-property-type" | "rename-column" | "add-enum-value" | "remove-enum-value" | "change-required" | "change-primary-key" | "rename-table"
|
|
27
|
+
export type SchemaChangeKind = "add-collection" | "remove-collection" | "add-property" | "remove-property" | "change-property-type" | "rename-column" | "add-enum-value" | "remove-enum-value" | "change-required" | "change-primary-key" | "rename-table"
|
|
28
|
+
/** A property key renamed while keeping its column: no data moves. */
|
|
29
|
+
| "rename-property"
|
|
30
|
+
/** A relation's kind, foreign key or link column changes. */
|
|
31
|
+
| "change-relation"
|
|
32
|
+
/** A UNIQUE constraint, an index or an update trigger appears or goes. */
|
|
33
|
+
| "change-constraint"
|
|
34
|
+
/** A column's DEFAULT is set, changed or dropped. */
|
|
35
|
+
| "change-default"
|
|
36
|
+
/** The proposed collections cannot be planned at all. */
|
|
37
|
+
| "invalid-collection"
|
|
38
|
+
/** A collection's row-level security policies change. */
|
|
39
|
+
| "change-security-rules";
|
|
28
40
|
export interface SchemaChange {
|
|
29
41
|
kind: SchemaChangeKind;
|
|
30
42
|
verdict: SchemaChangeVerdict;
|
|
@@ -36,6 +48,15 @@ export interface SchemaChange {
|
|
|
36
48
|
detail: string;
|
|
37
49
|
/** What to do instead, when the verdict is not `safe`. */
|
|
38
50
|
remedy?: string;
|
|
51
|
+
/**
|
|
52
|
+
* What "Edit source only" leaves behind for this change: the collection is
|
|
53
|
+
* committed and the database is not touched. Absent on a change that may
|
|
54
|
+
* not be written source-only at all — one that would leave a project that
|
|
55
|
+
* does not start, or reads its data as the wrong type.
|
|
56
|
+
*/
|
|
57
|
+
sourceOnly?: string;
|
|
58
|
+
/** The same, in a few words for a commit subject: `column products.sku kept`. */
|
|
59
|
+
kept?: string;
|
|
39
60
|
}
|
|
40
61
|
export interface ClassifiedSchemaChanges {
|
|
41
62
|
changes: SchemaChange[];
|
|
@@ -56,24 +77,23 @@ export interface ClassifiedSchemaChanges {
|
|
|
56
77
|
export interface SchemaCommitPaths {
|
|
57
78
|
/** Drizzle schema, imported by the backend. */
|
|
58
79
|
schemaFile: string;
|
|
59
|
-
/** Declarative DDL, what `db push` applies and Atlas diffs against. */
|
|
60
|
-
ddlFile: string;
|
|
61
|
-
policiesFile: string;
|
|
62
|
-
searchFile: string;
|
|
63
|
-
/** Vector columns and ANN indexes — like search, applied by Rebase not Atlas. */
|
|
64
|
-
vectorFile: string;
|
|
65
|
-
/**
|
|
66
|
-
* `autoValue: "on_update"` triggers and the function they share. Atlas's
|
|
67
|
-
* free tier will not parse a desired state containing a function, so this
|
|
68
|
-
* is applied by Rebase like search and vector.
|
|
69
|
-
*/
|
|
70
|
-
triggersFile: string;
|
|
71
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The SQL `db push` and `db generate` render — `schema.sql`, `policies.sql` and
|
|
83
|
+
* the rest — is not here, and must not come back. Both commands regenerate it
|
|
84
|
+
* from the collections before reading it, so nothing ever read a committed
|
|
85
|
+
* copy; it lives in the gitignored `.rebase/sql/`. When the commit carried it,
|
|
86
|
+
* it carried a second copy besides: these paths are from the project root,
|
|
87
|
+
* while the CLI writes beside the backend, so a live edit left `drizzle/` and
|
|
88
|
+
* `backend/drizzle/` disagreeing about the schema.
|
|
89
|
+
*/
|
|
72
90
|
export declare const DEFAULT_COMMIT_PATHS: SchemaCommitPaths;
|
|
73
91
|
/** One file the commit writes, as content rather than as a path on a disk. */
|
|
74
92
|
export interface SchemaChangeFile {
|
|
75
93
|
path: string;
|
|
76
94
|
contents: string;
|
|
95
|
+
/** The commit removes this file — a deleted collection. `contents` is ignored. */
|
|
96
|
+
deleted?: boolean;
|
|
77
97
|
}
|
|
78
98
|
/**
|
|
79
99
|
* Everything a change needs written and run.
|
|
@@ -131,5 +151,11 @@ export interface SchemaEditingAdmin {
|
|
|
131
151
|
*/
|
|
132
152
|
planSchemaChange(before: unknown[], after: unknown[], options?: {
|
|
133
153
|
paths?: Partial<SchemaCommitPaths>;
|
|
154
|
+
/**
|
|
155
|
+
* Plan a commit of the source alone — "Edit source only": never
|
|
156
|
+
* rejects an unapplicable change, carries no statements, and its
|
|
157
|
+
* message names what is left in the database.
|
|
158
|
+
*/
|
|
159
|
+
sourceOnly?: boolean;
|
|
134
160
|
}): Promise<SchemaChangePlan>;
|
|
135
161
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scopes — what a credential may do, named `resource:action`.
|
|
3
|
+
*
|
|
4
|
+
* One vocabulary for every way of reaching a Rebase backend: a person's
|
|
5
|
+
* session, a service key, a personal key and an OAuth token for `/mcp` all
|
|
6
|
+
* answer "may this caller do X?" with the same strings, so a grant reads the
|
|
7
|
+
* same on a key, on a role and on a consent screen.
|
|
8
|
+
*
|
|
9
|
+
* A scope may carry a target — `data:read:posts` is `data:read` on the `posts`
|
|
10
|
+
* collection only. The unqualified scope covers every target. Only the data
|
|
11
|
+
* plane and app scopes take targets; see {@link SCOPE_TARGETS}.
|
|
12
|
+
*
|
|
13
|
+
* Two planes, held differently:
|
|
14
|
+
*
|
|
15
|
+
* - **The data plane** (`data:*`, `storage:*`, `functions:invoke`). Every
|
|
16
|
+
* signed-in person holds all of it, because what a person may do with data
|
|
17
|
+
* is decided row by row by the collection's `securityRules` and by storage
|
|
18
|
+
* policies, never by a scope. On a key or a token these scopes narrow: a key
|
|
19
|
+
* holding `data:read:posts` reaches nothing else, whatever RLS would allow.
|
|
20
|
+
* - **The admin plane** (everything else). Nobody holds it implicitly. The
|
|
21
|
+
* `admin` role holds all of it; any other role holds what the app declares
|
|
22
|
+
* for it under `auth.roles` on the users collection.
|
|
23
|
+
*
|
|
24
|
+
* Apps declare their own scopes too (`auth.scopes`), named the same way —
|
|
25
|
+
* `project:deploy`. Every signed-in person holds every app scope: like the data
|
|
26
|
+
* plane, the function behind it decides whether the person may act, and the
|
|
27
|
+
* scope exists so a key can be narrowed to that one action.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
/** @group Auth */
|
|
32
|
+
export declare const DATA_PLANE_SCOPES: readonly ["data:read", "data:write", "data:delete", "storage:read", "storage:write", "storage:delete", "functions:invoke"];
|
|
33
|
+
/** @group Auth */
|
|
34
|
+
export declare const ADMIN_SCOPES: readonly ["users:read", "users:write", "schema:read", "schema:write", "database:read", "database:write", "backups:read", "cron:read", "cron:write", "logs:read", "keys:read", "keys:write"];
|
|
35
|
+
/** @group Auth */
|
|
36
|
+
export type DataPlaneScope = (typeof DATA_PLANE_SCOPES)[number];
|
|
37
|
+
/** @group Auth */
|
|
38
|
+
export type AdminScope = (typeof ADMIN_SCOPES)[number];
|
|
39
|
+
/** @group Auth */
|
|
40
|
+
export type BuiltInScope = DataPlaneScope | AdminScope;
|
|
41
|
+
/** Every scope Rebase itself defines, in display order. @group Auth */
|
|
42
|
+
export declare const BUILT_IN_SCOPES: readonly BuiltInScope[];
|
|
43
|
+
/**
|
|
44
|
+
* Scopes no key may hold.
|
|
45
|
+
*
|
|
46
|
+
* A key that could manage keys could mint its own successor, widen itself, or
|
|
47
|
+
* revoke the keys other integrations run on — and revoking it would undo none
|
|
48
|
+
* of that. Managing keys is for a person (or the service key).
|
|
49
|
+
*
|
|
50
|
+
* @group Auth
|
|
51
|
+
*/
|
|
52
|
+
export declare const KEY_MANAGEMENT_SCOPES: readonly AdminScope[];
|
|
53
|
+
/** The one built-in role. It holds every scope and is the RLS `admin` role. @group Auth */
|
|
54
|
+
export declare const ADMIN_ROLE = "admin";
|
|
55
|
+
/** What a scope's target names. @group Auth */
|
|
56
|
+
export type ScopeTargetKind = "collection" | "bucket" | "function";
|
|
57
|
+
/**
|
|
58
|
+
* The built-in scopes that accept a target, and what the target names.
|
|
59
|
+
*
|
|
60
|
+
* - `data:*` — a collection slug
|
|
61
|
+
* - `storage:*` — a storage source id (`(default)` for the default source)
|
|
62
|
+
* - `functions:invoke` — a custom function's name
|
|
63
|
+
*
|
|
64
|
+
* @group Auth
|
|
65
|
+
*/
|
|
66
|
+
export declare const SCOPE_TARGETS: Readonly<Partial<Record<BuiltInScope, ScopeTargetKind>>>;
|
|
67
|
+
/** A scope's wording, for the screens that ask a person to grant it. @group Auth */
|
|
68
|
+
export interface ScopeDescription {
|
|
69
|
+
/** Short, imperative: what a holder may do. */
|
|
70
|
+
label: string;
|
|
71
|
+
/** One sentence on what that reaches, including anything surprising. */
|
|
72
|
+
description: string;
|
|
73
|
+
}
|
|
74
|
+
/** @group Auth */
|
|
75
|
+
export declare const BUILT_IN_SCOPE_DESCRIPTIONS: Readonly<Record<BuiltInScope, ScopeDescription>>;
|
|
76
|
+
/**
|
|
77
|
+
* An app-declared scope, under `auth.scopes` on the users collection.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* auth: {
|
|
82
|
+
* enabled: true,
|
|
83
|
+
* scopes: {
|
|
84
|
+
* "project:deploy": { label: "Deploy projects", target: "project" }
|
|
85
|
+
* }
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @group Auth
|
|
90
|
+
*/
|
|
91
|
+
export interface ScopeDefinition {
|
|
92
|
+
/** Short, imperative: what a holder may do. Shown wherever the scope is granted. */
|
|
93
|
+
label: string;
|
|
94
|
+
/** One sentence on what that reaches. */
|
|
95
|
+
description?: string;
|
|
96
|
+
/**
|
|
97
|
+
* What a target names, when the scope accepts one — `"project"` lets a
|
|
98
|
+
* key hold `project:deploy:<id>`. Unset, the scope takes no target.
|
|
99
|
+
*/
|
|
100
|
+
target?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* An app-declared role, under `auth.roles` on the users collection.
|
|
104
|
+
*
|
|
105
|
+
* A role is a name the database sees (RLS policies match it) and a bundle of
|
|
106
|
+
* admin-plane and app scopes. Data-plane scopes are not role scopes: what a
|
|
107
|
+
* person may do with data is the collection's `securityRules`, so listing
|
|
108
|
+
* `data:write` here would read as a grant it is not, and is refused.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* auth: {
|
|
113
|
+
* enabled: true,
|
|
114
|
+
* roles: {
|
|
115
|
+
* support: { name: "Support", scopes: ["users:read", "users:write", "logs:read"] },
|
|
116
|
+
* developer: { name: "Developer", scopes: ["schema:read", "database:read", "logs:read", "cron:read"] }
|
|
117
|
+
* }
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @group Auth
|
|
122
|
+
*/
|
|
123
|
+
export interface RoleDefinition {
|
|
124
|
+
/** Display name. Defaults to the role id. */
|
|
125
|
+
name?: string;
|
|
126
|
+
/** One sentence on who holds it. */
|
|
127
|
+
description?: string;
|
|
128
|
+
/** Admin-plane and app scopes the role holds. */
|
|
129
|
+
scopes: readonly string[];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The declared access model: the app's own scopes and roles beside the
|
|
133
|
+
* built-in ones. What `scopesForRoles` and `validateScopes` read.
|
|
134
|
+
*
|
|
135
|
+
* @group Auth
|
|
136
|
+
*/
|
|
137
|
+
export interface AccessModel {
|
|
138
|
+
roles: Readonly<Record<string, RoleDefinition>>;
|
|
139
|
+
scopes: Readonly<Record<string, ScopeDefinition>>;
|
|
140
|
+
}
|
|
141
|
+
/** The access model of an app that declares nothing: built-ins only. @group Auth */
|
|
142
|
+
export declare const EMPTY_ACCESS_MODEL: AccessModel;
|
|
143
|
+
/** A scope string split into its scope and optional target. @group Auth */
|
|
144
|
+
export interface ParsedScope {
|
|
145
|
+
/** `resource:action`. */
|
|
146
|
+
scope: string;
|
|
147
|
+
/** The one collection, bucket, function or app resource it is narrowed to. */
|
|
148
|
+
target?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Split `resource:action[:target]`. Returns null for anything else.
|
|
152
|
+
*
|
|
153
|
+
* The target is everything after the second colon, so a target may itself
|
|
154
|
+
* contain colons. Resource and action are lower-case words.
|
|
155
|
+
*
|
|
156
|
+
* @group Auth
|
|
157
|
+
*/
|
|
158
|
+
export declare function parseScope(value: string): ParsedScope | null;
|
|
159
|
+
/** @group Auth */
|
|
160
|
+
export declare function isBuiltInScope(scope: string): scope is BuiltInScope;
|
|
161
|
+
/** @group Auth */
|
|
162
|
+
export declare function isDataPlaneScope(scope: string): scope is DataPlaneScope;
|
|
163
|
+
/** @group Auth */
|
|
164
|
+
export declare function isAdminScope(scope: string): scope is AdminScope;
|
|
165
|
+
/** Does this list of roles include the built-in `admin` role? @group Auth */
|
|
166
|
+
export declare function hasAdminRole(roles: readonly string[] | null | undefined): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Does a set of held scopes grant `scope`, optionally on one `target`?
|
|
169
|
+
*
|
|
170
|
+
* The unqualified grant covers every target. A qualified grant covers its own
|
|
171
|
+
* target only, and never answers an unqualified question: a key holding
|
|
172
|
+
* `data:read:posts` is not asked "may you read data?" and told yes.
|
|
173
|
+
*
|
|
174
|
+
* @group Auth
|
|
175
|
+
*/
|
|
176
|
+
export declare function scopeGrants(held: readonly string[], scope: string, target?: string): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Does a set of held scopes grant `scope` on at least one target?
|
|
179
|
+
*
|
|
180
|
+
* For a surface that lists what is reachable — the collections an MCP client
|
|
181
|
+
* may query — and filters per item afterwards.
|
|
182
|
+
*
|
|
183
|
+
* @group Auth
|
|
184
|
+
*/
|
|
185
|
+
export declare function scopeGrantsAny(held: readonly string[], scope: string): boolean;
|
|
186
|
+
/**
|
|
187
|
+
* The targets a held set narrows `scope` to: `"all"` when it holds the
|
|
188
|
+
* unqualified scope, otherwise the listed targets (possibly none).
|
|
189
|
+
*
|
|
190
|
+
* @group Auth
|
|
191
|
+
*/
|
|
192
|
+
export declare function scopeTargets(held: readonly string[], scope: string): "all" | string[];
|
|
193
|
+
/**
|
|
194
|
+
* Every scope a person holding `roles` has.
|
|
195
|
+
*
|
|
196
|
+
* The data plane and every app scope always; the whole admin plane with the
|
|
197
|
+
* `admin` role; otherwise each declared role's scopes. A role nobody declared
|
|
198
|
+
* adds nothing — it may still mean something to an RLS policy.
|
|
199
|
+
*
|
|
200
|
+
* @group Auth
|
|
201
|
+
*/
|
|
202
|
+
export declare function scopesForRoles(roles: readonly string[] | null | undefined, model?: AccessModel): string[];
|
|
203
|
+
/**
|
|
204
|
+
* Every scope this access model knows, unqualified: the built-ins, then the
|
|
205
|
+
* app's own.
|
|
206
|
+
*
|
|
207
|
+
* @group Auth
|
|
208
|
+
*/
|
|
209
|
+
export declare function knownScopes(model?: AccessModel): string[];
|
|
210
|
+
/** Does this scope accept a target? @group Auth */
|
|
211
|
+
export declare function scopeAcceptsTarget(scope: string, model?: AccessModel): boolean;
|
|
212
|
+
/** How a scope string failed to validate. @group Auth */
|
|
213
|
+
export interface ScopeProblem {
|
|
214
|
+
scope: string;
|
|
215
|
+
reason: "malformed" | "unknown" | "target-not-accepted";
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check a list of scope strings against an access model. Returns the
|
|
219
|
+
* problems, empty when every entry is a known scope with an acceptable target.
|
|
220
|
+
*
|
|
221
|
+
* @group Auth
|
|
222
|
+
*/
|
|
223
|
+
export declare function validateScopes(values: readonly unknown[], model?: AccessModel): ScopeProblem[];
|
|
224
|
+
/**
|
|
225
|
+
* The entries of `requested` that `held` does not cover — what a credential
|
|
226
|
+
* would hold beyond its minter. Empty means the request is within reach.
|
|
227
|
+
*
|
|
228
|
+
* `data:read:posts` is covered by `data:read`; `data:read` is not covered by
|
|
229
|
+
* `data:read:posts`.
|
|
230
|
+
*
|
|
231
|
+
* @group Auth
|
|
232
|
+
*/
|
|
233
|
+
export declare function scopesBeyond(requested: readonly string[], held: readonly string[]): string[];
|
|
234
|
+
/**
|
|
235
|
+
* Narrow `requested` to what `held` covers, entry by entry. What a personal
|
|
236
|
+
* key holds today when its owner has lost a role since it was minted.
|
|
237
|
+
*
|
|
238
|
+
* @group Auth
|
|
239
|
+
*/
|
|
240
|
+
export declare function intersectScopes(requested: readonly string[], held: readonly string[]): string[];
|
|
241
|
+
/**
|
|
242
|
+
* How a scope is worded for a person: the built-in description, the app's
|
|
243
|
+
* declared label, or the raw string when nothing describes it. A target is
|
|
244
|
+
* named after the label.
|
|
245
|
+
*
|
|
246
|
+
* @group Auth
|
|
247
|
+
*/
|
|
248
|
+
export declare function describeScope(value: string, model?: AccessModel): ScopeDescription;
|
|
249
|
+
/** A role as a person managing access sees it. @group Auth */
|
|
250
|
+
export interface RoleSummary {
|
|
251
|
+
id: string;
|
|
252
|
+
name: string;
|
|
253
|
+
description?: string;
|
|
254
|
+
/** Admin-plane and app scopes. The `admin` role lists every admin-plane scope. */
|
|
255
|
+
scopes: string[];
|
|
256
|
+
/** True for `admin`, which the app does not declare. */
|
|
257
|
+
builtIn: boolean;
|
|
258
|
+
}
|
|
259
|
+
/** A scope as a screen that grants it lists it. @group Auth */
|
|
260
|
+
export interface ScopeSummary {
|
|
261
|
+
scope: string;
|
|
262
|
+
label: string;
|
|
263
|
+
description: string;
|
|
264
|
+
/** What a target names, when the scope takes one. */
|
|
265
|
+
target?: string;
|
|
266
|
+
/** `data`, `admin` or `app`: which plane the scope belongs to. */
|
|
267
|
+
plane: "data" | "admin" | "app";
|
|
268
|
+
}
|
|
269
|
+
/** The roles of an access model, `admin` first. @group Auth */
|
|
270
|
+
export declare function summarizeRoles(model?: AccessModel): RoleSummary[];
|
|
271
|
+
/** Every scope of an access model, described, in display order. @group Auth */
|
|
272
|
+
export declare function summarizeScopes(model?: AccessModel): ScopeSummary[];
|