@centia-io/sdk 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +570 -570
- package/dist/centia-io-sdk.cjs +252 -115
- package/dist/centia-io-sdk.d.cts +117 -67
- package/dist/centia-io-sdk.d.cts.map +1 -1
- package/dist/centia-io-sdk.d.ts +117 -67
- package/dist/centia-io-sdk.d.ts.map +1 -1
- package/dist/centia-io-sdk.js +252 -116
- package/dist/centia-io-sdk.js.map +1 -1
- package/dist/centia-io-sdk.umd.js +1381 -1244
- package/package.json +16 -10
package/README.md
CHANGED
|
@@ -1,570 +1,570 @@
|
|
|
1
|
-
# SDK
|
|
2
|
-
|
|
3
|
-
TypeScript/JavaScript client SDK for Centia-io. It provides:
|
|
4
|
-
|
|
5
|
-
- Authentication helpers:
|
|
6
|
-
- CodeFlow (OAuth 2.0 Authorization Code + PKCE) for browser apps
|
|
7
|
-
- PasswordFlow for trusted/CLI/server environments
|
|
8
|
-
- Data access:
|
|
9
|
-
- Sql: Execute parameterized SQL
|
|
10
|
-
- Rpc: Call JSON-RPC methods
|
|
11
|
-
- createApi: A tiny type-safe helper that maps TypeScript interfaces to JSON‑RPC calls
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @centia-io/sdk
|
|
17
|
-
yarn add @centia-io/sdk
|
|
18
|
-
pnpm add @centia-io/sdk
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Or from CDN:
|
|
22
|
-
|
|
23
|
-
```html
|
|
24
|
-
<script src="https://cdn.jsdelivr.net/npm/@centia-io/sdk@latest/dist/centia-io-sdk.umd.js"></script>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Requirements:
|
|
28
|
-
- Browser or Node.js 18+ (for global `fetch`).
|
|
29
|
-
- An accessible Centia.io host URL and client credentials.
|
|
30
|
-
|
|
31
|
-
ESM import:
|
|
32
|
-
```ts
|
|
33
|
-
import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp, createSqlBuilder } from "@centia-io/sdk";
|
|
34
|
-
import type { RpcRequest, RpcResponse, PgTypes } from "@centia-io/sdk";
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Authentication
|
|
40
|
-
|
|
41
|
-
The SDK handles token storage and refresh for you.
|
|
42
|
-
- Tokens and minimal options are saved to `localStorage` in browsers. In non‑browser environments, an in‑memory store is used for the lifetime of the process.
|
|
43
|
-
- Authorization headers are added automatically for Sql/Rpc requests.
|
|
44
|
-
|
|
45
|
-
### CodeFlow (Browser, OAuth 2.0 Authorization Code + PKCE)
|
|
46
|
-
|
|
47
|
-
Use this flow in browser applications where you can redirect the user to the Centia-io login page.
|
|
48
|
-
|
|
49
|
-
Required options:
|
|
50
|
-
- `host`: Base URL of your Centia-io instance, e.g. `https://api.centia.io`
|
|
51
|
-
- `clientId`: OAuth client id configured in Centia.io
|
|
52
|
-
- `redirectUri`: The URL in your app that handles the redirect back from Centia.io (must be whitelisted)
|
|
53
|
-
- `scope` (optional): Not in use yet, but will be used to request additional permissions from the user.
|
|
54
|
-
|
|
55
|
-
Example (vanilla JS/TS + SPA):
|
|
56
|
-
```ts
|
|
57
|
-
import { CodeFlow } from "@centia-io/sdk";
|
|
58
|
-
|
|
59
|
-
const codeFlow = new CodeFlow({
|
|
60
|
-
host: "https://api.centia.io",
|
|
61
|
-
clientId: "your-client-id",
|
|
62
|
-
redirectUri: window.location.origin + "/auth/callback"
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// On app startup, call redirectHandle() once to complete a login redirect (if any)
|
|
66
|
-
codeFlow.redirectHandle().then((signedIn) => {
|
|
67
|
-
if (signedIn) {
|
|
68
|
-
console.log("User signed in");
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Start sign-in when user clicks Login
|
|
73
|
-
function onLoginClick() {
|
|
74
|
-
codeFlow.signIn(); // Redirects to GC2 auth page
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Sign out (clears tokens/options and redirects to signout endpoint)
|
|
78
|
-
function onLogoutClick() {
|
|
79
|
-
codeFlow.signOut();
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Notes:
|
|
84
|
-
- `redirectHandle()` detects errors from the auth server, validates `state` (CSRF protection), exchanges the `code` for tokens, performs `PKCE` (Proof Key for Code Exchange), stores tokens and cleans up the URL.
|
|
85
|
-
- `signOut()` clears local tokens/options and redirects to the sign-out URL. If you only need to clear local state without redirect, call `codeFlow.clear()`.
|
|
86
|
-
|
|
87
|
-
### PasswordFlow (Trusted environments, CLI/Server)
|
|
88
|
-
|
|
89
|
-
Use only in trusted environments. The user’s database credentials are exchanged directly for tokens.
|
|
90
|
-
|
|
91
|
-
Required options:
|
|
92
|
-
- `host`
|
|
93
|
-
- `clientId`
|
|
94
|
-
- `username`
|
|
95
|
-
- `password`
|
|
96
|
-
- `database`
|
|
97
|
-
|
|
98
|
-
Example (Node.js):
|
|
99
|
-
```ts
|
|
100
|
-
import { PasswordFlow } from "@centia-io/sdk";
|
|
101
|
-
|
|
102
|
-
const flow = new PasswordFlow({
|
|
103
|
-
host: "https://api.centia.io",
|
|
104
|
-
clientId: "your-client-id",
|
|
105
|
-
username: "your-username",
|
|
106
|
-
password: "your-password",
|
|
107
|
-
database: "parent-database" // The database to connect to. If superuser, this is the sam as username.
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await flow.signIn();
|
|
111
|
-
// Tokens are now stored; subsequent Sql/Rpc calls will include Authorization header.
|
|
112
|
-
|
|
113
|
-
// ... your code ...
|
|
114
|
-
|
|
115
|
-
flow.signOut(); // Clears tokens/options in local storage (no redirect)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### SignUp (Browser – Create a new user)
|
|
119
|
-
|
|
120
|
-
Use this helper in browser applications to redirect the user to the Centia‑io sign‑up page.
|
|
121
|
-
The user will create an account under a specified parent database/tenant and then be redirected back to your application.
|
|
122
|
-
|
|
123
|
-
Required options:
|
|
124
|
-
- host: Base URL of your Centia‑io instance, e.g. https://api.centia.io
|
|
125
|
-
- clientId: OAuth client id configured in Centia.io
|
|
126
|
-
- parentDb: The parent/tenant database under which the new user should be created
|
|
127
|
-
- redirectUri: URL in your app to return to after sign‑up
|
|
128
|
-
|
|
129
|
-
Example (vanilla JS/TS):
|
|
130
|
-
```ts
|
|
131
|
-
import { SignUp } from "@centia-io/sdk";
|
|
132
|
-
|
|
133
|
-
const signUp = new SignUp({
|
|
134
|
-
host: "https://api.centia.io",
|
|
135
|
-
clientId: "your-client-id",
|
|
136
|
-
parentDb: "your-parent-database",
|
|
137
|
-
redirectUri: window.location.origin + "/auth/callback"
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Start sign-up when the user clicks "Create account"
|
|
141
|
-
function onSignUpClick() {
|
|
142
|
-
signUp.signUp(); // Redirects to GC2 sign-up page
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Notes:
|
|
147
|
-
- Default endpoint is {host}/signup/. You can override with authUri if needed.
|
|
148
|
-
- 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.)
|
|
149
|
-
|
|
150
|
-
## SQL
|
|
151
|
-
|
|
152
|
-
Execute parameterized SQL against GC2.
|
|
153
|
-
|
|
154
|
-
- Class: `new Sql()`
|
|
155
|
-
- Method: `exec(request: SqlRequest): Promise<
|
|
156
|
-
- Endpoint: `POST https://api.centia.io/api/v4/sql`
|
|
157
|
-
|
|
158
|
-
Types (simplified):
|
|
159
|
-
- `SqlRequest` has:
|
|
160
|
-
- `q`: SQL string, you can use named placeholders like `:a` (server-side feature)
|
|
161
|
-
- `params?`: object with values for placeholders
|
|
162
|
-
- `type_hints?`: optional explicit type hints
|
|
163
|
-
- `type_formats?`: optional per-column format strings
|
|
164
|
-
- `
|
|
165
|
-
- `schema`: a map of column name -> `{ type: string, array: boolean }`
|
|
166
|
-
- `data`: an array of rows (records)
|
|
167
|
-
|
|
168
|
-
Example:
|
|
169
|
-
```ts
|
|
170
|
-
import { Sql } from "@centia-io/sdk";
|
|
171
|
-
|
|
172
|
-
const sql = new Sql();
|
|
173
|
-
|
|
174
|
-
const payload = {
|
|
175
|
-
a: 1,
|
|
176
|
-
b: "hello",
|
|
177
|
-
c: "3.14", // numeric/decimal values are strings
|
|
178
|
-
d: ["x", "y"], // arrays are supported
|
|
179
|
-
e: { nested: [1,2] } // JSON
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const res = await sql.exec({
|
|
183
|
-
q: "select :a::int as a, :b::varchar as b, :c::numeric as c, :d::varchar[] as d, :e::jsonb as e",
|
|
184
|
-
params: payload,
|
|
185
|
-
type_hints: { d: "varchar[]" } // Arrays are not inferred by default, and must be specified explicitly
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
console.log(res.schema); // { a: {type: 'int4', array: false}, ... }
|
|
189
|
-
console.log(res.data); // [{ a: 1, b: 'hello', c: '3.14', d: ['x','y'], e: {nested:[1,2]} }]
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Typing the rows:
|
|
193
|
-
```ts
|
|
194
|
-
import type { PgTypes } from "@centia-io/sdk";
|
|
195
|
-
|
|
196
|
-
interface Row extends PgTypes.DataRow {
|
|
197
|
-
a: number;
|
|
198
|
-
b: Pgtypes.Varchar;
|
|
199
|
-
c: PgTypes.NumericString;
|
|
200
|
-
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
201
|
-
e: PgTypes.JsonValue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// res: PgTypes.
|
|
205
|
-
const res = await sql.exec({ q: "...", params: payload }) as PgTypes.
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## SQL Builder
|
|
209
|
-
|
|
210
|
-
Build strongly typed SQL requests from a DB schema so you don't write raw SQL.
|
|
211
|
-
|
|
212
|
-
- Function: `createSqlBuilder(schema)`
|
|
213
|
-
- Types: `DBSchema`, `TableDef`, `ColumnDef`
|
|
214
|
-
- Supports: `select` (andWhere/orWhere, andWhereOp/orWhereOp, grouped predicates, orderBy, limit, offset, join, selectFrom), `insert(returning)`, `update(where, returning)`, `delete(where, returning)`
|
|
215
|
-
- Produces an object with `toSql(): SqlRequest` which you pass to `new Sql().exec()`
|
|
216
|
-
|
|
217
|
-
Example:
|
|
218
|
-
```ts
|
|
219
|
-
import { createSqlBuilder, Sql } from "@centia-io/sdk";
|
|
220
|
-
import type { DBSchema } from "@centia-io/sdk";
|
|
221
|
-
|
|
222
|
-
// Minimal schema (compatible with schema/schema.json).
|
|
223
|
-
const schema = {
|
|
224
|
-
name: "public",
|
|
225
|
-
tables: [
|
|
226
|
-
{
|
|
227
|
-
name: "items",
|
|
228
|
-
columns: [
|
|
229
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
230
|
-
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: true },
|
|
231
|
-
{ name: "type", _typname: "int4", _is_array: false, is_nullable: true }
|
|
232
|
-
]
|
|
233
|
-
}
|
|
234
|
-
]
|
|
235
|
-
} as const satisfies DBSchema;
|
|
236
|
-
|
|
237
|
-
const b = createSqlBuilder(schema);
|
|
238
|
-
|
|
239
|
-
// SELECT with where/order/limit
|
|
240
|
-
const selectReq = b.table("items")
|
|
241
|
-
.select(["id", "name"]) // or omit to select all: .select()
|
|
242
|
-
.andWhere({ type: [1, 2, 3] }) // => "type" = ANY(:param)
|
|
243
|
-
.orderBy([["id","desc"]])
|
|
244
|
-
.limit(10)
|
|
245
|
-
.toSql();
|
|
246
|
-
|
|
247
|
-
const sql = new Sql();
|
|
248
|
-
const rows = (await sql.exec(selectReq)).data;
|
|
249
|
-
|
|
250
|
-
// INSERT
|
|
251
|
-
const insertReq = b.table("items")
|
|
252
|
-
.insert({ id: 10, name: "Thing", type: 1 })
|
|
253
|
-
.returning(["id"])
|
|
254
|
-
.toSql();
|
|
255
|
-
await sql.exec(insertReq);
|
|
256
|
-
|
|
257
|
-
// UPDATE
|
|
258
|
-
const updateReq = b.table("items")
|
|
259
|
-
.update({ name: "Updated" })
|
|
260
|
-
.where({ id: 10 })
|
|
261
|
-
.returning(["id","name"])
|
|
262
|
-
.toSql();
|
|
263
|
-
await sql.exec(updateReq);
|
|
264
|
-
|
|
265
|
-
// DELETE
|
|
266
|
-
const deleteReq = b.table("items")
|
|
267
|
-
.delete()
|
|
268
|
-
.where({ id: 10 })
|
|
269
|
-
.toSql();
|
|
270
|
-
await sql.exec(deleteReq);
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Notes:
|
|
274
|
-
- The builder automatically adds `type_hints` for array parameters (e.g., `int4[]`), as arrays are not inferred by default by the server.
|
|
275
|
-
- Value types are inferred from `_typname` and `_is_array`. For `numeric/decimal`, use strings (`NumericString`).
|
|
276
|
-
- You can pass the same `SqlRequest` object to `Sql.exec`.
|
|
277
|
-
|
|
278
|
-
## RPC
|
|
279
|
-
|
|
280
|
-
Call JSON‑RPC methods exposed by GC2.
|
|
281
|
-
|
|
282
|
-
- Class: `new Rpc()`
|
|
283
|
-
- Method: `call(request: RpcRequest): Promise<RpcResponse>`
|
|
284
|
-
- Endpoint: `POST {host}/api/v4/call`
|
|
285
|
-
|
|
286
|
-
Types (simplified):
|
|
287
|
-
- `RpcRequest` has `jsonrpc: "2.0"`, `method`, optional `params`, optional `id`
|
|
288
|
-
- `RpcResponse` has `jsonrpc: "2.0"`, `id`, and `result` with `{ schema, data }`
|
|
289
|
-
|
|
290
|
-
Example:
|
|
291
|
-
```ts
|
|
292
|
-
import { Rpc } from "@centia-io/sdk";
|
|
293
|
-
|
|
294
|
-
const rpc = new Rpc();
|
|
295
|
-
|
|
296
|
-
const payload = { a: 1, b: "hello" };
|
|
297
|
-
|
|
298
|
-
const res = await rpc.call({
|
|
299
|
-
jsonrpc: "2.0",
|
|
300
|
-
method: "typeTest",
|
|
301
|
-
params: payload,
|
|
302
|
-
id: 1
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
console.log(res.result.schema);
|
|
306
|
-
console.log(res.result.data); // array of rows
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Typing the rows:
|
|
310
|
-
```ts
|
|
311
|
-
import type { PgTypes } from "@centia-io/sdk";
|
|
312
|
-
|
|
313
|
-
interface Row extends PgTypes.DataRow {
|
|
314
|
-
a: number;
|
|
315
|
-
b: string;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const res = await rpc.call({ jsonrpc: "2.0", method: "typeTest", params: payload }) as PgTypes.RpcResponse<Row>;
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## createApi
|
|
322
|
-
|
|
323
|
-
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.
|
|
324
|
-
|
|
325
|
-
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.
|
|
326
|
-
|
|
327
|
-
Example with typing:
|
|
328
|
-
```ts
|
|
329
|
-
import { createApi } from "@centia-io/sdk";
|
|
330
|
-
import type { PgTypes } from "@centia-io/sdk";
|
|
331
|
-
|
|
332
|
-
// Define the shape of your RPC methods and return types
|
|
333
|
-
interface MyApi {
|
|
334
|
-
typeTest(params: {
|
|
335
|
-
a: number;
|
|
336
|
-
b: Pgtypes.Varchar;
|
|
337
|
-
c: PgTypes.NumericString;
|
|
338
|
-
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
339
|
-
e: PgTypes.JsonValue;
|
|
340
|
-
}): Promise<Array<{
|
|
341
|
-
a: number;
|
|
342
|
-
b: Pgtypes.Varchar;
|
|
343
|
-
c: PgTypes.NumericString;
|
|
344
|
-
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
345
|
-
e: PgTypes.JsonValue;
|
|
346
|
-
}>>;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const api = createApi<MyApi>();
|
|
350
|
-
|
|
351
|
-
const rows = await api.typeTest({
|
|
352
|
-
a: 1,
|
|
353
|
-
b: "Hello world",
|
|
354
|
-
c: "3.4",
|
|
355
|
-
d: ["Hello", "world"],
|
|
356
|
-
e: { "x": [1,2,3,4,5,6,7,8,9,10] }
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
console.log(rows); // typed row array
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
Notes:
|
|
363
|
-
- `createApi<T>()` relies on naming conventions: the property name is the JSON‑RPC `method` name.
|
|
364
|
-
- Each call returns `result.data` from the RPC response (array of rows).
|
|
365
|
-
|
|
366
|
-
## Error handling
|
|
367
|
-
|
|
368
|
-
- Network/HTTP errors: thrown as `Error` with the status/body text when available.
|
|
369
|
-
- 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.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
## Environment details
|
|
373
|
-
|
|
374
|
-
- Storage: tokens/options stored in `localStorage` when available; otherwise a global in‑memory store is used (`globalThis.__gc2_memory_storage`).
|
|
375
|
-
- Fetch: Node.js 18+ recommended (includes native `fetch`). For older Node versions, add a Fetch polyfill.
|
|
376
|
-
|
|
377
|
-
## License
|
|
378
|
-
|
|
379
|
-
The SDK is licensed under [The MIT License](https://opensource.org/license/mit)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## Advanced SqlBuilder examples (developer guide)
|
|
385
|
-
|
|
386
|
-
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.
|
|
387
|
-
|
|
388
|
-
Setup (minimal schema with a foreign key for joins):
|
|
389
|
-
```ts
|
|
390
|
-
import { createSqlBuilder } from "@centia-io/sdk";
|
|
391
|
-
import type { DBSchema } from "@centia-io/sdk";
|
|
392
|
-
|
|
393
|
-
const schema = {
|
|
394
|
-
name: "public",
|
|
395
|
-
tables: [
|
|
396
|
-
{
|
|
397
|
-
name: "items",
|
|
398
|
-
columns: [
|
|
399
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
400
|
-
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: false },
|
|
401
|
-
{ name: "type", _typname: "int4", _is_array: false, is_nullable: false },
|
|
402
|
-
],
|
|
403
|
-
constraints: [
|
|
404
|
-
{ name: "items-pk", constraint: "primary", columns: ["id"] },
|
|
405
|
-
{
|
|
406
|
-
name: "items-type-fk",
|
|
407
|
-
constraint: "foreign",
|
|
408
|
-
columns: ["type"],
|
|
409
|
-
referenced_table: "item_types",
|
|
410
|
-
referenced_columns: ["id"],
|
|
411
|
-
},
|
|
412
|
-
],
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
name: "item_types",
|
|
416
|
-
columns: [
|
|
417
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
418
|
-
{ name: "type", _typname: "varchar", _is_array: false, is_nullable: true },
|
|
419
|
-
],
|
|
420
|
-
constraints: [{ name: "item_types-pk", constraint: "primary", columns: ["id"] }],
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
} as const satisfies DBSchema;
|
|
424
|
-
|
|
425
|
-
const b = createSqlBuilder(schema);
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
- Selecting all or specific columns
|
|
429
|
-
```ts
|
|
430
|
-
b.table("items").select().toSql();
|
|
431
|
-
// select "items".* from "public"."items"
|
|
432
|
-
|
|
433
|
-
b.table("items").select(["id", "name"]).toSql();
|
|
434
|
-
// select "items"."id", "items"."name" from "public"."items"
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
- AND filters (equality and arrays -> ANY)
|
|
438
|
-
```ts
|
|
439
|
-
b.table("items").select()
|
|
440
|
-
.andWhere({ id: 3, type: [1,2,3] })
|
|
441
|
-
.toSql();
|
|
442
|
-
// where "items"."id" = :items_id_1 and "items"."type" = ANY(:items_type_2)
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
- OR filters (object groups)
|
|
446
|
-
```ts
|
|
447
|
-
b.table("items").select()
|
|
448
|
-
.orWhere({ id: 1 })
|
|
449
|
-
.orWhere({ id: 2 })
|
|
450
|
-
.toSql();
|
|
451
|
-
// where ("items"."id" = :items_id_1) or ("items"."id" = :items_id_2)
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
- Operator predicates: comparisons, LIKE variants, IN/NOT IN, NULL checks
|
|
455
|
-
```ts
|
|
456
|
-
b.table("items").select()
|
|
457
|
-
.andWhereOp("id", ">", 10)
|
|
458
|
-
.andWhereOp("name", "ilike", "%foo%")
|
|
459
|
-
.andWhereOp("type", "in", [1,2])
|
|
460
|
-
.andWhereOp("name", "isnull")
|
|
461
|
-
.toSql();
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
- Grouped predicates and OR chains
|
|
465
|
-
```ts
|
|
466
|
-
b.table("items").select()
|
|
467
|
-
.andWhereOpGroup([
|
|
468
|
-
["type", "in", [1,2]],
|
|
469
|
-
["id", ">=", 10],
|
|
470
|
-
])
|
|
471
|
-
.orWhereOpGroup([["name", "ilike", "%foo%"]])
|
|
472
|
-
.orWhereOpGroup([["name", "ilike", "%bar%"], ["id", "<", 50]])
|
|
473
|
-
.toSql();
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
- JOIN by foreign key + selecting from the joined table
|
|
477
|
-
```ts
|
|
478
|
-
// Auto-detects ON using FK items.type -> item_types.id
|
|
479
|
-
b.table("items").select(["id","name"]).join("item_types").toSql();
|
|
480
|
-
// select ... from "public"."items" inner join "public"."item_types" on "items"."type" = "item_types"."id"
|
|
481
|
-
|
|
482
|
-
// Select specific columns from joined table
|
|
483
|
-
b.table("items")
|
|
484
|
-
.select(["id"]) // base table columns
|
|
485
|
-
.join("item_types", "left") // join type: inner|left|right|full
|
|
486
|
-
.selectFrom("item_types", ["type"]) // joined table columns
|
|
487
|
-
.toSql();
|
|
488
|
-
|
|
489
|
-
// Select all columns from the joined table
|
|
490
|
-
b.table("items").select(["id"]).join("item_types").selectFrom("item_types").toSql();
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
- ORDER BY, LIMIT, OFFSET
|
|
494
|
-
```ts
|
|
495
|
-
b.table("items").select().orderBy("id").toSql();
|
|
496
|
-
// order by "items"."id" asc
|
|
497
|
-
|
|
498
|
-
b.table("items").select().orderBy([["type","desc"],["id","asc"]]).toSql();
|
|
499
|
-
|
|
500
|
-
b.table("items").select().limit(25).offset(50).toSql();
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
- INSERT, UPDATE, DELETE
|
|
504
|
-
```ts
|
|
505
|
-
b.table("items").insert({ id: 1, name: "A", type: 1 }).returning(["id"]).toSql();
|
|
506
|
-
|
|
507
|
-
b.table("items").update({ name: "B" }).where({ id: 1 }).returning(["id","name"]).toSql();
|
|
508
|
-
|
|
509
|
-
b.table("items").delete().where({ id: 1 }).toSql();
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
- Special value types (ranges, intervals, geometry) – supported at compile‑time and runtime
|
|
513
|
-
```ts
|
|
514
|
-
// Ranges (e.g., tstzrange)
|
|
515
|
-
const events = {
|
|
516
|
-
name: "public",
|
|
517
|
-
tables: [{
|
|
518
|
-
name: "events",
|
|
519
|
-
columns: [
|
|
520
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
521
|
-
{ name: "period", _typname: "tstzrange", _is_array: false, is_nullable: true },
|
|
522
|
-
]
|
|
523
|
-
}]
|
|
524
|
-
} as const satisfies DBSchema;
|
|
525
|
-
|
|
526
|
-
createSqlBuilder(events).table("events").select().andWhere({
|
|
527
|
-
period: {
|
|
528
|
-
lower: "2024-01-01T00:00:00+00:00",
|
|
529
|
-
upper: "2024-12-31T23:59:59+00:00",
|
|
530
|
-
lowerInclusive: true,
|
|
531
|
-
upperInclusive: false,
|
|
532
|
-
}
|
|
533
|
-
}).toSql();
|
|
534
|
-
|
|
535
|
-
// Interval
|
|
536
|
-
const durations = {
|
|
537
|
-
name: "public",
|
|
538
|
-
tables: [{
|
|
539
|
-
name: "durations",
|
|
540
|
-
columns: [
|
|
541
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
542
|
-
{ name: "duration", _typname: "interval", _is_array: false, is_nullable: true },
|
|
543
|
-
]
|
|
544
|
-
}]
|
|
545
|
-
} as const satisfies DBSchema;
|
|
546
|
-
|
|
547
|
-
createSqlBuilder(durations).table("durations").select().andWhere({
|
|
548
|
-
duration: { y: 0, m: 1, d: 0, h: 2, i: 0, s: 0 }
|
|
549
|
-
}).toSql();
|
|
550
|
-
|
|
551
|
-
// Geometry (point example)
|
|
552
|
-
const shapes = {
|
|
553
|
-
name: "public",
|
|
554
|
-
tables: [{
|
|
555
|
-
name: "shapes",
|
|
556
|
-
columns: [
|
|
557
|
-
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
558
|
-
{ name: "pt", _typname: "point", _is_array: false, is_nullable: true },
|
|
559
|
-
]
|
|
560
|
-
}]
|
|
561
|
-
} as const satisfies DBSchema;
|
|
562
|
-
|
|
563
|
-
createSqlBuilder(shapes).table("shapes").select().andWhere({ pt: { x: 1, y: 2 } }).toSql();
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
Notes and tips:
|
|
567
|
-
- All SQL is schema‑qualified: from "schema"."table" and in JOINs.
|
|
568
|
-
- 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).
|
|
569
|
-
- 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.
|
|
570
|
-
- For more, see the full script in examples/test_builder.ts which prints the generated SQL and parameters for dozens of cases.
|
|
1
|
+
# SDK
|
|
2
|
+
|
|
3
|
+
TypeScript/JavaScript client SDK for Centia-io. It provides:
|
|
4
|
+
|
|
5
|
+
- Authentication helpers:
|
|
6
|
+
- CodeFlow (OAuth 2.0 Authorization Code + PKCE) for browser apps
|
|
7
|
+
- PasswordFlow for trusted/CLI/server environments
|
|
8
|
+
- Data access:
|
|
9
|
+
- Sql: Execute parameterized SQL
|
|
10
|
+
- Rpc: Call JSON-RPC methods
|
|
11
|
+
- createApi: A tiny type-safe helper that maps TypeScript interfaces to JSON‑RPC calls
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @centia-io/sdk
|
|
17
|
+
yarn add @centia-io/sdk
|
|
18
|
+
pnpm add @centia-io/sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or from CDN:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/@centia-io/sdk@latest/dist/centia-io-sdk.umd.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requirements:
|
|
28
|
+
- Browser or Node.js 18+ (for global `fetch`).
|
|
29
|
+
- An accessible Centia.io host URL and client credentials.
|
|
30
|
+
|
|
31
|
+
ESM import:
|
|
32
|
+
```ts
|
|
33
|
+
import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp, createSqlBuilder } from "@centia-io/sdk";
|
|
34
|
+
import type { RpcRequest, RpcResponse, PgTypes } from "@centia-io/sdk";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Authentication
|
|
40
|
+
|
|
41
|
+
The SDK handles token storage and refresh for you.
|
|
42
|
+
- Tokens and minimal options are saved to `localStorage` in browsers. In non‑browser environments, an in‑memory store is used for the lifetime of the process.
|
|
43
|
+
- Authorization headers are added automatically for Sql/Rpc requests.
|
|
44
|
+
|
|
45
|
+
### CodeFlow (Browser, OAuth 2.0 Authorization Code + PKCE)
|
|
46
|
+
|
|
47
|
+
Use this flow in browser applications where you can redirect the user to the Centia-io login page.
|
|
48
|
+
|
|
49
|
+
Required options:
|
|
50
|
+
- `host`: Base URL of your Centia-io instance, e.g. `https://api.centia.io`
|
|
51
|
+
- `clientId`: OAuth client id configured in Centia.io
|
|
52
|
+
- `redirectUri`: The URL in your app that handles the redirect back from Centia.io (must be whitelisted)
|
|
53
|
+
- `scope` (optional): Not in use yet, but will be used to request additional permissions from the user.
|
|
54
|
+
|
|
55
|
+
Example (vanilla JS/TS + SPA):
|
|
56
|
+
```ts
|
|
57
|
+
import { CodeFlow } from "@centia-io/sdk";
|
|
58
|
+
|
|
59
|
+
const codeFlow = new CodeFlow({
|
|
60
|
+
host: "https://api.centia.io",
|
|
61
|
+
clientId: "your-client-id",
|
|
62
|
+
redirectUri: window.location.origin + "/auth/callback"
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// On app startup, call redirectHandle() once to complete a login redirect (if any)
|
|
66
|
+
codeFlow.redirectHandle().then((signedIn) => {
|
|
67
|
+
if (signedIn) {
|
|
68
|
+
console.log("User signed in");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Start sign-in when user clicks Login
|
|
73
|
+
function onLoginClick() {
|
|
74
|
+
codeFlow.signIn(); // Redirects to GC2 auth page
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Sign out (clears tokens/options and redirects to signout endpoint)
|
|
78
|
+
function onLogoutClick() {
|
|
79
|
+
codeFlow.signOut();
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Notes:
|
|
84
|
+
- `redirectHandle()` detects errors from the auth server, validates `state` (CSRF protection), exchanges the `code` for tokens, performs `PKCE` (Proof Key for Code Exchange), stores tokens and cleans up the URL.
|
|
85
|
+
- `signOut()` clears local tokens/options and redirects to the sign-out URL. If you only need to clear local state without redirect, call `codeFlow.clear()`.
|
|
86
|
+
|
|
87
|
+
### PasswordFlow (Trusted environments, CLI/Server)
|
|
88
|
+
|
|
89
|
+
Use only in trusted environments. The user’s database credentials are exchanged directly for tokens.
|
|
90
|
+
|
|
91
|
+
Required options:
|
|
92
|
+
- `host`
|
|
93
|
+
- `clientId`
|
|
94
|
+
- `username`
|
|
95
|
+
- `password`
|
|
96
|
+
- `database`
|
|
97
|
+
|
|
98
|
+
Example (Node.js):
|
|
99
|
+
```ts
|
|
100
|
+
import { PasswordFlow } from "@centia-io/sdk";
|
|
101
|
+
|
|
102
|
+
const flow = new PasswordFlow({
|
|
103
|
+
host: "https://api.centia.io",
|
|
104
|
+
clientId: "your-client-id",
|
|
105
|
+
username: "your-username",
|
|
106
|
+
password: "your-password",
|
|
107
|
+
database: "parent-database" // The database to connect to. If superuser, this is the sam as username.
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await flow.signIn();
|
|
111
|
+
// Tokens are now stored; subsequent Sql/Rpc calls will include Authorization header.
|
|
112
|
+
|
|
113
|
+
// ... your code ...
|
|
114
|
+
|
|
115
|
+
flow.signOut(); // Clears tokens/options in local storage (no redirect)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### SignUp (Browser – Create a new user)
|
|
119
|
+
|
|
120
|
+
Use this helper in browser applications to redirect the user to the Centia‑io sign‑up page.
|
|
121
|
+
The user will create an account under a specified parent database/tenant and then be redirected back to your application.
|
|
122
|
+
|
|
123
|
+
Required options:
|
|
124
|
+
- host: Base URL of your Centia‑io instance, e.g. https://api.centia.io
|
|
125
|
+
- clientId: OAuth client id configured in Centia.io
|
|
126
|
+
- parentDb: The parent/tenant database under which the new user should be created
|
|
127
|
+
- redirectUri: URL in your app to return to after sign‑up
|
|
128
|
+
|
|
129
|
+
Example (vanilla JS/TS):
|
|
130
|
+
```ts
|
|
131
|
+
import { SignUp } from "@centia-io/sdk";
|
|
132
|
+
|
|
133
|
+
const signUp = new SignUp({
|
|
134
|
+
host: "https://api.centia.io",
|
|
135
|
+
clientId: "your-client-id",
|
|
136
|
+
parentDb: "your-parent-database",
|
|
137
|
+
redirectUri: window.location.origin + "/auth/callback"
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Start sign-up when the user clicks "Create account"
|
|
141
|
+
function onSignUpClick() {
|
|
142
|
+
signUp.signUp(); // Redirects to GC2 sign-up page
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Notes:
|
|
147
|
+
- Default endpoint is {host}/signup/. You can override with authUri if needed.
|
|
148
|
+
- 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.)
|
|
149
|
+
|
|
150
|
+
## SQL
|
|
151
|
+
|
|
152
|
+
Execute parameterized SQL against GC2.
|
|
153
|
+
|
|
154
|
+
- Class: `new Sql()`
|
|
155
|
+
- Method: `exec(request: SqlRequest): Promise<SqlResponse>`
|
|
156
|
+
- Endpoint: `POST https://api.centia.io/api/v4/sql`
|
|
157
|
+
|
|
158
|
+
Types (simplified):
|
|
159
|
+
- `SqlRequest` has:
|
|
160
|
+
- `q`: SQL string, you can use named placeholders like `:a` (server-side feature)
|
|
161
|
+
- `params?`: object with values for placeholders
|
|
162
|
+
- `type_hints?`: optional explicit type hints
|
|
163
|
+
- `type_formats?`: optional per-column format strings
|
|
164
|
+
- `SqlResponse` has:
|
|
165
|
+
- `schema`: a map of column name -> `{ type: string, array: boolean }`
|
|
166
|
+
- `data`: an array of rows (records)
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
```ts
|
|
170
|
+
import { Sql } from "@centia-io/sdk";
|
|
171
|
+
|
|
172
|
+
const sql = new Sql();
|
|
173
|
+
|
|
174
|
+
const payload = {
|
|
175
|
+
a: 1,
|
|
176
|
+
b: "hello",
|
|
177
|
+
c: "3.14", // numeric/decimal values are strings
|
|
178
|
+
d: ["x", "y"], // arrays are supported
|
|
179
|
+
e: { nested: [1,2] } // JSON
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const res = await sql.exec({
|
|
183
|
+
q: "select :a::int as a, :b::varchar as b, :c::numeric as c, :d::varchar[] as d, :e::jsonb as e",
|
|
184
|
+
params: payload,
|
|
185
|
+
type_hints: { d: "varchar[]" } // Arrays are not inferred by default, and must be specified explicitly
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log(res.schema); // { a: {type: 'int4', array: false}, ... }
|
|
189
|
+
console.log(res.data); // [{ a: 1, b: 'hello', c: '3.14', d: ['x','y'], e: {nested:[1,2]} }]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Typing the rows:
|
|
193
|
+
```ts
|
|
194
|
+
import type { PgTypes } from "@centia-io/sdk";
|
|
195
|
+
|
|
196
|
+
interface Row extends PgTypes.DataRow {
|
|
197
|
+
a: number;
|
|
198
|
+
b: Pgtypes.Varchar;
|
|
199
|
+
c: PgTypes.NumericString;
|
|
200
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
201
|
+
e: PgTypes.JsonValue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// res: PgTypes.SqlResponse<Row>
|
|
205
|
+
const res = await sql.exec({ q: "...", params: payload }) as PgTypes.SqlResponse<Row>;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## SQL Builder
|
|
209
|
+
|
|
210
|
+
Build strongly typed SQL requests from a DB schema so you don't write raw SQL.
|
|
211
|
+
|
|
212
|
+
- Function: `createSqlBuilder(schema)`
|
|
213
|
+
- Types: `DBSchema`, `TableDef`, `ColumnDef`
|
|
214
|
+
- Supports: `select` (andWhere/orWhere, andWhereOp/orWhereOp, grouped predicates, orderBy, limit, offset, join, selectFrom), `insert(returning)`, `update(where, returning)`, `delete(where, returning)`
|
|
215
|
+
- Produces an object with `toSql(): SqlRequest` which you pass to `new Sql().exec()`
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
```ts
|
|
219
|
+
import { createSqlBuilder, Sql } from "@centia-io/sdk";
|
|
220
|
+
import type { DBSchema } from "@centia-io/sdk";
|
|
221
|
+
|
|
222
|
+
// Minimal schema (compatible with schema/schema.json).
|
|
223
|
+
const schema = {
|
|
224
|
+
name: "public",
|
|
225
|
+
tables: [
|
|
226
|
+
{
|
|
227
|
+
name: "items",
|
|
228
|
+
columns: [
|
|
229
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
230
|
+
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: true },
|
|
231
|
+
{ name: "type", _typname: "int4", _is_array: false, is_nullable: true }
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
} as const satisfies DBSchema;
|
|
236
|
+
|
|
237
|
+
const b = createSqlBuilder(schema);
|
|
238
|
+
|
|
239
|
+
// SELECT with where/order/limit
|
|
240
|
+
const selectReq = b.table("items")
|
|
241
|
+
.select(["id", "name"]) // or omit to select all: .select()
|
|
242
|
+
.andWhere({ type: [1, 2, 3] }) // => "type" = ANY(:param)
|
|
243
|
+
.orderBy([["id","desc"]])
|
|
244
|
+
.limit(10)
|
|
245
|
+
.toSql();
|
|
246
|
+
|
|
247
|
+
const sql = new Sql();
|
|
248
|
+
const rows = (await sql.exec(selectReq)).data;
|
|
249
|
+
|
|
250
|
+
// INSERT
|
|
251
|
+
const insertReq = b.table("items")
|
|
252
|
+
.insert({ id: 10, name: "Thing", type: 1 })
|
|
253
|
+
.returning(["id"])
|
|
254
|
+
.toSql();
|
|
255
|
+
await sql.exec(insertReq);
|
|
256
|
+
|
|
257
|
+
// UPDATE
|
|
258
|
+
const updateReq = b.table("items")
|
|
259
|
+
.update({ name: "Updated" })
|
|
260
|
+
.where({ id: 10 })
|
|
261
|
+
.returning(["id","name"])
|
|
262
|
+
.toSql();
|
|
263
|
+
await sql.exec(updateReq);
|
|
264
|
+
|
|
265
|
+
// DELETE
|
|
266
|
+
const deleteReq = b.table("items")
|
|
267
|
+
.delete()
|
|
268
|
+
.where({ id: 10 })
|
|
269
|
+
.toSql();
|
|
270
|
+
await sql.exec(deleteReq);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Notes:
|
|
274
|
+
- The builder automatically adds `type_hints` for array parameters (e.g., `int4[]`), as arrays are not inferred by default by the server.
|
|
275
|
+
- Value types are inferred from `_typname` and `_is_array`. For `numeric/decimal`, use strings (`NumericString`).
|
|
276
|
+
- You can pass the same `SqlRequest` object to `Sql.exec`.
|
|
277
|
+
|
|
278
|
+
## RPC
|
|
279
|
+
|
|
280
|
+
Call JSON‑RPC methods exposed by GC2.
|
|
281
|
+
|
|
282
|
+
- Class: `new Rpc()`
|
|
283
|
+
- Method: `call(request: RpcRequest): Promise<RpcResponse>`
|
|
284
|
+
- Endpoint: `POST {host}/api/v4/call`
|
|
285
|
+
|
|
286
|
+
Types (simplified):
|
|
287
|
+
- `RpcRequest` has `jsonrpc: "2.0"`, `method`, optional `params`, optional `id`
|
|
288
|
+
- `RpcResponse` has `jsonrpc: "2.0"`, `id`, and `result` with `{ schema, data }`
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
```ts
|
|
292
|
+
import { Rpc } from "@centia-io/sdk";
|
|
293
|
+
|
|
294
|
+
const rpc = new Rpc();
|
|
295
|
+
|
|
296
|
+
const payload = { a: 1, b: "hello" };
|
|
297
|
+
|
|
298
|
+
const res = await rpc.call({
|
|
299
|
+
jsonrpc: "2.0",
|
|
300
|
+
method: "typeTest",
|
|
301
|
+
params: payload,
|
|
302
|
+
id: 1
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
console.log(res.result.schema);
|
|
306
|
+
console.log(res.result.data); // array of rows
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Typing the rows:
|
|
310
|
+
```ts
|
|
311
|
+
import type { PgTypes } from "@centia-io/sdk";
|
|
312
|
+
|
|
313
|
+
interface Row extends PgTypes.DataRow {
|
|
314
|
+
a: number;
|
|
315
|
+
b: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const res = await rpc.call({ jsonrpc: "2.0", method: "typeTest", params: payload }) as PgTypes.RpcResponse<Row>;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## createApi
|
|
322
|
+
|
|
323
|
+
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.
|
|
324
|
+
|
|
325
|
+
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.
|
|
326
|
+
|
|
327
|
+
Example with typing:
|
|
328
|
+
```ts
|
|
329
|
+
import { createApi } from "@centia-io/sdk";
|
|
330
|
+
import type { PgTypes } from "@centia-io/sdk";
|
|
331
|
+
|
|
332
|
+
// Define the shape of your RPC methods and return types
|
|
333
|
+
interface MyApi {
|
|
334
|
+
typeTest(params: {
|
|
335
|
+
a: number;
|
|
336
|
+
b: Pgtypes.Varchar;
|
|
337
|
+
c: PgTypes.NumericString;
|
|
338
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
339
|
+
e: PgTypes.JsonValue;
|
|
340
|
+
}): Promise<Array<{
|
|
341
|
+
a: number;
|
|
342
|
+
b: Pgtypes.Varchar;
|
|
343
|
+
c: PgTypes.NumericString;
|
|
344
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
345
|
+
e: PgTypes.JsonValue;
|
|
346
|
+
}>>;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const api = createApi<MyApi>();
|
|
350
|
+
|
|
351
|
+
const rows = await api.typeTest({
|
|
352
|
+
a: 1,
|
|
353
|
+
b: "Hello world",
|
|
354
|
+
c: "3.4",
|
|
355
|
+
d: ["Hello", "world"],
|
|
356
|
+
e: { "x": [1,2,3,4,5,6,7,8,9,10] }
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
console.log(rows); // typed row array
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Notes:
|
|
363
|
+
- `createApi<T>()` relies on naming conventions: the property name is the JSON‑RPC `method` name.
|
|
364
|
+
- Each call returns `result.data` from the RPC response (array of rows).
|
|
365
|
+
|
|
366
|
+
## Error handling
|
|
367
|
+
|
|
368
|
+
- Network/HTTP errors: thrown as `Error` with the status/body text when available.
|
|
369
|
+
- 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.
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
## Environment details
|
|
373
|
+
|
|
374
|
+
- Storage: tokens/options stored in `localStorage` when available; otherwise a global in‑memory store is used (`globalThis.__gc2_memory_storage`).
|
|
375
|
+
- Fetch: Node.js 18+ recommended (includes native `fetch`). For older Node versions, add a Fetch polyfill.
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
The SDK is licensed under [The MIT License](https://opensource.org/license/mit)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Advanced SqlBuilder examples (developer guide)
|
|
385
|
+
|
|
386
|
+
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.
|
|
387
|
+
|
|
388
|
+
Setup (minimal schema with a foreign key for joins):
|
|
389
|
+
```ts
|
|
390
|
+
import { createSqlBuilder } from "@centia-io/sdk";
|
|
391
|
+
import type { DBSchema } from "@centia-io/sdk";
|
|
392
|
+
|
|
393
|
+
const schema = {
|
|
394
|
+
name: "public",
|
|
395
|
+
tables: [
|
|
396
|
+
{
|
|
397
|
+
name: "items",
|
|
398
|
+
columns: [
|
|
399
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
400
|
+
{ name: "name", _typname: "varchar", _is_array: false, is_nullable: false },
|
|
401
|
+
{ name: "type", _typname: "int4", _is_array: false, is_nullable: false },
|
|
402
|
+
],
|
|
403
|
+
constraints: [
|
|
404
|
+
{ name: "items-pk", constraint: "primary", columns: ["id"] },
|
|
405
|
+
{
|
|
406
|
+
name: "items-type-fk",
|
|
407
|
+
constraint: "foreign",
|
|
408
|
+
columns: ["type"],
|
|
409
|
+
referenced_table: "item_types",
|
|
410
|
+
referenced_columns: ["id"],
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "item_types",
|
|
416
|
+
columns: [
|
|
417
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
418
|
+
{ name: "type", _typname: "varchar", _is_array: false, is_nullable: true },
|
|
419
|
+
],
|
|
420
|
+
constraints: [{ name: "item_types-pk", constraint: "primary", columns: ["id"] }],
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
} as const satisfies DBSchema;
|
|
424
|
+
|
|
425
|
+
const b = createSqlBuilder(schema);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
- Selecting all or specific columns
|
|
429
|
+
```ts
|
|
430
|
+
b.table("items").select().toSql();
|
|
431
|
+
// select "items".* from "public"."items"
|
|
432
|
+
|
|
433
|
+
b.table("items").select(["id", "name"]).toSql();
|
|
434
|
+
// select "items"."id", "items"."name" from "public"."items"
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
- AND filters (equality and arrays -> ANY)
|
|
438
|
+
```ts
|
|
439
|
+
b.table("items").select()
|
|
440
|
+
.andWhere({ id: 3, type: [1,2,3] })
|
|
441
|
+
.toSql();
|
|
442
|
+
// where "items"."id" = :items_id_1 and "items"."type" = ANY(:items_type_2)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
- OR filters (object groups)
|
|
446
|
+
```ts
|
|
447
|
+
b.table("items").select()
|
|
448
|
+
.orWhere({ id: 1 })
|
|
449
|
+
.orWhere({ id: 2 })
|
|
450
|
+
.toSql();
|
|
451
|
+
// where ("items"."id" = :items_id_1) or ("items"."id" = :items_id_2)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- Operator predicates: comparisons, LIKE variants, IN/NOT IN, NULL checks
|
|
455
|
+
```ts
|
|
456
|
+
b.table("items").select()
|
|
457
|
+
.andWhereOp("id", ">", 10)
|
|
458
|
+
.andWhereOp("name", "ilike", "%foo%")
|
|
459
|
+
.andWhereOp("type", "in", [1,2])
|
|
460
|
+
.andWhereOp("name", "isnull")
|
|
461
|
+
.toSql();
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
- Grouped predicates and OR chains
|
|
465
|
+
```ts
|
|
466
|
+
b.table("items").select()
|
|
467
|
+
.andWhereOpGroup([
|
|
468
|
+
["type", "in", [1,2]],
|
|
469
|
+
["id", ">=", 10],
|
|
470
|
+
])
|
|
471
|
+
.orWhereOpGroup([["name", "ilike", "%foo%"]])
|
|
472
|
+
.orWhereOpGroup([["name", "ilike", "%bar%"], ["id", "<", 50]])
|
|
473
|
+
.toSql();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
- JOIN by foreign key + selecting from the joined table
|
|
477
|
+
```ts
|
|
478
|
+
// Auto-detects ON using FK items.type -> item_types.id
|
|
479
|
+
b.table("items").select(["id","name"]).join("item_types").toSql();
|
|
480
|
+
// select ... from "public"."items" inner join "public"."item_types" on "items"."type" = "item_types"."id"
|
|
481
|
+
|
|
482
|
+
// Select specific columns from joined table
|
|
483
|
+
b.table("items")
|
|
484
|
+
.select(["id"]) // base table columns
|
|
485
|
+
.join("item_types", "left") // join type: inner|left|right|full
|
|
486
|
+
.selectFrom("item_types", ["type"]) // joined table columns
|
|
487
|
+
.toSql();
|
|
488
|
+
|
|
489
|
+
// Select all columns from the joined table
|
|
490
|
+
b.table("items").select(["id"]).join("item_types").selectFrom("item_types").toSql();
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
- ORDER BY, LIMIT, OFFSET
|
|
494
|
+
```ts
|
|
495
|
+
b.table("items").select().orderBy("id").toSql();
|
|
496
|
+
// order by "items"."id" asc
|
|
497
|
+
|
|
498
|
+
b.table("items").select().orderBy([["type","desc"],["id","asc"]]).toSql();
|
|
499
|
+
|
|
500
|
+
b.table("items").select().limit(25).offset(50).toSql();
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
- INSERT, UPDATE, DELETE
|
|
504
|
+
```ts
|
|
505
|
+
b.table("items").insert({ id: 1, name: "A", type: 1 }).returning(["id"]).toSql();
|
|
506
|
+
|
|
507
|
+
b.table("items").update({ name: "B" }).where({ id: 1 }).returning(["id","name"]).toSql();
|
|
508
|
+
|
|
509
|
+
b.table("items").delete().where({ id: 1 }).toSql();
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
- Special value types (ranges, intervals, geometry) – supported at compile‑time and runtime
|
|
513
|
+
```ts
|
|
514
|
+
// Ranges (e.g., tstzrange)
|
|
515
|
+
const events = {
|
|
516
|
+
name: "public",
|
|
517
|
+
tables: [{
|
|
518
|
+
name: "events",
|
|
519
|
+
columns: [
|
|
520
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
521
|
+
{ name: "period", _typname: "tstzrange", _is_array: false, is_nullable: true },
|
|
522
|
+
]
|
|
523
|
+
}]
|
|
524
|
+
} as const satisfies DBSchema;
|
|
525
|
+
|
|
526
|
+
createSqlBuilder(events).table("events").select().andWhere({
|
|
527
|
+
period: {
|
|
528
|
+
lower: "2024-01-01T00:00:00+00:00",
|
|
529
|
+
upper: "2024-12-31T23:59:59+00:00",
|
|
530
|
+
lowerInclusive: true,
|
|
531
|
+
upperInclusive: false,
|
|
532
|
+
}
|
|
533
|
+
}).toSql();
|
|
534
|
+
|
|
535
|
+
// Interval
|
|
536
|
+
const durations = {
|
|
537
|
+
name: "public",
|
|
538
|
+
tables: [{
|
|
539
|
+
name: "durations",
|
|
540
|
+
columns: [
|
|
541
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
542
|
+
{ name: "duration", _typname: "interval", _is_array: false, is_nullable: true },
|
|
543
|
+
]
|
|
544
|
+
}]
|
|
545
|
+
} as const satisfies DBSchema;
|
|
546
|
+
|
|
547
|
+
createSqlBuilder(durations).table("durations").select().andWhere({
|
|
548
|
+
duration: { y: 0, m: 1, d: 0, h: 2, i: 0, s: 0 }
|
|
549
|
+
}).toSql();
|
|
550
|
+
|
|
551
|
+
// Geometry (point example)
|
|
552
|
+
const shapes = {
|
|
553
|
+
name: "public",
|
|
554
|
+
tables: [{
|
|
555
|
+
name: "shapes",
|
|
556
|
+
columns: [
|
|
557
|
+
{ name: "id", _typname: "int4", _is_array: false, is_nullable: false },
|
|
558
|
+
{ name: "pt", _typname: "point", _is_array: false, is_nullable: true },
|
|
559
|
+
]
|
|
560
|
+
}]
|
|
561
|
+
} as const satisfies DBSchema;
|
|
562
|
+
|
|
563
|
+
createSqlBuilder(shapes).table("shapes").select().andWhere({ pt: { x: 1, y: 2 } }).toSql();
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Notes and tips:
|
|
567
|
+
- All SQL is schema‑qualified: from "schema"."table" and in JOINs.
|
|
568
|
+
- 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).
|
|
569
|
+
- 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.
|
|
570
|
+
- For more, see the full script in examples/test_builder.ts which prints the generated SQL and parameters for dozens of cases.
|