@shivaedev/sql 0.46.2 → 0.46.4
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 +12 -0
- package/README.md +3 -1
- package/dist/databaseAddress.d.ts.map +1 -1
- package/dist/databaseAddress.js +34 -18
- package/dist/databaseAddress.js.map +1 -1
- package/dist/postgresSettings.d.ts +2 -2
- package/dist/postgresSettings.d.ts.map +1 -1
- package/dist/postgresSettings.js +14 -1
- package/dist/postgresSettings.js.map +1 -1
- package/package.json +8 -8
- package/src/databaseAddress.ts +36 -18
- package/src/postgresSettings.ts +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.46.4 - 2026-11-08
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Update `@shivaedev/service` from 0.8.2 to 0.9.0.
|
|
8
|
+
|
|
9
|
+
## 0.46.3 - 2026-11-07
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Gate `applyPostgresSettings` on Railway's nonempty `RAILWAY_REPLICA_ID` and the native PostgreSQL client's destination. Always exclude development port 55432, including URL query-parameter addresses, even when every Railway variable is present. Service variables from `railway run` alone do not enable settings.
|
|
14
|
+
|
|
3
15
|
## 0.46.2 - 2026-11-07
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@ The work has three parts: describe the model and provide its database once, choo
|
|
|
33
33
|
|
|
34
34
|
Applications declare server settings through `postgresSettings` on server's [`appDatabase`](https://github.com/ShivaeDev/platform/tree/main/packages/server#postgresql-server-settings) and database-backed hosts. SQL owns the `PostgresSettings` type and `applyPostgresSettings` in `@shivaedev/sql/postgresSettings.ts`; the host runs the applier during database startup, before migrations, outside their transaction. Settings describe the cluster's current configuration, rather than numbered migration history. The declaration maps PostgreSQL setting names to string values, including PostgreSQL units; it has no preset or implicit settings.
|
|
35
35
|
|
|
36
|
-
The applier
|
|
36
|
+
The applier requires a nonempty `RAILWAY_REPLICA_ID`, which [Railway supplies to deployed replicas](https://docs.railway.com/reference/variables), and a native `PgClient` in the Effect environment so it can inspect the database destination. Destination port 55432 is always excluded, including when the URL selects its address through query parameters. This protects shared development PostgreSQL even when a local shell has every Railway variable. `railway run` service variables alone do not include the deployment replica ID, so `railway run`, stories, test-sql, `pnpm dev` and scripts do not apply settings. No Railway-positive test is needed; the server regression story verifies startup without Railway deployment variables against PostgreSQL. The app’s declaration stays the same in every environment.
|
|
37
|
+
|
|
38
|
+
On an eligible Railway connection, the applier reads `pg_settings`, compares live values with the declaration, writes only differences with `ALTER SYSTEM SET`, and requests `pg_reload_conf()`. Equivalent numeric, memory and time units compare equally. A restart-only change logs its setting name and wanted value as pending; the live value remains until PostgreSQL restarts. These settings affect every database in the cluster, so apps sharing a cluster must agree on them.
|
|
37
39
|
|
|
38
40
|
`ALTER SYSTEM` requires a superuser for these server settings. On a non-superuser deployment, or when PostgreSQL refuses a value, startup warns with the setting name, live value and wanted value, then continues starting the app. Manage those settings through the database operator when the app cannot change them.
|
|
39
41
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseAddress.d.ts","sourceRoot":"","sources":["../src/databaseAddress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"databaseAddress.d.ts","sourceRoot":"","sources":["../src/databaseAddress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAgBlC,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,CAyBrG"}
|
package/dist/databaseAddress.js
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import { Redacted } from "effect";
|
|
2
|
+
function isDecodableHost(hostname) {
|
|
3
|
+
const runs = hostname.match(/(?:%[\da-f]{2})+/giu) ?? [];
|
|
4
|
+
if (hostname.replace(/(?:%[\da-f]{2})+/giu, "").includes("%")) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const decoder = new TextDecoder();
|
|
9
|
+
return runs.every((run) => {
|
|
10
|
+
const bytes = Uint8Array.from(run.match(/[\da-f]{2}/giu) ?? [], (byte) => Number.parseInt(byte, 16));
|
|
11
|
+
const decoded = encoder.encode(decoder.decode(bytes));
|
|
12
|
+
return decoded.length === bytes.length && decoded.every((byte, index) => byte === bytes[index]);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
2
15
|
export function databaseAddress(value) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
...(host === "" && !url.searchParams.has("host") ? {} : { host }),
|
|
18
|
-
...(port === undefined ? {} : { port }),
|
|
19
|
-
};
|
|
16
|
+
const address = Redacted.value(value);
|
|
17
|
+
if (!URL.canParse(address)) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const url = new URL(address);
|
|
21
|
+
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const rawHostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
25
|
+
if (!isDecodableHost(rawHostname)) {
|
|
26
|
+
return {};
|
|
20
27
|
}
|
|
21
|
-
|
|
28
|
+
const hostname = decodeURIComponent(rawHostname);
|
|
29
|
+
// PgConnection applies query parameters in order, after the authority.
|
|
30
|
+
const host = url.searchParams.getAll("host").at(-1) ?? hostname;
|
|
31
|
+
const rawPort = url.searchParams.getAll("port").at(-1) ?? url.port;
|
|
32
|
+
const port = rawPort === "" ? undefined : Number(rawPort);
|
|
33
|
+
if (host.includes(",") || (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535))) {
|
|
22
34
|
return {};
|
|
23
35
|
}
|
|
36
|
+
return {
|
|
37
|
+
...(host === "" && !url.searchParams.has("host") ? {} : { host }),
|
|
38
|
+
...(port === undefined ? {} : { port }),
|
|
39
|
+
};
|
|
24
40
|
}
|
|
25
41
|
//# sourceMappingURL=databaseAddress.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseAddress.js","sourceRoot":"","sources":["../src/databaseAddress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,
|
|
1
|
+
{"version":3,"file":"databaseAddress.js","sourceRoot":"","sources":["../src/databaseAddress.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Config, Effect } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
export type PostgresSettings = Readonly<Record<string, string>>;
|
|
4
|
-
export declare const applyPostgresSettings: (settings: Readonly<Record<string, string>>) => Effect.Effect<void,
|
|
4
|
+
export declare const applyPostgresSettings: (settings: Readonly<Record<string, string>>) => Effect.Effect<void, Config.ConfigError, SqlClient.SqlClient>;
|
|
5
5
|
//# sourceMappingURL=postgresSettings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresSettings.d.ts","sourceRoot":"","sources":["../src/postgresSettings.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"postgresSettings.d.ts","sourceRoot":"","sources":["../src/postgresSettings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAGlD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAUhE,eAAO,MAAM,qBAAqB,8GAuDhC,CAAC"}
|
package/dist/postgresSettings.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
2
|
+
import { Config, Effect, Option, Schema } from "effect";
|
|
2
3
|
import { SqlClient, SqlSchema } from "effect/sql";
|
|
4
|
+
import { databaseAddress } from "#databaseAddress.ts";
|
|
3
5
|
const Setting = Schema.Struct({
|
|
4
6
|
context: Schema.String,
|
|
5
7
|
pendingRestart: Schema.Boolean,
|
|
@@ -8,6 +10,17 @@ const Setting = Schema.Struct({
|
|
|
8
10
|
vartype: Schema.String,
|
|
9
11
|
}).pipe(Schema.encodeKeys({ pendingRestart: "pending_restart" }));
|
|
10
12
|
export const applyPostgresSettings = Effect.fn("Sql.applyPostgresSettings")(function* (settings) {
|
|
13
|
+
const replica = yield* Config.String("RAILWAY_REPLICA_ID").pipe(Config.withDefault(""));
|
|
14
|
+
const client = yield* Effect.serviceOption(PgClient.PgClient);
|
|
15
|
+
if (replica === "" || Option.isNone(client)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const config = client.value.config;
|
|
19
|
+
const address = config.url === undefined ? config : { ...config, ...databaseAddress(config.url) };
|
|
20
|
+
// Port 55432 belongs to shared development PostgreSQL, even in a shell with Railway variables.
|
|
21
|
+
if (address.port === 55_432) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
11
24
|
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
12
25
|
const read = SqlSchema.findAll({
|
|
13
26
|
execute: (name) => sql `select setting, unit, vartype, context, pending_restart from pg_settings where name = ${name}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresSettings.js","sourceRoot":"","sources":["../src/postgresSettings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"postgresSettings.js","sourceRoot":"","sources":["../src/postgresSettings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAItD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,cAAc,EAAE,MAAM,CAAC,OAAO;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;AAElE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,EAAE,QAA0B;IAChH,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC9D,IAAI,OAAO,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAClG,+FAA+F;IAC/F,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC7B,OAAO;IACR,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,iBAAiB,EAAE,CAAC;IAC7D,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC;QAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAA,yFAAyF,IAAI,EAAE;QACrH,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO;KACf,CAAC,CAAC;IACH,MAAM,OAAO,GAA4E,EAAE,CAAC;IAC5F,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,IAAI,IAAI,GAAG,aAAa,CAAC;QACzB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC1B,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,+BAA+B,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACnG,CAAC;YACD,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9E,IAAI,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC5C,OAAO;YACR,CAAC;YACD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,qBAAqB,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC;YACrH,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;QACjF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACnI,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;IACR,CAAC;IACD,KAAK,CAAC,CAAC,GAAG,CAAA,yBAAyB,CAAC,IAAI,CACvC,MAAM,CAAC,OAAO,CACb,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAClC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3C,gGAAgG;QAChG,IAAI,OAAO,EAAE,cAAc,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/C,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,oCAAoC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACjH,CAAC;IACF,CAAC,CAAC,CACF,CACD,EACD,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACtB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAClD,MAAM,CAAC,UAAU,CAAC,qCAAqC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAChG,CACD,CACD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,OAA4B,EAAE,MAAc;IAC/F,IAAI,OAAO,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,IAAI,UAAU,GAAG,GAAG,CAAA,GAAG,OAAO,CAAC,OAAO,MAAM,MAAM,EAAE,CAAC;IACrD,IAAI,OAAO,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAChC,UAAU,GAAG,GAAG,CAAA,GAAG,OAAO,CAAC,OAAO,eAAe,MAAM,WAAW,CAAC;IACpE,CAAC;SAAM,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,IAAI,gDAAgD,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1E,UAAU,GAAG,GAAG,CAAA,GAAG,OAAO,CAAC,OAAO,eAAe,MAAM,WAAW,CAAC;QACpE,CAAC;aAAM,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjE,UAAU,GAAG,GAAG,CAAA,GAAG,OAAO,CAAC,OAAO,6BAA6B,IAAI,qBAAqB,MAAM,GAAG,CAAC;QACnG,CAAC;aAAM,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;YAC5E,UAAU,GAAG,GAAG,CAAA,GAAG,OAAO,CAAC,OAAO,kCAAkC,KAAK,OAAO,CAAC,IAAI,EAAE,oCAAoC,MAAM,aAAa,CAAC;QAChJ,CAAC;IACF,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;QACtC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAA,UAAU,UAAU,aAAa;QACnD,OAAO,EAAE,MAAM,CAAC,IAAI;QACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;KAClD,CAAC,EAAE,CAAC;IACL,OAAO,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAC9B,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.4",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -44,18 +44,18 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@shivaedev/changes": "0.11.1",
|
|
47
|
-
"@shivaedev/service": "0.
|
|
47
|
+
"@shivaedev/service": "0.9.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@effect/platform-node": "4.1.1",
|
|
51
51
|
"@effect/sql-pg": "4.1.1",
|
|
52
52
|
"@effect/sql-sqlite-node": "4.1.1",
|
|
53
|
-
"@shivaedev/contract": "0.43.
|
|
54
|
-
"@shivaedev/jobs": "0.
|
|
55
|
-
"@shivaedev/quality": "0.
|
|
56
|
-
"@shivaedev/test-kits": "0.10.
|
|
57
|
-
"@shivaedev/test-sql": "0.24.
|
|
58
|
-
"@shivaedev/test-story": "0.45.
|
|
53
|
+
"@shivaedev/contract": "0.43.4",
|
|
54
|
+
"@shivaedev/jobs": "0.42.0",
|
|
55
|
+
"@shivaedev/quality": "0.74.3",
|
|
56
|
+
"@shivaedev/test-kits": "0.10.7",
|
|
57
|
+
"@shivaedev/test-sql": "0.24.8",
|
|
58
|
+
"@shivaedev/test-story": "0.45.6",
|
|
59
59
|
"@types/node": "24.19.2",
|
|
60
60
|
"effect": "4.1.1",
|
|
61
61
|
"typescript": "7.0.2",
|
package/src/databaseAddress.ts
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
import type { PgPoolConfig } from "@effect/sql-pg/PgClient";
|
|
2
2
|
import { Redacted } from "effect";
|
|
3
3
|
|
|
4
|
+
function isDecodableHost(hostname: string): boolean {
|
|
5
|
+
const runs = hostname.match(/(?:%[\da-f]{2})+/giu) ?? [];
|
|
6
|
+
if (hostname.replace(/(?:%[\da-f]{2})+/giu, "").includes("%")) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
return runs.every((run) => {
|
|
12
|
+
const bytes = Uint8Array.from(run.match(/[\da-f]{2}/giu) ?? [], (byte) => Number.parseInt(byte, 16));
|
|
13
|
+
const decoded = encoder.encode(decoder.decode(bytes));
|
|
14
|
+
return decoded.length === bytes.length && decoded.every((byte, index) => byte === bytes[index]);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
export function databaseAddress(value: Redacted.Redacted<string>): Pick<PgPoolConfig, "host" | "port"> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const address = Redacted.value(value);
|
|
20
|
+
if (!URL.canParse(address)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const url = new URL(address);
|
|
24
|
+
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const rawHostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
28
|
+
if (!isDecodableHost(rawHostname)) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
const hostname = decodeURIComponent(rawHostname);
|
|
32
|
+
// PgConnection applies query parameters in order, after the authority.
|
|
33
|
+
const host = url.searchParams.getAll("host").at(-1) ?? hostname;
|
|
34
|
+
const rawPort = url.searchParams.getAll("port").at(-1) ?? url.port;
|
|
35
|
+
const port = rawPort === "" ? undefined : Number(rawPort);
|
|
36
|
+
if (host.includes(",") || (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535))) {
|
|
23
37
|
return {};
|
|
24
38
|
}
|
|
39
|
+
return {
|
|
40
|
+
...(host === "" && !url.searchParams.has("host") ? {} : { host }),
|
|
41
|
+
...(port === undefined ? {} : { port }),
|
|
42
|
+
};
|
|
25
43
|
}
|
package/src/postgresSettings.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
2
|
+
import { Config, Effect, Option, Schema } from "effect";
|
|
2
3
|
import { SqlClient, SqlSchema } from "effect/sql";
|
|
4
|
+
import { databaseAddress } from "#databaseAddress.ts";
|
|
3
5
|
|
|
4
6
|
export type PostgresSettings = Readonly<Record<string, string>>;
|
|
5
7
|
|
|
@@ -12,6 +14,17 @@ const Setting = Schema.Struct({
|
|
|
12
14
|
}).pipe(Schema.encodeKeys({ pendingRestart: "pending_restart" }));
|
|
13
15
|
|
|
14
16
|
export const applyPostgresSettings = Effect.fn("Sql.applyPostgresSettings")(function* (settings: PostgresSettings) {
|
|
17
|
+
const replica = yield* Config.String("RAILWAY_REPLICA_ID").pipe(Config.withDefault(""));
|
|
18
|
+
const client = yield* Effect.serviceOption(PgClient.PgClient);
|
|
19
|
+
if (replica === "" || Option.isNone(client)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const config = client.value.config;
|
|
23
|
+
const address = config.url === undefined ? config : { ...config, ...databaseAddress(config.url) };
|
|
24
|
+
// Port 55432 belongs to shared development PostgreSQL, even in a shell with Railway variables.
|
|
25
|
+
if (address.port === 55_432) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
15
28
|
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
16
29
|
const read = SqlSchema.findAll({
|
|
17
30
|
execute: (name) => sql`select setting, unit, vartype, context, pending_restart from pg_settings where name = ${name}`,
|