@rebasepro/server-postgres 0.11.1-canary.gfd39654 → 0.12.1-canary.g06f263c
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/PostgresBootstrapper.d.ts +33 -1
- package/dist/auth/services.d.ts +16 -0
- package/dist/backup-service-vAKWJkYL.js +8867 -0
- package/dist/backup-service-vAKWJkYL.js.map +1 -0
- package/dist/cli-helpers.d.ts +24 -0
- package/dist/collections/buildRegistry.d.ts +1 -1
- package/dist/connection-BuZ97wsr.js +250 -0
- package/dist/connection-BuZ97wsr.js.map +1 -0
- package/dist/connection.d.ts +42 -0
- package/dist/ensure-collection-policies-BjSwj0FM.js +57 -0
- package/dist/ensure-collection-policies-BjSwj0FM.js.map +1 -0
- package/dist/ensure-collection-tables-D6-XhqnQ.js +590 -0
- package/dist/ensure-collection-tables-D6-XhqnQ.js.map +1 -0
- package/dist/history/HistoryService.d.ts +9 -29
- package/dist/index.es.js +965 -9705
- package/dist/index.es.js.map +1 -1
- package/dist/policy-CeA1JcxP.js +105 -0
- package/dist/policy-CeA1JcxP.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +83 -144
- package/dist/schema/dynamic-tables.d.ts +1 -1
- package/dist/schema/ensure-collection-policies.d.ts +60 -0
- package/dist/schema/ensure-collection-tables.d.ts +24 -2
- package/dist/schema/generate-postgres-ddl-logic.d.ts +116 -1
- package/dist/schema/introspect-runtime.d.ts +1 -1
- package/dist/services/FetchService.d.ts +36 -1
- package/dist/services/row-pipeline.d.ts +3 -1
- package/dist/{src-3VmUJ8Xn.js → src-C_NHNVW2.js} +288 -168
- package/dist/src-C_NHNVW2.js.map +1 -0
- package/dist/{src-D5xBTl32.js → src-DoU9yPqq.js} +79 -189
- package/dist/src-DoU9yPqq.js.map +1 -0
- package/dist/utils/drizzle-conditions.d.ts +157 -3
- package/dist/utils/pg-error-utils.d.ts +25 -3
- package/dist/websocket-DB7TbFPT.js +529 -0
- package/dist/websocket-DB7TbFPT.js.map +1 -0
- package/package.json +14 -14
- package/src/PostgresAdapter.ts +14 -0
- package/src/PostgresBootstrapper.ts +212 -36
- package/src/auth/ensure-tables.ts +164 -9
- package/src/auth/services.ts +21 -2
- package/src/cli-helpers.ts +44 -0
- package/src/cli.ts +31 -3
- package/src/collections/buildRegistry.ts +1 -1
- package/src/connection.ts +73 -0
- package/src/databasePoolManager.ts +5 -2
- package/src/history/HistoryService.ts +13 -31
- package/src/schema/auth-schema.ts +30 -19
- package/src/schema/dynamic-tables.ts +1 -1
- package/src/schema/ensure-collection-policies.ts +105 -0
- package/src/schema/ensure-collection-tables.test.ts +105 -9
- package/src/schema/ensure-collection-tables.ts +142 -25
- package/src/schema/generate-drizzle-schema-logic.ts +26 -7
- package/src/schema/generate-postgres-ddl-logic.ts +335 -16
- package/src/schema/introspect-runtime.test.ts +56 -8
- package/src/schema/introspect-runtime.ts +32 -10
- package/src/services/FetchService.ts +79 -11
- package/src/services/RelationService.ts +38 -3
- package/src/services/realtimeService.ts +3 -3
- package/src/services/row-pipeline.ts +3 -1
- package/src/utils/drizzle-conditions.ts +509 -45
- package/src/utils/pg-error-utils.ts +98 -3
- package/src/websocket.ts +3 -3
- package/dist/chunk-DSJWtz9O.js +0 -40
- package/dist/ensure-collection-tables-DGMYK0fr.js +0 -304
- package/dist/ensure-collection-tables-DGMYK0fr.js.map +0 -1
- package/dist/src-3VmUJ8Xn.js.map +0 -1
- package/dist/src-D5xBTl32.js.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applying a bundle's RLS policies to a database at boot, idempotently.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* {@link ensureCollectionTables} creates the collection *tables* a managed
|
|
7
|
+
* runtime boots against, but a table with row-level security disabled and no
|
|
8
|
+
* policies is not servable: authenticated requests run as the restricted
|
|
9
|
+
* `rebase_user` role, so a read with no `SELECT` policy returns nothing (a
|
|
10
|
+
* public collection answered 401) and a write with no `INSERT`/`UPDATE` policy
|
|
11
|
+
* is denied. The policies live in the collections' `securityRules`; nothing at
|
|
12
|
+
* boot applied them. `rebase db push` does — but it drives Atlas against a
|
|
13
|
+
* local `DATABASE_URL`, and a managed tenant's database is reachable only from
|
|
14
|
+
* inside the cluster, by the runtime that is already connected to it. So the
|
|
15
|
+
* runtime is the only thing that *can* apply them, and this is where it does.
|
|
16
|
+
*
|
|
17
|
+
* ## Why this is safe to run on every boot
|
|
18
|
+
*
|
|
19
|
+
* Every statement is idempotent: `ENABLE ROW LEVEL SECURITY` is a no-op once
|
|
20
|
+
* enabled, and each policy is a `DROP POLICY IF EXISTS` immediately followed by
|
|
21
|
+
* a `CREATE POLICY`, so re-applying asserts exactly the declared state. It adds
|
|
22
|
+
* and replaces; it never drops data. (It does not *reconcile* — a policy a
|
|
23
|
+
* previous push left behind under an old name is not removed here; that stays a
|
|
24
|
+
* `db push` / `db migrate` concern, alongside destructive schema changes.)
|
|
25
|
+
*
|
|
26
|
+
* Unlike table creation, a failure here is not fatal: RLS stays enabled, so a
|
|
27
|
+
* table whose policies could not be applied fails **closed** (denies) rather
|
|
28
|
+
* than leaking rows. One collection's policy failing (e.g. a rule that
|
|
29
|
+
* references a table a real migration has not created yet) must not crash-loop
|
|
30
|
+
* the whole deployment and take the other collections' working routes down with
|
|
31
|
+
* it. Failures are reported loudly and per-table so the operator can see
|
|
32
|
+
* exactly which collection is not yet servable and why.
|
|
33
|
+
*/
|
|
34
|
+
import { type CollectionConfig } from "@rebasepro/types";
|
|
35
|
+
import { type Queryable } from "./ensure-collection-tables";
|
|
36
|
+
export interface PolicyEnsureResult {
|
|
37
|
+
/** `CREATE POLICY` statements that ran successfully. */
|
|
38
|
+
policiesApplied: number;
|
|
39
|
+
/** Tables that had RLS enabled. */
|
|
40
|
+
tablesSecured: number;
|
|
41
|
+
/** Declared tables absent from the database — left to a real migration. */
|
|
42
|
+
skipped: {
|
|
43
|
+
table: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}[];
|
|
46
|
+
/** Tables whose RLS could not be fully applied (fail closed). */
|
|
47
|
+
failures: {
|
|
48
|
+
table: string;
|
|
49
|
+
error: string;
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Bring the declared collections' RLS policies up to date. Returns what it did.
|
|
54
|
+
*
|
|
55
|
+
* Only tables that already exist are touched: the boot-time table creator runs
|
|
56
|
+
* first, so anything still missing is a table this additive path is not allowed
|
|
57
|
+
* to create (a junction, or a relation left to a migration). Enabling RLS on a
|
|
58
|
+
* non-existent table would error, so those are recorded as skipped, not failed.
|
|
59
|
+
*/
|
|
60
|
+
export declare function ensureCollectionPolicies(client: Queryable, collections: CollectionConfig[], log?: (message: string) => void): Promise<PolicyEnsureResult>;
|
|
@@ -46,9 +46,17 @@ export interface ExistingSchema {
|
|
|
46
46
|
tables: Map<string, Set<string>>;
|
|
47
47
|
/** `schema.typename` of every enum type that already exists. */
|
|
48
48
|
enums: Set<string>;
|
|
49
|
+
/**
|
|
50
|
+
* `schema.table.constraint` of every constraint that already exists.
|
|
51
|
+
*
|
|
52
|
+
* Optional so a caller that only cares about tables can still build one by
|
|
53
|
+
* hand; absent is read as "none known", which at worst re-attempts a
|
|
54
|
+
* constraint that then fails harmlessly as a duplicate.
|
|
55
|
+
*/
|
|
56
|
+
constraints?: Set<string>;
|
|
49
57
|
}
|
|
50
58
|
export interface EnsureAction {
|
|
51
|
-
kind: "create-enum" | "create-table" | "add-column";
|
|
59
|
+
kind: "create-enum" | "create-table" | "add-column" | "add-constraint";
|
|
52
60
|
/** Qualified target, for logging: `public.posts` or `public.posts.title`. */
|
|
53
61
|
target: string;
|
|
54
62
|
sql: string;
|
|
@@ -58,6 +66,20 @@ export interface EnsurePlan {
|
|
|
58
66
|
/** Every statement, in dependency order. Empty when the schema is current. */
|
|
59
67
|
statements: string[];
|
|
60
68
|
}
|
|
69
|
+
export interface EnsureOutcome extends EnsurePlan {
|
|
70
|
+
/**
|
|
71
|
+
* Constraints that could not be added — always non-fatal.
|
|
72
|
+
*
|
|
73
|
+
* A foreign key can only fail on data that already violates it, and the
|
|
74
|
+
* column it would police exists either way, so the collection still serves.
|
|
75
|
+
* Refusing to boot over one would turn a pre-existing data problem into an
|
|
76
|
+
* outage. Reported loudly instead.
|
|
77
|
+
*/
|
|
78
|
+
failures: {
|
|
79
|
+
target: string;
|
|
80
|
+
error: string;
|
|
81
|
+
}[];
|
|
82
|
+
}
|
|
61
83
|
/**
|
|
62
84
|
* Decide what to add. Pure — the caller supplies what exists and runs the result.
|
|
63
85
|
*
|
|
@@ -76,4 +98,4 @@ export declare function readExistingSchema(client: Queryable, schemas: string[])
|
|
|
76
98
|
* cannot be added, say) should not roll back the tables that were created fine.
|
|
77
99
|
* The error is surfaced with the statement that caused it.
|
|
78
100
|
*/
|
|
79
|
-
export declare function ensureCollectionTables(client: Queryable, collections: CollectionConfig[], log?: (message: string) => void): Promise<
|
|
101
|
+
export declare function ensureCollectionTables(client: Queryable, collections: CollectionConfig[], log?: (message: string) => void): Promise<EnsureOutcome>;
|
|
@@ -1,8 +1,123 @@
|
|
|
1
|
-
import { CollectionConfig, Property } from "@rebasepro/types";
|
|
1
|
+
import { CollectionConfig, Property, SecurityRule } from "@rebasepro/types";
|
|
2
2
|
export declare const resolveColumnName: (propName: string, prop?: Property | null) => string;
|
|
3
|
+
export declare const getPrimaryKeyProp: (collection: CollectionConfig) => {
|
|
4
|
+
name: string;
|
|
5
|
+
type: "string" | "number";
|
|
6
|
+
isUuid: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const isNumericId: (collection: CollectionConfig) => boolean;
|
|
9
|
+
export declare const getPrimaryKeyName: (collection: CollectionConfig) => string;
|
|
3
10
|
export declare const isIdProperty: (propName: string, prop: Property, collection: CollectionConfig) => boolean;
|
|
11
|
+
type ResolveCollection = (slug: string) => CollectionConfig | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The individual SQL statements a single security rule compiles to: a
|
|
14
|
+
* `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete
|
|
15
|
+
* statement (terminated by `;`, no trailing newline).
|
|
16
|
+
*
|
|
17
|
+
* This is the primitive the boot-time RLS applier runs one statement at a time
|
|
18
|
+
* (the runtime's DB handle speaks the extended query protocol, which forbids
|
|
19
|
+
* multiple commands in one execute), while `db push` writes the joined string.
|
|
20
|
+
*/
|
|
21
|
+
export declare const generatePolicyStatements: (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection) => string[];
|
|
4
22
|
export declare const getSqlColumnType: (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]) => string;
|
|
5
23
|
export declare const generatePostgresDdl: (collections: CollectionConfig[], options?: {
|
|
6
24
|
includePolicies?: boolean;
|
|
7
25
|
}) => Promise<string>;
|
|
26
|
+
/** The RLS statements one declared collection's table needs, ready to run. */
|
|
27
|
+
/**
|
|
28
|
+
* A foreign key, as both its parts and the statement that creates it.
|
|
29
|
+
*
|
|
30
|
+
* `ALTER TABLE … ADD CONSTRAINT` has no `IF NOT EXISTS`, so a caller applying
|
|
31
|
+
* these has to skip by name — hence the name is a field and not only a substring
|
|
32
|
+
* of the SQL.
|
|
33
|
+
*/
|
|
34
|
+
export interface ForeignKeyPlan {
|
|
35
|
+
constraintName: string;
|
|
36
|
+
schema: string;
|
|
37
|
+
/** Bare table name, no schema prefix. */
|
|
38
|
+
table: string;
|
|
39
|
+
column: string;
|
|
40
|
+
targetSchema: string;
|
|
41
|
+
targetTable: string;
|
|
42
|
+
targetColumn: string;
|
|
43
|
+
sql: string;
|
|
44
|
+
}
|
|
45
|
+
/** A column a `relation` or `reference` property owns on its own table. */
|
|
46
|
+
export interface RelationalColumnPlan {
|
|
47
|
+
schema: string;
|
|
48
|
+
/** Bare table name, no schema prefix. */
|
|
49
|
+
table: string;
|
|
50
|
+
column: string;
|
|
51
|
+
/** Postgres type, exactly as the DDL generator declares it. */
|
|
52
|
+
type: string;
|
|
53
|
+
/** Absent when the target collection is not part of this bundle. */
|
|
54
|
+
foreignKey?: ForeignKeyPlan;
|
|
55
|
+
}
|
|
56
|
+
/** The table behind a many-to-many `through` relation. */
|
|
57
|
+
export interface JunctionTablePlan {
|
|
58
|
+
schema: string;
|
|
59
|
+
/** Bare table name, no schema prefix. */
|
|
60
|
+
table: string;
|
|
61
|
+
columns: {
|
|
62
|
+
name: string;
|
|
63
|
+
type: string;
|
|
64
|
+
}[];
|
|
65
|
+
/** Both endpoint columns plus the composite primary key. */
|
|
66
|
+
createTable: string;
|
|
67
|
+
foreignKeys: ForeignKeyPlan[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The FK columns the declared collections own — one entry per `relation`
|
|
71
|
+
* (`belongsTo` side) or `reference` property.
|
|
72
|
+
*
|
|
73
|
+
* Split out of {@link generatePostgresDdl} so the boot-time schema ensure can
|
|
74
|
+
* create the same columns with the same names, types and constraints. Before
|
|
75
|
+
* this it skipped them outright, which was survivable only because `db push`
|
|
76
|
+
* always followed; on a managed tenant nothing follows, so a table arrived
|
|
77
|
+
* without the column its own collection reads and wrote 400 on every insert.
|
|
78
|
+
*
|
|
79
|
+
* A relation whose target is not in the bundle yields no column at all (the
|
|
80
|
+
* generator returns early on an unresolvable target); a `reference` whose target
|
|
81
|
+
* is unknown yields the column without a constraint. Both mirror the generator
|
|
82
|
+
* exactly — a divergence here is a schema fork between boot and `db push`.
|
|
83
|
+
*/
|
|
84
|
+
export declare const planRelationalColumns: (collections: CollectionConfig[]) => RelationalColumnPlan[];
|
|
85
|
+
/**
|
|
86
|
+
* The junction tables a bundle's many-to-many relations imply.
|
|
87
|
+
*
|
|
88
|
+
* Derived from {@link resolveJunctionSpecs}, the same source the junction RLS
|
|
89
|
+
* comes from, so a table created here always has policies planned for it — a
|
|
90
|
+
* junction with row-level security left off is readable and writable by every
|
|
91
|
+
* signed-in user, which is why the two must ship together.
|
|
92
|
+
*/
|
|
93
|
+
export declare const planJunctionTables: (collections: CollectionConfig[]) => JunctionTablePlan[];
|
|
94
|
+
export interface CollectionPolicyPlan {
|
|
95
|
+
/** The table's schema (e.g. `public`, `rebase`). */
|
|
96
|
+
schema: string;
|
|
97
|
+
/** The bare table name, no schema prefix. */
|
|
98
|
+
table: string;
|
|
99
|
+
/** `schema.table` — matches the keys `readExistingSchema` returns. */
|
|
100
|
+
qualified: string;
|
|
101
|
+
/** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */
|
|
102
|
+
enableRls: string;
|
|
103
|
+
/** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */
|
|
104
|
+
policyStatements: string[];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The per-table RLS plan for the *declared* collections, as executable
|
|
108
|
+
* statements — what the managed runtime applies at boot so a freshly
|
|
109
|
+
* provisioned tenant database serves data instead of 401ing every read.
|
|
110
|
+
*
|
|
111
|
+
* Mirrors {@link generatePostgresPoliciesDdl} exactly (same
|
|
112
|
+
* `generatePolicyStatements`, same enable-RLS, same effective rules, same
|
|
113
|
+
* derived junction rules), so boot and `db push` produce identical policies from
|
|
114
|
+
* identical collections.
|
|
115
|
+
*
|
|
116
|
+
* Junction tables are included, and have to be: boot creates them now
|
|
117
|
+
* ({@link planJunctionTables}), and a junction with RLS left off is readable and
|
|
118
|
+
* writable by every signed-in user. A junction whose table is still absent is
|
|
119
|
+
* skipped by the applier, not planned away here.
|
|
120
|
+
*/
|
|
121
|
+
export declare const planCollectionPolicies: (collections: CollectionConfig[]) => CollectionPolicyPlan[];
|
|
8
122
|
export declare const generatePostgresPoliciesDdl: (collections: CollectionConfig[]) => string;
|
|
123
|
+
export {};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* single config file.
|
|
8
8
|
*
|
|
9
9
|
* Distinct from `introspect-db.ts`, which runs the same queries but emits
|
|
10
|
-
* TypeScript *source* for a developer to edit and commit (
|
|
10
|
+
* TypeScript *source* for a developer to edit and commit (declared collections). The two
|
|
11
11
|
* share the mapping helpers in `introspect-db-logic.ts` so a table is described
|
|
12
12
|
* the same way whether it was generated or introspected.
|
|
13
13
|
*/
|
|
@@ -20,6 +20,32 @@ export declare class FetchService {
|
|
|
20
20
|
* Safely narrows the DrizzleClient union type to access db.query[tableName].
|
|
21
21
|
*/
|
|
22
22
|
private getQueryBuilder;
|
|
23
|
+
/**
|
|
24
|
+
* The context the condition builder needs to compile a filter key that is
|
|
25
|
+
* not a column name outright.
|
|
26
|
+
*
|
|
27
|
+
* Two such keys. An owning relation's key resolves through the collection's
|
|
28
|
+
* relations to its foreign-key column; a relation whose link lives on the
|
|
29
|
+
* target table or in a junction resolves to a correlated `EXISTS`, which
|
|
30
|
+
* needs the registry to reach that other table and this table's key column
|
|
31
|
+
* to correlate back.
|
|
32
|
+
*
|
|
33
|
+
* Looked up rather than passed: every read path already has the path, only
|
|
34
|
+
* some have the collection, and a path that names no registered collection
|
|
35
|
+
* (a nested/derived one) is not an error here — the builder simply falls
|
|
36
|
+
* back to guessing the default key shapes, and a relation filter it cannot
|
|
37
|
+
* compile stays unresolvable and so fails closed.
|
|
38
|
+
*/
|
|
39
|
+
private filterContext;
|
|
40
|
+
/**
|
|
41
|
+
* The table column this collection's rows are keyed by, or `undefined`.
|
|
42
|
+
*
|
|
43
|
+
* `getPrimaryKeys` rather than `requirePrimaryKeys`: a collection with no
|
|
44
|
+
* resolvable key is not an error on the filter path — it only means the
|
|
45
|
+
* relation filters that would correlate on it cannot be compiled, which
|
|
46
|
+
* the builder already handles by failing that field closed.
|
|
47
|
+
*/
|
|
48
|
+
private resolveIdColumn;
|
|
23
49
|
/**
|
|
24
50
|
* Build filter conditions from FilterValues
|
|
25
51
|
* Delegates to DrizzleConditionBuilder.buildFilterConditions
|
|
@@ -28,6 +54,15 @@ export declare class FetchService {
|
|
|
28
54
|
/**
|
|
29
55
|
* Resolves the correct Drizzle column for sorting.
|
|
30
56
|
* Automatically maps owning relation property keys to their underlying foreign key column.
|
|
57
|
+
*
|
|
58
|
+
* The relation's own `localKey` is the authority for that foreign key, not
|
|
59
|
+
* `<field>_id`. The default local key comes from `generateForeignKeyName`,
|
|
60
|
+
* which snake-cases *and singularises* — `userProfile` → `user_profile_id`,
|
|
61
|
+
* `users` → `user_id` — and an author can override it outright. A wrong
|
|
62
|
+
* guess resolves to nothing, the caller drops the `ORDER BY`, and the rows
|
|
63
|
+
* come back in whatever order Postgres pleases: paging over that repeats
|
|
64
|
+
* and skips rows rather than erroring. The guesses stay, last, for a
|
|
65
|
+
* caller that hands over no collection to resolve against.
|
|
31
66
|
*/
|
|
32
67
|
private resolveOrderByField;
|
|
33
68
|
/**
|
|
@@ -35,7 +70,7 @@ export declare class FetchService {
|
|
|
35
70
|
* Converts collection relations to a Drizzle-compatible `with` object.
|
|
36
71
|
*
|
|
37
72
|
* When `include` is provided, only those relations are loaded.
|
|
38
|
-
* When `include` is absent, ALL relations are loaded (
|
|
73
|
+
* When `include` is absent, ALL relations are loaded (the admin path).
|
|
39
74
|
*
|
|
40
75
|
* Automatically detects many-to-many junction tables and nests
|
|
41
76
|
* the target relation so actual row data is returned.
|
|
@@ -10,7 +10,9 @@ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionReg
|
|
|
10
10
|
*
|
|
11
11
|
* - `"ref"` — a `{ id, path, __type: "relation" }` reference carrying the
|
|
12
12
|
* target's values. This is what the admin renders.
|
|
13
|
-
* - `"inline"` — the target's own columns, flat. This is what REST serves
|
|
13
|
+
* - `"inline"` — the target's own columns, flat. This is what REST serves, and
|
|
14
|
+
* — since the in-process SDK reads through the same pipeline — what
|
|
15
|
+
* `rebase.data` / `context.data` serve too. A developer never sees a ref.
|
|
14
16
|
*
|
|
15
17
|
* They used to be two functions that happened to agree, and the agreement was
|
|
16
18
|
* not enforced by anything: the row-identity bug had to be fixed five times
|