@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,20 @@ 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
- import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
13
11
  import { sql } from 'drizzle-orm';
14
12
  import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
13
+ import { StringProperty } from '../../schema/index.js';
14
+ import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
15
15
  import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
16
16
  import { Entity } from '../entity.js';
17
- import { Database, injectRepository } from '../server/index.js';
17
+ import { getRepository } from '../server/index.js';
18
18
  describe('ORM Repository CTI (Integration)', () => {
19
19
  let injector;
20
20
  let database;
21
+ let adminRepository;
22
+ let guestRepository;
23
+ let userRepository;
24
+ let managerRepository;
21
25
  const schema = 'test_orm_cti';
22
26
  let BaseUser = class BaseUser extends Entity {
23
27
  type;
@@ -88,6 +92,10 @@ describe('ORM Repository CTI (Integration)', () => {
88
92
  ], Manager);
89
93
  beforeAll(async () => {
90
94
  ({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
95
+ adminRepository = injector.resolve(getRepository(Admin));
96
+ guestRepository = injector.resolve(getRepository(Guest));
97
+ userRepository = injector.resolve(getRepository(BaseUser));
98
+ managerRepository = injector.resolve(getRepository(Manager));
91
99
  await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
92
100
  await dropTables(database, schema, ['managers', 'staff', 'guests', 'admins', 'users']);
93
101
  await database.execute(sql `
@@ -141,185 +149,150 @@ describe('ORM Repository CTI (Integration)', () => {
141
149
  await truncateTables(database, schema, ['users']);
142
150
  });
143
151
  test('should insert into both parent and child tables', async () => {
144
- await runInInjectionContext(injector, async () => {
145
- const repository = injectRepository(Admin);
146
- const newAdmin = new Admin();
147
- newAdmin.name = 'Alice';
148
- newAdmin.role = 'SuperAdmin';
149
- const inserted = await repository.insert(newAdmin);
150
- expect(inserted).toBeInstanceOf(Admin);
151
- expect(inserted.id).toBeDefined();
152
- expect(inserted.name).toBe('Alice');
153
- expect(inserted.role).toBe('SuperAdmin');
154
- expect(inserted.type).toBe('admin');
155
- expect(inserted.metadata.revision).toBe(1);
156
- // Verify DB state
157
- const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
158
- expect(userRow).toBeDefined();
159
- expect(userRow['type']).toBe('admin');
160
- expect(userRow['name']).toBe('Alice');
161
- expect(userRow['revision']).toBe(1);
162
- const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
163
- expect(adminRow).toBeDefined();
164
- expect(adminRow['type']).toBe('admin');
165
- expect(adminRow['role']).toBe('SuperAdmin');
166
- });
152
+ const newAdmin = new Admin();
153
+ newAdmin.name = 'Alice';
154
+ newAdmin.role = 'SuperAdmin';
155
+ const inserted = await adminRepository.insert(newAdmin);
156
+ expect(inserted).toBeInstanceOf(Admin);
157
+ expect(inserted.id).toBeDefined();
158
+ expect(inserted.name).toBe('Alice');
159
+ expect(inserted.role).toBe('SuperAdmin');
160
+ expect(inserted.type).toBe('admin');
161
+ expect(inserted.metadata.revision).toBe(1);
162
+ // Verify DB state
163
+ const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
164
+ expect(userRow).toBeDefined();
165
+ expect(userRow['type']).toBe('admin');
166
+ expect(userRow['name']).toBe('Alice');
167
+ expect(userRow['revision']).toBe(1);
168
+ const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
169
+ expect(adminRow).toBeDefined();
170
+ expect(adminRow['type']).toBe('admin');
171
+ expect(adminRow['role']).toBe('SuperAdmin');
167
172
  });
168
173
  test('should insert many into both parent and child tables', async () => {
169
- await runInInjectionContext(injector, async () => {
170
- const repository = injectRepository(Admin);
171
- const admins = [
172
- Object.assign(new Admin(), { name: 'Bob', role: 'Admin' }),
173
- Object.assign(new Admin(), { name: 'Charlie', role: 'Moderator' }),
174
- ];
175
- const insertedAdmins = await repository.insertMany(admins);
176
- expect(insertedAdmins).toHaveLength(2);
177
- expect(insertedAdmins[0].name).toBe('Bob');
178
- expect(insertedAdmins[1].name).toBe('Charlie');
179
- expect(insertedAdmins[0].type).toBe('admin');
180
- expect(insertedAdmins[1].type).toBe('admin');
181
- for (const inserted of insertedAdmins) {
182
- const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
183
- expect(userRow['name']).toBe(inserted.name);
184
- const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
185
- expect(adminRow['role']).toBe(inserted.role);
186
- }
187
- });
188
- });
189
- test('should update both parent and child tables', async () => {
190
- await runInInjectionContext(injector, async () => {
191
- const repository = injectRepository(Admin);
192
- const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
193
- const inserted = await repository.insert(newAdmin);
194
- const update = { name: 'Alice Updated', role: 'MegaAdmin' };
195
- const updated = await repository.update(inserted.id, update);
196
- expect(updated.name).toBe('Alice Updated');
197
- expect(updated.role).toBe('MegaAdmin');
198
- expect(updated.metadata.revision).toBe(2);
199
- // Verify DB state
174
+ const admins = [
175
+ Object.assign(new Admin(), { name: 'Bob', role: 'Admin' }),
176
+ Object.assign(new Admin(), { name: 'Charlie', role: 'Moderator' }),
177
+ ];
178
+ const insertedAdmins = await adminRepository.insertMany(admins);
179
+ expect(insertedAdmins).toHaveLength(2);
180
+ expect(insertedAdmins[0].name).toBe('Bob');
181
+ expect(insertedAdmins[1].name).toBe('Charlie');
182
+ expect(insertedAdmins[0].type).toBe('admin');
183
+ expect(insertedAdmins[1].type).toBe('admin');
184
+ for (const inserted of insertedAdmins) {
200
185
  const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
201
- expect(userRow['name']).toBe('Alice Updated');
202
- expect(userRow['revision']).toBe(2);
186
+ expect(userRow['name']).toBe(inserted.name);
203
187
  const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
204
- expect(adminRow['role']).toBe('MegaAdmin');
205
- });
188
+ expect(adminRow['role']).toBe(inserted.role);
189
+ }
190
+ });
191
+ test('should update both parent and child tables', async () => {
192
+ const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
193
+ const inserted = await adminRepository.insert(newAdmin);
194
+ const update = { name: 'Alice Updated', role: 'MegaAdmin' };
195
+ const updated = await adminRepository.update(inserted.id, update);
196
+ expect(updated.name).toBe('Alice Updated');
197
+ expect(updated.role).toBe('MegaAdmin');
198
+ expect(updated.metadata.revision).toBe(2);
199
+ // Verify DB state
200
+ const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
201
+ expect(userRow['name']).toBe('Alice Updated');
202
+ expect(userRow['revision']).toBe(2);
203
+ const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
204
+ expect(adminRow['role']).toBe('MegaAdmin');
206
205
  });
207
206
  test('should soft delete from parent table', async () => {
208
- await runInInjectionContext(injector, async () => {
209
- const repository = injectRepository(Admin);
210
- const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
211
- const inserted = await repository.insert(newAdmin);
212
- await repository.delete(inserted.id);
213
- // Verify DB state
214
- const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
215
- expect(userRow['delete_timestamp']).not.toBeNull();
216
- // Child table should still have the row (soft delete only affects parent)
217
- const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
218
- expect(adminRow).toBeDefined();
219
- });
207
+ const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
208
+ const inserted = await adminRepository.insert(newAdmin);
209
+ await adminRepository.delete(inserted.id);
210
+ // Verify DB state
211
+ const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
212
+ expect(userRow['delete_timestamp']).not.toBeNull();
213
+ // Child table should still have the row (soft delete only affects parent)
214
+ const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
215
+ expect(adminRow).toBeDefined();
220
216
  });
221
217
  test('should hard delete from both tables via cascade', async () => {
222
- await runInInjectionContext(injector, async () => {
223
- const repository = injectRepository(Admin);
224
- const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
225
- const inserted = await repository.insert(newAdmin);
226
- await repository.hardDelete(inserted.id);
227
- // Verify DB state
228
- const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
229
- expect(userRows).toHaveLength(0);
230
- const { rows: adminRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
231
- expect(adminRows).toHaveLength(0);
232
- });
218
+ const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
219
+ const inserted = await adminRepository.insert(newAdmin);
220
+ await adminRepository.hardDelete(inserted.id);
221
+ // Verify DB state
222
+ const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
223
+ expect(userRows).toHaveLength(0);
224
+ const { rows: adminRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
225
+ expect(adminRows).toHaveLength(0);
233
226
  });
234
227
  test('should load entities polymorphically from parent repository', async () => {
235
- await runInInjectionContext(injector, async () => {
236
- const adminRepository = injectRepository(Admin);
237
- const guestRepository = injectRepository(Guest);
238
- const userRepository = injectRepository(BaseUser);
239
- await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
240
- await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
241
- // Load without subclasses (default)
242
- const users = await userRepository.loadAll();
243
- expect(users).toHaveLength(2);
244
- expect(users[0]).toBeInstanceOf(BaseUser);
245
- expect(users[1]).toBeInstanceOf(BaseUser);
246
- expect(users[0].role).toBeUndefined();
247
- // Load with subclasses
248
- const polymorphicUsers = await userRepository.loadAll({ includeSubclasses: true });
249
- expect(polymorphicUsers).toHaveLength(2);
250
- const admin = polymorphicUsers.find((u) => u instanceof Admin);
251
- const guest = polymorphicUsers.find((u) => u instanceof Guest);
252
- expect(admin).toBeDefined();
253
- expect(admin).toBeInstanceOf(Admin);
254
- expect(admin.name).toBe('Admin1');
255
- expect(admin.role).toBe('Super');
256
- expect(guest).toBeDefined();
257
- expect(guest).toBeInstanceOf(Guest);
258
- expect(guest.name).toBe('Guest1');
259
- expect(guest).toBeInstanceOf(Guest);
260
- expect(guest.name).toBe('Guest1');
261
- expect(guest.permissions).toBe('Read');
262
- });
228
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
229
+ await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
230
+ // Load without subclasses (default)
231
+ const users = await userRepository.loadAll();
232
+ expect(users).toHaveLength(2);
233
+ expect(users[0]).toBeInstanceOf(BaseUser);
234
+ expect(users[1]).toBeInstanceOf(BaseUser);
235
+ expect(users[0].role).toBeUndefined();
236
+ // Load with subclasses
237
+ const polymorphicUsers = await userRepository.loadAll({ includeSubclasses: true });
238
+ expect(polymorphicUsers).toHaveLength(2);
239
+ const admin = polymorphicUsers.find((u) => u instanceof Admin);
240
+ const guest = polymorphicUsers.find((u) => u instanceof Guest);
241
+ expect(admin).toBeDefined();
242
+ expect(admin).toBeInstanceOf(Admin);
243
+ expect(admin.name).toBe('Admin1');
244
+ expect(admin.role).toBe('Super');
245
+ expect(guest).toBeDefined();
246
+ expect(guest).toBeInstanceOf(Guest);
247
+ expect(guest.name).toBe('Guest1');
248
+ expect(guest).toBeInstanceOf(Guest);
249
+ expect(guest.name).toBe('Guest1');
250
+ expect(guest.permissions).toBe('Read');
263
251
  });
264
252
  test('should load polymorphic subset from parent repository', async () => {
265
- await runInInjectionContext(injector, async () => {
266
- const adminRepository = injectRepository(Admin);
267
- const guestRepository = injectRepository(Guest);
268
- const userRepository = injectRepository(BaseUser);
269
- await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
270
- await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
271
- // Load ONLY Admins polymorphically
272
- const onlyAdmins = await userRepository.loadManyByQuery({}, { includeSubclasses: [Admin] });
273
- expect(onlyAdmins).toHaveLength(2); // Still returns all users from base table
274
- const admin = onlyAdmins.find((u) => u instanceof Admin);
275
- const guestAsBase = onlyAdmins.find((u) => u.name === 'Guest1');
276
- expect(admin).toBeInstanceOf(Admin);
277
- expect(admin.role).toBe('Super');
278
- expect(guestAsBase).not.toBeInstanceOf(Guest);
279
- expect(guestAsBase).toBeInstanceOf(BaseUser);
280
- });
253
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
254
+ await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
255
+ // Load ONLY Admins polymorphically
256
+ const onlyAdmins = await userRepository.loadManyByQuery({}, { includeSubclasses: [Admin] });
257
+ expect(onlyAdmins).toHaveLength(2); // Still returns all users from base table
258
+ const admin = onlyAdmins.find((u) => u instanceof Admin);
259
+ const guestAsBase = onlyAdmins.find((u) => u.name === 'Guest1');
260
+ expect(admin).toBeInstanceOf(Admin);
261
+ expect(admin.role).toBe('Super');
262
+ expect(guestAsBase).not.toBeInstanceOf(Guest);
263
+ expect(guestAsBase).toBeInstanceOf(BaseUser);
281
264
  });
282
265
  test('should count and check existence for child entities', async () => {
283
- await runInInjectionContext(injector, async () => {
284
- const adminRepository = injectRepository(Admin);
285
- const guestRepository = injectRepository(Guest);
286
- await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
287
- await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin2', role: 'Normal' }));
288
- await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
289
- expect(await adminRepository.count()).toBe(2);
290
- expect(await guestRepository.count()).toBe(1);
291
- const admins = await adminRepository.loadAll();
292
- expect(await adminRepository.has(admins[0].id)).toBe(true);
293
- expect(await adminRepository.hasByQuery({ name: 'Admin2' })).toBe(true);
294
- expect(await adminRepository.hasByQuery({ name: 'Guest1' })).toBe(false); // Different table
295
- });
266
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
267
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin2', role: 'Normal' }));
268
+ await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
269
+ expect(await adminRepository.count()).toBe(2);
270
+ expect(await guestRepository.count()).toBe(1);
271
+ const admins = await adminRepository.loadAll();
272
+ expect(await adminRepository.has(admins[0].id)).toBe(true);
273
+ expect(await adminRepository.hasByQuery({ name: 'Admin2' })).toBe(true);
274
+ expect(await adminRepository.hasByQuery({ name: 'Guest1' })).toBe(false); // Different table
296
275
  });
297
276
  test('should query child entities by parent fields', async () => {
298
- await runInInjectionContext(injector, async () => {
299
- const adminRepository = injectRepository(Admin);
300
- await adminRepository.insert(Object.assign(new Admin(), { name: 'TargetAdmin', role: 'Super' }));
301
- await adminRepository.insert(Object.assign(new Admin(), { name: 'OtherAdmin', role: 'Normal' }));
302
- const results = await adminRepository.loadManyByQuery({ name: 'TargetAdmin' });
303
- expect(results).toHaveLength(1);
304
- expect(results[0].name).toBe('TargetAdmin');
305
- expect(results[0].role).toBe('Super');
306
- });
277
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'TargetAdmin', role: 'Super' }));
278
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'OtherAdmin', role: 'Normal' }));
279
+ const results = await adminRepository.loadManyByQuery({ name: 'TargetAdmin' });
280
+ expect(results).toHaveLength(1);
281
+ expect(results[0].name).toBe('TargetAdmin');
282
+ expect(results[0].role).toBe('Super');
307
283
  });
308
284
  test('should perform partial updates on parent or child fields', async () => {
309
- await runInInjectionContext(injector, async () => {
310
- const adminRepository = injectRepository(Admin);
311
- const inserted = await adminRepository.insert(Object.assign(new Admin(), { name: 'Alice', role: 'Super' }));
312
- // Update only parent field
313
- await adminRepository.update(inserted.id, { name: 'Alice Renamed' });
314
- let updated = await adminRepository.load(inserted.id);
315
- expect(updated.name).toBe('Alice Renamed');
316
- expect(updated.role).toBe('Super');
317
- // Update only child field
318
- await adminRepository.update(inserted.id, { role: 'Demoted' });
319
- updated = await adminRepository.load(inserted.id);
320
- expect(updated.name).toBe('Alice Renamed');
321
- expect(updated.role).toBe('Demoted');
322
- });
285
+ const inserted = await adminRepository.insert(Object.assign(new Admin(), { name: 'Alice', role: 'Super' }));
286
+ // Update only parent field
287
+ await adminRepository.update(inserted.id, { name: 'Alice Renamed' });
288
+ let updated = await adminRepository.load(inserted.id);
289
+ expect(updated.name).toBe('Alice Renamed');
290
+ expect(updated.role).toBe('Super');
291
+ // Update only child field
292
+ await adminRepository.update(inserted.id, { role: 'Demoted' });
293
+ updated = await adminRepository.load(inserted.id);
294
+ expect(updated.name).toBe('Alice Renamed');
295
+ expect(updated.role).toBe('Demoted');
323
296
  });
324
297
  test('should fail when violating discriminator check constraint', async () => {
325
298
  // Attempt manual insert with mismatched type
@@ -344,99 +317,74 @@ describe('ORM Repository CTI (Integration)', () => {
344
317
  await expect(query).rejects.toThrow();
345
318
  });
346
319
  test('should support nested inheritance', async () => {
347
- await runInInjectionContext(injector, async () => {
348
- const managerRepository = injectRepository(Manager);
349
- const manager = new Manager();
350
- manager.name = 'Big Boss';
351
- manager.employeeId = 'EMP001';
352
- manager.department = 'Executive';
353
- const inserted = await managerRepository.insert(manager);
354
- expect(inserted.name).toBe('Big Boss');
355
- expect(inserted.employeeId).toBe('EMP001');
356
- expect(inserted.department).toBe('Executive');
357
- expect(inserted.type).toBe('manager');
358
- // Verify DB state - all 3 tables should have rows
359
- const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
360
- expect(userRow['name']).toBe('Big Boss');
361
- const { rows: [staffRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
362
- expect(staffRow['employee_id']).toBe('EMP001');
363
- const { rows: [managerRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
364
- expect(managerRow['department']).toBe('Executive');
365
- });
320
+ const manager = new Manager();
321
+ manager.name = 'Big Boss';
322
+ manager.employeeId = 'EMP001';
323
+ manager.department = 'Executive';
324
+ const inserted = await managerRepository.insert(manager);
325
+ expect(inserted.name).toBe('Big Boss');
326
+ expect(inserted.employeeId).toBe('EMP001');
327
+ expect(inserted.department).toBe('Executive');
328
+ expect(inserted.type).toBe('manager');
329
+ // Verify DB state - all 3 tables should have rows
330
+ const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
331
+ expect(userRow['name']).toBe('Big Boss');
332
+ const { rows: [staffRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
333
+ expect(staffRow['employee_id']).toBe('EMP001');
334
+ const { rows: [managerRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
335
+ expect(managerRow['department']).toBe('Executive');
366
336
  });
367
337
  test('should rollback parent insert if child insert fails', async () => {
368
- await runInInjectionContext(injector, async () => {
369
- const adminRepository = injectRepository(Admin);
370
- // Attempt to insert an admin with a missing role (should fail because of NOT NULL)
371
- const invalidAdmin = new Admin();
372
- invalidAdmin.name = 'Broken Admin';
373
- // role is missing
374
- await expect(adminRepository.insert(invalidAdmin)).rejects.toThrow();
375
- // Verify that NO row was created in the users table
376
- const { rows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')}`);
377
- expect(rows).toHaveLength(0);
378
- });
338
+ // Attempt to insert an admin with a missing role (should fail because of NOT NULL)
339
+ const invalidAdmin = new Admin();
340
+ invalidAdmin.name = 'Broken Admin';
341
+ // role is missing
342
+ await expect(adminRepository.insert(invalidAdmin)).rejects.toThrow();
343
+ // Verify that NO row was created in the users table
344
+ const { rows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')}`);
345
+ expect(rows).toHaveLength(0);
379
346
  });
380
347
  test('should count and check existence polymorphically', async () => {
381
- await runInInjectionContext(injector, async () => {
382
- const adminRepository = injectRepository(Admin);
383
- const guestRepository = injectRepository(Guest);
384
- const userRepository = injectRepository(BaseUser);
385
- await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
386
- await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
387
- expect(await userRepository.count()).toBe(2);
388
- expect(await userRepository.hasByQuery({ name: 'A1' })).toBe(true);
389
- expect(await userRepository.hasByQuery({ name: 'G1' })).toBe(true);
390
- });
348
+ await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
349
+ await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
350
+ expect(await userRepository.count()).toBe(2);
351
+ expect(await userRepository.hasByQuery({ name: 'A1' })).toBe(true);
352
+ expect(await userRepository.hasByQuery({ name: 'G1' })).toBe(true);
391
353
  });
392
354
  test('should load many by IDs polymorphically', async () => {
393
- await runInInjectionContext(injector, async () => {
394
- const adminRepository = injectRepository(Admin);
395
- const guestRepository = injectRepository(Guest);
396
- const userRepository = injectRepository(BaseUser);
397
- const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
398
- const g1 = await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
399
- const results = await userRepository.loadMany([a1.id, g1.id], { includeSubclasses: true });
400
- expect(results).toHaveLength(2);
401
- expect(results.find((u) => u.id === a1.id)).toBeInstanceOf(Admin);
402
- expect(results.find((u) => u.id === g1.id)).toBeInstanceOf(Guest);
403
- });
355
+ const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
356
+ const g1 = await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
357
+ const results = await userRepository.loadMany([a1.id, g1.id], { includeSubclasses: true });
358
+ expect(results).toHaveLength(2);
359
+ expect(results.find((u) => u.id === a1.id)).toBeInstanceOf(Admin);
360
+ expect(results.find((u) => u.id === g1.id)).toBeInstanceOf(Guest);
404
361
  });
405
362
  test('should update many child entities', async () => {
406
- await runInInjectionContext(injector, async () => {
407
- const adminRepository = injectRepository(Admin);
408
- const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'Old' }));
409
- const a2 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A2', role: 'Old' }));
410
- await adminRepository.updateMany([a1.id, a2.id], { role: 'New' });
411
- const results = await adminRepository.loadMany([a1.id, a2.id]);
412
- expect(results[0].role).toBe('New');
413
- expect(results[1].role).toBe('New');
414
- });
363
+ const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'Old' }));
364
+ const a2 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A2', role: 'Old' }));
365
+ await adminRepository.updateMany([a1.id, a2.id], { role: 'New' });
366
+ const results = await adminRepository.loadMany([a1.id, a2.id]);
367
+ expect(results[0].role).toBe('New');
368
+ expect(results[1].role).toBe('New');
415
369
  });
416
370
  test('should update nested inheritance fields', async () => {
417
- await runInInjectionContext(injector, async () => {
418
- const managerRepository = injectRepository(Manager);
419
- const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
420
- const inserted = await managerRepository.insert(manager);
421
- await managerRepository.update(inserted.id, { name: 'New Boss', employeeId: 'E2', department: 'D2' });
422
- const updated = await managerRepository.load(inserted.id);
423
- expect(updated.name).toBe('New Boss');
424
- expect(updated.employeeId).toBe('E2');
425
- expect(updated.department).toBe('D2');
426
- });
371
+ const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
372
+ const inserted = await managerRepository.insert(manager);
373
+ await managerRepository.update(inserted.id, { name: 'New Boss', employeeId: 'E2', department: 'D2' });
374
+ const updated = await managerRepository.load(inserted.id);
375
+ expect(updated.name).toBe('New Boss');
376
+ expect(updated.employeeId).toBe('E2');
377
+ expect(updated.department).toBe('D2');
427
378
  });
428
379
  test('should delete from nested inheritance tables', async () => {
429
- await runInInjectionContext(injector, async () => {
430
- const managerRepository = injectRepository(Manager);
431
- const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
432
- const inserted = await managerRepository.insert(manager);
433
- await managerRepository.hardDelete(inserted.id);
434
- const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
435
- const { rows: staffRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
436
- const { rows: managerRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
437
- expect(userRows).toHaveLength(0);
438
- expect(staffRows).toHaveLength(0);
439
- expect(managerRows).toHaveLength(0);
440
- });
380
+ const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
381
+ const inserted = await managerRepository.insert(manager);
382
+ await managerRepository.hardDelete(inserted.id);
383
+ const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
384
+ const { rows: staffRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
385
+ const { rows: managerRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
386
+ expect(userRows).toHaveLength(0);
387
+ expect(staffRows).toHaveLength(0);
388
+ expect(managerRows).toHaveLength(0);
441
389
  });
442
390
  });
@@ -1 +1,2 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  export {};