Skip to content

Creating a Provider


Each provider is a self-contained unit that includes driver + connection manager. SQL providers (provider-pg-node, provider-sqlite-node) delegate SQL generation to @jsorm/compiler, which is dialect-configurable. Only IndexedDB keeps its own non-SQL compiler.

You can create custom providers. The contract is small and verified against the real implementation.

The core only requires a Provider:

// @jsorm/core — src/provider/types.ts
export interface Provider {
name: string;
execute<T = unknown>(ast: QueryAST): Promise<ProviderResult<T>>;
}
export type ProviderResult<T, Meta = {}> = {
data: T;
sql?: string;
params?: SqlParam[];
meta?: Meta;
};

The factory returns { provider } & ProviderExtras:

type ProviderExtras = {
raw?: (sql: string, params?: unknown[]) => Promise<unknown> | unknown;
close?: () => void;
syncSchema?: () => SchemaReport | Promise<SchemaReport>;
getMissingTables?: () => string[] | Promise<string[]>;
compile?: (ast: QueryAST) => { sql: string; params: unknown[] };
};

Every extra is optional. sqlite-node and pg-node expose the full surface; indexeddb marks raw?: never; all?: never to signal it has no raw SQL. The client’s ProviderManager calls extras only if present and throws a clear “Provider does not support X()” error otherwise. So a valid minimal provider is just { provider }.

The provider execute receives a QueryAST and must resolve every operation. Look at provider-indexeddb: its execute switches on ast.operation (create, createMany, get, first, cursor, count, exists, update, updateMany, delete, deleteMany, junctionInsert, junctionDelete, junctionToggle).

import type { Provider, ProviderResult, QueryAST } from "@jsorm/core";
type Options = { baseUrl: string };
export function createRestProvider(models: readonly ModelDefinition[], options: Options) {
const provider: Provider = {
name: "rest-node",
async execute<T = unknown>(ast: QueryAST): Promise<ProviderResult<T>> {
const res = await fetch(`${options.baseUrl}/${ast.context.table}`, {
method: httpMethodFor(ast.operation),
body: JSON.stringify({ op: ast.operation, data: ast.data ?? ast.rows }),
});
return { data: (await res.json()) as T };
},
};
return { provider, close: () => {} };
}
  • Receives QueryAST from the client
  • Converts AST to target format (SQL, REST, etc.)
  • Executes against the database
  • Returns { data: T }
  • Cannot know models, config, or other layers beyond its constructor args
provider-{database}-{runtime}

Example: provider-pg-node, provider-indexeddb