@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,17 +8,19 @@ 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
  };
10
- import { Injector, runInInjectionContext } from '../../injector/index.js';
11
- import { StringProperty } from '../../schema/index.js';
12
11
  import { sql } from 'drizzle-orm';
13
12
  import { beforeAll, describe, expect, test } from 'vitest';
13
+ import { StringProperty } from '../../schema/index.js';
14
+ import { setupIntegrationTest } from '../../testing/index.js';
14
15
  import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
15
16
  import { BaseEntity, Entity } from '../entity.js';
16
- import { configureOrm, Database } from '../server/index.js';
17
- import { injectRepository } from '../server/repository.js';
17
+ import { getRepository } from '../server/index.js';
18
18
  describe('ORM Repository Coverage', () => {
19
19
  let injector;
20
20
  let db;
21
+ let premiumRepo;
22
+ let plainRepo;
23
+ let simpleRepo;
21
24
  const schema = 'test_orm_coverage';
22
25
  let BaseItem = class BaseItem extends Entity {
23
26
  type;
@@ -72,18 +75,12 @@ describe('ORM Repository Coverage', () => {
72
75
  Table('plain_items', { schema })
73
76
  ], PlainItem);
74
77
  beforeAll(async () => {
75
- injector = new Injector('TestCoverage');
76
- configureOrm({
77
- repositoryConfig: { schema },
78
- connection: {
79
- host: '127.0.0.1',
80
- port: 5432,
81
- user: 'tstdl',
82
- password: 'wf7rq6glrk5jykne',
83
- database: 'tstdl',
84
- }
85
- });
86
- db = injector.resolve(Database);
78
+ ({ injector, database: db } = await setupIntegrationTest({
79
+ orm: { schema },
80
+ }));
81
+ premiumRepo = injector.resolve(getRepository(PremiumItem));
82
+ plainRepo = injector.resolve(getRepository(PlainItem));
83
+ simpleRepo = injector.resolve(getRepository(SimpleItem));
87
84
  await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
88
85
  await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('premium_items')} CASCADE`);
89
86
  await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('items')} CASCADE`);
@@ -129,175 +126,117 @@ describe('ORM Repository Coverage', () => {
129
126
  `);
130
127
  });
131
128
  test('upsertMany on CTI without update object (default update)', async () => {
132
- await runInInjectionContext(injector, async () => {
133
- const repository = injectRepository(PremiumItem);
134
- const item1 = Object.assign(new PremiumItem(), { name: 'Item 1', code: 'C1' });
135
- const inserted = await repository.insert(item1);
136
- // Create a conflict by using the same ID
137
- const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Item 1 Updated', code: 'C1' });
138
- // This should trigger the code path where `update` is undefined in upsertManyCTI
139
- const upserted = await repository.upsertMany(['id'], [conflictItem]);
140
- expect(upserted).toHaveLength(1);
141
- expect(upserted[0].name).toBe('Item 1 Updated');
142
- expect(upserted[0].metadata.revision).toBeGreaterThan(inserted.metadata.revision);
143
- });
129
+ const item1 = Object.assign(new PremiumItem(), { name: 'Item 1', code: 'C1' });
130
+ const inserted = await premiumRepo.insert(item1);
131
+ // Create a conflict by using the same ID
132
+ const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Item 1 Updated', code: 'C1' });
133
+ // This should trigger the code path where `update` is undefined in upsertManyCTI
134
+ const upserted = await premiumRepo.upsertMany(['id'], [conflictItem]);
135
+ expect(upserted).toHaveLength(1);
136
+ expect(upserted[0].name).toBe('Item 1 Updated');
137
+ expect(upserted[0].metadata.revision).toBeGreaterThan(inserted.metadata.revision);
144
138
  });
145
139
  test('upsertMany with empty array', async () => {
146
- await runInInjectionContext(injector, async () => {
147
- const repository = injectRepository(PremiumItem);
148
- const result = await repository.upsertMany(['id'], []);
149
- expect(result).toHaveLength(0);
150
- });
140
+ const result = await premiumRepo.upsertMany(['id'], []);
141
+ expect(result).toHaveLength(0);
151
142
  });
152
143
  test('updateManyByQuery on CTI with no matching rows', async () => {
153
- await runInInjectionContext(injector, async () => {
154
- const repository = injectRepository(PremiumItem);
155
- // Query matching nothing
156
- const results = await repository.updateManyByQuery({ name: 'NonExistent' }, { name: 'NewName' });
157
- expect(results).toEqual([]);
158
- });
144
+ // Query matching nothing
145
+ const results = await premiumRepo.updateManyByQuery({ name: 'NonExistent' }, { name: 'NewName' });
146
+ expect(results).toEqual([]);
159
147
  });
160
148
  test('tryUpdate returning undefined (non-CTI, no metadata)', async () => {
161
- await runInInjectionContext(injector, async () => {
162
- const repository = injectRepository(PlainItem);
163
- const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { description: 'New Desc' });
164
- expect(result).toBeUndefined();
165
- });
149
+ const result = await plainRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { description: 'New Desc' });
150
+ expect(result).toBeUndefined();
166
151
  });
167
152
  test('tryUpdate returning undefined (CTI)', async () => {
168
- await runInInjectionContext(injector, async () => {
169
- const repository = injectRepository(PremiumItem);
170
- const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'New Name' });
171
- expect(result).toBeUndefined();
172
- });
153
+ const result = await premiumRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'New Name' });
154
+ expect(result).toBeUndefined();
173
155
  });
174
156
  test('tryUpdate returning undefined (CTI) - child field only', async () => {
175
157
  // This hits line 1163 in tryUpdateCTI (else block where mappedUpdate is empty for base table)
176
- await runInInjectionContext(injector, async () => {
177
- const repository = injectRepository(PremiumItem);
178
- // Update ONLY 'code' (Child field).
179
- // Base table update will be empty.
180
- const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { code: 'New Code' });
181
- expect(result).toBeUndefined();
182
- });
158
+ // Update ONLY 'code' (Child field).
159
+ // Base table update will be empty.
160
+ const result = await premiumRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { code: 'New Code' });
161
+ expect(result).toBeUndefined();
183
162
  });
184
163
  test('tryDelete returning undefined (non-CTI, has metadata)', async () => {
185
- await runInInjectionContext(injector, async () => {
186
- const repository = injectRepository(SimpleItem);
187
- const result = await repository.tryDelete('00000000-0000-0000-0000-000000000000');
188
- expect(result).toBeUndefined();
189
- });
164
+ const result = await simpleRepo.tryDelete('00000000-0000-0000-0000-000000000000');
165
+ expect(result).toBeUndefined();
190
166
  });
191
167
  test('tryDelete returning undefined (non-CTI, no metadata)', async () => {
192
- await runInInjectionContext(injector, async () => {
193
- const repository = injectRepository(PlainItem);
194
- // This hits tryHardDelete
195
- const result = await repository.tryDelete('00000000-0000-0000-0000-000000000000');
196
- expect(result).toBeUndefined();
197
- });
168
+ // This hits tryHardDelete
169
+ const result = await plainRepo.tryDelete('00000000-0000-0000-0000-000000000000');
170
+ expect(result).toBeUndefined();
198
171
  });
199
172
  test('tryDeleteByQuery returning undefined (non-CTI, has metadata)', async () => {
200
- await runInInjectionContext(injector, async () => {
201
- const repository = injectRepository(SimpleItem);
202
- const result = await repository.tryDeleteByQuery({ label: 'NonExistent' });
203
- expect(result).toBeUndefined();
204
- });
173
+ const result = await simpleRepo.tryDeleteByQuery({ label: 'NonExistent' });
174
+ expect(result).toBeUndefined();
205
175
  });
206
176
  test('tryDeleteByQuery returning undefined (non-CTI, no metadata)', async () => {
207
- await runInInjectionContext(injector, async () => {
208
- const repository = injectRepository(PlainItem);
209
- // This hits tryHardDeleteByQuery
210
- const result = await repository.tryDeleteByQuery({ description: 'NonExistent' });
211
- expect(result).toBeUndefined();
212
- });
177
+ // This hits tryHardDeleteByQuery
178
+ const result = await plainRepo.tryDeleteByQuery({ description: 'NonExistent' });
179
+ expect(result).toBeUndefined();
213
180
  });
214
181
  test('tryUpsertCTI returning undefined when update condition fails', async () => {
215
- await runInInjectionContext(injector, async () => {
216
- const repository = injectRepository(PremiumItem);
217
- const item = Object.assign(new PremiumItem(), { name: 'Condition Item', code: 'COND' });
218
- const inserted = await repository.insert(item);
219
- const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Should Not Update', code: 'COND' });
220
- // Upsert with a condition that evaluates to false
221
- const result = await repository.tryUpsert(['id'], conflictItem, undefined, { set: { name: 'Impossible Name' } } // This query condition on the existing row must fail
222
- );
223
- expect(result).toBeUndefined();
224
- const loaded = await repository.load(inserted.id);
225
- expect(loaded.name).toBe('Condition Item'); // Should not have changed
226
- });
182
+ const item = Object.assign(new PremiumItem(), { name: 'Condition Item', code: 'COND' });
183
+ const inserted = await premiumRepo.insert(item);
184
+ const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Should Not Update', code: 'COND' });
185
+ // Upsert with a condition that evaluates to false
186
+ const result = await premiumRepo.tryUpsert(['id'], conflictItem, undefined, { set: { name: 'Impossible Name' } } // This query condition on the existing row must fail
187
+ );
188
+ expect(result).toBeUndefined();
189
+ const loaded = await premiumRepo.load(inserted.id);
190
+ expect(loaded.name).toBe('Condition Item'); // Should not have changed
227
191
  });
228
192
  test('Discriminator mismatch throws error', async () => {
229
- await runInInjectionContext(injector, async () => {
230
- const repository = injectRepository(PremiumItem);
231
- // Simulate a row returned from DB that has wrong type
232
- // PremiumItem expects type='premium'
233
- const corruptRow = {
234
- id: 'some-id',
235
- type: 'wrong_type',
236
- name: 'Test',
237
- code: 'C1',
238
- revision: 1,
239
- revision_timestamp: new Date(),
240
- create_timestamp: new Date(),
241
- delete_timestamp: null,
242
- attributes: {},
243
- };
244
- await expect(repository.mapToEntity(corruptRow)).rejects.toThrow('Discriminator mismatch');
245
- });
193
+ // Simulate a row returned from DB that has wrong type
194
+ // PremiumItem expects type='premium'
195
+ const corruptRow = {
196
+ id: 'some-id',
197
+ type: 'wrong_type',
198
+ name: 'Test',
199
+ code: 'C1',
200
+ revision: 1,
201
+ revision_timestamp: new Date(),
202
+ create_timestamp: new Date(),
203
+ delete_timestamp: null,
204
+ attributes: {},
205
+ };
206
+ await expect(premiumRepo.mapToEntity(corruptRow)).rejects.toThrow('Discriminator mismatch');
246
207
  });
247
208
  test('updateManyByQuery (CTI) should return empty array if no IDs found', async () => {
248
- // This hits line 1524
249
- await runInInjectionContext(injector, async () => {
250
- const repository = injectRepository(PremiumItem);
251
- // Query that returns nothing
252
- const result = await repository.updateManyByQuery({ name: 'Ghost' }, { name: 'Buster' });
253
- expect(result).toHaveLength(0);
254
- });
209
+ // Query that returns nothing
210
+ const result = await premiumRepo.updateManyByQuery({ name: 'Ghost' }, { name: 'Buster' });
211
+ expect(result).toHaveLength(0);
255
212
  });
256
213
  test('updateManyByQuery on Non-CTI', async () => {
257
- await runInInjectionContext(injector, async () => {
258
- const repository = injectRepository(SimpleItem);
259
- await repository.insert(Object.assign(new SimpleItem(), { label: 'To Update' }));
260
- const results = await repository.updateManyByQuery({ label: 'To Update' }, { label: 'Updated' });
261
- expect(results).toHaveLength(1);
262
- expect(results[0].label).toBe('Updated');
263
- });
214
+ await simpleRepo.insert(Object.assign(new SimpleItem(), { label: 'To Update' }));
215
+ const results = await simpleRepo.updateManyByQuery({ label: 'To Update' }, { label: 'Updated' });
216
+ expect(results).toHaveLength(1);
217
+ expect(results[0].label).toBe('Updated');
264
218
  });
265
219
  test('updateByQuery throws NotFoundError', async () => {
266
- await runInInjectionContext(injector, async () => {
267
- const repository = injectRepository(SimpleItem);
268
- await expect(repository.updateByQuery({ label: 'Ghost' }, { label: 'Buster' })).rejects.toThrow('SimpleItem not found');
269
- });
220
+ await expect(simpleRepo.updateByQuery({ label: 'Ghost' }, { label: 'Buster' })).rejects.toThrow('SimpleItem not found');
270
221
  });
271
222
  test('tryUpdateByQuery returns undefined (CTI)', async () => {
272
- await runInInjectionContext(injector, async () => {
273
- const repository = injectRepository(PremiumItem);
274
- const result = await repository.tryUpdateByQuery({ name: 'Ghost' }, { name: 'Buster' });
275
- expect(result).toBeUndefined();
276
- });
223
+ const result = await premiumRepo.tryUpdateByQuery({ name: 'Ghost' }, { name: 'Buster' });
224
+ expect(result).toBeUndefined();
277
225
  });
278
226
  test('tryUpdateByQuery updates entity (CTI)', async () => {
279
- await runInInjectionContext(injector, async () => {
280
- const repository = injectRepository(PremiumItem);
281
- const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'Target', code: 'T1' }));
282
- const result = await repository.tryUpdateByQuery({ name: 'Target' }, { name: 'Updated Target' });
283
- expect(result).toBeDefined();
284
- expect(result.name).toBe('Updated Target');
285
- });
227
+ const item = await premiumRepo.insert(Object.assign(new PremiumItem(), { name: 'Target', code: 'T1' }));
228
+ const result = await premiumRepo.tryUpdateByQuery({ name: 'Target' }, { name: 'Updated Target' });
229
+ expect(result).toBeDefined();
230
+ expect(result.name).toBe('Updated Target');
286
231
  });
287
232
  test('deleteManyByQuery (Soft Delete)', async () => {
288
- await runInInjectionContext(injector, async () => {
289
- const repository = injectRepository(SimpleItem);
290
- await repository.insert(Object.assign(new SimpleItem(), { label: 'To Delete' }));
291
- const results = await repository.deleteManyByQuery({ label: 'To Delete' });
292
- expect(results).toHaveLength(1);
293
- expect(results[0].label).toBe('To Delete');
294
- expect(results[0].metadata.deleteTimestamp).not.toBeNull();
295
- });
233
+ await simpleRepo.insert(Object.assign(new SimpleItem(), { label: 'To Delete' }));
234
+ const results = await simpleRepo.deleteManyByQuery({ label: 'To Delete' });
235
+ expect(results).toHaveLength(1);
236
+ expect(results[0].label).toBe('To Delete');
237
+ expect(results[0].metadata.deleteTimestamp).not.toBeNull();
296
238
  });
297
239
  test('deleteByQuery throws NotFoundError', async () => {
298
- await runInInjectionContext(injector, async () => {
299
- const repository = injectRepository(SimpleItem);
300
- await expect(repository.deleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
301
- });
240
+ await expect(simpleRepo.deleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
302
241
  });
303
242
  });
@@ -1 +1,2 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  export {};