@shivaedev/local-postgres 0.7.0 → 0.9.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +51 -38
  3. package/dist/server/LocalPostgresError.d.ts +8 -0
  4. package/dist/server/LocalPostgresError.d.ts.map +1 -0
  5. package/dist/server/LocalPostgresError.js +6 -0
  6. package/dist/server/LocalPostgresError.js.map +1 -0
  7. package/dist/server/PostgresCommandError.d.ts +11 -0
  8. package/dist/server/PostgresCommandError.d.ts.map +1 -0
  9. package/dist/server/PostgresCommandError.js +9 -0
  10. package/dist/server/PostgresCommandError.js.map +1 -0
  11. package/dist/server/PostgresVersionMismatch.d.ts +8 -5
  12. package/dist/server/PostgresVersionMismatch.d.ts.map +1 -1
  13. package/dist/server/PostgresVersionMismatch.js +7 -7
  14. package/dist/server/PostgresVersionMismatch.js.map +1 -1
  15. package/dist/server/clientConnection.d.ts +13 -0
  16. package/dist/server/clientConnection.d.ts.map +1 -0
  17. package/dist/server/clientConnection.js +19 -0
  18. package/dist/server/clientConnection.js.map +1 -0
  19. package/dist/server/localConnections.d.ts +16 -6
  20. package/dist/server/localConnections.d.ts.map +1 -1
  21. package/dist/server/localConnections.js +30 -24
  22. package/dist/server/localConnections.js.map +1 -1
  23. package/dist/server/localContainer.d.ts +8 -4
  24. package/dist/server/localContainer.d.ts.map +1 -1
  25. package/dist/server/localContainer.js +24 -36
  26. package/dist/server/localContainer.js.map +1 -1
  27. package/dist/server/localDocker.d.ts +7 -2
  28. package/dist/server/localDocker.d.ts.map +1 -1
  29. package/dist/server/localDocker.js +17 -9
  30. package/dist/server/localDocker.js.map +1 -1
  31. package/dist/server/localPostgres.d.ts +8 -5
  32. package/dist/server/localPostgres.d.ts.map +1 -1
  33. package/dist/server/localPostgres.js +53 -82
  34. package/dist/server/localPostgres.js.map +1 -1
  35. package/dist/server/runCommand.d.ts +7 -0
  36. package/dist/server/runCommand.d.ts.map +1 -0
  37. package/dist/server/runCommand.js +23 -0
  38. package/dist/server/runCommand.js.map +1 -0
  39. package/package.json +8 -4
  40. package/src/server/LocalPostgresError.ts +5 -0
  41. package/src/server/PostgresCommandError.ts +8 -0
  42. package/src/server/PostgresVersionMismatch.ts +7 -7
  43. package/src/server/clientConnection.ts +20 -0
  44. package/src/server/localConnections.ts +42 -24
  45. package/src/server/localContainer.ts +32 -36
  46. package/src/server/localDocker.ts +18 -10
  47. package/src/server/localPostgres.ts +62 -85
  48. package/src/server/runCommand.ts +31 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-11-02
4
+
5
+ ### Fixed
6
+
7
+ - Pin Docker operations to the validated local endpoint and refuse conflicting Docker host or context settings in the command environment before invoking Docker.
8
+ - Accept PostgreSQL 18.6 distribution version suffixes and leave native server connection limits unchanged when the managed container is unavailable.
9
+ - Return a typed `LocalPostgresError` for malformed URL password escapes instead of a defect.
10
+
11
+ ### Breaking
12
+
13
+ - `clientConnection` now returns an Effect. Replace direct calls with `yield* clientConnection(url, options)` inside an Effect program so password decoding failures stay in the typed error channel.
14
+
15
+ ## 0.8.0 - 2026-10-31
16
+
17
+ ### Fixed
18
+
19
+ - Generated applications provide Node services when running database setup, so setup commands participate in Effect interruption and typed failure handling.
20
+
21
+ ### Breaking
22
+
23
+ - `@shivaedev/local-postgres`: `assertLocalDatabase`, `sql`, `startPostgres`, `prepareDatabases` and lower-level command helpers return Effects. Yield them in an Effect program and provide `ChildProcessSpawner` for commands, for example with `NodeServices.layer` at a Node entry point. Replace synchronous try/catch with Effect error handling. Command failures use `PostgresCommandError`; local-target and image refusals use `LocalPostgresError`. Construct the Schema tagged errors as `new PostgresVersionMismatch({ expectedVersion: "18.6", observedVersion })` or `new PostgresSettingError({ container: "development-postgres", setting: "max_connections", cause })`. Exhausted readiness checks retain their final failure instead of reporting an unobserved version mismatch.
24
+ - `@shivaedev/server`: `setupDatabases()` requires `ChildProcessSpawner` and exposes local PostgreSQL failures in its typed error channel. Provide `NodeServices.layer` at the setup entry point instead of relying on synchronous commands hidden inside the host.
25
+
3
26
  ## 0.7.0 - 2026-10-25
4
27
 
5
28
  ### Breaking
package/README.md CHANGED
@@ -7,10 +7,16 @@ Make development and integration tests share one persistent local PostgreSQL ser
7
7
  A setup command should make a machine ready to work without erasing the work already on it. Reimplementing PostgreSQL startup in every repository makes that promise harder to keep. This package gives a setup script one place to prepare the service and its databases:
8
8
 
9
9
  ```ts
10
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
11
+ import * as NodeServices from "@effect/platform-node/NodeServices";
12
+ import { Effect } from "effect";
10
13
  import { LOCAL_SERVER, localPostgres } from "@shivaedev/local-postgres/server/localPostgres.ts";
11
14
 
12
- const development = process.env.DATABASE_URL ?? `${LOCAL_SERVER}/example_dev`;
13
- localPostgres(process.env).prepareDatabases([development]);
15
+ NodeRuntime.runMain(
16
+ localPostgres({})
17
+ .prepareDatabases([`${LOCAL_SERVER}/example_dev`])
18
+ .pipe(Effect.provide(NodeServices.layer)),
19
+ );
14
20
  ```
15
21
 
16
22
  The application supplies its configured database URL. Preparation checks that the target is local and can run again without losing the data already in the database.
@@ -20,12 +26,11 @@ The application supplies its configured database URL. Preparation checks that th
20
26
  ### How to think about it
21
27
 
22
28
  There are three separate responsibilities:
23
-
24
29
  1. The **service** is PostgreSQL running on the local machine. This package prepares access to it.
25
30
  2. A **database** is an application-chosen name on that service. This package creates missing databases; the application chooses their names through configuration or a fixed allowlist.
26
31
  3. The **schema and data** belong to the application. Its setup script runs migrations, loads fixtures or seeds data after preparation.
27
32
 
28
- `localPostgres(environment)` returns three synchronous functions: `startPostgres`, `prepareDatabases` and `sql`. Use them in setup tooling. Use your application's database client and `@shivaedev/effect-sql` for persistence and migrations in an Effect application.
33
+ `localPostgres(environment)` returns three functions that produce Effects: `startPostgres`, `prepareDatabases` and `sql`. Yield them in setup tooling, and provide `ChildProcessSpawner` through the Effect environment. `NodeServices.layer` supplies the Node implementation at an entry point; command scopes preserve interruption and readiness waits use Effect’s clock. Use your application's database client and `@shivaedev/sql` for persistence and migrations in an Effect application.
29
34
 
30
35
  ### 1. Once per application: choose the databases
31
36
 
@@ -34,79 +39,87 @@ There are three separate responsibilities:
34
39
  When database names come from the application's configured URLs, pass those URLs directly to preparation:
35
40
 
36
41
  ```ts
42
+ import { Config, Effect } from "effect";
37
43
  import { LOCAL_SERVER, localPostgres } from "@shivaedev/local-postgres/server/localPostgres.ts";
38
44
 
39
- const development = process.env.DATABASE_URL ?? `${LOCAL_SERVER}/example_dev`;
40
- const tests = process.env.TEST_DATABASE_URL ?? `${LOCAL_SERVER}/example_test`;
41
-
42
- localPostgres(process.env).prepareDatabases([development, tests]);
45
+ const setup = Effect.gen(function* () {
46
+ const development = yield* Config.String("DATABASE_URL").pipe(Config.withDefault(`${LOCAL_SERVER}/example_dev`));
47
+ const tests = yield* Config.String("TEST_DATABASE_URL").pipe(Config.withDefault(`${LOCAL_SERVER}/example_test`));
48
+ const environment = yield* Config.all({
49
+ "DOCKER_CONTEXT": Config.String("DOCKER_CONTEXT").pipe(Config.withDefault("")),
50
+ "DOCKER_HOST": Config.String("DOCKER_HOST").pipe(Config.withDefault("")),
51
+ });
52
+ yield* localPostgres(environment).prepareDatabases([development, tests]);
53
+ });
43
54
  ```
44
55
 
45
56
  Generated applications use this configuration-driven approach. An allowlist built from the same URLs adds no restriction; preparation's own local-target check is the guard.
46
57
 
47
- Use `assertLocalDatabase` when the setup script owns a fixed set of database names, as this repository's `script/setup-database.ts` does. Its allowlist restricts configuration to the names the script intends to touch:
58
+ Use `assertLocalDatabase` when the setup script owns a fixed set of database names, as the Platform repository's [`script/setup-database.ts`](https://github.com/ShivaeDev/platform/blob/main/script/setup-database.ts) does. Its allowlist restricts configuration to the names the script intends to touch:
48
59
 
49
60
  ```ts
61
+ import { Effect } from "effect";
50
62
  import { assertLocalDatabase, LOCAL_SERVER, localPostgres } from "@shivaedev/local-postgres/server/localPostgres.ts";
51
63
 
52
- const development = process.env.DATABASE_URL ?? `${LOCAL_SERVER}/example_dev`;
53
- const tests = process.env.TEST_DATABASE_URL ?? `${LOCAL_SERVER}/example_test`;
54
-
55
- assertLocalDatabase(development, ["example_dev"]);
56
- assertLocalDatabase(tests, ["example_test"]);
57
-
58
- const postgres = localPostgres(process.env);
59
- postgres.prepareDatabases([development, tests]);
64
+ const setup = Effect.gen(function* () {
65
+ const development = `${LOCAL_SERVER}/example_dev`;
66
+ const tests = `${LOCAL_SERVER}/example_test`;
67
+ yield* assertLocalDatabase(development, ["example_dev"]);
68
+ yield* assertLocalDatabase(tests, ["example_test"]);
69
+ yield* localPostgres({}).prepareDatabases([development, tests]);
70
+ });
60
71
  ```
61
72
 
62
73
  `assertLocalDatabase` applies the same local-target checks and also rejects a database name outside the caller's allowlist. The fixed names above provide an independent application policy.
63
74
 
64
75
  ### 2. Once per feature: prepare its schema in application tooling
65
76
 
66
- After `prepareDatabases`, run the application's own schema or migration entry point. This package provides no schema model, migration ledger, fixture catalogue or seed convention. `@shivaedev/effect-sql` owns the SQL and migration APIs.
77
+ After `prepareDatabases`, run the application's own schema or migration entry point. This package provides no schema model, migration ledger, fixture catalogue or seed convention. `@shivaedev/sql` owns the SQL and migration APIs.
67
78
 
68
79
  ### 3. In daily setup: prepare again and inspect with SQL
69
80
 
70
81
  ```ts
82
+ import { Effect } from "effect";
71
83
  import { LOCAL_SERVER, localPostgres } from "@shivaedev/local-postgres/server/localPostgres.ts";
72
84
 
73
- const database = process.env.DATABASE_URL ?? `${LOCAL_SERVER}/example_dev`;
74
-
75
- const postgres = localPostgres(process.env);
76
- postgres.prepareDatabases([database]);
77
- console.log(postgres.sql(database, "SHOW server_version"));
85
+ const setup = Effect.gen(function* () {
86
+ const database = `${LOCAL_SERVER}/example_dev`;
87
+ const postgres = localPostgres({});
88
+ yield* postgres.prepareDatabases([database]);
89
+ yield* Effect.log(yield* postgres.sql(database, "SHOW server_version"));
90
+ });
78
91
  ```
79
92
 
80
- The version query returns the string `18.6`. `sql` also accepts a `URL` and returns the command's trimmed text. URLs carrying the application's `schema` and `connection_limit` settings work for this query; those settings are removed before calling PostgreSQL's client.
93
+ Run these setup Effects at a Node entry point with `NodeRuntime.runMain(setup.pipe(Effect.provide(NodeServices.layer)))`, as in the first example. The version query returns `18.6` for the managed image; native distributions may append packaging details. `sql` also accepts a `URL` and returns the command's trimmed text. URLs carrying the application's `schema` and `connection_limit` settings work for this query; those settings are removed before calling PostgreSQL's client.
81
94
 
82
95
  `startPostgres(connection)` prepares the service without choosing an application database to create. Its connection defaults to `LOCAL_SERVER`. Prefer `prepareDatabases` when the setup script needs named databases as well.
83
96
 
84
- On each start, setup reads `SHOW max_connections`. When the shared container allows fewer than 500 connections, setup applies `ALTER SYSTEM SET max_connections = 500`, restarts that same container, waits for readiness and confirms the setting. Existing data stays in place. A limit of 500 or higher causes no setting change or restart. If the setting cannot be applied, setup throws `PostgresSettingError` from `@shivaedev/local-postgres/server/localConnections.ts`, with `_tag`, `container`, `setting` and the original `cause`.
97
+ On each start, setup reads `SHOW max_connections`. Native servers retain their configured limit. When the running managed container publishes port 55432 and allows fewer than 500 connections, setup applies `ALTER SYSTEM SET max_connections = 500`, restarts that same container, waits for readiness and confirms the setting. Existing data stays in place. A limit of 500 or higher causes no setting change or restart. If the setting cannot be applied, setup fails with `PostgresSettingError` from `@shivaedev/local-postgres/server/localConnections.ts`, with `_tag`, `container`, `setting` and the original `cause`.
85
98
 
86
99
  ### API
87
100
 
88
101
  Import the setup API from `@shivaedev/local-postgres/server/localPostgres.ts`:
89
102
 
90
- | Export | Purpose |
91
- | --- | --- |
92
- | `LOCAL_SERVER` | The base URL `postgresql://postgres:postgres@127.0.0.1:55432`. Append the application database name. |
93
- | `assertLocalDatabase(value: string, names: readonly string[]): URL` | Validate a local database URL against application-owned allowed names. |
103
+ | Export | Purpose |
104
+ | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | `LOCAL_SERVER` | The base URL `postgresql://postgres:postgres@127.0.0.1:55432`. Append the application database name. |
106
+ | `assertLocalDatabase(value: string, names: readonly string[])` | Return an Effect validating a local database URL against application-owned allowed names. |
94
107
  | `localPostgres(environment: DockerEnvironment, readiness?: ReadinessOptions)` | Create the setup functions using the Docker settings to validate. After starting the container, `startPostgres` checks readiness `attempts` times (default 30), waiting `delayMillis` between checks (default 1000). |
95
- | `prepareDatabases(values: readonly string[]): void` | Prepare the service and create absent databases. |
96
- | `startPostgres(connection?: string): void` | Prepare the service; the default connection is `LOCAL_SERVER`. |
97
- | `sql(value: string \| URL, query: string): string` | Execute SQL through PostgreSQL's command-line client. |
108
+ | `prepareDatabases(values: readonly string[])` | Return an Effect preparing the service and creating absent databases. |
109
+ | `startPostgres(connection?: string)` | Return an Effect preparing the service; the default connection is `LOCAL_SERVER`. |
110
+ | `sql(value: string \| URL, query: string)` | Return an Effect executing SQL through PostgreSQL's command-line client. |
98
111
 
99
- The last three functions are members of the object returned by `localPostgres`, not module-level exports. They throw on failure; they are synchronous Node functions rather than Effects.
112
+ The last three functions are members of the object returned by `localPostgres`, not module-level exports. They fail in the typed error channel. `PostgresCommandError` carries the command, diagnostic message and exit code or platform cause; `LocalPostgresError` describes local-target and image refusals, and malformed URL password escapes. Exhausted readiness checks retain their final failure; a version mismatch always names a version the service returned.
100
113
 
101
114
  The module export pattern also exposes the lower-level Docker and container command functions in `@shivaedev/local-postgres/server/localDocker.ts` and `@shivaedev/local-postgres/server/localContainer.ts`. Use `@shivaedev/local-postgres/server/localPostgres.ts` for application preparation.
102
115
 
103
116
  ### Install and machine setup
104
117
 
105
118
  ```sh
106
- pnpm add --save-dev @shivaedev/local-postgres
119
+ pnpm add --save-dev @shivaedev/local-postgres @effect/platform-node effect
107
120
  ```
108
121
 
109
- The package requires Node 24 or later and has no runtime dependencies. It needs host `psql` or a local Docker installation. SQL uses host `psql` when available and falls back to the container's client when the host executable is missing. Both clients receive the connection password through `PGPASSWORD`, outside the command line. Query failures retain PostgreSQL's diagnostics and the original error as `cause` without exposing the connection password.
122
+ The package requires Node 24 or later and uses Effect as a peer dependency. It needs host `psql` or a local Docker installation. SQL uses host `psql` when available and falls back to the container's client when the host executable is missing. Both clients receive the connection password through `PGPASSWORD`, outside the command line. Query failures retain PostgreSQL's diagnostics and command failure details (and a platform `cause` when a process cannot start) without exposing the connection password.
110
123
 
111
124
  The shared baseline is PostgreSQL 18.6 on loopback port 55432. Docker preparation uses container and volume name `development-postgres`, loopback publication `127.0.0.1:55432:5432`, and this image:
112
125
 
@@ -114,14 +127,14 @@ The shared baseline is PostgreSQL 18.6 on loopback port 55432. Docker preparatio
114
127
  postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873
115
128
  ```
116
129
 
117
- A newly created container uses username and password `postgres`; connection URLs supply credentials for client commands. Setup refuses a running version other than 18.6 with `PostgresVersionMismatch` from `@shivaedev/local-postgres/server/PostgresVersionMismatch.ts`. The error carries `_tag: "PostgresVersionMismatch"`, `expectedVersion` and `observedVersion`. A running PostgreSQL 17 service is refused without losing its existing rows; upgrading it while preserving its data is your job. Container preparation and container-client fallback also check the managed image; a successful host-`psql` version probe does not inspect that image.
130
+ A newly created container uses username and password `postgres`; connection URLs supply credentials for client commands. Setup accepts native PostgreSQL 18.6 version strings with distribution suffixes, such as `18.6 (Debian 18.6-1.pgdg13+2)`, without requiring a managed container or Docker installation. Setup refuses a running release other than 18.6 with `PostgresVersionMismatch` from `@shivaedev/local-postgres/server/PostgresVersionMismatch.ts`. The error carries `_tag: "PostgresVersionMismatch"`, `expectedVersion` and `observedVersion`. A running PostgreSQL 17 service is refused without losing its existing rows; upgrading it while preserving its data is your job. Container preparation and container-client fallback also check the managed image; a successful host-`psql` version probe does not inspect that image.
118
131
 
119
- Pass the process's actual Docker settings to `localPostgres`, as `process.env` in the examples does. With `DOCKER_CONTEXT`, endpoint validation uses that context and ignores `DOCKER_HOST`; otherwise it validates `DOCKER_HOST` or the active context. Remote endpoints are rejected before a Docker operation. Docker subprocesses inherit the process environment. Supplying a different object does not configure those subprocesses.
132
+ Read Docker settings through Effect Config and pass them to `localPostgres`, as in the configuration example. With `DOCKER_CONTEXT`, endpoint validation uses that context and ignores `DOCKER_HOST`; otherwise it validates `DOCKER_HOST` or the active context. Remote endpoints are rejected before a Docker operation. Each operation pins `DOCKER_HOST` to the validated endpoint and clears `DOCKER_CONTEXT`. The lower-level `docker` function refuses command environment settings that conflict with the supplied `DOCKER_HOST` or `DOCKER_CONTEXT` before invoking Docker.
120
133
 
121
134
  ### Limits
122
135
 
123
136
  - The service version, port, container name and volume name are fixed. Choose any upgrade and data-preservation procedure outside this package.
124
- - Commands block the calling thread. Keep them in development and test setup tooling.
137
+ - Keep commands in development and test setup tooling; setup never becomes an application request path.
125
138
  - Keep allowed names, schema changes, test isolation and cleanup in the application. Preparation is not a database reset.
126
139
  - A prepared database name has 1–63 lowercase letters, digits or underscores and starts with a letter. An allowlisted connection URL does not bypass this SQL-name constraint.
127
140
  - Concurrent creation of the same absent database is not supported. Prepare databases before parallel tests begin.
@@ -0,0 +1,8 @@
1
+ import { Schema } from "effect";
2
+ declare const LocalPostgresError_base: Schema.Class<LocalPostgresError, Schema.TaggedStruct<"LocalPostgresError", {
3
+ readonly message: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ export declare class LocalPostgresError extends LocalPostgresError_base {
6
+ }
7
+ export {};
8
+ //# sourceMappingURL=LocalPostgresError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalPostgresError.d.ts","sourceRoot":"","sources":["../../src/server/LocalPostgresError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAEhC,qBAAa,kBAAmB,SAAQ,uBAEtC;CAAG"}
@@ -0,0 +1,6 @@
1
+ import { Schema } from "effect";
2
+ export class LocalPostgresError extends Schema.TaggedError()("LocalPostgresError", {
3
+ message: Schema.String,
4
+ }) {
5
+ }
6
+ //# sourceMappingURL=LocalPostgresError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalPostgresError.js","sourceRoot":"","sources":["../../src/server/LocalPostgresError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,OAAO,kBAAmB,SAAQ,MAAM,CAAC,WAAW,EAAsB,CAAC,oBAAoB,EAAE;IACtG,OAAO,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC;CAAG"}
@@ -0,0 +1,11 @@
1
+ import { Schema } from "effect";
2
+ declare const PostgresCommandError_base: Schema.Class<PostgresCommandError, Schema.TaggedStruct<"PostgresCommandError", {
3
+ readonly cause: Schema.optional<Schema.Unknown>;
4
+ readonly command: Schema.String;
5
+ readonly exitCode: Schema.optional<Schema.Number>;
6
+ readonly message: Schema.String;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ export declare class PostgresCommandError extends PostgresCommandError_base {
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=PostgresCommandError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresCommandError.d.ts","sourceRoot":"","sources":["../../src/server/PostgresCommandError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;;;;AAEhC,qBAAa,oBAAqB,SAAQ,yBAKxC;CAAG"}
@@ -0,0 +1,9 @@
1
+ import { Schema } from "effect";
2
+ export class PostgresCommandError extends Schema.TaggedError()("PostgresCommandError", {
3
+ cause: Schema.optional(Schema.Unknown),
4
+ command: Schema.String,
5
+ exitCode: Schema.optional(Schema.Number),
6
+ message: Schema.String,
7
+ }) {
8
+ }
9
+ //# sourceMappingURL=PostgresCommandError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresCommandError.js","sourceRoot":"","sources":["../../src/server/PostgresCommandError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,OAAO,oBAAqB,SAAQ,MAAM,CAAC,WAAW,EAAwB,CAAC,sBAAsB,EAAE;IAC5G,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC;CAAG"}
@@ -1,7 +1,10 @@
1
- export declare class PostgresVersionMismatch extends Error {
2
- readonly _tag = "PostgresVersionMismatch";
3
- readonly expectedVersion = "18.6";
4
- readonly observedVersion: string | undefined;
5
- constructor(observedVersion: string | undefined);
1
+ import { Schema } from "effect";
2
+ declare const PostgresVersionMismatch_base: Schema.Class<PostgresVersionMismatch, Schema.TaggedStruct<"PostgresVersionMismatch", {
3
+ readonly expectedVersion: Schema.Literal<"18.6">;
4
+ readonly observedVersion: Schema.String;
5
+ }>, import("effect/Cause").YieldableError>;
6
+ export declare class PostgresVersionMismatch extends PostgresVersionMismatch_base {
7
+ get message(): string;
6
8
  }
9
+ export {};
7
10
  //# sourceMappingURL=PostgresVersionMismatch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresVersionMismatch.d.ts","sourceRoot":"","sources":["../../src/server/PostgresVersionMismatch.ts"],"names":[],"mappings":"AAAA,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,6BAA6B;IAC1C,QAAQ,CAAC,eAAe,UAAU;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7C,YAAY,eAAe,EAAE,MAAM,GAAG,SAAS,EAG9C;CACD"}
1
+ {"version":3,"file":"PostgresVersionMismatch.d.ts","sourceRoot":"","sources":["../../src/server/PostgresVersionMismatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;;AAEhC,qBAAa,uBAAwB,SAAQ,4BAG3C;IACD,IAAa,OAAO,WAEnB;CACD"}
@@ -1,10 +1,10 @@
1
- export class PostgresVersionMismatch extends Error {
2
- _tag = "PostgresVersionMismatch";
3
- expectedVersion = "18.6";
4
- observedVersion;
5
- constructor(observedVersion) {
6
- super("Expected local PostgreSQL 18.6. Check the existing service; setup never removes containers or data.");
7
- this.observedVersion = observedVersion;
1
+ import { Schema } from "effect";
2
+ export class PostgresVersionMismatch extends Schema.TaggedError()("PostgresVersionMismatch", {
3
+ expectedVersion: Schema.Literal("18.6"),
4
+ observedVersion: Schema.String,
5
+ }) {
6
+ get message() {
7
+ return "Expected local PostgreSQL 18.6. Check the existing service; setup never removes containers or data.";
8
8
  }
9
9
  }
10
10
  //# sourceMappingURL=PostgresVersionMismatch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresVersionMismatch.js","sourceRoot":"","sources":["../../src/server/PostgresVersionMismatch.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACxC,IAAI,GAAG,yBAAyB,CAAC;IACjC,eAAe,GAAG,MAAM,CAAC;IACzB,eAAe,CAAqB;IAE7C,YAAY,eAAmC;QAC9C,KAAK,CAAC,qGAAqG,CAAC,CAAC;QAC7G,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACxC,CAAC;CACD"}
1
+ {"version":3,"file":"PostgresVersionMismatch.js","sourceRoot":"","sources":["../../src/server/PostgresVersionMismatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,OAAO,uBAAwB,SAAQ,MAAM,CAAC,WAAW,EAA2B,CAAC,yBAAyB,EAAE;IACrH,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM;CAC9B,CAAC;IACD,IAAa,OAAO;QACnB,OAAO,qGAAqG,CAAC;IAC9G,CAAC;CACD"}
@@ -0,0 +1,13 @@
1
+ import { Effect } from "effect";
2
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
3
+ import type { CommandOptions } from "#runCommand.ts";
4
+ export declare const clientConnection: (value: URL, options?: CommandOptions | undefined) => Effect.Effect<{
5
+ options: {
6
+ env: {
7
+ PGPASSWORD?: string;
8
+ };
9
+ extendEnv: boolean;
10
+ };
11
+ url: import("url").URL;
12
+ }, LocalPostgresError, never>;
13
+ //# sourceMappingURL=clientConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientConnection.d.ts","sourceRoot":"","sources":["../../src/server/clientConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,gBAAgB;;;;;;;;6BAe3B,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Effect } from "effect";
2
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
3
+ export const clientConnection = Effect.fn("LocalPostgres.clientConnection")(function* (value, options = {}) {
4
+ const url = new URL(value);
5
+ // libpq treats + literally in URI passwords.
6
+ const password = yield* Effect.try({
7
+ catch: () => new LocalPostgresError({ message: "Invalid local database password escape." }),
8
+ try: () => new URLSearchParams(url.search.replaceAll("+", "%2B")).getAll("password").at(-1) ?? decodeURIComponent(url.password),
9
+ });
10
+ url.password = "";
11
+ for (const key of ["password", "schema", "connection_limit", "pool_timeout", "pgbouncer"]) {
12
+ url.searchParams.delete(key);
13
+ }
14
+ if (!url.searchParams.has("connect_timeout")) {
15
+ url.searchParams.set("connect_timeout", "3");
16
+ }
17
+ return { options: { ...options, env: { ...options.env, ...(password && { "PGPASSWORD": password }) }, extendEnv: true }, url };
18
+ });
19
+ //# sourceMappingURL=clientConnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientConnection.js","sourceRoot":"","sources":["../../src/server/clientConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,EAAE,CAAC,gCAAgC,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAU,EAAE,OAAO,GAAmB,EAAE;IAC9H,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;QAClC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,yCAAyC,EAAE,CAAC;QAC3F,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;KAC/H,CAAC,CAAC;IACH,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,CAAC,EAAE,CAAC;QAC3F,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC;AAChI,CAAC,CAAC,CAAC"}
@@ -1,8 +1,18 @@
1
- export declare class PostgresSettingError extends Error {
2
- readonly _tag = "PostgresSettingError";
3
- readonly container = "development-postgres";
4
- readonly setting = "max_connections";
5
- constructor(message: string, options: ErrorOptions);
1
+ import { Effect, Schema } from "effect";
2
+ import type { ChildProcessSpawner } from "effect/process";
3
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
4
+ import type { docker as localDocker } from "#localDocker.ts";
5
+ import type { PostgresCommandError } from "#PostgresCommandError.ts";
6
+ import type { PostgresVersionMismatch } from "#PostgresVersionMismatch.ts";
7
+ declare const PostgresSettingError_base: Schema.Class<PostgresSettingError, Schema.TaggedStruct<"PostgresSettingError", {
8
+ readonly cause: Schema.Unknown;
9
+ readonly container: Schema.Literal<"development-postgres">;
10
+ readonly setting: Schema.Literal<"max_connections">;
11
+ }>, import("effect/Cause").YieldableError>;
12
+ export declare class PostgresSettingError extends PostgresSettingError_base {
13
+ get message(): string;
6
14
  }
7
- export declare function ensureConnections(docker: (args: readonly string[]) => string, sql: (query: string) => string, ready: () => void): void;
15
+ type Query = (query: string) => Effect.Effect<string, LocalPostgresError | PostgresCommandError, ChildProcessSpawner.ChildProcessSpawner>;
16
+ export declare const ensureConnections: (docker: (args: readonly string[]) => ReturnType<typeof localDocker>, sql: Query, ready: () => Effect.Effect<void, LocalPostgresError | PostgresCommandError | PostgresVersionMismatch, ChildProcessSpawner.ChildProcessSpawner>) => Effect.Effect<void, PostgresSettingError, ChildProcessSpawner.ChildProcessSpawner>;
17
+ export {};
8
18
  //# sourceMappingURL=localConnections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"localConnections.d.ts","sourceRoot":"","sources":["../../src/server/localConnections.ts"],"names":[],"mappings":"AAEA,qBAAa,oBAAqB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,0BAA0B;IACvC,QAAQ,CAAC,SAAS,0BAA0B;IAC5C,QAAQ,CAAC,OAAO,qBAAqB;IAErC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAGjD;CACD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,QAqB/H"}
1
+ {"version":3,"file":"localConnections.d.ts","sourceRoot":"","sources":["../../src/server/localConnections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;;;;;;AAE3E,qBAAa,oBAAqB,SAAQ,yBAIxC;IACD,IAAa,OAAO,WAEnB;CACD;AAED,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,EAAE,mBAAmB,CAAC,mBAAmB,CAAC,CAAC;AAE1I,eAAO,MAAM,iBAAiB,kBACd,SAAS,MAAM,EAAE,KAAK,UAAU,CAAC,OAAO,WAAW,CAAC,2BAEtD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,GAAG,oBAAoB,GAAG,uBAAuB,EAAE,mBAAmB,CAAC,mBAAmB,CAAC,uFA6B7I,CAAC"}
@@ -1,34 +1,40 @@
1
- import { POSTGRES_IMAGE } from "./localContainer.js";
2
- export class PostgresSettingError extends Error {
3
- _tag = "PostgresSettingError";
4
- container = "development-postgres";
5
- setting = "max_connections";
6
- constructor(message, options) {
7
- super(message, options);
8
- this.name = "PostgresSettingError";
1
+ import { Effect, Schema } from "effect";
2
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
3
+ import { POSTGRES_IMAGE } from "#localContainer.ts";
4
+ export class PostgresSettingError extends Schema.TaggedError()("PostgresSettingError", {
5
+ cause: Schema.Unknown,
6
+ container: Schema.Literal("development-postgres"),
7
+ setting: Schema.Literal("max_connections"),
8
+ }) {
9
+ get message() {
10
+ return "Could not set max_connections to at least 500 in container development-postgres.";
9
11
  }
10
12
  }
11
- export function ensureConnections(docker, sql, ready) {
12
- try {
13
- const current = Number(sql("SHOW max_connections"));
13
+ export const ensureConnections = Effect.fn("LocalPostgres.ensureConnections")(function* (docker, sql, ready) {
14
+ yield* Effect.gen(function* () {
15
+ const current = Number(yield* sql("SHOW max_connections"));
14
16
  if (!Number.isInteger(current) || current <= 0) {
15
- throw new Error("PostgreSQL returned an invalid max_connections value.");
17
+ return yield* new LocalPostgresError({ message: "PostgreSQL returned an invalid max_connections value." });
16
18
  }
17
19
  if (current >= 500) {
18
20
  return;
19
21
  }
20
- if (docker(["inspect", "--format", "{{.Config.Image}}", "development-postgres"]).trim() !== POSTGRES_IMAGE) {
21
- throw new Error("The shared PostgreSQL container uses a different image. Preserve its data and upgrade it explicitly.");
22
+ const managed = yield* docker(["ps", "--filter", "name=^/development-postgres$", "--filter", "publish=55432", "--format", "{{.ID}}"]).pipe(Effect.catchTag("PostgresCommandError", () => Effect.succeed("")));
23
+ if (!managed) {
24
+ return;
22
25
  }
23
- sql("ALTER SYSTEM SET max_connections = 500");
24
- docker(["restart", "development-postgres"]);
25
- ready();
26
- if (!(Number(sql("SHOW max_connections")) >= 500)) {
27
- throw new Error("PostgreSQL did not apply max_connections after restarting.");
26
+ const image = yield* docker(["inspect", "--format", "{{.Config.Image}}", "development-postgres"]);
27
+ if (image !== POSTGRES_IMAGE) {
28
+ return yield* new LocalPostgresError({
29
+ message: "The shared PostgreSQL container uses a different image. Preserve its data and upgrade it explicitly.",
30
+ });
28
31
  }
29
- }
30
- catch (cause) {
31
- throw new PostgresSettingError("Could not set max_connections to at least 500 in container development-postgres.", { cause });
32
- }
33
- }
32
+ yield* sql("ALTER SYSTEM SET max_connections = 500");
33
+ yield* docker(["restart", "development-postgres"]);
34
+ yield* ready();
35
+ if (!(Number(yield* sql("SHOW max_connections")) >= 500)) {
36
+ return yield* new LocalPostgresError({ message: "PostgreSQL did not apply max_connections after restarting." });
37
+ }
38
+ }).pipe(Effect.mapError((cause) => new PostgresSettingError({ cause, container: "development-postgres", setting: "max_connections" })));
39
+ });
34
40
  //# sourceMappingURL=localConnections.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"localConnections.js","sourceRoot":"","sources":["../../src/server/localConnections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACrC,IAAI,GAAG,sBAAsB,CAAC;IAC9B,SAAS,GAAG,sBAAsB,CAAC;IACnC,OAAO,GAAG,iBAAiB,CAAC;IAErC,YAAY,OAAe,EAAE,OAAqB;QACjD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACpC,CAAC;CACD;AAED,MAAM,UAAU,iBAAiB,CAAC,MAA2C,EAAE,GAA8B,EAAE,KAAiB;IAC/H,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,IAAI,MAAM,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,cAAc,EAAE,CAAC;YAC5G,MAAM,IAAI,KAAK,CAAC,sGAAsG,CAAC,CAAC;QACzH,CAAC;QACD,GAAG,CAAC,wCAAwC,CAAC,CAAC;QAC9C,MAAM,CAAC,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;QAC5C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAC/E,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,oBAAoB,CAAC,kFAAkF,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/H,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"localConnections.js","sourceRoot":"","sources":["../../src/server/localConnections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKpD,MAAM,OAAO,oBAAqB,SAAQ,MAAM,CAAC,WAAW,EAAwB,CAAC,sBAAsB,EAAE;IAC5G,KAAK,EAAE,MAAM,CAAC,OAAO;IACrB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC;CAC1C,CAAC;IACD,IAAa,OAAO;QACnB,OAAO,kFAAkF,CAAC;IAC3F,CAAC;CACD;AAID,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,iCAAiC,CAAC,CAAC,QAAQ,CAAC,EACtF,MAAmE,EACnE,GAAU,EACV,KAA8I;IAE9I,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YAChD,OAAO,KAAK,CAAC,CAAC,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,uDAAuD,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,8BAA8B,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CACzI,MAAM,CAAC,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CACjE,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO;QACR,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,CAAC,CAAC;QAClG,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,CAAC,IAAI,kBAAkB,CAAC;gBACpC,OAAO,EAAE,sGAAsG;aAC/G,CAAC,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;QACrD,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC1D,OAAO,KAAK,CAAC,CAAC,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,4DAA4D,EAAE,CAAC,CAAC;QACjH,CAAC;IACF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,oBAAoB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;AACzI,CAAC,CAAC,CAAC"}
@@ -1,7 +1,11 @@
1
- import type { ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
1
+ import { Effect } from "effect";
2
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
3
+ import type { docker as runDocker } from "#localDocker.ts";
4
+ import { PostgresCommandError } from "#PostgresCommandError.ts";
5
+ import type { CommandOptions } from "#runCommand.ts";
2
6
  export declare const POSTGRES_IMAGE = "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873";
3
- type Docker = (args: readonly string[], options?: ExecFileSyncOptionsWithStringEncoding) => string;
4
- export declare function sqlInContainer(docker: Docker, url: URL, args: readonly string[], options: ExecFileSyncOptionsWithStringEncoding): string;
5
- export declare function startContainer(docker: Docker): void;
7
+ type Docker = (args: readonly string[], options?: CommandOptions) => ReturnType<typeof runDocker>;
8
+ export declare const sqlInContainer: (docker: Docker, url: URL, args: readonly string[], options: CommandOptions) => Effect.Effect<string, LocalPostgresError | PostgresCommandError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner>;
9
+ export declare const startContainer: (docker: Docker) => Effect.Effect<undefined, LocalPostgresError | PostgresCommandError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner>;
6
10
  export {};
7
11
  //# sourceMappingURL=localContainer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"localContainer.d.ts","sourceRoot":"","sources":["../../src/server/localContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,oBAAoB,CAAC;AAGhF,eAAO,MAAM,cAAc,iGAAiG,CAAC;AAE7H,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,qCAAqC,KAAK,MAAM,CAAC;AAEnG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,qCAAqC,UAuB/H;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,QA+B5C"}
1
+ {"version":3,"file":"localContainer.d.ts","sourceRoot":"","sources":["../../src/server/localContainer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,cAAc,iGAAiG,CAAC;AAE7H,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAElG,eAAO,MAAM,cAAc,oNAmBzB,CAAC;AAEH,eAAO,MAAM,cAAc,2JA2BzB,CAAC"}
@@ -1,49 +1,37 @@
1
- import { env } from "node:process";
1
+ import { Effect } from "effect";
2
+ import { clientConnection } from "#clientConnection.ts";
3
+ import { LocalPostgresError } from "#LocalPostgresError.ts";
4
+ import { PostgresCommandError } from "#PostgresCommandError.ts";
2
5
  export const POSTGRES_IMAGE = "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873";
3
6
  const CONTAINER = "development-postgres";
4
- export function sqlInContainer(docker, url, args, options) {
5
- const id = docker(["ps", "--filter", "publish=55432", "--format", "{{.ID}}"], options).trim();
7
+ export const sqlInContainer = Effect.fn("LocalPostgres.sqlInContainer")(function* (docker, url, args, options) {
8
+ const id = yield* docker(["ps", "--filter", "publish=55432", "--format", "{{.ID}}"], options);
6
9
  if (!id || id.includes("\n")) {
7
- throw new Error("Install psql or start the shared PostgreSQL service with Docker.");
10
+ return yield* new PostgresCommandError({ command: "psql", message: "Install psql or start the shared PostgreSQL service with Docker." });
8
11
  }
9
- if (docker(["inspect", "--format", "{{.Config.Image}}", id], options).trim() !== POSTGRES_IMAGE) {
10
- throw new Error("The shared PostgreSQL image differs from the pinned baseline. Upgrade explicitly, preserving data.");
12
+ if ((yield* docker(["inspect", "--format", "{{.Config.Image}}", id], options)) !== POSTGRES_IMAGE) {
13
+ return yield* new LocalPostgresError({
14
+ message: "The shared PostgreSQL image differs from the pinned baseline. Upgrade explicitly, preserving data.",
15
+ });
11
16
  }
12
- // libpq treats + literally in URI passwords.
13
- const password = new URLSearchParams(url.search.replaceAll("+", "%2B")).getAll("password").at(-1) ?? decodeURIComponent(url.password);
14
- const inside = new URL(url);
17
+ const { url: inside, options: settings } = yield* clientConnection(url, options);
15
18
  inside.hostname = "127.0.0.1";
16
19
  inside.port = "5432";
17
- inside.password = "";
18
- inside.searchParams.delete("password");
19
- try {
20
- return docker(["exec", "--env", "PGPASSWORD", id, "psql", ...args, "--dbname", inside.toString()], {
21
- ...options,
22
- env: { ...env, ...options.env, ...(password && { "PGPASSWORD": password }) },
23
- }).trim();
24
- }
25
- catch (error) {
26
- throw new Error(error instanceof Error && "stderr" in error ? String(error.stderr) : "PostgreSQL query failed.", { cause: error });
27
- }
28
- }
29
- export function startContainer(docker) {
30
- docker(["info"]);
31
- let existing;
32
- try {
33
- docker(["container", "inspect", CONTAINER]);
34
- existing = true;
35
- }
36
- catch {
37
- existing = false;
38
- }
20
+ return yield* docker(["exec", "--env", "PGPASSWORD", id, "psql", ...args, "--dbname", inside.toString()], settings);
21
+ });
22
+ export const startContainer = Effect.fn("LocalPostgres.startContainer")(function* (docker) {
23
+ yield* docker(["info"]);
24
+ const existing = yield* docker(["container", "ls", "--all", "--filter", `name=^/${CONTAINER}$`, "--format", "{{.ID}}"]);
39
25
  if (existing) {
40
- if (docker(["inspect", "--format", "{{.Config.Image}}", CONTAINER]).trim() !== POSTGRES_IMAGE) {
41
- throw new Error("The shared PostgreSQL container uses a different image. Preserve its volume and upgrade it explicitly.");
26
+ if ((yield* docker(["inspect", "--format", "{{.Config.Image}}", CONTAINER])) !== POSTGRES_IMAGE) {
27
+ return yield* new LocalPostgresError({
28
+ message: "The shared PostgreSQL container uses a different image. Preserve its volume and upgrade it explicitly.",
29
+ });
42
30
  }
43
- docker(["start", CONTAINER]);
31
+ yield* docker(["start", CONTAINER]);
44
32
  }
45
33
  else {
46
- docker([
34
+ yield* docker([
47
35
  "run",
48
36
  "--detach",
49
37
  "--name",
@@ -59,5 +47,5 @@ export function startContainer(docker) {
59
47
  POSTGRES_IMAGE,
60
48
  ]);
61
49
  }
62
- }
50
+ });
63
51
  //# sourceMappingURL=localContainer.js.map