@rebasepro/server-postgres 0.12.0 → 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 +25 -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/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/index.es.js +545 -9629
- 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/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/{src-BbFOPJ1S.js → src-C_NHNVW2.js} +94 -153
- package/dist/src-C_NHNVW2.js.map +1 -0
- package/dist/{src-Zqwaw3P5.js → src-DoU9yPqq.js} +3 -159
- package/dist/src-DoU9yPqq.js.map +1 -0
- package/dist/utils/pg-error-utils.d.ts +19 -0
- 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 +192 -33
- 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/connection.ts +73 -0
- package/src/databasePoolManager.ts +5 -2
- package/src/schema/auth-schema.ts +30 -19
- 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 +16 -5
- 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 +31 -9
- package/src/services/RelationService.ts +38 -3
- package/src/services/realtimeService.ts +3 -3
- package/src/utils/pg-error-utils.ts +46 -0
- package/src/websocket.ts +3 -3
- package/dist/chunk-DSJWtz9O.js +0 -40
- package/dist/ensure-collection-tables-CNTcZGvn.js +0 -304
- package/dist/ensure-collection-tables-CNTcZGvn.js.map +0 -1
- package/dist/src-BbFOPJ1S.js.map +0 -1
- package/dist/src-Zqwaw3P5.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 {};
|
|
@@ -1,8 +1,61 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
4
|
+
import { l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
|
|
5
|
+
import { a as getDataSourceCapabilities, c as toCanonicalOp, n as isPostgresCollectionConfig, o as NULL_OPS, r as isRelationalCollectionConfig, s as REST_TO_CANONICAL, t as getDeclaredSubcollections } from "./src-DoU9yPqq.js";
|
|
6
|
+
import { n as ANONYMOUS_USER_IDS, r as policy, t as ANONYMOUS_USER_ID } from "./policy-CeA1JcxP.js";
|
|
7
|
+
//#region ../types/src/types/entities.ts
|
|
8
|
+
/**
|
|
9
|
+
* Class used to create a reference to a entity in a different path
|
|
10
|
+
*/
|
|
11
|
+
var EntityRelation = class {
|
|
12
|
+
__type = "relation";
|
|
13
|
+
/**
|
|
14
|
+
* ID of the entity
|
|
15
|
+
*/
|
|
16
|
+
id;
|
|
17
|
+
/**
|
|
18
|
+
* A string representing the path of the referenced document (relative
|
|
19
|
+
* to the root of the database).
|
|
20
|
+
*/
|
|
21
|
+
path;
|
|
22
|
+
/**
|
|
23
|
+
* Pre-fetched data payload to eliminate N+1 queries.
|
|
24
|
+
* When present, clients can use this directly instead of fetching.
|
|
25
|
+
*/
|
|
26
|
+
data;
|
|
27
|
+
constructor(id, path, data) {
|
|
28
|
+
this.id = id;
|
|
29
|
+
this.path = path;
|
|
30
|
+
this.data = data;
|
|
31
|
+
}
|
|
32
|
+
get pathWithId() {
|
|
33
|
+
return `${this.path}/${this.id}`;
|
|
34
|
+
}
|
|
35
|
+
isEntityReference() {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
isEntityRelation() {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var Vector = class {
|
|
43
|
+
value;
|
|
44
|
+
constructor(value) {
|
|
45
|
+
this.value = value;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../types/src/types/relations.ts
|
|
50
|
+
/** @group Models */
|
|
51
|
+
function hasForeignKeyOnTarget(relation) {
|
|
52
|
+
return relation.kind === "hasOne" || relation.kind === "hasMany";
|
|
53
|
+
}
|
|
54
|
+
/** @group Models */
|
|
55
|
+
function isManyToMany(relation) {
|
|
56
|
+
return relation.kind === "manyToMany";
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
6
59
|
//#region ../common/src/util/common.ts
|
|
7
60
|
var DEFAULT_ONE_OF_TYPE = "type";
|
|
8
61
|
var DEFAULT_ONE_OF_VALUE = "value";
|
|
@@ -1811,7 +1864,12 @@ var UID_NOT_NULL = /auth\.uid\(\)\s+IS\s+NOT\s+NULL/i;
|
|
|
1811
1864
|
* is how the trusted *server* context is recognised), so:
|
|
1812
1865
|
*
|
|
1813
1866
|
* - `auth.uid() IS NOT NULL` is a tautology on the user path, and
|
|
1814
|
-
* - `auth.uid() != 'anon'`
|
|
1867
|
+
* - `auth.uid() != 'anon'` excludes one spelling of anonymous and admits the
|
|
1868
|
+
* other. This one is not hypothetical and was not only a foreign habit:
|
|
1869
|
+
* rebase's own request path reported `'anon'` while everything that compiled
|
|
1870
|
+
* or checked a policy used `'anonymous'`, so whichever literal an author
|
|
1871
|
+
* picked, half the anonymous callers walked through. See
|
|
1872
|
+
* {@link ANONYMOUS_USER_IDS}.
|
|
1815
1873
|
*
|
|
1816
1874
|
* Either one turns a lockdown into a full grant, and neither looks wrong. No
|
|
1817
1875
|
* real user id is ever one of these literals, and a user-context request is
|
|
@@ -1849,7 +1907,7 @@ function findAnonymousGrants(expr) {
|
|
|
1849
1907
|
found.push({
|
|
1850
1908
|
pattern: "foreign-uid-literal",
|
|
1851
1909
|
detail: literal.value,
|
|
1852
|
-
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in".`
|
|
1910
|
+
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in" — it compiles to NOT IN (${ANONYMOUS_USER_IDS.map((v) => `'${v}'`).join(", ")}), covering every spelling rebase has reported rather than whichever one you remember.`
|
|
1853
1911
|
});
|
|
1854
1912
|
return;
|
|
1855
1913
|
}
|
|
@@ -1944,7 +2002,7 @@ function compile(expr, scope) {
|
|
|
1944
2002
|
}
|
|
1945
2003
|
case "rolesOverlap": return `string_to_array(auth.roles(), ',') && ${rolesArraySql(expr.roles)}`;
|
|
1946
2004
|
case "rolesContain": return `string_to_array(auth.roles(), ',') @> ${rolesArraySql(expr.roles)}`;
|
|
1947
|
-
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid()
|
|
2005
|
+
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid() NOT IN (${ANONYMOUS_USER_IDS.map(quoteLiteral).join(", ")})`;
|
|
1948
2006
|
case "serverContext": return "auth.uid() IS NULL";
|
|
1949
2007
|
case "existsIn": return compileExistsIn(expr, scope);
|
|
1950
2008
|
case "raw": return expr.sql.replace(/\{(\w+)\}/g, (_, col) => `${outerQualifier(scope)}${resolveColumnName(col, scope.outerCollection)}`);
|
|
@@ -2016,75 +2074,6 @@ function rolesArraySql(roles) {
|
|
|
2016
2074
|
return `ARRAY[${[...roles].sort().map((r) => `'${r}'`).join(",")}]`;
|
|
2017
2075
|
}
|
|
2018
2076
|
//#endregion
|
|
2019
|
-
//#region ../common/src/util/callbacks.ts
|
|
2020
|
-
/**
|
|
2021
|
-
* Helper function to recursively check if there are any callbacks in the properties.
|
|
2022
|
-
*/
|
|
2023
|
-
function hasPropertyCallbacks(properties, callbackName) {
|
|
2024
|
-
if (!properties) return false;
|
|
2025
|
-
for (const property of Object.values(properties)) {
|
|
2026
|
-
if (property.callbacks?.[callbackName]) return true;
|
|
2027
|
-
if (property.type === "map" && property.properties) {
|
|
2028
|
-
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
2029
|
-
} else if (property.type === "array" && property.of) {
|
|
2030
|
-
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
2031
|
-
for (const of of ofs) {
|
|
2032
|
-
if (of.callbacks?.[callbackName]) return true;
|
|
2033
|
-
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
return false;
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Recursively process properties to apply field-level hooks.
|
|
2041
|
-
*/
|
|
2042
|
-
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
2043
|
-
if (!values || typeof values !== "object") return values;
|
|
2044
|
-
const result = { ...values };
|
|
2045
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
2046
|
-
if (result[key] === void 0) continue;
|
|
2047
|
-
let currentValue = result[key];
|
|
2048
|
-
const previousValue = previousValues?.[key];
|
|
2049
|
-
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
2050
|
-
if (property.of && !Array.isArray(property.of)) currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
2051
|
-
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
2052
|
-
return (await processProperties({ "_tmp": property.of }, { "_tmp": item }, { "_tmp": prevItem }, propsContext, callbackName))["_tmp"];
|
|
2053
|
-
}));
|
|
2054
|
-
} else if (property.type === "map" && property.properties && typeof currentValue === "object") currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
2055
|
-
if (property.callbacks?.[callbackName]) {
|
|
2056
|
-
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
2057
|
-
...propsContext,
|
|
2058
|
-
value: currentValue,
|
|
2059
|
-
previousValue
|
|
2060
|
-
}));
|
|
2061
|
-
if (cbRes !== void 0) currentValue = cbRes;
|
|
2062
|
-
}
|
|
2063
|
-
result[key] = currentValue;
|
|
2064
|
-
}
|
|
2065
|
-
return result;
|
|
2066
|
-
}
|
|
2067
|
-
/**
|
|
2068
|
-
* Helper function to extract field-level PropertyCallbacks from a properties schema
|
|
2069
|
-
* and wrap them into an CollectionCallbacks object recursively.
|
|
2070
|
-
*/
|
|
2071
|
-
var buildPropertyCallbacks = (properties) => {
|
|
2072
|
-
if (!properties) return void 0;
|
|
2073
|
-
const propertyCallbacks = {};
|
|
2074
|
-
if (hasPropertyCallbacks(properties, "afterRead")) propertyCallbacks.afterRead = async (props) => {
|
|
2075
|
-
const row = props.row;
|
|
2076
|
-
const processedValues = await processProperties(properties, row, row, props, "afterRead");
|
|
2077
|
-
return {
|
|
2078
|
-
...props.row,
|
|
2079
|
-
...processedValues
|
|
2080
|
-
};
|
|
2081
|
-
};
|
|
2082
|
-
if (hasPropertyCallbacks(properties, "beforeSave")) propertyCallbacks.beforeSave = async (props) => {
|
|
2083
|
-
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
2084
|
-
};
|
|
2085
|
-
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
2086
|
-
};
|
|
2087
|
-
//#endregion
|
|
2088
2077
|
//#region ../common/src/util/auth-default-policies.ts
|
|
2089
2078
|
/**
|
|
2090
2079
|
* Default RLS policies injected by the schema generator.
|
|
@@ -2659,10 +2648,27 @@ function getJunctionSecurityRules(spec) {
|
|
|
2659
2648
|
});
|
|
2660
2649
|
})))();
|
|
2661
2650
|
/**
|
|
2662
|
-
*
|
|
2651
|
+
* How wide a `varchar`/`char` column should be for a given property.
|
|
2652
|
+
*
|
|
2653
|
+
* One definition, three call sites, because they used to disagree. For the same
|
|
2654
|
+
* `columnType: "varchar"` property the DDL generator emitted `VARCHAR(255)`
|
|
2655
|
+
* while the Drizzle generator emitted a bare `varchar("col")` — which Postgres
|
|
2656
|
+
* reads as *unbounded* — so which of the two you ran decided whether the column
|
|
2657
|
+
* had a limit at all. Introspection then dropped the length entirely, so reading
|
|
2658
|
+
* an existing `character varying(500)` column back and regenerating it produced
|
|
2659
|
+
* a `VARCHAR(255)`: a silent narrowing of a column with data already in it.
|
|
2660
|
+
*
|
|
2661
|
+
* `validation.max` is the property's own statement about how long the value may
|
|
2662
|
+
* be, so it is the only sensible source for the column's width — and it keeps
|
|
2663
|
+
* the constraint the database enforces in step with the one the app enforces,
|
|
2664
|
+
* rather than inventing a second, different limit underneath it.
|
|
2663
2665
|
*/
|
|
2666
|
+
function resolveStringColumnLength(prop) {
|
|
2667
|
+
const max = prop.validation?.max;
|
|
2668
|
+
return typeof max === "number" && Number.isInteger(max) && max > 0 ? max : 255;
|
|
2669
|
+
}
|
|
2664
2670
|
//#endregion
|
|
2665
|
-
//#region ../../node_modules/.pnpm/fast-equals@6.0.
|
|
2671
|
+
//#region ../../node_modules/.pnpm/fast-equals@6.0.2/node_modules/fast-equals/dist/es/index.mjs
|
|
2666
2672
|
var { getOwnPropertyNames, getOwnPropertySymbols } = Object;
|
|
2667
2673
|
var { hasOwnProperty } = Object.prototype;
|
|
2668
2674
|
/**
|
|
@@ -2698,7 +2704,8 @@ function createIsCircular(areItemsEqual) {
|
|
|
2698
2704
|
* not enumerable and symbol properties.
|
|
2699
2705
|
*/
|
|
2700
2706
|
function getStrictProperties(object) {
|
|
2701
|
-
|
|
2707
|
+
const symbols = getOwnPropertySymbols(object);
|
|
2708
|
+
return symbols.length ? getOwnPropertyNames(object).concat(symbols) : getOwnPropertyNames(object);
|
|
2702
2709
|
}
|
|
2703
2710
|
/**
|
|
2704
2711
|
* Whether the object contains the property passed as an own property.
|
|
@@ -2771,7 +2778,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2771
2778
|
const size = a.size;
|
|
2772
2779
|
if (size !== b.size) return false;
|
|
2773
2780
|
if (!size) return true;
|
|
2774
|
-
const matchedIndices = new
|
|
2781
|
+
const matchedIndices = new Uint8Array(size);
|
|
2775
2782
|
const aIterable = a.entries();
|
|
2776
2783
|
let aResult;
|
|
2777
2784
|
let bResult;
|
|
@@ -2779,7 +2786,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2779
2786
|
while (aResult = aIterable.next()) {
|
|
2780
2787
|
if (aResult.done) break;
|
|
2781
2788
|
const bIterable = b.entries();
|
|
2782
|
-
let hasMatch =
|
|
2789
|
+
let hasMatch = 0;
|
|
2783
2790
|
let matchIndex = 0;
|
|
2784
2791
|
while (bResult = bIterable.next()) {
|
|
2785
2792
|
if (bResult.done) break;
|
|
@@ -2790,7 +2797,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2790
2797
|
const aEntry = aResult.value;
|
|
2791
2798
|
const bEntry = bResult.value;
|
|
2792
2799
|
if (state.equals(aEntry[0], bEntry[0], index, matchIndex, a, b, state) && state.equals(aEntry[1], bEntry[1], aEntry[0], bEntry[0], a, b, state)) {
|
|
2793
|
-
hasMatch = matchedIndices[matchIndex] =
|
|
2800
|
+
hasMatch = matchedIndices[matchIndex] = 1;
|
|
2794
2801
|
break;
|
|
2795
2802
|
}
|
|
2796
2803
|
matchIndex++;
|
|
@@ -2848,19 +2855,19 @@ function areSetsEqual(a, b, state) {
|
|
|
2848
2855
|
const size = a.size;
|
|
2849
2856
|
if (size !== b.size) return false;
|
|
2850
2857
|
if (!size) return true;
|
|
2851
|
-
const matchedIndices = new
|
|
2858
|
+
const matchedIndices = new Uint8Array(size);
|
|
2852
2859
|
const aIterable = a.values();
|
|
2853
2860
|
let aResult;
|
|
2854
2861
|
let bResult;
|
|
2855
2862
|
while (aResult = aIterable.next()) {
|
|
2856
2863
|
if (aResult.done) break;
|
|
2857
2864
|
const bIterable = b.values();
|
|
2858
|
-
let hasMatch =
|
|
2865
|
+
let hasMatch = 0;
|
|
2859
2866
|
let matchIndex = 0;
|
|
2860
2867
|
while (bResult = bIterable.next()) {
|
|
2861
2868
|
if (bResult.done) break;
|
|
2862
2869
|
if (!matchedIndices[matchIndex] && state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state)) {
|
|
2863
|
-
hasMatch = matchedIndices[matchIndex] =
|
|
2870
|
+
hasMatch = matchedIndices[matchIndex] = 1;
|
|
2864
2871
|
break;
|
|
2865
2872
|
}
|
|
2866
2873
|
matchIndex++;
|
|
@@ -2873,8 +2880,8 @@ function areSetsEqual(a, b, state) {
|
|
|
2873
2880
|
* Whether the TypedArray instances are equal in value.
|
|
2874
2881
|
*/
|
|
2875
2882
|
function areTypedArraysEqual(a, b) {
|
|
2876
|
-
let index = a.
|
|
2877
|
-
if (b.
|
|
2883
|
+
let index = a.length;
|
|
2884
|
+
if (b.length !== index || a.byteOffset !== b.byteOffset) return false;
|
|
2878
2885
|
while (index-- > 0) if (a[index] !== b[index]) return false;
|
|
2879
2886
|
return true;
|
|
2880
2887
|
}
|
|
@@ -4102,72 +4109,6 @@ function buildSdkData(driver) {
|
|
|
4102
4109
|
return wrapAsSdkData(buildRebaseData(driver));
|
|
4103
4110
|
}
|
|
4104
4111
|
//#endregion
|
|
4105
|
-
|
|
4106
|
-
/** Schemas that are always considered Rebase-internal. */
|
|
4107
|
-
var REBASE_INTERNAL_SCHEMAS = ["rebase", "auth"];
|
|
4108
|
-
/** Table-name prefixes that mark a table as Rebase-internal regardless of schema. */
|
|
4109
|
-
var REBASE_INTERNAL_PREFIXES = [
|
|
4110
|
-
"_rebase_",
|
|
4111
|
-
"_auth_",
|
|
4112
|
-
"drizzle_"
|
|
4113
|
-
];
|
|
4114
|
-
/**
|
|
4115
|
-
* Synchronously classify a table based on naming conventions.
|
|
4116
|
-
*
|
|
4117
|
-
* @param tableName - The unqualified name of the table.
|
|
4118
|
-
* @param schemaName - The schema the table belongs to (e.g. `"public"`, `"rebase"`).
|
|
4119
|
-
* @returns `"rebase-internal"` when the table belongs to a reserved schema or
|
|
4120
|
-
* carries a reserved prefix; `"user"` otherwise.
|
|
4121
|
-
*
|
|
4122
|
-
* @remarks
|
|
4123
|
-
* Junction-table detection requires an async database query and is therefore
|
|
4124
|
-
* **not** handled by this function. Use {@link detectJunctionTables} to obtain
|
|
4125
|
-
* the set of junction tables, then reclassify as needed.
|
|
4126
|
-
*/
|
|
4127
|
-
function classifyTable(tableName, schemaName) {
|
|
4128
|
-
if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "rebase-internal";
|
|
4129
|
-
return "user";
|
|
4130
|
-
}
|
|
4131
|
-
/** SQL query that detects junction tables in the `public` schema. */
|
|
4132
|
-
var JUNCTION_TABLES_SQL = `
|
|
4133
|
-
SELECT t.table_name
|
|
4134
|
-
FROM information_schema.tables t
|
|
4135
|
-
WHERE t.table_schema = 'public'
|
|
4136
|
-
AND t.table_type = 'BASE TABLE'
|
|
4137
|
-
AND NOT EXISTS (
|
|
4138
|
-
SELECT 1
|
|
4139
|
-
FROM information_schema.columns c
|
|
4140
|
-
WHERE c.table_schema = t.table_schema
|
|
4141
|
-
AND c.table_name = t.table_name
|
|
4142
|
-
AND c.column_name NOT IN (
|
|
4143
|
-
SELECT kcu.column_name
|
|
4144
|
-
FROM information_schema.key_column_usage kcu
|
|
4145
|
-
JOIN information_schema.table_constraints tc
|
|
4146
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
4147
|
-
AND tc.table_schema = kcu.table_schema
|
|
4148
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
4149
|
-
AND kcu.table_schema = t.table_schema
|
|
4150
|
-
AND kcu.table_name = t.table_name
|
|
4151
|
-
)
|
|
4152
|
-
)
|
|
4153
|
-
`;
|
|
4154
|
-
/**
|
|
4155
|
-
* Asynchronously detect junction (link) tables in the `public` schema.
|
|
4156
|
-
*
|
|
4157
|
-
* A junction table is defined as a table where **every** column participates in
|
|
4158
|
-
* at least one foreign-key constraint.
|
|
4159
|
-
*
|
|
4160
|
-
* @param executeSql - A callback that executes a raw SQL string and returns the
|
|
4161
|
-
* resulting rows.
|
|
4162
|
-
* @returns A `Set` containing the names of all detected junction tables.
|
|
4163
|
-
*/
|
|
4164
|
-
async function detectJunctionTables(executeSql) {
|
|
4165
|
-
const rows = await executeSql(JUNCTION_TABLES_SQL);
|
|
4166
|
-
const junctionTables = /* @__PURE__ */ new Set();
|
|
4167
|
-
for (const row of rows) if (typeof row.table_name === "string") junctionTables.add(row.table_name);
|
|
4168
|
-
return junctionTables;
|
|
4169
|
-
}
|
|
4170
|
-
//#endregion
|
|
4171
|
-
export { toSnakeCase as A, createRelationRefWithData as C, getPolicyNamesForRule as D, generateForeignKeyName as E, DEFAULT_ONE_OF_VALUE as M, mergeDeep as O, createRelationRef as S, updateDateAutoValues as T, getTableVarName as _, getJunctionCollectionConfig as a, getDeclaredPrimaryKeys as b, getEffectiveSecurityRules as c, securityRuleToConditions as d, findAnonymousGrants as f, getTableName as g, getEnumVarName as h, CollectionRegistry as i, DEFAULT_ONE_OF_TYPE as j, camelCase as k, buildPropertyCallbacks as l, getColumnName as m, detectJunctionTables as n, getJunctionSecurityRules as o, findRelation as p, buildSdkData as r, resolveJunctionSpecs as s, classifyTable as t, policyToPostgres as u, resolveCollectionRelations as v, normalizeToEntityRelation as w, parseIdValues as x, buildCompositeId as y };
|
|
4112
|
+
export { DEFAULT_ONE_OF_VALUE as A, updateDateAutoValues as C, camelCase as D, mergeDeep as E, isManyToMany as M, Vector as N, toSnakeCase as O, normalizeToEntityRelation as S, getPolicyNamesForRule as T, buildCompositeId as _, getJunctionSecurityRules as a, createRelationRef as b, policyToPostgres as c, findRelation as d, getColumnName as f, resolveCollectionRelations as g, getTableVarName as h, getJunctionCollectionConfig as i, hasForeignKeyOnTarget as j, DEFAULT_ONE_OF_TYPE as k, securityRuleToConditions as l, getTableName as m, CollectionRegistry as n, resolveJunctionSpecs as o, getEnumVarName as p, resolveStringColumnLength as r, getEffectiveSecurityRules as s, buildSdkData as t, findAnonymousGrants as u, getDeclaredPrimaryKeys as v, generateForeignKeyName as w, createRelationRefWithData as x, parseIdValues as y };
|
|
4172
4113
|
|
|
4173
|
-
//# sourceMappingURL=src-
|
|
4114
|
+
//# sourceMappingURL=src-C_NHNVW2.js.map
|