@tstdl/base 0.93.140 → 0.93.141

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 (98) 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/server/module.d.ts +5 -0
  8. package/authentication/server/module.js +9 -1
  9. package/authentication/tests/authentication.api-controller.test.js +1 -1
  10. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  11. package/authentication/tests/authentication.client-service.test.js +1 -1
  12. package/circuit-breaker/postgres/module.d.ts +1 -0
  13. package/circuit-breaker/postgres/module.js +5 -1
  14. package/document-management/server/configure.js +5 -1
  15. package/document-management/server/module.d.ts +1 -1
  16. package/document-management/server/module.js +1 -1
  17. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  18. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  19. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  20. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  21. package/examples/document-management/main.d.ts +1 -0
  22. package/examples/document-management/main.js +14 -11
  23. package/key-value-store/postgres/module.d.ts +1 -0
  24. package/key-value-store/postgres/module.js +5 -1
  25. package/lock/postgres/module.d.ts +1 -0
  26. package/lock/postgres/module.js +5 -1
  27. package/mail/module.d.ts +5 -1
  28. package/mail/module.js +11 -6
  29. package/module/modules/web-server.module.js +2 -3
  30. package/notification/server/module.d.ts +1 -0
  31. package/notification/server/module.js +5 -1
  32. package/notification/tests/notification-flow.test.js +2 -2
  33. package/orm/decorators.d.ts +5 -1
  34. package/orm/decorators.js +1 -1
  35. package/orm/server/drizzle/schema-converter.js +17 -30
  36. package/orm/server/encryption.d.ts +0 -1
  37. package/orm/server/encryption.js +1 -4
  38. package/orm/server/index.d.ts +1 -6
  39. package/orm/server/index.js +1 -6
  40. package/orm/server/migration.d.ts +19 -0
  41. package/orm/server/migration.js +72 -0
  42. package/orm/server/repository.d.ts +1 -1
  43. package/orm/server/transaction.d.ts +5 -10
  44. package/orm/server/transaction.js +22 -26
  45. package/orm/server/transactional.js +3 -3
  46. package/orm/tests/database-migration.test.d.ts +1 -0
  47. package/orm/tests/database-migration.test.js +82 -0
  48. package/orm/tests/encryption.test.js +3 -4
  49. package/orm/utils.d.ts +17 -2
  50. package/orm/utils.js +49 -1
  51. package/package.json +4 -3
  52. package/rate-limit/postgres/module.d.ts +1 -0
  53. package/rate-limit/postgres/module.js +5 -1
  54. package/reflection/decorator-data.js +11 -12
  55. package/task-queue/README.md +2 -9
  56. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  57. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  58. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  59. package/task-queue/postgres/module.d.ts +1 -0
  60. package/task-queue/postgres/module.js +5 -1
  61. package/task-queue/postgres/schemas.d.ts +9 -6
  62. package/task-queue/postgres/schemas.js +4 -3
  63. package/task-queue/postgres/task-queue.d.ts +2 -12
  64. package/task-queue/postgres/task-queue.js +431 -354
  65. package/task-queue/postgres/task.model.d.ts +12 -5
  66. package/task-queue/postgres/task.model.js +51 -25
  67. package/task-queue/task-context.d.ts +2 -2
  68. package/task-queue/task-context.js +7 -7
  69. package/task-queue/task-queue.d.ts +36 -19
  70. package/task-queue/task-queue.js +18 -10
  71. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  72. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  73. package/task-queue/tests/complex.test.js +44 -228
  74. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  75. package/task-queue/tests/coverage-branch.test.js +407 -0
  76. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  77. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  78. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  79. package/task-queue/tests/dag-dependencies.test.js +41 -0
  80. package/task-queue/tests/dependencies.test.js +26 -26
  81. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  82. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  83. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  84. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  85. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  86. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  87. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  88. package/task-queue/tests/queue.test.js +33 -24
  89. package/task-queue/tests/worker.test.js +20 -5
  90. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  91. package/task-queue/tests/zombie-parent.test.js +45 -0
  92. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  93. package/task-queue/tests/zombie-recovery.test.js +51 -0
  94. package/test5.js +5 -5
  95. package/testing/integration-setup.d.ts +4 -4
  96. package/testing/integration-setup.js +54 -29
  97. package/text/localization.service.js +2 -2
  98. package/utils/file-reader.js +1 -2
@@ -59,7 +59,7 @@ describe('Worker & Base Class Tests', () => {
59
59
  // Wait until task is processed (error recorded and status is Pending)
60
60
  for (let i = 0; i < 50; i++) {
61
61
  const updated = await queue.getTask(task.id);
62
- if (updated?.tries == 1) {
62
+ if (updated?.tries == 1 && updated.status == TaskStatus.Pending) {
63
63
  break;
64
64
  }
65
65
  await timeout(20);
@@ -134,16 +134,31 @@ describe('Worker & Base Class Tests', () => {
134
134
  // Other queue spawn
135
135
  const otherQueue = injector.resolve(TaskQueueProvider).get('other-queue');
136
136
  const otherChild = await context.spawn(otherQueue, 'other', { x: 1 });
137
- expect(otherChild.parentId).toBe(task.id);
138
137
  expect(otherChild.namespace).toBe('other-queue');
139
- const otherChildren = await context.spawnMany(otherQueue, [{ type: 'other', data: { x: 2 } }]);
140
- expect(otherChildren[0]?.parentId).toBe(task.id);
138
+ await context.spawnMany(otherQueue, [{ type: 'other', data: { x: 2 } }]);
141
139
  executed = true;
142
140
  return TaskProcessResult.Complete();
143
141
  });
144
- await queue.waitForTasks([task.id], { interval: 50, timeout: 1000 });
142
+ // Complete children so parent can finalize
143
+ void (async () => {
144
+ while (!executed) {
145
+ await timeout(50);
146
+ }
147
+ // At this point parent should be WaitingChildren if children are not done
148
+ const midTask = await queue.getTask(task.id);
149
+ expect(midTask?.status).toBe(TaskStatus.WaitingChildren);
150
+ while (true) {
151
+ const dChild = await queue.dequeue({ types: ['child'] });
152
+ if (!dChild)
153
+ break;
154
+ await queue.complete(dChild);
155
+ }
156
+ })();
157
+ await queue.waitForTasks([task.id], { interval: 50, timeout: 5000 });
145
158
  token.set();
146
159
  expect(executed).toBe(true);
160
+ const finalTask = await queue.getTask(task.id);
161
+ expect(finalTask?.status).toBe(TaskStatus.Completed);
147
162
  });
148
163
  it('should correctly report isFinalAttempt in TaskContext', async () => {
149
164
  const queueProvider = injector.resolve(TaskQueueProvider);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
2
+ import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
3
+ import { setupIntegrationTest } from '../../testing/index.js';
4
+ import { timeout } from '../../utils/timing.js';
5
+ describe('Zombie Parent Deadlock', () => {
6
+ let injector;
7
+ let queue;
8
+ beforeAll(async () => {
9
+ ({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
10
+ });
11
+ beforeEach(() => {
12
+ const queueProvider = injector.resolve(TaskQueueProvider);
13
+ const queueName = `zombie-queue-${Date.now()}-${Math.random()}`;
14
+ queue = queueProvider.get(queueName, {
15
+ visibilityTimeout: 1000,
16
+ });
17
+ });
18
+ afterEach(async () => {
19
+ await queue.clear();
20
+ });
21
+ afterAll(async () => {
22
+ await injector?.dispose();
23
+ });
24
+ it('should resolve parent even if child fails (failFast: false)', async () => {
25
+ const parent = await queue.enqueue('parent', {});
26
+ const dParent = await queue.dequeue();
27
+ // Spawn a child that will fail. Parent has failFast: false by default.
28
+ const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, failFast: false }], { returnTasks: true });
29
+ await queue.complete(dParent);
30
+ // Parent should be WaitingChildren
31
+ const uParent = await queue.getTask(parent.id);
32
+ expect(uParent?.status).toBe(TaskStatus.WaitingChildren);
33
+ // Fail the child fatally
34
+ const dChild = await queue.dequeue();
35
+ await queue.fail(dChild, new Error('child failed'), { fatal: true });
36
+ // Verify child is Dead
37
+ const uChild = await queue.getTask(child.id);
38
+ expect(uChild?.status).toBe(TaskStatus.Dead);
39
+ // Wait a bit for dependency resolution
40
+ await timeout(200);
41
+ // Parent should NOT be stuck. It should transition to Completed because the child is terminal.
42
+ const fParent = await queue.getTask(parent.id);
43
+ expect(fParent?.status).toBe(TaskStatus.Completed);
44
+ });
45
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
2
+ import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
3
+ import { setupIntegrationTest } from '../../testing/index.js';
4
+ import { timeout } from '../../utils/timing.js';
5
+ describe('Zombie Recovery Race Condition', () => {
6
+ let injector;
7
+ let queue;
8
+ beforeAll(async () => {
9
+ ({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
10
+ });
11
+ beforeEach(() => {
12
+ const queueProvider = injector.resolve(TaskQueueProvider);
13
+ const queueName = `zombie-recovery-queue-${Date.now()}-${Math.random()}`;
14
+ queue = queueProvider.get(queueName, {
15
+ visibilityTimeout: 100, // Very short visibility timeout
16
+ retryDelayMinimum: 0,
17
+ });
18
+ });
19
+ afterEach(async () => {
20
+ await queue.clear();
21
+ });
22
+ afterAll(async () => {
23
+ await injector?.dispose();
24
+ });
25
+ it('should NOT prematurely complete a recovered parent when children finish', async () => {
26
+ // 1. Enqueue parent
27
+ const parent = await queue.enqueue('parent', {});
28
+ // 2. Dequeue parent (it is now Running, startTimestamp is set)
29
+ const dParent = await queue.dequeue();
30
+ expect(dParent?.id).toBe(parent.id);
31
+ expect(dParent?.startTimestamp).not.toBeNull();
32
+ // 3. Parent spawns a child that it waits for
33
+ const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id }], { returnTasks: true });
34
+ // 4. Simulate crash: Wait for visibility timeout so it becomes a zombie
35
+ await timeout(200);
36
+ // 5. Run maintenance to recover the zombie parent
37
+ await queue.maintenance();
38
+ // Verify it was recovered to Pending
39
+ const recoveredParent = await queue.getTask(parent.id);
40
+ expect(recoveredParent?.status).toBe(TaskStatus.Pending);
41
+ // If the bug exists, startTimestamp is still set here.
42
+ // 6. Complete the child while parent is still Pending
43
+ const dChild = await queue.dequeue({ types: ['child'] });
44
+ await queue.complete(dChild);
45
+ // 7. Verify parent status
46
+ // BUG: evaluateTaskStatus will see unresolvedCompleteDependencies=0 and startTimestamp != null,
47
+ // and incorrectly transition the Pending parent to Completed.
48
+ const finalParent = await queue.getTask(parent.id);
49
+ expect(finalParent?.status).toBe(TaskStatus.Pending); // It should still be pending execution!
50
+ });
51
+ });
package/test5.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import './polyfills.js';
2
- import { promptBuilder } from './ai/index.js';
3
2
  import { Application } from './application/application.js';
4
3
  import { provideModule, provideSignalHandler } from './application/index.js';
5
4
  import { PrettyPrintLogFormatter } from './logger/index.js';
6
5
  import { provideConsoleLogTransport } from './logger/transports/console.js';
7
6
  async function main(_cancellationSignal) {
8
- const x = promptBuilder();
9
- console.log(x.buildSystemPrompt()[0].text);
10
- console.log('\n\n---\n\n');
11
- console.log(x.buildUserPrompt()[0].text);
7
+ const arr = [1, 2, 3, 4, 5];
8
+ for (const item of arr) {
9
+ console.log(item);
10
+ arr.push(arr.at(-1) + 1);
11
+ }
12
12
  }
13
13
  Application.run('Test', [
14
14
  provideConsoleLogTransport(PrettyPrintLogFormatter),
@@ -3,6 +3,7 @@ import { type AuthenticationAncillaryService } from '../authentication/server/in
3
3
  import { Injector } from '../injector/index.js';
4
4
  import { LogLevel } from '../logger/index.js';
5
5
  import { type S3ObjectStorageProviderConfig } from '../object-storage/s3/index.js';
6
+ import type { EntityType } from '../orm/entity.js';
6
7
  import { Database } from '../orm/server/index.js';
7
8
  import type { Type } from '../types/index.js';
8
9
  export type IntegrationTestOptions = {
@@ -30,7 +31,6 @@ export type IntegrationTestOptions = {
30
31
  rateLimiter?: boolean;
31
32
  signals?: boolean;
32
33
  taskQueue?: boolean;
33
- test?: boolean;
34
34
  webServer?: boolean;
35
35
  };
36
36
  authenticationAncillaryService?: Type<AuthenticationAncillaryService>;
@@ -47,14 +47,14 @@ export declare function setupIntegrationTest(options?: IntegrationTestOptions):
47
47
  * Helper to truncate specific tables in a schema.
48
48
  * Useful in beforeEach() to reset state.
49
49
  */
50
- export declare function truncateTables(database: Database, schema: string, tables: string[]): Promise<void>;
50
+ export declare function truncateTables(database: Database, schema: string, tables: (string | EntityType)[]): Promise<void>;
51
51
  /**
52
52
  * Helper to delete data for a specific tenant from specific tables.
53
53
  * Useful for parallel tests to avoid interference.
54
54
  */
55
- export declare function clearTenantData(database: Database, schema: string, tables: string[], tenantId: string): Promise<void>;
55
+ export declare function clearTenantData(database: Database, schema: string | undefined, tables: (string | EntityType)[], tenantId: string): Promise<void>;
56
56
  /**
57
57
  * Helper to drop specific tables.
58
58
  * Useful in beforeAll() cleanups.
59
59
  */
60
- export declare function dropTables(database: Database, schema: string, tables: string[]): Promise<void>;
60
+ export declare function dropTables(database: Database, schema: string | undefined, tables: (string | EntityType)[]): Promise<void>;
@@ -1,30 +1,31 @@
1
1
  import { sql } from 'drizzle-orm';
2
2
  import { configureApiServer } from '../api/server/index.js';
3
- import { configureAudit, migrateAuditSchema } from '../audit/index.js';
3
+ import { configureAudit } from '../audit/index.js';
4
4
  import { AuthenticationApiClient } from '../authentication/client/api.client.js';
5
5
  import { configureAuthenticationClient } from '../authentication/client/index.js';
6
- import { AuthenticationApiController, configureAuthenticationServer, migrateAuthenticationSchema } from '../authentication/server/index.js';
7
- import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../circuit-breaker/postgres/module.js';
8
- import { configureDocumentManagement, migrateDocumentManagementSchema } from '../document-management/server/index.js';
6
+ import { AuthenticationApiController, configureAuthenticationServer } from '../authentication/server/index.js';
7
+ import { configurePostgresCircuitBreaker } from '../circuit-breaker/postgres/module.js';
8
+ import { configureDocumentManagement } from '../document-management/server/index.js';
9
9
  import { configureUndiciHttpClientAdapter } from '../http/client/adapters/undici.adapter.js';
10
10
  import { configureHttpClient } from '../http/client/index.js';
11
11
  import { HttpServer } from '../http/server/index.js';
12
12
  import { configureNodeHttpServer } from '../http/server/node/module.js';
13
13
  import { Injector, runInInjectionContext } from '../injector/index.js';
14
- import { configurePostgresKeyValueStore, migratePostgresKeyValueStoreSchema } from '../key-value-store/postgres/module.js';
15
- import { configurePostgresLock, migratePostgresLockSchema } from '../lock/postgres/module.js';
14
+ import { configurePostgresKeyValueStore } from '../key-value-store/postgres/module.js';
15
+ import { configurePostgresLock } from '../lock/postgres/module.js';
16
16
  import { ConsoleLogTransport, DEFAULT_LOG_LEVEL, LogFormatter, LogLevel, LogManager, LogTransport, PrettyPrintLogFormatter } from '../logger/index.js';
17
17
  import { configureLocalMessageBus } from '../message-bus/index.js';
18
18
  import { configureWebServerModule, WebServerModule } from '../module/modules/web-server.module.js';
19
- import { configureNotification, migrateNotificationSchema } from '../notification/server/index.js';
19
+ import { configureNotification } from '../notification/server/index.js';
20
20
  import { configureS3ObjectStorage } from '../object-storage/s3/index.js';
21
- import { configureOrm, Database } from '../orm/server/index.js';
22
- import { configurePostgresRateLimiter, migratePostgresRateLimiterSchema } from '../rate-limit/postgres/module.js';
21
+ import { configureOrm, Database, runDatabaseMigrations } from '../orm/server/index.js';
22
+ import { getEntitySchema, getEntityTableName } from '../orm/utils.js';
23
+ import { configurePostgresRateLimiter } from '../rate-limit/postgres/module.js';
23
24
  import { configureDefaultSignalsImplementation } from '../signals/implementation/configure.js';
24
- import { configurePostgresTaskQueue, migratePostgresTaskQueueSchema } from '../task-queue/postgres/index.js';
25
+ import { configurePostgresTaskQueue } from '../task-queue/postgres/index.js';
25
26
  import * as configParser from '../utils/config-parser.js';
26
27
  import { objectEntries } from '../utils/object/object.js';
27
- import { isDefined, isNotNull } from '../utils/type-guards.js';
28
+ import { assertDefinedPass, isDefined, isNotNull, isString } from '../utils/type-guards.js';
28
29
  /**
29
30
  * Standard setup for integration tests.
30
31
  */
@@ -51,6 +52,8 @@ export async function setupIntegrationTest(options = {}) {
51
52
  ...options.dbConfig,
52
53
  };
53
54
  // 4. Configure ORM
55
+ // We disable autoMigrate here because APPLICATION_INITIALIZER is not used in integration tests
56
+ // We manually run migrations via runDatabaseMigrations below
54
57
  configureOrm({
55
58
  repositoryConfig: { schema: options.orm?.schema ?? 'test' },
56
59
  connection: dbConfig,
@@ -66,35 +69,29 @@ export async function setupIntegrationTest(options = {}) {
66
69
  await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(options.orm.schema)}`);
67
70
  }
68
71
  // 7. Optional Modules
69
- if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.test ?? options.modules?.notification) {
72
+ if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.notification) {
70
73
  configureLocalMessageBus({ injector });
71
74
  }
72
75
  if (options.modules?.taskQueue) {
73
76
  configurePostgresTaskQueue({ injector });
74
- await runInInjectionContext(injector, migratePostgresTaskQueueSchema);
75
77
  }
76
78
  if (options.modules?.circuitBreaker ?? options.modules?.taskQueue) {
77
79
  configurePostgresCircuitBreaker({ injector });
78
- await runInInjectionContext(injector, migratePostgresCircuitBreaker);
79
80
  }
80
81
  if (options.modules?.rateLimiter ?? options.modules?.taskQueue) {
81
82
  configurePostgresRateLimiter({ injector });
82
- await runInInjectionContext(injector, migratePostgresRateLimiterSchema);
83
83
  }
84
84
  if (options.modules?.keyValueStore ?? options.modules?.authentication) {
85
85
  configurePostgresKeyValueStore({ injector });
86
- await runInInjectionContext(injector, migratePostgresKeyValueStoreSchema);
87
86
  }
88
87
  if (options.modules?.lock ?? options.modules?.authentication) {
89
88
  configurePostgresLock({ injector });
90
- await runInInjectionContext(injector, migratePostgresLockSchema);
91
89
  }
92
- if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.test ?? options.modules?.notification) {
90
+ if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.notification) {
93
91
  configureDefaultSignalsImplementation();
94
92
  }
95
93
  if (options.modules?.audit ?? options.modules?.authentication) {
96
94
  configureAudit({ injector });
97
- await runInInjectionContext(injector, migrateAuditSchema);
98
95
  }
99
96
  if (options.modules?.authentication) {
100
97
  configureAuthenticationServer({
@@ -106,11 +103,9 @@ export async function setupIntegrationTest(options = {}) {
106
103
  authenticationAncillaryService: options.authenticationAncillaryService,
107
104
  injector,
108
105
  });
109
- await runInInjectionContext(injector, migrateAuthenticationSchema);
110
106
  }
111
107
  if (options.modules?.notification) {
112
108
  configureNotification({ injector });
113
- await runInInjectionContext(injector, migrateNotificationSchema);
114
109
  }
115
110
  if (options.modules?.documentManagement) {
116
111
  configureDocumentManagement({
@@ -122,8 +117,8 @@ export async function setupIntegrationTest(options = {}) {
122
117
  skipAi: true,
123
118
  injector,
124
119
  });
125
- await runInInjectionContext(injector, migrateDocumentManagementSchema);
126
120
  }
121
+ await runInInjectionContext(injector, runDatabaseMigrations);
127
122
  if (options.modules?.objectStorage) {
128
123
  const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
129
124
  configureS3ObjectStorage({
@@ -190,9 +185,18 @@ export async function truncateTables(database, schema, tables) {
190
185
  if (tables.length == 0) {
191
186
  return;
192
187
  }
193
- // Using CASCADE to handle foreign keys automatically
194
- for (const table of tables) {
195
- await database.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier(table)} CASCADE`);
188
+ const lockId = 987654321; // Different lock ID for table maintenance
189
+ await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
190
+ try {
191
+ // Using CASCADE to handle foreign keys automatically
192
+ for (const table of tables) {
193
+ const tableName = isString(table) ? table : getEntityTableName(table);
194
+ const tableSchema = isString(table) ? schema : getEntitySchema(table, schema);
195
+ await database.execute(sql `TRUNCATE TABLE ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
196
+ }
197
+ }
198
+ finally {
199
+ await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
196
200
  }
197
201
  }
198
202
  /**
@@ -203,8 +207,17 @@ export async function clearTenantData(database, schema, tables, tenantId) {
203
207
  if (tables.length == 0) {
204
208
  return;
205
209
  }
206
- for (const table of tables) {
207
- await database.execute(sql `DELETE FROM ${sql.identifier(schema)}.${sql.identifier(table)} WHERE tenant_id = ${tenantId}`);
210
+ const lockId = 987654321;
211
+ await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
212
+ try {
213
+ for (const table of tables) {
214
+ const tableName = isString(table) ? table : getEntityTableName(table);
215
+ const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
216
+ await database.execute(sql `DELETE FROM ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} WHERE tenant_id = ${tenantId}`);
217
+ }
218
+ }
219
+ finally {
220
+ await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
208
221
  }
209
222
  }
210
223
  /**
@@ -212,7 +225,19 @@ export async function clearTenantData(database, schema, tables, tenantId) {
212
225
  * Useful in beforeAll() cleanups.
213
226
  */
214
227
  export async function dropTables(database, schema, tables) {
215
- for (const table of tables) {
216
- await database.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier(table)} CASCADE`);
228
+ if (tables.length == 0) {
229
+ return;
230
+ }
231
+ const lockId = 987654321;
232
+ await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
233
+ try {
234
+ for (const table of tables) {
235
+ const tableName = isString(table) ? table : getEntityTableName(table);
236
+ const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
237
+ await database.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
238
+ }
239
+ }
240
+ finally {
241
+ await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
217
242
  }
218
243
  }
@@ -192,7 +192,7 @@ function buildMappedLocalization({ language, keys, enums }) {
192
192
  const mappedLocalization = {
193
193
  language,
194
194
  keys: new Map(deepObjectEntries(keys)),
195
- enums: new Map(enumsEntries)
195
+ enums: new Map(enumsEntries),
196
196
  };
197
197
  return mappedLocalization;
198
198
  }
@@ -215,7 +215,7 @@ function mergeMappedLocalization(a, b, force = false) {
215
215
  return {
216
216
  language: b.language,
217
217
  keys: new Map([...a.keys, ...b.keys]),
218
- enums: new Map([...a.enums, ...b.enums])
218
+ enums: new Map([...a.enums, ...b.enums]),
219
219
  };
220
220
  }
221
221
  export function isLocalizeItem(value) {
@@ -1,4 +1,3 @@
1
- import { DetailsError } from '../errors/details.error.js';
2
1
  export async function readAsText(blob, encoding) {
3
2
  return await setup((reader) => reader.readAsText(blob, encoding));
4
3
  }
@@ -12,7 +11,7 @@ async function setup(dispatcher) {
12
11
  return await new Promise((resolve, reject) => {
13
12
  const reader = new FileReader();
14
13
  reader.onload = () => resolve(reader.result);
15
- reader.onerror = () => reject(new DetailsError(reader.error.message, reader.error));
14
+ reader.onerror = () => reject(new Error('FileReader error', { cause: reader.error }));
16
15
  dispatcher(reader);
17
16
  });
18
17
  }