@rebasepro/server-postgres 0.17.3 → 0.18.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/LICENSE +0 -1
- package/README.md +31 -0
- package/dist/backup-argv.d.ts +19 -0
- package/dist/branch-argv.d.ts +28 -0
- package/dist/branch-prune.d.ts +77 -0
- package/dist/cli-collections-path.d.ts +31 -0
- package/dist/cli-errors.d.ts +123 -0
- package/dist/cli-flags.d.ts +101 -0
- package/dist/cli-helpers.d.ts +94 -10
- package/dist/collection-index-C7LopMyX.js +247 -0
- package/dist/collection-index-C7LopMyX.js.map +1 -0
- package/dist/databasePoolManager.d.ts +27 -0
- package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
- package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
- package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
- package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.es.js +19307 -3059
- package/dist/index.es.js.map +1 -1
- package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
- package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
- package/dist/schema/column-type-drift.d.ts +79 -0
- package/dist/schema/doctor.d.ts +42 -0
- package/dist/schema/ensure-collection-tables.d.ts +90 -0
- package/dist/schema/generate-next-step.d.ts +30 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
- package/dist/schema/generated-schema-staleness.d.ts +37 -0
- package/dist/schema/introspect-db-queries.d.ts +34 -0
- package/dist/security/rls-enforcement.d.ts +63 -2
- package/dist/services/BranchService.d.ts +86 -1
- package/dist/services/collection-helpers.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +30 -2
- package/dist/services/row-pipeline.d.ts +12 -1
- package/dist/utils/pg-error-utils.d.ts +3 -1
- package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
- package/package.json +32 -27
- package/dist/backup-service-DCk7KhhL.js +0 -9044
- package/dist/backup-service-DCk7KhhL.js.map +0 -1
- package/dist/collection-index-BRUg10H5.js +0 -5690
- package/dist/collection-index-BRUg10H5.js.map +0 -1
- package/dist/connection-GOKU3Hu5.js +0 -277
- package/dist/connection-GOKU3Hu5.js.map +0 -1
- package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
- package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
- package/dist/src-DiB5RP2Z.js +0 -541
- package/dist/src-DiB5RP2Z.js.map +0 -1
- package/dist/websocket-BZ4H5wUz.js +0 -8198
- package/dist/websocket-BZ4H5wUz.js.map +0 -1
- package/src/PostgresAdapter.ts +0 -91
- package/src/PostgresBackendDriver.ts +0 -1857
- package/src/PostgresBootstrapper.ts +0 -1157
- package/src/auth/ensure-tables.ts +0 -896
- package/src/auth/schema-version.ts +0 -260
- package/src/auth/services.ts +0 -1610
- package/src/backup/backup-cli.ts +0 -520
- package/src/backup/backup-cron.ts +0 -212
- package/src/backup/backup-logic.ts +0 -93
- package/src/backup/backup-service.ts +0 -485
- package/src/backup/index.ts +0 -12
- package/src/backup/pg-tools.ts +0 -403
- package/src/backup/retention.ts +0 -86
- package/src/cli-errors.ts +0 -265
- package/src/cli-helpers.ts +0 -569
- package/src/cli-output.ts +0 -43
- package/src/cli.ts +0 -1339
- package/src/collections/PostgresCollectionRegistry.ts +0 -108
- package/src/collections/buildRegistry.ts +0 -68
- package/src/collections/validate-relations.ts +0 -402
- package/src/connection.ts +0 -333
- package/src/data-transformer.ts +0 -845
- package/src/databasePoolManager.ts +0 -95
- package/src/history/HistoryService.ts +0 -254
- package/src/history/ensure-history-table.ts +0 -53
- package/src/index.ts +0 -19
- package/src/interfaces.ts +0 -60
- package/src/module-dir.ts +0 -7
- package/src/schema/atlas-argv.ts +0 -94
- package/src/schema/auth-schema.ts +0 -298
- package/src/schema/auth-users-columns.ts +0 -131
- package/src/schema/carved-out-migration.ts +0 -404
- package/src/schema/classify-change.ts +0 -436
- package/src/schema/collection-index.ts +0 -427
- package/src/schema/collections-schema-version.ts +0 -103
- package/src/schema/destructive-sql.ts +0 -94
- package/src/schema/doctor-cli.ts +0 -66
- package/src/schema/doctor-policy-checks.ts +0 -105
- package/src/schema/doctor.ts +0 -837
- package/src/schema/drizzle-ddl.ts +0 -33
- package/src/schema/dynamic-tables.test.ts +0 -302
- package/src/schema/dynamic-tables.ts +0 -293
- package/src/schema/ensure-collection-policies.ts +0 -198
- package/src/schema/ensure-collection-tables.test.ts +0 -518
- package/src/schema/ensure-collection-tables.ts +0 -1267
- package/src/schema/generate-drizzle-schema-logic.ts +0 -957
- package/src/schema/generate-drizzle-schema.ts +0 -143
- package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
- package/src/schema/generate-postgres-ddl.ts +0 -180
- package/src/schema/generate-schema-commit.ts +0 -267
- package/src/schema/generated-schema-staleness.ts +0 -284
- package/src/schema/introspect-db-constraints.ts +0 -385
- package/src/schema/introspect-db-inference.ts +0 -255
- package/src/schema/introspect-db-logic.ts +0 -1387
- package/src/schema/introspect-db-naming.ts +0 -15
- package/src/schema/introspect-db-project.ts +0 -78
- package/src/schema/introspect-db-queries.ts +0 -326
- package/src/schema/introspect-db-structure.ts +0 -670
- package/src/schema/introspect-db-types.ts +0 -56
- package/src/schema/introspect-db.ts +0 -257
- package/src/schema/introspect-runtime.test.ts +0 -260
- package/src/schema/introspect-runtime.ts +0 -327
- package/src/schema/non-sql-collections.test.ts +0 -131
- package/src/schema/rls-bootstrap-sql.ts +0 -288
- package/src/schema/search-column.ts +0 -643
- package/src/schema/test-schema.ts +0 -11
- package/src/schema/vector-index.ts +0 -491
- package/src/security/anonymous-grants.test.ts +0 -73
- package/src/security/policy-drift.test.ts +0 -435
- package/src/security/policy-drift.ts +0 -473
- package/src/security/rls-enforcement.ts +0 -505
- package/src/services/BranchService.ts +0 -326
- package/src/services/FetchService.ts +0 -2115
- package/src/services/PersistService.ts +0 -557
- package/src/services/RelationService.ts +0 -978
- package/src/services/RelationWriteService.ts +0 -653
- package/src/services/cdc/CdcListener.ts +0 -103
- package/src/services/cdc/junction-tables.ts +0 -91
- package/src/services/cdc/trigger-cdc.ts +0 -173
- package/src/services/channel-bus/ChannelBus.ts +0 -44
- package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
- package/src/services/channel-bus/index.ts +0 -114
- package/src/services/channel-history.ts +0 -424
- package/src/services/channel-presence.ts +0 -185
- package/src/services/collection-helpers.ts +0 -420
- package/src/services/dataService.ts +0 -256
- package/src/services/index.ts +0 -14
- package/src/services/junction-writes.ts +0 -295
- package/src/services/nested-path.ts +0 -145
- package/src/services/pg-notify-listener.ts +0 -151
- package/src/services/realtimeService.ts +0 -2545
- package/src/services/row-pipeline.ts +0 -240
- package/src/services/write-denial.ts +0 -55
- package/src/types.ts +0 -4
- package/src/utils/connection-string.ts +0 -58
- package/src/utils/drizzle-conditions.ts +0 -2743
- package/src/utils/pg-array-null-patch.ts +0 -42
- package/src/utils/pg-error-utils.ts +0 -391
- package/src/websocket.ts +0 -855
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
|
-
import "process";
|
|
3
|
-
__createRequire(import.meta.url);
|
|
4
|
-
import {
|
|
5
|
-
import { B as getPolicyNamesForRule, C as getTableName, I as generateForeignKeyName, L as legacyForeignKeyName, R as toPostgresIdentifier, T as resolveCollectionRelations, W as toSnakeCase, Z as isManyToMany, _ as securityRuleToConditions, b as findRelation, d as getJunctionCollectionConfig, f as getJunctionSecurityRules, g as policyToPostgres, h as getInjectedSecurityRules, i as collectionIndexStatements, m as getEffectiveSecurityRules, n as buildCollectionIndexSpecs, p as resolveJunctionSpecs, r as collectionIndexStatement, t as buildCollectionIndexPlan, u as relationalCollections } from "./collection-index-BRUg10H5.js";
|
|
6
|
-
import { l as isPostgresCollectionConfig, r as REBASE_SCHEMA, t as declaredDatabaseExtensions } from "./src-DiB5RP2Z.js";
|
|
2
|
+
import process from "process";
|
|
3
|
+
const require = __createRequire(import.meta.url);
|
|
4
|
+
import { i as collectionIndexStatements, n as buildCollectionIndexSpecs, r as collectionIndexStatement, t as buildCollectionIndexPlan } from "./collection-index-C7LopMyX.js";
|
|
7
5
|
import { isConcurrentDdlRace, isDuplicateObjectRace, logger } from "@rebasepro/server";
|
|
6
|
+
import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_SEARCH_COLUMN, DEFAULT_SEARCH_LANGUAGE, DEFAULT_SEARCH_WEIGHT, REBASE_SCHEMA, declaredDatabaseExtensions, isManyToMany, isPostgresCollectionConfig } from "@rebasepro/types";
|
|
7
|
+
import { findRelation, getEffectiveSecurityRules, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getTableName, policyToPostgres, relationalCollections, resolveCollectionRelations, resolveJunctionSpecs, resolveStringColumnLength, securityRuleToConditions } from "@rebasepro/common";
|
|
8
|
+
import { generateForeignKeyName, getPolicyNamesForRule, legacyForeignKeyName, toPostgresIdentifier, toSnakeCase } from "@rebasepro/utils";
|
|
8
9
|
import { createHash } from "node:crypto";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
//#region \0rolldown/runtime.js
|
|
11
|
+
var __create = Object.create;
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
16
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
18
|
+
var __exportAll = (all, no_symbols) => {
|
|
19
|
+
let target = {};
|
|
20
|
+
for (var name in all) __defProp(target, name, {
|
|
21
|
+
get: all[name],
|
|
22
|
+
enumerable: true
|
|
23
|
+
});
|
|
24
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
return target;
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
29
|
+
key = keys[i];
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
31
|
+
get: ((k) => from[k]).bind(null, key),
|
|
32
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
38
|
+
value: mod,
|
|
39
|
+
enumerable: true
|
|
40
|
+
}) : target, mod));
|
|
41
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
43
|
+
throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
|
44
|
+
});
|
|
29
45
|
//#endregion
|
|
30
46
|
//#region src/schema/search-column.ts
|
|
31
47
|
/**
|
|
@@ -184,7 +200,7 @@ var quote$1 = (v) => `'${v.replace(/'/g, "''")}'`;
|
|
|
184
200
|
*/
|
|
185
201
|
var resolveField = (entry, collection, cfg) => {
|
|
186
202
|
const path = typeof entry === "string" ? entry : entry.path;
|
|
187
|
-
const weight = (typeof entry === "string" ? void 0 : entry.weight) ??
|
|
203
|
+
const weight = (typeof entry === "string" ? void 0 : entry.weight) ?? DEFAULT_SEARCH_WEIGHT;
|
|
188
204
|
const where = `${collection.slug}.search`;
|
|
189
205
|
if (!path || typeof path !== "string") throw new SearchConfigError(`${where}: every entry in \`fields\` needs a property path.`);
|
|
190
206
|
const [head, ...rest] = path.split(".");
|
|
@@ -203,7 +219,7 @@ var resolveField = (entry, collection, cfg) => {
|
|
|
203
219
|
jsonPath: rest,
|
|
204
220
|
kind: classified.kind
|
|
205
221
|
}), cfg.unaccent === true);
|
|
206
|
-
const language = cfg.language ??
|
|
222
|
+
const language = cfg.language ?? DEFAULT_SEARCH_LANGUAGE;
|
|
207
223
|
return {
|
|
208
224
|
path,
|
|
209
225
|
column,
|
|
@@ -227,7 +243,7 @@ var buildSearchColumnSpec = (collection) => {
|
|
|
227
243
|
if (!Array.isArray(cfg.fields) || cfg.fields.length === 0) throw new SearchConfigError(`${collection.slug}.search: \`fields\` is empty. Name the properties to index, or remove the \`search\` block to keep the default ILIKE behaviour.`);
|
|
228
244
|
const table = getTableName(collection);
|
|
229
245
|
const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
|
|
230
|
-
const column = cfg.column ??
|
|
246
|
+
const column = cfg.column ?? DEFAULT_SEARCH_COLUMN;
|
|
231
247
|
if (collection.properties?.[column]) throw new SearchConfigError(`${collection.slug}.search: the generated column "${column}" collides with a declared property of the same name. Set \`search.column\` to something else.`);
|
|
232
248
|
const fields = cfg.fields.map((entry) => resolveField(entry, collection, cfg));
|
|
233
249
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -242,7 +258,7 @@ var buildSearchColumnSpec = (collection) => {
|
|
|
242
258
|
schema,
|
|
243
259
|
table,
|
|
244
260
|
column,
|
|
245
|
-
language: cfg.language ??
|
|
261
|
+
language: cfg.language ?? DEFAULT_SEARCH_LANGUAGE,
|
|
246
262
|
unaccent: cfg.unaccent === true,
|
|
247
263
|
fields,
|
|
248
264
|
expression: fields.map((f) => f.sql).join(" || "),
|
|
@@ -256,7 +272,7 @@ var buildSearchColumnSpec = (collection) => {
|
|
|
256
272
|
column: fuzzyColumn,
|
|
257
273
|
expression: fields.map((f) => f.textSql).join(" || ' ' || "),
|
|
258
274
|
indexName: toPostgresIdentifier(`${table}_${fuzzyColumn}_trgm`),
|
|
259
|
-
threshold: cfg.fuzzyThreshold ??
|
|
275
|
+
threshold: cfg.fuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD
|
|
260
276
|
};
|
|
261
277
|
}
|
|
262
278
|
return spec;
|
|
@@ -440,6 +456,284 @@ var excludedColumnNames = (tableColumns, collection) => {
|
|
|
440
456
|
return Object.keys(tableColumns).filter((name) => byName.has(name) || isSearchIndexColumn(tableColumns[name]));
|
|
441
457
|
};
|
|
442
458
|
//#endregion
|
|
459
|
+
//#region src/utils/pg-error-utils.ts
|
|
460
|
+
/**
|
|
461
|
+
* Shared PostgreSQL error extraction and user-friendly message formatting.
|
|
462
|
+
*
|
|
463
|
+
* Drizzle wraps native PG errors in a `.cause` chain. These utilities
|
|
464
|
+
* unwrap that chain to get the real PostgreSQL error (identified by a
|
|
465
|
+
* 5-character alphanumeric `code` such as `42P01`) and translate it into
|
|
466
|
+
* a message that is safe and helpful to show to end-users.
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* Return the error when it is a deliberate 4xx, otherwise null.
|
|
470
|
+
*
|
|
471
|
+
* A thrown `ApiError` is a decision the server made about the request, not a
|
|
472
|
+
* database failure: its message and code are already written for the client.
|
|
473
|
+
*/
|
|
474
|
+
function asClientFacingError(error) {
|
|
475
|
+
if (!(error instanceof Error)) return null;
|
|
476
|
+
const e = error;
|
|
477
|
+
if (typeof e.statusCode !== "number" || e.statusCode < 400 || e.statusCode >= 500) return null;
|
|
478
|
+
return e;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Extract the underlying PostgreSQL error from a Drizzle wrapper.
|
|
482
|
+
* Drizzle wraps PG errors in a `cause` property — this function
|
|
483
|
+
* recursively walks the chain until it finds an object with a PG
|
|
484
|
+
* error code (5-char alphanumeric, e.g. `42P01`).
|
|
485
|
+
*/
|
|
486
|
+
function extractPgError(error) {
|
|
487
|
+
if (!error || typeof error !== "object") return null;
|
|
488
|
+
if (!(error instanceof Error)) {
|
|
489
|
+
if ("cause" in error && error.cause && typeof error.cause === "object") return extractPgError(error.cause);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
if ("code" in error && typeof error.code === "string" && /^[0-9A-Z]{5}$/.test(error.code)) return error;
|
|
493
|
+
if (error.cause && typeof error.cause === "object") return extractPgError(error.cause);
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Whether the failure came back from Postgres rather than from building the
|
|
498
|
+
* query — which decides whether a fallback query is worth issuing.
|
|
499
|
+
*
|
|
500
|
+
* Reads here run inside a transaction (that is where `SET LOCAL ROLE` binds
|
|
501
|
+
* RLS). Once a statement raises, that transaction is aborted, and every later
|
|
502
|
+
* statement on it returns `25P02` — "current transaction is aborted, commands
|
|
503
|
+
* ignored until end of transaction block". So a retry after a database error
|
|
504
|
+
* cannot succeed, and it replaces a precise diagnosis ("invalid input syntax
|
|
505
|
+
* for type uuid") with a generic one. Rethrow instead.
|
|
506
|
+
*
|
|
507
|
+
* A query the driver could not even build — a missing reciprocal relation, say
|
|
508
|
+
* — never reached Postgres, leaves the transaction usable, and is exactly what
|
|
509
|
+
* the fallback paths exist for.
|
|
510
|
+
*/
|
|
511
|
+
function reachedDatabase(error) {
|
|
512
|
+
return extractPgError(error) !== null;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Walk the error cause chain and return the deepest meaningful message.
|
|
516
|
+
*/
|
|
517
|
+
function extractCauseMessage(error) {
|
|
518
|
+
if (!error || typeof error !== "object") return null;
|
|
519
|
+
if (!(error instanceof Error)) return null;
|
|
520
|
+
if (error.cause && typeof error.cause === "object") {
|
|
521
|
+
const deeper = extractCauseMessage(error.cause);
|
|
522
|
+
if (deeper) return deeper;
|
|
523
|
+
if (error.cause instanceof Error && error.cause.message) return error.cause.message;
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Codes that mean "this connection will never work as configured".
|
|
529
|
+
*
|
|
530
|
+
* A wrong password or a database that does not exist is a settled fact about
|
|
531
|
+
* the connection string, not a transient fault — retrying produces the same
|
|
532
|
+
* answer forever.
|
|
533
|
+
*/
|
|
534
|
+
var UNRECOVERABLE_CONNECT_CODES = /* @__PURE__ */ new Set([
|
|
535
|
+
"28P01",
|
|
536
|
+
"28000",
|
|
537
|
+
"3D000",
|
|
538
|
+
"42501"
|
|
539
|
+
]);
|
|
540
|
+
/**
|
|
541
|
+
* Describe a failed connection attempt in terms a developer can act on.
|
|
542
|
+
*
|
|
543
|
+
* The error a caller catches is Drizzle's wrapper: its message is
|
|
544
|
+
* `Failed query: SELECT 1` and its stack runs through drizzle internals, while
|
|
545
|
+
* the sentence that says what is actually wrong — "password authentication
|
|
546
|
+
* failed for user …", "database … does not exist" — sits in `.cause`. Logging
|
|
547
|
+
* the wrapper, as the bootstrapper used to, tells a developer with a typo in
|
|
548
|
+
* their `DATABASE_URL` nothing at all.
|
|
549
|
+
*/
|
|
550
|
+
function classifyConnectFailure(error) {
|
|
551
|
+
const pgError = extractPgError(error);
|
|
552
|
+
const reason = pgError?.message ?? extractCauseMessage(error) ?? (error instanceof Error ? error.message : String(error));
|
|
553
|
+
return {
|
|
554
|
+
fatal: Boolean(pgError?.code && UNRECOVERABLE_CONNECT_CODES.has(pgError.code)),
|
|
555
|
+
reason,
|
|
556
|
+
code: pgError?.code
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Detect whether an error is specifically a role-switching permission failure
|
|
561
|
+
* (e.g. "permission denied to set role" or "must be member of role"),
|
|
562
|
+
* as opposed to a table-level permission denial.
|
|
563
|
+
*
|
|
564
|
+
* This is used by the backend driver to auto-disable role switching when the
|
|
565
|
+
* connection user lacks SET ROLE privileges, rather than surfacing a confusing
|
|
566
|
+
* error to the Studio SQL Editor user.
|
|
567
|
+
*/
|
|
568
|
+
function isRoleSwitchingPermissionError(error) {
|
|
569
|
+
const pgError = extractPgError(error);
|
|
570
|
+
if (!pgError || pgError.code !== "42501") return false;
|
|
571
|
+
const msg = pgError.message.toLowerCase();
|
|
572
|
+
return msg.includes("set role") || msg.includes("member of role");
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Was this `42501` the *caller* being refused by a policy, rather than the
|
|
576
|
+
* server lacking a privilege?
|
|
577
|
+
*
|
|
578
|
+
* Both arrive as `insufficient_privilege`, and they are opposite kinds of
|
|
579
|
+
* problem. A row-level-security refusal is a working access-control system
|
|
580
|
+
* doing its job: the caller asked for something their policies do not permit,
|
|
581
|
+
* which is a 403 and nobody's bug. A missing `GRANT` is the deployment being
|
|
582
|
+
* wrong — the connection role cannot touch the table at all, no policy is
|
|
583
|
+
* involved, and nothing the caller changes about the request will help.
|
|
584
|
+
*
|
|
585
|
+
* Postgres distinguishes them in the message, so this does too:
|
|
586
|
+
*
|
|
587
|
+
* new row violates row-level security policy for table "notes" → the caller
|
|
588
|
+
* permission denied for table notes → the server
|
|
589
|
+
*
|
|
590
|
+
* Only writes reach this. A read that RLS excludes is not an error — the rows
|
|
591
|
+
* are filtered and the caller gets an empty page — so the erroring case is
|
|
592
|
+
* specifically an `INSERT`/`UPDATE` whose row fails a policy's `WITH CHECK`.
|
|
593
|
+
*
|
|
594
|
+
* Matched on the message because that is the only thing carrying the
|
|
595
|
+
* distinction; the SQLSTATE is identical either way. Narrow by design: anything
|
|
596
|
+
* not naming row-level security stays the server's problem, since reporting a
|
|
597
|
+
* genuine privilege misconfiguration as "forbidden" would send an operator
|
|
598
|
+
* hunting for a policy bug that does not exist.
|
|
599
|
+
*/
|
|
600
|
+
function isRowLevelSecurityDenial(error) {
|
|
601
|
+
const pgError = extractPgError(error);
|
|
602
|
+
if (!pgError || pgError.code !== "42501") return false;
|
|
603
|
+
return pgError.message.toLowerCase().includes("row-level security policy");
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Translate a raw PostgreSQL error into a user-friendly message.
|
|
607
|
+
*
|
|
608
|
+
* @param pgError - The extracted PostgreSQL error (from {@link extractPgError})
|
|
609
|
+
* @param context - A human-readable context string (e.g. collection slug or path)
|
|
610
|
+
* @returns An object with a `message` safe for the client and the PG `code`.
|
|
611
|
+
*/
|
|
612
|
+
function pgErrorToFriendlyMessage(pgError, context, options = {}) {
|
|
613
|
+
const verbose = options.verbose ?? process.env.NODE_ENV !== "production";
|
|
614
|
+
const detail = verbose ? pgError.detail : void 0;
|
|
615
|
+
const hint = verbose ? pgError.hint : void 0;
|
|
616
|
+
const constraint = pgError.constraint;
|
|
617
|
+
const column = pgError.column;
|
|
618
|
+
const table = pgError.table;
|
|
619
|
+
const dataType = verbose ? pgError.dataType : void 0;
|
|
620
|
+
const rawMessage = pgError.message || "Unknown database error";
|
|
621
|
+
const pgMessage = verbose ? rawMessage : "";
|
|
622
|
+
const code = pgError.code || "UNKNOWN";
|
|
623
|
+
const suffix = hint ? ` Hint: ${hint}` : "";
|
|
624
|
+
const tableRef = table ?? context;
|
|
625
|
+
/** `": <postgres said>"`, or nothing when the details are withheld. */
|
|
626
|
+
const said = pgMessage ? `: ${pgMessage}` : "";
|
|
627
|
+
switch (pgError.code) {
|
|
628
|
+
case "23503": return {
|
|
629
|
+
message: detail ? `Foreign key constraint violated: ${detail}${suffix}` : `Cannot complete operation: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
|
|
630
|
+
code
|
|
631
|
+
};
|
|
632
|
+
case "23505": return {
|
|
633
|
+
message: detail ? `Duplicate value: ${detail}${suffix}` : `Cannot complete operation: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
|
|
634
|
+
code
|
|
635
|
+
};
|
|
636
|
+
case "23502": return {
|
|
637
|
+
message: `Missing required field: "${column ?? "unknown"}" in "${tableRef}" cannot be empty.${suffix}`,
|
|
638
|
+
code
|
|
639
|
+
};
|
|
640
|
+
case "23514": return {
|
|
641
|
+
message: `Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
|
|
642
|
+
code
|
|
643
|
+
};
|
|
644
|
+
case "22P02": return {
|
|
645
|
+
message: `Invalid data format in "${context}"${said}.${suffix}`,
|
|
646
|
+
code
|
|
647
|
+
};
|
|
648
|
+
case "22001": return {
|
|
649
|
+
message: `Value too long for column "${column ?? "unknown"}" in "${tableRef}"${said}.${suffix}`,
|
|
650
|
+
code
|
|
651
|
+
};
|
|
652
|
+
case "22003": return {
|
|
653
|
+
message: `Numeric value out of range for column "${column ?? "unknown"}" in "${tableRef}"${said}.${suffix}`,
|
|
654
|
+
code
|
|
655
|
+
};
|
|
656
|
+
case "42703": return {
|
|
657
|
+
message: `Unknown column in "${tableRef}"${said}. Check if your schema is up to date (run migrations).${suffix}`,
|
|
658
|
+
code
|
|
659
|
+
};
|
|
660
|
+
case "42P01": return {
|
|
661
|
+
message: `Table not found for "${context}"${said}. Check if your schema is up to date (run migrations).${suffix}`,
|
|
662
|
+
code
|
|
663
|
+
};
|
|
664
|
+
case "42501": return rawMessage.toLowerCase().includes("row-level security policy") ? {
|
|
665
|
+
message: `Not permitted to write this row in "${tableRef}": it does not satisfy the row-level security policy.${suffix}`,
|
|
666
|
+
code
|
|
667
|
+
} : {
|
|
668
|
+
message: `Permission denied on "${tableRef}": the database role this server connects as lacks privileges on it.${suffix}`,
|
|
669
|
+
code
|
|
670
|
+
};
|
|
671
|
+
case "28000": return {
|
|
672
|
+
message: `Authorization failed for "${context}". Check your database credentials.${suffix}`,
|
|
673
|
+
code
|
|
674
|
+
};
|
|
675
|
+
default: {
|
|
676
|
+
const parts = [`Database error in "${context}" [${code}]${said}`];
|
|
677
|
+
if (detail) parts.push(`Detail: ${detail}`);
|
|
678
|
+
if (column) parts.push(`Column: ${column}`);
|
|
679
|
+
if (dataType) parts.push(`Data type: ${dataType}`);
|
|
680
|
+
if (constraint) parts.push(`Constraint: ${constraint}`);
|
|
681
|
+
if (hint) parts.push(`Hint: ${hint}`);
|
|
682
|
+
return {
|
|
683
|
+
message: parts.join(". "),
|
|
684
|
+
code
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Sanitize any error into a message safe and helpful for the client.
|
|
691
|
+
*
|
|
692
|
+
* A deliberate 4xx (`ApiError`) passes through untouched — the server already
|
|
693
|
+
* decided what the client should read. Otherwise the PG error is extracted
|
|
694
|
+
* from the Drizzle cause chain, falling back to a generic message that
|
|
695
|
+
* doesn't leak SQL.
|
|
696
|
+
*
|
|
697
|
+
* @param error - The raw caught error
|
|
698
|
+
* @param context - A human-readable context string (e.g. collection path)
|
|
699
|
+
* @returns An object with `message` (user-friendly) and optional `code`
|
|
700
|
+
* (the `ApiError` code, or the PG SQLSTATE).
|
|
701
|
+
*/
|
|
702
|
+
function sanitizeErrorForClient(error, context) {
|
|
703
|
+
const clientError = asClientFacingError(error);
|
|
704
|
+
if (clientError) {
|
|
705
|
+
const line = `[API ${clientError.statusCode} ${clientError.code ?? "BAD_REQUEST"}] in "${context}": ${clientError.message}`;
|
|
706
|
+
if (clientError.expected) logger.debug(line);
|
|
707
|
+
else logger.warn(`⚠️ ${line}`);
|
|
708
|
+
return {
|
|
709
|
+
message: clientError.message,
|
|
710
|
+
...clientError.code && { code: clientError.code }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const pgError = extractPgError(error);
|
|
714
|
+
if (pgError) {
|
|
715
|
+
logger.error(`[PG ${pgError.code}] Error in "${context}"`, {
|
|
716
|
+
code: pgError.code,
|
|
717
|
+
message: pgError.message,
|
|
718
|
+
detail: pgError.detail,
|
|
719
|
+
hint: pgError.hint,
|
|
720
|
+
column: pgError.column,
|
|
721
|
+
table: pgError.table,
|
|
722
|
+
constraint: pgError.constraint,
|
|
723
|
+
dataType: pgError.dataType
|
|
724
|
+
});
|
|
725
|
+
return pgErrorToFriendlyMessage(pgError, context);
|
|
726
|
+
}
|
|
727
|
+
logger.error(`Database error in "${context}" (no PG error extracted)`, {
|
|
728
|
+
error: error instanceof Error ? error.message : String(error),
|
|
729
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
730
|
+
cause: error instanceof Error && error.cause ? error.cause instanceof Error ? error.cause.message : String(error.cause) : void 0
|
|
731
|
+
});
|
|
732
|
+
const causeMessage = extractCauseMessage(error);
|
|
733
|
+
if (causeMessage) return { message: `Database error in "${context}": ${causeMessage}` };
|
|
734
|
+
return { message: `Could not load data for "${context}". Check server logs for details.` };
|
|
735
|
+
}
|
|
736
|
+
//#endregion
|
|
443
737
|
//#region src/schema/auth-users-columns.ts
|
|
444
738
|
/**
|
|
445
739
|
* `email` is NOT NULL on purpose, and the anonymous sign-in route depends on it
|
|
@@ -864,6 +1158,34 @@ var isIdProperty = (propName, prop, collection) => {
|
|
|
864
1158
|
* the generated `policies.sql` emit the same SQL, from the same source.
|
|
865
1159
|
*/
|
|
866
1160
|
var statementsToDdl = (statements) => statements.map((s) => `${s}\n`).join("");
|
|
1161
|
+
/**
|
|
1162
|
+
* The `ON DELETE` a foreign key gets when the author did not say.
|
|
1163
|
+
*
|
|
1164
|
+
* One rule for every property that emits one — `belongsTo` relations and
|
|
1165
|
+
* `reference` properties alike. `reference` kept its own `CASCADE` default for
|
|
1166
|
+
* a release after this rule was written, which is exactly the split this
|
|
1167
|
+
* function exists to prevent: two spellings of the same link, two data-retention
|
|
1168
|
+
* behaviours, and only one of them documented.
|
|
1169
|
+
*
|
|
1170
|
+
* An optional link is `SET NULL`: the column can hold NULL, so dropping the
|
|
1171
|
+
* parent leaves the child row with an empty pointer, which is what "optional"
|
|
1172
|
+
* already means.
|
|
1173
|
+
*
|
|
1174
|
+
* A required link is **`RESTRICT`**, not `CASCADE`. `NOT NULL` says the child
|
|
1175
|
+
* cannot exist without a parent; it does not say deleting the parent should
|
|
1176
|
+
* take the child with it. That second claim is a data-retention decision, and
|
|
1177
|
+
* defaulting to it meant `onDelete` — a field nobody has to write — silently
|
|
1178
|
+
* turned every `DELETE FROM authors` into a cascade through posts, comments and
|
|
1179
|
+
* anything else that hung off them. `RESTRICT` fails the delete instead and
|
|
1180
|
+
* names the constraint, so the author chooses `cascade` on purpose or clears
|
|
1181
|
+
* the children first.
|
|
1182
|
+
*
|
|
1183
|
+
* Both DDL paths — the `CREATE TABLE` generator and {@link planRelationalColumns},
|
|
1184
|
+
* which boot-ensure compares the live database against — call this, so a
|
|
1185
|
+
* default that appears in one and not the other cannot make `db push` plan a
|
|
1186
|
+
* constraint rewrite on every run.
|
|
1187
|
+
*/
|
|
1188
|
+
var defaultBelongsToOnDelete = (required) => required ? "RESTRICT" : "SET NULL";
|
|
867
1189
|
var generatePolicyDdl = (collection, rule, resolveCollection) => statementsToDdl(generatePolicyStatements(collection, rule, resolveCollection));
|
|
868
1190
|
/**
|
|
869
1191
|
* The individual SQL statements a single security rule compiles to: a
|
|
@@ -1234,7 +1556,7 @@ var generatePostgresDdl = async (allCollections, options = {
|
|
|
1234
1556
|
const fkColType = getSqlColumnType(propName, prop, collection, collections);
|
|
1235
1557
|
const onUpdate = relInfo.onUpdate ? ` ON UPDATE ${relInfo.onUpdate.toUpperCase()}` : "";
|
|
1236
1558
|
const required = prop.validation?.required;
|
|
1237
|
-
const onDeleteVal = relInfo.onDelete ?? (required
|
|
1559
|
+
const onDeleteVal = relInfo.onDelete ?? defaultBelongsToOnDelete(required);
|
|
1238
1560
|
let colDef = ` "${relInfo.localKey}" ${fkColType}`;
|
|
1239
1561
|
if (required) colDef += " NOT NULL";
|
|
1240
1562
|
columns.push(colDef);
|
|
@@ -1253,7 +1575,7 @@ var generatePostgresDdl = async (allCollections, options = {
|
|
|
1253
1575
|
const targetTable = getTableName(targetCollection);
|
|
1254
1576
|
const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : "public";
|
|
1255
1577
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
1256
|
-
const onDelete = required
|
|
1578
|
+
const onDelete = defaultBelongsToOnDelete(required);
|
|
1257
1579
|
let colDef = ` "${colName}" ${colType}`;
|
|
1258
1580
|
if (required) colDef += " NOT NULL";
|
|
1259
1581
|
columns.push(colDef);
|
|
@@ -1420,7 +1742,7 @@ var planRelationalColumns = (allCollections) => {
|
|
|
1420
1742
|
targetSchema: schemaOfCollection(targetCollection),
|
|
1421
1743
|
targetTable: bareTableName(getTableName(targetCollection)),
|
|
1422
1744
|
targetColumn: getPrimaryKeyName(targetCollection),
|
|
1423
|
-
onDelete: relInfo.onDelete ?? (required
|
|
1745
|
+
onDelete: relInfo.onDelete ?? defaultBelongsToOnDelete(required),
|
|
1424
1746
|
onUpdate: relInfo.onUpdate
|
|
1425
1747
|
})
|
|
1426
1748
|
});
|
|
@@ -1442,7 +1764,7 @@ var planRelationalColumns = (allCollections) => {
|
|
|
1442
1764
|
targetSchema: schemaOfCollection(targetCollection),
|
|
1443
1765
|
targetTable: bareTableName(getTableName(targetCollection)),
|
|
1444
1766
|
targetColumn: getPrimaryKeyName(targetCollection),
|
|
1445
|
-
onDelete: required
|
|
1767
|
+
onDelete: defaultBelongsToOnDelete(required)
|
|
1446
1768
|
}) : void 0
|
|
1447
1769
|
});
|
|
1448
1770
|
}
|
|
@@ -1599,6 +1921,111 @@ var generatePostgresPoliciesDdl = (allCollections) => {
|
|
|
1599
1921
|
return ddl;
|
|
1600
1922
|
};
|
|
1601
1923
|
//#endregion
|
|
1924
|
+
//#region src/schema/column-type-drift.ts
|
|
1925
|
+
/**
|
|
1926
|
+
* Types that mean the same thing to everything above the driver.
|
|
1927
|
+
*
|
|
1928
|
+
* Keyed by every spelling either side can produce: `information_schema` reports
|
|
1929
|
+
* `udt_name` (`int4`, `_numeric`, `bpchar`), while the generators emit SQL
|
|
1930
|
+
* (`INTEGER`, `NUMERIC[]`, `CHAR(3)`). Both are normalised through here.
|
|
1931
|
+
*/
|
|
1932
|
+
var FAMILIES = {
|
|
1933
|
+
int2: "number",
|
|
1934
|
+
smallint: "number",
|
|
1935
|
+
int4: "number",
|
|
1936
|
+
int: "number",
|
|
1937
|
+
integer: "number",
|
|
1938
|
+
int8: "number",
|
|
1939
|
+
bigint: "number",
|
|
1940
|
+
numeric: "number",
|
|
1941
|
+
decimal: "number",
|
|
1942
|
+
float4: "number",
|
|
1943
|
+
real: "number",
|
|
1944
|
+
float8: "number",
|
|
1945
|
+
"double precision": "number",
|
|
1946
|
+
money: "number",
|
|
1947
|
+
text: "text",
|
|
1948
|
+
varchar: "text",
|
|
1949
|
+
"character varying": "text",
|
|
1950
|
+
bpchar: "text",
|
|
1951
|
+
char: "text",
|
|
1952
|
+
character: "text",
|
|
1953
|
+
citext: "text",
|
|
1954
|
+
bool: "boolean",
|
|
1955
|
+
boolean: "boolean",
|
|
1956
|
+
json: "json",
|
|
1957
|
+
jsonb: "jsonb",
|
|
1958
|
+
uuid: "uuid",
|
|
1959
|
+
bytea: "bytea",
|
|
1960
|
+
date: "date",
|
|
1961
|
+
time: "time",
|
|
1962
|
+
"time without time zone": "time",
|
|
1963
|
+
timetz: "time",
|
|
1964
|
+
"time with time zone": "time",
|
|
1965
|
+
timestamp: "timestamp",
|
|
1966
|
+
"timestamp without time zone": "timestamp",
|
|
1967
|
+
timestamptz: "timestamptz",
|
|
1968
|
+
"timestamp with time zone": "timestamptz",
|
|
1969
|
+
interval: "interval",
|
|
1970
|
+
tsvector: "tsvector",
|
|
1971
|
+
vector: "vector",
|
|
1972
|
+
halfvec: "halfvec",
|
|
1973
|
+
sparsevec: "sparsevec"
|
|
1974
|
+
};
|
|
1975
|
+
/**
|
|
1976
|
+
* One SQL or `udt_name` spelling reduced to its family, or `null` when this
|
|
1977
|
+
* module has no opinion about it.
|
|
1978
|
+
*
|
|
1979
|
+
* `null` is not a failure. It is the answer for an enum type, a domain, a
|
|
1980
|
+
* PostGIS column, a quoted qualified name — everything whose identity is a
|
|
1981
|
+
* *name* rather than a shape, where two spellings being different says nothing
|
|
1982
|
+
* about whether the values are compatible.
|
|
1983
|
+
*/
|
|
1984
|
+
function typeFamily(raw) {
|
|
1985
|
+
if (!raw) return null;
|
|
1986
|
+
let type = raw.trim().toLowerCase();
|
|
1987
|
+
if (!type) return null;
|
|
1988
|
+
if (type.startsWith("\"")) return null;
|
|
1989
|
+
const generated = type.indexOf(" generated ");
|
|
1990
|
+
if (generated > 0) type = type.slice(0, generated).trim();
|
|
1991
|
+
if (type.endsWith("[]")) {
|
|
1992
|
+
const element = typeFamily(type.slice(0, -2));
|
|
1993
|
+
return element && `array:${element}`;
|
|
1994
|
+
}
|
|
1995
|
+
if (type.startsWith("_")) {
|
|
1996
|
+
const element = typeFamily(type.slice(1));
|
|
1997
|
+
return element && `array:${element}`;
|
|
1998
|
+
}
|
|
1999
|
+
const paren = type.indexOf("(");
|
|
2000
|
+
if (paren > 0) type = type.slice(0, paren).trim();
|
|
2001
|
+
return FAMILIES[type] ?? null;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Do these two spellings describe columns that hold the same kind of value?
|
|
2005
|
+
*
|
|
2006
|
+
* `true` whenever the answer is not confidently "no" — an unrecognised type on
|
|
2007
|
+
* either side agrees with everything, because a report needs evidence and an
|
|
2008
|
+
* unknown type is the absence of it.
|
|
2009
|
+
*/
|
|
2010
|
+
function typesAgree(declared, actual) {
|
|
2011
|
+
const a = typeFamily(declared);
|
|
2012
|
+
const b = typeFamily(actual);
|
|
2013
|
+
if (a === null || b === null) return true;
|
|
2014
|
+
return a === b;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* The sentence a boot log says about one drifted column.
|
|
2018
|
+
*
|
|
2019
|
+
* Written to be actionable in the place it is read, which is a runtime log with
|
|
2020
|
+
* no way to run a migration from it: it names both types, says which surface
|
|
2021
|
+
* will break first, and gives the statement that resolves it.
|
|
2022
|
+
*/
|
|
2023
|
+
function columnTypeDriftMessage(drift) {
|
|
2024
|
+
const [schema, table] = drift.table.split(".");
|
|
2025
|
+
return `Column "${drift.table}"."${drift.column}" is ${drift.actual} in the database, but this collection declares ${drift.declared}. Boot does not change an existing column's type, so the database keeps what it has and writes that match the declaration will be rejected by Postgres. Change it deliberately, when you can watch it:
|
|
2026
|
+
ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${drift.column}" TYPE ${drift.declared} USING "${drift.column}"::${drift.declared};`;
|
|
2027
|
+
}
|
|
2028
|
+
//#endregion
|
|
1602
2029
|
//#region src/schema/ensure-collection-tables.ts
|
|
1603
2030
|
/**
|
|
1604
2031
|
* Bringing a database up to date with a bundle's collections, additively.
|
|
@@ -1628,6 +2055,7 @@ var generatePostgresPoliciesDdl = (allCollections) => {
|
|
|
1628
2055
|
* no-op.
|
|
1629
2056
|
*/
|
|
1630
2057
|
var ensure_collection_tables_exports = /* @__PURE__ */ __exportAll({
|
|
2058
|
+
describeDriverError: () => describeDriverError,
|
|
1631
2059
|
ensureCollectionTables: () => ensureCollectionTables,
|
|
1632
2060
|
planCollectionSchemaEnsure: () => planCollectionSchemaEnsure,
|
|
1633
2061
|
readExistingSchema: () => readExistingSchema,
|
|
@@ -1639,11 +2067,33 @@ function assertSafeIdentifier(value, what) {
|
|
|
1639
2067
|
if (!SAFE_IDENTIFIER.test(value)) throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);
|
|
1640
2068
|
return value;
|
|
1641
2069
|
}
|
|
2070
|
+
/**
|
|
2071
|
+
* The schema a collection lives in — validated, because it is about to be
|
|
2072
|
+
* interpolated into DDL.
|
|
2073
|
+
*
|
|
2074
|
+
* Validation used to run only on the names read back OUT of the catalogue,
|
|
2075
|
+
* which is the direction that cannot hurt anyone: those came from Postgres. The
|
|
2076
|
+
* names going IN — the schema and table a collection declares — were quoted and
|
|
2077
|
+
* concatenated on trust, and quoting is not escaping. A table named
|
|
2078
|
+
* `x" (id int); DROP TABLE users; --` closes the quote and the statement, and
|
|
2079
|
+
* the whole thing runs on the owner connection, which is the one connection in
|
|
2080
|
+
* the system that is exempt from every RLS policy.
|
|
2081
|
+
*
|
|
2082
|
+
* That is only a config file on a self-hosted project, where the person writing
|
|
2083
|
+
* it could run the SQL directly anyway. It is not only a config file where a
|
|
2084
|
+
* collection can be defined over the wire — the live-schema and source editors
|
|
2085
|
+
* both do that — and there the caller is an admin on the app, not an operator
|
|
2086
|
+
* of the database.
|
|
2087
|
+
*/
|
|
1642
2088
|
function schemaOf(collection) {
|
|
1643
|
-
return isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
|
|
2089
|
+
return assertSafeIdentifier(isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public", "schema name");
|
|
2090
|
+
}
|
|
2091
|
+
/** A collection's table name, validated for the same reason as {@link schemaOf}. */
|
|
2092
|
+
function tableOf(collection) {
|
|
2093
|
+
return assertSafeIdentifier(getTableName(collection), "table name");
|
|
1644
2094
|
}
|
|
1645
2095
|
function qualified(collection) {
|
|
1646
|
-
return `${schemaOf(collection)}.${
|
|
2096
|
+
return `${schemaOf(collection)}.${tableOf(collection)}`;
|
|
1647
2097
|
}
|
|
1648
2098
|
/**
|
|
1649
2099
|
* Enum types a collection's properties require, as `schema.typename`.
|
|
@@ -1654,7 +2104,7 @@ function qualified(collection) {
|
|
|
1654
2104
|
* silent schema fork.
|
|
1655
2105
|
*/
|
|
1656
2106
|
function requiredEnums(collection) {
|
|
1657
|
-
const table =
|
|
2107
|
+
const table = tableOf(collection);
|
|
1658
2108
|
const schema = schemaOf(collection);
|
|
1659
2109
|
const out = [];
|
|
1660
2110
|
for (const [propName, prop] of Object.entries(collection.properties ?? {})) {
|
|
@@ -1680,6 +2130,7 @@ function requiredEnums(collection) {
|
|
|
1680
2130
|
function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
1681
2131
|
const constraintPolicy = options.constraints ?? "additive";
|
|
1682
2132
|
const withheldConstraints = [];
|
|
2133
|
+
const columnTypeDrift = [];
|
|
1683
2134
|
assertSearchIsPostgresOnly(allCollections);
|
|
1684
2135
|
const collections = relationalCollections(allCollections);
|
|
1685
2136
|
const actions = [];
|
|
@@ -1736,11 +2187,12 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1736
2187
|
if (existing.tables.has(key) || created.has(key)) continue;
|
|
1737
2188
|
created.add(key);
|
|
1738
2189
|
const schema = schemaOf(collection);
|
|
1739
|
-
const table =
|
|
2190
|
+
const table = tableOf(collection);
|
|
1740
2191
|
const idEntry = Object.entries(collection.properties ?? {}).find(([n, p]) => isIdProperty(n, p, collection));
|
|
1741
2192
|
const idName = idEntry ? resolveColumnName(idEntry[0], idEntry[1]) : "id";
|
|
1742
2193
|
const idProp = idEntry?.[1];
|
|
1743
|
-
|
|
2194
|
+
const idType = idProp ? getSqlColumnType(idEntry[0], idProp, collection, collections) : "TEXT";
|
|
2195
|
+
let idDef = `"${assertSafeIdentifier(idName, "column name")}" ${idType} PRIMARY KEY`;
|
|
1744
2196
|
if (idProp?.type === "string" && idProp.isId === "uuid") idDef += " DEFAULT gen_random_uuid()";
|
|
1745
2197
|
actions.push({
|
|
1746
2198
|
kind: "create-table",
|
|
@@ -1791,6 +2243,7 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1791
2243
|
};
|
|
1792
2244
|
const addColumn = (key, schema, table, column, definition) => {
|
|
1793
2245
|
if (existing.tables.get(key)?.has(column)) return;
|
|
2246
|
+
assertSafeIdentifier(column, "column name");
|
|
1794
2247
|
actions.push({
|
|
1795
2248
|
kind: "add-column",
|
|
1796
2249
|
target: `${key}.${column}`,
|
|
@@ -1800,7 +2253,7 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1800
2253
|
for (const collection of collections) {
|
|
1801
2254
|
const key = qualified(collection);
|
|
1802
2255
|
const schema = schemaOf(collection);
|
|
1803
|
-
const table =
|
|
2256
|
+
const table = tableOf(collection);
|
|
1804
2257
|
const fresh = created.has(key);
|
|
1805
2258
|
const auth = isAuthCollection(collection);
|
|
1806
2259
|
for (const [propName, prop] of Object.entries(collection.properties ?? {})) {
|
|
@@ -1813,7 +2266,8 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1813
2266
|
addColumn(key, schema, table, column, authDefinition);
|
|
1814
2267
|
continue;
|
|
1815
2268
|
}
|
|
1816
|
-
|
|
2269
|
+
const declaredType = getSqlColumnType(propName, p, collection, collections);
|
|
2270
|
+
let definition = declaredType;
|
|
1817
2271
|
if (fresh && p.validation?.unique) definition += " UNIQUE";
|
|
1818
2272
|
const autoValue = p.autoValue;
|
|
1819
2273
|
const hasDefault = p.type === "date" && (autoValue === "on_create" || autoValue === "on_update");
|
|
@@ -1821,6 +2275,13 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1821
2275
|
const required = p.validation?.required === true;
|
|
1822
2276
|
const columnKey = `${key}.${column}`;
|
|
1823
2277
|
const columnExists = existing.tables.get(key)?.has(column) === true;
|
|
2278
|
+
const actualType = existing.columnTypes?.get(columnKey);
|
|
2279
|
+
if (columnExists && actualType && !typesAgree(declaredType, actualType)) columnTypeDrift.push({
|
|
2280
|
+
table: key,
|
|
2281
|
+
column,
|
|
2282
|
+
declared: declaredType,
|
|
2283
|
+
actual: actualType
|
|
2284
|
+
});
|
|
1824
2285
|
const tableIsEmpty = existing.populatedTables !== void 0 && existing.tables.has(key) && !existing.populatedTables.has(key);
|
|
1825
2286
|
const notNullIsSafe = fresh || tableIsEmpty || hasDefault;
|
|
1826
2287
|
if (required && !columnExists) if (notNullIsSafe) definition += " NOT NULL";
|
|
@@ -1903,8 +2364,12 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1903
2364
|
addColumn(key, junction.schema, junction.table, column.name, column.type);
|
|
1904
2365
|
}
|
|
1905
2366
|
}
|
|
2367
|
+
const plannedRelationColumns = /* @__PURE__ */ new Map();
|
|
1906
2368
|
for (const relational of planRelationalColumns(collections)) {
|
|
1907
2369
|
const relKey = `${relational.schema}.${relational.table}`;
|
|
2370
|
+
if (!plannedRelationColumns.has(relKey)) plannedRelationColumns.set(relKey, /* @__PURE__ */ new Set());
|
|
2371
|
+
plannedRelationColumns.get(relKey).add(relational.column);
|
|
2372
|
+
if (relational.legacyColumn) plannedRelationColumns.get(relKey).add(relational.legacyColumn);
|
|
1908
2373
|
if (renameLegacyColumn(relKey, relational.schema, relational.table, relational.column, relational.legacyColumn)) continue;
|
|
1909
2374
|
addColumn(relKey, relational.schema, relational.table, relational.column, relational.type);
|
|
1910
2375
|
}
|
|
@@ -1922,10 +2387,11 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1922
2387
|
sql: fk.sql
|
|
1923
2388
|
});
|
|
1924
2389
|
}
|
|
2390
|
+
const concurrentIndexing = (schema, table) => !created.has(`${schema}.${table}`);
|
|
1925
2391
|
for (const spec of searchSpecs) for (const statement of searchIndexStatements(spec)) actions.push({
|
|
1926
2392
|
kind: "create-index",
|
|
1927
2393
|
target: `${spec.schema}.${spec.table}`,
|
|
1928
|
-
sql: statement.replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS")
|
|
2394
|
+
sql: concurrentIndexing(spec.schema, spec.table) ? statement.replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS") : statement
|
|
1929
2395
|
});
|
|
1930
2396
|
const vectorIndexSkipped = [];
|
|
1931
2397
|
for (const collection of collections) {
|
|
@@ -1933,7 +2399,7 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1933
2399
|
for (const spec of plan.specs) actions.push({
|
|
1934
2400
|
kind: "create-index",
|
|
1935
2401
|
target: `${spec.schema}.${spec.table}`,
|
|
1936
|
-
sql: vectorIndexStatement(spec).replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS")
|
|
2402
|
+
sql: concurrentIndexing(spec.schema, spec.table) ? vectorIndexStatement(spec).replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS") : vectorIndexStatement(spec)
|
|
1937
2403
|
});
|
|
1938
2404
|
vectorIndexSkipped.push(...plan.skipped);
|
|
1939
2405
|
}
|
|
@@ -1941,10 +2407,30 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1941
2407
|
kind: "create-index",
|
|
1942
2408
|
target: `${spec.schema}.${spec.table}`,
|
|
1943
2409
|
sql: collectionIndexStatement(spec, {
|
|
1944
|
-
concurrently:
|
|
2410
|
+
concurrently: concurrentIndexing(spec.schema, spec.table),
|
|
1945
2411
|
ifNotExists: true
|
|
1946
2412
|
})
|
|
1947
2413
|
});
|
|
2414
|
+
const orphanedRequiredColumns = [];
|
|
2415
|
+
if (existing.notNullColumns && existing.columnDefaults) for (const collection of collections) {
|
|
2416
|
+
const key = qualified(collection);
|
|
2417
|
+
const live = existing.tables.get(key);
|
|
2418
|
+
if (!live || created.has(key)) continue;
|
|
2419
|
+
const declared = /* @__PURE__ */ new Set();
|
|
2420
|
+
for (const [propName, prop] of Object.entries(collection.properties ?? {})) declared.add(resolveColumnName(propName, prop));
|
|
2421
|
+
for (const column of plannedRelationColumns.get(key) ?? []) declared.add(column);
|
|
2422
|
+
for (const column of live) {
|
|
2423
|
+
const columnKey = `${key}.${column}`;
|
|
2424
|
+
if (declared.has(column)) continue;
|
|
2425
|
+
if (!existing.notNullColumns.has(columnKey)) continue;
|
|
2426
|
+
if (existing.columnDefaults.has(columnKey)) continue;
|
|
2427
|
+
orphanedRequiredColumns.push({
|
|
2428
|
+
table: key,
|
|
2429
|
+
column,
|
|
2430
|
+
slug: collection.slug
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
1948
2434
|
return {
|
|
1949
2435
|
actions,
|
|
1950
2436
|
statements: actions.map((a) => a.sql),
|
|
@@ -1952,7 +2438,9 @@ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
|
|
|
1952
2438
|
searchDrift,
|
|
1953
2439
|
searchAdopted,
|
|
1954
2440
|
vectorIndexSkipped,
|
|
1955
|
-
withheldConstraints
|
|
2441
|
+
withheldConstraints,
|
|
2442
|
+
columnTypeDrift,
|
|
2443
|
+
orphanedRequiredColumns
|
|
1956
2444
|
};
|
|
1957
2445
|
}
|
|
1958
2446
|
/** Read what the database has, for the schemas the collections live in. */
|
|
@@ -1965,7 +2453,9 @@ async function readExistingSchema(client, schemas) {
|
|
|
1965
2453
|
};
|
|
1966
2454
|
const inList = schemas.map((schema) => `'${assertSafeIdentifier(schema, "schema name")}'`).join(", ");
|
|
1967
2455
|
const notNullColumns = /* @__PURE__ */ new Set();
|
|
1968
|
-
const
|
|
2456
|
+
const columnTypes = /* @__PURE__ */ new Map();
|
|
2457
|
+
const columnDefaults = /* @__PURE__ */ new Set();
|
|
2458
|
+
const { rows: columns } = await client.query(`SELECT table_schema, table_name, column_name, is_nullable, udt_name, column_default
|
|
1969
2459
|
FROM information_schema.columns
|
|
1970
2460
|
WHERE table_schema IN (${inList})`);
|
|
1971
2461
|
for (const row of columns) {
|
|
@@ -1973,6 +2463,8 @@ async function readExistingSchema(client, schemas) {
|
|
|
1973
2463
|
if (!tables.has(key)) tables.set(key, /* @__PURE__ */ new Set());
|
|
1974
2464
|
tables.get(key).add(row.column_name);
|
|
1975
2465
|
if (row.is_nullable === "NO") notNullColumns.add(`${key}.${row.column_name}`);
|
|
2466
|
+
if (row.udt_name) columnTypes.set(`${key}.${row.column_name}`, row.udt_name);
|
|
2467
|
+
if (row.column_default !== null) columnDefaults.add(`${key}.${row.column_name}`);
|
|
1976
2468
|
}
|
|
1977
2469
|
const populatedTables = /* @__PURE__ */ new Set();
|
|
1978
2470
|
const { rows: realTables } = await client.query(`SELECT n.nspname AS schema, c.relname AS name
|
|
@@ -2030,7 +2522,9 @@ async function readExistingSchema(client, schemas) {
|
|
|
2030
2522
|
columnComments,
|
|
2031
2523
|
enumValues,
|
|
2032
2524
|
notNullColumns,
|
|
2033
|
-
populatedTables
|
|
2525
|
+
populatedTables,
|
|
2526
|
+
columnTypes,
|
|
2527
|
+
columnDefaults
|
|
2034
2528
|
};
|
|
2035
2529
|
}
|
|
2036
2530
|
/**
|
|
@@ -2097,6 +2591,18 @@ async function ensureCollectionTables(client, collections, log, options = {}) {
|
|
|
2097
2591
|
logger.warn(`[schema] ${message}`);
|
|
2098
2592
|
log?.(message);
|
|
2099
2593
|
}
|
|
2594
|
+
for (const drift of plan.columnTypeDrift) {
|
|
2595
|
+
const message = columnTypeDriftMessage(drift);
|
|
2596
|
+
logger.warn(`[schema] ${message}`);
|
|
2597
|
+
log?.(message);
|
|
2598
|
+
}
|
|
2599
|
+
for (const orphan of plan.orphanedRequiredColumns) {
|
|
2600
|
+
const [schema, table] = orphan.table.split(".");
|
|
2601
|
+
const message = `"${orphan.table}"."${orphan.column}" is NOT NULL with no default, and the "${orphan.slug}" collection no longer declares it. Every insert will be rejected for a field you cannot set (PG_23502, naming "${orphan.column}"). This is what a renamed property looks like: the new column was added, and the old one cannot be dropped unattended. Fix it with ONE of:
|
|
2602
|
+
ALTER TABLE "${schema}"."${table}" DROP COLUMN "${orphan.column}"; -- the rename is complete\n ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${orphan.column}" DROP NOT NULL; -- keep the data, stop requiring it\n ...or declare the property again, if the column is still wanted.`;
|
|
2603
|
+
logger.warn(`[schema] ${message}`);
|
|
2604
|
+
log?.(message);
|
|
2605
|
+
}
|
|
2100
2606
|
if (plan.actions.length === 0) {
|
|
2101
2607
|
log?.("Schema is up to date; nothing to create.");
|
|
2102
2608
|
return {
|
|
@@ -2108,8 +2614,8 @@ async function ensureCollectionTables(client, collections, log, options = {}) {
|
|
|
2108
2614
|
if (await applyAction(client, action)) log?.(`${action.kind}: ${action.target}`);
|
|
2109
2615
|
else log?.(`${action.kind}: ${action.target} (already created by a peer)`);
|
|
2110
2616
|
} catch (err) {
|
|
2111
|
-
const message =
|
|
2112
|
-
if (action.kind === "add-constraint" || action.kind === "comment-column") {
|
|
2617
|
+
const message = describeDriverError(err);
|
|
2618
|
+
if (action.kind === "add-constraint" || action.kind === "comment-column" || action.kind === "create-index" && !/CREATE\s+UNIQUE\s+INDEX/i.test(action.sql)) {
|
|
2113
2619
|
failures.push({
|
|
2114
2620
|
kind: action.kind,
|
|
2115
2621
|
target: action.target,
|
|
@@ -2124,6 +2630,56 @@ async function ensureCollectionTables(client, collections, log, options = {}) {
|
|
|
2124
2630
|
failures
|
|
2125
2631
|
};
|
|
2126
2632
|
}
|
|
2633
|
+
/**
|
|
2634
|
+
* What actually went wrong, rather than what the ORM said about it.
|
|
2635
|
+
*
|
|
2636
|
+
* Drizzle wraps every driver error, and its `message` is literally
|
|
2637
|
+
* ``Failed query: ${sql}\nparams: ${params}`` — the statement we already know,
|
|
2638
|
+
* and not one word of Postgres's answer. Reporting `err.message` therefore
|
|
2639
|
+
* produced a failure that named the statement twice and the *reason* zero
|
|
2640
|
+
* times, so four unrelated boot failures — a missing extension, a wrong
|
|
2641
|
+
* privilege, a bad index method, a constraint the rows violate — all read
|
|
2642
|
+
* identically to whoever had to fix them.
|
|
2643
|
+
*
|
|
2644
|
+
* Two consequences, both observed in the field before this existed:
|
|
2645
|
+
*
|
|
2646
|
+
* 1. On a managed deployment the reason never reaches anybody. Boot dies, the
|
|
2647
|
+
* pod never goes ready, and the deploy reports a readiness timeout — so the
|
|
2648
|
+
* one line that says why is the one line that was thrown away here.
|
|
2649
|
+
* 2. {@link vectorExtensionHint} matches on the error text. Against the
|
|
2650
|
+
* wrapper it matched nothing, so the hint written specifically to explain a
|
|
2651
|
+
* missing pgvector never fired on the path that raises it.
|
|
2652
|
+
*
|
|
2653
|
+
* `detail` and `hint` come along because they are the fields Postgres uses to
|
|
2654
|
+
* say which object it meant and what to do about it — `CREATE EXTENSION` names
|
|
2655
|
+
* the missing library there, and a rejected constraint names the row. This
|
|
2656
|
+
* writes into the tenant's own boot log, about the tenant's own database.
|
|
2657
|
+
*/
|
|
2658
|
+
function describeDriverError(err) {
|
|
2659
|
+
const pg = extractPgError(err);
|
|
2660
|
+
if (pg) {
|
|
2661
|
+
const parts = [pg.code ? `${pg.message} [${pg.code}]` : pg.message];
|
|
2662
|
+
if (pg.detail) parts.push(` detail: ${pg.detail}`);
|
|
2663
|
+
if (pg.hint) parts.push(` hint: ${pg.hint}`);
|
|
2664
|
+
return parts.join("\n");
|
|
2665
|
+
}
|
|
2666
|
+
const cause = extractCauseMessage(err);
|
|
2667
|
+
if (cause) return cause;
|
|
2668
|
+
return err instanceof Error ? err.message : String(err);
|
|
2669
|
+
}
|
|
2670
|
+
/** ` CONCURRENTLY ` as it appears in a rendered `CREATE [UNIQUE] INDEX`. */
|
|
2671
|
+
var CONCURRENTLY_RE = /\sCONCURRENTLY\s/i;
|
|
2672
|
+
/**
|
|
2673
|
+
* `25001 active_sql_transaction` — "cannot run inside a transaction block".
|
|
2674
|
+
*
|
|
2675
|
+
* Only ever raised here by `CREATE INDEX CONCURRENTLY`, and only when the
|
|
2676
|
+
* connection we were handed is inside a transaction. Deliberately narrow: it
|
|
2677
|
+
* decides to retry a *different* statement, so it must not fire on anything it
|
|
2678
|
+
* has not been reasoned about.
|
|
2679
|
+
*/
|
|
2680
|
+
function isTransactionBlockRefusal(err) {
|
|
2681
|
+
return extractPgError(err)?.code === "25001";
|
|
2682
|
+
}
|
|
2127
2683
|
/** Attempts per action, including the first. Matches the server's bootstraps. */
|
|
2128
2684
|
var DDL_ATTEMPTS = 4;
|
|
2129
2685
|
/**
|
|
@@ -2148,10 +2704,16 @@ var DDL_ATTEMPTS = 4;
|
|
|
2148
2704
|
* permission failure, a unique constraint the customer's own rows violate.
|
|
2149
2705
|
*/
|
|
2150
2706
|
async function applyAction(client, action) {
|
|
2707
|
+
let sqlText = action.sql;
|
|
2151
2708
|
for (let attempt = 1;; attempt++) try {
|
|
2152
|
-
await client.query(
|
|
2709
|
+
await client.query(sqlText);
|
|
2153
2710
|
return true;
|
|
2154
2711
|
} catch (err) {
|
|
2712
|
+
if (isTransactionBlockRefusal(err) && CONCURRENTLY_RE.test(sqlText)) {
|
|
2713
|
+
sqlText = sqlText.replace(CONCURRENTLY_RE, " ");
|
|
2714
|
+
logger.warn(`[schema] ${action.kind} ${action.target}: this connection is inside a transaction, so the index is being built with a write lock rather than concurrently. Writes to this table block until it finishes.`);
|
|
2715
|
+
continue;
|
|
2716
|
+
}
|
|
2155
2717
|
if (isDuplicateObjectRace(err)) {
|
|
2156
2718
|
logger.debug(`[schema] ${action.kind} ${action.target}: already created by another instance`);
|
|
2157
2719
|
return false;
|
|
@@ -2165,6 +2727,6 @@ async function applyAction(client, action) {
|
|
|
2165
2727
|
}
|
|
2166
2728
|
}
|
|
2167
2729
|
//#endregion
|
|
2168
|
-
export {
|
|
2730
|
+
export { __toESM as A, SEARCH_UNACCENT_FN as C, __commonJSMin as D, visibleColumnProjection as E, __exportAll as O, sanitizeErrorForClient as S, hiddenColumnsOption as T, extractPgError as _, readSchemaFactsFor as a, pgErrorToFriendlyMessage as b, generatePostgresPoliciesDdl as c, planCollectionPolicies as d, resolveColumnName as f, extractCauseMessage as g, classifyConnectFailure as h, readExistingSchema as i, __require as k, generatePostgresSearchDdl as l, authUsersColumnSql as m, ensure_collection_tables_exports as n, defaultBelongsToOnDelete as o, AUTH_USERS_COLUMNS as p, planCollectionSchemaEnsure as r, generatePostgresDdl as s, describeDriverError as t, generatePostgresVectorDdl as u, isRoleSwitchingPermissionError as v, buildSearchColumnSpec as w, reachedDatabase as x, isRowLevelSecurityDenial as y };
|
|
2169
2731
|
|
|
2170
|
-
//# sourceMappingURL=ensure-collection-tables-
|
|
2732
|
+
//# sourceMappingURL=ensure-collection-tables-DbAhrjnG.js.map
|