npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@simtlix/simfinity-sql

v3.7.0

Published

Driver-free relational runtime and database plugin contract for Simfinity

Downloads

2,001

Readme

@simtlix/simfinity-sql

Driver-free relational planning, record reconstruction, and transaction orchestration for Simfinity. It requires Node.js 18.18 or later and GraphQL 16. It depends only on @simtlix/simfinity-core; it does not install pg, MongoDB, Mongoose, MCP, or a database plugin.

PostgreSQL 15 or later is the first and only supported SQL plugin. Install the SQL runtime and PostgreSQL plugin at matching versions:

npm install @simtlix/[email protected] @simtlix/[email protected] graphql@^16.11.0 pg@^8.16.3

Runtime

import pg from 'pg';
import { createSQL } from '@simtlix/simfinity-sql';
import { postgresPlugin } from '@simtlix/simfinity-postgres';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const simfinity = createSQL({ plugin: postgresPlugin({ pool, schema: 'library' }) });

simfinity.connect(null, BookType, 'book', 'books', bookController);
const schema = simfinity.createSchema();
await simfinity.initializeDatabase({ mode: 'create' });

// Serve schema. The application owns the pool and calls pool.end() at shutdown.

createPostgres({ pool, schema }) remains a convenience factory for this same implementation. Both entry points preserve PostgreSQL descriptions, generated SQL, GraphQL operations, scopes, hooks, state machines, model handles, and session behavior. The PostgreSQL package guide describes the supported storage and query shapes.

A runtime permanently binds one plugin and configuration. Its methods and capabilities are captured at creation; connection objects remain caller-owned. To configure later, use createSQL({ plugin: postgresPlugin() }), then call configure({ pool, schema }) once before createSchema(). Register all types before creating the schema and await successful initialization before serving operations. Give each runtime its own GraphQL type objects: a type whose relation resolvers one runtime generated, a toConfig() copy of it made afterwards, or a shared custom mutation result type with an unresolved relation field throws TYPE_BOUND_TO_OTHER_RUNTIME in another.

initializeDatabase({ mode: 'create' }) creates missing compatible storage; mode: 'validate' validates existing storage. Incompatible definitions are rejected. preventCreatingCollection(true) forces validation: a missing or false mode validates, and every other mode except validate fails with DATABASE_CREATION_DISABLED (409). On a runtime that allows creation, any mode other than create, validate or an omitted one, including null and '', fails with INVALID_INITIALIZATION_MODE (400). Both are rejected before the plugin is called. PostgreSQL initialization performs no destructive migration or schema synchronization.

Operations fail with DATABASE_NOT_INITIALIZED (503) until the first initialization succeeds. A rejected request leaves readiness as it was, a ready runtime keeps serving while it is initialized again, and any failure of the initialization started last makes storage unavailable until a later one succeeds. Check a live runtime with mode: 'validate': when PostgreSQL's create mode 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. It locks no table when nothing is missing.

The runtime exposes the core registration and GraphQL lifecycle APIs, configure, describeDatabase, compileDatabaseSchema (the plugin's DDL for this runtime's schema, for review or export; it does not touch storage), initializeDatabase, withTransaction, and shared auth, plugins, scalars, and validators helpers. Model handles expose findById, find, create, update, and delete. find(args, { session }) accepts the generated list query's filters, sorting, and pagination. Native model helpers bypass GraphQL controllers and validators and use storage field names.

withTransaction(session, callback) joins an active session belonging to this runtime, or owns a new transaction when no session is supplied. Sessions expose client, query(text, values), and inTransaction(). PostgreSQL uses repeatable-read isolation and retries confirmed serialization/deadlock aborts up to five times, each after a random delay of less than 10, 20, 40, 80 and 160 ms. Without a session, a read that one statement answers runs on one connection without a transaction when the plugin advertises singleStatementReads, as PostgreSQL does. Owned rows are inserted with multi-row statements when the plugin supports them, unless the configuration sets multiRowInserts: false. Unknown commit outcomes are not replayed, and the application-owned pool stays open.

Relational planning

import { describeModels } from '@simtlix/simfinity-core';
import { planRelationalSchema } from '@simtlix/simfinity-sql';

const models = describeModels([{ gqltype: BookType }]);
const plan = planRelationalSchema(models, { schema: 'library' });

planRelationalSchema(models, { schema, naming }) is pure: it returns serializable logical tables, scalar columns, foreign keys, indexes, checks, owned-record metadata, and requirements. Logical columns have scalar, list, and nullable properties. They contain no physical SQL types, SQL expressions, or driver objects. An optional naming object provides validateIdentifier(value) and generatedName(...parts) to enforce engine naming limits.

Plugin contract, version 1

A plugin factory returns an object with apiVersion: 1, name, displayName, defaultSchema, configuration options or null, a capabilities array, and these members:

| Member | Responsibility | | --- | --- | | naming | Validate identifiers and generate engine-compatible names. | | describeSchema(plan, configuration) | Lower the logical plan into physical storage metadata. Receives the bound configuration, or null, as its last argument. | | compileSchema(description) | Return reviewable DDL statements. The runtime calls it only from compileDatabaseSchema(). | | initialize(configuration, description, options) | Create or validate storage and return { mode, created }. Receives only create, validate or no mode, and must keep accepting indexes that earlier versions generated on its databases, such as the [ID] list index. | | compileQuery(models, description, queryPlan, extra, configuration) | Return { text, values, aggregateFields? }. Aggregate queries include the group field followed by each fact field in aggregateFields. Receives the bound configuration, or null, as its last argument. | | compileChildrenQuery(models, description, queryPlan, { column, ids }, configuration) | Optional; used only with batchedCollections: true. Return { text, values, parentColumn, setup? }: one statement whose rows, for each parent in ids, are exactly the rows compileQuery returns for { column, id }, in the same order, each with its parent's 1-based position in ids under parentColumn. The optional setup, which may also be null, lists statements ({ text, values? }) that run, in order, on the same connection right before it, such as transaction-scoped settings; their rows are ignored. Receives only paginated find plans and 1 to 100 distinct cast IDs. | | compileRecord(description, operation) | Compile one record operation into { text, values }. | | compileRecordBatch(description, operation) | Optional, with maxRecordBatchValues. Compile { kind: 'insertMany', table, rows } (SQLInsertManyOperation) into one statement that inserts every row; the rows share their columns in the same order and no result rows are needed. A single row goes through compileRecord. | | maxRecordBatchValues | Optional, with compileRecordBatch. A positive safe integer: the most values, rows times columns, of one batch statement. | | values | Create/cast IDs, encode/decode scalars, and encode embedded values. | | driver | Validate configuration, execute statements, acquire/release clients, begin/commit/rollback transactions, classify retryable aborts, and normalize errors. |

The driver supplies assertConfiguration, query, acquire, begin, commit, rollback, release, isRetryable, and normalizeError. query(configuration, statement, client?) returns a promise resolving to { rows, rowCount? }; client is the connection of a transaction or of a single-statement read, and counts and aggregates omit it. release(client, error?) receives the rollback failure when ROLLBACK did not complete, including the probe after a failed single-statement read; the driver must then discard the connection instead of returning it to a pool, and SQL does not retry that attempt. For an error that isRetryable accepts, SQL waits its retry delay only after release has completed, so the failed attempt holds no connection meanwhile. A single-statement read runs acquire, query and release without begin or commit; SQL retries it for an error that isRetryable accepts, with the same delays and limit, without a rollback step. After any other failed driver call it calls rollback(client) outside a transaction to probe the connection; an error raised before any statement, such as NOT_VALID_ID, releases the connection without a probe and passes a probe failure to release(client, error), so rollback must resolve on a usable connection with no open transaction and reject on an unusable one; the read's own error is reported. A transaction session stops accepting statements as soon as its callback settles, before COMMIT or ROLLBACK is sent, so un-awaited session work fails with INVALID_SESSION. commit(client) must reject when the engine did not commit, for example when it rolled the transaction back because a statement had failed; SQL retries a rejected commit only when isRetryable accepts its error, so reject with a non-retryable error in that case. isRetryable and normalizeError receive any object thrown in an owned transaction or operation, application errors included, and never null or a primitive; normalizeError never receives a SimfinityError. normalizeError must map its own engine's errors and return every other error unchanged. A failed acquire, begin, query or commit that it returns unchanged becomes DATABASE_ERROR (500), with the driver error as its non-enumerable cause and getCause(). session.query() rejects with the driver's own error, so a savepoint can recover from it. Value codecs supply createId(), castId(value), encodeScalar(field, value), decodeScalar(field, value, gqlField?), and encodeEmbedded(value).

Physical descriptions must preserve the table names, primary keys, column names, presence markers, and ownership metadata needed for record reconstruction. Plugins may extend those shapes with native types, functions, triggers, and other engine metadata. compileRecord receives a physical table object, with one of these operation shapes:

| kind | Additional fields | | --- | --- | | selectById | table, id, optional lock | | selectOwned | table, ids, ordered | | insert | table, data | | update | table, id, data | | deleteById | table, id | | deleteOwned | table, ownerId |

planRelationalSchema indexes single ID columns but not [ID] lists. Compiled SQL must not depend on the physical column order of a table.

Every runtime requires transactions. Planning adds foreignKeys, deferredForeignKeys, embeddedValues, ownedRecords, scalarLists, uniqueValues, and nullableUnique when the model needs those guarantees. The optional singleStatementReads is never required: a plugin advertises it when one statement reads one consistent snapshot without a transaction and rollback resolves on a connection with no open transaction, and sessionless reads of types without owned tables then run on one connection without begin and commit. To keep a transaction around every PostgreSQL read, pass createSQL() a copy of postgresPlugin(options) whose capabilities leave it out, such as { ...plugin, capabilities: plugin.capabilities.filter((name) => name !== 'singleStatementReads') }. Batched collection reads are opt-in: with batchedCollections: true in the configuration and a compileChildrenQuery, the adapter reads the generated collection fields of many parents with one statement per 100 parents, one after another, on the connection or transaction findChildren would use; when a statement has setup, the whole batch, owned-row hydration included, runs in one transaction from begin to commit, because settings such as PostgreSQL's SET LOCAL last only until the end of a transaction. Without batchedCollections: true, the adapter has no findChildrenBatch and keeps one compileQuery statement per field, as 3.6.0 did. A setup that is not a list of statements fails the batch with a TypeError before a connection is taken, and the runtime reports it once and reads each parent with compileQuery. The compiler carries, as its compileQuery property, the compileQuery whose rows it reproduces, and is bound only when that is the plugin's own: a copy that overrides compileQuery, such as { ...postgresPlugin(options), compileQuery }, reads each parent with its override. Embedded/owned capabilities include presence and shape enforcement. Unsupported guarantees fail before physical schema compilation or initialization; a plugin must not silently omit constraints.

Invalid plugin structure, including a compileRecordBatch or compileChildrenQuery that is not a function or a maxRecordBatchValues that is not a positive safe integer, raises INVALID_SQL_PLUGIN; unsupported contract versions raise UNSUPPORTED_SQL_PLUGIN_VERSION; missing required guarantees raise UNSUPPORTED_SQL_CAPABILITY.

compileQuery receives SQLModelDescription, which extends core model metadata with recursive SQLFieldDescription fields. The state field of a type with a state machine has stateNames: [{ value, name }]: value is the string form of the stored enum value, and name is its GraphQL enum name. Compilers can use this mapping for aggregate state names. SQL runtimes require the state field of a state machine to be a GraphQL enum and reject any other with INVALID_MODEL (400) at createSchema(). Scalar codecs and aggregateFields use the same enriched field type.

TypeScript declarations export SQLPlugin, SQLDriver, SQLValueCodec, RelationalPlan, discriminated RelationalCheck and SQLRecordOperation unions, SQLInsertManyOperation, SQLChildrenStatement, SQLChildrenQueryCompiler, physical storage interfaces, and SQLRuntime, SQLSession, and SQLModel. Plugin generics preserve configuration, native client, physical description, query-result, and ID types through createSQL. Application-defined row values use unknown until narrowed. The ./internal/adapter, ./internal/records and ./internal/transactions subpaths are deprecated: they are not plugin-author APIs, have no type declarations, are no longer used by Simfinity's packages, and will be removed in 4.0.