@b4moss/crudian 0.5.0 → 0.8.0

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
@@ -2,7 +2,9 @@
2
2
 
3
3
  CRUD abstraction for DDD repositories.
4
4
 
5
- Single npm package with adapter subpaths. Shared contracts live at the package root; concrete backends are imported from subpaths.
5
+ One npm package, adapter subpaths. Shared contracts live at the package root; backends are imported from subpaths. Callers inject their own DB/client — crudian never opens connections or reads env secrets for you.
6
+
7
+ **Versioning:** this npm package has its own SemVer (`package.json`). It is independent of the Go module (`packages/go/VERSION`). Shipping Go `0.7.0` does not require bumping this package.
6
8
 
7
9
  ## Install
8
10
 
@@ -10,15 +12,18 @@ Single npm package with adapter subpaths. Shared contracts live at the package r
10
12
  npm install @b4moss/crudian
11
13
  ```
12
14
 
13
- Adapter peers (install only what you use):
15
+ Install only the peer deps for the adapter you use:
16
+
17
+ | Subpath | Runtime | Sync / async | Peer dependencies |
18
+ |---------|---------|--------------|-------------------|
19
+ | `@b4moss/crudian/bun-sqlite` | Bun | sync | Bun (`bun:sqlite`) |
20
+ | `@b4moss/crudian/drizzle` | Node.js 24+ | sync | `drizzle-orm`, `better-sqlite3` |
21
+ | `@b4moss/crudian/prisma` | Node.js 24+ | async | `@prisma/client` |
22
+ | `@b4moss/crudian/libsql` | Node.js 24+ / Bun | async | `@libsql/client` |
14
23
 
15
- | Subpath | Peer dependencies |
16
- |---------|-------------------|
17
- | `@b4moss/crudian/bun-sqlite` | Bun (`bun:sqlite`) |
18
- | `@b4moss/crudian/drizzle` | `drizzle-orm`, `better-sqlite3` |
19
- | `@b4moss/crudian/prisma` | `@prisma/client` |
24
+ Node must not import `@b4moss/crudian/bun-sqlite` (resolves to an explicit error stub).
20
25
 
21
- ## Usage
26
+ ## Quick start (adapters)
22
27
 
23
28
  ### Shared contracts
24
29
 
@@ -26,7 +31,7 @@ Adapter peers (install only what you use):
26
31
  import { where, CrudianError } from "@b4moss/crudian"
27
32
  ```
28
33
 
29
- ### Bun (`bun:sqlite`)
34
+ ### Bun (`bun:sqlite`) — sync
30
35
 
31
36
  ```ts
32
37
  import { Database } from "bun:sqlite"
@@ -34,15 +39,19 @@ import { createCrud } from "@b4moss/crudian/bun-sqlite"
34
39
  import { where } from "@b4moss/crudian"
35
40
 
36
41
  const db = new Database(":memory:")
42
+ db.exec(`
43
+ CREATE TABLE items (
44
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ name TEXT NOT NULL,
46
+ score INTEGER,
47
+ note TEXT
48
+ )
49
+ `)
37
50
  const crud = createCrud(db)
38
-
39
- const row = crud.create("items", { name: "alpha", score: 1 })
40
- const found = crud.read("items", { where: where().eq("id", row.id) })
51
+ // raw DB is also available as crud.db
41
52
  ```
42
53
 
43
- Node.js must not import `@b4moss/crudian/bun-sqlite` (resolves to an explicit error stub).
44
-
45
- ### Drizzle (better-sqlite3)
54
+ ### Drizzle (better-sqlite3) sync
46
55
 
47
56
  ```ts
48
57
  import Database from "better-sqlite3"
@@ -50,11 +59,17 @@ import { drizzle } from "drizzle-orm/better-sqlite3"
50
59
  import { createCrud } from "@b4moss/crudian/drizzle"
51
60
 
52
61
  const sqlite = new Database(":memory:")
62
+ sqlite.exec(`CREATE TABLE items (
63
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
64
+ name TEXT NOT NULL,
65
+ score INTEGER,
66
+ note TEXT
67
+ )`)
53
68
  const db = drizzle(sqlite)
54
69
  const crud = createCrud(db)
55
70
  ```
56
71
 
57
- ### Prisma (SQLite)
72
+ ### Prisma (SQLite) — async
58
73
 
59
74
  ```ts
60
75
  import { PrismaClient } from "@prisma/client"
@@ -66,15 +81,312 @@ const crud = createCrud(client)
66
81
  await crud.create("items", { name: "alpha", score: 1 })
67
82
  ```
68
83
 
69
- Prisma methods are async. Raw SQL goes through the injected client.
84
+ ### libSQL (`@libsql/client`) async
85
+
86
+ ```ts
87
+ import { createClient } from "@libsql/client"
88
+ import { createCrud } from "@b4moss/crudian/libsql"
89
+
90
+ const client = createClient({
91
+ url: process.env.LIBSQL_URL ?? "file:local.db",
92
+ authToken: process.env.LIBSQL_AUTH_TOKEN, // optional; set for remote hosts
93
+ })
94
+ const crud = createCrud(client)
95
+
96
+ await crud.create("items", { name: "alpha", score: 1 })
97
+ ```
98
+
99
+ URL / auth token belong on the caller-created client (typically from `.env`).
100
+
101
+ ---
102
+
103
+ ## API reference (all methods)
104
+
105
+ Examples below use the **sync** Bun adapter. For `prisma` / `libsql`, `await` every call (same shapes).
106
+
107
+ Assume a table:
108
+
109
+ ```sql
110
+ CREATE TABLE items (
111
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
112
+ name TEXT NOT NULL,
113
+ score INTEGER,
114
+ note TEXT
115
+ );
116
+ ```
117
+
118
+ ```ts
119
+ import { Database } from "bun:sqlite"
120
+ import { createCrud } from "@b4moss/crudian/bun-sqlite"
121
+ import { where } from "@b4moss/crudian"
122
+
123
+ const db = new Database(":memory:")
124
+ db.exec(/* schema above */)
125
+ const crud = createCrud(db)
126
+
127
+ type Item = { id: number; name: string; score: number | null; note: string | null }
128
+ ```
129
+
130
+ ### `create(table, cols)` → row
131
+
132
+ Inserts one row and returns it (including generated `id`).
133
+
134
+ ```ts
135
+ const row = crud.create<Item>("items", { name: "alpha", score: 10, note: "n" })
136
+ // { id: 1, name: "alpha", score: 10, note: "n" }
137
+ ```
138
+
139
+ ### `read(table, query?)` → row | `null`
140
+
141
+ Reads one row. Misses return `null` (does not throw).
142
+
143
+ ```ts
144
+ const found = crud.read<Item>("items", {
145
+ columns: ["id", "name"],
146
+ where: where().eq("id", row.id),
147
+ })
148
+ // { id: 1, name: "alpha" }
149
+
150
+ const missing = crud.read<Item>("items", { where: where().eq("id", 999) })
151
+ // null
152
+ ```
153
+
154
+ ### `update(table, cols, query)` → row | `null`
155
+
156
+ Updates matching rows; returns the updated row. Zero matches → `null`. `where` is required.
157
+
158
+ ```ts
159
+ const updated = crud.update<Item>(
160
+ "items",
161
+ { score: 9 },
162
+ { where: where().eq("id", row.id) },
163
+ )
164
+ // { id: 1, name: "alpha", score: 9, note: "n" }
165
+ ```
166
+
167
+ ### `delete(table, query)` → number
168
+
169
+ Deletes matching rows; returns affected count. `where` is required.
170
+
171
+ ```ts
172
+ const deleted = crud.delete("items", { where: where().eq("id", row.id) })
173
+ // 1
174
+ ```
175
+
176
+ ### `search(table, query?)` → `{ items, nextCursor, hasMore, total }`
177
+
178
+ Canonical list API. Cursor pagination on `id` ascending. `total` is the full where-match count (ignores `limit` / `cursor`).
179
+
180
+ `SearchQuery` fields:
181
+
182
+ | Field | Meaning |
183
+ |-------|---------|
184
+ | `columns?` | Column list for `SELECT` (same idea as `read`). Omit → `*` |
185
+ | `where?` | Condition builder / node |
186
+ | `limit?` | Page size (default `20`) |
187
+ | `cursor?` | Raw `id` cursor (keyset); rows with `id > cursor` |
188
+
189
+ ```ts
190
+ for (let i = 0; i < 5; i++) {
191
+ crud.create("items", { name: `n${i}`, score: i })
192
+ }
193
+
194
+ const page1 = crud.search<Item>("items", { limit: 2 })
195
+ // {
196
+ // items: [ { id: 1, ... }, { id: 2, ... } ],
197
+ // nextCursor: 2,
198
+ // hasMore: true,
199
+ // total: 5,
200
+ // }
201
+
202
+ const page2 = crud.search<Item>("items", {
203
+ limit: 2,
204
+ cursor: page1.nextCursor,
205
+ })
206
+ // items ids [3, 4], total still 5
207
+
208
+ const filtered = crud.search<Item>("items", {
209
+ columns: ["id", "name"],
210
+ where: where().gte("score", 3),
211
+ limit: 10,
212
+ })
213
+ // each item is { id, name } only
214
+ ```
215
+
216
+ ### `list(table, query?)` → same as `search`
217
+
218
+ Thin alias of `search` (same `SearchQuery`, including `columns`).
219
+
220
+ ```ts
221
+ const a = crud.list<Item>("items", {
222
+ columns: ["id", "name"],
223
+ limit: 10,
224
+ })
225
+ const b = crud.search<Item>("items", {
226
+ columns: ["id", "name"],
227
+ limit: 10,
228
+ })
229
+ // a and b are deep-equal
230
+ ```
231
+
232
+ ### `count(table, query?)` → number
233
+
234
+ Where-match count only (`CountQuery` is `{ where? }`). No `limit` / `cursor` / `columns`.
235
+
236
+ ```ts
237
+ crud.count("items") // 5
238
+ crud.count("items", { where: where().eq("name", "n0") }) // 1
239
+ ```
240
+
241
+ ### `upsert(table, cols)` → row
242
+
243
+ Conflict target is primary key `id`. Inserts or updates; returns the row. `id` is required in `cols`.
244
+
245
+ ```ts
246
+ const inserted = crud.upsert<Item>("items", { id: 10, name: "new", score: 1 })
247
+ const again = crud.upsert<Item>("items", { id: 10, name: "upd", score: 9 })
248
+ // again.name === "upd"
249
+ ```
250
+
251
+ ### `duplicate(table, query)` → row | `null`
252
+
253
+ Copies the first matching row (new `id`). Optional `overrides`. Zero matches → `null`. `where` is required.
254
+
255
+ ```ts
256
+ const source = crud.create<Item>("items", { name: "a", score: 1, note: "n" })
257
+ const copy = crud.duplicate<Item>("items", {
258
+ where: where().eq("id", source.id),
259
+ overrides: { name: "b" },
260
+ })
261
+ // copy.id !== source.id, copy.name === "b"
262
+
263
+ const none = crud.duplicate<Item>("items", { where: where().eq("id", 999) })
264
+ // null
265
+ ```
266
+
267
+ ### `bulkCreate(table, rows)` → number
268
+
269
+ Inserts many rows; returns inserted count. Empty array → `0`.
270
+
271
+ ```ts
272
+ const n = crud.bulkCreate("items", [
273
+ { name: "x", score: 1 },
274
+ { name: "y", score: 2 },
275
+ ])
276
+ // 2
277
+ ```
278
+
279
+ ### `bulkUpdate(table, cols, query)` → number
280
+
281
+ Updates many rows; returns affected count. `where` is required.
282
+
283
+ ```ts
284
+ const n = crud.bulkUpdate(
285
+ "items",
286
+ { score: 8 },
287
+ { where: where().eq("name", "x") },
288
+ )
289
+ // 1
290
+ ```
291
+
292
+ ### `bulkDelete(table, query)` → number
293
+
294
+ Deletes many rows; returns affected count. `where` is required.
295
+
296
+ ```ts
297
+ const n = crud.bulkDelete("items", { where: where().eq("name", "y") })
298
+ // 1
299
+ ```
300
+
301
+ ### `bulkUpsert(table, rows)` → number
302
+
303
+ Upserts many rows (each row needs `id`); returns processed count. Empty array → `0`.
304
+
305
+ ```ts
306
+ const n = crud.bulkUpsert("items", [
307
+ { id: 100, name: "z", score: 1 },
308
+ { id: 10, name: "z2", score: 2 },
309
+ ])
310
+ // 2
311
+ ```
312
+
313
+ ### `transaction(fn)` → `fn` return value
314
+
315
+ Runs `fn` inside a transaction helper. Crudian does **not** auto-wrap each CRUD call; use this when you need atomic multi-step work. Success commits; throw rolls back.
316
+
317
+ ```ts
318
+ const result = crud.transaction(() => {
319
+ crud.create("items", { name: "a", score: 1 })
320
+ crud.create("items", { name: "b", score: 2 })
321
+ return "ok"
322
+ })
323
+ // result === "ok"; both rows visible afterward
324
+
325
+ try {
326
+ crud.transaction(() => {
327
+ crud.create("items", { name: "c", score: 3 })
328
+ throw new Error("boom")
329
+ })
330
+ } catch {
331
+ // partial writes from that callback are rolled back
332
+ }
333
+ ```
334
+
335
+ Async adapters:
336
+
337
+ ```ts
338
+ await crud.transaction(async () => {
339
+ await crud.create("items", { name: "a", score: 1 })
340
+ await crud.create("items", { name: "b", score: 2 })
341
+ })
342
+ ```
343
+
344
+ ---
345
+
346
+ ## `where()` builder
347
+
348
+ ```ts
349
+ import { where } from "@b4moss/crudian"
350
+
351
+ where().eq("name", "alpha")
352
+ where().ne("score", 0)
353
+ where().lt("score", 10)
354
+ where().lte("score", 10)
355
+ where().gt("score", 0)
356
+ where().gte("score", 0)
357
+ where().in("name", ["a", "b"])
358
+ where().like("name", "a%")
359
+ where().isNull("note")
360
+ where().isNotNull("note")
361
+
362
+ where()
363
+ .eq("name", "alpha")
364
+ .and(where().gte("score", 5))
365
+
366
+ where()
367
+ .eq("name", "alpha")
368
+ .or(where().eq("name", "beta"))
369
+ ```
370
+
371
+ Nestable `and` / `or`. Empty `in([])` is rejected.
70
372
 
71
- ## API surface
373
+ ---
72
374
 
73
- `create` / `read` / `update` / `delete` / `search` / `list` / `upsert` / `duplicate` / `bulkCreate` / `bulkUpdate` / `bulkDelete` / `bulkUpsert` / `transaction`
375
+ ## Behavior notes
74
376
 
75
- - `search` is canonical; `list` is an alias
76
- - Cursor pagination uses `id` ascending; response is `{ items, nextCursor, hasMore }`
77
- - Conditions use the `where()` builder (`eq` / `ne` / `lt` / `gt` / `lte` / `gte` / `in` / `like` / `isNull` / `isNotNull`, nestable `and` / `or`)
377
+ | Topic | Behavior |
378
+ |-------|----------|
379
+ | Entry | `createCrud(db)` inject a caller-owned client; exposed as `crud.db` |
380
+ | Sync vs async | `bun-sqlite` / `drizzle` sync; `prisma` / `libsql` return `Promise`s |
381
+ | `read` / `update` / `duplicate` miss | `null` |
382
+ | `delete` / bulk miss | `0` |
383
+ | Upsert conflict | primary key `id` |
384
+ | Pagination | cursor on `id` ASC only (no offset) |
385
+ | `columns` | optional on `read` / `search` / `list`; omit → `*` |
386
+ | `search.total` / `count` | full where count; not page length |
387
+ | Errors | minimal `CrudianError`; other errors propagate from the driver |
388
+ | Identifiers | string required; no format validation |
389
+ | Out of scope | relations, migrations, full-text search, ORM models |
78
390
 
79
391
  ## License
80
392
 
@@ -1,9 +1,10 @@
1
1
  import type { Database } from "bun:sqlite";
2
+ import { type CreateCrudOptions } from "../index.js";
2
3
  import { type SyncSqliteCrud } from "../sqlite/sync-crud.js";
3
4
  export type BunSqliteCrud = SyncSqliteCrud<Database>;
4
5
  /**
5
6
  * Create a Crud bound to a Bun SQLite Database.
6
7
  * The same Database instance is reused for every operation.
7
8
  */
8
- export declare function createCrud(db: Database): BunSqliteCrud;
9
+ export declare function createCrud(db: Database, options?: CreateCrudOptions): BunSqliteCrud;
9
10
  //# sourceMappingURL=crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/bun-sqlite/crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAA;AAE5D,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAA;AAM/B,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;AAEpD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,aAAa,CAyBtD"}
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/bun-sqlite/crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAA;AAM/B,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;AAEpD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,QAAQ,EACZ,OAAO,CAAC,EAAE,iBAAiB,GAC1B,aAAa,CA6Bf"}
@@ -7,7 +7,7 @@ function bindings(args) {
7
7
  * Create a Crud bound to a Bun SQLite Database.
8
8
  * The same Database instance is reused for every operation.
9
9
  */
10
- export function createCrud(db) {
10
+ export function createCrud(db, options) {
11
11
  if (db == null) {
12
12
  throw new CrudianError("db is required");
13
13
  }
@@ -28,5 +28,5 @@ export function createCrud(db) {
28
28
  transaction(fn) {
29
29
  return db.transaction(fn)();
30
30
  },
31
- });
31
+ }, options);
32
32
  }
@@ -1,5 +1,6 @@
1
1
  import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
2
  import type BetterSqlite3 from "better-sqlite3";
3
+ import { type CreateCrudOptions } from "../index.js";
3
4
  import { type SyncSqliteCrud } from "../sqlite/sync-crud.js";
4
5
  type DrizzleSqliteDb = BetterSQLite3Database<Record<string, unknown>> & {
5
6
  $client: BetterSqlite3.Database;
@@ -9,6 +10,6 @@ export type DrizzleCrud = SyncSqliteCrud<DrizzleSqliteDb>;
9
10
  * Create a Crud bound to a Drizzle better-sqlite3 database.
10
11
  * Raw SQL goes through the underlying better-sqlite3 client (`db.$client`).
11
12
  */
12
- export declare function createCrud(db: DrizzleSqliteDb): DrizzleCrud;
13
+ export declare function createCrud(db: DrizzleSqliteDb, options?: CreateCrudOptions): DrizzleCrud;
13
14
  export {};
14
15
  //# sourceMappingURL=crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/drizzle/crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAA;AAE/B,KAAK,eAAe,GAAG,qBAAqB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IACtE,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,CAAC,CAAA;AAEzD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,eAAe,GAAG,WAAW,CA2B3D"}
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/drizzle/crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAA;AAE/B,KAAK,eAAe,GAAG,qBAAqB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IACtE,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,CAAC,CAAA;AAEzD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,eAAe,EACnB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,WAAW,CA+Bb"}
@@ -4,7 +4,7 @@ import { createSyncSqliteCrud, } from "../sqlite/sync-crud.js";
4
4
  * Create a Crud bound to a Drizzle better-sqlite3 database.
5
5
  * Raw SQL goes through the underlying better-sqlite3 client (`db.$client`).
6
6
  */
7
- export function createCrud(db) {
7
+ export function createCrud(db, options) {
8
8
  if (db == null) {
9
9
  throw new CrudianError("db is required");
10
10
  }
@@ -29,5 +29,5 @@ export function createCrud(db) {
29
29
  transaction(fn) {
30
30
  return client.transaction(fn)();
31
31
  },
32
- });
32
+ }, options);
33
33
  }
package/dist/index.d.ts CHANGED
@@ -4,8 +4,9 @@
4
4
  * - @b4moss/crudian/bun-sqlite
5
5
  * - @b4moss/crudian/drizzle
6
6
  * - @b4moss/crudian/prisma
7
+ * - @b4moss/crudian/libsql
7
8
  */
8
9
  export { CrudianError, assertString } from "./errors.js";
9
10
  export { WhereBuilder, where, isWhereBuilder, type Op, type CondNode, type GroupNode, type WhereNode, } from "./where.js";
10
- export type { Row, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, } from "./types.js";
11
+ export type { Row, OffsetSearchResult, CursorSearchResult, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, CreateCrudOptions, } from "./types.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACL,YAAY,EACZ,KAAK,EACL,cAAc,EACd,KAAK,EAAE,EACP,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,GAAG,EACH,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACL,YAAY,EACZ,KAAK,EACL,cAAc,EACd,KAAK,EAAE,EACP,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,GAAG,EACH,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * - @b4moss/crudian/bun-sqlite
5
5
  * - @b4moss/crudian/drizzle
6
6
  * - @b4moss/crudian/prisma
7
+ * - @b4moss/crudian/libsql
7
8
  */
8
9
  export { CrudianError, assertString } from "./errors.js";
9
10
  export { WhereBuilder, where, isWhereBuilder, } from "./where.js";
@@ -0,0 +1,32 @@
1
+ import { type CreateCrudOptions } from "../index.js";
2
+ import { type AsyncSqliteCrud } from "../sqlite/async-crud.js";
3
+ import type { Row } from "../types.js";
4
+ /** Result shape used by the libSQL executor bridge. */
5
+ export type LibsqlResultSet = {
6
+ rows: Array<Row | unknown>;
7
+ rowsAffected: number;
8
+ };
9
+ /** Minimal execute surface shared by Client and Transaction. */
10
+ export type LibsqlExecutor = {
11
+ execute(stmt: {
12
+ sql: string;
13
+ args?: unknown[];
14
+ } | string): Promise<LibsqlResultSet>;
15
+ };
16
+ /** Minimal surface of @libsql/client Client used by the adapter. */
17
+ export type LibsqlLikeClient = LibsqlExecutor & {
18
+ transaction(mode?: "write" | "read" | "deferred"): Promise<LibsqlLikeTransaction>;
19
+ };
20
+ export type LibsqlLikeTransaction = LibsqlExecutor & {
21
+ commit(): Promise<void>;
22
+ rollback(): Promise<void>;
23
+ close(): void;
24
+ };
25
+ export type LibsqlCrud = AsyncSqliteCrud<LibsqlLikeClient>;
26
+ /**
27
+ * Create a Crud bound to a libSQL client (`@libsql/client`).
28
+ * Methods are async. The injected client is exposed as `crud.db`.
29
+ * Auth / URL are configured on the caller-created client (e.g. from env).
30
+ */
31
+ export declare function createCrud(client: LibsqlLikeClient, options?: CreateCrudOptions): LibsqlCrud;
32
+ //# sourceMappingURL=crud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/libsql/crud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC,uDAAuD;AACvD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CACL,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,MAAM,GAC/C,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5B,CAAA;AAED,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC9C,WAAW,CACT,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,UAAU,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG;IACnD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAA;AAa1D;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CAiDZ"}
@@ -0,0 +1,62 @@
1
+ import { CrudianError } from "../index.js";
2
+ import { createAsyncSqliteCrud, } from "../sqlite/async-crud.js";
3
+ function normalizeRow(row) {
4
+ if (row == null || typeof row !== "object") {
5
+ throw new CrudianError("expected row object");
6
+ }
7
+ const out = { ...row };
8
+ for (const [k, v] of Object.entries(out)) {
9
+ if (typeof v === "bigint")
10
+ out[k] = Number(v);
11
+ }
12
+ return out;
13
+ }
14
+ /**
15
+ * Create a Crud bound to a libSQL client (`@libsql/client`).
16
+ * Methods are async. The injected client is exposed as `crud.db`.
17
+ * Auth / URL are configured on the caller-created client (e.g. from env).
18
+ */
19
+ export function createCrud(client, options) {
20
+ if (client == null) {
21
+ throw new CrudianError("db is required");
22
+ }
23
+ if (typeof client !== "object" ||
24
+ typeof client.execute !== "function" ||
25
+ typeof client.transaction !== "function") {
26
+ throw new CrudianError("db must be a libSQL client");
27
+ }
28
+ let active = client;
29
+ return createAsyncSqliteCrud(client, {
30
+ async run(sql, args = []) {
31
+ const result = await active.execute({ sql, args });
32
+ return { changes: Number(result.rowsAffected ?? 0) };
33
+ },
34
+ async get(sql, args = []) {
35
+ const result = await active.execute({ sql, args });
36
+ const row = result.rows[0];
37
+ return row == null ? undefined : normalizeRow(row);
38
+ },
39
+ async all(sql, args = []) {
40
+ const result = await active.execute({ sql, args });
41
+ return result.rows.map((row) => normalizeRow(row));
42
+ },
43
+ async transaction(fn) {
44
+ const tx = await client.transaction("write");
45
+ const prev = active;
46
+ active = tx;
47
+ try {
48
+ const value = await fn();
49
+ await tx.commit();
50
+ return value;
51
+ }
52
+ catch (err) {
53
+ await tx.rollback();
54
+ throw err;
55
+ }
56
+ finally {
57
+ active = prev;
58
+ tx.close();
59
+ }
60
+ },
61
+ }, options);
62
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * libSQL adapter for @b4moss/crudian.
3
+ * Import via: `import { createCrud } from "@b4moss/crudian/libsql"`
4
+ */
5
+ export { createCrud, type LibsqlCrud, type LibsqlLikeClient, type LibsqlLikeTransaction, type LibsqlExecutor, type LibsqlResultSet, } from "./crud.js";
6
+ export type { Row, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, } from "../types.js";
7
+ export { CrudianError, where, WhereBuilder, type Op, type CondNode, type GroupNode, type WhereNode, } from "../index.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/libsql/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,UAAU,EACV,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,WAAW,CAAA;AAClB,YAAY,EACV,GAAG,EACH,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,YAAY,EACZ,KAAK,EACL,YAAY,EACZ,KAAK,EAAE,EACP,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,aAAa,CAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * libSQL adapter for @b4moss/crudian.
3
+ * Import via: `import { createCrud } from "@b4moss/crudian/libsql"`
4
+ */
5
+ export { createCrud, } from "./crud.js";
6
+ export { CrudianError, where, WhereBuilder, } from "../index.js";
@@ -1,3 +1,4 @@
1
+ import { type CreateCrudOptions } from "../index.js";
1
2
  import { type AsyncSqliteCrud } from "../sqlite/async-crud.js";
2
3
  /** Minimal surface of PrismaClient used by the adapter (raw SQL + TX). */
3
4
  export type PrismaLikeClient = {
@@ -10,5 +11,5 @@ export type PrismaCrud = AsyncSqliteCrud<PrismaLikeClient>;
10
11
  * Create a Crud bound to a PrismaClient (SQLite).
11
12
  * Methods are async. The injected client is exposed as `crud.db`.
12
13
  */
13
- export declare function createCrud(client: PrismaLikeClient): PrismaCrud;
14
+ export declare function createCrud(client: PrismaLikeClient, options?: CreateCrudOptions): PrismaCrud;
14
15
  //# sourceMappingURL=crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/prisma/crud.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAA;AAGhC,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7E,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACtE,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAA;AAU1D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CA0C/D"}
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/prisma/crud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAA;AAGhC,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7E,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACtE,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAA;AAU1D;;;GAGG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CA8CZ"}
@@ -12,7 +12,7 @@ function normalizeRow(row) {
12
12
  * Create a Crud bound to a PrismaClient (SQLite).
13
13
  * Methods are async. The injected client is exposed as `crud.db`.
14
14
  */
15
- export function createCrud(client) {
15
+ export function createCrud(client, options) {
16
16
  if (client == null) {
17
17
  throw new CrudianError("db is required");
18
18
  }
@@ -50,7 +50,7 @@ export function createCrud(client) {
50
50
  }
51
51
  });
52
52
  },
53
- });
53
+ }, options);
54
54
  }
55
55
  function normalizeQueryResult(result) {
56
56
  if (result == null)