@tstdl/base 0.93.105 → 0.93.106
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/gateway.d.ts +2 -1
- package/api/server/gateway.js +3 -2
- package/api/types.d.ts +3 -2
- package/document-management/server/services/document-file.service.js +1 -1
- package/http/server/http-server.d.ts +1 -0
- package/http/server/node/node-http-server.js +3 -0
- package/notification/api/notification.api.d.ts +13 -3
- package/notification/api/notification.api.js +9 -2
- package/notification/models/in-app-notification.model.d.ts +4 -4
- package/notification/models/notification-log.model.d.ts +15 -3
- package/notification/models/notification-log.model.js +8 -1
- package/notification/server/api/notification.api-controller.d.ts +3 -4
- package/notification/server/api/notification.api-controller.js +11 -2
- package/notification/server/drizzle/{0000_shiny_the_anarchist.sql → 0000_ancient_hellion.sql} +2 -2
- package/notification/server/drizzle/meta/0000_snapshot.json +3 -3
- package/notification/server/drizzle/meta/_journal.json +2 -2
- package/notification/server/module.d.ts +3 -1
- package/notification/server/module.js +6 -0
- package/notification/server/providers/channel-provider.d.ts +2 -2
- package/notification/server/providers/email-channel-provider.d.ts +2 -2
- package/notification/server/providers/in-app-channel-provider.d.ts +2 -2
- package/notification/server/providers/web-push-channel-provider.d.ts +2 -2
- package/notification/server/services/index.d.ts +1 -0
- package/notification/server/services/index.js +1 -0
- package/notification/server/services/notification-ancillary.service.d.ts +5 -0
- package/notification/server/services/notification-ancillary.service.js +14 -0
- package/notification/server/services/notification-delivery.worker.js +6 -2
- package/notification/server/services/notification-sse.service.d.ts +3 -2
- package/notification/server/services/notification-sse.service.js +18 -23
- package/notification/server/services/notification-template.service.d.ts +2 -2
- package/notification/server/services/notification.service.d.ts +5 -3
- package/notification/server/services/notification.service.js +10 -3
- package/notification/tests/notification-api.test.js +14 -7
- package/notification/tests/notification-flow.test.js +32 -23
- package/package.json +5 -5
- package/rxjs-utils/async-iterable.d.ts +2 -0
- package/rxjs-utils/async-iterable.js +15 -0
- package/rxjs-utils/index.d.ts +1 -0
- package/rxjs-utils/index.js +1 -0
- package/test5.d.ts +12 -0
- package/test5.js +10 -10
package/api/server/gateway.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type ApiGatewayMiddlewareContext = {
|
|
|
14
14
|
readonly resourcePatternResult: URLPatternResult;
|
|
15
15
|
readonly request: HttpServerRequest;
|
|
16
16
|
readonly response: HttpServerResponse;
|
|
17
|
+
readonly abortSignal: AbortSignal;
|
|
17
18
|
};
|
|
18
19
|
export type ApiGatewayMiddlewareNext = AsyncMiddlewareNext;
|
|
19
20
|
export type ApiGatewayMiddleware = AsyncMiddleware<ApiGatewayMiddlewareContext>;
|
|
@@ -59,7 +60,7 @@ export declare class ApiGateway implements Resolvable<ApiGatewayOptions> {
|
|
|
59
60
|
addMiddleware(middleware: ApiGatewayMiddleware): void;
|
|
60
61
|
supressErrors(...errorTypes: Type<Error>[]): void;
|
|
61
62
|
registerApi<T extends ApiDefinition>(definition: ApiDefinition, implementation: ApiController<T>): void;
|
|
62
|
-
handleHttpServerRequestContext({ request, respond, close }: HttpServerRequestContext): Promise<void>;
|
|
63
|
+
handleHttpServerRequestContext({ request, abortSignal, respond, close }: HttpServerRequestContext): Promise<void>;
|
|
63
64
|
getApiMetadata(resource: URL): ApiMetadata;
|
|
64
65
|
private updateMiddleware;
|
|
65
66
|
private endpointMiddleware;
|
package/api/server/gateway.js
CHANGED
|
@@ -121,13 +121,13 @@ let ApiGateway = ApiGateway_1 = class ApiGateway {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
async handleHttpServerRequestContext({ request, respond, close }) {
|
|
124
|
+
async handleHttpServerRequestContext({ request, abortSignal, respond, close }) {
|
|
125
125
|
let responded = false;
|
|
126
126
|
const response = new HttpServerResponse();
|
|
127
127
|
try {
|
|
128
128
|
const { api, patternResult } = this.getApiMetadata(request.url);
|
|
129
129
|
const endpoint = api.endpoints.get(request.method);
|
|
130
|
-
const context = { api, resourcePatternResult: patternResult, endpoint, request, response };
|
|
130
|
+
const context = { api, resourcePatternResult: patternResult, endpoint, request, response, abortSignal };
|
|
131
131
|
await this.#composedMiddleware(context);
|
|
132
132
|
responded = true;
|
|
133
133
|
await respond(context.response);
|
|
@@ -198,6 +198,7 @@ let ApiGateway = ApiGateway_1 = class ApiGateway {
|
|
|
198
198
|
parameters: validatedParameters,
|
|
199
199
|
body,
|
|
200
200
|
request: context.request,
|
|
201
|
+
abortSignal: context.abortSignal,
|
|
201
202
|
tryGetToken: async () => {
|
|
202
203
|
return await requestTokenProvider.tryGetToken(requestContext);
|
|
203
204
|
},
|
package/api/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Token } from '../authentication/index.js';
|
|
|
4
4
|
import type { HttpServerRequest, HttpServerResponse } from '../http/server/index.js';
|
|
5
5
|
import type { HttpMethod } from '../http/types.js';
|
|
6
6
|
import type { SchemaOutput, SchemaTestable } from '../schema/index.js';
|
|
7
|
-
import type { DataStream } from '../sse/index.js';
|
|
7
|
+
import type { DataStream, DataStreamSource } from '../sse/index.js';
|
|
8
8
|
import type { ServerSentEventsSource } from '../sse/server-sent-events-source.js';
|
|
9
9
|
import type { ServerSentEvents } from '../sse/server-sent-events.js';
|
|
10
10
|
import type { NonUndefinable, OneOrMany, Record, ReturnTypeOrT } from '../types/index.js';
|
|
@@ -95,7 +95,7 @@ export type ApiEndpointParametersSchema<T extends ApiDefinition, K extends ApiEn
|
|
|
95
95
|
export type ApiEndpointBodySchema<T extends ApiDefinition, K extends ApiEndpointKeys<T>> = NonUndefinable<ApiEndpoint<T, K>['body']>;
|
|
96
96
|
export type ApiEndpointResultSchema<T extends ApiDefinition, K extends ApiEndpointKeys<T>> = NonUndefinable<ApiEndpoint<T, K>['result']>;
|
|
97
97
|
export type ApiBinaryType = typeof Uint8Array | typeof Blob | typeof ReadableStream;
|
|
98
|
-
export type ApiInputType<T extends SchemaTestable> = T extends ApiBinaryType ? InstanceType<Exclude<ApiBinaryType, typeof ReadableStream> | typeof ReadableStream<Uint8Array<ArrayBuffer>>> : T extends typeof ServerSentEvents ? ServerSentEventsSource : T extends typeof DataStream<infer U> ? AsyncIterable<U> : T extends SchemaTestable ? SchemaOutput<T> : never;
|
|
98
|
+
export type ApiInputType<T extends SchemaTestable> = T extends ApiBinaryType ? InstanceType<Exclude<ApiBinaryType, typeof ReadableStream> | typeof ReadableStream<Uint8Array<ArrayBuffer>>> : T extends typeof ServerSentEvents ? ServerSentEventsSource : T extends typeof DataStream<infer U> ? AsyncIterable<U> | DataStreamSource<U> : T extends SchemaTestable ? SchemaOutput<T> : never;
|
|
99
99
|
export type ApiOutputType<T extends SchemaTestable> = T extends typeof ReadableStream ? ReadableStream<Uint8Array<ArrayBuffer>> : T extends typeof DataStream<infer U> ? Observable<U> : T extends SchemaTestable ? SchemaOutput<T> : never;
|
|
100
100
|
export type ApiParameters<T extends ApiDefinition, K extends ApiEndpointKeys<T>> = ApiInputType<ApiEndpointParametersSchema<T, K>>;
|
|
101
101
|
export type ApiClientBody<T extends ApiDefinition, K extends ApiEndpointKeys<T>> = ApiInputType<ApiEndpointBodySchema<T, K>>;
|
|
@@ -106,6 +106,7 @@ export type ApiRequestData<T extends ApiDefinition = ApiDefinition, K extends Ap
|
|
|
106
106
|
parameters: ApiParameters<T, K>;
|
|
107
107
|
body: ApiServerBody<T, K>;
|
|
108
108
|
request: HttpServerRequest;
|
|
109
|
+
abortSignal: AbortSignal;
|
|
109
110
|
};
|
|
110
111
|
export type ApiRequestContext<T extends ApiDefinition = ApiDefinition, K extends ApiEndpointKeys<T> = ApiEndpointKeys<T>> = ApiRequestData<T, K> & {
|
|
111
112
|
tryGetToken<T extends Token>(): Promise<T | null>;
|
|
@@ -73,7 +73,7 @@ import { currentTimestamp } from '../../../utils/date-time.js';
|
|
|
73
73
|
import { getRandomString } from '../../../utils/random.js';
|
|
74
74
|
import { readableStreamFromPromise, readBinaryStream } from '../../../utils/stream/index.js';
|
|
75
75
|
import { isDefined, isNotReadableStream, isNotUint8Array, isUint8Array } from '../../../utils/type-guards.js';
|
|
76
|
-
import { millisecondsPerMinute
|
|
76
|
+
import { millisecondsPerMinute } from '../../../utils/units.js';
|
|
77
77
|
import { Document } from '../../models/index.js';
|
|
78
78
|
import { DocumentManagementConfiguration } from '../module.js';
|
|
79
79
|
import { DocumentManagementSingleton } from './singleton.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HttpServerRequest, HttpServerResponse } from '../../http/server/index.js';
|
|
2
2
|
export type HttpServerRequestContext<Context = unknown> = {
|
|
3
3
|
request: HttpServerRequest;
|
|
4
|
+
abortSignal: AbortSignal;
|
|
4
5
|
context: Context;
|
|
5
6
|
respond(response: HttpServerResponse): Promise<void>;
|
|
6
7
|
close(): Promise<void>;
|
|
@@ -116,8 +116,11 @@ let NodeHttpServer = NodeHttpServer_1 = class NodeHttpServer extends HttpServer
|
|
|
116
116
|
ip: clientIp,
|
|
117
117
|
body: request,
|
|
118
118
|
});
|
|
119
|
+
const abortController = new AbortController();
|
|
120
|
+
response.on('close', () => abortController.abort());
|
|
119
121
|
const item = {
|
|
120
122
|
request: httpRequest,
|
|
123
|
+
abortSignal: abortController.signal,
|
|
121
124
|
respond: getResponder(response),
|
|
122
125
|
context,
|
|
123
126
|
async close() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataStream } from '../../sse/data-stream.js';
|
|
2
2
|
import { InAppNotificationView } from '../models/in-app-notification.model.js';
|
|
3
3
|
export declare const notificationApiDefinition: {
|
|
4
4
|
resource: string;
|
|
@@ -6,7 +6,11 @@ export declare const notificationApiDefinition: {
|
|
|
6
6
|
stream: {
|
|
7
7
|
resource: string;
|
|
8
8
|
method: "GET";
|
|
9
|
-
result:
|
|
9
|
+
result: {
|
|
10
|
+
new (): DataStream<InAppNotificationView<Record<string, import("../models/notification-log.model.js").NotificationDefinition<import("../../types/types.js").ObjectLiteral, import("../../types/types.js").ObjectLiteral>>>>;
|
|
11
|
+
parse<T>(eventSource: import("../../sse/server-sent-events.js").ServerSentEvents): import("rxjs").Observable<T>;
|
|
12
|
+
};
|
|
13
|
+
credentials: true;
|
|
10
14
|
};
|
|
11
15
|
listInApp: {
|
|
12
16
|
resource: string;
|
|
@@ -16,7 +20,8 @@ export declare const notificationApiDefinition: {
|
|
|
16
20
|
limit?: number | undefined;
|
|
17
21
|
includeArchived?: boolean | undefined;
|
|
18
22
|
}>;
|
|
19
|
-
result: import("../../schema/index.js").ArraySchema<InAppNotificationView
|
|
23
|
+
result: import("../../schema/index.js").ArraySchema<InAppNotificationView<Record<string, import("../models/notification-log.model.js").NotificationDefinition<import("../../types/types.js").ObjectLiteral, import("../../types/types.js").ObjectLiteral>>>>;
|
|
24
|
+
credentials: true;
|
|
20
25
|
};
|
|
21
26
|
markRead: {
|
|
22
27
|
resource: string;
|
|
@@ -25,6 +30,7 @@ export declare const notificationApiDefinition: {
|
|
|
25
30
|
id: string;
|
|
26
31
|
}>;
|
|
27
32
|
result: import("../../schema/index.js").LiteralSchema<"ok">;
|
|
33
|
+
credentials: true;
|
|
28
34
|
};
|
|
29
35
|
archive: {
|
|
30
36
|
resource: string;
|
|
@@ -33,6 +39,7 @@ export declare const notificationApiDefinition: {
|
|
|
33
39
|
id: string;
|
|
34
40
|
}>;
|
|
35
41
|
result: import("../../schema/index.js").LiteralSchema<"ok">;
|
|
42
|
+
credentials: true;
|
|
36
43
|
};
|
|
37
44
|
getPreferences: {
|
|
38
45
|
resource: string;
|
|
@@ -42,6 +49,7 @@ export declare const notificationApiDefinition: {
|
|
|
42
49
|
enabled: boolean;
|
|
43
50
|
channel: "email" | "in-app" | "web-push";
|
|
44
51
|
}>;
|
|
52
|
+
credentials: true;
|
|
45
53
|
};
|
|
46
54
|
updatePreference: {
|
|
47
55
|
resource: string;
|
|
@@ -52,6 +60,7 @@ export declare const notificationApiDefinition: {
|
|
|
52
60
|
readonly enabled: boolean;
|
|
53
61
|
}>;
|
|
54
62
|
result: import("../../schema/index.js").LiteralSchema<"ok">;
|
|
63
|
+
credentials: true;
|
|
55
64
|
};
|
|
56
65
|
registerWebPush: {
|
|
57
66
|
resource: string;
|
|
@@ -64,6 +73,7 @@ export declare const notificationApiDefinition: {
|
|
|
64
73
|
};
|
|
65
74
|
}>;
|
|
66
75
|
result: import("../../schema/index.js").LiteralSchema<"ok">;
|
|
76
|
+
credentials: true;
|
|
67
77
|
};
|
|
68
78
|
};
|
|
69
79
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineApi } from '../../api/types.js';
|
|
2
2
|
import { array, boolean, enumeration, explicitObject, literal, number, object, optional, string } from '../../schema/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DataStream } from '../../sse/data-stream.js';
|
|
4
4
|
import { InAppNotificationView } from '../models/in-app-notification.model.js';
|
|
5
5
|
import { NotificationChannel } from '../models/index.js';
|
|
6
6
|
export const notificationApiDefinition = defineApi({
|
|
@@ -9,7 +9,8 @@ export const notificationApiDefinition = defineApi({
|
|
|
9
9
|
stream: {
|
|
10
10
|
resource: 'stream',
|
|
11
11
|
method: 'GET',
|
|
12
|
-
result:
|
|
12
|
+
result: (DataStream),
|
|
13
|
+
credentials: true,
|
|
13
14
|
},
|
|
14
15
|
listInApp: {
|
|
15
16
|
resource: 'in-app',
|
|
@@ -20,18 +21,21 @@ export const notificationApiDefinition = defineApi({
|
|
|
20
21
|
includeArchived: optional(boolean()),
|
|
21
22
|
}),
|
|
22
23
|
result: array(InAppNotificationView),
|
|
24
|
+
credentials: true,
|
|
23
25
|
},
|
|
24
26
|
markRead: {
|
|
25
27
|
resource: 'in-app/:id/read',
|
|
26
28
|
method: 'POST',
|
|
27
29
|
parameters: object({ id: string() }),
|
|
28
30
|
result: literal('ok'),
|
|
31
|
+
credentials: true,
|
|
29
32
|
},
|
|
30
33
|
archive: {
|
|
31
34
|
resource: 'in-app/:id/archive',
|
|
32
35
|
method: 'POST',
|
|
33
36
|
parameters: object({ id: string() }),
|
|
34
37
|
result: literal('ok'),
|
|
38
|
+
credentials: true,
|
|
35
39
|
},
|
|
36
40
|
getPreferences: {
|
|
37
41
|
resource: 'preferences',
|
|
@@ -41,6 +45,7 @@ export const notificationApiDefinition = defineApi({
|
|
|
41
45
|
channel: enumeration(NotificationChannel),
|
|
42
46
|
enabled: boolean(),
|
|
43
47
|
})),
|
|
48
|
+
credentials: true,
|
|
44
49
|
},
|
|
45
50
|
updatePreference: {
|
|
46
51
|
resource: 'preferences',
|
|
@@ -51,6 +56,7 @@ export const notificationApiDefinition = defineApi({
|
|
|
51
56
|
enabled: boolean(),
|
|
52
57
|
}),
|
|
53
58
|
result: literal('ok'),
|
|
59
|
+
credentials: true,
|
|
54
60
|
},
|
|
55
61
|
registerWebPush: {
|
|
56
62
|
resource: 'web-push/register',
|
|
@@ -63,6 +69,7 @@ export const notificationApiDefinition = defineApi({
|
|
|
63
69
|
}),
|
|
64
70
|
}),
|
|
65
71
|
result: literal('ok'),
|
|
72
|
+
credentials: true,
|
|
66
73
|
},
|
|
67
74
|
},
|
|
68
75
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TenantBaseEntity, type Uuid } from '../../orm/index.js';
|
|
2
2
|
import type { Timestamp } from '../../orm/types.js';
|
|
3
|
-
import { type
|
|
3
|
+
import { type NotificationDefinitionMap, type NotificationLogView } from './notification-log.model.js';
|
|
4
4
|
export declare class InAppNotification extends TenantBaseEntity {
|
|
5
5
|
static readonly entityName = "InAppNotification";
|
|
6
6
|
userId: Uuid;
|
|
@@ -8,7 +8,7 @@ export declare class InAppNotification extends TenantBaseEntity {
|
|
|
8
8
|
readTimestamp: Timestamp | null;
|
|
9
9
|
archiveTimestamp: Timestamp | null;
|
|
10
10
|
}
|
|
11
|
-
export declare class InAppNotificationView extends InAppNotification {
|
|
12
|
-
notification:
|
|
11
|
+
export declare class InAppNotificationView<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> extends InAppNotification {
|
|
12
|
+
notification: NotificationLogView<Definitions>;
|
|
13
13
|
}
|
|
14
|
-
export declare function toInAppNotificationView(inAppNotification: InAppNotification, notification:
|
|
14
|
+
export declare function toInAppNotificationView<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap>(inAppNotification: InAppNotification, notification: NotificationLogView<Definitions>): InAppNotificationView<Definitions>;
|
|
@@ -22,12 +22,14 @@ export declare const NotificationStatus: {
|
|
|
22
22
|
readonly Failed: "failed";
|
|
23
23
|
};
|
|
24
24
|
export type NotificationStatus = EnumType<typeof NotificationStatus>;
|
|
25
|
-
export type NotificationDefinition<Payload extends ObjectLiteral = ObjectLiteral> = {
|
|
26
|
-
payload
|
|
25
|
+
export type NotificationDefinition<Payload extends ObjectLiteral = ObjectLiteral, View extends ObjectLiteral = ObjectLiteral> = {
|
|
26
|
+
payload?: Payload;
|
|
27
|
+
view?: View;
|
|
27
28
|
};
|
|
28
29
|
export type NotificationDefinitionMap<Definition extends Record<string, NotificationDefinition> = Record<string, NotificationDefinition>> = Definition;
|
|
29
30
|
export type NotificationTypes<Definitions extends NotificationDefinitionMap> = Extract<keyof Definitions, string>;
|
|
30
|
-
export type NotificationPayload<Definitions extends NotificationDefinitionMap, Type extends NotificationTypes<Definitions>> = Definitions[Type]['payload'];
|
|
31
|
+
export type NotificationPayload<Definitions extends NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> = Definitions[Type]['payload'];
|
|
32
|
+
export type NotificationView<Definitions extends NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> = Definitions[Type]['view'];
|
|
31
33
|
export type NotificationOfType<Definitions extends NotificationDefinitionMap, Type extends NotificationTypes<Definitions>> = string extends NotificationTypes<Definitions> ? NotificationLog<Definitions> & {
|
|
32
34
|
type: Type;
|
|
33
35
|
} : Extract<NotificationLog<Definitions>, {
|
|
@@ -39,6 +41,13 @@ export type NotificationLog<Definitions extends NotificationDefinitionMap = Noti
|
|
|
39
41
|
payload: Json<NotificationPayload<Definitions, Type>> | null;
|
|
40
42
|
};
|
|
41
43
|
}[NotificationTypes<Definitions>];
|
|
44
|
+
export type NotificationLogView<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> = {
|
|
45
|
+
[Type in NotificationTypes<Definitions>]: Omit<NotificationLogEntity<Definitions, Type>, 'type' | 'payload'> & {
|
|
46
|
+
type: Type;
|
|
47
|
+
payload: NotificationPayload<Definitions, Type>;
|
|
48
|
+
view: NotificationView<Definitions, Type>;
|
|
49
|
+
};
|
|
50
|
+
}[NotificationTypes<Definitions>];
|
|
42
51
|
export declare class NotificationLogEntity<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> extends TenantEntity {
|
|
43
52
|
static readonly entityName = "NotificationLog";
|
|
44
53
|
userId: Uuid;
|
|
@@ -48,3 +57,6 @@ export declare class NotificationLogEntity<Definitions extends NotificationDefin
|
|
|
48
57
|
currentStep: number;
|
|
49
58
|
payload: Json<NotificationPayload<Definitions, Type>> | null;
|
|
50
59
|
}
|
|
60
|
+
export declare class NotificationLogViewEntity<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> extends NotificationLogEntity<Definitions, Type> {
|
|
61
|
+
view?: unknown;
|
|
62
|
+
}
|
|
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
import { User } from '../../authentication/models/user.model.js';
|
|
11
11
|
import { defineEnum } from '../../enumeration/index.js';
|
|
12
12
|
import { JsonProperty, Reference, TenantEntity, TenantReference, Unique, UuidProperty } from '../../orm/index.js';
|
|
13
|
-
import { Enumeration, Integer, StringProperty } from '../../schema/index.js';
|
|
13
|
+
import { Enumeration, Integer, StringProperty, Unknown } from '../../schema/index.js';
|
|
14
14
|
import { NotificationTable } from './notification-table.js';
|
|
15
15
|
import { NotificationType } from './notification-type.model.js';
|
|
16
16
|
export const NotificationChannel = defineEnum('NotificationChannel', {
|
|
@@ -71,3 +71,10 @@ NotificationLogEntity = __decorate([
|
|
|
71
71
|
Unique(['tenantId', 'id', 'userId'])
|
|
72
72
|
], NotificationLogEntity);
|
|
73
73
|
export { NotificationLogEntity };
|
|
74
|
+
export class NotificationLogViewEntity extends NotificationLogEntity {
|
|
75
|
+
view;
|
|
76
|
+
}
|
|
77
|
+
__decorate([
|
|
78
|
+
Unknown(),
|
|
79
|
+
__metadata("design:type", Object)
|
|
80
|
+
], NotificationLogViewEntity.prototype, "view", void 0);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { ApiController, ApiRequestContext } from '../../../api/types.js';
|
|
2
|
-
import type { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
1
|
+
import type { ApiController, ApiRequestContext, ApiServerResult } from '../../../api/types.js';
|
|
3
2
|
import type { NotificationApiDefinition } from '../../api/notification.api.js';
|
|
4
3
|
import { NotificationSseService } from '../services/notification-sse.service.js';
|
|
5
4
|
import { NotificationService } from '../services/notification.service.js';
|
|
6
5
|
export declare class NotificationApiController implements ApiController<NotificationApiDefinition> {
|
|
7
|
-
protected readonly notificationService: NotificationService
|
|
6
|
+
protected readonly notificationService: NotificationService<Record<string, import("../../index.js").NotificationDefinition<import("../../../types/types.js").ObjectLiteral, import("../../../types/types.js").ObjectLiteral>>>;
|
|
8
7
|
protected readonly sseService: NotificationSseService;
|
|
9
|
-
stream({ getToken }: ApiRequestContext<NotificationApiDefinition, 'stream'>):
|
|
8
|
+
stream({ abortSignal, getToken }: ApiRequestContext<NotificationApiDefinition, 'stream'>): ApiServerResult<NotificationApiDefinition, 'stream'>;
|
|
10
9
|
listInApp({ parameters, getToken }: ApiRequestContext<NotificationApiDefinition, 'listInApp'>): Promise<any>;
|
|
11
10
|
markRead({ parameters, getToken }: ApiRequestContext<NotificationApiDefinition, 'markRead'>): Promise<'ok'>;
|
|
12
11
|
archive({ parameters, getToken }: ApiRequestContext<NotificationApiDefinition, 'archive'>): Promise<'ok'>;
|
|
@@ -6,6 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { apiController } from '../../../api/server/index.js';
|
|
8
8
|
import { inject } from '../../../injector/index.js';
|
|
9
|
+
import { toAsyncIterable } from '../../../rxjs-utils/index.js';
|
|
9
10
|
import { decodeBase64 } from '../../../utils/base64.js';
|
|
10
11
|
import { notificationApiDefinition } from '../../api/notification.api.js';
|
|
11
12
|
import { NotificationSseService } from '../services/notification-sse.service.js';
|
|
@@ -13,9 +14,17 @@ import { NotificationService } from '../services/notification.service.js';
|
|
|
13
14
|
let NotificationApiController = class NotificationApiController {
|
|
14
15
|
notificationService = inject(NotificationService);
|
|
15
16
|
sseService = inject(NotificationSseService);
|
|
16
|
-
async stream({ getToken }) {
|
|
17
|
+
async *stream({ abortSignal, getToken }) {
|
|
17
18
|
const token = await getToken();
|
|
18
|
-
|
|
19
|
+
const source = this.sseService.register(token.payload.tenant, token.payload.subject);
|
|
20
|
+
const asyncIterable = toAsyncIterable(source);
|
|
21
|
+
abortSignal.addEventListener('abort', () => this.sseService.unregister(token.payload.tenant, token.payload.subject, source));
|
|
22
|
+
try {
|
|
23
|
+
yield* asyncIterable;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
this.sseService.unregister(token.payload.tenant, token.payload.subject, source);
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
async listInApp({ parameters, getToken }) {
|
|
21
30
|
const token = await getToken();
|
package/notification/server/drizzle/{0000_shiny_the_anarchist.sql → 0000_ancient_hellion.sql}
RENAMED
|
@@ -64,8 +64,8 @@ CREATE TABLE "notification"."web_push_subscription" (
|
|
|
64
64
|
"tenant_id" uuid NOT NULL,
|
|
65
65
|
"user_id" uuid NOT NULL,
|
|
66
66
|
"endpoint" text NOT NULL,
|
|
67
|
-
"p256dh"
|
|
68
|
-
"auth"
|
|
67
|
+
"p256dh" "bytea" NOT NULL,
|
|
68
|
+
"auth" "bytea" NOT NULL,
|
|
69
69
|
"revision" integer NOT NULL,
|
|
70
70
|
"revision_timestamp" timestamp with time zone NOT NULL,
|
|
71
71
|
"create_timestamp" timestamp with time zone NOT NULL,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "7751e29f-1334-4511-8bf5-0d0f2550b5ae",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -521,13 +521,13 @@
|
|
|
521
521
|
},
|
|
522
522
|
"p256dh": {
|
|
523
523
|
"name": "p256dh",
|
|
524
|
-
"type": "
|
|
524
|
+
"type": "bytea",
|
|
525
525
|
"primaryKey": false,
|
|
526
526
|
"notNull": true
|
|
527
527
|
},
|
|
528
528
|
"auth": {
|
|
529
529
|
"name": "auth",
|
|
530
|
-
"type": "
|
|
530
|
+
"type": "bytea",
|
|
531
531
|
"primaryKey": false,
|
|
532
532
|
"notNull": true
|
|
533
533
|
},
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Injector } from '../../injector/index.js';
|
|
1
|
+
import { Injector, type InjectionToken } from '../../injector/index.js';
|
|
2
2
|
import { type DatabaseConfig } from '../../orm/server/index.js';
|
|
3
3
|
import type { NotificationChannel } from '../models/index.js';
|
|
4
|
+
import { NotificationAncillaryService } from './services/notification-ancillary.service.js';
|
|
4
5
|
export declare class NotificationConfiguration {
|
|
5
6
|
database?: DatabaseConfig;
|
|
6
7
|
defaultChannels?: NotificationChannel[];
|
|
8
|
+
ancillaryService?: InjectionToken<NotificationAncillaryService>;
|
|
7
9
|
}
|
|
8
10
|
export declare function configureNotification({ injector, ...config }: NotificationConfiguration & {
|
|
9
11
|
injector?: Injector;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { inject, Injector } from '../../injector/index.js';
|
|
2
2
|
import { Database, migrate } from '../../orm/server/index.js';
|
|
3
|
+
import { isDefined } from '../../utils/type-guards.js';
|
|
4
|
+
import { NotificationAncillaryService } from './services/notification-ancillary.service.js';
|
|
3
5
|
export class NotificationConfiguration {
|
|
4
6
|
database;
|
|
5
7
|
defaultChannels;
|
|
8
|
+
ancillaryService;
|
|
6
9
|
}
|
|
7
10
|
export function configureNotification({ injector, ...config }) {
|
|
8
11
|
const targetInjector = injector ?? Injector;
|
|
9
12
|
targetInjector.register(NotificationConfiguration, { useValue: config });
|
|
13
|
+
if (isDefined(config.ancillaryService)) {
|
|
14
|
+
targetInjector.register(NotificationAncillaryService, { useToken: config.ancillaryService });
|
|
15
|
+
}
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* Migrates the notification schema.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Transaction } from '../../../orm/server/index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { NotificationLogView } from '../../models/notification-log.model.js';
|
|
3
3
|
export declare abstract class ChannelProvider {
|
|
4
|
-
abstract send(notification:
|
|
4
|
+
abstract send(notification: NotificationLogView, tx?: Transaction): Promise<void>;
|
|
5
5
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NotificationLogView } from '../../models/index.js';
|
|
2
2
|
import { ChannelProvider } from './channel-provider.js';
|
|
3
3
|
export declare class EmailChannelProvider extends ChannelProvider {
|
|
4
4
|
#private;
|
|
5
|
-
send(notification:
|
|
5
|
+
send(notification: NotificationLogView): Promise<void>;
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Transaction } from '../../../orm/server/index.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type NotificationLogView } from '../../models/index.js';
|
|
3
3
|
import { ChannelProvider } from './channel-provider.js';
|
|
4
4
|
export declare class InAppChannelProvider extends ChannelProvider {
|
|
5
5
|
#private;
|
|
6
|
-
send(notification:
|
|
6
|
+
send(notification: NotificationLogView, tx?: Transaction): Promise<void>;
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Transaction } from '../../../orm/server/index.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type NotificationLogView } from '../../models/index.js';
|
|
3
3
|
import { ChannelProvider } from './channel-provider.js';
|
|
4
4
|
export declare class WebPushChannelProvider extends ChannelProvider {
|
|
5
5
|
#private;
|
|
6
|
-
send(notification:
|
|
6
|
+
send(notification: NotificationLogView, tx?: Transaction): Promise<void>;
|
|
7
7
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
+
import type { NotificationDefinitionMap, NotificationLog, NotificationView } from '../../models/index.js';
|
|
3
|
+
export declare abstract class NotificationAncillaryService<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> extends Transactional {
|
|
4
|
+
abstract getViewData(notifications: NotificationLog<Definitions>[]): Promise<NotificationView<Definitions>[]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Singleton } from '../../../injector/index.js';
|
|
8
|
+
import { Transactional } from '../../../orm/server/index.js';
|
|
9
|
+
let NotificationAncillaryService = class NotificationAncillaryService extends Transactional {
|
|
10
|
+
};
|
|
11
|
+
NotificationAncillaryService = __decorate([
|
|
12
|
+
Singleton()
|
|
13
|
+
], NotificationAncillaryService);
|
|
14
|
+
export { NotificationAncillaryService };
|
|
@@ -12,11 +12,13 @@ import { TaskProcessResult, TaskQueue } from '../../../task-queue/task-queue.js'
|
|
|
12
12
|
import { isNotNull, isNotNullOrUndefined, isUndefined } from '../../../utils/type-guards.js';
|
|
13
13
|
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationPreference, NotificationStatus, NotificationType } from '../../models/index.js';
|
|
14
14
|
import { NotificationConfiguration } from '../module.js';
|
|
15
|
+
import { NotificationAncillaryService } from './notification-ancillary.service.js';
|
|
15
16
|
let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transactional {
|
|
16
17
|
#notificationLogRepository = injectRepository(NotificationLogEntity);
|
|
17
18
|
#inAppRepository = injectRepository(InAppNotification);
|
|
18
19
|
#preferenceRepository = injectRepository(NotificationPreference);
|
|
19
20
|
#typeRepository = injectRepository(NotificationType);
|
|
21
|
+
#notificationAncillaryService = inject(NotificationAncillaryService);
|
|
20
22
|
#taskQueue = inject((TaskQueue), 'notification');
|
|
21
23
|
#rateLimiter = inject(RateLimiter, 'notification');
|
|
22
24
|
#logger = inject(Logger, 'NotificationDeliveryWorker');
|
|
@@ -77,7 +79,8 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
77
79
|
}
|
|
78
80
|
for (const channel of enabledChannels) {
|
|
79
81
|
// TODO: what if an error occurs here? partial delivery? Should we use tasks to allow retrying individual channels?
|
|
80
|
-
await this.
|
|
82
|
+
const [viewData] = await this.#notificationAncillaryService.getViewData([notification]);
|
|
83
|
+
await this.sendToChannel({ ...notification, view: viewData }, channel, tx);
|
|
81
84
|
}
|
|
82
85
|
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { status: NotificationStatus.Sent, currentStep: 1 });
|
|
83
86
|
if (isNotNull(type.escalations) && (type.escalations.length > 0)) {
|
|
@@ -95,7 +98,8 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
95
98
|
this.#logger.debug(`Notification ${notificationId} already read, skipping escalation step ${step}`);
|
|
96
99
|
return TaskProcessResult.Complete();
|
|
97
100
|
}
|
|
98
|
-
await this.
|
|
101
|
+
const [viewData] = await this.#notificationAncillaryService.getViewData([notification]);
|
|
102
|
+
await this.sendToChannel({ ...notification, view: viewData }, rule.channel, tx);
|
|
99
103
|
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { currentStep: step + 1 });
|
|
100
104
|
if (step < type.escalations.length) {
|
|
101
105
|
const nextRule = type.escalations[step];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
1
2
|
import { afterResolve } from '../../../injector/index.js';
|
|
2
|
-
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
3
3
|
import type { InAppNotificationView } from '../../models/index.js';
|
|
4
4
|
export declare class NotificationSseService {
|
|
5
5
|
#private;
|
|
6
6
|
[afterResolve](): void;
|
|
7
|
-
register(tenantId: string, userId: string):
|
|
7
|
+
register(tenantId: string, userId: string): Subject<InAppNotificationView>;
|
|
8
|
+
unregister(tenantId: string, userId: string, source: Subject<InAppNotificationView>): void;
|
|
8
9
|
send(notification: InAppNotificationView): Promise<void>;
|
|
9
10
|
private dispatchToLocal;
|
|
10
11
|
}
|
|
@@ -4,10 +4,9 @@ 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 { Subject } from 'rxjs';
|
|
7
8
|
import { afterResolve, inject, Singleton } from '../../../injector/index.js';
|
|
8
|
-
import { MessageBusProvider } from '../../../message-bus/
|
|
9
|
-
import { toObservable } from '../../../signals/api.js';
|
|
10
|
-
import { ServerSentEventsSource } from '../../../sse/server-sent-events-source.js';
|
|
9
|
+
import { MessageBusProvider } from '../../../message-bus/index.js';
|
|
11
10
|
let NotificationSseService = class NotificationSseService {
|
|
12
11
|
#messageBusProvider = inject(MessageBusProvider);
|
|
13
12
|
#messageBus = this.#messageBusProvider.get('notification');
|
|
@@ -16,7 +15,7 @@ let NotificationSseService = class NotificationSseService {
|
|
|
16
15
|
this.#messageBus.allMessages$.subscribe((message) => this.dispatchToLocal(message));
|
|
17
16
|
}
|
|
18
17
|
register(tenantId, userId) {
|
|
19
|
-
const source = new
|
|
18
|
+
const source = new Subject();
|
|
20
19
|
let tenantMap = this.#sources.get(tenantId);
|
|
21
20
|
if (tenantMap == null) {
|
|
22
21
|
tenantMap = new Map();
|
|
@@ -28,23 +27,22 @@ let NotificationSseService = class NotificationSseService {
|
|
|
28
27
|
tenantMap.set(userId, userSources);
|
|
29
28
|
}
|
|
30
29
|
userSources.add(source);
|
|
31
|
-
toObservable(source.closed).subscribe((closed) => {
|
|
32
|
-
if (closed) {
|
|
33
|
-
const currentTenantMap = this.#sources.get(tenantId);
|
|
34
|
-
const currentUserSources = currentTenantMap?.get(userId);
|
|
35
|
-
if (currentUserSources) {
|
|
36
|
-
currentUserSources.delete(source);
|
|
37
|
-
if (currentUserSources.size == 0) {
|
|
38
|
-
currentTenantMap?.delete(userId);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (currentTenantMap?.size == 0) {
|
|
42
|
-
this.#sources.delete(tenantId);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
30
|
return source;
|
|
47
31
|
}
|
|
32
|
+
unregister(tenantId, userId, source) {
|
|
33
|
+
const tenantMap = this.#sources.get(tenantId);
|
|
34
|
+
const userSources = tenantMap?.get(userId);
|
|
35
|
+
if (userSources) {
|
|
36
|
+
userSources.delete(source);
|
|
37
|
+
if (userSources.size == 0) {
|
|
38
|
+
tenantMap?.delete(userId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (tenantMap?.size == 0) {
|
|
42
|
+
this.#sources.delete(tenantId);
|
|
43
|
+
}
|
|
44
|
+
source.complete();
|
|
45
|
+
}
|
|
48
46
|
async send(notification) {
|
|
49
47
|
await this.#messageBus.publish(notification);
|
|
50
48
|
}
|
|
@@ -53,10 +51,7 @@ let NotificationSseService = class NotificationSseService {
|
|
|
53
51
|
const userSources = tenantMap?.get(notification.userId);
|
|
54
52
|
if (userSources != null) {
|
|
55
53
|
for (const source of userSources) {
|
|
56
|
-
|
|
57
|
-
name: 'notification',
|
|
58
|
-
data: notification,
|
|
59
|
-
});
|
|
54
|
+
source.next(notification);
|
|
60
55
|
}
|
|
61
56
|
}
|
|
62
57
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type NotificationLogView } from '../../models/index.js';
|
|
2
2
|
import type { NotificationContent, NotificationTemplate } from './notification-template.js';
|
|
3
3
|
export declare class NotificationTemplateService {
|
|
4
4
|
#private;
|
|
5
5
|
register(template: NotificationTemplate<any>): void;
|
|
6
|
-
render(notification:
|
|
6
|
+
render(notification: NotificationLogView, language?: string): Promise<NotificationContent>;
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type NewEntity } from '../../../orm/index.js';
|
|
1
2
|
import { Transactional } from '../../../orm/server/index.js';
|
|
2
|
-
import
|
|
3
|
-
|
|
3
|
+
import type { TypedOmit } from '../../../types/types.js';
|
|
4
|
+
import { NotificationPreference, type InAppNotificationView, type NotificationChannel, type NotificationDefinitionMap, type NotificationLog } from '../../models/index.js';
|
|
5
|
+
export declare class NotificationService<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> extends Transactional {
|
|
4
6
|
#private;
|
|
5
|
-
send(notification: NotificationLog): Promise<void>;
|
|
7
|
+
send(tenantId: string, userId: string, notification: TypedOmit<NewEntity<NotificationLog<Definitions>>, 'tenantId' | 'id' | 'userId' | 'status' | 'currentStep' | 'priority' | 'metadata'> & Partial<Pick<NotificationLog<Definitions>, 'priority'>>): Promise<void>;
|
|
6
8
|
listInApp(tenantId: string, userId: string, options?: {
|
|
7
9
|
limit?: number;
|
|
8
10
|
offset?: number;
|
|
@@ -10,17 +10,22 @@ import { inject, Singleton } from '../../../injector/index.js';
|
|
|
10
10
|
import { TRANSACTION_TIMESTAMP } from '../../../orm/index.js';
|
|
11
11
|
import { injectRepository, Transactional } from '../../../orm/server/index.js';
|
|
12
12
|
import { TaskQueue } from '../../../task-queue/task-queue.js';
|
|
13
|
-
import { InAppNotification, NotificationLogEntity, NotificationPreference, NotificationStatus, toInAppNotificationView, WebPushSubscription } from '../../models/index.js';
|
|
13
|
+
import { InAppNotification, NotificationLogEntity, NotificationPreference, NotificationPriority, NotificationStatus, toInAppNotificationView, WebPushSubscription } from '../../models/index.js';
|
|
14
14
|
import { inAppNotification, notificationLog } from '../schemas.js';
|
|
15
|
+
import { NotificationAncillaryService } from './notification-ancillary.service.js';
|
|
15
16
|
let NotificationService = class NotificationService extends Transactional {
|
|
16
17
|
#notificationLogRepository = injectRepository(NotificationLogEntity);
|
|
17
18
|
#inAppNotificationRepository = injectRepository(InAppNotification);
|
|
18
19
|
#preferenceRepository = injectRepository(NotificationPreference);
|
|
19
20
|
#webPushSubscriptionRepository = injectRepository(WebPushSubscription);
|
|
21
|
+
#notificationAncillaryService = inject(NotificationAncillaryService);
|
|
20
22
|
#taskQueue = inject((TaskQueue), 'notification');
|
|
21
|
-
async send(notification) {
|
|
23
|
+
async send(tenantId, userId, notification) {
|
|
22
24
|
await this.transaction(async (tx) => {
|
|
23
25
|
const notificationToInsert = {
|
|
26
|
+
tenantId,
|
|
27
|
+
userId,
|
|
28
|
+
priority: NotificationPriority.Medium,
|
|
24
29
|
...notification,
|
|
25
30
|
status: NotificationStatus.Pending,
|
|
26
31
|
currentStep: 0,
|
|
@@ -45,9 +50,11 @@ let NotificationService = class NotificationService extends Transactional {
|
|
|
45
50
|
const notificationRows = rows.map((row) => row.notification);
|
|
46
51
|
const notificationEntities = await this.#notificationLogRepository.mapManyToEntity(notificationRows);
|
|
47
52
|
const inAppEntities = await this.#inAppNotificationRepository.mapManyToEntity(inAppRows);
|
|
53
|
+
const notificationViewDatas = await this.#notificationAncillaryService.getViewData(notificationEntities);
|
|
48
54
|
const views = notificationEntities.map((notification, index) => {
|
|
49
55
|
const inApp = inAppEntities[index];
|
|
50
|
-
|
|
56
|
+
const viewData = notificationViewDatas[index];
|
|
57
|
+
return toInAppNotificationView(inApp, { ...notification, view: viewData });
|
|
51
58
|
});
|
|
52
59
|
return views;
|
|
53
60
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
1
2
|
import { beforeAll, describe, expect, test, vi } from 'vitest';
|
|
2
3
|
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
3
|
-
import {} from '../../
|
|
4
|
-
import { setupIntegrationTest } from '../../unit-test/index.js';
|
|
4
|
+
import { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
|
|
5
5
|
import { NotificationChannel } from '../models/index.js';
|
|
6
6
|
import { NotificationApiController } from '../server/api/notification.api-controller.js';
|
|
7
7
|
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
8
8
|
import { NotificationService } from '../server/services/notification.service.js';
|
|
9
9
|
describe('Notification API (Integration)', () => {
|
|
10
10
|
let injector;
|
|
11
|
+
let database;
|
|
11
12
|
let controller;
|
|
12
13
|
let notificationService;
|
|
13
14
|
let sseService;
|
|
@@ -16,7 +17,7 @@ describe('Notification API (Integration)', () => {
|
|
|
16
17
|
const tenantId = '00000000-0000-0000-0000-000000000000';
|
|
17
18
|
let userId;
|
|
18
19
|
beforeAll(async () => {
|
|
19
|
-
({ injector } = await setupIntegrationTest({
|
|
20
|
+
({ injector, database } = await setupIntegrationTest({
|
|
20
21
|
orm: { schema },
|
|
21
22
|
modules: {
|
|
22
23
|
notification: true,
|
|
@@ -25,6 +26,7 @@ describe('Notification API (Integration)', () => {
|
|
|
25
26
|
rateLimiter: true,
|
|
26
27
|
},
|
|
27
28
|
}));
|
|
29
|
+
await truncateTables(database, 'authentication', ['user', 'subject']);
|
|
28
30
|
controller = injector.resolve(NotificationApiController);
|
|
29
31
|
notificationService = injector.resolve(NotificationService);
|
|
30
32
|
sseService = injector.resolve(NotificationSseService);
|
|
@@ -40,6 +42,7 @@ describe('Notification API (Integration)', () => {
|
|
|
40
42
|
});
|
|
41
43
|
const createMockContext = (params = {}) => ({
|
|
42
44
|
parameters: params,
|
|
45
|
+
abortSignal: new AbortController().signal,
|
|
43
46
|
getToken: async () => ({
|
|
44
47
|
payload: {
|
|
45
48
|
tenant: tenantId,
|
|
@@ -49,12 +52,16 @@ describe('Notification API (Integration)', () => {
|
|
|
49
52
|
});
|
|
50
53
|
test('stream should register sse client', async () => {
|
|
51
54
|
const registerSpy = vi.spyOn(sseService, 'register');
|
|
55
|
+
const subject = new Subject();
|
|
52
56
|
// Mock register to return a dummy source or similar if needed,
|
|
53
57
|
// but the real one might work if it just returns an object.
|
|
54
58
|
// Looking at sseService, register returns a ServerSentEventsSource.
|
|
55
59
|
// We might need to mock implementation if it tries to do something complex
|
|
56
|
-
registerSpy.mockImplementation(() =>
|
|
57
|
-
|
|
60
|
+
registerSpy.mockImplementation(() => subject);
|
|
61
|
+
const generator = controller.stream(createMockContext());
|
|
62
|
+
const nextPromise = generator.next();
|
|
63
|
+
subject.complete();
|
|
64
|
+
await nextPromise;
|
|
58
65
|
expect(registerSpy).toHaveBeenCalledWith(tenantId, userId);
|
|
59
66
|
});
|
|
60
67
|
test('listInApp should call service', async () => {
|
|
@@ -88,8 +95,8 @@ describe('Notification API (Integration)', () => {
|
|
|
88
95
|
});
|
|
89
96
|
test('registerWebPush should call service', async () => {
|
|
90
97
|
const registerWebPushSpy = vi.spyOn(notificationService, 'registerWebPush').mockResolvedValue();
|
|
91
|
-
const params = { endpoint: 'url', keys: {
|
|
98
|
+
const params = { endpoint: 'url', keys: { p256dhBase64: 'key', authBase64: 'auth' } };
|
|
92
99
|
await controller.registerWebPush(createMockContext(params));
|
|
93
|
-
expect(registerWebPushSpy).toHaveBeenCalledWith(tenantId, userId, 'url',
|
|
100
|
+
expect(registerWebPushSpy).toHaveBeenCalledWith(tenantId, userId, 'url', expect.any(Uint8Array), expect.any(Uint8Array));
|
|
94
101
|
});
|
|
95
102
|
});
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
1
7
|
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
8
|
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
3
|
-
import { runInInjectionContext } from '../../injector/index.js';
|
|
9
|
+
import { runInInjectionContext, Singleton } from '../../injector/index.js';
|
|
4
10
|
import { MailService } from '../../mail/mail.service.js';
|
|
5
11
|
import { injectRepository } from '../../orm/server/index.js';
|
|
6
12
|
import { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
|
|
7
13
|
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationStatus, WebPushSubscription } from '../models/index.js';
|
|
14
|
+
import { configureNotification } from '../server/module.js';
|
|
8
15
|
import { EmailChannelProvider } from '../server/providers/email-channel-provider.js';
|
|
16
|
+
import { NotificationAncillaryService } from '../server/services/notification-ancillary.service.js';
|
|
9
17
|
import { NotificationDeliveryWorker } from '../server/services/notification-delivery.worker.js';
|
|
10
18
|
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
11
19
|
import { NotificationService } from '../server/services/notification.service.js';
|
|
20
|
+
let MockNotificationAncillaryService = class MockNotificationAncillaryService extends NotificationAncillaryService {
|
|
21
|
+
async getViewData(notifications) {
|
|
22
|
+
return notifications.map(() => ({}));
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
MockNotificationAncillaryService = __decorate([
|
|
26
|
+
Singleton()
|
|
27
|
+
], MockNotificationAncillaryService);
|
|
12
28
|
describe('Notification Flow (Integration)', () => {
|
|
13
29
|
let injector;
|
|
14
30
|
let database;
|
|
@@ -34,6 +50,8 @@ describe('Notification Flow (Integration)', () => {
|
|
|
34
50
|
// Mock MailService
|
|
35
51
|
mailServiceMock = { send: vi.fn() };
|
|
36
52
|
injector.register(MailService, { useValue: mailServiceMock });
|
|
53
|
+
// Mock NotificationAncillaryService
|
|
54
|
+
configureNotification({ injector, ancillaryService: MockNotificationAncillaryService });
|
|
37
55
|
// Resolve Services
|
|
38
56
|
notificationService = injector.resolve(NotificationService);
|
|
39
57
|
worker = injector.resolve(NotificationDeliveryWorker);
|
|
@@ -65,14 +83,11 @@ describe('Notification Flow (Integration)', () => {
|
|
|
65
83
|
escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
|
|
66
84
|
},
|
|
67
85
|
});
|
|
68
|
-
|
|
69
|
-
tenantId,
|
|
70
|
-
userId: user.id,
|
|
86
|
+
await notificationService.send(tenantId, user.id, {
|
|
71
87
|
type: 'test',
|
|
72
88
|
priority: 'high',
|
|
73
89
|
payload: { message: 'Hello', testField: 'Test Value' },
|
|
74
90
|
});
|
|
75
|
-
await notificationService.send(notification);
|
|
76
91
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
77
92
|
expect(logs).toHaveLength(1);
|
|
78
93
|
const log = logs[0];
|
|
@@ -107,14 +122,12 @@ describe('Notification Flow (Integration)', () => {
|
|
|
107
122
|
throttling: { limit: 1, interval: 60000 },
|
|
108
123
|
},
|
|
109
124
|
});
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
await notificationService.send(tenantId, user.id, {
|
|
126
|
+
type: 'throttled', priority: 'medium', payload: {},
|
|
112
127
|
});
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
await notificationService.send(tenantId, user.id, {
|
|
129
|
+
type: 'throttled', priority: 'medium', payload: {},
|
|
115
130
|
});
|
|
116
|
-
await notificationService.send(notification1);
|
|
117
|
-
await notificationService.send(notification2);
|
|
118
131
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
119
132
|
logs.sort((a, b) => Number(a.metadata.createTimestamp) - Number(b.metadata.createTimestamp));
|
|
120
133
|
// First one should pass
|
|
@@ -135,10 +148,9 @@ describe('Notification Flow (Integration)', () => {
|
|
|
135
148
|
escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
|
|
136
149
|
},
|
|
137
150
|
});
|
|
138
|
-
|
|
139
|
-
|
|
151
|
+
await notificationService.send(tenantId, user.id, {
|
|
152
|
+
type: 'readTest', priority: 'medium', payload: {},
|
|
140
153
|
});
|
|
141
|
-
await notificationService.send(notification);
|
|
142
154
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
143
155
|
const log = logs[0];
|
|
144
156
|
// Step 0: Deliver In-App
|
|
@@ -163,10 +175,9 @@ describe('Notification Flow (Integration)', () => {
|
|
|
163
175
|
// Disable InApp, Enable Email (even though not default)
|
|
164
176
|
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.InApp, false);
|
|
165
177
|
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.Email, true);
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
await notificationService.send(tenantId, user.id, {
|
|
179
|
+
type: 'prefTest', priority: 'medium', payload: {},
|
|
168
180
|
});
|
|
169
|
-
await notificationService.send(notification);
|
|
170
181
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
171
182
|
const log = logs[0];
|
|
172
183
|
// Deliver
|
|
@@ -189,10 +200,9 @@ describe('Notification Flow (Integration)', () => {
|
|
|
189
200
|
await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.WebPush, true);
|
|
190
201
|
// Disable InApp so only WebPush is attempted
|
|
191
202
|
await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.InApp, false);
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
await notificationService.send(tenantId, user.id, {
|
|
204
|
+
type: 'unknownTest', priority: 'medium', payload: {},
|
|
194
205
|
});
|
|
195
|
-
await notificationService.send(notification);
|
|
196
206
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
197
207
|
const log = logs[0];
|
|
198
208
|
// Deliver
|
|
@@ -207,10 +217,9 @@ describe('Notification Flow (Integration)', () => {
|
|
|
207
217
|
const inAppRepo = injectRepository(InAppNotification);
|
|
208
218
|
const user = await subjectService.createUser({ tenantId, email: 'manage@example.com', firstName: 'Manage', lastName: 'User' });
|
|
209
219
|
await typeService.initializeTypes({ manageTest: { label: 'Manage Test' } });
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
await notificationService.send(tenantId, user.id, {
|
|
221
|
+
type: 'manageTest', priority: 'medium', payload: {},
|
|
212
222
|
});
|
|
213
|
-
await notificationService.send(notification);
|
|
214
223
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
215
224
|
await worker.deliver(logs[0].id);
|
|
216
225
|
// List
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.106",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"peerDependencies": {
|
|
151
151
|
"@genkit-ai/google-genai": "^1.28",
|
|
152
152
|
"@google-cloud/storage": "^7.18",
|
|
153
|
-
"@google/genai": "^1.
|
|
153
|
+
"@google/genai": "^1.39",
|
|
154
154
|
"@toon-format/toon": "^2.1.0",
|
|
155
155
|
"@tstdl/angular": "^0.93",
|
|
156
156
|
"@zxcvbn-ts/core": "^3.0",
|
|
@@ -164,12 +164,12 @@
|
|
|
164
164
|
"minio": "^8.0",
|
|
165
165
|
"mjml": "^4.18",
|
|
166
166
|
"nodemailer": "^7.0",
|
|
167
|
-
"pg": "^8.
|
|
167
|
+
"pg": "^8.18",
|
|
168
168
|
"playwright": "^1.58",
|
|
169
169
|
"preact": "^10.28",
|
|
170
170
|
"preact-render-to-string": "^6.6",
|
|
171
171
|
"sharp": "^0.34",
|
|
172
|
-
"undici": "^7.
|
|
172
|
+
"undici": "^7.20",
|
|
173
173
|
"urlpattern-polyfill": "^10.1",
|
|
174
174
|
"zod": "^3.25"
|
|
175
175
|
},
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"concurrently": "9.2",
|
|
192
192
|
"drizzle-kit": "0.31",
|
|
193
193
|
"eslint": "9.39",
|
|
194
|
-
"globals": "17.
|
|
194
|
+
"globals": "17.3",
|
|
195
195
|
"tsc-alias": "1.8",
|
|
196
196
|
"typedoc-github-wiki-theme": "2.1",
|
|
197
197
|
"typedoc-plugin-markdown": "4.9",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FeedableAsyncIterable } from '../utils/feedable-async-iterable.js';
|
|
2
|
+
export async function* toAsyncIterable(observable) {
|
|
3
|
+
const feed = new FeedableAsyncIterable();
|
|
4
|
+
const subscription = observable.subscribe({
|
|
5
|
+
next: (value) => feed.feed(value),
|
|
6
|
+
error: (err) => feed.throw(err),
|
|
7
|
+
complete: () => feed.end(),
|
|
8
|
+
});
|
|
9
|
+
try {
|
|
10
|
+
yield* feed;
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
subscription.unsubscribe();
|
|
14
|
+
}
|
|
15
|
+
}
|
package/rxjs-utils/index.d.ts
CHANGED
package/rxjs-utils/index.js
CHANGED
package/test5.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
1
|
import './polyfills.js';
|
|
2
|
+
import type { NotificationDefinitionMap } from './notification/index.js';
|
|
3
|
+
export type Notifications = NotificationDefinitionMap<{
|
|
4
|
+
'analysis-started': {
|
|
5
|
+
payload: {
|
|
6
|
+
analysisId: string;
|
|
7
|
+
};
|
|
8
|
+
view: {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
'analysis-completed': Record<never, never>;
|
|
13
|
+
}>;
|
package/test5.js
CHANGED
|
@@ -3,18 +3,18 @@ import { Application } from './application/application.js';
|
|
|
3
3
|
import { provideModule, provideSignalHandler } from './application/index.js';
|
|
4
4
|
import { inject } from './injector/inject.js';
|
|
5
5
|
import { JsonLogFormatter, PrettyPrintLogFormatter } from './logger/index.js';
|
|
6
|
-
import { Logger } from './logger/logger.js';
|
|
7
6
|
import { provideConsoleLogTransport } from './logger/transports/console.js';
|
|
7
|
+
import { NotificationService } from './notification/server/index.js';
|
|
8
8
|
async function main(_cancellationSignal) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const notificationService = inject((NotificationService));
|
|
10
|
+
const notificationApi = new null();
|
|
11
|
+
const notification$ = (await notificationApi.stream());
|
|
12
|
+
const tenantId = 'test-tenant';
|
|
13
|
+
const userId = 'user-123';
|
|
14
|
+
await notificationService.send(tenantId, userId, {
|
|
15
|
+
type: 'analysis-started',
|
|
16
|
+
payload: { analysisId: 'analysis-456' },
|
|
17
|
+
});
|
|
18
18
|
}
|
|
19
19
|
Application.run('Test', [
|
|
20
20
|
provideConsoleLogTransport(PrettyPrintLogFormatter),
|