@geekmidas/constructs 10.0.0-alpha.117 → 10.0.0-alpha.118

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.
@@ -6,7 +6,7 @@ const require_functions = require("../functions-D7O5SGVc.cjs");
6
6
  const require_Endpoint = require("../Endpoint-yhR2T2Wt.cjs");
7
7
  const require_Queue = require("../Queue-BhuofM3k.cjs");
8
8
  const require_email = require("../email.cjs");
9
- const require_kysely = require("../kysely-B3zfg9ti.cjs");
9
+ const require_kysely = require("../kysely-CukArHJ0.cjs");
10
10
  const require_readRequestBody = require("../readRequestBody-Dej_L3c4.cjs");
11
11
  const require_Topic = require("../Topic-DwS3QO0m.cjs");
12
12
  const require_HonoEndpointAdaptor = require("../HonoEndpointAdaptor-CN1jjUBJ.cjs");
@@ -4,7 +4,7 @@ import { t as HttpMethod } from "../types-DMuSQeLL.cjs";
4
4
  import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-BWyKCa2x.cjs";
5
5
  import { t as Queue } from "../Queue-HPyi0e1i.cjs";
6
6
  import { t as Subscriber } from "../Subscriber-Bpp02cGr.cjs";
7
- import { t as KyselyDatabase } from "../kysely-D0bpzgPs.cjs";
7
+ import { t as KyselyDatabase } from "../kysely-BHYV-WC-.cjs";
8
8
  import { t as Fake } from "../fake-Dvion_Fh.cjs";
9
9
  import { o as TestStage } from "../stages-BSCF19o2.cjs";
10
10
  import { Logger } from "@geekmidas/logger";
@@ -4,7 +4,7 @@ import { t as HttpMethod } from "../types-DMuSQeLL.mjs";
4
4
  import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-CrE9Ppdk.mjs";
5
5
  import { t as Queue } from "../Queue-DlsHRzUp.mjs";
6
6
  import { t as Subscriber } from "../Subscriber-CtN9R48-.mjs";
7
- import { t as KyselyDatabase } from "../kysely-DKwnl0nf.mjs";
7
+ import { t as KyselyDatabase } from "../kysely-CAW3QSqp.mjs";
8
8
  import { t as Fake } from "../fake-DqKo8INJ.mjs";
9
9
  import { o as TestStage } from "../stages-BSCF19o2.mjs";
10
10
  import { EventPublisher, ExtractPublisherMessage } from "@geekmidas/events";
@@ -4,7 +4,7 @@ import { a as publishEvents, i as publishConstructEvents, t as FunctionBuilder }
4
4
  import { r as ResponseBuilder, t as Endpoint } from "../Endpoint-gPXhHKhJ.mjs";
5
5
  import { t as Queue } from "../Queue-cmhqEBOB.mjs";
6
6
  import { Email } from "../email.mjs";
7
- import { t as KyselyDatabase } from "../kysely-B_0BAr0T.mjs";
7
+ import { t as KyselyDatabase } from "../kysely-Cs4uPO8k.mjs";
8
8
  import { f as createObjectHeaders, s as createCookieHeaderAccessor } from "../readRequestBody-BTIl3k-D.mjs";
9
9
  import { t as Topic } from "../Topic-D2R0r24C.mjs";
10
10
  import { t as HonoEndpoint } from "../HonoEndpointAdaptor-euH1oSdY.mjs";
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_kysely = require("../kysely-B3zfg9ti.cjs");
2
+ const require_kysely = require("../kysely-CukArHJ0.cjs");
3
3
  exports.KyselyDatabase = require_kysely.KyselyDatabase;
4
4
  exports.closeDatabasePools = require_kysely.closeDatabasePools;
@@ -1,2 +1,2 @@
1
- import { i as closeDatabasePools, n as KyselyDatabaseOptions, r as ReadOnlyDatabase, t as KyselyDatabase } from "../kysely-D0bpzgPs.cjs";
2
- export { KyselyDatabase, KyselyDatabaseOptions, ReadOnlyDatabase, closeDatabasePools };
1
+ import { a as closeDatabasePools, i as ReadOnlyDatabase, n as KyselyDatabaseOptions, r as PostgresRuntime, t as KyselyDatabase } from "../kysely-BHYV-WC-.cjs";
2
+ export { KyselyDatabase, KyselyDatabaseOptions, PostgresRuntime, ReadOnlyDatabase, closeDatabasePools };
@@ -1,2 +1,2 @@
1
- import { i as closeDatabasePools, n as KyselyDatabaseOptions, r as ReadOnlyDatabase, t as KyselyDatabase } from "../kysely-DKwnl0nf.mjs";
2
- export { KyselyDatabase, KyselyDatabaseOptions, ReadOnlyDatabase, closeDatabasePools };
1
+ import { a as closeDatabasePools, i as ReadOnlyDatabase, n as KyselyDatabaseOptions, r as PostgresRuntime, t as KyselyDatabase } from "../kysely-CAW3QSqp.mjs";
2
+ export { KyselyDatabase, KyselyDatabaseOptions, PostgresRuntime, ReadOnlyDatabase, closeDatabasePools };
@@ -1,2 +1,2 @@
1
- import { n as closeDatabasePools, t as KyselyDatabase } from "../kysely-B_0BAr0T.mjs";
1
+ import { n as closeDatabasePools, t as KyselyDatabase } from "../kysely-Cs4uPO8k.mjs";
2
2
  export { KyselyDatabase, closeDatabasePools };
@@ -21,7 +21,7 @@ declare function closeDatabasePools(): Promise<void>;
21
21
  * straight through, so anything Kysely adds later arrives without being
22
22
  * tracked here.
23
23
  */
24
- interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
24
+ interface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {
25
25
  /**
26
26
  * The schema, pinned on both roles' `search_path`.
27
27
  *
@@ -39,16 +39,15 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
39
39
  */
40
40
  roles?: boolean;
41
41
  /**
42
- * The Postgres major version to provision.
42
+ * Extensions to create in this database, as the cluster master, before the
43
+ * first migration — for the ones the owner role a migration runs as may not
44
+ * create (PostGIS is not a trusted extension). They are created in
45
+ * `public`, which joins this database's roles' `search_path`, so their
46
+ * types resolve unqualified.
43
47
  *
44
- * Declared here so one statement reaches both targets. It was previously
45
- * set in two unrelated places — a container tag locally, nothing at all on
46
- * AWS (so Aurora chose) — which is how local came to run 18 while deployed
47
- * ran 17.7 with nothing recording the difference.
48
- *
49
- * Defaults to {@link DEFAULT_POSTGRES_VERSION}.
48
+ * Only attempted: the image has to ship them.
50
49
  */
51
- version?: PostgresVersion;
50
+ extensions?: readonly string[];
52
51
  /**
53
52
  * End each query run inside a request with a comment naming what ran it —
54
53
  * `/*operation='POST /orders',request_id='…'*\/` — so `pg_stat_activity`
@@ -58,6 +57,48 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
58
57
  */
59
58
  queryTags?: boolean;
60
59
  }
60
+ /**
61
+ * Which Postgres the database runs: a major on the stock image, or an image
62
+ * the project names. One or the other, never both — an image's tag already
63
+ * says its major, and a second statement of it is one that can disagree.
64
+ */
65
+ type PostgresRuntime = {
66
+ /**
67
+ * The Postgres major version to provision, on the stock image.
68
+ *
69
+ * Declared here so one statement reaches every target. It was
70
+ * previously set in two unrelated places — a container tag locally,
71
+ * nothing at all on AWS (so Aurora chose) — which is how local came to
72
+ * run 18 while deployed ran 17.7 with nothing recording the difference.
73
+ *
74
+ * Defaults to {@link DEFAULT_POSTGRES_VERSION}.
75
+ */
76
+ version?: PostgresVersion;
77
+ image?: never;
78
+ } | {
79
+ /**
80
+ * The container image to run, wherever a target runs one (local,
81
+ * Dokploy), in place of `postgres:<version>`.
82
+ *
83
+ * Yours to choose and used as written: its tag is the major, and it
84
+ * should ship every extension in `extensions`. AWS runs RDS, ignores
85
+ * it, and provisions {@link DEFAULT_POSTGRES_VERSION}.
86
+ *
87
+ * ```ts
88
+ * new KyselyDatabase<GeoDB>('Geo', {
89
+ * image: 'postgis/postgis:18-3.6-alpine',
90
+ * extensions: ['postgis'],
91
+ * });
92
+ * ```
93
+ */
94
+ image: string;
95
+ version?: never;
96
+ };
97
+ /**
98
+ * Everything Kysely takes, plus what this construct declares — including
99
+ * which Postgres it runs, as a version or an image.
100
+ */
101
+ type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;
61
102
  /** What makes a construct derived rather than a database in its own right. */
62
103
  interface DerivedFrom {
63
104
  kind: 'database-reader' | 'database-schema';
@@ -82,7 +123,7 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
82
123
  private readonly config;
83
124
  private readonly schemaName;
84
125
  private readonly roles;
85
- private readonly version;
126
+ private readonly runtime;
86
127
  private readonly derivedFrom?;
87
128
  constructor(id: ConstructName<TName>, options?: KyselyDatabaseOptions, derivedFrom?: DerivedFrom);
88
129
  declare(): Declaration[];
@@ -171,5 +212,5 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
171
212
  */
172
213
  type ReadOnlyDatabase<DB, TName extends string> = Omit<KyselyDatabase<DB, TName>, 'reader' | 'cache' | 'schema'>;
173
214
  //#endregion
174
- export { closeDatabasePools as i, KyselyDatabaseOptions as n, ReadOnlyDatabase as r, KyselyDatabase as t };
175
- //# sourceMappingURL=kysely-D0bpzgPs.d.cts.map
215
+ export { closeDatabasePools as a, ReadOnlyDatabase as i, KyselyDatabaseOptions as n, PostgresRuntime as r, KyselyDatabase as t };
216
+ //# sourceMappingURL=kysely-BHYV-WC-.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-BHYV-WC-.d.cts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChElC,kCAAkC,KAAK;;;;;;;;EAQhD;;;;;;;;EAQA;;;;;;;;;;EAUA;;;;;;;;EAQA;;;;;;;KAQW;;;;;;;;;;;EAYT,UAAU;EACV;;;;;;;;;;;;;;;;;EAkBA;EACA;;;;;;KAOS,wBAAwB,4BAA4B;;UAGtD;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EA2Bf,WAAW;;;;;;;;;;;;;EAyDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAiBX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}
@@ -21,7 +21,7 @@ declare function closeDatabasePools(): Promise<void>;
21
21
  * straight through, so anything Kysely adds later arrives without being
22
22
  * tracked here.
23
23
  */
24
- interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
24
+ interface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {
25
25
  /**
26
26
  * The schema, pinned on both roles' `search_path`.
27
27
  *
@@ -39,16 +39,15 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
39
39
  */
40
40
  roles?: boolean;
41
41
  /**
42
- * The Postgres major version to provision.
42
+ * Extensions to create in this database, as the cluster master, before the
43
+ * first migration — for the ones the owner role a migration runs as may not
44
+ * create (PostGIS is not a trusted extension). They are created in
45
+ * `public`, which joins this database's roles' `search_path`, so their
46
+ * types resolve unqualified.
43
47
  *
44
- * Declared here so one statement reaches both targets. It was previously
45
- * set in two unrelated places — a container tag locally, nothing at all on
46
- * AWS (so Aurora chose) — which is how local came to run 18 while deployed
47
- * ran 17.7 with nothing recording the difference.
48
- *
49
- * Defaults to {@link DEFAULT_POSTGRES_VERSION}.
48
+ * Only attempted: the image has to ship them.
50
49
  */
51
- version?: PostgresVersion;
50
+ extensions?: readonly string[];
52
51
  /**
53
52
  * End each query run inside a request with a comment naming what ran it —
54
53
  * `/*operation='POST /orders',request_id='…'*\/` — so `pg_stat_activity`
@@ -58,6 +57,48 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
58
57
  */
59
58
  queryTags?: boolean;
60
59
  }
60
+ /**
61
+ * Which Postgres the database runs: a major on the stock image, or an image
62
+ * the project names. One or the other, never both — an image's tag already
63
+ * says its major, and a second statement of it is one that can disagree.
64
+ */
65
+ type PostgresRuntime = {
66
+ /**
67
+ * The Postgres major version to provision, on the stock image.
68
+ *
69
+ * Declared here so one statement reaches every target. It was
70
+ * previously set in two unrelated places — a container tag locally,
71
+ * nothing at all on AWS (so Aurora chose) — which is how local came to
72
+ * run 18 while deployed ran 17.7 with nothing recording the difference.
73
+ *
74
+ * Defaults to {@link DEFAULT_POSTGRES_VERSION}.
75
+ */
76
+ version?: PostgresVersion;
77
+ image?: never;
78
+ } | {
79
+ /**
80
+ * The container image to run, wherever a target runs one (local,
81
+ * Dokploy), in place of `postgres:<version>`.
82
+ *
83
+ * Yours to choose and used as written: its tag is the major, and it
84
+ * should ship every extension in `extensions`. AWS runs RDS, ignores
85
+ * it, and provisions {@link DEFAULT_POSTGRES_VERSION}.
86
+ *
87
+ * ```ts
88
+ * new KyselyDatabase<GeoDB>('Geo', {
89
+ * image: 'postgis/postgis:18-3.6-alpine',
90
+ * extensions: ['postgis'],
91
+ * });
92
+ * ```
93
+ */
94
+ image: string;
95
+ version?: never;
96
+ };
97
+ /**
98
+ * Everything Kysely takes, plus what this construct declares — including
99
+ * which Postgres it runs, as a version or an image.
100
+ */
101
+ type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;
61
102
  /** What makes a construct derived rather than a database in its own right. */
62
103
  interface DerivedFrom {
63
104
  kind: 'database-reader' | 'database-schema';
@@ -82,7 +123,7 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
82
123
  private readonly config;
83
124
  private readonly schemaName;
84
125
  private readonly roles;
85
- private readonly version;
126
+ private readonly runtime;
86
127
  private readonly derivedFrom?;
87
128
  constructor(id: ConstructName<TName>, options?: KyselyDatabaseOptions, derivedFrom?: DerivedFrom);
88
129
  declare(): Declaration[];
@@ -171,5 +212,5 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
171
212
  */
172
213
  type ReadOnlyDatabase<DB, TName extends string> = Omit<KyselyDatabase<DB, TName>, 'reader' | 'cache' | 'schema'>;
173
214
  //#endregion
174
- export { closeDatabasePools as i, KyselyDatabaseOptions as n, ReadOnlyDatabase as r, KyselyDatabase as t };
175
- //# sourceMappingURL=kysely-DKwnl0nf.d.mts.map
215
+ export { closeDatabasePools as a, ReadOnlyDatabase as i, KyselyDatabaseOptions as n, PostgresRuntime as r, KyselyDatabase as t };
216
+ //# sourceMappingURL=kysely-CAW3QSqp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-CAW3QSqp.d.mts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChElC,kCAAkC,KAAK;;;;;;;;EAQhD;;;;;;;;EAQA;;;;;;;;;;EAUA;;;;;;;;EAQA;;;;;;;KAQW;;;;;;;;;;;EAYT,UAAU;EACV;;;;;;;;;;;;;;;;;EAkBA;EACA;;;;;;KAOS,wBAAwB,4BAA4B;;UAGtD;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EA2Bf,WAAW;;;;;;;;;;;;;EAyDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAiBX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}
@@ -222,7 +222,7 @@ var KyselyDatabase = class KyselyDatabase {
222
222
  config;
223
223
  schemaName;
224
224
  roles;
225
- version;
225
+ runtime;
226
226
  derivedFrom;
227
227
  constructor(id, options = {}, derivedFrom) {
228
228
  this.options = options;
@@ -234,7 +234,7 @@ var KyselyDatabase = class KyselyDatabase {
234
234
  };
235
235
  this.schemaName = options.schema ?? DEFAULT_SCHEMA;
236
236
  this.roles = options.roles ?? true;
237
- this.version = options.version ?? DEFAULT_POSTGRES_VERSION;
237
+ this.runtime = options.image ? { image: options.image } : { version: options.version ?? DEFAULT_POSTGRES_VERSION };
238
238
  this.derivedFrom = derivedFrom;
239
239
  this.service = {
240
240
  serviceName: serviceKey(canonical),
@@ -262,7 +262,8 @@ var KyselyDatabase = class KyselyDatabase {
262
262
  id: this.id,
263
263
  engine: "postgres",
264
264
  schema: this.schemaName,
265
- version: this.version,
265
+ ...this.runtime,
266
+ ...this.options.extensions?.length ? { extensions: [...new Set(this.options.extensions)] } : {},
266
267
  provides: [this.config.url],
267
268
  ...this.roles ? {} : { roles: false }
268
269
  }];
@@ -350,7 +351,7 @@ var KyselyDatabase = class KyselyDatabase {
350
351
  * plugins would write \`createdAt\` where production writes \`created_at\`.
351
352
  */
352
353
  get clientConfig() {
353
- const { schema: _schema, roles: _roles, version: _version, queryTags: _queryTags, ...kysely } = this.options;
354
+ const { schema: _schema, roles: _roles, version: _version, image: _image, extensions: _extensions, queryTags: _queryTags, ...kysely } = this.options;
354
355
  return kysely;
355
356
  }
356
357
  async connect(options, as = {}) {
@@ -367,4 +368,4 @@ var KyselyDatabase = class KyselyDatabase {
367
368
  //#endregion
368
369
  export { closeDatabasePools as n, KyselyDatabase as t };
369
370
 
370
- //# sourceMappingURL=kysely-B_0BAr0T.mjs.map
371
+ //# sourceMappingURL=kysely-Cs4uPO8k.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-Cs4uPO8k.mjs","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"sourcesContent":["/**\n * The `pg` pools a database construct opens, and what every one of them gets:\n *\n * - **A name.** `application_name` is what `pg_stat_activity` shows beside a\n * connection. Without one, a database at `max_connections` cannot say who\n * holds them. It is a *fallback*, so `PGAPPNAME` or `?application_name=` in\n * the URL still win.\n * - **Tagged queries.** Inside a request, each query ends in a comment naming\n * what ran it — `/*operation='POST /orders',request_id='…'*\\/` — in the\n * sqlcommenter form, and inside a trace the query's own span as\n * `traceparent='00-<trace>-<span>-<flags>'`. It shows in\n * `pg_stat_activity.query`, in the server's slow-query and `auto_explain`\n * logs, and costs no round trip; `pg_stat_statements` ignores comments, so\n * its grouping is unchanged.\n * - **A listener for idle-client errors.** When the server ends an idle\n * connection (`idle_session_timeout`, a failover, an admin), the pool emits\n * `'error'`. With no listener that is an uncaught exception that kills the\n * process — for a client the pool has already discarded.\n * - **A span per query.** Through the global OpenTelemetry tracer, so a no-op\n * without a provider. `pg` is bundled into a production entry, where no\n * instrumentation can hook its module load — so the client is wrapped here,\n * as the tags are. See {@link traceQueries}.\n * - **A way to close.** {@link closeDatabasePools} ends every pool opened, so a\n * server shutting down releases its connections instead of holding them\n * until the process dies.\n */\n\nimport { currentRequestContext } from '@geekmidas/services';\nimport {\n\ttype Attributes,\n\tcontext,\n\tisSpanContextValid,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport pg from 'pg';\nimport { onShutdown } from '../shutdown';\n\nexport interface DatabasePoolOptions {\n\t/** Tag queries with what ran them and the trace they ran in. Default true. */\n\tqueryTags?: boolean;\n}\n\n/** The longest statement text a query span carries. */\nexport const MAX_STATEMENT_LENGTH = 2048;\n\n/** Every pool opened and not yet closed, so a shutdown can end them all. */\nconst open = new Set<pg.Pool>();\nonShutdown(() => closeDatabasePools());\n\n/**\n * A pool for one URL, with its schema actually on the search path.\n *\n * `?search_path=` is not a libpq parameter — a URL carrying it connects\n * happily and then resolves every unqualified name against `public`, which is\n * how a schema tenant silently writes its tables into the database it was\n * separated from. Postgres takes it as a startup option instead, so the URL\n * keeps the readable form the target derives and this turns it into the thing\n * the server understands.\n */\nexport function openPool(\n\turl: string,\n\toptions: DatabasePoolOptions = {},\n): pg.Pool {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tparsed.searchParams.delete('search_path');\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: parsed.toString(),\n\t\t...(searchPath ? { options: `-c search_path=${searchPath}` } : {}),\n\t\tfallback_application_name: applicationName(),\n\t});\n\n\tpool.on('error', (error) => {\n\t\tconst logger = currentRequestContext()?.logger;\n\t\tif (logger) logger.warn({ err: error }, 'Idle database connection lost');\n\t\telse console.warn('Idle database connection lost:', error.message);\n\t});\n\tconst target = connectionAttributes(parsed);\n\tpool.on('connect', (client) => {\n\t\t// Tags first, so the span wraps them: it records the statement as the\n\t\t// application wrote it rather than with the comment on the end, and it\n\t\t// is the active span when the tag is written — so the `traceparent` a\n\t\t// query carries names the query's own span.\n\t\tif (options.queryTags !== false) tagQueries(client);\n\t\ttraceQueries(client, target);\n\t});\n\n\topen.add(pool);\n\tconst end = pool.end.bind(pool);\n\tpool.end = (async () => {\n\t\topen.delete(pool);\n\t\tawait end();\n\t}) as typeof pool.end;\n\n\treturn pool;\n}\n\n/**\n * End every pool opened, waiting for checked-out connections to come back.\n * For a server's shutdown, after it has stopped taking requests.\n */\nexport async function closeDatabasePools(): Promise<void> {\n\tawait Promise.all([...open].map((pool) => pool.end()));\n}\n\n/**\n * Who this process is, as Postgres will show it: the Lambda function, else the\n * app — `GKM_APP_NAME` where an entry sets it, `gkm dev`'s app tag otherwise.\n * Postgres keeps 63 bytes of it.\n */\nexport function applicationName(\n\tenv: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n\treturn (\n\t\tenv.AWS_LAMBDA_FUNCTION_NAME ||\n\t\tenv.GKM_APP_NAME ||\n\t\tenv.GKM_DEV_APP?.split('#').pop() ||\n\t\tundefined\n\t);\n}\n\n/**\n * The comment that says what ran a query: the request's operation and id, and\n * the active span as a W3C `traceparent` — the query's own span when it runs\n * through a traced client, since the span wraps the tagging. '' with neither.\n *\n * The keys are in sqlcommenter's sorted order. Values keep only characters\n * that cannot end a comment or a quoted value — `*` and `'` among those\n * dropped — so a route cannot inject SQL through it.\n */\nexport function queryTag(): string {\n\tconst request = currentRequestContext();\n\tconst span = trace.getActiveSpan()?.spanContext();\n\n\tconst tags = [\n\t\t['operation', request?.operation],\n\t\t['request_id', request?.requestId],\n\t\t['traceparent', span && isSpanContextValid(span) ? traceparent(span) : ''],\n\t].filter((tag): tag is [string, string] => Boolean(tag[1]));\n\tif (tags.length === 0) return '';\n\n\tconst clean = (value: string) => value.replace(/[^\\w ./:{}@-]/g, '');\n\treturn ` /*${tags.map(([key, value]) => `${key}='${clean(value)}'`).join(',')}*/`;\n}\n\n/** A span as W3C trace context's `traceparent`, version 00. */\nfunction traceparent(span: {\n\ttraceId: string;\n\tspanId: string;\n\ttraceFlags: number;\n}): string {\n\tconst flags = (span.traceFlags & 0xff).toString(16).padStart(2, '0');\n\treturn `00-${span.traceId}-${span.spanId}-${flags}`;\n}\n\n/**\n * Append {@link queryTag} to everything `client` runs: a SQL string — what\n * Kysely sends — or a query object such as a cursor's.\n */\nfunction tagQueries(client: pg.PoolClient): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst tag = queryTag();\n\t\tif (tag && typeof first === 'string') return query(first + tag, ...rest);\n\t\tif (\n\t\t\ttag &&\n\t\t\tfirst &&\n\t\t\ttypeof first === 'object' &&\n\t\t\ttypeof (first as { text?: unknown }).text === 'string'\n\t\t) {\n\t\t\tconst config = first as { text: string };\n\t\t\tconfig.text += tag;\n\t\t}\n\t\treturn query(first, ...rest);\n\t}) as typeof client.query;\n}\n\n/** What every span of one pool says about where it connects. */\nfunction connectionAttributes(url: URL): Attributes {\n\tconst name = decodeURIComponent(url.pathname.slice(1));\n\treturn {\n\t\t'db.system': 'postgresql',\n\t\t...(name && { 'db.name': name }),\n\t\t'server.address': url.hostname,\n\t\t'server.port': Number(url.port || 5432),\n\t};\n}\n\nconst TRACER = '@geekmidas/constructs';\n\n/** `select`, `insert`, … — the first keyword, or `with`'s main statement. */\nconst OPERATION = /^\\s*(?:\\/\\*.*?\\*\\/\\s*)?(\\w+)/s;\n/** The table a statement is about, when it names one where it usually does. */\nconst TABLE =\n\t/\\b(?:from|into|update|join)\\s+((?:\"[^\"]+\"|[\\w$]+)(?:\\.(?:\"[^\"]+\"|[\\w$]+))?)/i;\n\n/**\n * What a statement does and to which table, parsed cheaply from its first\n * characters: `select \"orders\"`, `insert \"public\".\"users\"`. A statement the\n * patterns cannot read leaves `table` unset.\n */\nexport function describeStatement(text: string): {\n\toperation?: string;\n\ttable?: string;\n} {\n\tconst head = text.length > 512 ? text.slice(0, 512) : text;\n\tconst operation = OPERATION.exec(head)?.[1]?.toLowerCase();\n\tconst table = TABLE.exec(head)?.[1]?.replace(/\"/g, '');\n\treturn {\n\t\t...(operation && { operation }),\n\t\t...(table && { table }),\n\t};\n}\n\n/**\n * A span around everything `client` runs — `<operation> <table>`, or\n * `db.query` when the statement does not say.\n *\n * The statement text goes on the span only for a parameterized query — values\n * passed separately, as Kysely always does — and truncated. Parameter values\n * never do. A bare string with no values could hold anything a caller\n * concatenated into it, so it gets no text at all.\n */\nfunction traceQueries(client: pg.PoolClient, connection: Attributes): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst config =\n\t\t\tfirst && typeof first === 'object'\n\t\t\t\t? (first as { text?: unknown; values?: unknown })\n\t\t\t\t: undefined;\n\t\tconst text =\n\t\t\ttypeof first === 'string'\n\t\t\t\t? first\n\t\t\t\t: typeof config?.text === 'string'\n\t\t\t\t\t? config.text\n\t\t\t\t\t: undefined;\n\t\tconst values = typeof first === 'string' ? rest[0] : config?.values;\n\t\tconst { operation, table } = text ? describeStatement(text) : {};\n\n\t\tconst span = trace\n\t\t\t.getTracer(TRACER)\n\t\t\t.startSpan(operation && table ? `${operation} ${table}` : 'db.query', {\n\t\t\t\tkind: SpanKind.CLIENT,\n\t\t\t\tattributes: {\n\t\t\t\t\t...connection,\n\t\t\t\t\t...(operation && { 'db.operation': operation }),\n\t\t\t\t\t...(table && { 'db.sql.table': table }),\n\t\t\t\t\t...(text &&\n\t\t\t\t\t\tArray.isArray(values) && {\n\t\t\t\t\t\t\t'db.statement':\n\t\t\t\t\t\t\t\ttext.length > MAX_STATEMENT_LENGTH\n\t\t\t\t\t\t\t\t\t? text.slice(0, MAX_STATEMENT_LENGTH)\n\t\t\t\t\t\t\t\t\t: text,\n\t\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\tconst end = (error?: unknown) => {\n\t\t\tif (error) {\n\t\t\t\tif (error instanceof Error) span.recordException(error);\n\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\t}\n\t\t\tspan.end();\n\t\t};\n\n\t\t// A callback, as `pg`'s own callback form takes it.\n\t\tconst last = rest[rest.length - 1];\n\t\tif (typeof last === 'function') {\n\t\t\tconst args = [...rest];\n\t\t\targs[args.length - 1] = (error: unknown, ...result: unknown[]) => {\n\t\t\t\tend(error);\n\t\t\t\treturn (last as (...a: unknown[]) => unknown)(error, ...result);\n\t\t\t};\n\t\t\treturn context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...args),\n\t\t\t);\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...rest),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tend(error);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (result && typeof (result as Promise<unknown>).then === 'function') {\n\t\t\t(result as Promise<unknown>).then(\n\t\t\t\t() => end(),\n\t\t\t\t(error) => end(error),\n\t\t\t);\n\t\t} else if (\n\t\t\tresult &&\n\t\t\ttypeof (result as { once?: unknown }).once === 'function'\n\t\t) {\n\t\t\t// A submittable — a cursor or a stream — ends when it says so.\n\t\t\tconst emitter = result as {\n\t\t\t\tonce(event: string, fn: (e?: unknown) => void): void;\n\t\t\t};\n\t\t\temitter.once('end', () => end());\n\t\t\temitter.once('error', (error) => end(error));\n\t\t} else {\n\t\t\tend();\n\t\t}\n\t\treturn result;\n\t}) as typeof client.query;\n}\n","/**\n * `KyselyDatabase` — a declared Postgres database, typed by its schema.\n *\n * It declares a logical database, a schema, and the roles that reach it, and\n * hands back a Kysely client built from the one URL the adapter supplied. It\n * names no cloud: `--target=aws` fills that URL from RDS, local dev from the\n * Postgres container.\n *\n * Transactions and RLS context are the execution wrapper's job — it opens the\n * transaction and puts it in `ctx.db`. This construct does not resolve\n * connections per request.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\tDEFAULT_POSTGRES_VERSION,\n\ttype Declaration,\n\ttype PostgresVersion,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport { Cache } from '../cache';\nimport type { Construct } from '../construct-interface';\nimport { openPool } from './pool';\n\n/** The schema a database uses when it does not say otherwise. */\nconst DEFAULT_SCHEMA = 'app';\n\n/**\n * Everything Kysely takes, plus what this construct declares.\n *\n * `dialect` is omitted because the construct owns it — it is built from the one\n * URL the adapter supplied, which is the whole point of the construct. The rest\n * of `KyselyConfig` (`plugins`, `log`) is the app's business and passes\n * straight through, so anything Kysely adds later arrives without being\n * tracked here.\n */\ninterface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {\n\t/**\n\t * The schema, pinned on both roles' `search_path`.\n\t *\n\t * Defaults to `app` — the role the schema plays, rather than a restatement\n\t * of the database's own name, which reads correctly beside `auth` and\n\t * `pgboss` and keeps the application off `public`.\n\t */\n\tschema?: string;\n\t/**\n\t * Provision the owner/runtime role split. Defaults to on.\n\t *\n\t * Off falls back to the cluster's master credential in both URLs, giving up\n\t * the DDL/DML split and putting credentials that can drop a table into the\n\t * function's environment. A deliberate downgrade, not a default.\n\t */\n\troles?: boolean;\n\t/**\n\t * Extensions to create in this database, as the cluster master, before the\n\t * first migration — for the ones the owner role a migration runs as may not\n\t * create (PostGIS is not a trusted extension). They are created in\n\t * `public`, which joins this database's roles' `search_path`, so their\n\t * types resolve unqualified.\n\t *\n\t * Only attempted: the image has to ship them.\n\t */\n\textensions?: readonly string[];\n\t/**\n\t * End each query run inside a request with a comment naming what ran it —\n\t * `/*operation='POST /orders',request_id='…'*\\/` — so `pg_stat_activity`\n\t * and the server's logs can tie a query to its endpoint — and, inside a\n\t * trace, with the query span's `traceparent='00-…'` (sqlcommenter), so\n\t * they can tie it to the trace too. Defaults to on.\n\t */\n\tqueryTags?: boolean;\n}\n\n/**\n * Which Postgres the database runs: a major on the stock image, or an image\n * the project names. One or the other, never both — an image's tag already\n * says its major, and a second statement of it is one that can disagree.\n */\nexport type PostgresRuntime =\n\t| {\n\t\t\t/**\n\t\t\t * The Postgres major version to provision, on the stock image.\n\t\t\t *\n\t\t\t * Declared here so one statement reaches every target. It was\n\t\t\t * previously set in two unrelated places — a container tag locally,\n\t\t\t * nothing at all on AWS (so Aurora chose) — which is how local came to\n\t\t\t * run 18 while deployed ran 17.7 with nothing recording the difference.\n\t\t\t *\n\t\t\t * Defaults to {@link DEFAULT_POSTGRES_VERSION}.\n\t\t\t */\n\t\t\tversion?: PostgresVersion;\n\t\t\timage?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * The container image to run, wherever a target runs one (local,\n\t\t\t * Dokploy), in place of `postgres:<version>`.\n\t\t\t *\n\t\t\t * Yours to choose and used as written: its tag is the major, and it\n\t\t\t * should ship every extension in `extensions`. AWS runs RDS, ignores\n\t\t\t * it, and provisions {@link DEFAULT_POSTGRES_VERSION}.\n\t\t\t *\n\t\t\t * ```ts\n\t\t\t * new KyselyDatabase<GeoDB>('Geo', {\n\t\t\t * image: 'postgis/postgis:18-3.6-alpine',\n\t\t\t * extensions: ['postgis'],\n\t\t\t * });\n\t\t\t * ```\n\t\t\t */\n\t\t\timage: string;\n\t\t\tversion?: never;\n\t };\n\n/**\n * Everything Kysely takes, plus what this construct declares — including\n * which Postgres it runs, as a version or an image.\n */\nexport type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;\n\n/** What makes a construct derived rather than a database in its own right. */\ninterface DerivedFrom {\n\tkind: 'database-reader' | 'database-schema';\n\tof: string;\n\tschema?: string;\n}\n\n/**\n * @typeParam DB - the database's schema.\n * @typeParam TName - the canonical id. TypeScript has no partial\n * type-argument inference, so `new KyselyDatabase<OrdersDB>('orders')` leaves\n * this at its default and the service key widens to `string`. `db` still types\n * through `.database()`, which infers from the service.\n */\nexport class KyselyDatabase<DB = unknown, TName extends string = string>\n\timplements Construct<TName, Kysely<DB>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Kysely<DB>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * build publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly config: { url: string; ownerUrl: string };\n\tprivate readonly schemaName: string;\n\tprivate readonly roles: boolean;\n\tprivate readonly runtime: { version: PostgresVersion } | { image: string };\n\tprivate readonly derivedFrom?: DerivedFrom;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: KyselyDatabaseOptions = {},\n\t\tderivedFrom?: DerivedFrom,\n\t) {\n\t\t// Canonicalises, so `orders` and `Orders` are one construct rather than\n\t\t// two that collide. Throws on anything that cannot become a valid id.\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.config = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\townerUrl: provideKey(canonical, 'ownerUrl'),\n\t\t};\n\t\tthis.schemaName = options.schema ?? DEFAULT_SCHEMA;\n\t\tthis.roles = options.roles ?? true;\n\t\tthis.runtime = options.image\n\t\t\t? { image: options.image }\n\t\t\t: { version: options.version ?? DEFAULT_POSTGRES_VERSION };\n\t\tthis.derivedFrom = derivedFrom;\n\n\t\t// A field, not a getter: service discovery caches by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\t// Forwards the whole options object — cherry-picking `envParser` strips\n\t\t\t// `context` and with it the request-scoped logger.\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\tif (this.derivedFrom) {\n\t\t\tconst { kind, of, schema } = this.derivedFrom;\n\n\t\t\treturn [\n\t\t\t\tkind === 'database-schema'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\tschema: schema ?? this.schemaName,\n\t\t\t\t\t\t\t// The runtime key alone — see the note below on the parent.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\t// A reader has no owner URL of its own — nothing migrates\n\t\t\t\t\t\t\t// through a read-only endpoint — so this is the one key.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'database',\n\t\t\t\tid: this.id,\n\t\t\t\tengine: 'postgres',\n\t\t\t\tschema: this.schemaName,\n\t\t\t\t// A version, or the image whose tag is one — never both.\n\t\t\t\t...this.runtime,\n\t\t\t\t...(this.options.extensions?.length\n\t\t\t\t\t? { extensions: [...new Set(this.options.extensions)] }\n\t\t\t\t\t: {}),\n\t\t\t\t// One key, the runtime role's. `config` also holds the owner key and\n\t\t\t\t// it is deliberately absent here: a key in `provides` is a key an\n\t\t\t\t// edge can name, and nothing should be able to depend on DDL rights.\n\t\t\t\tprovides: [this.config.url],\n\t\t\t\t...(this.roles ? {} : { roles: false }),\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * A read-only endpoint on this database.\n\t *\n\t * Read-only is enforced by the reader role's grants rather than by which\n\t * endpoint it resolves to, so falling back to the writer where no replica\n\t * exists is safe rather than a silently writable connection behind a name\n\t * that says reader. There is no `writer()`: the database *is* the writer, so\n\t * a replica can never be reached by accident.\n\t *\n\t * The return type is narrower than this class on purpose — see\n\t * {@link ReadOnlyDatabase}.\n\t */\n\treader(): ReadOnlyDatabase<DB, `${TName}Reader`> {\n\t\treturn new KyselyDatabase<DB, `${TName}Reader`>(\n\t\t\t`${this.id}Reader` as ConstructName<`${TName}Reader`>,\n\t\t\tthis.options,\n\t\t\t{ kind: 'database-reader', of: this.id },\n\t\t);\n\t}\n\n\t/**\n\t * A cache that lives in this database.\n\t *\n\t * The same strengthening `schema()` is over a second database: `new\n\t * Cache('Sessions')` says the app caches and leaves where to the deployment,\n\t * while this says it caches *here* — which is a fact about the application\n\t * and so belongs in its code.\n\t *\n\t * What it buys beyond being explicit: the table's schema and the role that\n\t * reaches it come from this database rather than from a second convention,\n\t * and the backend no longer has to guess which database \"the database\" meant\n\t * when an app declares two.\n\t *\n\t * A cache is not a `KyselyDatabase`, so this returns a `Cache` — the client\n\t * is a key/value store, not a query builder, and typing it as the parent\n\t * would be a convenience that lies.\n\t */\n\tcache<TCache extends string = `${TName}Cache`>(\n\t\tid: ConstructName<TCache> = `${this.id}Cache` as ConstructName<TCache>,\n\t\toptions: { table?: string } = {},\n\t): Cache<TCache> {\n\t\treturn new Cache<TCache>(id, {\n\t\t\tof: this.id,\n\t\t\t...(options.table ? { table: options.table } : {}),\n\t\t});\n\t}\n\n\t/**\n\t * A second schema in this database, with its own role(s) and its own URL —\n\t * so this database's role holds no grant on those tables at all.\n\t *\n\t * Typed separately because a tenant has its own tables: the point is that\n\t * `auth.selectFrom('sessions')` type-checks and\n\t * `orders.selectFrom('sessions')` does not.\n\t */\n\tschema<TenantDB, TTenant extends string>(\n\t\tid: ConstructName<TTenant>,\n\t\toptions: KyselyDatabaseOptions = {},\n\t): KyselyDatabase<TenantDB, TTenant> {\n\t\treturn new KyselyDatabase<TenantDB, TTenant>(id, options, {\n\t\t\tkind: 'database-schema',\n\t\t\tof: this.id,\n\t\t\tschema: options.schema ?? (id as string).toLowerCase(),\n\t\t});\n\t}\n\n\t/**\n\t * A connection as the *owner* role — the one that may create, alter and drop.\n\t *\n\t * A `Service` rather than part of the construct, and deliberately so: a\n\t * construct is what `.dependsOn()` accepts, and nothing should be able to\n\t * depend on this. A migrator asks for it by name; a handler cannot ask at\n\t * all, which is the security property the role split exists for.\n\t *\n\t * Falls back to the runtime URL where no owner URL was published — the\n\t * `roles: false` downgrade, where there is only one credential and it is the\n\t * cluster master's.\n\t */\n\tget owner(): Service<`${Uncapitalize<TName>}Owner`, Kysely<DB>> {\n\t\treturn {\n\t\t\tserviceName:\n\t\t\t\t`${serviceKey(this.id)}Owner` as `${Uncapitalize<TName>}Owner`,\n\t\t\tregister: (options) => this.connect(options, { owner: true }),\n\t\t};\n\t}\n\n\t/**\n\t * What this database's clients are built with besides a dialect — the\n\t * plugins and logging it was declared with.\n\t *\n\t * Public so that whatever opens a connection to it outside \\`connect()\\` —\n\t * a test's transaction — builds the same client. A test that dropped the\n\t * plugins would write \\`createdAt\\` where production writes \\`created_at\\`.\n\t */\n\tget clientConfig(): Omit<KyselyConfig, 'dialect'> {\n\t\t// Split what the manifest declared from what Kysely takes, so neither\n\t\t// leaks into the other: \\`schema\\`, \\`roles\\`, \\`version\\`, \\`image\\`\n\t\t// and \\`extensions\\` describe infrastructure and mean nothing to a\n\t\t// client.\n\t\tconst {\n\t\t\tschema: _schema,\n\t\t\troles: _roles,\n\t\t\tversion: _version,\n\t\t\timage: _image,\n\t\t\textensions: _extensions,\n\t\t\tqueryTags: _queryTags,\n\t\t\t...kysely\n\t\t} = this.options;\n\t\treturn kysely;\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Kysely<DB>> {\n\t\tconst { url, ownerUrl } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\turl: get(this.config.url).string(),\n\t\t\t\townerUrl: get(this.config.ownerUrl).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\treturn new Kysely<DB>({\n\t\t\t...this.clientConfig,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\tpool: openPool(as.owner ? (ownerUrl ?? url) : url, {\n\t\t\t\t\tqueryTags: this.options.queryTags,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n}\n\n/**\n * A database you may read through, and derive nothing from.\n *\n * A reader is terminal in the graph, and this is that fact in the type system.\n * `DERIVES_FROM` gives `cache` and `database-schema` no `database-reader`\n * parent, and there is no reader of a reader — so `orders.reader().cache(…)`\n * has always been illegal, but it type-checked and failed later at\n * `assertDerivations`. That is a long way round to learn that a read-only\n * endpoint cannot own a table something writes to.\n *\n * Omitting the three deriving methods rather than making them throw keeps the\n * failure where it costs least: the editor, on the line that wrote it.\n *\n * The result is deliberately not assignable back to `KyselyDatabase` — a\n * reader is not one, and anything that genuinely needs the writer should say\n * so.\n */\nexport type ReadOnlyDatabase<DB, TName extends string> = Omit<\n\tKyselyDatabase<DB, TName>,\n\t'reader' | 'cache' | 'schema'\n>;\nexport { closeDatabasePools } from './pool';\n"],"mappings":"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"}
@@ -224,7 +224,7 @@ var KyselyDatabase = class KyselyDatabase {
224
224
  config;
225
225
  schemaName;
226
226
  roles;
227
- version;
227
+ runtime;
228
228
  derivedFrom;
229
229
  constructor(id, options = {}, derivedFrom) {
230
230
  this.options = options;
@@ -236,7 +236,7 @@ var KyselyDatabase = class KyselyDatabase {
236
236
  };
237
237
  this.schemaName = options.schema ?? DEFAULT_SCHEMA;
238
238
  this.roles = options.roles ?? true;
239
- this.version = options.version ?? _geekmidas_manifest.DEFAULT_POSTGRES_VERSION;
239
+ this.runtime = options.image ? { image: options.image } : { version: options.version ?? _geekmidas_manifest.DEFAULT_POSTGRES_VERSION };
240
240
  this.derivedFrom = derivedFrom;
241
241
  this.service = {
242
242
  serviceName: (0, _geekmidas_manifest.serviceKey)(canonical),
@@ -264,7 +264,8 @@ var KyselyDatabase = class KyselyDatabase {
264
264
  id: this.id,
265
265
  engine: "postgres",
266
266
  schema: this.schemaName,
267
- version: this.version,
267
+ ...this.runtime,
268
+ ...this.options.extensions?.length ? { extensions: [...new Set(this.options.extensions)] } : {},
268
269
  provides: [this.config.url],
269
270
  ...this.roles ? {} : { roles: false }
270
271
  }];
@@ -352,7 +353,7 @@ var KyselyDatabase = class KyselyDatabase {
352
353
  * plugins would write \`createdAt\` where production writes \`created_at\`.
353
354
  */
354
355
  get clientConfig() {
355
- const { schema: _schema, roles: _roles, version: _version, queryTags: _queryTags, ...kysely$1 } = this.options;
356
+ const { schema: _schema, roles: _roles, version: _version, image: _image, extensions: _extensions, queryTags: _queryTags, ...kysely$1 } = this.options;
356
357
  return kysely$1;
357
358
  }
358
359
  async connect(options, as = {}) {
@@ -380,4 +381,4 @@ Object.defineProperty(exports, "closeDatabasePools", {
380
381
  }
381
382
  });
382
383
 
383
- //# sourceMappingURL=kysely-B3zfg9ti.cjs.map
384
+ //# sourceMappingURL=kysely-CukArHJ0.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-CukArHJ0.cjs","names":["onShutdown","currentRequestContext","trace","isSpanContextValid","SpanKind","SpanStatusCode","context","canonicalId","provideKey","DEFAULT_POSTGRES_VERSION","serviceKey","Cache","kysely","Kysely","PostgresDialect"],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"sourcesContent":["/**\n * The `pg` pools a database construct opens, and what every one of them gets:\n *\n * - **A name.** `application_name` is what `pg_stat_activity` shows beside a\n * connection. Without one, a database at `max_connections` cannot say who\n * holds them. It is a *fallback*, so `PGAPPNAME` or `?application_name=` in\n * the URL still win.\n * - **Tagged queries.** Inside a request, each query ends in a comment naming\n * what ran it — `/*operation='POST /orders',request_id='…'*\\/` — in the\n * sqlcommenter form, and inside a trace the query's own span as\n * `traceparent='00-<trace>-<span>-<flags>'`. It shows in\n * `pg_stat_activity.query`, in the server's slow-query and `auto_explain`\n * logs, and costs no round trip; `pg_stat_statements` ignores comments, so\n * its grouping is unchanged.\n * - **A listener for idle-client errors.** When the server ends an idle\n * connection (`idle_session_timeout`, a failover, an admin), the pool emits\n * `'error'`. With no listener that is an uncaught exception that kills the\n * process — for a client the pool has already discarded.\n * - **A span per query.** Through the global OpenTelemetry tracer, so a no-op\n * without a provider. `pg` is bundled into a production entry, where no\n * instrumentation can hook its module load — so the client is wrapped here,\n * as the tags are. See {@link traceQueries}.\n * - **A way to close.** {@link closeDatabasePools} ends every pool opened, so a\n * server shutting down releases its connections instead of holding them\n * until the process dies.\n */\n\nimport { currentRequestContext } from '@geekmidas/services';\nimport {\n\ttype Attributes,\n\tcontext,\n\tisSpanContextValid,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport pg from 'pg';\nimport { onShutdown } from '../shutdown';\n\nexport interface DatabasePoolOptions {\n\t/** Tag queries with what ran them and the trace they ran in. Default true. */\n\tqueryTags?: boolean;\n}\n\n/** The longest statement text a query span carries. */\nexport const MAX_STATEMENT_LENGTH = 2048;\n\n/** Every pool opened and not yet closed, so a shutdown can end them all. */\nconst open = new Set<pg.Pool>();\nonShutdown(() => closeDatabasePools());\n\n/**\n * A pool for one URL, with its schema actually on the search path.\n *\n * `?search_path=` is not a libpq parameter — a URL carrying it connects\n * happily and then resolves every unqualified name against `public`, which is\n * how a schema tenant silently writes its tables into the database it was\n * separated from. Postgres takes it as a startup option instead, so the URL\n * keeps the readable form the target derives and this turns it into the thing\n * the server understands.\n */\nexport function openPool(\n\turl: string,\n\toptions: DatabasePoolOptions = {},\n): pg.Pool {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tparsed.searchParams.delete('search_path');\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: parsed.toString(),\n\t\t...(searchPath ? { options: `-c search_path=${searchPath}` } : {}),\n\t\tfallback_application_name: applicationName(),\n\t});\n\n\tpool.on('error', (error) => {\n\t\tconst logger = currentRequestContext()?.logger;\n\t\tif (logger) logger.warn({ err: error }, 'Idle database connection lost');\n\t\telse console.warn('Idle database connection lost:', error.message);\n\t});\n\tconst target = connectionAttributes(parsed);\n\tpool.on('connect', (client) => {\n\t\t// Tags first, so the span wraps them: it records the statement as the\n\t\t// application wrote it rather than with the comment on the end, and it\n\t\t// is the active span when the tag is written — so the `traceparent` a\n\t\t// query carries names the query's own span.\n\t\tif (options.queryTags !== false) tagQueries(client);\n\t\ttraceQueries(client, target);\n\t});\n\n\topen.add(pool);\n\tconst end = pool.end.bind(pool);\n\tpool.end = (async () => {\n\t\topen.delete(pool);\n\t\tawait end();\n\t}) as typeof pool.end;\n\n\treturn pool;\n}\n\n/**\n * End every pool opened, waiting for checked-out connections to come back.\n * For a server's shutdown, after it has stopped taking requests.\n */\nexport async function closeDatabasePools(): Promise<void> {\n\tawait Promise.all([...open].map((pool) => pool.end()));\n}\n\n/**\n * Who this process is, as Postgres will show it: the Lambda function, else the\n * app — `GKM_APP_NAME` where an entry sets it, `gkm dev`'s app tag otherwise.\n * Postgres keeps 63 bytes of it.\n */\nexport function applicationName(\n\tenv: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n\treturn (\n\t\tenv.AWS_LAMBDA_FUNCTION_NAME ||\n\t\tenv.GKM_APP_NAME ||\n\t\tenv.GKM_DEV_APP?.split('#').pop() ||\n\t\tundefined\n\t);\n}\n\n/**\n * The comment that says what ran a query: the request's operation and id, and\n * the active span as a W3C `traceparent` — the query's own span when it runs\n * through a traced client, since the span wraps the tagging. '' with neither.\n *\n * The keys are in sqlcommenter's sorted order. Values keep only characters\n * that cannot end a comment or a quoted value — `*` and `'` among those\n * dropped — so a route cannot inject SQL through it.\n */\nexport function queryTag(): string {\n\tconst request = currentRequestContext();\n\tconst span = trace.getActiveSpan()?.spanContext();\n\n\tconst tags = [\n\t\t['operation', request?.operation],\n\t\t['request_id', request?.requestId],\n\t\t['traceparent', span && isSpanContextValid(span) ? traceparent(span) : ''],\n\t].filter((tag): tag is [string, string] => Boolean(tag[1]));\n\tif (tags.length === 0) return '';\n\n\tconst clean = (value: string) => value.replace(/[^\\w ./:{}@-]/g, '');\n\treturn ` /*${tags.map(([key, value]) => `${key}='${clean(value)}'`).join(',')}*/`;\n}\n\n/** A span as W3C trace context's `traceparent`, version 00. */\nfunction traceparent(span: {\n\ttraceId: string;\n\tspanId: string;\n\ttraceFlags: number;\n}): string {\n\tconst flags = (span.traceFlags & 0xff).toString(16).padStart(2, '0');\n\treturn `00-${span.traceId}-${span.spanId}-${flags}`;\n}\n\n/**\n * Append {@link queryTag} to everything `client` runs: a SQL string — what\n * Kysely sends — or a query object such as a cursor's.\n */\nfunction tagQueries(client: pg.PoolClient): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst tag = queryTag();\n\t\tif (tag && typeof first === 'string') return query(first + tag, ...rest);\n\t\tif (\n\t\t\ttag &&\n\t\t\tfirst &&\n\t\t\ttypeof first === 'object' &&\n\t\t\ttypeof (first as { text?: unknown }).text === 'string'\n\t\t) {\n\t\t\tconst config = first as { text: string };\n\t\t\tconfig.text += tag;\n\t\t}\n\t\treturn query(first, ...rest);\n\t}) as typeof client.query;\n}\n\n/** What every span of one pool says about where it connects. */\nfunction connectionAttributes(url: URL): Attributes {\n\tconst name = decodeURIComponent(url.pathname.slice(1));\n\treturn {\n\t\t'db.system': 'postgresql',\n\t\t...(name && { 'db.name': name }),\n\t\t'server.address': url.hostname,\n\t\t'server.port': Number(url.port || 5432),\n\t};\n}\n\nconst TRACER = '@geekmidas/constructs';\n\n/** `select`, `insert`, … — the first keyword, or `with`'s main statement. */\nconst OPERATION = /^\\s*(?:\\/\\*.*?\\*\\/\\s*)?(\\w+)/s;\n/** The table a statement is about, when it names one where it usually does. */\nconst TABLE =\n\t/\\b(?:from|into|update|join)\\s+((?:\"[^\"]+\"|[\\w$]+)(?:\\.(?:\"[^\"]+\"|[\\w$]+))?)/i;\n\n/**\n * What a statement does and to which table, parsed cheaply from its first\n * characters: `select \"orders\"`, `insert \"public\".\"users\"`. A statement the\n * patterns cannot read leaves `table` unset.\n */\nexport function describeStatement(text: string): {\n\toperation?: string;\n\ttable?: string;\n} {\n\tconst head = text.length > 512 ? text.slice(0, 512) : text;\n\tconst operation = OPERATION.exec(head)?.[1]?.toLowerCase();\n\tconst table = TABLE.exec(head)?.[1]?.replace(/\"/g, '');\n\treturn {\n\t\t...(operation && { operation }),\n\t\t...(table && { table }),\n\t};\n}\n\n/**\n * A span around everything `client` runs — `<operation> <table>`, or\n * `db.query` when the statement does not say.\n *\n * The statement text goes on the span only for a parameterized query — values\n * passed separately, as Kysely always does — and truncated. Parameter values\n * never do. A bare string with no values could hold anything a caller\n * concatenated into it, so it gets no text at all.\n */\nfunction traceQueries(client: pg.PoolClient, connection: Attributes): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst config =\n\t\t\tfirst && typeof first === 'object'\n\t\t\t\t? (first as { text?: unknown; values?: unknown })\n\t\t\t\t: undefined;\n\t\tconst text =\n\t\t\ttypeof first === 'string'\n\t\t\t\t? first\n\t\t\t\t: typeof config?.text === 'string'\n\t\t\t\t\t? config.text\n\t\t\t\t\t: undefined;\n\t\tconst values = typeof first === 'string' ? rest[0] : config?.values;\n\t\tconst { operation, table } = text ? describeStatement(text) : {};\n\n\t\tconst span = trace\n\t\t\t.getTracer(TRACER)\n\t\t\t.startSpan(operation && table ? `${operation} ${table}` : 'db.query', {\n\t\t\t\tkind: SpanKind.CLIENT,\n\t\t\t\tattributes: {\n\t\t\t\t\t...connection,\n\t\t\t\t\t...(operation && { 'db.operation': operation }),\n\t\t\t\t\t...(table && { 'db.sql.table': table }),\n\t\t\t\t\t...(text &&\n\t\t\t\t\t\tArray.isArray(values) && {\n\t\t\t\t\t\t\t'db.statement':\n\t\t\t\t\t\t\t\ttext.length > MAX_STATEMENT_LENGTH\n\t\t\t\t\t\t\t\t\t? text.slice(0, MAX_STATEMENT_LENGTH)\n\t\t\t\t\t\t\t\t\t: text,\n\t\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\tconst end = (error?: unknown) => {\n\t\t\tif (error) {\n\t\t\t\tif (error instanceof Error) span.recordException(error);\n\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\t}\n\t\t\tspan.end();\n\t\t};\n\n\t\t// A callback, as `pg`'s own callback form takes it.\n\t\tconst last = rest[rest.length - 1];\n\t\tif (typeof last === 'function') {\n\t\t\tconst args = [...rest];\n\t\t\targs[args.length - 1] = (error: unknown, ...result: unknown[]) => {\n\t\t\t\tend(error);\n\t\t\t\treturn (last as (...a: unknown[]) => unknown)(error, ...result);\n\t\t\t};\n\t\t\treturn context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...args),\n\t\t\t);\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...rest),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tend(error);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (result && typeof (result as Promise<unknown>).then === 'function') {\n\t\t\t(result as Promise<unknown>).then(\n\t\t\t\t() => end(),\n\t\t\t\t(error) => end(error),\n\t\t\t);\n\t\t} else if (\n\t\t\tresult &&\n\t\t\ttypeof (result as { once?: unknown }).once === 'function'\n\t\t) {\n\t\t\t// A submittable — a cursor or a stream — ends when it says so.\n\t\t\tconst emitter = result as {\n\t\t\t\tonce(event: string, fn: (e?: unknown) => void): void;\n\t\t\t};\n\t\t\temitter.once('end', () => end());\n\t\t\temitter.once('error', (error) => end(error));\n\t\t} else {\n\t\t\tend();\n\t\t}\n\t\treturn result;\n\t}) as typeof client.query;\n}\n","/**\n * `KyselyDatabase` — a declared Postgres database, typed by its schema.\n *\n * It declares a logical database, a schema, and the roles that reach it, and\n * hands back a Kysely client built from the one URL the adapter supplied. It\n * names no cloud: `--target=aws` fills that URL from RDS, local dev from the\n * Postgres container.\n *\n * Transactions and RLS context are the execution wrapper's job — it opens the\n * transaction and puts it in `ctx.db`. This construct does not resolve\n * connections per request.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\tDEFAULT_POSTGRES_VERSION,\n\ttype Declaration,\n\ttype PostgresVersion,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport { Cache } from '../cache';\nimport type { Construct } from '../construct-interface';\nimport { openPool } from './pool';\n\n/** The schema a database uses when it does not say otherwise. */\nconst DEFAULT_SCHEMA = 'app';\n\n/**\n * Everything Kysely takes, plus what this construct declares.\n *\n * `dialect` is omitted because the construct owns it — it is built from the one\n * URL the adapter supplied, which is the whole point of the construct. The rest\n * of `KyselyConfig` (`plugins`, `log`) is the app's business and passes\n * straight through, so anything Kysely adds later arrives without being\n * tracked here.\n */\ninterface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {\n\t/**\n\t * The schema, pinned on both roles' `search_path`.\n\t *\n\t * Defaults to `app` — the role the schema plays, rather than a restatement\n\t * of the database's own name, which reads correctly beside `auth` and\n\t * `pgboss` and keeps the application off `public`.\n\t */\n\tschema?: string;\n\t/**\n\t * Provision the owner/runtime role split. Defaults to on.\n\t *\n\t * Off falls back to the cluster's master credential in both URLs, giving up\n\t * the DDL/DML split and putting credentials that can drop a table into the\n\t * function's environment. A deliberate downgrade, not a default.\n\t */\n\troles?: boolean;\n\t/**\n\t * Extensions to create in this database, as the cluster master, before the\n\t * first migration — for the ones the owner role a migration runs as may not\n\t * create (PostGIS is not a trusted extension). They are created in\n\t * `public`, which joins this database's roles' `search_path`, so their\n\t * types resolve unqualified.\n\t *\n\t * Only attempted: the image has to ship them.\n\t */\n\textensions?: readonly string[];\n\t/**\n\t * End each query run inside a request with a comment naming what ran it —\n\t * `/*operation='POST /orders',request_id='…'*\\/` — so `pg_stat_activity`\n\t * and the server's logs can tie a query to its endpoint — and, inside a\n\t * trace, with the query span's `traceparent='00-…'` (sqlcommenter), so\n\t * they can tie it to the trace too. Defaults to on.\n\t */\n\tqueryTags?: boolean;\n}\n\n/**\n * Which Postgres the database runs: a major on the stock image, or an image\n * the project names. One or the other, never both — an image's tag already\n * says its major, and a second statement of it is one that can disagree.\n */\nexport type PostgresRuntime =\n\t| {\n\t\t\t/**\n\t\t\t * The Postgres major version to provision, on the stock image.\n\t\t\t *\n\t\t\t * Declared here so one statement reaches every target. It was\n\t\t\t * previously set in two unrelated places — a container tag locally,\n\t\t\t * nothing at all on AWS (so Aurora chose) — which is how local came to\n\t\t\t * run 18 while deployed ran 17.7 with nothing recording the difference.\n\t\t\t *\n\t\t\t * Defaults to {@link DEFAULT_POSTGRES_VERSION}.\n\t\t\t */\n\t\t\tversion?: PostgresVersion;\n\t\t\timage?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * The container image to run, wherever a target runs one (local,\n\t\t\t * Dokploy), in place of `postgres:<version>`.\n\t\t\t *\n\t\t\t * Yours to choose and used as written: its tag is the major, and it\n\t\t\t * should ship every extension in `extensions`. AWS runs RDS, ignores\n\t\t\t * it, and provisions {@link DEFAULT_POSTGRES_VERSION}.\n\t\t\t *\n\t\t\t * ```ts\n\t\t\t * new KyselyDatabase<GeoDB>('Geo', {\n\t\t\t * image: 'postgis/postgis:18-3.6-alpine',\n\t\t\t * extensions: ['postgis'],\n\t\t\t * });\n\t\t\t * ```\n\t\t\t */\n\t\t\timage: string;\n\t\t\tversion?: never;\n\t };\n\n/**\n * Everything Kysely takes, plus what this construct declares — including\n * which Postgres it runs, as a version or an image.\n */\nexport type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;\n\n/** What makes a construct derived rather than a database in its own right. */\ninterface DerivedFrom {\n\tkind: 'database-reader' | 'database-schema';\n\tof: string;\n\tschema?: string;\n}\n\n/**\n * @typeParam DB - the database's schema.\n * @typeParam TName - the canonical id. TypeScript has no partial\n * type-argument inference, so `new KyselyDatabase<OrdersDB>('orders')` leaves\n * this at its default and the service key widens to `string`. `db` still types\n * through `.database()`, which infers from the service.\n */\nexport class KyselyDatabase<DB = unknown, TName extends string = string>\n\timplements Construct<TName, Kysely<DB>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Kysely<DB>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * build publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly config: { url: string; ownerUrl: string };\n\tprivate readonly schemaName: string;\n\tprivate readonly roles: boolean;\n\tprivate readonly runtime: { version: PostgresVersion } | { image: string };\n\tprivate readonly derivedFrom?: DerivedFrom;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: KyselyDatabaseOptions = {},\n\t\tderivedFrom?: DerivedFrom,\n\t) {\n\t\t// Canonicalises, so `orders` and `Orders` are one construct rather than\n\t\t// two that collide. Throws on anything that cannot become a valid id.\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.config = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\townerUrl: provideKey(canonical, 'ownerUrl'),\n\t\t};\n\t\tthis.schemaName = options.schema ?? DEFAULT_SCHEMA;\n\t\tthis.roles = options.roles ?? true;\n\t\tthis.runtime = options.image\n\t\t\t? { image: options.image }\n\t\t\t: { version: options.version ?? DEFAULT_POSTGRES_VERSION };\n\t\tthis.derivedFrom = derivedFrom;\n\n\t\t// A field, not a getter: service discovery caches by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\t// Forwards the whole options object — cherry-picking `envParser` strips\n\t\t\t// `context` and with it the request-scoped logger.\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\tif (this.derivedFrom) {\n\t\t\tconst { kind, of, schema } = this.derivedFrom;\n\n\t\t\treturn [\n\t\t\t\tkind === 'database-schema'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\tschema: schema ?? this.schemaName,\n\t\t\t\t\t\t\t// The runtime key alone — see the note below on the parent.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\t// A reader has no owner URL of its own — nothing migrates\n\t\t\t\t\t\t\t// through a read-only endpoint — so this is the one key.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'database',\n\t\t\t\tid: this.id,\n\t\t\t\tengine: 'postgres',\n\t\t\t\tschema: this.schemaName,\n\t\t\t\t// A version, or the image whose tag is one — never both.\n\t\t\t\t...this.runtime,\n\t\t\t\t...(this.options.extensions?.length\n\t\t\t\t\t? { extensions: [...new Set(this.options.extensions)] }\n\t\t\t\t\t: {}),\n\t\t\t\t// One key, the runtime role's. `config` also holds the owner key and\n\t\t\t\t// it is deliberately absent here: a key in `provides` is a key an\n\t\t\t\t// edge can name, and nothing should be able to depend on DDL rights.\n\t\t\t\tprovides: [this.config.url],\n\t\t\t\t...(this.roles ? {} : { roles: false }),\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * A read-only endpoint on this database.\n\t *\n\t * Read-only is enforced by the reader role's grants rather than by which\n\t * endpoint it resolves to, so falling back to the writer where no replica\n\t * exists is safe rather than a silently writable connection behind a name\n\t * that says reader. There is no `writer()`: the database *is* the writer, so\n\t * a replica can never be reached by accident.\n\t *\n\t * The return type is narrower than this class on purpose — see\n\t * {@link ReadOnlyDatabase}.\n\t */\n\treader(): ReadOnlyDatabase<DB, `${TName}Reader`> {\n\t\treturn new KyselyDatabase<DB, `${TName}Reader`>(\n\t\t\t`${this.id}Reader` as ConstructName<`${TName}Reader`>,\n\t\t\tthis.options,\n\t\t\t{ kind: 'database-reader', of: this.id },\n\t\t);\n\t}\n\n\t/**\n\t * A cache that lives in this database.\n\t *\n\t * The same strengthening `schema()` is over a second database: `new\n\t * Cache('Sessions')` says the app caches and leaves where to the deployment,\n\t * while this says it caches *here* — which is a fact about the application\n\t * and so belongs in its code.\n\t *\n\t * What it buys beyond being explicit: the table's schema and the role that\n\t * reaches it come from this database rather than from a second convention,\n\t * and the backend no longer has to guess which database \"the database\" meant\n\t * when an app declares two.\n\t *\n\t * A cache is not a `KyselyDatabase`, so this returns a `Cache` — the client\n\t * is a key/value store, not a query builder, and typing it as the parent\n\t * would be a convenience that lies.\n\t */\n\tcache<TCache extends string = `${TName}Cache`>(\n\t\tid: ConstructName<TCache> = `${this.id}Cache` as ConstructName<TCache>,\n\t\toptions: { table?: string } = {},\n\t): Cache<TCache> {\n\t\treturn new Cache<TCache>(id, {\n\t\t\tof: this.id,\n\t\t\t...(options.table ? { table: options.table } : {}),\n\t\t});\n\t}\n\n\t/**\n\t * A second schema in this database, with its own role(s) and its own URL —\n\t * so this database's role holds no grant on those tables at all.\n\t *\n\t * Typed separately because a tenant has its own tables: the point is that\n\t * `auth.selectFrom('sessions')` type-checks and\n\t * `orders.selectFrom('sessions')` does not.\n\t */\n\tschema<TenantDB, TTenant extends string>(\n\t\tid: ConstructName<TTenant>,\n\t\toptions: KyselyDatabaseOptions = {},\n\t): KyselyDatabase<TenantDB, TTenant> {\n\t\treturn new KyselyDatabase<TenantDB, TTenant>(id, options, {\n\t\t\tkind: 'database-schema',\n\t\t\tof: this.id,\n\t\t\tschema: options.schema ?? (id as string).toLowerCase(),\n\t\t});\n\t}\n\n\t/**\n\t * A connection as the *owner* role — the one that may create, alter and drop.\n\t *\n\t * A `Service` rather than part of the construct, and deliberately so: a\n\t * construct is what `.dependsOn()` accepts, and nothing should be able to\n\t * depend on this. A migrator asks for it by name; a handler cannot ask at\n\t * all, which is the security property the role split exists for.\n\t *\n\t * Falls back to the runtime URL where no owner URL was published — the\n\t * `roles: false` downgrade, where there is only one credential and it is the\n\t * cluster master's.\n\t */\n\tget owner(): Service<`${Uncapitalize<TName>}Owner`, Kysely<DB>> {\n\t\treturn {\n\t\t\tserviceName:\n\t\t\t\t`${serviceKey(this.id)}Owner` as `${Uncapitalize<TName>}Owner`,\n\t\t\tregister: (options) => this.connect(options, { owner: true }),\n\t\t};\n\t}\n\n\t/**\n\t * What this database's clients are built with besides a dialect — the\n\t * plugins and logging it was declared with.\n\t *\n\t * Public so that whatever opens a connection to it outside \\`connect()\\` —\n\t * a test's transaction — builds the same client. A test that dropped the\n\t * plugins would write \\`createdAt\\` where production writes \\`created_at\\`.\n\t */\n\tget clientConfig(): Omit<KyselyConfig, 'dialect'> {\n\t\t// Split what the manifest declared from what Kysely takes, so neither\n\t\t// leaks into the other: \\`schema\\`, \\`roles\\`, \\`version\\`, \\`image\\`\n\t\t// and \\`extensions\\` describe infrastructure and mean nothing to a\n\t\t// client.\n\t\tconst {\n\t\t\tschema: _schema,\n\t\t\troles: _roles,\n\t\t\tversion: _version,\n\t\t\timage: _image,\n\t\t\textensions: _extensions,\n\t\t\tqueryTags: _queryTags,\n\t\t\t...kysely\n\t\t} = this.options;\n\t\treturn kysely;\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Kysely<DB>> {\n\t\tconst { url, ownerUrl } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\turl: get(this.config.url).string(),\n\t\t\t\townerUrl: get(this.config.ownerUrl).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\treturn new Kysely<DB>({\n\t\t\t...this.clientConfig,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\tpool: openPool(as.owner ? (ownerUrl ?? url) : url, {\n\t\t\t\t\tqueryTags: this.options.queryTags,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n}\n\n/**\n * A database you may read through, and derive nothing from.\n *\n * A reader is terminal in the graph, and this is that fact in the type system.\n * `DERIVES_FROM` gives `cache` and `database-schema` no `database-reader`\n * parent, and there is no reader of a reader — so `orders.reader().cache(…)`\n * has always been illegal, but it type-checked and failed later at\n * `assertDerivations`. That is a long way round to learn that a read-only\n * endpoint cannot own a table something writes to.\n *\n * Omitting the three deriving methods rather than making them throw keeps the\n * failure where it costs least: the editor, on the line that wrote it.\n *\n * The result is deliberately not assignable back to `KyselyDatabase` — a\n * reader is not one, and anything that genuinely needs the writer should say\n * so.\n */\nexport type ReadOnlyDatabase<DB, TName extends string> = Omit<\n\tKyselyDatabase<DB, TName>,\n\t'reader' | 'cache' | 'schema'\n>;\nexport { closeDatabasePools } from './pool';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/constructs",
3
- "version": "10.0.0-alpha.117",
3
+ "version": "10.0.0-alpha.118",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -307,13 +307,13 @@
307
307
  "lodash.uniqby": "~4.7.0",
308
308
  "openapi-types": "~12.1.3",
309
309
  "qs": "~6.16.0",
310
- "@geekmidas/envkit": "^10.0.0-alpha.117",
311
- "@geekmidas/errors": "^10.0.0-alpha.117",
312
- "@geekmidas/events": "^10.0.0-alpha.117",
313
- "@geekmidas/logger": "^10.0.0-alpha.117",
314
- "@geekmidas/manifest": "^10.0.0-alpha.117",
315
- "@geekmidas/schema": "^10.0.0-alpha.117",
316
- "@geekmidas/services": "^10.0.0-alpha.117"
310
+ "@geekmidas/envkit": "^10.0.0-alpha.118",
311
+ "@geekmidas/errors": "^10.0.0-alpha.118",
312
+ "@geekmidas/events": "^10.0.0-alpha.118",
313
+ "@geekmidas/logger": "^10.0.0-alpha.118",
314
+ "@geekmidas/manifest": "^10.0.0-alpha.118",
315
+ "@geekmidas/schema": "^10.0.0-alpha.118",
316
+ "@geekmidas/services": "^10.0.0-alpha.118"
317
317
  },
318
318
  "devDependencies": {
319
319
  "@aws-sdk/client-kms": "~3.1136.0",
@@ -331,20 +331,20 @@
331
331
  "msw": "~2.15.0",
332
332
  "pg": "~8.23.0",
333
333
  "zod": "~4.6.5",
334
- "@geekmidas/audit": "^10.0.0-alpha.117",
335
- "@geekmidas/auth": "10.0.0-alpha.117",
336
- "@geekmidas/cache": "^10.0.0-alpha.117",
337
- "@geekmidas/db": "^10.0.0-alpha.117",
338
- "@geekmidas/emailkit": "^10.0.0-alpha.117",
339
- "@geekmidas/envkit": "^10.0.0-alpha.117",
340
- "@geekmidas/errors": "^10.0.0-alpha.117",
341
- "@geekmidas/events": "^10.0.0-alpha.117",
342
- "@geekmidas/logger": "^10.0.0-alpha.117",
343
- "@geekmidas/rate-limit": "^10.0.0-alpha.117",
344
- "@geekmidas/schema": "^10.0.0-alpha.117",
345
- "@geekmidas/services": "^10.0.0-alpha.117",
346
- "@geekmidas/telescope": "10.0.0-alpha.117",
347
- "@geekmidas/testkit": "^10.0.0-alpha.117"
334
+ "@geekmidas/audit": "^10.0.0-alpha.118",
335
+ "@geekmidas/auth": "10.0.0-alpha.118",
336
+ "@geekmidas/cache": "^10.0.0-alpha.118",
337
+ "@geekmidas/db": "^10.0.0-alpha.118",
338
+ "@geekmidas/emailkit": "^10.0.0-alpha.118",
339
+ "@geekmidas/envkit": "^10.0.0-alpha.118",
340
+ "@geekmidas/errors": "^10.0.0-alpha.118",
341
+ "@geekmidas/events": "^10.0.0-alpha.118",
342
+ "@geekmidas/logger": "^10.0.0-alpha.118",
343
+ "@geekmidas/rate-limit": "^10.0.0-alpha.118",
344
+ "@geekmidas/schema": "^10.0.0-alpha.118",
345
+ "@geekmidas/services": "^10.0.0-alpha.118",
346
+ "@geekmidas/telescope": "10.0.0-alpha.118",
347
+ "@geekmidas/testkit": "^10.0.0-alpha.118"
348
348
  },
349
349
  "peerDependencies": {
350
350
  "@aws-sdk/client-kms": ">=3.1136.0",
@@ -355,15 +355,15 @@
355
355
  "kysely": "~0.29.6",
356
356
  "msw": ">=2.15.0",
357
357
  "pg": ">=8.23.0",
358
- "@geekmidas/audit": "^10.0.0-alpha.117",
359
- "@geekmidas/auth": "10.0.0-alpha.117",
360
- "@geekmidas/cache": "^10.0.0-alpha.117",
361
- "@geekmidas/db": "^10.0.0-alpha.117",
362
- "@geekmidas/emailkit": "^10.0.0-alpha.117",
363
- "@geekmidas/rate-limit": "^10.0.0-alpha.117",
364
- "@geekmidas/storage": "^10.0.0-alpha.117",
365
- "@geekmidas/telescope": "10.0.0-alpha.117",
366
- "@geekmidas/testkit": "^10.0.0-alpha.117"
358
+ "@geekmidas/audit": "^10.0.0-alpha.118",
359
+ "@geekmidas/auth": "10.0.0-alpha.118",
360
+ "@geekmidas/cache": "^10.0.0-alpha.118",
361
+ "@geekmidas/db": "^10.0.0-alpha.118",
362
+ "@geekmidas/emailkit": "^10.0.0-alpha.118",
363
+ "@geekmidas/rate-limit": "^10.0.0-alpha.118",
364
+ "@geekmidas/storage": "^10.0.0-alpha.118",
365
+ "@geekmidas/telescope": "10.0.0-alpha.118",
366
+ "@geekmidas/testkit": "^10.0.0-alpha.118"
367
367
  },
368
368
  "peerDependenciesMeta": {
369
369
  "@aws-sdk/client-kms": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"kysely-B3zfg9ti.cjs","names":["onShutdown","currentRequestContext","trace","isSpanContextValid","SpanKind","SpanStatusCode","context","canonicalId","provideKey","DEFAULT_POSTGRES_VERSION","serviceKey","Cache","kysely","Kysely","PostgresDialect"],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"sourcesContent":["/**\n * The `pg` pools a database construct opens, and what every one of them gets:\n *\n * - **A name.** `application_name` is what `pg_stat_activity` shows beside a\n * connection. Without one, a database at `max_connections` cannot say who\n * holds them. It is a *fallback*, so `PGAPPNAME` or `?application_name=` in\n * the URL still win.\n * - **Tagged queries.** Inside a request, each query ends in a comment naming\n * what ran it — `/*operation='POST /orders',request_id='…'*\\/` — in the\n * sqlcommenter form, and inside a trace the query's own span as\n * `traceparent='00-<trace>-<span>-<flags>'`. It shows in\n * `pg_stat_activity.query`, in the server's slow-query and `auto_explain`\n * logs, and costs no round trip; `pg_stat_statements` ignores comments, so\n * its grouping is unchanged.\n * - **A listener for idle-client errors.** When the server ends an idle\n * connection (`idle_session_timeout`, a failover, an admin), the pool emits\n * `'error'`. With no listener that is an uncaught exception that kills the\n * process — for a client the pool has already discarded.\n * - **A span per query.** Through the global OpenTelemetry tracer, so a no-op\n * without a provider. `pg` is bundled into a production entry, where no\n * instrumentation can hook its module load — so the client is wrapped here,\n * as the tags are. See {@link traceQueries}.\n * - **A way to close.** {@link closeDatabasePools} ends every pool opened, so a\n * server shutting down releases its connections instead of holding them\n * until the process dies.\n */\n\nimport { currentRequestContext } from '@geekmidas/services';\nimport {\n\ttype Attributes,\n\tcontext,\n\tisSpanContextValid,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport pg from 'pg';\nimport { onShutdown } from '../shutdown';\n\nexport interface DatabasePoolOptions {\n\t/** Tag queries with what ran them and the trace they ran in. Default true. */\n\tqueryTags?: boolean;\n}\n\n/** The longest statement text a query span carries. */\nexport const MAX_STATEMENT_LENGTH = 2048;\n\n/** Every pool opened and not yet closed, so a shutdown can end them all. */\nconst open = new Set<pg.Pool>();\nonShutdown(() => closeDatabasePools());\n\n/**\n * A pool for one URL, with its schema actually on the search path.\n *\n * `?search_path=` is not a libpq parameter — a URL carrying it connects\n * happily and then resolves every unqualified name against `public`, which is\n * how a schema tenant silently writes its tables into the database it was\n * separated from. Postgres takes it as a startup option instead, so the URL\n * keeps the readable form the target derives and this turns it into the thing\n * the server understands.\n */\nexport function openPool(\n\turl: string,\n\toptions: DatabasePoolOptions = {},\n): pg.Pool {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tparsed.searchParams.delete('search_path');\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: parsed.toString(),\n\t\t...(searchPath ? { options: `-c search_path=${searchPath}` } : {}),\n\t\tfallback_application_name: applicationName(),\n\t});\n\n\tpool.on('error', (error) => {\n\t\tconst logger = currentRequestContext()?.logger;\n\t\tif (logger) logger.warn({ err: error }, 'Idle database connection lost');\n\t\telse console.warn('Idle database connection lost:', error.message);\n\t});\n\tconst target = connectionAttributes(parsed);\n\tpool.on('connect', (client) => {\n\t\t// Tags first, so the span wraps them: it records the statement as the\n\t\t// application wrote it rather than with the comment on the end, and it\n\t\t// is the active span when the tag is written — so the `traceparent` a\n\t\t// query carries names the query's own span.\n\t\tif (options.queryTags !== false) tagQueries(client);\n\t\ttraceQueries(client, target);\n\t});\n\n\topen.add(pool);\n\tconst end = pool.end.bind(pool);\n\tpool.end = (async () => {\n\t\topen.delete(pool);\n\t\tawait end();\n\t}) as typeof pool.end;\n\n\treturn pool;\n}\n\n/**\n * End every pool opened, waiting for checked-out connections to come back.\n * For a server's shutdown, after it has stopped taking requests.\n */\nexport async function closeDatabasePools(): Promise<void> {\n\tawait Promise.all([...open].map((pool) => pool.end()));\n}\n\n/**\n * Who this process is, as Postgres will show it: the Lambda function, else the\n * app — `GKM_APP_NAME` where an entry sets it, `gkm dev`'s app tag otherwise.\n * Postgres keeps 63 bytes of it.\n */\nexport function applicationName(\n\tenv: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n\treturn (\n\t\tenv.AWS_LAMBDA_FUNCTION_NAME ||\n\t\tenv.GKM_APP_NAME ||\n\t\tenv.GKM_DEV_APP?.split('#').pop() ||\n\t\tundefined\n\t);\n}\n\n/**\n * The comment that says what ran a query: the request's operation and id, and\n * the active span as a W3C `traceparent` — the query's own span when it runs\n * through a traced client, since the span wraps the tagging. '' with neither.\n *\n * The keys are in sqlcommenter's sorted order. Values keep only characters\n * that cannot end a comment or a quoted value — `*` and `'` among those\n * dropped — so a route cannot inject SQL through it.\n */\nexport function queryTag(): string {\n\tconst request = currentRequestContext();\n\tconst span = trace.getActiveSpan()?.spanContext();\n\n\tconst tags = [\n\t\t['operation', request?.operation],\n\t\t['request_id', request?.requestId],\n\t\t['traceparent', span && isSpanContextValid(span) ? traceparent(span) : ''],\n\t].filter((tag): tag is [string, string] => Boolean(tag[1]));\n\tif (tags.length === 0) return '';\n\n\tconst clean = (value: string) => value.replace(/[^\\w ./:{}@-]/g, '');\n\treturn ` /*${tags.map(([key, value]) => `${key}='${clean(value)}'`).join(',')}*/`;\n}\n\n/** A span as W3C trace context's `traceparent`, version 00. */\nfunction traceparent(span: {\n\ttraceId: string;\n\tspanId: string;\n\ttraceFlags: number;\n}): string {\n\tconst flags = (span.traceFlags & 0xff).toString(16).padStart(2, '0');\n\treturn `00-${span.traceId}-${span.spanId}-${flags}`;\n}\n\n/**\n * Append {@link queryTag} to everything `client` runs: a SQL string — what\n * Kysely sends — or a query object such as a cursor's.\n */\nfunction tagQueries(client: pg.PoolClient): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst tag = queryTag();\n\t\tif (tag && typeof first === 'string') return query(first + tag, ...rest);\n\t\tif (\n\t\t\ttag &&\n\t\t\tfirst &&\n\t\t\ttypeof first === 'object' &&\n\t\t\ttypeof (first as { text?: unknown }).text === 'string'\n\t\t) {\n\t\t\tconst config = first as { text: string };\n\t\t\tconfig.text += tag;\n\t\t}\n\t\treturn query(first, ...rest);\n\t}) as typeof client.query;\n}\n\n/** What every span of one pool says about where it connects. */\nfunction connectionAttributes(url: URL): Attributes {\n\tconst name = decodeURIComponent(url.pathname.slice(1));\n\treturn {\n\t\t'db.system': 'postgresql',\n\t\t...(name && { 'db.name': name }),\n\t\t'server.address': url.hostname,\n\t\t'server.port': Number(url.port || 5432),\n\t};\n}\n\nconst TRACER = '@geekmidas/constructs';\n\n/** `select`, `insert`, … — the first keyword, or `with`'s main statement. */\nconst OPERATION = /^\\s*(?:\\/\\*.*?\\*\\/\\s*)?(\\w+)/s;\n/** The table a statement is about, when it names one where it usually does. */\nconst TABLE =\n\t/\\b(?:from|into|update|join)\\s+((?:\"[^\"]+\"|[\\w$]+)(?:\\.(?:\"[^\"]+\"|[\\w$]+))?)/i;\n\n/**\n * What a statement does and to which table, parsed cheaply from its first\n * characters: `select \"orders\"`, `insert \"public\".\"users\"`. A statement the\n * patterns cannot read leaves `table` unset.\n */\nexport function describeStatement(text: string): {\n\toperation?: string;\n\ttable?: string;\n} {\n\tconst head = text.length > 512 ? text.slice(0, 512) : text;\n\tconst operation = OPERATION.exec(head)?.[1]?.toLowerCase();\n\tconst table = TABLE.exec(head)?.[1]?.replace(/\"/g, '');\n\treturn {\n\t\t...(operation && { operation }),\n\t\t...(table && { table }),\n\t};\n}\n\n/**\n * A span around everything `client` runs — `<operation> <table>`, or\n * `db.query` when the statement does not say.\n *\n * The statement text goes on the span only for a parameterized query — values\n * passed separately, as Kysely always does — and truncated. Parameter values\n * never do. A bare string with no values could hold anything a caller\n * concatenated into it, so it gets no text at all.\n */\nfunction traceQueries(client: pg.PoolClient, connection: Attributes): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst config =\n\t\t\tfirst && typeof first === 'object'\n\t\t\t\t? (first as { text?: unknown; values?: unknown })\n\t\t\t\t: undefined;\n\t\tconst text =\n\t\t\ttypeof first === 'string'\n\t\t\t\t? first\n\t\t\t\t: typeof config?.text === 'string'\n\t\t\t\t\t? config.text\n\t\t\t\t\t: undefined;\n\t\tconst values = typeof first === 'string' ? rest[0] : config?.values;\n\t\tconst { operation, table } = text ? describeStatement(text) : {};\n\n\t\tconst span = trace\n\t\t\t.getTracer(TRACER)\n\t\t\t.startSpan(operation && table ? `${operation} ${table}` : 'db.query', {\n\t\t\t\tkind: SpanKind.CLIENT,\n\t\t\t\tattributes: {\n\t\t\t\t\t...connection,\n\t\t\t\t\t...(operation && { 'db.operation': operation }),\n\t\t\t\t\t...(table && { 'db.sql.table': table }),\n\t\t\t\t\t...(text &&\n\t\t\t\t\t\tArray.isArray(values) && {\n\t\t\t\t\t\t\t'db.statement':\n\t\t\t\t\t\t\t\ttext.length > MAX_STATEMENT_LENGTH\n\t\t\t\t\t\t\t\t\t? text.slice(0, MAX_STATEMENT_LENGTH)\n\t\t\t\t\t\t\t\t\t: text,\n\t\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\tconst end = (error?: unknown) => {\n\t\t\tif (error) {\n\t\t\t\tif (error instanceof Error) span.recordException(error);\n\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\t}\n\t\t\tspan.end();\n\t\t};\n\n\t\t// A callback, as `pg`'s own callback form takes it.\n\t\tconst last = rest[rest.length - 1];\n\t\tif (typeof last === 'function') {\n\t\t\tconst args = [...rest];\n\t\t\targs[args.length - 1] = (error: unknown, ...result: unknown[]) => {\n\t\t\t\tend(error);\n\t\t\t\treturn (last as (...a: unknown[]) => unknown)(error, ...result);\n\t\t\t};\n\t\t\treturn context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...args),\n\t\t\t);\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...rest),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tend(error);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (result && typeof (result as Promise<unknown>).then === 'function') {\n\t\t\t(result as Promise<unknown>).then(\n\t\t\t\t() => end(),\n\t\t\t\t(error) => end(error),\n\t\t\t);\n\t\t} else if (\n\t\t\tresult &&\n\t\t\ttypeof (result as { once?: unknown }).once === 'function'\n\t\t) {\n\t\t\t// A submittable — a cursor or a stream — ends when it says so.\n\t\t\tconst emitter = result as {\n\t\t\t\tonce(event: string, fn: (e?: unknown) => void): void;\n\t\t\t};\n\t\t\temitter.once('end', () => end());\n\t\t\temitter.once('error', (error) => end(error));\n\t\t} else {\n\t\t\tend();\n\t\t}\n\t\treturn result;\n\t}) as typeof client.query;\n}\n","/**\n * `KyselyDatabase` — a declared Postgres database, typed by its schema.\n *\n * It declares a logical database, a schema, and the roles that reach it, and\n * hands back a Kysely client built from the one URL the adapter supplied. It\n * names no cloud: `--target=aws` fills that URL from RDS, local dev from the\n * Postgres container.\n *\n * Transactions and RLS context are the execution wrapper's job — it opens the\n * transaction and puts it in `ctx.db`. This construct does not resolve\n * connections per request.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\tDEFAULT_POSTGRES_VERSION,\n\ttype Declaration,\n\ttype PostgresVersion,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport { Cache } from '../cache';\nimport type { Construct } from '../construct-interface';\nimport { openPool } from './pool';\n\n/** The schema a database uses when it does not say otherwise. */\nconst DEFAULT_SCHEMA = 'app';\n\n/**\n * Everything Kysely takes, plus what this construct declares.\n *\n * `dialect` is omitted because the construct owns it — it is built from the one\n * URL the adapter supplied, which is the whole point of the construct. The rest\n * of `KyselyConfig` (`plugins`, `log`) is the app's business and passes\n * straight through, so anything Kysely adds later arrives without being\n * tracked here.\n */\nexport interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {\n\t/**\n\t * The schema, pinned on both roles' `search_path`.\n\t *\n\t * Defaults to `app` — the role the schema plays, rather than a restatement\n\t * of the database's own name, which reads correctly beside `auth` and\n\t * `pgboss` and keeps the application off `public`.\n\t */\n\tschema?: string;\n\t/**\n\t * Provision the owner/runtime role split. Defaults to on.\n\t *\n\t * Off falls back to the cluster's master credential in both URLs, giving up\n\t * the DDL/DML split and putting credentials that can drop a table into the\n\t * function's environment. A deliberate downgrade, not a default.\n\t */\n\troles?: boolean;\n\t/**\n\t * The Postgres major version to provision.\n\t *\n\t * Declared here so one statement reaches both targets. It was previously\n\t * set in two unrelated places — a container tag locally, nothing at all on\n\t * AWS (so Aurora chose) — which is how local came to run 18 while deployed\n\t * ran 17.7 with nothing recording the difference.\n\t *\n\t * Defaults to {@link DEFAULT_POSTGRES_VERSION}.\n\t */\n\tversion?: PostgresVersion;\n\t/**\n\t * End each query run inside a request with a comment naming what ran it —\n\t * `/*operation='POST /orders',request_id='…'*\\/` — so `pg_stat_activity`\n\t * and the server's logs can tie a query to its endpoint — and, inside a\n\t * trace, with the query span's `traceparent='00-…'` (sqlcommenter), so\n\t * they can tie it to the trace too. Defaults to on.\n\t */\n\tqueryTags?: boolean;\n}\n\n/** What makes a construct derived rather than a database in its own right. */\ninterface DerivedFrom {\n\tkind: 'database-reader' | 'database-schema';\n\tof: string;\n\tschema?: string;\n}\n\n/**\n * @typeParam DB - the database's schema.\n * @typeParam TName - the canonical id. TypeScript has no partial\n * type-argument inference, so `new KyselyDatabase<OrdersDB>('orders')` leaves\n * this at its default and the service key widens to `string`. `db` still types\n * through `.database()`, which infers from the service.\n */\nexport class KyselyDatabase<DB = unknown, TName extends string = string>\n\timplements Construct<TName, Kysely<DB>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Kysely<DB>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * build publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly config: { url: string; ownerUrl: string };\n\tprivate readonly schemaName: string;\n\tprivate readonly roles: boolean;\n\tprivate readonly version: PostgresVersion;\n\tprivate readonly derivedFrom?: DerivedFrom;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: KyselyDatabaseOptions = {},\n\t\tderivedFrom?: DerivedFrom,\n\t) {\n\t\t// Canonicalises, so `orders` and `Orders` are one construct rather than\n\t\t// two that collide. Throws on anything that cannot become a valid id.\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.config = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\townerUrl: provideKey(canonical, 'ownerUrl'),\n\t\t};\n\t\tthis.schemaName = options.schema ?? DEFAULT_SCHEMA;\n\t\tthis.roles = options.roles ?? true;\n\t\tthis.version = options.version ?? DEFAULT_POSTGRES_VERSION;\n\t\tthis.derivedFrom = derivedFrom;\n\n\t\t// A field, not a getter: service discovery caches by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\t// Forwards the whole options object — cherry-picking `envParser` strips\n\t\t\t// `context` and with it the request-scoped logger.\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\tif (this.derivedFrom) {\n\t\t\tconst { kind, of, schema } = this.derivedFrom;\n\n\t\t\treturn [\n\t\t\t\tkind === 'database-schema'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\tschema: schema ?? this.schemaName,\n\t\t\t\t\t\t\t// The runtime key alone — see the note below on the parent.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\t// A reader has no owner URL of its own — nothing migrates\n\t\t\t\t\t\t\t// through a read-only endpoint — so this is the one key.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'database',\n\t\t\t\tid: this.id,\n\t\t\t\tengine: 'postgres',\n\t\t\t\tschema: this.schemaName,\n\t\t\t\tversion: this.version,\n\t\t\t\t// One key, the runtime role's. `config` also holds the owner key and\n\t\t\t\t// it is deliberately absent here: a key in `provides` is a key an\n\t\t\t\t// edge can name, and nothing should be able to depend on DDL rights.\n\t\t\t\tprovides: [this.config.url],\n\t\t\t\t...(this.roles ? {} : { roles: false }),\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * A read-only endpoint on this database.\n\t *\n\t * Read-only is enforced by the reader role's grants rather than by which\n\t * endpoint it resolves to, so falling back to the writer where no replica\n\t * exists is safe rather than a silently writable connection behind a name\n\t * that says reader. There is no `writer()`: the database *is* the writer, so\n\t * a replica can never be reached by accident.\n\t *\n\t * The return type is narrower than this class on purpose — see\n\t * {@link ReadOnlyDatabase}.\n\t */\n\treader(): ReadOnlyDatabase<DB, `${TName}Reader`> {\n\t\treturn new KyselyDatabase<DB, `${TName}Reader`>(\n\t\t\t`${this.id}Reader` as ConstructName<`${TName}Reader`>,\n\t\t\tthis.options,\n\t\t\t{ kind: 'database-reader', of: this.id },\n\t\t);\n\t}\n\n\t/**\n\t * A cache that lives in this database.\n\t *\n\t * The same strengthening `schema()` is over a second database: `new\n\t * Cache('Sessions')` says the app caches and leaves where to the deployment,\n\t * while this says it caches *here* — which is a fact about the application\n\t * and so belongs in its code.\n\t *\n\t * What it buys beyond being explicit: the table's schema and the role that\n\t * reaches it come from this database rather than from a second convention,\n\t * and the backend no longer has to guess which database \"the database\" meant\n\t * when an app declares two.\n\t *\n\t * A cache is not a `KyselyDatabase`, so this returns a `Cache` — the client\n\t * is a key/value store, not a query builder, and typing it as the parent\n\t * would be a convenience that lies.\n\t */\n\tcache<TCache extends string = `${TName}Cache`>(\n\t\tid: ConstructName<TCache> = `${this.id}Cache` as ConstructName<TCache>,\n\t\toptions: { table?: string } = {},\n\t): Cache<TCache> {\n\t\treturn new Cache<TCache>(id, {\n\t\t\tof: this.id,\n\t\t\t...(options.table ? { table: options.table } : {}),\n\t\t});\n\t}\n\n\t/**\n\t * A second schema in this database, with its own role(s) and its own URL —\n\t * so this database's role holds no grant on those tables at all.\n\t *\n\t * Typed separately because a tenant has its own tables: the point is that\n\t * `auth.selectFrom('sessions')` type-checks and\n\t * `orders.selectFrom('sessions')` does not.\n\t */\n\tschema<TenantDB, TTenant extends string>(\n\t\tid: ConstructName<TTenant>,\n\t\toptions: KyselyDatabaseOptions = {},\n\t): KyselyDatabase<TenantDB, TTenant> {\n\t\treturn new KyselyDatabase<TenantDB, TTenant>(id, options, {\n\t\t\tkind: 'database-schema',\n\t\t\tof: this.id,\n\t\t\tschema: options.schema ?? (id as string).toLowerCase(),\n\t\t});\n\t}\n\n\t/**\n\t * A connection as the *owner* role — the one that may create, alter and drop.\n\t *\n\t * A `Service` rather than part of the construct, and deliberately so: a\n\t * construct is what `.dependsOn()` accepts, and nothing should be able to\n\t * depend on this. A migrator asks for it by name; a handler cannot ask at\n\t * all, which is the security property the role split exists for.\n\t *\n\t * Falls back to the runtime URL where no owner URL was published — the\n\t * `roles: false` downgrade, where there is only one credential and it is the\n\t * cluster master's.\n\t */\n\tget owner(): Service<`${Uncapitalize<TName>}Owner`, Kysely<DB>> {\n\t\treturn {\n\t\t\tserviceName:\n\t\t\t\t`${serviceKey(this.id)}Owner` as `${Uncapitalize<TName>}Owner`,\n\t\t\tregister: (options) => this.connect(options, { owner: true }),\n\t\t};\n\t}\n\n\t/**\n\t * What this database's clients are built with besides a dialect — the\n\t * plugins and logging it was declared with.\n\t *\n\t * Public so that whatever opens a connection to it outside \\`connect()\\` —\n\t * a test's transaction — builds the same client. A test that dropped the\n\t * plugins would write \\`createdAt\\` where production writes \\`created_at\\`.\n\t */\n\tget clientConfig(): Omit<KyselyConfig, 'dialect'> {\n\t\t// Split what the manifest declared from what Kysely takes, so neither\n\t\t// leaks into the other: \\`schema\\`, \\`roles\\` and \\`version\\` describe\n\t\t// infrastructure and mean nothing to a client.\n\t\tconst {\n\t\t\tschema: _schema,\n\t\t\troles: _roles,\n\t\t\tversion: _version,\n\t\t\tqueryTags: _queryTags,\n\t\t\t...kysely\n\t\t} = this.options;\n\t\treturn kysely;\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Kysely<DB>> {\n\t\tconst { url, ownerUrl } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\turl: get(this.config.url).string(),\n\t\t\t\townerUrl: get(this.config.ownerUrl).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\treturn new Kysely<DB>({\n\t\t\t...this.clientConfig,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\tpool: openPool(as.owner ? (ownerUrl ?? url) : url, {\n\t\t\t\t\tqueryTags: this.options.queryTags,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n}\n\n/**\n * A database you may read through, and derive nothing from.\n *\n * A reader is terminal in the graph, and this is that fact in the type system.\n * `DERIVES_FROM` gives `cache` and `database-schema` no `database-reader`\n * parent, and there is no reader of a reader — so `orders.reader().cache(…)`\n * has always been illegal, but it type-checked and failed later at\n * `assertDerivations`. That is a long way round to learn that a read-only\n * endpoint cannot own a table something writes to.\n *\n * Omitting the three deriving methods rather than making them throw keeps the\n * failure where it costs least: the editor, on the line that wrote it.\n *\n * The result is deliberately not assignable back to `KyselyDatabase` — a\n * reader is not one, and anything that genuinely needs the writer should say\n * so.\n */\nexport type ReadOnlyDatabase<DB, TName extends string> = Omit<\n\tKyselyDatabase<DB, TName>,\n\t'reader' | 'cache' | 'schema'\n>;\nexport { closeDatabasePools } from './pool';\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"kysely-B_0BAr0T.mjs","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"sourcesContent":["/**\n * The `pg` pools a database construct opens, and what every one of them gets:\n *\n * - **A name.** `application_name` is what `pg_stat_activity` shows beside a\n * connection. Without one, a database at `max_connections` cannot say who\n * holds them. It is a *fallback*, so `PGAPPNAME` or `?application_name=` in\n * the URL still win.\n * - **Tagged queries.** Inside a request, each query ends in a comment naming\n * what ran it — `/*operation='POST /orders',request_id='…'*\\/` — in the\n * sqlcommenter form, and inside a trace the query's own span as\n * `traceparent='00-<trace>-<span>-<flags>'`. It shows in\n * `pg_stat_activity.query`, in the server's slow-query and `auto_explain`\n * logs, and costs no round trip; `pg_stat_statements` ignores comments, so\n * its grouping is unchanged.\n * - **A listener for idle-client errors.** When the server ends an idle\n * connection (`idle_session_timeout`, a failover, an admin), the pool emits\n * `'error'`. With no listener that is an uncaught exception that kills the\n * process — for a client the pool has already discarded.\n * - **A span per query.** Through the global OpenTelemetry tracer, so a no-op\n * without a provider. `pg` is bundled into a production entry, where no\n * instrumentation can hook its module load — so the client is wrapped here,\n * as the tags are. See {@link traceQueries}.\n * - **A way to close.** {@link closeDatabasePools} ends every pool opened, so a\n * server shutting down releases its connections instead of holding them\n * until the process dies.\n */\n\nimport { currentRequestContext } from '@geekmidas/services';\nimport {\n\ttype Attributes,\n\tcontext,\n\tisSpanContextValid,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport pg from 'pg';\nimport { onShutdown } from '../shutdown';\n\nexport interface DatabasePoolOptions {\n\t/** Tag queries with what ran them and the trace they ran in. Default true. */\n\tqueryTags?: boolean;\n}\n\n/** The longest statement text a query span carries. */\nexport const MAX_STATEMENT_LENGTH = 2048;\n\n/** Every pool opened and not yet closed, so a shutdown can end them all. */\nconst open = new Set<pg.Pool>();\nonShutdown(() => closeDatabasePools());\n\n/**\n * A pool for one URL, with its schema actually on the search path.\n *\n * `?search_path=` is not a libpq parameter — a URL carrying it connects\n * happily and then resolves every unqualified name against `public`, which is\n * how a schema tenant silently writes its tables into the database it was\n * separated from. Postgres takes it as a startup option instead, so the URL\n * keeps the readable form the target derives and this turns it into the thing\n * the server understands.\n */\nexport function openPool(\n\turl: string,\n\toptions: DatabasePoolOptions = {},\n): pg.Pool {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tparsed.searchParams.delete('search_path');\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: parsed.toString(),\n\t\t...(searchPath ? { options: `-c search_path=${searchPath}` } : {}),\n\t\tfallback_application_name: applicationName(),\n\t});\n\n\tpool.on('error', (error) => {\n\t\tconst logger = currentRequestContext()?.logger;\n\t\tif (logger) logger.warn({ err: error }, 'Idle database connection lost');\n\t\telse console.warn('Idle database connection lost:', error.message);\n\t});\n\tconst target = connectionAttributes(parsed);\n\tpool.on('connect', (client) => {\n\t\t// Tags first, so the span wraps them: it records the statement as the\n\t\t// application wrote it rather than with the comment on the end, and it\n\t\t// is the active span when the tag is written — so the `traceparent` a\n\t\t// query carries names the query's own span.\n\t\tif (options.queryTags !== false) tagQueries(client);\n\t\ttraceQueries(client, target);\n\t});\n\n\topen.add(pool);\n\tconst end = pool.end.bind(pool);\n\tpool.end = (async () => {\n\t\topen.delete(pool);\n\t\tawait end();\n\t}) as typeof pool.end;\n\n\treturn pool;\n}\n\n/**\n * End every pool opened, waiting for checked-out connections to come back.\n * For a server's shutdown, after it has stopped taking requests.\n */\nexport async function closeDatabasePools(): Promise<void> {\n\tawait Promise.all([...open].map((pool) => pool.end()));\n}\n\n/**\n * Who this process is, as Postgres will show it: the Lambda function, else the\n * app — `GKM_APP_NAME` where an entry sets it, `gkm dev`'s app tag otherwise.\n * Postgres keeps 63 bytes of it.\n */\nexport function applicationName(\n\tenv: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n\treturn (\n\t\tenv.AWS_LAMBDA_FUNCTION_NAME ||\n\t\tenv.GKM_APP_NAME ||\n\t\tenv.GKM_DEV_APP?.split('#').pop() ||\n\t\tundefined\n\t);\n}\n\n/**\n * The comment that says what ran a query: the request's operation and id, and\n * the active span as a W3C `traceparent` — the query's own span when it runs\n * through a traced client, since the span wraps the tagging. '' with neither.\n *\n * The keys are in sqlcommenter's sorted order. Values keep only characters\n * that cannot end a comment or a quoted value — `*` and `'` among those\n * dropped — so a route cannot inject SQL through it.\n */\nexport function queryTag(): string {\n\tconst request = currentRequestContext();\n\tconst span = trace.getActiveSpan()?.spanContext();\n\n\tconst tags = [\n\t\t['operation', request?.operation],\n\t\t['request_id', request?.requestId],\n\t\t['traceparent', span && isSpanContextValid(span) ? traceparent(span) : ''],\n\t].filter((tag): tag is [string, string] => Boolean(tag[1]));\n\tif (tags.length === 0) return '';\n\n\tconst clean = (value: string) => value.replace(/[^\\w ./:{}@-]/g, '');\n\treturn ` /*${tags.map(([key, value]) => `${key}='${clean(value)}'`).join(',')}*/`;\n}\n\n/** A span as W3C trace context's `traceparent`, version 00. */\nfunction traceparent(span: {\n\ttraceId: string;\n\tspanId: string;\n\ttraceFlags: number;\n}): string {\n\tconst flags = (span.traceFlags & 0xff).toString(16).padStart(2, '0');\n\treturn `00-${span.traceId}-${span.spanId}-${flags}`;\n}\n\n/**\n * Append {@link queryTag} to everything `client` runs: a SQL string — what\n * Kysely sends — or a query object such as a cursor's.\n */\nfunction tagQueries(client: pg.PoolClient): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst tag = queryTag();\n\t\tif (tag && typeof first === 'string') return query(first + tag, ...rest);\n\t\tif (\n\t\t\ttag &&\n\t\t\tfirst &&\n\t\t\ttypeof first === 'object' &&\n\t\t\ttypeof (first as { text?: unknown }).text === 'string'\n\t\t) {\n\t\t\tconst config = first as { text: string };\n\t\t\tconfig.text += tag;\n\t\t}\n\t\treturn query(first, ...rest);\n\t}) as typeof client.query;\n}\n\n/** What every span of one pool says about where it connects. */\nfunction connectionAttributes(url: URL): Attributes {\n\tconst name = decodeURIComponent(url.pathname.slice(1));\n\treturn {\n\t\t'db.system': 'postgresql',\n\t\t...(name && { 'db.name': name }),\n\t\t'server.address': url.hostname,\n\t\t'server.port': Number(url.port || 5432),\n\t};\n}\n\nconst TRACER = '@geekmidas/constructs';\n\n/** `select`, `insert`, … — the first keyword, or `with`'s main statement. */\nconst OPERATION = /^\\s*(?:\\/\\*.*?\\*\\/\\s*)?(\\w+)/s;\n/** The table a statement is about, when it names one where it usually does. */\nconst TABLE =\n\t/\\b(?:from|into|update|join)\\s+((?:\"[^\"]+\"|[\\w$]+)(?:\\.(?:\"[^\"]+\"|[\\w$]+))?)/i;\n\n/**\n * What a statement does and to which table, parsed cheaply from its first\n * characters: `select \"orders\"`, `insert \"public\".\"users\"`. A statement the\n * patterns cannot read leaves `table` unset.\n */\nexport function describeStatement(text: string): {\n\toperation?: string;\n\ttable?: string;\n} {\n\tconst head = text.length > 512 ? text.slice(0, 512) : text;\n\tconst operation = OPERATION.exec(head)?.[1]?.toLowerCase();\n\tconst table = TABLE.exec(head)?.[1]?.replace(/\"/g, '');\n\treturn {\n\t\t...(operation && { operation }),\n\t\t...(table && { table }),\n\t};\n}\n\n/**\n * A span around everything `client` runs — `<operation> <table>`, or\n * `db.query` when the statement does not say.\n *\n * The statement text goes on the span only for a parameterized query — values\n * passed separately, as Kysely always does — and truncated. Parameter values\n * never do. A bare string with no values could hold anything a caller\n * concatenated into it, so it gets no text at all.\n */\nfunction traceQueries(client: pg.PoolClient, connection: Attributes): void {\n\tconst query = client.query.bind(client) as (...args: unknown[]) => unknown;\n\tclient.query = ((first: unknown, ...rest: unknown[]) => {\n\t\tconst config =\n\t\t\tfirst && typeof first === 'object'\n\t\t\t\t? (first as { text?: unknown; values?: unknown })\n\t\t\t\t: undefined;\n\t\tconst text =\n\t\t\ttypeof first === 'string'\n\t\t\t\t? first\n\t\t\t\t: typeof config?.text === 'string'\n\t\t\t\t\t? config.text\n\t\t\t\t\t: undefined;\n\t\tconst values = typeof first === 'string' ? rest[0] : config?.values;\n\t\tconst { operation, table } = text ? describeStatement(text) : {};\n\n\t\tconst span = trace\n\t\t\t.getTracer(TRACER)\n\t\t\t.startSpan(operation && table ? `${operation} ${table}` : 'db.query', {\n\t\t\t\tkind: SpanKind.CLIENT,\n\t\t\t\tattributes: {\n\t\t\t\t\t...connection,\n\t\t\t\t\t...(operation && { 'db.operation': operation }),\n\t\t\t\t\t...(table && { 'db.sql.table': table }),\n\t\t\t\t\t...(text &&\n\t\t\t\t\t\tArray.isArray(values) && {\n\t\t\t\t\t\t\t'db.statement':\n\t\t\t\t\t\t\t\ttext.length > MAX_STATEMENT_LENGTH\n\t\t\t\t\t\t\t\t\t? text.slice(0, MAX_STATEMENT_LENGTH)\n\t\t\t\t\t\t\t\t\t: text,\n\t\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\tconst end = (error?: unknown) => {\n\t\t\tif (error) {\n\t\t\t\tif (error instanceof Error) span.recordException(error);\n\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\t}\n\t\t\tspan.end();\n\t\t};\n\n\t\t// A callback, as `pg`'s own callback form takes it.\n\t\tconst last = rest[rest.length - 1];\n\t\tif (typeof last === 'function') {\n\t\t\tconst args = [...rest];\n\t\t\targs[args.length - 1] = (error: unknown, ...result: unknown[]) => {\n\t\t\t\tend(error);\n\t\t\t\treturn (last as (...a: unknown[]) => unknown)(error, ...result);\n\t\t\t};\n\t\t\treturn context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...args),\n\t\t\t);\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = context.with(trace.setSpan(context.active(), span), () =>\n\t\t\t\tquery(first, ...rest),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tend(error);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (result && typeof (result as Promise<unknown>).then === 'function') {\n\t\t\t(result as Promise<unknown>).then(\n\t\t\t\t() => end(),\n\t\t\t\t(error) => end(error),\n\t\t\t);\n\t\t} else if (\n\t\t\tresult &&\n\t\t\ttypeof (result as { once?: unknown }).once === 'function'\n\t\t) {\n\t\t\t// A submittable — a cursor or a stream — ends when it says so.\n\t\t\tconst emitter = result as {\n\t\t\t\tonce(event: string, fn: (e?: unknown) => void): void;\n\t\t\t};\n\t\t\temitter.once('end', () => end());\n\t\t\temitter.once('error', (error) => end(error));\n\t\t} else {\n\t\t\tend();\n\t\t}\n\t\treturn result;\n\t}) as typeof client.query;\n}\n","/**\n * `KyselyDatabase` — a declared Postgres database, typed by its schema.\n *\n * It declares a logical database, a schema, and the roles that reach it, and\n * hands back a Kysely client built from the one URL the adapter supplied. It\n * names no cloud: `--target=aws` fills that URL from RDS, local dev from the\n * Postgres container.\n *\n * Transactions and RLS context are the execution wrapper's job — it opens the\n * transaction and puts it in `ctx.db`. This construct does not resolve\n * connections per request.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\tDEFAULT_POSTGRES_VERSION,\n\ttype Declaration,\n\ttype PostgresVersion,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport { Cache } from '../cache';\nimport type { Construct } from '../construct-interface';\nimport { openPool } from './pool';\n\n/** The schema a database uses when it does not say otherwise. */\nconst DEFAULT_SCHEMA = 'app';\n\n/**\n * Everything Kysely takes, plus what this construct declares.\n *\n * `dialect` is omitted because the construct owns it — it is built from the one\n * URL the adapter supplied, which is the whole point of the construct. The rest\n * of `KyselyConfig` (`plugins`, `log`) is the app's business and passes\n * straight through, so anything Kysely adds later arrives without being\n * tracked here.\n */\nexport interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {\n\t/**\n\t * The schema, pinned on both roles' `search_path`.\n\t *\n\t * Defaults to `app` — the role the schema plays, rather than a restatement\n\t * of the database's own name, which reads correctly beside `auth` and\n\t * `pgboss` and keeps the application off `public`.\n\t */\n\tschema?: string;\n\t/**\n\t * Provision the owner/runtime role split. Defaults to on.\n\t *\n\t * Off falls back to the cluster's master credential in both URLs, giving up\n\t * the DDL/DML split and putting credentials that can drop a table into the\n\t * function's environment. A deliberate downgrade, not a default.\n\t */\n\troles?: boolean;\n\t/**\n\t * The Postgres major version to provision.\n\t *\n\t * Declared here so one statement reaches both targets. It was previously\n\t * set in two unrelated places — a container tag locally, nothing at all on\n\t * AWS (so Aurora chose) — which is how local came to run 18 while deployed\n\t * ran 17.7 with nothing recording the difference.\n\t *\n\t * Defaults to {@link DEFAULT_POSTGRES_VERSION}.\n\t */\n\tversion?: PostgresVersion;\n\t/**\n\t * End each query run inside a request with a comment naming what ran it —\n\t * `/*operation='POST /orders',request_id='…'*\\/` — so `pg_stat_activity`\n\t * and the server's logs can tie a query to its endpoint — and, inside a\n\t * trace, with the query span's `traceparent='00-…'` (sqlcommenter), so\n\t * they can tie it to the trace too. Defaults to on.\n\t */\n\tqueryTags?: boolean;\n}\n\n/** What makes a construct derived rather than a database in its own right. */\ninterface DerivedFrom {\n\tkind: 'database-reader' | 'database-schema';\n\tof: string;\n\tschema?: string;\n}\n\n/**\n * @typeParam DB - the database's schema.\n * @typeParam TName - the canonical id. TypeScript has no partial\n * type-argument inference, so `new KyselyDatabase<OrdersDB>('orders')` leaves\n * this at its default and the service key widens to `string`. `db` still types\n * through `.database()`, which infers from the service.\n */\nexport class KyselyDatabase<DB = unknown, TName extends string = string>\n\timplements Construct<TName, Kysely<DB>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Kysely<DB>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * build publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly config: { url: string; ownerUrl: string };\n\tprivate readonly schemaName: string;\n\tprivate readonly roles: boolean;\n\tprivate readonly version: PostgresVersion;\n\tprivate readonly derivedFrom?: DerivedFrom;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: KyselyDatabaseOptions = {},\n\t\tderivedFrom?: DerivedFrom,\n\t) {\n\t\t// Canonicalises, so `orders` and `Orders` are one construct rather than\n\t\t// two that collide. Throws on anything that cannot become a valid id.\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.config = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\townerUrl: provideKey(canonical, 'ownerUrl'),\n\t\t};\n\t\tthis.schemaName = options.schema ?? DEFAULT_SCHEMA;\n\t\tthis.roles = options.roles ?? true;\n\t\tthis.version = options.version ?? DEFAULT_POSTGRES_VERSION;\n\t\tthis.derivedFrom = derivedFrom;\n\n\t\t// A field, not a getter: service discovery caches by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\t// Forwards the whole options object — cherry-picking `envParser` strips\n\t\t\t// `context` and with it the request-scoped logger.\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\tif (this.derivedFrom) {\n\t\t\tconst { kind, of, schema } = this.derivedFrom;\n\n\t\t\treturn [\n\t\t\t\tkind === 'database-schema'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\tschema: schema ?? this.schemaName,\n\t\t\t\t\t\t\t// The runtime key alone — see the note below on the parent.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\t\tof,\n\t\t\t\t\t\t\t// A reader has no owner URL of its own — nothing migrates\n\t\t\t\t\t\t\t// through a read-only endpoint — so this is the one key.\n\t\t\t\t\t\t\tprovides: [this.config.url],\n\t\t\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'database',\n\t\t\t\tid: this.id,\n\t\t\t\tengine: 'postgres',\n\t\t\t\tschema: this.schemaName,\n\t\t\t\tversion: this.version,\n\t\t\t\t// One key, the runtime role's. `config` also holds the owner key and\n\t\t\t\t// it is deliberately absent here: a key in `provides` is a key an\n\t\t\t\t// edge can name, and nothing should be able to depend on DDL rights.\n\t\t\t\tprovides: [this.config.url],\n\t\t\t\t...(this.roles ? {} : { roles: false }),\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * A read-only endpoint on this database.\n\t *\n\t * Read-only is enforced by the reader role's grants rather than by which\n\t * endpoint it resolves to, so falling back to the writer where no replica\n\t * exists is safe rather than a silently writable connection behind a name\n\t * that says reader. There is no `writer()`: the database *is* the writer, so\n\t * a replica can never be reached by accident.\n\t *\n\t * The return type is narrower than this class on purpose — see\n\t * {@link ReadOnlyDatabase}.\n\t */\n\treader(): ReadOnlyDatabase<DB, `${TName}Reader`> {\n\t\treturn new KyselyDatabase<DB, `${TName}Reader`>(\n\t\t\t`${this.id}Reader` as ConstructName<`${TName}Reader`>,\n\t\t\tthis.options,\n\t\t\t{ kind: 'database-reader', of: this.id },\n\t\t);\n\t}\n\n\t/**\n\t * A cache that lives in this database.\n\t *\n\t * The same strengthening `schema()` is over a second database: `new\n\t * Cache('Sessions')` says the app caches and leaves where to the deployment,\n\t * while this says it caches *here* — which is a fact about the application\n\t * and so belongs in its code.\n\t *\n\t * What it buys beyond being explicit: the table's schema and the role that\n\t * reaches it come from this database rather than from a second convention,\n\t * and the backend no longer has to guess which database \"the database\" meant\n\t * when an app declares two.\n\t *\n\t * A cache is not a `KyselyDatabase`, so this returns a `Cache` — the client\n\t * is a key/value store, not a query builder, and typing it as the parent\n\t * would be a convenience that lies.\n\t */\n\tcache<TCache extends string = `${TName}Cache`>(\n\t\tid: ConstructName<TCache> = `${this.id}Cache` as ConstructName<TCache>,\n\t\toptions: { table?: string } = {},\n\t): Cache<TCache> {\n\t\treturn new Cache<TCache>(id, {\n\t\t\tof: this.id,\n\t\t\t...(options.table ? { table: options.table } : {}),\n\t\t});\n\t}\n\n\t/**\n\t * A second schema in this database, with its own role(s) and its own URL —\n\t * so this database's role holds no grant on those tables at all.\n\t *\n\t * Typed separately because a tenant has its own tables: the point is that\n\t * `auth.selectFrom('sessions')` type-checks and\n\t * `orders.selectFrom('sessions')` does not.\n\t */\n\tschema<TenantDB, TTenant extends string>(\n\t\tid: ConstructName<TTenant>,\n\t\toptions: KyselyDatabaseOptions = {},\n\t): KyselyDatabase<TenantDB, TTenant> {\n\t\treturn new KyselyDatabase<TenantDB, TTenant>(id, options, {\n\t\t\tkind: 'database-schema',\n\t\t\tof: this.id,\n\t\t\tschema: options.schema ?? (id as string).toLowerCase(),\n\t\t});\n\t}\n\n\t/**\n\t * A connection as the *owner* role — the one that may create, alter and drop.\n\t *\n\t * A `Service` rather than part of the construct, and deliberately so: a\n\t * construct is what `.dependsOn()` accepts, and nothing should be able to\n\t * depend on this. A migrator asks for it by name; a handler cannot ask at\n\t * all, which is the security property the role split exists for.\n\t *\n\t * Falls back to the runtime URL where no owner URL was published — the\n\t * `roles: false` downgrade, where there is only one credential and it is the\n\t * cluster master's.\n\t */\n\tget owner(): Service<`${Uncapitalize<TName>}Owner`, Kysely<DB>> {\n\t\treturn {\n\t\t\tserviceName:\n\t\t\t\t`${serviceKey(this.id)}Owner` as `${Uncapitalize<TName>}Owner`,\n\t\t\tregister: (options) => this.connect(options, { owner: true }),\n\t\t};\n\t}\n\n\t/**\n\t * What this database's clients are built with besides a dialect — the\n\t * plugins and logging it was declared with.\n\t *\n\t * Public so that whatever opens a connection to it outside \\`connect()\\` —\n\t * a test's transaction — builds the same client. A test that dropped the\n\t * plugins would write \\`createdAt\\` where production writes \\`created_at\\`.\n\t */\n\tget clientConfig(): Omit<KyselyConfig, 'dialect'> {\n\t\t// Split what the manifest declared from what Kysely takes, so neither\n\t\t// leaks into the other: \\`schema\\`, \\`roles\\` and \\`version\\` describe\n\t\t// infrastructure and mean nothing to a client.\n\t\tconst {\n\t\t\tschema: _schema,\n\t\t\troles: _roles,\n\t\t\tversion: _version,\n\t\t\tqueryTags: _queryTags,\n\t\t\t...kysely\n\t\t} = this.options;\n\t\treturn kysely;\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Kysely<DB>> {\n\t\tconst { url, ownerUrl } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\turl: get(this.config.url).string(),\n\t\t\t\townerUrl: get(this.config.ownerUrl).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\treturn new Kysely<DB>({\n\t\t\t...this.clientConfig,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\tpool: openPool(as.owner ? (ownerUrl ?? url) : url, {\n\t\t\t\t\tqueryTags: this.options.queryTags,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n}\n\n/**\n * A database you may read through, and derive nothing from.\n *\n * A reader is terminal in the graph, and this is that fact in the type system.\n * `DERIVES_FROM` gives `cache` and `database-schema` no `database-reader`\n * parent, and there is no reader of a reader — so `orders.reader().cache(…)`\n * has always been illegal, but it type-checked and failed later at\n * `assertDerivations`. That is a long way round to learn that a read-only\n * endpoint cannot own a table something writes to.\n *\n * Omitting the three deriving methods rather than making them throw keeps the\n * failure where it costs least: the editor, on the line that wrote it.\n *\n * The result is deliberately not assignable back to `KyselyDatabase` — a\n * reader is not one, and anything that genuinely needs the writer should say\n * so.\n */\nexport type ReadOnlyDatabase<DB, TName extends string> = Omit<\n\tKyselyDatabase<DB, TName>,\n\t'reader' | 'cache' | 'schema'\n>;\nexport { closeDatabasePools } from './pool';\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"kysely-D0bpzgPs.d.cts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChE3B,8BAA8B,KAAK;;;;;;;;EAQnD;;;;;;;;EAQA;;;;;;;;;;;EAWA,UAAU;;;;;;;;EAQV;;;UAIS;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EAyBf,WAAW;;;;;;;;;;;;;EAqDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAcX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"kysely-DKwnl0nf.d.mts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChE3B,8BAA8B,KAAK;;;;;;;;EAQnD;;;;;;;;EAQA;;;;;;;;;;;EAWA,UAAU;;;;;;;;EAQV;;;UAIS;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EAyBf,WAAW;;;;;;;;;;;;;EAqDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAcX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}