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
- Yarn
- pnpm
npm install @centia-io/sdk
yarn add @centia-io/sdk
pnpm add @centia-io/sdk
Or 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 } 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 Centia.io auth page
}
// Sign out (clears tokens/options and redirects to signout endpoint)
function onLogoutClick() {
codeFlow.signOut();
}
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)
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: "https://myapp.com/login"
});
// Start sign-up when the user clicks "Create account"
function onSignUpClick() {
signUp.signUp(); // Redirects to Centia.io sign-up page
}
- 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.)
- Client property
allow_signupmust be set totruefor the used OAuth client. - Client property
social_signupmust be set totruefor the user can sign up with social login.
SQL
Execute parameterized SQL against Centia.io. See SQL for more details about the SQL API.
- 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, RowOfSelect } from "@centia-io/sdk";
// Minimal schema.
// Don't write this by hand.
// You can get the schema from the Sql endpoint: https://api.centia.io/api/v4/sql/schema
// or use the CLI tool: centia schema get public --raw
const schema =
{
"name": "public",
"tables": [
{
"name": "albums",
"columns": [
{"name": "album_id", "_typname": "uuid", "_is_array": false, "is_nullable": false},
{"name": "band_id", "_typname": "uuid", "_is_array": false, "is_nullable": false},
{"name": "title", "_typname": "text", "_is_array": false, "is_nullable": false},
{"name": "release_date", "_typname": "date", "_is_array": false, "is_nullable": true},
{"name": "critic_score", "_typname": "int2", "_is_array": false, "is_nullable": true}
],
"constraints": [
{"name": "albums_pkey", "constraint": "primary", "columns": ["album_id"]},
{"name": "fk_albums_band", "constraint": "foreign", "columns": ["band_id"], "referenced_table": "bands", "referenced_columns": ["band_id"]}
]
},
{
"name": "bands",
"columns": [
{"name": "band_id", "_typname": "uuid", "_is_array": false, "is_nullable": false},
{"name": "name", "_typname": "text", "_is_array": false, "is_nullable": false},
{"name": "subgenre", "_typname": "rock_subgenre", "_is_array": false, "is_nullable": false}
],
"constraints": [
{"name": "bands_pkey", "constraint": "primary", "columns": ["band_id"]},
]
}
]
} as const satisfies DBSchema;
const b = createSqlBuilder(schema);
// SELECT with where/order/limit
const selectReq = b.table("bands")
.select(["name", "subgenre"]) // or omit to select all: .select()
.andWhere({ band_id: [1, 2, 3] }) // => "type" = ANY(:param)
.orderBy([["name","desc"]])
.limit(10)
// SELECT with join
const selectReq = b.table("bands")
.select(["name", "subgenre"])
.join("albums")
.selectFrom("albums", ["title", "critic_score", "release_date"])
.andWhereOp("name", "=", "Guns n’ Roses")
// If you need the type of the row, use the helper RowOfSelect
type Row = RowOfSelect<typeof selectReq>
const sql = new Sql();
const rows: Row[] = (await sql.exec(selectReq.toSql())).data;
// INSERT
const insertReq = b.table("albums")
.insert({ band_id: 1, title: "Appetite For Destruction", release_date: "1987-07-21." })
.returning(["id"])
await sql.exec(insertReq.toSql());
// UPDATE
const updateReq = b.table("albums")
.update({ critic_score: 10 })
.where({ album_id: 10 })
.returning(["title"])
await sql.exec(updateReq.toSql());
// DELETE
const deleteReq = b.table("albums")
.delete()
.where({ album_id: 11 })
await sql.exec(deleteReq.toSql());
The builder automatically adds type_hints for all parameters (e.g., int4[]).
RPC
Call JSON‑RPC methods exposed by Centia.io. See Methods for more details about the RPC API.
- 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 rows
Typing 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
// Don't write this by hand. Get the interfaces from /api/v4/interfaces
interface MyApi {
getBandById(params: {
band_id: number;
}): Promise<Array<{
name: Pgtypes.Varchar;
subgenre: Pgtypes.Varchar;
albums: PgTypes.PgArray<Pgtypes.Varchar>;
}>>;
}
// Create the API proxy
const api = createApi<MyApi>();
// If you need the type of the result, use the helper RowOfApiMethod
type Band = RowOfApiMethod<Api, "getBandById">
// Call the method
const bands: Band[] = await api.getAlbumsByBandId({
band_id: 1
});
console.log(bands); // typed row array
createApi<T>()relies on naming conventions: the property name is the JSON‑RPCmethodname.- Each call returns
result.datafrom the RPC response (array of rows).
JSON-RPC TypeScript interfaces
Instead of defining the interfaces yourself as shown above, you can use the API /api/v4/interfaces helper to get the interfaces for all RPC-JSON methods.
- Defined your RPC-JSON method. Input and output types are inferred from an actual request/response.
Consider this method definition:
{
"q": "select :x as unknown",
"method": "getX"
}
The values of x can not be inferred from the definition. So requesting /api/v4/interfaces will return:
export interface Api {
getX(params: Record<string, unknown>): Promise<Record<string, unknown>>;
}
- You will need to 'cast' the field 'x' to the correct type. This is not necessary for real columns in tables, but for the example above, you can cast the field to
int
{
"q": "select :x::int as my_int",
"method": "getX"
}
- Dry-run the request to set the types. Types are only inferred for dry-run requests. This will cache the types:
POST https://api.centia.io/api/v4/call
Content-Type: application/json
Accept: application/json; charset=utf-8
Authorization: Bearer {{token}}
X-Dry-Run: true
{
"jsonrpc": "2.0",
"method": "getX",
"params":{ "x": 1 },
"id": 1"
}
And /api/v4/interfaces will now return a typed interface:
export interface Api {
getX(params: { x: number; }): Promise<{ my_int: number; }[]>;
}
This is ready for import and use in your application:
export interface Api {
getX(params: { x: number; }): Promise<{ my_int: number; }[]>
}
import { createApi } from "@centia-io/sdk"
import type { Api } from "./api"
const api = createApi<Api>()
const res = await api.getX({ x: 1 })
console.log(res)
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