@b4moss/crudian 0.6.0 → 0.9.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
@@ -4,6 +4,8 @@ CRUD abstraction for DDD repositories.
4
4
 
5
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
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.
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
@@ -236,6 +238,15 @@ crud.count("items") // 5
236
238
  crud.count("items", { where: where().eq("name", "n0") }) // 1
237
239
  ```
238
240
 
241
+ ### `exists(table, query?)` → boolean
242
+
243
+ Whether any row matches (`CountQuery` shape). Does not return a count. Prefer this over `count(...) > 0` when you only need presence (e.g. email already registered).
244
+
245
+ ```ts
246
+ crud.exists("items") // true if the table has any row
247
+ crud.exists("items", { where: where().eq("name", "n0") }) // true / false
248
+ ```
249
+
239
250
  ### `upsert(table, cols)` → row
240
251
 
241
252
  Conflict target is primary key `id`. Inserts or updates; returns the row. `id` is required in `cols`.
@@ -382,6 +393,7 @@ Nestable `and` / `or`. Empty `in([])` is rejected.
382
393
  | Pagination | cursor on `id` ASC only (no offset) |
383
394
  | `columns` | optional on `read` / `search` / `list`; omit → `*` |
384
395
  | `search.total` / `count` | full where count; not page length |
396
+ | `exists` | boolean presence; same input as `count`; use instead of `count > 0` when you only need yes/no |
385
397
  | Errors | minimal `CrudianError`; other errors propagate from the driver |
386
398
  | Identifiers | string required; no format validation |
387
399
  | Out of scope | relations, migrations, full-text search, ORM models |
@@ -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
@@ -8,5 +8,5 @@
8
8
  */
9
9
  export { CrudianError, assertString } from "./errors.js";
10
10
  export { WhereBuilder, where, isWhereBuilder, type Op, type CondNode, type GroupNode, type WhereNode, } from "./where.js";
11
- 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";
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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,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"}
@@ -1,3 +1,4 @@
1
+ import { type CreateCrudOptions } from "../index.js";
1
2
  import { type AsyncSqliteCrud } from "../sqlite/async-crud.js";
2
3
  import type { Row } from "../types.js";
3
4
  /** Result shape used by the libSQL executor bridge. */
@@ -27,5 +28,5 @@ export type LibsqlCrud = AsyncSqliteCrud<LibsqlLikeClient>;
27
28
  * Methods are async. The injected client is exposed as `crud.db`.
28
29
  * Auth / URL are configured on the caller-created client (e.g. from env).
29
30
  */
30
- export declare function createCrud(client: LibsqlLikeClient): LibsqlCrud;
31
+ export declare function createCrud(client: LibsqlLikeClient, options?: CreateCrudOptions): LibsqlCrud;
31
32
  //# sourceMappingURL=crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/libsql/crud.ts"],"names":[],"mappings":"AACA,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,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CA6C/D"}
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"}
@@ -16,7 +16,7 @@ function normalizeRow(row) {
16
16
  * Methods are async. The injected client is exposed as `crud.db`.
17
17
  * Auth / URL are configured on the caller-created client (e.g. from env).
18
18
  */
19
- export function createCrud(client) {
19
+ export function createCrud(client, options) {
20
20
  if (client == null) {
21
21
  throw new CrudianError("db is required");
22
22
  }
@@ -58,5 +58,5 @@ export function createCrud(client) {
58
58
  tx.close();
59
59
  }
60
60
  },
61
- });
61
+ }, options);
62
62
  }
@@ -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)
@@ -1,4 +1,4 @@
1
- import { type CountQuery, type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
1
+ import { type CountQuery, type CreateCrudOptions, type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
2
2
  export type AsyncSqliteExecutor = {
3
3
  run(sql: string, args?: unknown[]): Promise<{
4
4
  changes: number;
@@ -22,7 +22,8 @@ export type AsyncSqliteCrud<TDb> = {
22
22
  search<T extends Row = Row>(table: string, query?: SearchQuery): Promise<SearchResult<T>>;
23
23
  list<T extends Row = Row>(table: string, query?: SearchQuery): Promise<SearchResult<T>>;
24
24
  count(table: string, query?: CountQuery): Promise<number>;
25
+ exists(table: string, query?: CountQuery): Promise<boolean>;
25
26
  transaction<T>(fn: () => Promise<T>): Promise<T>;
26
27
  };
27
- export declare function createAsyncSqliteCrud<TDb>(db: TDb, ex: AsyncSqliteExecutor): AsyncSqliteCrud<TDb>;
28
+ export declare function createAsyncSqliteCrud<TDb>(db: TDb, ex: AsyncSqliteExecutor, options?: CreateCrudOptions): AsyncSqliteCrud<TDb>;
28
29
  //# sourceMappingURL=async-crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"async-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/async-crud.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"async-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/async-crud.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { CrudianError, assertString, } from "../index.js";
2
+ import { createAsyncPkGuard, resolvePk } from "./pk.js";
2
3
  import { compileWhere, quoteIdent, resolveWhere } from "./sql.js";
3
4
  function requireWhere(query, label) {
4
5
  if (query.where === undefined || query.where === null) {
@@ -16,11 +17,14 @@ function rowFromObject(value) {
16
17
  }
17
18
  return { ...value };
18
19
  }
19
- export function createAsyncSqliteCrud(db, ex) {
20
+ export function createAsyncSqliteCrud(db, ex, options) {
21
+ const pk = resolvePk(options);
22
+ const ensurePkColumn = createAsyncPkGuard(pk, ex.all);
20
23
  const crud = {
21
24
  db,
22
25
  async create(table, cols) {
23
26
  assertString(table, "table");
27
+ await ensurePkColumn(table);
24
28
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
25
29
  throw new CrudianError("cols must be an object");
26
30
  }
@@ -39,6 +43,7 @@ export function createAsyncSqliteCrud(db, ex) {
39
43
  },
40
44
  async read(table, query = {}) {
41
45
  assertString(table, "table");
46
+ await ensurePkColumn(table);
42
47
  const tbl = quoteIdent(table);
43
48
  const where = compileWhere(resolveWhere(query.where));
44
49
  const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
@@ -49,6 +54,7 @@ export function createAsyncSqliteCrud(db, ex) {
49
54
  },
50
55
  async update(table, cols, query) {
51
56
  assertString(table, "table");
57
+ await ensurePkColumn(table);
52
58
  requireWhere(query, "update");
53
59
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
54
60
  throw new CrudianError("cols must be an object");
@@ -72,6 +78,7 @@ export function createAsyncSqliteCrud(db, ex) {
72
78
  },
73
79
  async delete(table, query) {
74
80
  assertString(table, "table");
81
+ await ensurePkColumn(table);
75
82
  requireWhere(query, "delete");
76
83
  const tbl = quoteIdent(table);
77
84
  const where = compileWhere(resolveWhere(query.where));
@@ -83,10 +90,21 @@ export function createAsyncSqliteCrud(db, ex) {
83
90
  },
84
91
  async search(table, query = {}) {
85
92
  assertString(table, "table");
93
+ await ensurePkColumn(table);
86
94
  const limit = query.limit ?? 20;
87
95
  if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
88
96
  throw new CrudianError("limit must be a positive number");
89
97
  }
98
+ const paging = query.paging ?? "offset";
99
+ if (paging !== "offset" && paging !== "cursor") {
100
+ throw new CrudianError('paging must be "offset" or "cursor"');
101
+ }
102
+ if (paging === "offset" && query.cursor !== undefined) {
103
+ throw new CrudianError("offset paging does not accept cursor");
104
+ }
105
+ if (paging === "cursor" && query.offset !== undefined) {
106
+ throw new CrudianError("cursor paging does not accept offset");
107
+ }
90
108
  if (query.cursor != null &&
91
109
  typeof query.cursor !== "number" &&
92
110
  typeof query.cursor !== "string") {
@@ -94,27 +112,48 @@ export function createAsyncSqliteCrud(db, ex) {
94
112
  }
95
113
  const tbl = quoteIdent(table);
96
114
  const where = compileWhere(resolveWhere(query.where));
115
+ const total = await crud.count(table, { where: query.where });
116
+ if (paging === "offset") {
117
+ const offset = query.offset ?? 0;
118
+ if (typeof offset !== "number" || !Number.isFinite(offset) || offset < 0) {
119
+ throw new CrudianError("offset must be a non-negative number");
120
+ }
121
+ const args = [...where.args];
122
+ const whereSql = where.sql ? ` WHERE ${where.sql}` : "";
123
+ const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
124
+ whereSql +
125
+ ` ORDER BY ${quoteIdent(pk)} ASC LIMIT ? OFFSET ?`;
126
+ args.push(limit, offset);
127
+ const items = (await ex.all(sql, args)).map((r) => rowFromObject(r));
128
+ return {
129
+ items,
130
+ total,
131
+ offset,
132
+ limit,
133
+ hasMore: offset + items.length < total,
134
+ };
135
+ }
97
136
  const args = [...where.args];
98
137
  const parts = [];
99
138
  if (where.sql)
100
139
  parts.push(`(${where.sql})`);
101
140
  if (query.cursor != null) {
102
- parts.push(`${quoteIdent("id")} > ?`);
141
+ parts.push(`${quoteIdent(pk)} > ?`);
103
142
  args.push(query.cursor);
104
143
  }
105
144
  const whereSql = parts.length > 0 ? ` WHERE ${parts.join(" AND ")}` : "";
106
145
  const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
107
146
  whereSql +
108
- ` ORDER BY ${quoteIdent("id")} ASC LIMIT ?`;
147
+ ` ORDER BY ${quoteIdent(pk)} ASC LIMIT ?`;
109
148
  args.push(limit + 1);
110
149
  const rows = (await ex.all(sql, args)).map((r) => rowFromObject(r));
111
150
  const hasMore = rows.length > limit;
112
151
  const items = hasMore ? rows.slice(0, limit) : rows;
113
152
  const last = items[items.length - 1];
114
- const nextCursor = hasMore && last != null && (typeof last.id === "number" || typeof last.id === "string")
115
- ? last.id
153
+ const pkVal = last != null ? last[pk] : undefined;
154
+ const nextCursor = hasMore && last != null && (typeof pkVal === "number" || typeof pkVal === "string")
155
+ ? pkVal
116
156
  : null;
117
- const total = await crud.count(table, { where: query.where });
118
157
  return { items, nextCursor, hasMore, total };
119
158
  },
120
159
  async list(table, query) {
@@ -122,6 +161,7 @@ export function createAsyncSqliteCrud(db, ex) {
122
161
  },
123
162
  async count(table, query = {}) {
124
163
  assertString(table, "table");
164
+ await ensurePkColumn(table);
125
165
  const tbl = quoteIdent(table);
126
166
  const where = compileWhere(resolveWhere(query.where));
127
167
  const sql = `SELECT COUNT(*) AS ${quoteIdent("row_count")} FROM ${tbl}` +
@@ -129,8 +169,20 @@ export function createAsyncSqliteCrud(db, ex) {
129
169
  const row = await ex.get(sql, where.args);
130
170
  return Number(row?.row_count ?? 0);
131
171
  },
172
+ async exists(table, query = {}) {
173
+ assertString(table, "table");
174
+ await ensurePkColumn(table);
175
+ const tbl = quoteIdent(table);
176
+ const where = compileWhere(resolveWhere(query.where));
177
+ const sql = `SELECT 1 AS ${quoteIdent("ok")} FROM ${tbl}` +
178
+ (where.sql ? ` WHERE ${where.sql}` : "") +
179
+ ` LIMIT 1`;
180
+ const row = await ex.get(sql, where.args);
181
+ return row != null;
182
+ },
132
183
  async upsert(table, cols) {
133
184
  assertString(table, "table");
185
+ await ensurePkColumn(table);
134
186
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
135
187
  throw new CrudianError("cols must be an object");
136
188
  }
@@ -138,19 +190,20 @@ export function createAsyncSqliteCrud(db, ex) {
138
190
  if (keys.length === 0) {
139
191
  throw new CrudianError("cols must not be empty");
140
192
  }
141
- if (!Object.prototype.hasOwnProperty.call(cols, "id")) {
142
- throw new CrudianError("upsert requires cols.id");
193
+ if (!Object.prototype.hasOwnProperty.call(cols, pk)) {
194
+ throw new CrudianError(`upsert requires cols.${pk}`);
143
195
  }
144
- const id = cols.id;
196
+ const id = cols[pk];
145
197
  const existing = await crud.read(table, {
146
- where: { type: "cond", op: "eq", column: "id", value: id },
198
+ where: { type: "cond", op: "eq", column: pk, value: id },
147
199
  });
148
200
  if (existing != null) {
149
- const { id: _id, ...patch } = cols;
201
+ const patch = { ...cols };
202
+ delete patch[pk];
150
203
  if (Object.keys(patch).length === 0)
151
204
  return existing;
152
205
  const updated = await crud.update(table, patch, {
153
- where: { type: "cond", op: "eq", column: "id", value: id },
206
+ where: { type: "cond", op: "eq", column: pk, value: id },
154
207
  });
155
208
  if (updated == null) {
156
209
  throw new CrudianError("upsert update failed");
@@ -161,22 +214,25 @@ export function createAsyncSqliteCrud(db, ex) {
161
214
  },
162
215
  async duplicate(table, query) {
163
216
  assertString(table, "table");
217
+ await ensurePkColumn(table);
164
218
  requireWhere(query, "duplicate");
165
219
  const source = await crud.read(table, { where: query.where });
166
220
  if (source == null)
167
221
  return null;
168
- const { id: _id, ...rest } = source;
222
+ const rest = { ...source };
223
+ delete rest[pk];
169
224
  const overrides = query.overrides != null &&
170
225
  typeof query.overrides === "object" &&
171
226
  !Array.isArray(query.overrides)
172
227
  ? query.overrides
173
228
  : {};
174
229
  const cols = { ...rest, ...overrides };
175
- delete cols.id;
230
+ delete cols[pk];
176
231
  return crud.create(table, cols);
177
232
  },
178
233
  async bulkCreate(table, rows) {
179
234
  assertString(table, "table");
235
+ await ensurePkColumn(table);
180
236
  if (!Array.isArray(rows)) {
181
237
  throw new CrudianError("rows must be an array");
182
238
  }
@@ -194,6 +250,7 @@ export function createAsyncSqliteCrud(db, ex) {
194
250
  },
195
251
  async bulkUpdate(table, cols, query) {
196
252
  assertString(table, "table");
253
+ await ensurePkColumn(table);
197
254
  requireWhere(query, "bulkUpdate");
198
255
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
199
256
  throw new CrudianError("cols must be an object");
@@ -217,6 +274,7 @@ export function createAsyncSqliteCrud(db, ex) {
217
274
  },
218
275
  async bulkUpsert(table, rows) {
219
276
  assertString(table, "table");
277
+ await ensurePkColumn(table);
220
278
  if (!Array.isArray(rows)) {
221
279
  throw new CrudianError("rows must be an array");
222
280
  }
@@ -227,8 +285,8 @@ export function createAsyncSqliteCrud(db, ex) {
227
285
  if (row == null || typeof row !== "object" || Array.isArray(row)) {
228
286
  throw new CrudianError("each row must be an object");
229
287
  }
230
- if (!Object.prototype.hasOwnProperty.call(row, "id")) {
231
- throw new CrudianError("bulkUpsert requires each row to have id");
288
+ if (!Object.prototype.hasOwnProperty.call(row, pk)) {
289
+ throw new CrudianError(`bulkUpsert requires each row to have ${pk}`);
232
290
  }
233
291
  await crud.upsert(table, row);
234
292
  count += 1;
@@ -0,0 +1,22 @@
1
+ import { type CreateCrudOptions } from "../index.js";
2
+ /** Resolve PK column name from createCrud options. Default: `"id"`. */
3
+ export declare function resolvePk(options?: CreateCrudOptions): string;
4
+ export type PkColumnChecker = {
5
+ get(sql: string, args?: unknown[]): {
6
+ name?: unknown;
7
+ } | RowLike | undefined;
8
+ };
9
+ export type AsyncPkColumnChecker = {
10
+ get(sql: string, args?: unknown[]): Promise<{
11
+ name?: unknown;
12
+ } | RowLike | undefined>;
13
+ };
14
+ type RowLike = Record<string, unknown>;
15
+ /**
16
+ * Ensure `pk` exists on `table` via PRAGMA table_info. Caches per table.
17
+ * Throws CrudianError when the column is missing.
18
+ */
19
+ export declare function createPkGuard(pk: string, get: (sql: string, args?: unknown[]) => RowLike | undefined, all: (sql: string, args?: unknown[]) => RowLike[]): (table: string) => void;
20
+ export declare function createAsyncPkGuard(pk: string, all: (sql: string, args?: unknown[]) => Promise<RowLike[]>): (table: string) => Promise<void>;
21
+ export {};
22
+ //# sourceMappingURL=pk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pk.d.ts","sourceRoot":"","sources":["../../src/sqlite/pk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGlE,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAW7D;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,GAAG,SAAS,CAAA;CAC7E,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,OAAO,EAAE,GACf,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;CACrD,CAAA;AAED,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEtC;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,SAAS,EAC3D,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,IAGlB,OAAO,MAAM,KAAG,IAAI,CAapD;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,IAGrB,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC,CAanE"}
@@ -0,0 +1,45 @@
1
+ import { CrudianError } from "../index.js";
2
+ import { quoteIdent } from "./sql.js";
3
+ /** Resolve PK column name from createCrud options. Default: `"id"`. */
4
+ export function resolvePk(options) {
5
+ if (options == null || options.pk === undefined) {
6
+ return "id";
7
+ }
8
+ if (typeof options.pk !== "string") {
9
+ throw new CrudianError("pk must be a string");
10
+ }
11
+ if (options.pk === "") {
12
+ throw new CrudianError("pk must not be empty");
13
+ }
14
+ return options.pk;
15
+ }
16
+ /**
17
+ * Ensure `pk` exists on `table` via PRAGMA table_info. Caches per table.
18
+ * Throws CrudianError when the column is missing.
19
+ */
20
+ export function createPkGuard(pk, get, all) {
21
+ const ok = new Set();
22
+ return function ensurePkColumn(table) {
23
+ if (ok.has(table))
24
+ return;
25
+ const rows = all(`PRAGMA table_info(${quoteIdent(table)})`);
26
+ const names = new Set(rows.map((r) => String(r.name ?? "")).filter((n) => n.length > 0));
27
+ if (!names.has(pk)) {
28
+ throw new CrudianError(`pk column "${pk}" does not exist on table "${table}"`);
29
+ }
30
+ ok.add(table);
31
+ };
32
+ }
33
+ export function createAsyncPkGuard(pk, all) {
34
+ const ok = new Set();
35
+ return async function ensurePkColumn(table) {
36
+ if (ok.has(table))
37
+ return;
38
+ const rows = await all(`PRAGMA table_info(${quoteIdent(table)})`);
39
+ const names = new Set(rows.map((r) => String(r.name ?? "")).filter((n) => n.length > 0));
40
+ if (!names.has(pk)) {
41
+ throw new CrudianError(`pk column "${pk}" does not exist on table "${table}"`);
42
+ }
43
+ ok.add(table);
44
+ };
45
+ }
@@ -1,4 +1,4 @@
1
- import { type CountQuery, type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
1
+ import { type CountQuery, type CreateCrudOptions, type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
2
2
  export type SyncSqliteExecutor = {
3
3
  run(sql: string, args?: unknown[]): {
4
4
  changes: number;
@@ -22,7 +22,8 @@ export type SyncSqliteCrud<TDb> = {
22
22
  search<T extends Row = Row>(table: string, query?: SearchQuery): SearchResult<T>;
23
23
  list<T extends Row = Row>(table: string, query?: SearchQuery): SearchResult<T>;
24
24
  count(table: string, query?: CountQuery): number;
25
+ exists(table: string, query?: CountQuery): boolean;
25
26
  transaction<T>(fn: () => T): T;
26
27
  };
27
- export declare function createSyncSqliteCrud<TDb>(db: TDb, ex: SyncSqliteExecutor): SyncSqliteCrud<TDb>;
28
+ export declare function createSyncSqliteCrud<TDb>(db: TDb, ex: SyncSqliteExecutor, options?: CreateCrudOptions): SyncSqliteCrud<TDb>;
28
29
  //# sourceMappingURL=sync-crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/sync-crud.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/sync-crud.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { CrudianError, assertString, } from "../index.js";
2
+ import { createPkGuard, resolvePk } from "./pk.js";
2
3
  import { compileWhere, quoteIdent, resolveWhere } from "./sql.js";
3
4
  function requireWhere(query, label) {
4
5
  if (query.where === undefined || query.where === null) {
@@ -16,11 +17,14 @@ function rowFromObject(value) {
16
17
  }
17
18
  return { ...value };
18
19
  }
19
- export function createSyncSqliteCrud(db, ex) {
20
+ export function createSyncSqliteCrud(db, ex, options) {
21
+ const pk = resolvePk(options);
22
+ const ensurePkColumn = createPkGuard(pk, ex.get, ex.all);
20
23
  const crud = {
21
24
  db,
22
25
  create(table, cols) {
23
26
  assertString(table, "table");
27
+ ensurePkColumn(table);
24
28
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
25
29
  throw new CrudianError("cols must be an object");
26
30
  }
@@ -33,13 +37,15 @@ export function createSyncSqliteCrud(db, ex) {
33
37
  const placeholders = keys.map(() => "?").join(", ");
34
38
  const args = keys.map((k) => cols[k]);
35
39
  ex.run(`INSERT INTO ${tbl} (${colSql}) VALUES (${placeholders})`, args);
36
- const idRow = ex.get("SELECT last_insert_rowid() AS id");
37
- const id = Number(idRow?.id);
38
- const row = ex.get(`SELECT * FROM ${tbl} WHERE "id" = ?`, [id]);
40
+ const pkValue = Object.prototype.hasOwnProperty.call(cols, pk)
41
+ ? cols[pk]
42
+ : Number(ex.get("SELECT last_insert_rowid() AS id")?.id);
43
+ const row = ex.get(`SELECT * FROM ${tbl} WHERE ${quoteIdent(pk)} = ?`, [pkValue]);
39
44
  return rowFromObject(row);
40
45
  },
41
46
  read(table, query = {}) {
42
47
  assertString(table, "table");
48
+ ensurePkColumn(table);
43
49
  const tbl = quoteIdent(table);
44
50
  const where = compileWhere(resolveWhere(query.where));
45
51
  const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
@@ -50,6 +56,7 @@ export function createSyncSqliteCrud(db, ex) {
50
56
  },
51
57
  update(table, cols, query) {
52
58
  assertString(table, "table");
59
+ ensurePkColumn(table);
53
60
  requireWhere(query, "update");
54
61
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
55
62
  throw new CrudianError("cols must be an object");
@@ -73,6 +80,7 @@ export function createSyncSqliteCrud(db, ex) {
73
80
  },
74
81
  delete(table, query) {
75
82
  assertString(table, "table");
83
+ ensurePkColumn(table);
76
84
  requireWhere(query, "delete");
77
85
  const tbl = quoteIdent(table);
78
86
  const where = compileWhere(resolveWhere(query.where));
@@ -84,10 +92,21 @@ export function createSyncSqliteCrud(db, ex) {
84
92
  },
85
93
  search(table, query = {}) {
86
94
  assertString(table, "table");
95
+ ensurePkColumn(table);
87
96
  const limit = query.limit ?? 20;
88
97
  if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
89
98
  throw new CrudianError("limit must be a positive number");
90
99
  }
100
+ const paging = query.paging ?? "offset";
101
+ if (paging !== "offset" && paging !== "cursor") {
102
+ throw new CrudianError('paging must be "offset" or "cursor"');
103
+ }
104
+ if (paging === "offset" && query.cursor !== undefined) {
105
+ throw new CrudianError("offset paging does not accept cursor");
106
+ }
107
+ if (paging === "cursor" && query.offset !== undefined) {
108
+ throw new CrudianError("cursor paging does not accept offset");
109
+ }
91
110
  if (query.cursor != null &&
92
111
  typeof query.cursor !== "number" &&
93
112
  typeof query.cursor !== "string") {
@@ -95,27 +114,48 @@ export function createSyncSqliteCrud(db, ex) {
95
114
  }
96
115
  const tbl = quoteIdent(table);
97
116
  const where = compileWhere(resolveWhere(query.where));
117
+ const total = crud.count(table, { where: query.where });
118
+ if (paging === "offset") {
119
+ const offset = query.offset ?? 0;
120
+ if (typeof offset !== "number" || !Number.isFinite(offset) || offset < 0) {
121
+ throw new CrudianError("offset must be a non-negative number");
122
+ }
123
+ const args = [...where.args];
124
+ const whereSql = where.sql ? ` WHERE ${where.sql}` : "";
125
+ const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
126
+ whereSql +
127
+ ` ORDER BY ${quoteIdent(pk)} ASC LIMIT ? OFFSET ?`;
128
+ args.push(limit, offset);
129
+ const items = ex.all(sql, args).map((r) => rowFromObject(r));
130
+ return {
131
+ items,
132
+ total,
133
+ offset,
134
+ limit,
135
+ hasMore: offset + items.length < total,
136
+ };
137
+ }
98
138
  const args = [...where.args];
99
139
  const parts = [];
100
140
  if (where.sql)
101
141
  parts.push(`(${where.sql})`);
102
142
  if (query.cursor != null) {
103
- parts.push(`${quoteIdent("id")} > ?`);
143
+ parts.push(`${quoteIdent(pk)} > ?`);
104
144
  args.push(query.cursor);
105
145
  }
106
146
  const whereSql = parts.length > 0 ? ` WHERE ${parts.join(" AND ")}` : "";
107
147
  const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
108
148
  whereSql +
109
- ` ORDER BY ${quoteIdent("id")} ASC LIMIT ?`;
149
+ ` ORDER BY ${quoteIdent(pk)} ASC LIMIT ?`;
110
150
  args.push(limit + 1);
111
151
  const rows = ex.all(sql, args).map((r) => rowFromObject(r));
112
152
  const hasMore = rows.length > limit;
113
153
  const items = hasMore ? rows.slice(0, limit) : rows;
114
154
  const last = items[items.length - 1];
115
- const nextCursor = hasMore && last != null && (typeof last.id === "number" || typeof last.id === "string")
116
- ? last.id
155
+ const pkVal = last != null ? last[pk] : undefined;
156
+ const nextCursor = hasMore && last != null && (typeof pkVal === "number" || typeof pkVal === "string")
157
+ ? pkVal
117
158
  : null;
118
- const total = crud.count(table, { where: query.where });
119
159
  return { items, nextCursor, hasMore, total };
120
160
  },
121
161
  list(table, query) {
@@ -123,6 +163,7 @@ export function createSyncSqliteCrud(db, ex) {
123
163
  },
124
164
  count(table, query = {}) {
125
165
  assertString(table, "table");
166
+ ensurePkColumn(table);
126
167
  const tbl = quoteIdent(table);
127
168
  const where = compileWhere(resolveWhere(query.where));
128
169
  const sql = `SELECT COUNT(*) AS ${quoteIdent("row_count")} FROM ${tbl}` +
@@ -130,8 +171,19 @@ export function createSyncSqliteCrud(db, ex) {
130
171
  const row = ex.get(sql, where.args);
131
172
  return Number(row?.row_count ?? 0);
132
173
  },
174
+ exists(table, query = {}) {
175
+ assertString(table, "table");
176
+ ensurePkColumn(table);
177
+ const tbl = quoteIdent(table);
178
+ const where = compileWhere(resolveWhere(query.where));
179
+ const sql = `SELECT 1 AS ${quoteIdent("ok")} FROM ${tbl}` +
180
+ (where.sql ? ` WHERE ${where.sql}` : "") +
181
+ ` LIMIT 1`;
182
+ return ex.get(sql, where.args) != null;
183
+ },
133
184
  upsert(table, cols) {
134
185
  assertString(table, "table");
186
+ ensurePkColumn(table);
135
187
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
136
188
  throw new CrudianError("cols must be an object");
137
189
  }
@@ -139,19 +191,20 @@ export function createSyncSqliteCrud(db, ex) {
139
191
  if (keys.length === 0) {
140
192
  throw new CrudianError("cols must not be empty");
141
193
  }
142
- if (!Object.prototype.hasOwnProperty.call(cols, "id")) {
143
- throw new CrudianError("upsert requires cols.id");
194
+ if (!Object.prototype.hasOwnProperty.call(cols, pk)) {
195
+ throw new CrudianError(`upsert requires cols.${pk}`);
144
196
  }
145
- const id = cols.id;
197
+ const id = cols[pk];
146
198
  const existing = crud.read(table, {
147
- where: { type: "cond", op: "eq", column: "id", value: id },
199
+ where: { type: "cond", op: "eq", column: pk, value: id },
148
200
  });
149
201
  if (existing != null) {
150
- const { id: _id, ...patch } = cols;
202
+ const patch = { ...cols };
203
+ delete patch[pk];
151
204
  if (Object.keys(patch).length === 0)
152
205
  return existing;
153
206
  const updated = crud.update(table, patch, {
154
- where: { type: "cond", op: "eq", column: "id", value: id },
207
+ where: { type: "cond", op: "eq", column: pk, value: id },
155
208
  });
156
209
  if (updated == null) {
157
210
  throw new CrudianError("upsert update failed");
@@ -162,22 +215,25 @@ export function createSyncSqliteCrud(db, ex) {
162
215
  },
163
216
  duplicate(table, query) {
164
217
  assertString(table, "table");
218
+ ensurePkColumn(table);
165
219
  requireWhere(query, "duplicate");
166
220
  const source = crud.read(table, { where: query.where });
167
221
  if (source == null)
168
222
  return null;
169
- const { id: _id, ...rest } = source;
223
+ const rest = { ...source };
224
+ delete rest[pk];
170
225
  const overrides = query.overrides != null &&
171
226
  typeof query.overrides === "object" &&
172
227
  !Array.isArray(query.overrides)
173
228
  ? query.overrides
174
229
  : {};
175
230
  const cols = { ...rest, ...overrides };
176
- delete cols.id;
231
+ delete cols[pk];
177
232
  return crud.create(table, cols);
178
233
  },
179
234
  bulkCreate(table, rows) {
180
235
  assertString(table, "table");
236
+ ensurePkColumn(table);
181
237
  if (!Array.isArray(rows)) {
182
238
  throw new CrudianError("rows must be an array");
183
239
  }
@@ -195,6 +251,7 @@ export function createSyncSqliteCrud(db, ex) {
195
251
  },
196
252
  bulkUpdate(table, cols, query) {
197
253
  assertString(table, "table");
254
+ ensurePkColumn(table);
198
255
  requireWhere(query, "bulkUpdate");
199
256
  if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
200
257
  throw new CrudianError("cols must be an object");
@@ -218,6 +275,7 @@ export function createSyncSqliteCrud(db, ex) {
218
275
  },
219
276
  bulkUpsert(table, rows) {
220
277
  assertString(table, "table");
278
+ ensurePkColumn(table);
221
279
  if (!Array.isArray(rows)) {
222
280
  throw new CrudianError("rows must be an array");
223
281
  }
@@ -228,8 +286,8 @@ export function createSyncSqliteCrud(db, ex) {
228
286
  if (row == null || typeof row !== "object" || Array.isArray(row)) {
229
287
  throw new CrudianError("each row must be an object");
230
288
  }
231
- if (!Object.prototype.hasOwnProperty.call(row, "id")) {
232
- throw new CrudianError("bulkUpsert requires each row to have id");
289
+ if (!Object.prototype.hasOwnProperty.call(row, pk)) {
290
+ throw new CrudianError(`bulkUpsert requires each row to have ${pk}`);
233
291
  }
234
292
  crud.upsert(table, row);
235
293
  count += 1;
package/dist/types.d.ts CHANGED
@@ -1,12 +1,20 @@
1
1
  import type { WhereBuilder, WhereNode } from "./where.js";
2
2
  export type Row = Record<string, unknown>;
3
- export type SearchResult<T = Row> = {
3
+ export type OffsetSearchResult<T = Row> = {
4
+ items: T[];
5
+ total: number;
6
+ offset: number;
7
+ limit: number;
8
+ hasMore: boolean;
9
+ };
10
+ export type CursorSearchResult<T = Row> = {
4
11
  items: T[];
5
12
  nextCursor: number | string | null;
6
13
  hasMore: boolean;
7
14
  /** Rows matching `where` (ignores limit/cursor). */
8
15
  total: number;
9
16
  };
17
+ export type SearchResult<T = Row> = OffsetSearchResult<T> | CursorSearchResult<T>;
10
18
  export type WhereInput = WhereBuilder | WhereNode;
11
19
  export type ReadQuery = {
12
20
  columns?: string[];
@@ -16,7 +24,11 @@ export type SearchQuery = {
16
24
  columns?: string[];
17
25
  where?: WhereInput;
18
26
  limit?: number;
19
- /** Raw `id` cursor (keyset). */
27
+ /** Pagination mode. Default: `"offset"`. */
28
+ paging?: "offset" | "cursor";
29
+ /** Offset for `paging: "offset"` (default `0`). */
30
+ offset?: number;
31
+ /** Raw PK cursor (keyset; default column `id`) for `paging: "cursor"`. */
20
32
  cursor?: number | string | null;
21
33
  };
22
34
  export type CountQuery = {
@@ -32,4 +44,9 @@ export type DuplicateQuery = {
32
44
  where: WhereInput;
33
45
  overrides?: Record<string, unknown>;
34
46
  };
47
+ /** Options for `createCrud(db, options?)`. */
48
+ export type CreateCrudOptions = {
49
+ /** Primary key column name. Default: `"id"`. */
50
+ pk?: string;
51
+ };
35
52
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEzD,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI;IAClC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,SAAS,CAAA;AAEjD,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEzD,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,GAAG,IAAI;IACxC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,GAAG,IAAI;IACxC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAA;AAEjF,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,SAAS,CAAA;AAEjD,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,CAAA;AAED,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gDAAgD;IAChD,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b4moss/crudian",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "description": "CRUD abstraction for DDD repositories",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,9 +43,9 @@
43
43
  "prisma:generate": "prisma generate --schema prisma/schema.prisma",
44
44
  "test": "bun run test:bun-sqlite && bun run test:drizzle && bun run test:prisma && bun run test:libsql",
45
45
  "test:bun-sqlite": "bun test src/bun-sqlite",
46
- "test:drizzle": "tsx --test src/drizzle/crud.test.ts",
47
- "test:prisma": "bun run prisma:generate && tsx --test src/prisma/crud.test.ts",
48
- "test:libsql": "tsx --test src/libsql/crud.test.ts"
46
+ "test:drizzle": "tsx --test src/drizzle/crud.test.ts src/drizzle/pk.test.ts",
47
+ "test:prisma": "bun run prisma:generate && tsx --test src/prisma/crud.test.ts src/prisma/pk.test.ts",
48
+ "test:libsql": "tsx --test src/libsql/crud.test.ts src/libsql/pk.test.ts"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24",
@@ -77,7 +77,7 @@
77
77
  "@types/better-sqlite3": "^7.6.13",
78
78
  "better-sqlite3": "^13",
79
79
  "bun-types": "^1.2.0",
80
- "drizzle-orm": "^0.44.4",
80
+ "drizzle-orm": "^0.45.2",
81
81
  "prisma": "^6.14.0",
82
82
  "tsx": "^4.23.12",
83
83
  "typescript": "^5.8.0"