@b4moss/crudian 0.9.0 → 0.10.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 +32 -8
- package/dist/bun-sqlite/sql.d.ts +5 -1
- package/dist/bun-sqlite/sql.d.ts.map +1 -1
- package/dist/bun-sqlite/sql.js +7 -1
- package/dist/dialect/index.d.ts +7 -0
- package/dist/dialect/index.d.ts.map +1 -0
- package/dist/dialect/index.js +19 -0
- package/dist/dialect/mysql.d.ts +4 -0
- package/dist/dialect/mysql.d.ts.map +1 -0
- package/dist/dialect/mysql.js +24 -0
- package/dist/dialect/postgres.d.ts +4 -0
- package/dist/dialect/postgres.d.ts.map +1 -0
- package/dist/dialect/postgres.js +25 -0
- package/dist/dialect/sqlite.d.ts +4 -0
- package/dist/dialect/sqlite.d.ts.map +1 -0
- package/dist/dialect/sqlite.js +23 -0
- package/dist/dialect/types.d.ts +25 -0
- package/dist/dialect/types.d.ts.map +1 -0
- package/dist/dialect/types.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/pool.d.ts +18 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +1 -0
- package/dist/prisma/crud.d.ts +2 -1
- package/dist/prisma/crud.d.ts.map +1 -1
- package/dist/prisma/crud.js +7 -4
- package/dist/prisma/server-dialect-harness.d.ts +16 -0
- package/dist/prisma/server-dialect-harness.d.ts.map +1 -0
- package/dist/prisma/server-dialect-harness.js +120 -0
- package/dist/sqlite/async-crud.d.ts +4 -0
- package/dist/sqlite/async-crud.d.ts.map +1 -1
- package/dist/sqlite/async-crud.js +57 -36
- package/dist/sqlite/pk.d.ts +6 -13
- package/dist/sqlite/pk.d.ts.map +1 -1
- package/dist/sqlite/pk.js +13 -6
- package/dist/sqlite/sql.d.ts +10 -1
- package/dist/sqlite/sql.d.ts.map +1 -1
- package/dist/sqlite/sql.js +60 -23
- package/dist/sqlite/sync-crud.d.ts +4 -0
- package/dist/sqlite/sync-crud.d.ts.map +1 -1
- package/dist/sqlite/sync-crud.js +48 -35
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -69,18 +69,39 @@ const db = drizzle(sqlite)
|
|
|
69
69
|
const crud = createCrud(db)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
### Prisma (SQLite) — async
|
|
72
|
+
### Prisma (SQLite / Postgres / MySQL) — async
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
75
|
import { PrismaClient } from "@prisma/client"
|
|
76
76
|
import { createCrud } from "@b4moss/crudian/prisma"
|
|
77
77
|
|
|
78
78
|
const client = new PrismaClient()
|
|
79
|
-
|
|
79
|
+
// Default dialect is "sqlite". For server DBs:
|
|
80
|
+
const crudPg = createCrud(client, { dialect: "postgres" })
|
|
81
|
+
const crudMy = createCrud(client, { dialect: "mysql" })
|
|
82
|
+
const crudSqlite = createCrud(client) // or { dialect: "sqlite" }
|
|
80
83
|
|
|
81
|
-
await
|
|
84
|
+
await crudSqlite.create("items", { name: "alpha", score: 1 })
|
|
82
85
|
```
|
|
83
86
|
|
|
87
|
+
Match `options.dialect` to the Prisma schema `provider`. Placeholders and
|
|
88
|
+
insert-return strategies follow the dialect (`$n` + `RETURNING` on Postgres;
|
|
89
|
+
`` `ident` `` + `LAST_INSERT_ID` fetch on MySQL — no `RETURNING`).
|
|
90
|
+
|
|
91
|
+
### Connection pool / lifetime (JS)
|
|
92
|
+
|
|
93
|
+
crudian never opens connections. For **Postgres / MySQL**, configure pooling on
|
|
94
|
+
the client you inject:
|
|
95
|
+
|
|
96
|
+
| Adapter | How to set pool |
|
|
97
|
+
|---------|-----------------|
|
|
98
|
+
| **Prisma** | Prisma datasource / connection URL params / engine pool settings (caller-owned). See Prisma docs for your provider. |
|
|
99
|
+
| **bun-sqlite / drizzle (sqlite) / libsql** | No pool settings needed (no-op). |
|
|
100
|
+
|
|
101
|
+
Optional type for docs / future drivers: `PoolOptions` from `@b4moss/crudian`
|
|
102
|
+
(not applied by Prisma — configure the client instead). Go GORM uses
|
|
103
|
+
`crudian.Options.Pool` / `ApplyPool` (see `packages/go/README.md`).
|
|
104
|
+
|
|
84
105
|
### libSQL (`@libsql/client`) — async
|
|
85
106
|
|
|
86
107
|
```ts
|
|
@@ -385,18 +406,21 @@ Nestable `and` / `or`. Empty `in([])` is rejected.
|
|
|
385
406
|
|
|
386
407
|
| Topic | Behavior |
|
|
387
408
|
|-------|----------|
|
|
388
|
-
| Entry | `createCrud(db)` — inject a caller-owned client; exposed as `crud.db` |
|
|
409
|
+
| Entry | `createCrud(db, options?)` — inject a caller-owned client; exposed as `crud.db` |
|
|
410
|
+
| Dialects | Shared Dialect layer: SQLite (all adapters), Postgres / MySQL via **Prisma** (`options.dialect`) |
|
|
411
|
+
| Drizzle | **SQLite (better-sqlite3) only** in this line; PG/MySQL subpaths are out of scope |
|
|
389
412
|
| Sync vs async | `bun-sqlite` / `drizzle` sync; `prisma` / `libsql` return `Promise`s |
|
|
390
413
|
| `read` / `update` / `duplicate` miss | `null` |
|
|
391
414
|
| `delete` / bulk miss | `0` |
|
|
392
|
-
| Upsert conflict |
|
|
393
|
-
| Pagination |
|
|
415
|
+
| Upsert conflict | application-level on PK (default `id`; no SQL `ON CONFLICT`) |
|
|
416
|
+
| Pagination | `paging?: "offset" \| "cursor"` (default `"offset"`) |
|
|
394
417
|
| `columns` | optional on `read` / `search` / `list`; omit → `*` |
|
|
395
418
|
| `search.total` / `count` | full where count; not page length |
|
|
396
|
-
| `exists` | boolean presence; same input as `count
|
|
419
|
+
| `exists` | boolean presence; same input as `count` |
|
|
420
|
+
| Pool | caller-owned; Prisma via datasource settings; SQLite adapters no-op |
|
|
397
421
|
| Errors | minimal `CrudianError`; other errors propagate from the driver |
|
|
398
422
|
| Identifiers | string required; no format validation |
|
|
399
|
-
| Out of scope | relations, migrations, full-text search, ORM models |
|
|
423
|
+
| Out of scope | relations, migrations, full-text search, ORM models, TypeORM (#43) |
|
|
400
424
|
|
|
401
425
|
## License
|
|
402
426
|
|
package/dist/bun-sqlite/sql.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
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,
|
|
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"}
|
package/dist/bun-sqlite/sql.js
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,6 @@
|
|
|
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, OffsetSearchResult, CursorSearchResult, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, CreateCrudOptions, } from "./types.js";
|
|
11
|
+
export type { Row, OffsetSearchResult, CursorSearchResult, SearchResult, WhereInput, ReadQuery, SearchQuery, CountQuery, DeleteQuery, UpdateQuery, DuplicateQuery, CreateCrudOptions, CreateCrudDialect, } from "./types.js";
|
|
12
|
+
export type { PoolOptions } from "./pool.js";
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;;;;;;;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,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
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 {};
|
package/dist/prisma/crud.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
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"}
|
package/dist/prisma/crud.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CrudianError } from "../index.js";
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,GAAG,EACR,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"async-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/async-crud.ts"],"names":[],"mappings":"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"}
|