@homeflare/alchemy 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/litellm/budget-errors.d.ts +36 -0
- package/dist/litellm/budget-errors.d.ts.map +1 -0
- package/dist/litellm/budget-form.d.ts +50 -0
- package/dist/litellm/budget-form.d.ts.map +1 -0
- package/dist/litellm/budget-operations.d.ts +20 -0
- package/dist/litellm/budget-operations.d.ts.map +1 -0
- package/dist/litellm/budget.d.ts +61 -0
- package/dist/litellm/budget.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +5 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/index.d.ts +2 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +125 -2
- package/dist/litellm/index.js.map +9 -5
- package/dist/litellm/operations.d.ts +22 -0
- package/dist/litellm/operations.d.ts.map +1 -1
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/openbao/auth-role-form.d.ts +9 -0
- package/dist/openbao/auth-role-form.d.ts.map +1 -1
- package/dist/openbao/auth-role-wire.d.ts.map +1 -1
- package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
- package/dist/openbao/index.js +9 -6
- package/dist/openbao/index.js.map +4 -4
- package/dist/postgres/connection.d.ts +20 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +1 -1
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/database.d.ts +1 -1
- package/dist/postgres/database.d.ts.map +1 -1
- package/dist/postgres/index.d.ts +5 -3
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +124 -38
- package/dist/postgres/index.js.map +8 -7
- package/dist/postgres/providers.d.ts +4 -2
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +21 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -0
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/api-token.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag-types.d.ts +57 -0
- package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
- package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag.d.ts +15 -50
- package/dist/proxmox/ceph-flag.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +10 -13
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/credential-errors.d.ts +18 -4
- package/dist/proxmox/credential-errors.d.ts.map +1 -1
- package/dist/proxmox/credentials.d.ts +35 -1
- package/dist/proxmox/credentials.d.ts.map +1 -1
- package/dist/proxmox/group.d.ts.map +1 -1
- package/dist/proxmox/index.js +805 -757
- package/dist/proxmox/index.js.map +21 -19
- package/dist/proxmox/lease-cache.d.ts +67 -5
- package/dist/proxmox/lease-cache.d.ts.map +1 -1
- package/dist/proxmox/mint.d.ts +4 -2
- package/dist/proxmox/mint.d.ts.map +1 -1
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/role.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/unreadable-read.d.ts +8 -1
- package/dist/proxmox/unreadable-read.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/docs/credentials.md +14 -1
- package/docs/litellm-budget.md +55 -0
- package/docs/litellm.md +7 -6
- package/docs/postgres.md +22 -0
- package/docs/proxmox-sdk-migration.md +45 -0
- package/package.json +3 -3
- package/src/litellm/budget-errors.ts +38 -0
- package/src/litellm/budget-form.ts +94 -0
- package/src/litellm/budget-operations.ts +51 -0
- package/src/litellm/budget.test.ts +105 -0
- package/src/litellm/budget.ts +127 -0
- package/src/litellm/fake-litellm.ts +50 -1
- package/src/litellm/index.ts +13 -0
- package/src/litellm/operations.ts +1 -1
- package/src/litellm/providers.ts +4 -1
- package/src/openbao/auth-role-form.test.ts +37 -0
- package/src/openbao/auth-role-form.ts +12 -1
- package/src/openbao/auth-role-wire.test.ts +1 -0
- package/src/openbao/auth-role-wire.ts +13 -1
- package/src/openbao/distilled-retry.test.ts +1 -0
- package/src/openbao/fake-engines-roles.ts +7 -1
- package/src/postgres/connection.ts +37 -3
- package/src/postgres/database-sql.ts +4 -3
- package/src/postgres/database.ts +3 -2
- package/src/postgres/index.ts +10 -3
- package/src/postgres/providers.ts +15 -2
- package/src/postgres/psql-executor.test.ts +106 -0
- package/src/postgres/psql-executor.ts +130 -0
- package/src/proxmox/acl.ts +7 -1
- package/src/proxmox/api-token.ts +7 -1
- package/src/proxmox/ceph-flag-types.ts +70 -0
- package/src/proxmox/ceph-flag-wire.test.ts +72 -0
- package/src/proxmox/ceph-flag-wire.ts +123 -0
- package/src/proxmox/ceph-flag.ts +77 -130
- package/src/proxmox/ceph-osd-write.ts +10 -13
- package/src/proxmox/credential-errors.ts +19 -5
- package/src/proxmox/credentials.test.ts +73 -1
- package/src/proxmox/credentials.ts +72 -1
- package/src/proxmox/group.ts +7 -1
- package/src/proxmox/lease-cache-mint.test.ts +70 -0
- package/src/proxmox/lease-cache.test.ts +83 -7
- package/src/proxmox/lease-cache.ts +74 -10
- package/src/proxmox/mint.ts +12 -5
- package/src/proxmox/node-network.ts +7 -1
- package/src/proxmox/role.ts +7 -1
- package/src/proxmox/storage.ts +7 -1
- package/src/proxmox/unreadable-read.ts +16 -4
- package/src/proxmox/user.ts +7 -1
- package/src/proxmox/zfs-pool.ts +7 -1
|
@@ -18,6 +18,8 @@ import * as Context from 'effect/Context';
|
|
|
18
18
|
import * as Effect from 'effect/Effect';
|
|
19
19
|
import * as Layer from 'effect/Layer';
|
|
20
20
|
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
21
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
22
|
+
import { type PsqlRunner } from './psql-executor.ts';
|
|
21
23
|
/** What a stack passes to reach one cluster. `host` may be a socket directory (one beginning
|
|
22
24
|
* with `/` expands to `${host}/.s.PGSQL.${port}` — measured in
|
|
23
25
|
* `node_modules/@effect/sql-pg/src/PgConnection.ts`, the "Details" doc on `Config`) or a TCP
|
|
@@ -29,7 +31,21 @@ export interface PostgresConnectionConfig {
|
|
|
29
31
|
readonly username: string;
|
|
30
32
|
readonly ssl?: boolean;
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
/** The runner transport (`psql-executor.ts`): SQL goes through `run` instead of a socket, for a
|
|
35
|
+
* host whose Postgres is reachable only by a command (CT100: ssh + `podman exec`). `template`
|
|
36
|
+
* is the `CREATE DATABASE` template — `template0` by default, because CT100's `template1` is
|
|
37
|
+
* ParadeDB's and would copy postgis/pg_ivm/paradedb objects into every new database. */
|
|
38
|
+
export interface PostgresRunnerConfig {
|
|
39
|
+
readonly run: PsqlRunner;
|
|
40
|
+
readonly database: string;
|
|
41
|
+
readonly username: string;
|
|
42
|
+
readonly template?: string;
|
|
43
|
+
}
|
|
44
|
+
/** What `withPg`'s callback learns about the transport beyond the client. */
|
|
45
|
+
export interface PgContext {
|
|
46
|
+
readonly template?: string;
|
|
47
|
+
}
|
|
48
|
+
declare const PostgresConnection_base: Context.ServiceClass<PostgresConnection, "Postgres.Connection", Effect.Effect<PostgresConnectionConfig | PostgresRunnerConfig, never, never>>;
|
|
33
49
|
/** The lazy connection service. Its value is an `Effect` of the config, per S24 — see the file
|
|
34
50
|
* header for why. */
|
|
35
51
|
export declare class PostgresConnection extends PostgresConnection_base {
|
|
@@ -37,6 +53,8 @@ export declare class PostgresConnection extends PostgresConnection_base {
|
|
|
37
53
|
/** A stack's one-line way to provide a cluster: `Layer.provide(postgresConnection({ … }))` on
|
|
38
54
|
* `PostgresDatabaseProvider()`, or merged into the stack's own layer tree. */
|
|
39
55
|
export declare const postgresConnection: (config: PostgresConnectionConfig) => Layer.Layer<PostgresConnection>;
|
|
56
|
+
/** A stack's one-line way to provide a cluster reached through a command runner. */
|
|
57
|
+
export declare const postgresRunnerConnection: (config: PostgresRunnerConfig) => Layer.Layer<PostgresConnection>;
|
|
40
58
|
/**
|
|
41
59
|
* Run one operation against a freshly built, freshly closed connection pool.
|
|
42
60
|
*
|
|
@@ -45,6 +63,6 @@ export declare const postgresConnection: (config: PostgresConnectionConfig) => L
|
|
|
45
63
|
* then a re-read shares one pool and closes it once, while a plan-only `read` still opens and
|
|
46
64
|
* closes its own.
|
|
47
65
|
*/
|
|
48
|
-
export declare const withPg: <A, E>(build: (pg: PgClient.PgClient) => Effect.Effect<A, E>) => Effect.Effect<A, E | SqlError, PostgresConnection>;
|
|
66
|
+
export declare const withPg: <A, E>(build: (pg: PgClient.PgClient | PgExecutor, context: PgContext) => Effect.Effect<A, E>) => Effect.Effect<A, E | SqlError, PostgresConnection>;
|
|
49
67
|
export {};
|
|
50
68
|
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/postgres/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/postgres/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,oBAAoB,CAAC;AAEvE;;;uFAGuF;AACvF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;wFAGwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;;AAED;qBACqB;AACrB,qBAAa,kBAAmB,SAAQ,uBAGd;CAAG;AAE7B;8EAC8E;AAC9E,eAAO,MAAM,kBAAkB,WACrB,wBAAwB,KAC/B,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAA8D,CAAC;AAEhG,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,WAC3B,oBAAoB,KAC3B,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAA8D,CAAC;AAKhG;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,SAClB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,UAAU,EAAE,OAAO,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KACrF,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,kBAAkB,CAahD,CAAC"}
|
|
@@ -46,7 +46,7 @@ export declare const quoteStringLiteral: (value: string) => string;
|
|
|
46
46
|
* `Math.trunc`ed and stringified, and a boolean is one of exactly two literal words this file
|
|
47
47
|
* writes, so no declared value reaches the statement through string interpolation unescaped.
|
|
48
48
|
*/
|
|
49
|
-
export declare const buildCreateDatabaseSql: (props: PostgresDatabaseProps) => string;
|
|
49
|
+
export declare const buildCreateDatabaseSql: (props: PostgresDatabaseProps, template?: string) => string;
|
|
50
50
|
/** `42P04` (`duplicate_database`) is a race: something else created the name between our read
|
|
51
51
|
* and our create. Measured at `internal/sqlError.ts@effect/sql-pg 4.0.0-rc.115`: every SQLSTATE
|
|
52
52
|
* starting `42` — this one included — classifies as `SqlSyntaxError`, the same tag a genuine
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-sql.d.ts","sourceRoot":"","sources":["../../src/postgres/database-sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE7F;;;gFAGgF;AAChF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,EAChC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,KAC5B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;CAChD;AAED;;0DAE0D;AAC1D,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,MAA0C,CAAC;AAEtF;2FAC2F;AAC3F,eAAO,MAAM,kBAAkB,UAAW,MAAM,KAAG,MAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"database-sql.d.ts","sourceRoot":"","sources":["../../src/postgres/database-sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE7F;;;gFAGgF;AAChF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,EAChC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,KAC5B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;CAChD;AAED;;0DAE0D;AAC1D,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,MAA0C,CAAC;AAEtF;2FAC2F;AAC3F,eAAO,MAAM,kBAAkB,UAAW,MAAM,KAAG,MAA0C,CAAC;AAwB9F;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,UAAW,qBAAqB,aAAa,MAAM,KAAG,MACP,CAAC;AAEnF;;;;;mFAKmF;AACnF,eAAO,MAAM,uBAAuB,UAAW,QAAQ,KAAG,OAMzD,CAAC;AAIF;;sFAEsF;AACtF,eAAO,MAAM,UAAU,OAAQ,UAAU,QAAQ,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAItF,CAAC;AAwCJ;;sFAEsF;AACtF,eAAO,MAAM,cAAc,OACrB,UAAU,QACR,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,0BAA0B,GAAG,SAAS,EAAE,QAAQ,CACkC,CAAC;AAEpG;;qFAEqF;AACrF,eAAO,MAAM,UAAU,UACd,qBAAqB,QACtB,0BAA0B,KAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAgBlF,CAAC"}
|
|
@@ -36,7 +36,7 @@ export declare const isPostgresDatabase: (value: unknown) => value is PostgresDa
|
|
|
36
36
|
* `withPg`, or `fake-sql.ts`'s recording fake in tests. Kept apart from the provider wiring
|
|
37
37
|
* below so a lifecycle test drives this exact function, not a re-implementation of it.
|
|
38
38
|
*/
|
|
39
|
-
export declare const reconcileWithClient: (pg: PgExecutor, props: PostgresDatabaseProps) => Effect.Effect<PostgresDatabaseAttributes, PostgresDatabaseOwnerMissing | PostgresDatabaseDrift | PostgresDatabaseCreateVanished | SqlError>;
|
|
39
|
+
export declare const reconcileWithClient: (pg: PgExecutor, props: PostgresDatabaseProps, template?: string) => Effect.Effect<PostgresDatabaseAttributes, PostgresDatabaseOwnerMissing | PostgresDatabaseDrift | PostgresDatabaseCreateVanished | SqlError>;
|
|
40
40
|
/** The core of `read`: one bound `SELECT`, no ownership branding — the caller (the provider's
|
|
41
41
|
* `read` below, or a test) decides whether to wrap a match in `Unowned`. */
|
|
42
42
|
export declare const readWithClient: (pg: PgExecutor, name: string) => Effect.Effect<PostgresDatabaseAttributes | undefined, SqlError, never>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/postgres/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAS7F,OAAO,EAAE,KAAK,kBAAkB,EAAU,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EAErB,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,gBAAiB,SAAQ,QAAQ,CAChD,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,kBAAkB,CACnB;CAAG;AAEJ,eAAO,MAAM,gBAAgB,mDAE3B,CAAC;AAEH;;;kFAGkF;AAClF,eAAO,MAAM,kBAAkB,UAAW,OAAO,KAAG,KAAK,IAAI,gBAGD,CAAC;AAY7D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,OAC1B,UAAU,SACP,qBAAqB,
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/postgres/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAS7F,OAAO,EAAE,KAAK,kBAAkB,EAAU,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EAErB,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,gBAAiB,SAAQ,QAAQ,CAChD,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,kBAAkB,CACnB;CAAG;AAEJ,eAAO,MAAM,gBAAgB,mDAE3B,CAAC;AAEH;;;kFAGkF;AAClF,eAAO,MAAM,kBAAkB,UAAW,OAAO,KAAG,KAAK,IAAI,gBAGD,CAAC;AAY7D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,OAC1B,UAAU,SACP,qBAAqB,aACjB,MAAM,KAChB,MAAM,CAAC,MAAM,CACd,0BAA0B,EAC1B,4BAA4B,GAAG,qBAAqB,GAAG,8BAA8B,GAAG,QAAQ,CAqC9F,CAAC;AAEL;4EAC4E;AAC5E,eAAO,MAAM,cAAc,OAAQ,UAAU,QAAQ,MAAM,2EAA6B,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,SACzB,KAAK,CAAC,qBAAqB,CAAC,UAC1B,0BAA0B,GAAG,SAAS;qBAgBhB,QAAQ;;qBAA0B,MAAM;kFACpE,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,8HA4BnC,CAAC;AAEH,eAAO,MAAM,wBAAwB,oGACyB,CAAC"}
|
package/dist/postgres/index.d.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* ⛔ THIS BARREL IS THE PUBLIC API, deliberately smaller than the directory: `fake-sql.ts` is a
|
|
6
6
|
* test double, not something a consuming stack should import.
|
|
7
7
|
*/
|
|
8
|
-
export type { PostgresConnectionConfig } from './connection.ts';
|
|
9
|
-
export { PostgresConnection, postgresConnection, withPg } from './connection.ts';
|
|
8
|
+
export type { PostgresConnectionConfig, PostgresRunnerConfig } from './connection.ts';
|
|
9
|
+
export { PostgresConnection, postgresConnection, postgresRunnerConnection, withPg, } from './connection.ts';
|
|
10
|
+
export type { PsqlResult, PsqlRunner } from './psql-executor.ts';
|
|
11
|
+
export { inlineParams, makePsqlExecutor } from './psql-executor.ts';
|
|
10
12
|
export type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';
|
|
11
13
|
export { POSTGRES_NAME_MAX_BYTES, nameByteRefusal, utf8ByteLength } from './database-attrs.ts';
|
|
12
14
|
export { buildCreateDatabaseSql, firstDrift, isDuplicateDatabaseRace, quoteIdent, quoteStringLiteral, roleExists, selectDatabase, } from './database-sql.ts';
|
|
13
15
|
export { PostgresDatabase, PostgresDatabaseProvider, isPostgresDatabase } from './database.ts';
|
|
14
16
|
export { PostgresDatabaseDrift, PostgresDatabaseDropRefused, PostgresDatabaseNameRefused, PostgresDatabaseOwnerMissing, PostgresDatabaseRenameRefused, type PostgresDatabaseError, } from './errors.ts';
|
|
15
|
-
export { postgresProviders } from './providers.ts';
|
|
17
|
+
export { postgresProviders, postgresRunnerProviders } from './providers.ts';
|
|
16
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/postgres/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/postgres/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,MAAM,GACP,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EACL,sBAAsB,EACtB,UAAU,EACV,uBAAuB,EACvB,UAAU,EACV,kBAAkB,EAClB,UAAU,EACV,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/postgres/index.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
// src/postgres/connection.ts
|
|
2
2
|
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
3
3
|
import * as Context from "effect/Context";
|
|
4
|
-
import * as
|
|
4
|
+
import * as Effect3 from "effect/Effect";
|
|
5
5
|
import * as Layer from "effect/Layer";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect.succeed(config));
|
|
10
|
-
var withPg = (build) => Effect.gen(function* () {
|
|
11
|
-
const resolveConfig = yield* PostgresConnection;
|
|
12
|
-
const config = yield* resolveConfig;
|
|
13
|
-
return yield* Effect.provide(Effect.flatMap(PgClient.PgClient, build), PgClient.layer(config));
|
|
14
|
-
});
|
|
15
|
-
// src/postgres/database-attrs.ts
|
|
16
|
-
var POSTGRES_NAME_MAX_BYTES = 63;
|
|
17
|
-
var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
18
|
-
var nameByteRefusal = (name) => {
|
|
19
|
-
const byteLength = utf8ByteLength(name);
|
|
20
|
-
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
21
|
-
};
|
|
22
|
-
// src/postgres/database-sql.ts
|
|
7
|
+
// src/postgres/psql-executor.ts
|
|
23
8
|
import * as Effect2 from "effect/Effect";
|
|
9
|
+
import {
|
|
10
|
+
ConnectionError,
|
|
11
|
+
SqlError,
|
|
12
|
+
SqlSyntaxError,
|
|
13
|
+
UnknownError
|
|
14
|
+
} from "effect/unstable/sql/SqlError";
|
|
15
|
+
|
|
16
|
+
// src/postgres/database-sql.ts
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
24
18
|
var quoteIdent = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
25
19
|
var quoteStringLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
26
|
-
var withOptions = (props) => {
|
|
20
|
+
var withOptions = (props, template) => {
|
|
27
21
|
const parts = [`OWNER ${quoteIdent(props.owner)}`];
|
|
22
|
+
if (template !== undefined)
|
|
23
|
+
parts.push(`TEMPLATE ${quoteIdent(template)}`);
|
|
28
24
|
if (props.encoding !== undefined)
|
|
29
25
|
parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);
|
|
30
26
|
if (props.localeProvider !== undefined) {
|
|
@@ -46,7 +42,7 @@ var withOptions = (props) => {
|
|
|
46
42
|
parts.push(`IS_TEMPLATE ${props.isTemplate ? "true" : "false"}`);
|
|
47
43
|
return parts.join(" ");
|
|
48
44
|
};
|
|
49
|
-
var buildCreateDatabaseSql = (props) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props)}`;
|
|
45
|
+
var buildCreateDatabaseSql = (props, template) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props, template)}`;
|
|
50
46
|
var isDuplicateDatabaseRace = (error) => {
|
|
51
47
|
if (error.reason._tag !== "SqlSyntaxError")
|
|
52
48
|
return false;
|
|
@@ -54,7 +50,7 @@ var isDuplicateDatabaseRace = (error) => {
|
|
|
54
50
|
return typeof cause === "object" && cause !== null && cause.code === "42P04";
|
|
55
51
|
};
|
|
56
52
|
var ROLE_EXISTS_SQL = "SELECT 1 AS present FROM pg_roles WHERE rolname = $1";
|
|
57
|
-
var roleExists = (pg, role) =>
|
|
53
|
+
var roleExists = (pg, role) => Effect.map(pg.unsafe(ROLE_EXISTS_SQL, [role]), (rows) => rows.length > 0);
|
|
58
54
|
var SELECT_DATABASE_SQL = `SELECT
|
|
59
55
|
d.oid AS oid,
|
|
60
56
|
d.datname AS name,
|
|
@@ -73,7 +69,7 @@ var SELECT_DATABASE_SQL = `SELECT
|
|
|
73
69
|
FROM pg_database d
|
|
74
70
|
JOIN pg_tablespace t ON t.oid = d.dattablespace
|
|
75
71
|
WHERE d.datname = $1`;
|
|
76
|
-
var selectDatabase = (pg, name) =>
|
|
72
|
+
var selectDatabase = (pg, name) => Effect.map(pg.unsafe(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);
|
|
77
73
|
var firstDrift = (props, live) => {
|
|
78
74
|
const checks = [
|
|
79
75
|
["encoding", props.encoding, live.encoding],
|
|
@@ -91,12 +87,97 @@ var firstDrift = (props, live) => {
|
|
|
91
87
|
}
|
|
92
88
|
return;
|
|
93
89
|
};
|
|
90
|
+
|
|
91
|
+
// src/postgres/psql-executor.ts
|
|
92
|
+
var inlineParams = (sql, params) => params.length === 0 ? sql : sql.replace(/\$(\d+)/g, (_match, index) => {
|
|
93
|
+
const value = params[Number(index) - 1];
|
|
94
|
+
if (typeof value !== "string") {
|
|
95
|
+
throw new Error(`psql executor: param $${index} must be a string, got ${typeof value}`);
|
|
96
|
+
}
|
|
97
|
+
return quoteStringLiteral(value);
|
|
98
|
+
});
|
|
99
|
+
var normalizeRow = (row) => typeof row === "object" && row !== null && typeof row.oid === "string" ? { ...row, oid: Number(row.oid) } : row;
|
|
100
|
+
var isSelect = (sql) => /^\s*SELECT\b/i.test(sql);
|
|
101
|
+
var wrapRows = (sql) => `SELECT coalesce(json_agg(t), '[]'::json)::text FROM (${sql}) t;`;
|
|
102
|
+
var failure = (operation, result) => {
|
|
103
|
+
const state = /ERROR:\s+([0-9A-Z]{5}):/.exec(result.stderr)?.[1];
|
|
104
|
+
const fields = {
|
|
105
|
+
cause: Object.assign(new Error(result.stderr.trim()), state === undefined ? {} : { code: state }),
|
|
106
|
+
message: result.stderr.trim(),
|
|
107
|
+
operation
|
|
108
|
+
};
|
|
109
|
+
const reason = state === undefined ? new ConnectionError(fields) : state.startsWith("42") ? new SqlSyntaxError(fields) : new UnknownError(fields);
|
|
110
|
+
return new SqlError({ reason });
|
|
111
|
+
};
|
|
112
|
+
var makePsqlExecutor = (run, target) => ({
|
|
113
|
+
unsafe: (sql, params = []) => Effect2.gen(function* () {
|
|
114
|
+
const inlined = yield* Effect2.try({
|
|
115
|
+
try: () => inlineParams(sql, params),
|
|
116
|
+
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql inline params" }) })
|
|
117
|
+
});
|
|
118
|
+
const rows = isSelect(inlined);
|
|
119
|
+
const result = yield* Effect2.tryPromise({
|
|
120
|
+
try: () => run({
|
|
121
|
+
argv: [
|
|
122
|
+
"psql",
|
|
123
|
+
"-X",
|
|
124
|
+
"-q",
|
|
125
|
+
"-A",
|
|
126
|
+
"-t",
|
|
127
|
+
"-v",
|
|
128
|
+
"ON_ERROR_STOP=1",
|
|
129
|
+
"-v",
|
|
130
|
+
"VERBOSITY=verbose",
|
|
131
|
+
"-U",
|
|
132
|
+
target.username,
|
|
133
|
+
"-d",
|
|
134
|
+
target.database
|
|
135
|
+
],
|
|
136
|
+
stdin: rows ? wrapRows(inlined) : `${inlined};`
|
|
137
|
+
}),
|
|
138
|
+
catch: (cause) => new SqlError({ reason: new ConnectionError({ cause, operation: "psql exec" }) })
|
|
139
|
+
});
|
|
140
|
+
if (result.code !== 0)
|
|
141
|
+
return yield* Effect2.fail(failure(inlined.slice(0, 40), result));
|
|
142
|
+
if (!rows)
|
|
143
|
+
return [];
|
|
144
|
+
const parsed = yield* Effect2.try({
|
|
145
|
+
try: () => JSON.parse(result.stdout.trim() || "[]").map(normalizeRow),
|
|
146
|
+
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql parse output" }) })
|
|
147
|
+
});
|
|
148
|
+
return parsed;
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// src/postgres/connection.ts
|
|
153
|
+
class PostgresConnection extends Context.Service()("Postgres.Connection") {
|
|
154
|
+
}
|
|
155
|
+
var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
156
|
+
var postgresRunnerConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
157
|
+
var isRunner = (c) => ("run" in c);
|
|
158
|
+
var withPg = (build) => Effect3.gen(function* () {
|
|
159
|
+
const resolveConfig = yield* PostgresConnection;
|
|
160
|
+
const config = yield* resolveConfig;
|
|
161
|
+
if (isRunner(config)) {
|
|
162
|
+
return yield* build(makePsqlExecutor(config.run, config), {
|
|
163
|
+
template: config.template ?? "template0"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return yield* Effect3.provide(Effect3.flatMap(PgClient.PgClient, (pg) => build(pg, {})), PgClient.layer(config));
|
|
167
|
+
});
|
|
168
|
+
// src/postgres/database-attrs.ts
|
|
169
|
+
var POSTGRES_NAME_MAX_BYTES = 63;
|
|
170
|
+
var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
171
|
+
var nameByteRefusal = (name) => {
|
|
172
|
+
const byteLength = utf8ByteLength(name);
|
|
173
|
+
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
174
|
+
};
|
|
94
175
|
// src/postgres/database.ts
|
|
95
176
|
import { Resource } from "alchemy";
|
|
96
177
|
import { Unowned } from "alchemy/AdoptPolicy";
|
|
97
178
|
import { isResolved } from "alchemy/Diff";
|
|
98
179
|
import * as Provider from "alchemy/Provider";
|
|
99
|
-
import * as
|
|
180
|
+
import * as Effect4 from "effect/Effect";
|
|
100
181
|
|
|
101
182
|
// src/postgres/errors.ts
|
|
102
183
|
import * as Data from "effect/Data";
|
|
@@ -148,23 +229,23 @@ var liveDrift = (props, live) => {
|
|
|
148
229
|
}
|
|
149
230
|
return firstDrift(props, live);
|
|
150
231
|
};
|
|
151
|
-
var reconcileWithClient = (pg, props) =>
|
|
232
|
+
var reconcileWithClient = (pg, props, template) => Effect4.gen(function* () {
|
|
152
233
|
const observed = yield* selectDatabase(pg, props.name);
|
|
153
234
|
if (observed === undefined) {
|
|
154
235
|
const ownerPresent = yield* roleExists(pg, props.owner);
|
|
155
236
|
if (!ownerPresent) {
|
|
156
|
-
return yield*
|
|
237
|
+
return yield* Effect4.fail(new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }));
|
|
157
238
|
}
|
|
158
|
-
yield* pg.unsafe(buildCreateDatabaseSql(props)).pipe(
|
|
239
|
+
yield* pg.unsafe(buildCreateDatabaseSql(props, template)).pipe(Effect4.asVoid, Effect4.catchTag("SqlError", (error) => isDuplicateDatabaseRace(error) ? Effect4.succeed(undefined) : Effect4.fail(error)));
|
|
159
240
|
const created = yield* selectDatabase(pg, props.name);
|
|
160
241
|
if (created === undefined) {
|
|
161
|
-
return yield*
|
|
242
|
+
return yield* Effect4.fail(new PostgresDatabaseCreateVanished({ database: props.name }));
|
|
162
243
|
}
|
|
163
244
|
return created;
|
|
164
245
|
}
|
|
165
246
|
const drift = liveDrift(props, observed);
|
|
166
247
|
if (drift !== undefined) {
|
|
167
|
-
return yield*
|
|
248
|
+
return yield* Effect4.fail(new PostgresDatabaseDrift({
|
|
168
249
|
database: props.name,
|
|
169
250
|
prop: drift.prop,
|
|
170
251
|
declared: drift.declared,
|
|
@@ -174,41 +255,42 @@ var reconcileWithClient = (pg, props) => Effect3.gen(function* () {
|
|
|
174
255
|
return observed;
|
|
175
256
|
});
|
|
176
257
|
var readWithClient = (pg, name) => selectDatabase(pg, name);
|
|
177
|
-
var diffPostgresDatabase = (news, output) =>
|
|
258
|
+
var diffPostgresDatabase = (news, output) => Effect4.gen(function* () {
|
|
178
259
|
if (output === undefined || !isResolved(news))
|
|
179
260
|
return;
|
|
180
261
|
if (news.name !== output.name) {
|
|
181
|
-
return yield*
|
|
262
|
+
return yield* Effect4.fail(new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }));
|
|
182
263
|
}
|
|
183
264
|
const nameRefusal = nameByteRefusal(news.name);
|
|
184
265
|
if (nameRefusal !== undefined) {
|
|
185
|
-
return yield*
|
|
266
|
+
return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
|
|
186
267
|
}
|
|
187
268
|
const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;
|
|
188
269
|
return changed ? { action: "update" } : { action: "noop" };
|
|
189
270
|
});
|
|
190
271
|
var postgresDatabaseHandlers = PostgresDatabase.Provider.of({
|
|
191
|
-
list: () =>
|
|
272
|
+
list: () => Effect4.succeed([]),
|
|
192
273
|
nuke: { skip: true },
|
|
193
|
-
read: ({ olds, output }) =>
|
|
274
|
+
read: ({ olds, output }) => Effect4.gen(function* () {
|
|
194
275
|
const name = output?.name ?? olds.name;
|
|
195
276
|
const live = yield* withPg((pg) => readWithClient(pg, name));
|
|
196
277
|
return live === undefined ? undefined : Unowned(live);
|
|
197
278
|
}),
|
|
198
279
|
diff: ({ news, output }) => diffPostgresDatabase(news, output),
|
|
199
|
-
reconcile: ({ news }) =>
|
|
280
|
+
reconcile: ({ news }) => Effect4.gen(function* () {
|
|
200
281
|
const nameRefusal = nameByteRefusal(news.name);
|
|
201
282
|
if (nameRefusal !== undefined) {
|
|
202
|
-
return yield*
|
|
283
|
+
return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
|
|
203
284
|
}
|
|
204
|
-
return yield* withPg((pg) => reconcileWithClient(pg, news));
|
|
285
|
+
return yield* withPg((pg, ctx) => reconcileWithClient(pg, news, ctx.template));
|
|
205
286
|
}),
|
|
206
|
-
delete: ({ olds }) =>
|
|
287
|
+
delete: ({ olds }) => Effect4.fail(new PostgresDatabaseDropRefused({ database: olds.name }))
|
|
207
288
|
});
|
|
208
289
|
var PostgresDatabaseProvider = () => Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);
|
|
209
290
|
// src/postgres/providers.ts
|
|
210
291
|
import * as Layer2 from "effect/Layer";
|
|
211
|
-
var postgresProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.
|
|
292
|
+
var postgresProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresConnection(config)));
|
|
293
|
+
var postgresRunnerProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config)));
|
|
212
294
|
export {
|
|
213
295
|
POSTGRES_NAME_MAX_BYTES,
|
|
214
296
|
PostgresConnection,
|
|
@@ -221,11 +303,15 @@ export {
|
|
|
221
303
|
PostgresDatabaseRenameRefused,
|
|
222
304
|
buildCreateDatabaseSql,
|
|
223
305
|
firstDrift,
|
|
306
|
+
inlineParams,
|
|
224
307
|
isDuplicateDatabaseRace,
|
|
225
308
|
isPostgresDatabase,
|
|
309
|
+
makePsqlExecutor,
|
|
226
310
|
nameByteRefusal,
|
|
227
311
|
postgresConnection,
|
|
228
312
|
postgresProviders,
|
|
313
|
+
postgresRunnerConnection,
|
|
314
|
+
postgresRunnerProviders,
|
|
229
315
|
quoteIdent,
|
|
230
316
|
quoteStringLiteral,
|
|
231
317
|
roleExists,
|
|
@@ -234,5 +320,5 @@ export {
|
|
|
234
320
|
withPg
|
|
235
321
|
};
|
|
236
322
|
|
|
237
|
-
//# debugId=
|
|
323
|
+
//# debugId=0FE6812621B3699B64756E2164756E21
|
|
238
324
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/postgres/connection.ts", "../src/postgres/
|
|
3
|
+
"sources": ["../src/postgres/connection.ts", "../src/postgres/psql-executor.ts", "../src/postgres/database-sql.ts", "../src/postgres/database-attrs.ts", "../src/postgres/database.ts", "../src/postgres/errors.ts", "../src/postgres/providers.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Where a database's cluster comes from, and how each lifecycle operation gets a client.\n *\n * ⛔ NO ESTATE VALUE LIVES HERE. The kit is PUBLIC; the mini's socket path, its user and its\n * database names are the CONSUMER's props, passed to `postgresConnection(...)` in a house\n * stack — never a default, never a fallback (`docs/postgres.md#path`).\n * ★ A LAZY `Context.Service` WHOSE VALUE IS AN `Effect` (S24), THE SAME SHAPE THE HOUSE USES FOR\n * CREDENTIALS: `yield* yield* PostgresConnection` resolves the config INSIDE each operation,\n * never at layer build. Nothing here holds a secret today (`PostgresConnectionConfig` has no\n * `password` — S25, and CREATE DATABASE has none to assert either), but the shape is the one a\n * later TLS path (`Config.redacted` from the environment) drops into without a rewrite.\n * ★ THE POOL IS BUILT IN A SCOPE AND CLOSED PER OPERATION. `Effect.provide(op, PgClient.layer(config))`\n * already opens a scope for the layer's build and closes it when `op` finishes — one pool per\n * `withPg` call, never held across a whole `reconcile`.\n */\nimport * as PgClient from '@effect/sql-pg/PgClient';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\n\n/** What a stack passes to reach one cluster. `host` may be a socket directory (one beginning\n * with `/` expands to `${host}/.s.PGSQL.${port}` — measured in\n * `node_modules/@effect/sql-pg/src/PgConnection.ts`, the \"Details\" doc on `Config`) or a TCP\n * host name; `port` defaults to `@effect/sql-pg`'s own default (5432) when omitted. */\nexport interface PostgresConnectionConfig {\n readonly host: string;\n readonly port?: number;\n readonly database: string;\n readonly username: string;\n readonly ssl?: boolean;\n}\n\n/** The lazy connection service. Its value is an `Effect` of the config, per S24 — see the file\n * header for why. */\nexport class PostgresConnection extends Context.Service<\n PostgresConnection,\n Effect.Effect<PostgresConnectionConfig>\n>()('Postgres.Connection') {}\n\n/** A stack's one-line way to provide a cluster: `Layer.provide(postgresConnection({ … }))` on\n * `PostgresDatabaseProvider()`, or merged into the stack's own layer tree. */\nexport const postgresConnection = (\n config: PostgresConnectionConfig,\n): Layer.Layer<PostgresConnection> => Layer.succeed(PostgresConnection, Effect.succeed(config));\n\n/**\n * Run one operation against a freshly built, freshly closed connection pool.\n *\n * Every `read`, `reconcile` step and `delete` in this family calls this once for the whole\n * operation — never once per statement — so a reconcile that runs a `SELECT`, then a `CREATE`,\n * then a re-read shares one pool and closes it once, while a plan-only `read` still opens and\n * closes its own.\n */\nexport const withPg = <A, E>(\n build: (pg: PgClient.PgClient) => Effect.Effect<A, E>,\n): Effect.Effect<A, E | SqlError, PostgresConnection> =>\n Effect.gen(function* () {\n const resolveConfig = yield* PostgresConnection;\n const config = yield* resolveConfig;\n return yield* Effect.provide(Effect.flatMap(PgClient.PgClient, build
|
|
5
|
+
"/**\n * Where a database's cluster comes from, and how each lifecycle operation gets a client.\n *\n * ⛔ NO ESTATE VALUE LIVES HERE. The kit is PUBLIC; the mini's socket path, its user and its\n * database names are the CONSUMER's props, passed to `postgresConnection(...)` in a house\n * stack — never a default, never a fallback (`docs/postgres.md#path`).\n * ★ A LAZY `Context.Service` WHOSE VALUE IS AN `Effect` (S24), THE SAME SHAPE THE HOUSE USES FOR\n * CREDENTIALS: `yield* yield* PostgresConnection` resolves the config INSIDE each operation,\n * never at layer build. Nothing here holds a secret today (`PostgresConnectionConfig` has no\n * `password` — S25, and CREATE DATABASE has none to assert either), but the shape is the one a\n * later TLS path (`Config.redacted` from the environment) drops into without a rewrite.\n * ★ THE POOL IS BUILT IN A SCOPE AND CLOSED PER OPERATION. `Effect.provide(op, PgClient.layer(config))`\n * already opens a scope for the layer's build and closes it when `op` finishes — one pool per\n * `withPg` call, never held across a whole `reconcile`.\n */\nimport * as PgClient from '@effect/sql-pg/PgClient';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\nimport type { PgExecutor } from './database-sql.ts';\nimport { type PsqlRunner, makePsqlExecutor } from './psql-executor.ts';\n\n/** What a stack passes to reach one cluster. `host` may be a socket directory (one beginning\n * with `/` expands to `${host}/.s.PGSQL.${port}` — measured in\n * `node_modules/@effect/sql-pg/src/PgConnection.ts`, the \"Details\" doc on `Config`) or a TCP\n * host name; `port` defaults to `@effect/sql-pg`'s own default (5432) when omitted. */\nexport interface PostgresConnectionConfig {\n readonly host: string;\n readonly port?: number;\n readonly database: string;\n readonly username: string;\n readonly ssl?: boolean;\n}\n\n/** The runner transport (`psql-executor.ts`): SQL goes through `run` instead of a socket, for a\n * host whose Postgres is reachable only by a command (CT100: ssh + `podman exec`). `template`\n * is the `CREATE DATABASE` template — `template0` by default, because CT100's `template1` is\n * ParadeDB's and would copy postgis/pg_ivm/paradedb objects into every new database. */\nexport interface PostgresRunnerConfig {\n readonly run: PsqlRunner;\n readonly database: string;\n readonly username: string;\n readonly template?: string;\n}\n\n/** What `withPg`'s callback learns about the transport beyond the client. */\nexport interface PgContext {\n readonly template?: string;\n}\n\n/** The lazy connection service. Its value is an `Effect` of the config, per S24 — see the file\n * header for why. */\nexport class PostgresConnection extends Context.Service<\n PostgresConnection,\n Effect.Effect<PostgresConnectionConfig | PostgresRunnerConfig>\n>()('Postgres.Connection') {}\n\n/** A stack's one-line way to provide a cluster: `Layer.provide(postgresConnection({ … }))` on\n * `PostgresDatabaseProvider()`, or merged into the stack's own layer tree. */\nexport const postgresConnection = (\n config: PostgresConnectionConfig,\n): Layer.Layer<PostgresConnection> => Layer.succeed(PostgresConnection, Effect.succeed(config));\n\n/** A stack's one-line way to provide a cluster reached through a command runner. */\nexport const postgresRunnerConnection = (\n config: PostgresRunnerConfig,\n): Layer.Layer<PostgresConnection> => Layer.succeed(PostgresConnection, Effect.succeed(config));\n\nconst isRunner = (c: PostgresConnectionConfig | PostgresRunnerConfig): c is PostgresRunnerConfig =>\n 'run' in c;\n\n/**\n * Run one operation against a freshly built, freshly closed connection pool.\n *\n * Every `read`, `reconcile` step and `delete` in this family calls this once for the whole\n * operation — never once per statement — so a reconcile that runs a `SELECT`, then a `CREATE`,\n * then a re-read shares one pool and closes it once, while a plan-only `read` still opens and\n * closes its own.\n */\nexport const withPg = <A, E>(\n build: (pg: PgClient.PgClient | PgExecutor, context: PgContext) => Effect.Effect<A, E>,\n): Effect.Effect<A, E | SqlError, PostgresConnection> =>\n Effect.gen(function* () {\n const resolveConfig = yield* PostgresConnection;\n const config = yield* resolveConfig;\n if (isRunner(config)) {\n return yield* build(makePsqlExecutor(config.run, config), {\n template: config.template ?? 'template0',\n });\n }\n return yield* Effect.provide(\n Effect.flatMap(PgClient.PgClient, (pg) => build(pg, {})),\n PgClient.layer(config),\n );\n });\n",
|
|
6
|
+
"/**\n * A `PgExecutor` that runs SQL through a caller-supplied command runner instead of a socket.\n *\n * For a host whose Postgres listens on loopback only inside a container (CT100: rootful podman\n * container `postgres`, local socket, user `postgres`, no password): the consumer's runner is\n * `ssh <host> sudo -n podman exec -i postgres <argv…>`, and this file only builds the `psql`\n * argv and stdin and parses the answer.\n *\n * ⛔ NO ESTATE VALUE LIVES HERE (public kit). The ssh destination, container name and sudo\n * allowlist are the runner's, in the consuming stack.\n * ⛔ `psql` HAS NO BIND PARAMETERS OVER STDIN. Every `$n` is replaced by a `quoteStringLiteral`\n * literal, and ONLY string params are accepted — this family binds a database or role name,\n * nothing else — so a number or object is a refusal, never silently stringified.\n * ★ ROWS COME BACK AS JSON: a `SELECT` is wrapped in `json_agg`, so `oid` and `datconnlimit`\n * arrive as JS numbers exactly as the socket client decodes them (database-sql.ts, no bigint).\n * ★ `VERBOSITY=verbose` puts the SQLSTATE in the error line, so `42P04` (duplicate database)\n * stays recognisable to `isDuplicateDatabaseRace`.\n */\nimport * as Effect from 'effect/Effect';\nimport {\n ConnectionError,\n SqlError,\n SqlSyntaxError,\n UnknownError,\n} from 'effect/unstable/sql/SqlError';\nimport { type PgExecutor, quoteStringLiteral } from './database-sql.ts';\n\nexport interface PsqlResult {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Runs one `psql` argv (starting with `psql`) with `stdin` and answers its exit. The consumer's\n * implementation prepends the transport (`ssh … sudo -n podman exec -i <container>`) and owns\n * the timeout and output bound: the kit sets neither. */\nexport type PsqlRunner = (call: {\n readonly argv: readonly string[];\n readonly stdin: string;\n}) => Promise<PsqlResult>;\n\nexport interface PsqlTarget {\n readonly database: string;\n readonly username: string;\n}\n\n/** Replace `$1…$n` with string literals; refuses a non-string param or a missing one. */\nexport const inlineParams = (sql: string, params: ReadonlyArray<unknown>): string =>\n params.length === 0\n ? sql\n : sql.replace(/\\$(\\d+)/g, (_match, index: string) => {\n const value = params[Number(index) - 1];\n if (typeof value !== 'string') {\n throw new Error(`psql executor: param $${index} must be a string, got ${typeof value}`);\n }\n return quoteStringLiteral(value);\n });\n\n/** Postgres `to_json` emits the `oid` type as a string; the socket driver returns a number. */\nconst normalizeRow = (row: unknown): unknown =>\n typeof row === 'object' && row !== null && typeof (row as { oid?: unknown }).oid === 'string'\n ? { ...row, oid: Number((row as { oid: string }).oid) }\n : row;\n\nconst isSelect = (sql: string): boolean => /^\\s*SELECT\\b/i.test(sql);\n\nconst wrapRows = (sql: string): string =>\n `SELECT coalesce(json_agg(t), '[]'::json)::text FROM (${sql}) t;`;\n\nconst failure = (operation: string, result: PsqlResult): SqlError => {\n const state = /ERROR:\\s+([0-9A-Z]{5}):/.exec(result.stderr)?.[1];\n const fields = {\n cause: Object.assign(\n new Error(result.stderr.trim()),\n state === undefined ? {} : { code: state },\n ),\n message: result.stderr.trim(),\n operation,\n };\n const reason =\n state === undefined\n ? new ConnectionError(fields)\n : state.startsWith('42')\n ? new SqlSyntaxError(fields)\n : new UnknownError(fields);\n return new SqlError({ reason });\n};\n\nexport const makePsqlExecutor = (run: PsqlRunner, target: PsqlTarget): PgExecutor => ({\n unsafe: <A extends object>(sql: string, params: ReadonlyArray<unknown> = []) =>\n Effect.gen(function* () {\n const inlined = yield* Effect.try({\n try: () => inlineParams(sql, params),\n catch: (cause) =>\n new SqlError({ reason: new UnknownError({ cause, operation: 'psql inline params' }) }),\n });\n const rows = isSelect(inlined);\n const result = yield* Effect.tryPromise({\n try: () =>\n run({\n argv: [\n 'psql',\n '-X',\n '-q',\n '-A',\n '-t',\n '-v',\n 'ON_ERROR_STOP=1',\n '-v',\n 'VERBOSITY=verbose',\n '-U',\n target.username,\n '-d',\n target.database,\n ],\n stdin: rows ? wrapRows(inlined) : `${inlined};`,\n }),\n catch: (cause) =>\n new SqlError({ reason: new ConnectionError({ cause, operation: 'psql exec' }) }),\n });\n if (result.code !== 0) return yield* Effect.fail(failure(inlined.slice(0, 40), result));\n if (!rows) return [] as ReadonlyArray<A>;\n const parsed = yield* Effect.try({\n try: () => (JSON.parse(result.stdout.trim() || '[]') as unknown[]).map(normalizeRow),\n catch: (cause) =>\n new SqlError({ reason: new UnknownError({ cause, operation: 'psql parse output' }) }),\n });\n return parsed as ReadonlyArray<A>;\n }),\n});\n",
|
|
7
|
+
"/**\n * The four statements `Postgres.Database` ever issues, and the quoting each one needs.\n *\n * ⛔ `CREATE DATABASE` TAKES NO BIND PARAMETERS FOR ANY OF ITS VALUES, INCLUDING `OWNER`.\n * Measured at `gram.y@REL_18_6`: `CreatedbStmt: CREATE DATABASE name opt_with\n * createdb_opt_list`, and every `createdb_opt_item` value is `NumericOnly`,\n * `opt_boolean_or_string` or the bare keyword `DEFAULT` — none of those productions reaches\n * `PARAM` (`$n`). `sql\\`…\\`` cannot help here; every free-form value in this statement is\n * escaped by hand, once, in this file, and the assembled text runs through `.unsafe()`.\n * ⚠️ THE DATABASE NAME AND EFFECT'S OWN `sql(value)` IDENTIFIER HELPER DO NOT MIX. `name` is a\n * `ColId` (`gram.y@REL_18_6` line 17327), a single token — but `Statement.defaultEscape('\"')`\n * (what `sql(value)` compiles to) also rewrites every `.` in the value to `\".\"`, because it is\n * built for a *qualified* name like `schema.table`. A database named `\"my.app\"` would come out\n * as `\"my\".\"app\"` — two tokens where `CreatedbStmt` parses exactly one — a syntax error the\n * engine would blame on this provider. `quoteIdent` below is the single-token form: it quotes\n * and doubles embedded `\"`, and nothing else.\n * ★ EVERY OTHER `WITH` VALUE (`OWNER`, `TABLESPACE`, `ENCODING`, `LOCALE_PROVIDER`,\n * `LC_COLLATE`, `LC_CTYPE`) IS A STRING LITERAL, NOT AN IDENTIFIER. Measured at the same tag:\n * `createdb_opt_item`'s string branch is `opt_boolean_or_string → NonReservedWord_or_Sconst →\n * NonReservedWord | Sconst`. A locale like `en_US.UTF-8` would hit the same dot-splitting\n * `quoteIdent` avoids if it were identifier-quoted; a plain `Sconst` (`quoteStringLiteral`) has\n * no such rule. This rests on `standard_conforming_strings = on` (the PG18 default, and\n * measured live 2026-09-23 — `docs/postgres.md#measured`); the escaper only ever doubles `'`\n * and never emits a backslash, so it never depends on which way that setting points.\n */\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';\n\n/** What every function in this file needs from a client — real (`PgClient.PgClient`) or fake\n * (`fake-sql.ts`). Deliberately smaller than `SqlClient`: nothing here ever needs the tagged\n * template, transactions or streaming, because every statement is either bound params through\n * `.unsafe(text, params)` or a hand-escaped literal through `.unsafe(text)`. */\nexport interface PgExecutor {\n readonly unsafe: <A extends object>(\n sql: string,\n params?: ReadonlyArray<unknown>,\n ) => Effect.Effect<ReadonlyArray<A>, SqlError>;\n}\n\n/** Single-token identifier quoting: wrap in `\"`, double any embedded `\"`. No dot-splitting —\n * see the file header. Used for the database name and, since a role or tablespace name is a\n * `NonReservedWord` too, for `OWNER` and `TABLESPACE`. */\nexport const quoteIdent = (value: string): string => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\n/** SQL string-literal quoting: wrap in `'`, double any embedded `'`. Used for every `WITH`\n * value that is locale or encoding text, never for an identifier (see the file header). */\nexport const quoteStringLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\nconst withOptions = (props: PostgresDatabaseProps, template?: string): string => {\n const parts: string[] = [`OWNER ${quoteIdent(props.owner)}`];\n if (template !== undefined) parts.push(`TEMPLATE ${quoteIdent(template)}`);\n if (props.encoding !== undefined) parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);\n if (props.localeProvider !== undefined) {\n parts.push(`LOCALE_PROVIDER ${quoteStringLiteral(props.localeProvider)}`);\n }\n if (props.lcCollate !== undefined)\n parts.push(`LC_COLLATE ${quoteStringLiteral(props.lcCollate)}`);\n if (props.lcCtype !== undefined) parts.push(`LC_CTYPE ${quoteStringLiteral(props.lcCtype)}`);\n if (props.tablespace !== undefined) parts.push(`TABLESPACE ${quoteIdent(props.tablespace)}`);\n if (props.allowConnections !== undefined) {\n parts.push(`ALLOW_CONNECTIONS ${props.allowConnections ? 'true' : 'false'}`);\n }\n if (props.connectionLimit !== undefined) {\n parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);\n }\n if (props.isTemplate !== undefined)\n parts.push(`IS_TEMPLATE ${props.isTemplate ? 'true' : 'false'}`);\n return parts.join(' ');\n};\n\n/**\n * Build the exact `CREATE DATABASE` text — a pure function, unit-tested on its own with no\n * client at all. `CONNECTION LIMIT` and the two booleans are never string-escaped: an integer is\n * `Math.trunc`ed and stringified, and a boolean is one of exactly two literal words this file\n * writes, so no declared value reaches the statement through string interpolation unescaped.\n */\nexport const buildCreateDatabaseSql = (props: PostgresDatabaseProps, template?: string): string =>\n `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props, template)}`;\n\n/** `42P04` (`duplicate_database`) is a race: something else created the name between our read\n * and our create. Measured at `internal/sqlError.ts@effect/sql-pg 4.0.0-rc.115`: every SQLSTATE\n * starting `42` — this one included — classifies as `SqlSyntaxError`, the same tag a genuine\n * syntax error would raise; there is no more specific tag to `catchTag` on. So this reads the\n * raw code PostgreSQL sent, carried on `reason.cause.code` (`PgConnection.ts#errorFromFields`,\n * `Object.assign(new Error(...), fields)`), rather than trusting the tag alone. */\nexport const isDuplicateDatabaseRace = (error: SqlError): boolean => {\n if (error.reason._tag !== 'SqlSyntaxError') return false;\n const cause = error.reason.cause;\n return (\n typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '42P04'\n );\n};\n\nconst ROLE_EXISTS_SQL = 'SELECT 1 AS present FROM pg_roles WHERE rolname = $1';\n\n/** `OWNER` is checked against `pg_roles` before any `CREATE`, so a missing role is\n * `PostgresDatabaseOwnerMissing` at reconcile time rather than the server's own\n * `ERROR: role \"…\" does not exist` surfacing as an unclassified statement failure. */\nexport const roleExists = (pg: PgExecutor, role: string): Effect.Effect<boolean, SqlError> =>\n Effect.map(\n pg.unsafe<{ readonly present: number }>(ROLE_EXISTS_SQL, [role]),\n (rows) => rows.length > 0,\n );\n\n/** `datlocprovider` is Postgres's internal 1-byte `\"char\"` type, which this client decodes as\n * raw bytes rather than text (measured 2026-09-23 against the live socket — `docs/postgres.md`);\n * the `CASE` maps it to the same three words `pg_collation.h@REL_18_6#collprovider_name` returns\n * for the same codes, so the row never crosses the wire as anything but ordinary `text`.\n *\n * ⛔ `oid` AND `datconnlimit` ARE READ AT THEIR NATIVE CATALOG WIDTH — NEVER CAST TO `::int8`.\n * `pg_database.h@REL_18_6` types them `Oid oid` (4 bytes, builtin OID 26) and\n * `int32 datconnlimit` (4 bytes, builtin OID 23); `@effect/sql-pg`'s codec table\n * (`PgTypes.ts@4.0.0-rc.115`) decodes both of those as a plain JS `number`\n * (`readUint32`/`readInt32`), matching `PostgresDatabaseAttributes`'s declared `number`\n * fields. Casting either to `::int8` changes the WIRE type the server sends, so the same\n * codec table decodes it as a JS `bigint` instead (`OID.int8`'s codec calls\n * `DataView.getBigInt64`) — and alchemy's `encodeState`\n * (`node_modules/alchemy/lib/State/StateEncoding.js`) has no `bigint` branch, so\n * `JSON.stringify`ing the persisted state throws `TypeError: Do not know how to serialize a\n * BigInt` on every successful reconcile, and a live `bigint` compared against a declared\n * `number` in `firstDrift` below is never `===`, so a matching `connectionLimit` reads as\n * permanent drift. Measured 2026-09-23; regression test:\n * `database-bigint-serialization.test.ts`. */\nconst SELECT_DATABASE_SQL = `SELECT\n d.oid AS oid,\n d.datname AS name,\n pg_get_userbyid(d.datdba) AS owner,\n pg_encoding_to_char(d.encoding) AS encoding,\n CASE d.datlocprovider\n WHEN 'b' THEN 'builtin' WHEN 'i' THEN 'icu' WHEN 'c' THEN 'libc'\n ELSE 'libc'\n END AS \"localeProvider\",\n d.datcollate AS collate,\n d.datctype AS ctype,\n d.datallowconn AS \"allowConnections\",\n d.datconnlimit AS \"connectionLimit\",\n d.datistemplate AS \"isTemplate\",\n t.spcname AS tablespace\n FROM pg_database d\n JOIN pg_tablespace t ON t.oid = d.dattablespace\n WHERE d.datname = $1`;\n\n/** The read: one bound `SELECT` on `pg_database`, joined to the two functions that turn its raw\n * owner and encoding columns into names (`docs/postgres.md#mapping`). `undefined` when the row\n * is absent — ownership branding happens one layer up, in `database.ts`, not here. */\nexport const selectDatabase = (\n pg: PgExecutor,\n name: string,\n): Effect.Effect<PostgresDatabaseAttributes | undefined, SqlError> =>\n Effect.map(pg.unsafe<PostgresDatabaseAttributes>(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);\n\n/** Compare every declared, asserted prop against the live row. Returns the name of the first\n * prop that drifted, or `undefined` when every declared prop matches. `name` and `owner` are\n * compared by their callers (identity and the create-time assertion), never here. */\nexport const firstDrift = (\n props: PostgresDatabaseProps,\n live: PostgresDatabaseAttributes,\n): { readonly prop: string; readonly declared: unknown; readonly live: unknown } | undefined => {\n const checks: ReadonlyArray<readonly [string, unknown, unknown]> = [\n ['encoding', props.encoding, live.encoding],\n ['localeProvider', props.localeProvider, live.localeProvider],\n ['lcCollate', props.lcCollate, live.collate],\n ['lcCtype', props.lcCtype, live.ctype],\n ['allowConnections', props.allowConnections, live.allowConnections],\n ['connectionLimit', props.connectionLimit, live.connectionLimit],\n ['isTemplate', props.isTemplate, live.isTemplate],\n ['tablespace', props.tablespace, live.tablespace],\n ];\n for (const [prop, declared, liveValue] of checks) {\n if (declared !== undefined && declared !== liveValue)\n return { prop, declared, live: liveValue };\n }\n return undefined;\n};\n",
|
|
6
8
|
"/**\n * `Postgres.Database` props and attributes, and the one prop-level check that runs before any\n * statement: the name's byte length against `NAMEDATALEN`.\n *\n * ⛔ EVERY ASSERTED PROP HERE HAS A CITED COLUMN. `docs/postgres.md#mapping` and\n * `database-provenance.test.ts` are what make that true rather than asserted: the test parses\n * the two committed fixtures (`create_database.sgml`'s synopsis, `pg_database.h`'s struct) and\n * fails if a prop here has no synopsis option or no catalog column behind it.\n * ⛔ NO PASSWORD, EVER. A database has none in `pg_database` (S25) — `CREATE DATABASE` has no\n * password option at all (`gram.y@REL_18_6#createdb_opt_name`, measured: `IDENT | CONNECTION\n * LIMIT | ENCODING | LOCATION | OWNER | TABLESPACE | TEMPLATE`).\n */\n\n/** NAMEDATALEN (`pg_config_manual.h@REL_18_6` line 29) minus the C string terminator: the\n * longest name the server stores without truncating. */\nexport const POSTGRES_NAME_MAX_BYTES = 63;\n\n/** UTF-8 byte length, the same unit `NAMEDATALEN` counts in (`scansup.c@REL_18_6#truncate_identifier`\n * clips by `pg_mbcliplen`, a byte-oriented, multibyte-aware clip — never a JS `.length`, which\n * counts UTF-16 code units and would pass a 63-byte multibyte name that is really 70+ bytes, or\n * refuse a 63-byte name whose `.length` looks smaller). */\nexport const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).byteLength;\n\n/**\n * Declared, at-most-once-asserted properties of one database.\n *\n * Every prop here maps to a `CREATE DATABASE` `WITH` option `create_database.sgml` documents\n * AND a `pg_database` column that stores it (`database-provenance.test.ts`). `TEMPLATE`,\n * `STRATEGY` and `OID` are excluded because nothing in `pg_database` reads them back once the\n * database exists; `ICU_RULES` and `COLLATION_VERSION` are excluded for having no consumer in\n * this family today. See `docs/postgres.md#excluded`.\n */\nexport interface PostgresDatabaseProps {\n /** At most 63 UTF-8 bytes — refused at plan past that (`PostgresDatabaseNameRefused`). The\n * identity: a name change is a plan-time refusal, never a replace. */\n readonly name: string;\n /** A role name in `pg_roles`, asserted before create (`PostgresDatabaseOwnerMissing`) and\n * compared against `pg_get_userbyid(datdba)` on every plan thereafter. */\n readonly owner: string;\n /** `pg_encoding_to_char(datdatabase.encoding)`, e.g. `\"UTF8\"`. @default the template's. */\n readonly encoding?: string;\n /** `'builtin' | 'icu' | 'libc'` — `pg_database.datlocprovider` decoded\n * (`pg_collation.h@REL_18_6#collprovider_name`). @default the template's. */\n readonly localeProvider?: 'builtin' | 'icu' | 'libc';\n /** `pg_database.datcollate`. @default the template's. */\n readonly lcCollate?: string;\n /** `pg_database.datctype`. @default the template's. */\n readonly lcCtype?: string;\n /** `pg_database.datallowconn`. @default `true`. */\n readonly allowConnections?: boolean;\n /** `pg_database.datconnlimit`; `-1` means unlimited. @default `-1`. */\n readonly connectionLimit?: number;\n /** `pg_database.datistemplate`. @default `false`. */\n readonly isTemplate?: boolean;\n /** A tablespace name in `pg_tablespace`. @default the template's (usually `pg_default`). */\n readonly tablespace?: string;\n}\n\n/** Live attributes, read back after every reconcile. No `password` — see the file header. */\nexport interface PostgresDatabaseAttributes {\n readonly name: string;\n readonly oid: number;\n readonly owner: string;\n readonly encoding: string;\n readonly localeProvider: 'builtin' | 'icu' | 'libc';\n readonly collate: string;\n readonly ctype: string;\n readonly allowConnections: boolean;\n readonly connectionLimit: number;\n readonly isTemplate: boolean;\n readonly tablespace: string;\n}\n\n/**\n * Refuse a name the server would truncate, before any statement is built.\n *\n * ★ CALLED FROM `diff` (so a plan fails, never a deploy) AND FROM `reconcile` (so a fake or a\n * direct call gets the same refusal, never just a broken statement).\n */\nexport const nameByteRefusal = (\n name: string,\n): { readonly byteLength: number; readonly limit: number } | undefined => {\n const byteLength = utf8ByteLength(name);\n return byteLength > POSTGRES_NAME_MAX_BYTES\n ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES }\n : undefined;\n};\n",
|
|
7
|
-
"/**\n * The four statements `Postgres.Database` ever issues, and the quoting each one needs.\n *\n * ⛔ `CREATE DATABASE` TAKES NO BIND PARAMETERS FOR ANY OF ITS VALUES, INCLUDING `OWNER`.\n * Measured at `gram.y@REL_18_6`: `CreatedbStmt: CREATE DATABASE name opt_with\n * createdb_opt_list`, and every `createdb_opt_item` value is `NumericOnly`,\n * `opt_boolean_or_string` or the bare keyword `DEFAULT` — none of those productions reaches\n * `PARAM` (`$n`). `sql\\`…\\`` cannot help here; every free-form value in this statement is\n * escaped by hand, once, in this file, and the assembled text runs through `.unsafe()`.\n * ⚠️ THE DATABASE NAME AND EFFECT'S OWN `sql(value)` IDENTIFIER HELPER DO NOT MIX. `name` is a\n * `ColId` (`gram.y@REL_18_6` line 17327), a single token — but `Statement.defaultEscape('\"')`\n * (what `sql(value)` compiles to) also rewrites every `.` in the value to `\".\"`, because it is\n * built for a *qualified* name like `schema.table`. A database named `\"my.app\"` would come out\n * as `\"my\".\"app\"` — two tokens where `CreatedbStmt` parses exactly one — a syntax error the\n * engine would blame on this provider. `quoteIdent` below is the single-token form: it quotes\n * and doubles embedded `\"`, and nothing else.\n * ★ EVERY OTHER `WITH` VALUE (`OWNER`, `TABLESPACE`, `ENCODING`, `LOCALE_PROVIDER`,\n * `LC_COLLATE`, `LC_CTYPE`) IS A STRING LITERAL, NOT AN IDENTIFIER. Measured at the same tag:\n * `createdb_opt_item`'s string branch is `opt_boolean_or_string → NonReservedWord_or_Sconst →\n * NonReservedWord | Sconst`. A locale like `en_US.UTF-8` would hit the same dot-splitting\n * `quoteIdent` avoids if it were identifier-quoted; a plain `Sconst` (`quoteStringLiteral`) has\n * no such rule. This rests on `standard_conforming_strings = on` (the PG18 default, and\n * measured live 2026-09-23 — `docs/postgres.md#measured`); the escaper only ever doubles `'`\n * and never emits a backslash, so it never depends on which way that setting points.\n */\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';\n\n/** What every function in this file needs from a client — real (`PgClient.PgClient`) or fake\n * (`fake-sql.ts`). Deliberately smaller than `SqlClient`: nothing here ever needs the tagged\n * template, transactions or streaming, because every statement is either bound params through\n * `.unsafe(text, params)` or a hand-escaped literal through `.unsafe(text)`. */\nexport interface PgExecutor {\n readonly unsafe: <A extends object>(\n sql: string,\n params?: ReadonlyArray<unknown>,\n ) => Effect.Effect<ReadonlyArray<A>, SqlError>;\n}\n\n/** Single-token identifier quoting: wrap in `\"`, double any embedded `\"`. No dot-splitting —\n * see the file header. Used for the database name and, since a role or tablespace name is a\n * `NonReservedWord` too, for `OWNER` and `TABLESPACE`. */\nexport const quoteIdent = (value: string): string => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\n/** SQL string-literal quoting: wrap in `'`, double any embedded `'`. Used for every `WITH`\n * value that is locale or encoding text, never for an identifier (see the file header). */\nexport const quoteStringLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\nconst withOptions = (props: PostgresDatabaseProps): string => {\n const parts: string[] = [`OWNER ${quoteIdent(props.owner)}`];\n if (props.encoding !== undefined) parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);\n if (props.localeProvider !== undefined) {\n parts.push(`LOCALE_PROVIDER ${quoteStringLiteral(props.localeProvider)}`);\n }\n if (props.lcCollate !== undefined)\n parts.push(`LC_COLLATE ${quoteStringLiteral(props.lcCollate)}`);\n if (props.lcCtype !== undefined) parts.push(`LC_CTYPE ${quoteStringLiteral(props.lcCtype)}`);\n if (props.tablespace !== undefined) parts.push(`TABLESPACE ${quoteIdent(props.tablespace)}`);\n if (props.allowConnections !== undefined) {\n parts.push(`ALLOW_CONNECTIONS ${props.allowConnections ? 'true' : 'false'}`);\n }\n if (props.connectionLimit !== undefined) {\n parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);\n }\n if (props.isTemplate !== undefined)\n parts.push(`IS_TEMPLATE ${props.isTemplate ? 'true' : 'false'}`);\n return parts.join(' ');\n};\n\n/**\n * Build the exact `CREATE DATABASE` text — a pure function, unit-tested on its own with no\n * client at all. `CONNECTION LIMIT` and the two booleans are never string-escaped: an integer is\n * `Math.trunc`ed and stringified, and a boolean is one of exactly two literal words this file\n * writes, so no declared value reaches the statement through string interpolation unescaped.\n */\nexport const buildCreateDatabaseSql = (props: PostgresDatabaseProps): string =>\n `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props)}`;\n\n/** `42P04` (`duplicate_database`) is a race: something else created the name between our read\n * and our create. Measured at `internal/sqlError.ts@effect/sql-pg 4.0.0-rc.115`: every SQLSTATE\n * starting `42` — this one included — classifies as `SqlSyntaxError`, the same tag a genuine\n * syntax error would raise; there is no more specific tag to `catchTag` on. So this reads the\n * raw code PostgreSQL sent, carried on `reason.cause.code` (`PgConnection.ts#errorFromFields`,\n * `Object.assign(new Error(...), fields)`), rather than trusting the tag alone. */\nexport const isDuplicateDatabaseRace = (error: SqlError): boolean => {\n if (error.reason._tag !== 'SqlSyntaxError') return false;\n const cause = error.reason.cause;\n return (\n typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '42P04'\n );\n};\n\nconst ROLE_EXISTS_SQL = 'SELECT 1 AS present FROM pg_roles WHERE rolname = $1';\n\n/** `OWNER` is checked against `pg_roles` before any `CREATE`, so a missing role is\n * `PostgresDatabaseOwnerMissing` at reconcile time rather than the server's own\n * `ERROR: role \"…\" does not exist` surfacing as an unclassified statement failure. */\nexport const roleExists = (pg: PgExecutor, role: string): Effect.Effect<boolean, SqlError> =>\n Effect.map(\n pg.unsafe<{ readonly present: number }>(ROLE_EXISTS_SQL, [role]),\n (rows) => rows.length > 0,\n );\n\n/** `datlocprovider` is Postgres's internal 1-byte `\"char\"` type, which this client decodes as\n * raw bytes rather than text (measured 2026-09-23 against the live socket — `docs/postgres.md`);\n * the `CASE` maps it to the same three words `pg_collation.h@REL_18_6#collprovider_name` returns\n * for the same codes, so the row never crosses the wire as anything but ordinary `text`.\n *\n * ⛔ `oid` AND `datconnlimit` ARE READ AT THEIR NATIVE CATALOG WIDTH — NEVER CAST TO `::int8`.\n * `pg_database.h@REL_18_6` types them `Oid oid` (4 bytes, builtin OID 26) and\n * `int32 datconnlimit` (4 bytes, builtin OID 23); `@effect/sql-pg`'s codec table\n * (`PgTypes.ts@4.0.0-rc.115`) decodes both of those as a plain JS `number`\n * (`readUint32`/`readInt32`), matching `PostgresDatabaseAttributes`'s declared `number`\n * fields. Casting either to `::int8` changes the WIRE type the server sends, so the same\n * codec table decodes it as a JS `bigint` instead (`OID.int8`'s codec calls\n * `DataView.getBigInt64`) — and alchemy's `encodeState`\n * (`node_modules/alchemy/lib/State/StateEncoding.js`) has no `bigint` branch, so\n * `JSON.stringify`ing the persisted state throws `TypeError: Do not know how to serialize a\n * BigInt` on every successful reconcile, and a live `bigint` compared against a declared\n * `number` in `firstDrift` below is never `===`, so a matching `connectionLimit` reads as\n * permanent drift. Measured 2026-09-23; regression test:\n * `database-bigint-serialization.test.ts`. */\nconst SELECT_DATABASE_SQL = `SELECT\n d.oid AS oid,\n d.datname AS name,\n pg_get_userbyid(d.datdba) AS owner,\n pg_encoding_to_char(d.encoding) AS encoding,\n CASE d.datlocprovider\n WHEN 'b' THEN 'builtin' WHEN 'i' THEN 'icu' WHEN 'c' THEN 'libc'\n ELSE 'libc'\n END AS \"localeProvider\",\n d.datcollate AS collate,\n d.datctype AS ctype,\n d.datallowconn AS \"allowConnections\",\n d.datconnlimit AS \"connectionLimit\",\n d.datistemplate AS \"isTemplate\",\n t.spcname AS tablespace\n FROM pg_database d\n JOIN pg_tablespace t ON t.oid = d.dattablespace\n WHERE d.datname = $1`;\n\n/** The read: one bound `SELECT` on `pg_database`, joined to the two functions that turn its raw\n * owner and encoding columns into names (`docs/postgres.md#mapping`). `undefined` when the row\n * is absent — ownership branding happens one layer up, in `database.ts`, not here. */\nexport const selectDatabase = (\n pg: PgExecutor,\n name: string,\n): Effect.Effect<PostgresDatabaseAttributes | undefined, SqlError> =>\n Effect.map(pg.unsafe<PostgresDatabaseAttributes>(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);\n\n/** Compare every declared, asserted prop against the live row. Returns the name of the first\n * prop that drifted, or `undefined` when every declared prop matches. `name` and `owner` are\n * compared by their callers (identity and the create-time assertion), never here. */\nexport const firstDrift = (\n props: PostgresDatabaseProps,\n live: PostgresDatabaseAttributes,\n): { readonly prop: string; readonly declared: unknown; readonly live: unknown } | undefined => {\n const checks: ReadonlyArray<readonly [string, unknown, unknown]> = [\n ['encoding', props.encoding, live.encoding],\n ['localeProvider', props.localeProvider, live.localeProvider],\n ['lcCollate', props.lcCollate, live.collate],\n ['lcCtype', props.lcCtype, live.ctype],\n ['allowConnections', props.allowConnections, live.allowConnections],\n ['connectionLimit', props.connectionLimit, live.connectionLimit],\n ['isTemplate', props.isTemplate, live.isTemplate],\n ['tablespace', props.tablespace, live.tablespace],\n ];\n for (const [prop, declared, liveValue] of checks) {\n if (declared !== undefined && declared !== liveValue)\n return { prop, declared, live: liveValue };\n }\n return undefined;\n};\n",
|
|
8
|
-
"/**\n * `Postgres.Database` — create-and-assert over a self-hosted PostgreSQL 18 cluster.\n *\n * ⛔ THIS IS NOT AN ALTER-CAPABLE RESOURCE. Every optional prop is asserted ONCE, at create, and\n * compared against the live row on every later plan; a mismatch is a typed\n * `PostgresDatabaseDrift` refusal, never an `ALTER DATABASE`. `docs/postgres.md` says why:\n * several of these props (`LC_COLLATE`, `LC_CTYPE`, `LOCALE_PROVIDER`) cannot be altered on a\n * live database at all without `ALTER … REFRESH COLLATION VERSION` semantics this family does\n * not implement, and the rest are left alone on principle rather than by accident.\n * ⛔ `delete` NEVER DROPS. See `errors.ts#PostgresDatabaseDropRefused` and\n * `defaultRemovalPolicy: 'retain'` below — two independent reasons the engine never issues\n * `DROP DATABASE` through this provider.\n * ★ A DATABASE CARRIES NO OWNERSHIP MARK (H1). `read` always answers `Unowned` for a match, the\n * same rule `Cloudflare.Snippets.Snippet` uses for a marker-less API — so every one of the\n * mini's 19 already-live databases needs `adopt(true)` in the stack that declares it.\n */\nimport { Resource } from 'alchemy';\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\nimport type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';\nimport { nameByteRefusal } from './database-attrs.ts';\nimport {\n buildCreateDatabaseSql,\n firstDrift,\n isDuplicateDatabaseRace,\n roleExists,\n selectDatabase,\n} from './database-sql.ts';\nimport { type PostgresConnection, withPg } from './connection.ts';\nimport type { PgExecutor } from './database-sql.ts';\nimport {\n PostgresDatabaseCreateVanished,\n PostgresDatabaseDrift,\n PostgresDatabaseDropRefused,\n PostgresDatabaseNameRefused,\n PostgresDatabaseOwnerMissing,\n PostgresDatabaseRenameRefused,\n} from './errors.ts';\n\nexport interface PostgresDatabase extends Resource<\n 'Postgres.Database',\n PostgresDatabaseProps,\n PostgresDatabaseAttributes,\n never,\n PostgresConnection\n> {}\n\nexport const PostgresDatabase = Resource<PostgresDatabase>('Postgres.Database', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** Mirrors `alchemy/Resource`'s own `isResourceOfType`: a resource constructor is a callable\n * `Object.assign`ed function, not a plain object, so `typeof` must accept both — a bare\n * `'object'` check (measured live in `scripts/smoke.ts`: it rejects `PostgresDatabase` itself,\n * matching only an instance) is the regression this comment exists to prevent. */\nexport const isPostgresDatabase = (value: unknown): value is PostgresDatabase =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Postgres.Database';\n\n/** Every asserted-prop mismatch against the LIVE row, owner included — reconcile's own\n * authoritative check, independent of what `diff` compared at plan time (S10: cloud state is\n * authoritative). */\nconst liveDrift = (props: PostgresDatabaseProps, live: PostgresDatabaseAttributes) => {\n if (props.owner !== live.owner) {\n return { prop: 'owner', declared: props.owner, live: live.owner };\n }\n return firstDrift(props, live);\n};\n\n/**\n * The core of `reconcile`, against any {@link PgExecutor} — the real pooled client through\n * `withPg`, or `fake-sql.ts`'s recording fake in tests. Kept apart from the provider wiring\n * below so a lifecycle test drives this exact function, not a re-implementation of it.\n */\nexport const reconcileWithClient = (\n pg: PgExecutor,\n props: PostgresDatabaseProps,\n): Effect.Effect<\n PostgresDatabaseAttributes,\n PostgresDatabaseOwnerMissing | PostgresDatabaseDrift | PostgresDatabaseCreateVanished | SqlError\n> =>\n Effect.gen(function* () {\n const observed = yield* selectDatabase(pg, props.name);\n if (observed === undefined) {\n const ownerPresent = yield* roleExists(pg, props.owner);\n if (!ownerPresent) {\n return yield* Effect.fail(\n new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }),\n );\n }\n yield* pg.unsafe(buildCreateDatabaseSql(props)).pipe(\n Effect.asVoid,\n Effect.catchTag('SqlError', (error) =>\n isDuplicateDatabaseRace(error) ? Effect.succeed(undefined) : Effect.fail(error),\n ),\n );\n // Never trust the write's own report (S10): re-read what the server actually stored.\n const created = yield* selectDatabase(pg, props.name);\n if (created === undefined) {\n return yield* Effect.fail(new PostgresDatabaseCreateVanished({ database: props.name }));\n }\n return created;\n }\n const drift = liveDrift(props, observed);\n if (drift !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseDrift({\n database: props.name,\n prop: drift.prop,\n declared: drift.declared,\n live: drift.live,\n }),\n );\n }\n // Unchanged: the row already read is the authoritative answer. No write, no second round trip.\n return observed;\n });\n\n/** The core of `read`: one bound `SELECT`, no ownership branding — the caller (the provider's\n * `read` below, or a test) decides whether to wrap a match in `Unowned`. */\nexport const readWithClient = (pg: PgExecutor, name: string) => selectDatabase(pg, name);\n\n/**\n * The core of `diff`, entirely offline: `news` (declared) against `output` (last-applied\n * attributes) — never a live query (that authoritative check is `reconcileWithClient`'s job, per\n * S10). A rename is a plan-time refusal; over-long names are too, so a bad name never even reaches\n * `reconcile`; every other change answers `update`, which `reconcileWithClient` then either\n * applies (create) or refuses (drift on an already-live database).\n */\nexport const diffPostgresDatabase = (\n news: Input<PostgresDatabaseProps>,\n output: PostgresDatabaseAttributes | undefined,\n) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n if (news.name !== output.name) {\n return yield* Effect.fail(\n new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }),\n );\n }\n const nameRefusal = nameByteRefusal(news.name);\n if (nameRefusal !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),\n );\n }\n const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;\n return changed ? ({ action: 'update' } as const) : ({ action: 'noop' } as const);\n });\n\n/**\n * The five lifecycle handlers, wired to `withPg` for anything that touches a client. Exported\n * on its own — not only through {@link PostgresDatabaseProvider}'s `Layer` — so a test can call\n * `postgresDatabaseHandlers.delete(...)` directly, against the real implementation, without\n * standing up the engine's provider machinery.\n */\nexport const postgresDatabaseHandlers = PostgresDatabase.Provider.of({\n // ⚠️ EMPTY, NOT A LIVE SWEEP. Enumerating `pg_database` would list every database on the\n // cluster, most of which nothing declared — the same reasoning `NetboxPrefix` documents\n // for its own marker-less `list`. Adoption for this family is one declaration at a time.\n list: () => Effect.succeed([]),\n nuke: { skip: true },\n\n read: ({ olds, output }) =>\n Effect.gen(function* () {\n const name = output?.name ?? olds.name;\n const live = yield* withPg((pg) => readWithClient(pg, name));\n return live === undefined ? undefined : Unowned(live);\n }),\n\n diff: ({ news, output }) => diffPostgresDatabase(news, output),\n\n reconcile: ({ news }) =>\n Effect.gen(function* () {\n const nameRefusal = nameByteRefusal(news.name);\n if (nameRefusal !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),\n );\n }\n return yield* withPg((pg) => reconcileWithClient(pg, news));\n }),\n\n delete: ({ olds }) => Effect.fail(new PostgresDatabaseDropRefused({ database: olds.name })),\n});\n\nexport const PostgresDatabaseProvider = () =>\n Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);\n",
|
|
9
|
+
"/**\n * `Postgres.Database` — create-and-assert over a self-hosted PostgreSQL 18 cluster.\n *\n * ⛔ THIS IS NOT AN ALTER-CAPABLE RESOURCE. Every optional prop is asserted ONCE, at create, and\n * compared against the live row on every later plan; a mismatch is a typed\n * `PostgresDatabaseDrift` refusal, never an `ALTER DATABASE`. `docs/postgres.md` says why:\n * several of these props (`LC_COLLATE`, `LC_CTYPE`, `LOCALE_PROVIDER`) cannot be altered on a\n * live database at all without `ALTER … REFRESH COLLATION VERSION` semantics this family does\n * not implement, and the rest are left alone on principle rather than by accident.\n * ⛔ `delete` NEVER DROPS. See `errors.ts#PostgresDatabaseDropRefused` and\n * `defaultRemovalPolicy: 'retain'` below — two independent reasons the engine never issues\n * `DROP DATABASE` through this provider.\n * ★ A DATABASE CARRIES NO OWNERSHIP MARK (H1). `read` always answers `Unowned` for a match, the\n * same rule `Cloudflare.Snippets.Snippet` uses for a marker-less API — so every one of the\n * mini's 19 already-live databases needs `adopt(true)` in the stack that declares it.\n */\nimport { Resource } from 'alchemy';\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport type { SqlError } from 'effect/unstable/sql/SqlError';\nimport type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';\nimport { nameByteRefusal } from './database-attrs.ts';\nimport {\n buildCreateDatabaseSql,\n firstDrift,\n isDuplicateDatabaseRace,\n roleExists,\n selectDatabase,\n} from './database-sql.ts';\nimport { type PostgresConnection, withPg } from './connection.ts';\nimport type { PgExecutor } from './database-sql.ts';\nimport {\n PostgresDatabaseCreateVanished,\n PostgresDatabaseDrift,\n PostgresDatabaseDropRefused,\n PostgresDatabaseNameRefused,\n PostgresDatabaseOwnerMissing,\n PostgresDatabaseRenameRefused,\n} from './errors.ts';\n\nexport interface PostgresDatabase extends Resource<\n 'Postgres.Database',\n PostgresDatabaseProps,\n PostgresDatabaseAttributes,\n never,\n PostgresConnection\n> {}\n\nexport const PostgresDatabase = Resource<PostgresDatabase>('Postgres.Database', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** Mirrors `alchemy/Resource`'s own `isResourceOfType`: a resource constructor is a callable\n * `Object.assign`ed function, not a plain object, so `typeof` must accept both — a bare\n * `'object'` check (measured live in `scripts/smoke.ts`: it rejects `PostgresDatabase` itself,\n * matching only an instance) is the regression this comment exists to prevent. */\nexport const isPostgresDatabase = (value: unknown): value is PostgresDatabase =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Postgres.Database';\n\n/** Every asserted-prop mismatch against the LIVE row, owner included — reconcile's own\n * authoritative check, independent of what `diff` compared at plan time (S10: cloud state is\n * authoritative). */\nconst liveDrift = (props: PostgresDatabaseProps, live: PostgresDatabaseAttributes) => {\n if (props.owner !== live.owner) {\n return { prop: 'owner', declared: props.owner, live: live.owner };\n }\n return firstDrift(props, live);\n};\n\n/**\n * The core of `reconcile`, against any {@link PgExecutor} — the real pooled client through\n * `withPg`, or `fake-sql.ts`'s recording fake in tests. Kept apart from the provider wiring\n * below so a lifecycle test drives this exact function, not a re-implementation of it.\n */\nexport const reconcileWithClient = (\n pg: PgExecutor,\n props: PostgresDatabaseProps,\n template?: string,\n): Effect.Effect<\n PostgresDatabaseAttributes,\n PostgresDatabaseOwnerMissing | PostgresDatabaseDrift | PostgresDatabaseCreateVanished | SqlError\n> =>\n Effect.gen(function* () {\n const observed = yield* selectDatabase(pg, props.name);\n if (observed === undefined) {\n const ownerPresent = yield* roleExists(pg, props.owner);\n if (!ownerPresent) {\n return yield* Effect.fail(\n new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }),\n );\n }\n yield* pg.unsafe(buildCreateDatabaseSql(props, template)).pipe(\n Effect.asVoid,\n Effect.catchTag('SqlError', (error) =>\n isDuplicateDatabaseRace(error) ? Effect.succeed(undefined) : Effect.fail(error),\n ),\n );\n // Never trust the write's own report (S10): re-read what the server actually stored.\n const created = yield* selectDatabase(pg, props.name);\n if (created === undefined) {\n return yield* Effect.fail(new PostgresDatabaseCreateVanished({ database: props.name }));\n }\n return created;\n }\n const drift = liveDrift(props, observed);\n if (drift !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseDrift({\n database: props.name,\n prop: drift.prop,\n declared: drift.declared,\n live: drift.live,\n }),\n );\n }\n // Unchanged: the row already read is the authoritative answer. No write, no second round trip.\n return observed;\n });\n\n/** The core of `read`: one bound `SELECT`, no ownership branding — the caller (the provider's\n * `read` below, or a test) decides whether to wrap a match in `Unowned`. */\nexport const readWithClient = (pg: PgExecutor, name: string) => selectDatabase(pg, name);\n\n/**\n * The core of `diff`, entirely offline: `news` (declared) against `output` (last-applied\n * attributes) — never a live query (that authoritative check is `reconcileWithClient`'s job, per\n * S10). A rename is a plan-time refusal; over-long names are too, so a bad name never even reaches\n * `reconcile`; every other change answers `update`, which `reconcileWithClient` then either\n * applies (create) or refuses (drift on an already-live database).\n */\nexport const diffPostgresDatabase = (\n news: Input<PostgresDatabaseProps>,\n output: PostgresDatabaseAttributes | undefined,\n) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n if (news.name !== output.name) {\n return yield* Effect.fail(\n new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }),\n );\n }\n const nameRefusal = nameByteRefusal(news.name);\n if (nameRefusal !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),\n );\n }\n const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;\n return changed ? ({ action: 'update' } as const) : ({ action: 'noop' } as const);\n });\n\n/**\n * The five lifecycle handlers, wired to `withPg` for anything that touches a client. Exported\n * on its own — not only through {@link PostgresDatabaseProvider}'s `Layer` — so a test can call\n * `postgresDatabaseHandlers.delete(...)` directly, against the real implementation, without\n * standing up the engine's provider machinery.\n */\nexport const postgresDatabaseHandlers = PostgresDatabase.Provider.of({\n // ⚠️ EMPTY, NOT A LIVE SWEEP. Enumerating `pg_database` would list every database on the\n // cluster, most of which nothing declared — the same reasoning `NetboxPrefix` documents\n // for its own marker-less `list`. Adoption for this family is one declaration at a time.\n list: () => Effect.succeed([]),\n nuke: { skip: true },\n\n read: ({ olds, output }) =>\n Effect.gen(function* () {\n const name = output?.name ?? olds.name;\n const live = yield* withPg((pg) => readWithClient(pg, name));\n return live === undefined ? undefined : Unowned(live);\n }),\n\n diff: ({ news, output }) => diffPostgresDatabase(news, output),\n\n reconcile: ({ news }) =>\n Effect.gen(function* () {\n const nameRefusal = nameByteRefusal(news.name);\n if (nameRefusal !== undefined) {\n return yield* Effect.fail(\n new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),\n );\n }\n return yield* withPg((pg, ctx) => reconcileWithClient(pg, news, ctx.template));\n }),\n\n delete: ({ olds }) => Effect.fail(new PostgresDatabaseDropRefused({ database: olds.name })),\n});\n\nexport const PostgresDatabaseProvider = () =>\n Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);\n",
|
|
9
10
|
"/**\n * `Postgres.Database`'s failures as typed tags (S21).\n *\n * ⛔ NONE OF THESE IS A STATUS-CODE OR MESSAGE MATCH. Every one is raised by this family's own\n * code from a fact it already checked (a byte count, a live catalog row, a role lookup) — never\n * from sniffing a driver error's text. The one live-SQLSTATE check this family makes —\n * `isDuplicateDatabaseRace` in `database-sql.ts`, for the `42P04` create race — reads the raw\n * code `@effect/sql-pg` classified onto `SqlSyntaxError.cause.code` (measured in\n * `node_modules/@effect/sql-pg/src/internal/sqlError.ts#classifySqlState`: every `42xxx` code,\n * `42P04` included, becomes `SqlSyntaxError` — there is no more specific tag for it), and it\n * never produces one of these tags: a race converges instead of failing.\n */\nimport * as Data from 'effect/Data';\n\n/**\n * A declared name would be silently truncated by the server (`NAMEDATALEN` 64, so 63 UTF-8\n * bytes is the last one that survives whole) — refused at plan, before any statement runs.\n *\n * ⛔ WHY THIS IS A PLAN-TIME REFUSAL AND NOT A SERVER ERROR. `truncate_identifier`\n * (`scansup.c@REL_18_6` line 93) clips a long name to 63 bytes and raises only a `NOTICE`\n * (`errcode(ERRCODE_NAME_TOO_LONG)`), never an error. A create would succeed against the\n * TRUNCATED name, so the next plan's read (`WHERE datname = $1`, the name exactly as\n * declared) would find nothing and try to create it again — forever. See\n * `doc/postgres.md#name`.\n */\nexport class PostgresDatabaseNameRefused extends Data.TaggedError('PostgresDatabaseNameRefused')<{\n readonly name: string;\n readonly byteLength: number;\n readonly limit: number;\n}> {\n override get message(): string {\n return (\n `Postgres.Database \"${this.name}\": ${String(this.byteLength)} UTF-8 bytes, over the ` +\n `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` +\n 'accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses ' +\n 'it instead of creating a database no later read will ever find again.'\n );\n }\n}\n\n/** A logical id's `name` changed. A database's name is its identity; there is no `ALTER … RENAME`\n * in this family's vocabulary (see `docs/postgres.md`), and `diff` never turns this into a\n * `replace` — a replace here is `DROP DATABASE` before `CREATE DATABASE`, on data. */\nexport class PostgresDatabaseRenameRefused extends Data.TaggedError(\n 'PostgresDatabaseRenameRefused',\n)<{\n readonly from: string;\n readonly to: string;\n}> {\n override get message(): string {\n return (\n `Postgres.Database: the declared name changed from \"${this.from}\" to \"${this.to}\". A ` +\n 'rename is refused at plan — this provider never replaces a database (that is DROP then ' +\n 'CREATE) and never runs ALTER … RENAME. Declare a new logical id for the new name, and ' +\n 'remove the old one once its data has moved.'\n );\n }\n}\n\n/** `OWNER` names a role `pg_roles` does not have, checked before the `CREATE DATABASE` that\n * would otherwise fail on it. Postgres itself refuses the same case with `ERROR: role \"…\" does\n * not exist` (`createdb()`, `dbcommands.c`) — this tag exists so the refusal is typed and the\n * check runs before any statement, not caught from the driver's text. */\nexport class PostgresDatabaseOwnerMissing extends Data.TaggedError('PostgresDatabaseOwnerMissing')<{\n readonly database: string;\n readonly owner: string;\n}> {\n override get message(): string {\n return `Postgres.Database \"${this.database}\": OWNER \"${this.owner}\" is not a role in pg_roles.`;\n }\n}\n\n/** A live, asserted property no longer matches the declaration. `reconcile` never issues an\n * `ALTER DATABASE` to fix this — see `docs/postgres.md` for why each asserted prop stops at\n * create. */\nexport class PostgresDatabaseDrift extends Data.TaggedError('PostgresDatabaseDrift')<{\n readonly database: string;\n readonly prop: string;\n readonly declared: unknown;\n readonly live: unknown;\n}> {\n override get message(): string {\n return (\n `Postgres.Database \"${this.database}\": ${this.prop} is declared as ` +\n `${JSON.stringify(this.declared)} but the live database has ${JSON.stringify(this.live)}. ` +\n 'This provider never issues ALTER DATABASE — update the declaration to match the live ' +\n 'value, or change it by hand and re-plan.'\n );\n }\n}\n\n/** `delete` never drops a database. `DROP DATABASE` is destructive and irreversible on data\n * this family did not create from nothing (most declared databases here are adopted, not\n * created); the decision to remove one stays a human act on the host. */\nexport class PostgresDatabaseDropRefused extends Data.TaggedError('PostgresDatabaseDropRefused')<{\n readonly database: string;\n}> {\n override get message(): string {\n return (\n `Postgres.Database \"${this.database}\": delete is refused. This provider never issues DROP ` +\n 'DATABASE. Drop it by hand over the trust socket if you mean to remove it, then remove the ' +\n 'declaration.'\n );\n }\n}\n\n/** `CREATE DATABASE` raised no error, but the immediate re-read (S10: never trust the write's\n * own report) still finds nothing. Measured to have no real trigger in a correctly speaking\n * client, but S20 forbids `Effect.die` here regardless — an invariant violation is a typed\n * failure the caller can see and retry, never a defect that crashes the whole engine. */\nexport class PostgresDatabaseCreateVanished extends Data.TaggedError(\n 'PostgresDatabaseCreateVanished',\n)<{\n readonly database: string;\n}> {\n override get message(): string {\n return (\n `Postgres.Database \"${this.database}\": CREATE DATABASE raised no error, but the database ` +\n 'is still absent on the immediate re-read. Re-plan — this is not a state this provider ' +\n 'expects to reach.'\n );\n }\n}\n\nexport type PostgresDatabaseError =\n | PostgresDatabaseNameRefused\n | PostgresDatabaseRenameRefused\n | PostgresDatabaseOwnerMissing\n | PostgresDatabaseDrift\n | PostgresDatabaseDropRefused\n | PostgresDatabaseCreateVanished;\n",
|
|
10
|
-
"/**\n * `Postgres.Database`'s provider, pre-wired to one cluster:\n *\n * const providers = postgresProviders({ host: '/opt/homeflare/postgres/sockets', database: 'postgres', username: 'tim' });\n *\n * ★ THE CLUSTER IS A PARAMETER, NOT A DEFAULT (S15, and `docs/postgres.md#path`). This kit is\n * PUBLIC; the mini's socket directory, its user and its port are the CONSUMING stack's values,\n * never baked in here. A stack with more than one cluster calls this more than once and merges\n * the results under whatever logical grouping it wants.\n */\nimport * as Layer from 'effect/Layer';\nimport {
|
|
11
|
+
"/**\n * `Postgres.Database`'s provider, pre-wired to one cluster:\n *\n * const providers = postgresProviders({ host: '/opt/homeflare/postgres/sockets', database: 'postgres', username: 'tim' });\n *\n * ★ THE CLUSTER IS A PARAMETER, NOT A DEFAULT (S15, and `docs/postgres.md#path`). This kit is\n * PUBLIC; the mini's socket directory, its user and its port are the CONSUMING stack's values,\n * never baked in here. A stack with more than one cluster calls this more than once and merges\n * the results under whatever logical grouping it wants.\n */\nimport * as Layer from 'effect/Layer';\nimport {\n type PostgresConnectionConfig,\n type PostgresRunnerConfig,\n postgresConnection,\n postgresRunnerConnection,\n} from './connection.ts';\nimport { PostgresDatabaseProvider } from './database.ts';\n\n// ⚠️ `Layer.provideMerge`, NEVER plain `Layer.provide`: the handlers' `withPg` reads\n// `PostgresConnection` when the ENGINE later calls `read`/`reconcile`, and plain `provide`\n// seals it away (\"Service not found: Postgres.Connection\" — measured for `caddyProviders`,\n// see `caddy/providers.ts`).\nexport const postgresProviders = (config: PostgresConnectionConfig) =>\n PostgresDatabaseProvider().pipe(Layer.provideMerge(postgresConnection(config)));\n\n/** The same provider over a command runner (`psql-executor.ts`) — for a loopback-only cluster. */\nexport const postgresRunnerProviders = (config: PostgresRunnerConfig) =>\n PostgresDatabaseProvider().pipe(Layer.provideMerge(postgresRunnerConnection(config)));\n"
|
|
11
12
|
],
|
|
12
|
-
"mappings": ";AAeA;AACA;AACA;AACA;
|
|
13
|
-
"debugId": "
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "0FE6812621B3699B64756E2164756E21",
|
|
14
15
|
"names": []
|
|
15
16
|
}
|