@b4moss/crudian 0.9.0 → 0.11.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.
Files changed (55) hide show
  1. package/README.md +64 -10
  2. package/dist/bun-sqlite/sql.d.ts +5 -1
  3. package/dist/bun-sqlite/sql.d.ts.map +1 -1
  4. package/dist/bun-sqlite/sql.js +7 -1
  5. package/dist/dialect/index.d.ts +7 -0
  6. package/dist/dialect/index.d.ts.map +1 -0
  7. package/dist/dialect/index.js +19 -0
  8. package/dist/dialect/mysql.d.ts +4 -0
  9. package/dist/dialect/mysql.d.ts.map +1 -0
  10. package/dist/dialect/mysql.js +24 -0
  11. package/dist/dialect/postgres.d.ts +4 -0
  12. package/dist/dialect/postgres.d.ts.map +1 -0
  13. package/dist/dialect/postgres.js +25 -0
  14. package/dist/dialect/sqlite.d.ts +4 -0
  15. package/dist/dialect/sqlite.d.ts.map +1 -0
  16. package/dist/dialect/sqlite.js +23 -0
  17. package/dist/dialect/types.d.ts +25 -0
  18. package/dist/dialect/types.d.ts.map +1 -0
  19. package/dist/dialect/types.js +5 -0
  20. package/dist/index.d.ts +3 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/pool.d.ts +18 -0
  24. package/dist/pool.d.ts.map +1 -0
  25. package/dist/pool.js +1 -0
  26. package/dist/prisma/crud.d.ts +2 -1
  27. package/dist/prisma/crud.d.ts.map +1 -1
  28. package/dist/prisma/crud.js +7 -4
  29. package/dist/prisma/server-dialect-harness.d.ts +16 -0
  30. package/dist/prisma/server-dialect-harness.d.ts.map +1 -0
  31. package/dist/prisma/server-dialect-harness.js +120 -0
  32. package/dist/sqlite/async-crud.d.ts +4 -0
  33. package/dist/sqlite/async-crud.d.ts.map +1 -1
  34. package/dist/sqlite/async-crud.js +57 -36
  35. package/dist/sqlite/pk.d.ts +6 -13
  36. package/dist/sqlite/pk.d.ts.map +1 -1
  37. package/dist/sqlite/pk.js +13 -6
  38. package/dist/sqlite/sql.d.ts +10 -1
  39. package/dist/sqlite/sql.d.ts.map +1 -1
  40. package/dist/sqlite/sql.js +60 -23
  41. package/dist/sqlite/sync-crud.d.ts +4 -0
  42. package/dist/sqlite/sync-crud.d.ts.map +1 -1
  43. package/dist/sqlite/sync-crud.js +48 -35
  44. package/dist/typeorm/crud.d.ts +24 -0
  45. package/dist/typeorm/crud.d.ts.map +1 -0
  46. package/dist/typeorm/crud.js +142 -0
  47. package/dist/typeorm/index.d.ts +8 -0
  48. package/dist/typeorm/index.d.ts.map +1 -0
  49. package/dist/typeorm/index.js +6 -0
  50. package/dist/typeorm/server-dialect-harness.d.ts +13 -0
  51. package/dist/typeorm/server-dialect-harness.d.ts.map +1 -0
  52. package/dist/typeorm/server-dialect-harness.js +120 -0
  53. package/dist/types.d.ts +7 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/package.json +31 -6
package/README.md CHANGED
@@ -20,6 +20,7 @@ Install only the peer deps for the adapter you use:
20
20
  | `@b4moss/crudian/drizzle` | Node.js 24+ | sync | `drizzle-orm`, `better-sqlite3` |
21
21
  | `@b4moss/crudian/prisma` | Node.js 24+ | async | `@prisma/client` |
22
22
  | `@b4moss/crudian/libsql` | Node.js 24+ / Bun | async | `@libsql/client` |
23
+ | `@b4moss/crudian/typeorm` | Node.js 24+ / Bun | async | `typeorm` |
23
24
 
24
25
  Node must not import `@b4moss/crudian/bun-sqlite` (resolves to an explicit error stub).
25
26
 
@@ -69,18 +70,40 @@ const db = drizzle(sqlite)
69
70
  const crud = createCrud(db)
70
71
  ```
71
72
 
72
- ### Prisma (SQLite) — async
73
+ ### Prisma (SQLite / Postgres / MySQL) — async
73
74
 
74
75
  ```ts
75
76
  import { PrismaClient } from "@prisma/client"
76
77
  import { createCrud } from "@b4moss/crudian/prisma"
77
78
 
78
79
  const client = new PrismaClient()
79
- const crud = createCrud(client)
80
+ // Default dialect is "sqlite". For server DBs:
81
+ const crudPg = createCrud(client, { dialect: "postgres" })
82
+ const crudMy = createCrud(client, { dialect: "mysql" })
83
+ const crudSqlite = createCrud(client) // or { dialect: "sqlite" }
80
84
 
81
- await crud.create("items", { name: "alpha", score: 1 })
85
+ await crudSqlite.create("items", { name: "alpha", score: 1 })
82
86
  ```
83
87
 
88
+ Match `options.dialect` to the Prisma schema `provider`. Placeholders and
89
+ insert-return strategies follow the dialect (`$n` + `RETURNING` on Postgres;
90
+ `` `ident` `` + `LAST_INSERT_ID` fetch on MySQL — no `RETURNING`).
91
+
92
+ ### Connection pool / lifetime (JS)
93
+
94
+ crudian never opens connections. For **Postgres / MySQL**, configure pooling on
95
+ the client you inject:
96
+
97
+ | Adapter | How to set pool |
98
+ |---------|-----------------|
99
+ | **Prisma** | Prisma datasource / connection URL params / engine pool settings (caller-owned). See Prisma docs for your provider. |
100
+ | **TypeORM** | `DataSource` options (caller-owned). |
101
+ | **bun-sqlite / drizzle (sqlite) / libsql** | No pool settings needed (no-op). |
102
+
103
+ Optional type for docs / future drivers: `PoolOptions` from `@b4moss/crudian`
104
+ (not applied by Prisma — configure the client instead). Go GORM uses
105
+ `crudian.Options.Pool` / `ApplyPool` (see `packages/go/README.md`).
106
+
84
107
  ### libSQL (`@libsql/client`) — async
85
108
 
86
109
  ```ts
@@ -98,11 +121,39 @@ await crud.create("items", { name: "alpha", score: 1 })
98
121
 
99
122
  URL / auth token belong on the caller-created client (typically from `.env`).
100
123
 
124
+ ### TypeORM (`DataSource`) — async
125
+
126
+ ```ts
127
+ import "reflect-metadata"
128
+ import { DataSource } from "typeorm"
129
+ import { createCrud } from "@b4moss/crudian/typeorm"
130
+ import { where } from "@b4moss/crudian"
131
+
132
+ const dataSource = new DataSource({
133
+ type: "better-sqlite3",
134
+ database: ":memory:",
135
+ // entities / migrations / pool: caller-owned
136
+ })
137
+ await dataSource.initialize()
138
+
139
+ const crud = createCrud(dataSource)
140
+ await crud.create("items", { name: "alpha", score: 1 })
141
+ await crud.search("items", { where: where().eq("name", "alpha") })
142
+
143
+ // JOIN / QueryBuilder / relation graphs → use crud.db (raw DataSource)
144
+ ```
145
+
146
+ Dialect is inferred from `dataSource.options.type` (`sqlite` / `better-sqlite3` → sqlite, `postgres` → postgres, `mysql` / `mariadb` → mysql). Override with `createCrud(dataSource, { dialect: "postgres" })`.
147
+
148
+ **Boundaries:** callers run migrations; crudian does single-table CRUD by table name. Relations may exist on the schema, but cross-relation CRUD is out of scope (`crud.db`). Subscribers fire only if the write path goes through TypeORM persistence hooks your app configured — treat side effects as caller-owned.
149
+
150
+ Pool / lifetime: configure on the `DataSource` options (same ownership model as Prisma).
151
+
101
152
  ---
102
153
 
103
154
  ## API reference (all methods)
104
155
 
105
- Examples below use the **sync** Bun adapter. For `prisma` / `libsql`, `await` every call (same shapes).
156
+ Examples below use the **sync** Bun adapter. For `prisma` / `libsql` / `typeorm`, `await` every call (same shapes).
106
157
 
107
158
  Assume a table:
108
159
 
@@ -385,18 +436,21 @@ Nestable `and` / `or`. Empty `in([])` is rejected.
385
436
 
386
437
  | Topic | Behavior |
387
438
  |-------|----------|
388
- | Entry | `createCrud(db)` — inject a caller-owned client; exposed as `crud.db` |
389
- | Sync vs async | `bun-sqlite` / `drizzle` sync; `prisma` / `libsql` return `Promise`s |
439
+ | Entry | `createCrud(db, options?)` — inject a caller-owned client; exposed as `crud.db` |
440
+ | Dialects | Shared Dialect layer: SQLite (all adapters), Postgres / MySQL via **Prisma** / **TypeORM** (`options.dialect` or DataSource type) |
441
+ | Drizzle | **SQLite (better-sqlite3) only** in this line; PG/MySQL subpaths are out of scope |
442
+ | Sync vs async | `bun-sqlite` / `drizzle` sync; `prisma` / `libsql` / `typeorm` return `Promise`s |
390
443
  | `read` / `update` / `duplicate` miss | `null` |
391
444
  | `delete` / bulk miss | `0` |
392
- | Upsert conflict | primary key `id` |
393
- | Pagination | cursor on `id` ASC only (no offset) |
445
+ | Upsert conflict | application-level on PK (default `id`; no SQL `ON CONFLICT`) |
446
+ | Pagination | `paging?: "offset" \| "cursor"` (default `"offset"`) |
394
447
  | `columns` | optional on `read` / `search` / `list`; omit → `*` |
395
448
  | `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 |
449
+ | `exists` | boolean presence; same input as `count` |
450
+ | Pool | caller-owned; Prisma via datasource settings; TypeORM via DataSource options; SQLite adapters no-op |
397
451
  | Errors | minimal `CrudianError`; other errors propagate from the driver |
398
452
  | Identifiers | string required; no format validation |
399
- | Out of scope | relations, migrations, full-text search, ORM models |
453
+ | Out of scope | relations graph CRUD, migrations ownership, full-text search, Entity-as-first-arg API |
400
454
 
401
455
  ## License
402
456
 
@@ -1,2 +1,6 @@
1
- export { compileWhere, quoteIdent, resolveWhere, } from "../sqlite/sql.js";
1
+ import type { WhereNode } from "../index.js";
2
+ import { quoteIdent, resolveWhere, type CompiledWhere } from "../sqlite/sql.js";
3
+ export { quoteIdent, resolveWhere };
4
+ /** SQLite-default compileWhere (compat for bun-sqlite tests). */
5
+ export declare function compileWhere(node: WhereNode | undefined, startIndex?: number): CompiledWhere;
2
6
  //# sourceMappingURL=sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/bun-sqlite/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/bun-sqlite/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,OAAO,EAEL,UAAU,EACV,YAAY,EACZ,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,CAAA;AAEnC,iEAAiE;AACjE,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,UAAU,SAAI,GACb,aAAa,CAEf"}
@@ -1 +1,7 @@
1
- export { compileWhere, quoteIdent, resolveWhere, } from "../sqlite/sql.js";
1
+ import { sqliteDialect } from "../dialect/sqlite.js";
2
+ import { compileWhere as compileWhereDialect, quoteIdent, resolveWhere, } from "../sqlite/sql.js";
3
+ export { quoteIdent, resolveWhere };
4
+ /** SQLite-default compileWhere (compat for bun-sqlite tests). */
5
+ export function compileWhere(node, startIndex = 1) {
6
+ return compileWhereDialect(sqliteDialect, node, startIndex);
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { Dialect, DialectName } from "./types.js";
2
+ export type { Dialect, DialectName, DescribeColumnsQuery } from "./types.js";
3
+ export { sqliteDialect } from "./sqlite.js";
4
+ export { postgresDialect } from "./postgres.js";
5
+ export { mysqlDialect } from "./mysql.js";
6
+ export declare function resolveDialect(name: DialectName | undefined): Dialect;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialect/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEtD,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAWrE"}
@@ -0,0 +1,19 @@
1
+ import { CrudianError } from "../index.js";
2
+ import { mysqlDialect } from "./mysql.js";
3
+ import { postgresDialect } from "./postgres.js";
4
+ import { sqliteDialect } from "./sqlite.js";
5
+ export { sqliteDialect } from "./sqlite.js";
6
+ export { postgresDialect } from "./postgres.js";
7
+ export { mysqlDialect } from "./mysql.js";
8
+ export function resolveDialect(name) {
9
+ switch (name ?? "sqlite") {
10
+ case "sqlite":
11
+ return sqliteDialect;
12
+ case "postgres":
13
+ return postgresDialect;
14
+ case "mysql":
15
+ return mysqlDialect;
16
+ default:
17
+ throw new CrudianError(`unknown dialect: ${String(name)}`);
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ import type { Dialect } from "./types.js";
2
+ /** MySQL dialect: `` `ident` ``, `?`, LAST_INSERT_ID, information_schema (no RETURNING). */
3
+ export declare const mysqlDialect: Dialect;
4
+ //# sourceMappingURL=mysql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../src/dialect/mysql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAGzC,4FAA4F;AAC5F,eAAO,MAAM,YAAY,EAAE,OA0B1B,CAAA"}
@@ -0,0 +1,24 @@
1
+ import { assertIdent } from "./types.js";
2
+ /** MySQL dialect: `` `ident` ``, `?`, LAST_INSERT_ID, information_schema (no RETURNING). */
3
+ export const mysqlDialect = {
4
+ name: "mysql",
5
+ supportsInsertReturning: false,
6
+ quoteIdent(name) {
7
+ const n = assertIdent(name);
8
+ return `\`${n.replaceAll("`", "``")}\``;
9
+ },
10
+ placeholder(_i) {
11
+ return "?";
12
+ },
13
+ lastInsertIdSql() {
14
+ return "SELECT LAST_INSERT_ID() AS id";
15
+ },
16
+ describeColumns(table) {
17
+ const n = assertIdent(table);
18
+ return {
19
+ sql: `SELECT COLUMN_NAME AS name FROM information_schema.COLUMNS` +
20
+ ` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
21
+ args: [n],
22
+ };
23
+ },
24
+ };
@@ -0,0 +1,4 @@
1
+ import type { Dialect } from "./types.js";
2
+ /** Postgres dialect: `"ident"`, `$n`, RETURNING *, information_schema. */
3
+ export declare const postgresDialect: Dialect;
4
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/dialect/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAGzC,0EAA0E;AAC1E,eAAO,MAAM,eAAe,EAAE,OA2B7B,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { assertIdent } from "./types.js";
2
+ /** Postgres dialect: `"ident"`, `$n`, RETURNING *, information_schema. */
3
+ export const postgresDialect = {
4
+ name: "postgres",
5
+ supportsInsertReturning: true,
6
+ quoteIdent(name) {
7
+ const n = assertIdent(name);
8
+ return `"${n.replaceAll('"', '""')}"`;
9
+ },
10
+ placeholder(i) {
11
+ return `$${i}`;
12
+ },
13
+ lastInsertIdSql() {
14
+ // Prefer RETURNING; this is a fallback for sync-style paths.
15
+ return "SELECT lastval() AS id";
16
+ },
17
+ describeColumns(table) {
18
+ const n = assertIdent(table);
19
+ return {
20
+ sql: `SELECT column_name AS name FROM information_schema.columns` +
21
+ ` WHERE table_schema = current_schema() AND table_name = $1`,
22
+ args: [n],
23
+ };
24
+ },
25
+ };
@@ -0,0 +1,4 @@
1
+ import type { Dialect } from "./types.js";
2
+ /** SQLite / libSQL dialect: `"ident"`, `?`, PRAGMA, last_insert_rowid / RETURNING. */
3
+ export declare const sqliteDialect: Dialect;
4
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/dialect/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAGzC,sFAAsF;AACtF,eAAO,MAAM,aAAa,EAAE,OAwB3B,CAAA"}
@@ -0,0 +1,23 @@
1
+ import { assertIdent } from "./types.js";
2
+ /** SQLite / libSQL dialect: `"ident"`, `?`, PRAGMA, last_insert_rowid / RETURNING. */
3
+ export const sqliteDialect = {
4
+ name: "sqlite",
5
+ supportsInsertReturning: true,
6
+ quoteIdent(name) {
7
+ const n = assertIdent(name);
8
+ return `"${n.replaceAll('"', '""')}"`;
9
+ },
10
+ placeholder(_i) {
11
+ return "?";
12
+ },
13
+ lastInsertIdSql() {
14
+ return "SELECT last_insert_rowid() AS id";
15
+ },
16
+ describeColumns(table) {
17
+ const n = assertIdent(table);
18
+ return {
19
+ sql: `PRAGMA table_info(${sqliteDialect.quoteIdent(n)})`,
20
+ args: [],
21
+ };
22
+ },
23
+ };
@@ -0,0 +1,25 @@
1
+ /** Supported SQL dialect names. */
2
+ export type DialectName = "sqlite" | "postgres" | "mysql";
3
+ /** Result of compiling a column-description query for PK checks. */
4
+ export type DescribeColumnsQuery = {
5
+ sql: string;
6
+ args: unknown[];
7
+ };
8
+ /**
9
+ * SQL dialect hooks for shared CRUD.
10
+ * Pool / connection lifetime is intentionally not part of this interface.
11
+ */
12
+ export type Dialect = {
13
+ readonly name: DialectName;
14
+ quoteIdent(name: string): string;
15
+ /** 1-based placeholder index into the statement args. */
16
+ placeholder(i: number): string;
17
+ /** When true, async create prefers `INSERT … RETURNING *`. */
18
+ readonly supportsInsertReturning: boolean;
19
+ /** Query returning a single column `id` for fetch-after-insert. */
20
+ lastInsertIdSql(): string;
21
+ /** List column names (`name` field) for a table. */
22
+ describeColumns(table: string): DescribeColumnsQuery;
23
+ };
24
+ export declare function assertIdent(name: string): string;
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dialect/types.ts"],"names":[],"mappings":"AAEA,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;CAChB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,yDAAyD;IACzD,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAA;IACzC,mEAAmE;IACnE,eAAe,IAAI,MAAM,CAAA;IACzB,oDAAoD;IACpD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAAA;CACrD,CAAA;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD"}
@@ -0,0 +1,5 @@
1
+ import { assertString } from "../index.js";
2
+ export function assertIdent(name) {
3
+ assertString(name, "identifier");
4
+ return name;
5
+ }
package/dist/index.d.ts CHANGED
@@ -5,8 +5,10 @@
5
5
  * - @b4moss/crudian/drizzle
6
6
  * - @b4moss/crudian/prisma
7
7
  * - @b4moss/crudian/libsql
8
+ * - @b4moss/crudian/typeorm
8
9
  */
9
10
  export { CrudianError, assertString } from "./errors.js";
10
11
  export { WhereBuilder, where, isWhereBuilder, type Op, type CondNode, type GroupNode, type WhereNode, } from "./where.js";
11
- export type { Row, OffsetSearchResult, CursorSearchResult, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, CreateCrudOptions, } from "./types.js";
12
+ export type { Row, OffsetSearchResult, CursorSearchResult, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, CreateCrudOptions, CreateCrudDialect, } from "./types.js";
13
+ export type { PoolOptions } from "./pool.js";
12
14
  //# 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,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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * - @b4moss/crudian/drizzle
6
6
  * - @b4moss/crudian/prisma
7
7
  * - @b4moss/crudian/libsql
8
+ * - @b4moss/crudian/typeorm
8
9
  */
9
10
  export { CrudianError, assertString } from "./errors.js";
10
11
  export { WhereBuilder, where, isWhereBuilder, } from "./where.js";
package/dist/pool.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Optional pool / lifetime hints for documentation and future driver adapters.
3
+ * Prisma owns pooling via datasource / engine settings — callers configure that
4
+ * on the PrismaClient they inject. crudian does not open connections.
5
+ *
6
+ * Not part of the Dialect interface (#105 / #73).
7
+ */
8
+ export type PoolOptions = {
9
+ /** Max open connections (driver-dependent). */
10
+ maxOpenConns?: number;
11
+ /** Max idle connections. */
12
+ maxIdleConns?: number;
13
+ /** Max connection lifetime in milliseconds. */
14
+ connMaxLifetimeMs?: number;
15
+ /** Max idle time in milliseconds. */
16
+ connMaxIdleTimeMs?: number;
17
+ };
18
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qCAAqC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA"}
package/dist/pool.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -8,8 +8,9 @@ export type PrismaLikeClient = {
8
8
  };
9
9
  export type PrismaCrud = AsyncSqliteCrud<PrismaLikeClient>;
10
10
  /**
11
- * Create a Crud bound to a PrismaClient (SQLite).
11
+ * Create a Crud bound to a PrismaClient.
12
12
  * Methods are async. The injected client is exposed as `crud.db`.
13
+ * Pass `options.dialect` for Postgres / MySQL (`"sqlite"` default).
13
14
  */
14
15
  export declare function createCrud(client: PrismaLikeClient, options?: CreateCrudOptions): PrismaCrud;
15
16
  //# sourceMappingURL=crud.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/prisma/crud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAElE,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;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CAgDZ"}
@@ -1,5 +1,6 @@
1
1
  import { CrudianError } from "../index.js";
2
- import { createAsyncSqliteCrud, } from "../sqlite/async-crud.js";
2
+ import { resolveDialect } from "../dialect/index.js";
3
+ import { createAsyncCrud, } from "../sqlite/async-crud.js";
3
4
  function normalizeRow(row) {
4
5
  const out = { ...row };
5
6
  for (const [k, v] of Object.entries(out)) {
@@ -9,8 +10,9 @@ function normalizeRow(row) {
9
10
  return out;
10
11
  }
11
12
  /**
12
- * Create a Crud bound to a PrismaClient (SQLite).
13
+ * Create a Crud bound to a PrismaClient.
13
14
  * Methods are async. The injected client is exposed as `crud.db`.
15
+ * Pass `options.dialect` for Postgres / MySQL (`"sqlite"` default).
14
16
  */
15
17
  export function createCrud(client, options) {
16
18
  if (client == null) {
@@ -23,7 +25,8 @@ export function createCrud(client, options) {
23
25
  throw new CrudianError("db must be a PrismaClient");
24
26
  }
25
27
  let active = client;
26
- return createAsyncSqliteCrud(client, {
28
+ const dialect = resolveDialect(options?.dialect);
29
+ return createAsyncCrud(client, {
27
30
  async run(sql, args = []) {
28
31
  const changes = await active.$executeRawUnsafe(sql, ...args);
29
32
  return { changes: Number(changes ?? 0) };
@@ -50,7 +53,7 @@ export function createCrud(client, options) {
50
53
  }
51
54
  });
52
55
  },
53
- }, options);
56
+ }, dialect, options);
54
57
  }
55
58
  function normalizeQueryResult(result) {
56
59
  if (result == null)
@@ -0,0 +1,16 @@
1
+ import { type PrismaLikeClient } from "./index.js";
2
+ export type DialectName = "postgres" | "mysql";
3
+ export type ServerDbHarness = {
4
+ dialect: DialectName;
5
+ envKey: "DATABASE_URL_POSTGRES" | "DATABASE_URL_MYSQL";
6
+ createClient: () => PrismaLikeClient & {
7
+ $disconnect(): Promise<void>;
8
+ $executeRawUnsafe(q: string, ...a: unknown[]): Promise<number>;
9
+ };
10
+ createItemsDDL: string;
11
+ dropItemsDDL: string;
12
+ createAltPkDDL: string;
13
+ dropAltPkDDL: string;
14
+ };
15
+ export declare function registerPrismaServerDialectTests(h: ServerDbHarness): void;
16
+ //# sourceMappingURL=server-dialect-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-dialect-harness.d.ts","sourceRoot":"","sources":["../../src/prisma/server-dialect-harness.ts"],"names":[],"mappings":"AAOA,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE9D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,CAAA;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,EAAE,uBAAuB,GAAG,oBAAoB,CAAA;IACtD,YAAY,EAAE,MAAM,gBAAgB,GAAG;QAAE,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IACvI,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,wBAAgB,gCAAgC,CAAC,CAAC,EAAE,eAAe,QA0IlE"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Shared contract suite for Prisma against Postgres / MySQL.
3
+ * Skipped automatically when the DATABASE_URL_* env var is unset.
4
+ */
5
+ import assert from "node:assert/strict";
6
+ import { after, before, describe, test } from "node:test";
7
+ import { CrudianError, where } from "../index.js";
8
+ import { createCrud } from "./index.js";
9
+ export function registerPrismaServerDialectTests(h) {
10
+ const url = process.env[h.envKey];
11
+ const suite = describe;
12
+ suite(`prisma.${h.dialect}`, { skip: !url }, () => {
13
+ let client;
14
+ let crud;
15
+ before(async () => {
16
+ if (!url)
17
+ return;
18
+ process.env[h.envKey] = url;
19
+ client = h.createClient();
20
+ await client.$executeRawUnsafe(h.dropItemsDDL);
21
+ await client.$executeRawUnsafe(h.createItemsDDL);
22
+ crud = createCrud(client, { dialect: h.dialect });
23
+ });
24
+ after(async () => {
25
+ if (!url)
26
+ return;
27
+ try {
28
+ await client.$executeRawUnsafe(h.dropItemsDDL);
29
+ await client.$executeRawUnsafe(h.dropAltPkDDL);
30
+ }
31
+ catch {
32
+ /* ignore */
33
+ }
34
+ await client.$disconnect();
35
+ });
36
+ async function reset() {
37
+ await client.$executeRawUnsafe(h.dropItemsDDL);
38
+ await client.$executeRawUnsafe(h.createItemsDDL);
39
+ }
40
+ test("正常系: create / read / update / delete", async () => {
41
+ await reset();
42
+ const created = await crud.create("items", { name: "alice", score: 10 });
43
+ assert.equal(created.name, "alice");
44
+ assert.ok(created.id != null);
45
+ const got = await crud.read("items", {
46
+ where: where().eq("id", created.id),
47
+ });
48
+ assert.equal(got?.name, "alice");
49
+ const updated = await crud.update("items", { score: 20 }, { where: where().eq("id", created.id) });
50
+ assert.equal(updated?.score, 20);
51
+ const n = await crud.delete("items", {
52
+ where: where().eq("id", created.id),
53
+ });
54
+ assert.equal(n, 1);
55
+ assert.equal(await crud.read("items", { where: where().eq("id", created.id) }), null);
56
+ });
57
+ test("正常系: count / exists / search offset / cursor", async () => {
58
+ await reset();
59
+ await crud.bulkCreate("items", [
60
+ { name: "a", score: 1 },
61
+ { name: "b", score: 2 },
62
+ { name: "c", score: 3 },
63
+ ]);
64
+ assert.equal(await crud.count("items"), 3);
65
+ assert.equal(await crud.exists("items", { where: where().eq("name", "b") }), true);
66
+ assert.equal(await crud.exists("items", { where: where().eq("name", "z") }), false);
67
+ const page = await crud.search("items", { limit: 2, offset: 0 });
68
+ assert.equal(page.items.length, 2);
69
+ assert.equal("total" in page && page.total, 3);
70
+ assert.equal("hasMore" in page && page.hasMore, true);
71
+ const cursorPage = await crud.search("items", {
72
+ paging: "cursor",
73
+ limit: 2,
74
+ });
75
+ assert.equal(cursorPage.items.length, 2);
76
+ assert.ok("nextCursor" in cursorPage);
77
+ });
78
+ test("正常系: upsert / duplicate / transaction", async () => {
79
+ await reset();
80
+ const a = await crud.create("items", { name: "x", score: 1 });
81
+ const up = await crud.upsert("items", {
82
+ id: a.id,
83
+ name: "x",
84
+ score: 9,
85
+ });
86
+ assert.equal(up.score, 9);
87
+ const dup = await crud.duplicate("items", {
88
+ where: where().eq("id", a.id),
89
+ overrides: { name: "y" },
90
+ });
91
+ assert.ok(dup);
92
+ assert.notEqual(dup.id, a.id);
93
+ assert.equal(dup.name, "y");
94
+ await crud.transaction(async () => {
95
+ await crud.create("items", { name: "tx", score: 1 });
96
+ });
97
+ assert.equal(await crud.exists("items", { where: where().eq("name", "tx") }), true);
98
+ });
99
+ test("正常系: 可変 PK", async () => {
100
+ await reset();
101
+ await client.$executeRawUnsafe(h.dropAltPkDDL);
102
+ await client.$executeRawUnsafe(h.createAltPkDDL);
103
+ const alt = createCrud(client, { dialect: h.dialect, pk: "item_id" });
104
+ const row = await alt.create("alt_items", { name: "pk" });
105
+ assert.ok(row.item_id != null);
106
+ const got = await alt.read("alt_items", {
107
+ where: where().eq("item_id", row.item_id),
108
+ });
109
+ assert.equal(got?.name, "pk");
110
+ await client.$executeRawUnsafe(h.dropAltPkDDL);
111
+ });
112
+ test("異常系: 空 in / 不正 dialect は拒否", async () => {
113
+ await reset();
114
+ await assert.rejects(() => crud.search("items", {
115
+ where: where().in("id", []),
116
+ }), CrudianError);
117
+ assert.throws(() => createCrud(client, { dialect: "nope" }), CrudianError);
118
+ });
119
+ });
120
+ }
@@ -1,4 +1,5 @@
1
1
  import { type CountQuery, type CreateCrudOptions, type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
2
+ import type { Dialect } from "../dialect/types.js";
2
3
  export type AsyncSqliteExecutor = {
3
4
  run(sql: string, args?: unknown[]): Promise<{
4
5
  changes: number;
@@ -25,5 +26,8 @@ export type AsyncSqliteCrud<TDb> = {
25
26
  exists(table: string, query?: CountQuery): Promise<boolean>;
26
27
  transaction<T>(fn: () => Promise<T>): Promise<T>;
27
28
  };
29
+ /** Dialect-aware async CRUD factory. */
30
+ export declare function createAsyncCrud<TDb>(db: TDb, ex: AsyncSqliteExecutor, dialect: Dialect, options?: CreateCrudOptions): AsyncSqliteCrud<TDb>;
31
+ /** SQLite async CRUD (compat). */
28
32
  export declare function createAsyncSqliteCrud<TDb>(db: TDb, ex: AsyncSqliteExecutor, options?: CreateCrudOptions): AsyncSqliteCrud<TDb>;
29
33
  //# 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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"}