@tstdl/base 0.93.145 → 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 (39) hide show
  1. package/authentication/tests/authentication.client-service.test.js +15 -19
  2. package/authentication/tests/authentication.service.test.js +92 -119
  3. package/notification/tests/notification-client.test.js +39 -50
  4. package/notification/tests/notification-flow.test.js +204 -238
  5. package/notification/tests/notification-sse.service.test.js +20 -27
  6. package/notification/tests/notification-type.service.test.js +17 -20
  7. package/orm/tests/query-complex.test.js +80 -111
  8. package/orm/tests/repository-advanced.test.js +100 -143
  9. package/orm/tests/repository-attributes.test.js +30 -39
  10. package/orm/tests/repository-compound-primary-key.test.js +67 -75
  11. package/orm/tests/repository-comprehensive.test.js +76 -101
  12. package/orm/tests/repository-coverage.test.d.ts +1 -0
  13. package/orm/tests/repository-coverage.test.js +88 -149
  14. package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
  15. package/orm/tests/repository-cti-extensive.test.js +118 -147
  16. package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
  17. package/orm/tests/repository-cti-mapping.test.js +29 -42
  18. package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
  19. package/orm/tests/repository-cti-soft-delete.test.js +25 -37
  20. package/orm/tests/repository-cti-transactions.test.js +19 -33
  21. package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
  22. package/orm/tests/repository-cti-upsert-many.test.js +38 -50
  23. package/orm/tests/repository-cti.test.d.ts +1 -0
  24. package/orm/tests/repository-cti.test.js +195 -247
  25. package/orm/tests/repository-expiration.test.d.ts +1 -0
  26. package/orm/tests/repository-expiration.test.js +46 -59
  27. package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
  28. package/orm/tests/repository-extra-coverage.test.js +195 -337
  29. package/orm/tests/repository-mapping.test.d.ts +1 -0
  30. package/orm/tests/repository-mapping.test.js +20 -20
  31. package/orm/tests/repository-regression.test.js +124 -163
  32. package/orm/tests/repository-search.test.js +30 -44
  33. package/orm/tests/repository-soft-delete.test.js +54 -79
  34. package/orm/tests/repository-types.test.js +77 -111
  35. package/package.json +1 -1
  36. package/task-queue/tests/worker.test.js +5 -5
  37. package/testing/README.md +38 -16
  38. package/testing/integration-setup.d.ts +11 -0
  39. package/testing/integration-setup.js +57 -30
@@ -1 +1,2 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  export {};
@@ -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);
@@ -8,12 +9,12 @@ 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
11
  import { describe, expect, test } from 'vitest';
12
+ import { Injector } from '../../injector/index.js';
11
13
  import { StringProperty } from '../../schema/index.js';
14
+ import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
12
15
  import { Entity } from '../entity.js';
13
- import { Column, Inheritance, ChildEntity, Table } from '../decorators.js';
14
- import { getRepository } from '../server/repository.js';
15
16
  import { configureOrm } from '../server/index.js';
16
- import { Injector, runInInjectionContext } from '../../injector/index.js';
17
+ import { getRepository } from '../server/repository.js';
17
18
  describe('ORM Repository Mapping (CTI)', () => {
18
19
  test('should flatten joined result when mapping child entity', async () => {
19
20
  let BaseUser = class BaseUser extends Entity {
@@ -49,23 +50,22 @@ describe('ORM Repository Mapping (CTI)', () => {
49
50
  const injector = new Injector('Test');
50
51
  configureOrm({
51
52
  repositoryConfig: { schema: 'test' },
52
- connection: { database: 'test' } // Mock config, we won't query DB
53
- });
54
- await runInInjectionContext(injector, async () => {
55
- const repository = new (getRepository(Admin))();
56
- // Simulate Drizzle joined result structure
57
- const mockResult = {
58
- users: { id: '123', type: 'admin', name: 'John Doe', revision: 1 },
59
- admins: { id: '123', type: 'admin', role: 'super-admin' }
60
- };
61
- // Access private method for testing (using any cast)
62
- const mappedEntity = await repository._mapToEntity(mockResult, {});
63
- expect(mappedEntity).toBeInstanceOf(Admin);
64
- expect(mappedEntity.id).toBe('123');
65
- expect(mappedEntity.type).toBe('admin');
66
- expect(mappedEntity.name).toBe('John Doe');
67
- expect(mappedEntity.role).toBe('super-admin');
68
- expect(mappedEntity.metadata.revision).toBe(1);
53
+ connection: { database: 'test' }, // Mock config, we won't query DB
54
+ injector,
69
55
  });
56
+ const repository = injector.resolve(getRepository(Admin));
57
+ // Simulate Drizzle joined result structure
58
+ const mockResult = {
59
+ users: { id: '123', type: 'admin', name: 'John Doe', revision: 1 },
60
+ admins: { id: '123', type: 'admin', role: 'super-admin' }
61
+ };
62
+ // Access private method for testing (using any cast)
63
+ const mappedEntity = await repository._mapToEntity(mockResult, {});
64
+ expect(mappedEntity).toBeInstanceOf(Admin);
65
+ expect(mappedEntity.id).toBe('123');
66
+ expect(mappedEntity.type).toBe('admin');
67
+ expect(mappedEntity.name).toBe('John Doe');
68
+ expect(mappedEntity.role).toBe('super-admin');
69
+ expect(mappedEntity.metadata.revision).toBe(1);
70
70
  });
71
71
  });
@@ -7,20 +7,22 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
+ import { sql } from 'drizzle-orm';
11
+ import { beforeAll, describe, expect, test } from 'vitest';
10
12
  import { defineEnum } from '../../enumeration/index.js';
11
13
  import { NotFoundError } from '../../errors/not-found.error.js';
12
- import { Injector, runInInjectionContext } from '../../injector/index.js';
13
14
  import { Array, Enumeration, Integer, string, StringProperty } from '../../schema/index.js';
14
- import { sql } from 'drizzle-orm';
15
- import { beforeAll, describe, expect, test } from 'vitest';
15
+ import { setupIntegrationTest } from '../../testing/index.js';
16
16
  import { Column, EmbeddedProperty, EncryptedProperty, Reference, Table } from '../decorators.js';
17
17
  import { Entity } from '../entity.js';
18
18
  import { JsonProperty, NumericDateProperty } from '../schemas/index.js';
19
- import { configureOrm, Database } from '../server/index.js';
20
- import { injectRepository } from '../server/repository.js';
19
+ import { getRepository } from '../server/index.js';
20
+ import { ENCRYPTION_SECRET } from '../server/tokens.js';
21
21
  describe('ORM Repository Regression (Integration)', () => {
22
22
  let injector;
23
23
  let db;
24
+ let standardRepository;
25
+ let postRepository;
24
26
  const schema = 'test_orm_regression';
25
27
  const UserRole = defineEnum('UserRole', {
26
28
  Admin: 'admin',
@@ -99,20 +101,13 @@ describe('ORM Repository Regression (Integration)', () => {
99
101
  Table('posts', { schema })
100
102
  ], Post);
101
103
  beforeAll(async () => {
102
- injector = new Injector('Test');
103
104
  const encryptionSecret = new Uint8Array(32).fill(1);
104
- configureOrm({
105
- repositoryConfig: { schema },
106
- connection: {
107
- host: '127.0.0.1',
108
- port: 5432,
109
- user: 'tstdl',
110
- password: 'wf7rq6glrk5jykne',
111
- database: 'tstdl',
112
- },
113
- encryptionSecret
114
- });
115
- db = injector.resolve(Database);
105
+ ({ injector, database: db } = await setupIntegrationTest({
106
+ orm: { schema },
107
+ }));
108
+ injector.register(ENCRYPTION_SECRET, { useValue: encryptionSecret });
109
+ standardRepository = injector.resolve(getRepository(StandardEntity));
110
+ postRepository = injector.resolve(getRepository(Post));
116
111
  await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
117
112
  await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('posts')} CASCADE`);
118
113
  await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
@@ -151,180 +146,146 @@ describe('ORM Repository Regression (Integration)', () => {
151
146
  });
152
147
  test('should perform basic CRUD on standard entity', async () => {
153
148
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
154
- await runInInjectionContext(injector, async () => {
155
- const repository = injectRepository(StandardEntity);
156
- const entity = new StandardEntity();
157
- entity.name = 'Test';
158
- entity.address = { street: 'Main St', number: 123 };
159
- entity.secret = 'my-secret-password';
160
- entity.role = UserRole.User;
161
- entity.data = { foo: 'bar' };
162
- entity.tags = ['a', 'b'];
163
- entity.birthday = 19716; // 2023-12-25
164
- const inserted = await repository.insert(entity);
165
- expect(inserted.id).toBeDefined();
166
- expect(inserted.name).toBe('Test');
167
- expect(inserted.address.street).toBe('Main St');
168
- expect(inserted.secret).toBe('my-secret-password');
169
- expect(inserted.role).toBe(UserRole.User);
170
- expect(inserted.data).toEqual({ foo: 'bar' });
171
- expect(inserted.tags).toEqual(['a', 'b']);
172
- expect(inserted.birthday).toBe(19716);
173
- // Verify DB encrypted state
174
- const { rows: [row] } = await db.execute(sql `SELECT secret, dob FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
175
- expect(row['secret']).toBeInstanceOf(Uint8Array);
176
- expect(row['secret']).not.toEqual(new TextEncoder().encode('my-secret-password'));
177
- expect(row['dob']).toBe('2023-12-25');
178
- // Update
179
- await repository.update(inserted.id, { name: 'Updated' });
180
- const updated = await repository.load(inserted.id);
181
- expect(updated.name).toBe('Updated');
182
- expect(updated.secret).toBe('my-secret-password'); // Still readable
183
- // Soft Delete
184
- await repository.delete(inserted.id);
185
- const deleted = await repository.tryLoad(inserted.id);
186
- expect(deleted).toBeUndefined();
187
- const { rows: [deletedRow] } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
188
- expect(deletedRow['delete_timestamp']).not.toBeNull();
189
- });
149
+ const entity = new StandardEntity();
150
+ entity.name = 'Test';
151
+ entity.address = { street: 'Main St', number: 123 };
152
+ entity.secret = 'my-secret-password';
153
+ entity.role = UserRole.User;
154
+ entity.data = { foo: 'bar' };
155
+ entity.tags = ['a', 'b'];
156
+ entity.birthday = 19716; // 2023-12-25
157
+ const inserted = await standardRepository.insert(entity);
158
+ expect(inserted.id).toBeDefined();
159
+ expect(inserted.name).toBe('Test');
160
+ expect(inserted.address.street).toBe('Main St');
161
+ expect(inserted.secret).toBe('my-secret-password');
162
+ expect(inserted.role).toBe(UserRole.User);
163
+ expect(inserted.data).toEqual({ foo: 'bar' });
164
+ expect(inserted.tags).toEqual(['a', 'b']);
165
+ expect(inserted.birthday).toBe(19716);
166
+ // Verify DB encrypted state
167
+ const { rows: [row] } = await db.execute(sql `SELECT secret, dob FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
168
+ expect(row['secret']).toBeInstanceOf(Uint8Array);
169
+ expect(row['secret']).not.toEqual(new TextEncoder().encode('my-secret-password'));
170
+ expect(row['dob']).toBe('2023-12-25');
171
+ // Update
172
+ await standardRepository.update(inserted.id, { name: 'Updated' });
173
+ const updated = await standardRepository.load(inserted.id);
174
+ expect(updated.name).toBe('Updated');
175
+ expect(updated.secret).toBe('my-secret-password'); // Still readable
176
+ // Soft Delete
177
+ await standardRepository.delete(inserted.id);
178
+ const deleted = await standardRepository.tryLoad(inserted.id);
179
+ expect(deleted).toBeUndefined();
180
+ const { rows: [deletedRow] } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
181
+ expect(deletedRow['delete_timestamp']).not.toBeNull();
190
182
  });
191
183
  test('should support Reference decorator', async () => {
192
184
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
193
- await runInInjectionContext(injector, async () => {
194
- const standardRepository = injectRepository(StandardEntity);
195
- const postRepository = injectRepository(Post);
196
- const author = Object.assign(new StandardEntity(), { name: 'Author', address: { street: 'A', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
197
- const insertedAuthor = await standardRepository.insert(author);
198
- const post = Object.assign(new Post(), { title: 'Hello World', authorId: insertedAuthor.id });
199
- const insertedPost = await postRepository.insert(post);
200
- expect(insertedPost.authorId).toBe(insertedAuthor.id);
201
- const loadedPost = await postRepository.load(insertedPost.id);
202
- expect(loadedPost.authorId).toBe(insertedAuthor.id);
203
- });
185
+ const author = Object.assign(new StandardEntity(), { name: 'Author', address: { street: 'A', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
186
+ const insertedAuthor = await standardRepository.insert(author);
187
+ const post = Object.assign(new Post(), { title: 'Hello World', authorId: insertedAuthor.id });
188
+ const insertedPost = await postRepository.insert(post);
189
+ expect(insertedPost.authorId).toBe(insertedAuthor.id);
190
+ const loadedPost = await postRepository.load(insertedPost.id);
191
+ expect(loadedPost.authorId).toBe(insertedAuthor.id);
204
192
  });
205
193
  test('should perform insertMany and loadMany', async () => {
206
194
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
207
- await runInInjectionContext(injector, async () => {
208
- const repository = injectRepository(StandardEntity);
209
- const entities = [
210
- Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's1', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
211
- Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's2', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
212
- ];
213
- const inserted = await repository.insertMany(entities);
214
- expect(inserted).toHaveLength(2);
215
- const loaded = await repository.loadMany(inserted.map((e) => e.id));
216
- expect(loaded).toHaveLength(2);
217
- expect(loaded.map((e) => e.name).sort()).toEqual(['E1', 'E2']);
218
- });
195
+ const entities = [
196
+ Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's1', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
197
+ Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's2', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
198
+ ];
199
+ const inserted = await standardRepository.insertMany(entities);
200
+ expect(inserted).toHaveLength(2);
201
+ const loaded = await standardRepository.loadMany(inserted.map((e) => e.id));
202
+ expect(loaded).toHaveLength(2);
203
+ expect(loaded.map((e) => e.name).sort()).toEqual(['E1', 'E2']);
219
204
  });
220
205
  test('should support upsert', async () => {
221
206
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
222
- await runInInjectionContext(injector, async () => {
223
- const repository = injectRepository(StandardEntity);
224
- const entity = Object.assign(new StandardEntity(), { name: 'Original', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
225
- const inserted = await repository.insert(entity);
226
- // Upsert: update existing
227
- const updateEntity = Object.assign(new StandardEntity(), { id: inserted.id, name: 'Upserted', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
228
- const upserted = await repository.upsert('id', updateEntity);
229
- expect(upserted.id).toBe(inserted.id);
230
- expect(upserted.name).toBe('Upserted');
231
- // Verify DB
232
- const loaded = await repository.load(inserted.id);
233
- expect(loaded.name).toBe('Upserted');
234
- });
207
+ const entity = Object.assign(new StandardEntity(), { name: 'Original', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
208
+ const inserted = await standardRepository.insert(entity);
209
+ // Upsert: update existing
210
+ const updateEntity = Object.assign(new StandardEntity(), { id: inserted.id, name: 'Upserted', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
211
+ const upserted = await standardRepository.upsert('id', updateEntity);
212
+ expect(upserted.id).toBe(inserted.id);
213
+ expect(upserted.name).toBe('Upserted');
214
+ // Verify DB
215
+ const loaded = await standardRepository.load(inserted.id);
216
+ expect(loaded.name).toBe('Upserted');
235
217
  });
236
218
  test('should support complex queries with $or and $and', async () => {
237
219
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
238
- await runInInjectionContext(injector, async () => {
239
- const repository = injectRepository(StandardEntity);
240
- await repository.insertMany([
241
- Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S1', number: 10 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
242
- Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S2', number: 20 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
243
- Object.assign(new StandardEntity(), { name: 'C', address: { street: 'S3', number: 30 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
244
- ]);
245
- const results = await repository.loadManyByQuery({
246
- $or: [
247
- { name: 'B' },
248
- { $and: [{ role: UserRole.Admin }, { 'address.number': { $gt: 25 } }] }
249
- ]
250
- });
251
- expect(results).toHaveLength(2);
252
- expect(results.map((r) => r.name).sort()).toEqual(['B', 'C']);
220
+ await standardRepository.insertMany([
221
+ Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S1', number: 10 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
222
+ Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S2', number: 20 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
223
+ Object.assign(new StandardEntity(), { name: 'C', address: { street: 'S3', number: 30 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
224
+ ]);
225
+ const results = await standardRepository.loadManyByQuery({
226
+ $or: [
227
+ { name: 'B' },
228
+ { $and: [{ role: UserRole.Admin }, { 'address.number': { $gt: 25 } }] }
229
+ ]
253
230
  });
231
+ expect(results).toHaveLength(2);
232
+ expect(results.map((r) => r.name).sort()).toEqual(['B', 'C']);
254
233
  });
255
234
  test('should support hasAll', async () => {
256
235
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
257
- await runInInjectionContext(injector, async () => {
258
- const repository = injectRepository(StandardEntity);
259
- const inserted = await repository.insertMany([
260
- Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
261
- Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
262
- ]);
263
- expect(await repository.hasAll(inserted.map((e) => e.id))).toBe(true);
264
- expect(await repository.hasAll([...inserted.map((e) => e.id), '00000000-0000-0000-0000-000000000000'])).toBe(false);
265
- });
236
+ const inserted = await standardRepository.insertMany([
237
+ Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
238
+ Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
239
+ ]);
240
+ expect(await standardRepository.hasAll(inserted.map((e) => e.id))).toBe(true);
241
+ expect(await standardRepository.hasAll([...inserted.map((e) => e.id), '00000000-0000-0000-0000-000000000000'])).toBe(false);
266
242
  });
267
243
  test('should throw NotFoundError on missing load', async () => {
268
- await runInInjectionContext(injector, async () => {
269
- const repository = injectRepository(StandardEntity);
270
- await expect(repository.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
271
- });
244
+ await expect(standardRepository.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
272
245
  });
273
246
  test('should support upsertMany', async () => {
274
247
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
275
- await runInInjectionContext(injector, async () => {
276
- const repository = injectRepository(StandardEntity);
277
- const e1 = await repository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
278
- const upsertData = [
279
- Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Updated', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
280
- Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
281
- ];
282
- await repository.upsertMany('id', upsertData);
283
- const all = await repository.loadAll();
284
- expect(all).toHaveLength(2);
285
- expect(all.find((e) => e.id === e1.id).name).toBe('E1-Updated');
286
- expect(all.find((e) => e.name === 'E2')).toBeDefined();
287
- });
248
+ const e1 = await standardRepository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
249
+ const upsertData = [
250
+ Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Updated', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
251
+ Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
252
+ ];
253
+ await standardRepository.upsertMany('id', upsertData);
254
+ const all = await standardRepository.loadAll();
255
+ expect(all).toHaveLength(2);
256
+ expect(all.find((e) => e.id === e1.id).name).toBe('E1-Updated');
257
+ expect(all.find((e) => e.name === 'E2')).toBeDefined();
288
258
  });
289
259
  test('should support deleteManyByQuery', async () => {
290
260
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
291
- await runInInjectionContext(injector, async () => {
292
- const repository = injectRepository(StandardEntity);
293
- await repository.insertMany([
294
- Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
295
- Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
296
- Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S', number: 3 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
297
- ]);
298
- await repository.deleteManyByQuery({ name: 'A' });
299
- const remaining = await repository.loadAll();
300
- expect(remaining).toHaveLength(1);
301
- expect(remaining[0].name).toBe('B');
302
- });
261
+ await standardRepository.insertMany([
262
+ Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
263
+ Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
264
+ Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S', number: 3 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
265
+ ]);
266
+ await standardRepository.deleteManyByQuery({ name: 'A' });
267
+ const remaining = await standardRepository.loadAll();
268
+ expect(remaining).toHaveLength(1);
269
+ expect(remaining[0].name).toBe('B');
303
270
  });
304
271
  test('should support tryInsert', async () => {
305
272
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
306
- await runInInjectionContext(injector, async () => {
307
- const repository = injectRepository(StandardEntity);
308
- const entity = Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
309
- const inserted = await repository.tryInsert(entity);
310
- expect(inserted).toBeDefined();
311
- // Conflict
312
- const conflict = await repository.tryInsert(Object.assign(new StandardEntity(), { id: inserted.id, name: 'Conflict', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
313
- expect(conflict).toBeUndefined();
314
- });
273
+ const entity = Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
274
+ const inserted = await standardRepository.tryInsert(entity);
275
+ expect(inserted).toBeDefined();
276
+ // Conflict
277
+ const conflict = await standardRepository.tryInsert(Object.assign(new StandardEntity(), { id: inserted.id, name: 'Conflict', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
278
+ expect(conflict).toBeUndefined();
315
279
  });
316
280
  test('should support insertManyIfNotExists', async () => {
317
281
  await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
318
- await runInInjectionContext(injector, async () => {
319
- const repository = injectRepository(StandardEntity);
320
- const e1 = await repository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
321
- const data = [
322
- Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Conflict', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
323
- Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
324
- ];
325
- const results = await repository.insertManyIfNotExists('id', data);
326
- expect(results).toHaveLength(1);
327
- expect(results[0].name).toBe('E2');
328
- });
282
+ const e1 = await standardRepository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
283
+ const data = [
284
+ Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Conflict', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
285
+ Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
286
+ ];
287
+ const results = await standardRepository.insertManyIfNotExists('id', data);
288
+ expect(results).toHaveLength(1);
289
+ expect(results[0].name).toBe('E2');
329
290
  });
330
291
  });
@@ -9,16 +9,16 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { sql } from 'drizzle-orm';
11
11
  import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
12
- import { Injector, runInInjectionContext } from '../../injector/index.js';
13
12
  import { StringProperty } from '../../schema/index.js';
14
13
  import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
14
+ import { isArray } from '../../utils/type-guards.js';
15
15
  import { Table } from '../decorators.js';
16
16
  import { Entity } from '../entity.js';
17
- import { Database } from '../server/index.js';
18
- import { injectRepository } from '../server/repository.js';
17
+ import { getRepository } from '../server/index.js';
19
18
  describe('ORM Repository Search', () => {
20
19
  let injector;
21
20
  let database;
21
+ let repository;
22
22
  const schema = 'test_orm_search';
23
23
  let SearchEntity = class SearchEntity extends Entity {
24
24
  title;
@@ -37,6 +37,7 @@ describe('ORM Repository Search', () => {
37
37
  ], SearchEntity);
38
38
  beforeAll(async () => {
39
39
  ({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
40
+ repository = injector.resolve(getRepository(SearchEntity));
40
41
  await dropTables(database, schema, ['search_entities']);
41
42
  await database.execute(sql `
42
43
  CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('search_entities')} (
@@ -55,40 +56,31 @@ describe('ORM Repository Search', () => {
55
56
  await truncateTables(database, schema, ['search_entities']);
56
57
  });
57
58
  test('should support search with score transformer', async () => {
58
- await runInInjectionContext(injector, async () => {
59
- const repository = injectRepository(SearchEntity);
60
- await repository.insert(Object.assign(new SearchEntity(), { title: 'Hello World', content: 'Some content here' }));
61
- const results = await repository.search({
62
- query: { $tsvector: { fields: ['title'], query: 'hello' } },
63
- score: (score) => sql `(${score}) * 100`,
64
- });
65
- expect(results).toHaveLength(1);
66
- expect(results[0].score).toBeGreaterThan(1); // normalized score is small, * 100 should be > 1
59
+ await repository.insert(Object.assign(new SearchEntity(), { title: 'Hello World', content: 'Some content here' }));
60
+ const results = await repository.search({
61
+ query: { $tsvector: { fields: ['title'], query: 'hello' } },
62
+ score: (score) => sql `(${score}) * 100`,
67
63
  });
64
+ expect(results).toHaveLength(1);
65
+ expect(results[0].score).toBeGreaterThan(1); // normalized score is small, * 100 should be > 1
68
66
  });
69
67
  test('should support search with highlight (column names)', async () => {
70
- await runInInjectionContext(injector, async () => {
71
- const repository = injectRepository(SearchEntity);
72
- await repository.insert(Object.assign(new SearchEntity(), { title: 'Highlander', content: 'There can be only one' }));
73
- const results = await repository.search({
74
- query: { $tsvector: { fields: ['title', 'content'], query: 'Highlander' } },
75
- highlight: { source: 'title' },
76
- });
77
- expect(results).toHaveLength(1);
78
- expect(results[0].highlight).toContain('<b>Highlander</b>');
68
+ await repository.insert(Object.assign(new SearchEntity(), { title: 'Highlander', content: 'There can be only one' }));
69
+ const results = await repository.search({
70
+ query: { $tsvector: { fields: ['title', 'content'], query: 'Highlander' } },
71
+ highlight: { source: 'title' },
79
72
  });
73
+ expect(results).toHaveLength(1);
74
+ expect(results[0].highlight).toContain('<b>Highlander</b>');
80
75
  });
81
76
  test('should support search with highlight (raw SQL)', async () => {
82
- await runInInjectionContext(injector, async () => {
83
- const repository = injectRepository(SearchEntity);
84
- await repository.insert(Object.assign(new SearchEntity(), { title: 'SQL Master', content: 'Database wizard' }));
85
- const results = await repository.search({
86
- query: { $tsvector: { fields: ['title', 'content'], query: 'Master' } },
87
- highlight: { source: sql `title || ' ' || content` },
88
- });
89
- expect(results).toHaveLength(1);
90
- expect(results[0].highlight).toContain('<b>Master</b>');
77
+ await repository.insert(Object.assign(new SearchEntity(), { title: 'SQL Master', content: 'Database wizard' }));
78
+ const results = await repository.search({
79
+ query: { $tsvector: { fields: ['title', 'content'], query: 'Master' } },
80
+ highlight: { source: sql `title || ' ' || content` },
91
81
  });
82
+ expect(results).toHaveLength(1);
83
+ expect(results[0].highlight).toContain('<b>Master</b>');
92
84
  });
93
85
  test('should support ParadeDB search with highlightPositions', async () => {
94
86
  // This test requires pg_search extension to be installed.
@@ -100,20 +92,14 @@ describe('ORM Repository Search', () => {
100
92
  console.warn('ParadeDB (pg_search) not available or index creation failed, skipping ParadeDB test.', e);
101
93
  return;
102
94
  }
103
- await runInInjectionContext(injector, async () => {
104
- const repository = injectRepository(SearchEntity);
105
- await repository.insert(Object.assign(new SearchEntity(), { title: 'Parade', content: 'ParadeDB is fast' }));
106
- const results = await repository.search({
107
- query: { $parade: { fields: ['title'], query: 'Parade' } },
108
- highlight: { source: 'title', includePositions: true },
109
- });
110
- expect(results).toHaveLength(1);
111
- expect(results[0].highlight).toBeDefined();
112
- expect(results[0].highlightPositions).toBeDefined();
113
- expect(isArray(results[0].highlightPositions)).toBe(true);
95
+ await repository.insert(Object.assign(new SearchEntity(), { title: 'Parade', content: 'ParadeDB is fast' }));
96
+ const results = await repository.search({
97
+ query: { $parade: { fields: ['title'], query: 'Parade' } },
98
+ highlight: { source: 'title', includePositions: true },
114
99
  });
100
+ expect(results).toHaveLength(1);
101
+ expect(results[0].highlight).toBeDefined();
102
+ expect(results[0].highlightPositions).toBeDefined();
103
+ expect(isArray(results[0].highlightPositions)).toBe(true);
115
104
  });
116
105
  });
117
- function isArray(val) {
118
- return Array.isArray(val);
119
- }