@simtlix/simfinity-postgres 3.5.3 → 3.5.6

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 CHANGED
@@ -31,11 +31,11 @@ import { postgresPlugin } from '@simtlix/simfinity-postgres';
31
31
  const simfinity = createSQL({ plugin: postgresPlugin({ pool, schema: 'library' }) });
32
32
  ```
33
33
 
34
- Install `@simtlix/simfinity-sql@3.5.3` as a direct dependency when importing its factory. PostgreSQL 15 or later is the only supported SQL plugin. The [SQL package guide](https://github.com/simtlix/simfinity.js/tree/master/packages/sql) documents relational planning and the versioned plugin contract.
34
+ Install `@simtlix/simfinity-sql@3.5.6` as a direct dependency when importing its factory. PostgreSQL 15 or later is the only supported SQL plugin. The [SQL package guide](https://github.com/simtlix/simfinity.js/tree/master/packages/sql) documents relational planning and the versioned plugin contract.
35
35
 
36
- Both factories permanently bind one pool and schema. The returned runtime supports registration, generated queries and mutations, middleware and scopes, controllers and validators, custom mutations, state machines, nested relationship writes, embedded reconstruction, and native PostgreSQL model handles. Native handles expose `findById`, `find`, `create`, `update`, and `delete`; they are not Mongoose models. `find(args, { session })` accepts the generated GraphQL list query arguments. Existing `pg.Pool` configuration and `pg.PoolClient` query interfaces remain compatible across both factories.
36
+ Both factories permanently bind one pool and schema. The returned runtime supports registration, generated queries and mutations, middleware and scopes, controllers and validators, custom mutations, state machines, nested relationship writes, embedded reconstruction, and native PostgreSQL model handles. Native handles expose `findById`, `find`, `create`, `update`, and `delete`; they are not Mongoose models. `find(args, { session })` accepts the generated GraphQL list query arguments. Existing `pg.Pool` configuration and `pg.PoolClient` query interfaces remain compatible across both factories. A custom pool or client wrapper should pass `pg`'s `command` through in query results: the plugin reads it from the `COMMIT` result to detect a transaction that PostgreSQL rolled back because one of its statements failed, and without it such a transaction reports success with nothing stored.
37
37
 
38
- Operations attempted before successful initialization fail with `DATABASE_NOT_INITIALIZED`. Call `createSchema` before `initializeDatabase`. Configuration and registrations become immutable once schema preparation begins. `preventCreatingCollection(true)` makes instance initialization validate existing storage without issuing creation DDL.
38
+ Operations attempted before successful initialization fail with `DATABASE_NOT_INITIALIZED`. Call `createSchema` before `initializeDatabase`. Configuration and registrations become immutable once schema preparation begins. `preventCreatingCollection(true)` makes instance initialization validate existing storage without issuing creation DDL. A rejected initialization request leaves readiness as it was; a ready instance keeps serving while it is initialized again, and becomes unavailable if that initialization fails. Use `mode: 'validate'` to check a live instance, because `create` mode locks the managed tables against writes until it commits. `simfinity.compileDatabaseSchema()` returns the instance's DDL for review or export.
39
39
 
40
40
  A default module instance is also available when a singleton facade is preferable:
41
41
 
@@ -53,14 +53,16 @@ const schema = createSchema();
53
53
  await initializeDatabase({ mode: 'validate' });
54
54
  ```
55
55
 
56
- Transactions use one borrowed `pg` client and repeatable-read isolation. A supplied Simfinity PostgreSQL session joins the active transaction. Serialization failures and deadlocks are retried up to five times; mutation input is reset for each attempt. Unknown commit outcomes are not replayed. The caller owns the pool lifecycle.
56
+ Called without arguments after `createSchema()`, the module's `describeDatabase()` and `compileDatabaseSchema()` describe and compile this default instance in its configured schema.
57
+
58
+ Transactions use one borrowed `pg` client and repeatable-read isolation. A supplied Simfinity PostgreSQL session joins the active transaction. Serialization failures and deadlocks are retried up to five times, each after a short random delay that starts once the client is released; mutation input is reset for each attempt. Unknown commit outcomes are not replayed. The caller owns the pool lifecycle.
57
59
 
58
60
  The module and every `createPostgres` instance expose the shared `auth`, `validators`, `scalars`, and `plugins` helpers. These are the same helper objects exported by the MongoDB package, so rules, scalar identities, and errors can be shared safely between backends.
59
61
 
60
62
  MCP integration is an independent opt-in and does not add MCP dependencies to PostgreSQL applications:
61
63
 
62
64
  ```sh
63
- npm install @simtlix/simfinity-mcp@3.5.3 @modelcontextprotocol/sdk@^1.13.0
65
+ npm install @simtlix/simfinity-mcp@3.5.6 @modelcontextprotocol/sdk@^1.31.0
64
66
  ```
65
67
 
66
68
  ```javascript
@@ -71,10 +73,12 @@ const mcpServer = await createMCPServer(schema);
71
73
 
72
74
  ## Storage and compatible subset
73
75
 
74
- Entity identities and references use UUIDs. Scalars use native PostgreSQL types, scalar lists use arrays, reference-free embedded values use JSONB, and embedded values containing references use private owned tables. Foreign keys, inverse collections, explicit linking entities, missing/null markers, nullable embedded-list items, hooks, rollback, and state transitions are supported by the runtime.
76
+ Entity identities and references use UUIDs. Scalars use native PostgreSQL types, scalar lists (including `[ID]` lists) use arrays without a generated index, reference-free embedded values use JSONB, and embedded values containing references use private owned tables. Foreign keys, inverse collections, explicit linking entities, missing/null markers, nullable embedded-list items, hooks, rollback, and state transitions are supported by the runtime.
75
77
 
76
78
  Queries support nested scalar/reference paths, logical filter groups, scopes, sort, pagination, count, and grouped `SUM`, `COUNT`, `AVG`, `MIN`, and `MAX` facts. The PostgreSQL implementation preserves root multiplicity when joining collection paths.
77
79
 
80
+ Errors that PostgreSQL reports become `SimfinityError`s without SQL details, with the driver error as `getCause()`; errors that application code throws propagate unchanged. Text cannot contain NUL and native date-times start at 4714-11-24 BC: such input fails with `INVALID_VALUE` (400).
81
+
78
82
  PostgreSQL enforces required values, real FKs, JSONB/owned embedded shapes, root and embedded scalar/reference uniqueness, and scalar-list multikey uniqueness. Root unique indexes use `NULLS NOT DISTINCT`; private typed owner-key tables allow repeated keys within one owner but reject the same key across owners. Presence markers preserve missing versus explicit null fields. This is stronger and broader than generated Mongoose storage.
79
83
 
80
84
  Scalar-list leaves inside nested embedded lists support filters, sorts, ragged group projections, and array-valued facts. Array `MIN`/`MAX` compares complete arrays; result sorting uses immediate extrema. Date parameters use per-query UTC encoding without changing global `pg` behavior.
@@ -97,4 +101,4 @@ const statements = compileDatabaseSchema(description);
97
101
  await initializeDatabase(pool, description, { mode: 'validate' });
98
102
  ```
99
103
 
100
- `compileDatabaseSchema` returns reviewable SQL. Low-level `initializeDatabase(pool, description, { mode })` creates or validates storage transactionally and never closes the pool. Create mode adds missing objects and rejects incompatible definitions; validate mode performs no DDL. Neither mode performs destructive schema synchronization or data migration.
104
+ Low-level `describeDatabase(registrations, { schema })` uses the `public` schema when `schema` is omitted, not the schema of a configured runtime; pass the schema given to `configure()` or `createPostgres()`, or call `describeDatabase()` and `compileDatabaseSchema()` without arguments for the default instance. `compileDatabaseSchema` returns reviewable SQL. Low-level `initializeDatabase(pool, description, { mode })` creates or validates storage transactionally and never closes the pool. Create mode adds missing objects and rejects incompatible definitions; validate mode performs no DDL. Neither mode performs destructive schema synchronization or data migration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simtlix/simfinity-postgres",
3
- "version": "3.5.3",
3
+ "version": "3.5.6",
4
4
  "description": "PostgreSQL GraphQL runtime and schema generation with foreign keys for Simfinity",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -27,8 +27,8 @@
27
27
  "directory": "packages/postgres"
28
28
  },
29
29
  "dependencies": {
30
- "@simtlix/simfinity-core": "3.5.3",
31
- "@simtlix/simfinity-sql": "3.5.3",
30
+ "@simtlix/simfinity-core": "3.5.6",
31
+ "@simtlix/simfinity-sql": "3.5.6",
32
32
  "pg": "^8.16.3"
33
33
  },
34
34
  "peerDependencies": {
package/src/codecs.js CHANGED
@@ -44,19 +44,40 @@ export const decodeScalar = (field, value, gqlField) => {
44
44
  return value;
45
45
  };
46
46
 
47
+ const databaseErrors = {
48
+ '23503': ['Reference constraint violated', 'REFERENCE_CONSTRAINT_VIOLATION', 409],
49
+ '23505': ['Unique value already exists', 'DUPLICATE_KEY', 409],
50
+ '23502': ['A required value is missing', 'REQUIRED_VALUE', 400],
51
+ '23514': ['A value violates the generated schema', 'INVALID_VALUE', 400],
52
+ '22P02': ['Invalid stored value', 'INVALID_VALUE', 400],
53
+ '22003': ['Numeric value is out of range', 'INVALID_VALUE', 400],
54
+ // Values PostgreSQL cannot store: longer than a sized column, a timestamp or date outside its range
55
+ // (including generated make_date calls), and text or JSON with NUL (U+0000) or a character the
56
+ // server encoding lacks. Index row size limits (54000) stay a DATABASE_ERROR.
57
+ '22001': ['Value is too long', 'INVALID_VALUE', 400],
58
+ '22008': ['Date or time value is out of range', 'INVALID_VALUE', 400],
59
+ '22021': ['Text contains a character the database cannot store', 'INVALID_VALUE', 400],
60
+ '22P05': ['Text contains a character the database cannot store', 'INVALID_VALUE', 400],
61
+ '40001': ['Concurrent write could not be completed', 'TRANSACTION_RETRY_EXCEEDED', 409],
62
+ '40P01': ['Concurrent write could not be completed', 'TRANSACTION_RETRY_EXCEEDED', 409],
63
+ };
64
+
65
+ /** Serialization failures and deadlocks: confirmed aborts that are safe to retry. */
66
+ export const isRetryableDatabaseError = (error) => ['40001', '40P01'].includes(error?.code);
67
+
68
+ // An error the server reported, as node-postgres exposes it: a five-character SQLSTATE and a severity.
69
+ // An application error that only has a `code`, such as ECONNREFUSED or a domain code, is not one.
70
+ export const isPostgresError = (error) => typeof error?.code === 'string' && /^[0-9A-Z]{5}$/.test(error.code) && typeof error.severity === 'string';
71
+
72
+ /** Maps PostgreSQL errors (and exhausted retries) without SQL details; returns every other value unchanged. */
47
73
  export const normalizeDatabaseError = (error) => {
48
- if (error instanceof SimfinityError || !error.code) return error;
49
- const errors = {
50
- '23503': ['Reference constraint violated', 'REFERENCE_CONSTRAINT_VIOLATION', 409],
51
- '23505': ['Unique value already exists', 'DUPLICATE_KEY', 409],
52
- '23502': ['A required value is missing', 'REQUIRED_VALUE', 400],
53
- '23514': ['A value violates the generated schema', 'INVALID_VALUE', 400],
54
- '22P02': ['Invalid stored value', 'INVALID_VALUE', 400],
55
- '22003': ['Numeric value is out of range', 'INVALID_VALUE', 400],
56
- '40001': ['Concurrent write could not be completed', 'TRANSACTION_RETRY_EXCEEDED', 409],
57
- '40P01': ['Concurrent write could not be completed', 'TRANSACTION_RETRY_EXCEEDED', 409],
58
- };
59
- return new SimfinityError(...(errors[error.code] || ['Database operation failed', 'DATABASE_ERROR', 500]));
74
+ if (error instanceof SimfinityError || !(isPostgresError(error) || isRetryableDatabaseError(error))) return error;
75
+ const normalized = new SimfinityError(...(databaseErrors[error.code] || ['Database operation failed', 'DATABASE_ERROR', 500]));
76
+ // Kept for server logging, but non-enumerable as on a native Error, so serializing or spreading the
77
+ // client-facing error never copies the driver's detail, table or constraint.
78
+ Object.defineProperty(normalized, 'cause', { value: error, writable: true, configurable: true, enumerable: false });
79
+ normalized.getCause = () => normalized.cause;
80
+ return normalized;
60
81
  };
61
82
 
62
83
  /** pg's local Date formatting truncates historical offset seconds; use UTC per parameter. */
package/src/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createSQL } from '@simtlix/simfinity-sql';
2
2
  import { postgresPlugin } from './plugin.js';
3
+ import { describeDatabase as describeSchema } from './schema/describe.js';
4
+ import { compileDatabaseSchema as compileSchemaSQL } from './schema/ddl.js';
3
5
  import { initializeDatabase as initializeSchema } from './schema/initialize.js';
4
6
 
5
7
  export { postgresPlugin } from './plugin.js';
6
- export { describeDatabase } from './schema/describe.js';
7
- export { compileDatabaseSchema } from './schema/ddl.js';
8
8
  export {
9
9
  auth,
10
10
  buildErrorFormatter,
@@ -25,3 +25,14 @@ export const { configureQueryLimits, configureMutationLimits, configure, connect
25
25
  export const initializeDatabase = (poolOrOptions, description, options) => description
26
26
  ? initializeSchema(poolOrOptions, description, options)
27
27
  : defaultInstance.initializeDatabase(poolOrOptions);
28
+ /**
29
+ * With arguments, the low-level describer, whose schema defaults to `public`; without, the configured
30
+ * default instance's description, in the schema given to configure().
31
+ */
32
+ export const describeDatabase = (registrations, options) => registrations === undefined && options === undefined
33
+ ? defaultInstance.describeDatabase()
34
+ : describeSchema(registrations, options);
35
+ /** With a description, the low-level DDL compiler; without, the configured default instance's DDL. */
36
+ export const compileDatabaseSchema = (description) => description === undefined
37
+ ? defaultInstance.compileDatabaseSchema()
38
+ : compileSchemaSQL(description);
package/src/plugin.js CHANGED
@@ -6,7 +6,7 @@ import { initializeDatabase } from './schema/initialize.js';
6
6
  import { compileDatabaseSchema } from './schema/ddl.js';
7
7
  import { compileQuery } from './query/compiler.js';
8
8
  import { compileRecord } from './record-statements.js';
9
- import { castId, encodeScalar, decodeScalar, encodeParameter, normalizeDatabaseError } from './codecs.js';
9
+ import { castId, encodeScalar, decodeScalar, encodeParameter, isRetryableDatabaseError, normalizeDatabaseError } from './codecs.js';
10
10
 
11
11
  export const postgresPlugin = (options) => ({
12
12
  apiVersion: 1,
@@ -29,11 +29,22 @@ export const postgresPlugin = (options) => ({
29
29
  query: (configuration, statement, client) => (client || configuration.pool).query(statement.text, statement.values?.map(encodeParameter)),
30
30
  acquire: (configuration) => configuration.pool.connect(),
31
31
  begin: (client) => client.query('BEGIN ISOLATION LEVEL REPEATABLE READ'),
32
- commit: (client) => client.query('COMMIT'),
32
+ // PostgreSQL answers COMMIT with ROLLBACK, and no error, when a statement in the transaction failed
33
+ // and was caught; nothing was stored, so the operation must fail. A result without a command, from a
34
+ // pool wrapper that does not pass pg's through, resolves as before.
35
+ commit: async (client) => {
36
+ const result = await client.query('COMMIT');
37
+ if (result?.command !== 'ROLLBACK') return;
38
+ const error = new SimfinityError('Transaction was rolled back because one of its statements failed', 'DATABASE_ERROR', 500);
39
+ // The same shape as every other PostgreSQL DATABASE_ERROR: a non-enumerable cause, without SQL.
40
+ Object.defineProperty(error, 'cause', { value: new Error('COMMIT reported ROLLBACK'), writable: true, configurable: true, enumerable: false });
41
+ error.getCause = () => error.cause;
42
+ throw error;
43
+ },
33
44
  rollback: (client) => client.query('ROLLBACK'),
34
45
  // A release error makes pg-pool destroy a connection whose transaction may still be open.
35
46
  release: (client, error) => client.release(error),
36
- isRetryable: (error) => ['40001', '40P01'].includes(error.code),
47
+ isRetryable: isRetryableDatabaseError,
37
48
  normalizeError: normalizeDatabaseError,
38
49
  },
39
50
  });
@@ -87,9 +87,12 @@ export const compileQuery = (models, database, plan, extra = null) => {
87
87
  const stored = alias();
88
88
  // Missing/null lists contribute a missing path, empty lists and null items do not.
89
89
  const condition = `${col(stored, '__owner_id')} = ${ownerId} AND ${state}${presence(stored)}`;
90
+ // UNION ALL pairs columns by position, and stored.* would follow the physical column order,
91
+ // which a migration that appends columns changes. Both branches list the description's columns.
92
+ const storedRow = owned.columns.map((column) => col(stored, column.name)).join(', ');
90
93
  const nullRow = owned.columns.map((column) => `NULL::${column.type} AS ${q(column.name)}`).join(', ');
91
94
  const noItems = usage === 'sort' ? ` OR NOT EXISTS (SELECT 1 FROM ${qualified(database.schema, owned.name)} empty_child WHERE empty_child.__owner_id = ${ownerId})` : '';
92
- const source = `LATERAL (SELECT ${stored}.* FROM ${qualified(database.schema, owned.name)} ${stored} WHERE ${condition} UNION ALL SELECT ${nullRow} WHERE ${col(ownerAlias, owned.ownership.stateColumn)} IS DISTINCT FROM 'present'${noItems}) ${name}`;
95
+ const source = `LATERAL (SELECT ${storedRow} FROM ${qualified(database.schema, owned.name)} ${stored} WHERE ${condition} UNION ALL SELECT ${nullRow} WHERE ${col(ownerAlias, owned.ownership.stateColumn)} IS DISTINCT FROM 'present'${noItems}) ${name}`;
93
96
  const vector = context.vector
94
97
  ? { from: `${context.vector.from} CROSS JOIN ${source}`, where: context.vector.where }
95
98
  : { from: source, where: 'TRUE' };
@@ -1,5 +1,5 @@
1
1
  import { SimfinityError } from '@simtlix/simfinity-core';
2
- import { identifier, qualified } from './sql.js';
2
+ import { identifier, qualified, generatedName } from './sql.js';
3
3
  import { createTableSQL, createForeignKeySQL, createIndexSQL, createFunctionSQL, createTriggerSQL } from './ddl.js';
4
4
 
5
5
  const mismatch = (object, reason) => {
@@ -7,12 +7,24 @@ const mismatch = (object, reason) => {
7
7
  };
8
8
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
9
9
 
10
+ // literal() writes a value containing a backslash as E'' with doubled backslashes, while pg_get_expr,
11
+ // read with standard_conforming_strings on, never renders E'' and doubles only quotes. Compare string
12
+ // constants by value: decode either form and re-emit one standard form. The decoder inverts only the
13
+ // escapes literal() emits (\\ and ''); it is not a general E'' decoder (\n or \x41 would be wrong).
14
+ const canonicalString = (token) => {
15
+ const escape = token[0] !== '\'';
16
+ const body = token.slice(escape ? 2 : 1, -1);
17
+ const value = escape ? body.replace(/\\(.)|''/gs, (match, character) => character ?? '\'') : body.replaceAll('\'\'', '\'');
18
+ return `'${value.replaceAll('\'', '\'\'')}'`;
19
+ };
20
+
10
21
  // Ignore renderer whitespace/optional quotes, keeping string contents and expression grouping.
11
- const normalizeExpression = (expression) => {
22
+ export const normalizeExpression = (expression) => {
12
23
  if (expression == null) return null;
13
- const tokens = expression.match(/'(?:[^']|'')*'|"(?:[^"]|"")*"|\s+|./gs) || [];
24
+ // An E-string is one token unless the E ends an identifier.
25
+ const tokens = expression.match(/(?<![\w$"])[Ee]'(?:\\.|''|[^'\\])*'|'(?:[^']|'')*'|"(?:[^"]|"")*"|\s+|./gs) || [];
14
26
  let value = tokens.map((token) => {
15
- if (token.startsWith('\'')) return token;
27
+ if (token.length > 1 && /^[Ee]?'/.test(token)) return canonicalString(token);
16
28
  if (/^"[a-z_][a-z0-9_]*"$/.test(token)) return token.slice(1, -1);
17
29
  return /^\s+$/.test(token) ? '' : token;
18
30
  }).join('');
@@ -122,6 +134,16 @@ const validateIndex = (schema, table, index, actual) => {
122
134
  }
123
135
  };
124
136
 
137
+ // Earlier versions generated a btree index on every [ID] list column. It caps each list at about 167
138
+ // identifiers (btree row size) and no generated query uses it. Initialization keeps it, as it keeps
139
+ // any extra non-unique index, but reports it when it carries the exact generated name and shape.
140
+ const legacyListIndexes = (schema, table, actual) => table.columns
141
+ .filter((column) => column.type === 'uuid[]')
142
+ .map((column) => ({ column: column.name, name: generatedName(table.name, column.name, 'idx') }))
143
+ .filter(({ column, name }) => !table.indexes.some((item) => item.name === name) && actual.indexes.some((index) => index.name === name
144
+ && !index.unique && !index.primary && index.method === 'btree' && same(index.columns, [column]) && index.no_predicate && index.no_expression))
145
+ .map(({ name }) => `${identifier(schema)}.${identifier(name)}`);
146
+
125
147
  const ensureFunctions = async (client, description, mode, created) => {
126
148
  for (const fn of description.functions || []) {
127
149
  const { rows } = await client.query(`SELECT p.oid, pg_get_function_identity_arguments(p.oid) AS arguments,
@@ -205,6 +227,7 @@ export const initializeDatabase = async (pool, description, { mode = 'create' }
205
227
  if (schema.startsWith('pg_') || schema === 'information_schema') throw new SimfinityError('System schemas cannot be managed', 'INVALID_DATABASE_SCHEMA', 400);
206
228
  const client = await pool.connect();
207
229
  const created = [];
230
+ const legacy = [];
208
231
  let rollbackError;
209
232
  try {
210
233
  await client.query(mode === 'validate' ? 'BEGIN READ ONLY' : 'BEGIN');
@@ -258,10 +281,16 @@ export const initializeDatabase = async (pool, description, { mode = 'create' }
258
281
  for (const index of actual.indexes) {
259
282
  if (index.unique && !index.primary && !table.indexes.some((item) => item.name === index.name)) mismatch(`${schema}.${table.name}.${index.name}`, 'unexpected unique index');
260
283
  }
284
+ legacy.push(...legacyListIndexes(schema, table, actual));
261
285
  }
262
286
  await ensureTriggers(client, description, mode, created);
263
287
  if (mode === 'create' && created.length) await backfill(client, description);
264
288
  await client.query('COMMIT');
289
+ if (legacy.length) {
290
+ console.warn(`Simfinity: PostgreSQL schema ${identifier(schema)} has btree indexes that earlier versions generated on [ID] list columns: ${legacy.join(', ')}. `
291
+ + 'Each limits its list to about 167 identifiers and no generated query uses it. Once every process that uses this schema runs this version, drop them: '
292
+ + `${legacy.map((name) => `DROP INDEX CONCURRENTLY IF EXISTS ${name};`).join(' ')} See the upgrade notes at https://simtlix.github.io/simfinity.js/resources/compatibility.html`);
293
+ }
265
294
  return { mode, created };
266
295
  } catch (error) {
267
296
  try { await client.query('ROLLBACK'); } catch (failure) { rollbackError = failure; }
package/types/index.d.ts CHANGED
@@ -109,19 +109,37 @@ export interface DatabaseDescription {
109
109
  }
110
110
  export interface DatabasePool {
111
111
  connect(): Promise<{
112
- query(sql: string, values?: unknown[]): Promise<{ rows: any[]; rowCount: number | null }>;
112
+ query(sql: string, values?: unknown[]): Promise<{
113
+ rows: any[];
114
+ rowCount: number | null;
115
+ /** Command tag reported by pg, such as `COMMIT`. Wrappers should pass pg's `command` through, so that a COMMIT that PostgreSQL turned into ROLLBACK is detected. */
116
+ command?: string;
117
+ }>;
113
118
  /** An error means the connection must be destroyed instead of reused. */
114
119
  release(error?: unknown): void;
115
120
  }>;
116
121
  }
122
+ /**
123
+ * Low-level describer for any registrations. `options.schema` defaults to `public`, not to the schema
124
+ * of a configured runtime; pass the schema given to configure() or createPostgres().
125
+ */
117
126
  export function describeDatabase(registrations: ModelRegistration[], options?: { schema?: string }): DatabaseDescription;
127
+ /** Describes the default module instance in its configured schema, after configure/connect/createSchema. */
128
+ export function describeDatabase(): DatabaseDescription;
118
129
  /** Accepts a trusted description produced by describeDatabase. */
119
130
  export function compileDatabaseSchema(description: DatabaseDescription): string[];
131
+ /** DDL of the default module instance in its configured schema, after configure/connect/createSchema. */
132
+ export function compileDatabaseSchema(): string[];
120
133
  /** Borrows one pool client; owns its transaction, not the pool lifecycle. PostgreSQL >=15. */
121
134
  export function initializeDatabase(pool: DatabasePool, description: DatabaseDescription, options?: { mode?: 'create' | 'validate' }): Promise<{ mode: 'create' | 'validate'; created: string[] }>;
122
135
 
123
136
  export interface DatabaseQueryable {
124
- query(sql: string, values?: any[]): Promise<{ rows: any[]; rowCount: number | null }>;
137
+ query(sql: string, values?: any[]): Promise<{
138
+ rows: any[];
139
+ rowCount: number | null;
140
+ /** Command tag reported by pg, such as `COMMIT`. Wrappers should pass pg's `command` through, so that a COMMIT that PostgreSQL turned into ROLLBACK is detected. */
141
+ command?: string;
142
+ }>;
125
143
  }
126
144
  export interface PostgresPool extends DatabasePool, DatabaseQueryable {}
127
145
  export interface PostgresSession extends DatabaseQueryable {
@@ -156,7 +174,14 @@ export interface PostgresRuntime extends Runtime<PostgresModel, PostgresSession>
156
174
  /** Bind pool/schema once, before createSchema. The caller owns the pool lifetime. */
157
175
  configure(options: PostgresConfiguration): void;
158
176
  describeDatabase(): DatabaseDescription;
159
- /** Must be awaited before operations execute. Does not synchronize incompatible schemas. */
177
+ /** Physical DDL for this instance's description and schema, for review or export; initializeDatabase does not run it. */
178
+ compileDatabaseSchema(): string[];
179
+ /**
180
+ * Must be awaited before operations execute. Does not synchronize incompatible schemas. A request rejected before
181
+ * storage is touched, such as an unknown mode (INVALID_INITIALIZATION_MODE, 400) or, on a validation-only runtime,
182
+ * a non-empty mode other than validate (DATABASE_CREATION_DISABLED, 409), keeps the current readiness; a ready runtime keeps serving while it is initialized again, and any failure of the latest
183
+ * initialization makes it unavailable.
184
+ */
160
185
  initializeDatabase(options?: InitializationOptions): Promise<InitializationResult>;
161
186
  withTransaction<T>(session: PostgresSession | null | undefined, callback: (session: PostgresSession) => Promise<T> | T): Promise<T>;
162
187
  }
@@ -165,7 +190,12 @@ export function postgresPlugin(options?: PostgresConfiguration): SQLPlugin<
165
190
  PostgresConfiguration,
166
191
  DatabaseQueryable,
167
192
  DatabaseDescription,
168
- { rows: Record<string, unknown>[]; rowCount: number | null }
193
+ {
194
+ rows: Record<string, unknown>[];
195
+ rowCount: number | null;
196
+ /** Command tag reported by pg, such as `COMMIT`. Wrappers should pass pg's `command` through, so that a COMMIT that PostgreSQL turned into ROLLBACK is detected. */
197
+ command?: string;
198
+ }
169
199
  >;
170
200
  export function createPostgres(options?: PostgresConfiguration): PostgresRuntime;
171
201
  export const configure: PostgresRuntime['configure'];
package/src/adapter.js DELETED
@@ -1,4 +0,0 @@
1
- import { createSQLAdapter } from '@simtlix/simfinity-sql/internal/adapter';
2
- import { postgresPlugin } from './plugin.js';
3
-
4
- export const createPostgresAdapter = (options) => createSQLAdapter(postgresPlugin(options));
package/src/records.js DELETED
@@ -1,4 +0,0 @@
1
- import { createRecordStore as createSQLRecordStore } from '@simtlix/simfinity-sql/internal/records';
2
- import { postgresPlugin } from './plugin.js';
3
-
4
- export const createRecordStore = (models, database, query) => createSQLRecordStore(models, database, (statement, session) => query(statement.text, statement.values, session), postgresPlugin());
@@ -1,4 +0,0 @@
1
- import { createTransactions as createSQLTransactions } from '@simtlix/simfinity-sql/internal/transactions';
2
- import { postgresPlugin } from './plugin.js';
3
-
4
- export const createTransactions = (getPool, assertReady) => createSQLTransactions(() => ({ pool: getPool() }), assertReady, postgresPlugin());