Skip to content
Flowblade
Esc
↑↓navigate↵open⌘Jpreview
On this page

@flowblade/core

Shared result, error, metadata, datasource and transaction contracts used by Flowblade adapters.

@flowblade/core defines the contracts shared by Flowblade datasources. It is normally installed automatically as an adapter dependency; install it directly when implementing a datasource or writing code against the common interfaces.

npm install @flowblade/core

Datasource contract

The DatasourceInterface exposes a consistent set of operations:

Method Behavior
query(query, options?) Returns a QResult containing rows or an error; it does not throw.
queryOrThrow(query, options?) Returns rows and throws a typed QErrors error on failure.
queryOne(query, options?) Returns one row or null; returns TOO_MANY_RESULTS if more than one row matches.
queryOneOrThrow(query, options?) Requires exactly one row, unless defaultValue is provided for no row.
stream(query, options?) Iterates through rows without materializing the full result in application memory.
getConnection() Exposes the underlying driver object for advanced use; it is not covered by API stability.

queryOne() and queryOneOrThrow() do not rewrite SQL or add a limit. They check the returned row count after fetching results. When the query is not constrained by a unique key, add a database-side bound such as LIMIT 2 (or TOP 2 on SQL Server).

QResult and errors

QResult<TData> is a discriminated result: it contains either data or error, and always has meta. Checking error narrows the type of data. result.map(mapper) transforms successful rows and returns a new result; mapper failures become MAP_FAILED. result.toJsonifiable() omits the original cause, so driver internals are not serialized.

Error code Meaning
QUERY_FAILED The database or query execution failed.
ABORTED An AbortSignal cancelled or interrupted the operation.
TOO_MANY_RESULTS A one-row operation received multiple rows.
MAP_FAILED A QResult.map() mapper threw.
ROLLED_BACK A transaction callback threw or called tx.rollback().
TRANSACTION_FAILED A query failed in a transaction, or transaction setup/finalization failed.
TRANSACTION_CONFLICT A transient serialization conflict or deadlock occurred.

The original driver error (or abort reason) is available as error.cause. Throwing methods convert query and transaction failures to classes under QErrors, including QueryFailed, Aborted, NoResult, TooManyResults, TransactionFailed, and TransactionConflict. These errors provide suggested HTTP status codes; applications choose how to map them to responses.

const { data, error, meta } = await ds.query(query);

if (error) {
  console.error(error.code, error.message, error.cause);
} else {
  console.log(`${data.length} rows in ${meta.getTotalTimeMs()}ms`);
}

// For exception-based control flow:
const { data: rows } = await ds.queryOrThrow(query);

For queryOneOrThrow(), defaultValue only handles a missing row; query failures and multiple rows still fail. Error factories (onError, onNoResult, onTooManyResults) let applications replace the default thrown errors per call or through a datasource’s defaultOptions.

Metadata

Every result includes QMeta. SQL spans record the SQL text, bound parameters, elapsed time and affected row count. Transaction and savepoint spans record their status and timing; transaction metadata also includes isolation/access mode and retry count where applicable. Metadata is useful for logging and diagnostics. Adapter loggers can omit parameter values, while parameters remain available in metadata.

Transactions

Datasources that support transactions implement TransactionalDatasourceInterface. transaction() returns a result; transactionOrThrow() returns data or throws. Each callback receives tx, a datasource bound to the transaction. Use it for every query in the callback and await queries before returning. The transaction waits for tracked in-flight queries before it commits or rolls back.

Transaction error code Meaning
ROLLED_BACK The callback threw or called tx.rollback().
TRANSACTION_FAILED A transaction query failed, or begin/commit/rollback failed.
TRANSACTION_CONFLICT A concurrent transaction conflict occurred; it may be retried.
ABORTED The transaction signal was aborted; the transaction is not committed.

Options are database-dependent:

  • isolationLevel and accessMode request a database transaction mode.
  • signal aborts the transaction and prevents a commit.
  • retry: { maxRetries } retries recognized transaction conflicts; retries are disabled unless configured. The callback runs again, so do not put non-transactional side effects (such as sending email) inside it.
  • defaultOptions.transaction.retry sets datasource-wide retry defaults; per-transaction retry values override them.
  • By default (onFailedQuery: "rollback"), a handled query error still marks the transaction for rollback. onFailedQuery: "commit" is only safe on databases/configurations where a failed statement does not abort the transaction; do not use it with Postgres. Prefer savepoints where available.

Retries use exponential backoff with jitter by default. Use createExponentialBackoff() from @flowblade/core to customize the delay.

Some adapters support savepoints through tx.savepoint() and tx.savepointOrThrow(). A failed savepoint can roll back its own work while allowing the outer transaction to continue; support and database semantics depend on the adapter.

Implementing a datasource

@flowblade/core/datasource exports helpers for datasource implementations: createQResultSuccess(), createQResultError(), createSqlSpan(), toOneRowQResult(), getOneRowOrThrow(), runTransaction() and runSavepoint().

import { QMeta } from "@flowblade/core";
import {
  createQResultError,
  createQResultSuccess,
  createSqlSpan,
} from "@flowblade/core/datasource";

const span = createSqlSpan({ sql, params });
try {
  const rows = await driver.execute(sql, params);
  return createQResultSuccess(rows, new QMeta({ name, spans: span }));
} catch (cause) {
  return createQResultError(
    { message: String(cause), code: "QUERY_FAILED", cause },
    new QMeta({ name, spans: span })
  );
}

See the adapter guides for concrete implementations and adapter-specific behavior: Kysely and DuckDB. The core package README and API reference provide further details.

Was this page helpful?