@avytrix/nestjs-drizzle-crud 3.8.1 → 3.8.2
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
CHANGED
|
@@ -151,6 +151,8 @@ Those are legacy manual patterns and they break registration.
|
|
|
151
151
|
`withCount: false` it skips the count, `total` is `null` and `hasMore` is set.
|
|
152
152
|
`delete` and `softDelete` return a boolean. `update` and `restore` return the entity and
|
|
153
153
|
throw `EntityNotFoundException` when the row is missing.
|
|
154
|
+
Writes turn an ISO string or a number for a date-mode `timestamp` / `date` /
|
|
155
|
+
`datetime` column into a `Date`; a value that is not a date is a 400 naming the column.
|
|
154
156
|
|
|
155
157
|
Every method takes `options`: `{ transaction, relations, select, search, hooks, lock, forNoKeyUpdate, withDeleted, onlyDeleted, skipAllowlists, bulkMode, counts, context, skipScope, skipEvents, expectedVersion, expectedVersions }`.
|
|
156
158
|
|
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,58 @@ restore, `ER_DUP_ENTRY` mapping, dialect locking) are tracked separately and unc
|
|
|
11
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
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
13
|
|
|
14
|
+
## [3.8.2] - 2026-11-04
|
|
15
|
+
|
|
16
|
+
Fixes from the 3.8.1 audit (G-01 to G-08). The tenant key check now lets the database compare keys,
|
|
17
|
+
so it follows each column's collation, and a malformed key or an ISO date string in a write body is
|
|
18
|
+
no longer a 500. No public API or override point changed, and no operation sends more statements
|
|
19
|
+
than in 3.8.1.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The tenant key check, `attach` and `sync` leave the key comparison to the database. They join the
|
|
24
|
+
keys they were sent, as an inline `VALUES` list, against the related table in the same scoped
|
|
25
|
+
`SELECT`, so the column's type and collation decide which key matched. A `citext` or SQLite
|
|
26
|
+
`NOCASE` key of the caller's own tenant in another case is accepted again (3.8.1 refused it with
|
|
27
|
+
a 400), and on MySQL a key under a `_bin` collation that differs from another tenant's only in
|
|
28
|
+
case is refused. PostgreSQL `text` still compares exactly, and a uuid matches in any case. On
|
|
29
|
+
MySQL an integer or decimal key is compared in its column's type, so a `BIGINT` past 2^53 is
|
|
30
|
+
matched exactly. `attach` and `sync` link each target by its key as stored, so `"ann@x.io"` and
|
|
31
|
+
`"Ann@X.io"` on a `citext` column give one link.
|
|
32
|
+
- `massUpdate` and `massUpsert` load `relations` and `counts` inside their transaction when a value
|
|
33
|
+
holds a Drizzle `sql` expression. A load that fails then rolls the write back, so a retry no
|
|
34
|
+
longer adds `views + 1` twice.
|
|
35
|
+
- A `RangeError` thrown while a statement's values are mapped (a custom type's own check, say) is
|
|
36
|
+
no longer reported as "Too many values in one statement". Only the call stack overflow of an
|
|
37
|
+
oversized parameter list is.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- A belongs-to key the column's type cannot hold (`"nope"` for a uuid) is a 400 in `massCreate`,
|
|
42
|
+
`massUpdate` and `massUpsert`, and in `update` inside a caller's transaction. It was a raw
|
|
43
|
+
`22P02` or `25P02`, a 500.
|
|
44
|
+
- Every write (`create`, `update`, `upsert`, the mass writes and nested rows) turns an ISO date
|
|
45
|
+
string or a number for a date-mode `timestamp`, `date` or `datetime` column into a `Date`, as
|
|
46
|
+
filters already did. It was a 500 `TypeError` (`value.toISOString is not a function`). A value
|
|
47
|
+
that is not a date, an Invalid Date included, is a 400 that names the column, in a write and in a
|
|
48
|
+
filter.
|
|
49
|
+
- `massUpdate` groups SETs by their raw values: a hook that returns `null` for one id and an
|
|
50
|
+
Invalid Date for another no longer gets both into one `UPDATE`. A `Date` and its ISO string are
|
|
51
|
+
two values, and byte arrays compare by their bytes.
|
|
52
|
+
- A `massUpdate` whose SETs all hold the same `sql` expression object, as in
|
|
53
|
+
``massUpdate(ids, { n: sql`n + 1` })``, sends one `UPDATE` instead of one per id.
|
|
54
|
+
- The per-call scope memo ends with the public call that started it. Options a hook kept and passed
|
|
55
|
+
to a later call reused the old memo, which grew with each call and resolved a context object
|
|
56
|
+
mutated in place to its old tenant.
|
|
57
|
+
|
|
58
|
+
### Docs
|
|
59
|
+
|
|
60
|
+
- The 3.8.1 entry now says the 404 ahead of a refused value holds outside a caller's transaction,
|
|
61
|
+
and that a mass write with an SQL value was not safe to retry. The bulk operations page says when
|
|
62
|
+
such a write loads its relations and that one shared `sql` object is one `UPDATE`.
|
|
63
|
+
- The multi-tenancy page says the database compares the keys. The CRUD operations page, the README
|
|
64
|
+
and `AGENT.md` say date strings are parsed on writes.
|
|
65
|
+
|
|
14
66
|
## [3.8.1] - 2026-11-04
|
|
15
67
|
|
|
16
68
|
Fixes from the 3.8.0 audit (D-01 to D-11). Three of them close gaps in tenant isolation: a scope
|
|
@@ -34,10 +86,13 @@ and nested children that skipped the key check. No public API or override point
|
|
|
34
86
|
write is a 404 entry and the batch writes nothing, instead of being stamped again or
|
|
35
87
|
hard-deleted. `massForceDelete` still removes a row in either state.
|
|
36
88
|
- `massCreate` loads `relations` and `counts` inside its transaction again. A load that fails
|
|
37
|
-
rolls the rows back, so a retry no longer stores them twice. The other mass writes
|
|
38
|
-
the same rows
|
|
89
|
+
rolls the rows back, so a retry no longer stores them twice. The other mass writes still load
|
|
90
|
+
after the commit, where a retry gives the same rows, except for a write whose values hold an SQL
|
|
91
|
+
expression (``massUpdate(ids, { views: sql`views + 1` })``), which 3.8.2 loads inside too.
|
|
39
92
|
- `update` of a missing row with a value the database refuses (`update(999, { views: "abc" })`)
|
|
40
|
-
is a 404, as it was before 3.8.0, instead of a 400.
|
|
93
|
+
is a 404, as it was before 3.8.0, instead of a 400, outside a caller's transaction. Inside one
|
|
94
|
+
(`options.transaction`) the failed statement has aborted the transaction on PostgreSQL, so the
|
|
95
|
+
answer stays the 400, which is the same for every tenant.
|
|
41
96
|
|
|
42
97
|
### Fixed
|
|
43
98
|
|
package/README.md
CHANGED
|
@@ -445,6 +445,10 @@ export class UsersService extends SqlBaseCrudService<User, CreateUserDto, Update
|
|
|
445
445
|
| `detach(id, relation, ids?, options?)` | `Promise<T>` | Unlink some, or all when `ids` is left out. |
|
|
446
446
|
| `sync(id, relation, ids, options?)` | `Promise<T>` | Make the links exactly `ids`. |
|
|
447
447
|
|
|
448
|
+
Every write turns an ISO string or a number for a date-mode `timestamp`, `date`
|
|
449
|
+
or `datetime` column into a `Date`, so a JSON body needs no transforming DTO for
|
|
450
|
+
it. A value that is not a date is a 400 that names the column.
|
|
451
|
+
|
|
448
452
|
### Bulk (run inside a transaction)
|
|
449
453
|
|
|
450
454
|
| Method | Returns |
|
|
@@ -21,12 +21,15 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
21
21
|
protected primaryKeyOf(table: any): string;
|
|
22
22
|
protected isTextColumn(column: any): boolean;
|
|
23
23
|
protected coerceFilterValue(column: any, value: any): any;
|
|
24
|
+
private toDate;
|
|
25
|
+
private coerceDates;
|
|
24
26
|
private static readonly ALLOWLIST_LABELS;
|
|
25
27
|
protected assertAllowed(list: keyof CrudAllowlists, path: string, options?: SqlOperationOptions): void;
|
|
26
28
|
protected checkReadOptions(options?: SqlOperationOptions): void;
|
|
27
29
|
protected rowScope(options?: SqlOperationOptions): any[];
|
|
28
30
|
protected resolveScope(options?: SqlOperationOptions): CrudScopeValues | null;
|
|
29
31
|
private withScopeMemo;
|
|
32
|
+
private scopedCall;
|
|
30
33
|
private checkScope;
|
|
31
34
|
protected scopeConditions(options?: SqlOperationOptions, table?: any): any[];
|
|
32
35
|
protected relatedRowScope(target: any, options?: SqlOperationOptions): any[];
|
|
@@ -186,7 +189,11 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
186
189
|
sync(id: any, relation: string, relatedIds: unknown[], options?: SqlOperationOptions): Promise<T>;
|
|
187
190
|
private changeLinks;
|
|
188
191
|
private valueKey;
|
|
189
|
-
private
|
|
192
|
+
private distinct;
|
|
193
|
+
private matchKeys;
|
|
194
|
+
private keyInput;
|
|
195
|
+
private mysqlKeyCast;
|
|
196
|
+
private pgKeyType;
|
|
190
197
|
private tableName;
|
|
191
198
|
protected prepareUpdate(data: UpdateDto, options?: SqlOperationOptions): Record<string, any>;
|
|
192
199
|
update(id: any, data: UpdateDto, options?: SqlOperationOptions): Promise<T>;
|
|
@@ -219,6 +226,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
|
|
|
219
226
|
massRestore(ids: any[], options?: SqlOperationOptions): Promise<T[]>;
|
|
220
227
|
massDelete(ids: any[], options?: SqlOperationOptions): Promise<boolean>;
|
|
221
228
|
massForceDelete(ids: any[], options?: SqlOperationOptions): Promise<boolean>;
|
|
229
|
+
private holdsSql;
|
|
222
230
|
private uniqueIds;
|
|
223
231
|
private fingerprint;
|
|
224
232
|
private assertAllExist;
|