@tstdl/base 0.93.140 → 0.93.142

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 (123) hide show
  1. package/application/application.d.ts +1 -1
  2. package/application/application.js +1 -1
  3. package/application/providers.d.ts +20 -2
  4. package/application/providers.js +34 -7
  5. package/audit/module.d.ts +5 -0
  6. package/audit/module.js +9 -1
  7. package/authentication/client/authentication.service.d.ts +1 -0
  8. package/authentication/client/authentication.service.js +3 -2
  9. package/authentication/server/module.d.ts +5 -0
  10. package/authentication/server/module.js +9 -1
  11. package/authentication/tests/authentication.api-controller.test.js +1 -1
  12. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  13. package/authentication/tests/authentication.client-service.test.js +1 -1
  14. package/circuit-breaker/circuit-breaker.d.ts +6 -4
  15. package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
  16. package/circuit-breaker/postgres/circuit-breaker.js +8 -5
  17. package/circuit-breaker/postgres/module.d.ts +1 -0
  18. package/circuit-breaker/postgres/module.js +5 -1
  19. package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
  20. package/document-management/server/configure.js +5 -1
  21. package/document-management/server/module.d.ts +1 -1
  22. package/document-management/server/module.js +1 -1
  23. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  24. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  25. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  26. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  27. package/examples/document-management/main.d.ts +1 -0
  28. package/examples/document-management/main.js +14 -11
  29. package/key-value-store/postgres/module.d.ts +1 -0
  30. package/key-value-store/postgres/module.js +5 -1
  31. package/lock/postgres/module.d.ts +1 -0
  32. package/lock/postgres/module.js +5 -1
  33. package/mail/module.d.ts +5 -1
  34. package/mail/module.js +11 -6
  35. package/module/modules/web-server.module.js +2 -3
  36. package/notification/server/module.d.ts +1 -0
  37. package/notification/server/module.js +5 -1
  38. package/notification/tests/notification-api.test.js +5 -1
  39. package/notification/tests/notification-flow.test.js +8 -5
  40. package/orm/decorators.d.ts +22 -5
  41. package/orm/decorators.js +10 -1
  42. package/orm/server/bootstrap.d.ts +11 -0
  43. package/orm/server/bootstrap.js +31 -0
  44. package/orm/server/drizzle/schema-converter.d.ts +3 -1
  45. package/orm/server/drizzle/schema-converter.js +85 -56
  46. package/orm/server/encryption.d.ts +0 -1
  47. package/orm/server/encryption.js +1 -4
  48. package/orm/server/extension.d.ts +14 -0
  49. package/orm/server/extension.js +27 -0
  50. package/orm/server/index.d.ts +3 -6
  51. package/orm/server/index.js +3 -6
  52. package/orm/server/migration.d.ts +18 -0
  53. package/orm/server/migration.js +58 -0
  54. package/orm/server/repository.d.ts +2 -1
  55. package/orm/server/repository.js +19 -9
  56. package/orm/server/transaction.d.ts +6 -10
  57. package/orm/server/transaction.js +25 -26
  58. package/orm/server/transactional.js +3 -3
  59. package/orm/tests/database-extension.test.js +63 -0
  60. package/orm/tests/database-migration.test.js +83 -0
  61. package/orm/tests/encryption.test.js +3 -4
  62. package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
  63. package/orm/tests/repository-compound-primary-key.test.js +234 -0
  64. package/orm/tests/schema-generation.test.d.ts +1 -0
  65. package/orm/tests/schema-generation.test.js +52 -5
  66. package/orm/utils.d.ts +17 -2
  67. package/orm/utils.js +49 -1
  68. package/package.json +5 -4
  69. package/rate-limit/postgres/module.d.ts +1 -0
  70. package/rate-limit/postgres/module.js +5 -1
  71. package/reflection/decorator-data.js +11 -12
  72. package/task-queue/README.md +2 -10
  73. package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
  74. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
  75. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  76. package/task-queue/postgres/module.d.ts +1 -0
  77. package/task-queue/postgres/module.js +6 -1
  78. package/task-queue/postgres/schemas.d.ts +15 -6
  79. package/task-queue/postgres/schemas.js +4 -3
  80. package/task-queue/postgres/task-queue.d.ts +18 -15
  81. package/task-queue/postgres/task-queue.js +797 -499
  82. package/task-queue/postgres/task.model.d.ts +20 -9
  83. package/task-queue/postgres/task.model.js +65 -39
  84. package/task-queue/task-context.d.ts +12 -7
  85. package/task-queue/task-context.js +8 -6
  86. package/task-queue/task-queue.d.ts +364 -43
  87. package/task-queue/task-queue.js +153 -41
  88. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  89. package/task-queue/tests/coverage-branch.test.js +395 -0
  90. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  91. package/task-queue/tests/coverage-enhancement.test.js +150 -0
  92. package/task-queue/tests/dag.test.d.ts +1 -0
  93. package/task-queue/tests/dag.test.js +188 -0
  94. package/task-queue/tests/dependencies.test.js +165 -47
  95. package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
  96. package/task-queue/tests/enqueue-batch.test.js +125 -0
  97. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  98. package/task-queue/tests/fan-out-spawning.test.js +94 -0
  99. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  100. package/task-queue/tests/idempotent-replacement.test.js +114 -0
  101. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  102. package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
  103. package/task-queue/tests/queue.test.js +294 -49
  104. package/task-queue/tests/shutdown.test.d.ts +1 -0
  105. package/task-queue/tests/shutdown.test.js +41 -0
  106. package/task-queue/tests/transactions.test.d.ts +1 -0
  107. package/task-queue/tests/transactions.test.js +47 -0
  108. package/task-queue/tests/worker.test.js +63 -15
  109. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  110. package/task-queue/tests/zombie-parent.test.js +45 -0
  111. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  112. package/task-queue/tests/zombie-recovery.test.js +51 -0
  113. package/test5.js +5 -5
  114. package/testing/integration-setup.d.ts +4 -4
  115. package/testing/integration-setup.js +56 -29
  116. package/text/localization.service.js +2 -2
  117. package/utils/file-reader.js +1 -2
  118. package/utils/timing.d.ts +2 -2
  119. package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
  120. package/task-queue/tests/complex.test.js +0 -306
  121. package/task-queue/tests/extensive-dependencies.test.js +0 -234
  122. /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
  123. /package/{task-queue/tests/extensive-dependencies.test.d.ts → orm/tests/database-migration.test.d.ts} +0 -0
package/mail/module.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Injector } from '../injector/injector.js';
1
2
  import { type DatabaseConfig } from '../orm/server/index.js';
2
3
  import type { Type } from '../types/index.js';
3
4
  import { MailClient, MailClientConfig } from './mail.client.js';
@@ -7,9 +8,12 @@ export declare class MailModuleConfig {
7
8
  defaultClientConfig?: MailClientConfig;
8
9
  client?: Type<MailClient>;
9
10
  defaultData?: DefaultMailData;
11
+ autoMigrate?: boolean;
10
12
  }
11
13
  /**
12
14
  * configure mail module
13
15
  */
14
- export declare function configureMail(config: MailModuleConfig): void;
16
+ export declare function configureMail({ injector, ...config }: MailModuleConfig & {
17
+ injector?: Injector;
18
+ }): void;
15
19
  export declare function migrateMailSchema(): Promise<void>;
package/mail/module.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { inject } from '../injector/index.js';
2
2
  import { Injector } from '../injector/injector.js';
3
- import { Database, migrate } from '../orm/server/index.js';
3
+ import { Database, migrate, registerDatabaseMigration } from '../orm/server/index.js';
4
4
  import { isDefined } from '../utils/type-guards.js';
5
5
  import { MailClient, MailClientConfig } from './mail.client.js';
6
6
  import { MAIL_DEFAULT_DATA } from './tokens.js';
@@ -9,20 +9,25 @@ export class MailModuleConfig {
9
9
  defaultClientConfig;
10
10
  client;
11
11
  defaultData;
12
+ autoMigrate;
12
13
  }
13
14
  /**
14
15
  * configure mail module
15
16
  */
16
- export function configureMail(config) {
17
- Injector.register(MailModuleConfig, { useValue: config });
17
+ export function configureMail({ injector, ...config }) {
18
+ const targetInjector = injector ?? Injector;
19
+ targetInjector.register(MailModuleConfig, { useValue: config });
18
20
  if (isDefined(config.defaultClientConfig)) {
19
- Injector.registerSingleton(MailClientConfig, { useValue: config.defaultClientConfig });
21
+ targetInjector.registerSingleton(MailClientConfig, { useValue: config.defaultClientConfig });
20
22
  }
21
23
  if (isDefined(config.client)) {
22
- Injector.registerSingleton(MailClient, { useToken: config.client });
24
+ targetInjector.registerSingleton(MailClient, { useToken: config.client });
23
25
  }
24
26
  if (isDefined(config.defaultData)) {
25
- Injector.registerSingleton(MAIL_DEFAULT_DATA, { useValue: config.defaultData });
27
+ targetInjector.registerSingleton(MAIL_DEFAULT_DATA, { useValue: config.defaultData });
28
+ }
29
+ if (config.autoMigrate != false) {
30
+ registerDatabaseMigration('Mail', migrateMailSchema, { injector });
26
31
  }
27
32
  }
28
33
  export async function migrateMailSchema() {
@@ -18,7 +18,7 @@ export class WebServerModuleConfiguration {
18
18
  }
19
19
  ;
20
20
  let WebServerModule = class WebServerModule extends Module {
21
- config = inject(WebServerModuleConfiguration);
21
+ config = inject(WebServerModuleConfiguration, undefined, { optional: true });
22
22
  httpServer = inject(HttpServer);
23
23
  apiGateway = inject(ApiGateway);
24
24
  apiControllers = inject(API_CONTROLLERS);
@@ -37,7 +37,7 @@ let WebServerModule = class WebServerModule extends Module {
37
37
  }
38
38
  async _run(cancellationSignal) {
39
39
  this.initialize();
40
- await this.httpServer.listen(this.config.port ?? 8000);
40
+ await this.httpServer.listen(this.config?.port ?? 8000);
41
41
  const closePromise = cancellationSignal.wait().then(async () => {
42
42
  await this.httpServer[Symbol.asyncDispose]();
43
43
  });
@@ -58,5 +58,4 @@ export { WebServerModule };
58
58
  export function configureWebServerModule({ injector, ...config } = {}) {
59
59
  const targetInjector = injector ?? Injector;
60
60
  targetInjector.register(WebServerModuleConfiguration, { useValue: config });
61
- targetInjector.registerSingleton(WebServerModule, { useClass: WebServerModule });
62
61
  }
@@ -11,6 +11,7 @@ export declare class NotificationConfiguration {
11
11
  * Defaults to 30 days.
12
12
  */
13
13
  autoArchiveAfter?: number;
14
+ autoMigrate?: boolean;
14
15
  }
15
16
  export declare function configureNotification({ injector, ...config }: NotificationConfiguration & {
16
17
  injector?: Injector;
@@ -1,5 +1,5 @@
1
1
  import { inject, Injector } from '../../injector/index.js';
2
- import { Database, migrate } from '../../orm/server/index.js';
2
+ import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
3
3
  import { isDefined } from '../../utils/type-guards.js';
4
4
  import { NotificationAncillaryService } from './services/notification-ancillary.service.js';
5
5
  export class NotificationConfiguration {
@@ -11,6 +11,7 @@ export class NotificationConfiguration {
11
11
  * Defaults to 30 days.
12
12
  */
13
13
  autoArchiveAfter;
14
+ autoMigrate;
14
15
  }
15
16
  export function configureNotification({ injector, ...config }) {
16
17
  const targetInjector = injector ?? Injector;
@@ -18,6 +19,9 @@ export function configureNotification({ injector, ...config }) {
18
19
  if (isDefined(config.ancillaryService)) {
19
20
  targetInjector.register(NotificationAncillaryService, { useToken: config.ancillaryService });
20
21
  }
22
+ if (config.autoMigrate != false) {
23
+ registerDatabaseMigration('Notification', migrateNotificationSchema, { injector });
24
+ }
21
25
  }
22
26
  /**
23
27
  * Migrates the notification schema.
@@ -1,5 +1,5 @@
1
1
  import { Subject } from 'rxjs';
2
- import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
3
3
  import { SubjectService } from '../../authentication/server/subject.service.js';
4
4
  import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
5
5
  import { NotificationChannel } from '../models/index.js';
@@ -44,6 +44,10 @@ describe('Notification API (Integration)', () => {
44
44
  beforeEach(() => {
45
45
  vi.clearAllMocks();
46
46
  });
47
+ afterEach(async () => {
48
+ await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
49
+ await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
50
+ });
47
51
  const createMockContext = (params = {}) => ({
48
52
  parameters: params,
49
53
  abortSignal: new AbortController().signal,
@@ -4,12 +4,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
7
+ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
8
8
  import { SubjectService } from '../../authentication/server/subject.service.js';
9
9
  import { runInInjectionContext, Singleton } from '../../injector/index.js';
10
10
  import { MailService } from '../../mail/mail.service.js';
11
11
  import { injectRepository } from '../../orm/server/index.js';
12
- import { clearTenantData, setupIntegrationTest, truncateTables } from '../../testing/index.js';
12
+ import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
13
13
  import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationStatus, WebPushSubscription } from '../models/index.js';
14
14
  import { configureNotification } from '../server/module.js';
15
15
  import { EmailChannelProvider } from '../server/providers/email-channel-provider.js';
@@ -34,7 +34,7 @@ describe('Notification Flow (Integration)', () => {
34
34
  let subjectService;
35
35
  let mailServiceMock;
36
36
  const schema = 'notification';
37
- const tenantId = crypto.randomUUID();
37
+ let tenantId;
38
38
  beforeAll(async () => {
39
39
  ({ injector, database } = await setupIntegrationTest({
40
40
  orm: { schema },
@@ -63,10 +63,13 @@ describe('Notification Flow (Integration)', () => {
63
63
  worker.registerProvider(NotificationChannel.InApp, injector.resolve(InAppChannelProvider));
64
64
  });
65
65
  beforeEach(async () => {
66
- await truncateTables(database, schema, ['log', 'in_app', 'in_app_archive', 'type', 'preference', 'web_push_subscription']);
67
- await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
66
+ tenantId = crypto.randomUUID();
68
67
  vi.clearAllMocks();
69
68
  });
69
+ afterEach(async () => {
70
+ await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
71
+ await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
72
+ });
70
73
  test('should execute full notification flow with escalation', async () => {
71
74
  await runInInjectionContext(injector, async () => {
72
75
  const logRepo = injectRepository(NotificationLogEntity);
@@ -3,7 +3,7 @@
3
3
  * Defines decorators for ORM entities and columns, used to configure database schema mapping.
4
4
  */
5
5
  import type { SQL } from 'drizzle-orm';
6
- import type { ExtraConfigColumn } from 'drizzle-orm/pg-core';
6
+ import type { ExtraConfigColumn, UpdateDeleteAction } from 'drizzle-orm/pg-core';
7
7
  import type { LiteralUnion, SetRequired } from 'type-fest';
8
8
  import { type SpecificCreateDecoratorOptions } from '../reflection/index.js';
9
9
  import type { AbstractConstructor, Record, TypedOmit } from '../types/index.js';
@@ -40,8 +40,7 @@ export type WhereBuilder<T extends BaseEntity = BaseEntity> = SqlBuilder<T, Quer
40
40
  export type OrmTableReflectionData<T extends BaseEntity = BaseEntity> = {
41
41
  name?: string;
42
42
  schema?: string;
43
- compoundPrimaryKeyName?: string;
44
- compoundPrimaryKeyNaming?: NamingStrategy;
43
+ primaryKey?: PrimaryKeyReflectionData<T>;
45
44
  unique?: UniqueReflectionData[];
46
45
  index?: IndexReflectionData[];
47
46
  paradeIndex?: ParadeIndexReflectionData<T>;
@@ -87,12 +86,21 @@ type ReferenceReflectionData<T extends AnyEntity = AnyEntity> = {
87
86
  target: () => EntityType<T>;
88
87
  targetColumn?: TargetColumnPath<T>;
89
88
  excludeTenant?: boolean;
89
+ onDelete?: UpdateDeleteAction;
90
+ onUpdate?: UpdateDeleteAction;
91
+ };
92
+ export type PrimaryKeyReflectionData<T extends BaseEntity = any> = {
93
+ columns?: ValueOrProvider<(TargetColumnPath<T> | SQL | ExtraConfigColumn)[], ExtraConfigColumnsFromType<EntityType<T>>>;
94
+ options?: {
95
+ name?: string;
96
+ naming?: NamingStrategy;
97
+ };
90
98
  };
91
99
  /**
92
100
  * Reflection data for unique constraints.
93
101
  */
94
- export type UniqueReflectionData = {
95
- columns?: string[];
102
+ export type UniqueReflectionData<T extends BaseEntity = any> = {
103
+ columns?: ValueOrProvider<(TargetColumnPath<T> | SQL | ExtraConfigColumn)[], ExtraConfigColumnsFromType<EntityType<T>>>;
96
104
  options?: {
97
105
  name?: string;
98
106
  naming?: NamingStrategy;
@@ -122,6 +130,8 @@ export type ForeignKeyReflectionData = {
122
130
  options?: {
123
131
  name?: string;
124
132
  naming?: NamingStrategy;
133
+ onDelete?: UpdateDeleteAction;
134
+ onUpdate?: UpdateDeleteAction;
125
135
  };
126
136
  };
127
137
  export type IndexOptions<T extends BaseEntity> = {
@@ -290,6 +300,13 @@ export declare function Table(name?: string, options?: TableOptions): ClassDecor
290
300
  * @param options Table options including name and schema.
291
301
  */
292
302
  export declare function Table(options?: TableOptions): ClassDecorator;
303
+ /**
304
+ * Define a composite primary key on multiple columns.
305
+ * @template T The entity type.
306
+ * @param columns An array of property names included in the primary key.
307
+ * @param options Additional primary key options.
308
+ */
309
+ export declare function PrimaryKey<T extends AnyEntity>(columns: Columns<T>, options?: PrimaryKeyReflectionData['options']): ClassDecorator;
293
310
  /**
294
311
  * Define a foreign key relationship.
295
312
  * @param target A function returning the referenced entity type.
package/orm/decorators.js CHANGED
@@ -71,7 +71,7 @@ export function PrimaryKeyProperty() {
71
71
  export function Reference(target, targetColumnOrOptions) {
72
72
  const targetColumn = (isString(targetColumnOrOptions) ? targetColumnOrOptions : targetColumnOrOptions?.targetColumn);
73
73
  const options = isString(targetColumnOrOptions) ? undefined : targetColumnOrOptions;
74
- return createColumnDecorator({ references: [{ target, targetColumn, excludeTenant: options?.excludeTenant }] });
74
+ return createColumnDecorator({ references: [{ target, targetColumn, excludeTenant: options?.excludeTenant, onDelete: options?.onDelete, onUpdate: options?.onUpdate }] });
75
75
  }
76
76
  export {
77
77
  /** @deprecated use {@link Reference} instead. */
@@ -113,6 +113,15 @@ export function Table(nameOrOptions, optionsOrNothing) {
113
113
  : filterUndefinedObjectProperties({ name, schema });
114
114
  return createTableDecorator(data);
115
115
  }
116
+ /**
117
+ * Define a composite primary key on multiple columns.
118
+ * @template T The entity type.
119
+ * @param columns An array of property names included in the primary key.
120
+ * @param options Additional primary key options.
121
+ */
122
+ export function PrimaryKey(columns, options) {
123
+ return createTableDecorator({ primaryKey: { columns, options } });
124
+ }
116
125
  /**
117
126
  * Define a foreign key relationship.
118
127
  * @param target A function returning the referenced entity type.
@@ -0,0 +1,11 @@
1
+ import { type ProvidersItem } from '../../injector/injector.js';
2
+ /**
3
+ * Provides the ORM bootstrap functionality by returning an initializer provider.
4
+ * This will run database extensions and migrations during application startup.
5
+ * @returns A provider item.
6
+ */
7
+ export declare function provideOrm(): ProvidersItem;
8
+ /**
9
+ * Bootstraps the ORM by running database extensions and migrations within an advisory lock.
10
+ */
11
+ export declare function bootstrapOrm(): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import { sql } from 'drizzle-orm';
2
+ import { provideInitializer } from '../../application/providers.js';
3
+ import { inject, runInInjectionContext } from '../../injector/inject.js';
4
+ import { Injector } from '../../injector/injector.js';
5
+ import { Database } from './database.js';
6
+ import { runDatabaseExtensions } from './extension.js';
7
+ import { runDatabaseMigrationsCore } from './migration.js';
8
+ /**
9
+ * Provides the ORM bootstrap functionality by returning an initializer provider.
10
+ * This will run database extensions and migrations during application startup.
11
+ * @returns A provider item.
12
+ */
13
+ export function provideOrm() {
14
+ return provideInitializer(bootstrapOrm);
15
+ }
16
+ /**
17
+ * Bootstraps the ORM by running database extensions and migrations within an advisory lock.
18
+ */
19
+ export async function bootstrapOrm() {
20
+ const injector = inject(Injector);
21
+ const database = inject(Database);
22
+ const lockId = 123456789; // Fixed lock ID for database setup
23
+ await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
24
+ try {
25
+ await runInInjectionContext(injector, async () => await runDatabaseExtensions());
26
+ await runInInjectionContext(injector, async () => await runDatabaseMigrationsCore());
27
+ }
28
+ finally {
29
+ await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
30
+ }
31
+ }
@@ -1,4 +1,4 @@
1
- import { type PgEnum, type PgSchema, type PgTableWithColumns } from 'drizzle-orm/pg-core';
1
+ import { type PgColumn, type PgEnum, type PgSchema, type PgTableWithColumns } from 'drizzle-orm/pg-core';
2
2
  import type { AbstractConstructor, Enumeration } from '../../../types/index.js';
3
3
  import { type EntityType } from '../../entity.js';
4
4
  import type { ColumnDefinition, ColumnDefinitionsMap, PgTableFromType } from '../types.js';
@@ -13,4 +13,6 @@ export declare function getColumnDefinitionsMap(table: PgTableWithColumns<any>):
13
13
  export declare function _getDrizzleTableFromType<T extends EntityType, S extends string>(type: T, fallbackSchemaName?: S): PgTableFromType<T, S>;
14
14
  export declare function isTableOwning(type: AbstractConstructor): boolean;
15
15
  export declare function getPgEnum(schema: string | PgSchema, enumeration: Enumeration, context?: ConverterContext): PgEnum<[string, ...string[]]>;
16
+ export declare function getPrimaryKeyColumnDefinitions(type: EntityType, table: PgTableFromType, columnDefinitions?: ColumnDefinition[], columnDefinitionsMap?: ColumnDefinitionsMap): ColumnDefinition[];
17
+ export declare function getPrimaryKeyColumns(type: EntityType, table: PgTableFromType, columnDefinitions?: ColumnDefinition[], columnDefinitionsMap?: ColumnDefinitionsMap): PgColumn[];
16
18
  export {};
@@ -15,13 +15,13 @@ import { typeExtends } from '../../../utils/index.js';
15
15
  import { merge } from '../../../utils/merge.js';
16
16
  import { compileDereferencer } from '../../../utils/object/dereference.js';
17
17
  import { fromEntries, mapObjectKeysToSnakeCase, objectEntries } from '../../../utils/object/object.js';
18
- import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNull, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
18
+ import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNull, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
19
19
  import { resolveValueOrProvider } from '../../../utils/value-or-provider.js';
20
20
  import { bytea, numericDate, timestamp, tsvector } from '../../data-types/index.js';
21
21
  import { TenantBaseEntity, TenantEntity } from '../../entity.js';
22
22
  import { getEnumName } from '../../enums.js';
23
23
  import { JsonSchema, NumericDateSchema, NumericSchema, TimestampSchema, TsVectorSchema, UuidSchema } from '../../schemas/index.js';
24
- import { getInheritanceMetadata, isChildEntity } from '../../utils.js';
24
+ import { getEntitySchema, getEntityTableName, getInheritanceMetadata, getTableReflectionDatas, isChildEntity } from '../../utils.js';
25
25
  import { decryptBytes, encryptBytes } from '../encryption.js';
26
26
  import { convertQuery, resolveTargetColumn, resolveTargetColumns } from '../query-converter.js';
27
27
  const getDbSchema = memoizeSingle(pgSchema);
@@ -42,13 +42,18 @@ export function getColumnDefinitionsMap(table) {
42
42
  export function _getDrizzleTableFromType(type, fallbackSchemaName) {
43
43
  const tableReflectionDatas = getTableReflectionDatas(type);
44
44
  const mergedTableReflectionData = tableReflectionDatas.reduceRight((merged, data) => ({ ...merged, ...data }), {});
45
- const schema = assertDefinedPass(mergedTableReflectionData.schema ?? fallbackSchemaName, 'Table schema not provided');
46
- const tableName = getTableName(type);
45
+ const schema = getEntitySchema(type, fallbackSchemaName);
46
+ const tableName = getEntityTableName(type);
47
47
  const dbSchema = getDbSchema(schema);
48
48
  const inheritanceMetadata = getInheritanceMetadata(type);
49
49
  const allColumnDefinitions = getPostgresColumnEntries(type, dbSchema, tableName, undefined, '', { filterInherited: false }, type);
50
+ const primaryKeyPropertyNames = new Set(isDefined(mergedTableReflectionData.primaryKey?.columns)
51
+ ? resolveValueOrProvider(mergedTableReflectionData.primaryKey.columns, {}).filter((c) => isString(c))
52
+ : []);
50
53
  const columnDefinitions = allColumnDefinitions.filter((cd) => !isChildEntity(type)
51
54
  || !cd.inherited
55
+ || (cd.reflectionData?.primaryKey == true)
56
+ || primaryKeyPropertyNames.has(cd.objectPath.path)
52
57
  || (cd.objectPath.path == 'id')
53
58
  || (cd.objectPath.path == 'tenantId')
54
59
  || (cd.objectPath.path == inheritanceMetadata?.discriminatorColumn));
@@ -100,23 +105,43 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
100
105
  }
101
106
  return builder;
102
107
  }
103
- function buildInheritanceCompositeUniqueConstraint(table) {
104
- const inheritanceMetadata = mergedTableReflectionData.inheritance;
108
+ function getInheritanceColumns(inheritanceTable, inheritanceType) {
109
+ const inheritanceMetadata = getInheritanceMetadata(inheritanceType);
105
110
  const discriminatorColumn = inheritanceMetadata.discriminatorColumn;
106
- const hasTenantId = isDefined(table['tenantId']);
111
+ const pkColumnDefinitions = getPrimaryKeyColumnDefinitions(inheritanceType, inheritanceTable, columnDefinitions, columnDefinitionsMap);
112
+ const discriminatorColumnObject = inheritanceTable[discriminatorColumn];
113
+ const hasTenantId = pkColumnDefinitions.some((d) => d.objectPath.path == 'tenantId');
114
+ const tenantIdColumn = hasTenantId ? inheritanceTable['tenantId'] : undefined;
107
115
  const columns = [
108
- ...(hasTenantId ? [table['tenantId']] : []),
109
- table[discriminatorColumn],
110
- table['id'],
116
+ ...(isDefined(tenantIdColumn) ? [tenantIdColumn] : []),
117
+ discriminatorColumnObject,
118
+ ...pkColumnDefinitions.filter((d) => d.objectPath.path != 'tenantId').map((d) => inheritanceTable[d.name]),
119
+ ];
120
+ const identifierParts = [
121
+ ...(hasTenantId ? ['tenantId'] : []),
122
+ discriminatorColumn,
123
+ ...pkColumnDefinitions.filter((d) => d.objectPath.path != 'tenantId').map((d) => d.objectPath.path),
111
124
  ];
125
+ return { columns, identifierParts };
126
+ }
127
+ function buildInheritanceCompositeUniqueConstraint(table) {
128
+ const { columns } = getInheritanceColumns(table, type);
112
129
  return unique(getUniqueName(tableName, columns)).on(...columns);
113
130
  }
114
131
  function buildPrimaryKey(table) {
115
- const columns = primaryKeyColumnDefinitions
116
- .toSorted(compareByValueSelectionToOrder(['tenantId', 'id', orderRest], (columnDefinition) => columnDefinition.name))
117
- .map((columnDefinition) => table[columnDefinition.name]);
132
+ const primaryKeyData = mergedTableReflectionData.primaryKey;
133
+ const resolvedColumns = isDefined(primaryKeyData?.columns) ? resolveValueOrProvider(primaryKeyData.columns, table) : undefined;
134
+ const columns = match(resolvedColumns)
135
+ .with(P.nonNullable, (primaryColumns) => primaryColumns.map((columnValue) => {
136
+ if (columnValue instanceof SQL) {
137
+ return columnValue;
138
+ }
139
+ return getColumn(table, columnValue);
140
+ }))
141
+ .with(undefined, () => getPrimaryKeyColumns(type, table, columnDefinitions, columnDefinitionsMap))
142
+ .exhaustive();
118
143
  return primaryKey({
119
- name: mergedTableReflectionData.compoundPrimaryKeyName ?? getPrimaryKeyName(tableName, columns, { naming: mergedTableReflectionData.compoundPrimaryKeyNaming }),
144
+ name: primaryKeyData?.options?.name ?? getPrimaryKeyName(tableName, columns, { naming: primaryKeyData?.options?.naming }),
120
145
  columns,
121
146
  });
122
147
  }
@@ -128,22 +153,14 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
128
153
  constraints.push(buildInheritanceCompositeUniqueConstraint(table));
129
154
  }
130
155
  if (isDefined(inheritanceMetadata) && isDefined(childEntityMetadata)) {
131
- const hasTenantId = isDefined(table['tenantId']);
156
+ const parentType = reflectionRegistry.getMetadata(type).parent;
157
+ const parentTable = getDrizzleTableFromType(parentType, schema);
158
+ const { columns: foreignKeyColumns, identifierParts: foreignKeyIdentifierParts } = getInheritanceColumns(table, type);
159
+ const { columns: parentForeignKeyColumns } = getInheritanceColumns(parentTable, parentType);
132
160
  constraints.push(check(getIdentifier(tableName, discriminatorColumn, 'check'), eq(table[discriminatorColumn], sql.raw(`'${childEntityMetadata.discriminatorValue}'`))), foreignKey({
133
- name: getForeignKeyName(tableName, getTableName(reflectionRegistry.getMetadata(type).parent), [...(hasTenantId ? ['tenantId'] : []), discriminatorColumn, 'id']),
134
- columns: [
135
- ...(hasTenantId ? [table['tenantId']] : []),
136
- table[discriminatorColumn],
137
- table['id'],
138
- ],
139
- foreignColumns: (() => {
140
- const parentTable = getDrizzleTableFromType(reflectionRegistry.getMetadata(type).parent, schema);
141
- return [
142
- ...(hasTenantId ? [parentTable['tenantId']] : []),
143
- parentTable[discriminatorColumn],
144
- parentTable.id,
145
- ];
146
- })(),
161
+ name: getForeignKeyName(tableName, getEntityTableName(parentType), foreignKeyIdentifierParts),
162
+ columns: foreignKeyColumns,
163
+ foreignColumns: parentForeignKeyColumns,
147
164
  }).onDelete('cascade'));
148
165
  }
149
166
  return constraints;
@@ -222,7 +239,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
222
239
  return builder;
223
240
  }
224
241
  const primaryKeyColumnDefinitions = columnDefinitions.filter((columnDefinition) => (columnDefinition.reflectionData?.primaryKey == true) || (columnDefinition.objectPath.path == 'id' && columnDefinition.reflectionData?.primaryKey != false));
225
- const skipPrimaryKey = primaryKeyColumnDefinitions.length > 1;
242
+ const skipPrimaryKey = (primaryKeyColumnDefinitions.length > 1) || isDefined(mergedTableReflectionData.primaryKey?.columns);
226
243
  const columnEntries = columnDefinitions.map((entry) => [entry.name, entry.buildType({ skipPrimaryKey })]);
227
244
  const drizzleSchema = dbSchema.table(tableName, fromEntries(columnEntries), (drizzleTable) => {
228
245
  const table = drizzleTable;
@@ -231,7 +248,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
231
248
  const bm25Index = buildParadeIndex(table);
232
249
  const checks = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.checks).filter(isDefined).map((data) => check(data.name, data.builder(table)));
233
250
  return [
234
- ...((primaryKeyColumnDefinitions.length > 1)
251
+ ...(skipPrimaryKey
235
252
  ? [buildPrimaryKey(table)]
236
253
  : []),
237
254
  ...buildInheritanceConstraints(table),
@@ -247,7 +264,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
247
264
  const foreignTable = getDrizzleTableFromType(tenantReferenceData.target(), dbSchema.schemaName);
248
265
  const nonTenantColumn = tenantReferenceData.targetColumn ?? 'id';
249
266
  return foreignKey({
250
- name: getForeignKeyName(tableName, getTableName(tenantReferenceData.target()), [nonTenantColumn]),
267
+ name: getForeignKeyName(tableName, getEntityTableName(tenantReferenceData.target()), [nonTenantColumn]),
251
268
  columns: [getColumn(table, 'tenantId'), getColumn(table, columnDefinition.name)],
252
269
  foreignColumns: [getColumn(foreignTable, 'tenantId'), getColumn(foreignTable, nonTenantColumn)],
253
270
  });
@@ -257,15 +274,23 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
257
274
  return tableReflectionData.foreignKeys?.map((foreignKeyData) => {
258
275
  const foreignKeyTarget = foreignKeyData.target();
259
276
  const foreignTable = getDrizzleTableFromType(foreignKeyTarget, dbSchema.schemaName);
260
- return foreignKey({
261
- name: foreignKeyData.options?.name ?? getForeignKeyName(tableName, getTableName(foreignKeyData.target()), foreignKeyData.columns, { naming: foreignKeyData.options?.naming }),
277
+ let builder = foreignKey({
278
+ name: foreignKeyData.options?.name ?? getForeignKeyName(tableName, getEntityTableName(foreignKeyData.target()), foreignKeyData.columns, { naming: foreignKeyData.options?.naming }),
262
279
  columns: foreignKeyData.columns.map((column) => getColumn(table, column)),
263
280
  foreignColumns: foreignKeyData.foreignColumns.map((column) => getColumn(foreignTable, column)),
264
281
  });
282
+ if (isDefined(foreignKeyData.options?.onDelete)) {
283
+ builder = builder.onDelete(foreignKeyData.options.onDelete);
284
+ }
285
+ if (isDefined(foreignKeyData.options?.onUpdate)) {
286
+ builder = builder.onUpdate(foreignKeyData.options.onUpdate);
287
+ }
288
+ return builder;
265
289
  }) ?? [];
266
290
  }),
267
291
  ...tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.unique).filter(isDefined).map((data) => {
268
- const columns = data.columns?.map((column) => getColumn(table, column));
292
+ const resolvedColumns = resolveValueOrProvider(data.columns, table);
293
+ const columns = resolvedColumns.map((column) => getColumn(table, column));
269
294
  let constraint = unique(data.options?.name ?? getUniqueName(tableName, columns, { naming: data.options?.naming })).on(...columns);
270
295
  if (data.options?.nulls == 'not distinct') {
271
296
  constraint = constraint.nullsNotDistinct();
@@ -398,7 +423,7 @@ function getPostgresColumn(tableName, columnName, dbSchema, propertySchema, refl
398
423
  if (((reflectionData.primaryKey == true) || (context.property == 'id' && reflectionData.primaryKey != false)) && (options.skipPrimaryKey != true)) {
399
424
  column = column.primaryKey();
400
425
  }
401
- for (const { target, targetColumn, excludeTenant } of reflectionData.references ?? []) {
426
+ for (const { target, targetColumn, excludeTenant, onDelete, onUpdate } of reflectionData.references ?? []) {
402
427
  column = column.references(() => {
403
428
  const targetType = target();
404
429
  if ((excludeTenant != true) && (typeExtends(context.type, TenantBaseEntity) || typeExtends(context.type, TenantEntity)) && (typeExtends(targetType, TenantBaseEntity) || typeExtends(targetType, TenantEntity))) {
@@ -406,7 +431,7 @@ function getPostgresColumn(tableName, columnName, dbSchema, propertySchema, refl
406
431
  }
407
432
  const targetTable = getDrizzleTableFromType(targetType, dbSchema.schemaName);
408
433
  return targetTable[(targetColumn ?? 'id')];
409
- });
434
+ }, { onDelete, onUpdate });
410
435
  }
411
436
  return column;
412
437
  }
@@ -462,8 +487,29 @@ export function getPgEnum(schema, enumeration, context) {
462
487
  }
463
488
  return dbEnum;
464
489
  }
465
- function getDefaultTableName(type) {
466
- return toSnakeCase(isString(type.entityName) ? type.entityName : type.name.replace(/\d+$/u, ''));
490
+ export function getPrimaryKeyColumnDefinitions(type, table, columnDefinitions, columnDefinitionsMap) {
491
+ const tableReflectionDatas = getTableReflectionDatas(type);
492
+ const mergedTableReflectionData = tableReflectionDatas.reduceRight((merged, data) => ({ ...merged, ...data }), {});
493
+ const resolvedColumnDefinitions = columnDefinitions ?? getColumnDefinitions(table);
494
+ const resolvedColumnDefinitionsMap = columnDefinitionsMap ?? getColumnDefinitionsMap(table);
495
+ const primaryKeyData = mergedTableReflectionData.primaryKey;
496
+ let definitions;
497
+ if (isDefined(primaryKeyData?.columns)) {
498
+ const resolvedColumns = resolveValueOrProvider(primaryKeyData.columns, table);
499
+ definitions = resolvedColumns.map((columnValue) => {
500
+ if (columnValue instanceof SQL) {
501
+ throw new NotSupportedError('SQL in primary key is not supported by Repository.');
502
+ }
503
+ return assertDefinedPass(resolvedColumnDefinitionsMap.get(columnValue), `Could not map primary key property ${columnValue} to column.`);
504
+ });
505
+ }
506
+ else {
507
+ definitions = resolvedColumnDefinitions.filter((columnDefinition) => (columnDefinition.reflectionData?.primaryKey == true) || (columnDefinition.objectPath.path == 'id' && columnDefinition.reflectionData?.primaryKey != false));
508
+ }
509
+ return definitions.toSorted(compareByValueSelectionToOrder(['tenantId', 'id', orderRest], (columnDefinition) => columnDefinition.objectPath.path));
510
+ }
511
+ export function getPrimaryKeyColumns(type, table, columnDefinitions, columnDefinitionsMap) {
512
+ return getPrimaryKeyColumnDefinitions(type, table, columnDefinitions, columnDefinitionsMap).map((def) => table[def.name]);
467
513
  }
468
514
  function getPrimaryKeyName(tableName, columnsOrBaseName, options) {
469
515
  return getIdentifier(tableName, columnsOrBaseName, 'pk', options);
@@ -486,23 +532,6 @@ function getForeignKeyName(tableName, foreignTableName, columnsOrBaseName, optio
486
532
  }
487
533
  return identifier;
488
534
  }
489
- function getTableName(type) {
490
- const tableReflectionDatas = getTableReflectionDatas(type);
491
- const tableReflectionData = tableReflectionDatas[0];
492
- return tableReflectionData?.name ?? getDefaultTableName(type);
493
- }
494
- function getTableReflectionDatas(type) {
495
- const metadata = reflectionRegistry.getMetadata(type);
496
- assertDefined(metadata, `Type ${type.name} does not have reflection metadata.`);
497
- const tableReflectionDatas = [];
498
- for (let currentMetadata = metadata; isNotNullOrUndefined(currentMetadata?.parent); currentMetadata = reflectionRegistry.getMetadata(currentMetadata.parent)) {
499
- const tableReflectionData = currentMetadata.data.tryGet('orm');
500
- if (isDefined(tableReflectionData)) {
501
- tableReflectionDatas.push(tableReflectionData);
502
- }
503
- }
504
- return tableReflectionDatas;
505
- }
506
535
  function getIdentifier(tableName, columnsOrBaseName, suffix, options) {
507
536
  const middle = isString(columnsOrBaseName) ? columnsOrBaseName : getColumnNames(columnsOrBaseName).join('_');
508
537
  const identifier = `${getTablePrefix(tableName, options?.naming)}_${middle}_${suffix}`;
@@ -12,7 +12,6 @@ export declare function encryptBytes(bytes: Uint8Array<ArrayBuffer>, key: Crypto
12
12
  * @param bytes The byte array to decrypt (must include version and IV).
13
13
  * @param key The CryptoKey to use for decryption.
14
14
  * @returns A promise that resolves to the original decrypted byte array.
15
- * @throws {DetailsError} If decryption fails (e.g., wrong key, corrupted data).
16
15
  * @throws {Error} If the encryption version is invalid.
17
16
  */
18
17
  export declare function decryptBytes(bytes: Uint8Array, key: CryptoKey): Promise<Uint8Array<ArrayBuffer>>;
@@ -3,7 +3,6 @@
3
3
  * Provides utility functions for encrypting and decrypting byte arrays using AES-GCM.
4
4
  * It includes versioning to handle potential future changes in the encryption format.
5
5
  */
6
- import { DetailsError } from '../../errors/index.js';
7
6
  import { decrypt, encrypt } from '../../utils/cryptography.js';
8
7
  import { getRandomBytes } from '../../utils/random.js';
9
8
  import { assert } from '../../utils/type-guards.js';
@@ -36,7 +35,6 @@ export async function encryptBytes(bytes, key) {
36
35
  * @param bytes The byte array to decrypt (must include version and IV).
37
36
  * @param key The CryptoKey to use for decryption.
38
37
  * @returns A promise that resolves to the original decrypted byte array.
39
- * @throws {DetailsError} If decryption fails (e.g., wrong key, corrupted data).
40
38
  * @throws {Error} If the encryption version is invalid.
41
39
  */
42
40
  export async function decryptBytes(bytes, key) {
@@ -49,7 +47,6 @@ export async function decryptBytes(bytes, key) {
49
47
  return new Uint8Array(decrypted);
50
48
  }
51
49
  catch (error) {
52
- // Wrap decryption errors for better context
53
- throw new DetailsError('Decrypt error', error);
50
+ throw new Error('Decryption failed.', { cause: error });
54
51
  }
55
52
  }
@@ -0,0 +1,14 @@
1
+ import { Injector } from '../../injector/index.js';
2
+ export type DatabaseExtension = {
3
+ name: string;
4
+ };
5
+ export declare const DATABASE_EXTENSION: import("../../injector/index.js").InjectionToken<DatabaseExtension, never>;
6
+ /**
7
+ * Registers a database extension in the provided injector.
8
+ * @param name - The name of the extension.
9
+ * @param options - Optional injector.
10
+ */
11
+ export declare function registerDatabaseExtension(name: string, { injector }?: {
12
+ injector?: Injector;
13
+ }): void;
14
+ export declare function runDatabaseExtensions(): Promise<void>;