@avytrix/nestjs-drizzle-crud 3.7.2 → 3.8.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 CHANGED
@@ -189,7 +189,10 @@ Operators inside an object value: `eq`, `gt`, `gte`, `lt`, `lte`, `neq`, `like`,
189
189
  `sql.caseSensitive`), `iStartsWith`, `iEndsWith`, `iContains` (always
190
190
  case-insensitive), and on PostgreSQL only `arrayContains`, `arrayOverlaps` and
191
191
  `jsonContains`. Any other operator is a 400. A bare string is an exact match,
192
- not a pattern, and `%` in it is a literal character. `isNull` and `isNotNull`
192
+ not a pattern, and `%` in it is a literal character. On a text column it is
193
+ `lower(col) = lower($1)` in `findAll` and `count` (index `lower(col)`), but a
194
+ plain, case-sensitive `=` in `findOne`. https://crud.avytrix.dev/docs/indexes/
195
+ lists the index each query shape needs; the package creates none. `isNull` and `isNotNull`
193
196
  take `true` / `false` or the strings `"true"` / `"false"`.
194
197
 
195
198
  OR and NOT need no raw Drizzle. `and`, `or` and `not` group filter objects, nest,
@@ -392,7 +395,9 @@ restore, force delete, attach included) and written onto every created row, so
392
395
  another tenant's id is a 404. A missing context value is a 403, never an
393
396
  unscoped query. A belongs-to key a write sets must point at a row of the
394
397
  caller's tenant when the related table has the scope columns; another tenant's
395
- row is the same 400 as a missing one. Do not add `tenant_id` to filters or DTOs
398
+ row is the same 400 as a missing one. Nested create children get the same check
399
+ for the keys their nested relation declares in its own `relations`; declare
400
+ every belongs-to a child can set. Do not add `tenant_id` to filters or DTOs
396
401
  by hand, and use `skipScope: true` only in trusted server code.
397
402
 
398
403
  # Optimistic locking
package/CHANGELOG.md CHANGED
@@ -11,6 +11,146 @@ 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.1] - 2026-11-04
15
+
16
+ Fixes from the 3.8.0 audit (D-01 to D-11). Three of them close gaps in tenant isolation: a scope
17
+ handed from one service to another, a batch key that differs from another tenant's only in case,
18
+ and nested children that skipped the key check. No public API or override point changed.
19
+
20
+ ### Changed
21
+
22
+ - Text keys compare exactly in the tenant key check, `attach` and `sync`: `"US"` and `"us"` are
23
+ two keys, as they are two rows on PostgreSQL and SQLite. Before, every key was lowercased, so a
24
+ batch holding both could point a row at another tenant's `"us"` and get a 201 where `"us"`
25
+ alone was a 400. A uuid column still ignores case, and so does MySQL, to follow its default
26
+ collations, which match `"us"` to a stored `"US"` and cannot hold both as distinct keys.
27
+ - On a scoped entity, a nested create checks the belongs-to keys of the rows it writes (a
28
+ belongs-to parent, has-many children, new many-to-many targets) against the caller's tenant,
29
+ using the nested relation's own `relations`. A child key to another tenant's row is the same
30
+ 400 `ForeignKeyViolationException` as a key to a missing row, and nothing is written. It was a
31
+ 201. A child key that no nested relation declares is still left to the database's foreign key.
32
+ - `massSoftDelete` and `massDelete` put `deleted_at IS NULL` in the `WHERE` of their write, as the
33
+ single-row writes have since 3.8.0. A row soft-deleted between the existence check and the
34
+ write is a 404 entry and the batch writes nothing, instead of being stamped again or
35
+ hard-deleted. `massForceDelete` still removes a row in either state.
36
+ - `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, which give
38
+ the same rows when retried, still load after the commit.
39
+ - `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.
41
+
42
+ ### Fixed
43
+
44
+ - The per-call scope memo is keyed by service and by the context it was resolved for. Options
45
+ handed from one service to another, the natural way for a subclass override to share a
46
+ transaction, carried the first service's scope: a service whose scope is `null` let a scoped
47
+ one read every tenant's rows, and a second service scoped by another column stored `NULL` in
48
+ it. Each service still runs its scope function once per call.
49
+ - An override of `emitEvent` or `prepareUpdate` keeps the guard read ahead of the write, like an
50
+ override of a `before*` hook. An `emitEvent` override saw the before event for another
51
+ tenant's id before the 404.
52
+ - `massUpdate` with a `beforeUpdate` hook that returns `NaN` for one id and `null` for another no
53
+ longer writes `NaN` to both, and a Drizzle `sql` expression as a value (`views + 1`) works as
54
+ it does in `update` instead of failing with a circular `TypeError` (a 500).
55
+ - The tenant key check sends its `SELECT`s, one per relation, together instead of one after the
56
+ other.
57
+
58
+ ### Docs
59
+
60
+ - The multi-tenancy FAQ answered "No" to "Are eager-loaded relations filtered by tenant?", against
61
+ the section on the same page. It now says yes, when the related table has the scope columns.
62
+ The multi-tenancy and relations-on-writes pages say which nested keys are checked, and the bulk
63
+ operations page says when a mass write loads its relations.
64
+ - `AGENTS.md`, `CONTRIBUTING.md` and `apps/docs/AGENTS.md` point at the changelog page where it
65
+ lives now, `(support)/changelog.mdx`.
66
+
67
+ ## [3.8.0] - 2026-11-03
68
+
69
+ Performance fixes from the 3.7.1 performance report. Calls return the same rows, in the same
70
+ order, with the same errors, in fewer statements and shorter transactions. The one visible
71
+ difference is under Changed: a row soft-deleted between the guard read and the write is now a 404
72
+ instead of a write. Inside `SqlBaseCrudService` the six mass writes share one skeleton, and so
73
+ do `softDelete`, `restore` and `delete`; no public API, override point or message changed.
74
+
75
+ ### Added
76
+
77
+ - Full-text search takes a column that already is a `tsvector`, such as a generated, stored
78
+ column, and uses it as is instead of wrapping it in `to_tsvector`, so a GIN index on the column
79
+ serves `fullTextSearch()` and `search.mode: "fullText"`. Before, such a column failed with
80
+ `function to_tsvector(unknown, tsvector) does not exist`. Next to text columns it is
81
+ `coalesce(col, ''::tsvector)`; text columns keep the 3.7.2 vector, bare for one column and
82
+ coalesced for several. On 200,000 rows a search on the stored column with its index took
83
+ 0.6 ms, against 600 to 900 ms computing `to_tsvector` per row without one. The full-text page
84
+ shows the index for each shape.
85
+ - A docs page, [Database indexes for fast Drizzle queries](https://crud.avytrix.dev/docs/indexes/),
86
+ lists the index each query shape needs: `lower(col)` (or `citext`, or `caseSensitive: true`) for
87
+ the default case-insensitive equality, the sort columns plus the primary key for cursor pages,
88
+ GIN for full-text search, `pg_trgm` for `contains` and `search`, foreign keys and both join
89
+ table columns for relations, partial indexes on `deleted_at IS NULL`, and the tenant column
90
+ first. It says when to use `withCount: false` or the cursor on a large table. The filtering
91
+ page now spells out that `findOne` compares strings with a plain, case-sensitive `=` while
92
+ `findAll` and `count` use `lower()` by default (unchanged; a default change waits for a major),
93
+ and the hooks page says where after hooks run: after the commit for a single write, inside
94
+ the transaction for a mass write.
95
+
96
+ ### Changed
97
+
98
+ - A row soft-deleted between the guard read and the write is no longer written: `update` and
99
+ `softDelete` only touch a live row, `restore` only a row in the trash, and `delete` no longer
100
+ hard-deletes a row that was soft-deleted meanwhile (`forceDelete` still does). Such a call is a
101
+ 404 instead of a write, a `false` return or a "Failed to update entity" error, and one whose
102
+ row vanished with an expected version is a 404 instead of a 409.
103
+
104
+ ### Fixed
105
+
106
+ - Keyset pages seek instead of filtering. `findByCursor` adds a bound on the first sort column
107
+ (`col <= $1` descending, `col >= $1` ascending) ahead of the OR expansion, drops the `IS NULL`
108
+ branch for NOT NULL and primary key columns (nullable and relation columns keep it), and on
109
+ PostgreSQL compares `(a, id) > ($1, $2)` as one row value when every column is NOT NULL and
110
+ sorts the same way. With an index on the sort columns and the primary key, page 9,000 of
111
+ 200,000 rows went from 24.4 ms to 0.6 ms (`created_at` descending) and from 50.3 ms to 0.5 ms
112
+ (`views` ascending) on PGlite. A sort column the row already carries is no longer selected a
113
+ second time for the cursor. Pages, their order and cursors are unchanged.
114
+ - Single-row writes skip the read first. On PostgreSQL and SQLite, `update`, `softDelete`,
115
+ `restore`, `delete` and `forceDelete` send one statement whose `WHERE` holds the whole guard
116
+ (primary key, tenant scope, expected version, soft delete state) and read the row only when
117
+ nothing came back, to answer with the same 404 or 409 as before. The read still runs first
118
+ when something a caller can observe sits between it and the write (an overridden `before*`
119
+ hook, `validateUpdate` or `mapUpdateDtoToEntity`, or a before event), so hooks and events see
120
+ exactly what they saw before, and on MySQL. A missing row is still a 404 ahead of a bad body.
121
+ - Mass writes send no savepoint on the happy path when they open their own transaction:
122
+ `massUpdate`, `massSoftDelete`, `massRestore` and `massDelete` take 4 round trips instead of 8
123
+ (`BEGIN`, `SELECT`, the set statement, `COMMIT`), `massCreate` and `massUpsert` 3 instead of 5.
124
+ When the set statement fails, the transaction rolls back and the items are replayed in a fresh
125
+ transaction that is rolled back too, so `BulkOperationException` names the same items with the
126
+ same statuses and messages. Inside a caller's transaction, or once a before event handed the
127
+ transaction to a subscriber, the savepoints stay, so a failed item does not abort the caller's
128
+ PostgreSQL transaction.
129
+ - `massUpdate` sends one `UPDATE` per distinct SET when `beforeUpdate` returns different data
130
+ per id, instead of one `UPDATE` and a savepoint pair per id: a hook with two results over 50
131
+ ids is 5 round trips instead of 155. Rows still come back in the input order, after hooks run
132
+ in that order, and a failure still names every failing id.
133
+ - On MySQL, `massCreate` of rows with auto-increment keys reads the inserted rows back with one
134
+ `SELECT ... WHERE id IN (...)` instead of one per row. The inserts stay one per row: MySQL only
135
+ reports the first id of a multi-row `INSERT`, and InnoDB does not promise the others are
136
+ consecutive.
137
+ - Relation loaders and relation counts split a parent key list that would not fit in one
138
+ statement. `loadBelongsTo`, `loadHasMany`, `loadManyToMany` and `loadCounts` send the keys in
139
+ chunks of 64,535 on PostgreSQL and MySQL, 31,766 on SQLite and 90 on Cloudflare D1, one query per
140
+ chunk, so a nested page whose level holds 70,000 keys loads in two queries instead of failing
141
+ with a 400 (3.7.2) or a 500 (before). Below that, it is one query per relation as before. The
142
+ chunk size is the new protected `relationChunkSize(db)`.
143
+ - Fewer statements in a few calls. `exists(id, options)` is one `SELECT` and no longer loads
144
+ `options.relations` or `options.counts` it throws away (4 statements to 1 with two relations and
145
+ a count); they are still checked against the allowlists. A nested create skips the "already
146
+ linked?" read for its many-to-many ids. `attach`, `sync` and `detach` read the row once and load
147
+ the relation onto it, instead of reading it before and after (8, 9 and 6 round trips to 7, 8 and
148
+ 5). A mass write that opened its own transaction loads `options.relations` and `options.counts`
149
+ after it commits, so it holds its row locks for less time; with a count filter, or in a caller's
150
+ transaction, they still load inside. The returned rows are the same.
151
+ - A `scope` function runs once per call instead of once per statement and relation: 5 times for
152
+ a `findAll` with three relations and a count, 7 for a `massUpdate`, now 1 each.
153
+
14
154
  ## [3.7.2] - 2026-11-03
15
155
 
16
156
  Fixes from the 3.7.1 audit. The batch limit now counts the statement the database receives.
@@ -335,7 +475,8 @@ app.
335
475
  only operator on MySQL is a 400.
336
476
  - **Keyset pagination.** `findByCursor(filters, { cursor, limit, sortBy, count }, options)` returns
337
477
  `{ data, limit, nextCursor, total? }`. The cursor is base64url JSON of the last row's sort values
338
- plus the primary key, so pages stay stable under inserts and deep pages cost the same as the
478
+ plus the primary key, so pages stay stable under inserts and, with an index on the sort columns
479
+ (since 3.8.0), deep pages cost the same as the
339
480
  first. It works with every sort, filter, relation and `select`, keeps timestamp microseconds and
340
481
  follows the database's NULL ordering. The count only runs with `count: true`. A cursor that is not
341
482
  valid, or was made for another sort, is a 400.
package/README.md CHANGED
@@ -563,6 +563,12 @@ instead of returning an arbitrary row.
563
563
  > `like` / `ilike` operators and supply your own wildcards, or `startsWith` /
564
564
  > `endsWith` / `contains`, which escape the value for you.
565
565
 
566
+ `findOne` is the exception: it compiles a string to a plain `=`, so it is
567
+ case-sensitive whatever `sql.caseSensitive` says. A plain index on the column
568
+ cannot serve `lower(column) = lower($1)`; create one on `lower(column)`. The
569
+ [indexes page](https://crud.avytrix.dev/docs/indexes/) lists the index each
570
+ filter, sort, search and relation needs.
571
+
566
572
  ---
567
573
 
568
574
  ## Query strings: parseQuery
@@ -654,7 +660,9 @@ in `forRoot` to apply a `created_at` fallback to every entity that doesn't defin
654
660
  ## Cursor pagination
655
661
 
656
662
  `findByCursor` pages with `WHERE (sort columns) > (last row)` instead of
657
- `OFFSET`, so deep pages stay fast and rows inserted earlier do not shift the
663
+ `OFFSET`, starting with a bound on the first sort column, so with an index on
664
+ the sort columns plus the primary key (`(created_at DESC, id)` below) deep
665
+ pages stay as fast as the first, and rows inserted earlier do not shift the
658
666
  next page:
659
667
 
660
668
  ```typescript
@@ -931,7 +939,9 @@ Each bulk method is one statement for the whole set (plus one `SELECT` that
931
939
  checks the ids exist), inside a single transaction: `massUpdate` over 100 ids is
932
940
  two queries. If any item fails, a `BulkOperationException` (400) is thrown and
933
941
  the transaction rolls back. The failing items are found by replaying the batch
934
- inside a savepoint that is rolled back, and the response body lists only them:
942
+ in a transaction (or, inside your own transaction, a savepoint) that is rolled
943
+ back, and the response body lists only them. The happy path sends no savepoint
944
+ unless it runs inside your transaction:
935
945
 
936
946
  ```json
937
947
  { "statusCode": 400, "error": "Bad Request",
@@ -26,6 +26,8 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
26
26
  protected checkReadOptions(options?: SqlOperationOptions): void;
27
27
  protected rowScope(options?: SqlOperationOptions): any[];
28
28
  protected resolveScope(options?: SqlOperationOptions): CrudScopeValues | null;
29
+ private withScopeMemo;
30
+ private checkScope;
29
31
  protected scopeConditions(options?: SqlOperationOptions, table?: any): any[];
30
32
  protected relatedRowScope(target: any, options?: SqlOperationOptions): any[];
31
33
  protected relatedScopeConditions(target: any, options?: SqlOperationOptions): any[];
@@ -61,6 +63,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
61
63
  protected beforeRestore(_id: any): Promise<void>;
62
64
  protected afterRestore(_entity: T): Promise<void>;
63
65
  protected relationType(relation: RelationConfig): RelationType;
66
+ private relationNamed;
64
67
  protected isCollection(relation: RelationConfig): boolean;
65
68
  protected eagerRelations(relations: string[]): string[];
66
69
  protected relationTree(paths: string[], configured?: RelationsConfig): RelationNode[];
@@ -78,7 +81,12 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
78
81
  protected loadRelations(db: any, hosts: any[], nodes: RelationNode[], select?: SelectTree, hostTable?: any, options?: SqlOperationOptions): Promise<void>;
79
82
  protected loadBelongsTo(db: any, hosts: any[], node: RelationNode, select: SelectTree, options?: SqlOperationOptions): Promise<void>;
80
83
  protected loadHasMany(db: any, hosts: any[], node: RelationNode, select: SelectTree, hostTable?: any, options?: SqlOperationOptions): Promise<void>;
84
+ private loaderKeys;
85
+ private loadCollection;
86
+ private selectThrough;
81
87
  protected loadManyToMany(db: any, hosts: any[], node: RelationNode, select: SelectTree, hostTable?: any, options?: SqlOperationOptions): Promise<void>;
88
+ protected relationChunkSize(db: any): number;
89
+ private inChunks;
82
90
  private groupCollection;
83
91
  protected applyJoins(query: any, relationNames: string[], options?: SqlOperationOptions): any;
84
92
  protected normalizeRelations(rows: any[], eager: string[]): any[];
@@ -111,6 +119,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
111
119
  vector: any;
112
120
  query: any;
113
121
  };
122
+ private isTsVector;
114
123
  protected buildSearchCondition(search?: SqlOperationOptions["search"]): any;
115
124
  protected caseInsensitiveLike(column: any, pattern: string): any;
116
125
  protected likePattern(column: any, pattern: string): any;
@@ -118,14 +127,20 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
118
127
  idKind(): "int" | "bigint" | "uuid" | "string";
119
128
  parseId(raw: unknown): Promise<unknown>;
120
129
  private findForGuard;
130
+ private guardRow;
131
+ private skipsGuard;
132
+ private guardOnError;
121
133
  protected assertVersion(id: any, actual: unknown, expected: unknown): void;
122
134
  protected writeWhere(ids: any[], options?: SqlOperationOptions, table?: any): any;
123
135
  protected withVersionBump(set: Record<string, any>): Record<string, any>;
124
136
  find(id: any, options?: SqlOperationOptions): Promise<T | null>;
125
137
  findOne(where: Partial<T>, options?: SqlOperationOptions): Promise<T | null>;
126
138
  private findFirst;
139
+ private readFirst;
140
+ private hydrate;
127
141
  private planList;
128
142
  private listConditions;
143
+ private parseCount;
129
144
  private buildCountQuery;
130
145
  findAll(filters: FilterDto | undefined, pagination: PaginationOptions & {
131
146
  withCount: false;
@@ -138,6 +153,10 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
138
153
  protected cursorField(column: any): any;
139
154
  private compareCursor;
140
155
  protected keysetCondition(specs: SortSpec[], values: unknown[]): any;
156
+ private keysetBound;
157
+ private keysetRowComparison;
158
+ private keysetNullable;
159
+ private nullsFirst;
141
160
  private afterCursor;
142
161
  protected emitEvent(phase: "before" | "after", operation: CrudWriteOperation, bulk: boolean, payload: {
143
162
  ids?: unknown[];
@@ -147,6 +166,8 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
147
166
  }, options?: SqlOperationOptions): Promise<void>;
148
167
  private idsOf;
149
168
  protected loadWriteRelations(db: any, rows: T[], options?: SqlOperationOptions): Promise<void>;
169
+ private loadsAfterCommit;
170
+ private writeThenLoad;
150
171
  private expectedVersionOf;
151
172
  protected prepareCreate(data: CreateDto, options?: SqlOperationOptions): Promise<Record<string, any>>;
152
173
  private insertOne;
@@ -157,6 +178,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
157
178
  private keysOutsideScope;
158
179
  private assertBatchKeysInScope;
159
180
  private assertKeysInScope;
181
+ private assertNestedKeysInScope;
160
182
  private missingParentException;
161
183
  private linkTargets;
162
184
  attach(id: any, relation: string, relatedIds: unknown[], options?: SqlOperationOptions): Promise<T>;
@@ -171,6 +193,8 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
171
193
  private updateWhereIn;
172
194
  private fetchByIds;
173
195
  private inIdOrder;
196
+ private guardedWrite;
197
+ private staleOrFalse;
174
198
  softDelete(id: any, options?: SqlOperationOptions): Promise<boolean>;
175
199
  private softDeleteSet;
176
200
  restore(id: any, options?: SqlOperationOptions): Promise<T>;
@@ -178,14 +202,18 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
178
202
  forceDelete(id: any, options?: SqlOperationOptions): Promise<boolean>;
179
203
  upsert(data: CreateDto, upsert?: UpsertOptions, options?: SqlOperationOptions): Promise<T>;
180
204
  massUpsert(data: CreateDto[], upsert?: UpsertOptions, options?: SqlOperationOptions): Promise<T[]>;
205
+ private upsertSet;
181
206
  private conflictKeys;
182
207
  private conflictValue;
183
208
  private upsertRows;
184
209
  private excludedValue;
210
+ private massWrite;
185
211
  massCreate(data: CreateDto[], options?: SqlOperationOptions): Promise<T[]>;
186
212
  private insertMany;
213
+ private readBackCreated;
187
214
  private assertAllWritten;
188
215
  massUpdate(ids: any[], data: UpdateDto, options?: SqlOperationOptions): Promise<T[]>;
216
+ private updateSets;
189
217
  private runKeyed;
190
218
  massSoftDelete(ids: any[], options?: SqlOperationOptions): Promise<boolean>;
191
219
  massRestore(ids: any[], options?: SqlOperationOptions): Promise<T[]>;
@@ -201,12 +229,9 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
201
229
  id?: any;
202
230
  run: (sp: any) => Promise<unknown>;
203
231
  }>, operation: "write" | "delete"): Promise<R>;
232
+ private settleSet;
204
233
  private diagnose;
205
- private massCreatePerItem;
206
- private massUpdatePerItem;
207
- private massSoftDeletePerItem;
208
- private massRestorePerItem;
209
- private massDeletePerItem;
234
+ private eachInSavepoint;
210
235
  protected savepoint<R>(tx: any, run: (sp: any) => Promise<R>): Promise<R>;
211
236
  exists(id: any, options?: SqlOperationOptions): Promise<boolean>;
212
237
  count(filters?: FilterDto, options?: SqlOperationOptions): Promise<number>;
@@ -215,6 +240,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
215
240
  protected countNames(counts?: RelationCounts): string[];
216
241
  protected withCountKeys(base: string[], counts?: RelationCounts): string[];
217
242
  protected loadCounts(db: any, rows: any[], counts: RelationCounts | undefined, options?: SqlOperationOptions): Promise<void>;
243
+ private countPlan;
218
244
  private countQuery;
219
245
  protected pushColumnCondition(conditions: any[], column: any, value: any, caseInsensitive?: boolean): void;
220
246
  protected buildCollectionFilter(relation: RelationConfig, value: Record<string, any>, db?: any, name?: string, options?: SqlOperationOptions): any | undefined;
@@ -241,6 +267,7 @@ export declare abstract class SqlBaseCrudService<T extends Record<string, any>,
241
267
  private patternCondition;
242
268
  protected applyComplexFilter(conditions: any[], column: any, filterObj: any): void;
243
269
  protected executeSqlTransaction<R>(operation: (tx: any) => Promise<R>, existingTransaction?: any): Promise<R>;
270
+ private readonly ownedTransactions;
244
271
  protected assertTransactions(db: any): void;
245
272
  protected getEntityName(): string;
246
273
  fullTextSearch(searchTerm: string, searchColumns: string[], pagination?: PaginationOptions, options?: FullTextSearchOptions<FilterDto>): Promise<{