@rebasepro/types 0.15.0 → 0.16.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/call_context.d.ts +1 -1
- package/dist/controllers/client.d.ts +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/types/backend.d.ts +28 -1
- package/dist/types/collections.d.ts +4 -4
- package/dist/types/cron.d.ts +8 -4
- package/dist/types/database_adapter.d.ts +50 -0
- package/dist/types/project_manifest.d.ts +39 -0
- package/dist/types/security_rules.d.ts +8 -8
- package/package.json +1 -1
- package/src/call_context.ts +1 -1
- package/src/controllers/client.ts +1 -1
- package/src/types/backend.ts +35 -1
- package/src/types/collections.ts +4 -4
- package/src/types/cron.ts +8 -4
- package/src/types/database_adapter.ts +57 -0
- package/src/types/project_manifest.ts +39 -0
- package/src/types/security_rules.ts +8 -8
package/dist/types/backend.d.ts
CHANGED
|
@@ -345,7 +345,7 @@ export interface SQLAdmin {
|
|
|
345
345
|
* Fetch the *application-level* roles in use in this project.
|
|
346
346
|
*
|
|
347
347
|
* These are the strings stored on the users table's `roles` column and
|
|
348
|
-
* exposed to policies as `
|
|
348
|
+
* exposed to policies as `rebase.roles()` — what `SecurityRule.roles`
|
|
349
349
|
* matches against. Distinct from {@link fetchAvailableRoles}; the two are
|
|
350
350
|
* not interchangeable.
|
|
351
351
|
*/
|
|
@@ -667,6 +667,33 @@ export interface BackendBootstrapper {
|
|
|
667
667
|
ensureCollectionPolicies?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
|
|
668
668
|
applied: number;
|
|
669
669
|
}>;
|
|
670
|
+
/**
|
|
671
|
+
* Read the collections schema version this database was last provisioned
|
|
672
|
+
* from, or `null` when nothing has ever stamped it.
|
|
673
|
+
*
|
|
674
|
+
* The companion to {@link stampCollectionsSchemaVersion}: one process writes
|
|
675
|
+
* what it applied, every other process compares itself to it. This is what
|
|
676
|
+
* lets a split deployment — several processes over one database, only one of
|
|
677
|
+
* which provisions — notice that a unit is serving against a schema it was
|
|
678
|
+
* not built for. That failure is otherwise silent in both directions: a
|
|
679
|
+
* column that does not exist is a SQL error on one route, and a policy that
|
|
680
|
+
* was never applied is a 200 with no rows.
|
|
681
|
+
*
|
|
682
|
+
* `null` is not an error and MUST NOT be treated as one — every database
|
|
683
|
+
* provisioned before the stamp existed reads this way, and so does every
|
|
684
|
+
* fresh one until its first provisioning boot finishes.
|
|
685
|
+
*/
|
|
686
|
+
readCollectionsSchemaVersion?(driverResult?: InitializedDriver): Promise<string | null>;
|
|
687
|
+
/**
|
|
688
|
+
* Record the collections schema version this process just applied.
|
|
689
|
+
*
|
|
690
|
+
* Called only by the process that provisions, and only after both
|
|
691
|
+
* {@link ensureCollectionSchema} and {@link ensureCollectionPolicies} have
|
|
692
|
+
* run — a stamp written before the policies would claim a schema that is
|
|
693
|
+
* only half in place, and the half that is missing is the one that fails
|
|
694
|
+
* without an error.
|
|
695
|
+
*/
|
|
696
|
+
stampCollectionsSchemaVersion?(version: string, driverResult?: InitializedDriver): Promise<void>;
|
|
670
697
|
/**
|
|
671
698
|
* Initialize WebSocket server for realtime operations.
|
|
672
699
|
*/
|
|
@@ -210,9 +210,9 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
210
210
|
* 3. **Combined** — mix shortcuts with `roles` for common patterns
|
|
211
211
|
*
|
|
212
212
|
* The authenticated user context is available in raw SQL via:
|
|
213
|
-
* - `
|
|
214
|
-
* - `
|
|
215
|
-
* - `
|
|
213
|
+
* - `rebase.uid()` — the current user's ID
|
|
214
|
+
* - `rebase.roles()` — comma-separated app role IDs
|
|
215
|
+
* - `rebase.jwt()` — full JWT claims as JSONB
|
|
216
216
|
*/
|
|
217
217
|
securityRules?: readonly SecurityRule[];
|
|
218
218
|
/**
|
|
@@ -222,7 +222,7 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
222
222
|
* baseline SELECT policy granting the trusted server context and the
|
|
223
223
|
* `admin` role read access (reads run under a restricted role, so RLS
|
|
224
224
|
* default-denies without it). For auth collections it additionally injects
|
|
225
|
-
* a self-read policy (`id =
|
|
225
|
+
* a self-read policy (`id = rebase.uid()`) and an admin-only write gate
|
|
226
226
|
* (INSERT/UPDATE/DELETE require the `admin` role or the trusted server
|
|
227
227
|
* context), making privileged columns such as `roles` safe by default.
|
|
228
228
|
*
|
package/dist/types/cron.d.ts
CHANGED
|
@@ -81,10 +81,14 @@ export interface CronJobContext {
|
|
|
81
81
|
* hands its callback. Spelled the same way here so that one thing has one
|
|
82
82
|
* name across every server-side authoring surface.
|
|
83
83
|
*
|
|
84
|
-
* Its data plane is {@link RebaseServerClient.dataAsAdmin},
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
84
|
+
* Its data plane is {@link RebaseServerClient.dataAsAdmin}, scoped as
|
|
85
|
+
* `{ uid: "service", roles: ["admin"] }` — **admin-scoped, not
|
|
86
|
+
* RLS-bypassing**. Statements still run as `rebase_user` with the policies
|
|
87
|
+
* evaluated against that identity; the admin role merely clears the default
|
|
88
|
+
* policies through their `rolesOverlap(['admin'])` arm, and
|
|
89
|
+
* `policy.serverContext()` (`rebase.uid() IS NULL`) is *false* for it.
|
|
90
|
+
* `rebase.sql()` is the unconditional bypass. A cron has no per-request
|
|
91
|
+
* user, so there is no user-scoped alternative here: scope every query's
|
|
88
92
|
* filters yourself.
|
|
89
93
|
*
|
|
90
94
|
* @example
|
|
@@ -109,6 +109,27 @@ export interface DatabaseAdapter {
|
|
|
109
109
|
ensureCollectionPolicies?(collections: unknown[], driverResult?: InitializedDriver, log?: (message: string) => void): Promise<{
|
|
110
110
|
applied: number;
|
|
111
111
|
}>;
|
|
112
|
+
/**
|
|
113
|
+
* Read the collections schema version this database was last provisioned
|
|
114
|
+
* from, or `null` when nothing has ever stamped it.
|
|
115
|
+
*
|
|
116
|
+
* `null` is not an error and MUST NOT be treated as one — every database
|
|
117
|
+
* provisioned before the stamp existed reads this way, and so does every
|
|
118
|
+
* fresh one until its first provisioning boot finishes.
|
|
119
|
+
*
|
|
120
|
+
* Same forwarding requirement as the two hooks above: a wrapper that omits
|
|
121
|
+
* it turns the check off, and a check that is off looks exactly like a check
|
|
122
|
+
* that passed.
|
|
123
|
+
*/
|
|
124
|
+
readCollectionsSchemaVersion?(driverResult?: InitializedDriver): Promise<string | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Record the collections schema version this process just applied.
|
|
127
|
+
*
|
|
128
|
+
* Called only by the process that provisions, and only after the tables AND
|
|
129
|
+
* the policies are in place — a stamp written earlier would claim a schema
|
|
130
|
+
* that a half-finished boot never finished creating.
|
|
131
|
+
*/
|
|
132
|
+
stampCollectionsSchemaVersion?(version: string, driverResult?: InitializedDriver): Promise<void>;
|
|
112
133
|
/**
|
|
113
134
|
* Return admin capabilities for this database (SQL editor, schema browser, branching).
|
|
114
135
|
*/
|
|
@@ -176,4 +197,33 @@ export interface DatabaseAdapterInitConfig {
|
|
|
176
197
|
/** Why it did not, when it did not — safe to print verbatim. */
|
|
177
198
|
reason?: string;
|
|
178
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* What this process wants from the realtime subsystem.
|
|
202
|
+
*
|
|
203
|
+
* Both halves used to be assumed true, and both were wrong for a split
|
|
204
|
+
* deployment. A `functions` or `worker` process has no websocket clients, so
|
|
205
|
+
* consuming change events buys it a dedicated `LISTEN` connection to deliver
|
|
206
|
+
* to nobody; and it is explicitly not the process that owns schema DDL, so
|
|
207
|
+
* installing capture triggers from it contradicts the one-owner rule the
|
|
208
|
+
* runtime otherwise refuses to boot without.
|
|
209
|
+
*
|
|
210
|
+
* Absent means both — every caller that predates this field is a
|
|
211
|
+
* single-process deployment, where both are true.
|
|
212
|
+
*/
|
|
213
|
+
realtime?: {
|
|
214
|
+
/**
|
|
215
|
+
* Consume change events: open the `LISTEN` connection, start CDC or the
|
|
216
|
+
* app-level fallback. False for a process that serves no websockets.
|
|
217
|
+
*
|
|
218
|
+
* Writes made by a non-consuming process are still published: capture is
|
|
219
|
+
* database triggers, so the publisher is the database.
|
|
220
|
+
*/
|
|
221
|
+
subscribe: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Install what capture needs — the trigger function, the per-table
|
|
224
|
+
* triggers, any channel-history tables. This is DDL, and it follows the
|
|
225
|
+
* same single-owner rule as every other boot-time schema change.
|
|
226
|
+
*/
|
|
227
|
+
provision: boolean;
|
|
228
|
+
};
|
|
179
229
|
}
|
|
@@ -395,6 +395,45 @@ export interface RebaseBundleManifest {
|
|
|
395
395
|
deps: {
|
|
396
396
|
/** Runtime dependencies of user code, as declared. */
|
|
397
397
|
declared: Record<string, string>;
|
|
398
|
+
/**
|
|
399
|
+
* The dependency tree ships *inside* the bundle, already installed.
|
|
400
|
+
*
|
|
401
|
+
* Absent or false means the tree is declared but not present, and
|
|
402
|
+
* whoever boots the bundle has to install it. On the managed runtime that
|
|
403
|
+
* install runs in an init container on **every** pod start — the bundle
|
|
404
|
+
* lives on a volume that is wiped each time — and it is the single
|
|
405
|
+
* largest cost in a managed pod's life: 35–55 seconds of a 40–60 second
|
|
406
|
+
* cold start. Since a pod restarts on every eviction, node failure, OOM
|
|
407
|
+
* and runtime rollout, that number is not a startup detail. It is what an
|
|
408
|
+
* outage costs.
|
|
409
|
+
*
|
|
410
|
+
* Vendoring moves the install to build time, where it happens once. It is
|
|
411
|
+
* skipped when the closure contains native code, because a prebuilt
|
|
412
|
+
* binary is only valid for the platform it was built for — see
|
|
413
|
+
* {@link vendorTarget} for what "the platform" means here.
|
|
414
|
+
*/
|
|
415
|
+
vendored?: boolean;
|
|
416
|
+
/**
|
|
417
|
+
* What {@link vendored} was resolved for, recorded so a mismatch can be
|
|
418
|
+
* refused rather than discovered at import time.
|
|
419
|
+
*
|
|
420
|
+
* Cross-platform vendoring is safe for pure JavaScript and unsafe for
|
|
421
|
+
* anything compiled, and the boundary between them is not always visible
|
|
422
|
+
* in a dependency list: `esbuild` is pure-JS with a *platform-specific
|
|
423
|
+
* optional dependency* holding the actual binary, so an install run on a
|
|
424
|
+
* developer's Mac silently produces a tree that cannot run on the Linux
|
|
425
|
+
* image. The install therefore resolves optional dependencies for the
|
|
426
|
+
* target explicitly rather than for the machine it runs on, and records
|
|
427
|
+
* the answer here.
|
|
428
|
+
*/
|
|
429
|
+
vendorTarget?: {
|
|
430
|
+
/** npm `--os`, e.g. `linux`. */
|
|
431
|
+
os: string;
|
|
432
|
+
/** npm `--cpu`, e.g. `x64`. */
|
|
433
|
+
cpu: string;
|
|
434
|
+
/** Node major the tree was resolved for. */
|
|
435
|
+
node: string;
|
|
436
|
+
};
|
|
398
437
|
};
|
|
399
438
|
build: {
|
|
400
439
|
/** `@rebasepro/cli` version that produced this bundle. */
|
|
@@ -29,9 +29,9 @@ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all
|
|
|
29
29
|
* full power of PostgreSQL Row Level Security.
|
|
30
30
|
*
|
|
31
31
|
* The authenticated user's identity is available in raw SQL via:
|
|
32
|
-
* - `
|
|
33
|
-
* - `
|
|
34
|
-
* - `
|
|
32
|
+
* - `rebase.uid()` — the user's ID
|
|
33
|
+
* - `rebase.roles()` — comma-separated app role IDs
|
|
34
|
+
* - `rebase.jwt()` — full JWT claims as JSONB
|
|
35
35
|
*
|
|
36
36
|
* These are set automatically per-transaction by the backend.
|
|
37
37
|
*
|
|
@@ -119,13 +119,13 @@ export interface SecurityRuleBase {
|
|
|
119
119
|
* produce a condition no user can satisfy if used here. These are
|
|
120
120
|
* application roles managed by Rebase, stored as an inline `roles TEXT[]`
|
|
121
121
|
* column on the users table, and injected into each transaction as
|
|
122
|
-
* `app.user_roles` — which `
|
|
122
|
+
* `app.user_roles` — which `rebase.roles()` reads.
|
|
123
123
|
*
|
|
124
124
|
* There is no roles registry: a role exists once it is assigned to a user.
|
|
125
125
|
*
|
|
126
126
|
* Generates a safe array-overlap condition — the user passes if they hold
|
|
127
127
|
* *any* of the listed roles:
|
|
128
|
-
* `string_to_array(
|
|
128
|
+
* `string_to_array(rebase.roles(), ',') && ARRAY['<role1>', '<role2>']`
|
|
129
129
|
*
|
|
130
130
|
* (Note: this is a true set intersection, NOT a regex/substring match, so
|
|
131
131
|
* a role named `admin` never matches `nonadmin` or `superadmin`.)
|
|
@@ -152,7 +152,7 @@ export interface SecurityRuleBase {
|
|
|
152
152
|
*
|
|
153
153
|
* **Important:** These are NOT the same as the application-level
|
|
154
154
|
* {@link roles} (admin, editor, viewer, etc.) — those are enforced in the
|
|
155
|
-
* USING/WITH CHECK clauses via `
|
|
155
|
+
* USING/WITH CHECK clauses via `rebase.roles()`. This field controls the
|
|
156
156
|
* PostgreSQL `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
157
157
|
*
|
|
158
158
|
* Use this if you have dedicated PostgreSQL roles (e.g. `app_read`,
|
|
@@ -168,7 +168,7 @@ export interface SecurityRuleBase {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* Security rule that grants access based on row ownership.
|
|
171
|
-
* Generates a USING/WITH CHECK clause like: `<column> =
|
|
171
|
+
* Generates a USING/WITH CHECK clause like: `<column> = rebase.uid()`
|
|
172
172
|
*
|
|
173
173
|
* Cannot be combined with `using`, `withCheck`, or `access`.
|
|
174
174
|
*
|
|
@@ -271,7 +271,7 @@ export interface StructuredSecurityRule extends SecurityRuleBase {
|
|
|
271
271
|
* // Only the owner, or users with 'moderator' role
|
|
272
272
|
* {
|
|
273
273
|
* operation: "select",
|
|
274
|
-
* using: "{user_id} =
|
|
274
|
+
* using: "{user_id} = rebase.uid() OR rebase.roles() ~ 'moderator'"
|
|
275
275
|
* }
|
|
276
276
|
*
|
|
277
277
|
* @group Models
|
package/package.json
CHANGED
package/src/call_context.ts
CHANGED
|
@@ -67,7 +67,7 @@ export type RebaseCallContext<USER extends User = User> = {
|
|
|
67
67
|
* because RLS filters rather than raises. Write callbacks that tolerate
|
|
68
68
|
* that, or reach for {@link client}`.dataAsAdmin` deliberately when the
|
|
69
69
|
* callback genuinely has to see what an admin may see. Note what that does
|
|
70
|
-
* *not* buy you: `policy.serverContext()` (`
|
|
70
|
+
* *not* buy you: `policy.serverContext()` (`rebase.uid() IS NULL`) is false
|
|
71
71
|
* for the service identity, so a collection whose only rule is
|
|
72
72
|
* `serverContext()` stays closed to it.
|
|
73
73
|
*
|
|
@@ -279,7 +279,7 @@ export interface RebaseClient<DB = unknown> {
|
|
|
279
279
|
*
|
|
280
280
|
* Two consequences the name does not suggest:
|
|
281
281
|
*
|
|
282
|
-
* - `policy.serverContext()` compiles to `
|
|
282
|
+
* - `policy.serverContext()` compiles to `rebase.uid() IS NULL` and is
|
|
283
283
|
* therefore **false** here. A collection with `disableDefaultPolicies:
|
|
284
284
|
* true` whose only rule is `serverContext()` refuses these writes
|
|
285
285
|
* (`42501`) and returns zero rows — HTTP 200, empty — for these reads.
|
package/src/types/backend.ts
CHANGED
|
@@ -470,7 +470,7 @@ export interface SQLAdmin {
|
|
|
470
470
|
* Fetch the *application-level* roles in use in this project.
|
|
471
471
|
*
|
|
472
472
|
* These are the strings stored on the users table's `roles` column and
|
|
473
|
-
* exposed to policies as `
|
|
473
|
+
* exposed to policies as `rebase.roles()` — what `SecurityRule.roles`
|
|
474
474
|
* matches against. Distinct from {@link fetchAvailableRoles}; the two are
|
|
475
475
|
* not interchangeable.
|
|
476
476
|
*/
|
|
@@ -857,6 +857,40 @@ export interface BackendBootstrapper {
|
|
|
857
857
|
log?: (message: string) => void
|
|
858
858
|
): Promise<{ applied: number }>;
|
|
859
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Read the collections schema version this database was last provisioned
|
|
862
|
+
* from, or `null` when nothing has ever stamped it.
|
|
863
|
+
*
|
|
864
|
+
* The companion to {@link stampCollectionsSchemaVersion}: one process writes
|
|
865
|
+
* what it applied, every other process compares itself to it. This is what
|
|
866
|
+
* lets a split deployment — several processes over one database, only one of
|
|
867
|
+
* which provisions — notice that a unit is serving against a schema it was
|
|
868
|
+
* not built for. That failure is otherwise silent in both directions: a
|
|
869
|
+
* column that does not exist is a SQL error on one route, and a policy that
|
|
870
|
+
* was never applied is a 200 with no rows.
|
|
871
|
+
*
|
|
872
|
+
* `null` is not an error and MUST NOT be treated as one — every database
|
|
873
|
+
* provisioned before the stamp existed reads this way, and so does every
|
|
874
|
+
* fresh one until its first provisioning boot finishes.
|
|
875
|
+
*/
|
|
876
|
+
readCollectionsSchemaVersion?(
|
|
877
|
+
driverResult?: InitializedDriver
|
|
878
|
+
): Promise<string | null>;
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Record the collections schema version this process just applied.
|
|
882
|
+
*
|
|
883
|
+
* Called only by the process that provisions, and only after both
|
|
884
|
+
* {@link ensureCollectionSchema} and {@link ensureCollectionPolicies} have
|
|
885
|
+
* run — a stamp written before the policies would claim a schema that is
|
|
886
|
+
* only half in place, and the half that is missing is the one that fails
|
|
887
|
+
* without an error.
|
|
888
|
+
*/
|
|
889
|
+
stampCollectionsSchemaVersion?(
|
|
890
|
+
version: string,
|
|
891
|
+
driverResult?: InitializedDriver
|
|
892
|
+
): Promise<void>;
|
|
893
|
+
|
|
860
894
|
/**
|
|
861
895
|
* Initialize WebSocket server for realtime operations.
|
|
862
896
|
*/
|
package/src/types/collections.ts
CHANGED
|
@@ -283,9 +283,9 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
283
283
|
* 3. **Combined** — mix shortcuts with `roles` for common patterns
|
|
284
284
|
*
|
|
285
285
|
* The authenticated user context is available in raw SQL via:
|
|
286
|
-
* - `
|
|
287
|
-
* - `
|
|
288
|
-
* - `
|
|
286
|
+
* - `rebase.uid()` — the current user's ID
|
|
287
|
+
* - `rebase.roles()` — comma-separated app role IDs
|
|
288
|
+
* - `rebase.jwt()` — full JWT claims as JSONB
|
|
289
289
|
*/
|
|
290
290
|
securityRules?: readonly SecurityRule[];
|
|
291
291
|
|
|
@@ -296,7 +296,7 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
296
296
|
* baseline SELECT policy granting the trusted server context and the
|
|
297
297
|
* `admin` role read access (reads run under a restricted role, so RLS
|
|
298
298
|
* default-denies without it). For auth collections it additionally injects
|
|
299
|
-
* a self-read policy (`id =
|
|
299
|
+
* a self-read policy (`id = rebase.uid()`) and an admin-only write gate
|
|
300
300
|
* (INSERT/UPDATE/DELETE require the `admin` role or the trusted server
|
|
301
301
|
* context), making privileged columns such as `roles` safe by default.
|
|
302
302
|
*
|
package/src/types/cron.ts
CHANGED
|
@@ -99,10 +99,14 @@ export interface CronJobContext {
|
|
|
99
99
|
* hands its callback. Spelled the same way here so that one thing has one
|
|
100
100
|
* name across every server-side authoring surface.
|
|
101
101
|
*
|
|
102
|
-
* Its data plane is {@link RebaseServerClient.dataAsAdmin},
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
102
|
+
* Its data plane is {@link RebaseServerClient.dataAsAdmin}, scoped as
|
|
103
|
+
* `{ uid: "service", roles: ["admin"] }` — **admin-scoped, not
|
|
104
|
+
* RLS-bypassing**. Statements still run as `rebase_user` with the policies
|
|
105
|
+
* evaluated against that identity; the admin role merely clears the default
|
|
106
|
+
* policies through their `rolesOverlap(['admin'])` arm, and
|
|
107
|
+
* `policy.serverContext()` (`rebase.uid() IS NULL`) is *false* for it.
|
|
108
|
+
* `rebase.sql()` is the unconditional bypass. A cron has no per-request
|
|
109
|
+
* user, so there is no user-scoped alternative here: scope every query's
|
|
106
110
|
* filters yourself.
|
|
107
111
|
*
|
|
108
112
|
* @example
|
|
@@ -139,6 +139,34 @@ export interface DatabaseAdapter {
|
|
|
139
139
|
log?: (message: string) => void,
|
|
140
140
|
): Promise<{ applied: number }>;
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Read the collections schema version this database was last provisioned
|
|
144
|
+
* from, or `null` when nothing has ever stamped it.
|
|
145
|
+
*
|
|
146
|
+
* `null` is not an error and MUST NOT be treated as one — every database
|
|
147
|
+
* provisioned before the stamp existed reads this way, and so does every
|
|
148
|
+
* fresh one until its first provisioning boot finishes.
|
|
149
|
+
*
|
|
150
|
+
* Same forwarding requirement as the two hooks above: a wrapper that omits
|
|
151
|
+
* it turns the check off, and a check that is off looks exactly like a check
|
|
152
|
+
* that passed.
|
|
153
|
+
*/
|
|
154
|
+
readCollectionsSchemaVersion?(
|
|
155
|
+
driverResult?: InitializedDriver,
|
|
156
|
+
): Promise<string | null>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Record the collections schema version this process just applied.
|
|
160
|
+
*
|
|
161
|
+
* Called only by the process that provisions, and only after the tables AND
|
|
162
|
+
* the policies are in place — a stamp written earlier would claim a schema
|
|
163
|
+
* that a half-finished boot never finished creating.
|
|
164
|
+
*/
|
|
165
|
+
stampCollectionsSchemaVersion?(
|
|
166
|
+
version: string,
|
|
167
|
+
driverResult?: InitializedDriver,
|
|
168
|
+
): Promise<void>;
|
|
169
|
+
|
|
142
170
|
/**
|
|
143
171
|
* Return admin capabilities for this database (SQL editor, schema browser, branching).
|
|
144
172
|
*/
|
|
@@ -207,4 +235,33 @@ export interface DatabaseAdapterInitConfig {
|
|
|
207
235
|
/** Why it did not, when it did not — safe to print verbatim. */
|
|
208
236
|
reason?: string;
|
|
209
237
|
};
|
|
238
|
+
/**
|
|
239
|
+
* What this process wants from the realtime subsystem.
|
|
240
|
+
*
|
|
241
|
+
* Both halves used to be assumed true, and both were wrong for a split
|
|
242
|
+
* deployment. A `functions` or `worker` process has no websocket clients, so
|
|
243
|
+
* consuming change events buys it a dedicated `LISTEN` connection to deliver
|
|
244
|
+
* to nobody; and it is explicitly not the process that owns schema DDL, so
|
|
245
|
+
* installing capture triggers from it contradicts the one-owner rule the
|
|
246
|
+
* runtime otherwise refuses to boot without.
|
|
247
|
+
*
|
|
248
|
+
* Absent means both — every caller that predates this field is a
|
|
249
|
+
* single-process deployment, where both are true.
|
|
250
|
+
*/
|
|
251
|
+
realtime?: {
|
|
252
|
+
/**
|
|
253
|
+
* Consume change events: open the `LISTEN` connection, start CDC or the
|
|
254
|
+
* app-level fallback. False for a process that serves no websockets.
|
|
255
|
+
*
|
|
256
|
+
* Writes made by a non-consuming process are still published: capture is
|
|
257
|
+
* database triggers, so the publisher is the database.
|
|
258
|
+
*/
|
|
259
|
+
subscribe: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Install what capture needs — the trigger function, the per-table
|
|
262
|
+
* triggers, any channel-history tables. This is DDL, and it follows the
|
|
263
|
+
* same single-owner rule as every other boot-time schema change.
|
|
264
|
+
*/
|
|
265
|
+
provision: boolean;
|
|
266
|
+
};
|
|
210
267
|
}
|
|
@@ -415,6 +415,45 @@ export interface RebaseBundleManifest {
|
|
|
415
415
|
deps: {
|
|
416
416
|
/** Runtime dependencies of user code, as declared. */
|
|
417
417
|
declared: Record<string, string>;
|
|
418
|
+
/**
|
|
419
|
+
* The dependency tree ships *inside* the bundle, already installed.
|
|
420
|
+
*
|
|
421
|
+
* Absent or false means the tree is declared but not present, and
|
|
422
|
+
* whoever boots the bundle has to install it. On the managed runtime that
|
|
423
|
+
* install runs in an init container on **every** pod start — the bundle
|
|
424
|
+
* lives on a volume that is wiped each time — and it is the single
|
|
425
|
+
* largest cost in a managed pod's life: 35–55 seconds of a 40–60 second
|
|
426
|
+
* cold start. Since a pod restarts on every eviction, node failure, OOM
|
|
427
|
+
* and runtime rollout, that number is not a startup detail. It is what an
|
|
428
|
+
* outage costs.
|
|
429
|
+
*
|
|
430
|
+
* Vendoring moves the install to build time, where it happens once. It is
|
|
431
|
+
* skipped when the closure contains native code, because a prebuilt
|
|
432
|
+
* binary is only valid for the platform it was built for — see
|
|
433
|
+
* {@link vendorTarget} for what "the platform" means here.
|
|
434
|
+
*/
|
|
435
|
+
vendored?: boolean;
|
|
436
|
+
/**
|
|
437
|
+
* What {@link vendored} was resolved for, recorded so a mismatch can be
|
|
438
|
+
* refused rather than discovered at import time.
|
|
439
|
+
*
|
|
440
|
+
* Cross-platform vendoring is safe for pure JavaScript and unsafe for
|
|
441
|
+
* anything compiled, and the boundary between them is not always visible
|
|
442
|
+
* in a dependency list: `esbuild` is pure-JS with a *platform-specific
|
|
443
|
+
* optional dependency* holding the actual binary, so an install run on a
|
|
444
|
+
* developer's Mac silently produces a tree that cannot run on the Linux
|
|
445
|
+
* image. The install therefore resolves optional dependencies for the
|
|
446
|
+
* target explicitly rather than for the machine it runs on, and records
|
|
447
|
+
* the answer here.
|
|
448
|
+
*/
|
|
449
|
+
vendorTarget?: {
|
|
450
|
+
/** npm `--os`, e.g. `linux`. */
|
|
451
|
+
os: string;
|
|
452
|
+
/** npm `--cpu`, e.g. `x64`. */
|
|
453
|
+
cpu: string;
|
|
454
|
+
/** Node major the tree was resolved for. */
|
|
455
|
+
node: string;
|
|
456
|
+
};
|
|
418
457
|
};
|
|
419
458
|
build: {
|
|
420
459
|
/** `@rebasepro/cli` version that produced this bundle. */
|
|
@@ -31,9 +31,9 @@ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all
|
|
|
31
31
|
* full power of PostgreSQL Row Level Security.
|
|
32
32
|
*
|
|
33
33
|
* The authenticated user's identity is available in raw SQL via:
|
|
34
|
-
* - `
|
|
35
|
-
* - `
|
|
36
|
-
* - `
|
|
34
|
+
* - `rebase.uid()` — the user's ID
|
|
35
|
+
* - `rebase.roles()` — comma-separated app role IDs
|
|
36
|
+
* - `rebase.jwt()` — full JWT claims as JSONB
|
|
37
37
|
*
|
|
38
38
|
* These are set automatically per-transaction by the backend.
|
|
39
39
|
*
|
|
@@ -131,13 +131,13 @@ export interface SecurityRuleBase {
|
|
|
131
131
|
* produce a condition no user can satisfy if used here. These are
|
|
132
132
|
* application roles managed by Rebase, stored as an inline `roles TEXT[]`
|
|
133
133
|
* column on the users table, and injected into each transaction as
|
|
134
|
-
* `app.user_roles` — which `
|
|
134
|
+
* `app.user_roles` — which `rebase.roles()` reads.
|
|
135
135
|
*
|
|
136
136
|
* There is no roles registry: a role exists once it is assigned to a user.
|
|
137
137
|
*
|
|
138
138
|
* Generates a safe array-overlap condition — the user passes if they hold
|
|
139
139
|
* *any* of the listed roles:
|
|
140
|
-
* `string_to_array(
|
|
140
|
+
* `string_to_array(rebase.roles(), ',') && ARRAY['<role1>', '<role2>']`
|
|
141
141
|
*
|
|
142
142
|
* (Note: this is a true set intersection, NOT a regex/substring match, so
|
|
143
143
|
* a role named `admin` never matches `nonadmin` or `superadmin`.)
|
|
@@ -167,7 +167,7 @@ export interface SecurityRuleBase {
|
|
|
167
167
|
*
|
|
168
168
|
* **Important:** These are NOT the same as the application-level
|
|
169
169
|
* {@link roles} (admin, editor, viewer, etc.) — those are enforced in the
|
|
170
|
-
* USING/WITH CHECK clauses via `
|
|
170
|
+
* USING/WITH CHECK clauses via `rebase.roles()`. This field controls the
|
|
171
171
|
* PostgreSQL `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
172
172
|
*
|
|
173
173
|
* Use this if you have dedicated PostgreSQL roles (e.g. `app_read`,
|
|
@@ -184,7 +184,7 @@ export interface SecurityRuleBase {
|
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* Security rule that grants access based on row ownership.
|
|
187
|
-
* Generates a USING/WITH CHECK clause like: `<column> =
|
|
187
|
+
* Generates a USING/WITH CHECK clause like: `<column> = rebase.uid()`
|
|
188
188
|
*
|
|
189
189
|
* Cannot be combined with `using`, `withCheck`, or `access`.
|
|
190
190
|
*
|
|
@@ -292,7 +292,7 @@ export interface StructuredSecurityRule extends SecurityRuleBase {
|
|
|
292
292
|
* // Only the owner, or users with 'moderator' role
|
|
293
293
|
* {
|
|
294
294
|
* operation: "select",
|
|
295
|
-
* using: "{user_id} =
|
|
295
|
+
* using: "{user_id} = rebase.uid() OR rebase.roles() ~ 'moderator'"
|
|
296
296
|
* }
|
|
297
297
|
*
|
|
298
298
|
* @group Models
|