@centia-io/sdk 0.0.29 → 0.0.31

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 CHANGED
@@ -30,7 +30,7 @@ Requirements:
30
30
 
31
31
  ESM import:
32
32
  ```ts
33
- import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp } from "@centia-io/sdk";
33
+ import { CodeFlow, PasswordFlow, Sql, Rpc, createApi, SignUp, createSqlBuilder } from "@centia-io/sdk";
34
34
  import type { RpcRequest, RpcResponse, PgTypes } from "@centia-io/sdk";
35
35
  ```
36
36
 
@@ -205,6 +205,76 @@ interface Row extends PgTypes.DataRow {
205
205
  const res = await sql.exec({ q: "...", params: payload }) as PgTypes.SQLResponse<Row>;
206
206
  ```
207
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
+
208
278
  ## RPC
209
279
 
210
280
  Call JSON‑RPC methods exposed by GC2.
@@ -307,3 +377,194 @@ Notes:
307
377
  ## License
308
378
 
309
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.