@simtlix/simfinity-postgres 3.11.0 → 3.13.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 +13 -3
- package/package.json +3 -3
- package/src/codecs.js +6 -1
- package/src/index.js +3 -1
- package/src/plugin.js +1 -1
- package/src/record-statements.js +7 -0
- package/src/schema/describe.js +1 -1
- package/src/schema/initialize.js +34 -3
- package/types/index.d.ts +13 -2
package/README.md
CHANGED
|
@@ -31,9 +31,9 @@ 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.
|
|
34
|
+
Install `@simtlix/simfinity-sql@3.13.0` 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, and five optional settings: `multiRowInserts: false` keeps one `INSERT` per owned row instead of multi-row statements; `explicitNullsOrder: true` keeps the `NULLS` clause on sorts of `id` and required fields, as 3.5.9 did, instead of letting indexes serve them; `batchedCollections: true` reads the generated collection fields of the parents that share their arguments with one statement per 100 parents, in a transaction that turns JIT off for that transaction only, instead of one statement per field as 3.6.0 did and as the default still does, and can return unsorted and tied rows in another order; the opt-in `refreshTriggers: 'perRoot'` refreshes each changed root's owned shape and unique keys once per transaction, while its guard trigger stays deferred, instead of once per changed row; and the opt-in `ownerIndexes: 'minimal'` leaves out the `(__owner_id)` indexes that the primary key or a unique index covers on new owned and guard tables. An unknown `refreshTriggers` or `ownerIndexes` value throws `INVALID_DATABASE_SCHEMA` (400). Read the [upgrade notes](https://simtlix.github.io/simfinity.js/resources/compatibility.html#upgrade-to-3-6-0) before enabling the opt-ins: versions up to 3.5.9 cannot validate databases created with them. The returned runtime supports registration, generated queries and mutations, middleware and scopes, controllers and validators, custom queries and 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.
|
|
36
|
+
Both factories permanently bind one pool and schema, and five optional settings: `multiRowInserts: false` keeps one `INSERT` per owned row instead of multi-row statements; `explicitNullsOrder: true` keeps the `NULLS` clause on sorts of `id` and required fields, as 3.5.9 did, instead of letting indexes serve them; `batchedCollections: true` reads the generated collection fields of the parents that share their arguments with one statement per 100 parents, in a transaction that turns JIT off for that transaction only, instead of one statement per field as 3.6.0 did and as the default still does, and can return unsorted and tied rows in another order; the opt-in `refreshTriggers: 'perRoot'` refreshes each changed root's owned shape and unique keys once per transaction, while its guard trigger stays deferred, instead of once per changed row; and the opt-in `ownerIndexes: 'minimal'` leaves out the `(__owner_id)` indexes that the primary key or a unique index covers on new owned and guard tables. An unknown `refreshTriggers` or `ownerIndexes` value throws `INVALID_DATABASE_SCHEMA` (400). Read the [upgrade notes](https://simtlix.github.io/simfinity.js/resources/compatibility.html#upgrade-to-3-6-0) before enabling the opt-ins: versions up to 3.5.9 cannot validate databases created with them. The returned runtime supports registration, generated queries and mutations, middleware and scopes, controllers and validators, custom queries and mutations, named views, 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
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. `create` mode reads the catalog without table locks and locks nothing when nothing is missing. When it creates objects, writers of the tables that it changes, of existing tables that a new foreign key references, or of roots whose keys it rebuilds wait until it commits, so use `mode: 'validate'` to check a live instance. `simfinity.compileDatabaseSchema()` returns the instance's DDL for review or export.
|
|
39
39
|
|
|
@@ -62,7 +62,7 @@ The module and every `createPostgres` instance expose the shared `auth`, `valida
|
|
|
62
62
|
MCP integration is an independent opt-in and does not add MCP dependencies to PostgreSQL applications:
|
|
63
63
|
|
|
64
64
|
```sh
|
|
65
|
-
npm install @simtlix/simfinity-mcp@3.
|
|
65
|
+
npm install @simtlix/simfinity-mcp@3.13.0 @modelcontextprotocol/sdk@^1.31.0
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
```javascript
|
|
@@ -93,6 +93,16 @@ Scalar-list leaves inside nested embedded lists support filters, sorts, ragged g
|
|
|
93
93
|
|
|
94
94
|
The following mappings remain unsupported and fail explicitly: implicit many-to-many reciprocal lists, non-embedded collections inside embedded objects, embedded cycles, nested list wrappers, whole embedded-object uniqueness, conflicting relationship metadata, enum values that collide after text conversion, and stored custom scalars without a recognized base scalar. Query paths cannot sort or group whole embedded objects. Arbitrary MongoDB pipelines and Mongoose-native methods are outside the PostgreSQL contract.
|
|
95
95
|
|
|
96
|
+
## Optimistic concurrency
|
|
97
|
+
|
|
98
|
+
Declare `extensions: { concurrency: { mode: 'optimistic' } }` on an entity before building its schema to receive the managed `version` field. PostgreSQL stores it as `bigint NOT NULL DEFAULT 1`; migrating an existing table requires an explicit migration, such as `ALTER TABLE "library"."Book" ADD COLUMN "version" bigint NOT NULL DEFAULT 1;`. Updates atomically match the ID and expected version and advance it, including version-only updates. Those updates fire the touch and refresh triggers of maintained roots. Stale writes fail with `VERSION_CONFLICT` (409); an update at `MAX_RECORD_VERSION` fails with `VERSION_EXHAUSTED` (409), while a delete at that version remains valid. Valid bigint values from `pg`, including a custom BigInt parser, are decoded to numbers; unsafe stored values remain invalid rather than being rounded into valid versions. `RecordVersion` and `MAX_RECORD_VERSION` are shared core exports.
|
|
99
|
+
|
|
100
|
+
Initialization in both `create` and `validate` modes rejects a missing or differently defined version column with `SCHEMA_MISMATCH`, explaining the required definition and, when absent, the qualified `ALTER TABLE ... ADD COLUMN` statement. An instance without the concurrency declaration also cannot initialize while its table retains the bigint version column: deploy the declaration on that instance, or stop every protected instance before dropping the column for a rollback. Run the adding migration immediately before the first protected instance starts; instances already running without the declaration can still write without advancing versions. Initialization never migrates the column automatically.
|
|
101
|
+
|
|
102
|
+
Protected native handles initialize `model.create(data)` at version 1. Use `model.update(id, { ...changes, version: record.version }, { session })` and `model.delete(id, { session, version: record.version })`. The update captures the version once before transaction retries without mutating your changes. Missing versions fail with `REQUIRED_VALUE` (400) and a message explaining the handle protocol. For a derived value that a hook maintains in the same transaction, `model.update(id, changes, { session, version: false })` writes without checking or advancing the version and ignores `changes.version`; delete also accepts `version: false`. Handles still bypass validators, hooks, scopes and authorization. Unprotected handles keep their existing behavior and ignore the option. Native SQL writers must follow the same conditional-write protocol to participate; writes that bypass it are not detected.
|
|
103
|
+
|
|
104
|
+
The plugin declares `optimisticConcurrency`. Copies overriding `compileRecord` must honor `operation.version` or delegate versioned updates and deletes to the original compiler. Versioned deletes use `RETURNING id`, including after the SQL runtime's locked read. Record-result checks report `DATABASE_ERROR` for inconsistent update versions or a missing delete result; they are defensive diagnostics and cannot prove that arbitrary overrides preserved the atomic condition.
|
|
105
|
+
|
|
96
106
|
## Low-level schema API
|
|
97
107
|
|
|
98
108
|
The foundation API remains available independently of a runtime:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simtlix/simfinity-postgres",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
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.
|
|
31
|
-
"@simtlix/simfinity-sql": "3.
|
|
30
|
+
"@simtlix/simfinity-core": "3.13.0",
|
|
31
|
+
"@simtlix/simfinity-sql": "3.13.0",
|
|
32
32
|
"pg": "^8.16.3"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
package/src/codecs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getNamedType, isEnumType } from 'graphql';
|
|
|
2
2
|
import { SimfinityError } from '@simtlix/simfinity-core';
|
|
3
3
|
import { isCaseFoldUnsupported } from './query/case-fold.js';
|
|
4
4
|
|
|
5
|
-
export const storageTypes = { ID: 'uuid', String: 'text', Enum: 'text', Int: 'integer', Float: 'double precision', Boolean: 'boolean', DateTime: 'timestamp with time zone' };
|
|
5
|
+
export const storageTypes = { ID: 'uuid', Version: 'bigint', String: 'text', Enum: 'text', Int: 'integer', Float: 'double precision', Boolean: 'boolean', DateTime: 'timestamp with time zone' };
|
|
6
6
|
export const invalidValue = (message) => { throw new SimfinityError(message, 'INVALID_FILTER_VALUE', 400); };
|
|
7
7
|
export const castId = (value) => {
|
|
8
8
|
const id = typeof value === 'object' && value ? value._id || value.id : value;
|
|
@@ -23,6 +23,7 @@ export const encodeScalar = (field, value) => {
|
|
|
23
23
|
}
|
|
24
24
|
case 'String': if (typeof value !== 'string') invalidValue(`Expected a string for ${field.name}`); break;
|
|
25
25
|
case 'Boolean': if (typeof value !== 'boolean') invalidValue(`Expected a boolean for ${field.name}`); break;
|
|
26
|
+
case 'Version': if (!Number.isSafeInteger(value) || value < 1) invalidValue(`Expected a safe positive integer for ${field.name}`); break;
|
|
26
27
|
case 'Int': if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) invalidValue(`Expected a 32-bit integer for ${field.name}`); break;
|
|
27
28
|
case 'Float': if (typeof value !== 'number' || !Number.isFinite(value)) invalidValue(`Expected a finite number for ${field.name}`); break;
|
|
28
29
|
case 'DateTime': {
|
|
@@ -37,6 +38,10 @@ export const encodeScalar = (field, value) => {
|
|
|
37
38
|
|
|
38
39
|
export const decodeScalar = (field, value, gqlField) => {
|
|
39
40
|
if (value == null) return value;
|
|
41
|
+
if (field.scalar === 'Version' && ['string', 'number', 'bigint'].includes(typeof value)) {
|
|
42
|
+
const version = Number(value);
|
|
43
|
+
return Number.isSafeInteger(version) && version >= 1 ? version : value;
|
|
44
|
+
}
|
|
40
45
|
if (field.scalar === 'Enum' && gqlField) {
|
|
41
46
|
const type = getNamedType(gqlField.type);
|
|
42
47
|
if (isEnumType(type)) return type.getValues().find((item) => String(item.value) === value)?.value ?? value;
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,9 @@ export {
|
|
|
11
11
|
buildErrorFormatter,
|
|
12
12
|
createValidatedScalar,
|
|
13
13
|
InternalServerError,
|
|
14
|
+
MAX_RECORD_VERSION,
|
|
14
15
|
plugins,
|
|
16
|
+
RecordVersion,
|
|
15
17
|
scalars,
|
|
16
18
|
SimfinityError,
|
|
17
19
|
validators,
|
|
@@ -20,7 +22,7 @@ export {
|
|
|
20
22
|
/** Each instance permanently binds one pool/schema; getModel exposes PostgreSQL native helpers. */
|
|
21
23
|
export const createPostgres = (options) => createSQL({ plugin: postgresPlugin(options) });
|
|
22
24
|
const defaultInstance = createPostgres();
|
|
23
|
-
export const { configureQueryLimits, configureMutationLimits, configure, connect, addNoEndpointType, createSchema, getModel, getType, getInputType, getRegistrations, use, registerMutation, registerCustomQuery, saveObject, preventCreatingCollection, withTransaction } = defaultInstance;
|
|
25
|
+
export const { configureQueryLimits, configureMutationLimits, configure, connect, addNoEndpointType, createSchema, getModel, getType, getInputType, getRegistrations, use, registerMutation, registerCustomQuery, registerView, saveObject, preventCreatingCollection, withTransaction } = defaultInstance;
|
|
24
26
|
|
|
25
27
|
/** Preserve the foundation low-level API while allowing initialization of the configured default instance. */
|
|
26
28
|
export const initializeDatabase = (poolOrOptions, description, options) => description
|
package/src/plugin.js
CHANGED
|
@@ -17,7 +17,7 @@ export const postgresPlugin = (options) => ({
|
|
|
17
17
|
options: options || null,
|
|
18
18
|
// singleStatementReads: one statement reads one snapshot at any isolation level, so a read without owned
|
|
19
19
|
// tables needs no repeatable-read transaction.
|
|
20
|
-
capabilities: ['transactions', 'foreignKeys', 'deferredForeignKeys', 'embeddedValues', 'ownedRecords', 'scalarLists', 'uniqueValues', 'nullableUnique', 'singleStatementReads'],
|
|
20
|
+
capabilities: ['transactions', 'optimisticConcurrency', 'foreignKeys', 'deferredForeignKeys', 'embeddedValues', 'ownedRecords', 'scalarLists', 'uniqueValues', 'nullableUnique', 'singleStatementReads'],
|
|
21
21
|
// Every filter operator compileQuery and compileChildrenQuery compile, listed one by one so that a later
|
|
22
22
|
// QLOperator value is rejected until the compiler supports it, and emptiness through references and collections
|
|
23
23
|
// (#108 part 2b): the scoped joins and frames of client constructs, and stored semantics for the others. The
|
package/src/record-statements.js
CHANGED
|
@@ -7,6 +7,7 @@ export const compileRecord = (database, operation) => {
|
|
|
7
7
|
const target = qualified(database.schema, storage.name);
|
|
8
8
|
if (kind === 'selectById') return { text: `SELECT * FROM ${target} WHERE id = $1::uuid${operation.lock ? ' FOR UPDATE' : ''}`, values: [id] };
|
|
9
9
|
if (kind === 'selectOwned') return { text: `SELECT * FROM ${target} WHERE "__owner_id" = ANY ($1::uuid[])${operation.ordered ? ' ORDER BY "__position"' : ''}`, values: [operation.ids] };
|
|
10
|
+
if (kind === 'deleteById' && operation.version) return { text: `DELETE FROM ${target} WHERE id = $1::uuid AND ${q(operation.version.column)} = $2::bigint RETURNING id`, values: [id, operation.version.expected] };
|
|
10
11
|
if (kind === 'deleteById') return { text: `DELETE FROM ${target} WHERE id = $1::uuid`, values: [id] };
|
|
11
12
|
if (kind === 'deleteOwned') return { text: `DELETE FROM ${target} WHERE "__owner_id" = $1::uuid`, values: [operation.ownerId] };
|
|
12
13
|
if (kind === 'insert') {
|
|
@@ -16,6 +17,12 @@ export const compileRecord = (database, operation) => {
|
|
|
16
17
|
}
|
|
17
18
|
if (kind === 'update') {
|
|
18
19
|
const entries = Object.entries(data);
|
|
20
|
+
if (operation.version) {
|
|
21
|
+
const column = q(operation.version.column);
|
|
22
|
+
const assignments = entries.map(([key], index) => `${q(key)} = $${index + 2}::${storage.columns.find((column) => column.name === key).type}`);
|
|
23
|
+
assignments.push(`${column} = ${column} + 1`);
|
|
24
|
+
return { text: `UPDATE ${target} SET ${assignments.join(', ')} WHERE id = $1::uuid AND ${column} = $${entries.length + 2}::bigint RETURNING *`, values: [id, ...entries.map(([, value]) => value), operation.version.expected] };
|
|
25
|
+
}
|
|
19
26
|
return { text: `UPDATE ${target} SET ${entries.map(([key], index) => `${q(key)} = $${index + 2}::${storage.columns.find((column) => column.name === key).type}`).join(', ')} WHERE id = $1::uuid RETURNING *`, values: [id, ...entries.map(([, value]) => value)] };
|
|
20
27
|
}
|
|
21
28
|
throw new SimfinityError(`Unknown record operation ${kind}`, 'INVALID_RECORD_OPERATION', 400);
|
package/src/schema/describe.js
CHANGED
|
@@ -4,7 +4,7 @@ import { queryFunctions } from '../query/functions.js';
|
|
|
4
4
|
import { constraintBuilder, withLayouts } from './constraints.js';
|
|
5
5
|
import { identifier, literal, generatedName, invalid } from './sql.js';
|
|
6
6
|
|
|
7
|
-
const scalarTypes = { ID: 'uuid', String: 'text', Enum: 'text', Int: 'integer', Float: 'double precision', Boolean: 'boolean', DateTime: 'timestamp with time zone', Embedded: 'jsonb' };
|
|
7
|
+
const scalarTypes = { ID: 'uuid', Version: 'bigint', String: 'text', Enum: 'text', Int: 'integer', Float: 'double precision', Boolean: 'boolean', DateTime: 'timestamp with time zone', Embedded: 'jsonb' };
|
|
8
8
|
const physicalType = (column) => `${scalarTypes[column.scalar]}${column.list ? '[]' : ''}`;
|
|
9
9
|
const physicalDefault = (value) => value.kind === 'identity' ? 'gen_random_uuid()' : typeof value.value === 'string' ? `${literal(value.value)}::text` : String(value.value);
|
|
10
10
|
// The first value is the default, which keeps the 3.5.9 description.
|
package/src/schema/initialize.js
CHANGED
|
@@ -560,14 +560,45 @@ const virtualLeftovers = (description, catalog, virtualFields) => {
|
|
|
560
560
|
return `${series(fields)} ${many ? 'are' : 'is'} virtual (extensions.virtual), so ${many ? 'they are' : 'it is'} never stored, but this database still holds ${many ? 'their' : 'its'} `
|
|
561
561
|
+ `earlier storage. Once nothing reads that storage, migrate it: ${statements.join(' ')} See the upgrade notes at ${UPGRADE_NOTES}`;
|
|
562
562
|
};
|
|
563
|
-
//
|
|
563
|
+
// Explain version-column drift from catalog contents, including when equal column counts hide a missing
|
|
564
|
+
// or leftover version in the original mismatch. Private owned and auxiliary tables have no record version.
|
|
565
|
+
const versionColumns = (description, catalog) => {
|
|
566
|
+
const explanations = [];
|
|
567
|
+
for (const table of description.tables) {
|
|
568
|
+
if (table.ownership || table.auxiliary) continue;
|
|
569
|
+
const actual = catalog.tables.get(table.name);
|
|
570
|
+
if (!actual) continue;
|
|
571
|
+
const column = table.columns.find((item) => item.name === 'version');
|
|
572
|
+
const stored = actual.columns.find((item) => item.name === 'version');
|
|
573
|
+
const target = qualified(description.schema, table.name);
|
|
574
|
+
if (column?.type === 'bigint') {
|
|
575
|
+
const declaration = `${table.name} declares optimistic concurrency (extensions.concurrency), so its table needs the version column`;
|
|
576
|
+
if (!stored) explanations.push(`${declaration}. Migrate it: ALTER TABLE ${target} ADD COLUMN "version" bigint NOT NULL DEFAULT 1;`);
|
|
577
|
+
else if (stored.type !== column.type || stored.nullable !== column.nullable
|
|
578
|
+
|| (column.collation && (stored.collation !== column.collation || stored.collation_schema !== 'pg_catalog'))
|
|
579
|
+
|| normalizeExpression(stored.default) !== normalizeExpression(column.default || null) || stored.attidentity || stored.attgenerated) {
|
|
580
|
+
explanations.push(`${declaration} defined as bigint NOT NULL DEFAULT 1.`);
|
|
581
|
+
}
|
|
582
|
+
} else if (!column && stored?.type === 'bigint') {
|
|
583
|
+
explanations.push(`${table.name} does not declare optimistic concurrency, but its table has the version column. `
|
|
584
|
+
+ 'If other instances of this application declare it, deploy the declaration here: while the column exists, instances without it cannot start. '
|
|
585
|
+
+ `Otherwise, once no instance declares it: ALTER TABLE ${target} DROP COLUMN "version";`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return explanations.length ? explanations.join(' ') : null;
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// A SCHEMA_MISMATCH also explains optimistic-concurrency columns and earlier storage of virtual fields.
|
|
564
592
|
const explainMismatch = (error, description, catalog, layouts, virtualFields) => {
|
|
565
|
-
if (!
|
|
593
|
+
if (!catalog || !(error instanceof SimfinityError) || error.extensions?.code !== 'SCHEMA_MISMATCH') return error;
|
|
566
594
|
let resolved = description;
|
|
567
595
|
// Refresh-trigger objects of two layouts in one root are themselves the mismatch; compare with the description.
|
|
568
596
|
try { resolved = resolveLayouts(description, catalog, layouts); } catch { /* keep the description */ }
|
|
569
597
|
try {
|
|
570
|
-
const explanation =
|
|
598
|
+
const explanation = [
|
|
599
|
+
versionColumns(resolved, catalog),
|
|
600
|
+
virtualFields.length ? virtualLeftovers(resolved, catalog, virtualFields) : null,
|
|
601
|
+
].filter(Boolean).join(' ');
|
|
571
602
|
return explanation ? new SimfinityError(`${error.message} ${explanation}`, 'SCHEMA_MISMATCH', 409) : error;
|
|
572
603
|
} catch {
|
|
573
604
|
// An explanation never replaces the mismatch it explains.
|
package/types/index.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export {
|
|
|
7
7
|
buildErrorFormatter,
|
|
8
8
|
createValidatedScalar,
|
|
9
9
|
InternalServerError,
|
|
10
|
+
MAX_RECORD_VERSION,
|
|
10
11
|
plugins,
|
|
12
|
+
RecordVersion,
|
|
11
13
|
scalars,
|
|
12
14
|
SimfinityError,
|
|
13
15
|
validators,
|
|
@@ -17,11 +19,14 @@ export type {
|
|
|
17
19
|
AuthRule,
|
|
18
20
|
AuthRuleFunction,
|
|
19
21
|
AuthSchemaPlugin,
|
|
22
|
+
ConcurrencyDeclaration,
|
|
20
23
|
CreateSchemaOptions,
|
|
21
24
|
EntityController,
|
|
22
25
|
EnvelopSchemaPlugin,
|
|
23
26
|
FieldValidations,
|
|
24
27
|
FieldValidator,
|
|
28
|
+
FilterCondition,
|
|
29
|
+
FilterGroup,
|
|
25
30
|
ItemValidators,
|
|
26
31
|
MiddlewareContext,
|
|
27
32
|
MutationLimitsOptions,
|
|
@@ -33,6 +38,9 @@ export type {
|
|
|
33
38
|
StateMachine,
|
|
34
39
|
TypePermissions,
|
|
35
40
|
TypeScopes,
|
|
41
|
+
ViewOptions,
|
|
42
|
+
ViewParam,
|
|
43
|
+
ViewParamType,
|
|
36
44
|
VirtualFieldDeclaration,
|
|
37
45
|
VirtualFieldOptions,
|
|
38
46
|
} from '@simtlix/simfinity-core';
|
|
@@ -169,8 +177,10 @@ export interface PostgresModel {
|
|
|
169
177
|
find(args?: Record<string, any>, options?: { session?: PostgresSession }): Promise<PostgresRecord[]>;
|
|
170
178
|
/** Native helpers bypass GraphQL validators/controllers; references use storage column names. */
|
|
171
179
|
create(data: Record<string, any>, options?: { session?: PostgresSession }): Promise<PostgresRecord>;
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
/** Protected entities require changes.version; version:false ignores that field and does not advance it. */
|
|
181
|
+
update(id: string, changes: Record<string, any>, options?: { session?: PostgresSession; version?: false }): Promise<PostgresRecord | null>;
|
|
182
|
+
/** Protected entities require the version read, or false to bypass the version precondition. */
|
|
183
|
+
delete(id: string, options?: { session?: PostgresSession; version?: number | false }): Promise<PostgresRecord | null>;
|
|
174
184
|
}
|
|
175
185
|
export interface PostgresConfiguration {
|
|
176
186
|
pool: PostgresPool;
|
|
@@ -292,6 +302,7 @@ export const getRegistrations: PostgresRuntime['getRegistrations'];
|
|
|
292
302
|
export const use: PostgresRuntime['use'];
|
|
293
303
|
export const registerMutation: PostgresRuntime['registerMutation'];
|
|
294
304
|
export const registerCustomQuery: PostgresRuntime['registerCustomQuery'];
|
|
305
|
+
export const registerView: PostgresRuntime['registerView'];
|
|
295
306
|
export const saveObject: PostgresRuntime['saveObject'];
|
|
296
307
|
export const preventCreatingCollection: PostgresRuntime['preventCreatingCollection'];
|
|
297
308
|
export const withTransaction: PostgresRuntime['withTransaction'];
|