@adminiumjs/adapter-postgres 0.1.0-rc.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-engine.d.ts","sourceRoot":"","sources":["../src/query-engine.ts"],"names":[],"mappings":"AAYA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAUlC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,GAAG,WAAW,CAoDpF"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `createQueryEngine()` — the Kysely dialect factory for the pooled data
3
+ * connection (05-introspection-engine.md §3 `QueryEngine`, 08-server-api.md
4
+ * §3.7 "query port").
5
+ *
6
+ * `@adminium/engine` does not depend on `kysely`, so `QueryEngine.dialect`
7
+ * is typed opaquely there; `@adminium/server` casts it to `kysely.Dialect`
8
+ * at the composition boundary. This package owns the concrete dependency.
9
+ */
10
+ import { PostgresDialect } from 'kysely';
11
+ import pg from 'pg';
12
+ import { AdapterError, } from '@adminium/engine/adapter';
13
+ import { PG_MAX_IDENTIFIER_LENGTH, postgresSerializers, quoteIdentifier, } from './serialization.js';
14
+ const DEFAULT_DATA_POOL_MAX = 10;
15
+ /**
16
+ * Build the CRUD query port for a data-role connection. Accepts a DSN string
17
+ * or the role-branded config (which must carry a `dsn`). The pool is lazy —
18
+ * no connection is opened until the first query — and `destroy()` tears it
19
+ * down idempotently.
20
+ */
21
+ export function createQueryEngine(config) {
22
+ const dsn = typeof config === 'string' ? config : config.dsn;
23
+ if (dsn === undefined || dsn.length === 0) {
24
+ throw new AdapterError('UNKNOWN', 'postgres query engine requires a DSN', {
25
+ hint: 'pass a postgres:// connection string (TLS options honored from sslmode)',
26
+ });
27
+ }
28
+ const poolMax = typeof config === 'string' ? undefined : config.poolMax;
29
+ const pool = new pg.Pool({
30
+ connectionString: dsn,
31
+ max: poolMax ?? DEFAULT_DATA_POOL_MAX,
32
+ });
33
+ // Surface idle-client failures as pool-level noise, not process crashes —
34
+ // the same guard `createAdapter`'s pool carries in ./index.ts, which this
35
+ // pool was missing.
36
+ //
37
+ // `pg` emits 'error' on the POOL when a client dies while idle in the pool
38
+ // (nobody is awaiting a query, so there is no promise to reject). An EventEmitter
39
+ // 'error' event with no listener is rethrown as an uncaught exception, so a
40
+ // Postgres-side termination of an idle connection — failover, a restart, an
41
+ // admin `pg_terminate_backend`, `DROP DATABASE ... WITH (FORCE)`, or an idle
42
+ // timeout on the server — would take the whole Node process down rather than
43
+ // being retried on the next checkout. Every one of those is routine operational
44
+ // life for a long-lived data pool, and the pool recovers on its own: the dead
45
+ // client is discarded and the next `connect()` opens a fresh one.
46
+ //
47
+ // Found by the M7 Wave 4 verification pass: apps/server's crud.test.ts drops its
48
+ // test database WITH (FORCE) in `afterAll`, which terminates any client still
49
+ // closing (57P01). That surfaced as a *rare* unhandled error failing the whole
50
+ // `pnpm test` run — the harness merely reproduced, under load, what a production
51
+ // failover does on purpose.
52
+ pool.on('error', () => {
53
+ /* mapped when the next query fails */
54
+ });
55
+ // Structural cast: kysely's `PostgresPool` narrows `QueryResult.command`
56
+ // to a command-name union while `pg` types it as `string`. The runtime
57
+ // shapes are identical (kysely's own docs pass a `pg.Pool` here), and the
58
+ // dialect crosses the `QueryEngine` boundary as `unknown` anyway.
59
+ const kyselyPool = pool;
60
+ let destroyed = false;
61
+ return {
62
+ dialect: new PostgresDialect({ pool: kyselyPool }),
63
+ identifiers: { quote: quoteIdentifier, maxLength: PG_MAX_IDENTIFIER_LENGTH },
64
+ serializers: postgresSerializers,
65
+ async destroy() {
66
+ if (destroyed)
67
+ return;
68
+ destroyed = true;
69
+ await pool.end();
70
+ },
71
+ };
72
+ }
73
+ //# sourceMappingURL=query-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-engine.js","sourceRoot":"","sources":["../src/query-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,eAAe,EAA8B,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EACL,YAAY,GAGb,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqC;IACrE,MAAM,GAAG,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAC7D,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,sCAAsC,EAAE;YACxE,IAAI,EAAE,yEAAyE;SAChF,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACxE,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;QACvB,gBAAgB,EAAE,GAAG;QACrB,GAAG,EAAE,OAAO,IAAI,qBAAqB;KACtC,CAAC,CAAC;IACH,0EAA0E;IAC1E,0EAA0E;IAC1E,oBAAoB;IACpB,EAAE;IACF,2EAA2E;IAC3E,kFAAkF;IAClF,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,gFAAgF;IAChF,8EAA8E;IAC9E,kEAAkE;IAClE,EAAE;IACF,iFAAiF;IACjF,8EAA8E;IAC9E,+EAA+E;IAC/E,iFAAiF;IACjF,4BAA4B;IAC5B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACpB,sCAAsC;IACxC,CAAC,CAAC,CAAC;IAEH,yEAAyE;IACzE,uEAAuE;IACvE,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,UAAU,GAAG,IAAgD,CAAC;IAEpE,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,OAAO;QACL,OAAO,EAAE,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAClD,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,wBAAwB,EAAE;QAC5E,WAAW,EAAE,mBAAmB;QAChC,KAAK,CAAC,OAAO;YACX,IAAI,SAAS;gBAAE,OAAO;YACtB,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Identifier quoting + per-LogicalType value serialization for the Postgres
3
+ * data connection — 05-introspection-engine.md §3 (`QueryEngine`) and
4
+ * 08-server-api.md §3.7. Pure module (no `pg`/`kysely` import) so the policy
5
+ * is unit-testable offline.
6
+ *
7
+ * Serialization policy (05 §3 `TypeSerializer` contract):
8
+ * - `bigint` (int8) and `decimal` (numeric/money) travel as STRINGS end to
9
+ * end — the pg driver already returns them as strings and JS numbers would
10
+ * silently lose precision.
11
+ * - timestamps serialize to ISO-8601 UTC; `date`/`time` keep their SQL text
12
+ * forms (`YYYY-MM-DD`, `HH:MM:SS`).
13
+ * - `json` values pass through as parsed objects; `toDb` stringifies so
14
+ * Kysely binds a single jsonb parameter.
15
+ * - BYTEA POLICY: `binary` columns are EXCLUDED from CRUD v1 — no serializer
16
+ * is registered for `'binary'`. Grids/forms never select bytea columns
17
+ * (they render as an attachment placeholder); file/blob transfer arrives
18
+ * with the dedicated attachment endpoints post-v1.
19
+ */
20
+ import type { LogicalType, TypeSerializer } from '@adminium/engine/adapter';
21
+ /** Postgres truncates identifiers at 63 bytes (NAMEDATALEN - 1). */
22
+ export declare const PG_MAX_IDENTIFIER_LENGTH = 63;
23
+ /**
24
+ * Double-quote an identifier, escaping embedded quotes. Identifiers are
25
+ * snapshot-validated before they ever reach this point (no raw SQL escape
26
+ * hatch — 05 §3); quoting is defense in depth, not sanitization.
27
+ */
28
+ export declare function quoteIdentifier(identifier: string): string;
29
+ /** Per-LogicalType converters handed to the CRUD layer via `QueryEngine`. */
30
+ export declare const postgresSerializers: Partial<Record<LogicalType, TypeSerializer>>;
31
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE5E,oEAAoE;AACpE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AA2BD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAW5E,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** Postgres truncates identifiers at 63 bytes (NAMEDATALEN - 1). */
2
+ export const PG_MAX_IDENTIFIER_LENGTH = 63;
3
+ /**
4
+ * Double-quote an identifier, escaping embedded quotes. Identifiers are
5
+ * snapshot-validated before they ever reach this point (no raw SQL escape
6
+ * hatch — 05 §3); quoting is defense in depth, not sanitization.
7
+ */
8
+ export function quoteIdentifier(identifier) {
9
+ return `"${identifier.replaceAll('"', '""')}"`;
10
+ }
11
+ function toIsoUtc(value) {
12
+ if (value instanceof Date)
13
+ return value.toISOString();
14
+ return value;
15
+ }
16
+ /** `YYYY-MM-DD` from a driver-parsed Date (local calendar date, not UTC). */
17
+ function toIsoDate(value) {
18
+ if (value instanceof Date) {
19
+ const month = String(value.getMonth() + 1).padStart(2, '0');
20
+ const day = String(value.getDate()).padStart(2, '0');
21
+ return `${value.getFullYear()}-${month}-${day}`;
22
+ }
23
+ return value;
24
+ }
25
+ function numericToDb(value) {
26
+ if (typeof value === 'bigint' || typeof value === 'number')
27
+ return value.toString();
28
+ return value;
29
+ }
30
+ const lossless = {
31
+ toDb: numericToDb,
32
+ fromDb: (value) => (typeof value === 'number' ? value.toString() : value),
33
+ };
34
+ /** Per-LogicalType converters handed to the CRUD layer via `QueryEngine`. */
35
+ export const postgresSerializers = {
36
+ bigint: lossless,
37
+ decimal: lossless,
38
+ timestamp: { toDb: (value) => value, fromDb: toIsoUtc },
39
+ timestamptz: { toDb: (value) => value, fromDb: toIsoUtc },
40
+ date: { toDb: (value) => value, fromDb: toIsoDate },
41
+ json: {
42
+ toDb: (value) => (typeof value === 'string' ? value : JSON.stringify(value)),
43
+ fromDb: (value) => value,
44
+ },
45
+ // NOTE: no 'binary' entry on purpose — bytea is excluded from CRUD v1.
46
+ };
47
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAqBA,oEAAoE;AACpE,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;AACjD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IACtD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrD,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IACpF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,QAAQ,GAAmB;IAC/B,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;CAC1E,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAiD;IAC/E,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE;IACvD,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE;IACzD,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;IACnD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC5E,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;KACzB;IACD,uEAAuE;CACxE,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Postgres table-statistics collector — 06-llm-assist.md §4.2.
3
+ *
4
+ * Cheap-first strategy (sample-free by default): row-count from
5
+ * `pg_class.reltuples` with an exact `COUNT(*)` fallback for small/stale
6
+ * tables; per-column null-fraction and distinct-count from `pg_stats` when the
7
+ * table has been analyzed, else a single bounded full-scan of aggregates
8
+ * (`SUM(col IS NULL)`, `COUNT(DISTINCT col)`) — aggregates only, never a cell
9
+ * value. `pg_stats.most_common_vals` is row data and is deliberately never
10
+ * read: concrete values appear ONLY under the sampling opt-in, and NEVER for
11
+ * PII-suspected or secret columns.
12
+ *
13
+ * Executor-agnostic (like introspect.ts): takes a `sql → rows` runner so the
14
+ * logic is unit-testable without the `pg` driver.
15
+ */
16
+ import type { CollectStatsOptions, StatsResult, TableRef } from '@adminium/engine/adapter';
17
+ /** Any `sql → rows` runner (the pg data pool in production). */
18
+ export type StatsExecutor = (sql: string) => Promise<Record<string, unknown>[]>;
19
+ /**
20
+ * Normalize `pg_stats.n_distinct`: `>0` is an absolute estimate, `<0` is the
21
+ * negative fraction of the row count, `0`/absent means unknown.
22
+ */
23
+ export declare function normalizePgDistinct(nDistinct: number | null, rowCount: number | null): number | null;
24
+ export declare function collectPostgresStats(exec: StatsExecutor, table: TableRef, opts?: CollectStatsOptions): Promise<StatsResult>;
25
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,mBAAmB,EAGnB,WAAW,EAEX,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAUlC,gEAAgE;AAChE,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;AAmChF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAKpG;AA8BD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,QAAQ,EACf,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,WAAW,CAAC,CAmItB"}
package/dist/stats.js ADDED
@@ -0,0 +1,195 @@
1
+ import { STATS_DEFAULT_SAMPLE_VALUES, STATS_EXACT_COUNT_THRESHOLD, STATS_MAX_SCAN_ROWS, STATS_SAMPLE_VALUE_MAX_CHARS, } from '@adminium/engine/adapter';
2
+ import { quoteIdentifier } from './serialization.js';
3
+ /** Ordered logical types eligible for min/max under sampling opt-in. */
4
+ const ORDERED_TYPES = new Set([
5
+ 'integer',
6
+ 'bigint',
7
+ 'decimal',
8
+ 'float',
9
+ 'date',
10
+ 'time',
11
+ 'timestamp',
12
+ 'timestamptz',
13
+ ]);
14
+ function num(value) {
15
+ if (value === null || value === undefined)
16
+ return null;
17
+ const n = Number(value);
18
+ return Number.isFinite(n) ? n : null;
19
+ }
20
+ function str(value) {
21
+ return typeof value === 'string' && value.length > 0 ? value : null;
22
+ }
23
+ function literal(value) {
24
+ return `'${value.replaceAll("'", "''")}'`;
25
+ }
26
+ function clampFraction(value) {
27
+ if (value === null)
28
+ return null;
29
+ if (value < 0)
30
+ return 0;
31
+ if (value > 1)
32
+ return 1;
33
+ return value;
34
+ }
35
+ /**
36
+ * Normalize `pg_stats.n_distinct`: `>0` is an absolute estimate, `<0` is the
37
+ * negative fraction of the row count, `0`/absent means unknown.
38
+ */
39
+ export function normalizePgDistinct(nDistinct, rowCount) {
40
+ if (nDistinct === null || nDistinct === 0)
41
+ return null;
42
+ if (nDistinct > 0)
43
+ return Math.round(nDistinct);
44
+ if (rowCount === null)
45
+ return null;
46
+ return Math.round(Math.abs(nDistinct) * rowCount);
47
+ }
48
+ function toScalar(value) {
49
+ if (value === null || value === undefined)
50
+ return null;
51
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
52
+ return value;
53
+ }
54
+ if (typeof value === 'bigint')
55
+ return value.toString();
56
+ if (value instanceof Date)
57
+ return value.toISOString();
58
+ if (value instanceof Uint8Array)
59
+ return '[binary]';
60
+ return String(value);
61
+ }
62
+ function truncate(value) {
63
+ if (typeof value === 'string' && value.length > STATS_SAMPLE_VALUE_MAX_CHARS) {
64
+ return value.slice(0, STATS_SAMPLE_VALUE_MAX_CHARS);
65
+ }
66
+ return value;
67
+ }
68
+ /** One batched full-scan: `COUNT(*)` + per-column null count + distinct count. */
69
+ function buildNullDistinctScan(qualified, columns) {
70
+ const parts = columns.map((name, k) => {
71
+ const q = quoteIdentifier(name);
72
+ return `sum(CASE WHEN ${q} IS NULL THEN 1 ELSE 0 END)::int8 AS nf_${k}, count(DISTINCT ${q})::int8 AS dc_${k}`;
73
+ });
74
+ const cols = parts.length > 0 ? `, ${parts.join(', ')}` : '';
75
+ return `SELECT count(*)::int8 AS row_total${cols} FROM ${qualified}`;
76
+ }
77
+ export async function collectPostgresStats(exec, table, opts = {}) {
78
+ const schema = table.schema ?? 'public';
79
+ const name = table.name;
80
+ const qualified = `${quoteIdentifier(schema)}.${quoteIdentifier(name)}`;
81
+ const columns = opts.columns ?? [];
82
+ const sampling = opts.sampling ?? null;
83
+ const maxScanRows = opts.maxScanRows ?? STATS_MAX_SCAN_ROWS;
84
+ const warnings = [];
85
+ // 1. Row-count estimate from the catalog, exact COUNT for small/stale tables.
86
+ const relRows = await exec(`SELECT c.reltuples::float8 AS reltuples
87
+ FROM pg_catalog.pg_class c
88
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
89
+ WHERE n.nspname = ${literal(schema)} AND c.relname = ${literal(name)}
90
+ LIMIT 1`);
91
+ const reltuples = num(relRows[0]?.['reltuples']);
92
+ let rowCountEstimate;
93
+ let rowCountExact;
94
+ if (reltuples !== null && reltuples >= STATS_EXACT_COUNT_THRESHOLD) {
95
+ rowCountEstimate = Math.round(reltuples);
96
+ rowCountExact = false;
97
+ }
98
+ else {
99
+ // reltuples null / negative (never analyzed) / below threshold → exact count.
100
+ const countRows = await exec(`SELECT count(*)::int8 AS n FROM ${qualified}`);
101
+ rowCountEstimate = num(countRows[0]?.['n']);
102
+ rowCountExact = rowCountEstimate !== null;
103
+ }
104
+ // 2. Cheap per-column stats from pg_stats (never most_common_vals — row data).
105
+ const statByCol = new Map();
106
+ if (columns.length > 0) {
107
+ const statRows = await exec(`SELECT attname, null_frac::float8 AS null_frac, n_distinct::float8 AS n_distinct
108
+ FROM pg_catalog.pg_stats
109
+ WHERE schemaname = ${literal(schema)} AND tablename = ${literal(name)}`);
110
+ for (const row of statRows) {
111
+ const attname = str(row['attname']);
112
+ if (attname === null)
113
+ continue;
114
+ statByCol.set(attname, {
115
+ nullFrac: num(row['null_frac']),
116
+ nDistinct: num(row['n_distinct']),
117
+ });
118
+ }
119
+ }
120
+ const colStats = columns.map((c) => ({
121
+ column: c.name,
122
+ nullFraction: null,
123
+ distinctCount: null,
124
+ }));
125
+ const uncovered = [];
126
+ columns.forEach((c, i) => {
127
+ const covered = statByCol.get(c.name);
128
+ if (covered !== undefined) {
129
+ colStats[i].nullFraction = clampFraction(covered.nullFrac);
130
+ colStats[i].distinctCount = normalizePgDistinct(covered.nDistinct, rowCountEstimate);
131
+ }
132
+ else {
133
+ uncovered.push(i);
134
+ }
135
+ });
136
+ // 3. Full-scan fallback for un-analyzed columns on tables under the cap.
137
+ if (uncovered.length > 0) {
138
+ const scannable = rowCountEstimate !== null && rowCountEstimate <= maxScanRows;
139
+ if (scannable) {
140
+ const scanRows = await exec(buildNullDistinctScan(qualified, uncovered.map((i) => columns[i].name)));
141
+ const row = scanRows[0] ?? {};
142
+ const total = num(row['row_total']);
143
+ uncovered.forEach((ci, k) => {
144
+ const nulls = num(row[`nf_${k}`]);
145
+ colStats[ci].nullFraction =
146
+ total === null || total === 0 ? (total === 0 ? 0 : null) : nulls === null ? null : nulls / total;
147
+ colStats[ci].distinctCount = num(row[`dc_${k}`]);
148
+ });
149
+ }
150
+ else {
151
+ warnings.push(`null-fraction/distinct-count skipped for ${uncovered.length} column(s): table exceeds maxScanRows and lacks analyzed statistics`);
152
+ }
153
+ }
154
+ // 4. Sampling opt-in — most-common values + ordered min/max; never PII/secret.
155
+ let sampled = false;
156
+ if (sampling !== null) {
157
+ const maxValues = sampling.maxValuesPerColumn > 0
158
+ ? Math.floor(sampling.maxValuesPerColumn)
159
+ : STATS_DEFAULT_SAMPLE_VALUES;
160
+ for (let i = 0; i < columns.length; i += 1) {
161
+ const c = columns[i];
162
+ if (c.piiSuspected === true || c.secret === true)
163
+ continue; // NEVER sample PII/secret.
164
+ const q = quoteIdentifier(c.name);
165
+ if (ORDERED_TYPES.has(c.logicalType)) {
166
+ // No ::text cast — ordered types are numeric/temporal; the driver
167
+ // returns numbers/dates that toScalar keeps JSON-native (bigint/decimal
168
+ // arrive as strings for lossless transport).
169
+ const mm = await exec(`SELECT min(${q}) AS lo, max(${q}) AS hi FROM ${qualified}`);
170
+ colStats[i].min = toScalar(mm[0]?.['lo']);
171
+ colStats[i].max = toScalar(mm[0]?.['hi']);
172
+ sampled = true;
173
+ }
174
+ const mcv = await exec(`SELECT ${q}::text AS v, count(*)::int8 AS c
175
+ FROM ${qualified}
176
+ WHERE ${q} IS NOT NULL
177
+ GROUP BY ${q}
178
+ ORDER BY count(*) DESC, ${q}
179
+ LIMIT ${maxValues}`);
180
+ colStats[i].sampleValues = mcv.map((r) => truncate(toScalar(r['v'])));
181
+ sampled = true;
182
+ }
183
+ }
184
+ const result = {
185
+ table: { schema: table.schema, name },
186
+ rowCountEstimate,
187
+ rowCountExact,
188
+ columns: colStats,
189
+ sampled,
190
+ };
191
+ if (warnings.length > 0)
192
+ result.warnings = warnings;
193
+ return result;
194
+ }
195
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Postgres dbType → portable LogicalType mapping — 05-introspection-engine.md
3
+ * §2.2 ("canonical table — extend, never fork"), Postgres column.
4
+ *
5
+ * Input is the verbatim `pg_catalog.format_type(atttypid, atttypmod)` string
6
+ * (e.g. `character varying(120)`, `timestamp with time zone`, `numeric(10,2)`)
7
+ * so the mapping is pure and unit-testable offline. Enum/domain/array
8
+ * resolution happens in the introspection assembler (it needs `pg_type`
9
+ * rows); this module only handles the base-name mapping plus the
10
+ * length/precision/scale extraction rules.
11
+ */
12
+ import type { ColumnDefault, LogicalType } from '@adminium/engine/adapter';
13
+ export interface MappedType {
14
+ logicalType: LogicalType;
15
+ maxLength: number | null;
16
+ numericPrecision: number | null;
17
+ numericScale: number | null;
18
+ }
19
+ /**
20
+ * Map a verbatim Postgres type string to its portable shape. Unmappable
21
+ * types become `'unknown'` with the verbatim `dbType` preserved by the
22
+ * caller (rendered read-only as text — 05 §2.2).
23
+ */
24
+ export declare function mapPostgresType(dbType: string): MappedType;
25
+ /**
26
+ * Classify a column default — 05 §4.1: `serial`/`nextval` and identity
27
+ * columns → `autoincrement`; `now()`/`CURRENT_TIMESTAMP` → `now`;
28
+ * `gen_random_uuid()`/`uuid_generate_v4()` → `uuid`.
29
+ *
30
+ * @param defaultExpr `pg_get_expr(adbin, adrelid)` output, or null.
31
+ * @param identity `pg_attribute.attidentity` ('' | 'a' | 'd').
32
+ */
33
+ export declare function classifyDefault(defaultExpr: string | null, identity: string): ColumnDefault;
34
+ //# sourceMappingURL=type-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-map.d.ts","sourceRoot":"","sources":["../src/type-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA+D3E,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAsBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAoB1D;AAUD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAS3F"}
@@ -0,0 +1,135 @@
1
+ /** `format_type` base name (parenthesized modifiers stripped) → LogicalType. */
2
+ const BASE_TYPE_MAP = {
3
+ // text family
4
+ text: 'text',
5
+ citext: 'text',
6
+ name: 'text',
7
+ 'character varying': 'varchar',
8
+ varchar: 'varchar',
9
+ character: 'varchar',
10
+ char: 'varchar',
11
+ bpchar: 'varchar',
12
+ // integers (serial pseudo-types never appear in format_type output but are
13
+ // kept for defensive parsing of imported DDL strings)
14
+ smallint: 'integer',
15
+ integer: 'integer',
16
+ int2: 'integer',
17
+ int4: 'integer',
18
+ serial: 'integer',
19
+ smallserial: 'integer',
20
+ bigint: 'bigint',
21
+ int8: 'bigint',
22
+ bigserial: 'bigint',
23
+ oid: 'integer',
24
+ // exact + approximate numerics
25
+ numeric: 'decimal',
26
+ decimal: 'decimal',
27
+ money: 'decimal',
28
+ real: 'float',
29
+ float4: 'float',
30
+ 'double precision': 'float',
31
+ float8: 'float',
32
+ // booleans
33
+ boolean: 'boolean',
34
+ bool: 'boolean',
35
+ // date/time
36
+ date: 'date',
37
+ 'time without time zone': 'time',
38
+ 'time with time zone': 'time',
39
+ time: 'time',
40
+ timetz: 'time',
41
+ 'timestamp without time zone': 'timestamp',
42
+ timestamp: 'timestamp',
43
+ 'timestamp with time zone': 'timestamptz',
44
+ timestamptz: 'timestamptz',
45
+ interval: 'interval',
46
+ // structured / misc
47
+ uuid: 'uuid',
48
+ json: 'json',
49
+ jsonb: 'json',
50
+ bytea: 'binary',
51
+ inet: 'inet',
52
+ cidr: 'inet',
53
+ // PostGIS (flag; rendered read-only as text in v1 — 05 §2.2)
54
+ geometry: 'geometry',
55
+ geography: 'geometry',
56
+ };
57
+ /** Binary-float precision constants surfaced by the catalogs. */
58
+ const FLOAT4_PRECISION = 24;
59
+ const FLOAT8_PRECISION = 53;
60
+ /** Strip `(…)` modifiers, `[]` suffixes, and interval field qualifiers. */
61
+ function baseTypeName(dbType) {
62
+ let base = dbType.trim().toLowerCase();
63
+ base = base.replace(/\[\s*\]\s*$/g, ''); // array suffix (possibly multi-dim already collapsed)
64
+ while (/\[\s*\]$/.test(base))
65
+ base = base.replace(/\[\s*\]$/, '');
66
+ base = base.replace(/\(([^)]*)\)/, '').replace(/\s+/g, ' ').trim();
67
+ if (base.startsWith('interval'))
68
+ return 'interval';
69
+ return base;
70
+ }
71
+ /** Extract `(n)` / `(p,s)` modifier numbers, when present. */
72
+ function modifiers(dbType) {
73
+ const match = /\(([\d\s,]+)\)/.exec(dbType);
74
+ if (match?.[1] === undefined)
75
+ return [];
76
+ return match[1]
77
+ .split(',')
78
+ .map((part) => Number.parseInt(part.trim(), 10))
79
+ .filter((n) => Number.isFinite(n));
80
+ }
81
+ /**
82
+ * Map a verbatim Postgres type string to its portable shape. Unmappable
83
+ * types become `'unknown'` with the verbatim `dbType` preserved by the
84
+ * caller (rendered read-only as text — 05 §2.2).
85
+ */
86
+ export function mapPostgresType(dbType) {
87
+ const base = baseTypeName(dbType);
88
+ const logicalType = BASE_TYPE_MAP[base] ?? 'unknown';
89
+ const mods = modifiers(dbType);
90
+ let maxLength = null;
91
+ let numericPrecision = null;
92
+ let numericScale = null;
93
+ if (logicalType === 'varchar' || logicalType === 'text') {
94
+ maxLength = mods[0] ?? null;
95
+ }
96
+ else if (logicalType === 'decimal') {
97
+ numericPrecision = mods[0] ?? null;
98
+ numericScale = mods[1] ?? null;
99
+ }
100
+ else if (logicalType === 'float') {
101
+ numericPrecision =
102
+ base === 'real' || base === 'float4' ? FLOAT4_PRECISION : FLOAT8_PRECISION;
103
+ }
104
+ return { logicalType, maxLength, numericPrecision, numericScale };
105
+ }
106
+ const NOW_DEFAULT = /^(now\(\)|current_timestamp(\(\d*\))?|transaction_timestamp\(\)|statement_timestamp\(\)|current_date|current_time(\(\d*\))?)/i;
107
+ const UUID_DEFAULT = /^(gen_random_uuid\(\)|uuid_generate_v4\(\))/i;
108
+ const AUTOINCREMENT_DEFAULT = /^nextval\(/i;
109
+ /** `'…'::type`, bare numbers, booleans, or a typed NULL are literals. */
110
+ const LITERAL_DEFAULT = /^('(?:[^']|'')*'(?:::[a-z_"][\w ."[\]]*(?:\(\d+(?:,\s*\d+)?\))?)?|-?\d+(\.\d+)?|true|false|NULL(?:::[a-z_"][\w ."[\]]*)?)$/i;
111
+ /**
112
+ * Classify a column default — 05 §4.1: `serial`/`nextval` and identity
113
+ * columns → `autoincrement`; `now()`/`CURRENT_TIMESTAMP` → `now`;
114
+ * `gen_random_uuid()`/`uuid_generate_v4()` → `uuid`.
115
+ *
116
+ * @param defaultExpr `pg_get_expr(adbin, adrelid)` output, or null.
117
+ * @param identity `pg_attribute.attidentity` ('' | 'a' | 'd').
118
+ */
119
+ export function classifyDefault(defaultExpr, identity) {
120
+ if (identity === 'a' || identity === 'd')
121
+ return { kind: 'autoincrement' };
122
+ if (defaultExpr === null || defaultExpr === '')
123
+ return null;
124
+ const text = defaultExpr.trim();
125
+ if (AUTOINCREMENT_DEFAULT.test(text))
126
+ return { kind: 'autoincrement' };
127
+ if (NOW_DEFAULT.test(text))
128
+ return { kind: 'now' };
129
+ if (UUID_DEFAULT.test(text))
130
+ return { kind: 'uuid' };
131
+ if (LITERAL_DEFAULT.test(text))
132
+ return { kind: 'literal', text };
133
+ return { kind: 'expression', text };
134
+ }
135
+ //# sourceMappingURL=type-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-map.js","sourceRoot":"","sources":["../src/type-map.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@adminiumjs/adapter-postgres",
3
+ "version": "0.1.0-rc.1",
4
+ "license": "AGPL-3.0-only",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/MoSofi/Adminium.git",
8
+ "directory": "packages/adapter-postgres"
9
+ },
10
+ "homepage": "https://adminium.dev",
11
+ "bugs": "https://github.com/MoSofi/Adminium/issues",
12
+ "type": "module",
13
+ "description": "PostgreSQL adapter",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@adminium/engine": "npm:@adminiumjs/engine@0.1.0-rc.1",
24
+ "kysely": "^0.28.2",
25
+ "pg": "^8.16.3"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ]
30
+ }