@tstdl/base 0.93.107 → 0.93.109
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/data-structures/collection.d.ts +1 -1
- package/data-structures/collection.js +2 -1
- package/notification/models/in-app-notification.model.js +1 -1
- package/notification/models/notification-log.model.d.ts +6 -7
- package/notification/models/notification-log.model.js +17 -12
- package/notification/server/drizzle/{0000_ancient_hellion.sql → 0000_oval_rage.sql} +5 -6
- package/notification/server/drizzle/meta/0000_snapshot.json +62 -42
- package/notification/server/drizzle/meta/_journal.json +2 -2
- package/notification/server/services/notification-delivery.worker.js +2 -2
- package/notification/server/services/notification.service.d.ts +1 -1
- package/notification/server/services/notification.service.js +6 -4
- package/notification/tests/notification-flow.test.js +8 -7
- package/package.json +4 -3
- package/process/spawn.d.ts +3 -3
- package/process/spawn.js +1 -1
- package/task-queue/postgres/task-queue.d.ts +1 -0
- package/task-queue/postgres/task-queue.js +4 -1
- package/task-queue/task-context.d.ts +1 -0
- package/task-queue/task-context.js +3 -0
- package/task-queue/task-queue.d.ts +7 -0
- package/task-queue/tests/worker.test.js +29 -0
- package/test5.d.ts +0 -12
- package/test5.js +9 -13
- package/typst/index.d.ts +1 -0
- package/typst/index.js +1 -0
- package/typst/render.d.ts +23 -0
- package/typst/render.js +32 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Observable } from 'rxjs';
|
|
2
2
|
import type { ToJson } from '../interfaces.js';
|
|
3
|
-
import { type Signal } from '../signals/
|
|
3
|
+
import { type Signal } from '../signals/api.js';
|
|
4
4
|
export declare abstract class Collection<T, TItemsIterator extends IterableIterator<any>, TThis extends Collection<T, TItemsIterator, TThis> = Collection<T, TItemsIterator, any>> implements Iterable<T>, ToJson {
|
|
5
5
|
private readonly sizeSubject;
|
|
6
6
|
private readonly changeSubject;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BehaviorSubject, Subject, distinctUntilChanged, filter, firstValueFrom, map, startWith } from 'rxjs';
|
|
2
|
-
import {
|
|
2
|
+
import { untracked } from '../signals/api.js';
|
|
3
|
+
import { toLazySignal } from '../signals/to-lazy-signal.js';
|
|
3
4
|
import { lazyProperty } from '../utils/object/lazy-property.js';
|
|
4
5
|
export class Collection {
|
|
5
6
|
sizeSubject = new BehaviorSubject(0);
|
|
@@ -40,7 +40,7 @@ __decorate([
|
|
|
40
40
|
InAppNotification = __decorate([
|
|
41
41
|
NotificationTable({ name: 'in_app' }),
|
|
42
42
|
ForeignKey(() => NotificationLogEntity, ['tenantId', 'logId', 'userId'], ['tenantId', 'id', 'userId']),
|
|
43
|
-
Index(['tenantId', 'userId', '
|
|
43
|
+
Index(['tenantId', 'userId', 'logId'], { where: () => ({ archiveTimestamp: null }) })
|
|
44
44
|
], InAppNotification);
|
|
45
45
|
export { InAppNotification };
|
|
46
46
|
export class InAppNotificationView extends InAppNotification {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { Merge } from 'type-fest';
|
|
1
2
|
import { type EnumType } from '../../enumeration/index.js';
|
|
2
|
-
import { type Json,
|
|
3
|
+
import { type Json, TenantBaseEntity, type Timestamp, type Uuid } from '../../orm/index.js';
|
|
3
4
|
import type { ObjectLiteral } from '../../types/types.js';
|
|
4
5
|
export declare const NotificationChannel: {
|
|
5
6
|
readonly InApp: "in-app";
|
|
@@ -44,19 +45,17 @@ export type NotificationLog<Definitions extends NotificationDefinitionMap = Noti
|
|
|
44
45
|
export type NotificationLogView<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> = {
|
|
45
46
|
[Type in NotificationTypes<Definitions>]: Omit<NotificationLogEntity<Definitions, Type>, 'type' | 'payload'> & {
|
|
46
47
|
type: Type;
|
|
47
|
-
payload: NotificationPayload<Definitions, Type
|
|
48
|
-
view: NotificationView<Definitions, Type>;
|
|
48
|
+
payload: Merge<NotificationPayload<Definitions, Type>, NotificationView<Definitions, Type>>;
|
|
49
49
|
};
|
|
50
50
|
}[NotificationTypes<Definitions>];
|
|
51
|
-
export declare class NotificationLogEntity<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> extends
|
|
51
|
+
export declare class NotificationLogEntity<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> extends TenantBaseEntity {
|
|
52
52
|
static readonly entityName = "NotificationLog";
|
|
53
53
|
userId: Uuid;
|
|
54
54
|
type: Type;
|
|
55
|
+
timestamp: Timestamp;
|
|
55
56
|
priority: NotificationPriority;
|
|
56
57
|
status: NotificationStatus;
|
|
57
58
|
currentStep: number;
|
|
59
|
+
triggerSubjectId: Uuid;
|
|
58
60
|
payload: Json<NotificationPayload<Definitions, Type>> | null;
|
|
59
61
|
}
|
|
60
|
-
export declare class NotificationLogViewEntity<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap, Type extends NotificationTypes<Definitions> = NotificationTypes<Definitions>> extends NotificationLogEntity<Definitions, Type> {
|
|
61
|
-
view?: unknown;
|
|
62
|
-
}
|
|
@@ -7,10 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { User } from '../../authentication/
|
|
10
|
+
import { Subject, User } from '../../authentication/index.js';
|
|
11
11
|
import { defineEnum } from '../../enumeration/index.js';
|
|
12
|
-
import { JsonProperty, Reference,
|
|
13
|
-
import { Enumeration, Integer, StringProperty
|
|
12
|
+
import { Index, JsonProperty, Reference, TenantBaseEntity, TenantReference, TimestampProperty, Unique, UuidProperty } from '../../orm/index.js';
|
|
13
|
+
import { Enumeration, Integer, StringProperty } 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', {
|
|
@@ -31,13 +31,15 @@ export const NotificationStatus = defineEnum('NotificationStatus', {
|
|
|
31
31
|
Read: 'read',
|
|
32
32
|
Failed: 'failed',
|
|
33
33
|
});
|
|
34
|
-
let NotificationLogEntity = class NotificationLogEntity extends
|
|
34
|
+
let NotificationLogEntity = class NotificationLogEntity extends TenantBaseEntity {
|
|
35
35
|
static entityName = 'NotificationLog';
|
|
36
36
|
userId;
|
|
37
37
|
type;
|
|
38
|
+
timestamp;
|
|
38
39
|
priority;
|
|
39
40
|
status;
|
|
40
41
|
currentStep;
|
|
42
|
+
triggerSubjectId;
|
|
41
43
|
payload;
|
|
42
44
|
};
|
|
43
45
|
__decorate([
|
|
@@ -50,6 +52,10 @@ __decorate([
|
|
|
50
52
|
StringProperty(),
|
|
51
53
|
__metadata("design:type", String)
|
|
52
54
|
], NotificationLogEntity.prototype, "type", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
TimestampProperty(),
|
|
57
|
+
__metadata("design:type", Number)
|
|
58
|
+
], NotificationLogEntity.prototype, "timestamp", void 0);
|
|
53
59
|
__decorate([
|
|
54
60
|
Enumeration(NotificationPriority),
|
|
55
61
|
__metadata("design:type", String)
|
|
@@ -62,19 +68,18 @@ __decorate([
|
|
|
62
68
|
Integer(),
|
|
63
69
|
__metadata("design:type", Number)
|
|
64
70
|
], NotificationLogEntity.prototype, "currentStep", void 0);
|
|
71
|
+
__decorate([
|
|
72
|
+
TenantReference(() => Subject),
|
|
73
|
+
UuidProperty(),
|
|
74
|
+
__metadata("design:type", String)
|
|
75
|
+
], NotificationLogEntity.prototype, "triggerSubjectId", void 0);
|
|
65
76
|
__decorate([
|
|
66
77
|
JsonProperty({ nullable: true }),
|
|
67
78
|
__metadata("design:type", Object)
|
|
68
79
|
], NotificationLogEntity.prototype, "payload", void 0);
|
|
69
80
|
NotificationLogEntity = __decorate([
|
|
70
81
|
NotificationTable({ name: 'log' }),
|
|
71
|
-
Unique(['tenantId', 'id', 'userId'])
|
|
82
|
+
Unique(['tenantId', 'id', 'userId']),
|
|
83
|
+
Index(['tenantId', 'userId', 'timestamp'])
|
|
72
84
|
], NotificationLogEntity);
|
|
73
85
|
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);
|
|
@@ -16,15 +16,12 @@ CREATE TABLE "notification"."log" (
|
|
|
16
16
|
"tenant_id" uuid NOT NULL,
|
|
17
17
|
"user_id" uuid NOT NULL,
|
|
18
18
|
"type" text NOT NULL,
|
|
19
|
+
"timestamp" timestamp with time zone NOT NULL,
|
|
19
20
|
"priority" "notification"."notification_priority" NOT NULL,
|
|
20
21
|
"status" "notification"."notification_status" NOT NULL,
|
|
21
22
|
"current_step" integer NOT NULL,
|
|
23
|
+
"trigger_subject_id" uuid NOT NULL,
|
|
22
24
|
"payload" jsonb,
|
|
23
|
-
"revision" integer NOT NULL,
|
|
24
|
-
"revision_timestamp" timestamp with time zone NOT NULL,
|
|
25
|
-
"create_timestamp" timestamp with time zone NOT NULL,
|
|
26
|
-
"delete_timestamp" timestamp with time zone,
|
|
27
|
-
"attributes" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
28
25
|
CONSTRAINT "log_tenant_id_id_pk" PRIMARY KEY("tenant_id","id"),
|
|
29
26
|
CONSTRAINT "log_tenant_id_id_user_id_unique" UNIQUE("tenant_id","id","user_id")
|
|
30
27
|
);
|
|
@@ -79,7 +76,9 @@ ALTER TABLE "notification"."in_app" ADD CONSTRAINT "in_app_id_user_fkey" FOREIGN
|
|
|
79
76
|
ALTER TABLE "notification"."in_app" ADD CONSTRAINT "in_app_tenantId_logId_userId_log_fkey" FOREIGN KEY ("tenant_id","log_id","user_id") REFERENCES "notification"."log"("tenant_id","id","user_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
80
77
|
ALTER TABLE "notification"."log" ADD CONSTRAINT "log_type_type_key_fk" FOREIGN KEY ("type") REFERENCES "notification"."type"("key") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
81
78
|
ALTER TABLE "notification"."log" ADD CONSTRAINT "log_id_user_fkey" FOREIGN KEY ("tenant_id","user_id") REFERENCES "authentication"."user"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
79
|
+
ALTER TABLE "notification"."log" ADD CONSTRAINT "log_id_subject_fkey" FOREIGN KEY ("tenant_id","trigger_subject_id") REFERENCES "authentication"."subject"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
82
80
|
ALTER TABLE "notification"."preference" ADD CONSTRAINT "preference_type_type_key_fk" FOREIGN KEY ("type") REFERENCES "notification"."type"("key") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
83
81
|
ALTER TABLE "notification"."preference" ADD CONSTRAINT "preference_id_user_fkey" FOREIGN KEY ("tenant_id","user_id") REFERENCES "authentication"."user"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
84
82
|
ALTER TABLE "notification"."web_push_subscription" ADD CONSTRAINT "web_push_subscription_id_user_fkey" FOREIGN KEY ("tenant_id","user_id") REFERENCES "authentication"."user"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
85
|
-
CREATE INDEX "
|
|
83
|
+
CREATE INDEX "in_app_tenant_id_user_id_log_id_idx" ON "notification"."in_app" USING btree ("tenant_id","user_id","log_id") WHERE "notification"."in_app"."archive_timestamp" is null;--> statement-breakpoint
|
|
84
|
+
CREATE INDEX "log_tenant_id_user_id_timestamp_idx" ON "notification"."log" USING btree ("tenant_id","user_id","timestamp");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "90cd6144-d455-4a13-8148-ca119f8bfe79",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"indexes": {
|
|
50
|
-
"
|
|
51
|
-
"name": "
|
|
50
|
+
"in_app_tenant_id_user_id_log_id_idx": {
|
|
51
|
+
"name": "in_app_tenant_id_user_id_log_id_idx",
|
|
52
52
|
"columns": [
|
|
53
53
|
{
|
|
54
54
|
"expression": "tenant_id",
|
|
@@ -63,19 +63,14 @@
|
|
|
63
63
|
"nulls": "last"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
|
-
"expression": "
|
|
67
|
-
"isExpression": false,
|
|
68
|
-
"asc": true,
|
|
69
|
-
"nulls": "last"
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"expression": "archive_timestamp",
|
|
66
|
+
"expression": "log_id",
|
|
73
67
|
"isExpression": false,
|
|
74
68
|
"asc": true,
|
|
75
69
|
"nulls": "last"
|
|
76
70
|
}
|
|
77
71
|
],
|
|
78
72
|
"isUnique": false,
|
|
73
|
+
"where": "\"notification\".\"in_app\".\"archive_timestamp\" is null",
|
|
79
74
|
"concurrently": false,
|
|
80
75
|
"method": "btree",
|
|
81
76
|
"with": {}
|
|
@@ -160,6 +155,12 @@
|
|
|
160
155
|
"primaryKey": false,
|
|
161
156
|
"notNull": true
|
|
162
157
|
},
|
|
158
|
+
"timestamp": {
|
|
159
|
+
"name": "timestamp",
|
|
160
|
+
"type": "timestamp with time zone",
|
|
161
|
+
"primaryKey": false,
|
|
162
|
+
"notNull": true
|
|
163
|
+
},
|
|
163
164
|
"priority": {
|
|
164
165
|
"name": "priority",
|
|
165
166
|
"type": "notification_priority",
|
|
@@ -180,45 +181,48 @@
|
|
|
180
181
|
"primaryKey": false,
|
|
181
182
|
"notNull": true
|
|
182
183
|
},
|
|
183
|
-
"
|
|
184
|
-
"name": "
|
|
185
|
-
"type": "
|
|
186
|
-
"primaryKey": false,
|
|
187
|
-
"notNull": false
|
|
188
|
-
},
|
|
189
|
-
"revision": {
|
|
190
|
-
"name": "revision",
|
|
191
|
-
"type": "integer",
|
|
192
|
-
"primaryKey": false,
|
|
193
|
-
"notNull": true
|
|
194
|
-
},
|
|
195
|
-
"revision_timestamp": {
|
|
196
|
-
"name": "revision_timestamp",
|
|
197
|
-
"type": "timestamp with time zone",
|
|
198
|
-
"primaryKey": false,
|
|
199
|
-
"notNull": true
|
|
200
|
-
},
|
|
201
|
-
"create_timestamp": {
|
|
202
|
-
"name": "create_timestamp",
|
|
203
|
-
"type": "timestamp with time zone",
|
|
184
|
+
"trigger_subject_id": {
|
|
185
|
+
"name": "trigger_subject_id",
|
|
186
|
+
"type": "uuid",
|
|
204
187
|
"primaryKey": false,
|
|
205
188
|
"notNull": true
|
|
206
189
|
},
|
|
207
|
-
"
|
|
208
|
-
"name": "
|
|
209
|
-
"type": "timestamp with time zone",
|
|
210
|
-
"primaryKey": false,
|
|
211
|
-
"notNull": false
|
|
212
|
-
},
|
|
213
|
-
"attributes": {
|
|
214
|
-
"name": "attributes",
|
|
190
|
+
"payload": {
|
|
191
|
+
"name": "payload",
|
|
215
192
|
"type": "jsonb",
|
|
216
193
|
"primaryKey": false,
|
|
217
|
-
"notNull":
|
|
218
|
-
|
|
194
|
+
"notNull": false
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"indexes": {
|
|
198
|
+
"log_tenant_id_user_id_timestamp_idx": {
|
|
199
|
+
"name": "log_tenant_id_user_id_timestamp_idx",
|
|
200
|
+
"columns": [
|
|
201
|
+
{
|
|
202
|
+
"expression": "tenant_id",
|
|
203
|
+
"isExpression": false,
|
|
204
|
+
"asc": true,
|
|
205
|
+
"nulls": "last"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"expression": "user_id",
|
|
209
|
+
"isExpression": false,
|
|
210
|
+
"asc": true,
|
|
211
|
+
"nulls": "last"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"expression": "timestamp",
|
|
215
|
+
"isExpression": false,
|
|
216
|
+
"asc": true,
|
|
217
|
+
"nulls": "last"
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"isUnique": false,
|
|
221
|
+
"concurrently": false,
|
|
222
|
+
"method": "btree",
|
|
223
|
+
"with": {}
|
|
219
224
|
}
|
|
220
225
|
},
|
|
221
|
-
"indexes": {},
|
|
222
226
|
"foreignKeys": {
|
|
223
227
|
"log_type_type_key_fk": {
|
|
224
228
|
"name": "log_type_type_key_fk",
|
|
@@ -249,6 +253,22 @@
|
|
|
249
253
|
],
|
|
250
254
|
"onDelete": "no action",
|
|
251
255
|
"onUpdate": "no action"
|
|
256
|
+
},
|
|
257
|
+
"log_id_subject_fkey": {
|
|
258
|
+
"name": "log_id_subject_fkey",
|
|
259
|
+
"tableFrom": "log",
|
|
260
|
+
"tableTo": "subject",
|
|
261
|
+
"schemaTo": "authentication",
|
|
262
|
+
"columnsFrom": [
|
|
263
|
+
"tenant_id",
|
|
264
|
+
"trigger_subject_id"
|
|
265
|
+
],
|
|
266
|
+
"columnsTo": [
|
|
267
|
+
"tenant_id",
|
|
268
|
+
"id"
|
|
269
|
+
],
|
|
270
|
+
"onDelete": "no action",
|
|
271
|
+
"onUpdate": "no action"
|
|
252
272
|
}
|
|
253
273
|
},
|
|
254
274
|
"compositePrimaryKeys": {
|
|
@@ -80,7 +80,7 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
80
80
|
for (const channel of enabledChannels) {
|
|
81
81
|
// TODO: what if an error occurs here? partial delivery? Should we use tasks to allow retrying individual channels?
|
|
82
82
|
const [viewData] = await this.#notificationAncillaryService.getViewData([notification]);
|
|
83
|
-
await this.sendToChannel({ ...notification,
|
|
83
|
+
await this.sendToChannel({ ...notification, payload: { ...notification.payload, ...viewData } }, channel, tx);
|
|
84
84
|
}
|
|
85
85
|
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { status: NotificationStatus.Sent, currentStep: 1 });
|
|
86
86
|
if (isNotNull(type.escalations) && (type.escalations.length > 0)) {
|
|
@@ -99,7 +99,7 @@ let NotificationDeliveryWorker = class NotificationDeliveryWorker extends Transa
|
|
|
99
99
|
return TaskProcessResult.Complete();
|
|
100
100
|
}
|
|
101
101
|
const [viewData] = await this.#notificationAncillaryService.getViewData([notification]);
|
|
102
|
-
await this.sendToChannel({ ...notification,
|
|
102
|
+
await this.sendToChannel({ ...notification, payload: { ...notification.payload, ...viewData } }, rule.channel, tx);
|
|
103
103
|
await this.#notificationLogRepository.withTransaction(tx).update(notificationId, { currentStep: step + 1 });
|
|
104
104
|
if (step < type.escalations.length) {
|
|
105
105
|
const nextRule = type.escalations[step];
|
|
@@ -4,7 +4,7 @@ import type { TypedOmit } from '../../../types/types.js';
|
|
|
4
4
|
import { NotificationPreference, type InAppNotificationView, type NotificationChannel, type NotificationDefinitionMap, type NotificationLog } from '../../models/index.js';
|
|
5
5
|
export declare class NotificationService<Definitions extends NotificationDefinitionMap = NotificationDefinitionMap> extends Transactional {
|
|
6
6
|
#private;
|
|
7
|
-
send(tenantId: string, userId: string, notification: TypedOmit<NewEntity<NotificationLog<Definitions>>, 'tenantId' | 'id' | 'userId' | '
|
|
7
|
+
send(tenantId: string, userId: string, notification: TypedOmit<NewEntity<NotificationLog<Definitions>>, 'tenantId' | 'id' | 'userId' | 'timestamp' | 'status' | 'currentStep' | 'priority'> & Partial<Pick<NotificationLog<Definitions>, 'priority'>>): Promise<void>;
|
|
8
8
|
listInApp(tenantId: string, userId: string, options?: {
|
|
9
9
|
limit?: number;
|
|
10
10
|
offset?: number;
|
|
@@ -25,6 +25,7 @@ let NotificationService = class NotificationService extends Transactional {
|
|
|
25
25
|
const notificationToInsert = {
|
|
26
26
|
tenantId,
|
|
27
27
|
userId,
|
|
28
|
+
timestamp: TRANSACTION_TIMESTAMP,
|
|
28
29
|
priority: NotificationPriority.Medium,
|
|
29
30
|
...notification,
|
|
30
31
|
status: NotificationStatus.Pending,
|
|
@@ -41,11 +42,11 @@ let NotificationService = class NotificationService extends Transactional {
|
|
|
41
42
|
inApp: inAppNotification,
|
|
42
43
|
})
|
|
43
44
|
.from(notificationLog)
|
|
44
|
-
.innerJoin(inAppNotification, eq(inAppNotification.logId, notificationLog.id))
|
|
45
|
-
.where(and(eq(
|
|
45
|
+
.innerJoin(inAppNotification, and(eq(inAppNotification.tenantId, notificationLog.tenantId), eq(inAppNotification.userId, notificationLog.userId), eq(inAppNotification.logId, notificationLog.id)))
|
|
46
|
+
.where(and(eq(notificationLog.tenantId, tenantId), eq(notificationLog.userId, userId), options.includeArchived ? undefined : isNull(inAppNotification.archiveTimestamp)))
|
|
46
47
|
.limit(options.limit ?? 50)
|
|
47
48
|
.offset(options.offset ?? 0)
|
|
48
|
-
.orderBy(desc(notificationLog.
|
|
49
|
+
.orderBy(desc(notificationLog.timestamp));
|
|
49
50
|
const inAppRows = rows.map((row) => row.inApp);
|
|
50
51
|
const notificationRows = rows.map((row) => row.notification);
|
|
51
52
|
const notificationEntities = await this.#notificationLogRepository.mapManyToEntity(notificationRows);
|
|
@@ -54,7 +55,8 @@ let NotificationService = class NotificationService extends Transactional {
|
|
|
54
55
|
const views = notificationEntities.map((notification, index) => {
|
|
55
56
|
const inApp = inAppEntities[index];
|
|
56
57
|
const viewData = notificationViewDatas[index];
|
|
57
|
-
|
|
58
|
+
const payload = { ...notification.payload, ...viewData };
|
|
59
|
+
return toInAppNotificationView(inApp, { ...notification, payload });
|
|
58
60
|
});
|
|
59
61
|
return views;
|
|
60
62
|
}
|
|
@@ -86,6 +86,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
86
86
|
await notificationService.send(tenantId, user.id, {
|
|
87
87
|
type: 'test',
|
|
88
88
|
priority: 'high',
|
|
89
|
+
triggerSubjectId: user.id,
|
|
89
90
|
payload: { message: 'Hello', testField: 'Test Value' },
|
|
90
91
|
});
|
|
91
92
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
@@ -123,13 +124,13 @@ describe('Notification Flow (Integration)', () => {
|
|
|
123
124
|
},
|
|
124
125
|
});
|
|
125
126
|
await notificationService.send(tenantId, user.id, {
|
|
126
|
-
type: 'throttled', priority: 'medium', payload: {},
|
|
127
|
+
type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
127
128
|
});
|
|
128
129
|
await notificationService.send(tenantId, user.id, {
|
|
129
|
-
type: 'throttled', priority: 'medium', payload: {},
|
|
130
|
+
type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
130
131
|
});
|
|
131
132
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
132
|
-
logs.sort((a, b) => Number(a.
|
|
133
|
+
logs.sort((a, b) => Number(a.timestamp) - Number(b.timestamp));
|
|
133
134
|
// First one should pass
|
|
134
135
|
const result1 = await worker.deliver(logs[0].id);
|
|
135
136
|
expect(result1.payload.action).toBe('complete'); // No escalations
|
|
@@ -149,7 +150,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
149
150
|
},
|
|
150
151
|
});
|
|
151
152
|
await notificationService.send(tenantId, user.id, {
|
|
152
|
-
type: 'readTest', priority: 'medium', payload: {},
|
|
153
|
+
type: 'readTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
153
154
|
});
|
|
154
155
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
155
156
|
const log = logs[0];
|
|
@@ -176,7 +177,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
176
177
|
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.InApp, false);
|
|
177
178
|
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.Email, true);
|
|
178
179
|
await notificationService.send(tenantId, user.id, {
|
|
179
|
-
type: 'prefTest', priority: 'medium', payload: {},
|
|
180
|
+
type: 'prefTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
180
181
|
});
|
|
181
182
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
182
183
|
const log = logs[0];
|
|
@@ -201,7 +202,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
201
202
|
// Disable InApp so only WebPush is attempted
|
|
202
203
|
await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.InApp, false);
|
|
203
204
|
await notificationService.send(tenantId, user.id, {
|
|
204
|
-
type: 'unknownTest', priority: 'medium', payload: {},
|
|
205
|
+
type: 'unknownTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
205
206
|
});
|
|
206
207
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
207
208
|
const log = logs[0];
|
|
@@ -218,7 +219,7 @@ describe('Notification Flow (Integration)', () => {
|
|
|
218
219
|
const user = await subjectService.createUser({ tenantId, email: 'manage@example.com', firstName: 'Manage', lastName: 'User' });
|
|
219
220
|
await typeService.initializeTypes({ manageTest: { label: 'Manage Test' } });
|
|
220
221
|
await notificationService.send(tenantId, user.id, {
|
|
221
|
-
type: 'manageTest', priority: 'medium', payload: {},
|
|
222
|
+
type: 'manageTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
222
223
|
});
|
|
223
224
|
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
224
225
|
await worker.deliver(logs[0].id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.109",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -101,8 +101,6 @@
|
|
|
101
101
|
"./pool": "./pool/index.js",
|
|
102
102
|
"./process": "./process/index.js",
|
|
103
103
|
"./promise": "./promise/index.js",
|
|
104
|
-
"./task-queue": "./task-queue/index.js",
|
|
105
|
-
"./task-queue/postgres": "./task-queue/postgres/index.js",
|
|
106
104
|
"./random": "./random/index.js",
|
|
107
105
|
"./rate-limit": "./rate-limit/index.js",
|
|
108
106
|
"./rate-limit/postgres": "./rate-limit/postgres/index.js",
|
|
@@ -120,6 +118,8 @@
|
|
|
120
118
|
"./signals": "./signals/index.js",
|
|
121
119
|
"./signals/implementation": "./signals/implementation/index.js",
|
|
122
120
|
"./sse": "./sse/index.js",
|
|
121
|
+
"./task-queue": "./task-queue/index.js",
|
|
122
|
+
"./task-queue/postgres": "./task-queue/postgres/index.js",
|
|
123
123
|
"./templates": "./templates/index.js",
|
|
124
124
|
"./templates/providers": "./templates/providers/index.js",
|
|
125
125
|
"./templates/renderers": "./templates/renderers/index.js",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"./text": "./text/index.js",
|
|
129
129
|
"./threading": "./threading/index.js",
|
|
130
130
|
"./types": "./types/index.js",
|
|
131
|
+
"./typst": "./typst/index.js",
|
|
131
132
|
"./unit-test": "./unit-test/index.js",
|
|
132
133
|
"./utils": "./utils/index.js",
|
|
133
134
|
"./utils/array": "./utils/array/index.js",
|
package/process/spawn.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
-
import type { Record } from '../types/types.js';
|
|
2
|
+
import type { Record, TypedOmit } from '../types/types.js';
|
|
3
3
|
type WaitReadResultFormat = 'string' | 'binary';
|
|
4
4
|
type WaitReadResultFormatType<T extends WaitReadResultFormat> = T extends 'string' ? string : Uint8Array<ArrayBuffer>;
|
|
5
5
|
export type WaitOptions = {
|
|
@@ -35,9 +35,9 @@ export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array> & {
|
|
|
35
35
|
export declare function spawnWaitCommand(command: string, args?: string[], options?: SpawnOptions & WaitOptions): Promise<WaitResult>;
|
|
36
36
|
export declare function spawnWaitCommand(command: string, options?: SpawnOptions & WaitOptions): Promise<WaitResult>;
|
|
37
37
|
/** spwans a command, waits for it to complete and reads its output */
|
|
38
|
-
export declare function spawnWaitReadCommand<F extends WaitReadResultFormat>(format: F, command: string, args?: string[], options?: SpawnOptions & WaitOptions): Promise<WaitReadResult<F>>;
|
|
38
|
+
export declare function spawnWaitReadCommand<F extends WaitReadResultFormat>(format: F, command: string, args?: string[], options?: TypedOmit<SpawnOptions, 'arguments'> & WaitOptions): Promise<WaitReadResult<F>>;
|
|
39
39
|
export declare function spawnWaitReadCommand<F extends WaitReadResultFormat>(format: F, command: string, options?: SpawnOptions & WaitOptions): Promise<WaitReadResult<F>>;
|
|
40
40
|
/** Spawns a command as a child process. */
|
|
41
|
-
export declare function spawnCommand(command: string, args?: string[], options?: SpawnOptions): Promise<SpawnCommandResult>;
|
|
41
|
+
export declare function spawnCommand(command: string, args?: string[], options?: TypedOmit<SpawnOptions, 'arguments'>): Promise<SpawnCommandResult>;
|
|
42
42
|
export declare function spawnCommand(command: string, options?: SpawnOptions): Promise<SpawnCommandResult>;
|
|
43
43
|
export {};
|
package/process/spawn.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function spawnWaitCommand(command, argsOrOptions, optionsOrNothing)
|
|
|
10
10
|
return await process.wait({ throwOnNonZeroExitCode: options?.throwOnNonZeroExitCode });
|
|
11
11
|
}
|
|
12
12
|
export async function spawnWaitReadCommand(format, command, argsOrOptions, optionsOrNothing) {
|
|
13
|
-
const [args, options] = isArray(argsOrOptions) ? [argsOrOptions, optionsOrNothing] : [
|
|
13
|
+
const [args, options] = isArray(argsOrOptions) ? [argsOrOptions, optionsOrNothing] : [argsOrOptions?.arguments, argsOrOptions];
|
|
14
14
|
const process = await spawnCommand(command, args, options);
|
|
15
15
|
return await process.waitRead(format, { throwOnNonZeroExitCode: options?.throwOnNonZeroExitCode });
|
|
16
16
|
}
|
|
@@ -116,6 +116,7 @@ export declare class PostgresTaskQueue<Definitions extends TaskDefinitionMap = T
|
|
|
116
116
|
resetState?: boolean;
|
|
117
117
|
transaction?: Transaction;
|
|
118
118
|
}): Promise<void>;
|
|
119
|
+
notify(): void;
|
|
119
120
|
getConsumer<Type extends TaskTypes<Definitions>>(cancellationSignal: CancellationSignal, options?: {
|
|
120
121
|
forceDequeue?: boolean;
|
|
121
122
|
types?: Type[];
|
|
@@ -60,6 +60,7 @@ import { and, asc, count, eq, gt, gte, inArray, lt, lte, notInArray, or, sql, is
|
|
|
60
60
|
import { merge } from 'rxjs';
|
|
61
61
|
import { CancellationSignal } from '../../cancellation/index.js';
|
|
62
62
|
import { CircuitBreaker, CircuitBreakerState } from '../../circuit-breaker/index.js';
|
|
63
|
+
import { serializeError } from '../../errors/index.js';
|
|
63
64
|
import { afterResolve, inject, provide, Singleton } from '../../injector/index.js';
|
|
64
65
|
import { Logger } from '../../logger/index.js';
|
|
65
66
|
import { MessageBus } from '../../message-bus/index.js';
|
|
@@ -69,7 +70,6 @@ import { RateLimiter } from '../../rate-limit/index.js';
|
|
|
69
70
|
import { createArray, distinct, toArray } from '../../utils/array/array.js';
|
|
70
71
|
import { digest } from '../../utils/cryptography.js';
|
|
71
72
|
import { currentTimestamp } from '../../utils/date-time.js';
|
|
72
|
-
import { serializeError } from '../../errors/index.js';
|
|
73
73
|
import { cancelableTimeout } from '../../utils/timing.js';
|
|
74
74
|
import { isDefined, isNotNull, isNull, isString, isUndefined } from '../../utils/type-guards.js';
|
|
75
75
|
import { millisecondsPerSecond } from '../../utils/units.js';
|
|
@@ -987,6 +987,9 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
987
987
|
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.id, id), or(eq(taskTable.status, TaskStatus.Pending), eq(taskTable.status, TaskStatus.Completed), eq(taskTable.status, TaskStatus.Cancelled), eq(taskTable.status, TaskStatus.Dead), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP))))
|
|
988
988
|
.execute();
|
|
989
989
|
}
|
|
990
|
+
notify() {
|
|
991
|
+
this.#messageBus.publishAndForget();
|
|
992
|
+
}
|
|
990
993
|
async *getConsumer(cancellationSignal, options) {
|
|
991
994
|
const continue$ = merge(this.#messageBus.allMessages$, cancellationSignal);
|
|
992
995
|
while (cancellationSignal.isUnset) {
|
|
@@ -16,6 +16,7 @@ export declare class TaskContext<Definitions extends TaskDefinitionMap, Type ext
|
|
|
16
16
|
get state(): TaskState<Definitions, Type> | null;
|
|
17
17
|
get attempt(): number;
|
|
18
18
|
get triesLeft(): number;
|
|
19
|
+
get isFinalAttempt(): boolean;
|
|
19
20
|
get signal(): CancellationSignal;
|
|
20
21
|
get logger(): Logger;
|
|
21
22
|
complete(result?: TaskResult<Definitions, Type>, options?: {
|
|
@@ -285,6 +285,13 @@ export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap =
|
|
|
285
285
|
resetState?: boolean;
|
|
286
286
|
transaction?: Transaction;
|
|
287
287
|
}): Promise<void>;
|
|
288
|
+
/**
|
|
289
|
+
* Notifies the queue that new tasks might be available for processing.
|
|
290
|
+
* Used to wake up waiting consumers.
|
|
291
|
+
* Useful for unit tests.
|
|
292
|
+
* @internal
|
|
293
|
+
*/
|
|
294
|
+
abstract notify(): void;
|
|
288
295
|
abstract getConsumer<Type extends TaskTypes<Definitions>>(cancellationSignal: CancellationSignal, options?: {
|
|
289
296
|
forceDequeue?: boolean;
|
|
290
297
|
types?: Type[];
|
|
@@ -139,4 +139,33 @@ describe('Worker & Base Class Tests', () => {
|
|
|
139
139
|
token.set();
|
|
140
140
|
expect(executed).toBe(true);
|
|
141
141
|
});
|
|
142
|
+
it('should correctly report isFinalAttempt in TaskContext', async () => {
|
|
143
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
144
|
+
const queueName = `final-try-test-${Date.now()}`;
|
|
145
|
+
const testQueue = queueProvider.get(queueName, {
|
|
146
|
+
maxTries: 2,
|
|
147
|
+
retryDelayMinimum: 0,
|
|
148
|
+
retryDelayGrowth: 1,
|
|
149
|
+
});
|
|
150
|
+
await testQueue.enqueue('work', {});
|
|
151
|
+
testQueue.notify();
|
|
152
|
+
const token = new CancellationToken();
|
|
153
|
+
const finalAttemptValues = [];
|
|
154
|
+
testQueue.process({ cancellationSignal: token }, async (context) => {
|
|
155
|
+
finalAttemptValues.push(context.isFinalAttempt);
|
|
156
|
+
if (context.attempt === 1) {
|
|
157
|
+
throw new Error('fail first attempt');
|
|
158
|
+
}
|
|
159
|
+
return TaskProcessResult.Complete();
|
|
160
|
+
});
|
|
161
|
+
for (let i = 0; i < 100; i++) {
|
|
162
|
+
if (finalAttemptValues.length === 2)
|
|
163
|
+
break;
|
|
164
|
+
testQueue.notify();
|
|
165
|
+
await timeout(100);
|
|
166
|
+
}
|
|
167
|
+
token.set();
|
|
168
|
+
expect(finalAttemptValues).toEqual([false, true]);
|
|
169
|
+
await testQueue.clear();
|
|
170
|
+
});
|
|
142
171
|
});
|
package/test5.d.ts
CHANGED
|
@@ -1,13 +1 @@
|
|
|
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
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import './polyfills.js';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
3
|
import { Application } from './application/application.js';
|
|
3
4
|
import { provideModule, provideSignalHandler } from './application/index.js';
|
|
4
|
-
import {
|
|
5
|
-
import { JsonLogFormatter, PrettyPrintLogFormatter } from './logger/index.js';
|
|
5
|
+
import { PrettyPrintLogFormatter } from './logger/index.js';
|
|
6
6
|
import { provideConsoleLogTransport } from './logger/transports/console.js';
|
|
7
|
-
import {
|
|
7
|
+
import { renderTypst } from './typst/render.js';
|
|
8
|
+
const template = `
|
|
9
|
+
I got an ice cream for
|
|
10
|
+
\\$1.50! \\u{1f600}
|
|
11
|
+
`;
|
|
8
12
|
async function main(_cancellationSignal) {
|
|
9
|
-
const
|
|
10
|
-
|
|
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
|
-
});
|
|
13
|
+
const pdfBytes = await renderTypst(template, { format: 'docx' });
|
|
14
|
+
await writeFile('/tmp/output.docx', pdfBytes);
|
|
18
15
|
}
|
|
19
16
|
Application.run('Test', [
|
|
20
17
|
provideConsoleLogTransport(PrettyPrintLogFormatter),
|
|
21
|
-
provideConsoleLogTransport(JsonLogFormatter),
|
|
22
18
|
provideModule(main),
|
|
23
19
|
provideSignalHandler(),
|
|
24
20
|
]);
|
package/typst/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './render.js';
|
package/typst/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './render.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type TypstRenderOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* The root directory for resolving imports in the Typst source. If not specified, imports will be resolved relative to the location of the source file.
|
|
4
|
+
*/
|
|
5
|
+
root?: string;
|
|
6
|
+
/**
|
|
7
|
+
* The output format for the rendered document.
|
|
8
|
+
* Note: `docx` output uses pandoc under the hood, which does not support all Typst features.
|
|
9
|
+
* @default 'pdf'
|
|
10
|
+
*/
|
|
11
|
+
format?: 'pdf' | 'png' | 'svg' | 'html' | 'docx';
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Renders Typst source code to a file in the specified format.
|
|
15
|
+
*
|
|
16
|
+
* ## WARNING
|
|
17
|
+
* **This function should not be used with untrusted typst source, as it can lead to arbitrary code execution on the system.**
|
|
18
|
+
*
|
|
19
|
+
* Requires typst to be installed on the system.
|
|
20
|
+
* @param source
|
|
21
|
+
* @param options
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderTypst(source: string, options?: TypstRenderOptions): Promise<Uint8Array>;
|
package/typst/render.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawnCommand } from '../process/spawn.js';
|
|
2
|
+
import { decodeText } from '../utils/encoding.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders Typst source code to a file in the specified format.
|
|
5
|
+
*
|
|
6
|
+
* ## WARNING
|
|
7
|
+
* **This function should not be used with untrusted typst source, as it can lead to arbitrary code execution on the system.**
|
|
8
|
+
*
|
|
9
|
+
* Requires typst to be installed on the system.
|
|
10
|
+
* @param source
|
|
11
|
+
* @param options
|
|
12
|
+
*/
|
|
13
|
+
export async function renderTypst(source, options) {
|
|
14
|
+
const format = options?.format ?? 'pdf';
|
|
15
|
+
const command = (format == 'docx') ? 'pandoc' : 'typst';
|
|
16
|
+
const args = (format == 'docx')
|
|
17
|
+
? ['--from', 'typst', '--to', 'docx', '--output', '-', '-']
|
|
18
|
+
: ['compile', '--format', format, '-', '-'];
|
|
19
|
+
const process = await spawnCommand(command, args);
|
|
20
|
+
const [{ code, output, error }] = await Promise.all([
|
|
21
|
+
process.waitRead('binary', { throwOnNonZeroExitCode: false }),
|
|
22
|
+
process.write(source),
|
|
23
|
+
]);
|
|
24
|
+
const errorString = decodeText(error);
|
|
25
|
+
if (code !== 0) {
|
|
26
|
+
throw new Error(`
|
|
27
|
+
Typst compilation failed with exit code ${code}.\n
|
|
28
|
+
Error Output:\n${errorString}
|
|
29
|
+
`.trim());
|
|
30
|
+
}
|
|
31
|
+
return output;
|
|
32
|
+
}
|