@tstdl/base 0.93.175 → 0.93.177
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.
- package/api/server/middlewares/response-time.middleware.js +2 -5
- package/audit/auditor.js +1 -1
- package/authentication/server/authentication.service.js +1 -1
- package/circuit-breaker/postgres/circuit-breaker.js +1 -1
- package/document-management/server/services/singleton.js +1 -1
- package/errors/format.d.ts +6 -1
- package/errors/format.js +1 -1
- package/http/index.d.ts +1 -0
- package/http/index.js +1 -0
- package/http/server/http-server-response.d.ts +2 -0
- package/http/server/http-server-response.js +2 -0
- package/http/server/node/node-http-server.js +3 -0
- package/http/server-timing.d.ts +31 -0
- package/http/server-timing.js +47 -0
- package/http/tests/server-timing.test.d.ts +1 -0
- package/http/tests/server-timing.test.js +42 -0
- package/key-value-store/postgres/key-value-store.service.js +1 -1
- package/lock/postgres/provider.js +1 -1
- package/mail/README.md +1 -1
- package/mail/drizzle/0001_married_tarantula.sql +12 -0
- package/mail/drizzle/meta/0001_snapshot.json +69 -0
- package/mail/drizzle/meta/_journal.json +7 -0
- package/mail/index.d.ts +1 -0
- package/mail/index.js +1 -0
- package/mail/mail.service.d.ts +21 -4
- package/mail/mail.service.js +44 -13
- package/mail/models/mail-log.model.d.ts +2 -1
- package/mail/models/mail-log.model.js +1 -1
- package/mail/task-definitions.d.ts +20 -0
- package/mail/task-definitions.js +1 -0
- package/package.json +3 -3
- package/rate-limit/postgres/postgres-rate-limiter.js +1 -1
- package/task-queue/postgres/drizzle/0001_rapid_infant_terrible.sql +16 -0
- package/task-queue/postgres/drizzle/meta/0001_snapshot.json +753 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +7 -0
- package/task-queue/postgres/task-queue.js +13 -13
- package/task-queue/postgres/task.model.d.ts +2 -1
- package/task-queue/postgres/task.model.js +3 -3
- package/task-queue/task-queue.d.ts +4 -3
- package/task-queue/task-queue.js +12 -2
- package/task-queue/tests/coverage-branch.test.js +1 -1
- package/task-queue/tests/dependencies.test.js +2 -2
- package/task-queue/tests/queue.test.js +2 -2
- package/task-queue/tests/worker.test.js +39 -5
- package/tsconfig.json +0 -2
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { round } from '../../../utils/math.js';
|
|
2
|
-
import { Timer } from '../../../utils/timer.js';
|
|
3
1
|
export async function responseTimeMiddleware({ response }, next) {
|
|
4
|
-
const
|
|
2
|
+
const stop = response.serverTiming.start('total');
|
|
5
3
|
await next();
|
|
6
|
-
|
|
7
|
-
response.headers.set('X-Response-Time', `${time}ms`);
|
|
4
|
+
stop();
|
|
8
5
|
}
|
package/audit/auditor.js
CHANGED
|
@@ -235,7 +235,7 @@ __decorate([
|
|
|
235
235
|
Auditor = Auditor_1 = __decorate([
|
|
236
236
|
Injectable({
|
|
237
237
|
providers: [
|
|
238
|
-
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(AuditModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
238
|
+
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(AuditModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) },
|
|
239
239
|
],
|
|
240
240
|
})
|
|
241
241
|
], Auditor);
|
|
@@ -787,7 +787,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
787
787
|
AuthenticationService = AuthenticationService_1 = __decorate([
|
|
788
788
|
Singleton({
|
|
789
789
|
providers: [
|
|
790
|
-
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(AuthenticationModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
790
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(AuthenticationModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
791
791
|
],
|
|
792
792
|
})
|
|
793
793
|
], AuthenticationService);
|
|
@@ -93,7 +93,7 @@ PostgresCircuitBreakerService = __decorate([
|
|
|
93
93
|
Singleton({
|
|
94
94
|
argumentIdentityProvider: (arg) => isString(arg) ? arg : arg.key,
|
|
95
95
|
providers: [
|
|
96
|
-
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresCircuitBreakerModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
96
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresCircuitBreakerModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
97
97
|
],
|
|
98
98
|
})
|
|
99
99
|
], PostgresCircuitBreakerService);
|
|
@@ -3,7 +3,7 @@ import { provide } from '../../../injector/injector.js';
|
|
|
3
3
|
import { factoryProvider } from '../../../injector/provider.js';
|
|
4
4
|
import { DatabaseConfig } from '../../../orm/server/index.js';
|
|
5
5
|
import { DocumentManagementConfiguration } from '../module.js';
|
|
6
|
-
export const documentManagementDatabaseConfigFactoryProvider = factoryProvider((_, context) => context.resolve(DocumentManagementConfiguration).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
6
|
+
export const documentManagementDatabaseConfigFactoryProvider = factoryProvider((_, context) => context.resolve(DocumentManagementConfiguration).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }));
|
|
7
7
|
export const documentManagementDatabaseConfigProvider = provide(DatabaseConfig, documentManagementDatabaseConfigFactoryProvider);
|
|
8
8
|
export function DocumentManagementSingleton() {
|
|
9
9
|
return Singleton({ providers: [documentManagementDatabaseConfigProvider] });
|
package/errors/format.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Record, UndefinableJson } from '../types/index.js';
|
|
1
|
+
import type { ObjectLiteral, Record, UndefinableJson } from '../types/index.js';
|
|
2
2
|
export type FormatErrorOptions = {
|
|
3
3
|
/** Include error name in message */
|
|
4
4
|
includeName?: boolean;
|
|
@@ -20,6 +20,11 @@ export type SerializedError = {
|
|
|
20
20
|
cause?: SerializedError;
|
|
21
21
|
aggregateErrors?: SerializedError[];
|
|
22
22
|
};
|
|
23
|
+
export type NonRecursiveSerializedError = Omit<SerializedError, 'extraInfo' | 'cause' | 'aggregateErrors'> & {
|
|
24
|
+
extraInfo?: ObjectLiteral | null | undefined;
|
|
25
|
+
cause?: ObjectLiteral;
|
|
26
|
+
aggregateErrors?: ObjectLiteral[];
|
|
27
|
+
};
|
|
23
28
|
export interface ErrorExtraInfo {
|
|
24
29
|
/** Format extra data (without message and stack) as JSON */
|
|
25
30
|
getExtraInfo(): UndefinableJson | undefined;
|
package/errors/format.js
CHANGED
|
@@ -29,7 +29,7 @@ export function serializeError(error, options = {}) {
|
|
|
29
29
|
aggregateErrors = actualError.errors;
|
|
30
30
|
}
|
|
31
31
|
// Extract Extra Info
|
|
32
|
-
if (includeExtraInfo && isFunction(actualError
|
|
32
|
+
if (includeExtraInfo && isFunction(actualError?.getExtraInfo)) {
|
|
33
33
|
extraInfo = actualError.getExtraInfo();
|
|
34
34
|
}
|
|
35
35
|
// Isolate 'rest' properties via destructuring
|
package/http/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './http-query.js';
|
|
|
12
12
|
export * from './http-url-parameters.js';
|
|
13
13
|
export * from './http-value-map.js';
|
|
14
14
|
export * from './http.error.js';
|
|
15
|
+
export * from './server-timing.js';
|
|
15
16
|
export * from './server/index.js';
|
|
16
17
|
export * from './tokens.js';
|
|
17
18
|
export * from './types.js';
|
package/http/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './http-query.js';
|
|
|
12
12
|
export * from './http-url-parameters.js';
|
|
13
13
|
export * from './http-value-map.js';
|
|
14
14
|
export * from './http.error.js';
|
|
15
|
+
export * from './server-timing.js';
|
|
15
16
|
export * from './server/index.js';
|
|
16
17
|
export * from './tokens.js';
|
|
17
18
|
export * from './types.js';
|
|
@@ -3,6 +3,7 @@ import type { ServerSentEventsSource } from '../../sse/server-sent-events-source
|
|
|
3
3
|
import type { Record } from '../../types/index.js';
|
|
4
4
|
import type { HttpHeadersInput } from '../http-headers.js';
|
|
5
5
|
import { HttpHeaders } from '../http-headers.js';
|
|
6
|
+
import { ServerTiming } from '../server-timing.js';
|
|
6
7
|
export type SetCookieObject = SetCookieOptions & {
|
|
7
8
|
value: string;
|
|
8
9
|
};
|
|
@@ -24,6 +25,7 @@ export type HttpServerResponseOptions = {
|
|
|
24
25
|
export declare class HttpServerResponse {
|
|
25
26
|
#private;
|
|
26
27
|
readonly headers: HttpHeaders;
|
|
28
|
+
readonly serverTiming: ServerTiming;
|
|
27
29
|
statusCode: number | undefined;
|
|
28
30
|
statusMessage: string | undefined;
|
|
29
31
|
get body(): HttpServerResponseBody | undefined;
|
|
@@ -2,10 +2,12 @@ import { formatSetCookie } from '../../cookie/cookie.js';
|
|
|
2
2
|
import { objectEntries } from '../../utils/object/object.js';
|
|
3
3
|
import { isDefined, isUndefined } from '../../utils/type-guards.js';
|
|
4
4
|
import { HttpHeaders } from '../http-headers.js';
|
|
5
|
+
import { ServerTiming } from '../server-timing.js';
|
|
5
6
|
export class HttpServerResponse {
|
|
6
7
|
#body;
|
|
7
8
|
#bodyType;
|
|
8
9
|
headers = new HttpHeaders();
|
|
10
|
+
serverTiming = new ServerTiming();
|
|
9
11
|
statusCode;
|
|
10
12
|
statusMessage;
|
|
11
13
|
get body() {
|
|
@@ -141,6 +141,9 @@ function getResponder(httpResponse) {
|
|
|
141
141
|
return respond;
|
|
142
142
|
}
|
|
143
143
|
function writeHeaders(response, httpResponse) {
|
|
144
|
+
if (response.serverTiming.hasMetrics) {
|
|
145
|
+
response.headers.set('Server-Timing', response.serverTiming.toString());
|
|
146
|
+
}
|
|
144
147
|
for (const [name, value] of response.headers.normalizedEntries()) {
|
|
145
148
|
httpResponse.setHeader(name, value);
|
|
146
149
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ServerTimingMetric = {
|
|
2
|
+
name: string;
|
|
3
|
+
duration?: number;
|
|
4
|
+
description?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class ServerTiming {
|
|
7
|
+
#private;
|
|
8
|
+
/**
|
|
9
|
+
* Whether there are any metrics.
|
|
10
|
+
*/
|
|
11
|
+
get hasMetrics(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Add a metric to the server timing.
|
|
14
|
+
* @param name The name of the metric.
|
|
15
|
+
* @param duration The duration of the metric in milliseconds.
|
|
16
|
+
* @param description An optional description for the metric.
|
|
17
|
+
*/
|
|
18
|
+
add(name: string, duration?: number, description?: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Start a timer for a metric.
|
|
21
|
+
* @param name The name of the metric.
|
|
22
|
+
* @param description An optional description for the metric.
|
|
23
|
+
* @returns A function to stop the timer and add the metric.
|
|
24
|
+
*/
|
|
25
|
+
start(name: string, description?: string): () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Format the metrics as a `Server-Timing` header value.
|
|
28
|
+
* @returns Comma-separated list of metrics.
|
|
29
|
+
*/
|
|
30
|
+
toString(): string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { round } from '../utils/math.js';
|
|
2
|
+
import { Timer } from '../utils/timer.js';
|
|
3
|
+
import { isDefined } from '../utils/type-guards.js';
|
|
4
|
+
export class ServerTiming {
|
|
5
|
+
#metrics = [];
|
|
6
|
+
/**
|
|
7
|
+
* Whether there are any metrics.
|
|
8
|
+
*/
|
|
9
|
+
get hasMetrics() {
|
|
10
|
+
return this.#metrics.length > 0;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Add a metric to the server timing.
|
|
14
|
+
* @param name The name of the metric.
|
|
15
|
+
* @param duration The duration of the metric in milliseconds.
|
|
16
|
+
* @param description An optional description for the metric.
|
|
17
|
+
*/
|
|
18
|
+
add(name, duration, description) {
|
|
19
|
+
this.#metrics.push({ name, duration, description });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Start a timer for a metric.
|
|
23
|
+
* @param name The name of the metric.
|
|
24
|
+
* @param description An optional description for the metric.
|
|
25
|
+
* @returns A function to stop the timer and add the metric.
|
|
26
|
+
*/
|
|
27
|
+
start(name, description) {
|
|
28
|
+
const timer = Timer.startNew();
|
|
29
|
+
return () => this.add(name, round(timer.milliseconds, 2), description);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format the metrics as a `Server-Timing` header value.
|
|
33
|
+
* @returns Comma-separated list of metrics.
|
|
34
|
+
*/
|
|
35
|
+
toString() {
|
|
36
|
+
return this.#metrics.map((metric) => {
|
|
37
|
+
let result = metric.name;
|
|
38
|
+
if (isDefined(metric.duration)) {
|
|
39
|
+
result += `;dur=${metric.duration}`;
|
|
40
|
+
}
|
|
41
|
+
if (isDefined(metric.description)) {
|
|
42
|
+
result += `;desc="${metric.description}"`;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}).join(', ');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { ServerTiming } from '../server-timing.js';
|
|
3
|
+
describe('ServerTiming', () => {
|
|
4
|
+
test('should be empty by default', () => {
|
|
5
|
+
const timing = new ServerTiming();
|
|
6
|
+
expect(timing.hasMetrics).toBe(false);
|
|
7
|
+
expect(timing.toString()).toBe('');
|
|
8
|
+
});
|
|
9
|
+
test('should add a simple metric', () => {
|
|
10
|
+
const timing = new ServerTiming();
|
|
11
|
+
timing.add('db');
|
|
12
|
+
expect(timing.hasMetrics).toBe(true);
|
|
13
|
+
expect(timing.toString()).toBe('db');
|
|
14
|
+
});
|
|
15
|
+
test('should add a metric with duration', () => {
|
|
16
|
+
const timing = new ServerTiming();
|
|
17
|
+
timing.add('db', 53.2);
|
|
18
|
+
expect(timing.toString()).toBe('db;dur=53.2');
|
|
19
|
+
});
|
|
20
|
+
test('should add a metric with duration and description', () => {
|
|
21
|
+
const timing = new ServerTiming();
|
|
22
|
+
timing.add('db', 53.2, 'Database Query');
|
|
23
|
+
expect(timing.toString()).toBe('db;dur=53.2;desc="Database Query"');
|
|
24
|
+
});
|
|
25
|
+
test('should add multiple metrics', () => {
|
|
26
|
+
const timing = new ServerTiming();
|
|
27
|
+
timing.add('cache', undefined, 'Cache Miss');
|
|
28
|
+
timing.add('db', 53.2);
|
|
29
|
+
timing.add('total', 123.45);
|
|
30
|
+
expect(timing.toString()).toBe('cache;desc="Cache Miss", db;dur=53.2, total;dur=123.45');
|
|
31
|
+
});
|
|
32
|
+
test('should use start/stop for metrics', async () => {
|
|
33
|
+
const timing = new ServerTiming();
|
|
34
|
+
const stop = timing.start('total');
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 11));
|
|
36
|
+
stop();
|
|
37
|
+
const result = timing.toString();
|
|
38
|
+
expect(result).toMatch(/total;dur=\d+\.\d+/);
|
|
39
|
+
const duration = Number.parseFloat(result.split('=')[1]);
|
|
40
|
+
expect(duration).toBeGreaterThanOrEqual(10);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -56,7 +56,7 @@ let PostgresKeyValueStore = class PostgresKeyValueStore extends Transactional {
|
|
|
56
56
|
PostgresKeyValueStore = __decorate([
|
|
57
57
|
Singleton({
|
|
58
58
|
providers: [
|
|
59
|
-
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(PostgresKeyValueStoreModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
59
|
+
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(PostgresKeyValueStoreModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) },
|
|
60
60
|
],
|
|
61
61
|
})
|
|
62
62
|
], PostgresKeyValueStore);
|
|
@@ -20,7 +20,7 @@ let PostgresLockProvider = class PostgresLockProvider extends LockProvider {
|
|
|
20
20
|
PostgresLockProvider = __decorate([
|
|
21
21
|
Singleton({
|
|
22
22
|
providers: [
|
|
23
|
-
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(PostgresLockModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
23
|
+
{ provide: DatabaseConfig, useFactory: (_, context) => context.resolve(PostgresLockModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) },
|
|
24
24
|
],
|
|
25
25
|
})
|
|
26
26
|
], PostgresLockProvider);
|
package/mail/README.md
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION mail.map_errors_to_jsonb(text_array text[])
|
|
2
|
+
RETURNS jsonb AS $$
|
|
3
|
+
SELECT COALESCE(jsonb_agg(jsonb_build_object('message', err)), '[]'::jsonb)
|
|
4
|
+
FROM unnest(text_array) AS err;
|
|
5
|
+
$$ LANGUAGE sql IMMUTABLE;
|
|
6
|
+
|
|
7
|
+
ALTER TABLE "mail"."log"
|
|
8
|
+
ALTER COLUMN "errors" SET DATA TYPE jsonb
|
|
9
|
+
USING mail.map_errors_to_jsonb("errors");
|
|
10
|
+
|
|
11
|
+
ALTER TABLE "mail"."log" ALTER COLUMN "errors" SET NOT NULL;
|
|
12
|
+
DROP FUNCTION mail.map_errors_to_jsonb(text[]);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "c74f667f-8acf-42e7-ad58-3aea45df3743",
|
|
3
|
+
"prevId": "0c48afa4-9ab0-4965-a93e-05a6c1b88e58",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"mail.log": {
|
|
8
|
+
"name": "log",
|
|
9
|
+
"schema": "mail",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "uuid",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true,
|
|
16
|
+
"default": "gen_random_uuid()"
|
|
17
|
+
},
|
|
18
|
+
"timestamp": {
|
|
19
|
+
"name": "timestamp",
|
|
20
|
+
"type": "timestamp with time zone",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
24
|
+
"template": {
|
|
25
|
+
"name": "template",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": false
|
|
29
|
+
},
|
|
30
|
+
"data": {
|
|
31
|
+
"name": "data",
|
|
32
|
+
"type": "jsonb",
|
|
33
|
+
"primaryKey": false,
|
|
34
|
+
"notNull": true
|
|
35
|
+
},
|
|
36
|
+
"send_result": {
|
|
37
|
+
"name": "send_result",
|
|
38
|
+
"type": "jsonb",
|
|
39
|
+
"primaryKey": false,
|
|
40
|
+
"notNull": false
|
|
41
|
+
},
|
|
42
|
+
"errors": {
|
|
43
|
+
"name": "errors",
|
|
44
|
+
"type": "jsonb",
|
|
45
|
+
"primaryKey": false,
|
|
46
|
+
"notNull": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"indexes": {},
|
|
50
|
+
"foreignKeys": {},
|
|
51
|
+
"compositePrimaryKeys": {},
|
|
52
|
+
"uniqueConstraints": {},
|
|
53
|
+
"policies": {},
|
|
54
|
+
"checkConstraints": {},
|
|
55
|
+
"isRLSEnabled": false
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"enums": {},
|
|
59
|
+
"schemas": {},
|
|
60
|
+
"sequences": {},
|
|
61
|
+
"roles": {},
|
|
62
|
+
"policies": {},
|
|
63
|
+
"views": {},
|
|
64
|
+
"_meta": {
|
|
65
|
+
"columns": {},
|
|
66
|
+
"schemas": {},
|
|
67
|
+
"tables": {}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/mail/index.d.ts
CHANGED
package/mail/index.js
CHANGED
package/mail/mail.service.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
import type { AfterResolveContext } from '../injector/index.js';
|
|
2
|
+
import { afterResolve } from '../injector/index.js';
|
|
3
|
+
import type { Transaction } from '../orm/server/transaction.js';
|
|
1
4
|
import type { TypedOmit } from '../types/index.js';
|
|
2
5
|
import { MailClientConfig } from './mail.client.js';
|
|
3
6
|
import { type MailData, type MailSendResult, type MailTemplate } from './models/index.js';
|
|
7
|
+
export type MailSendOptions = {
|
|
8
|
+
clientConfig?: MailClientConfig;
|
|
9
|
+
/** @internal */
|
|
10
|
+
templateName?: string;
|
|
11
|
+
};
|
|
4
12
|
export declare class MailService {
|
|
5
13
|
#private;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
[afterResolve](_: unknown, { cancellationSignal }: AfterResolveContext): void;
|
|
15
|
+
send(mailData: MailData, options?: MailSendOptions): Promise<MailSendResult>;
|
|
16
|
+
sendTemplate<Context extends object>(keyOrTemplate: string | MailTemplate<Context>, mailData: TypedOmit<MailData, 'content' | 'subject'>, templateContext?: Context, options?: MailSendOptions): Promise<MailSendResult>;
|
|
17
|
+
enqueue(mailData: MailData, options?: {
|
|
18
|
+
transaction?: Transaction;
|
|
19
|
+
priority?: number;
|
|
20
|
+
delay?: number;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
enqueueTemplate<Context extends object>(keyOrTemplate: string | MailTemplate<Context>, mailData: TypedOmit<MailData, 'content' | 'subject'>, templateContext?: Context, options?: {
|
|
23
|
+
transaction?: Transaction;
|
|
24
|
+
priority?: number;
|
|
25
|
+
delay?: number;
|
|
26
|
+
}): Promise<void>;
|
|
10
27
|
}
|
package/mail/mail.service.js
CHANGED
|
@@ -4,45 +4,62 @@ 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 {
|
|
7
|
+
import { sql } from 'drizzle-orm';
|
|
8
|
+
import { serializeError } from '../errors/index.js';
|
|
9
|
+
import { Singleton, afterResolve, inject, provide } from '../injector/index.js';
|
|
8
10
|
import { Logger } from '../logger/index.js';
|
|
11
|
+
import { buildJsonb } from '../orm/index.js';
|
|
9
12
|
import { DatabaseConfig, injectRepository } from '../orm/server/index.js';
|
|
13
|
+
import { TaskProcessResult, TaskQueue } from '../task-queue/task-queue.js';
|
|
10
14
|
import { TemplateService } from '../templates/template.service.js';
|
|
11
15
|
import { currentTimestamp } from '../utils/date-time.js';
|
|
12
|
-
import { formatError } from '../errors/index.js';
|
|
13
16
|
import { assertDefined } from '../utils/type-guards.js';
|
|
14
17
|
import { MailClient, MailClientConfig } from './mail.client.js';
|
|
15
18
|
import { MailLog } from './models/index.js';
|
|
19
|
+
import { mailLog as mailLogTable } from './models/schemas.js';
|
|
16
20
|
import { MailModuleConfig } from './module.js';
|
|
17
21
|
import { MAIL_DEFAULT_DATA } from './tokens.js';
|
|
18
22
|
let MailService = class MailService {
|
|
19
23
|
#mailClient = inject(MailClient);
|
|
20
24
|
#templateService = inject(TemplateService);
|
|
21
25
|
#mailLogRepository = injectRepository(MailLog);
|
|
26
|
+
#taskQueue = inject((TaskQueue), 'mail');
|
|
22
27
|
#defaultClientConfig = inject(MailClientConfig, undefined, { optional: true });
|
|
23
28
|
#defaultData = inject(MAIL_DEFAULT_DATA, undefined, { optional: true });
|
|
24
29
|
#logger = inject(Logger, 'MailService');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
[afterResolve](_, { cancellationSignal }) {
|
|
31
|
+
this.#taskQueue.process({ cancellationSignal, types: ['send-email', 'send-template-email'] }, async (context) => {
|
|
32
|
+
if (context.type == 'send-email') {
|
|
33
|
+
await this.send(context.data);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await this.sendTemplate(context.data.templateKey, context.data.mailData, context.data.templateContext);
|
|
37
|
+
}
|
|
38
|
+
return TaskProcessResult.Complete();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async send(mailData, options) {
|
|
42
|
+
const config = options?.clientConfig ?? this.#defaultClientConfig;
|
|
43
|
+
assertDefined(config, 'No mail client config provided.');
|
|
29
44
|
const data = { ...this.#defaultData, ...mailData };
|
|
30
45
|
let mailLog;
|
|
31
46
|
mailLog = await this.#mailLogRepository.insert({
|
|
32
47
|
timestamp: currentTimestamp(),
|
|
33
|
-
template: templateName ?? null,
|
|
48
|
+
template: options?.templateName ?? null,
|
|
34
49
|
data,
|
|
35
50
|
sendResult: null,
|
|
36
|
-
errors:
|
|
51
|
+
errors: [],
|
|
37
52
|
});
|
|
38
53
|
try {
|
|
39
|
-
const result = await this.#mailClient.send(data,
|
|
54
|
+
const result = await this.#mailClient.send(data, config);
|
|
40
55
|
await this.#mailLogRepository.update(mailLog.id, { sendResult: result });
|
|
41
56
|
return result;
|
|
42
57
|
}
|
|
43
58
|
catch (error) {
|
|
44
59
|
try {
|
|
45
|
-
await this.#mailLogRepository.update(mailLog.id, {
|
|
60
|
+
await this.#mailLogRepository.update(mailLog.id, {
|
|
61
|
+
errors: sql `${mailLogTable.errors} || ${buildJsonb([serializeError(error)])}`,
|
|
62
|
+
});
|
|
46
63
|
}
|
|
47
64
|
catch (logError) {
|
|
48
65
|
this.#logger.error(logError);
|
|
@@ -50,16 +67,30 @@ let MailService = class MailService {
|
|
|
50
67
|
throw error;
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
|
-
async sendTemplate(keyOrTemplate, mailData, templateContext,
|
|
70
|
+
async sendTemplate(keyOrTemplate, mailData, templateContext, options) {
|
|
54
71
|
const { name, fields: { subject, html, text } } = await this.#templateService.render(keyOrTemplate, templateContext);
|
|
55
72
|
const fullMailData = { ...mailData, subject, content: { html, text } };
|
|
56
|
-
return await this.send(fullMailData,
|
|
73
|
+
return await this.send(fullMailData, { ...options, templateName: name });
|
|
74
|
+
}
|
|
75
|
+
async enqueue(mailData, options) {
|
|
76
|
+
const data = { ...this.#defaultData, ...mailData };
|
|
77
|
+
await this.#taskQueue.enqueue('send-email', data, options);
|
|
78
|
+
}
|
|
79
|
+
async enqueueTemplate(keyOrTemplate, mailData, templateContext, options) {
|
|
80
|
+
if (typeof keyOrTemplate == 'string') {
|
|
81
|
+
await this.#taskQueue.enqueue('send-template-email', { templateKey: keyOrTemplate, mailData, templateContext }, options);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const { fields: { subject, html, text } } = await this.#templateService.render(keyOrTemplate, templateContext);
|
|
85
|
+
const fullMailData = { ...mailData, subject, content: { html, text } };
|
|
86
|
+
await this.enqueue(fullMailData, options);
|
|
87
|
+
}
|
|
57
88
|
}
|
|
58
89
|
};
|
|
59
90
|
MailService = __decorate([
|
|
60
91
|
Singleton({
|
|
61
92
|
providers: [
|
|
62
|
-
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(MailModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
93
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(MailModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
63
94
|
],
|
|
64
95
|
})
|
|
65
96
|
], MailService);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SerializedError } from '../../errors/format.js';
|
|
1
2
|
import { BaseEntity, type Json, type Timestamp } from '../../orm/index.js';
|
|
2
3
|
import type { MailData } from './mail-data.model.js';
|
|
3
4
|
import type { MailSendResult } from './mail-send-result.model.js';
|
|
@@ -6,5 +7,5 @@ export declare class MailLog extends BaseEntity {
|
|
|
6
7
|
template: string | null;
|
|
7
8
|
data: Json<MailData>;
|
|
8
9
|
sendResult: Json<MailSendResult> | null;
|
|
9
|
-
errors:
|
|
10
|
+
errors: Json<SerializedError[]>;
|
|
10
11
|
}
|
|
@@ -33,7 +33,7 @@ __decorate([
|
|
|
33
33
|
__metadata("design:type", Object)
|
|
34
34
|
], MailLog.prototype, "sendResult", void 0);
|
|
35
35
|
__decorate([
|
|
36
|
-
|
|
36
|
+
JsonProperty(),
|
|
37
37
|
__metadata("design:type", Object)
|
|
38
38
|
], MailLog.prototype, "errors", void 0);
|
|
39
39
|
MailLog = __decorate([
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TaskDefinitionMap } from '../task-queue/types.js';
|
|
2
|
+
import type { Record, TypedOmit } from '../types/index.js';
|
|
3
|
+
import type { MailData } from './models/index.js';
|
|
4
|
+
export type MailTemplateTaskData = {
|
|
5
|
+
templateKey: string;
|
|
6
|
+
mailData: TypedOmit<MailData, 'content' | 'subject'>;
|
|
7
|
+
templateContext?: Record<string>;
|
|
8
|
+
};
|
|
9
|
+
export type MailTaskDefinitions = TaskDefinitionMap<{
|
|
10
|
+
'send-email': {
|
|
11
|
+
data: MailData;
|
|
12
|
+
state: void;
|
|
13
|
+
result: void;
|
|
14
|
+
};
|
|
15
|
+
'send-template-email': {
|
|
16
|
+
data: MailTemplateTaskData;
|
|
17
|
+
state: void;
|
|
18
|
+
result: void;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.177",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"type-fest": "^5.5"
|
|
153
153
|
},
|
|
154
154
|
"peerDependencies": {
|
|
155
|
-
"@aws-sdk/client-s3": "^3.
|
|
156
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
155
|
+
"@aws-sdk/client-s3": "^3.1015",
|
|
156
|
+
"@aws-sdk/s3-request-presigner": "^3.1015",
|
|
157
157
|
"@genkit-ai/google-genai": "^1.30",
|
|
158
158
|
"@google-cloud/storage": "^7.19",
|
|
159
159
|
"@toon-format/toon": "^2.1.0",
|
|
@@ -56,7 +56,7 @@ PostgresRateLimiter = __decorate([
|
|
|
56
56
|
Singleton({
|
|
57
57
|
argumentIdentityProvider: JSON.stringify,
|
|
58
58
|
providers: [
|
|
59
|
-
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresRateLimiterModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
59
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresRateLimiterModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
60
60
|
],
|
|
61
61
|
})
|
|
62
62
|
], PostgresRateLimiter);
|