@q32/core 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./mcp.js";
14
14
  export * from "./oauth.js";
15
15
  export * from "./ops-events.js";
16
16
  export * from "./pg.js";
17
+ export * from "./pg-kysely.js";
17
18
  export * from "./r2-json.js";
18
19
  export * from "./rate-limit.js";
19
20
  export * from "./seo.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from "./mcp.js";
14
14
  export * from "./oauth.js";
15
15
  export * from "./ops-events.js";
16
16
  export * from "./pg.js";
17
+ export * from "./pg-kysely.js";
17
18
  export * from "./r2-json.js";
18
19
  export * from "./rate-limit.js";
19
20
  export * from "./seo.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { Kysely } from "kysely";
2
+ import postgres from "postgres";
3
+ export type PgConnectionSource = "hyperdrive" | "url" | "fallback";
4
+ export type PgSslMode = "auto" | "disable" | "require" | "verify-ca";
5
+ export type HyperdriveLike = {
6
+ connectionString?: string;
7
+ };
8
+ export type PgEnvLike = Record<string, unknown> & {
9
+ HYPERDRIVE?: HyperdriveLike;
10
+ PG_URL?: string;
11
+ PG_CA_CERT?: string;
12
+ };
13
+ export type ResolvedPgConnection = {
14
+ connectionString: string;
15
+ source: PgConnectionSource;
16
+ closeConnection: boolean;
17
+ caCert?: string;
18
+ };
19
+ export type PgConnectionOptions = {
20
+ hyperdriveKey?: string;
21
+ urlKey?: string;
22
+ caCertKey?: string;
23
+ fallbackConnectionString?: string;
24
+ requireConnection?: boolean;
25
+ closeConnection?: boolean;
26
+ };
27
+ export type PgClientOptions = {
28
+ max?: number;
29
+ prepare?: boolean;
30
+ idleTimeoutSeconds?: number;
31
+ connectTimeoutSeconds?: number;
32
+ sslMode?: PgSslMode;
33
+ };
34
+ export type KyselyPgOptions = PgConnectionOptions & PgClientOptions & {
35
+ onDestroyError?: (error: unknown) => void;
36
+ };
37
+ type PostgresOptions = NonNullable<Parameters<typeof postgres>[1]>;
38
+ export declare function resolvePgConnection(env: PgEnvLike, options?: PgConnectionOptions): ResolvedPgConnection;
39
+ export declare function postgresClientOptions(connection: Pick<ResolvedPgConnection, "connectionString" | "source" | "caCert">, options?: PgClientOptions): PostgresOptions;
40
+ export declare function createPostgresSql(connection: ResolvedPgConnection, options?: PgClientOptions): postgres.Sql;
41
+ export declare function createKyselyPg<Database>(env: PgEnvLike, options?: KyselyPgOptions): {
42
+ db: Kysely<Database>;
43
+ sql: postgres.Sql;
44
+ connection: ResolvedPgConnection;
45
+ destroy(): Promise<void>;
46
+ };
47
+ export declare function withKyselyPg<Database, Result>(env: PgEnvLike, fn: (db: Kysely<Database>, context: {
48
+ sql: postgres.Sql;
49
+ connection: ResolvedPgConnection;
50
+ }) => Promise<Result>, options?: KyselyPgOptions): Promise<Result>;
51
+ export declare function normalizePem(value?: string): string | undefined;
52
+ export {};
53
+ //# sourceMappingURL=pg-kysely.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-kysely.d.ts","sourceRoot":"","sources":["../src/pg-kysely.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ import { Kysely } from "kysely";
2
+ import { PostgresJSDialect } from "kysely-postgres-js";
3
+ import postgres from "postgres";
4
+ export function resolvePgConnection(env, options = {}) {
5
+ const hyperdriveKey = options.hyperdriveKey ?? "HYPERDRIVE";
6
+ const urlKey = options.urlKey ?? "PG_URL";
7
+ const caCertKey = options.caCertKey ?? "PG_CA_CERT";
8
+ const hyperdrive = env[hyperdriveKey];
9
+ const hyperdriveString = cleanString(hyperdrive?.connectionString);
10
+ if (hyperdriveString) {
11
+ return {
12
+ connectionString: hyperdriveString,
13
+ source: "hyperdrive",
14
+ closeConnection: options.closeConnection ?? false,
15
+ };
16
+ }
17
+ const urlString = cleanString(env[urlKey]);
18
+ if (urlString) {
19
+ return {
20
+ connectionString: urlString,
21
+ source: "url",
22
+ closeConnection: options.closeConnection ?? true,
23
+ caCert: normalizePem(cleanString(env[caCertKey])),
24
+ };
25
+ }
26
+ const fallback = cleanString(options.fallbackConnectionString);
27
+ if (fallback) {
28
+ return {
29
+ connectionString: fallback,
30
+ source: "fallback",
31
+ closeConnection: options.closeConnection ?? true,
32
+ caCert: normalizePem(cleanString(env[caCertKey])),
33
+ };
34
+ }
35
+ if (options.requireConnection ?? true) {
36
+ throw new Error(`${urlKey} or ${hyperdriveKey}.connectionString is required for Postgres.`);
37
+ }
38
+ return {
39
+ connectionString: "",
40
+ source: "fallback",
41
+ closeConnection: options.closeConnection ?? true,
42
+ };
43
+ }
44
+ export function postgresClientOptions(connection, options = {}) {
45
+ const clientOptions = {
46
+ max: options.max ?? 1,
47
+ prepare: options.prepare ?? false,
48
+ idle_timeout: options.idleTimeoutSeconds ?? 20,
49
+ connect_timeout: options.connectTimeoutSeconds ?? 5,
50
+ };
51
+ const ssl = resolvePgSsl(connection, options.sslMode ?? "auto");
52
+ if (ssl !== undefined)
53
+ clientOptions.ssl = ssl;
54
+ return clientOptions;
55
+ }
56
+ export function createPostgresSql(connection, options = {}) {
57
+ return postgres(connection.connectionString, postgresClientOptions(connection, options));
58
+ }
59
+ export function createKyselyPg(env, options = {}) {
60
+ const connection = resolvePgConnection(env, options);
61
+ const sql = createPostgresSql(connection, options);
62
+ const db = new Kysely({
63
+ dialect: new PostgresJSDialect({ postgres: sql }),
64
+ });
65
+ return {
66
+ db,
67
+ sql,
68
+ connection,
69
+ async destroy() {
70
+ try {
71
+ await db.destroy();
72
+ }
73
+ catch (error) {
74
+ options.onDestroyError?.(error);
75
+ if (!options.onDestroyError)
76
+ throw error;
77
+ }
78
+ },
79
+ };
80
+ }
81
+ export async function withKyselyPg(env, fn, options = {}) {
82
+ const handle = createKyselyPg(env, options);
83
+ try {
84
+ return await fn(handle.db, { sql: handle.sql, connection: handle.connection });
85
+ }
86
+ finally {
87
+ if (handle.connection.closeConnection) {
88
+ await handle.destroy();
89
+ }
90
+ }
91
+ }
92
+ export function normalizePem(value) {
93
+ const trimmed = value?.trim();
94
+ if (!trimmed)
95
+ return undefined;
96
+ return trimmed.includes("\\n") ? trimmed.replace(/\\n/g, "\n") : trimmed;
97
+ }
98
+ function resolvePgSsl(connection, mode) {
99
+ if (mode === "disable")
100
+ return false;
101
+ if (mode === "require")
102
+ return "require";
103
+ if (mode === "verify-ca") {
104
+ if (!connection.caCert)
105
+ throw new Error("PG_CA_CERT is required when sslMode is verify-ca.");
106
+ return { ca: connection.caCert, rejectUnauthorized: true };
107
+ }
108
+ if (connection.source === "hyperdrive")
109
+ return false;
110
+ if (connection.caCert)
111
+ return { ca: connection.caCert, rejectUnauthorized: true };
112
+ if (isLocalPgUrl(connection.connectionString))
113
+ return false;
114
+ return "require";
115
+ }
116
+ function isLocalPgUrl(connectionString) {
117
+ try {
118
+ const host = new URL(connectionString).hostname;
119
+ return host === "localhost" || host === "127.0.0.1" || host === "::1";
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ function cleanString(value) {
126
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
127
+ }
128
+ //# sourceMappingURL=pg-kysely.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-kysely.js","sourceRoot":"","sources":["../src/pg-kysely.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ # Adgiro, Relin, and DirtSignal Commonality
2
+
3
+ This is the current direction for `@q32/core`: choose the best reusable pattern across the projects, then move the projects toward it.
4
+
5
+ ## Chosen Defaults
6
+
7
+ - Postgres access should use Kysely over raw SQL once a project has long-lived relational read models. DirtSignal has the largest raw `postgres` surface today, but Relin's Kysely layer is the better long-term default because it makes schema ownership, joins, transactions, and refactors safer.
8
+ - Cloudflare Workers should create PG clients per request, queue invocation, cron tick, or script scope. Hyperdrive owns pooling in Worker runtime; Node scripts and tests should close clients explicitly.
9
+ - PG connection resolution should prefer Hyperdrive, then `PG_URL`, then an explicit test fallback only when the caller opts in.
10
+ - Non-local PG should use TLS by default. A provided `PG_CA_CERT` should be normalized and used for certificate verification.
11
+ - PG migrations should be explicit, status/dry-run capable, and separated from runtime credentials. Relin's admin/runtime split is the better default; DirtSignal's typed migration-array model is better than ad hoc SQL file parsing for application-owned schema modules.
12
+ - D1 remains the default for small control-plane state, but shared auth, jobs, and ops-event tables should be configurable D1 modules, not app-local forks.
13
+
14
+ ## Core Modules To Grow
15
+
16
+ - `pg-kysely`: Kysely + `postgres.js` construction, Hyperdrive/PG_URL/CA-cert policy, and scoped `withKyselyPg`.
17
+ - `pg-migrations`: one migration runner that can consume SQL files or typed migration arrays, with status, dry-run, reset guard, and configurable migrations table.
18
+ - `d1-auth`: configurable users/orgs/memberships/identities/magic-link/session helpers. Apps keep policy and copy; core owns token hashing, one-time consume, identity upsert, and session persistence patterns.
19
+ - `mcp-oauth-d1`: the common OAuth client/code/token/device-flow repository used by Relin and DirtSignal, with app-provided principal lookup and entitlement policy.
20
+ - `d1-jobs`: Adgiro's richer job driver is closer to the target than the current minimal core job helper. Core should own enqueue policies, active locks, concurrency keys, delayed requeue, stale-running recovery, and linked ops events.
21
+ - `queue`: a typed Cloudflare Queue publisher/consumer adapter with inline mode for tests and local single-process execution.
22
+ - `ops-events`: converge on run IDs, event names, statuses/severity, target identifiers, metadata/error normalization, and best-effort writes.
23
+
24
+ ## Project Movement
25
+
26
+ - Relin should keep using Kysely for PG and replace its local connection lifecycle with `@q32/core/pg-kysely`.
27
+ - DirtSignal should migrate new PG repositories to Kysely first, then wrap existing raw `postgres` repositories behind Kysely-compatible interfaces as they change.
28
+ - Adgiro should adopt the shared D1 job driver shape and ops-event schema before adding PG. If it adds PG, it should start on Kysely rather than raw `postgres`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q32/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Shared TypeScript primitives for Q32 Cloudflare Worker projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -88,6 +88,10 @@
88
88
  "types": "./dist/pg.d.ts",
89
89
  "default": "./dist/pg.js"
90
90
  },
91
+ "./pg-kysely": {
92
+ "types": "./dist/pg-kysely.d.ts",
93
+ "default": "./dist/pg-kysely.js"
94
+ },
91
95
  "./r2-json": {
92
96
  "types": "./dist/r2-json.d.ts",
93
97
  "default": "./dist/r2-json.js"
@@ -120,15 +124,28 @@
120
124
  "README.md",
121
125
  "LICENSE"
122
126
  ],
127
+ "peerDependencies": {
128
+ "kysely": ">=0.28 <0.30",
129
+ "kysely-postgres-js": "^3.0.0",
130
+ "postgres": "^3.4.0"
131
+ },
123
132
  "devDependencies": {
124
133
  "@cloudflare/workers-types": "^4.20260606.0",
125
134
  "@vitest/coverage-v8": "^4.1.8",
135
+ "kysely": "^0.29.2",
136
+ "kysely-postgres-js": "^3.0.0",
137
+ "postgres": "^3.4.9",
126
138
  "typescript": "^5.9.3",
127
139
  "vitest": "^4.0.15"
128
140
  },
129
141
  "publishConfig": {
130
142
  "access": "public"
131
143
  },
144
+ "dependencies": {
145
+ "kysely": "^0.29.2",
146
+ "kysely-postgres-js": "^3.0.0",
147
+ "postgres": "^3.4.9"
148
+ },
132
149
  "scripts": {
133
150
  "build": "tsc -p tsconfig.json",
134
151
  "test": "vitest run",
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./mcp.js";
14
14
  export * from "./oauth.js";
15
15
  export * from "./ops-events.js";
16
16
  export * from "./pg.js";
17
+ export * from "./pg-kysely.js";
17
18
  export * from "./r2-json.js";
18
19
  export * from "./rate-limit.js";
19
20
  export * from "./seo.js";
@@ -0,0 +1,198 @@
1
+ import { Kysely } from "kysely";
2
+ import { PostgresJSDialect } from "kysely-postgres-js";
3
+ import postgres from "postgres";
4
+
5
+ export type PgConnectionSource = "hyperdrive" | "url" | "fallback";
6
+ export type PgSslMode = "auto" | "disable" | "require" | "verify-ca";
7
+
8
+ export type HyperdriveLike = {
9
+ connectionString?: string;
10
+ };
11
+
12
+ export type PgEnvLike = Record<string, unknown> & {
13
+ HYPERDRIVE?: HyperdriveLike;
14
+ PG_URL?: string;
15
+ PG_CA_CERT?: string;
16
+ };
17
+
18
+ export type ResolvedPgConnection = {
19
+ connectionString: string;
20
+ source: PgConnectionSource;
21
+ closeConnection: boolean;
22
+ caCert?: string;
23
+ };
24
+
25
+ export type PgConnectionOptions = {
26
+ hyperdriveKey?: string;
27
+ urlKey?: string;
28
+ caCertKey?: string;
29
+ fallbackConnectionString?: string;
30
+ requireConnection?: boolean;
31
+ closeConnection?: boolean;
32
+ };
33
+
34
+ export type PgClientOptions = {
35
+ max?: number;
36
+ prepare?: boolean;
37
+ idleTimeoutSeconds?: number;
38
+ connectTimeoutSeconds?: number;
39
+ sslMode?: PgSslMode;
40
+ };
41
+
42
+ export type KyselyPgOptions = PgConnectionOptions &
43
+ PgClientOptions & {
44
+ onDestroyError?: (error: unknown) => void;
45
+ };
46
+
47
+ type PostgresOptions = NonNullable<Parameters<typeof postgres>[1]>;
48
+
49
+ export function resolvePgConnection(
50
+ env: PgEnvLike,
51
+ options: PgConnectionOptions = {},
52
+ ): ResolvedPgConnection {
53
+ const hyperdriveKey = options.hyperdriveKey ?? "HYPERDRIVE";
54
+ const urlKey = options.urlKey ?? "PG_URL";
55
+ const caCertKey = options.caCertKey ?? "PG_CA_CERT";
56
+ const hyperdrive = env[hyperdriveKey] as HyperdriveLike | undefined;
57
+ const hyperdriveString = cleanString(hyperdrive?.connectionString);
58
+ if (hyperdriveString) {
59
+ return {
60
+ connectionString: hyperdriveString,
61
+ source: "hyperdrive",
62
+ closeConnection: options.closeConnection ?? false,
63
+ };
64
+ }
65
+
66
+ const urlString = cleanString(env[urlKey]);
67
+ if (urlString) {
68
+ return {
69
+ connectionString: urlString,
70
+ source: "url",
71
+ closeConnection: options.closeConnection ?? true,
72
+ caCert: normalizePem(cleanString(env[caCertKey])),
73
+ };
74
+ }
75
+
76
+ const fallback = cleanString(options.fallbackConnectionString);
77
+ if (fallback) {
78
+ return {
79
+ connectionString: fallback,
80
+ source: "fallback",
81
+ closeConnection: options.closeConnection ?? true,
82
+ caCert: normalizePem(cleanString(env[caCertKey])),
83
+ };
84
+ }
85
+
86
+ if (options.requireConnection ?? true) {
87
+ throw new Error(`${urlKey} or ${hyperdriveKey}.connectionString is required for Postgres.`);
88
+ }
89
+
90
+ return {
91
+ connectionString: "",
92
+ source: "fallback",
93
+ closeConnection: options.closeConnection ?? true,
94
+ };
95
+ }
96
+
97
+ export function postgresClientOptions(
98
+ connection: Pick<ResolvedPgConnection, "connectionString" | "source" | "caCert">,
99
+ options: PgClientOptions = {},
100
+ ): PostgresOptions {
101
+ const clientOptions: PostgresOptions = {
102
+ max: options.max ?? 1,
103
+ prepare: options.prepare ?? false,
104
+ idle_timeout: options.idleTimeoutSeconds ?? 20,
105
+ connect_timeout: options.connectTimeoutSeconds ?? 5,
106
+ };
107
+
108
+ const ssl = resolvePgSsl(connection, options.sslMode ?? "auto");
109
+ if (ssl !== undefined) clientOptions.ssl = ssl;
110
+ return clientOptions;
111
+ }
112
+
113
+ export function createPostgresSql(
114
+ connection: ResolvedPgConnection,
115
+ options: PgClientOptions = {},
116
+ ): postgres.Sql {
117
+ return postgres(connection.connectionString, postgresClientOptions(connection, options));
118
+ }
119
+
120
+ export function createKyselyPg<Database>(
121
+ env: PgEnvLike,
122
+ options: KyselyPgOptions = {},
123
+ ): {
124
+ db: Kysely<Database>;
125
+ sql: postgres.Sql;
126
+ connection: ResolvedPgConnection;
127
+ destroy(): Promise<void>;
128
+ } {
129
+ const connection = resolvePgConnection(env, options);
130
+ const sql = createPostgresSql(connection, options);
131
+ const db = new Kysely<Database>({
132
+ dialect: new PostgresJSDialect({ postgres: sql }),
133
+ });
134
+
135
+ return {
136
+ db,
137
+ sql,
138
+ connection,
139
+ async destroy() {
140
+ try {
141
+ await db.destroy();
142
+ } catch (error) {
143
+ options.onDestroyError?.(error);
144
+ if (!options.onDestroyError) throw error;
145
+ }
146
+ },
147
+ };
148
+ }
149
+
150
+ export async function withKyselyPg<Database, Result>(
151
+ env: PgEnvLike,
152
+ fn: (db: Kysely<Database>, context: { sql: postgres.Sql; connection: ResolvedPgConnection }) => Promise<Result>,
153
+ options: KyselyPgOptions = {},
154
+ ): Promise<Result> {
155
+ const handle = createKyselyPg<Database>(env, options);
156
+ try {
157
+ return await fn(handle.db, { sql: handle.sql, connection: handle.connection });
158
+ } finally {
159
+ if (handle.connection.closeConnection) {
160
+ await handle.destroy();
161
+ }
162
+ }
163
+ }
164
+
165
+ export function normalizePem(value?: string): string | undefined {
166
+ const trimmed = value?.trim();
167
+ if (!trimmed) return undefined;
168
+ return trimmed.includes("\\n") ? trimmed.replace(/\\n/g, "\n") : trimmed;
169
+ }
170
+
171
+ function resolvePgSsl(
172
+ connection: Pick<ResolvedPgConnection, "connectionString" | "source" | "caCert">,
173
+ mode: PgSslMode,
174
+ ): PostgresOptions["ssl"] | undefined {
175
+ if (mode === "disable") return false;
176
+ if (mode === "require") return "require";
177
+ if (mode === "verify-ca") {
178
+ if (!connection.caCert) throw new Error("PG_CA_CERT is required when sslMode is verify-ca.");
179
+ return { ca: connection.caCert, rejectUnauthorized: true };
180
+ }
181
+ if (connection.source === "hyperdrive") return false;
182
+ if (connection.caCert) return { ca: connection.caCert, rejectUnauthorized: true };
183
+ if (isLocalPgUrl(connection.connectionString)) return false;
184
+ return "require";
185
+ }
186
+
187
+ function isLocalPgUrl(connectionString: string): boolean {
188
+ try {
189
+ const host = new URL(connectionString).hostname;
190
+ return host === "localhost" || host === "127.0.0.1" || host === "::1";
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ function cleanString(value: unknown): string | undefined {
197
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
198
+ }