@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/common/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index.es.js +1254 -591
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1254 -591
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
- package/dist/server-postgresql/src/auth/services.d.ts +7 -3
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
- package/dist/server-postgresql/src/connection.d.ts +34 -1
- package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
- package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
- package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
- package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
- package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
- package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
- package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/drizzle-test/0000_woozy_junta.sql +6 -0
- package/drizzle-test/0001_youthful_arachne.sql +1 -0
- package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
- package/drizzle-test/0003_mean_king_cobra.sql +2 -0
- package/drizzle-test/meta/0000_snapshot.json +47 -0
- package/drizzle-test/meta/0001_snapshot.json +48 -0
- package/drizzle-test/meta/0002_snapshot.json +38 -0
- package/drizzle-test/meta/0003_snapshot.json +48 -0
- package/drizzle-test/meta/_journal.json +34 -0
- package/drizzle-test-out/0000_tan_trauma.sql +6 -0
- package/drizzle-test-out/0001_rapid_drax.sql +1 -0
- package/drizzle-test-out/meta/0000_snapshot.json +44 -0
- package/drizzle-test-out/meta/0001_snapshot.json +54 -0
- package/drizzle-test-out/meta/_journal.json +20 -0
- package/drizzle.test.config.ts +10 -0
- package/package.json +88 -89
- package/scratch.ts +41 -0
- package/src/PostgresBackendDriver.ts +63 -79
- package/src/PostgresBootstrapper.ts +7 -8
- package/src/auth/ensure-tables.ts +158 -86
- package/src/auth/services.ts +109 -50
- package/src/cli.ts +259 -16
- package/src/collections/PostgresCollectionRegistry.ts +6 -6
- package/src/connection.ts +70 -48
- package/src/data-transformer.ts +155 -116
- package/src/databasePoolManager.ts +6 -5
- package/src/history/HistoryService.ts +3 -12
- package/src/interfaces.ts +3 -3
- package/src/schema/auth-schema.ts +26 -3
- package/src/schema/doctor-cli.ts +47 -0
- package/src/schema/doctor.ts +595 -0
- package/src/schema/generate-drizzle-schema-logic.ts +204 -57
- package/src/schema/generate-drizzle-schema.ts +6 -6
- package/src/schema/test-schema.ts +11 -0
- package/src/services/BranchService.ts +5 -5
- package/src/services/EntityFetchService.ts +317 -188
- package/src/services/EntityPersistService.ts +15 -17
- package/src/services/RelationService.ts +299 -37
- package/src/services/entity-helpers.ts +39 -13
- package/src/services/entityService.ts +11 -9
- package/src/services/realtimeService.ts +58 -29
- package/src/utils/drizzle-conditions.ts +25 -24
- package/src/websocket.ts +52 -21
- package/test/auth-services.test.ts +131 -39
- package/test/batch-many-to-many-regression.test.ts +573 -0
- package/test/branchService.test.ts +22 -12
- package/test/data-transformer-hardening.test.ts +417 -0
- package/test/data-transformer.test.ts +175 -0
- package/test/doctor.test.ts +182 -0
- package/test/entityService.errors.test.ts +31 -16
- package/test/entityService.relations.test.ts +155 -59
- package/test/entityService.subcollection-search.test.ts +107 -57
- package/test/entityService.test.ts +105 -47
- package/test/generate-drizzle-schema.test.ts +262 -69
- package/test/historyService.test.ts +31 -16
- package/test/n-plus-one-regression.test.ts +314 -0
- package/test/postgresDataDriver.test.ts +260 -168
- package/test/realtimeService.test.ts +70 -39
- package/test/relation-pipeline-gaps.test.ts +637 -0
- package/test/relations.test.ts +492 -39
- package/test-drizzle-bug.ts +18 -0
- package/test-drizzle-out/0000_cultured_freak.sql +7 -0
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
- package/test-drizzle-out/meta/0000_snapshot.json +55 -0
- package/test-drizzle-out/meta/0001_snapshot.json +63 -0
- package/test-drizzle-out/meta/_journal.json +20 -0
- package/test-drizzle-prompt.sh +2 -0
- package/test-policy-prompt.sh +3 -0
- package/test-programmatic.ts +30 -0
- package/test-programmatic2.ts +59 -0
- package/test-schema-no-policies.ts +12 -0
- package/test_drizzle_mock.js +2 -2
- package/test_find_changed.mjs +3 -1
- package/test_hash.js +14 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +5 -5
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import { EntityCollection, NumberProperty, Property, Relation, RelationProperty, SecurityOperation, SecurityRule, StringProperty } from "@rebasepro/types";
|
|
1
|
+
import { EntityCollection, NumberProperty, Property, Relation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollection, DateProperty, ArrayProperty, MapProperty, ReferenceProperty } from "@rebasepro/types";
|
|
2
2
|
import { getPrimaryKeys } from "../services/entity-helpers";
|
|
3
|
-
import { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations } from "@rebasepro/common";
|
|
3
|
+
import { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations, findRelation } from "@rebasepro/common";
|
|
4
4
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
5
|
-
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
6
|
// --- Helper Functions ---
|
|
7
7
|
|
|
8
|
-
const getPrimaryKeyProp = (collection: EntityCollection): { name: string, type: "string" | "number" } => {
|
|
8
|
+
const getPrimaryKeyProp = (collection: EntityCollection): { name: string, type: "string" | "number", isUuid: boolean } => {
|
|
9
9
|
if (collection.properties) {
|
|
10
10
|
const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => "isId" in (prop as object) && Boolean((prop as unknown as Record<string, unknown>).isId));
|
|
11
11
|
if (idPropEntry) {
|
|
12
|
-
|
|
12
|
+
const prop = idPropEntry[1] as Property;
|
|
13
|
+
const isUuid = prop.type === "string" && "isId" in prop && (prop as StringProperty).isId === "uuid";
|
|
14
|
+
return { name: idPropEntry[0],
|
|
15
|
+
type: prop.type === "number" ? "number" : "string",
|
|
16
|
+
isUuid };
|
|
13
17
|
}
|
|
14
18
|
}
|
|
15
19
|
// Fallback
|
|
16
20
|
const idProp = collection.properties?.["id"] as Property | undefined;
|
|
17
21
|
if (idProp?.type === "number") {
|
|
18
|
-
return { name: "id",
|
|
22
|
+
return { name: "id",
|
|
23
|
+
type: "number",
|
|
24
|
+
isUuid: false };
|
|
19
25
|
}
|
|
20
|
-
|
|
26
|
+
const isUuid = idProp?.type === "string" && "isId" in idProp && (idProp as StringProperty).isId === "uuid";
|
|
27
|
+
return { name: "id",
|
|
28
|
+
type: "string",
|
|
29
|
+
isUuid: isUuid ?? false };
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
const isNumericId = (collection: EntityCollection): boolean => {
|
|
@@ -36,7 +45,7 @@ const isIdProperty = (propName: string, prop: Property, collection: EntityCollec
|
|
|
36
45
|
return !hasExplicitId && propName === "id";
|
|
37
46
|
};
|
|
38
47
|
|
|
39
|
-
const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCollection): string | null => {
|
|
48
|
+
const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCollection, collections: EntityCollection[]): string | null => {
|
|
40
49
|
const colName = toSnakeCase(propName);
|
|
41
50
|
let columnDefinition: string;
|
|
42
51
|
|
|
@@ -56,13 +65,13 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
56
65
|
columnDefinition = `varchar("${colName}")`;
|
|
57
66
|
}
|
|
58
67
|
if (isIdProperty(propName, prop, collection)) {
|
|
59
|
-
columnDefinition +=
|
|
68
|
+
columnDefinition += ".primaryKey()";
|
|
60
69
|
}
|
|
61
70
|
if ("isId" in stringProp && stringProp.isId !== "manual" && stringProp.isId !== true) {
|
|
62
71
|
if (stringProp.isId === "uuid") {
|
|
63
|
-
columnDefinition +=
|
|
72
|
+
columnDefinition += ".defaultRandom()";
|
|
64
73
|
} else if (stringProp.isId === "cuid") {
|
|
65
|
-
columnDefinition +=
|
|
74
|
+
columnDefinition += ".default(sql`cuid()`)";
|
|
66
75
|
} else if (typeof stringProp.isId === "string") {
|
|
67
76
|
const sqlContent = stringProp.isId.startsWith("sql`") && stringProp.isId.endsWith("`")
|
|
68
77
|
? stringProp.isId.substring(4, stringProp.isId.length - 1)
|
|
@@ -71,7 +80,7 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
if (stringProp.validation?.unique) {
|
|
74
|
-
columnDefinition +=
|
|
83
|
+
columnDefinition += ".unique()";
|
|
75
84
|
}
|
|
76
85
|
break;
|
|
77
86
|
}
|
|
@@ -98,10 +107,10 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
if (isId) {
|
|
101
|
-
columnDefinition +=
|
|
110
|
+
columnDefinition += ".primaryKey()";
|
|
102
111
|
}
|
|
103
112
|
if (numProp.validation?.unique) {
|
|
104
|
-
columnDefinition +=
|
|
113
|
+
columnDefinition += ".unique()";
|
|
105
114
|
}
|
|
106
115
|
break;
|
|
107
116
|
}
|
|
@@ -109,7 +118,7 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
109
118
|
columnDefinition = `boolean("${colName}")`;
|
|
110
119
|
break;
|
|
111
120
|
case "date": {
|
|
112
|
-
const dateProp = prop as
|
|
121
|
+
const dateProp = prop as DateProperty;
|
|
113
122
|
if (dateProp.columnType === "date") {
|
|
114
123
|
columnDefinition = `date("${colName}", { mode: 'string' })`;
|
|
115
124
|
} else if (dateProp.columnType === "time") {
|
|
@@ -121,7 +130,7 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
121
130
|
}
|
|
122
131
|
case "map":
|
|
123
132
|
case "array": {
|
|
124
|
-
const arrayOrMapProp = prop as
|
|
133
|
+
const arrayOrMapProp = prop as ArrayProperty | MapProperty;
|
|
125
134
|
if (arrayOrMapProp.columnType === "json") {
|
|
126
135
|
columnDefinition = `json("${colName}")`;
|
|
127
136
|
} else {
|
|
@@ -131,8 +140,8 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
131
140
|
}
|
|
132
141
|
case "relation": {
|
|
133
142
|
const refProp = prop as RelationProperty;
|
|
134
|
-
const resolvedRelations = resolveCollectionRelations(collection
|
|
135
|
-
const relation = resolvedRelations
|
|
143
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
144
|
+
const relation = findRelation(resolvedRelations, refProp.relationName ?? propName);
|
|
136
145
|
|
|
137
146
|
// Only owning one-to-one/many-to-one relations create a column here.
|
|
138
147
|
if (!relation || relation.direction !== "owning" || relation.cardinality !== "one") {
|
|
@@ -160,10 +169,11 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
160
169
|
|
|
161
170
|
const fkColumnName = toSnakeCase(relation.localKey);
|
|
162
171
|
const targetTableVar = getTableVarName(getTableName(targetCollection));
|
|
163
|
-
const
|
|
164
|
-
const
|
|
172
|
+
const pkProp = getPrimaryKeyProp(targetCollection);
|
|
173
|
+
const targetIdField = pkProp.name;
|
|
174
|
+
const baseColumn = pkProp.type === "number" ? `integer("${fkColumnName}")` : (pkProp.isUuid ? `uuid("${fkColumnName}")` : `varchar("${fkColumnName}")`);
|
|
165
175
|
|
|
166
|
-
const onUpdate = relation.onUpdate ? `onUpdate:
|
|
176
|
+
const onUpdate = relation.onUpdate ? `onUpdate: "${relation.onUpdate}"` : "";
|
|
167
177
|
const required = prop.validation?.required;
|
|
168
178
|
const onDeleteVal = relation.onDelete ?? (required ? "cascade" : "set null");
|
|
169
179
|
const onDelete = `onDelete: \"${onDeleteVal}\"`;
|
|
@@ -179,6 +189,30 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
|
|
|
179
189
|
|
|
180
190
|
return ` ${relation.localKey}: ${columnDef}`;
|
|
181
191
|
}
|
|
192
|
+
case "reference": {
|
|
193
|
+
const refProp = prop as ReferenceProperty;
|
|
194
|
+
const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);
|
|
195
|
+
if (!targetCollection) {
|
|
196
|
+
columnDefinition = `varchar("${colName}")`;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const pkProp = getPrimaryKeyProp(targetCollection);
|
|
201
|
+
const targetTableVar = getTableVarName(getTableName(targetCollection));
|
|
202
|
+
const targetIdField = pkProp.name;
|
|
203
|
+
const baseColumn = pkProp.type === "number" ? `integer("${colName}")` : (pkProp.isUuid ? `uuid("${colName}")` : `varchar("${colName}")`);
|
|
204
|
+
|
|
205
|
+
const required = prop.validation?.required;
|
|
206
|
+
const onDelete = required ? "cascade" : "set null";
|
|
207
|
+
const refOptions = `{ onDelete: "${onDelete}" }`;
|
|
208
|
+
|
|
209
|
+
columnDefinition = `${baseColumn}.references(() => ${targetTableVar}.${targetIdField}, ${refOptions})`;
|
|
210
|
+
if (required) {
|
|
211
|
+
columnDefinition += ".notNull()";
|
|
212
|
+
}
|
|
213
|
+
// Skip the standard notNull() handling below because we did it here with references
|
|
214
|
+
return ` ${propName}: ${columnDefinition}`;
|
|
215
|
+
}
|
|
182
216
|
default:
|
|
183
217
|
return null;
|
|
184
218
|
}
|
|
@@ -205,7 +239,7 @@ const resolveRawSql = (expression: string): string => {
|
|
|
205
239
|
* Generates: `(<clause>) AND (string_to_array(auth.roles(), ',') && ARRAY['<role1>','<role2>'])`
|
|
206
240
|
*/
|
|
207
241
|
const wrapWithRoleCheck = (clause: string, roles: string[]): string => {
|
|
208
|
-
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(
|
|
242
|
+
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(",")}]`;
|
|
209
243
|
const roleCondition = `string_to_array(auth.roles(), ',') @> ${rolesArrayString}`;
|
|
210
244
|
return `sql\`(${unwrapSql(clause)}) AND (${roleCondition})\``;
|
|
211
245
|
};
|
|
@@ -226,7 +260,7 @@ const buildUsingClause = (rule: SecurityRule): string | null => {
|
|
|
226
260
|
return resolveRawSql(rule.using);
|
|
227
261
|
}
|
|
228
262
|
if (rule.access === "public") {
|
|
229
|
-
return `
|
|
263
|
+
return "sql`true`";
|
|
230
264
|
}
|
|
231
265
|
if (rule.ownerField) {
|
|
232
266
|
return `sql\`\${table.${rule.ownerField}} = auth.uid()\``;
|
|
@@ -246,6 +280,24 @@ const buildWithCheckClause = (rule: SecurityRule): string | null => {
|
|
|
246
280
|
return buildUsingClause(rule);
|
|
247
281
|
};
|
|
248
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Generates a deterministic hash based on the rule configuration.
|
|
285
|
+
*/
|
|
286
|
+
const getPolicyNameHash = (rule: SecurityRule): string => {
|
|
287
|
+
const data = JSON.stringify({
|
|
288
|
+
a: rule.access,
|
|
289
|
+
m: rule.mode,
|
|
290
|
+
op: rule.operation,
|
|
291
|
+
ops: rule.operations?.slice().sort(),
|
|
292
|
+
own: rule.ownerField,
|
|
293
|
+
rol: rule.roles?.slice().sort(),
|
|
294
|
+
pg: rule.pgRoles?.slice().sort(),
|
|
295
|
+
u: rule.using,
|
|
296
|
+
w: rule.withCheck
|
|
297
|
+
});
|
|
298
|
+
return createHash("sha1").update(data).digest("hex").substring(0, 7);
|
|
299
|
+
};
|
|
300
|
+
|
|
249
301
|
/**
|
|
250
302
|
* Generates Drizzle pgPolicy() calls from a declarative SecurityRule definition.
|
|
251
303
|
*
|
|
@@ -262,11 +314,13 @@ const generatePolicyCode = (tableName: string, rule: SecurityRule, index: number
|
|
|
262
314
|
? rule.operations
|
|
263
315
|
: [rule.operation ?? "all"];
|
|
264
316
|
|
|
317
|
+
const ruleHash = getPolicyNameHash(rule);
|
|
318
|
+
|
|
265
319
|
// Generate one pgPolicy per operation
|
|
266
320
|
return ops.map((op, opIdx) => {
|
|
267
321
|
const policyName = rule.name
|
|
268
322
|
? (ops.length > 1 ? `${rule.name}_${op}` : rule.name)
|
|
269
|
-
: `${tableName}_${op}
|
|
323
|
+
: `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
|
|
270
324
|
|
|
271
325
|
return generateSinglePolicyCode(tableName, rule, op, policyName);
|
|
272
326
|
}).join("");
|
|
@@ -277,7 +331,7 @@ const generatePolicyCode = (tableName: string, rule: SecurityRule, index: number
|
|
|
277
331
|
*/
|
|
278
332
|
const generateSinglePolicyCode = (tableName: string, rule: SecurityRule, operation: SecurityOperation, policyName: string): string => {
|
|
279
333
|
const mode = rule.mode ?? "permissive";
|
|
280
|
-
const roles = rule.roles;
|
|
334
|
+
const roles = rule.roles ? [...rule.roles].sort() : undefined;
|
|
281
335
|
|
|
282
336
|
// Determine which clauses this operation needs:
|
|
283
337
|
// SELECT, DELETE → USING only
|
|
@@ -296,30 +350,30 @@ const generateSinglePolicyCode = (tableName: string, rule: SecurityRule, operati
|
|
|
296
350
|
usingClause = wrapWithRoleCheck(usingClause, roles);
|
|
297
351
|
} else if (needsUsing) {
|
|
298
352
|
// Roles-only rule (e.g. { operation: "select", roles: ["admin"] })
|
|
299
|
-
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(
|
|
353
|
+
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(",")}]`;
|
|
300
354
|
usingClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
|
|
301
355
|
}
|
|
302
356
|
if (withCheckClause) {
|
|
303
357
|
withCheckClause = wrapWithRoleCheck(withCheckClause, roles);
|
|
304
358
|
} else if (needsWithCheck) {
|
|
305
|
-
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(
|
|
359
|
+
const rolesArrayString = `ARRAY[${roles.map(r => `'${r}'`).join(",")}]`;
|
|
306
360
|
withCheckClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
|
|
307
361
|
}
|
|
308
362
|
}
|
|
309
363
|
|
|
310
364
|
// Fallback: if we still have no clauses, deny all (safety net)
|
|
311
365
|
if (!usingClause && needsUsing) {
|
|
312
|
-
usingClause = `
|
|
366
|
+
usingClause = "sql`false`";
|
|
313
367
|
}
|
|
314
368
|
if (!withCheckClause && needsWithCheck) {
|
|
315
|
-
withCheckClause = `
|
|
369
|
+
withCheckClause = "sql`false`";
|
|
316
370
|
}
|
|
317
371
|
|
|
318
372
|
// Build the policy options object
|
|
319
373
|
const parts: string[] = [];
|
|
320
374
|
parts.push(`as: "${mode}"`);
|
|
321
375
|
parts.push(`for: "${operation}"`);
|
|
322
|
-
const toRoles = rule.pgRoles
|
|
376
|
+
const toRoles = rule.pgRoles ? [...rule.pgRoles].sort() : ["public"];
|
|
323
377
|
parts.push(`to: [${toRoles.map(r => `"${r}"`).join(", ")}]`);
|
|
324
378
|
if (usingClause) parts.push(`using: ${usingClause}`);
|
|
325
379
|
if (withCheckClause) parts.push(`withCheck: ${withCheckClause}`);
|
|
@@ -327,8 +381,84 @@ const generateSinglePolicyCode = (tableName: string, rule: SecurityRule, operati
|
|
|
327
381
|
return ` pgPolicy("${policyName}", { ${parts.join(", ")} }),\n`;
|
|
328
382
|
};
|
|
329
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Computes a deterministic shared relation name for Drizzle.
|
|
386
|
+
*
|
|
387
|
+
* Drizzle requires both sides of a relation (owning + inverse) to use the
|
|
388
|
+
* exact same `relationName` string so it can pair them. Each collection
|
|
389
|
+
* definition may use a different local `relationName`, so we need a canonical
|
|
390
|
+
* form that both sides can independently compute.
|
|
391
|
+
*
|
|
392
|
+
* Strategy: `{owningTable}_{foreignKey}`
|
|
393
|
+
* - owning side → `{thisTable}_{localKey}` e.g. "jobs_company_id"
|
|
394
|
+
* - inverse side → `{targetTable}_{foreignKeyOnTarget}` e.g. "jobs_company_id"
|
|
395
|
+
*
|
|
396
|
+
* For M2M with junction tables the owning relation name is already shared via
|
|
397
|
+
* the junction table wiring, so we keep it as-is.
|
|
398
|
+
*
|
|
399
|
+
* Falls back to the local relation name when the counterpart can't be resolved.
|
|
400
|
+
*/
|
|
401
|
+
const computeSharedRelationName = (
|
|
402
|
+
rel: Relation,
|
|
403
|
+
sourceCollection: EntityCollection,
|
|
404
|
+
_collections: EntityCollection[]
|
|
405
|
+
): string => {
|
|
406
|
+
const fallback = rel.relationName ?? toSnakeCase(rel.target().slug);
|
|
407
|
+
|
|
408
|
+
// --- owning one (belongs-to) ---
|
|
409
|
+
if (rel.direction === "owning" && rel.cardinality === "one" && rel.localKey) {
|
|
410
|
+
return `${getTableName(sourceCollection)}_${rel.localKey}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// --- inverse many (one-to-many has-many) ---
|
|
414
|
+
if (rel.direction === "inverse" && rel.cardinality === "many" && rel.foreignKeyOnTarget) {
|
|
415
|
+
// The owning table is the *target*, the FK column is foreignKeyOnTarget
|
|
416
|
+
try {
|
|
417
|
+
const targetCollection = rel.target();
|
|
418
|
+
return `${getTableName(targetCollection)}_${rel.foreignKeyOnTarget}`;
|
|
419
|
+
} catch {
|
|
420
|
+
return fallback;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// --- inverse one (one-to-one inverse) ---
|
|
425
|
+
if (rel.direction === "inverse" && rel.cardinality === "one") {
|
|
426
|
+
if (rel.foreignKeyOnTarget) {
|
|
427
|
+
// FK lives on the target table
|
|
428
|
+
try {
|
|
429
|
+
const targetCollection = rel.target();
|
|
430
|
+
return `${getTableName(targetCollection)}_${rel.foreignKeyOnTarget}`;
|
|
431
|
+
} catch {
|
|
432
|
+
return fallback;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// No explicit foreignKeyOnTarget — try to find the corresponding owning relation
|
|
436
|
+
try {
|
|
437
|
+
const targetCollection = rel.target();
|
|
438
|
+
const targetResolvedRelations = resolveCollectionRelations(targetCollection);
|
|
439
|
+
const correspondingRelation = Object.values(targetResolvedRelations).find(targetRel =>
|
|
440
|
+
targetRel.direction === "owning" &&
|
|
441
|
+
targetRel.cardinality === "one" &&
|
|
442
|
+
targetRel.localKey &&
|
|
443
|
+
targetRel.target().slug === sourceCollection.slug
|
|
444
|
+
);
|
|
445
|
+
if (correspondingRelation && correspondingRelation.localKey) {
|
|
446
|
+
return `${getTableName(targetCollection)}_${correspondingRelation.localKey}`;
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
// ignore
|
|
450
|
+
}
|
|
451
|
+
return fallback;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// --- M2M owning (through) — keep local name (already shared via junction wiring) ---
|
|
455
|
+
// --- M2M inverse — keep local name ---
|
|
456
|
+
// --- joinPath — not emitted as Drizzle relations ---
|
|
457
|
+
return fallback;
|
|
458
|
+
};
|
|
459
|
+
|
|
330
460
|
// --- Main Schema Generation Logic ---
|
|
331
|
-
export const generateSchema = async (collections: EntityCollection[]): Promise<string> => {
|
|
461
|
+
export const generateSchema = async (collections: EntityCollection[], stripPolicies = false): Promise<string> => {
|
|
332
462
|
let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
|
|
333
463
|
|
|
334
464
|
const hasUuid = collections.some(c =>
|
|
@@ -347,7 +477,7 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
347
477
|
const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
|
|
348
478
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
349
479
|
schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';\n`;
|
|
350
|
-
schemaContent +=
|
|
480
|
+
schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
|
|
351
481
|
|
|
352
482
|
const exportedTableVars: string[] = [];
|
|
353
483
|
const exportedEnumVars: string[] = [];
|
|
@@ -386,7 +516,7 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
386
516
|
allTablesToGenerate.set(tableName, { collection });
|
|
387
517
|
}
|
|
388
518
|
|
|
389
|
-
const resolvedRelations = resolveCollectionRelations(collection
|
|
519
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
390
520
|
for (const relation of Object.values(resolvedRelations)) {
|
|
391
521
|
if (relation.through) { // Standard M2M junction table
|
|
392
522
|
const junctionTableName = relation.through.table;
|
|
@@ -424,22 +554,22 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
424
554
|
const onDelete = relation.onDelete ?? "cascade";
|
|
425
555
|
const refOptions = `{ onDelete: \"${onDelete}\" }`;
|
|
426
556
|
|
|
427
|
-
const sourceColType = isNumericId(sourceCollection) ? "integer" : "varchar";
|
|
428
|
-
const targetColType = isNumericId(targetCollection) ? "integer" : "varchar";
|
|
557
|
+
const sourceColType = isNumericId(sourceCollection) ? "integer" : (getPrimaryKeyProp(sourceCollection).isUuid ? "uuid" : "varchar");
|
|
558
|
+
const targetColType = isNumericId(targetCollection) ? "integer" : (getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar");
|
|
429
559
|
const sourceId = getPrimaryKeyName(sourceCollection);
|
|
430
560
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
431
561
|
|
|
432
562
|
schemaContent += `export const ${tableVarName} = pgTable(\"${tableName}\", {\n`;
|
|
433
563
|
schemaContent += ` ${sourceColumn}: ${sourceColType}(\"${toSnakeCase(sourceColumn)}\").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),\n`;
|
|
434
564
|
schemaContent += ` ${targetColumn}: ${targetColType}(\"${toSnakeCase(targetColumn)}\").notNull().references(() => ${getTableVarName(getTableName(targetCollection))}.${targetId}, ${refOptions}),\n`;
|
|
435
|
-
schemaContent +=
|
|
565
|
+
schemaContent += "}, (table) => ({\n";
|
|
436
566
|
schemaContent += ` pk: primaryKey({ columns: [table.${sourceColumn}, table.${targetColumn}] })\n`;
|
|
437
|
-
schemaContent +=
|
|
567
|
+
schemaContent += "}));\n\n";
|
|
438
568
|
} else if (!isJunction) {
|
|
439
569
|
schemaContent += `export const ${tableVarName} = pgTable(\"${tableName}\", {\n`;
|
|
440
570
|
const columns = new Set<string>();
|
|
441
571
|
Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
|
|
442
|
-
const columnString = getDrizzleColumn(propName, prop as Property, collection);
|
|
572
|
+
const columnString = getDrizzleColumn(propName, prop as Property, collection, collections);
|
|
443
573
|
if (columnString) columns.add(columnString);
|
|
444
574
|
});
|
|
445
575
|
|
|
@@ -447,13 +577,13 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
447
577
|
// We should generate a basic id column if one was completely omitted.
|
|
448
578
|
const hasIdColumn = Array.from(columns).some(col => col.includes(".primaryKey()"));
|
|
449
579
|
if (!hasIdColumn) {
|
|
450
|
-
columns.add(
|
|
580
|
+
columns.add(" id: varchar(\"id\").primaryKey()");
|
|
451
581
|
}
|
|
452
582
|
|
|
453
583
|
schemaContent += `${Array.from(columns).join(",\n")}`;
|
|
454
584
|
|
|
455
|
-
const securityRules = (collection
|
|
456
|
-
if (securityRules && securityRules.length > 0) {
|
|
585
|
+
const securityRules = isPostgresCollection(collection) ? collection.securityRules : undefined;
|
|
586
|
+
if (!stripPolicies && securityRules && securityRules.length > 0) {
|
|
457
587
|
schemaContent += "\n}, (table) => ([\n";
|
|
458
588
|
securityRules.forEach((rule: SecurityRule, idx: number) => {
|
|
459
589
|
schemaContent += generatePolicyCode(tableName, rule, idx);
|
|
@@ -500,7 +630,7 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
500
630
|
// inverse many() on the target table.
|
|
501
631
|
let inverseRelationName: string | null = null;
|
|
502
632
|
try {
|
|
503
|
-
const targetRelations = resolveCollectionRelations(targetCollection
|
|
633
|
+
const targetRelations = resolveCollectionRelations(targetCollection);
|
|
504
634
|
for (const [, targetRel] of Object.entries(targetRelations)) {
|
|
505
635
|
if (targetRel.direction === "inverse" &&
|
|
506
636
|
targetRel.cardinality === "many" &&
|
|
@@ -514,37 +644,54 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
514
644
|
}
|
|
515
645
|
|
|
516
646
|
// Source side one(): pairs with owning table's many(junctionTable, { relationName })
|
|
517
|
-
tableRelations.push(` ${relation.through.sourceColumn}: one(${sourceTableVar}, {\n fields: [${tableVarName}.${relation.through.sourceColumn}],\n references: [${sourceTableVar}.${sourceId}],\n relationName: \"${owningRelationName}\"\n })`);
|
|
647
|
+
tableRelations.push(` "${relation.through.sourceColumn}": one(${sourceTableVar}, {\n fields: [${tableVarName}.${relation.through.sourceColumn}],\n references: [${sourceTableVar}.${sourceId}],\n relationName: \"${owningRelationName}\"\n })`);
|
|
518
648
|
|
|
519
649
|
// Target side one(): pairs with inverse table's many(junctionTable, { relationName })
|
|
520
650
|
const targetRelName = inverseRelationName ?? owningRelationName;
|
|
521
|
-
tableRelations.push(` ${relation.through.targetColumn}: one(${targetTableVar}, {\n fields: [${tableVarName}.${relation.through.targetColumn}],\n references: [${targetTableVar}.${targetId}],\n relationName: \"${targetRelName}\"\n })`);
|
|
651
|
+
tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {\n fields: [${tableVarName}.${relation.through.targetColumn}],\n references: [${targetTableVar}.${targetId}],\n relationName: \"${targetRelName}\"\n })`);
|
|
522
652
|
}
|
|
523
653
|
} else {
|
|
524
|
-
const resolvedRelations = resolveCollectionRelations(collection
|
|
654
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
655
|
+
// Defensive safety net: track emitted `drizzleRelationName` values
|
|
656
|
+
// to prevent duplicate one()/many() entries in the generated schema.
|
|
657
|
+
// The root deduplication happens inside resolveCollectionRelations,
|
|
658
|
+
// but this guards against any future regressions in that utility.
|
|
659
|
+
const emittedRelationNames = new Set<string>();
|
|
525
660
|
for (const [relationKey, rel] of Object.entries(resolvedRelations)) {
|
|
526
661
|
try {
|
|
527
662
|
const target = rel.target();
|
|
528
663
|
const targetTableVar = getTableVarName(getTableName(target));
|
|
529
|
-
const relationName = rel.relationName ?? relationKey;
|
|
530
664
|
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
|
|
665
|
+
// Compute a deterministic shared relationName for Drizzle.
|
|
666
|
+
// Both sides of an owning/inverse pair MUST share the same
|
|
667
|
+
// relationName, otherwise Drizzle cannot pair them.
|
|
668
|
+
//
|
|
669
|
+
// Strategy: use "{ownerTable}_{foreignKey}" which is
|
|
670
|
+
// computable from either side:
|
|
671
|
+
// - owning side: {thisTable}_{localKey}
|
|
672
|
+
// - inverse side: {targetTable}_{foreignKeyOnTarget}
|
|
673
|
+
const drizzleRelationName = computeSharedRelationName(rel, collection, collections);
|
|
674
|
+
|
|
675
|
+
// Skip if we've already emitted a relation with this drizzleRelationName
|
|
676
|
+
// for this table — prevents duplicate definitions when
|
|
677
|
+
// resolveCollectionRelations returns alias entries for the same FK.
|
|
678
|
+
const deduplicationKey = `${drizzleRelationName}::${rel.direction}`;
|
|
679
|
+
if (emittedRelationNames.has(deduplicationKey)) continue;
|
|
680
|
+
emittedRelationNames.add(deduplicationKey);
|
|
534
681
|
|
|
535
682
|
if (rel.cardinality === "one") {
|
|
536
683
|
if (rel.direction === "owning" && rel.localKey) {
|
|
537
|
-
tableRelations.push(` ${relationKey}: one(${targetTableVar}, {\n fields: [${tableVarName}.${rel.localKey}],\n references: [${targetTableVar}.${getPrimaryKeyName(target)}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
684
|
+
tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${rel.localKey}],\n references: [${targetTableVar}.${getPrimaryKeyName(target)}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
538
685
|
} else if (rel.direction === "inverse" && rel.foreignKeyOnTarget) {
|
|
539
686
|
const sourceIdField = getPrimaryKeyName(collection);
|
|
540
|
-
tableRelations.push(` ${relationKey}: one(${targetTableVar}, {\n fields: [${tableVarName}.${sourceIdField}],\n references: [${targetTableVar}.${rel.foreignKeyOnTarget}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
687
|
+
tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${sourceIdField}],\n references: [${targetTableVar}.${rel.foreignKeyOnTarget}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
541
688
|
} else if (rel.direction === "inverse" && !rel.foreignKeyOnTarget) {
|
|
542
689
|
// Handle inverse one-to-one relations where the FK is on the target table
|
|
543
690
|
// but foreignKeyOnTarget is not explicitly specified
|
|
544
691
|
// In this case, we need to find the corresponding owning relation on the target
|
|
545
692
|
try {
|
|
546
693
|
const targetCollection = rel.target();
|
|
547
|
-
const targetResolvedRelations = resolveCollectionRelations(targetCollection
|
|
694
|
+
const targetResolvedRelations = resolveCollectionRelations(targetCollection);
|
|
548
695
|
|
|
549
696
|
// Find the owning relation on the target that points back to this collection
|
|
550
697
|
const correspondingRelation = Object.values(targetResolvedRelations).find(targetRel =>
|
|
@@ -555,7 +702,7 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
555
702
|
|
|
556
703
|
if (correspondingRelation && correspondingRelation.localKey) {
|
|
557
704
|
const sourceIdField = getPrimaryKeyName(collection);
|
|
558
|
-
tableRelations.push(` ${relationKey}: one(${targetTableVar}, {\n fields: [${tableVarName}.${sourceIdField}],\n references: [${targetTableVar}.${correspondingRelation.localKey}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
705
|
+
tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${sourceIdField}],\n references: [${targetTableVar}.${correspondingRelation.localKey}],\n relationName: \"${drizzleRelationName}\"\n })`);
|
|
559
706
|
}
|
|
560
707
|
} catch (e) {
|
|
561
708
|
console.warn(`Could not resolve inverse one-to-one relation '${relationKey}':`, e);
|
|
@@ -564,16 +711,16 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
564
711
|
} else if (rel.cardinality === "many") {
|
|
565
712
|
if (rel.direction === "inverse" && rel.foreignKeyOnTarget) {
|
|
566
713
|
// One-to-many inverse relation
|
|
567
|
-
tableRelations.push(` ${relationKey}: many(${targetTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
714
|
+
tableRelations.push(` "${relationKey}": many(${targetTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
568
715
|
} else if (rel.through) {
|
|
569
716
|
// Many-to-many owning relation with explicit junction table
|
|
570
717
|
const junctionTableVar = getTableVarName(rel.through.table);
|
|
571
|
-
tableRelations.push(` ${relationKey}: many(${junctionTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
718
|
+
tableRelations.push(` "${relationKey}": many(${junctionTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
572
719
|
} else if (rel.direction === "inverse" && rel.inverseRelationName) {
|
|
573
720
|
// Many-to-many inverse relation - find the corresponding owning relation's junction table
|
|
574
721
|
try {
|
|
575
722
|
const targetCollection = rel.target();
|
|
576
|
-
const targetResolvedRelations = resolveCollectionRelations(targetCollection
|
|
723
|
+
const targetResolvedRelations = resolveCollectionRelations(targetCollection);
|
|
577
724
|
|
|
578
725
|
// Find the corresponding owning many-to-many relation on the target
|
|
579
726
|
const correspondingRelation = Object.values(targetResolvedRelations).find(targetRel =>
|
|
@@ -585,7 +732,7 @@ export const generateSchema = async (collections: EntityCollection[]): Promise<s
|
|
|
585
732
|
|
|
586
733
|
if (correspondingRelation && correspondingRelation.through) {
|
|
587
734
|
const junctionTableVar = getTableVarName(correspondingRelation.through.table);
|
|
588
|
-
tableRelations.push(` ${relationKey}: many(${junctionTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
735
|
+
tableRelations.push(` "${relationKey}": many(${junctionTableVar}, { relationName: \"${drizzleRelationName}\" })`);
|
|
589
736
|
} else {
|
|
590
737
|
console.warn(`Could not find corresponding owning many-to-many relation for inverse relation '${relationKey}' on '${collection.name}'`);
|
|
591
738
|
}
|
|
@@ -58,15 +58,15 @@ const runGeneration = async (collectionsFilePath?: string, outputPath?: string)
|
|
|
58
58
|
if (stats.isDirectory()) {
|
|
59
59
|
const files = fs.readdirSync(resolvedPath);
|
|
60
60
|
for (const file of files) {
|
|
61
|
-
if ((file.endsWith(
|
|
62
|
-
!file.includes(
|
|
63
|
-
!file.endsWith(
|
|
64
|
-
file !==
|
|
61
|
+
if ((file.endsWith(".ts") || file.endsWith(".js")) &&
|
|
62
|
+
!file.includes(".test.") &&
|
|
63
|
+
!file.endsWith(".d.ts") &&
|
|
64
|
+
file !== "index.ts" && file !== "index.js") {
|
|
65
65
|
|
|
66
66
|
const filePath = path.join(resolvedPath, file);
|
|
67
67
|
try {
|
|
68
68
|
const fileUrl = pathToFileURL(filePath).href;
|
|
69
|
-
const dynamicImport = new Function(
|
|
69
|
+
const dynamicImport = new Function("url", "return import(url)");
|
|
70
70
|
const module = await dynamicImport(fileUrl);
|
|
71
71
|
if (module && module.default) {
|
|
72
72
|
collections.push(module.default);
|
|
@@ -79,7 +79,7 @@ const runGeneration = async (collectionsFilePath?: string, outputPath?: string)
|
|
|
79
79
|
}
|
|
80
80
|
} else {
|
|
81
81
|
const fileUrl = pathToFileURL(resolvedPath).href + `?t=${Date.now()}`;
|
|
82
|
-
const dynamicImport = new Function(
|
|
82
|
+
const dynamicImport = new Function("url", "return import(url)");
|
|
83
83
|
const imported = await dynamicImport(fileUrl);
|
|
84
84
|
collections = imported.backendCollections || imported.collections;
|
|
85
85
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { pgTable, text, pgPolicy } from "drizzle-orm/pg-core";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
|
|
4
|
+
export const testTable = pgTable("test", {
|
|
5
|
+
id: text("id").primaryKey()
|
|
6
|
+
}, (t) => [
|
|
7
|
+
pgPolicy("renamed_policy", { as: "permissive",
|
|
8
|
+
to: "public",
|
|
9
|
+
for: "select",
|
|
10
|
+
using: sql`true` })
|
|
11
|
+
]);
|
|
@@ -103,7 +103,7 @@ export class BranchService {
|
|
|
103
103
|
if (msg.includes("being accessed by other users")) {
|
|
104
104
|
throw new Error(
|
|
105
105
|
`Cannot create branch: the source database "${sourceDb}" has active connections. ` +
|
|
106
|
-
|
|
106
|
+
"Close other clients or connections and try again."
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
throw err;
|
|
@@ -119,7 +119,7 @@ export class BranchService {
|
|
|
119
119
|
return {
|
|
120
120
|
name: sanitizedName,
|
|
121
121
|
parentDatabase: sourceDb,
|
|
122
|
-
createdAt: now
|
|
122
|
+
createdAt: now
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -156,7 +156,7 @@ export class BranchService {
|
|
|
156
156
|
if (msg.includes("being accessed by other users")) {
|
|
157
157
|
throw new Error(
|
|
158
158
|
`Cannot delete branch "${sanitizedName}": the database has active connections. ` +
|
|
159
|
-
|
|
159
|
+
"Close other clients and try again."
|
|
160
160
|
);
|
|
161
161
|
}
|
|
162
162
|
throw err;
|
|
@@ -188,7 +188,7 @@ export class BranchService {
|
|
|
188
188
|
name: row.name as string,
|
|
189
189
|
parentDatabase: row.parent_db as string,
|
|
190
190
|
createdAt: new Date(row.created_at as string),
|
|
191
|
-
sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : undefined
|
|
191
|
+
sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : undefined
|
|
192
192
|
}));
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -231,7 +231,7 @@ export class BranchService {
|
|
|
231
231
|
name: row.name as string,
|
|
232
232
|
parentDatabase: row.parent_db as string,
|
|
233
233
|
createdAt: new Date(row.created_at as string),
|
|
234
|
-
sizeBytes
|
|
234
|
+
sizeBytes
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
}
|