@rebasepro/server-postgres 0.13.0 → 0.13.1-canary.g06dbe5b
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/PostgresBackendDriver.d.ts +48 -1
- package/dist/auth/services.d.ts +19 -0
- package/dist/{src-DlPBctw_.js → auth-users-columns-CBEOeYqa.js} +835 -63
- package/dist/auth-users-columns-CBEOeYqa.js.map +1 -0
- package/dist/{backup-service-CD8o_1Sl.js → backup-service-Bww-Lg0s.js} +2 -2
- package/dist/{backup-service-CD8o_1Sl.js.map → backup-service-Bww-Lg0s.js.map} +1 -1
- package/dist/cli-helpers.d.ts +57 -1
- package/dist/data-transformer.d.ts +7 -2
- package/dist/data_driver-ULAyJEi9.js +193 -0
- package/dist/data_driver-ULAyJEi9.js.map +1 -0
- package/dist/{ensure-collection-policies-ViG8XiPn.js → ensure-collection-policies-B_JMGa5K.js} +2 -2
- package/dist/{ensure-collection-policies-ViG8XiPn.js.map → ensure-collection-policies-B_JMGa5K.js.map} +1 -1
- package/dist/{ensure-collection-tables-CBQdOETu.js → ensure-collection-tables-DzeTEvMv.js} +170 -20
- package/dist/ensure-collection-tables-DzeTEvMv.js.map +1 -0
- package/dist/index.es.js +1365 -338
- package/dist/index.es.js.map +1 -1
- package/dist/rls-bootstrap-sql-Bpv3nUZo.js +244 -0
- package/dist/rls-bootstrap-sql-Bpv3nUZo.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +102 -0
- package/dist/schema/auth-users-columns.d.ts +97 -0
- package/dist/schema/doctor-policy-checks.d.ts +28 -0
- package/dist/schema/doctor.d.ts +23 -25
- package/dist/schema/ensure-collection-tables.d.ts +61 -7
- package/dist/schema/generate-drizzle-schema-logic.d.ts +10 -2
- package/dist/schema/generate-postgres-ddl-logic.d.ts +53 -5
- package/dist/schema/generated-schema-staleness.d.ts +39 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +135 -0
- package/dist/schema/search-column.d.ts +248 -0
- package/dist/security/rls-enforcement.d.ts +61 -5
- package/dist/services/FetchService.d.ts +34 -7
- package/dist/services/RelationService.d.ts +30 -0
- package/dist/services/collection-helpers.d.ts +26 -0
- package/dist/services/dataService.d.ts +5 -0
- package/dist/services/realtimeService.d.ts +131 -21
- package/dist/{src-DoU9yPqq.js → src-C_wvdMnl.js} +91 -2
- package/dist/src-C_wvdMnl.js.map +1 -0
- package/dist/utils/drizzle-conditions.d.ts +124 -2
- package/dist/{websocket-B2LsrINK.js → websocket-D1qbmLZ2.js} +75 -18
- package/dist/websocket-D1qbmLZ2.js.map +1 -0
- package/package.json +9 -8
- package/src/PostgresBackendDriver.ts +172 -6
- package/src/PostgresBootstrapper.ts +55 -6
- package/src/auth/ensure-tables.ts +207 -86
- package/src/auth/services.ts +69 -5
- package/src/cli-helpers.ts +129 -10
- package/src/cli.ts +232 -30
- package/src/collections/validate-relations.ts +124 -17
- package/src/data-transformer.ts +120 -17
- package/src/history/ensure-history-table.ts +7 -0
- package/src/schema/auth-schema.ts +17 -1
- package/src/schema/auth-users-columns.ts +131 -0
- package/src/schema/doctor-cli.ts +12 -63
- package/src/schema/doctor-policy-checks.ts +105 -0
- package/src/schema/doctor.ts +135 -76
- package/src/schema/ensure-collection-tables.ts +374 -32
- package/src/schema/generate-drizzle-schema-logic.ts +132 -42
- package/src/schema/generate-postgres-ddl-logic.ts +294 -16
- package/src/schema/generate-postgres-ddl.ts +25 -2
- package/src/schema/generated-schema-staleness.ts +169 -0
- package/src/schema/introspect-db-logic.ts +66 -34
- package/src/schema/non-sql-collections.test.ts +131 -0
- package/src/schema/rls-bootstrap-sql.ts +288 -0
- package/src/schema/search-column.ts +643 -0
- package/src/security/anonymous-grants.test.ts +4 -2
- package/src/security/rls-enforcement.ts +149 -6
- package/src/services/BranchService.ts +5 -0
- package/src/services/FetchService.ts +175 -108
- package/src/services/PersistService.ts +38 -2
- package/src/services/RelationService.ts +110 -67
- package/src/services/channel-history.ts +14 -0
- package/src/services/channel-presence.ts +13 -0
- package/src/services/collection-helpers.ts +54 -1
- package/src/services/dataService.ts +5 -0
- package/src/services/realtimeService.ts +344 -79
- package/src/utils/drizzle-conditions.ts +365 -23
- package/src/utils/pg-error-utils.ts +8 -3
- package/src/websocket.ts +113 -16
- package/dist/ensure-collection-tables-CBQdOETu.js.map +0 -1
- package/dist/policy-CeA1JcxP.js +0 -105
- package/dist/policy-CeA1JcxP.js.map +0 -1
- package/dist/schema/auth-bootstrap-sql.d.ts +0 -24
- package/dist/src-DlPBctw_.js.map +0 -1
- package/dist/src-DoU9yPqq.js.map +0 -1
- package/dist/websocket-B2LsrINK.js.map +0 -1
- package/src/schema/auth-bootstrap-sql.ts +0 -47
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo, type ResolvedForeignKeyOnTarget, hasForeignKeyOnTarget } from "@rebasepro/types";
|
|
2
2
|
import { getPrimaryKeys } from "../services/collection-helpers";
|
|
3
|
-
import {
|
|
3
|
+
import { buildSearchColumnSpec } from "./search-column";
|
|
4
|
+
import { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength, relationalCollections, sortCollectionsBySlug } from "@rebasepro/common";
|
|
4
5
|
import { toSnakeCase, getPolicyNamesForRule } from "@rebasepro/utils";
|
|
5
6
|
import { logger } from "@rebasepro/server";
|
|
6
7
|
// --- Helper Functions ---
|
|
@@ -10,6 +11,31 @@ import { logger } from "@rebasepro/server";
|
|
|
10
11
|
* Uses the explicit `columnName` when set (e.g. from introspection),
|
|
11
12
|
* falling back to `toSnakeCase(propName)` for manually-authored collections.
|
|
12
13
|
*/
|
|
14
|
+
const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A string literal for the generated schema file.
|
|
18
|
+
*
|
|
19
|
+
* Column names, table names and enum values are all written into this file as
|
|
20
|
+
* literals, and none of them is constrained to be quote-free: a Postgres
|
|
21
|
+
* identifier only has to be quoted, and `O'Brien` is an ordinary enum value.
|
|
22
|
+
* Interpolating them raw ended the literal early — for enum values, inside
|
|
23
|
+
* single quotes, where an apostrophe is not an edge case.
|
|
24
|
+
*/
|
|
25
|
+
const quote = (value: string): string => JSON.stringify(value);
|
|
26
|
+
|
|
27
|
+
/** An object key: verbatim when it is an identifier, quoted otherwise. */
|
|
28
|
+
const propKey = (name: string): string => (JS_IDENTIFIER.test(name) ? name : quote(name));
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A property access on a generated table variable.
|
|
32
|
+
*
|
|
33
|
+
* `users.full name` is not an expression; `users["full name"]` is, and Drizzle
|
|
34
|
+
* treats the two identically.
|
|
35
|
+
*/
|
|
36
|
+
const member = (object: string, key: string): string =>
|
|
37
|
+
(JS_IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${quote(key)}]`);
|
|
38
|
+
|
|
13
39
|
const resolveColumnName = (propName: string, prop?: Property | null): string => {
|
|
14
40
|
if (prop && "columnName" in prop && typeof prop.columnName === "string") {
|
|
15
41
|
return prop.columnName;
|
|
@@ -82,7 +108,14 @@ const isIdProperty = (propName: string, prop: Property, collection: CollectionCo
|
|
|
82
108
|
return !hasExplicitId && propName === "id";
|
|
83
109
|
};
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The Drizzle column declaration a property compiles to, or `null` when the
|
|
113
|
+
* property puts no column on *this* table (an inverse relation, whose column
|
|
114
|
+
* lives on the target). Exported so it can be checked against its DDL twin
|
|
115
|
+
* `getSqlColumnType` directly — the two disagreeing is what left `geopoint`
|
|
116
|
+
* with a database column and no Drizzle key.
|
|
117
|
+
*/
|
|
118
|
+
export const getDrizzleColumn = (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]): string | null => {
|
|
86
119
|
|
|
87
120
|
const colName = resolveColumnName(propName, prop);
|
|
88
121
|
let columnDefinition: string;
|
|
@@ -92,23 +125,23 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
92
125
|
const stringProp = prop as unknown as StringProperty;
|
|
93
126
|
if (stringProp.enum) {
|
|
94
127
|
const enumName = getEnumVarName(getTableName(collection), propName);
|
|
95
|
-
columnDefinition = `${enumName}(
|
|
128
|
+
columnDefinition = `${enumName}(${quote(colName)})`;
|
|
96
129
|
} else if ("isId" in stringProp && stringProp.isId === "uuid") {
|
|
97
|
-
columnDefinition = `uuid(
|
|
130
|
+
columnDefinition = `uuid(${quote(colName)})`;
|
|
98
131
|
} else if (stringProp.columnType === "uuid") {
|
|
99
|
-
columnDefinition = `uuid(
|
|
132
|
+
columnDefinition = `uuid(${quote(colName)})`;
|
|
100
133
|
} else if (stringProp.columnType === "char") {
|
|
101
|
-
columnDefinition = `char(
|
|
134
|
+
columnDefinition = `char(${quote(colName)}, { length: ${resolveStringColumnLength(stringProp)} })`;
|
|
102
135
|
} else if (stringProp.columnType === "varchar") {
|
|
103
136
|
// The length is not optional decoration: `varchar("col")` with
|
|
104
137
|
// no length is an UNBOUNDED varchar in Postgres, which is what
|
|
105
138
|
// this emitted while the DDL generator emitted VARCHAR(255) for
|
|
106
139
|
// the very same property.
|
|
107
|
-
columnDefinition = `varchar(
|
|
140
|
+
columnDefinition = `varchar(${quote(colName)}, { length: ${resolveStringColumnLength(stringProp)} })`;
|
|
108
141
|
} else {
|
|
109
142
|
// `text` is the default, and the only length-unbounded choice.
|
|
110
143
|
// Ask for `varchar` explicitly if you want the length constraint.
|
|
111
|
-
columnDefinition = `text(
|
|
144
|
+
columnDefinition = `text(${quote(colName)})`;
|
|
112
145
|
}
|
|
113
146
|
if (isIdProperty(propName, prop, collection)) {
|
|
114
147
|
columnDefinition += ".primaryKey()";
|
|
@@ -134,9 +167,9 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
134
167
|
const numProp = prop as unknown as NumberProperty;
|
|
135
168
|
const isId = isIdProperty(propName, prop, collection);
|
|
136
169
|
|
|
137
|
-
let baseType = (numProp.validation?.integer || isId) ? `integer(
|
|
170
|
+
let baseType = (numProp.validation?.integer || isId) ? `integer(${quote(colName)})` : `numeric(${quote(colName)})`;
|
|
138
171
|
if (numProp.columnType) {
|
|
139
|
-
if (numProp.columnType === "double precision") baseType = `doublePrecision(
|
|
172
|
+
if (numProp.columnType === "double precision") baseType = `doublePrecision(${quote(colName)})`;
|
|
140
173
|
// `bigint` and `bigserial` are the only pg-core builders that
|
|
141
174
|
// *require* a config argument: without `mode`, drizzle cannot
|
|
142
175
|
// know whether to hand back a `number` or a `bigint`, and the
|
|
@@ -154,9 +187,9 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
154
187
|
// switching the runtime type would be a breaking change to
|
|
155
188
|
// every consumer of the generated schema.
|
|
156
189
|
else if (numProp.columnType === "bigint" || numProp.columnType === "bigserial") {
|
|
157
|
-
baseType = `${numProp.columnType}(
|
|
190
|
+
baseType = `${numProp.columnType}(${quote(colName)}, { mode: "number" })`;
|
|
158
191
|
}
|
|
159
|
-
else baseType = `${numProp.columnType}(
|
|
192
|
+
else baseType = `${numProp.columnType}(${quote(colName)})`;
|
|
160
193
|
}
|
|
161
194
|
|
|
162
195
|
if ("isId" in numProp && numProp.isId === "increment") {
|
|
@@ -180,16 +213,16 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
180
213
|
break;
|
|
181
214
|
}
|
|
182
215
|
case "boolean":
|
|
183
|
-
columnDefinition = `boolean(
|
|
216
|
+
columnDefinition = `boolean(${quote(colName)})`;
|
|
184
217
|
break;
|
|
185
218
|
case "date": {
|
|
186
219
|
const dateProp = prop as DateProperty;
|
|
187
220
|
if (dateProp.columnType === "date") {
|
|
188
|
-
columnDefinition = `date(
|
|
221
|
+
columnDefinition = `date(${quote(colName)}, { mode: 'string' })`;
|
|
189
222
|
} else if (dateProp.columnType === "time") {
|
|
190
|
-
columnDefinition = `time(
|
|
223
|
+
columnDefinition = `time(${quote(colName)})`;
|
|
191
224
|
} else {
|
|
192
|
-
columnDefinition = `timestamp(
|
|
225
|
+
columnDefinition = `timestamp(${quote(colName)}, { withTimezone: true, mode: 'string' })`;
|
|
193
226
|
}
|
|
194
227
|
// autoValue: database-level default for initial value on INSERT
|
|
195
228
|
if (dateProp.autoValue === "on_create" || dateProp.autoValue === "on_update") {
|
|
@@ -200,12 +233,22 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
200
233
|
case "map": {
|
|
201
234
|
const mapProp = prop as MapProperty;
|
|
202
235
|
if (mapProp.columnType === "json") {
|
|
203
|
-
columnDefinition = `json(
|
|
236
|
+
columnDefinition = `json(${quote(colName)})`;
|
|
204
237
|
} else {
|
|
205
|
-
columnDefinition = `jsonb(
|
|
238
|
+
columnDefinition = `jsonb(${quote(colName)})`;
|
|
206
239
|
}
|
|
207
240
|
break;
|
|
208
241
|
}
|
|
242
|
+
case "geopoint": {
|
|
243
|
+
// `{ latitude, longitude }`, which is what the OpenAPI schema, the
|
|
244
|
+
// generated TS type and the admin's field binding all describe.
|
|
245
|
+
// This arm did not exist: `geopoint` fell to `default: return null`
|
|
246
|
+
// and the caller dropped the column, so the DDL generator created a
|
|
247
|
+
// column the Drizzle table had no key for — and every write to it
|
|
248
|
+
// was silently discarded with a 201.
|
|
249
|
+
columnDefinition = `jsonb(${quote(colName)})`;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
209
252
|
case "array": {
|
|
210
253
|
const arrayProp = prop as ArrayProperty;
|
|
211
254
|
let colType = arrayProp.columnType;
|
|
@@ -221,27 +264,27 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
221
264
|
}
|
|
222
265
|
|
|
223
266
|
if (colType === "json") {
|
|
224
|
-
columnDefinition = `json(
|
|
267
|
+
columnDefinition = `json(${quote(colName)})`;
|
|
225
268
|
} else if (colType === "text[]") {
|
|
226
|
-
columnDefinition = `text(
|
|
269
|
+
columnDefinition = `text(${quote(colName)}).array()`;
|
|
227
270
|
} else if (colType === "integer[]") {
|
|
228
|
-
columnDefinition = `integer(
|
|
271
|
+
columnDefinition = `integer(${quote(colName)}).array()`;
|
|
229
272
|
} else if (colType === "boolean[]") {
|
|
230
|
-
columnDefinition = `boolean(
|
|
273
|
+
columnDefinition = `boolean(${quote(colName)}).array()`;
|
|
231
274
|
} else if (colType === "numeric[]") {
|
|
232
|
-
columnDefinition = `numeric(
|
|
275
|
+
columnDefinition = `numeric(${quote(colName)}).array()`;
|
|
233
276
|
} else {
|
|
234
|
-
columnDefinition = `jsonb(
|
|
277
|
+
columnDefinition = `jsonb(${quote(colName)})`;
|
|
235
278
|
}
|
|
236
279
|
break;
|
|
237
280
|
}
|
|
238
281
|
case "vector": {
|
|
239
282
|
const vp = prop as VectorProperty;
|
|
240
|
-
columnDefinition = `vector(
|
|
283
|
+
columnDefinition = `vector(${quote(colName)}, { dimensions: ${vp.dimensions} })`;
|
|
241
284
|
break;
|
|
242
285
|
}
|
|
243
286
|
case "binary": {
|
|
244
|
-
columnDefinition = `customType({ dataType() { return 'bytea'; } })(
|
|
287
|
+
columnDefinition = `customType({ dataType() { return 'bytea'; } })(${quote(colName)})`;
|
|
245
288
|
break;
|
|
246
289
|
}
|
|
247
290
|
case "relation": {
|
|
@@ -282,47 +325,60 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
|
|
|
282
325
|
const refOptionsParts = [onUpdate, onDelete].filter(Boolean);
|
|
283
326
|
const refOptions = refOptionsParts.length > 0 ? `{ ${refOptionsParts.join(", ")} }` : "";
|
|
284
327
|
|
|
285
|
-
let columnDef = `${baseColumn}.references(() => ${targetTableVar
|
|
328
|
+
let columnDef = `${baseColumn}.references(() => ${member(targetTableVar, targetIdField)}${refOptions ? `, ${refOptions}` : ""})`;
|
|
286
329
|
|
|
287
330
|
if (required) {
|
|
288
331
|
columnDef += ".notNull()";
|
|
289
332
|
}
|
|
290
333
|
|
|
291
|
-
return ` ${relation.localKey}: ${columnDef}`;
|
|
334
|
+
return ` ${propKey(relation.localKey)}: ${columnDef}`;
|
|
292
335
|
}
|
|
293
336
|
case "reference": {
|
|
294
337
|
const refProp = prop as ReferenceProperty;
|
|
295
338
|
const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);
|
|
296
339
|
if (!targetCollection) {
|
|
297
|
-
columnDefinition = `text(
|
|
340
|
+
columnDefinition = `text(${quote(colName)})`;
|
|
298
341
|
break;
|
|
299
342
|
}
|
|
300
343
|
|
|
301
344
|
const pkProp = getPrimaryKeyProp(targetCollection);
|
|
302
345
|
const targetTableVar = getTableVarName(getTableName(targetCollection));
|
|
303
346
|
const targetIdField = pkProp.name;
|
|
304
|
-
const baseColumn = pkProp.type === "number" ? `integer(
|
|
347
|
+
const baseColumn = pkProp.type === "number" ? `integer(${quote(colName)})` : (pkProp.isUuid ? `uuid(${quote(colName)})` : `text(${quote(colName)})`);
|
|
305
348
|
|
|
306
349
|
const required = prop.validation?.required;
|
|
307
350
|
const onDelete = required ? "cascade" : "set null";
|
|
308
351
|
const refOptions = `{ onDelete: "${onDelete}" }`;
|
|
309
352
|
|
|
310
|
-
columnDefinition = `${baseColumn}.references(() => ${targetTableVar
|
|
353
|
+
columnDefinition = `${baseColumn}.references(() => ${member(targetTableVar, targetIdField)}, ${refOptions})`;
|
|
311
354
|
if (required) {
|
|
312
355
|
columnDefinition += ".notNull()";
|
|
313
356
|
}
|
|
314
357
|
// Skip the standard notNull() handling below because we did it here with references
|
|
315
|
-
return ` ${propName}: ${columnDefinition}`;
|
|
358
|
+
return ` ${propKey(propName)}: ${columnDefinition}`;
|
|
316
359
|
}
|
|
317
360
|
default:
|
|
318
|
-
return null
|
|
361
|
+
// Not `return null`. A `null` here means "this property puts no
|
|
362
|
+
// column on this table", which is true of an inverse relation and
|
|
363
|
+
// false of everything else — and the caller cannot tell the two
|
|
364
|
+
// apart, so a type this switch simply forgot produced a table
|
|
365
|
+
// missing a column while the DDL generator happily created one.
|
|
366
|
+
// That is how `geopoint` stayed unpersistable: writes to it were
|
|
367
|
+
// dropped before the SQL was built, forever, with a 201. A property
|
|
368
|
+
// type nobody mapped is a generator bug, and it says so here rather
|
|
369
|
+
// than at some caller's next INSERT.
|
|
370
|
+
throw new Error(
|
|
371
|
+
`No Postgres column mapping for property '${propName}' of type ` +
|
|
372
|
+
`'${(prop as Property).type}' in collection '${collection.slug}'. ` +
|
|
373
|
+
"Add a case to `getDrizzleColumn` (and to `getSqlColumnType`, which must agree)."
|
|
374
|
+
);
|
|
319
375
|
}
|
|
320
376
|
|
|
321
377
|
if (prop.validation?.required) {
|
|
322
378
|
columnDefinition += ".notNull()";
|
|
323
379
|
}
|
|
324
380
|
|
|
325
|
-
return ` ${propName}: ${columnDefinition}`;
|
|
381
|
+
return ` ${propKey(propName)}: ${columnDefinition}`;
|
|
326
382
|
};
|
|
327
383
|
|
|
328
384
|
/**
|
|
@@ -399,7 +455,7 @@ const generateSinglePolicyCode = (collection: CollectionConfig, rule: SecurityRu
|
|
|
399
455
|
if (usingClause) parts.push(`using: ${usingClause}`);
|
|
400
456
|
if (withCheckClause) parts.push(`withCheck: ${withCheckClause}`);
|
|
401
457
|
|
|
402
|
-
return ` pgPolicy(
|
|
458
|
+
return ` pgPolicy(${quote(policyName)}, { ${parts.join(", ")} }),\n`;
|
|
403
459
|
};
|
|
404
460
|
|
|
405
461
|
/**
|
|
@@ -451,7 +507,18 @@ const computeSharedRelationName = (
|
|
|
451
507
|
};
|
|
452
508
|
|
|
453
509
|
// --- Main Schema Generation Logic ---
|
|
454
|
-
export const generateSchema = async (
|
|
510
|
+
export const generateSchema = async (allCollections: CollectionConfig[], stripPolicies = false): Promise<string> => {
|
|
511
|
+
// A Firestore or MongoDB collection has no table to generate, and generating
|
|
512
|
+
// one for it is not merely wasted output: `db push` would create it, and
|
|
513
|
+
// `rebase doctor` would then report the store the collection actually reads
|
|
514
|
+
// from as drift. Non-SQL collections leave the toolchain here, once, rather
|
|
515
|
+
// than being filtered again in each stage below.
|
|
516
|
+
//
|
|
517
|
+
// Sorted here rather than in the writer: the output is order-dependent and
|
|
518
|
+
// `rebase doctor` regenerates it in memory to diff against the file on
|
|
519
|
+
// disk. With the sort in the writer only, a project whose file order
|
|
520
|
+
// differed from its slug order was reported stale forever.
|
|
521
|
+
const collections = sortCollectionsBySlug(relationalCollections(allCollections));
|
|
455
522
|
let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
|
|
456
523
|
|
|
457
524
|
|
|
@@ -473,11 +540,15 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
473
540
|
)
|
|
474
541
|
);
|
|
475
542
|
|
|
543
|
+
// drizzle ships no `tsvector` builder, so an opted-in search column reaches
|
|
544
|
+
// the schema the same way `bytea` does — through `customType`.
|
|
545
|
+
const hasSearch = collections.some(c => buildSearchColumnSpec(c) !== undefined);
|
|
546
|
+
|
|
476
547
|
// Always import pgPolicy and sql — RLS is enabled on every table (secure by default)
|
|
477
548
|
const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
|
|
478
549
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
479
550
|
if (hasVector) pgCoreImports.push("vector");
|
|
480
|
-
if (hasBinary) pgCoreImports.push("customType");
|
|
551
|
+
if (hasBinary || hasSearch) pgCoreImports.push("customType");
|
|
481
552
|
|
|
482
553
|
const uniqueSchemas = Array.from(new Set(
|
|
483
554
|
collections.map(c => isPostgresCollectionConfig(c) ? c.schema : undefined).filter(Boolean)
|
|
@@ -521,7 +592,7 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
521
592
|
)
|
|
522
593
|
: Object.keys(prop.enum);
|
|
523
594
|
if (values.length > 0) {
|
|
524
|
-
schemaContent += `export const ${enumVarName} = pgEnum(
|
|
595
|
+
schemaContent += `export const ${enumVarName} = pgEnum(${quote(enumDbName)}, [${values.map(v => quote(v)).join(", ")}]);\n`;
|
|
525
596
|
if (!exportedEnumVars.includes(enumVarName)) exportedEnumVars.push(enumVarName);
|
|
526
597
|
}
|
|
527
598
|
}
|
|
@@ -626,6 +697,25 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
626
697
|
|
|
627
698
|
});
|
|
628
699
|
|
|
700
|
+
// The opt-in search column. Declared with its real generation
|
|
701
|
+
// expression rather than as a bare custom type: `schema.generated.ts`
|
|
702
|
+
// is what a developer running drizzle-kit themselves diffs against,
|
|
703
|
+
// and a column declared without its expression reads to drizzle-kit
|
|
704
|
+
// as one it should alter.
|
|
705
|
+
const searchSpec = buildSearchColumnSpec(collection);
|
|
706
|
+
if (searchSpec) {
|
|
707
|
+
columns.add(
|
|
708
|
+
` ${searchSpec.column}: customType({ dataType() { return 'tsvector'; } })("${searchSpec.column}")` +
|
|
709
|
+
`.generatedAlwaysAs(sql\`${searchSpec.expression}\`)`
|
|
710
|
+
);
|
|
711
|
+
if (searchSpec.fuzzy) {
|
|
712
|
+
columns.add(
|
|
713
|
+
` ${searchSpec.fuzzy.column}: text("${searchSpec.fuzzy.column}")` +
|
|
714
|
+
`.generatedAlwaysAs(sql\`${searchSpec.fuzzy.expression}\`)`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
629
719
|
// Backwards compatibility: if no id/primary key column is found in properties, but `id` wasn't explicitly provided
|
|
630
720
|
// We should generate a basic id column if one was completely omitted.
|
|
631
721
|
const hasIdColumn = Array.from(columns).some(col => col.includes(".primaryKey()"));
|
|
@@ -698,7 +788,7 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
698
788
|
}
|
|
699
789
|
|
|
700
790
|
// Source side one(): pairs with owning table's many(junctionTable, { relationName })
|
|
701
|
-
tableRelations.push(`
|
|
791
|
+
tableRelations.push(` ${quote(relation.through.sourceColumn)}: one(${sourceTableVar}, {\n fields: [${member(tableVarName, relation.through.sourceColumn)}],\n references: [${member(sourceTableVar, sourceId)}],\n relationName: ${quote(owningRelationName)}\n })`);
|
|
702
792
|
|
|
703
793
|
// Target side one(): pairs with inverse table's many(junctionTable, { relationName })
|
|
704
794
|
// Always emit a relationName to avoid collisions with the source-side's owningRelationName.
|
|
@@ -706,7 +796,7 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
706
796
|
const targetRelationName = inverseRelationName
|
|
707
797
|
? inverseRelationName
|
|
708
798
|
: `${tableName}_${relation.through.targetColumn}`;
|
|
709
|
-
tableRelations.push(`
|
|
799
|
+
tableRelations.push(` ${quote(relation.through.targetColumn)}: one(${targetTableVar}, {\n fields: [${member(tableVarName, relation.through.targetColumn)}],\n references: [${member(targetTableVar, targetId)}],\n relationName: ${quote(targetRelationName)}\n })`);
|
|
710
800
|
}
|
|
711
801
|
} else {
|
|
712
802
|
const resolvedRelations = resolveCollectionRelations(collection);
|
|
@@ -746,7 +836,7 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
746
836
|
// schema that does not compile. The three other
|
|
747
837
|
// emission sites normalise; this one did not.
|
|
748
838
|
const localFieldKey = resolvePropertyKeyForColumn(collection, rel.localKey);
|
|
749
|
-
tableRelations.push(`
|
|
839
|
+
tableRelations.push(` ${quote(relationKey)}: one(${targetTableVar}, {\n fields: [${member(tableVarName, localFieldKey)}],\n references: [${member(targetTableVar, getPrimaryKeyName(target))}],\n relationName: ${quote(drizzleRelationName)}\n })`);
|
|
750
840
|
break;
|
|
751
841
|
}
|
|
752
842
|
|
|
@@ -811,7 +901,7 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
|
|
|
811
901
|
? resolvePropertyKeyForColumn(otherCollection, otherRel.sourceKey)
|
|
812
902
|
: getPrimaryKeyName(otherCollection);
|
|
813
903
|
const synthKey = `_synth_${otherTableVar}_${drizzleFieldKey}`;
|
|
814
|
-
tableRelations.push(`
|
|
904
|
+
tableRelations.push(` ${quote(synthKey)}: one(${otherTableVar}, {\n fields: [${member(tableVarName, drizzleFieldKey)}],\n references: [${member(otherTableVar, referencedKey)}],\n relationName: ${quote(drizzleRelationName)}\n })`);
|
|
815
905
|
emittedRelationNames.add(deduplicationKey);
|
|
816
906
|
}
|
|
817
907
|
}
|