@rebasepro/server-postgres 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-iXXZeVYD.js → BranchService-CWBnDhcv.js} +88 -12
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/backup/backup-cli.d.ts +40 -3
- package/dist/backup/backup-service.d.ts +11 -0
- package/dist/{backup-service-Bb39plam.js → backup-cli-PoRG-zEq.js} +777 -10
- package/dist/backup-cli-PoRG-zEq.js.map +1 -0
- package/dist/{boot-ddl-s_3Hkz94.js → boot-ddl-EsgqDkx0.js} +3 -3
- package/dist/{boot-ddl-s_3Hkz94.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-DQ_K-sdM.js → cli-console-f0k6_TsJ.js} +41 -9
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DwQnNATs.js → cli-errors-DsYyNYzd.js} +90 -11
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli-errors.d.ts +14 -0
- package/dist/cli-flags.d.ts +51 -0
- package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
- package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
- package/dist/cli.js +121 -271
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
- package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
- package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
- package/dist/connection-czvI7TZL.js.map +1 -0
- package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
- package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
- package/dist/db-command-line.d.ts +14 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Cis_HoGn.js → deferred-index-build-CqaOv1Xx.js} +8 -8
- package/dist/{deferred-index-build-Cis_HoGn.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-DvJH-IQG.js → doctor-DW_mlTzz.js} +186 -42
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-CCqayoUz.js → ensure-collection-policies-DLWgabQ2.js} +13 -10
- package/dist/ensure-collection-policies-DLWgabQ2.js.map +1 -0
- package/dist/{ensure-collection-tables-DvvImXDe.js → ensure-collection-tables-CFIRq8ni.js} +9 -9
- package/dist/{ensure-collection-tables-DvvImXDe.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-Dpgwx5sy.js → ensure-tables-4nA-tfwi.js} +5 -5
- package/dist/{ensure-tables-Dpgwx5sy.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-CUlUlRkO.js → enum-labels-kept-PcPW65xy.js} +3 -3
- package/dist/{enum-labels-kept-CUlUlRkO.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/{generate-drizzle-schema-C3EAP9HE.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-C3EAP9HE.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-ClmOUHXj.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-ClmOUHXj.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCeS4LrJ.js → generate-next-step-DgK5BlBn.js} +2 -2
- package/dist/{generate-next-step-DCeS4LrJ.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
- package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
- package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
- package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
- package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.es.js +288 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-CI5CU_n0.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-CI5CU_n0.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-Bd-hJk08.js → migrate-baseline-B-2Ef4Mv.js} +718 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js +89 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js.map +1 -0
- package/dist/{open-rebase-BKA7Nyoe.js → open-rebase-C8jQ6s5a.js} +324 -1047
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-Bv5n2PLn.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-Bv5n2PLn.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{plan-schema-CXOucCw-.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CXOucCw-.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-vm0BcvcE.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-vm0BcvcE.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-DIequ4bV.js → push-index-prebuild-UTvB5pqH.js} +4 -4
- package/dist/{push-index-prebuild-DIequ4bV.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-BqJut4F3.js → push-legacy-renames-BwaKi8xF.js} +4 -4
- package/dist/{push-legacy-renames-BqJut4F3.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-CvX8hiPb.js → push-policy-plan-DQ2tWceD.js} +5 -5
- package/dist/{push-policy-plan-CvX8hiPb.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{render-ddl-CQNwZdse.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CQNwZdse.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-BOHuHVlc.js} +43 -4
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +4 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +21 -0
- package/dist/schema/introspect-db.js +5 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/push-policy-plan.d.ts +5 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/BranchService.d.ts +20 -2
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{source-Bv5KPeIx.js → source-fZb92pN8.js} +2 -2
- package/dist/{source-Bv5KPeIx.js.map → source-fZb92pN8.js.map} +1 -1
- package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
- package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
- package/dist/{websocket-MC0zTBar.js → websocket-CKYc3V4N.js} +56 -14
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-iXXZeVYD.js.map +0 -1
- package/dist/backup-cli-B2CAn9fR.js +0 -647
- package/dist/backup-cli-B2CAn9fR.js.map +0 -1
- package/dist/backup-service-Bb39plam.js.map +0 -1
- package/dist/cli-console-DQ_K-sdM.js.map +0 -1
- package/dist/cli-errors-DwQnNATs.js.map +0 -1
- package/dist/cli-flags-D-b0qL9T.js +0 -206
- package/dist/cli-flags-D-b0qL9T.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-DvJH-IQG.js.map +0 -1
- package/dist/ensure-collection-policies-CCqayoUz.js.map +0 -1
- package/dist/open-rebase-BKA7Nyoe.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rls-enforcement-D7P4-ZYX.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/websocket-MC0zTBar.js.map +0 -1
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
import { a as diagnoseDbError, r as deepestErrorMessage, u as parseHostInfo } from "./cli-errors-
|
|
5
|
-
import { i as outError, r as out, t as chalk } from "./source-
|
|
6
|
-
import { n as planSchema, t as columnPgType } from "./plan-schema-
|
|
7
|
-
import { t as generateSchema } from "./generate-drizzle-schema-logic-
|
|
8
|
-
import { r as renderPgType } from "./render-ddl-
|
|
9
|
-
import {
|
|
3
|
+
import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
|
|
4
|
+
import { a as diagnoseDbError, r as deepestErrorMessage, u as parseHostInfo } from "./cli-errors-DsYyNYzd.js";
|
|
5
|
+
import { i as outError, r as out, t as chalk } from "./source-fZb92pN8.js";
|
|
6
|
+
import { n as planSchema, t as columnPgType } from "./plan-schema-Czf3q4-Q.js";
|
|
7
|
+
import { t as generateSchema } from "./generate-drizzle-schema-logic-CPzCRD-8.js";
|
|
8
|
+
import { r as renderPgType } from "./render-ddl-4v88y-Gv.js";
|
|
9
|
+
import { r as readGeneratedSchemaDrift, t as describeSchemaDifference } from "./generated-schema-diff-eZCklRZQ.js";
|
|
10
|
+
import { n as describeDeclarationDifferences, t as compareGeneratedDeclarations } from "./generated-schema-staleness-BvNF3i-s.js";
|
|
10
11
|
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
11
|
-
import { computeSchemaVersion, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig } from "@rebasepro/types";
|
|
12
|
-
import { effectiveAccess, fieldKeyForColumn, findRelation, getTableName, isRelationRequired, relationalCollections, resolveCollectionRelations, resolveJunctionSpecs, sortCollectionsBySlug, toCollectionAccessor, toSafeIdentifier } from "@rebasepro/common";
|
|
12
|
+
import { DEFAULT_DATA_SOURCE_KEY, computeSchemaVersion, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig } from "@rebasepro/types";
|
|
13
|
+
import { effectiveAccess, fieldKeyForColumn, findRelation, getTableName, isRelationRequired, relationalCollections, resolveCollectionRelations, resolveJunctionSpecs, restrictedFieldNames, sortCollectionsBySlug, toCollectionAccessor, toSafeIdentifier } from "@rebasepro/common";
|
|
13
14
|
import { toPostgresIdentifier, toSnakeCase } from "@rebasepro/utils";
|
|
14
15
|
import * as fs$1 from "fs";
|
|
15
16
|
import { promises } from "fs";
|
|
17
|
+
import { pathToFileURL } from "url";
|
|
16
18
|
//#region ../codegen/dist/index.es.js
|
|
17
19
|
/**
|
|
18
20
|
* A schema that cannot be expressed as a valid TypeScript file.
|
|
@@ -234,20 +236,40 @@ function line(key, type, optional) {
|
|
|
234
236
|
* reader who holds `hr` and one who does not. The server is the enforcement
|
|
235
237
|
* point; the types describe the surface a caller may name.
|
|
236
238
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
239
|
+
* Every spelling the server restricts, read from the server's own function
|
|
240
|
+
* (`restrictedFieldNames`): the property name, its column name, and — for a
|
|
241
|
+
* closed to-one relation — the foreign key that names the same row. Keying on
|
|
242
|
+
* the property and column alone, which this used to, kept `authorId` on all
|
|
243
|
+
* three types for an `excludeFromApi` `author`, though the server strips it
|
|
244
|
+
* from every read and refuses it on every write.
|
|
240
245
|
*/
|
|
241
|
-
function excludedApiKeys(
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
function excludedApiKeys(collection) {
|
|
247
|
+
const unreadable = closedNames(collection, "read");
|
|
248
|
+
const unwritable = closedNames(collection, "write");
|
|
249
|
+
return new Set([...unreadable].filter((name) => unwritable.has(name)));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The names nobody may read (or write), as the server judges them: no viewer
|
|
253
|
+
* is the trusted plane, which every role-naming rule admits, so what is left
|
|
254
|
+
* is exactly the empty rules.
|
|
255
|
+
*
|
|
256
|
+
* `restrictedFieldNames` resolves the relations of a closed relation property
|
|
257
|
+
* to find its foreign key. When those cannot be resolved — already warned
|
|
258
|
+
* about by the caller — the property and column names are still closed.
|
|
259
|
+
*/
|
|
260
|
+
function closedNames(collection, kind) {
|
|
261
|
+
try {
|
|
262
|
+
return restrictedFieldNames(collection, void 0, kind).refused;
|
|
263
|
+
} catch {
|
|
264
|
+
const names = /* @__PURE__ */ new Set();
|
|
265
|
+
for (const [key, rawProp] of Object.entries(collection.properties ?? {})) {
|
|
266
|
+
const prop = rawProp;
|
|
267
|
+
if (effectiveAccess(prop)?.[kind]?.length !== 0) continue;
|
|
268
|
+
names.add(key);
|
|
269
|
+
if (prop.columnName) names.add(prop.columnName);
|
|
270
|
+
}
|
|
271
|
+
return names;
|
|
249
272
|
}
|
|
250
|
-
return excluded;
|
|
251
273
|
}
|
|
252
274
|
/**
|
|
253
275
|
* The keys nobody may write: `access: { write: [] }`, readable or not.
|
|
@@ -259,17 +281,11 @@ function excludedApiKeys(properties) {
|
|
|
259
281
|
* generated `create({ computed })` compiled and could only ever be a 400.
|
|
260
282
|
*
|
|
261
283
|
* A write rule naming *roles* is not this: some caller can write it, and one
|
|
262
|
-
* generated shape cannot say which.
|
|
284
|
+
* generated shape cannot say which. A closed to-one relation's foreign key is:
|
|
285
|
+
* the server refuses `reviewerId` exactly as it refuses `reviewer`.
|
|
263
286
|
*/
|
|
264
|
-
function unwritableKeys(
|
|
265
|
-
|
|
266
|
-
for (const [key, rawProp] of Object.entries(properties)) {
|
|
267
|
-
const prop = rawProp;
|
|
268
|
-
if (effectiveAccess(prop)?.write?.length !== 0) continue;
|
|
269
|
-
unwritable.add(key);
|
|
270
|
-
if (prop.columnName) unwritable.add(prop.columnName);
|
|
271
|
-
}
|
|
272
|
-
return unwritable;
|
|
287
|
+
function unwritableKeys(collection) {
|
|
288
|
+
return closedNames(collection, "write");
|
|
273
289
|
}
|
|
274
290
|
function generateTypedefs(input) {
|
|
275
291
|
const collections = sortCollectionsBySlug(input);
|
|
@@ -301,7 +317,7 @@ function generateTypedefs(input) {
|
|
|
301
317
|
lines.push(` ${emitKey(accessors.get(collection.slug))}: {`);
|
|
302
318
|
lines.push(" Row: {");
|
|
303
319
|
const emittedKeys = /* @__PURE__ */ new Set();
|
|
304
|
-
const excluded = excludedApiKeys(
|
|
320
|
+
const excluded = excludedApiKeys(collection);
|
|
305
321
|
for (const key of excluded) emittedKeys.add(key);
|
|
306
322
|
for (const [key, rawProp] of Object.entries(properties)) {
|
|
307
323
|
const prop = rawProp;
|
|
@@ -335,7 +351,7 @@ function generateTypedefs(input) {
|
|
|
335
351
|
}
|
|
336
352
|
lines.push(" };");
|
|
337
353
|
lines.push(" Insert: {");
|
|
338
|
-
const notWritable = /* @__PURE__ */ new Set([...excluded, ...unwritableKeys(
|
|
354
|
+
const notWritable = /* @__PURE__ */ new Set([...excluded, ...unwritableKeys(collection)]);
|
|
339
355
|
emittedKeys.clear();
|
|
340
356
|
for (const key of notWritable) emittedKeys.add(key);
|
|
341
357
|
for (const [key, rawProp] of Object.entries(properties)) {
|
|
@@ -347,8 +363,8 @@ function generateTypedefs(input) {
|
|
|
347
363
|
lines.push(line(key, writableType(prop, tsType), isOptional));
|
|
348
364
|
emittedKeys.add(key);
|
|
349
365
|
}
|
|
350
|
-
emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);
|
|
351
|
-
lines.push(" };");
|
|
366
|
+
const eitherSpelling = emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);
|
|
367
|
+
lines.push(eitherSpelling.length === 0 ? " };" : ` } & ${eitherSpelling.join(" & ")};`);
|
|
352
368
|
lines.push(" Update: {");
|
|
353
369
|
emittedKeys.clear();
|
|
354
370
|
for (const key of notWritable) emittedKeys.add(key);
|
|
@@ -362,14 +378,15 @@ function generateTypedefs(input) {
|
|
|
362
378
|
}
|
|
363
379
|
emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);
|
|
364
380
|
lines.push(" };");
|
|
365
|
-
|
|
381
|
+
const includable = Object.entries(resolvedRelations).filter(([key]) => !excluded.has(key));
|
|
382
|
+
if (includable.length === 0) lines.push(" Relations: Record<never, never>;");
|
|
366
383
|
else lines.push(" Relations: {");
|
|
367
|
-
for (const [key, relation] of
|
|
384
|
+
for (const [key, relation] of includable) {
|
|
368
385
|
const slug = resolveTargetCollection(relation)?.slug ?? relation.targetSlug;
|
|
369
386
|
const accessor = slug ? accessors.get(slug) : void 0;
|
|
370
387
|
lines.push(` ${emitKey(key)}: ${accessor ? emitString(accessor) : "never"};`);
|
|
371
388
|
}
|
|
372
|
-
if (
|
|
389
|
+
if (includable.length > 0) lines.push(" };");
|
|
373
390
|
lines.push(` Slug: ${emitString(collection.slug)};`);
|
|
374
391
|
lines.push(" };");
|
|
375
392
|
}
|
|
@@ -489,14 +506,38 @@ function includeHelperLines() {
|
|
|
489
506
|
* finds there is one. A relation whose `relationName` differs from its property
|
|
490
507
|
* key is reachable as the property and not as the name, so emitting the name
|
|
491
508
|
* would have offered a key that writes to a column that does not exist.
|
|
509
|
+
*
|
|
510
|
+
* A **required** `belongsTo` is satisfied by either spelling — the server's
|
|
511
|
+
* required check reads both (`write-validation.ts`). Emitting each name as a
|
|
512
|
+
* required key made a create name the same row twice to compile. So on an
|
|
513
|
+
* `Insert` both names go in optional, and the requirement comes back as one
|
|
514
|
+
* returned `({ authorId: T } | { author: T })` for the caller to intersect
|
|
515
|
+
* with the object. Both stay in the object so `keyof Insert` still names them.
|
|
492
516
|
*/
|
|
493
517
|
function emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, allOptional) {
|
|
518
|
+
const spellings = /* @__PURE__ */ new Map();
|
|
519
|
+
const addSpelling = (relation, key) => {
|
|
520
|
+
if (emittedKeys.has(key)) return;
|
|
521
|
+
const keys = spellings.get(relation) ?? [];
|
|
522
|
+
if (!keys.includes(key)) keys.push(key);
|
|
523
|
+
spellings.set(relation, keys);
|
|
524
|
+
};
|
|
525
|
+
for (const relation of Object.values(resolvedRelations)) if (relation.kind === "belongsTo" && relation.localKey) addSpelling(relation, fieldKeyForColumn(collection, relation.localKey));
|
|
526
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
527
|
+
if (rawProp.type !== "relation") continue;
|
|
528
|
+
const relation = findRelation(resolvedRelations, key);
|
|
529
|
+
if (relation?.kind === "belongsTo" && relation.localKey) addSpelling(relation, key);
|
|
530
|
+
}
|
|
531
|
+
const eitherSpelling = [];
|
|
494
532
|
const emit = (key, relation) => {
|
|
495
533
|
if (emittedKeys.has(key)) return;
|
|
496
534
|
const required = isRelationRequired(collection, relation);
|
|
497
535
|
const type = required ? foreignKeyType(relation) : `${foreignKeyType(relation)} | null`;
|
|
498
|
-
|
|
536
|
+
const names = spellings.get(relation) ?? [key];
|
|
537
|
+
const oneOfSeveral = required && !allOptional && names.length > 1;
|
|
538
|
+
lines.push(line(key, type, allOptional || !required || oneOfSeveral));
|
|
499
539
|
emittedKeys.add(key);
|
|
540
|
+
if (oneOfSeveral && names[0] === key) eitherSpelling.push(`(${names.map((name) => `{ ${emitKey(name)}: ${type} }`).join(" | ")})`);
|
|
500
541
|
};
|
|
501
542
|
for (const relation of Object.values(resolvedRelations)) if (relation.kind === "belongsTo" && relation.localKey) emit(fieldKeyForColumn(collection, relation.localKey), relation);
|
|
502
543
|
for (const [key, rawProp] of Object.entries(properties)) {
|
|
@@ -504,6 +545,14 @@ function emitWritableRelations(lines, collection, properties, resolvedRelations,
|
|
|
504
545
|
const relation = findRelation(resolvedRelations, key);
|
|
505
546
|
if (relation?.kind === "belongsTo" && relation.localKey) emit(key, relation);
|
|
506
547
|
}
|
|
548
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
549
|
+
if (rawProp.type !== "relation" || emittedKeys.has(key)) continue;
|
|
550
|
+
const relation = findRelation(resolvedRelations, key);
|
|
551
|
+
const toOneWritable = relation?.kind === "hasOne" || relation?.kind === "via" && relation.cardinality === "one" && relation.writable;
|
|
552
|
+
if (!relation || !toOneWritable) continue;
|
|
553
|
+
lines.push(line(key, `${foreignKeyType(relation)} | null`, true));
|
|
554
|
+
emittedKeys.add(key);
|
|
555
|
+
}
|
|
507
556
|
for (const [key, relation] of Object.entries(resolvedRelations)) {
|
|
508
557
|
if (relation.cardinality !== "many" || !relation.writable) continue;
|
|
509
558
|
if (emittedKeys.has(key)) continue;
|
|
@@ -513,6 +562,7 @@ function emitWritableRelations(lines, collection, properties, resolvedRelations,
|
|
|
513
562
|
lines.push(line(key, `Array<${element}>`, true));
|
|
514
563
|
emittedKeys.add(key);
|
|
515
564
|
}
|
|
565
|
+
return eitherSpelling;
|
|
516
566
|
}
|
|
517
567
|
/**
|
|
518
568
|
* The `_pivot` a many-to-many link carries: the junction's payload columns,
|
|
@@ -545,6 +595,7 @@ var doctor_exports = /* @__PURE__ */ __exportAll({
|
|
|
545
595
|
checkCollectionsVsDatabase: () => checkCollectionsVsDatabase,
|
|
546
596
|
checkCollectionsVsSchema: () => checkCollectionsVsSchema,
|
|
547
597
|
checkCollectionsVsSdk: () => checkCollectionsVsSdk,
|
|
598
|
+
generatedSchemaDriftIssues: () => generatedSchemaDriftIssues,
|
|
548
599
|
getExpectedColumnType: () => getExpectedColumnType,
|
|
549
600
|
junctionKeyIssues: () => junctionKeyIssues,
|
|
550
601
|
loadCollections: () => loadCollections,
|
|
@@ -860,7 +911,63 @@ function junctionKeyIssues(junctionPlan, dbColumns, dbPk, displayName) {
|
|
|
860
911
|
issues.push(...primaryKeyIssues(junctionPlan, dbPk, displayName));
|
|
861
912
|
return issues;
|
|
862
913
|
}
|
|
863
|
-
|
|
914
|
+
/** A table as the other issues name it: bare in `public`, `schema.table` elsewhere. */
|
|
915
|
+
function displayTableOf(difference, collections) {
|
|
916
|
+
const collection = difference.collection ? collections.find((c) => c.slug === difference.collection) : void 0;
|
|
917
|
+
const schema = collection && isPostgresCollectionConfig(collection) ? collection.schema : void 0;
|
|
918
|
+
return schema && schema !== "public" ? `${schema}.${difference.table}` : difference.table;
|
|
919
|
+
}
|
|
920
|
+
/** Which of the doctor's own categories already says what a boot difference says. */
|
|
921
|
+
var COVERING_CATEGORY = {
|
|
922
|
+
"missing-table": "missing_table",
|
|
923
|
+
"missing-column": "missing_column",
|
|
924
|
+
type: "type_mismatch",
|
|
925
|
+
nullability: "nullability_mismatch",
|
|
926
|
+
"extra-column": "extra_column"
|
|
927
|
+
};
|
|
928
|
+
/** What to do about one boot difference, said the way the boot warning says it. */
|
|
929
|
+
function generatedDriftFix(difference) {
|
|
930
|
+
switch (difference.kind) {
|
|
931
|
+
case "nullability": return difference.generated === "NOT NULL" ? "`rebase db push` applies it; a NOT NULL needs every row filled first (UPDATE the empty ones, or give the property a default). Boot adds one only with a new column on an empty table. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first." : "`rebase db push` drops the NOT NULL, which boot never does. If the column should stay required, mark the property `validation: { required: true }`. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
932
|
+
case "extra-column": return "Boot never drops a column, so its data is still there. `rebase db push` removes it (it asks first, since that deletes the data), or a migration does (`rebase db generate`). If a property should still read it, declare it again. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
933
|
+
case "type": return "`rebase db push` converts it (it asks first when values may not convert), or a migration does. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
934
|
+
case "missing-column": return "`rebase db push` adds it. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
935
|
+
case "missing-table": return "Run `rebase db push` or `rebase db generate && rebase db migrate`";
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Boot's drift warnings, as doctor issues — one per difference, in boot's own
|
|
940
|
+
* words ({@link describeSchemaDifference}), and a warning, as boot raises them.
|
|
941
|
+
*
|
|
942
|
+
* Except where an issue the collections-side checks above already raised says
|
|
943
|
+
* the same thing: a missing table, a missing column or a type mismatch on the
|
|
944
|
+
* same column. Nullability and columns no collection declares are boot's alone;
|
|
945
|
+
* the doctor never compared them, which is how it came to say "In sync" beside
|
|
946
|
+
* a boot that had just warned about both.
|
|
947
|
+
*/
|
|
948
|
+
function generatedSchemaDriftIssues(differences, already, collections) {
|
|
949
|
+
const bare = (table) => table?.split(".").pop();
|
|
950
|
+
const covered = (difference) => already.some((issue) => issue.category === COVERING_CATEGORY[difference.kind] && bare(issue.table) === difference.table && (difference.kind === "missing-table" || issue.column === difference.column));
|
|
951
|
+
return differences.filter((difference) => !covered(difference)).map((difference) => ({
|
|
952
|
+
severity: "warning",
|
|
953
|
+
category: COVERING_CATEGORY[difference.kind],
|
|
954
|
+
table: displayTableOf(difference, collections),
|
|
955
|
+
...difference.column ? { column: difference.column } : {},
|
|
956
|
+
...difference.generated ? { expected: difference.generated } : {},
|
|
957
|
+
...difference.live ? { actual: difference.live } : {},
|
|
958
|
+
message: describeSchemaDifference(difference),
|
|
959
|
+
fix: generatedDriftFix(difference)
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* The collections whose tables the default database holds — the filter boot
|
|
964
|
+
* applies before it reads the catalogue, for the same reason: a collection
|
|
965
|
+
* routed to `database("analytics")` has no table here.
|
|
966
|
+
*/
|
|
967
|
+
function defaultSourceCollections(collections) {
|
|
968
|
+
return collections.filter((c) => !c.dataSource || c.dataSource === DEFAULT_DATA_SOURCE_KEY);
|
|
969
|
+
}
|
|
970
|
+
async function checkCollectionsVsDatabase(collections, databaseUrl, options = {}) {
|
|
864
971
|
const issues = [];
|
|
865
972
|
const pgModule = await import("pg");
|
|
866
973
|
const { Pool } = pgModule.default ?? pgModule;
|
|
@@ -1099,6 +1206,15 @@ async function checkCollectionsVsDatabase(collections, databaseUrl) {
|
|
|
1099
1206
|
}
|
|
1100
1207
|
}
|
|
1101
1208
|
if (plan) issues.push(...orphanedJunctionIssues(plan, columnsByTable, pksByTable, fksByTable));
|
|
1209
|
+
if (options.generated) {
|
|
1210
|
+
const differences = await readGeneratedSchemaDrift({
|
|
1211
|
+
client: pool,
|
|
1212
|
+
generated: options.generated,
|
|
1213
|
+
collections: defaultSourceCollections(collections),
|
|
1214
|
+
defaultSchema: "public"
|
|
1215
|
+
});
|
|
1216
|
+
issues.push(...generatedSchemaDriftIssues(differences, issues, collections));
|
|
1217
|
+
}
|
|
1102
1218
|
} finally {
|
|
1103
1219
|
await pool.end();
|
|
1104
1220
|
}
|
|
@@ -1221,6 +1337,8 @@ function formatCategory(cat) {
|
|
|
1221
1337
|
missing_table: "Missing Table",
|
|
1222
1338
|
missing_column: "Missing Column",
|
|
1223
1339
|
type_mismatch: "Type Mismatch",
|
|
1340
|
+
nullability_mismatch: "Nullability Mismatch",
|
|
1341
|
+
extra_column: "Extra Column",
|
|
1224
1342
|
missing_constraint: "Missing Constraint",
|
|
1225
1343
|
primary_key_mismatch: "Primary Key Mismatch",
|
|
1226
1344
|
schema_stale: "Stale Schema",
|
|
@@ -1233,6 +1351,22 @@ function formatCategory(cat) {
|
|
|
1233
1351
|
sdk_ungeneratable: "SDK Cannot Be Generated"
|
|
1234
1352
|
}[cat];
|
|
1235
1353
|
}
|
|
1354
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
1355
|
+
/**
|
|
1356
|
+
* The `tables` export of `schema.generated.ts`, imported as boot imports it.
|
|
1357
|
+
*
|
|
1358
|
+
* Nothing, and no error, when the file is not there: phase 1 reports that, and
|
|
1359
|
+
* boot then has nothing to compare either.
|
|
1360
|
+
*/
|
|
1361
|
+
async function loadGeneratedTables(schemaPath) {
|
|
1362
|
+
if (!fs$1.existsSync(schemaPath)) return {};
|
|
1363
|
+
try {
|
|
1364
|
+
const module = await import(pathToFileURL(schemaPath).href);
|
|
1365
|
+
return isRecord(module) && isRecord(module.tables) ? { tables: module.tables } : {};
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1236
1370
|
async function runDoctor(options) {
|
|
1237
1371
|
out();
|
|
1238
1372
|
out(chalk.bold(" 🩺 Loading collections..."));
|
|
@@ -1249,7 +1383,17 @@ async function runDoctor(options) {
|
|
|
1249
1383
|
if (options.databaseUrl) {
|
|
1250
1384
|
out(chalk.gray(" Checking Collections → Database..."));
|
|
1251
1385
|
try {
|
|
1252
|
-
|
|
1386
|
+
const generated = await loadGeneratedTables(options.schemaPath);
|
|
1387
|
+
schemaToDatabase = await checkCollectionsVsDatabase(collections, options.databaseUrl, { generated: generated.tables });
|
|
1388
|
+
if (generated.error) {
|
|
1389
|
+
schemaToDatabase.issues.push({
|
|
1390
|
+
severity: "warning",
|
|
1391
|
+
category: "schema_stale",
|
|
1392
|
+
message: `schema.generated.ts could not be loaded, so it was not compared against the database as boot compares it: ${generated.error}`,
|
|
1393
|
+
fix: "Run `rebase schema generate`, then `rebase doctor` again"
|
|
1394
|
+
});
|
|
1395
|
+
schemaToDatabase.passed = false;
|
|
1396
|
+
}
|
|
1253
1397
|
} catch (err) {
|
|
1254
1398
|
const banner = diagnoseDbError(err, options.databaseUrl);
|
|
1255
1399
|
if (banner) outError(banner);
|
|
@@ -1297,4 +1441,4 @@ async function runDoctor(options) {
|
|
|
1297
1441
|
//#endregion
|
|
1298
1442
|
export { loadCollections as n, runDoctor as r, doctor_exports as t };
|
|
1299
1443
|
|
|
1300
|
-
//# sourceMappingURL=doctor-
|
|
1444
|
+
//# sourceMappingURL=doctor-DW_mlTzz.js.map
|