@tstdl/base 0.93.144 → 0.93.146

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 (45) hide show
  1. package/authentication/authentication.api.d.ts +9 -0
  2. package/authentication/authentication.api.js +3 -0
  3. package/authentication/client/authentication.service.js +5 -5
  4. package/authentication/client/http-client.middleware.js +6 -2
  5. package/authentication/tests/authentication.client-middleware.test.js +35 -0
  6. package/authentication/tests/authentication.client-service-refresh.test.js +7 -0
  7. package/authentication/tests/authentication.client-service.test.js +15 -19
  8. package/authentication/tests/authentication.service.test.js +92 -119
  9. package/notification/tests/notification-client.test.js +39 -50
  10. package/notification/tests/notification-flow.test.js +204 -238
  11. package/notification/tests/notification-sse.service.test.js +20 -27
  12. package/notification/tests/notification-type.service.test.js +17 -20
  13. package/orm/tests/query-complex.test.js +80 -111
  14. package/orm/tests/repository-advanced.test.js +100 -143
  15. package/orm/tests/repository-attributes.test.js +30 -39
  16. package/orm/tests/repository-compound-primary-key.test.js +67 -75
  17. package/orm/tests/repository-comprehensive.test.js +76 -101
  18. package/orm/tests/repository-coverage.test.d.ts +1 -0
  19. package/orm/tests/repository-coverage.test.js +88 -149
  20. package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
  21. package/orm/tests/repository-cti-extensive.test.js +118 -147
  22. package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
  23. package/orm/tests/repository-cti-mapping.test.js +29 -42
  24. package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
  25. package/orm/tests/repository-cti-soft-delete.test.js +25 -37
  26. package/orm/tests/repository-cti-transactions.test.js +19 -33
  27. package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
  28. package/orm/tests/repository-cti-upsert-many.test.js +38 -50
  29. package/orm/tests/repository-cti.test.d.ts +1 -0
  30. package/orm/tests/repository-cti.test.js +195 -247
  31. package/orm/tests/repository-expiration.test.d.ts +1 -0
  32. package/orm/tests/repository-expiration.test.js +46 -59
  33. package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
  34. package/orm/tests/repository-extra-coverage.test.js +195 -337
  35. package/orm/tests/repository-mapping.test.d.ts +1 -0
  36. package/orm/tests/repository-mapping.test.js +20 -20
  37. package/orm/tests/repository-regression.test.js +124 -163
  38. package/orm/tests/repository-search.test.js +30 -44
  39. package/orm/tests/repository-soft-delete.test.js +54 -79
  40. package/orm/tests/repository-types.test.js +77 -111
  41. package/package.json +1 -1
  42. package/task-queue/tests/worker.test.js +5 -5
  43. package/testing/README.md +38 -16
  44. package/testing/integration-setup.d.ts +11 -0
  45. package/testing/integration-setup.js +57 -30
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -7,21 +8,23 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
8
  var __metadata = (this && this.__metadata) || function (k, v) {
8
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
10
  };
11
+ import { sql } from 'drizzle-orm';
12
+ import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
10
13
  import { NotFoundError } from '../../errors/not-found.error.js';
11
- import { Injector, runInInjectionContext } from '../../injector/index.js';
12
14
  import { StringProperty } from '../../schema/index.js';
13
15
  import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
14
16
  import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
15
- import { sql } from 'drizzle-orm';
16
- import { beforeAll, describe, expect, test } from 'vitest';
17
17
  import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
18
18
  import { BaseEntity, Entity } from '../entity.js';
19
- import { Database } from '../server/index.js';
20
- import { injectRepository } from '../server/repository.js';
19
+ import { getRepository } from '../server/repository.js';
21
20
  import { ENCRYPTION_SECRET } from '../server/tokens.js';
22
21
  describe('ORM Repository Extra Coverage', () => {
23
22
  let injector;
24
23
  let database;
24
+ let baseItemRepo;
25
+ let premiumItemRepo;
26
+ let simpleItemRepo;
27
+ let plainItemRepo;
25
28
  const schema = 'test_orm_extra_coverage';
26
29
  let BaseItem = class BaseItem extends Entity {
27
30
  type;
@@ -78,6 +81,10 @@ describe('ORM Repository Extra Coverage', () => {
78
81
  beforeAll(async () => {
79
82
  ({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
80
83
  injector.register(ENCRYPTION_SECRET, { useValue: new Uint8Array(32).fill(1) });
84
+ baseItemRepo = injector.resolve(getRepository(BaseItem));
85
+ premiumItemRepo = injector.resolve(getRepository(PremiumItem));
86
+ simpleItemRepo = injector.resolve(getRepository(SimpleItem));
87
+ plainItemRepo = injector.resolve(getRepository(PlainItem));
81
88
  await dropTables(database, schema, ['premium_items', 'items', 'simple_items', 'plain_items']);
82
89
  await database.execute(sql `
83
90
  CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('items')} (
@@ -118,429 +125,280 @@ describe('ORM Repository Extra Coverage', () => {
118
125
  )
119
126
  `);
120
127
  });
121
- test.beforeEach(async () => {
128
+ beforeEach(async () => {
122
129
  await truncateTables(database, schema, ['premium_items', 'items', 'simple_items', 'plain_items']);
123
130
  });
124
131
  test('loadManyCursor should work', async () => {
125
- await runInInjectionContext(injector, async () => {
126
- const repository = injectRepository(SimpleItem);
127
- const item = await repository.insert(Object.assign(new SimpleItem(), { label: 'Cursor Test' }));
128
- const cursor = repository.loadManyCursor([item.id]);
129
- const results = await toArrayAsync(cursor);
130
- expect(results).toHaveLength(1);
131
- expect(results[0].id).toBe(item.id);
132
- });
132
+ const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Cursor Test' }));
133
+ const cursor = simpleItemRepo.loadManyCursor([item.id]);
134
+ const results = await toArrayAsync(cursor);
135
+ expect(results).toHaveLength(1);
136
+ expect(results[0].id).toBe(item.id);
133
137
  });
134
138
  test('loadAllCursor should work', async () => {
135
- await runInInjectionContext(injector, async () => {
136
- const repository = injectRepository(SimpleItem);
137
- await repository.insert(Object.assign(new SimpleItem(), { label: 'All Cursor Test' }));
138
- const cursor = repository.loadAllCursor();
139
- const results = await toArrayAsync(cursor);
140
- expect(results.length).toBeGreaterThan(0);
141
- });
139
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'All Cursor Test' }));
140
+ const cursor = simpleItemRepo.loadAllCursor();
141
+ const results = await toArrayAsync(cursor);
142
+ expect(results.length).toBeGreaterThan(0);
142
143
  });
143
144
  test('tryLoadByQuery with order option', async () => {
144
- await runInInjectionContext(injector, async () => {
145
- const repository = injectRepository(SimpleItem);
146
- await repository.insert(Object.assign(new SimpleItem(), { label: 'A' }));
147
- await repository.insert(Object.assign(new SimpleItem(), { label: 'B' }));
148
- const item = await repository.tryLoadByQuery({}, { order: { label: 'desc' } });
149
- expect(item.label).toBe('B');
150
- });
145
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'A' }));
146
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'B' }));
147
+ const item = await simpleItemRepo.tryLoadByQuery({}, { order: { label: 'desc' } });
148
+ expect(item.label).toBe('B');
151
149
  });
152
150
  test('insertManyIfNotExists with empty array', async () => {
153
- await runInInjectionContext(injector, async () => {
154
- const repository = injectRepository(SimpleItem);
155
- const results = await repository.insertManyIfNotExists(['label'], []);
156
- expect(results).toHaveLength(0);
157
- });
151
+ const results = await simpleItemRepo.insertManyIfNotExists(['label'], []);
152
+ expect(results).toHaveLength(0);
158
153
  });
159
154
  test('insertManyIfNotExists with items', async () => {
160
- await runInInjectionContext(injector, async () => {
161
- const repository = injectRepository(SimpleItem);
162
- const item = Object.assign(new SimpleItem(), { label: 'Unique' });
163
- const results = await repository.insertManyIfNotExists(['label'], [item]);
164
- expect(results).toHaveLength(1);
165
- const results2 = await repository.insertManyIfNotExists(['label'], [item]);
166
- expect(results2).toHaveLength(0);
167
- });
155
+ const item = Object.assign(new SimpleItem(), { label: 'Unique' });
156
+ const results = await simpleItemRepo.insertManyIfNotExists(['label'], [item]);
157
+ expect(results).toHaveLength(1);
158
+ const results2 = await simpleItemRepo.insertManyIfNotExists(['label'], [item]);
159
+ expect(results2).toHaveLength(0);
168
160
  });
169
161
  test('tryInsert returning entity', async () => {
170
- await runInInjectionContext(injector, async () => {
171
- const repository = injectRepository(SimpleItem);
172
- const item = Object.assign(new SimpleItem(), { label: 'TryInsert' });
173
- const inserted = await repository.tryInsert(item);
174
- expect(inserted).toBeDefined();
175
- expect(inserted.label).toBe('TryInsert');
176
- const conflict = await repository.tryInsert(inserted); // Should conflict on ID
177
- expect(conflict).toBeUndefined();
178
- });
162
+ const item = Object.assign(new SimpleItem(), { label: 'TryInsert' });
163
+ const inserted = await simpleItemRepo.tryInsert(item);
164
+ expect(inserted).toBeDefined();
165
+ expect(inserted.label).toBe('TryInsert');
166
+ const conflict = await simpleItemRepo.tryInsert(inserted); // Should conflict on ID
167
+ expect(conflict).toBeUndefined();
179
168
  });
180
169
  test('loadMany with empty array', async () => {
181
- await runInInjectionContext(injector, async () => {
182
- const repository = injectRepository(SimpleItem);
183
- const results = await repository.loadMany([]);
184
- expect(results).toHaveLength(0);
185
- });
170
+ const results = await simpleItemRepo.loadMany([]);
171
+ expect(results).toHaveLength(0);
186
172
  });
187
173
  test('insertMany for non-CTI with empty array', async () => {
188
- await runInInjectionContext(injector, async () => {
189
- const repository = injectRepository(SimpleItem);
190
- const results = await repository.insertMany([]);
191
- expect(results).toHaveLength(0);
192
- });
174
+ const results = await simpleItemRepo.insertMany([]);
175
+ expect(results).toHaveLength(0);
193
176
  });
194
177
  test('countByQuery returning count', async () => {
195
- await runInInjectionContext(injector, async () => {
196
- const repository = injectRepository(SimpleItem);
197
- const initialCount = await repository.count();
198
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Count Test' }));
199
- const newCount = await repository.count();
200
- expect(newCount).toBe(initialCount + 1);
201
- });
178
+ const initialCount = await simpleItemRepo.count();
179
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Count Test' }));
180
+ const newCount = await simpleItemRepo.count();
181
+ expect(newCount).toBe(initialCount + 1);
202
182
  });
203
183
  test('mapManyToColumns and mapManyToInsertColumns', async () => {
204
- await runInInjectionContext(injector, async () => {
205
- const repository = injectRepository(SimpleItem);
206
- const item = Object.assign(new SimpleItem(), { label: 'Map Test' });
207
- const cols = await repository.mapManyToColumns([item]);
208
- expect(cols).toHaveLength(1);
209
- expect(cols[0].label).toBe('Map Test');
210
- const insertCols = await repository.mapManyToInsertColumns([item]);
211
- expect(insertCols).toHaveLength(1);
212
- expect(insertCols[0].label).toBe('Map Test');
213
- });
184
+ const item = Object.assign(new SimpleItem(), { label: 'Map Test' });
185
+ const cols = await simpleItemRepo.mapManyToColumns([item]);
186
+ expect(cols).toHaveLength(1);
187
+ expect(cols[0].label).toBe('Map Test');
188
+ const insertCols = await simpleItemRepo.mapManyToInsertColumns([item]);
189
+ expect(insertCols).toHaveLength(1);
190
+ expect(insertCols[0].label).toBe('Map Test');
214
191
  });
215
192
  test('tryUpsert on non-CTI returning undefined when condition fails', async () => {
216
- await runInInjectionContext(injector, async () => {
217
- const repository = injectRepository(SimpleItem);
218
- const item = await repository.insert(Object.assign(new SimpleItem(), { label: 'Upsert Condition' }));
219
- const conflictItem = Object.assign(new SimpleItem(), { id: item.id, label: 'Should Fail' });
220
- const result = await repository.tryUpsert(['id'], conflictItem, undefined, { set: { label: 'Impossible' } });
221
- expect(result).toBeUndefined();
222
- });
193
+ const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Upsert Condition' }));
194
+ const conflictItem = Object.assign(new SimpleItem(), { id: item.id, label: 'Should Fail' });
195
+ const result = await simpleItemRepo.tryUpsert(['id'], conflictItem, undefined, { set: { label: 'Impossible' } });
196
+ expect(result).toBeUndefined();
223
197
  });
224
198
  test('tryUpdateByQuery on non-CTI', async () => {
225
- await runInInjectionContext(injector, async () => {
226
- const repository = injectRepository(SimpleItem);
227
- const item = await repository.insert(Object.assign(new SimpleItem(), { label: 'UpdateByQuery' }));
228
- const result = await repository.tryUpdateByQuery({ label: 'UpdateByQuery' }, { label: 'UpdatedByQuery' });
229
- expect(result).toBeDefined();
230
- expect(result.label).toBe('UpdatedByQuery');
231
- const result2 = await repository.tryUpdateByQuery({ label: 'Ghost' }, { label: 'Buster' });
232
- expect(result2).toBeUndefined();
233
- });
199
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'UpdateByQuery' }));
200
+ const result = await simpleItemRepo.tryUpdateByQuery({ label: 'UpdateByQuery' }, { label: 'UpdatedByQuery' });
201
+ expect(result).toBeDefined();
202
+ expect(result.label).toBe('UpdatedByQuery');
203
+ const result2 = await simpleItemRepo.tryUpdateByQuery({ label: 'Ghost' }, { label: 'Buster' });
204
+ expect(result2).toBeUndefined();
234
205
  });
235
206
  test('tryDeleteByQuery on CTI', async () => {
236
- await runInInjectionContext(injector, async () => {
237
- const repository = injectRepository(PremiumItem);
238
- const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'DeleteMe', code: 'D1' }));
239
- const result = await repository.tryDeleteByQuery({ name: 'DeleteMe' });
240
- expect(result).toBeDefined();
241
- expect(result.metadata.deleteTimestamp).not.toBeNull();
242
- const result2 = await repository.tryDeleteByQuery({ name: 'Ghost' });
243
- expect(result2).toBeUndefined();
244
- });
207
+ const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'DeleteMe', code: 'D1' }));
208
+ const result = await premiumItemRepo.tryDeleteByQuery({ name: 'DeleteMe' });
209
+ expect(result).toBeDefined();
210
+ expect(result.metadata.deleteTimestamp).not.toBeNull();
211
+ const result2 = await premiumItemRepo.tryDeleteByQuery({ name: 'Ghost' });
212
+ expect(result2).toBeUndefined();
245
213
  });
246
214
  test('getIdLimitQuery for update', async () => {
247
- await runInInjectionContext(injector, async () => {
248
- const repository = injectRepository(SimpleItem);
249
- const query = repository.getIdLimitQuery({ label: 'Any' }).for('update');
250
- expect(query).toBeDefined();
251
- });
215
+ const query = simpleItemRepo.getIdLimitQuery({ label: 'Any' }).for('update');
216
+ expect(query).toBeDefined();
252
217
  });
253
218
  test('applySelect with distinct and distinctOn', async () => {
254
- await runInInjectionContext(injector, async () => {
255
- const repository = injectRepository(SimpleItem);
256
- const q1 = repository.applySelect(database, true);
257
- expect(q1).toBeDefined();
258
- const q2 = repository.applySelect(database, ['label']);
259
- expect(q2).toBeDefined();
260
- });
219
+ const q1 = simpleItemRepo.applySelect(database, true);
220
+ expect(q1).toBeDefined();
221
+ const q2 = simpleItemRepo.applySelect(database, ['label']);
222
+ expect(q2).toBeDefined();
261
223
  });
262
224
  test('mapToColumns and mapToInsertColumns', async () => {
263
- await runInInjectionContext(injector, async () => {
264
- const repository = injectRepository(SimpleItem);
265
- const item = Object.assign(new SimpleItem(), { label: 'Map Single' });
266
- const col = await repository.mapToColumns(item);
267
- expect(col.label).toBe('Map Single');
268
- const insertCol = await repository.mapToInsertColumns(item);
269
- expect(insertCol.label).toBe('Map Single');
270
- });
225
+ const item = Object.assign(new SimpleItem(), { label: 'Map Single' });
226
+ const col = await simpleItemRepo.mapToColumns(item);
227
+ expect(col.label).toBe('Map Single');
228
+ const insertCol = await simpleItemRepo.mapToInsertColumns(item);
229
+ expect(insertCol.label).toBe('Map Single');
271
230
  });
272
231
  test('hasAll with duplicates and missing IDs', async () => {
273
- await runInInjectionContext(injector, async () => {
274
- const repository = injectRepository(SimpleItem);
275
- const item = await repository.insert(Object.assign(new SimpleItem(), { label: 'HasAll' }));
276
- expect(await repository.hasAll([item.id, item.id])).toBe(true);
277
- expect(await repository.hasAll([item.id, '00000000-0000-0000-0000-000000000000'])).toBe(false);
278
- expect(await repository.hasAll([])).toBe(false);
279
- });
232
+ const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'HasAll' }));
233
+ expect(await simpleItemRepo.hasAll([item.id, item.id])).toBe(true);
234
+ expect(await simpleItemRepo.hasAll([item.id, '00000000-0000-0000-0000-000000000000'])).toBe(false);
235
+ expect(await simpleItemRepo.hasAll([])).toBe(false);
280
236
  });
281
237
  test('loadManyByQueryCursor should work', async () => {
282
- await runInInjectionContext(injector, async () => {
283
- const repository = injectRepository(SimpleItem);
284
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Many Cursor' }));
285
- const cursor = repository.loadManyByQueryCursor({ label: 'Many Cursor' });
286
- const results = await toArrayAsync(cursor);
287
- expect(results).toHaveLength(1);
288
- });
238
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Many Cursor' }));
239
+ const cursor = simpleItemRepo.loadManyByQueryCursor({ label: 'Many Cursor' });
240
+ const results = await toArrayAsync(cursor);
241
+ expect(results).toHaveLength(1);
289
242
  });
290
243
  test('upsert (non-CTI) with wheres', async () => {
291
- await runInInjectionContext(injector, async () => {
292
- const repository = injectRepository(SimpleItem);
293
- const item = await repository.insert(Object.assign(new SimpleItem(), { label: 'Upsert' }));
294
- // This should succeed
295
- const upserted = await repository.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert' }), { label: 'Upsert Updated' }, { set: { label: 'Upsert' } });
296
- expect(upserted.label).toBe('Upsert Updated');
297
- // This should fail to return a row due to condition
298
- await expect(repository.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert Updated' }), { label: 'Should Not Happen' }, { set: { label: 'Wrong' } }))
299
- .rejects.toThrow('upsert failed to return a row');
300
- });
244
+ const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Upsert' }));
245
+ // This should succeed
246
+ const upserted = await simpleItemRepo.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert' }), { label: 'Upsert Updated' }, { set: { label: 'Upsert' } });
247
+ expect(upserted.label).toBe('Upsert Updated');
248
+ // This should fail to return a row due to condition
249
+ await expect(simpleItemRepo.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert Updated' }), { label: 'Should Not Happen' }, { set: { label: 'Wrong' } }))
250
+ .rejects.toThrow('upsert failed to return a row');
301
251
  });
302
252
  test('updateMany (non-CTI) with empty array', async () => {
303
- await runInInjectionContext(injector, async () => {
304
- const repository = injectRepository(SimpleItem);
305
- expect(await repository.updateMany([], { label: 'None' })).toEqual([]);
306
- });
253
+ expect(await simpleItemRepo.updateMany([], { label: 'None' })).toEqual([]);
307
254
  });
308
255
  test('hardDeleteByQuery throws NotFoundError', async () => {
309
- await runInInjectionContext(injector, async () => {
310
- const repository = injectRepository(SimpleItem);
311
- await expect(repository.hardDeleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
312
- });
256
+ await expect(simpleItemRepo.hardDeleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
313
257
  });
314
258
  test('hardDeleteMany with empty array', async () => {
315
- await runInInjectionContext(injector, async () => {
316
- const repository = injectRepository(SimpleItem);
317
- expect(await repository.hardDeleteMany([])).toEqual([]);
318
- });
259
+ expect(await simpleItemRepo.hardDeleteMany([])).toEqual([]);
319
260
  });
320
261
  test('convertOrderBy with various inputs', async () => {
321
- await runInInjectionContext(injector, async () => {
322
- const repository = injectRepository(SimpleItem);
323
- expect(repository.convertOrderBy('label')).toBeDefined();
324
- expect(repository.convertOrderBy(['label', ['id', 'desc']])).toHaveLength(2);
325
- expect(repository.convertOrderBy(sql `label`)).toHaveLength(1);
326
- });
262
+ expect(simpleItemRepo.convertOrderBy('label')).toBeDefined();
263
+ expect(simpleItemRepo.convertOrderBy(['label', ['id', 'desc']])).toHaveLength(2);
264
+ expect(simpleItemRepo.convertOrderBy(sql `label`)).toHaveLength(1);
327
265
  });
328
266
  test('mapManyToEntity and mapToEntity with subclasses', async () => {
329
- await runInInjectionContext(injector, async () => {
330
- const repository = injectRepository(BaseItem);
331
- const item = await injectRepository(PremiumItem).insert(Object.assign(new PremiumItem(), { name: 'P1', code: 'C1' }));
332
- const polymorphic = await repository.load(item.id, { includeSubclasses: true });
333
- expect(polymorphic).toBeInstanceOf(PremiumItem);
334
- const rows = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('items')} i LEFT JOIN ${sql.identifier(schema)}.${sql.identifier('premium_items')} p ON i.id = p.id`);
335
- // We need to manually construct the polymorphic row structure if we want to test mapManyToEntity directly with subclasses
336
- // But loadAll({ includeSubclasses: true }) already does this.
337
- });
267
+ const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'P1', code: 'C1' }));
268
+ const polymorphic = await baseItemRepo.load(item.id, { includeSubclasses: true });
269
+ expect(polymorphic).toBeInstanceOf(PremiumItem);
270
+ // We need to manually construct the polymorphic row structure if we want to test mapManyToEntity directly with subclasses
271
+ // But loadAll({ includeSubclasses: true }) already does this.
338
272
  });
339
273
  test('tryUpdateCTI with no changes', async () => {
340
- await runInInjectionContext(injector, async () => {
341
- const repository = injectRepository(PremiumItem);
342
- const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'NoChange', code: 'NC' }));
343
- const updated = await repository.tryUpdate(item.id, {});
344
- expect(updated.id).toBe(item.id);
345
- });
274
+ const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'NoChange', code: 'NC' }));
275
+ const updated = await premiumItemRepo.tryUpdate(item.id, {});
276
+ expect(updated.id).toBe(item.id);
346
277
  });
347
278
  test('tsvector search with various options', async () => {
348
- await runInInjectionContext(injector, async () => {
349
- const repository = injectRepository(SimpleItem);
350
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Search Item' }));
351
- // score: false
352
- const r1 = await repository.search({
353
- query: { $tsvector: { fields: ['label'], query: 'Search' } },
354
- score: false,
355
- });
356
- expect(r1[0].score).toBeUndefined();
357
- // highlight variants
358
- const r2 = await repository.search({
359
- query: { $tsvector: { fields: ['label'], query: 'Search' } },
360
- highlight: 'label',
361
- });
362
- expect(r2[0].highlight).toBeDefined();
363
- const r3 = await repository.search({
364
- query: { $tsvector: { fields: ['label'], query: 'Search' } },
365
- highlight: { source: sql `label`, minWords: 5, maxWords: 10 },
366
- });
367
- expect(r3[0].highlight).toBeDefined();
368
- // offset and limit
369
- const r4 = await repository.search({
370
- query: { $tsvector: { fields: ['label'], query: 'Search' } },
371
- offset: 0,
372
- limit: 1,
373
- });
374
- expect(r4).toHaveLength(1);
375
- });
279
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Search Item' }));
280
+ // score: false
281
+ const r1 = await simpleItemRepo.search({
282
+ query: { $tsvector: { fields: ['label'], query: 'Search' } },
283
+ score: false,
284
+ });
285
+ expect(r1[0].score).toBeUndefined();
286
+ // highlight variants
287
+ const r2 = await simpleItemRepo.search({
288
+ query: { $tsvector: { fields: ['label'], query: 'Search' } },
289
+ highlight: 'label',
290
+ });
291
+ expect(r2[0].highlight).toBeDefined();
292
+ const r3 = await simpleItemRepo.search({
293
+ query: { $tsvector: { fields: ['label'], query: 'Search' } },
294
+ highlight: { source: sql `label`, minWords: 5, maxWords: 10 },
295
+ });
296
+ expect(r3[0].highlight).toBeDefined();
297
+ // offset and limit
298
+ const r4 = await simpleItemRepo.search({
299
+ query: { $tsvector: { fields: ['label'], query: 'Search' } },
300
+ offset: 0,
301
+ limit: 1,
302
+ });
303
+ expect(r4).toHaveLength(1);
376
304
  });
377
305
  test('trigram search with rank: false', async () => {
378
- await runInInjectionContext(injector, async () => {
379
- const repository = injectRepository(SimpleItem);
380
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Trigram' }));
381
- const results = await repository.search({
382
- query: { $trigram: { fields: ['label'], query: 'Trigram' } },
383
- rank: false,
384
- });
385
- expect(results).toHaveLength(1);
306
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Trigram' }));
307
+ const results = await simpleItemRepo.search({
308
+ query: { $trigram: { fields: ['label'], query: 'Trigram' } },
309
+ rank: false,
386
310
  });
311
+ expect(results).toHaveLength(1);
387
312
  });
388
313
  test('PlainItem (no metadata) operations', async () => {
389
- await runInInjectionContext(injector, async () => {
390
- const repository = injectRepository(PlainItem);
391
- const item = await repository.insert(Object.assign(new PlainItem(), { description: 'Plain' }));
392
- expect(repository.hasMetadata).toBe(false);
393
- const deleted = await repository.delete(item.id);
394
- expect(deleted.description).toBe('Plain');
395
- const exists = await repository.has(item.id);
396
- expect(exists).toBe(false);
397
- });
314
+ const item = await plainItemRepo.insert(Object.assign(new PlainItem(), { description: 'Plain' }));
315
+ expect(plainItemRepo.hasMetadata).toBe(false);
316
+ const deleted = await plainItemRepo.delete(item.id);
317
+ expect(deleted.description).toBe('Plain');
318
+ const exists = await plainItemRepo.has(item.id);
319
+ expect(exists).toBe(false);
398
320
  });
399
321
  test('convertOrderBy with object', async () => {
400
- await runInInjectionContext(injector, async () => {
401
- const repository = injectRepository(SimpleItem);
402
- const order = repository.convertOrderBy({ label: 'desc' });
403
- expect(order).toHaveLength(1);
404
- });
322
+ const order = simpleItemRepo.convertOrderBy({ label: 'desc' });
323
+ expect(order).toHaveLength(1);
405
324
  });
406
325
  test('tryHardDelete returning undefined', async () => {
407
- await runInInjectionContext(injector, async () => {
408
- const repository = injectRepository(SimpleItem);
409
- const result = await repository.tryHardDelete('00000000-0000-0000-0000-000000000000');
410
- expect(result).toBeUndefined();
411
- });
326
+ const result = await simpleItemRepo.tryHardDelete('00000000-0000-0000-0000-000000000000');
327
+ expect(result).toBeUndefined();
412
328
  });
413
329
  test('tryHardDeleteByQuery returning undefined', async () => {
414
- await runInInjectionContext(injector, async () => {
415
- const repository = injectRepository(SimpleItem);
416
- const result = await repository.tryHardDeleteByQuery({ label: 'Ghost' });
417
- expect(result).toBeUndefined();
418
- });
330
+ const result = await simpleItemRepo.tryHardDeleteByQuery({ label: 'Ghost' });
331
+ expect(result).toBeUndefined();
419
332
  });
420
333
  test('hardDeleteMany with empty array branch', async () => {
421
- await runInInjectionContext(injector, async () => {
422
- const repository = injectRepository(SimpleItem);
423
- const results = await repository.hardDeleteMany([]);
424
- expect(results).toEqual([]);
425
- });
334
+ const results = await simpleItemRepo.hardDeleteMany([]);
335
+ expect(results).toEqual([]);
426
336
  });
427
337
  test('hardDeleteManyByQuery works', async () => {
428
- await runInInjectionContext(injector, async () => {
429
- const repository = injectRepository(SimpleItem);
430
- await repository.insert(Object.assign(new SimpleItem(), { label: 'To Hard Delete' }));
431
- const results = await repository.hardDeleteManyByQuery({ label: 'To Hard Delete' });
432
- expect(results).toHaveLength(1);
433
- });
338
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'To Hard Delete' }));
339
+ const results = await simpleItemRepo.hardDeleteManyByQuery({ label: 'To Hard Delete' });
340
+ expect(results).toHaveLength(1);
434
341
  });
435
342
  test('applySelect with false (default)', async () => {
436
- await runInInjectionContext(injector, async () => {
437
- const repository = injectRepository(SimpleItem);
438
- const q = repository.applySelect(database, undefined, false);
439
- expect(q).toBeDefined();
440
- });
343
+ const q = simpleItemRepo.applySelect(database, undefined, false);
344
+ expect(q).toBeDefined();
441
345
  });
442
346
  test('load should throw NotFoundError', async () => {
443
- await runInInjectionContext(injector, async () => {
444
- const repository = injectRepository(SimpleItem);
445
- await expect(repository.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
446
- });
347
+ await expect(simpleItemRepo.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
447
348
  });
448
349
  test('tsvector search with rank number', async () => {
449
- await runInInjectionContext(injector, async () => {
450
- const repository = injectRepository(SimpleItem);
451
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
452
- const results = await repository.search({
453
- query: { $tsvector: { fields: ['label'], query: 'Rank' } },
454
- rank: { normalization: 1 }, // normalization
455
- });
456
- expect(results).toHaveLength(1);
350
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
351
+ const results = await simpleItemRepo.search({
352
+ query: { $tsvector: { fields: ['label'], query: 'Rank' } },
353
+ rank: { normalization: 1 }, // normalization
457
354
  });
355
+ expect(results).toHaveLength(1);
458
356
  });
459
357
  test('encryptionSecret usage in getTransformContext', async () => {
460
- await runInInjectionContext(injector, async () => {
461
- await runInInjectionContext(injector, async () => {
462
- const repository = injectRepository(SimpleItem);
463
- const context = await repository.getTransformContext();
464
- expect(context.encryptionKey).toBeDefined();
465
- // Call it again to hit the cached branch
466
- const context2 = await repository.getTransformContext();
467
- expect(context2).toBe(context);
468
- });
469
- });
358
+ const context = await simpleItemRepo.getTransformContext();
359
+ expect(context.encryptionKey).toBeDefined();
360
+ // Call it again to hit the cached branch
361
+ const context2 = await simpleItemRepo.getTransformContext();
362
+ expect(context2).toBe(context);
470
363
  });
471
364
  test('tsvector search with rank true/false', async () => {
472
- await runInInjectionContext(injector, async () => {
473
- const repository = injectRepository(SimpleItem);
474
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
475
- await repository.search({
476
- query: { $tsvector: { fields: ['label'], query: 'Rank' } },
477
- rank: true,
478
- });
479
- await repository.search({
480
- query: { $tsvector: { fields: ['label'], query: 'Rank' } },
481
- rank: false,
482
- });
365
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
366
+ await simpleItemRepo.search({
367
+ query: { $tsvector: { fields: ['label'], query: 'Rank' } },
368
+ rank: true,
369
+ });
370
+ await simpleItemRepo.search({
371
+ query: { $tsvector: { fields: ['label'], query: 'Rank' } },
372
+ rank: false,
483
373
  });
484
374
  });
485
375
  test('trigram search with threshold', async () => {
486
- await runInInjectionContext(injector, async () => {
487
- const repository = injectRepository(SimpleItem);
488
- await repository.insert(Object.assign(new SimpleItem(), { label: 'Trigram Threshold' }));
489
- await database.execute(sql `SET pg_trgm.similarity_threshold = 0.1`);
490
- const results = await repository.search({
491
- query: { $trigram: { fields: ['label'], query: 'Trigram', threshold: 0.1 } },
492
- });
493
- expect(results).toHaveLength(1);
376
+ await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Trigram Threshold' }));
377
+ await database.execute(sql `SET pg_trgm.similarity_threshold = 0.1`);
378
+ const results = await simpleItemRepo.search({
379
+ query: { $trigram: { fields: ['label'], query: 'Trigram', threshold: 0.1 } },
494
380
  });
381
+ expect(results).toHaveLength(1);
495
382
  });
496
383
  test('repository resolution inside transaction', async () => {
497
- await database.transaction(async (transaction) => {
498
- await runInInjectionContext(injector, async () => {
499
- const repository = injectRepository(SimpleItem, transaction);
500
- expect(repository).toBeDefined();
501
- });
384
+ await simpleItemRepo.transaction(async (transaction) => {
385
+ const repository = injector.resolve(getRepository(SimpleItem)).withTransaction(transaction);
386
+ expect(repository).toBeDefined();
502
387
  });
503
388
  });
504
389
  test('updateManyCTI with no matches', async () => {
505
- await runInInjectionContext(injector, async () => {
506
- const repository = injectRepository(PremiumItem);
507
- const results = await repository.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
508
- expect(results).toEqual([]);
509
- });
390
+ const results = await premiumItemRepo.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
391
+ expect(results).toEqual([]);
510
392
  });
511
393
  test('upsertMany on CTI with wheres and update object', async () => {
512
- await runInInjectionContext(injector, async () => {
513
- const repository = injectRepository(PremiumItem);
514
- const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'UpsertManyCTI', code: 'U1' }));
515
- const conflictItem = Object.assign(new PremiumItem(), { id: item.id, name: 'UpsertManyCTI', code: 'U1' });
516
- const results = await repository.upsertMany(['id'], [conflictItem], { name: 'UpsertManyCTI Updated', code: 'U2' }, { target: { name: 'UpsertManyCTI' } });
517
- expect(results).toHaveLength(1);
518
- expect(results[0].name).toBe('UpsertManyCTI Updated');
519
- expect(results[0].code).toBe('U2');
520
- });
521
- });
522
- test('updateManyCTI with no matches', async () => {
523
- await runInInjectionContext(injector, async () => {
524
- const repository = injectRepository(PremiumItem);
525
- const results = await repository.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
526
- expect(results).toEqual([]);
527
- });
528
- });
529
- test('upsertMany on CTI with wheres and update object', async () => {
530
- await runInInjectionContext(injector, async () => {
531
- const repository = injectRepository(PremiumItem);
532
- const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'UpsertManyCTI', code: 'U1' }));
533
- const conflictItem = Object.assign(new PremiumItem(), { id: item.id, name: 'UpsertManyCTI', code: 'U1' });
534
- const results = await repository.upsertMany(['id'], [conflictItem], { name: 'UpsertManyCTI Updated', code: 'U2' }, { target: { name: 'UpsertManyCTI' } });
535
- expect(results).toHaveLength(1);
536
- expect(results[0].name).toBe('UpsertManyCTI Updated');
537
- expect(results[0].code).toBe('U2');
538
- });
394
+ const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'UpsertManyCTI', code: 'U1' }));
395
+ const conflictItem = Object.assign(new PremiumItem(), { id: item.id, name: 'UpsertManyCTI', code: 'U1' });
396
+ const results = await premiumItemRepo.upsertMany(['id'], [conflictItem], { name: 'UpsertManyCTI Updated', code: 'U2' }, { target: { name: 'UpsertManyCTI' } });
397
+ expect(results).toHaveLength(1);
398
+ expect(results[0].name).toBe('UpsertManyCTI Updated');
399
+ expect(results[0].code).toBe('U2');
539
400
  });
540
401
  test('processExpirations with no expiration columns', async () => {
541
- await runInInjectionContext(injector, async () => {
542
- const repository = injectRepository(SimpleItem);
543
- await repository.processExpirations(); // Should just return
544
- });
402
+ await simpleItemRepo.processExpirations(); // Should just return
545
403
  });
546
404
  });