@headless-lms/core 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/assets/file-urls.d.ts +5 -19
  2. package/dist/assets/file-urls.js +2 -23
  3. package/dist/assets/file-urls.js.map +1 -1
  4. package/dist/assets/index.d.ts +3 -3
  5. package/dist/assets/ports.d.ts +9 -10
  6. package/dist/assets/service.d.ts +10 -13
  7. package/dist/assets/service.js +41 -62
  8. package/dist/assets/service.js.map +1 -1
  9. package/dist/automations/index.d.ts +4 -4
  10. package/dist/automations/notifications/delivery.d.ts +3 -3
  11. package/dist/automations/notifications/delivery.js +1 -1
  12. package/dist/automations/notifications/delivery.js.map +1 -1
  13. package/dist/automations/ports.d.ts +2 -2
  14. package/dist/automations/service.d.ts +1 -8
  15. package/dist/automations/service.js +0 -8
  16. package/dist/automations/service.js.map +1 -1
  17. package/dist/content/service.d.ts +4 -1
  18. package/dist/content/service.js +52 -29
  19. package/dist/content/service.js.map +1 -1
  20. package/dist/discussion/chats.d.ts +1 -2
  21. package/dist/discussion/chats.js +8 -17
  22. package/dist/discussion/chats.js.map +1 -1
  23. package/dist/discussion/ports.d.ts +0 -6
  24. package/dist/discussion/ports.js.map +1 -1
  25. package/dist/discussion/posts.d.ts +1 -4
  26. package/dist/discussion/posts.js +8 -21
  27. package/dist/discussion/posts.js.map +1 -1
  28. package/dist/entitlements/events.d.ts +33 -33
  29. package/dist/identity/events.d.ts +2 -2
  30. package/dist/integrations/events.d.ts +3 -3
  31. package/dist/organizations/events.d.ts +2 -2
  32. package/dist/reporting/member/ports.d.ts +1 -10
  33. package/dist/reporting/member/service.d.ts +2 -11
  34. package/dist/reporting/member/service.js +12 -62
  35. package/dist/reporting/member/service.js.map +1 -1
  36. package/dist/types/automations.d.ts +5 -2
  37. package/dist/types/index.d.ts +2 -2
  38. package/dist/types/ports.d.ts +4 -2
  39. package/dist/types/ports.js.map +1 -1
  40. package/dist/types/schemas/content.d.ts +2 -2
  41. package/dist/types/schemas/discussion.d.ts +14 -14
  42. package/dist/types/schemas/entitlements.d.ts +8 -8
  43. package/dist/types/schemas/notifications.d.ts +3 -3
  44. package/package.json +1 -1
@@ -17,7 +17,7 @@ var NotificationDelivery = class {
17
17
  this.devices = params.devices;
18
18
  this.logger = params.logger ?? noopLogger;
19
19
  }
20
- async run(d, lastAttempt) {
20
+ async runDelivery(d, lastAttempt) {
21
21
  const found = await this.repo.findDelivery(d.orgId, d.deliveryId);
22
22
  if (!found || found.delivery.status !== "pending") {
23
23
  this.logger.debug("notification delivery skipped", {
@@ -1 +1 @@
1
- {"version":3,"file":"delivery.js","names":[],"sources":["../../../src/automations/notifications/delivery.ts"],"sourcesContent":["import type { DevicesService } from '../../identity/index.js';\nimport { noopLogger } from '../../shared/logger.js';\nimport type { ChannelRegistry, Logger } from '../../shared/ports.js';\nimport type { ChannelRecipient, DeliveryDispatch, DeliveryResult } from '../../types/index.js';\nimport type { NotificationsRepository } from '../ports.js';\n\nexport const CHANNEL_NOT_CONFIGURED = 'channel not configured';\nexport const NO_ADDRESS = 'no address';\nexport const NOT_ACCEPTED = 'not accepted';\n\nexport type NotificationDeliveryParams = {\n repo: Pick<NotificationsRepository, 'findDelivery' | 'markSent' | 'recordError' | 'markFailed'>;\n channels: Pick<ChannelRegistry, 'get'>;\n devices: Pick<DevicesService, 'removeDead'>;\n logger?: Logger;\n};\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport class NotificationDelivery {\n private readonly repo: NotificationDeliveryParams['repo'];\n private readonly channels: Pick<ChannelRegistry, 'get'>;\n private readonly devices: Pick<DevicesService, 'removeDead'>;\n private readonly logger: Logger;\n\n constructor(params: NotificationDeliveryParams) {\n this.repo = params.repo;\n this.channels = params.channels;\n this.devices = params.devices;\n this.logger = params.logger ?? noopLogger;\n }\n\n async run(d: DeliveryDispatch, lastAttempt: boolean): Promise<void> {\n const found = await this.repo.findDelivery(d.orgId, d.deliveryId);\n if (!found || found.delivery.status !== 'pending') {\n this.logger.debug('notification delivery skipped', {\n orgId: d.orgId,\n deliveryId: d.deliveryId,\n status: found?.delivery.status ?? null,\n });\n return;\n }\n const { delivery, notification } = found;\n const log = {\n orgId: d.orgId,\n deliveryId: d.deliveryId,\n notificationId: notification.id,\n channel: delivery.channel,\n type: notification.type,\n };\n const channel = this.channels.get(delivery.channel);\n if (!channel) {\n await this.repo.markFailed(d.orgId, d.deliveryId, CHANNEL_NOT_CONFIGURED);\n this.logger.warn('notification delivery failed: channel not configured', log);\n return;\n }\n let to: ChannelRecipient | null;\n let result: DeliveryResult;\n try {\n to = await channel.recipient(d.orgId, notification.userId);\n if (!to) {\n await this.repo.markFailed(d.orgId, d.deliveryId, NO_ADDRESS);\n this.logger.info('notification delivery failed: no address', log);\n return;\n }\n result = await channel.deliver(notification, to);\n } catch (err) {\n const error = messageOf(err);\n if (lastAttempt) {\n await this.repo.markFailed(d.orgId, d.deliveryId, error);\n this.logger.warn('notification delivery failed on its last attempt', { ...log, err });\n return;\n }\n await this.repo.recordError(d.orgId, d.deliveryId, error);\n this.logger.warn('notification delivery attempt failed', { ...log, err });\n throw err;\n }\n if (result.deadDeviceIds.length > 0) {\n await this.devices.removeDead(d.orgId, result.deadDeviceIds);\n }\n if (!result.accepted) {\n const error = result.error ?? NOT_ACCEPTED;\n await this.repo.markFailed(d.orgId, d.deliveryId, error);\n this.logger.info('notification delivery failed: not accepted', { ...log, error });\n return;\n }\n await this.repo.markSent(d.orgId, d.deliveryId, new Date());\n this.logger.info('notification delivered', log);\n }\n}\n"],"mappings":";;AAMA,MAAa,yBAAyB;AACtC,MAAa,aAAa;AAC1B,MAAa,eAAe;AAS5B,SAAS,UAAU,KAAsB;CACvC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,IAAa,uBAAb,MAAkC;CAChC;CACA;CACA;CACA;CAEA,YAAY,QAAoC;EAC9C,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW,OAAO;EACvB,KAAK,UAAU,OAAO;EACtB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,MAAM,IAAI,GAAqB,aAAqC;EAClE,MAAM,QAAQ,MAAM,KAAK,KAAK,aAAa,EAAE,OAAO,EAAE,UAAU;EAChE,IAAI,CAAC,SAAS,MAAM,SAAS,WAAW,WAAW;GACjD,KAAK,OAAO,MAAM,iCAAiC;IACjD,OAAO,EAAE;IACT,YAAY,EAAE;IACd,QAAQ,OAAO,SAAS,UAAU;GACpC,CAAC;GACD;EACF;EACA,MAAM,EAAE,UAAU,iBAAiB;EACnC,MAAM,MAAM;GACV,OAAO,EAAE;GACT,YAAY,EAAE;GACd,gBAAgB,aAAa;GAC7B,SAAS,SAAS;GAClB,MAAM,aAAa;EACrB;EACA,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS,OAAO;EAClD,IAAI,CAAC,SAAS;GACZ,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,sBAAsB;GACxE,KAAK,OAAO,KAAK,wDAAwD,GAAG;GAC5E;EACF;EACA,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,KAAK,MAAM,QAAQ,UAAU,EAAE,OAAO,aAAa,MAAM;GACzD,IAAI,CAAC,IAAI;IACP,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,UAAU;IAC5D,KAAK,OAAO,KAAK,4CAA4C,GAAG;IAChE;GACF;GACA,SAAS,MAAM,QAAQ,QAAQ,cAAc,EAAE;EACjD,SAAS,KAAK;GACZ,MAAM,QAAQ,UAAU,GAAG;GAC3B,IAAI,aAAa;IACf,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK;IACvD,KAAK,OAAO,KAAK,oDAAoD;KAAE,GAAG;KAAK;IAAI,CAAC;IACpF;GACF;GACA,MAAM,KAAK,KAAK,YAAY,EAAE,OAAO,EAAE,YAAY,KAAK;GACxD,KAAK,OAAO,KAAK,wCAAwC;IAAE,GAAG;IAAK;GAAI,CAAC;GACxE,MAAM;EACR;EACA,IAAI,OAAO,cAAc,SAAS,GAChC,MAAM,KAAK,QAAQ,WAAW,EAAE,OAAO,OAAO,aAAa;EAE7D,IAAI,CAAC,OAAO,UAAU;GACpB,MAAM,QAAQ,OAAO,SAAA;GACrB,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK;GACvD,KAAK,OAAO,KAAK,8CAA8C;IAAE,GAAG;IAAK;GAAM,CAAC;GAChF;EACF;EACA,MAAM,KAAK,KAAK,SAAS,EAAE,OAAO,EAAE,4BAAY,IAAI,KAAK,CAAC;EAC1D,KAAK,OAAO,KAAK,0BAA0B,GAAG;CAChD;AACF"}
1
+ {"version":3,"file":"delivery.js","names":[],"sources":["../../../src/automations/notifications/delivery.ts"],"sourcesContent":["import type { DevicesService } from '../../identity/index.js';\nimport { noopLogger } from '../../shared/logger.js';\nimport type { ChannelRegistry, Logger } from '../../shared/ports.js';\nimport type { ChannelRecipient, DeliveryDispatch, DeliveryResult } from '../../types/index.js';\nimport type { DeliveryExecutor, NotificationsRepository } from '../ports.js';\n\nexport const CHANNEL_NOT_CONFIGURED = 'channel not configured';\nexport const NO_ADDRESS = 'no address';\nexport const NOT_ACCEPTED = 'not accepted';\n\nexport type NotificationDeliveryParams = {\n repo: Pick<NotificationsRepository, 'findDelivery' | 'markSent' | 'recordError' | 'markFailed'>;\n channels: Pick<ChannelRegistry, 'get'>;\n devices: Pick<DevicesService, 'removeDead'>;\n logger?: Logger;\n};\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport class NotificationDelivery implements DeliveryExecutor {\n private readonly repo: NotificationDeliveryParams['repo'];\n private readonly channels: Pick<ChannelRegistry, 'get'>;\n private readonly devices: Pick<DevicesService, 'removeDead'>;\n private readonly logger: Logger;\n\n constructor(params: NotificationDeliveryParams) {\n this.repo = params.repo;\n this.channels = params.channels;\n this.devices = params.devices;\n this.logger = params.logger ?? noopLogger;\n }\n\n async runDelivery(d: DeliveryDispatch, lastAttempt: boolean): Promise<void> {\n const found = await this.repo.findDelivery(d.orgId, d.deliveryId);\n if (!found || found.delivery.status !== 'pending') {\n this.logger.debug('notification delivery skipped', {\n orgId: d.orgId,\n deliveryId: d.deliveryId,\n status: found?.delivery.status ?? null,\n });\n return;\n }\n const { delivery, notification } = found;\n const log = {\n orgId: d.orgId,\n deliveryId: d.deliveryId,\n notificationId: notification.id,\n channel: delivery.channel,\n type: notification.type,\n };\n const channel = this.channels.get(delivery.channel);\n if (!channel) {\n await this.repo.markFailed(d.orgId, d.deliveryId, CHANNEL_NOT_CONFIGURED);\n this.logger.warn('notification delivery failed: channel not configured', log);\n return;\n }\n let to: ChannelRecipient | null;\n let result: DeliveryResult;\n try {\n to = await channel.recipient(d.orgId, notification.userId);\n if (!to) {\n await this.repo.markFailed(d.orgId, d.deliveryId, NO_ADDRESS);\n this.logger.info('notification delivery failed: no address', log);\n return;\n }\n result = await channel.deliver(notification, to);\n } catch (err) {\n const error = messageOf(err);\n if (lastAttempt) {\n await this.repo.markFailed(d.orgId, d.deliveryId, error);\n this.logger.warn('notification delivery failed on its last attempt', { ...log, err });\n return;\n }\n await this.repo.recordError(d.orgId, d.deliveryId, error);\n this.logger.warn('notification delivery attempt failed', { ...log, err });\n throw err;\n }\n if (result.deadDeviceIds.length > 0) {\n await this.devices.removeDead(d.orgId, result.deadDeviceIds);\n }\n if (!result.accepted) {\n const error = result.error ?? NOT_ACCEPTED;\n await this.repo.markFailed(d.orgId, d.deliveryId, error);\n this.logger.info('notification delivery failed: not accepted', { ...log, error });\n return;\n }\n await this.repo.markSent(d.orgId, d.deliveryId, new Date());\n this.logger.info('notification delivered', log);\n }\n}\n"],"mappings":";;AAMA,MAAa,yBAAyB;AACtC,MAAa,aAAa;AAC1B,MAAa,eAAe;AAS5B,SAAS,UAAU,KAAsB;CACvC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,IAAa,uBAAb,MAA8D;CAC5D;CACA;CACA;CACA;CAEA,YAAY,QAAoC;EAC9C,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW,OAAO;EACvB,KAAK,UAAU,OAAO;EACtB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,MAAM,YAAY,GAAqB,aAAqC;EAC1E,MAAM,QAAQ,MAAM,KAAK,KAAK,aAAa,EAAE,OAAO,EAAE,UAAU;EAChE,IAAI,CAAC,SAAS,MAAM,SAAS,WAAW,WAAW;GACjD,KAAK,OAAO,MAAM,iCAAiC;IACjD,OAAO,EAAE;IACT,YAAY,EAAE;IACd,QAAQ,OAAO,SAAS,UAAU;GACpC,CAAC;GACD;EACF;EACA,MAAM,EAAE,UAAU,iBAAiB;EACnC,MAAM,MAAM;GACV,OAAO,EAAE;GACT,YAAY,EAAE;GACd,gBAAgB,aAAa;GAC7B,SAAS,SAAS;GAClB,MAAM,aAAa;EACrB;EACA,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS,OAAO;EAClD,IAAI,CAAC,SAAS;GACZ,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,sBAAsB;GACxE,KAAK,OAAO,KAAK,wDAAwD,GAAG;GAC5E;EACF;EACA,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,KAAK,MAAM,QAAQ,UAAU,EAAE,OAAO,aAAa,MAAM;GACzD,IAAI,CAAC,IAAI;IACP,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,UAAU;IAC5D,KAAK,OAAO,KAAK,4CAA4C,GAAG;IAChE;GACF;GACA,SAAS,MAAM,QAAQ,QAAQ,cAAc,EAAE;EACjD,SAAS,KAAK;GACZ,MAAM,QAAQ,UAAU,GAAG;GAC3B,IAAI,aAAa;IACf,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK;IACvD,KAAK,OAAO,KAAK,oDAAoD;KAAE,GAAG;KAAK;IAAI,CAAC;IACpF;GACF;GACA,MAAM,KAAK,KAAK,YAAY,EAAE,OAAO,EAAE,YAAY,KAAK;GACxD,KAAK,OAAO,KAAK,wCAAwC;IAAE,GAAG;IAAK;GAAI,CAAC;GACxE,MAAM;EACR;EACA,IAAI,OAAO,cAAc,SAAS,GAChC,MAAM,KAAK,QAAQ,WAAW,EAAE,OAAO,OAAO,aAAa;EAE7D,IAAI,CAAC,OAAO,UAAU;GACpB,MAAM,QAAQ,OAAO,SAAA;GACrB,MAAM,KAAK,KAAK,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK;GACvD,KAAK,OAAO,KAAK,8CAA8C;IAAE,GAAG;IAAK;GAAM,CAAC;GAChF;EACF;EACA,MAAM,KAAK,KAAK,SAAS,EAAE,OAAO,EAAE,4BAAY,IAAI,KAAK,CAAC;EAC1D,KAAK,OAAO,KAAK,0BAA0B,GAAG;CAChD;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { DomainEvent, Page } from "../types/shared.js";
2
2
  import { Event } from "../types/schemas/content.js";
3
3
  import { Automation, AutomationKind, AutomationRun, AutomationRunsQuery, AutomationsQuery, AvailableActions, AvailableTriggers, CreateAutomationInput, CreateWebhookInput, CreatedWebhook, UpdateAutomationInput, UpdateWebhookInput, Webhook, WebhookSecret } from "../types/schemas/automations.js";
4
- import { AutomationDispatch, AutomationEngine, AutomationExecutor, DeliveryDispatch, ScheduledAutomation, WebhookRequest, WebhookResponse, WebhookSender } from "../types/automations.js";
4
+ import { AutomationDispatch, AutomationEngine, AutomationExecutor, DeliveryDispatch, DeliveryExecutor, ScheduledAutomation, WebhookRequest, WebhookResponse, WebhookSender } from "../types/automations.js";
5
5
  import { Delivery, NotificationCategory, NotificationFilter, RecipientList, StoredNotification, SubjectRef } from "../types/notifications.js";
6
6
  import "../types/index.js";
7
7
  import { Actor } from "../shared/actor.js";
@@ -184,5 +184,5 @@ interface AutomationsTxScope {
184
184
  }
185
185
  type AutomationsUnitOfWork = UnitOfWork<AutomationsTxScope>;
186
186
  //#endregion
187
- export { type AutomationDispatch, type AutomationEngine, type AutomationExecutor, AutomationRunsRepository, AutomationsRepository, AutomationsService, AutomationsTxScope, AutomationsUnitOfWork, ChatDebounce, type DeliveryDispatch, ListedNotification, NewAutomation, NewAutomationRun, NewDelivery, NewNotification, NotificationCategories, NotificationPage, NotificationReads, NotificationsRepository, NotificationsService, RecipientRule, UnreadCounts, type WebhookRequest, type WebhookResponse, type WebhookSender };
187
+ export { type AutomationDispatch, type AutomationEngine, type AutomationExecutor, AutomationRunsRepository, AutomationsRepository, AutomationsService, AutomationsTxScope, AutomationsUnitOfWork, ChatDebounce, type DeliveryDispatch, type DeliveryExecutor, ListedNotification, NewAutomation, NewAutomationRun, NewDelivery, NewNotification, NotificationCategories, NotificationPage, NotificationReads, NotificationsRepository, NotificationsService, RecipientRule, UnreadCounts, type WebhookRequest, type WebhookResponse, type WebhookSender };
188
188
  //# sourceMappingURL=ports.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { DomainEvent, Page } from "../types/shared.js";
2
2
  import { Logger } from "../types/ports.js";
3
3
  import { Automation, AutomationActionResult, AutomationRun, AutomationRunsQuery, AutomationsQuery, AvailableActions, AvailableTriggers, CreateAutomationInput, CreateWebhookInput, CreatedWebhook, UpdateAutomationInput, UpdateWebhookInput, Webhook, WebhookSecret } from "../types/schemas/automations.js";
4
- import { AutomationDispatch, AutomationEngine, AutomationExecutor, DeliveryDispatch, ScheduledAutomation, WebhookSender } from "../types/automations.js";
4
+ import { AutomationDispatch, AutomationEngine, AutomationExecutor, ScheduledAutomation, WebhookSender } from "../types/automations.js";
5
5
  import "../types/index.js";
6
6
  import { Actor } from "../shared/actor.js";
7
7
  import { CredentialStore } from "../shared/ports.js";
@@ -10,8 +10,6 @@ import { Mailer, MailerLookups } from "../shared/mailer.js";
10
10
  import "./types.js";
11
11
  import { AutomationRunsRepository, AutomationsRepository, AutomationsService, AutomationsUnitOfWork } from "./ports.js";
12
12
  import { CommunityMailReads, GrantWriter } from "./community-mail.js";
13
- import { NotificationDelivery } from "./notifications/delivery.js";
14
- import { NotificationProcessor } from "./notifications/processor.js";
15
13
  import { IntegrationsService } from "../integrations/ports.js";
16
14
  import "../integrations/index.js";
17
15
  //#region src/automations/service.d.ts
@@ -30,8 +28,6 @@ type AutomationsServiceParams = {
30
28
  webhooks: WebhookSender;
31
29
  mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;
32
30
  grants: GrantWriter;
33
- delivery: Pick<NotificationDelivery, 'run'>;
34
- processor: Pick<NotificationProcessor, 'process'>;
35
31
  logger?: Logger;
36
32
  };
37
33
  declare class AutomationsServiceImpl implements AutomationsService, AutomationExecutor {
@@ -47,8 +43,6 @@ declare class AutomationsServiceImpl implements AutomationsService, AutomationEx
47
43
  private readonly webhooks;
48
44
  private readonly mail;
49
45
  private readonly grants;
50
- private readonly delivery;
51
- private readonly processor;
52
46
  private readonly logger;
53
47
  constructor(params: AutomationsServiceParams);
54
48
  handle(event: DomainEvent): Promise<void>;
@@ -60,7 +54,6 @@ declare class AutomationsServiceImpl implements AutomationsService, AutomationEx
60
54
  /** Best-effort: logs and never rethrows, so an org automation never fails `handle`. */
61
55
  private recordFailure;
62
56
  runAction(d: AutomationDispatch, index: number): Promise<AutomationActionResult>;
63
- runDelivery(d: DeliveryDispatch, lastAttempt: boolean): Promise<void>;
64
57
  listSchedules(): Promise<ScheduledAutomation[]>;
65
58
  fireSchedule(orgId: string, automationId: string, at: Date): Promise<void>;
66
59
  finalize(d: AutomationDispatch, results: AutomationActionResult[]): Promise<void>;
@@ -134,8 +134,6 @@ var AutomationsServiceImpl = class {
134
134
  webhooks;
135
135
  mail;
136
136
  grants;
137
- delivery;
138
- processor;
139
137
  logger;
140
138
  constructor(params) {
141
139
  this.repo = params.repo;
@@ -153,8 +151,6 @@ var AutomationsServiceImpl = class {
153
151
  recentMessageRecipients: (orgId, automationId, chatId, since) => this.runsRepo.recentRecipients(orgId, automationId, chatId, since)
154
152
  };
155
153
  this.grants = params.grants;
156
- this.delivery = params.delivery;
157
- this.processor = params.processor;
158
154
  this.logger = params.logger ?? noopLogger;
159
155
  }
160
156
  async handle(event) {
@@ -172,7 +168,6 @@ var AutomationsServiceImpl = class {
172
168
  }
173
169
  await this.dispatchOne(automation, event);
174
170
  }
175
- await this.processor.process(event);
176
171
  }
177
172
  async reconcileSchedules(orgId) {
178
173
  try {
@@ -317,9 +312,6 @@ var AutomationsServiceImpl = class {
317
312
  ...failed.length > 0 ? { error: `delivery failed for ${failed.length} recipients: ${failed.join(", ")}` } : {}
318
313
  };
319
314
  }
320
- runDelivery(d, lastAttempt) {
321
- return this.delivery.run(d, lastAttempt);
322
- }
323
315
  async listSchedules() {
324
316
  return this.repo.listSchedules();
325
317
  }
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../src/automations/service.ts"],"sourcesContent":["// automations context — service implementation (inbound port) and the\n// AutomationExecutor the engine drives.\n//\n// `handle` is subscribed to all events on the EventBus: it matches an event\n// against enabled automations for its trigger, opens one run per match, and\n// hands it to the injected AutomationEngine, which calls back into\n// `runAction`/`finalize`.\nimport { assertGrantsAllowed, assertReadsEvents } from './event-permissions.js';\nimport {\n catalogActions,\n catalogTriggers,\n DELIVER_WEBHOOK_ACTION,\n publicDataSchema,\n} from './catalog.js';\nimport { executeAction, type ActionOutcome } from './actions.js';\nimport { generateWebhookSecret } from './webhook-signing.js';\nimport type { CommunityMailReads, GrantWriter } from './community-mail.js';\nimport type { NotificationDelivery } from './notifications/delivery.js';\nimport type { NotificationProcessor } from './notifications/processor.js';\nimport { cronSupported } from './schedule.js';\nimport {\n ActionFailedError,\n AutomationKindMismatchError,\n InvalidTriggerError,\n ReservedActionError,\n type Automation,\n type AutomationAction,\n type AutomationActionResult,\n type AutomationRun,\n type CreatedWebhook,\n type DeliverWebhookInput,\n type Page,\n type Webhook,\n type WebhookSecret,\n} from './model.js';\nimport type {\n AutomationRunsQuery,\n AutomationsQuery,\n AvailableActions,\n AvailableTriggers,\n CreateAutomationInput,\n CreateWebhookInput,\n UpdateAutomationInput,\n UpdateWebhookInput,\n} from './types.js';\nimport type {\n AutomationDispatch,\n AutomationEngine,\n AutomationExecutor,\n AutomationRunsRepository,\n AutomationsRepository,\n AutomationsService,\n AutomationsUnitOfWork,\n DeliveryDispatch,\n WebhookSender,\n} from './ports.js';\nimport type { CredentialStore, DomainEvent, Logger } from '../shared/ports.js';\nimport type { JsonValue, ScheduledAutomation } from '../types/index.js';\nimport { deliverWebhookInputSchema } from '../types/schemas/index.js';\nimport { ConflictError, InvalidInputError, NotFoundError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { Mailer, MailerLookups } from '../shared/mailer.js';\nimport type { IntegrationsService } from '../integrations/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport { automationEvents } from './events.js';\n\n/** True only when `input` sets `enabled` and nothing else — emits automation.enabled|disabled instead of automation.updated. */\nfunction isEnabledOnlyUpdate<T extends { enabled?: boolean }>(\n input: T,\n): input is T & { enabled: boolean } {\n if (input.enabled === undefined) {\n return false;\n }\n const { enabled: _enabled, ...rest } = input;\n return Object.values(rest).every((v) => v === undefined);\n}\n\nfunction floorMinute(at: Date): number {\n const copy = new Date(at);\n copy.setUTCSeconds(0, 0);\n return copy.getTime();\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction filterMatches(filter: Record<string, JsonValue> | null, data: unknown): boolean {\n if (filter == null) {\n return true;\n }\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return false;\n }\n const record = data as Record<string, unknown>;\n return Object.entries(filter).every(([key, expected]) => {\n if (!Object.prototype.hasOwnProperty.call(record, key)) {\n return false;\n }\n return JSON.stringify(record[key]) === JSON.stringify(expected);\n });\n}\n\nfunction assertAuthorableTriggers(triggers: string[]): void {\n for (const trigger of triggers) {\n if (trigger.startsWith('automation.')) {\n throw new InvalidTriggerError(trigger);\n }\n if (trigger.startsWith('schedule:') && !cronSupported(trigger.slice('schedule:'.length))) {\n throw new InvalidTriggerError(trigger, 'schedule');\n }\n }\n}\n\nfunction assertCatalogTriggers(triggers: string[], schedules: boolean): void {\n assertAuthorableTriggers(triggers);\n const known = new Set(catalogTriggers().map((t) => t.type));\n for (const trigger of triggers) {\n if (!(schedules && trigger.startsWith('schedule:')) && !known.has(trigger)) {\n throw new InvalidTriggerError(trigger, 'unknown');\n }\n }\n}\n\nconst REMOVED_ACTIONS = new Set([\n 'notify',\n 'publishDuePosts',\n 'expireDueGrants',\n 'sendDigest',\n 'removeUnattachedUploads',\n]);\n\nfunction assertAuthorableActions(actions: AutomationAction[]): void {\n for (const action of actions) {\n if (action.type === DELIVER_WEBHOOK_ACTION) {\n throw new ReservedActionError(action.type);\n }\n if (REMOVED_ACTIONS.has(action.type)) {\n throw new InvalidInputError(`The action \"${action.type}\" is no longer available`);\n }\n }\n}\n\nconst SCHEDULE_FIRED = 'automation.schedule.fired';\n\nfunction publicEventData(event: DomainEvent): JsonValue {\n const parsed =\n event.type === SCHEDULE_FIRED\n ? { success: true as const, data: event.data }\n : publicDataSchema(event.type)?.safeParse(event.data);\n return parsed?.success ? (JSON.parse(JSON.stringify(parsed.data)) as JsonValue) : null;\n}\n\n/** A run as callers see it: its event carries only what a webhook of that event would. */\nfunction toRunView(run: AutomationRun): AutomationRun {\n const { type, version, id, orgId, occurredAt } = run.event;\n return {\n ...run,\n event: { type, version, id, orgId, occurredAt, data: publicEventData(run.event) },\n };\n}\n\n/** An automation as callers see it: a webhook's credential reference stays internal. */\nfunction toAutomationView(automation: Automation): Automation {\n return {\n ...automation,\n actions: automation.actions.map((action) => {\n if (action.type !== DELIVER_WEBHOOK_ACTION) {\n return action;\n }\n const { secretRef: _secretRef, ...input } = action.input;\n return { ...action, input };\n }),\n };\n}\n\nfunction webhookTarget(automation: Automation): DeliverWebhookInput {\n return deliverWebhookInputSchema.parse(automation.actions[0]?.input);\n}\n\nfunction toWebhook(automation: Automation): Webhook {\n return {\n orgId: automation.orgId,\n id: automation.id,\n url: webhookTarget(automation).url,\n events: automation.triggers,\n description: automation.description,\n enabled: automation.enabled,\n createdAt: automation.createdAt,\n updatedAt: automation.updatedAt,\n };\n}\n\nfunction updateEvent(orgId: string, input: { enabled?: boolean }, updated: Automation) {\n if (isEnabledOnlyUpdate(input)) {\n return input.enabled\n ? automationEvents.automationEnabled.make({ orgId, data: updated })\n : automationEvents.automationDisabled.make({ orgId, data: updated });\n }\n return automationEvents.automationUpdated.make({ orgId, data: updated });\n}\n\nexport type AutomationsServiceParams = {\n /** Read-only access (list/get/available) — runs outside any transaction. */\n repo: AutomationsRepository;\n runsRepo: AutomationRunsRepository;\n /** Atomic write scope: tx-bound repos + credential store + outbox appender. */\n uow: AutomationsUnitOfWork;\n engine: AutomationEngine;\n mailer: Pick<Mailer, 'send'>;\n lookups: MailerLookups;\n integrations: Pick<IntegrationsService, 'available'>;\n systemActor: (orgId: string) => Actor;\n credentials: CredentialStore;\n webhooks: WebhookSender;\n mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;\n grants: GrantWriter;\n delivery: Pick<NotificationDelivery, 'run'>;\n processor: Pick<NotificationProcessor, 'process'>;\n logger?: Logger;\n};\n\nexport class AutomationsServiceImpl implements AutomationsService, AutomationExecutor {\n private readonly repo: AutomationsRepository;\n private readonly runsRepo: AutomationRunsRepository;\n private readonly uow: AutomationsUnitOfWork;\n private readonly engine: AutomationEngine;\n private readonly mailer: Pick<Mailer, 'send'>;\n private readonly lookups: MailerLookups;\n private readonly integrations: Pick<IntegrationsService, 'available'>;\n private readonly systemActor: (orgId: string) => Actor;\n private readonly credentials: CredentialStore;\n private readonly webhooks: WebhookSender;\n private readonly mail: CommunityMailReads;\n private readonly grants: GrantWriter;\n private readonly delivery: Pick<NotificationDelivery, 'run'>;\n private readonly processor: Pick<NotificationProcessor, 'process'>;\n private readonly logger: Logger;\n\n constructor(params: AutomationsServiceParams) {\n this.repo = params.repo;\n this.runsRepo = params.runsRepo;\n this.uow = params.uow;\n this.engine = params.engine;\n this.mailer = params.mailer;\n this.lookups = params.lookups;\n this.integrations = params.integrations;\n this.systemActor = params.systemActor;\n this.credentials = params.credentials;\n this.webhooks = params.webhooks;\n this.mail = {\n ...params.mail,\n recentMessageRecipients: (orgId, automationId, chatId, since) =>\n this.runsRepo.recentRecipients(orgId, automationId, chatId, since),\n };\n this.grants = params.grants;\n this.delivery = params.delivery;\n this.processor = params.processor;\n this.logger = params.logger ?? noopLogger;\n }\n\n async handle(event: DomainEvent): Promise<void> {\n // automation.* events are this service's own output — matching one as a trigger would self-loop.\n if (event.type.startsWith('automation.')) {\n return;\n }\n const automations = await this.safeListByTrigger(event);\n for (const automation of automations) {\n const matches = filterMatches(automation.filter, event.data);\n if (!automation.enabled || !matches) {\n if (automation.enabled && !matches) {\n this.logger.debug('automation filter skipped', {\n orgId: event.orgId,\n automationId: automation.id,\n trigger: event.type,\n });\n }\n continue;\n }\n await this.dispatchOne(automation, event);\n }\n await this.processor.process(event);\n }\n\n private async reconcileSchedules(orgId: string): Promise<void> {\n try {\n await this.engine.syncSchedules?.();\n } catch (err) {\n this.logger.error('automation schedules not reconciled', { orgId, err });\n }\n }\n\n private async safeListByTrigger(event: DomainEvent): Promise<Automation[]> {\n try {\n return await this.repo.listByTrigger(event.orgId, event.type);\n } catch (err) {\n this.logger.error('automations: failed to load trigger matches', {\n orgId: event.orgId,\n trigger: event.type,\n err,\n });\n return [];\n }\n }\n\n private async dispatchOne(automation: Automation, event: DomainEvent): Promise<void> {\n let run: AutomationRun | null = null;\n try {\n run = await this.openRun(automation, event);\n if (!run) {\n // At-least-once redelivery of a trigger event already run for this automation — no-op.\n return;\n }\n await this.engine.dispatch(this.dispatchFor(automation, run, event));\n } catch (err) {\n this.logger.error('automation run failed', {\n orgId: event.orgId,\n automationId: automation.id,\n runId: run?.id,\n trigger: event.type,\n eventId: event.id,\n err,\n });\n if (run) {\n await this.recordFailure(run, err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n private openRun(automation: Automation, event: DomainEvent): Promise<AutomationRun | null> {\n const now = new Date();\n return this.uow.run(async ({ runs, outbox }) => {\n const inserted = await runs.insert(event.orgId, {\n orgId: event.orgId,\n automationId: automation.id,\n trigger: event.type,\n event,\n status: 'running',\n actionResults: [],\n startedAt: now,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!inserted) {\n return null;\n }\n await outbox.append([\n automationEvents.runStarted.make({\n orgId: event.orgId,\n data: inserted,\n }),\n ]);\n return inserted;\n });\n }\n\n private dispatchFor(\n automation: Automation,\n run: AutomationRun,\n event: DomainEvent,\n ): AutomationDispatch {\n return {\n runId: run.id,\n orgId: event.orgId,\n automationId: automation.id,\n actions: automation.actions,\n event,\n };\n }\n\n /** Best-effort: logs and never rethrows, so an org automation never fails `handle`. */\n private async recordFailure(run: AutomationRun, error: string): Promise<AutomationRun | null> {\n try {\n return await this.uow.run(async ({ runs, outbox }) => {\n const failed = await runs.recordOutcome(run.orgId, run.id, {\n status: 'failed',\n actionResults: run.actionResults,\n finishedAt: new Date(),\n });\n if (failed) {\n await outbox.append([\n automationEvents.runFailed.make({\n orgId: run.orgId,\n data: failed,\n }),\n ]);\n }\n return failed;\n });\n } catch (err) {\n this.logger.error('failed to record automation run failure', {\n orgId: run.orgId,\n automationId: run.automationId,\n runId: run.id,\n err,\n originalError: error,\n });\n return null;\n }\n }\n\n async runAction(d: AutomationDispatch, index: number): Promise<AutomationActionResult> {\n const action = d.actions[index];\n if (!action) {\n throw new Error(`automation ${d.automationId} run ${d.runId}: no action at index ${index}`);\n }\n let outcome: ActionOutcome;\n try {\n outcome = await executeAction(action, d.event, {\n mailer: this.mailer,\n lookups: this.lookups,\n credentials: this.credentials,\n webhooks: this.webhooks,\n mail: this.mail,\n grants: this.grants,\n automationId: d.automationId,\n actionInput: action.input,\n logger: this.logger,\n });\n } catch (err) {\n this.logger.error('automation action failed', {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n trigger: d.event.type,\n index,\n type: action.type,\n err,\n });\n throw err instanceof ActionFailedError ? err : new ActionFailedError();\n }\n const { recipients, failed } = outcome;\n this.logger.info('automation action completed', {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n type: action.type,\n recipients: recipients.length,\n failed: failed.length,\n });\n return {\n index,\n type: action.type,\n status: 'completed',\n ...(recipients.length > 0 ? { recipients } : {}),\n ...(failed.length > 0\n ? { error: `delivery failed for ${failed.length} recipients: ${failed.join(', ')}` }\n : {}),\n };\n }\n\n runDelivery(d: DeliveryDispatch, lastAttempt: boolean): Promise<void> {\n return this.delivery.run(d, lastAttempt);\n }\n\n async listSchedules(): Promise<ScheduledAutomation[]> {\n return this.repo.listSchedules();\n }\n\n async fireSchedule(orgId: string, automationId: string, at: Date): Promise<void> {\n const firedAt = new Date(floorMinute(at));\n const automation = await this.repo.findById(orgId, automationId);\n if (!automation?.enabled || firedAt.getTime() < floorMinute(automation.createdAt)) {\n this.logger.debug('schedule skipped', { orgId, automationId });\n return;\n }\n const event: DomainEvent = {\n type: SCHEDULE_FIRED,\n id: `schedule_${automationId}_${firedAt.toISOString()}`,\n orgId,\n version: 1,\n occurredAt: firedAt.toISOString(),\n data: { automationId, firedAt: firedAt.toISOString() },\n };\n await this.dispatchOne(automation, event);\n }\n\n async finalize(d: AutomationDispatch, results: AutomationActionResult[]): Promise<void> {\n const status: 'completed' | 'failed' =\n results.length === d.actions.length && results.every((r) => r.status === 'completed')\n ? 'completed'\n : 'failed';\n await this.uow.run(async ({ runs, outbox }) => {\n const run = await runs.recordOutcome(d.orgId, d.runId, {\n status,\n actionResults: results,\n finishedAt: new Date(),\n });\n if (!run) {\n return;\n }\n const runEvent =\n status === 'completed'\n ? automationEvents.runCompleted.make({ orgId: d.orgId, data: run })\n : automationEvents.runFailed.make({ orgId: d.orgId, data: run });\n const actionFailedEvents = results\n .filter((r) => r.status === 'failed')\n .map((result) => ({\n ...automationEvents.actionFailed.make({\n orgId: d.orgId,\n data: result,\n }),\n }));\n await outbox.append([runEvent, ...actionFailedEvents]);\n });\n this.logOutcome(d, status, results);\n }\n\n private logOutcome(\n d: AutomationDispatch,\n status: 'completed' | 'failed',\n results: AutomationActionResult[],\n ): void {\n const run = {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n trigger: d.event.type,\n eventId: d.event.id,\n };\n if (status === 'failed') {\n const failed = results.find((r) => r.status === 'failed');\n const index = failed?.index ?? results.length;\n this.logger.error('automation run failed', {\n ...run,\n index,\n action: failed?.type ?? d.actions[index]?.type,\n error: failed?.error ?? 'not every action ran',\n });\n return;\n }\n const partial = results.filter((r) => r.error !== undefined);\n if (partial.length > 0) {\n this.logger.warn('automation run completed with failed deliveries', {\n ...run,\n actions: partial.map(({ index, type, error }) => ({ index, type, error })),\n });\n }\n }\n\n async create(input: CreateAutomationInput, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n assertCatalogTriggers(input.triggers, true);\n assertAuthorableActions(input.actions);\n assertGrantsAllowed(actor, input.actions);\n const automation = await this.uow.run(async ({ automations, outbox }) => {\n const created = await automations.insert(orgId, {\n ...input,\n triggers: unique(input.triggers),\n kind: 'workflow',\n });\n await outbox.append([automationEvents.automationCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('automation created', {\n orgId,\n automationId: automation.id,\n actorId: actor.id,\n });\n await this.reconcileSchedules(orgId);\n return automation;\n }\n\n async update(id: string, input: UpdateAutomationInput, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n if (input.triggers !== undefined) {\n assertCatalogTriggers(input.triggers, true);\n }\n if (input.actions !== undefined) {\n assertAuthorableActions(input.actions);\n assertGrantsAllowed(actor, input.actions);\n }\n const existing = await this.repo.findById(orgId, id);\n if (!existing) {\n throw new NotFoundError('Automation', id);\n }\n if (existing.kind !== 'workflow') {\n throw new AutomationKindMismatchError(id, existing.kind);\n }\n const patch =\n input.triggers === undefined ? input : { ...input, triggers: unique(input.triggers) };\n const automation = await this.uow.run(async ({ automations, outbox }) => {\n const updated = await automations.update(orgId, id, patch);\n if (!updated) {\n return null;\n }\n await outbox.append([updateEvent(orgId, input, updated)]);\n return updated;\n });\n if (!automation) {\n throw new NotFoundError('Automation', id);\n }\n this.logger.info('automation updated', { orgId, automationId: id });\n await this.reconcileSchedules(orgId);\n return automation;\n }\n\n async delete(id: string, actor: Actor): Promise<void> {\n actor.permissions.require('automations:delete');\n const orgId = actor.orgId;\n const existing = await this.repo.findById(orgId, id);\n if (!existing) {\n throw new NotFoundError('Automation', id);\n }\n if (existing.kind !== 'workflow') {\n throw new AutomationKindMismatchError(id, existing.kind);\n }\n const deleted = await this.uow.run(async ({ automations, outbox }) => {\n const removed = await automations.delete(orgId, id);\n if (!removed) {\n return null;\n }\n await outbox.append([automationEvents.automationDeleted.make({ orgId, data: removed })]);\n return removed;\n });\n if (!deleted) {\n throw new NotFoundError('Automation', id);\n }\n this.logger.info('automation deleted', { orgId, automationId: id });\n await this.reconcileSchedules(orgId);\n }\n\n async list(query: AutomationsQuery | undefined, actor: Actor): Promise<Automation[]> {\n actor.permissions.require('automations:read');\n const automations = await this.repo.listByOrg(actor.orgId, query?.kind);\n return automations.map(toAutomationView);\n }\n\n async get(id: string, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:read');\n const automation = await this.repo.findById(actor.orgId, id);\n if (!automation) {\n throw new NotFoundError('Automation', id);\n }\n return toAutomationView(automation);\n }\n\n async listRuns(\n automationId: string,\n query: AutomationRunsQuery,\n actor: Actor,\n ): Promise<Page<AutomationRun>> {\n actor.permissions.require('automations:read');\n const page = await this.runsRepo.list(actor.orgId, automationId, query);\n return { ...page, rows: page.rows.map(toRunView) };\n }\n\n async rerun(automationId: string, runId: string, actor: Actor): Promise<AutomationRun> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.runsRepo.findById(orgId, runId);\n if (!existing || existing.automationId !== automationId) {\n throw new NotFoundError('Automation run', runId);\n }\n const automation = await this.repo.findById(orgId, automationId);\n if (!automation) {\n throw new NotFoundError('Automation', automationId);\n }\n assertGrantsAllowed(actor, automation.actions);\n const run = await this.uow.run(async ({ runs, outbox }) => {\n const restarted = await runs.restart(orgId, runId, new Date());\n if (!restarted) {\n return null;\n }\n await outbox.append([automationEvents.runStarted.make({ orgId, data: restarted })]);\n return restarted;\n });\n if (!run) {\n throw new ConflictError(`automation run ${runId} is still running`);\n }\n try {\n await this.engine.dispatch(this.dispatchFor(automation, run, run.event));\n } catch (err) {\n this.logger.error('automation rerun failed', {\n orgId,\n automationId,\n runId: run.id,\n trigger: run.trigger,\n err,\n });\n const failed = await this.recordFailure(\n run,\n err instanceof Error ? err.message : String(err),\n );\n return toRunView(failed ?? run);\n }\n this.logger.info('automation rerun started', { orgId, automationId, runId: run.id });\n return toRunView(run);\n }\n\n async listWebhooks(actor: Actor): Promise<Webhook[]> {\n actor.permissions.require('automations:read');\n const automations = await this.repo.listByOrg(actor.orgId, 'webhook');\n return automations.map(toWebhook);\n }\n\n async getWebhook(id: string, actor: Actor): Promise<Webhook> {\n actor.permissions.require('automations:read');\n return toWebhook(await this.requireWebhook(actor.orgId, id));\n }\n\n async createWebhook(input: CreateWebhookInput, actor: Actor): Promise<CreatedWebhook> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const events = unique(input.events);\n assertCatalogTriggers(events, false);\n assertReadsEvents(actor, events);\n const secret = generateWebhookSecret();\n const automation = await this.uow.run(async ({ automations, credentials, outbox }) => {\n const secretRef = await credentials.store(orgId, { secret });\n const created = await automations.insert(orgId, {\n kind: 'webhook',\n name: input.url,\n description: input.description,\n triggers: events,\n actions: [{ type: DELIVER_WEBHOOK_ACTION, input: { url: input.url, secretRef } }],\n });\n await outbox.append([automationEvents.automationCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('webhook created', { orgId, automationId: automation.id });\n return { ...toWebhook(automation), secret };\n }\n\n async updateWebhook(id: string, input: UpdateWebhookInput, actor: Actor): Promise<Webhook> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const events = input.events === undefined ? undefined : unique(input.events);\n if (events) {\n assertCatalogTriggers(events, false);\n assertReadsEvents(actor, events);\n }\n const { secretRef } = webhookTarget(existing);\n const patch: UpdateAutomationInput = {\n name: input.url,\n description: input.description,\n triggers: events,\n actions:\n input.url === undefined\n ? undefined\n : [{ type: DELIVER_WEBHOOK_ACTION, input: { url: input.url, secretRef } }],\n enabled: input.enabled,\n };\n const updated = await this.uow.run(async ({ automations, outbox }) => {\n const row = await automations.update(orgId, id, patch);\n if (!row) {\n return null;\n }\n await outbox.append([updateEvent(orgId, input, row)]);\n return row;\n });\n if (!updated) {\n throw new NotFoundError('Webhook', id);\n }\n this.logger.info('webhook updated', { orgId, automationId: id });\n return toWebhook(updated);\n }\n\n async deleteWebhook(id: string, actor: Actor): Promise<void> {\n actor.permissions.require('automations:delete');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const { secretRef } = webhookTarget(existing);\n const deleted = await this.uow.run(async ({ automations, credentials, outbox }) => {\n const removed = await automations.delete(orgId, id);\n if (!removed) {\n return null;\n }\n await credentials.destroy(orgId, secretRef);\n await outbox.append([automationEvents.automationDeleted.make({ orgId, data: removed })]);\n return removed;\n });\n if (!deleted) {\n throw new NotFoundError('Webhook', id);\n }\n this.logger.info('webhook deleted', { orgId, automationId: id });\n }\n\n async revealWebhookSecret(id: string, actor: Actor): Promise<WebhookSecret> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const secrets = await this.credentials.reveal(orgId, webhookTarget(existing).secretRef);\n const secret = secrets?.['secret'];\n if (typeof secret !== 'string') {\n throw new Error(`webhook ${id}: signing secret is missing`);\n }\n return { secret };\n }\n\n async rotateWebhookSecret(id: string, actor: Actor): Promise<WebhookSecret> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const secret = generateWebhookSecret();\n await this.credentials.update(orgId, webhookTarget(existing).secretRef, { secret });\n this.logger.info('webhook secret rotated', { orgId, automationId: id });\n return { secret };\n }\n\n private async requireWebhook(orgId: string, id: string): Promise<Automation> {\n const automation = await this.repo.findById(orgId, id);\n if (automation?.kind !== 'webhook') {\n throw new NotFoundError('Webhook', id);\n }\n return automation;\n }\n\n availableActions(actor: Actor): AvailableActions {\n actor.permissions.require('automations:read');\n return [\n ...catalogActions(),\n ...this.integrations.available(this.systemActor(actor.orgId)).flatMap((integration) =>\n integration.actions.map((action) => ({\n type: `${integration.id}.${action.id}`,\n description: action.description,\n inputSchema: action.inputSchema as Record<string, JsonValue>,\n source: integration.id,\n })),\n ),\n ];\n }\n\n availableTriggers(actor: Actor): AvailableTriggers {\n actor.permissions.require('automations:read');\n return { triggers: catalogTriggers() };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../src/automations/service.ts"],"sourcesContent":["// automations context — service implementation (inbound port) and the\n// AutomationExecutor the engine drives.\n//\n// `handle` is subscribed to all events on the EventBus: it matches an event\n// against enabled automations for its trigger, opens one run per match, and\n// hands it to the injected AutomationEngine, which calls back into\n// `runAction`/`finalize`.\nimport { assertGrantsAllowed, assertReadsEvents } from './event-permissions.js';\nimport {\n catalogActions,\n catalogTriggers,\n DELIVER_WEBHOOK_ACTION,\n publicDataSchema,\n} from './catalog.js';\nimport { executeAction, type ActionOutcome } from './actions.js';\nimport { generateWebhookSecret } from './webhook-signing.js';\nimport type { CommunityMailReads, GrantWriter } from './community-mail.js';\nimport { cronSupported } from './schedule.js';\nimport {\n ActionFailedError,\n AutomationKindMismatchError,\n InvalidTriggerError,\n ReservedActionError,\n type Automation,\n type AutomationAction,\n type AutomationActionResult,\n type AutomationRun,\n type CreatedWebhook,\n type DeliverWebhookInput,\n type Page,\n type Webhook,\n type WebhookSecret,\n} from './model.js';\nimport type {\n AutomationRunsQuery,\n AutomationsQuery,\n AvailableActions,\n AvailableTriggers,\n CreateAutomationInput,\n CreateWebhookInput,\n UpdateAutomationInput,\n UpdateWebhookInput,\n} from './types.js';\nimport type {\n AutomationDispatch,\n AutomationEngine,\n AutomationExecutor,\n AutomationRunsRepository,\n AutomationsRepository,\n AutomationsService,\n AutomationsUnitOfWork,\n WebhookSender,\n} from './ports.js';\nimport type { CredentialStore, DomainEvent, Logger } from '../shared/ports.js';\nimport type { JsonValue, ScheduledAutomation } from '../types/index.js';\nimport { deliverWebhookInputSchema } from '../types/schemas/index.js';\nimport { ConflictError, InvalidInputError, NotFoundError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { Mailer, MailerLookups } from '../shared/mailer.js';\nimport type { IntegrationsService } from '../integrations/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport { automationEvents } from './events.js';\n\n/** True only when `input` sets `enabled` and nothing else — emits automation.enabled|disabled instead of automation.updated. */\nfunction isEnabledOnlyUpdate<T extends { enabled?: boolean }>(\n input: T,\n): input is T & { enabled: boolean } {\n if (input.enabled === undefined) {\n return false;\n }\n const { enabled: _enabled, ...rest } = input;\n return Object.values(rest).every((v) => v === undefined);\n}\n\nfunction floorMinute(at: Date): number {\n const copy = new Date(at);\n copy.setUTCSeconds(0, 0);\n return copy.getTime();\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction filterMatches(filter: Record<string, JsonValue> | null, data: unknown): boolean {\n if (filter == null) {\n return true;\n }\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return false;\n }\n const record = data as Record<string, unknown>;\n return Object.entries(filter).every(([key, expected]) => {\n if (!Object.prototype.hasOwnProperty.call(record, key)) {\n return false;\n }\n return JSON.stringify(record[key]) === JSON.stringify(expected);\n });\n}\n\nfunction assertAuthorableTriggers(triggers: string[]): void {\n for (const trigger of triggers) {\n if (trigger.startsWith('automation.')) {\n throw new InvalidTriggerError(trigger);\n }\n if (trigger.startsWith('schedule:') && !cronSupported(trigger.slice('schedule:'.length))) {\n throw new InvalidTriggerError(trigger, 'schedule');\n }\n }\n}\n\nfunction assertCatalogTriggers(triggers: string[], schedules: boolean): void {\n assertAuthorableTriggers(triggers);\n const known = new Set(catalogTriggers().map((t) => t.type));\n for (const trigger of triggers) {\n if (!(schedules && trigger.startsWith('schedule:')) && !known.has(trigger)) {\n throw new InvalidTriggerError(trigger, 'unknown');\n }\n }\n}\n\nconst REMOVED_ACTIONS = new Set([\n 'notify',\n 'publishDuePosts',\n 'expireDueGrants',\n 'sendDigest',\n 'removeUnattachedUploads',\n]);\n\nfunction assertAuthorableActions(actions: AutomationAction[]): void {\n for (const action of actions) {\n if (action.type === DELIVER_WEBHOOK_ACTION) {\n throw new ReservedActionError(action.type);\n }\n if (REMOVED_ACTIONS.has(action.type)) {\n throw new InvalidInputError(`The action \"${action.type}\" is no longer available`);\n }\n }\n}\n\nconst SCHEDULE_FIRED = 'automation.schedule.fired';\n\nfunction publicEventData(event: DomainEvent): JsonValue {\n const parsed =\n event.type === SCHEDULE_FIRED\n ? { success: true as const, data: event.data }\n : publicDataSchema(event.type)?.safeParse(event.data);\n return parsed?.success ? (JSON.parse(JSON.stringify(parsed.data)) as JsonValue) : null;\n}\n\n/** A run as callers see it: its event carries only what a webhook of that event would. */\nfunction toRunView(run: AutomationRun): AutomationRun {\n const { type, version, id, orgId, occurredAt } = run.event;\n return {\n ...run,\n event: { type, version, id, orgId, occurredAt, data: publicEventData(run.event) },\n };\n}\n\n/** An automation as callers see it: a webhook's credential reference stays internal. */\nfunction toAutomationView(automation: Automation): Automation {\n return {\n ...automation,\n actions: automation.actions.map((action) => {\n if (action.type !== DELIVER_WEBHOOK_ACTION) {\n return action;\n }\n const { secretRef: _secretRef, ...input } = action.input;\n return { ...action, input };\n }),\n };\n}\n\nfunction webhookTarget(automation: Automation): DeliverWebhookInput {\n return deliverWebhookInputSchema.parse(automation.actions[0]?.input);\n}\n\nfunction toWebhook(automation: Automation): Webhook {\n return {\n orgId: automation.orgId,\n id: automation.id,\n url: webhookTarget(automation).url,\n events: automation.triggers,\n description: automation.description,\n enabled: automation.enabled,\n createdAt: automation.createdAt,\n updatedAt: automation.updatedAt,\n };\n}\n\nfunction updateEvent(orgId: string, input: { enabled?: boolean }, updated: Automation) {\n if (isEnabledOnlyUpdate(input)) {\n return input.enabled\n ? automationEvents.automationEnabled.make({ orgId, data: updated })\n : automationEvents.automationDisabled.make({ orgId, data: updated });\n }\n return automationEvents.automationUpdated.make({ orgId, data: updated });\n}\n\nexport type AutomationsServiceParams = {\n /** Read-only access (list/get/available) — runs outside any transaction. */\n repo: AutomationsRepository;\n runsRepo: AutomationRunsRepository;\n /** Atomic write scope: tx-bound repos + credential store + outbox appender. */\n uow: AutomationsUnitOfWork;\n engine: AutomationEngine;\n mailer: Pick<Mailer, 'send'>;\n lookups: MailerLookups;\n integrations: Pick<IntegrationsService, 'available'>;\n systemActor: (orgId: string) => Actor;\n credentials: CredentialStore;\n webhooks: WebhookSender;\n mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;\n grants: GrantWriter;\n logger?: Logger;\n};\n\nexport class AutomationsServiceImpl implements AutomationsService, AutomationExecutor {\n private readonly repo: AutomationsRepository;\n private readonly runsRepo: AutomationRunsRepository;\n private readonly uow: AutomationsUnitOfWork;\n private readonly engine: AutomationEngine;\n private readonly mailer: Pick<Mailer, 'send'>;\n private readonly lookups: MailerLookups;\n private readonly integrations: Pick<IntegrationsService, 'available'>;\n private readonly systemActor: (orgId: string) => Actor;\n private readonly credentials: CredentialStore;\n private readonly webhooks: WebhookSender;\n private readonly mail: CommunityMailReads;\n private readonly grants: GrantWriter;\n private readonly logger: Logger;\n\n constructor(params: AutomationsServiceParams) {\n this.repo = params.repo;\n this.runsRepo = params.runsRepo;\n this.uow = params.uow;\n this.engine = params.engine;\n this.mailer = params.mailer;\n this.lookups = params.lookups;\n this.integrations = params.integrations;\n this.systemActor = params.systemActor;\n this.credentials = params.credentials;\n this.webhooks = params.webhooks;\n this.mail = {\n ...params.mail,\n recentMessageRecipients: (orgId, automationId, chatId, since) =>\n this.runsRepo.recentRecipients(orgId, automationId, chatId, since),\n };\n this.grants = params.grants;\n this.logger = params.logger ?? noopLogger;\n }\n\n async handle(event: DomainEvent): Promise<void> {\n // automation.* events are this service's own output — matching one as a trigger would self-loop.\n if (event.type.startsWith('automation.')) {\n return;\n }\n const automations = await this.safeListByTrigger(event);\n for (const automation of automations) {\n const matches = filterMatches(automation.filter, event.data);\n if (!automation.enabled || !matches) {\n if (automation.enabled && !matches) {\n this.logger.debug('automation filter skipped', {\n orgId: event.orgId,\n automationId: automation.id,\n trigger: event.type,\n });\n }\n continue;\n }\n await this.dispatchOne(automation, event);\n }\n }\n\n private async reconcileSchedules(orgId: string): Promise<void> {\n try {\n await this.engine.syncSchedules?.();\n } catch (err) {\n this.logger.error('automation schedules not reconciled', { orgId, err });\n }\n }\n\n private async safeListByTrigger(event: DomainEvent): Promise<Automation[]> {\n try {\n return await this.repo.listByTrigger(event.orgId, event.type);\n } catch (err) {\n this.logger.error('automations: failed to load trigger matches', {\n orgId: event.orgId,\n trigger: event.type,\n err,\n });\n return [];\n }\n }\n\n private async dispatchOne(automation: Automation, event: DomainEvent): Promise<void> {\n let run: AutomationRun | null = null;\n try {\n run = await this.openRun(automation, event);\n if (!run) {\n // At-least-once redelivery of a trigger event already run for this automation — no-op.\n return;\n }\n await this.engine.dispatch(this.dispatchFor(automation, run, event));\n } catch (err) {\n this.logger.error('automation run failed', {\n orgId: event.orgId,\n automationId: automation.id,\n runId: run?.id,\n trigger: event.type,\n eventId: event.id,\n err,\n });\n if (run) {\n await this.recordFailure(run, err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n private openRun(automation: Automation, event: DomainEvent): Promise<AutomationRun | null> {\n const now = new Date();\n return this.uow.run(async ({ runs, outbox }) => {\n const inserted = await runs.insert(event.orgId, {\n orgId: event.orgId,\n automationId: automation.id,\n trigger: event.type,\n event,\n status: 'running',\n actionResults: [],\n startedAt: now,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!inserted) {\n return null;\n }\n await outbox.append([\n automationEvents.runStarted.make({\n orgId: event.orgId,\n data: inserted,\n }),\n ]);\n return inserted;\n });\n }\n\n private dispatchFor(\n automation: Automation,\n run: AutomationRun,\n event: DomainEvent,\n ): AutomationDispatch {\n return {\n runId: run.id,\n orgId: event.orgId,\n automationId: automation.id,\n actions: automation.actions,\n event,\n };\n }\n\n /** Best-effort: logs and never rethrows, so an org automation never fails `handle`. */\n private async recordFailure(run: AutomationRun, error: string): Promise<AutomationRun | null> {\n try {\n return await this.uow.run(async ({ runs, outbox }) => {\n const failed = await runs.recordOutcome(run.orgId, run.id, {\n status: 'failed',\n actionResults: run.actionResults,\n finishedAt: new Date(),\n });\n if (failed) {\n await outbox.append([\n automationEvents.runFailed.make({\n orgId: run.orgId,\n data: failed,\n }),\n ]);\n }\n return failed;\n });\n } catch (err) {\n this.logger.error('failed to record automation run failure', {\n orgId: run.orgId,\n automationId: run.automationId,\n runId: run.id,\n err,\n originalError: error,\n });\n return null;\n }\n }\n\n async runAction(d: AutomationDispatch, index: number): Promise<AutomationActionResult> {\n const action = d.actions[index];\n if (!action) {\n throw new Error(`automation ${d.automationId} run ${d.runId}: no action at index ${index}`);\n }\n let outcome: ActionOutcome;\n try {\n outcome = await executeAction(action, d.event, {\n mailer: this.mailer,\n lookups: this.lookups,\n credentials: this.credentials,\n webhooks: this.webhooks,\n mail: this.mail,\n grants: this.grants,\n automationId: d.automationId,\n actionInput: action.input,\n logger: this.logger,\n });\n } catch (err) {\n this.logger.error('automation action failed', {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n trigger: d.event.type,\n index,\n type: action.type,\n err,\n });\n throw err instanceof ActionFailedError ? err : new ActionFailedError();\n }\n const { recipients, failed } = outcome;\n this.logger.info('automation action completed', {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n type: action.type,\n recipients: recipients.length,\n failed: failed.length,\n });\n return {\n index,\n type: action.type,\n status: 'completed',\n ...(recipients.length > 0 ? { recipients } : {}),\n ...(failed.length > 0\n ? { error: `delivery failed for ${failed.length} recipients: ${failed.join(', ')}` }\n : {}),\n };\n }\n\n async listSchedules(): Promise<ScheduledAutomation[]> {\n return this.repo.listSchedules();\n }\n\n async fireSchedule(orgId: string, automationId: string, at: Date): Promise<void> {\n const firedAt = new Date(floorMinute(at));\n const automation = await this.repo.findById(orgId, automationId);\n if (!automation?.enabled || firedAt.getTime() < floorMinute(automation.createdAt)) {\n this.logger.debug('schedule skipped', { orgId, automationId });\n return;\n }\n const event: DomainEvent = {\n type: SCHEDULE_FIRED,\n id: `schedule_${automationId}_${firedAt.toISOString()}`,\n orgId,\n version: 1,\n occurredAt: firedAt.toISOString(),\n data: { automationId, firedAt: firedAt.toISOString() },\n };\n await this.dispatchOne(automation, event);\n }\n\n async finalize(d: AutomationDispatch, results: AutomationActionResult[]): Promise<void> {\n const status: 'completed' | 'failed' =\n results.length === d.actions.length && results.every((r) => r.status === 'completed')\n ? 'completed'\n : 'failed';\n await this.uow.run(async ({ runs, outbox }) => {\n const run = await runs.recordOutcome(d.orgId, d.runId, {\n status,\n actionResults: results,\n finishedAt: new Date(),\n });\n if (!run) {\n return;\n }\n const runEvent =\n status === 'completed'\n ? automationEvents.runCompleted.make({ orgId: d.orgId, data: run })\n : automationEvents.runFailed.make({ orgId: d.orgId, data: run });\n const actionFailedEvents = results\n .filter((r) => r.status === 'failed')\n .map((result) => ({\n ...automationEvents.actionFailed.make({\n orgId: d.orgId,\n data: result,\n }),\n }));\n await outbox.append([runEvent, ...actionFailedEvents]);\n });\n this.logOutcome(d, status, results);\n }\n\n private logOutcome(\n d: AutomationDispatch,\n status: 'completed' | 'failed',\n results: AutomationActionResult[],\n ): void {\n const run = {\n orgId: d.orgId,\n automationId: d.automationId,\n runId: d.runId,\n trigger: d.event.type,\n eventId: d.event.id,\n };\n if (status === 'failed') {\n const failed = results.find((r) => r.status === 'failed');\n const index = failed?.index ?? results.length;\n this.logger.error('automation run failed', {\n ...run,\n index,\n action: failed?.type ?? d.actions[index]?.type,\n error: failed?.error ?? 'not every action ran',\n });\n return;\n }\n const partial = results.filter((r) => r.error !== undefined);\n if (partial.length > 0) {\n this.logger.warn('automation run completed with failed deliveries', {\n ...run,\n actions: partial.map(({ index, type, error }) => ({ index, type, error })),\n });\n }\n }\n\n async create(input: CreateAutomationInput, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n assertCatalogTriggers(input.triggers, true);\n assertAuthorableActions(input.actions);\n assertGrantsAllowed(actor, input.actions);\n const automation = await this.uow.run(async ({ automations, outbox }) => {\n const created = await automations.insert(orgId, {\n ...input,\n triggers: unique(input.triggers),\n kind: 'workflow',\n });\n await outbox.append([automationEvents.automationCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('automation created', {\n orgId,\n automationId: automation.id,\n actorId: actor.id,\n });\n await this.reconcileSchedules(orgId);\n return automation;\n }\n\n async update(id: string, input: UpdateAutomationInput, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n if (input.triggers !== undefined) {\n assertCatalogTriggers(input.triggers, true);\n }\n if (input.actions !== undefined) {\n assertAuthorableActions(input.actions);\n assertGrantsAllowed(actor, input.actions);\n }\n const existing = await this.repo.findById(orgId, id);\n if (!existing) {\n throw new NotFoundError('Automation', id);\n }\n if (existing.kind !== 'workflow') {\n throw new AutomationKindMismatchError(id, existing.kind);\n }\n const patch =\n input.triggers === undefined ? input : { ...input, triggers: unique(input.triggers) };\n const automation = await this.uow.run(async ({ automations, outbox }) => {\n const updated = await automations.update(orgId, id, patch);\n if (!updated) {\n return null;\n }\n await outbox.append([updateEvent(orgId, input, updated)]);\n return updated;\n });\n if (!automation) {\n throw new NotFoundError('Automation', id);\n }\n this.logger.info('automation updated', { orgId, automationId: id });\n await this.reconcileSchedules(orgId);\n return automation;\n }\n\n async delete(id: string, actor: Actor): Promise<void> {\n actor.permissions.require('automations:delete');\n const orgId = actor.orgId;\n const existing = await this.repo.findById(orgId, id);\n if (!existing) {\n throw new NotFoundError('Automation', id);\n }\n if (existing.kind !== 'workflow') {\n throw new AutomationKindMismatchError(id, existing.kind);\n }\n const deleted = await this.uow.run(async ({ automations, outbox }) => {\n const removed = await automations.delete(orgId, id);\n if (!removed) {\n return null;\n }\n await outbox.append([automationEvents.automationDeleted.make({ orgId, data: removed })]);\n return removed;\n });\n if (!deleted) {\n throw new NotFoundError('Automation', id);\n }\n this.logger.info('automation deleted', { orgId, automationId: id });\n await this.reconcileSchedules(orgId);\n }\n\n async list(query: AutomationsQuery | undefined, actor: Actor): Promise<Automation[]> {\n actor.permissions.require('automations:read');\n const automations = await this.repo.listByOrg(actor.orgId, query?.kind);\n return automations.map(toAutomationView);\n }\n\n async get(id: string, actor: Actor): Promise<Automation> {\n actor.permissions.require('automations:read');\n const automation = await this.repo.findById(actor.orgId, id);\n if (!automation) {\n throw new NotFoundError('Automation', id);\n }\n return toAutomationView(automation);\n }\n\n async listRuns(\n automationId: string,\n query: AutomationRunsQuery,\n actor: Actor,\n ): Promise<Page<AutomationRun>> {\n actor.permissions.require('automations:read');\n const page = await this.runsRepo.list(actor.orgId, automationId, query);\n return { ...page, rows: page.rows.map(toRunView) };\n }\n\n async rerun(automationId: string, runId: string, actor: Actor): Promise<AutomationRun> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.runsRepo.findById(orgId, runId);\n if (!existing || existing.automationId !== automationId) {\n throw new NotFoundError('Automation run', runId);\n }\n const automation = await this.repo.findById(orgId, automationId);\n if (!automation) {\n throw new NotFoundError('Automation', automationId);\n }\n assertGrantsAllowed(actor, automation.actions);\n const run = await this.uow.run(async ({ runs, outbox }) => {\n const restarted = await runs.restart(orgId, runId, new Date());\n if (!restarted) {\n return null;\n }\n await outbox.append([automationEvents.runStarted.make({ orgId, data: restarted })]);\n return restarted;\n });\n if (!run) {\n throw new ConflictError(`automation run ${runId} is still running`);\n }\n try {\n await this.engine.dispatch(this.dispatchFor(automation, run, run.event));\n } catch (err) {\n this.logger.error('automation rerun failed', {\n orgId,\n automationId,\n runId: run.id,\n trigger: run.trigger,\n err,\n });\n const failed = await this.recordFailure(\n run,\n err instanceof Error ? err.message : String(err),\n );\n return toRunView(failed ?? run);\n }\n this.logger.info('automation rerun started', { orgId, automationId, runId: run.id });\n return toRunView(run);\n }\n\n async listWebhooks(actor: Actor): Promise<Webhook[]> {\n actor.permissions.require('automations:read');\n const automations = await this.repo.listByOrg(actor.orgId, 'webhook');\n return automations.map(toWebhook);\n }\n\n async getWebhook(id: string, actor: Actor): Promise<Webhook> {\n actor.permissions.require('automations:read');\n return toWebhook(await this.requireWebhook(actor.orgId, id));\n }\n\n async createWebhook(input: CreateWebhookInput, actor: Actor): Promise<CreatedWebhook> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const events = unique(input.events);\n assertCatalogTriggers(events, false);\n assertReadsEvents(actor, events);\n const secret = generateWebhookSecret();\n const automation = await this.uow.run(async ({ automations, credentials, outbox }) => {\n const secretRef = await credentials.store(orgId, { secret });\n const created = await automations.insert(orgId, {\n kind: 'webhook',\n name: input.url,\n description: input.description,\n triggers: events,\n actions: [{ type: DELIVER_WEBHOOK_ACTION, input: { url: input.url, secretRef } }],\n });\n await outbox.append([automationEvents.automationCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('webhook created', { orgId, automationId: automation.id });\n return { ...toWebhook(automation), secret };\n }\n\n async updateWebhook(id: string, input: UpdateWebhookInput, actor: Actor): Promise<Webhook> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const events = input.events === undefined ? undefined : unique(input.events);\n if (events) {\n assertCatalogTriggers(events, false);\n assertReadsEvents(actor, events);\n }\n const { secretRef } = webhookTarget(existing);\n const patch: UpdateAutomationInput = {\n name: input.url,\n description: input.description,\n triggers: events,\n actions:\n input.url === undefined\n ? undefined\n : [{ type: DELIVER_WEBHOOK_ACTION, input: { url: input.url, secretRef } }],\n enabled: input.enabled,\n };\n const updated = await this.uow.run(async ({ automations, outbox }) => {\n const row = await automations.update(orgId, id, patch);\n if (!row) {\n return null;\n }\n await outbox.append([updateEvent(orgId, input, row)]);\n return row;\n });\n if (!updated) {\n throw new NotFoundError('Webhook', id);\n }\n this.logger.info('webhook updated', { orgId, automationId: id });\n return toWebhook(updated);\n }\n\n async deleteWebhook(id: string, actor: Actor): Promise<void> {\n actor.permissions.require('automations:delete');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const { secretRef } = webhookTarget(existing);\n const deleted = await this.uow.run(async ({ automations, credentials, outbox }) => {\n const removed = await automations.delete(orgId, id);\n if (!removed) {\n return null;\n }\n await credentials.destroy(orgId, secretRef);\n await outbox.append([automationEvents.automationDeleted.make({ orgId, data: removed })]);\n return removed;\n });\n if (!deleted) {\n throw new NotFoundError('Webhook', id);\n }\n this.logger.info('webhook deleted', { orgId, automationId: id });\n }\n\n async revealWebhookSecret(id: string, actor: Actor): Promise<WebhookSecret> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const secrets = await this.credentials.reveal(orgId, webhookTarget(existing).secretRef);\n const secret = secrets?.['secret'];\n if (typeof secret !== 'string') {\n throw new Error(`webhook ${id}: signing secret is missing`);\n }\n return { secret };\n }\n\n async rotateWebhookSecret(id: string, actor: Actor): Promise<WebhookSecret> {\n actor.permissions.require('automations:write');\n const orgId = actor.orgId;\n const existing = await this.requireWebhook(orgId, id);\n const secret = generateWebhookSecret();\n await this.credentials.update(orgId, webhookTarget(existing).secretRef, { secret });\n this.logger.info('webhook secret rotated', { orgId, automationId: id });\n return { secret };\n }\n\n private async requireWebhook(orgId: string, id: string): Promise<Automation> {\n const automation = await this.repo.findById(orgId, id);\n if (automation?.kind !== 'webhook') {\n throw new NotFoundError('Webhook', id);\n }\n return automation;\n }\n\n availableActions(actor: Actor): AvailableActions {\n actor.permissions.require('automations:read');\n return [\n ...catalogActions(),\n ...this.integrations.available(this.systemActor(actor.orgId)).flatMap((integration) =>\n integration.actions.map((action) => ({\n type: `${integration.id}.${action.id}`,\n description: action.description,\n inputSchema: action.inputSchema as Record<string, JsonValue>,\n source: integration.id,\n })),\n ),\n ];\n }\n\n availableTriggers(actor: Actor): AvailableTriggers {\n actor.permissions.require('automations:read');\n return { triggers: catalogTriggers() };\n }\n}\n"],"mappings":"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"}
@@ -14,7 +14,7 @@ type ContentServiceParams = {
14
14
  repo: ContentRepository;
15
15
  uow: ContentUnitOfWork;
16
16
  libraryAssets: ContentAssetReader;
17
- files: Pick<AssetFiles, 'readyFiles' | 'syncPublicImages'>;
17
+ files: Pick<AssetFiles, 'readyFiles' | 'fileLinks' | 'syncPublicImages'>;
18
18
  fileUrls: FileUrls;
19
19
  scheduler: Pick<Scheduler, 'at' | 'cancel'>;
20
20
  logger?: Logger;
@@ -32,15 +32,18 @@ declare class ContentService implements CourseManagementService, DownloadablesSe
32
32
  private requireAny;
33
33
  private visibleIds;
34
34
  private withThumbnail;
35
+ private withThumbnails;
35
36
  private syncThumbnail;
36
37
  private withdrawThumbnail;
37
38
  private spaceView;
39
+ private spaceViews;
38
40
  private communityPublished;
39
41
  private oneSpaceView;
40
42
  private syncSpaceImages;
41
43
  private pageWithThumbnails;
42
44
  private downloadFiles;
43
45
  private eventFiles;
46
+ private listedFiles;
44
47
  private assertAttachable;
45
48
  private courseAssetIds;
46
49
  private downloadAssetIds;
@@ -93,11 +93,17 @@ var ContentService = class {
93
93
  }
94
94
  return ids;
95
95
  }
96
- withThumbnail(item) {
97
- return {
96
+ async withThumbnail(item) {
97
+ const [view] = await this.withThumbnails([item]);
98
+ return view;
99
+ }
100
+ async withThumbnails(items) {
101
+ const gated = items.flatMap((item) => item.thumbnailAssetId && !shownPublicly(item) ? [{ assetId: item.thumbnailAssetId }] : []);
102
+ const links = gated.length > 0 ? await this.files.fileLinks(items[0].orgId, gated) : {};
103
+ return items.map((item) => ({
98
104
  ...item,
99
- thumbnailUrl: !item.thumbnailAssetId ? null : shownPublicly(item) ? this.fileUrls.publicImage(item.orgId, item.id, item.thumbnailAssetId) : this.fileUrls.thumbnail(item.type, item.id)
100
- };
105
+ thumbnailUrl: !item.thumbnailAssetId ? null : shownPublicly(item) ? this.fileUrls.publicImage(item.orgId, item.id, item.thumbnailAssetId) : links[item.thumbnailAssetId] ?? null
106
+ }));
101
107
  }
102
108
  syncThumbnail(item) {
103
109
  return this.files.syncPublicImages(item.orgId, item.id, shownPublicly(item) && item.thumbnailAssetId ? [item.thumbnailAssetId] : []);
@@ -105,14 +111,22 @@ var ContentService = class {
105
111
  withdrawThumbnail(orgId, id) {
106
112
  return this.files.syncPublicImages(orgId, id, []);
107
113
  }
108
- spaceView(space, published) {
109
- const url = (image, assetId) => !assetId ? null : published ? this.fileUrls.publicImage(space.orgId, space.id, assetId) : this.fileUrls.spaceImage(space.id, image);
110
- return {
111
- ...space,
112
- coverUrl: url("cover", space.coverAssetId),
113
- backgroundUrl: url("background", space.backgroundAssetId),
114
- iconUrl: url("icon", space.iconAssetId)
115
- };
114
+ async spaceView(space, published) {
115
+ const [view] = await this.spaceViews([space], published);
116
+ return view;
117
+ }
118
+ async spaceViews(spaces, published) {
119
+ const gated = published ? [] : spaces.flatMap((space) => spaceImageIds(space));
120
+ const links = gated.length > 0 ? await this.files.fileLinks(spaces[0].orgId, gated.map((assetId) => ({ assetId }))) : {};
121
+ return spaces.map((space) => {
122
+ const url = (assetId) => !assetId ? null : published ? this.fileUrls.publicImage(space.orgId, space.id, assetId) : links[assetId] ?? null;
123
+ return {
124
+ ...space,
125
+ coverUrl: url(space.coverAssetId),
126
+ backgroundUrl: url(space.backgroundAssetId),
127
+ iconUrl: url(space.iconAssetId)
128
+ };
129
+ });
116
130
  }
117
131
  async communityPublished(orgId, communityId) {
118
132
  return (await this.repo.findCommunityById(orgId, communityId))?.status === "published";
@@ -123,31 +137,34 @@ var ContentService = class {
123
137
  syncSpaceImages(space, published) {
124
138
  return this.files.syncPublicImages(space.orgId, space.id, published ? spaceImageIds(space) : []);
125
139
  }
126
- pageWithThumbnails(page) {
140
+ async pageWithThumbnails(page) {
127
141
  return {
128
142
  ...page,
129
- rows: page.rows.map((row) => this.withThumbnail(row))
143
+ rows: await this.withThumbnails(page.rows)
130
144
  };
131
145
  }
132
- async downloadFiles(orgId, links) {
146
+ downloadFiles(orgId, links) {
147
+ return this.listedFiles(orgId, links);
148
+ }
149
+ eventFiles(orgId, links) {
150
+ return this.listedFiles(orgId, links);
151
+ }
152
+ async listedFiles(orgId, links) {
133
153
  const ready = await this.files.readyFiles(orgId, links.map((link) => link.assetId));
134
- return links.flatMap((link) => {
154
+ const urls = await this.files.fileLinks(orgId, links.flatMap((link) => {
135
155
  const file = ready[link.assetId];
136
156
  return file ? [{
137
- ...link,
138
- ...file,
139
- url: this.fileUrls.downloadFile(link.downloadId, link.assetId)
157
+ assetId: link.assetId,
158
+ saveAs: link.displayName ?? file.filename
140
159
  }] : [];
141
- });
142
- }
143
- async eventFiles(orgId, links) {
144
- const ready = await this.files.readyFiles(orgId, links.map((link) => link.assetId));
160
+ }));
145
161
  return links.flatMap((link) => {
146
162
  const file = ready[link.assetId];
147
- return file ? [{
163
+ const url = urls[link.assetId];
164
+ return file && url ? [{
148
165
  ...link,
149
166
  ...file,
150
- url: this.fileUrls.eventFile(link.eventId, link.assetId)
167
+ url
151
168
  }] : [];
152
169
  });
153
170
  }
@@ -321,9 +338,13 @@ var ContentService = class {
321
338
  async listCourseActivityViews(courseId, actor) {
322
339
  this.require(actor, "courses:read", courseId);
323
340
  const [activities, links] = await Promise.all([this.repo.listActivitiesForCourse(actor.orgId, courseId), this.repo.listActivityAssetsForCourse(actor.orgId, courseId)]);
341
+ const urls = await this.files.fileLinks(actor.orgId, links.map((link) => ({ assetId: link.assetId })));
324
342
  return activities.map((activity) => ({
325
343
  ...activity,
326
- assetUrls: Object.fromEntries(links.filter((link) => link.activityId === activity.id).map((link) => [link.assetId, this.fileUrls.activityAsset(activity.id, link.assetId)]))
344
+ assetUrls: Object.fromEntries(links.flatMap((link) => {
345
+ const url = urls[link.assetId];
346
+ return link.activityId === activity.id && url ? [[link.assetId, url]] : [];
347
+ }))
327
348
  }));
328
349
  }
329
350
  async listCourseActivityAssets(courseId, actor) {
@@ -763,7 +784,8 @@ var ContentService = class {
763
784
  async findEvents(ids, actor) {
764
785
  for (const id of ids) this.require(actor, "events:read", id);
765
786
  if (ids.length === 0) return [];
766
- return (await this.repo.findEventsByIds(actor.orgId, ids)).map((event) => this.withThumbnail(event));
787
+ const found = await this.repo.findEventsByIds(actor.orgId, ids);
788
+ return this.withThumbnails(found);
767
789
  }
768
790
  async createEvent(input, actor) {
769
791
  this.require(actor, "events:write");
@@ -1269,7 +1291,8 @@ var ContentService = class {
1269
1291
  this.requireAny(actor, COMMUNITY_READERS, communityId);
1270
1292
  const community = await this.repo.findCommunityById(actor.orgId, communityId);
1271
1293
  if (!community) throw new NotFoundError("Community", communityId);
1272
- return (await this.repo.listSpaces(actor.orgId, communityId)).map((space) => this.spaceView(space, community.status === "published"));
1294
+ const spaces = await this.repo.listSpaces(actor.orgId, communityId);
1295
+ return this.spaceViews(spaces, community.status === "published");
1273
1296
  }
1274
1297
  async getDmSettings(communityId, actor) {
1275
1298
  this.require(actor, "communities:read", communityId);
@@ -1529,7 +1552,7 @@ var ContentService = class {
1529
1552
  actorId: actor.id
1530
1553
  });
1531
1554
  const published = await this.communityPublished(orgId, communityId);
1532
- return spaces.map((space) => this.spaceView(space, published));
1555
+ return this.spaceViews(spaces, published);
1533
1556
  }
1534
1557
  async setSpaceTopics(communityId, spaceId, input, actor) {
1535
1558
  this.require(actor, "communities:write", communityId);