@rebasepro/server-postgres 0.24.0 → 0.24.1-canary.gcb918fc
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/README.md +16 -0
- package/dist/{BranchService-BRt78gfa.js → BranchService-Bsn2wfaj.js} +3 -2
- package/dist/{BranchService-BRt78gfa.js.map → BranchService-Bsn2wfaj.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +91 -2
- package/dist/PostgresBootstrapper.d.ts +101 -2
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +6 -0
- package/dist/backup/backup-cli.d.ts +14 -0
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-DW5p9_zv.js → backup-cli-DnpJi6I7.js} +37 -29
- package/dist/backup-cli-DnpJi6I7.js.map +1 -0
- package/dist/{backup-service-EwcDVG-8.js → backup-service-BTBB7RXn.js} +55 -21
- package/dist/backup-service-BTBB7RXn.js.map +1 -0
- package/dist/boot-ddl-DVpsFN6r.js +105 -0
- package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
- package/dist/cli-console-Ca6ZlAVU.js +154 -0
- package/dist/cli-console-Ca6ZlAVU.js.map +1 -0
- package/dist/cli-console.d.ts +39 -0
- package/dist/cli-errors-CaI4ntyD.js +1919 -0
- package/dist/cli-errors-CaI4ntyD.js.map +1 -0
- package/dist/cli-errors.d.ts +51 -5
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-JNI-pwSf.js} +6 -3
- package/dist/cli-flags-JNI-pwSf.js.map +1 -0
- package/dist/cli-helpers.d.ts +40 -0
- package/dist/cli-output.d.ts +16 -28
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli.js +566 -182
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-drZIsSNf.js} +10 -4
- package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-drZIsSNf.js.map} +1 -1
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C9mT8bgf.js} +24 -112
- package/dist/connection-C9mT8bgf.js.map +1 -0
- package/dist/connection.d.ts +13 -2
- package/dist/databasePoolManager-BIuewidb.js +119 -0
- package/dist/databasePoolManager-BIuewidb.js.map +1 -0
- package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-DP1_SNFo.js} +484 -346
- package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
- package/dist/{doctor-C-sYWbmt.js → doctor-C4WiftHm.js} +63 -52
- package/dist/doctor-C4WiftHm.js.map +1 -0
- package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
- package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
- package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-CVm_yvzb.js} +173 -77
- package/dist/ensure-tables-CVm_yvzb.js.map +1 -0
- package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
- package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-B74WN37S.js} +5 -5
- package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
- package/dist/generate-files-CdEckX6c.js +199 -0
- package/dist/generate-files-CdEckX6c.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B64O2oZF.js} +3 -3
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B64O2oZF.js.map} +1 -1
- package/dist/generated-sql.d.ts +8 -2
- package/dist/history/HistoryService.d.ts +64 -5
- package/dist/history/ensure-history-table.d.ts +63 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +139 -19236
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-C-Di8dA9.js} +44 -6
- package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
- package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-D6kAqReI.js} +23 -1
- package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
- package/dist/open-rebase-CbOur0TU.js +20858 -0
- package/dist/open-rebase-CbOur0TU.js.map +1 -0
- package/dist/open-rebase.d.ts +86 -0
- package/dist/pg-error-utils-vJezlXdI.js +388 -0
- package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
- package/dist/{plan-schema-DU9exq6C.js → plan-schema-B1YF_tYq.js} +151 -41
- package/dist/plan-schema-B1YF_tYq.js.map +1 -0
- package/dist/{policy-drift-B-J2hhm0.js → policy-drift-Dm8i-s1J.js} +23 -6
- package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
- package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
- package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
- package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
- package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
- package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CtC3y9Ol.js} +8 -4
- package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
- package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1270
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
- package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-DvzqlaL1.js} +129 -17
- package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +2 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +5 -0
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +7 -0
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +20 -1
- package/dist/schema/ensure-collection-policies.d.ts +73 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/introspect-db-logic.d.ts +12 -0
- package/dist/schema/introspect-db-queries.d.ts +11 -1
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +4 -5
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +126 -0
- package/dist/schema/plan/types.d.ts +8 -0
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/security/rls-enforcement.d.ts +32 -4
- package/dist/services/FetchService.d.ts +58 -8
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +88 -3
- package/dist/services/cdc/CdcListener.d.ts +17 -6
- package/dist/services/cdc/trigger-cdc.d.ts +151 -27
- package/dist/services/channel-bus/index.d.ts +1 -1
- package/dist/services/channel-history.d.ts +15 -4
- package/dist/services/collection-helpers.d.ts +15 -0
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +7 -0
- package/dist/services/realtimeService.d.ts +314 -14
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +42 -2
- package/dist/services/sql-script.d.ts +207 -3
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +6 -0
- package/dist/services/write-transaction-scope.d.ts +9 -0
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DBDbBTL4.js +811 -0
- package/dist/source-DBDbBTL4.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/drizzle-conditions.d.ts +21 -1
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/websocket-BLGc5yhs.js +1604 -0
- package/dist/websocket-BLGc5yhs.js.map +1 -0
- package/dist/websocket.d.ts +13 -0
- package/package.json +7 -7
- package/dist/backup-cli-DW5p9_zv.js.map +0 -1
- package/dist/backup-service-EwcDVG-8.js.map +0 -1
- package/dist/cli-errors-DsA-K9uP.js +0 -893
- package/dist/cli-errors-DsA-K9uP.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-C-sYWbmt.js.map +0 -1
- package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
- package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
- package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
- package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
- package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
- package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
- package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
- package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
- package/dist/plan-schema-DU9exq6C.js.map +0 -1
- package/dist/policy-drift-B-J2hhm0.js.map +0 -1
- package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
- package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
- package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
- package/dist/utils/sql-redaction.d.ts +0 -21
|
@@ -12,7 +12,8 @@ var column_plan_helpers_exports = /* @__PURE__ */ __exportAll({
|
|
|
12
12
|
idColumnDefault: () => idColumnDefault,
|
|
13
13
|
isIdProperty: () => isIdProperty,
|
|
14
14
|
primaryKeyPropertyNames: () => primaryKeyPropertyNames,
|
|
15
|
-
resolveColumnName: () => resolveColumnName
|
|
15
|
+
resolveColumnName: () => resolveColumnName,
|
|
16
|
+
sqlExpressionText: () => sqlExpressionText
|
|
16
17
|
});
|
|
17
18
|
/**
|
|
18
19
|
* Resolve the SQL column name for a property.
|
|
@@ -142,10 +143,15 @@ var idColumnDefault = (propName, prop, collection) => {
|
|
|
142
143
|
if (isId === "cuid") throw new Error(`Property "${propName}" of collection "${collection.slug ?? collection.name ?? "(unnamed)"}" uses \`isId: "cuid"\`, which Postgres cannot honour: the generated default calls a \`cuid()\` function that Rebase has never created, so every insert relying on it fails. Use \`isId: "uuid"\`, or give the strategy as SQL — \`\`isId: "sql\`my_id()\`"\`\` — and create that function in a migration.`);
|
|
143
144
|
return {
|
|
144
145
|
kind: "sql",
|
|
145
|
-
expression:
|
|
146
|
+
expression: sqlExpressionText(isId)
|
|
146
147
|
};
|
|
147
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* The SQL inside a template-form expression (``sql`now()` `` → `now()`); a
|
|
151
|
+
* bare expression is returned as it is.
|
|
152
|
+
*/
|
|
153
|
+
var sqlExpressionText = (value) => value.startsWith("sql`") && value.endsWith("`") && value.length >= 5 ? value.substring(4, value.length - 1) : value;
|
|
148
154
|
//#endregion
|
|
149
|
-
export { getPrimaryKeyName as a, isIdProperty as c, getPrimaryKeyColumn as i, primaryKeyPropertyNames as l, declaresEnumType as n, getPrimaryKeyProp as o, enumLabelsOf as r, idColumnDefault as s, column_plan_helpers_exports as t, resolveColumnName as u };
|
|
155
|
+
export { getPrimaryKeyName as a, isIdProperty as c, sqlExpressionText as d, getPrimaryKeyColumn as i, primaryKeyPropertyNames as l, declaresEnumType as n, getPrimaryKeyProp as o, enumLabelsOf as r, idColumnDefault as s, column_plan_helpers_exports as t, resolveColumnName as u };
|
|
150
156
|
|
|
151
|
-
//# sourceMappingURL=column-plan-helpers-
|
|
157
|
+
//# sourceMappingURL=column-plan-helpers-drZIsSNf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-plan-helpers-1-LQD0yI.js","names":[],"sources":["../src/schema/column-plan-helpers.ts"],"sourcesContent":["/**\n * The questions about a property that the schema planner asks first.\n *\n * What is the primary key, what is this column called, what does an id default\n * to, what labels does this enum have. `schema/plan/plan-schema.ts` is the only\n * caller — it is the only reader of a `Property` — and these live beside it\n * rather than inside it because they are also where the *refusals* are, and a\n * refusal wants to be reachable and testable on its own.\n *\n * They were extracted while there were still three emitters, each carrying its\n * own verbatim copy of all four rules until one of them was edited: the audit\n * that produced this module counted twelve open disagreements and found that a\n * third of the commits touching those files were re-synchronisations. The\n * emitters are renderers now and there is nothing left to keep in step, but the\n * questions are still questions about a property rather than about a plan, so\n * this is where they belong.\n *\n * Everything here is pure and synchronous, and it throws rather than guesses:\n * an enum with no values, a `cuid` id on Postgres and a foreign key into a\n * composite key are all configurations that produce a broken database in\n * silence, so they are refused where every path passes.\n */\nimport type {\n CollectionConfig,\n NumberProperty,\n Property,\n StringProperty\n} from \"@rebasepro/types\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Resolve the SQL column name for a property.\n *\n * Uses the explicit `columnName` when set (e.g. from introspection), falling\n * back to `toSnakeCase(propName)` for manually-authored collections.\n */\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n/** Every property that carries a truthy `isId`, in declaration order. */\nconst idPropertyEntries = (collection: CollectionConfig): [string, Property][] =>\n Object.entries(collection.properties ?? {}).filter(\n ([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)\n ) as [string, Property][];\n\n/**\n * The properties marked `isId`, in declaration order.\n *\n * Several of them are one composite primary key: the table carries a single\n * `PRIMARY KEY (a, b)` constraint over their columns, and a row's address lists\n * the parts in this same order (`buildCompositeId`), which is the order the\n * admin reads them in too.\n */\nexport const primaryKeyPropertyNames = (collection: CollectionConfig): string[] =>\n idPropertyEntries(collection).map(([name]) => name);\n\n/**\n * A collection's primary key, by both of its names.\n *\n * `name` is the property key — what the generated Drizzle table is keyed by,\n * so it is what a relation's `references` names. `column` is the SQL\n * identifier, so it is what a foreign key's `REFERENCES` names. They differ\n * whenever the key is camelCase or carries a `columnName` (`authorId` is\n * `author_id`, and every table `rebase schema introspect` adopts is spelled\n * that way), and a constraint written with the key named a column that does\n * not exist: `db push` could not build its desired state, and boot failed the\n * constraint on every start.\n */\nexport interface PrimaryKey {\n /** Property key: the Drizzle object key and the JSON field. */\n name: string;\n /** SQL column. */\n column: string;\n /** The declaring property; absent for the implicit `id TEXT` of a collection that declares none. */\n prop?: Property;\n}\n\n/**\n * The one column a link into this collection points at.\n *\n * A foreign key column references a single column, and Postgres only accepts\n * that column when it is unique on its own — `REFERENCES t (a)` against a\n * composite key `(a, b)` fails with \"there is no unique constraint matching\n * given keys for referenced table\". So a link into a collection whose key\n * spans several columns has no column to point at, and it is refused here,\n * naming the link, rather than emitted as a constraint `db push` or boot would\n * then fail to create.\n *\n * @param via the link that needs the column, for the refusal: `relation \"posts.author\"`.\n */\nexport const getPrimaryKeyProp = (collection: CollectionConfig, via: string): PrimaryKey => {\n const ids = idPropertyEntries(collection);\n if (ids.length > 1) {\n throw new Error(\n `${via} points at collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\", whose primary ` +\n `key spans ${ids.length} columns (${ids.map(([name]) => `\"${name}\"`).join(\", \")}). A foreign key is ` +\n \"one column and cannot reference a composite key. Point the link at a collection with a \" +\n \"single-column key, or reach these rows with a `via` relation, whose `joinPath` compares every \" +\n \"key column.\"\n );\n }\n const idPropEntry = ids[0];\n if (idPropEntry) {\n const [name, prop] = idPropEntry;\n return { name, column: resolveColumnName(name, prop), prop };\n }\n // Fallback: a collection that declares no `isId` gets an implicit `id`.\n const idProp = collection.properties?.[\"id\"] as Property | undefined;\n return { name: \"id\", column: resolveColumnName(\"id\", idProp), prop: idProp };\n};\n\n/**\n * The primary key's property key. What a Drizzle relation's `references`\n * names. Refused on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyName = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).name;\n\n/**\n * The primary key's column. What a foreign key's `REFERENCES` names. Refused\n * on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyColumn = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).column;\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // Only fall back to \"id\" when NO property is explicitly marked.\n return idPropertyEntries(collection).length === 0 && propName === \"id\";\n};\n\n/**\n * The labels an `enum` declares, in order, whichever form it was written in.\n *\n * `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend\n * the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`\n * — so a record-form enum threw `p.enum.map is not a function` on a managed\n * tenant, at boot, with no developer in the loop. The other two emitters each\n * carried their own copy of the both-forms walk.\n *\n * An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`\n * is not valid SQL, so every emitter \"handled\" it by skipping the type and then\n * typing the column with it anyway: Drizzle referenced an enum variable it never\n * declared (the generated file does not compile), the DDL named a type nothing\n * creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a\n * survivable action, so the boot died. There is no useful column at the end of\n * any of those paths.\n */\nexport const enumLabelsOf = (propName: string, prop: Property, collection: CollectionConfig): string[] => {\n const values = (prop as { enum?: unknown }).enum;\n if (values === undefined || values === null) return [];\n\n const labels = Array.isArray(values)\n ? values.map((entry: unknown) =>\n String(entry !== null && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? (entry as Record<string, unknown>).id\n : entry))\n : Object.keys(values as Record<string, unknown>).map(String);\n\n const usable = labels.filter(label => label.length > 0);\n if (usable.length === 0) {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"declares an empty `enum`. A Postgres enum type needs at least one label — \" +\n \"`CREATE TYPE … AS ENUM ()` is not valid SQL — so the column would reference a type \" +\n \"nothing creates. List the values, or drop the `enum` for a plain column.\"\n );\n }\n return usable;\n};\n\n/**\n * Does this property compile to a Postgres enum *type*?\n *\n * Strings only. A `number` property with an `enum` used to create a type on all\n * three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was\n * orphaned the moment it was created and the values it listed were enforced by\n * nothing. The dropdown in the panel is driven by the config, not the column, so\n * nothing is lost by not creating it — and a type no column uses is one more\n * object `db push` has to plan a DROP for.\n */\nexport const declaresEnumType = (prop: Property): boolean =>\n prop.type === \"string\" && Boolean((prop as StringProperty).enum);\n\n/**\n * The `DEFAULT` an id column carries, as the thing itself rather than a\n * rendered string — each emitter spells it differently.\n *\n * The three of them disagreed on all four strategies. The DDL generator wrote\n * `DEFAULT ${prop.isId}` verbatim, so the documented `` isId: \"sql`gen_id()`\" ``\n * reached Postgres with the template wrapper still on it — a syntax error in\n * `schema.sql`. Boot-ensure gave a default to `uuid` and to nothing else, so on\n * a managed tenant every other strategy produced a column with no default at\n * all, and the first insert that trusted the config failed on a NULL primary\n * key. Only the Drizzle generator stripped the wrapper.\n */\nexport type IdColumnDefault =\n /** `gen_random_uuid()` / `.defaultRandom()`. */\n | { kind: \"uuid\" }\n /** `GENERATED BY DEFAULT AS IDENTITY` / `.generatedByDefaultAsIdentity()` — part of the type, not a DEFAULT. */\n | { kind: \"identity\" }\n /** A raw SQL expression, wrapper already stripped. */\n | { kind: \"sql\"; expression: string };\n\n/**\n * Refuses `isId: \"cuid\"`.\n *\n * `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`\n * function has ever been created — not by the generators, not by boot, not by\n * any migration. So the column has never had a working default on Postgres: the\n * first insert that relied on it failed with `function cuid() does not exist`.\n * There is nothing to keep compatible.\n *\n * It is refused rather than implemented. Shipping a real `rebase.cuid()` means a\n * new SQL function in the same carved-out class as the search helpers — excluded\n * from Atlas by pattern, applied by boot, re-applied by push, checked by the\n * doctor, and versioned across the fleet forever — to reproduce a monotonic id\n * that `uuid` already provides and that a project wanting cuid's exact shape can\n * write itself in one line of SQL.\n */\nexport const idColumnDefault = (propName: string, prop: Property, collection: CollectionConfig): IdColumnDefault | undefined => {\n if (!(\"isId\" in prop)) return undefined;\n const isId = (prop as { isId?: unknown }).isId;\n // `true` and `\"manual\"` both mean \"the application supplies it\".\n if (isId === undefined || isId === null || isId === true || isId === \"manual\" || isId === false) return undefined;\n if (typeof isId !== \"string\") return undefined;\n\n if (prop.type === \"string\" && (isId as StringProperty[\"isId\"]) === \"uuid\") return { kind: \"uuid\" };\n if (prop.type === \"number\" && (isId as NumberProperty[\"isId\"]) === \"increment\") return { kind: \"identity\" };\n\n if (isId === \"cuid\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"uses `isId: \\\"cuid\\\"`, which Postgres cannot honour: the generated default calls a `cuid()` \" +\n \"function that Rebase has never created, so every insert relying on it fails. \" +\n \"Use `isId: \\\"uuid\\\"`, or give the strategy as SQL — ``isId: \\\"sql`my_id()`\\\"`` — and create that \" +\n \"function in a migration.\"\n );\n }\n\n // Any other string is a raw SQL default. Written either bare\n // (`gen_random_uuid()::text`) or in the documented template form\n // (``sql`gen_random_uuid()::text` ``); the wrapper is markup, not SQL.\n const expression = isId.startsWith(\"sql`\") && isId.endsWith(\"`\")\n ? isId.substring(4, isId.length - 1)\n : isId;\n return { kind: \"sql\", expression };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAa,qBAAqB,UAAkB,SAAmC;CACnF,IAAI,QAAQ,gBAAgB,QAAQ,OAAO,KAAK,eAAe,UAC3D,OAAO,KAAK;CAEhB,OAAO,YAAY,QAAQ;AAC/B;;AAGA,IAAM,qBAAqB,eACvB,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,CAAC,CAAC,QACvC,GAAG,UAAU,QAAQ,OAAO,SAAS,YAAY,UAAU,QAAQ,QAAS,KAA4B,IAAI,CACjH;;;;;;;;;AAUJ,IAAa,2BAA2B,eACpC,kBAAkB,UAAU,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI;;;;;;;;;;;;;;AAoCtD,IAAa,qBAAqB,YAA8B,QAA4B;CACxF,MAAM,MAAM,kBAAkB,UAAU;CACxC,IAAI,IAAI,SAAS,GACb,MAAM,IAAI,MACN,GAAG,IAAI,yBAAyB,WAAW,QAAQ,WAAW,QAAQ,YAAY,6BACrE,IAAI,OAAO,YAAY,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,yNAIpF;CAEJ,MAAM,cAAc,IAAI;CACxB,IAAI,aAAa;EACb,MAAM,CAAC,MAAM,QAAQ;EACrB,OAAO;GAAE;GAAM,QAAQ,kBAAkB,MAAM,IAAI;GAAG;EAAK;CAC/D;CAEA,MAAM,SAAS,WAAW,aAAa;CACvC,OAAO;EAAE,MAAM;EAAM,QAAQ,kBAAkB,MAAM,MAAM;EAAG,MAAM;CAAO;AAC/E;;;;;AAMA,IAAa,qBAAqB,YAA8B,QAC5D,kBAAkB,YAAY,GAAG,CAAC,CAAC;;;;;AAMvC,IAAa,uBAAuB,YAA8B,QAC9D,kBAAkB,YAAY,GAAG,CAAC,CAAC;AAEvC,IAAa,gBAAgB,UAAkB,MAAgB,eAA0C;CACrG,IAAI,UAAU,QAAQ,QAAQ,KAAK,IAAI,GAAG,OAAO;CAGjD,OAAO,kBAAkB,UAAU,CAAC,CAAC,WAAW,KAAK,aAAa;AACtE;;;;;;;;;;;;;;;;;;AAmBA,IAAa,gBAAgB,UAAkB,MAAgB,eAA2C;CACtG,MAAM,SAAU,KAA4B;CAC5C,IAAI,WAAW,KAAA,KAAa,WAAW,MAAM,OAAO,CAAC;CASrD,MAAM,UAPS,MAAM,QAAQ,MAAM,IAC7B,OAAO,KAAK,UACV,OAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,QAAS,QACxD,MAAkC,KACnC,KAAK,CAAC,IACd,OAAO,KAAK,MAAiC,CAAC,CAAC,IAAI,MAAM,EAAA,CAEzC,QAAO,UAAS,MAAM,SAAS,CAAC;CACtD,IAAI,OAAO,WAAW,GAClB,MAAM,IAAI,MACN,aAAa,SAAS,mBAAmB,WAAW,QAAQ,WAAW,QAAQ,YAAY,8OAI/F;CAEJ,OAAO;AACX;;;;;;;;;;;AAYA,IAAa,oBAAoB,SAC7B,KAAK,SAAS,YAAY,QAAS,KAAwB,IAAI;;;;;;;;;;;;;;;;;AAsCnE,IAAa,mBAAmB,UAAkB,MAAgB,eAA8D;CAC5H,IAAI,EAAE,UAAU,OAAO,OAAO,KAAA;CAC9B,MAAM,OAAQ,KAA4B;CAE1C,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,SAAS,QAAQ,SAAS,YAAY,SAAS,OAAO,OAAO,KAAA;CACxG,IAAI,OAAO,SAAS,UAAU,OAAO,KAAA;CAErC,IAAI,KAAK,SAAS,YAAa,SAAoC,QAAQ,OAAO,EAAE,MAAM,OAAO;CACjG,IAAI,KAAK,SAAS,YAAa,SAAoC,aAAa,OAAO,EAAE,MAAM,WAAW;CAE1G,IAAI,SAAS,QACT,MAAM,IAAI,MACN,aAAa,SAAS,mBAAmB,WAAW,QAAQ,WAAW,QAAQ,YAAY,2SAK/F;CASJ,OAAO;EAAE,MAAM;EAAO,YAHH,KAAK,WAAW,MAAM,KAAK,KAAK,SAAS,GAAG,IACzD,KAAK,UAAU,GAAG,KAAK,SAAS,CAAC,IACjC;CAC2B;AACrC"}
|
|
1
|
+
{"version":3,"file":"column-plan-helpers-drZIsSNf.js","names":[],"sources":["../src/schema/column-plan-helpers.ts"],"sourcesContent":["/**\n * The questions about a property that the schema planner asks first.\n *\n * What is the primary key, what is this column called, what does an id default\n * to, what labels does this enum have. `schema/plan/plan-schema.ts` is the only\n * caller — it is the only reader of a `Property` — and these live beside it\n * rather than inside it because they are also where the *refusals* are, and a\n * refusal wants to be reachable and testable on its own.\n *\n * They were extracted while there were still three emitters, each carrying its\n * own verbatim copy of all four rules until one of them was edited: the audit\n * that produced this module counted twelve open disagreements and found that a\n * third of the commits touching those files were re-synchronisations. The\n * emitters are renderers now and there is nothing left to keep in step, but the\n * questions are still questions about a property rather than about a plan, so\n * this is where they belong.\n *\n * Everything here is pure and synchronous, and it throws rather than guesses:\n * an enum with no values, a `cuid` id on Postgres and a foreign key into a\n * composite key are all configurations that produce a broken database in\n * silence, so they are refused where every path passes.\n */\nimport type {\n CollectionConfig,\n NumberProperty,\n Property,\n StringProperty\n} from \"@rebasepro/types\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Resolve the SQL column name for a property.\n *\n * Uses the explicit `columnName` when set (e.g. from introspection), falling\n * back to `toSnakeCase(propName)` for manually-authored collections.\n */\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n/** Every property that carries a truthy `isId`, in declaration order. */\nconst idPropertyEntries = (collection: CollectionConfig): [string, Property][] =>\n Object.entries(collection.properties ?? {}).filter(\n ([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)\n ) as [string, Property][];\n\n/**\n * The properties marked `isId`, in declaration order.\n *\n * Several of them are one composite primary key: the table carries a single\n * `PRIMARY KEY (a, b)` constraint over their columns, and a row's address lists\n * the parts in this same order (`buildCompositeId`), which is the order the\n * admin reads them in too.\n */\nexport const primaryKeyPropertyNames = (collection: CollectionConfig): string[] =>\n idPropertyEntries(collection).map(([name]) => name);\n\n/**\n * A collection's primary key, by both of its names.\n *\n * `name` is the property key — what the generated Drizzle table is keyed by,\n * so it is what a relation's `references` names. `column` is the SQL\n * identifier, so it is what a foreign key's `REFERENCES` names. They differ\n * whenever the key is camelCase or carries a `columnName` (`authorId` is\n * `author_id`, and every table `rebase schema introspect` adopts is spelled\n * that way), and a constraint written with the key named a column that does\n * not exist: `db push` could not build its desired state, and boot failed the\n * constraint on every start.\n */\nexport interface PrimaryKey {\n /** Property key: the Drizzle object key and the JSON field. */\n name: string;\n /** SQL column. */\n column: string;\n /** The declaring property; absent for the implicit `id TEXT` of a collection that declares none. */\n prop?: Property;\n}\n\n/**\n * The one column a link into this collection points at.\n *\n * A foreign key column references a single column, and Postgres only accepts\n * that column when it is unique on its own — `REFERENCES t (a)` against a\n * composite key `(a, b)` fails with \"there is no unique constraint matching\n * given keys for referenced table\". So a link into a collection whose key\n * spans several columns has no column to point at, and it is refused here,\n * naming the link, rather than emitted as a constraint `db push` or boot would\n * then fail to create.\n *\n * @param via the link that needs the column, for the refusal: `relation \"posts.author\"`.\n */\nexport const getPrimaryKeyProp = (collection: CollectionConfig, via: string): PrimaryKey => {\n const ids = idPropertyEntries(collection);\n if (ids.length > 1) {\n throw new Error(\n `${via} points at collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\", whose primary ` +\n `key spans ${ids.length} columns (${ids.map(([name]) => `\"${name}\"`).join(\", \")}). A foreign key is ` +\n \"one column and cannot reference a composite key. Point the link at a collection with a \" +\n \"single-column key, or reach these rows with a `via` relation, whose `joinPath` compares every \" +\n \"key column.\"\n );\n }\n const idPropEntry = ids[0];\n if (idPropEntry) {\n const [name, prop] = idPropEntry;\n return { name, column: resolveColumnName(name, prop), prop };\n }\n // Fallback: a collection that declares no `isId` gets an implicit `id`.\n const idProp = collection.properties?.[\"id\"] as Property | undefined;\n return { name: \"id\", column: resolveColumnName(\"id\", idProp), prop: idProp };\n};\n\n/**\n * The primary key's property key. What a Drizzle relation's `references`\n * names. Refused on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyName = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).name;\n\n/**\n * The primary key's column. What a foreign key's `REFERENCES` names. Refused\n * on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyColumn = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).column;\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // Only fall back to \"id\" when NO property is explicitly marked.\n return idPropertyEntries(collection).length === 0 && propName === \"id\";\n};\n\n/**\n * The labels an `enum` declares, in order, whichever form it was written in.\n *\n * `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend\n * the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`\n * — so a record-form enum threw `p.enum.map is not a function` on a managed\n * tenant, at boot, with no developer in the loop. The other two emitters each\n * carried their own copy of the both-forms walk.\n *\n * An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`\n * is not valid SQL, so every emitter \"handled\" it by skipping the type and then\n * typing the column with it anyway: Drizzle referenced an enum variable it never\n * declared (the generated file does not compile), the DDL named a type nothing\n * creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a\n * survivable action, so the boot died. There is no useful column at the end of\n * any of those paths.\n */\nexport const enumLabelsOf = (propName: string, prop: Property, collection: CollectionConfig): string[] => {\n const values = (prop as { enum?: unknown }).enum;\n if (values === undefined || values === null) return [];\n\n const labels = Array.isArray(values)\n ? values.map((entry: unknown) =>\n String(entry !== null && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? (entry as Record<string, unknown>).id\n : entry))\n : Object.keys(values as Record<string, unknown>).map(String);\n\n const usable = labels.filter(label => label.length > 0);\n if (usable.length === 0) {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"declares an empty `enum`. A Postgres enum type needs at least one label — \" +\n \"`CREATE TYPE … AS ENUM ()` is not valid SQL — so the column would reference a type \" +\n \"nothing creates. List the values, or drop the `enum` for a plain column.\"\n );\n }\n return usable;\n};\n\n/**\n * Does this property compile to a Postgres enum *type*?\n *\n * Strings only. A `number` property with an `enum` used to create a type on all\n * three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was\n * orphaned the moment it was created and the values it listed were enforced by\n * nothing. The dropdown in the panel is driven by the config, not the column, so\n * nothing is lost by not creating it — and a type no column uses is one more\n * object `db push` has to plan a DROP for.\n */\nexport const declaresEnumType = (prop: Property): boolean =>\n prop.type === \"string\" && Boolean((prop as StringProperty).enum);\n\n/**\n * The `DEFAULT` an id column carries, as the thing itself rather than a\n * rendered string — each emitter spells it differently.\n *\n * The three of them disagreed on all four strategies. The DDL generator wrote\n * `DEFAULT ${prop.isId}` verbatim, so the documented `` isId: \"sql`gen_id()`\" ``\n * reached Postgres with the template wrapper still on it — a syntax error in\n * `schema.sql`. Boot-ensure gave a default to `uuid` and to nothing else, so on\n * a managed tenant every other strategy produced a column with no default at\n * all, and the first insert that trusted the config failed on a NULL primary\n * key. Only the Drizzle generator stripped the wrapper.\n */\nexport type IdColumnDefault =\n /** `gen_random_uuid()` / `.defaultRandom()`. */\n | { kind: \"uuid\" }\n /** `GENERATED BY DEFAULT AS IDENTITY` / `.generatedByDefaultAsIdentity()` — part of the type, not a DEFAULT. */\n | { kind: \"identity\" }\n /** A raw SQL expression, wrapper already stripped. */\n | { kind: \"sql\"; expression: string };\n\n/**\n * Refuses `isId: \"cuid\"`.\n *\n * `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`\n * function has ever been created — not by the generators, not by boot, not by\n * any migration. So the column has never had a working default on Postgres: the\n * first insert that relied on it failed with `function cuid() does not exist`.\n * There is nothing to keep compatible.\n *\n * It is refused rather than implemented. Shipping a real `rebase.cuid()` means a\n * new SQL function in the same carved-out class as the search helpers — excluded\n * from Atlas by pattern, applied by boot, re-applied by push, checked by the\n * doctor, and versioned across the fleet forever — to reproduce a monotonic id\n * that `uuid` already provides and that a project wanting cuid's exact shape can\n * write itself in one line of SQL.\n */\nexport const idColumnDefault = (propName: string, prop: Property, collection: CollectionConfig): IdColumnDefault | undefined => {\n if (!(\"isId\" in prop)) return undefined;\n const isId = (prop as { isId?: unknown }).isId;\n // `true` and `\"manual\"` both mean \"the application supplies it\".\n if (isId === undefined || isId === null || isId === true || isId === \"manual\" || isId === false) return undefined;\n if (typeof isId !== \"string\") return undefined;\n\n if (prop.type === \"string\" && (isId as StringProperty[\"isId\"]) === \"uuid\") return { kind: \"uuid\" };\n if (prop.type === \"number\" && (isId as NumberProperty[\"isId\"]) === \"increment\") return { kind: \"identity\" };\n\n if (isId === \"cuid\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"uses `isId: \\\"cuid\\\"`, which Postgres cannot honour: the generated default calls a `cuid()` \" +\n \"function that Rebase has never created, so every insert relying on it fails. \" +\n \"Use `isId: \\\"uuid\\\"`, or give the strategy as SQL — ``isId: \\\"sql`my_id()`\\\"`` — and create that \" +\n \"function in a migration.\"\n );\n }\n\n // Any other string is a raw SQL default. Written either bare\n // (`gen_random_uuid()::text`) or in the documented template form\n // (``sql`gen_random_uuid()::text` ``); the wrapper is markup, not SQL.\n return { kind: \"sql\", expression: sqlExpressionText(isId) };\n};\n\n/**\n * The SQL inside a template-form expression (``sql`now()` `` → `now()`); a\n * bare expression is returned as it is.\n */\nexport const sqlExpressionText = (value: string): string =>\n value.startsWith(\"sql`\") && value.endsWith(\"`\") && value.length >= 5\n ? value.substring(4, value.length - 1)\n : value;\n"],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ var connection_exports = /* @__PURE__ */ __exportAll({
|
|
|
10
10
|
createDirectDatabaseConnection: () => createDirectDatabaseConnection,
|
|
11
11
|
createPostgresDatabaseConnection: () => createPostgresDatabaseConnection,
|
|
12
12
|
createReadReplicaConnection: () => createReadReplicaConnection,
|
|
13
|
+
defaultPoolTimeouts: () => defaultPoolTimeouts,
|
|
13
14
|
guardPoolAgainstDirtyRelease: () => guardPoolAgainstDirtyRelease,
|
|
14
15
|
pinSearchPath: () => pinSearchPath,
|
|
15
16
|
poolMaxCeiling: () => poolMaxCeiling
|
|
@@ -48,6 +49,22 @@ var DEFAULT_POOL = {
|
|
|
48
49
|
statementTimeout: 3e4,
|
|
49
50
|
keepAlive: true
|
|
50
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* The time limits every pool to this server shares: how long to wait for a
|
|
54
|
+
* connection (then 503), the server-side statement timeout, the client-side
|
|
55
|
+
* read backstop above it, and TCP keep-alive. Exported for the per-database
|
|
56
|
+
* pools (`DatabasePoolManager`), which had none of them — pg's default
|
|
57
|
+
* `connectionTimeoutMillis` is 0, wait forever.
|
|
58
|
+
*/
|
|
59
|
+
function defaultPoolTimeouts() {
|
|
60
|
+
return {
|
|
61
|
+
connectionTimeoutMillis: DEFAULT_POOL.connectionTimeoutMillis,
|
|
62
|
+
query_timeout: DEFAULT_POOL.queryTimeout,
|
|
63
|
+
statement_timeout: DEFAULT_POOL.statementTimeout,
|
|
64
|
+
keepAlive: DEFAULT_POOL.keepAlive,
|
|
65
|
+
keepAliveInitialDelayMillis: 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
51
68
|
/** ReadyForQuery status byte: `I` idle, `T` in transaction, `E` failed transaction. */
|
|
52
69
|
var TX_IDLE = "I";
|
|
53
70
|
/**
|
|
@@ -138,12 +155,15 @@ function guardPoolAgainstDirtyRelease(pool, label) {
|
|
|
138
155
|
* @param connectionString Postgres connection URL
|
|
139
156
|
* @param schema Optional Drizzle schema for the relational API
|
|
140
157
|
* @param poolConfig Optional pool tuning (merged over defaults)
|
|
158
|
+
* @param env Where the pool-size ceiling `REBASE_DB_POOL_MAX`
|
|
159
|
+
* is read from (see {@link poolMaxCeiling}); pass
|
|
160
|
+
* `{}` to read none.
|
|
141
161
|
*
|
|
142
162
|
* @returns `{ db, pool, connectionString }` — the `pool` is exposed so
|
|
143
163
|
* callers can register shutdown hooks (`pool.end()`) or monitor
|
|
144
164
|
* pool metrics.
|
|
145
165
|
*/
|
|
146
|
-
function createPostgresDatabaseConnection(connectionString, schema, poolConfig) {
|
|
166
|
+
function createPostgresDatabaseConnection(connectionString, schema, poolConfig, env = process.env) {
|
|
147
167
|
const opts = {
|
|
148
168
|
...DEFAULT_POOL,
|
|
149
169
|
...poolConfig
|
|
@@ -151,7 +171,7 @@ function createPostgresDatabaseConnection(connectionString, schema, poolConfig)
|
|
|
151
171
|
connectionString = pinSearchPath(connectionString, opts.searchPath);
|
|
152
172
|
const pool = new Pool({
|
|
153
173
|
connectionString,
|
|
154
|
-
max: cappedPoolMax(opts.max),
|
|
174
|
+
max: cappedPoolMax(opts.max, env),
|
|
155
175
|
idleTimeoutMillis: opts.idleTimeoutMillis,
|
|
156
176
|
connectionTimeoutMillis: opts.connectionTimeoutMillis,
|
|
157
177
|
query_timeout: opts.queryTimeout,
|
|
@@ -236,114 +256,6 @@ function createReadReplicaConnection(connectionString, schema, poolConfig) {
|
|
|
236
256
|
};
|
|
237
257
|
}
|
|
238
258
|
//#endregion
|
|
239
|
-
|
|
240
|
-
var databasePoolManager_exports = /* @__PURE__ */ __exportAll({ DatabasePoolManager: () => DatabasePoolManager });
|
|
241
|
-
var DatabasePoolManager = class {
|
|
242
|
-
pools = /* @__PURE__ */ new Map();
|
|
243
|
-
drizzleInstances = /* @__PURE__ */ new Map();
|
|
244
|
-
defaultDatabaseName;
|
|
245
|
-
rootConnectionString;
|
|
246
|
-
constructor(adminConnectionString) {
|
|
247
|
-
this.rootConnectionString = adminConnectionString;
|
|
248
|
-
try {
|
|
249
|
-
const url = new URL(adminConnectionString);
|
|
250
|
-
this.defaultDatabaseName = url.pathname.slice(1);
|
|
251
|
-
} catch (e) {
|
|
252
|
-
throw new Error(`Invalid adminConnectionString provided: ${e}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
getDrizzle(databaseName) {
|
|
256
|
-
const existing = this.drizzleInstances.get(databaseName);
|
|
257
|
-
if (existing) return existing;
|
|
258
|
-
const db = drizzle(this.getPool(databaseName));
|
|
259
|
-
this.drizzleInstances.set(databaseName, db);
|
|
260
|
-
return db;
|
|
261
|
-
}
|
|
262
|
-
getPool(databaseName) {
|
|
263
|
-
if (this.pools.has(databaseName)) return this.pools.get(databaseName);
|
|
264
|
-
const url = new URL(this.rootConnectionString);
|
|
265
|
-
url.pathname = `/${databaseName}`;
|
|
266
|
-
const pool = new Pool({
|
|
267
|
-
connectionString: pinSearchPath(url.toString()),
|
|
268
|
-
max: cappedPoolMax(10),
|
|
269
|
-
idleTimeoutMillis: 1e4,
|
|
270
|
-
allowExitOnIdle: true
|
|
271
|
-
});
|
|
272
|
-
pool.on("error", (err) => {
|
|
273
|
-
logger.error(`[DatabasePoolManager] Unexpected error on idle client for db ${databaseName}`, { error: err });
|
|
274
|
-
});
|
|
275
|
-
guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);
|
|
276
|
-
this.pools.set(databaseName, pool);
|
|
277
|
-
return pool;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Disconnect and remove the pool for a specific database.
|
|
281
|
-
* Required before `CREATE DATABASE ... TEMPLATE` or `DROP DATABASE`,
|
|
282
|
-
* which need exclusive access to the target database.
|
|
283
|
-
*/
|
|
284
|
-
async disconnectDatabase(databaseName) {
|
|
285
|
-
const pool = this.pools.get(databaseName);
|
|
286
|
-
if (pool) {
|
|
287
|
-
await pool.end();
|
|
288
|
-
this.pools.delete(databaseName);
|
|
289
|
-
this.drizzleInstances.delete(databaseName);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
/** Check if a pool exists for a given database name. */
|
|
293
|
-
hasPool(databaseName) {
|
|
294
|
-
return this.pools.has(databaseName);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Does this server serve exactly one database?
|
|
298
|
-
*
|
|
299
|
-
* The managed development database is PGlite, and PGlite serves exactly
|
|
300
|
-
* one. `CREATE DATABASE "rb_feature" TEMPLATE "postgres"` there writes a
|
|
301
|
-
* `pg_database` catalogue row and nothing else: nothing errors, the row is
|
|
302
|
-
* listable, `pg_database_size` answers — and connecting to the "branch"
|
|
303
|
-
* lands you in the parent. Every write made in the belief that it is
|
|
304
|
-
* sandboxed goes to the developer's real development database.
|
|
305
|
-
*
|
|
306
|
-
* The CLI already refuses `rebase db branch` on this ground, but it does it
|
|
307
|
-
* by parsing argv, so Studio — which reaches `BranchService` over the
|
|
308
|
-
* websocket — walked straight past it. The connection is the only thing
|
|
309
|
-
* both callers share, so the answer belongs here.
|
|
310
|
-
*
|
|
311
|
-
* Measured, not assumed: `select version()` over the PGlite socket returns
|
|
312
|
-
* `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`, which is how a wasm build
|
|
313
|
-
* announces itself and what `dev-db/constraints.ts` documents. A real
|
|
314
|
-
* Postgres never carries `(PGlite `.
|
|
315
|
-
*
|
|
316
|
-
* Cached, because a live connection cannot change engine underneath us, and
|
|
317
|
-
* a `PostgresBackendDriver` — which builds a `BranchService` — is
|
|
318
|
-
* constructed per transaction.
|
|
319
|
-
*/
|
|
320
|
-
servesOneDatabase() {
|
|
321
|
-
this.oneDatabaseProbe ??= this.probeOneDatabase();
|
|
322
|
-
return this.oneDatabaseProbe;
|
|
323
|
-
}
|
|
324
|
-
oneDatabaseProbe;
|
|
325
|
-
async probeOneDatabase() {
|
|
326
|
-
try {
|
|
327
|
-
const result = await this.getPool(this.defaultDatabaseName).query("SELECT version() AS version");
|
|
328
|
-
return /\(PGlite\b/i.test(result.rows[0]?.version ?? "");
|
|
329
|
-
} catch (err) {
|
|
330
|
-
this.oneDatabaseProbe = void 0;
|
|
331
|
-
logger.warn("[DatabasePoolManager] Could not read the server version", { error: err });
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
async shutdown() {
|
|
336
|
-
const promises = [];
|
|
337
|
-
for (const [dbName, pool] of this.pools.entries()) {
|
|
338
|
-
logger.info(`[DatabasePoolManager] Shutting down pool for ${dbName}`);
|
|
339
|
-
promises.push(pool.end());
|
|
340
|
-
}
|
|
341
|
-
await Promise.all(promises);
|
|
342
|
-
this.pools.clear();
|
|
343
|
-
this.drizzleInstances.clear();
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
//#endregion
|
|
347
|
-
export { createDirectDatabaseConnection as a, guardPoolAgainstDirtyRelease as c, connection_exports as i, pinSearchPath as l, databasePoolManager_exports as n, createPostgresDatabaseConnection as o, cappedPoolMax as r, createReadReplicaConnection as s, DatabasePoolManager as t, poolMaxCeiling as u };
|
|
259
|
+
export { createReadReplicaConnection as a, pinSearchPath as c, createPostgresDatabaseConnection as i, poolMaxCeiling as l, connection_exports as n, defaultPoolTimeouts as o, createDirectDatabaseConnection as r, guardPoolAgainstDirtyRelease as s, cappedPoolMax as t };
|
|
348
260
|
|
|
349
|
-
//# sourceMappingURL=
|
|
261
|
+
//# sourceMappingURL=connection-C9mT8bgf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-C9mT8bgf.js","names":[],"sources":["../src/connection.ts"],"sourcesContent":["import { Pool, PoolConfig } from \"pg\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Configuration for the Postgres connection pool.\n *\n * Sensible defaults are provided for production Cloud Run / single-instance\n * deployments. Override via environment variables or explicit config.\n */\nexport interface PostgresPoolConfig {\n /** Maximum number of connections in the pool (default: 20) */\n max?: number;\n /** Close idle connections after this many ms (default: 30 000) */\n idleTimeoutMillis?: number;\n /** Abort connection attempts after this many ms (default: 10 000) */\n connectionTimeoutMillis?: number;\n /** Per-query timeout in ms (default: 30 000) */\n queryTimeout?: number;\n /** Per-statement timeout in ms (default: 30 000) */\n statementTimeout?: number;\n /** Enable TCP keep-alive (default: true) */\n keepAlive?: boolean;\n /**\n * `search_path` pinned on every connection (default: `\"public\"`).\n *\n * Pass `false` to send no `search_path` at all and inherit whatever the\n * server/role defaults to. See {@link pinSearchPath} for why the default\n * is not \"inherit\".\n */\n searchPath?: string | false;\n}\n\n/**\n * Ceiling on pool size, from the environment.\n *\n * Exists for the managed development database. PGlite is a single session\n * behind a multiplexing socket server, and two pooled clients holding\n * overlapping transactions deadlock there — which a request-per-transaction\n * server produces the moment two requests overlap. One connection turns that\n * deadlock into ordinary queueing.\n *\n * A ceiling rather than a value, so it constrains every pool in the process\n * without any of them having to know why. Ignored when unset or unparseable,\n * because a malformed limit must not silently serialize a production server.\n */\nexport function poolMaxCeiling(env: NodeJS.ProcessEnv = process.env): number | null {\n const raw = env.REBASE_DB_POOL_MAX;\n if (typeof raw !== \"string\") return null;\n\n // Plain decimal digits only. `Number` would also accept `1e3`, `0x10` and\n // ` 4 ` — none of which anyone writes on purpose in a config value, and all\n // of which would mean this silently did something other than it appears to.\n if (!/^[0-9]+$/.test(raw.trim())) return null;\n\n const parsed = Number(raw.trim());\n\n return parsed >= 1 ? parsed : null;\n}\n\n/** Apply {@link poolMaxCeiling} to a requested pool size. */\nexport function cappedPoolMax(requested: number, env: NodeJS.ProcessEnv = process.env): number {\n const ceiling = poolMaxCeiling(env);\n\n return ceiling === null ? requested : Math.min(requested, ceiling);\n}\n\nconst DEFAULT_POOL: Required<PostgresPoolConfig> = {\n max: 20,\n searchPath: \"public\",\n idleTimeoutMillis: 30_000,\n connectionTimeoutMillis: 10_000,\n // The client-side read timeout MUST be comfortably above the server-side\n // statement_timeout. When the client timer fires first, node-postgres\n // abandons the in-flight statement but keeps the connection — inside a\n // transaction that leaves the tx open (and any pending ROLLBACK is\n // spliced out of the client queue before it ever reaches the wire), so\n // the pooled connection is returned still in-transaction with its RLS\n // GUCs set. The server abort (SQLSTATE 57014) is the clean path; the\n // client timeout is only a backstop for a dead network.\n queryTimeout: 60_000,\n statementTimeout: 30_000,\n keepAlive: true\n};\n\n/**\n * The time limits every pool to this server shares: how long to wait for a\n * connection (then 503), the server-side statement timeout, the client-side\n * read backstop above it, and TCP keep-alive. Exported for the per-database\n * pools (`DatabasePoolManager`), which had none of them — pg's default\n * `connectionTimeoutMillis` is 0, wait forever.\n */\nexport function defaultPoolTimeouts(): Pick<PoolConfig,\n \"connectionTimeoutMillis\" | \"query_timeout\" | \"statement_timeout\" | \"keepAlive\" | \"keepAliveInitialDelayMillis\"> {\n return {\n connectionTimeoutMillis: DEFAULT_POOL.connectionTimeoutMillis,\n query_timeout: DEFAULT_POOL.queryTimeout,\n statement_timeout: DEFAULT_POOL.statementTimeout,\n keepAlive: DEFAULT_POOL.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n}\n\n/** ReadyForQuery status byte: `I` idle, `T` in transaction, `E` failed transaction. */\nconst TX_IDLE = \"I\";\n\n/**\n * Pin `search_path` into a connection string, so unqualified SQL resolves to a\n * schema this framework chose rather than to one Postgres inferred.\n *\n * Postgres defaults `search_path` to `\"$user\", public`: the *first* candidate\n * is a schema named after the connecting role. Rebase creates a schema called\n * `rebase` (auth, history, api keys), and every template, compose file and\n * deployment doc names the database role `rebase` too — so `$user` resolves to\n * a schema that exists, and every unqualified statement lands there instead of\n * in `public`. The generated Drizzle schema emits bare `pgTable(\"posts\", …)`\n * for any collection without an explicit `schema`, which makes the *runtime's*\n * own reads and writes unqualified; a developer's raw `rebase.sql(...)`, the\n * Studio SQL editor and any hand-written migration are unqualified too. The\n * result is collection tables created in, and served from, `rebase`.\n *\n * Drizzle cannot express the fix on its side: `pgSchema(\"public\")` throws by\n * design (\"just use pgTable() instead\"), so there is no way to emit a\n * public-qualified table from the generator. The pin has to live on the\n * connection.\n *\n * Precedence is deliberate and verified against node-postgres: `options` in\n * the connection string wins over the `options` field passed to `Pool`, so\n * rewriting the URL — rather than setting the field — is what makes this\n * authoritative. Two escape hatches survive it:\n *\n * - an `options` that already mentions `search_path` is left untouched, so a\n * deployment that deliberately pins something else keeps it;\n * - `searchPath: false` (or an unparseable, non-URL connection string) sends\n * nothing and inherits the server default.\n *\n * Anything else in `options` (a `statement_timeout`, say) is preserved and the\n * `search_path` flag is appended to it.\n */\nexport function pinSearchPath(connectionString: string, searchPath: string | false = \"public\"): string {\n if (searchPath === false) return connectionString;\n\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch {\n // Key/value DSNs and anything else we cannot parse are returned as\n // given: a connection that works unpinned beats one we corrupted.\n return connectionString;\n }\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") return connectionString;\n\n const existing = url.searchParams.get(\"options\");\n if (existing && /(^|\\s)-c\\s*search_path\\s*=/.test(existing)) return connectionString;\n\n const flag = `-c search_path=${searchPath}`;\n url.searchParams.set(\"options\", existing ? `${existing} ${flag}` : flag);\n // Re-serialize by hand. `URLSearchParams` writes a space as `+`, which\n // node-postgres happens to decode but libpq does not — and this same string\n // is handed to `pg_dump`/`psql` for backups. Percent-encoding is the form\n // both agree on, and is what the scaffolded `.env` already ships.\n url.search = Array.from(url.searchParams.entries())\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join(\"&\");\n return url.toString();\n}\n\n/**\n * Destroy pool clients that are released while still inside a transaction.\n *\n * pg-pool returns a client to the idle list whenever `release()` is called\n * without an error — even if the connection is still mid-transaction (status\n * `T`/`E`). That happens in practice: drizzle's pool transaction releases in\n * a `finally` after attempting ROLLBACK, and if the ROLLBACK itself fails\n * (e.g. it was queued behind a statement that hit the client-side\n * query_timeout), the client goes back dirty. The next checkout then runs\n * its statements inside the zombie transaction — with the previous request's\n * `app.*` RLS GUCs still applied, which turns unrelated queries into\n * RLS-scoped ones (observed in production as registration failing with\n * SQLSTATE 42501 under a leaked anonymous context).\n *\n * pg-pool emits `release` before it consults its private `_expired` set, so\n * marking the client expired here makes `_release()` destroy it instead of\n * pooling it. Both `client._txStatus` (pg ≥ 8.16) and `pool._expired` are\n * private APIs — feature-detect and fall back to loud logging so an upstream\n * change degrades to observability, never to silent corruption.\n */\nexport function guardPoolAgainstDirtyRelease(pool: Pool, label: string): void {\n pool.on(\"release\", (err: Error | undefined, client: unknown) => {\n if (err) return; // errored clients are already destroyed by pg-pool\n const txStatus = (client as { _txStatus?: string | null })?._txStatus;\n if (typeof txStatus !== \"string\" || txStatus === TX_IDLE) return;\n\n // pg-pool keeps expired clients in a WeakSet (a plain object works too\n // if upstream ever changes it — duck-type on add/has).\n const expired = (pool as unknown as { _expired?: { add(c: object): unknown; has(c: object): boolean } })._expired;\n if (expired && typeof expired.add === \"function\" && typeof expired.has === \"function\" && client && typeof client === \"object\") {\n expired.add(client);\n logger.error(\n `[${label}] Client released back to the pool while still in a transaction ` +\n `(status '${txStatus}') — destroying it so the open transaction and its ` +\n `session state (RLS GUCs) cannot leak into the next request.`\n );\n } else {\n logger.error(\n `[${label}] Client released mid-transaction (status '${txStatus}') but the ` +\n `pool's internal expiry set is unavailable (pg-pool internals changed?). ` +\n `The connection may leak its open transaction into subsequent requests.`\n );\n }\n });\n}\n\n/**\n * Create a Drizzle-backed Postgres connection with a production-grade\n * connection pool.\n *\n * @param connectionString Postgres connection URL\n * @param schema Optional Drizzle schema for the relational API\n * @param poolConfig Optional pool tuning (merged over defaults)\n * @param env Where the pool-size ceiling `REBASE_DB_POOL_MAX`\n * is read from (see {@link poolMaxCeiling}); pass\n * `{}` to read none.\n *\n * @returns `{ db, pool, connectionString }` — the `pool` is exposed so\n * callers can register shutdown hooks (`pool.end()`) or monitor\n * pool metrics.\n */\nexport function createPostgresDatabaseConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig,\n env: NodeJS.ProcessEnv = process.env\n) {\n const opts = { ...DEFAULT_POOL,\n...poolConfig };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max, env),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n // ── Pool event logging ────────────────────────────────────────────────\n // Uses console.* because the structured logger lives in server\n // (a separate package). The caller can replace these with the structured\n // logger if desired via pool.on() after creation.\n pool.on(\"error\", (err) => {\n logger.error(\"[pg-pool] Unexpected pool error\", { detail: err.message });\n if (err.message.includes(\"ETIMEDOUT\")) {\n logger.warn(\"[pg-pool] Connection timeout detected — pool will auto-retry\");\n }\n });\n guardPoolAgainstDirtyRelease(pool, \"pg-pool\");\n\n // Create drizzle instance — pass schema when available to enable db.query relational API\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n\n/**\n * Create a direct (non-pooled) connection for operations that require\n * session-level features incompatible with PgBouncer transaction mode,\n * such as LISTEN/NOTIFY, prepared statements, or advisory locks.\n *\n * Uses a smaller pool since this is only for specific use cases.\n */\nexport function createDirectDatabaseConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig\n) {\n const opts = {\n ...DEFAULT_POOL,\n max: 5,\n ...poolConfig\n };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n pool.on(\"error\", (err) => {\n logger.error(\"[pg-direct-pool] Unexpected pool error\", { detail: err.message });\n });\n guardPoolAgainstDirtyRelease(pool, \"pg-direct-pool\");\n\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n\n/**\n * Create a read-only connection for routing read queries to replicas.\n * Uses a moderate pool size since reads are distributed across replicas.\n */\nexport function createReadReplicaConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig\n) {\n const opts = {\n ...DEFAULT_POOL,\n max: 10,\n ...poolConfig\n };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n pool.on(\"error\", (err) => {\n logger.error(\"[pg-replica-pool] Unexpected pool error\", { detail: err.message });\n });\n guardPoolAgainstDirtyRelease(pool, \"pg-replica-pool\");\n\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n"],"mappings":"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"}
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Pool } from "pg";
|
|
1
|
+
import { Pool, PoolConfig } from "pg";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration for the Postgres connection pool.
|
|
4
4
|
*
|
|
@@ -43,6 +43,14 @@ export interface PostgresPoolConfig {
|
|
|
43
43
|
export declare function poolMaxCeiling(env?: NodeJS.ProcessEnv): number | null;
|
|
44
44
|
/** Apply {@link poolMaxCeiling} to a requested pool size. */
|
|
45
45
|
export declare function cappedPoolMax(requested: number, env?: NodeJS.ProcessEnv): number;
|
|
46
|
+
/**
|
|
47
|
+
* The time limits every pool to this server shares: how long to wait for a
|
|
48
|
+
* connection (then 503), the server-side statement timeout, the client-side
|
|
49
|
+
* read backstop above it, and TCP keep-alive. Exported for the per-database
|
|
50
|
+
* pools (`DatabasePoolManager`), which had none of them — pg's default
|
|
51
|
+
* `connectionTimeoutMillis` is 0, wait forever.
|
|
52
|
+
*/
|
|
53
|
+
export declare function defaultPoolTimeouts(): Pick<PoolConfig, "connectionTimeoutMillis" | "query_timeout" | "statement_timeout" | "keepAlive" | "keepAliveInitialDelayMillis">;
|
|
46
54
|
/**
|
|
47
55
|
* Pin `search_path` into a connection string, so unqualified SQL resolves to a
|
|
48
56
|
* schema this framework chose rather than to one Postgres inferred.
|
|
@@ -105,12 +113,15 @@ export declare function guardPoolAgainstDirtyRelease(pool: Pool, label: string):
|
|
|
105
113
|
* @param connectionString Postgres connection URL
|
|
106
114
|
* @param schema Optional Drizzle schema for the relational API
|
|
107
115
|
* @param poolConfig Optional pool tuning (merged over defaults)
|
|
116
|
+
* @param env Where the pool-size ceiling `REBASE_DB_POOL_MAX`
|
|
117
|
+
* is read from (see {@link poolMaxCeiling}); pass
|
|
118
|
+
* `{}` to read none.
|
|
108
119
|
*
|
|
109
120
|
* @returns `{ db, pool, connectionString }` — the `pool` is exposed so
|
|
110
121
|
* callers can register shutdown hooks (`pool.end()`) or monitor
|
|
111
122
|
* pool metrics.
|
|
112
123
|
*/
|
|
113
|
-
export declare function createPostgresDatabaseConnection(connectionString: string, schema?: Record<string, unknown>, poolConfig?: PostgresPoolConfig): {
|
|
124
|
+
export declare function createPostgresDatabaseConnection(connectionString: string, schema?: Record<string, unknown>, poolConfig?: PostgresPoolConfig, env?: NodeJS.ProcessEnv): {
|
|
114
125
|
db: import("drizzle-orm/node-postgres").NodePgDatabase<Record<string, unknown>> & {
|
|
115
126
|
$client: Pool;
|
|
116
127
|
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
__createRequire(import.meta.url);
|
|
3
|
+
import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
|
|
4
|
+
import { c as pinSearchPath, o as defaultPoolTimeouts, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax } from "./connection-C9mT8bgf.js";
|
|
5
|
+
import { Pool } from "pg";
|
|
6
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
7
|
+
import { logger } from "@rebasepro/server";
|
|
8
|
+
//#region src/databasePoolManager.ts
|
|
9
|
+
var databasePoolManager_exports = /* @__PURE__ */ __exportAll({ DatabasePoolManager: () => DatabasePoolManager });
|
|
10
|
+
var DatabasePoolManager = class {
|
|
11
|
+
pools = /* @__PURE__ */ new Map();
|
|
12
|
+
drizzleInstances = /* @__PURE__ */ new Map();
|
|
13
|
+
defaultDatabaseName;
|
|
14
|
+
rootConnectionString;
|
|
15
|
+
constructor(adminConnectionString) {
|
|
16
|
+
this.rootConnectionString = adminConnectionString;
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(adminConnectionString);
|
|
19
|
+
this.defaultDatabaseName = url.pathname.slice(1);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
throw new Error(`Invalid adminConnectionString provided: ${e}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
getDrizzle(databaseName) {
|
|
25
|
+
const existing = this.drizzleInstances.get(databaseName);
|
|
26
|
+
if (existing) return existing;
|
|
27
|
+
const db = drizzle(this.getPool(databaseName));
|
|
28
|
+
this.drizzleInstances.set(databaseName, db);
|
|
29
|
+
return db;
|
|
30
|
+
}
|
|
31
|
+
getPool(databaseName) {
|
|
32
|
+
if (this.pools.has(databaseName)) return this.pools.get(databaseName);
|
|
33
|
+
const url = new URL(this.rootConnectionString);
|
|
34
|
+
url.pathname = `/${databaseName}`;
|
|
35
|
+
const pool = new Pool({
|
|
36
|
+
connectionString: pinSearchPath(url.toString()),
|
|
37
|
+
max: cappedPoolMax(10),
|
|
38
|
+
idleTimeoutMillis: 1e4,
|
|
39
|
+
allowExitOnIdle: true,
|
|
40
|
+
...defaultPoolTimeouts()
|
|
41
|
+
});
|
|
42
|
+
pool.on("error", (err) => {
|
|
43
|
+
logger.error(`[DatabasePoolManager] Unexpected error on idle client for db ${databaseName}`, { error: err });
|
|
44
|
+
});
|
|
45
|
+
guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);
|
|
46
|
+
this.pools.set(databaseName, pool);
|
|
47
|
+
return pool;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Disconnect and remove the pool for a specific database.
|
|
51
|
+
* Required before `CREATE DATABASE ... TEMPLATE` or `DROP DATABASE`,
|
|
52
|
+
* which need exclusive access to the target database.
|
|
53
|
+
*/
|
|
54
|
+
async disconnectDatabase(databaseName) {
|
|
55
|
+
const pool = this.pools.get(databaseName);
|
|
56
|
+
if (pool) {
|
|
57
|
+
await pool.end();
|
|
58
|
+
this.pools.delete(databaseName);
|
|
59
|
+
this.drizzleInstances.delete(databaseName);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Check if a pool exists for a given database name. */
|
|
63
|
+
hasPool(databaseName) {
|
|
64
|
+
return this.pools.has(databaseName);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Does this server serve exactly one database?
|
|
68
|
+
*
|
|
69
|
+
* The managed development database is PGlite, and PGlite serves exactly
|
|
70
|
+
* one. `CREATE DATABASE "rb_feature" TEMPLATE "postgres"` there writes a
|
|
71
|
+
* `pg_database` catalogue row and nothing else: nothing errors, the row is
|
|
72
|
+
* listable, `pg_database_size` answers — and connecting to the "branch"
|
|
73
|
+
* lands you in the parent. Every write made in the belief that it is
|
|
74
|
+
* sandboxed goes to the developer's real development database.
|
|
75
|
+
*
|
|
76
|
+
* The CLI already refuses `rebase db branch` on this ground, but it does it
|
|
77
|
+
* by parsing argv, so Studio — which reaches `BranchService` over the
|
|
78
|
+
* websocket — walked straight past it. The connection is the only thing
|
|
79
|
+
* both callers share, so the answer belongs here.
|
|
80
|
+
*
|
|
81
|
+
* Measured, not assumed: `select version()` over the PGlite socket returns
|
|
82
|
+
* `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`, which is how a wasm build
|
|
83
|
+
* announces itself and what `dev-db/constraints.ts` documents. A real
|
|
84
|
+
* Postgres never carries `(PGlite `.
|
|
85
|
+
*
|
|
86
|
+
* Cached, because a live connection cannot change engine underneath us, and
|
|
87
|
+
* a `PostgresBackendDriver` — which builds a `BranchService` — is
|
|
88
|
+
* constructed per transaction.
|
|
89
|
+
*/
|
|
90
|
+
servesOneDatabase() {
|
|
91
|
+
this.oneDatabaseProbe ??= this.probeOneDatabase();
|
|
92
|
+
return this.oneDatabaseProbe;
|
|
93
|
+
}
|
|
94
|
+
oneDatabaseProbe;
|
|
95
|
+
async probeOneDatabase() {
|
|
96
|
+
try {
|
|
97
|
+
const result = await this.getPool(this.defaultDatabaseName).query("SELECT version() AS version");
|
|
98
|
+
return /\(PGlite\b/i.test(result.rows[0]?.version ?? "");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
this.oneDatabaseProbe = void 0;
|
|
101
|
+
logger.warn("[DatabasePoolManager] Could not read the server version", { error: err });
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async shutdown() {
|
|
106
|
+
const promises = [];
|
|
107
|
+
for (const [dbName, pool] of this.pools.entries()) {
|
|
108
|
+
logger.info(`[DatabasePoolManager] Shutting down pool for ${dbName}`);
|
|
109
|
+
promises.push(pool.end());
|
|
110
|
+
}
|
|
111
|
+
await Promise.all(promises);
|
|
112
|
+
this.pools.clear();
|
|
113
|
+
this.drizzleInstances.clear();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { databasePoolManager_exports as n, DatabasePoolManager as t };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=databasePoolManager-BIuewidb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"databasePoolManager-BIuewidb.js","names":[],"sources":["../src/databasePoolManager.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { guardPoolAgainstDirtyRelease, pinSearchPath, cappedPoolMax, defaultPoolTimeouts } from \"./connection\";\n\nexport class DatabasePoolManager {\n private pools: Map<string, Pool> = new Map();\n private drizzleInstances: Map<string, NodePgDatabase> = new Map();\n public readonly defaultDatabaseName: string;\n private readonly rootConnectionString: string;\n\n constructor(adminConnectionString: string) {\n this.rootConnectionString = adminConnectionString;\n try {\n const url = new URL(adminConnectionString);\n this.defaultDatabaseName = url.pathname.slice(1);\n } catch (e) {\n throw new Error(`Invalid adminConnectionString provided: ${e}`);\n }\n }\n\n public getDrizzle(databaseName: string): NodePgDatabase<Record<string, never>> {\n const existing = this.drizzleInstances.get(databaseName);\n if (existing) {\n return existing;\n }\n\n const pool = this.getPool(databaseName);\n const db = drizzle(pool);\n this.drizzleInstances.set(databaseName, db);\n return db;\n }\n\n public getPool(databaseName: string): Pool {\n if (this.pools.has(databaseName)) {\n return this.pools.get(databaseName)!;\n }\n\n const url = new URL(this.rootConnectionString);\n url.pathname = `/${databaseName}`;\n\n const pool = new Pool({\n // Same pin as the primary pool: these are branch/multi-database\n // connections to the *same* server, so they inherit the same\n // `\"$user\"` hazard. See `pinSearchPath`.\n connectionString: pinSearchPath(url.toString()),\n // Capped by REBASE_DB_POOL_MAX, which the managed development\n // database sets to 1: PGlite multiplexes onto a single session and\n // overlapping transactions deadlock there.\n max: cappedPoolMax(10),\n idleTimeoutMillis: 10000, // Reduced from 30000 for aggressive cleanup\n allowExitOnIdle: true, // Prevent idle clients from hanging the Node.js process\n // The main pool's limits: a full pool answers 503 after 10 s rather\n // than queueing forever, and a statement stops at 30 s server-side.\n ...defaultPoolTimeouts()\n });\n\n // Prevent idle client errors from crashing the Node.js process\n pool.on(\"error\", (err) => {\n logger.error(`[DatabasePoolManager] Unexpected error on idle client for db ${databaseName}`, { error: err });\n });\n guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);\n\n this.pools.set(databaseName, pool);\n return pool;\n }\n\n /**\n * Disconnect and remove the pool for a specific database.\n * Required before `CREATE DATABASE ... TEMPLATE` or `DROP DATABASE`,\n * which need exclusive access to the target database.\n */\n public async disconnectDatabase(databaseName: string): Promise<void> {\n const pool = this.pools.get(databaseName);\n if (pool) {\n await pool.end();\n this.pools.delete(databaseName);\n this.drizzleInstances.delete(databaseName);\n }\n }\n\n /** Check if a pool exists for a given database name. */\n public hasPool(databaseName: string): boolean {\n return this.pools.has(databaseName);\n }\n\n /**\n * Does this server serve exactly one database?\n *\n * The managed development database is PGlite, and PGlite serves exactly\n * one. `CREATE DATABASE \"rb_feature\" TEMPLATE \"postgres\"` there writes a\n * `pg_database` catalogue row and nothing else: nothing errors, the row is\n * listable, `pg_database_size` answers — and connecting to the \"branch\"\n * lands you in the parent. Every write made in the belief that it is\n * sandboxed goes to the developer's real development database.\n *\n * The CLI already refuses `rebase db branch` on this ground, but it does it\n * by parsing argv, so Studio — which reaches `BranchService` over the\n * websocket — walked straight past it. The connection is the only thing\n * both callers share, so the answer belongs here.\n *\n * Measured, not assumed: `select version()` over the PGlite socket returns\n * `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`, which is how a wasm build\n * announces itself and what `dev-db/constraints.ts` documents. A real\n * Postgres never carries `(PGlite `.\n *\n * Cached, because a live connection cannot change engine underneath us, and\n * a `PostgresBackendDriver` — which builds a `BranchService` — is\n * constructed per transaction.\n */\n public servesOneDatabase(): Promise<boolean> {\n this.oneDatabaseProbe ??= this.probeOneDatabase();\n return this.oneDatabaseProbe;\n }\n\n private oneDatabaseProbe?: Promise<boolean>;\n\n private async probeOneDatabase(): Promise<boolean> {\n try {\n const result = await this.getPool(this.defaultDatabaseName)\n .query<{ version: string }>(\"SELECT version() AS version\");\n return /\\(PGlite\\b/i.test(result.rows[0]?.version ?? \"\");\n } catch (err) {\n // Fail open, and do not cache the failure. The cost of a wrong\n // \"yes\" is refusing branching on a real Postgres that momentarily\n // could not answer; the cost of a wrong \"no\" is one fake branch,\n // which is what this exists to prevent — but a server that cannot\n // run `select version()` is not one we can conclude anything about.\n this.oneDatabaseProbe = undefined;\n logger.warn(\"[DatabasePoolManager] Could not read the server version\", { error: err });\n return false;\n }\n }\n\n public async shutdown(): Promise<void> {\n const promises = [];\n for (const [dbName, pool] of this.pools.entries()) {\n logger.info(`[DatabasePoolManager] Shutting down pool for ${dbName}`);\n promises.push(pool.end());\n }\n await Promise.all(promises);\n this.pools.clear();\n this.drizzleInstances.clear();\n }\n}\n"],"mappings":";;;;;;;;;AAMA,IAAa,sBAAb,MAAiC;CAC7B,wBAAmC,IAAI,IAAI;CAC3C,mCAAwD,IAAI,IAAI;CAChE;CACA;CAEA,YAAY,uBAA+B;EACvC,KAAK,uBAAuB;EAC5B,IAAI;GACA,MAAM,MAAM,IAAI,IAAI,qBAAqB;GACzC,KAAK,sBAAsB,IAAI,SAAS,MAAM,CAAC;EACnD,SAAS,GAAG;GACR,MAAM,IAAI,MAAM,2CAA2C,GAAG;EAClE;CACJ;CAEA,WAAkB,cAA6D;EAC3E,MAAM,WAAW,KAAK,iBAAiB,IAAI,YAAY;EACvD,IAAI,UACA,OAAO;EAIX,MAAM,KAAK,QADE,KAAK,QAAQ,YACP,CAAI;EACvB,KAAK,iBAAiB,IAAI,cAAc,EAAE;EAC1C,OAAO;CACX;CAEA,QAAe,cAA4B;EACvC,IAAI,KAAK,MAAM,IAAI,YAAY,GAC3B,OAAO,KAAK,MAAM,IAAI,YAAY;EAGtC,MAAM,MAAM,IAAI,IAAI,KAAK,oBAAoB;EAC7C,IAAI,WAAW,IAAI;EAEnB,MAAM,OAAO,IAAI,KAAK;GAIlB,kBAAkB,cAAc,IAAI,SAAS,CAAC;GAI9C,KAAK,cAAc,EAAE;GACrB,mBAAmB;GACnB,iBAAiB;GAGjB,GAAG,oBAAoB;EAC3B,CAAC;EAGD,KAAK,GAAG,UAAU,QAAQ;GACtB,OAAO,MAAM,gEAAgE,gBAAgB,EAAE,OAAO,IAAI,CAAC;EAC/G,CAAC;EACD,6BAA6B,MAAM,WAAW,cAAc;EAE5D,KAAK,MAAM,IAAI,cAAc,IAAI;EACjC,OAAO;CACX;;;;;;CAOA,MAAa,mBAAmB,cAAqC;EACjE,MAAM,OAAO,KAAK,MAAM,IAAI,YAAY;EACxC,IAAI,MAAM;GACN,MAAM,KAAK,IAAI;GACf,KAAK,MAAM,OAAO,YAAY;GAC9B,KAAK,iBAAiB,OAAO,YAAY;EAC7C;CACJ;;CAGA,QAAe,cAA+B;EAC1C,OAAO,KAAK,MAAM,IAAI,YAAY;CACtC;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,oBAA6C;EACzC,KAAK,qBAAqB,KAAK,iBAAiB;EAChD,OAAO,KAAK;CAChB;CAEA;CAEA,MAAc,mBAAqC;EAC/C,IAAI;GACA,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,mBAAmB,CAAC,CACtD,MAA2B,6BAA6B;GAC7D,OAAO,cAAc,KAAK,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE;EAC3D,SAAS,KAAK;GAMV,KAAK,mBAAmB,KAAA;GACxB,OAAO,KAAK,2DAA2D,EAAE,OAAO,IAAI,CAAC;GACrF,OAAO;EACX;CACJ;CAEA,MAAa,WAA0B;EACnC,MAAM,WAAW,CAAC;EAClB,KAAK,MAAM,CAAC,QAAQ,SAAS,KAAK,MAAM,QAAQ,GAAG;GAC/C,OAAO,KAAK,gDAAgD,QAAQ;GACpE,SAAS,KAAK,KAAK,IAAI,CAAC;EAC5B;EACA,MAAM,QAAQ,IAAI,QAAQ;EAC1B,KAAK,MAAM,MAAM;EACjB,KAAK,iBAAiB,MAAM;CAChC;AACJ"}
|