@centia-io/sdk
v0.2.16
Published
Centia-io TypeScript SDK
Readme
SDK
TypeScript/JavaScript client SDK for Centia-io. It provides:
- Authentication helpers:
- CodeFlow (OAuth 2.0 Authorization Code + PKCE) for browser apps
- PasswordFlow for trusted/CLI/server environments
- Data access:
- Sql: Execute parameterized SQL
- Rpc: Call JSON-RPC methods
- createApi: A tiny type-safe helper that maps TypeScript interfaces to JSON‑RPC calls
Installation
npm install @centia-io/sdk
yarn add @centia-io/sdk
pnpm add @centia-io/sdkOr from CDN:
<script src="https://cdn.jsdelivr.net/npm/@centia-io/sdk@latest/dist/centia-io-sdk.umd.js"></script>Requirements:
- Browser or Node.js 18+ (for global
fetch). - An accessible Centia.io host URL and client credentials.
ESM import:
import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp, createSqlBuilder } from "@centia-io/sdk";
import type { RpcRequest, RpcResponse, PgTypes } from "@centia-io/sdk";Authentication
The SDK handles token storage and refresh for you.
- Tokens and minimal options are saved to
localStoragein browsers. In non‑browser environments, an in‑memory store is used for the lifetime of the process. - Authorization headers are added automatically for Sql/Rpc requests.
CodeFlow (Browser, OAuth 2.0 Authorization Code + PKCE)
Use this flow in browser applications where you can redirect the user to the Centia-io login page.
Required options:
host: Base URL of your Centia-io instance, e.g.https://api.centia.ioclientId: OAuth client id configured in Centia.ioredirectUri: The URL in your app that handles the redirect back from Centia.io (must be whitelisted)scope(optional): Not in use yet, but will be used to request additional permissions from the user.
Example (vanilla JS/TS + SPA):
import { CodeFlow } from "@centia-io/sdk";
const codeFlow = new CodeFlow({
host: "https://api.centia.io",
clientId: "your-client-id",
redirectUri: window.location.origin + "/auth/callback"
});
// On app startup, call redirectHandle() once to complete a login redirect (if any)
codeFlow.redirectHandle().then((signedIn) => {
if (signedIn) {
console.log("User signed in");
}
});
// Start sign-in when user clicks Login
function onLoginClick() {
codeFlow.signIn(); // Redirects to GC2 auth page
}
// Sign out (clears tokens/options and redirects to signout endpoint)
function onLogoutClick() {
codeFlow.signOut();
}Notes:
redirectHandle()detects errors from the auth server, validatesstate(CSRF protection), exchanges thecodefor tokens, performsPKCE(Proof Key for Code Exchange), stores tokens and cleans up the URL.signOut()clears local tokens/options and redirects to the sign-out URL. If you only need to clear local state without redirect, callcodeFlow.clear().
PasswordFlow (Trusted environments, CLI/Server)
Use only in trusted environments. The user’s database credentials are exchanged directly for tokens.
Required options:
hostclientIdusernamepassworddatabase
Example (Node.js):
import { PasswordFlow } from "@centia-io/sdk";
const flow = new PasswordFlow({
host: "https://api.centia.io",
clientId: "your-client-id",
username: "your-username",
password: "your-password",
database: "parent-database" // The database to connect to. If superuser, this is the sam as username.
});
await flow.signIn();
// Tokens are now stored; subsequent Sql/Rpc calls will include Authorization header.
// ... your code ...
flow.signOut(); // Clears tokens/options in local storage (no redirect)GuestFlow (Guest tokens – anonymous access)
Obtain tokens for a database's default user (the sub-user used for anonymous access) without any user credentials. Useful for public apps that need a bearer token for public data. Requires that the database has a default user; clientSecret is only needed when the OAuth client is not public.
Required options:
hostclientIddatabaseclientSecret(only for confidential clients)
Example:
import { GuestFlow } from "@centia-io/sdk";
const flow = new GuestFlow({
host: "https://api.centia.io",
clientId: "your-client-id",
database: "your-database"
});
await flow.signIn();
// Tokens for the default user are now stored; subsequent Sql/Rpc calls
// include the Authorization header and refresh works as usual.
flow.signOut(); // Clears tokens/options in local storage (no redirect)SignUp (Browser – Create a new user)
Use this helper in browser applications to redirect the user to the Centia‑io sign‑up page. The user will create an account under a specified parent database/tenant and then be redirected back to your application.
Required options:
- host: Base URL of your Centia‑io instance, e.g. https://api.centia.io
- clientId: OAuth client id configured in Centia.io
- parentDb: The parent/tenant database under which the new user should be created
- redirectUri: URL in your app to return to after sign‑up
Example (vanilla JS/TS):
import { SignUp } from "@centia-io/sdk";
const signUp = new SignUp({
host: "https://api.centia.io",
clientId: "your-client-id",
parentDb: "your-parent-database",
redirectUri: window.location.origin + "/auth/callback"
});
// Start sign-up when the user clicks "Create account"
function onSignUpClick() {
signUp.signUp(); // Redirects to GC2 sign-up page
}Notes:
- Default endpoint is {host}/signup/. You can override with authUri if needed.
- After the user completes sign‑up and is redirected back to your app, start your normal sign‑in flow (e.g., CodeFlow. A session is started when the user signed up, so the user will be signed in automatically in the flow.)
SQL
Execute parameterized SQL against GC2.
- Class:
new Sql() - Method:
exec(request: SqlRequest): Promise<SqlResponse> - Endpoint:
POST https://api.centia.io/api/v4/sql
Types (simplified):
SqlRequesthas:q: SQL string, you can use named placeholders like:a(server-side feature)params?: object with values for placeholderstype_hints?: optional explicit type hintstype_formats?: optional per-column format strings
SqlResponsehas:schema: a map of column name ->{ type: string, array: boolean }data: an array of rows (records)
Example:
import { Sql } from "@centia-io/sdk";
const sql = new Sql();
const payload = {
a: 1,
b: "hello",
c: "3.14", // numeric/decimal values are strings
d: ["x", "y"], // arrays are supported
e: { nested: [1,2] } // JSON
};
const res = await sql.exec({
q: "select :a::int as a, :b::varchar as b, :c::numeric as c, :d::varchar[] as d, :e::jsonb as e",
params: payload,
type_hints: { d: "varchar[]" } // Arrays are not inferred by default, and must be specified explicitly
});
console.log(res.schema); // { a: {type: 'int4', array: false}, ... }
console.log(res.data); // [{ a: 1, b: 'hello', c: '3.14', d: ['x','y'], e: {nested:[1,2]} }]Typing the rows:
import type { PgTypes } from "@centia-io/sdk";
interface Row extends PgTypes.DataRow {
a: number;
b: Pgtypes.Varchar;
c: PgTypes.NumericString;
d: PgTypes.PgArray<Pgtypes.Varchar>;
e: PgTypes.JsonValue;
}
// res: PgTypes.SqlResponse<Row>
const res = await sql.exec({ q: "...", params: payload }) as PgTypes.SqlResponse<Row>;SQL Builder
Build strongly typed SQL requests from a DB schema so you don't write raw SQL.
- Function:
createSqlBuilder(schema) - Types:
DBSchema,TableDef,ColumnDef - Supports:
select(andWhere/orWhere, andWhereOp/orWhereOp, grouped predicates, orderBy, limit, offset, join, selectFrom),insert(returning),update(where, returning),delete(where, returning) - Produces an object with
toSql(): SqlRequestwhich you pass tonew Sql().exec()
Example:
import { createSqlBuilder, Sql } from "@centia-io/sdk";
import type { DBSchema } from "@centia-io/sdk";
// Minimal schema (compatible with schema/schema.json).
const schema = {
name: "public",
tables: [
{
name: "items",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: true },
{ name: "type", _typname: "int4", _is_array: false, is_nullable: true }
]
}
]
} as const satisfies DBSchema;
const b = createSqlBuilder(schema);
// SELECT with where/order/limit
const selectReq = b.table("items")
.select(["id", "name"]) // or omit to select all: .select()
.andWhere({ type: [1, 2, 3] }) // => "type" = ANY(:param)
.orderBy([["id","desc"]])
.limit(10)
.toSql();
const sql = new Sql();
const rows = (await sql.exec(selectReq)).data;
// INSERT
const insertReq = b.table("items")
.insert({ id: 10, name: "Thing", type: 1 })
.returning(["id"])
.toSql();
await sql.exec(insertReq);
// UPDATE
const updateReq = b.table("items")
.update({ name: "Updated" })
.where({ id: 10 })
.returning(["id","name"])
.toSql();
await sql.exec(updateReq);
// DELETE
const deleteReq = b.table("items")
.delete()
.where({ id: 10 })
.toSql();
await sql.exec(deleteReq);Notes:
- The builder automatically adds
type_hintsfor array parameters (e.g.,int4[]), as arrays are not inferred by default by the server. - Value types are inferred from
_typnameand_is_array. Fornumeric/decimal, use strings (NumericString). - You can pass the same
SqlRequestobject toSql.exec.
RPC
Call JSON‑RPC methods exposed by GC2.
- Class:
new Rpc() - Method:
call(request: RpcRequest): Promise<RpcResponse> - Endpoint:
POST {host}/api/v4/call
Types (simplified):
RpcRequesthasjsonrpc: "2.0",method, optionalparams, optionalidRpcResponsehasjsonrpc: "2.0",id, andresultwith{ schema, data }
Example:
import { Rpc } from "@centia-io/sdk";
const rpc = new Rpc();
const payload = { a: 1, b: "hello" };
const res = await rpc.call({
jsonrpc: "2.0",
method: "typeTest",
params: payload,
id: 1
});
console.log(res.result.schema);
console.log(res.result.data); // array of rowsTyping the rows:
import type { PgTypes } from "@centia-io/sdk";
interface Row extends PgTypes.DataRow {
a: number;
b: string;
}
const res = await rpc.call({ jsonrpc: "2.0", method: "typeTest", params: payload }) as PgTypes.RpcResponse<Row>;createApi
A tiny helper that builds a Proxy around Rpc so you can call api.someMethod(params) directly, with TypeScript autocompletion and type‑checking based on your own interface.
Under the hood, each property access becomes a JSON‑RPC call with the property name as the method. The helper returns result.data (array of rows) from the RPC response.
Example with typing:
import { createApi } from "@centia-io/sdk";
import type { PgTypes } from "@centia-io/sdk";
// Define the shape of your RPC methods and return types
interface MyApi {
typeTest(params: {
a: number;
b: Pgtypes.Varchar;
c: PgTypes.NumericString;
d: PgTypes.PgArray<Pgtypes.Varchar>;
e: PgTypes.JsonValue;
}): Promise<Array<{
a: number;
b: Pgtypes.Varchar;
c: PgTypes.NumericString;
d: PgTypes.PgArray<Pgtypes.Varchar>;
e: PgTypes.JsonValue;
}>>;
}
const api = createApi<MyApi>();
const rows = await api.typeTest({
a: 1,
b: "Hello world",
c: "3.4",
d: ["Hello", "world"],
e: { "x": [1,2,3,4,5,6,7,8,9,10] }
});
console.log(rows); // typed row arrayNotes:
createApi<T>()relies on naming conventions: the property name is the JSON‑RPCmethodname.- Each call returns
result.datafrom the RPC response (array of rows).
Layer styling (admin client)
Layer configuration (properties, classes, styles and labels — MapServer-backed cartography) is managed through the admin client:
import { createCentiaAdminClient } from '@centia-io/sdk'
const client = createCentiaAdminClient({
baseUrl: 'https://example.centia.io',
auth: { getAccessToken: async () => token },
})
const layers = client.provisioning.layers
// List layer keys
const names = await layers.getLayer(undefined, { namesOnly: true })
// Read a full layer definition (properties + classes with styles/labels)
const layer = await layers.getLayer('my_schema.my_table.the_geom')
// Update layer properties (key-merge)
await layers.patchLayer('my_schema.my_table.the_geom', {
name: 'my_schema.my_table.the_geom',
properties: { opacity: '80', geotype: 'POLYGON' },
})
// Classes, styles and labels have their own CRUD methods
const { location } = await layers.postLayerClass('my_schema.my_table.the_geom', {
name: 'Roads',
expression: "[type]='road'",
})
await layers.postStyle('my_schema.my_table.the_geom', 'a1b2c3d4', { color: '#008000', width: '2' })
await layers.postLabel('my_schema.my_table.the_geom', 'a1b2c3d4', { text: '[name]', on: true })Map view configuration (admin client)
Each schema has a map view configuration (initial center, zoom and extent, all in EPSG:3857):
const maps = client.provisioning.maps
const view = await maps.getMap('my_schema')
// { center: [1386651, 7503372], zoom: 12, extent: [1354000, 7478000, 1419000, 7528000] }
// Only the provided properties are updated; null clears a value
await maps.patchMap('my_schema', { center: [1386651, 7503372], zoom: 12 })
await maps.patchMap('my_schema', { extent: null })OGC services (OWS / WFS)
Ows and Wfs wrap the OGC endpoints and take a CentiaHttpClient:
import { createCentiaClient, Ows, Wfs } from '@centia-io/sdk'
const http = createCentiaClient({
baseUrl: 'https://example.centia.io',
auth: { getAccessToken: async () => token },
})
// WFS — all requests target the database-qualified endpoint
const wfs = new Wfs(http)
const capabilities = await wfs.getWfs('my_schema', 'my_database', { REQUEST: 'GetCapabilities' })
const gml = await wfs.getWfs(
'my_schema',
'my_database',
{ REQUEST: 'GetFeature', TYPENAME: 'my_table', MAXFEATURES: 100 },
{ srs: 25832 }, // optional output SRID (and optional timeSlice for versioned layers)
)
// WFS-T transactions are posted as XML
await wfs.postWfs('my_schema', 'my_database', '<wfs:Transaction>…</wfs:Transaction>')
// Generic OWS (WMS/WFS/UTFGRID)
const ows = new Ows(http)
const wmsCaps = await ows.getOws('my_schema', 'my_database', { SERVICE: 'WMS', REQUEST: 'GetCapabilities' })One endpoint serves all identities: Bearer token (must match the database in the path), HTTP Basic and anonymous. Protected layers challenge token-less requests with HTTP Basic auth. Responses are returned as XML text (or parsed JSON for JSON formats such as UTFGRID). Binary responses like WMS GetMap images are not supported by these wrappers.
MapCache (tiles: WMTS / TMS / WMS / Google Maps)
Mapcache wraps the authorizing MapCache proxy. Use getMapcache for text responses such as capabilities documents, and mapcacheUrl to build tile URL templates for map libraries:
import { createCentiaClient, Mapcache } from '@centia-io/sdk'
const mapcache = new Mapcache(http)
// Capabilities (XML)
const wmtsCaps = await mapcache.getMapcache('my_database', 'wmts/1.0.0/WMTSCapabilities.xml')
// Tile URL template for OpenLayers / MapLibre / Leaflet — {z}/{x}/{y} is preserved
const template = mapcache.mapcacheUrl('my_database', 'tms/1.0.0/my_schema.my_table@g20/{z}/{x}/{y}.png')Cached tiles can be deleted per tileset — optionally scoped by extent and zoom. The deletion runs as a background job on the server (202 Accepted) and requires write/owner authorization for the tileset's layer:
const job = await mapcache.deleteMapcacheTileset('my_database', 'my_schema.roads', {
bbox: '890000,7260000,1730000,7870000', // optional, in the grid SRS
zoom: '0,12', // optional, minzoom,maxzoom or a single zoom
grid: 'g20', // optional, defaults to g20
})
// { success: true, uuid: '...', pid: 4711, tileset: 'my_schema.roads', ... }The endpoint accepts anonymous, HTTP Basic and Bearer token requests; tile requests are authorized against the tileset's layer. Authorization travels in the Authorization header — it cannot be embedded in the URL — so for protected tilesets, inject the header per tile request via the map library's request hook (e.g. MapLibre's transformRequest or OpenLayers' tileLoadFunction).
OGC API (Features / Maps)
Ogc wraps the RESTful OGC API under /api/v4/ogc/database/{database} — OGC API Features (Part 1 Core, Part 2 CRS) for reading features as GeoJSON and OGC API Maps (Part 1 Core) for rendered images. Like Ows/Wfs it takes a CentiaHttpClient; Bearer token, HTTP Basic and anonymous requests are all accepted:
import { createCentiaClient, Ogc, OGC_CRS84, ogcEpsgCrs } from '@centia-io/sdk'
const ogc = new Ogc(http)
const { collections } = await ogc.getCollections('my_database')
const collection = await ogc.getCollection('my_database', 'my_schema.roads') // extent, crs list, links
// GeoJSON items — default page size is 10; follow the `next` link or pass offset
const page = await ogc.getItems<{ gid: number; name: string }>('my_database', 'my_schema.roads', {
bbox: [9, 55, 10, 56], // lon/lat in CRS84 (the default bbox-crs)
crs: ogcEpsgCrs(25832), // output CRS from the collection's crs list
limit: 100,
datetime: '2024-01-01T00:00:00Z', // versioned layers: the version valid at that time
})
page.numberMatched // total; page.numberReturned; page.links (next/prev)
const feature = await ogc.getItem('my_database', 'my_schema.roads', 42)
// Map images are fetched by URL (e.g. an <img> or a map library)
const url = ogc.mapUrl('my_database', 'my_schema.roads', { bbox: [9, 55, 10, 56], width: 512, format: 'png' })
const multi = ogc.datasetMapUrl('my_database', ['my_schema.roads', 'my_schema.buildings'], { width: 512 })CRS values are URIs: OGC_CRS84 (lon/lat, default) or ogcEpsgCrs(code); note ogcEpsgCrs(4326) is lat/lon order per OGC API Features Part 2. A collection that exists but is not readable answers 401 (anonymous) or 403 (no privilege); unknown collections and features are 404 — all thrown as CentiaApiError. Geofence rules, versioning and workflow are applied server-side.
Key/value store
Keyvalue wraps the /api/v4/keyvalue endpoints for storing arbitrary JSON under globally unique keys. It takes a CentiaHttpClient and requires a Bearer token:
import { createCentiaClient, Keyvalue } from '@centia-io/sdk'
const kv = new Keyvalue(http)
// Create (201; 409 if the key already exists)
await kv.postKeyvalue('app_settings', { value: { theme: 'dark', user: { name: 'Alice' } }, public: false })
// Read one key — value is returned decoded, and can be typed
const entry = await kv.getKeyvalue<{ theme: string }>('app_settings')
// { id: 1, key: 'app_settings', value: { theme: 'dark', ... }, owner: 'alice', public: false }
// List all keys visible to the caller
const entries = await kv.getKeyvalue()
// Project only named JSON sub-trees of the value (dot notation, result keyed by path)
const { value } = await kv.getKeyvalue('app_settings', ['user.name', 'theme'])
// { 'user.name': 'Alice', theme: 'dark' }
// Partial update of value and/or public flag (303)
await kv.patchKeyvalue('app_settings', { public: true })
// Delete (204)
await kv.deleteKeyvalue('app_settings')Access model: super users have full CRUD on all keys; sub-users can read their own keys plus all public keys, and can only create/update/delete their own. owner is always set server-side from the token and cannot be sent in the body; legacy keys without an owner are treated as public and super-owned.
Feature API (GeoJSON)
Features wraps the /api/v4/schemas/{schema}/tables/{table}/features endpoints for reading and writing table rows as GeoJSON through WFS-T transactions. It takes a CentiaHttpClient and requires a Bearer token:
import { createCentiaClient, Features } from '@centia-io/sdk'
const features = new Features(http)
// Get by primary key — one match returns a bare Feature, several a FeatureCollection
const one = await features.getFeature('my_schema', 'my_table', 1)
const many = await features.getFeature('my_schema', 'my_table', [1, 2, 3])
// Reproject the output geometry (default is EPSG:4326, lon/lat)
const projected = await features.getFeature('my_schema', 'my_table', 1, { srs: 25832 })
// Insert from a Feature or FeatureCollection (201; Location points at the new feature(s)).
// A primary-key value in properties is used as the new key; otherwise one is generated.
const { location } = await features.postFeature('my_schema', 'my_table', {
type: 'Feature',
geometry: { type: 'Point', coordinates: [10.0, 55.0] },
properties: { name: 'New point' },
})
// Update (303). Address a single feature by path key, or omit it and let each
// feature carry its primary-key value in properties.
await features.patchFeature('my_schema', 'my_table', {
type: 'Feature',
geometry: { type: 'Point', coordinates: [10.1, 55.1] },
properties: { name: 'Moved point' },
}, { feature: 1 })
// Delete one or more features (204; 404 only when no key matches)
await features.deleteFeature('my_schema', 'my_table', 1)
await features.deleteFeature('my_schema', 'my_table', [1, 2, 3])srs on postFeature/patchFeature declares the SRID of the incoming geometry. Reading requires a key — use the SQL or WFS APIs to query whole collections. PUT is not supported.
Snapshots (GeoParquet exports)
Snapshots wraps the snapshot API: asynchronous GeoParquet exports of a table or view to S3, and read access to the published snapshot files. It takes a CentiaHttpClient and requires a Bearer token:
import { createCentiaClient, Snapshots } from '@centia-io/sdk'
const snapshots = new Snapshots(http)
// Queue an export (202; super-user only). The export runs asynchronously.
const { id } = await snapshots.postSnapshot({ schema: 'geodanmark', relation: 'bygning', srs: 25832 })
// Or queue several at once (all-or-nothing; accepted jobs come back in request order).
// getSnapshot also accepts an array of ids.
const accepted = await snapshots.postSnapshot([
{ schema: 'geodanmark', relation: 'bygning' },
{ schema: 'geodanmark', relation: 'vej' },
])
const both = await snapshots.getSnapshot(accepted.map((a) => a.id))
// Poll until it finishes (succeeded/failed/superseded) — or use getSnapshot(id) yourself
const job = await snapshots.waitForSnapshot(id, { intervalMs: 2000, timeoutMs: 300_000 })
if (job.status === 'failed') throw new Error(job.error ?? 'export failed')
// List jobs, optionally filtered (super-user only)
const jobs = await snapshots.getSnapshots({ schema: 'geodanmark', relation: 'bygning' })
// Read API — any user with read access to the relation:
const published = await snapshots.getRelationSnapshots('geodanmark', 'bygning') // newest first
const meta = await snapshots.getRelationSnapshot('geodanmark', 'bygning', '2026-09-16')
// The Parquet file itself. dataUrl for DuckDB/GDAL/plain fetch; the data
// methods return the raw Response (stream or buffer it yourself) and follow
// the 302 redirect to presigned storage URLs.
const url = snapshots.getRelationSnapshotDataUrl('geodanmark', 'bygning', '2026-09-16')
const head = await snapshots.headRelationSnapshotData('geodanmark', 'bygning', '2026-09-16') // Content-Length/ETag
const part = await snapshots.getRelationSnapshotData('geodanmark', 'bygning', '2026-09-16', { range: [0, 1023] })
const file = await snapshots.getRelationSnapshotFile('geodanmark', 'bygning', '2026-09-16', 'metadata-<id>.json')The job API is super-user only (403 SUPER_USER_ONLY); creating throws 404 (relation not found), 409 (a snapshot of the relation is already pending or running) or 501 (snapshot storage not configured). The read API needs read access to the relation — sub-users with a deny/limit geofence rule get 403 GEOFENCE_RULES_APPLY. A snapshot with several data files answers 409 MULTI_FILE_SNAPSHOT on /data; list files in the metadata and fetch them with getRelationSnapshotFile.
Scheduler (recurring imports)
Scheduler wraps the scheduler API: cron-scheduled data-import jobs and their runs. Super-user only. It takes a CentiaHttpClient and requires a Bearer token:
import { createCentiaClient, Scheduler } from '@centia-io/sdk'
const scheduler = new Scheduler(http)
// Create one or more jobs (201; arrays are all-or-nothing). The new ids are
// parsed from the Location header, in request order.
const { ids } = await scheduler.postSchedulerJob({
name: 'import buildings',
schema: 'geodanmark',
url: 'https://example.com/data.zip',
schedule: '0 3 * * *', // 5-field cron
epsg: 25832, // defaults: epsg 4326, type "AUTO", encoding "UTF8",
}) // delete_append false, download_schema true, active true, snapshot false
// Read jobs — a single id returns one job, an array returns an array
const jobs = await scheduler.getSchedulerJobs()
const job = await scheduler.getSchedulerJob(ids[0])
const some = await scheduler.getSchedulerJob([5497, 5498])
// Update / delete (delete is all-or-nothing; 409 if a run is in progress)
await scheduler.patchSchedulerJob(ids[0], { active: false })
await scheduler.deleteSchedulerJob([5497, 5498])
// Runs. Starting is asynchronous (202) — poll getSchedulerRuns until it finishes.
await scheduler.postSchedulerRun({ job: ids[0], force: true }) // force ignores delete_append and overwrites
const runs = await scheduler.getSchedulerRuns({ job: ids[0], status: 'running' })
const run = await scheduler.getSchedulerRun(runs[0].uuid)
// Stop a running run: SIGINT, escalated to SIGKILL by the server after 30 s.
// The request itself can take up to ~30 s — do not use a short timeout.
const { signal } = await scheduler.deleteSchedulerRun(runs[0].uuid)Errors throw CentiaApiError with status/code: 400 INVALID_CRON_FIELD/INPUT_VALIDATION_ERROR, 404 JOB_NOT_FOUND/RUN_NOT_FOUND, 409 JOB_RUNNING.
Error handling
- Network/HTTP errors: thrown as
Errorwith the status/body text when available. - Auth errors: the SDK auto‑refreshes access tokens when possible. If the refresh token is expired or missing, you’ll get an error and should re‑authenticate.
Environment details
- Storage: tokens/options stored in
localStoragewhen available; otherwise a global in‑memory store is used (globalThis.__gc2_memory_storage). - Fetch: Node.js 18+ recommended (includes native
fetch). For older Node versions, add a Fetch polyfill.
License
The SDK is licensed under The MIT License
Advanced SqlBuilder examples (developer guide)
Below are practical, copy/paste‑ready snippets that demonstrate the SqlBuilder API in real scenarios. These mirror and condense the exhaustive examples in examples/test_builder.ts.
Setup (minimal schema with a foreign key for joins):
import { createSqlBuilder } from "@centia-io/sdk";
import type { DBSchema } from "@centia-io/sdk";
const schema = {
name: "public",
tables: [
{
name: "items",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: false },
{ name: "type", _typname: "int4", _is_array: false, is_nullable: false },
],
constraints: [
{ name: "items-pk", constraint: "primary", columns: ["id"] },
{
name: "items-type-fk",
constraint: "foreign",
columns: ["type"],
referenced_table: "item_types",
referenced_columns: ["id"],
},
],
},
{
name: "item_types",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "type", _typname: "varchar", _is_array: false, is_nullable: true },
],
constraints: [{ name: "item_types-pk", constraint: "primary", columns: ["id"] }],
},
],
} as const satisfies DBSchema;
const b = createSqlBuilder(schema);- Selecting all or specific columns
b.table("items").select().toSql();
// select "items".* from "public"."items"
b.table("items").select(["id", "name"]).toSql();
// select "items"."id", "items"."name" from "public"."items"- AND filters (equality and arrays -> ANY)
b.table("items").select()
.andWhere({ id: 3, type: [1,2,3] })
.toSql();
// where "items"."id" = :items_id_1 and "items"."type" = ANY(:items_type_2)- OR filters (object groups)
b.table("items").select()
.orWhere({ id: 1 })
.orWhere({ id: 2 })
.toSql();
// where ("items"."id" = :items_id_1) or ("items"."id" = :items_id_2)- Operator predicates: comparisons, LIKE variants, IN/NOT IN, NULL checks
b.table("items").select()
.andWhereOp("id", ">", 10)
.andWhereOp("name", "ilike", "%foo%")
.andWhereOp("type", "in", [1,2])
.andWhereOp("name", "isnull")
.toSql();- Grouped predicates and OR chains
b.table("items").select()
.andWhereOpGroup([
["type", "in", [1,2]],
["id", ">=", 10],
])
.orWhereOpGroup([["name", "ilike", "%foo%"]])
.orWhereOpGroup([["name", "ilike", "%bar%"], ["id", "<", 50]])
.toSql();- JOIN by foreign key + selecting from the joined table
// Auto-detects ON using FK items.type -> item_types.id
b.table("items").select(["id","name"]).join("item_types").toSql();
// select ... from "public"."items" inner join "public"."item_types" on "items"."type" = "item_types"."id"
// Select specific columns from joined table
b.table("items")
.select(["id"]) // base table columns
.join("item_types", "left") // join type: inner|left|right|full
.selectFrom("item_types", ["type"]) // joined table columns
.toSql();
// Select all columns from the joined table
b.table("items").select(["id"]).join("item_types").selectFrom("item_types").toSql();- ORDER BY, LIMIT, OFFSET
b.table("items").select().orderBy("id").toSql();
// order by "items"."id" asc
b.table("items").select().orderBy([["type","desc"],["id","asc"]]).toSql();
b.table("items").select().limit(25).offset(50).toSql();- INSERT, UPDATE, DELETE
b.table("items").insert({ id: 1, name: "A", type: 1 }).returning(["id"]).toSql();
b.table("items").update({ name: "B" }).where({ id: 1 }).returning(["id","name"]).toSql();
b.table("items").delete().where({ id: 1 }).toSql();- Special value types (ranges, intervals, geometry) – supported at compile‑time and runtime
// Ranges (e.g., tstzrange)
const events = {
name: "public",
tables: [{
name: "events",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "period", _typname: "tstzrange", _is_array: false, is_nullable: true },
]
}]
} as const satisfies DBSchema;
createSqlBuilder(events).table("events").select().andWhere({
period: {
lower: "2024-01-01T00:00:00+00:00",
upper: "2024-12-31T23:59:59+00:00",
lowerInclusive: true,
upperInclusive: false,
}
}).toSql();
// Interval
const durations = {
name: "public",
tables: [{
name: "durations",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "duration", _typname: "interval", _is_array: false, is_nullable: true },
]
}]
} as const satisfies DBSchema;
createSqlBuilder(durations).table("durations").select().andWhere({
duration: { y: 0, m: 1, d: 0, h: 2, i: 0, s: 0 }
}).toSql();
// Geometry (point example)
const shapes = {
name: "public",
tables: [{
name: "shapes",
columns: [
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
{ name: "pt", _typname: "point", _is_array: false, is_nullable: true },
]
}]
} as const satisfies DBSchema;
createSqlBuilder(shapes).table("shapes").select().andWhere({ pt: { x: 1, y: 2 } }).toSql();Notes and tips:
- All SQL is schema‑qualified: from "schema"."table" and in JOINs.
- Type hints are added automatically for all parameters (scalars and arrays). Arrays are hinted as e.g. int4[], scalars as their base type (e.g., int4, varchar, jsonb).
- Runtime validation mirrors the editor’s type checks: invalid column names, wrong orderBy direction, bad join type, negative limit/offset, wrong where/whereOp value shapes (including range/interval/geometry), and nulls on non‑nullable columns produce clear errors.
- For more, see the full script in examples/test_builder.ts which prints the generated SQL and parameters for dozens of cases.
