@simtlix/simfinity-core 3.18.0 → 3.19.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.
Files changed (2) hide show
  1. package/README.md +78 -63
  2. package/package.json +2 -1
package/README.md CHANGED
@@ -1,90 +1,105 @@
1
- # @simtlix/simfinity-core
2
-
3
- Driver-free GraphQL runtime, model metadata, and shared GraphQL helpers for Simfinity. It requires GraphQL 16 and Node.js 18.18 or later, and does not import Mongoose, MongoDB, PostgreSQL, or MCP packages.
4
-
5
- All Simfinity packages are released together. Install from npm and keep their versions aligned. Source: [simtlix/simfinity.js](https://github.com/simtlix/simfinity.js).
6
-
7
- For relational storage, `@simtlix/simfinity-sql` builds on core and delegates physical database behavior to a plugin. PostgreSQL is the first supported plugin; its `createPostgres` facade remains available. See the [SQL plugin guide](https://simtlix.github.io/simfinity.js/guide/sql-plugins.html).
8
-
9
- ## Runtime
1
+ [![Simfinity.js — Define once. Build beyond. GraphQL, MongoDB, PostgreSQL, and optional MCP.](https://raw.githubusercontent.com/simtlix/simfinity.js/master/.github/assets/readme-cover.png)](https://simtlix.github.io/simfinity.js/)
10
2
 
11
- `createRuntime(adapter)` creates a Simfinity runtime. Each instance owns its registrations, generated input types, middleware, scopes, hooks, custom queries and mutations, views, and state machines; the GraphQL type objects it builds resolvers for are bound to it, and query and mutation limits are process-wide. It exposes:
12
-
13
- - `connect`, `addNoEndpointType`, and `createSchema` for registration and schema creation;
14
- - `getModel`, `getType`, `getInputType`, and `getRegistrations` for runtime inspection;
15
- - `use`, `registerMutation`, and `saveObject` for middleware and write orchestration, and `updateObject`, `deleteObject` and `transitionObject`, which run the lifecycle of the generated update, delete and state action mutations from code;
16
- - `registerCustomQuery` for reads whose results are not stored records, such as previews, other sources or permission evaluations;
17
- - `registerView` for a named list read of one registered type, with params and a fixed predicate, run through the type's scope;
18
- - `preventCreatingCollection` to pass validation-only storage setup to an adapter;
19
- - `configureQueryLimits` and `configureMutationLimits` for the process-wide page-size, compiled-frame, frame-nesting, nested-operation and transaction-operation limits (`maxTransactionOperations`).
20
-
21
- The adapter supplies model creation, identifiers, transactions, record reads and writes, queries, counts, aggregations, and inverse collection reads. `DatabaseAdapter`, `Runtime`, controller, middleware, registration, and state-machine interfaces are exported in the TypeScript declarations. A runtime binds one adapter; create another runtime to use another database or configuration. The GraphQL types that `prepare()` and `createModel()` receive still contain virtual fields, which must never be stored: an adapter that builds storage from a type's fields skips each field for which `isVirtualField(field)`, exported by this package with `virtualFieldOptions(field)`, returns true, on entities and embedded types alike, or builds it from `describeModels()`, which leaves them out. Generated writes, filters, sorts and aggregations never name a virtual field, but a controller hook can still assign one. An adapter can also define `getByIds(model, ids, { context })`, which returns the records it finds in `getById` shape and any order; the runtime then batches generated single-reference reads per request and type, matching records by `castId`, splitting batches at `maxPageSize`, and reading an ID with `getById` when a batch read fails or when `castId` would normalize or reject it. `castId` should throw a `NOT_VALID_ID` (400) `SimfinityError` for a malformed value and must not create a new identifier. An adapter can define `readByIdIsInert(model)`, returning `true` when a `getById` of that model has no side effects and `update()` and `delete()` reject the same malformed IDs with `NOT_VALID_ID` (400) and return null for a missing record; the runtime then skips the reads before deletes without an `onDelete` hook and before updates that merge no embedded value and have neither `onUpdating` nor nested collection items. The SQL adapter always reports it, and the MongoDB adapter does for models without `find`, `findOne`, `findOneAndUpdate` or `findOneAndDelete` middleware and without application `_id` setters or overrides of the statics those reads and writes call. When `getById` rejects a stored reference value that way, including one that `castId` rejected first, the generated reference field fails with an `InternalServerError` (500), since the client did not send the value. `update()` resolves to the updated record as stored, or null; nested collection children of that record are linked to and checked against its `_id`, else `id`, else the requested ID. An adapter that hydrates records can define `readEmbeddedValue(value)`: every nullable, singular embedded object field without a resolver then passes its value through it, so an explicitly stored `null` reads as `null`. The field is read as graphql's default resolver reads it, so a method on the parent is called and a promise it returns is awaited before the hook receives the value. Simfinity then reads as `null` an object that holds no data when a required member would read as `null`, not counting `id`, `readOnly` and virtual members, interface and union members, members with an application resolver and referenced collections; runtimes that share the type read the same. It reads the stored members, never calling a getter, and counts a stored `_id` as `id`; it reads nothing from objects whose type has no member that counts, and an error while reading returns the object unchanged. An adapter whose hydrated records render members through getters can also define `rawEmbeddedValue(value)`, which returns a record's stored data as a plain object, or the record unchanged when that data does not show what it renders; a value that is not a plain object counts as data. The MongoDB adapter defines both.
22
-
23
- An adapter can also define `findChildrenBatch(model, type, connectionField, parentIds, args, { context })`, the batch form of `findChildren` for generated collection fields, and a synchronous `batchesChildren(model, type, connectionField)` predicate. The runtime calls both as adapter methods and checks them on every read. When the method is missing or the predicate returns `false`, each field calls `findChildren` at once with the arguments object it built, as before. Otherwise, within one request (a context object), the runtime groups the collection reads of one related type and connection storage field whose effective arguments (after middleware, the scope and joined scopes) hold the same plain data and are requested in the same tick, that is before the promise jobs queued so far have run. It calls the method with 2 to 100 distinct parent IDs, each a raw ID that `castId` accepts without changing its string form, and with a copy of the arguments that the method may change; a group with one distinct parent calls `findChildren`. Return one array per parent ID, in order, each holding exactly the records, order and page that `findChildren` returns for that parent; records should show their parent under the connection field, or not show one. Fields of one parent share its records, each with its own array. After a rejection, or a result of another shape or with records of another parent, each field reads alone with `findChildren`; failures other than a `SimfinityError`, and results of another shape, are logged once per type. Pairing rule: the batch must reproduce `this.findChildren`, so `batchesChildren` must return `false` when `this.findChildren` is not the adapter's own function, as with a spread copy or an `Object.assign` override that changes how children are read. The bundled adapters follow this rule: both define the two members only with `batchedCollections: true`: the SQL adapter when the schema is prepared and its plugin's `compileChildrenQuery` is paired with its `compileQuery`, and the MongoDB adapter at creation. To opt out on one adapter instance, set `adapter.findChildrenBatch = undefined` (deleting it does nothing for a class whose method is on the prototype), or make `batchesChildren` return `false`. A copy that overrides `getById` still inherits the base `getByIds`, as before.
24
-
25
- An adapter declares the filter operators it compiles as `queryFeatures: { operators }`, a list of `QLOperator` values that includes `EQ`, which a condition without an operator means; names this version does not know are ignored, until a version that knows them, so a declaration that listed `CONTAINS_ALL` or `IIN` before they existed starts receiving them on upgrade: list only the operators the adapter compiles. `createRuntime()` reads it once and throws `INVALID_QUERY_FEATURES` (500) unless it is `undefined`, `null` or an object whose `operators` is an array of strings and whose optional `referenceEmptiness` is a boolean, `undefined` or `null`. Without a declaration the adapter supports the operators of 3.7.0, `EQ`, `NE`, `LT`, `LTE`, `GT`, `GTE`, `BTW`, `IN`, `NIN` and `LIKE`, so it never receives a text operator such as `ILIKE`, an emptiness operator, `IS_EMPTY` or `IS_NOT_EMPTY`, or a list operator, `CONTAINS_ALL` or `IIN`, that it would drop or misread. A list, count, aggregate or collection read, or a by-ID read through a `get_by_id` scope, that uses another `QLOperator` value fails with `UNSUPPORTED_QUERY` (400): the runtime checks the client's arguments before middleware runs and the final arguments, with what middleware, scopes and joined scopes added, before it calls the adapter. Unknown operators on actual filter predicates fail with `INVALID_FILTER_OPERATOR` (400) before the adapter is called. An adapter that receives the text operators must apply their rules: literal values, a `String` field or list, and `INVALID_FILTER_VALUE` (400) for a value that is not a string or contains U+0000. `createQueryPlan(models, entity, args, { operators })` validates them and rejects operators outside `operators` with `UNSUPPORTED_QUERY`. An adapter that declares `IIN` receives an array of strings without U+0000, possibly empty, on a `String` field or list, and must match where `IEQ` would match one of them, under the same case-folding rules, and nothing for `[]`. An adapter that declares `CONTAINS_ALL` receives a non-empty array of non-null scalars, validated but not cast, on a path that ends at a scalar list or at a scalar read through embedded lists and crosses no collection and no reference after an embedded list; it must cast each element as for `IN` and match the records whose values at the path, those of every item of embedded lists included, hold every element, and stay two-valued under negation, where a missing or `null` list holds none. See [filters on lists](https://simtlix.github.io/simfinity.js/guide/queries.html#filters-on-lists). An adapter that declares `IS_EMPTY` and `IS_NOT_EMPTY` must compile their semantics on the queried record's own fields, where `IS_NOT_EMPTY` is the exact complement of `IS_EMPTY`: a `String` is empty when missing, `null` or `""`, any other scalar or enum when missing or `null`, a list when missing, `null` or `[]`, and an embedded object when missing, `null` or every stored member is empty. The runtime rejects a value other than `null` and a path inside a list before it calls the adapter, and also emptiness through references and collections unless the adapter declares `referenceEmptiness: true`, so the adapter receives conditions whose `value` may be absent and whose term or group `path` may be omitted, `null` or `""` to name the field itself; `createQueryPlan` plans them as `{ kind: 'predicate', path, operator, value: null }`, whose `path` may end at a scalar list, an embedded list or an embedded object. See [empty values](https://simtlix.github.io/simfinity.js/guide/queries.html#empty-values). An adapter that declares `referenceEmptiness: true` also receives emptiness conditions whose path crosses single references and ends at a reference or a collection, and embedded objects with reference and collection members. A client's such term or group condition carries the key `__scope: { id, scopes }`, where `scopes` holds, for each reference it crosses and each reference, collection or member it ends at or reads, keyed by the dotted path relative to the containing frame, the arguments that the target type's `find` scope produced, or `null` when it added nothing. The adapter must join a crossed reference only where that scope admits the target, and count at an end or member only the records that the scope admits, so that a record the caller may not see reads as a missing one, also on a later hop with a `null` scope; a condition without `__scope` reads stored data, as server conditions and unmarked clients on unscoped chains do. `createQueryPlan(models, entity, args, { operators, referenceEmptiness: true })` lifts each filled `__scope` into the predicate's `scopes`, planning each scope on its target type, and throws `DETACHED_CLIENT_FILTER` (500) for a `__scope` that lacks a path; its `referenceEmptiness` option defaults to `true` when `operators` is omitted and to `false` otherwise. See [references and collections](https://simtlix.github.io/simfinity.js/guide/queries.html#references-and-collections).
3
+ # @simtlix/simfinity-core
26
4
 
27
- An adapter opts into collection frames with `queryFeatures.quantifiers: true` and `referenceEmptiness: true`; omission/null means false and invalid combinations give `INVALID_QUERY_FEATURES`. Every generated schema exposes `QLQuantifier`, `QLQuantifiedGroup`, group `ANY`/`ALL`/`NONE` lists and relation `quantifier`, with request-time capability gates. Collection shorthand becomes correlated `ANY`, with one child per group/prefix and no duplicated roots; explicit `ALL`/`NONE` use the same element-relative frame. Client scope keys are relative to their containing frame and apply `find` visibility before quantification; server conditions range over stored elements. `get_by_id` scopes are trusted, so `NE`, `NIN` and `EQ null` collection terms no longer admit childless records.
5
+ [![npm version](https://img.shields.io/npm/v/@simtlix/simfinity-core.svg)](https://www.npmjs.com/package/@simtlix/simfinity-core)
6
+ [![Node.js](https://img.shields.io/node/v/@simtlix/simfinity-core.svg)](https://nodejs.org/)
7
+ [![License](https://img.shields.io/npm/l/@simtlix/simfinity-core.svg)](https://github.com/simtlix/simfinity.js/blob/master/LICENSE)
8
+ [![Documentation](https://img.shields.io/badge/docs-simtlix.github.io-2563eb.svg)](https://simtlix.github.io/simfinity.js/)
28
9
 
29
- `createQueryPlan()` enables quantifiers by default without `operators`; with `operators`, declare capabilities explicitly. Its `QueryPredicate` includes `quantified`, element `scope`/`where`, scoped-reference `joins` and aggregate `anchor`. Compilers must honour these members and fail closed on incomplete metadata. `configureQueryLimits` adds `maxFrameNesting` (default 3), while `maxReferenceFrames` (10) counts compiled client units, even when unmarked, after all client 400 checks. Named views preserve separate client-argument and `where` budgets of that size. Every adapter rejects list sorts through collections, including count mode. See the [plugin contract](https://simtlix.github.io/simfinity.js/guide/sql-plugins.html#query-features) and [upgrade notes](https://simtlix.github.io/simfinity.js/resources/compatibility.html#collection-frames).
10
+ **The driver-free GraphQL runtime of [Simfinity.js](https://simtlix.github.io/simfinity.js/).** It holds model metadata, schema generation, the query and mutation engine, and the shared `auth`, `validators`, `scalars` and `plugins` helpers. It imports no Mongoose, MongoDB, PostgreSQL or MCP package.
30
11
 
31
- Entity types, those registered with `connect()` and the targets of non-embedded relations, resolve `id` to `_id`, falling back to `id`, whatever the `createSchema()` allowlists include; types used only as embedded values keep their declared `id`. Embedded copies of an entity type resolve the same way, so they return their declared `id` unless the adapter stores an `_id` for them, as MongoDB does for embedded list items. The resolver is set on the type object, so it applies in every runtime that shares the type. `createSchema()` marks these registrations with `storedIdentity: true` before calling `adapter.prepare()`.
12
+ Applications rarely install it by hand: the database packages depend on it. Start with [`@simtlix/simfinity-js`](https://www.npmjs.com/package/@simtlix/simfinity-js) for MongoDB or [`@simtlix/simfinity-postgres`](https://www.npmjs.com/package/@simtlix/simfinity-postgres) for PostgreSQL. Install core directly when you import one of its subpaths or write a database adapter.
32
13
 
33
- When a runtime first creates a schema, it binds each GraphQL object type whose non-embedded relation fields receive its generated resolvers. It also reserves each reachable object type that still has a non-embedded relation field without a resolver, such as an unregistered custom mutation result. Registering a bound or reserved type in another runtime, or building another runtime's schema that reaches it through fields, interfaces, union members or custom mutation results, throws `TYPE_BOUND_TO_OTHER_RUNTIME` (409). `createSchema()` also rejects a type with a relation field copied, by `toConfig()` or a field spread, after another runtime generated that field's resolver. Create type objects once per runtime, for example with a factory function. Types without non-embedded relation fields, and types whose relation fields all have application resolvers, can be shared, and another runtime can use a `toConfig()` copy made before the first runtime built its schema. A shared custom mutation result type with an unresolved relation field is rejected.
14
+ **[Documentation](https://simtlix.github.io/simfinity.js/)** ·
15
+ **[API reference](https://simtlix.github.io/simfinity.js/reference/api.html)** ·
16
+ **[Field extensions](https://simtlix.github.io/simfinity.js/reference/extensions.html)** ·
17
+ **[Errors](https://simtlix.github.io/simfinity.js/reference/errors.html)** ·
18
+ **[Releases](https://github.com/simtlix/simfinity.js/releases)**
34
19
 
35
- Registration and `createSchema()` validate `extensions.scope` (`INVALID_SCOPE`) and reject list or embedded relations to unregistered types (`UNREGISTERED_RELATION_TARGET`); `use()` rejects non-functions (`INVALID_MIDDLEWARE`). Before `adapter.prepare()`, `createSchema()` rejects a writable non-embedded list relation without a non-empty child `connectionField` whose resolver it would generate (`INVALID_MODEL`, 400). A collection without `connectionField` that has an application resolver, or is `readOnly`, only logs a `Configuration issue` warning, unless it is both, and nested writes through it fail with `INVALID_MODEL` (500) before anything is written; adapters that describe the whole model, such as PostgreSQL and transactional MongoDB, still reject it in `prepare()`. Fields named like generated query arguments (`sort`, `pagination`, `AND`, `OR`, and `aggregation` on aggregate endpoints) also log a warning once per runtime. Virtual fields (`extensions: { virtual: true }` or `{ virtual: { mcp: 'tool' | 'select' | false } }`, with the field's own `resolve`) are never stored, get no generated input and no filter argument, and resolve wherever their type appears; `createSchema()` rejects an invalid declaration, a virtual field without its own resolver or with `relation`, `unique: true`, `validations`, `queryable: true` or `readOnly: false`, and a virtual identity, index, state-machine `state` or `connectionField` field (`INVALID_MODEL`, 400). A client filter, sort, `groupId` or fact path through a virtual field fails with `FORBIDDEN_FILTER_PATH` (403), and one that middleware or a scope adds fails with `INVALID_MODEL` (500). A `readOnly` field with arguments and its own resolver that is not virtual, a reference or a collection logs a `Configuration issue` warning once per runtime. Generated inputs that would have no fields are left out with a `Configuration issue` warning, with the collection operations, embedded fields and mutations that would take them; a non-null embedded field whose embedded type has no writable fields throws `INVALID_MODEL` (400) when generated mutations reach its owner. An application type named `FieldExtensionsType` or `RelationType` makes Simfinity rename its introspection metadata types to `SimfinityFieldExtensionsType` and `SimfinityRelationType` when it is in the process's first Simfinity schema. That schema fixes the names: in a later `createSchema()`, an application type with one of the current names, the original ones or the fallback names after a rename, throws `RESERVED_TYPE_NAME` (500), before `adapter.prepare()` when it is an included endpoint type, a registered mutation's input or output type, an output type they reach or a field argument's type, and when the schema is constructed otherwise, such as for a type inside an input object's fields.
20
+ ## Install
36
21
 
37
- Generated and custom mutations run through `adapter.withTransaction`. Retried attempts receive fresh clones of GraphQL input objects, lists, and dates while enum values keep their identity. Custom queries run without a transaction: their callback receives `(input, context)` and opens one through the engine's API when it needs it. `createSchema()` takes an optional fourth argument, an options object whose key `includedCustomQueries` selects custom queries by name and whose key `transaction: { name?, types }` adds the generated transaction mutation; any other value or key throws `INVALID_SCHEMA_OPTIONS` (500) before `adapter.prepare()`. An invalid `transaction` option raises the same code, after the models are prepared when the field name or a derived type name collides with the built schema, or when no operation is left in it. A schema without mutation fields has no `Mutation` root. An adapter can define `stateValue` when its persisted enum representation differs from GraphQL enum names.
22
+ ```sh
23
+ npm install @simtlix/simfinity-core graphql@^16.11.0
24
+ ```
38
25
 
39
- Importing the runtime installs Simfinity's `__Field.extensions` introspection field once per GraphQL module instance; its `readOnly` flag reads `true` for virtual fields. Multiple runtimes and driver packages can be imported together without cloning schemas or duplicating introspection types.
26
+ | Requirement | Version |
27
+ | --- | --- |
28
+ | Node.js | 18.18 or later, ES modules |
29
+ | `graphql` (peer) | `^16.11.0` |
40
30
 
41
- ## Lifecycle helpers and the transaction mutation
31
+ As a direct dependency, pin core to the exact version of the database package you use, also under pnpm or Yarn PnP.
42
32
 
43
- These APIs are available since 3.16.0. `updateObject(typeName, input, session?, context?)`, `deleteObject(typeName, { id, version? }, session?, context?)` and `transitionObject(typeName, actionName, input, session?, context?)` are `Runtime` methods, so SQL runtimes inherit them. They validate and copy their input (declared fields, internal enum values, record keys as strings), run the update, delete and state action lifecycle in an owned transaction or in the active session supplied, skip GraphQL coercion, field authorization and the root record's global middleware like `saveObject()`, and never commit, retry or abort a supplied session themselves. See the [server-side operations guide](https://simtlix.github.io/simfinity.js/guide/server-side-operations.html#update-delete-and-transition-from-code).
33
+ ## Create a runtime
44
34
 
45
- `createSchema(…, { transaction: { name?, types } })` adds one mutation field that runs a list of the generated mutations of `types` in one database transaction, with `$K` references to earlier results, the authorization checks of each operation's root and the position of a failing operation in `extensions.operationIndex` and `extensions.operation`. An adapter enables it by implementing the optional, synchronous `assertTransactionModels(models)`, which core calls on each invocation before the session opens with the models the transaction may write; it throws when they cannot share one session, and a promise it returns fails with `TRANSACTION_UNSUPPORTED`. The `withTransaction` of such an adapter must hand its body an object session. See the [mutations guide](https://simtlix.github.io/simfinity.js/guide/mutations.html#run-several-mutations-in-one-transaction).
35
+ `createRuntime(adapter)` binds one database adapter. Each runtime owns its registrations, generated input types, middleware, scopes, hooks, custom operations, views and state machines.
46
36
 
47
- Controller `onSaveCommitted`, `onUpdateCommitted`, `onDeleteCommitted` and `onTransitionCommitted` callbacks use the optional `DatabaseAdapter.enqueueAfterCommit(session, run)` capability. It accepts jobs only for an active owned attempt, discards aborted/retried queues, and drains a confirmed queue serially after cleanup is attempted. Core preserves existing transactional hooks and exposes per-runtime `configureAfterCommit({ onError })` for operational reporting. Unsupported adapters reject configured committed callbacks with `UNSUPPORTED_AFTER_COMMIT`. External commit ownership is not inferred. See [callback guarantees and limits](https://simtlix.github.io/simfinity.js/guide/controllers.html#after-commit-callbacks).
37
+ ```javascript
38
+ import { createRuntime } from '@simtlix/simfinity-core';
48
39
 
49
- ## Shared metadata and helpers
40
+ const simfinity = createRuntime(adapter); // for example, createMongoAdapter()
41
+ simfinity.connect(null, BookType, 'book', 'books');
42
+ const schema = simfinity.createSchema();
43
+ ```
50
44
 
51
- `describeModels([{ gqltype, endpoint }])` discovers the persistent entity graph from `GraphQLObjectType` definitions and `extensions.relation`, leaving virtual fields out and describing no type that only virtual fields reach. Types registered with `endpoint: false` are still included when referenced. Reciprocal one-to-many definitions resolve to one child reference; explicit linking entities represent many-to-many relationships.
45
+ | Area | Functions | Reference |
46
+ | --- | --- | --- |
47
+ | Registration and schema | `connect`, `addNoEndpointType`, `createSchema` | [connect](https://simtlix.github.io/simfinity.js/reference/api.html#connect), [createSchema](https://simtlix.github.io/simfinity.js/reference/api.html#createschema) |
48
+ | Inspection | `getModel`, `getType`, `getInputType`, `getRegistrations` | [Core API](https://simtlix.github.io/simfinity.js/reference/api.html) |
49
+ | Middleware and custom writes | `use`, `registerMutation` | [Middleware](https://simtlix.github.io/simfinity.js/guide/middleware.html), [registerMutation](https://simtlix.github.io/simfinity.js/reference/api.html#registermutation) |
50
+ | Custom reads | `registerCustomQuery`, `registerView` | [registerCustomQuery](https://simtlix.github.io/simfinity.js/reference/api.html#registercustomquery), [registerView](https://simtlix.github.io/simfinity.js/reference/api.html#registerview) |
51
+ | Writes from your own code | `saveObject`, `updateObject`, `deleteObject`, `transitionObject` | [Server-side operations](https://simtlix.github.io/simfinity.js/guide/server-side-operations.html) |
52
+ | Limits and setup | `configureQueryLimits`, `configureMutationLimits`, `preventCreatingCollection` | [configureQueryLimits](https://simtlix.github.io/simfinity.js/reference/api.html#configurequerylimits) |
52
53
 
53
- `createQueryPlan` and `resolveModelPath` turn Simfinity filter, sort, pagination, and aggregation inputs into a driver-neutral plan resolved against that metadata.
54
+ ## Shared helpers
54
55
 
55
- `isVirtualField(field)` tells whether a GraphQL field is virtual, and `virtualFieldOptions(field)` returns its normalized options, `{ mcp }`, or `null` for any other field. Adapters that build storage from GraphQL types use them to skip virtual fields.
56
+ The package root also exports what every database package re-exports:
56
57
 
57
- The package also exports the `auth`, `validators`, `scalars`, and `plugins` helper namespaces alongside `createValidatedScalar`, `SimfinityError`, `InternalServerError`, `UnauthenticatedError` and `ForbiddenError` (the same classes as `auth.UnauthenticatedError` and `auth.ForbiddenError`), `buildErrorFormatter`, `configureQueryLimits`, `configureMutationLimits`, `QLOperator`, `QLQuantifier`, `QLSort`, and `QLValue`. `buildErrorFormatter` returns a `GraphQLError` whose `originalError` is the classified Simfinity error. Database packages reuse these exports so helpers, errors, and globally named GraphQL types retain one identity.
58
+ | Export | Purpose | Reference |
59
+ | --- | --- | --- |
60
+ | `auth` | Authorization rules and policy expressions | [Authorization](https://simtlix.github.io/simfinity.js/guide/authorization.html) |
61
+ | `validators`, `createValidatedScalar` | Field validators and validated scalars | [Validation](https://simtlix.github.io/simfinity.js/guide/validation.html), [scalars](https://simtlix.github.io/simfinity.js/reference/scalars.html) |
62
+ | `scalars` | Built-in scalar types | [Scalars](https://simtlix.github.io/simfinity.js/reference/scalars.html) |
63
+ | `plugins` | Server plugins: `createAuthPlugin`, `envelopCountPlugin` and `apolloCountPlugin` | [Plugins](https://simtlix.github.io/simfinity.js/reference/plugins.html) |
64
+ | `SimfinityError` and subclasses, `buildErrorFormatter` | Error identities and formatting | [Errors](https://simtlix.github.io/simfinity.js/reference/errors.html) |
65
+ | `RecordVersion`, `MAX_RECORD_VERSION` | The managed version of optimistic concurrency | [Optimistic concurrency](https://simtlix.github.io/simfinity.js/guide/concurrency.html) |
66
+ | `describeModels`, `createQueryPlan` | Model metadata and query plans for adapter authors | [SQL core & plugins](https://simtlix.github.io/simfinity.js/guide/sql-plugins.html) |
58
67
 
59
- The same helpers are available from the `@simtlix/simfinity-core/auth`, `/auth/errors`, `/auth/expressions`, `/auth/rules`, `/plugins`, `/scalars` and `/validators` subpaths, each with TypeScript declarations whose types match the root namespaces. `moduleResolution` `node16`, `nodenext` and `bundler` read them through the export map, and `node10` through `typesVersions`. The `internal/*` subpaths are not public API and have no declarations.
68
+ Public subpaths: `/auth`, `/auth/errors`, `/auth/expressions`, `/auth/rules`, `/plugins`, `/scalars`, `/validators` and `/introspection`. Paths under `/internal/` are for the Simfinity packages and can change in any release.
60
69
 
61
- ## Optimistic concurrency
70
+ ## Documentation
62
71
 
63
- `extensions: { concurrency: { mode: 'optimistic' } }` opts an entity into the managed `version: RecordVersion!` field. Core exports `RecordVersion` and `MAX_RECORD_VERSION` (`9007199254740991`); import from the application's facade to share their identity. See the [concurrency guide](https://simtlix.github.io/simfinity.js/guide/concurrency.html).
72
+ | I want to… | Read |
73
+ | --- | --- |
74
+ | Learn the model and schema rules | [Schema & models](https://simtlix.github.io/simfinity.js/guide/schema.html), [relationships](https://simtlix.github.io/simfinity.js/guide/relationships.html) |
75
+ | Understand filters, quantifiers and pagination | [Queries & filtering](https://simtlix.github.io/simfinity.js/guide/queries.html) |
76
+ | Understand generated and transaction mutations | [Mutations](https://simtlix.github.io/simfinity.js/guide/mutations.html) |
77
+ | Add business logic | [Controllers & hooks](https://simtlix.github.io/simfinity.js/guide/controllers.html), [state machines](https://simtlix.github.io/simfinity.js/guide/state-machines.html) |
78
+ | Restrict what a request can read | [Query scope](https://simtlix.github.io/simfinity.js/guide/query-scope.html) |
79
+ | Serve client metadata or disable the legacy introspection field | [Client descriptor](https://simtlix.github.io/simfinity.js/reference/client-descriptor.html) |
80
+ | Write a database adapter | [Custom database adapters](https://simtlix.github.io/simfinity.js/reference/api.html#custom-database-adapters) |
81
+ | Write a relational database plugin | [SQL core & plugins](https://simtlix.github.io/simfinity.js/guide/sql-plugins.html) |
82
+ | Upgrade between versions | [Compatibility & releases](https://simtlix.github.io/simfinity.js/resources/compatibility.html) |
64
83
 
65
- An adapter advertises `writeFeatures: { optimisticConcurrency: true }`; `createRuntime()` snapshots/freezes this declaration and rejects invalid shapes with `INVALID_WRITE_FEATURES` (500). A protected schema rejects an adapter without support. `update(model, id, changes, session, { expectedVersion })` must atomically match ID/version, apply changes and set `expectedVersion + 1`, returning the updated record; `delete(model, id, session, { expectedVersion })` must atomically match both and return the deleted record. An update at the maximum skips the write. After no match, read the same cast key in the same session: return `null` if absent, report `VERSION_CONFLICT` if stale or `VERSION_EXHAUSTED` for an equal maximum on update, otherwise return `null`. MongoDB missing/null versions count as 1. Without `expectedVersion`, existing writes and call paths remain unchanged.
84
+ ## Packages
66
85
 
67
- Support also requires `find(model, type, args, session, { requiredId, context })` to honor the session, required key and context for scoped visibility checks. Wrappers must forward every argument, including options. Core's update result diagnostic requires `expected + 1` and advancement beyond its immutable pre-read version; delete results must carry the expected version. These diagnostics raise `DATABASE_ERROR` for inconsistent results but cannot prove arbitrary wrappers kept the atomic predicate. They retain supplied-session ownership; adapters must preserve their own guarded-write abort semantics. The `get_by_id` scope defers stale/exhausted errors for hidden callers until write hooks authorize the operation, but malformed stored versions can fail before hooks. Protected writes always perform their required pre-read, regardless of `readByIdIsInert`.
86
+ All Simfinity packages are released together under the same version. Keep the ones you install aligned. The ESLint plugin has its own version.
68
87
 
69
- ## Client metadata
88
+ | Package | Use it for |
89
+ | --- | --- |
90
+ | [`@simtlix/simfinity-js`](https://www.npmjs.com/package/@simtlix/simfinity-js) | MongoDB/Mongoose applications |
91
+ | [`@simtlix/simfinity-postgres`](https://www.npmjs.com/package/@simtlix/simfinity-postgres) | PostgreSQL 15+ applications, with real tables and foreign keys |
92
+ | [`@simtlix/simfinity-mcp`](https://www.npmjs.com/package/@simtlix/simfinity-mcp) | MCP tools and transports for any Simfinity schema |
93
+ | [`@simtlix/simfinity-js-client`](https://www.npmjs.com/package/@simtlix/simfinity-js-client) | JavaScript client for Simfinity APIs |
94
+ | [`@simtlix/simfinity-descriptor`](https://www.npmjs.com/package/@simtlix/simfinity-descriptor) | Client metadata endpoint for a schema |
95
+ | **`@simtlix/simfinity-core`** | Shared GraphQL runtime, installed automatically (this package) |
96
+ | [`@simtlix/simfinity-sql`](https://www.npmjs.com/package/@simtlix/simfinity-sql) | Relational runtime and database plugin contract |
97
+ | [`@simtlix/eslint-plugin-simfinity`](https://www.npmjs.com/package/@simtlix/eslint-plugin-simfinity) | Optional lint rules for Simfinity applications |
70
98
 
71
- Since 3.15.0, install the optional `@simtlix/simfinity-descriptor` package,
72
- aligned with your Simfinity version. It exports `buildClientDescriptor(schema)`
73
- and `createClientDescriptorHandler(schema, { authorize: 'public' })`. The builder
74
- describes the exact `createSchema()` result; the explicitly mounted handler can
75
- instead use an application access function. These functions are not facade/core
76
- exports. The descriptor does not evaluate dynamic permissions or serialize
77
- arbitrary validators.
99
+ ## Contributing
78
100
 
79
- Legacy introspection stays enabled by default in 3.x. To opt out, import
80
- `configureIntrospection` from `@simtlix/simfinity-core/introspection` in a separate
81
- bootstrap module and call it with `{ fieldExtensions: false }` **before importing
82
- the runtime or facades**. That direct import requires core as a direct dependency
83
- pinned to the facade's exact version, including under pnpm or Yarn PnP. A late
84
- call fails without deleting installed metadata. Migrate clients selecting
85
- `__Field.extensions` before opting out. See the [descriptor reference](https://simtlix.github.io/simfinity.js/reference/client-descriptor.html)
86
- for the format, protected HTTP access and composition limits.
101
+ Source, issues and the contribution guide live in the [simtlix/simfinity.js](https://github.com/simtlix/simfinity.js) monorepo, under `packages/core`. See [Contributing](https://simtlix.github.io/simfinity.js/resources/contributing.html).
87
102
 
88
- `queryFeatures.valuePath: true` opts an adapter into stored-field comparisons and requires `referenceEmptiness: true`; only true enables it. `valuePath` is nullable on all three generated filter inputs and is mutually exclusive with a supplied `value` (including null). Core validates both operand paths, their logical types, visibility and collection correlation, and preserves outer origins internally. `createQueryPlan` adds `operand?: QueryOperand` with `{ up, path, joins? }` and a null value placeholder; same-frame scope unions live in predicate `joins`, ancestor scopes in operand `joins`. Literal nodes stay unchanged. `describeModels` retains additive `enumName` and effective `temporalName` metadata. See [the comparison rules and limits](https://simtlix.github.io/simfinity.js/guide/queries.html#compare-stored-fields) and [compiler contract](https://simtlix.github.io/simfinity.js/guide/sql-plugins.html#query-features).
103
+ ## License
89
104
 
90
- Version 3.18.0 adds `queryFeatures.elementFrames: true` for embedded object `ANY`/`ALL`/`NONE` and structural `$parent` operands. It requires `quantifiers`, `referenceEmptiness` and `valuePath`, all true. Missing/null lists and null items contribute no elements; `ALL` and `NONE` are true without elements. Parent traversal includes single embedded containers and stops at entity boundaries. Filters preserve selected arrays and outer row cardinality. See [the syntax and examples](https://simtlix.github.io/simfinity.js/guide/queries.html#embedded-object-lists-and-parent); versions through 3.17.0 do not support this extension.
105
+ [Apache-2.0](https://github.com/simtlix/simfinity.js/blob/master/LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simtlix/simfinity-core",
3
- "version": "3.18.0",
3
+ "version": "3.19.1",
4
4
  "description": "Driver-free GraphQL runtime and model metadata for Simfinity",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -88,6 +88,7 @@
88
88
  "node": ">=18.18.0"
89
89
  },
90
90
  "license": "Apache-2.0",
91
+ "homepage": "https://simtlix.github.io/simfinity.js/reference/api.html",
91
92
  "repository": {
92
93
  "type": "git",
93
94
  "url": "https://github.com/simtlix/simfinity.js",