@avytrix/nestjs-drizzle-crud 3.6.0 → 3.7.1
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/AGENT.md +17 -9
- package/CHANGELOG.md +56 -1
- package/README.md +56 -20
- package/dist/core/abstract/sql-base-crud.service.d.ts +13 -2
- package/dist/core/abstract/sql-base-crud.service.js +158 -46
- package/dist/core/abstract/sql-base-crud.service.js.map +1 -1
- package/dist/core/interfaces/drizzle-crud-config.interface.d.ts +16 -0
- package/dist/core/interfaces/sql-crud-config.interface.d.ts +9 -1
- package/dist/core/types/sql.types.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/drizzle-connection.d.ts +3 -1
- package/dist/modules/drizzle-connection.js +49 -2
- package/dist/modules/drizzle-connection.js.map +1 -1
- package/dist/modules/drizzle-crud.module.d.ts +2 -6
- package/dist/modules/drizzle-crud.module.js +28 -6
- package/dist/modules/drizzle-crud.module.js.map +1 -1
- package/package.json +10 -2
package/AGENT.md
CHANGED
|
@@ -60,11 +60,16 @@ pnpm add @avytrix/nestjs-drizzle-crud
|
|
|
60
60
|
pnpm add @nestjs/common @nestjs/core drizzle-orm reflect-metadata # peers, always
|
|
61
61
|
pnpm add postgres # PostgreSQL
|
|
62
62
|
pnpm add mysql2 # MySQL
|
|
63
|
+
pnpm add @libsql/client # SQLite / libSQL
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
`postgres`
|
|
66
|
-
the connection from a `connectionString`
|
|
67
|
-
`
|
|
66
|
+
`postgres` and `mysql2` are optional peers. You only need them when you let the
|
|
67
|
+
module build the connection from a `connectionString` (PostgreSQL or MySQL;
|
|
68
|
+
`connection: { poolSize, idleTimeout, ssl }` tunes it). If you pass your own
|
|
69
|
+
`db`, no driver peer is required. SQLite (`dialect: 'sqlite'`, also libSQL and
|
|
70
|
+
Cloudflare D1) always takes your own `db`. On D1 and synchronous SQLite drivers
|
|
71
|
+
the mass writes, nested creates and attach / detach / sync throw a
|
|
72
|
+
`TransactionException`; use libSQL when you need them.
|
|
68
73
|
|
|
69
74
|
Requires Node >= 20 and `emitDecoratorMetadata` / `experimentalDecorators` in
|
|
70
75
|
`tsconfig.json`, which the Nest CLI already sets.
|
|
@@ -87,7 +92,8 @@ DrizzleCrudModule.forRootAsync({
|
|
|
87
92
|
```
|
|
88
93
|
|
|
89
94
|
Use `forRoot` for a static config and `forRootAsync` when the value comes from
|
|
90
|
-
`ConfigService` or the environment
|
|
95
|
+
`ConfigService` or the environment (`useFactory`, or `useClass` / `useExisting`
|
|
96
|
+
with a class that implements `DrizzleCrudOptionsFactory`). Provide exactly one of `connectionString`
|
|
91
97
|
or `db`. The module is global, so feature modules do not import it again for
|
|
92
98
|
the connection.
|
|
93
99
|
|
|
@@ -138,7 +144,7 @@ Those are legacy manual patterns and they break registration.
|
|
|
138
144
|
| OpenAPI | `@ApiCrudList(source)`, `@ApiCrudQuery(source)` (needs `@nestjs/swagger`) |
|
|
139
145
|
| Bulk | `massCreate`, `massUpdate`, `massUpsert`, `massSoftDelete`, `massRestore`, `massDelete`, `massForceDelete` |
|
|
140
146
|
| Query string | `parseQuery(query, options)`, or the standalone `parseCrudQuery(query, table, options)` |
|
|
141
|
-
| Search | `fullTextSearch(term, columns, pagination)`, PostgreSQL only |
|
|
147
|
+
| Search | `fullTextSearch(term, columns, pagination, { filters, relations, select, language, queryMode })`, PostgreSQL only |
|
|
142
148
|
| Transaction | `executeSqlTransaction(cb)` |
|
|
143
149
|
|
|
144
150
|
`findAll` returns `{ data, total, page, limit }`, not a bare array; with
|
|
@@ -427,7 +433,8 @@ Driver error codes are translated at the service boundary, so bad client input
|
|
|
427
433
|
never reaches the caller as a 500: a unique violation is a 409, a value that is
|
|
428
434
|
too long, fails a check, has the wrong type or is not a valid date is a 400 (on
|
|
429
435
|
reads too), a foreign key pointing at a row that does not exist is a 400, and a
|
|
430
|
-
delete blocked by dependent rows is a 409.
|
|
436
|
+
delete blocked by dependent rows is a 409. The same holds on MySQL (strict mode
|
|
437
|
+
errors included) and SQLite (constraint errors).
|
|
431
438
|
|
|
432
439
|
---
|
|
433
440
|
|
|
@@ -452,9 +459,10 @@ To insert or update by a unique key, use
|
|
|
452
459
|
than a find followed by a create.
|
|
453
460
|
|
|
454
461
|
Take a row lock with `options.lock: 'update' | 'share'` only inside a
|
|
455
|
-
transaction, and prefer `forNoKeyUpdate` when the key is not changing.
|
|
456
|
-
covers the service's own table only (`FOR UPDATE OF
|
|
457
|
-
an eager belongs-to.
|
|
462
|
+
transaction, and prefer `forNoKeyUpdate` when the key is not changing. On
|
|
463
|
+
PostgreSQL the lock covers the service's own table only (`FOR UPDATE OF
|
|
464
|
+
<table>`), so it works with an eager belongs-to. MySQL gets `FOR UPDATE` or
|
|
465
|
+
`FOR SHARE`; on SQLite a lock is a 400.
|
|
458
466
|
|
|
459
467
|
---
|
|
460
468
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to `@avytrix/nestjs-drizzle-crud` (published as `nestjs-drizzle-crud` up to 3.
|
|
3
|
+
All notable changes to `@avytrix/nestjs-drizzle-crud` (published as `nestjs-drizzle-crud` up to 3.3.0) are documented here.
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
@@ -8,6 +8,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
Scope note: the 3.0.x line is PostgreSQL-first. MySQL-specific paths (non-`RETURNING`
|
|
9
9
|
restore, `ER_DUP_ENTRY` mapping, dialect locking) are tracked separately and unchanged here.
|
|
10
10
|
|
|
11
|
+
3.3.1, 3.4.0, 3.5.0 and 3.5.2 were tagged but never published to npm. Their changes reached npm
|
|
12
|
+
in 3.5.1 (3.3.1 to 3.5.0) and 3.6.0 (3.5.2). The unscoped `nestjs-drizzle-crud` stops at 3.3.0.
|
|
13
|
+
|
|
14
|
+
## [3.7.1] - 2026-11-02
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- The error for an upsert on a scoped entity on MySQL now reads "upsert on a scoped entity needs
|
|
19
|
+
PostgreSQL or SQLite". It said PostgreSQL only, though SQLite supports a scoped upsert since 3.7.0.
|
|
20
|
+
- `?fields=` paths named `__proto__`, `constructor` or `prototype` are treated as plain names.
|
|
21
|
+
Before, they reached the select tree's object internals (CodeQL js/remote-property-injection).
|
|
22
|
+
|
|
23
|
+
## [3.7.0] - 2026-11-02
|
|
24
|
+
|
|
25
|
+
SQLite support, MySQL connections built for you, and full-text search that works like `findAll`.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `dialect: "sqlite"` for SQLite, libSQL (Turso) and Cloudflare D1. Pass the Drizzle instance as
|
|
30
|
+
`db`. Writes use `RETURNING`, `upsert` uses `ON CONFLICT DO UPDATE` with the tenant guard, and
|
|
31
|
+
keyset pages follow SQLite's NULL order. `LIKE` gets `ESCAPE '\'`, so `%`, `_` and `\` in a
|
|
32
|
+
search term match literally. Constraint errors map like the other dialects: `UNIQUE` 409,
|
|
33
|
+
`FOREIGN KEY` 400 on a write and 409 on a delete, `NOT NULL` and `CHECK` 400. Full-text search,
|
|
34
|
+
row locks and the array and JSON operators are a 400.
|
|
35
|
+
- On Cloudflare D1 and synchronous SQLite drivers (better-sqlite3, bun:sqlite, sql.js), the calls
|
|
36
|
+
that need an interactive transaction (mass writes, nested creates, `attach`, `detach`, `sync`)
|
|
37
|
+
throw a `TransactionException` that says why, before any statement runs. `TransactionException`
|
|
38
|
+
is no longer deprecated.
|
|
39
|
+
- `connectionString` works with `dialect: "mysql"`: the module builds a `mysql2` pool, wraps it
|
|
40
|
+
with `drizzle-orm/mysql2` and ends it on shutdown. `mysql2` is a new optional peer, loaded only
|
|
41
|
+
on that path.
|
|
42
|
+
- `connection: { poolSize, idleTimeout, ssl }` for the connection the module builds from
|
|
43
|
+
`connectionString`, on PostgreSQL (postgres-js) and MySQL (mysql2).
|
|
44
|
+
- `forRootAsync` takes `useClass` and `useExisting`, with a class that implements
|
|
45
|
+
`DrizzleCrudOptionsFactory` (`createDrizzleCrudOptions()`), next to `useFactory`.
|
|
46
|
+
- `fullTextSearch()` takes `options.filters`, and honours `options.relations`, `options.select`
|
|
47
|
+
and `options.counts`, the same as `findAll`. Before, they were ignored.
|
|
48
|
+
- `language` (the text search configuration, default `"english"`) and `queryMode` (`"plain"`, the
|
|
49
|
+
default, or `"websearch"` for `websearch_to_tsquery`) on `fullTextSearch()` options and on
|
|
50
|
+
`search` with `mode: "fullText"`. A language that is not a plain identifier is a 400.
|
|
51
|
+
- New types: `DrizzleConnectionOptions`, `DrizzleCrudAsyncOptions`, `DrizzleCrudOptionsFactory`,
|
|
52
|
+
`FullTextOptions`, `FullTextQueryMode` and `FullTextSearchOptions`.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- MySQL: `lock` and `forNoKeyUpdate` add `FOR UPDATE` or `FOR SHARE` (`forNoKeyUpdate` becomes
|
|
57
|
+
`FOR UPDATE`). They used to be accepted and silently ignored.
|
|
58
|
+
- MySQL strict mode data errors (a missing required value, a value too long, out of range, of
|
|
59
|
+
the wrong type, a bad date, a failed check) are a 400 that names the column, instead of a 500.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- The default `sql.useReturning` is `true` for every dialect except MySQL, so SQLite gets it.
|
|
64
|
+
PostgreSQL and MySQL keep the values they had.
|
|
65
|
+
|
|
11
66
|
## [3.6.0] - 2026-11-02
|
|
12
67
|
|
|
13
68
|
Fixes from the second audit. The first one is a security fix: upgrade if you use `scope`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://github.com/sponsors/avytrix)
|
|
7
7
|
|
|
8
|
-
**nestjs-drizzle-crud is a NestJS + Drizzle ORM CRUD library: extend one base class and every table gets type-safe find, create, update, delete, filtering, pagination, relations and soft delete, on PostgreSQL or
|
|
8
|
+
**nestjs-drizzle-crud is a NestJS + Drizzle ORM CRUD library: extend one base class and every table gets type-safe find, create, update, delete, filtering, pagination, relations and soft delete, on PostgreSQL, MySQL or SQLite.**
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
pnpm add @avytrix/nestjs-drizzle-crud drizzle-orm
|
|
@@ -94,7 +94,7 @@ sponsors are what make keeping it free sustainable.
|
|
|
94
94
|
- 🚀 **CRUD**: `find`, `findOne`, `findAll`, `create`, `update`, `delete`, and more
|
|
95
95
|
- 🧩 **Configure once**: `forRoot()` owns the connection; services only declare a table
|
|
96
96
|
- ⚡ **Type-safe**: generics over your entity, create/update DTOs and filter types
|
|
97
|
-
- 🗃️ **PostgreSQL &
|
|
97
|
+
- 🗃️ **PostgreSQL, MySQL & SQLite**: dialect-aware (`RETURNING` vs `insertId`), libSQL and Cloudflare D1 included
|
|
98
98
|
- 🔄 **Soft delete**: opt-in, with `restore`, a trash view (`onlyDeleted`) and `forceDelete`
|
|
99
99
|
- 📦 **Bulk operations**: one statement per mass create/update/delete, inside a transaction, plus `upsert`
|
|
100
100
|
- 🔍 **Filtering**: comparison, `in`/`notIn`, `between`, escaped `contains`/`startsWith`, array and JSON operators, `or`/`not` groups
|
|
@@ -136,23 +136,23 @@ pnpm add @nestjs/common @nestjs/core drizzle-orm reflect-metadata
|
|
|
136
136
|
# PostgreSQL (also required if you use `connectionString` with dialect 'postgresql')
|
|
137
137
|
pnpm add postgres
|
|
138
138
|
|
|
139
|
-
# MySQL
|
|
139
|
+
# MySQL (also required if you use `connectionString` with dialect 'mysql')
|
|
140
140
|
pnpm add mysql2
|
|
141
141
|
|
|
142
|
+
# SQLite / libSQL (you build the Drizzle instance and pass it as `db`)
|
|
143
|
+
pnpm add @libsql/client
|
|
144
|
+
|
|
142
145
|
# Only for ApiCrudQuery / ApiCrudList (OpenAPI docs)
|
|
143
146
|
pnpm add @nestjs/swagger
|
|
144
147
|
```
|
|
145
148
|
|
|
146
149
|
> Using npm or yarn? Swap `pnpm add` for `npm install` / `yarn add`.
|
|
147
150
|
|
|
148
|
-
> **pnpm note:** `postgres`
|
|
149
|
-
>
|
|
150
|
-
> `connectionString` make sure
|
|
151
|
-
>
|
|
152
|
-
|
|
153
|
-
> `postgres` is an **optional** peer dependency. It's only needed when you let the
|
|
154
|
-
> module build the connection from a `connectionString` for the `postgresql` dialect.
|
|
155
|
-
> If you pass a pre-built `db` instead, you don't need it.
|
|
151
|
+
> **pnpm note:** `postgres` and `mysql2` are optional peer dependencies. pnpm
|
|
152
|
+
> enforces peer deps strictly, so if you build the connection from a
|
|
153
|
+
> `connectionString` make sure the driver for your dialect is installed in your app.
|
|
154
|
+
> If you pass a pre-built `db` instead, you don't need either. SQLite always takes a
|
|
155
|
+
> `db` you build.
|
|
156
156
|
|
|
157
157
|
---
|
|
158
158
|
|
|
@@ -285,15 +285,16 @@ export class UsersController {
|
|
|
285
285
|
|
|
286
286
|
| Field | Type | Description |
|
|
287
287
|
|---|---|---|
|
|
288
|
-
| `dialect` | `'postgresql' \| 'mysql'` | **Required.** Database dialect. |
|
|
289
|
-
| `connectionString` | `string` | Connection string. The module builds the connection (PostgreSQL
|
|
288
|
+
| `dialect` | `'postgresql' \| 'mysql' \| 'sqlite'` | **Required.** Database dialect. `'sqlite'` covers SQLite, libSQL and Cloudflare D1. |
|
|
289
|
+
| `connectionString` | `string` | Connection string. The module builds the connection (PostgreSQL with `postgres`, MySQL with `mysql2`). |
|
|
290
|
+
| `connection` | `{ poolSize?, idleTimeout?, ssl? }` | Pool size, idle timeout (seconds) and TLS for the connection built from `connectionString`. |
|
|
290
291
|
| `db` | `Drizzle instance` | Alternatively, pass a Drizzle instance you built yourself (any dialect). |
|
|
291
292
|
| `schema` | `Record<string, unknown>` | Drizzle schema, used when building from `connectionString`. |
|
|
292
293
|
| `defaults.softDelete` | `boolean` | Enable soft delete for all entities (default `true`). |
|
|
293
294
|
| `defaults.timestamps` | `boolean` | Auto-manage `created_at`/`updated_at` for all entities (default `true`). |
|
|
294
295
|
| `defaults.pagination` | `{ defaultLimit?, maxLimit? }` | Pagination defaults (default `{ 20, 100 }`). Either key alone keeps the other's default. |
|
|
295
296
|
| `defaults.sortOrder` | `'asc' \| 'desc'` | Opt-in default sort: entities **without** an explicit `defaultSort` fall back to ordering by their `created_at` column in this direction. Omit it and `findAll` orders by the primary key only. |
|
|
296
|
-
| `sql` | `{ caseSensitive?, useReturning? }` | Dialect tuning. `useReturning` defaults to `true` for PostgreSQL, `false` for MySQL. `jsonSupport` and `enableFullTextSearch` are deprecated and do nothing. |
|
|
297
|
+
| `sql` | `{ caseSensitive?, useReturning? }` | Dialect tuning. `useReturning` defaults to `true` for PostgreSQL and SQLite, `false` for MySQL. `jsonSupport` and `enableFullTextSearch` are deprecated and do nothing. |
|
|
297
298
|
| `hooks` | `{ enableGlobalHooks?, subscribers? }` | Module-level write events on the `CrudEventBus`. See [Events](#events-and-global-hooks). |
|
|
298
299
|
|
|
299
300
|
> **Provide exactly one of `connectionString` or `db`.** If your tables have no
|
|
@@ -306,7 +307,7 @@ export class UsersController {
|
|
|
306
307
|
> table does not have stops the app at startup, and the message names the key
|
|
307
308
|
> to use.
|
|
308
309
|
|
|
309
|
-
**Build the connection yourself (any dialect,
|
|
310
|
+
**Build the connection yourself (any dialect, required for SQLite):**
|
|
310
311
|
|
|
311
312
|
```typescript
|
|
312
313
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
@@ -332,6 +333,27 @@ DrizzleCrudModule.forRootAsync({
|
|
|
332
333
|
});
|
|
333
334
|
```
|
|
334
335
|
|
|
336
|
+
`forRootAsync` also takes `useClass` or `useExisting` with a class that
|
|
337
|
+
implements `DrizzleCrudOptionsFactory`:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
@Injectable()
|
|
341
|
+
class CrudConfig implements DrizzleCrudOptionsFactory {
|
|
342
|
+
constructor(private readonly cfg: ConfigService) {}
|
|
343
|
+
createDrizzleCrudOptions(): DrizzleCrudConfig {
|
|
344
|
+
return { dialect: 'mysql', connectionString: this.cfg.get('DATABASE_URL') };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
DrizzleCrudModule.forRootAsync({ useClass: CrudConfig });
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
On SQLite (`dialect: 'sqlite'`), mass writes, nested creates and attach / detach
|
|
352
|
+
/ sync need an async transaction: they work on libSQL and throw a
|
|
353
|
+
`TransactionException` on Cloudflare D1 and synchronous drivers such as
|
|
354
|
+
better-sqlite3. Full-text search, row locks and the array and JSON operators
|
|
355
|
+
are a 400 there.
|
|
356
|
+
|
|
335
357
|
### `DrizzleCrudModule.forFeature(entities)`
|
|
336
358
|
|
|
337
359
|
Registers one or more services and binds each to its table. Per-entity overrides
|
|
@@ -1111,7 +1133,19 @@ const { data, total } = await service.fullTextSearch(
|
|
|
1111
1133
|
```
|
|
1112
1134
|
|
|
1113
1135
|
Builds `to_tsvector(...) @@ plainto_tsquery(...)` across the given columns and
|
|
1114
|
-
orders by `ts_rank`. Throws if the dialect is not `postgresql`.
|
|
1136
|
+
orders by `ts_rank`. Throws a 400 if the dialect is not `postgresql`. The options
|
|
1137
|
+
take `filters`, `relations`, `select` and `counts` as on `findAll`, plus
|
|
1138
|
+
`language` (default `'english'`) and `queryMode: 'websearch'` for
|
|
1139
|
+
`websearch_to_tsquery`:
|
|
1140
|
+
|
|
1141
|
+
```typescript
|
|
1142
|
+
await service.fullTextSearch('"zero downtime" -kubernetes', ['title', 'body'], {}, {
|
|
1143
|
+
filters: { status: 'published' },
|
|
1144
|
+
relations: ['author'],
|
|
1145
|
+
language: 'english',
|
|
1146
|
+
queryMode: 'websearch',
|
|
1147
|
+
});
|
|
1148
|
+
```
|
|
1115
1149
|
|
|
1116
1150
|
For paginated list endpoints, prefer `findAll(..., ..., { search })` so search is
|
|
1117
1151
|
combined with filters and the count query. Use `fullTextSearch` directly when
|
|
@@ -1211,7 +1245,7 @@ read-only and never touches your database. Full setup:
|
|
|
1211
1245
|
|
|
1212
1246
|
Use these facts instead of guessing.
|
|
1213
1247
|
|
|
1214
|
-
**Package:** `nestjs-drizzle-crud` · **Peers:** `@nestjs/common`, `@nestjs/core`, `drizzle-orm`, `reflect-metadata`; optional `postgres` (PG) / `@nestjs/swagger` (for `ApiCrudList`)
|
|
1248
|
+
**Package:** `nestjs-drizzle-crud` · **Peers:** `@nestjs/common`, `@nestjs/core`, `drizzle-orm`, `reflect-metadata`; optional `postgres` (PG `connectionString`) / `mysql2` (MySQL `connectionString`) / `@nestjs/swagger` (for `ApiCrudList`). SQLite drivers are yours, passed as `db`.
|
|
1215
1249
|
|
|
1216
1250
|
**Setup is two steps and no per-service connection wiring:**
|
|
1217
1251
|
1. `DrizzleCrudModule.forRoot({ dialect, connectionString | db, schema, defaults })` once in `AppModule`.
|
|
@@ -1242,15 +1276,17 @@ export class XService extends SqlBaseCrudService<X, CreateXDto, UpdateXDto, XFil
|
|
|
1242
1276
|
- Relations: declare in forFeature `config.relations` and eager-load via `options.relations: ['relName']`, where a dotted path nests (`['states', 'states.cities']`, `['state.country']`; the parent path is pulled in automatically). Belongs-to is `{ table, localKey, references? }`, LEFT JOINed into the main query, resolving to an object or `null`. Has-many is `{ table, type: 'hasMany', foreignKey, parentKey?, orderBy? }`. Many-to-many is `{ table, type: 'manyToMany', through, throughLocalKey, throughForeignKey, references?, parentKey?, orderBy? }`. Nested relations live in a `relations` map on the relation. Every collection and every nested level costs exactly one extra query for the whole result set, never one per row. Filter via `findAll({ relName: { col: value } })` with the same operators; collection filters compile to `parentKey IN (SELECT ... WHERE ...)`, so `total` stays correct. Filters go through relation paths at any depth (`{ state: { country: { code: 'IN' } } }`), and collections take `some` / `none` / `every` (`{ posts: { none: { status: 'draft' } } }`), compiled to subqueries.
|
|
1243
1277
|
- `options.select` narrows the projection and takes dotted relation paths at any depth: `['id', 'name', 'region.code', 'states.cities.name']`. Unknown names are ignored; an all-dotted list leaves the base row complete; the parent key, the child foreign key and any nested key are re-added automatically.
|
|
1244
1278
|
- Primary keys: `primaryKey` (default `'id'`) per entity in forFeature config (`primaryKeyType` is deprecated and does nothing). UUID works on Postgres via RETURNING; with uuid keys, route params are strings, so don't use `ParseIntPipe`.
|
|
1245
|
-
- Full-text search is PostgreSQL-only.
|
|
1279
|
+
- Full-text search is PostgreSQL-only; it takes `filters`, `relations`, `select`, `language` and `queryMode` in its options.
|
|
1280
|
+
- SQLite (`dialect: 'sqlite'`): pass `db`. Bulk writes need libSQL; D1 and sync drivers throw `TransactionException`. Locks, full-text and array/JSON operators are a 400.
|
|
1281
|
+
- Exports (3.7): `DrizzleConnectionOptions`, `DrizzleCrudAsyncOptions`, `DrizzleCrudOptionsFactory`, `FullTextOptions`, `FullTextQueryMode`, `FullTextSearchOptions`.
|
|
1246
1282
|
- Exports (3.5): `createCrudController`, `CrudContext`, `CrudEventBus`, `ApiCrudQuery`, `ApiCrudList`, `MissingScopeException`, `StaleVersionException`, and the types `CrudControllerOptions`, `CrudEvent`, `CrudSubscriber`, `CrudScope`, `CollectionFilter`, `CountByRow`, `CrudPage`, `UncountedCrudPage`, `RelationCounts`.
|
|
1247
|
-
- Exports: `SqlBaseCrudService`, `DrizzleCrudModule`, `DRIZZLE_DB`, `DRIZZLE_CRUD_CONFIG`, `TestCrudFactory`, exceptions (`EntityNotFoundException`, `DuplicateEntityException`, `ForeignKeyViolationException`, `EntityInUseException`, `ValidationFailedException`, `BulkOperationException`, plus the deprecated `DatabaseConnectionException`
|
|
1283
|
+
- Exports: `SqlBaseCrudService`, `DrizzleCrudModule`, `DRIZZLE_DB`, `DRIZZLE_CRUD_CONFIG`, `TestCrudFactory`, exceptions (`EntityNotFoundException`, `DuplicateEntityException`, `ForeignKeyViolationException`, `EntityInUseException`, `ValidationFailedException`, `BulkOperationException`, `TransactionException`, plus the deprecated `DatabaseConnectionException`), and types (`SqlCrudConfig`, `SqlOperationOptions`, `DrizzleCrudConfig`, `CrudFeature`, `CrudFeatureConfig`, `PaginationOptions`, `OperationOptions`, `SqlSearchOptions`, `SqlSearchMode`, `BulkOperationResult`, `BulkOperationItemError`, `SqlDialect`, `PrimaryKeyType`, `CrudFilter`, `FilterOperators`, `SortInput`, `CursorPaginationOptions`, `CursorPage`, `CrudAllowlists`, `UpsertOptions`, `BulkMode`, `ParsedCrudQuery`), and `parseCrudQuery`.
|
|
1248
1284
|
|
|
1249
1285
|
### `SqlCrudConfig`
|
|
1250
1286
|
|
|
1251
1287
|
```typescript
|
|
1252
1288
|
interface SqlCrudConfig {
|
|
1253
|
-
dialect: 'postgresql' | 'mysql';
|
|
1289
|
+
dialect: 'postgresql' | 'mysql' | 'sqlite';
|
|
1254
1290
|
db: any; // Drizzle instance (injected by forFeature)
|
|
1255
1291
|
table: any; // Drizzle table (set by forFeature)
|
|
1256
1292
|
primaryKey: string; // default 'id'
|
|
@@ -3,7 +3,7 @@ import type { ICrudService, PaginationOptions } from "../interfaces/crud-service
|
|
|
3
3
|
import type { CrudWriteOperation } from "../interfaces/events.interface";
|
|
4
4
|
import type { CountByRow, CrudAllowlists, CrudPage, CursorPage, CursorPaginationOptions, ParseCrudQueryOptions, ParsedCrudQuery, RelationCounts, SortInput, UncountedCrudPage, UpsertOptions } from "../interfaces/query.interface";
|
|
5
5
|
import type { CrudScopeValues } from "../interfaces/scope.interface";
|
|
6
|
-
import type { RelationConfig, RelationNode, RelationsConfig, RelationType, SelectTree, SqlCrudConfig, SqlOperationOptions } from "../interfaces/sql-crud-config.interface";
|
|
6
|
+
import type { FullTextOptions, FullTextSearchOptions, RelationConfig, RelationNode, RelationsConfig, RelationType, SelectTree, SqlCrudConfig, SqlOperationOptions } from "../interfaces/sql-crud-config.interface";
|
|
7
7
|
import type { SortOrder } from "../types/sql.types";
|
|
8
8
|
interface SortSpec {
|
|
9
9
|
key: string;
|
|
@@ -85,8 +85,12 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
85
85
|
offset: number;
|
|
86
86
|
};
|
|
87
87
|
protected isUniqueViolation(error: any): boolean;
|
|
88
|
+
protected sqliteConstraint(error: any): string | undefined;
|
|
88
89
|
protected toDuplicateException(error: any): DuplicateEntityException;
|
|
89
90
|
private static readonly DATA_ERRORS;
|
|
91
|
+
private static readonly MYSQL_DATA_ERRORS;
|
|
92
|
+
private static readonly SQLITE_DATA_ERRORS;
|
|
93
|
+
private dataErrorMessage;
|
|
90
94
|
protected isDataException(error: any): boolean;
|
|
91
95
|
protected toDataException(error: any): ValidationFailedException;
|
|
92
96
|
protected toClientError(error: any, operation?: "read" | "write" | "delete"): unknown;
|
|
@@ -94,8 +98,14 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
94
98
|
protected isForeignKeyViolation(error: any): boolean;
|
|
95
99
|
protected toForeignKeyException(error: any): ForeignKeyViolationException;
|
|
96
100
|
protected applyLock(query: any, options?: SqlOperationOptions): any;
|
|
101
|
+
protected textSearchConfig(language?: unknown): any;
|
|
102
|
+
protected fullTextParts(columns: any[], term: string, options?: FullTextOptions): {
|
|
103
|
+
vector: any;
|
|
104
|
+
query: any;
|
|
105
|
+
};
|
|
97
106
|
protected buildSearchCondition(search?: SqlOperationOptions["search"]): any;
|
|
98
107
|
protected caseInsensitiveLike(column: any, pattern: string): any;
|
|
108
|
+
protected likePattern(column: any, pattern: string): any;
|
|
99
109
|
parseQuery(query: Record<string, unknown> | null | undefined, options?: ParseCrudQueryOptions): ParsedCrudQuery;
|
|
100
110
|
idKind(): "int" | "bigint" | "uuid" | "string";
|
|
101
111
|
parseId(raw: unknown): Promise<unknown>;
|
|
@@ -219,8 +229,9 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
219
229
|
private patternCondition;
|
|
220
230
|
protected applyComplexFilter(conditions: any[], column: any, filterObj: any): void;
|
|
221
231
|
protected executeSqlTransaction<R>(operation: (tx: any) => Promise<R>, existingTransaction?: any): Promise<R>;
|
|
232
|
+
protected assertTransactions(db: any): void;
|
|
222
233
|
protected getEntityName(): string;
|
|
223
|
-
fullTextSearch(searchTerm: string, searchColumns: string[], pagination?: PaginationOptions, options?:
|
|
234
|
+
fullTextSearch(searchTerm: string, searchColumns: string[], pagination?: PaginationOptions, options?: FullTextSearchOptions<FilterDto>): Promise<{
|
|
224
235
|
data: T[];
|
|
225
236
|
total: number;
|
|
226
237
|
page: number;
|