@shivaedev/local-postgres 0.0.0 → 0.1.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +18 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/local-container.d.ts +6 -0
- package/dist/local-container.d.ts.map +1 -0
- package/dist/local-container.js +45 -0
- package/dist/local-container.js.map +1 -0
- package/dist/local-docker.d.ts +7 -0
- package/dist/local-docker.d.ts.map +1 -0
- package/dist/local-docker.js +15 -0
- package/dist/local-docker.js.map +1 -0
- package/package.json +46 -3
- package/src/index.ts +78 -0
- package/src/local-container.ts +46 -0
- package/src/local-docker.ts +24 -0
- package/login.log +0 -26
- package/publish.log +0 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShivaeDev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
# @shivaedev/local-postgres
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Prepare one shared local PostgreSQL service without replacing containers, deleting volumes or resetting data. The service uses PostgreSQL 18.6 on loopback port 55432, pinned to `postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873`.
|
|
4
|
+
|
|
5
|
+
This Node 24+ package has no runtime dependencies. It uses a local Docker daemon when no service is available; an existing native service requires host `psql`. With Docker, the container provides `psql` when it is absent from the host.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { assertLocalDatabase, localPostgres, localServer } from "@shivaedev/local-postgres";
|
|
9
|
+
|
|
10
|
+
const development = `${localServer}/example_dev`;
|
|
11
|
+
assertLocalDatabase(development, ["example_dev"]);
|
|
12
|
+
const postgres = localPostgres(process.env);
|
|
13
|
+
postgres.prepareDatabases([development]);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`localPostgres(environment)` reads only `DOCKER_CONTEXT` and `DOCKER_HOST` from the supplied settings. Pass the actual process environment or both settings from the application's configuration service so remote Docker endpoints are rejected. Importing this package does not start a service.
|
|
17
|
+
|
|
18
|
+
`prepareDatabases(urls)` starts or reuses the service, checks its version and creates missing databases. `startPostgres(connection)` only prepares the service. `sql(connection, query)` uses host `psql`, or the pinned container's client. Database connection settings come from the supplied URL, including credentials. A new container uses the documented local defaults and a persistent `development-postgres` volume; it is never automatically upgraded.
|
|
19
|
+
|
|
20
|
+
`assertLocalDatabase(url, names)` permits only the supplied names on localhost or 127.0.0.1 port 55432. It rejects connection parameters that override the destination. Callers own their application-specific names, test isolation, schema initialization, migrations and seeds. This package does not select application databases or run migrations or seeds.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DockerEnvironment } from "./local-docker.ts";
|
|
2
|
+
export declare const localServer = "postgresql://postgres:postgres@127.0.0.1:55432";
|
|
3
|
+
export declare function assertLocalDatabase(value: string, names: readonly string[]): URL;
|
|
4
|
+
export declare function localPostgres(environment: DockerEnvironment): {
|
|
5
|
+
sql: (value: string | URL, query: string) => string;
|
|
6
|
+
startPostgres: (connection?: string) => void;
|
|
7
|
+
prepareDatabases: (values: readonly string[]) => void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAAU,MAAM,mBAAmB,CAAC;AAEnE,eAAO,MAAM,WAAW,mDAAmD,CAAC;AAE5E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,OAY1E;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,iBAAiB;iBAGvC,MAAM,GAAG,GAAG,SAAS,MAAM;;+BAsCb,SAAS,MAAM,EAAE;EAgBnD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { sqlInContainer, startContainer } from "./local-container.js";
|
|
3
|
+
import { docker } from "./local-docker.js";
|
|
4
|
+
export const localServer = "postgresql://postgres:postgres@127.0.0.1:55432";
|
|
5
|
+
export function assertLocalDatabase(value, names) {
|
|
6
|
+
const url = new URL(value);
|
|
7
|
+
const name = url.pathname.slice(1);
|
|
8
|
+
if (!["postgres:", "postgresql:"].includes(url.protocol) ||
|
|
9
|
+
!["localhost", "127.0.0.1"].includes(url.hostname) ||
|
|
10
|
+
url.port !== "55432" ||
|
|
11
|
+
["host", "hostaddr", "port", "dbname", "service"].some((key) => url.searchParams.has(key)) ||
|
|
12
|
+
!names.includes(name))
|
|
13
|
+
throw new Error(`Use a local database on 127.0.0.1:55432 named ${names.join(" or ")}.`);
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
export function localPostgres(environment) {
|
|
17
|
+
const localDocker = (args, options = { encoding: "utf8" }) => docker(environment, args, options);
|
|
18
|
+
function sql(value, query) {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
assertLocalDatabase(url.toString(), [url.pathname.slice(1)]);
|
|
21
|
+
for (const key of ["schema", "connection_limit", "pool_timeout", "pgbouncer"])
|
|
22
|
+
url.searchParams.delete(key);
|
|
23
|
+
const args = ["-X", "--set", "ON_ERROR_STOP=1", "-At", "-c", query];
|
|
24
|
+
if (!url.searchParams.has("connect_timeout"))
|
|
25
|
+
url.searchParams.set("connect_timeout", "3");
|
|
26
|
+
const options = { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] };
|
|
27
|
+
try {
|
|
28
|
+
return execFileSync("psql", [...args, "--dbname", String(url)], options).trim();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
32
|
+
throw new Error(error instanceof Error && "stderr" in error ? String(error.stderr) : "PostgreSQL query failed.");
|
|
33
|
+
return sqlInContainer(localDocker, url, args, options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function startPostgres(connection = localServer) {
|
|
37
|
+
const server = new URL(connection);
|
|
38
|
+
server.pathname = "/postgres";
|
|
39
|
+
assertLocalDatabase(server.toString(), ["postgres"]);
|
|
40
|
+
let version;
|
|
41
|
+
try {
|
|
42
|
+
version = sql(server, "SHOW server_version");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
startContainer(localDocker);
|
|
46
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
47
|
+
try {
|
|
48
|
+
version = sql(server, "SHOW server_version");
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (version !== "18.6")
|
|
57
|
+
throw new Error("Expected local PostgreSQL 18.6. Check the existing service; setup never removes containers or data.");
|
|
58
|
+
}
|
|
59
|
+
function prepareDatabases(values) {
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
const name = new URL(value).pathname.slice(1);
|
|
62
|
+
assertLocalDatabase(value, [name]);
|
|
63
|
+
if (!/^[a-z][a-z0-9_]{0,62}$/.test(name))
|
|
64
|
+
throw new Error("Invalid local database name.");
|
|
65
|
+
}
|
|
66
|
+
startPostgres(values[0]);
|
|
67
|
+
for (const value of values) {
|
|
68
|
+
const url = new URL(value);
|
|
69
|
+
const name = url.pathname.slice(1);
|
|
70
|
+
const server = new URL(url);
|
|
71
|
+
server.pathname = "/postgres";
|
|
72
|
+
if (!sql(server, `SELECT 1 FROM pg_database WHERE datname = '${name}'`))
|
|
73
|
+
sql(server, `CREATE DATABASE "${name}"`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { sql, startPostgres, prepareDatabases };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
|
|
2
|
+
type Docker = (args: readonly string[], options?: ExecFileSyncOptionsWithStringEncoding) => string;
|
|
3
|
+
export declare function sqlInContainer(docker: Docker, url: URL, args: readonly string[], options: ExecFileSyncOptionsWithStringEncoding): string;
|
|
4
|
+
export declare function startContainer(docker: Docker): void;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=local-container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-container.d.ts","sourceRoot":"","sources":["../src/local-container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,oBAAoB,CAAC;AAIhF,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,UAS/H;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,QA4B5C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const image = "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873";
|
|
2
|
+
const container = "development-postgres";
|
|
3
|
+
export function sqlInContainer(docker, url, args, options) {
|
|
4
|
+
const id = docker(["ps", "--filter", "publish=55432", "--format", "{{.ID}}"], options).trim();
|
|
5
|
+
if (!id || id.includes("\n"))
|
|
6
|
+
throw new Error("Install psql or start the shared PostgreSQL service with Docker.");
|
|
7
|
+
if (docker(["inspect", "--format", "{{.Config.Image}}", id], options).trim() !== image)
|
|
8
|
+
throw new Error("The shared PostgreSQL image differs from the pinned baseline. Upgrade explicitly, preserving data.");
|
|
9
|
+
const inside = new URL(url);
|
|
10
|
+
inside.hostname = "127.0.0.1";
|
|
11
|
+
inside.port = "5432";
|
|
12
|
+
return docker(["exec", id, "psql", ...args, "--dbname", inside.toString()], options).trim();
|
|
13
|
+
}
|
|
14
|
+
export function startContainer(docker) {
|
|
15
|
+
docker(["info"]);
|
|
16
|
+
let existing = false;
|
|
17
|
+
try {
|
|
18
|
+
docker(["container", "inspect", container]);
|
|
19
|
+
existing = true;
|
|
20
|
+
}
|
|
21
|
+
catch { }
|
|
22
|
+
if (existing) {
|
|
23
|
+
if (docker(["inspect", "--format", "{{.Config.Image}}", container]).trim() !== image)
|
|
24
|
+
throw new Error("The shared PostgreSQL container uses a different image. Preserve its volume and upgrade it explicitly.");
|
|
25
|
+
docker(["start", container]);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
docker([
|
|
29
|
+
"run",
|
|
30
|
+
"--detach",
|
|
31
|
+
"--name",
|
|
32
|
+
container,
|
|
33
|
+
"--publish",
|
|
34
|
+
"127.0.0.1:55432:5432",
|
|
35
|
+
"--env",
|
|
36
|
+
"POSTGRES_USER=postgres",
|
|
37
|
+
"--env",
|
|
38
|
+
"POSTGRES_PASSWORD=postgres",
|
|
39
|
+
"--mount",
|
|
40
|
+
"type=volume,src=development-postgres,dst=/var/lib/postgresql",
|
|
41
|
+
image,
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=local-container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-container.js","sourceRoot":"","sources":["../src/local-container.ts"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAG,8FAA8F,CAAC;AAC7G,MAAM,SAAS,GAAG,sBAAsB,CAAC;AAGzC,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,GAAQ,EAAE,IAAuB,EAAE,OAA8C;IAC/H,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9F,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IAClH,IAAI,MAAM,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK;QACrF,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACvH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC;IAC9B,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC;IACrB,OAAO,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc;IAC5C,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,CAAC;QACJ,MAAM,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5C,QAAQ,GAAG,IAAI,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,IAAI,QAAQ,EAAE,CAAC;QACd,IAAI,MAAM,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK;YACnF,MAAM,IAAI,KAAK,CAAC,wGAAwG,CAAC,CAAC;QAC3H,MAAM,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9B,CAAC;SAAM,CAAC;QACP,MAAM,CAAC;YACN,KAAK;YACL,UAAU;YACV,QAAQ;YACR,SAAS;YACT,WAAW;YACX,sBAAsB;YACtB,OAAO;YACP,wBAAwB;YACxB,OAAO;YACP,4BAA4B;YAC5B,SAAS;YACT,8DAA8D;YAC9D,KAAK;SACL,CAAC,CAAC;IACJ,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
|
|
2
|
+
export type DockerEnvironment = Readonly<{
|
|
3
|
+
DOCKER_CONTEXT?: string | undefined;
|
|
4
|
+
DOCKER_HOST?: string | undefined;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function docker(environment: DockerEnvironment, args: readonly string[], options?: ExecFileSyncOptionsWithStringEncoding): string;
|
|
7
|
+
//# sourceMappingURL=local-docker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-docker.d.ts","sourceRoot":"","sources":["../src/local-docker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qCAAqC,EAAgB,MAAM,oBAAoB,CAAC;AAE9F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAAC;AAEpH,wBAAgB,MAAM,CACrB,WAAW,EAAE,iBAAiB,EAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,qCAA4D,UAgBrE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export function docker(environment, args, options = { encoding: "utf8" }) {
|
|
3
|
+
const context = environment.DOCKER_CONTEXT;
|
|
4
|
+
const host = environment.DOCKER_HOST;
|
|
5
|
+
const endpoint = (!context && host) ||
|
|
6
|
+
execFileSync("docker", ["context", "inspect", ...(context ? [context] : []), "--format", "{{.Endpoints.docker.Host}}"], {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
}).trim();
|
|
9
|
+
const url = new URL(endpoint);
|
|
10
|
+
if (!(["unix:", "npipe:"].includes(url.protocol) && ["", "localhost"].includes(url.hostname)) &&
|
|
11
|
+
!(url.protocol === "tcp:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
|
|
12
|
+
throw new Error("Local database setup requires a local Docker endpoint; remote contexts are refused.");
|
|
13
|
+
return execFileSync("docker", args, options);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=local-docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-docker.js","sourceRoot":"","sources":["../src/local-docker.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI9F,MAAM,UAAU,MAAM,CACrB,WAA8B,EAC9B,IAAuB,EACvB,UAAiD,EAAE,QAAQ,EAAE,MAAM,EAAE;IAErE,MAAM,OAAO,GAAG,WAAW,CAAC,cAAc,CAAC;IAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC;IACrC,MAAM,QAAQ,GACb,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC;QAClB,YAAY,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,4BAA4B,CAAC,EAAE;YACvH,QAAQ,EAAE,MAAM;SAChB,CAAC,CAAC,IAAI,EAAE,CAAC;IACX,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,IACC,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzF,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExF,MAAM,IAAI,KAAK,CAAC,qFAAqF,CAAC,CAAC;IACxG,OAAO,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/local-postgres",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Repeatable local PostgreSQL preparation with one shared persistent service",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/ShivaeDev/platform.git"
|
|
9
|
+
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
10
|
+
"directory": "packages/local-postgres"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/local-postgres#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ShivaeDev/platform/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"source": "./src/index.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"provenance": true
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "24.10.1",
|
|
41
|
+
"@typescript/native": "npm:typescript@7.0.2",
|
|
42
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
43
|
+
"vitest": "4.1.9"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
|
|
47
|
+
"check": "biome check .",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"typecheck:compat": "tsc6 --noEmit",
|
|
51
|
+
"ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build"
|
|
9
52
|
}
|
|
10
53
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ExecFileSyncOptionsWithStringEncoding, execFileSync } from "node:child_process";
|
|
2
|
+
import { sqlInContainer, startContainer } from "./local-container.ts";
|
|
3
|
+
import { type DockerEnvironment, docker } from "./local-docker.ts";
|
|
4
|
+
|
|
5
|
+
export const localServer = "postgresql://postgres:postgres@127.0.0.1:55432";
|
|
6
|
+
|
|
7
|
+
export function assertLocalDatabase(value: string, names: readonly string[]) {
|
|
8
|
+
const url = new URL(value);
|
|
9
|
+
const name = url.pathname.slice(1);
|
|
10
|
+
if (
|
|
11
|
+
!["postgres:", "postgresql:"].includes(url.protocol) ||
|
|
12
|
+
!["localhost", "127.0.0.1"].includes(url.hostname) ||
|
|
13
|
+
url.port !== "55432" ||
|
|
14
|
+
["host", "hostaddr", "port", "dbname", "service"].some((key) => url.searchParams.has(key)) ||
|
|
15
|
+
!names.includes(name)
|
|
16
|
+
)
|
|
17
|
+
throw new Error(`Use a local database on 127.0.0.1:55432 named ${names.join(" or ")}.`);
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function localPostgres(environment: DockerEnvironment) {
|
|
22
|
+
const localDocker = (args: readonly string[], options: ExecFileSyncOptionsWithStringEncoding = { encoding: "utf8" }) =>
|
|
23
|
+
docker(environment, args, options);
|
|
24
|
+
function sql(value: string | URL, query: string) {
|
|
25
|
+
const url = new URL(value);
|
|
26
|
+
assertLocalDatabase(url.toString(), [url.pathname.slice(1)]);
|
|
27
|
+
for (const key of ["schema", "connection_limit", "pool_timeout", "pgbouncer"]) url.searchParams.delete(key);
|
|
28
|
+
|
|
29
|
+
const args = ["-X", "--set", "ON_ERROR_STOP=1", "-At", "-c", query];
|
|
30
|
+
if (!url.searchParams.has("connect_timeout")) url.searchParams.set("connect_timeout", "3");
|
|
31
|
+
const options = { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] } satisfies ExecFileSyncOptionsWithStringEncoding;
|
|
32
|
+
try {
|
|
33
|
+
return execFileSync("psql", [...args, "--dbname", String(url)], options).trim();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
36
|
+
throw new Error(error instanceof Error && "stderr" in error ? String(error.stderr) : "PostgreSQL query failed.");
|
|
37
|
+
return sqlInContainer(localDocker, url, args, options);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function startPostgres(connection = localServer) {
|
|
42
|
+
const server = new URL(connection);
|
|
43
|
+
server.pathname = "/postgres";
|
|
44
|
+
assertLocalDatabase(server.toString(), ["postgres"]);
|
|
45
|
+
let version: string | undefined;
|
|
46
|
+
try {
|
|
47
|
+
version = sql(server, "SHOW server_version");
|
|
48
|
+
} catch {
|
|
49
|
+
startContainer(localDocker);
|
|
50
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
51
|
+
try {
|
|
52
|
+
version = sql(server, "SHOW server_version");
|
|
53
|
+
break;
|
|
54
|
+
} catch {
|
|
55
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (version !== "18.6") throw new Error("Expected local PostgreSQL 18.6. Check the existing service; setup never removes containers or data.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function prepareDatabases(values: readonly string[]) {
|
|
63
|
+
for (const value of values) {
|
|
64
|
+
const name = new URL(value).pathname.slice(1);
|
|
65
|
+
assertLocalDatabase(value, [name]);
|
|
66
|
+
if (!/^[a-z][a-z0-9_]{0,62}$/.test(name)) throw new Error("Invalid local database name.");
|
|
67
|
+
}
|
|
68
|
+
startPostgres(values[0]);
|
|
69
|
+
for (const value of values) {
|
|
70
|
+
const url = new URL(value);
|
|
71
|
+
const name = url.pathname.slice(1);
|
|
72
|
+
const server = new URL(url);
|
|
73
|
+
server.pathname = "/postgres";
|
|
74
|
+
if (!sql(server, `SELECT 1 FROM pg_database WHERE datname = '${name}'`)) sql(server, `CREATE DATABASE "${name}"`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { sql, startPostgres, prepareDatabases };
|
|
78
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const image = "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873";
|
|
4
|
+
const container = "development-postgres";
|
|
5
|
+
type Docker = (args: readonly string[], options?: ExecFileSyncOptionsWithStringEncoding) => string;
|
|
6
|
+
|
|
7
|
+
export function sqlInContainer(docker: Docker, url: URL, args: readonly string[], options: ExecFileSyncOptionsWithStringEncoding) {
|
|
8
|
+
const id = docker(["ps", "--filter", "publish=55432", "--format", "{{.ID}}"], options).trim();
|
|
9
|
+
if (!id || id.includes("\n")) throw new Error("Install psql or start the shared PostgreSQL service with Docker.");
|
|
10
|
+
if (docker(["inspect", "--format", "{{.Config.Image}}", id], options).trim() !== image)
|
|
11
|
+
throw new Error("The shared PostgreSQL image differs from the pinned baseline. Upgrade explicitly, preserving data.");
|
|
12
|
+
const inside = new URL(url);
|
|
13
|
+
inside.hostname = "127.0.0.1";
|
|
14
|
+
inside.port = "5432";
|
|
15
|
+
return docker(["exec", id, "psql", ...args, "--dbname", inside.toString()], options).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function startContainer(docker: Docker) {
|
|
19
|
+
docker(["info"]);
|
|
20
|
+
let existing = false;
|
|
21
|
+
try {
|
|
22
|
+
docker(["container", "inspect", container]);
|
|
23
|
+
existing = true;
|
|
24
|
+
} catch {}
|
|
25
|
+
if (existing) {
|
|
26
|
+
if (docker(["inspect", "--format", "{{.Config.Image}}", container]).trim() !== image)
|
|
27
|
+
throw new Error("The shared PostgreSQL container uses a different image. Preserve its volume and upgrade it explicitly.");
|
|
28
|
+
docker(["start", container]);
|
|
29
|
+
} else {
|
|
30
|
+
docker([
|
|
31
|
+
"run",
|
|
32
|
+
"--detach",
|
|
33
|
+
"--name",
|
|
34
|
+
container,
|
|
35
|
+
"--publish",
|
|
36
|
+
"127.0.0.1:55432:5432",
|
|
37
|
+
"--env",
|
|
38
|
+
"POSTGRES_USER=postgres",
|
|
39
|
+
"--env",
|
|
40
|
+
"POSTGRES_PASSWORD=postgres",
|
|
41
|
+
"--mount",
|
|
42
|
+
"type=volume,src=development-postgres,dst=/var/lib/postgresql",
|
|
43
|
+
image,
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ExecFileSyncOptionsWithStringEncoding, execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export type DockerEnvironment = Readonly<{ DOCKER_CONTEXT?: string | undefined; DOCKER_HOST?: string | undefined }>;
|
|
4
|
+
|
|
5
|
+
export function docker(
|
|
6
|
+
environment: DockerEnvironment,
|
|
7
|
+
args: readonly string[],
|
|
8
|
+
options: ExecFileSyncOptionsWithStringEncoding = { encoding: "utf8" },
|
|
9
|
+
) {
|
|
10
|
+
const context = environment.DOCKER_CONTEXT;
|
|
11
|
+
const host = environment.DOCKER_HOST;
|
|
12
|
+
const endpoint =
|
|
13
|
+
(!context && host) ||
|
|
14
|
+
execFileSync("docker", ["context", "inspect", ...(context ? [context] : []), "--format", "{{.Endpoints.docker.Host}}"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
}).trim();
|
|
17
|
+
const url = new URL(endpoint);
|
|
18
|
+
if (
|
|
19
|
+
!(["unix:", "npipe:"].includes(url.protocol) && ["", "localhost"].includes(url.hostname)) &&
|
|
20
|
+
!(url.protocol === "tcp:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
|
|
21
|
+
)
|
|
22
|
+
throw new Error("Local database setup requires a local Docker endpoint; remote contexts are refused.");
|
|
23
|
+
return execFileSync("docker", args, options);
|
|
24
|
+
}
|
package/login.log
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
^D
|
|
2
|
-
https://www.npmjs.com/login?next=/login/cli/534beaea-3e9d-4402-a0fd-8783af95c2f8
|
|
3
|
-
|
|
4
|
-
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
|
5
|
-
█ ▄▄▄▄▄ █ █▄███▀▄▀▀▄█▀▄█▀█▄██ ▄▄▄▄▄ █
|
|
6
|
-
█ █ █ █ ▀▄ █▄▀ ▄█▀▄▄█ █▀▄ ██ █ █ █
|
|
7
|
-
█ █▄▄▄█ █▀██▀▀ ▀▄▀█▀ ██▄██▄▄▄█ █▄▄▄█ █
|
|
8
|
-
█▄▄▄▄▄▄▄█▄▀▄█ █ █▄▀ █ ▀▄▀ █ ▀ █▄▄▄▄▄▄▄█
|
|
9
|
-
█ ▀█▄▄▄███▀▄▀▄▄▄▄ ▄▄▄ █▀▄█▀ ██▄▀▄ ▄█
|
|
10
|
-
█▄▄█▀█ ▄ ▄▄▄ ▄ ▄ ▀ █▀▀ █▄██▄ █▄▀▀ ▀▄▀█
|
|
11
|
-
██ ██▄█▄▄ ▀█▄ ▀█▀▄▄▄▄▄▀▀▄█▀▀▀▄▄ ▀▀ ▀▄█
|
|
12
|
-
█ ▄███ ▄ ▄ █▀█▀█▄▀▀██▀▄ ▀▄ ▄▄█ ▄▄▀▀▀██
|
|
13
|
-
█▀████▄▄▀▀█ ▄▀▄▄▄▄▄▀▄▀▄▄▀▀▀▀ ▀▄▄█▀▀ ▀▄█
|
|
14
|
-
█▀█▄█▄▀▄▀███ ▄ ▄ ▀ ▀█▄ █▄▄▄ ▄██▄█ █▀▀██
|
|
15
|
-
██▄ ▄▄▄█▄▄▄█▄ ▀█▀▄▀▄▄ ▀▀▀█▀ ▄█▄▄▀ ▄ ▄█
|
|
16
|
-
█▀█▀▄▄ ▄█ ▄▄█▀█▀█▄▀▄█ ▄ █▀▄▄█ ▀█▀▀██
|
|
17
|
-
█ ▄▄▀ ▄█▀██▄▀▄▄▄█ ▄█ ▄▄▀▀█▀ ▀▄▄▄▀ ▄ ▄█
|
|
18
|
-
█████▄▀▄█ ▀█ ▄ ▄ ▀▀█ ▀▄ ▄▄ ▀▀▄███▀▀██
|
|
19
|
-
█▄▄▄▄▄█▄▄ ▀ █▄ ▀ ▄▄▄▄▀ █ █ ▄ ▄▄▄ ▄███
|
|
20
|
-
█ ▄▄▄▄▄ █▀▄▀█▀█▀▄▄ ██ █ ▄▄ █ █▄█ ▀▀▀██
|
|
21
|
-
█ █ █ █▄ ▄▀▄▄▀▀▄▀▄▀▄▀ ▄▀▀▀ ▄▄▀ ▀ █
|
|
22
|
-
█ █▄▄▄█ █▀██ ▄ ▄▄ ▀▀█▀ ▀ ▀▄▀▀▄▄ ▄▀▄▀▀██
|
|
23
|
-
█▄▄▄▄▄▄▄█▄▄▄█▄▄██▄▄▄▄█▄█▄▄██▄█▄████▄▄▄█
|
|
24
|
-
[K[0J
|
|
25
|
-
Press ENTER to open the URL in your browser.
|
|
26
|
-
[K[0JLogged in on https://registry.npmjs.org/
|
package/publish.log
DELETED
|
File without changes
|