@centia-io/sdk 0.0.28 → 0.0.30
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/README.md +340 -58
- package/dist/centia-io-sdk.cjs +837 -7
- package/dist/centia-io-sdk.d.cts +131 -3
- package/dist/centia-io-sdk.d.cts.map +1 -1
- package/dist/centia-io-sdk.d.ts +131 -3
- package/dist/centia-io-sdk.d.ts.map +1 -1
- package/dist/centia-io-sdk.js +835 -7
- package/dist/centia-io-sdk.js.map +1 -1
- package/dist/centia-io-sdk.umd.js +836 -6
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SDK
|
|
2
2
|
|
|
3
|
-
TypeScript/JavaScript client SDK for Centia
|
|
3
|
+
TypeScript/JavaScript client SDK for Centia-io. It provides:
|
|
4
4
|
|
|
5
5
|
- Authentication helpers:
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
- CodeFlow (OAuth 2.0 Authorization Code + PKCE) for browser apps
|
|
7
|
+
- PasswordFlow for trusted/CLI/server environments
|
|
8
8
|
- Data access:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
This README focuses on CodeFlow, PasswordFlow, Sql, Rpc and Api.ts (createApi).
|
|
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
|
|
14
12
|
|
|
15
13
|
## Installation
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
```
|
|
20
26
|
|
|
21
27
|
Requirements:
|
|
22
28
|
- Browser or Node.js 18+ (for global `fetch`).
|
|
23
|
-
- An accessible
|
|
29
|
+
- An accessible Centia.io host URL and client credentials.
|
|
24
30
|
|
|
25
31
|
ESM import:
|
|
26
32
|
```ts
|
|
27
|
-
import { CodeFlow, PasswordFlow, Sql, Rpc, createApi } from "@centia-io/sdk";
|
|
33
|
+
import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp, createSqlBuilder } from "@centia-io/sdk";
|
|
28
34
|
import type { RpcRequest, RpcResponse, PgTypes } from "@centia-io/sdk";
|
|
29
35
|
```
|
|
30
36
|
|
|
@@ -38,23 +44,22 @@ The SDK handles token storage and refresh for you.
|
|
|
38
44
|
|
|
39
45
|
### CodeFlow (Browser, OAuth 2.0 Authorization Code + PKCE)
|
|
40
46
|
|
|
41
|
-
Use this flow in browser applications where you can redirect the user to the
|
|
47
|
+
Use this flow in browser applications where you can redirect the user to the Centia-io login page.
|
|
42
48
|
|
|
43
49
|
Required options:
|
|
44
|
-
- `host`: Base URL of your
|
|
45
|
-
- `clientId`: OAuth client id configured in
|
|
46
|
-
- `redirectUri`: The URL in your app that handles the redirect back from
|
|
47
|
-
- `scope` (optional):
|
|
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.
|
|
48
54
|
|
|
49
55
|
Example (vanilla JS/TS + SPA):
|
|
50
56
|
```ts
|
|
51
57
|
import { CodeFlow } from "@centia-io/sdk";
|
|
52
58
|
|
|
53
59
|
const codeFlow = new CodeFlow({
|
|
54
|
-
host: "https://
|
|
60
|
+
host: "https://api.centia.io",
|
|
55
61
|
clientId: "your-client-id",
|
|
56
|
-
redirectUri: window.location.origin + "/auth/callback"
|
|
57
|
-
scope: "openid"
|
|
62
|
+
redirectUri: window.location.origin + "/auth/callback"
|
|
58
63
|
});
|
|
59
64
|
|
|
60
65
|
// On app startup, call redirectHandle() once to complete a login redirect (if any)
|
|
@@ -76,7 +81,7 @@ function onLogoutClick() {
|
|
|
76
81
|
```
|
|
77
82
|
|
|
78
83
|
Notes:
|
|
79
|
-
- `redirectHandle()` detects errors from the auth server, validates `state` (CSRF protection), exchanges the `code` for tokens, stores tokens and cleans up the URL.
|
|
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.
|
|
80
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()`.
|
|
81
86
|
|
|
82
87
|
### PasswordFlow (Trusted environments, CLI/Server)
|
|
@@ -95,11 +100,11 @@ Example (Node.js):
|
|
|
95
100
|
import { PasswordFlow } from "@centia-io/sdk";
|
|
96
101
|
|
|
97
102
|
const flow = new PasswordFlow({
|
|
98
|
-
host: "
|
|
99
|
-
clientId: "
|
|
100
|
-
username: "
|
|
101
|
-
password: "
|
|
102
|
-
database: "
|
|
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.
|
|
103
108
|
});
|
|
104
109
|
|
|
105
110
|
await flow.signIn();
|
|
@@ -110,7 +115,37 @@ await flow.signIn();
|
|
|
110
115
|
flow.signOut(); // Clears tokens/options in local storage (no redirect)
|
|
111
116
|
```
|
|
112
117
|
|
|
113
|
-
|
|
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.)
|
|
114
149
|
|
|
115
150
|
## SQL
|
|
116
151
|
|
|
@@ -118,17 +153,17 @@ Execute parameterized SQL against GC2.
|
|
|
118
153
|
|
|
119
154
|
- Class: `new Sql()`
|
|
120
155
|
- Method: `exec(request: SqlRequest): Promise<SQLResponse>`
|
|
121
|
-
- Endpoint: `POST
|
|
156
|
+
- Endpoint: `POST https://api.centia.io/api/v4/sql`
|
|
122
157
|
|
|
123
158
|
Types (simplified):
|
|
124
159
|
- `SqlRequest` has:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
129
164
|
- `SQLResponse` has:
|
|
130
|
-
|
|
131
|
-
|
|
165
|
+
- `schema`: a map of column name -> `{ type: string, array: boolean }`
|
|
166
|
+
- `data`: an array of rows (records)
|
|
132
167
|
|
|
133
168
|
Example:
|
|
134
169
|
```ts
|
|
@@ -146,7 +181,8 @@ const payload = {
|
|
|
146
181
|
|
|
147
182
|
const res = await sql.exec({
|
|
148
183
|
q: "select :a::int as a, :b::varchar as b, :c::numeric as c, :d::varchar[] as d, :e::jsonb as e",
|
|
149
|
-
params: payload
|
|
184
|
+
params: payload,
|
|
185
|
+
type_hints: { d: "varchar[]" } // Arrays are not inferred by default, and must be specified explicitly
|
|
150
186
|
});
|
|
151
187
|
|
|
152
188
|
console.log(res.schema); // { a: {type: 'int4', array: false}, ... }
|
|
@@ -159,9 +195,9 @@ import type { PgTypes } from "@centia-io/sdk";
|
|
|
159
195
|
|
|
160
196
|
interface Row extends PgTypes.DataRow {
|
|
161
197
|
a: number;
|
|
162
|
-
b:
|
|
198
|
+
b: Pgtypes.Varchar;
|
|
163
199
|
c: PgTypes.NumericString;
|
|
164
|
-
d: PgTypes.PgArray<
|
|
200
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
165
201
|
e: PgTypes.JsonValue;
|
|
166
202
|
}
|
|
167
203
|
|
|
@@ -169,7 +205,75 @@ interface Row extends PgTypes.DataRow {
|
|
|
169
205
|
const res = await sql.exec({ q: "...", params: payload }) as PgTypes.SQLResponse<Row>;
|
|
170
206
|
```
|
|
171
207
|
|
|
172
|
-
|
|
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`.
|
|
173
277
|
|
|
174
278
|
## RPC
|
|
175
279
|
|
|
@@ -214,9 +318,7 @@ interface Row extends PgTypes.DataRow {
|
|
|
214
318
|
const res = await rpc.call({ jsonrpc: "2.0", method: "typeTest", params: payload }) as PgTypes.RpcResponse<Row>;
|
|
215
319
|
```
|
|
216
320
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
## createApi (Api.ts)
|
|
321
|
+
## createApi
|
|
220
322
|
|
|
221
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.
|
|
222
324
|
|
|
@@ -231,15 +333,15 @@ import type { PgTypes } from "@centia-io/sdk";
|
|
|
231
333
|
interface MyApi {
|
|
232
334
|
typeTest(params: {
|
|
233
335
|
a: number;
|
|
234
|
-
b:
|
|
336
|
+
b: Pgtypes.Varchar;
|
|
235
337
|
c: PgTypes.NumericString;
|
|
236
|
-
d: PgTypes.PgArray<
|
|
338
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
237
339
|
e: PgTypes.JsonValue;
|
|
238
340
|
}): Promise<Array<{
|
|
239
341
|
a: number;
|
|
240
|
-
b:
|
|
342
|
+
b: Pgtypes.Varchar;
|
|
241
343
|
c: PgTypes.NumericString;
|
|
242
|
-
d: PgTypes.PgArray<
|
|
344
|
+
d: PgTypes.PgArray<Pgtypes.Varchar>;
|
|
243
345
|
e: PgTypes.JsonValue;
|
|
244
346
|
}>>;
|
|
245
347
|
}
|
|
@@ -248,10 +350,10 @@ const api = createApi<MyApi>();
|
|
|
248
350
|
|
|
249
351
|
const rows = await api.typeTest({
|
|
250
352
|
a: 1,
|
|
251
|
-
b: "
|
|
353
|
+
b: "Hello world",
|
|
252
354
|
c: "3.4",
|
|
253
|
-
d: ["
|
|
254
|
-
e: {
|
|
355
|
+
d: ["Hello", "world"],
|
|
356
|
+
e: { "x": [1,2,3,4,5,6,7,8,9,10] }
|
|
255
357
|
});
|
|
256
358
|
|
|
257
359
|
console.log(rows); // typed row array
|
|
@@ -261,28 +363,208 @@ Notes:
|
|
|
261
363
|
- `createApi<T>()` relies on naming conventions: the property name is the JSON‑RPC `method` name.
|
|
262
364
|
- Each call returns `result.data` from the RPC response (array of rows).
|
|
263
365
|
|
|
264
|
-
---
|
|
265
|
-
|
|
266
366
|
## Error handling
|
|
267
367
|
|
|
268
368
|
- Network/HTTP errors: thrown as `Error` with the status/body text when available.
|
|
269
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.
|
|
270
370
|
|
|
271
|
-
---
|
|
272
371
|
|
|
273
372
|
## Environment details
|
|
274
373
|
|
|
275
374
|
- Storage: tokens/options stored in `localStorage` when available; otherwise a global in‑memory store is used (`globalThis.__gc2_memory_storage`).
|
|
276
375
|
- Fetch: Node.js 18+ recommended (includes native `fetch`). For older Node versions, add a Fetch polyfill.
|
|
277
376
|
|
|
278
|
-
|
|
377
|
+
## License
|
|
279
378
|
|
|
280
|
-
|
|
379
|
+
The SDK is licensed under [The MIT License](https://opensource.org/license/mit)
|
|
281
380
|
|
|
282
|
-
See the `examples/` folder for runnable snippets showing PasswordFlow + Sql/Rpc and browser CodeFlow usage.
|
|
283
381
|
|
|
284
382
|
---
|
|
285
383
|
|
|
286
|
-
##
|
|
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
|
+
```
|
|
287
565
|
|
|
288
|
-
|
|
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.
|