@homeflare/alchemy 0.23.0 → 0.24.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/README.md +15 -1
- package/dist/litellm/client.d.ts +76 -0
- package/dist/litellm/client.d.ts.map +1 -0
- package/dist/litellm/credentials.d.ts +48 -0
- package/dist/litellm/credentials.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +36 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +30 -0
- package/dist/litellm/fake-stack.d.ts.map +1 -0
- package/dist/litellm/generated/pass-through.d.ts +71 -0
- package/dist/litellm/generated/pass-through.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +13 -0
- package/dist/litellm/index.d.ts.map +1 -0
- package/dist/litellm/index.js +270 -0
- package/dist/litellm/index.js.map +14 -0
- package/dist/litellm/pass-through-endpoint.d.ts +94 -0
- package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
- package/dist/litellm/pass-through-form.d.ts +40 -0
- package/dist/litellm/pass-through-form.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +16 -0
- package/dist/litellm/providers.d.ts.map +1 -0
- package/dist/postgres/connection.d.ts +50 -0
- package/dist/postgres/connection.d.ts.map +1 -0
- package/dist/postgres/database-attrs.d.ts +79 -0
- package/dist/postgres/database-attrs.d.ts.map +1 -0
- package/dist/postgres/database-sql.d.ts +73 -0
- package/dist/postgres/database-sql.d.ts.map +1 -0
- package/dist/postgres/database.d.ts +63 -0
- package/dist/postgres/database.d.ts.map +1 -0
- package/dist/postgres/errors.d.ts +86 -0
- package/dist/postgres/errors.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +22 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +16 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +238 -0
- package/dist/postgres/index.js.map +15 -0
- package/dist/postgres/providers.d.ts +14 -0
- package/dist/postgres/providers.d.ts.map +1 -0
- package/dist/proxmox/index.js.map +2 -2
- package/dist/proxmox/zfs-pool.d.ts +10 -2
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/release/archive.d.ts +6 -2
- package/dist/release/archive.d.ts.map +1 -1
- package/dist/release/binary-form.d.ts +14 -9
- package/dist/release/binary-form.d.ts.map +1 -1
- package/dist/release/binary-pins.d.ts +17 -0
- package/dist/release/binary-pins.d.ts.map +1 -0
- package/dist/release/catalog.d.ts.map +1 -1
- package/dist/release/index.js +90 -40
- package/dist/release/index.js.map +8 -7
- package/dist/release/tar.d.ts +9 -2
- package/dist/release/tar.d.ts.map +1 -1
- package/docs/litellm.md +110 -0
- package/docs/peers.md +8 -1
- package/docs/postgres.md +125 -0
- package/docs/release-binary-catalogs.md +40 -4
- package/docs/release-binary-refusals.md +22 -8
- package/docs/release-binary-upstream.md +33 -15
- package/package.json +10 -1
- package/src/litellm/client.ts +195 -0
- package/src/litellm/credentials.ts +73 -0
- package/src/litellm/fake-litellm.ts +114 -0
- package/src/litellm/fake-stack.ts +100 -0
- package/src/litellm/generated/pass-through.ts +73 -0
- package/src/litellm/index.ts +42 -0
- package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
- package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
- package/src/litellm/pass-through-endpoint.test.ts +208 -0
- package/src/litellm/pass-through-endpoint.ts +258 -0
- package/src/litellm/pass-through-form.test.ts +128 -0
- package/src/litellm/pass-through-form.ts +136 -0
- package/src/litellm/providers.ts +19 -0
- package/src/postgres/connection.ts +62 -0
- package/src/postgres/database-attrs.ts +87 -0
- package/src/postgres/database-bigint-serialization.test.ts +140 -0
- package/src/postgres/database-lifecycle.test.ts +113 -0
- package/src/postgres/database-provenance.test.ts +118 -0
- package/src/postgres/database-refusals.test.ts +135 -0
- package/src/postgres/database-sql.ts +174 -0
- package/src/postgres/database.ts +193 -0
- package/src/postgres/errors.ts +131 -0
- package/src/postgres/fake-sql.ts +123 -0
- package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
- package/src/postgres/index.ts +30 -0
- package/src/postgres/providers.ts +16 -0
- package/src/proxmox/zfs-pool.ts +10 -2
- package/src/release/archive.ts +10 -4
- package/src/release/binary-form.ts +24 -76
- package/src/release/binary-pins.ts +95 -0
- package/src/release/binary-root.test.ts +122 -0
- package/src/release/catalog.ts +10 -2
- package/src/release/tar-root.test.ts +114 -0
- package/src/release/tar.ts +78 -12
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Database`'s failures as typed tags (S21).
|
|
3
|
+
*
|
|
4
|
+
* ⛔ NONE OF THESE IS A STATUS-CODE OR MESSAGE MATCH. Every one is raised by this family's own
|
|
5
|
+
* code from a fact it already checked (a byte count, a live catalog row, a role lookup) — never
|
|
6
|
+
* from sniffing a driver error's text. The one live-SQLSTATE check this family makes —
|
|
7
|
+
* `isDuplicateDatabaseRace` in `database-sql.ts`, for the `42P04` create race — reads the raw
|
|
8
|
+
* code `@effect/sql-pg` classified onto `SqlSyntaxError.cause.code` (measured in
|
|
9
|
+
* `node_modules/@effect/sql-pg/src/internal/sqlError.ts#classifySqlState`: every `42xxx` code,
|
|
10
|
+
* `42P04` included, becomes `SqlSyntaxError` — there is no more specific tag for it), and it
|
|
11
|
+
* never produces one of these tags: a race converges instead of failing.
|
|
12
|
+
*/
|
|
13
|
+
import * as Data from 'effect/Data';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A declared name would be silently truncated by the server (`NAMEDATALEN` 64, so 63 UTF-8
|
|
17
|
+
* bytes is the last one that survives whole) — refused at plan, before any statement runs.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ WHY THIS IS A PLAN-TIME REFUSAL AND NOT A SERVER ERROR. `truncate_identifier`
|
|
20
|
+
* (`scansup.c@REL_18_6` line 93) clips a long name to 63 bytes and raises only a `NOTICE`
|
|
21
|
+
* (`errcode(ERRCODE_NAME_TOO_LONG)`), never an error. A create would succeed against the
|
|
22
|
+
* TRUNCATED name, so the next plan's read (`WHERE datname = $1`, the name exactly as
|
|
23
|
+
* declared) would find nothing and try to create it again — forever. See
|
|
24
|
+
* `doc/postgres.md#name`.
|
|
25
|
+
*/
|
|
26
|
+
export class PostgresDatabaseNameRefused extends Data.TaggedError('PostgresDatabaseNameRefused')<{
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly byteLength: number;
|
|
29
|
+
readonly limit: number;
|
|
30
|
+
}> {
|
|
31
|
+
override get message(): string {
|
|
32
|
+
return (
|
|
33
|
+
`Postgres.Database "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` +
|
|
34
|
+
`${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` +
|
|
35
|
+
'accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses ' +
|
|
36
|
+
'it instead of creating a database no later read will ever find again.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A logical id's `name` changed. A database's name is its identity; there is no `ALTER … RENAME`
|
|
42
|
+
* in this family's vocabulary (see `docs/postgres.md`), and `diff` never turns this into a
|
|
43
|
+
* `replace` — a replace here is `DROP DATABASE` before `CREATE DATABASE`, on data. */
|
|
44
|
+
export class PostgresDatabaseRenameRefused extends Data.TaggedError(
|
|
45
|
+
'PostgresDatabaseRenameRefused',
|
|
46
|
+
)<{
|
|
47
|
+
readonly from: string;
|
|
48
|
+
readonly to: string;
|
|
49
|
+
}> {
|
|
50
|
+
override get message(): string {
|
|
51
|
+
return (
|
|
52
|
+
`Postgres.Database: the declared name changed from "${this.from}" to "${this.to}". A ` +
|
|
53
|
+
'rename is refused at plan — this provider never replaces a database (that is DROP then ' +
|
|
54
|
+
'CREATE) and never runs ALTER … RENAME. Declare a new logical id for the new name, and ' +
|
|
55
|
+
'remove the old one once its data has moved.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `OWNER` names a role `pg_roles` does not have, checked before the `CREATE DATABASE` that
|
|
61
|
+
* would otherwise fail on it. Postgres itself refuses the same case with `ERROR: role "…" does
|
|
62
|
+
* not exist` (`createdb()`, `dbcommands.c`) — this tag exists so the refusal is typed and the
|
|
63
|
+
* check runs before any statement, not caught from the driver's text. */
|
|
64
|
+
export class PostgresDatabaseOwnerMissing extends Data.TaggedError('PostgresDatabaseOwnerMissing')<{
|
|
65
|
+
readonly database: string;
|
|
66
|
+
readonly owner: string;
|
|
67
|
+
}> {
|
|
68
|
+
override get message(): string {
|
|
69
|
+
return `Postgres.Database "${this.database}": OWNER "${this.owner}" is not a role in pg_roles.`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A live, asserted property no longer matches the declaration. `reconcile` never issues an
|
|
74
|
+
* `ALTER DATABASE` to fix this — see `docs/postgres.md` for why each asserted prop stops at
|
|
75
|
+
* create. */
|
|
76
|
+
export class PostgresDatabaseDrift extends Data.TaggedError('PostgresDatabaseDrift')<{
|
|
77
|
+
readonly database: string;
|
|
78
|
+
readonly prop: string;
|
|
79
|
+
readonly declared: unknown;
|
|
80
|
+
readonly live: unknown;
|
|
81
|
+
}> {
|
|
82
|
+
override get message(): string {
|
|
83
|
+
return (
|
|
84
|
+
`Postgres.Database "${this.database}": ${this.prop} is declared as ` +
|
|
85
|
+
`${JSON.stringify(this.declared)} but the live database has ${JSON.stringify(this.live)}. ` +
|
|
86
|
+
'This provider never issues ALTER DATABASE — update the declaration to match the live ' +
|
|
87
|
+
'value, or change it by hand and re-plan.'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** `delete` never drops a database. `DROP DATABASE` is destructive and irreversible on data
|
|
93
|
+
* this family did not create from nothing (most declared databases here are adopted, not
|
|
94
|
+
* created); the decision to remove one stays a human act on the host. */
|
|
95
|
+
export class PostgresDatabaseDropRefused extends Data.TaggedError('PostgresDatabaseDropRefused')<{
|
|
96
|
+
readonly database: string;
|
|
97
|
+
}> {
|
|
98
|
+
override get message(): string {
|
|
99
|
+
return (
|
|
100
|
+
`Postgres.Database "${this.database}": delete is refused. This provider never issues DROP ` +
|
|
101
|
+
'DATABASE. Drop it by hand over the trust socket if you mean to remove it, then remove the ' +
|
|
102
|
+
'declaration.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** `CREATE DATABASE` raised no error, but the immediate re-read (S10: never trust the write's
|
|
108
|
+
* own report) still finds nothing. Measured to have no real trigger in a correctly speaking
|
|
109
|
+
* client, but S20 forbids `Effect.die` here regardless — an invariant violation is a typed
|
|
110
|
+
* failure the caller can see and retry, never a defect that crashes the whole engine. */
|
|
111
|
+
export class PostgresDatabaseCreateVanished extends Data.TaggedError(
|
|
112
|
+
'PostgresDatabaseCreateVanished',
|
|
113
|
+
)<{
|
|
114
|
+
readonly database: string;
|
|
115
|
+
}> {
|
|
116
|
+
override get message(): string {
|
|
117
|
+
return (
|
|
118
|
+
`Postgres.Database "${this.database}": CREATE DATABASE raised no error, but the database ` +
|
|
119
|
+
'is still absent on the immediate re-read. Re-plan — this is not a state this provider ' +
|
|
120
|
+
'expects to reach.'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type PostgresDatabaseError =
|
|
126
|
+
| PostgresDatabaseNameRefused
|
|
127
|
+
| PostgresDatabaseRenameRefused
|
|
128
|
+
| PostgresDatabaseOwnerMissing
|
|
129
|
+
| PostgresDatabaseDrift
|
|
130
|
+
| PostgresDatabaseDropRefused
|
|
131
|
+
| PostgresDatabaseCreateVanished;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A recording fake `PgExecutor` (S28: "Tests use `bun:test`… a test never trusts the deploy's
|
|
3
|
+
* own report" — here read back from the fake's own catalog, not from the statement list) for
|
|
4
|
+
* every lifecycle test in this family. No socket, no `@effect/sql-pg` client — it is small
|
|
5
|
+
* enough to re-derive the one thing this provider ever asks of a real one: three statement
|
|
6
|
+
* shapes, matched by text, against an in-memory `pg_roles` set and `pg_database` map.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ IT PARSES ITS OWN OUTPUT, NOT SQL IN GENERAL. `parseCreate` below understands exactly the
|
|
9
|
+
* text `buildCreateDatabaseSql` (`database-sql.ts`) produces — quoted with `quoteIdent` /
|
|
10
|
+
* `quoteStringLiteral` — because that is the only `CREATE DATABASE` this family ever issues. A
|
|
11
|
+
* general SQL parser would hide a quoting bug instead of tripping over it.
|
|
12
|
+
*/
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
import { SqlError, SqlSyntaxError } from 'effect/unstable/sql/SqlError';
|
|
15
|
+
import type { PostgresDatabaseAttributes } from './database-attrs.ts';
|
|
16
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
17
|
+
|
|
18
|
+
export interface RecordedStatement {
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly params: ReadonlyArray<unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FakeSql extends PgExecutor {
|
|
24
|
+
readonly statements: ReadonlyArray<RecordedStatement>;
|
|
25
|
+
/** Mutable on purpose: a test seeds a row a "competing" statement would have produced (the
|
|
26
|
+
* `42P04` race case) before the fake ever sees it. */
|
|
27
|
+
readonly databases: Map<string, PostgresDatabaseAttributes>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FakeSqlOptions {
|
|
31
|
+
readonly roles?: ReadonlyArray<string>;
|
|
32
|
+
readonly databases?: ReadonlyArray<PostgresDatabaseAttributes>;
|
|
33
|
+
/** Fail the NEXT `CREATE DATABASE` with SQLSTATE `42P04` (duplicate_database), once, the way a
|
|
34
|
+
* concurrent creator racing this reconcile would — classified exactly as
|
|
35
|
+
* `@effect/sql-pg`'s own driver classifies it (`database-sql.ts`'s header). */
|
|
36
|
+
readonly raceNextCreate?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const unquoteIdent = (raw: string): string => raw.replace(/""/g, '"');
|
|
40
|
+
const unquoteLiteral = (raw: string): string => raw.replace(/''/g, "'");
|
|
41
|
+
|
|
42
|
+
/** Pull every field back out of exactly the text `buildCreateDatabaseSql` writes. */
|
|
43
|
+
const parseCreate = (text: string): PostgresDatabaseAttributes => {
|
|
44
|
+
const name = /^CREATE DATABASE "((?:[^"]|"")*)" WITH /.exec(text);
|
|
45
|
+
const owner = /OWNER "((?:[^"]|"")*)"/.exec(text);
|
|
46
|
+
const encoding = /ENCODING '((?:[^']|'')*)'/.exec(text);
|
|
47
|
+
const localeProvider = /LOCALE_PROVIDER '((?:[^']|'')*)'/.exec(text);
|
|
48
|
+
const lcCollate = /LC_COLLATE '((?:[^']|'')*)'/.exec(text);
|
|
49
|
+
const lcCtype = /LC_CTYPE '((?:[^']|'')*)'/.exec(text);
|
|
50
|
+
const tablespace = /TABLESPACE "((?:[^"]|"")*)"/.exec(text);
|
|
51
|
+
const allowConnections = /ALLOW_CONNECTIONS (true|false)/.exec(text);
|
|
52
|
+
const connectionLimit = /CONNECTION LIMIT (-?\d+)/.exec(text);
|
|
53
|
+
const isTemplate = /IS_TEMPLATE (true|false)/.exec(text);
|
|
54
|
+
if (name === null || owner === null) {
|
|
55
|
+
throw new Error(`fake-sql: could not parse a generated CREATE DATABASE statement: ${text}`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: unquoteIdent(name[1] as string),
|
|
59
|
+
oid: 0,
|
|
60
|
+
owner: unquoteIdent(owner[1] as string),
|
|
61
|
+
encoding: encoding === null ? 'UTF8' : unquoteLiteral(encoding[1] as string),
|
|
62
|
+
localeProvider:
|
|
63
|
+
localeProvider === null ? 'libc' : (unquoteLiteral(localeProvider[1] as string) as 'libc'),
|
|
64
|
+
collate: lcCollate === null ? 'C' : unquoteLiteral(lcCollate[1] as string),
|
|
65
|
+
ctype: lcCtype === null ? 'C' : unquoteLiteral(lcCtype[1] as string),
|
|
66
|
+
tablespace: tablespace === null ? 'pg_default' : unquoteIdent(tablespace[1] as string),
|
|
67
|
+
allowConnections: allowConnections === null ? true : allowConnections[1] === 'true',
|
|
68
|
+
connectionLimit: connectionLimit === null ? -1 : Number(connectionLimit[1]),
|
|
69
|
+
isTemplate: isTemplate === null ? false : isTemplate[1] === 'true',
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const makeFakeSql = (options: FakeSqlOptions = {}): FakeSql => {
|
|
74
|
+
const statements: RecordedStatement[] = [];
|
|
75
|
+
const roles = new Set(options.roles ?? []);
|
|
76
|
+
const databases = new Map(options.databases?.map((d) => [d.name, d] as const) ?? []);
|
|
77
|
+
let raceRemaining = options.raceNextCreate === true ? 1 : 0;
|
|
78
|
+
let oidCounter = 20000;
|
|
79
|
+
|
|
80
|
+
const unsafe = <A extends object>(
|
|
81
|
+
text: string,
|
|
82
|
+
params: ReadonlyArray<unknown> = [],
|
|
83
|
+
): Effect.Effect<ReadonlyArray<A>, SqlError> =>
|
|
84
|
+
Effect.suspend(() => {
|
|
85
|
+
statements.push({ text, params });
|
|
86
|
+
|
|
87
|
+
if (text.startsWith('SELECT 1 AS present FROM pg_roles')) {
|
|
88
|
+
const role = params[0] as string;
|
|
89
|
+
return Effect.succeed(
|
|
90
|
+
(roles.has(role) ? [{ present: 1 }] : []) as unknown as ReadonlyArray<A>,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (text.includes('FROM pg_database')) {
|
|
95
|
+
const name = params[0] as string;
|
|
96
|
+
const row = databases.get(name);
|
|
97
|
+
return Effect.succeed((row === undefined ? [] : [row]) as unknown as ReadonlyArray<A>);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (text.startsWith('CREATE DATABASE')) {
|
|
101
|
+
if (raceRemaining > 0) {
|
|
102
|
+
raceRemaining -= 1;
|
|
103
|
+
return Effect.fail(
|
|
104
|
+
new SqlError({
|
|
105
|
+
reason: new SqlSyntaxError({
|
|
106
|
+
cause: Object.assign(new Error('database "x" already exists'), { code: '42P04' }),
|
|
107
|
+
message: 'duplicate_database (fake race)',
|
|
108
|
+
operation: 'CREATE DATABASE',
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const row = { ...parseCreate(text), oid: oidCounter };
|
|
114
|
+
oidCounter += 1;
|
|
115
|
+
databases.set(row.name, row);
|
|
116
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new Error(`fake-sql: unrecognised statement: ${text}`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return { unsafe, statements, databases };
|
|
123
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# path: doc/src/sgml/ref/create_database.sgml
|
|
2
|
+
# blob: 4da8aeebb50a29e89bd9f68a8deac4b1a3ec7b18
|
|
3
|
+
# tag: REL_18_6 (commit 724edf9bde9d356724ad384a2e196edc3c9f80f7)
|
|
4
|
+
# fetched: 2026-09-23 via gh api repos/postgres/postgres/contents/...?ref=724edf9bde9d356724ad384a2e196edc3c9f80f7
|
|
5
|
+
# excerpt: the <synopsis> block only (lines 23-42 of the full file), cut per house doc-size convention
|
|
6
|
+
#
|
|
7
|
+
<synopsis>
|
|
8
|
+
CREATE DATABASE <replaceable class="parameter">name</replaceable>
|
|
9
|
+
[ WITH ] [ OWNER [=] <replaceable class="parameter">user_name</replaceable> ]
|
|
10
|
+
[ TEMPLATE [=] <replaceable class="parameter">template</replaceable> ]
|
|
11
|
+
[ ENCODING [=] <replaceable class="parameter">encoding</replaceable> ]
|
|
12
|
+
[ STRATEGY [=] <replaceable class="parameter">strategy</replaceable> ]
|
|
13
|
+
[ LOCALE [=] <replaceable class="parameter">locale</replaceable> ]
|
|
14
|
+
[ LC_COLLATE [=] <replaceable class="parameter">lc_collate</replaceable> ]
|
|
15
|
+
[ LC_CTYPE [=] <replaceable class="parameter">lc_ctype</replaceable> ]
|
|
16
|
+
[ BUILTIN_LOCALE [=] <replaceable class="parameter">builtin_locale</replaceable> ]
|
|
17
|
+
[ ICU_LOCALE [=] <replaceable class="parameter">icu_locale</replaceable> ]
|
|
18
|
+
[ ICU_RULES [=] <replaceable class="parameter">icu_rules</replaceable> ]
|
|
19
|
+
[ LOCALE_PROVIDER [=] <replaceable class="parameter">locale_provider</replaceable> ]
|
|
20
|
+
[ COLLATION_VERSION = <replaceable>collation_version</replaceable> ]
|
|
21
|
+
[ TABLESPACE [=] <replaceable class="parameter">tablespace_name</replaceable> ]
|
|
22
|
+
[ ALLOW_CONNECTIONS [=] <replaceable class="parameter">allowconn</replaceable> ]
|
|
23
|
+
[ CONNECTION LIMIT [=] <replaceable class="parameter">connlimit</replaceable> ]
|
|
24
|
+
[ IS_TEMPLATE [=] <replaceable class="parameter">istemplate</replaceable> ]
|
|
25
|
+
[ OID [=] <replaceable class="parameter">oid</replaceable> ]
|
|
26
|
+
</synopsis>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# path: src/include/catalog/pg_database.h
|
|
2
|
+
# blob: 54f0d38c9c9e1cd4d419b05b296b8f84875e9768
|
|
3
|
+
# tag: REL_18_6 (commit 724edf9bde9d356724ad384a2e196edc3c9f80f7)
|
|
4
|
+
# fetched: 2026-09-23 via gh api repos/postgres/postgres/contents/...?ref=724edf9bde9d356724ad384a2e196edc3c9f80f7
|
|
5
|
+
# excerpt: the CATALOG(pg_database...) { ... } FormData_pg_database struct only (lines 29-89 of the full file)
|
|
6
|
+
#
|
|
7
|
+
CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248,DatabaseRelation_Rowtype_Id) BKI_SCHEMA_MACRO
|
|
8
|
+
{
|
|
9
|
+
/* oid */
|
|
10
|
+
Oid oid;
|
|
11
|
+
|
|
12
|
+
/* database name */
|
|
13
|
+
NameData datname;
|
|
14
|
+
|
|
15
|
+
/* owner of database */
|
|
16
|
+
Oid datdba BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid);
|
|
17
|
+
|
|
18
|
+
/* character encoding */
|
|
19
|
+
int32 encoding;
|
|
20
|
+
|
|
21
|
+
/* locale provider, see pg_collation.collprovider */
|
|
22
|
+
char datlocprovider;
|
|
23
|
+
|
|
24
|
+
/* allowed as CREATE DATABASE template? */
|
|
25
|
+
bool datistemplate;
|
|
26
|
+
|
|
27
|
+
/* new connections allowed? */
|
|
28
|
+
bool datallowconn;
|
|
29
|
+
|
|
30
|
+
/* database has login event triggers? */
|
|
31
|
+
bool dathasloginevt;
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Max connections allowed. Negative values have special meaning, see
|
|
35
|
+
* DATCONNLIMIT_* defines below.
|
|
36
|
+
*/
|
|
37
|
+
int32 datconnlimit;
|
|
38
|
+
|
|
39
|
+
/* all Xids < this are frozen in this DB */
|
|
40
|
+
TransactionId datfrozenxid;
|
|
41
|
+
|
|
42
|
+
/* all multixacts in the DB are >= this */
|
|
43
|
+
TransactionId datminmxid;
|
|
44
|
+
|
|
45
|
+
/* default table space for this DB */
|
|
46
|
+
Oid dattablespace BKI_LOOKUP(pg_tablespace);
|
|
47
|
+
|
|
48
|
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
|
|
49
|
+
/* LC_COLLATE setting */
|
|
50
|
+
text datcollate BKI_FORCE_NOT_NULL;
|
|
51
|
+
|
|
52
|
+
/* LC_CTYPE setting */
|
|
53
|
+
text datctype BKI_FORCE_NOT_NULL;
|
|
54
|
+
|
|
55
|
+
/* ICU locale ID */
|
|
56
|
+
text datlocale;
|
|
57
|
+
|
|
58
|
+
/* ICU collation rules */
|
|
59
|
+
text daticurules;
|
|
60
|
+
|
|
61
|
+
/* provider-dependent version of collation data */
|
|
62
|
+
text datcollversion BKI_DEFAULT(_null_);
|
|
63
|
+
|
|
64
|
+
/* access permissions */
|
|
65
|
+
aclitem datacl[1];
|
|
66
|
+
#endif
|
|
67
|
+
} FormData_pg_database;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL providers for Alchemy — `Postgres.Database`, create-and-assert over a self-hosted
|
|
3
|
+
* cluster, walked against PostgreSQL 18.6. See `docs/postgres.md`.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ THIS BARREL IS THE PUBLIC API, deliberately smaller than the directory: `fake-sql.ts` is a
|
|
6
|
+
* test double, not something a consuming stack should import.
|
|
7
|
+
*/
|
|
8
|
+
export type { PostgresConnectionConfig } from './connection.ts';
|
|
9
|
+
export { PostgresConnection, postgresConnection, withPg } from './connection.ts';
|
|
10
|
+
export type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';
|
|
11
|
+
export { POSTGRES_NAME_MAX_BYTES, nameByteRefusal, utf8ByteLength } from './database-attrs.ts';
|
|
12
|
+
export {
|
|
13
|
+
buildCreateDatabaseSql,
|
|
14
|
+
firstDrift,
|
|
15
|
+
isDuplicateDatabaseRace,
|
|
16
|
+
quoteIdent,
|
|
17
|
+
quoteStringLiteral,
|
|
18
|
+
roleExists,
|
|
19
|
+
selectDatabase,
|
|
20
|
+
} from './database-sql.ts';
|
|
21
|
+
export { PostgresDatabase, PostgresDatabaseProvider, isPostgresDatabase } from './database.ts';
|
|
22
|
+
export {
|
|
23
|
+
PostgresDatabaseDrift,
|
|
24
|
+
PostgresDatabaseDropRefused,
|
|
25
|
+
PostgresDatabaseNameRefused,
|
|
26
|
+
PostgresDatabaseOwnerMissing,
|
|
27
|
+
PostgresDatabaseRenameRefused,
|
|
28
|
+
type PostgresDatabaseError,
|
|
29
|
+
} from './errors.ts';
|
|
30
|
+
export { postgresProviders } from './providers.ts';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Database`'s provider, pre-wired to one cluster:
|
|
3
|
+
*
|
|
4
|
+
* const providers = postgresProviders({ host: '/opt/homeflare/postgres/sockets', database: 'postgres', username: 'tim' });
|
|
5
|
+
*
|
|
6
|
+
* ★ THE CLUSTER IS A PARAMETER, NOT A DEFAULT (S15, and `docs/postgres.md#path`). This kit is
|
|
7
|
+
* PUBLIC; the mini's socket directory, its user and its port are the CONSUMING stack's values,
|
|
8
|
+
* never baked in here. A stack with more than one cluster calls this more than once and merges
|
|
9
|
+
* the results under whatever logical grouping it wants.
|
|
10
|
+
*/
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
import { type PostgresConnectionConfig, postgresConnection } from './connection.ts';
|
|
13
|
+
import { PostgresDatabaseProvider } from './database.ts';
|
|
14
|
+
|
|
15
|
+
export const postgresProviders = (config: PostgresConnectionConfig) =>
|
|
16
|
+
PostgresDatabaseProvider().pipe(Layer.provide(postgresConnection(config)));
|
package/src/proxmox/zfs-pool.ts
CHANGED
|
@@ -54,8 +54,16 @@
|
|
|
54
54
|
*
|
|
55
55
|
* ⚠️ PRIVILEGES, FROM THE SCHEMA. Read and diff need `Sys.Audit` on `/` — both GETs check it.
|
|
56
56
|
* Reconcile needs `Sys.Modify` on `/` for the POST. `Datastore.Allocate` on `/storage` is NOT
|
|
57
|
-
* needed, because `add_storage` is not offered — see the ⛔ on `ZfsPoolProps`.
|
|
58
|
-
*
|
|
57
|
+
* needed, because `add_storage` is not offered — see the ⛔ on `ZfsPoolProps`.
|
|
58
|
+
* ★ CORRECTED 2026-09-23, ALONGSIDE THE HEADER ABOVE: `delete` needs `Sys.Modify` on `/` too,
|
|
59
|
+
* for the DELETE it now sends under `.pipe(RemovalPolicy.destroy())` — the whole `disks/zfs`
|
|
60
|
+
* family checks it, not only the POST. MEASURED the same day as the rest of this paragraph:
|
|
61
|
+
* `ceph-osd.ts`'s own privilege comparison names `disks/zfs` as one of the sibling families
|
|
62
|
+
* whose write verbs DO carry a `Sys.Modify` check, unlike `ceph/osd`'s create and delete, which
|
|
63
|
+
* carry no permissions block at all and 403 for any identity but `root@pam`. This line
|
|
64
|
+
* previously said delete "needs nothing at all, since it calls nothing", which repeated the same
|
|
65
|
+
* mistake the header above corrects: `delete` calls the DELETE endpoint, so it needs whatever
|
|
66
|
+
* that endpoint checks.
|
|
59
67
|
*/
|
|
60
68
|
import { Resource } from 'alchemy';
|
|
61
69
|
import * as Provider from 'alchemy/Provider';
|
package/src/release/archive.ts
CHANGED
|
@@ -24,12 +24,17 @@ import { type TarContents, tarReader } from './tar.ts';
|
|
|
24
24
|
/** How much gzip is handed to the inflater at a time. */
|
|
25
25
|
const SLICE = 1 << 20;
|
|
26
26
|
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Gunzip and read a tar, keeping only `wanted`. Throws ArchiveRefused.
|
|
29
|
+
* @param root The one declared leading directory to strip (tar.ts) — a directory-wrapped vendor
|
|
30
|
+
* archive (the Prometheus family, measured 2026-09-23) without it, or one that carries no root.
|
|
31
|
+
*/
|
|
28
32
|
export const extractMembers = async (
|
|
29
33
|
gzipped: Uint8Array,
|
|
30
34
|
wanted: ReadonlySet<string>,
|
|
35
|
+
root?: string,
|
|
31
36
|
): Promise<TarContents> => {
|
|
32
|
-
const tar = tarReader(wanted);
|
|
37
|
+
const tar = tarReader(wanted, root);
|
|
33
38
|
// ⚠️ FED IN SLICES, ON DEMAND. Enqueued whole, the 123.6 MB vmutils archive is inflated in one
|
|
34
39
|
// transform step and all 264 MB of output queue up before the first read: MEASURED 2026-09-22,
|
|
35
40
|
// a peak RSS of 1116 MB for vmalert alone under bun 1.4.0. `pull` hands over the next slice only
|
|
@@ -79,11 +84,12 @@ export const verifiedMember = async (
|
|
|
79
84
|
): Promise<Uint8Array> => {
|
|
80
85
|
const archiveSha = sha256Hex(archive);
|
|
81
86
|
if (archiveSha !== release.sha256) throw mismatch(release.url, release.sha256, archiveSha);
|
|
82
|
-
const { members, names } = await extractMembers(archive, new Set([release.member]));
|
|
87
|
+
const { members, names } = await extractMembers(archive, new Set([release.member]), release.root);
|
|
83
88
|
const bytes = members.get(release.member);
|
|
84
89
|
if (bytes === undefined) {
|
|
90
|
+
const under = release.root === undefined ? '' : ` under root "${release.root}/"`;
|
|
85
91
|
throw new ArchiveRefused({
|
|
86
|
-
message: `${release.url} has no member named exactly "${release.member}" (it has ${names.map((n) => `"${n}"`).join(', ') || 'none'})`,
|
|
92
|
+
message: `${release.url} has no member named exactly "${release.member}"${under} (it has ${names.map((n) => `"${n}"`).join(', ') || 'none'})`,
|
|
87
93
|
});
|
|
88
94
|
}
|
|
89
95
|
const memberSha = sha256Hex(bytes);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { HostFileAttributes } from '../launchd/host-file-form.ts';
|
|
16
16
|
import { identityProblems, pathProblems } from '../launchd/host-file-form.ts';
|
|
17
|
+
import { pinProblems } from './binary-pins.ts';
|
|
17
18
|
|
|
18
19
|
/** One release archive, pinned: where it is published, and the bytes it must be. */
|
|
19
20
|
export interface ReleaseArchive {
|
|
@@ -30,6 +31,17 @@ export interface ReleaseArchive {
|
|
|
30
31
|
readonly size: number;
|
|
31
32
|
/** The archive's SHA-256, lower-case hex, as the vendor's checksum file lists it. */
|
|
32
33
|
readonly sha256: string;
|
|
34
|
+
/**
|
|
35
|
+
* The one directory the vendor wraps every entry in, exactly as its own archive names it —
|
|
36
|
+
* `'alertmanager-0.33.1.darwin-arm64'`, never `'alertmanager-0.33.1.darwin-arm64/'` (no trailing
|
|
37
|
+
* slash). Pinned from the walk-down, never derived from `asset` or `tag`: the vendor spells its
|
|
38
|
+
* wrapper however it likes (`darwin_arm64`, an `-enterprise` suffix, no version at all), so a
|
|
39
|
+
* computed name would drift onto a sibling the moment one vendor's naming changes. Omit it for a
|
|
40
|
+
* flat archive (Victoria, OpenBao); the tar reader then refuses a directory entry exactly as it
|
|
41
|
+
* always has (tar.ts).
|
|
42
|
+
* @default undefined — no wrapping directory; the archive is flat.
|
|
43
|
+
*/
|
|
44
|
+
readonly root?: string;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
export interface ReleaseBinaryProps {
|
|
@@ -82,6 +94,8 @@ export type PinnedDownload = {
|
|
|
82
94
|
readonly member: string;
|
|
83
95
|
/** The member's pinned SHA-256. */
|
|
84
96
|
readonly memberSha256: string;
|
|
97
|
+
/** The one declared leading directory to strip, if the archive has one — `archive.root`. */
|
|
98
|
+
readonly root?: string;
|
|
85
99
|
};
|
|
86
100
|
|
|
87
101
|
export const DEFAULT_BINARY_MODE = 0o755;
|
|
@@ -109,6 +123,8 @@ export const pinnedDownload = (props: ReleaseBinaryProps): PinnedDownload => ({
|
|
|
109
123
|
sha256: props.archive.sha256,
|
|
110
124
|
size: props.archive.size,
|
|
111
125
|
url: releaseUrl(props.archive.repo, props.archive.tag, props.archive.asset),
|
|
126
|
+
// ⚠️ exactOptionalPropertyTypes: never spell out `root: undefined`; leave the key off entirely.
|
|
127
|
+
...(props.archive.root === undefined ? {} : { root: props.archive.root }),
|
|
112
128
|
});
|
|
113
129
|
|
|
114
130
|
export const modeProblems = (mode: number): string[] => {
|
|
@@ -123,83 +139,15 @@ export const modeProblems = (mode: number): string[] => {
|
|
|
123
139
|
return found;
|
|
124
140
|
};
|
|
125
141
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
const SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
131
|
-
// ★ ONLY WHAT THE READER HANDLES: a gzipped tar. A `.zip` or a bare binary is a new capability
|
|
132
|
-
// with its own walk-down, not a name this should accept and then fail to unpack.
|
|
133
|
-
const TARBALL = /\.(tar\.gz|tgz)$/;
|
|
134
|
-
|
|
135
|
-
/** A single path segment that cannot climb, hide or name the directory itself. */
|
|
136
|
-
const segmentProblem = (what: string, value: string): string | undefined =>
|
|
137
|
-
!SEGMENT.test(value) || value === '.' || value === '..'
|
|
138
|
-
? `${what} must be one path segment of letters, digits, '.', '_' or '-': ${JSON.stringify(value)}`
|
|
139
|
-
: undefined;
|
|
140
|
-
|
|
141
|
-
/** Everything wrong with the pins alone — plain strings in any declaration, resolved or not. */
|
|
142
|
-
export const pinProblems = (props: {
|
|
143
|
-
readonly archive?: Partial<ReleaseArchive>;
|
|
144
|
-
readonly member?: unknown;
|
|
145
|
-
readonly sha256?: unknown;
|
|
146
|
-
readonly name?: unknown;
|
|
147
|
-
}): string[] => {
|
|
148
|
-
const archive = props.archive ?? {};
|
|
149
|
-
const found: string[] = [];
|
|
150
|
-
const text = (what: string, value: unknown): string | undefined => {
|
|
151
|
-
if (typeof value === 'string') return value;
|
|
152
|
-
found.push(`${what} is required, as a plain string`);
|
|
153
|
-
return undefined;
|
|
154
|
-
};
|
|
155
|
-
const repo = text('archive.repo', archive.repo);
|
|
156
|
-
// ⚠️ `owner/..` passes the alphabet, and a URL parser normalises it away: the request would go
|
|
157
|
-
// to `github.com/releases/…`, somewhere nobody pinned. Refused by name.
|
|
158
|
-
const repoName = repo?.slice(repo.indexOf('/') + 1);
|
|
159
|
-
if (repo !== undefined && (!REPO.test(repo) || repoName === '.' || repoName === '..'))
|
|
160
|
-
found.push(`archive.repo must be "owner/name": ${JSON.stringify(repo)}`);
|
|
161
|
-
for (const [what, value] of [
|
|
162
|
-
['archive.tag', archive.tag],
|
|
163
|
-
['name', props.name],
|
|
164
|
-
] as const) {
|
|
165
|
-
const given = text(what, value);
|
|
166
|
-
const problem = given === undefined ? undefined : segmentProblem(what, given);
|
|
167
|
-
if (problem !== undefined) found.push(problem);
|
|
168
|
-
}
|
|
169
|
-
const asset = text('archive.asset', archive.asset);
|
|
170
|
-
if (asset !== undefined) {
|
|
171
|
-
const problem = segmentProblem('archive.asset', asset);
|
|
172
|
-
if (problem !== undefined) found.push(problem);
|
|
173
|
-
else if (!TARBALL.test(asset))
|
|
174
|
-
found.push(`archive.asset must be a .tar.gz or .tgz: "${asset}"`);
|
|
175
|
-
}
|
|
176
|
-
// ⛔ THE SIZE IS ALLOCATED UP FRONT (download.ts fills one buffer of exactly this many bytes), so
|
|
177
|
-
// a typo'd size is a memory bomb in the deploying process. vmutils, the largest archive pinned,
|
|
178
|
-
// is 123.6 MB; a gibibyte is a ceiling nothing single-binary should reach.
|
|
179
|
-
const size = archive.size;
|
|
180
|
-
if (typeof size !== 'number' || !Number.isSafeInteger(size) || size <= 0 || size > MAX_ARCHIVE) {
|
|
181
|
-
found.push(`archive.size must be the asset's byte count, 1 to ${String(MAX_ARCHIVE)}`);
|
|
182
|
-
}
|
|
183
|
-
for (const [what, value] of [
|
|
184
|
-
['archive.sha256', archive.sha256],
|
|
185
|
-
['sha256', props.sha256],
|
|
186
|
-
] as const) {
|
|
187
|
-
const given = text(what, value);
|
|
188
|
-
if (given !== undefined && !HEX64.test(given))
|
|
189
|
-
found.push(`${what} must be 64 lower-case hex digits`);
|
|
190
|
-
}
|
|
191
|
-
const member = text('member', props.member);
|
|
192
|
-
// ⛔ The tar reader refuses these entries anyway; asking for one is a mistake to name at plan.
|
|
193
|
-
if (
|
|
194
|
-
member !== undefined &&
|
|
195
|
-
(member === '' || member.startsWith('/') || member.split('/').includes('..'))
|
|
196
|
-
) {
|
|
197
|
-
found.push(`member must be a relative name inside the archive: ${JSON.stringify(member)}`);
|
|
198
|
-
}
|
|
199
|
-
return found;
|
|
200
|
-
};
|
|
142
|
+
// `pinProblems` (imported above, for `binaryProblems` below) and the constant it needs,
|
|
143
|
+
// `MAX_ARCHIVE`, moved to binary-pins.ts, split out to stay under the file-size cap; re-exported
|
|
144
|
+
// here so every importer of './binary-form.ts' is unchanged.
|
|
145
|
+
export { MAX_ARCHIVE, pinProblems } from './binary-pins.ts';
|
|
201
146
|
|
|
202
|
-
|
|
147
|
+
// ★ 'root' JOINED THIS LIST 2026-09-23. A state row from before this unit has no `root` at all
|
|
148
|
+
// (`undefined`), and `undefined !== undefined` is false, so an old row with no root and a new
|
|
149
|
+
// declaration that still has none does not move; declaring — or changing — a root does.
|
|
150
|
+
const ARCHIVE_KEYS = ['repo', 'tag', 'asset', 'size', 'sha256', 'root'] as const;
|
|
203
151
|
|
|
204
152
|
type Pins = Pick<ReleaseBinaryProps, 'archive' | 'member' | 'sha256'>;
|
|
205
153
|
|