@dalmore/api-contracts 0.0.0-dev.2dc8e92 → 0.0.0-dev.4bb6b7a

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 (206) hide show
  1. package/README.md +23 -19
  2. package/common/types/account-contact.types.ts +2 -1
  3. package/common/types/account-manager.types.ts +3 -7
  4. package/common/types/account-setting.types.ts +65 -0
  5. package/common/types/account.types.ts +1 -0
  6. package/common/types/activity.types.ts +1 -1
  7. package/common/types/api-key-logs.types.ts +1 -1
  8. package/common/types/asset.types.ts +14 -14
  9. package/common/types/auth.types.ts +7 -18
  10. package/common/types/bonus-tier.types.ts +33 -0
  11. package/common/types/cart.types.ts +4 -1
  12. package/common/types/common.types.ts +38 -6
  13. package/common/types/contact-us.types.ts +6 -2
  14. package/common/types/covered-person.types.ts +2 -1
  15. package/common/types/dashboard.types.ts +2 -9
  16. package/common/types/disbursements.types.ts +119 -3
  17. package/common/types/escrow-account.types.ts +3 -3
  18. package/common/types/file.types.ts +20 -4
  19. package/common/types/i-will-do-it-later.types.ts +68 -0
  20. package/common/types/index.ts +2 -0
  21. package/common/types/individuals.types.ts +5 -17
  22. package/common/types/investor-account.types.ts +2 -1
  23. package/common/types/invite.types.ts +27 -1
  24. package/common/types/issuer-offering.types.ts +113 -30
  25. package/common/types/issuer-payment-method.types.ts +41 -0
  26. package/common/types/issuer.types.ts +9 -0
  27. package/common/types/legal-entity.types.ts +3 -2
  28. package/common/types/mail-template.types.ts +34 -0
  29. package/common/types/note.types.ts +1 -1
  30. package/common/types/notification.types.ts +525 -29
  31. package/common/types/offering.types.ts +106 -20
  32. package/common/types/site-settings.types.ts +2 -1
  33. package/common/types/site.types.ts +2 -9
  34. package/common/types/{trade-line-item.type.ts → trade-line-item.types.ts} +2 -9
  35. package/common/types/trade.types.ts +118 -2
  36. package/common/types/transaction.types.ts +12 -1
  37. package/common/types/trusted-contact.types.ts +7 -7
  38. package/common/types/user.types.ts +17 -33
  39. package/common/types/webhook.types.ts +142 -0
  40. package/contracts/{investors → clients}/cart/index.ts +11 -6
  41. package/contracts/clients/index.ts +10 -0
  42. package/contracts/{investors → clients}/issuer-payment-methods/index.ts +5 -2
  43. package/contracts/{compliance → clients}/payment-methods/index.ts +40 -12
  44. package/contracts/{investors → clients}/trade-line-items/index.ts +19 -28
  45. package/contracts/clients/trades/index.ts +65 -1
  46. package/contracts/{investors → clients}/transactions/index.ts +3 -3
  47. package/index.ts +10 -0
  48. package/package.json +23 -32
  49. package/contracts/compliance/account-contacts/index.ts +0 -82
  50. package/contracts/compliance/account-managers/index.ts +0 -142
  51. package/contracts/compliance/accounts/index.ts +0 -187
  52. package/contracts/compliance/activities/index.ts +0 -55
  53. package/contracts/compliance/aic/index.ts +0 -60
  54. package/contracts/compliance/api-keys/index.ts +0 -91
  55. package/contracts/compliance/assets/index.ts +0 -122
  56. package/contracts/compliance/auth/index.ts +0 -134
  57. package/contracts/compliance/batch-jobs/index.ts +0 -62
  58. package/contracts/compliance/bonus-tiers/index.ts +0 -55
  59. package/contracts/compliance/checklist/index.ts +0 -87
  60. package/contracts/compliance/checklist-items/index.ts +0 -86
  61. package/contracts/compliance/covered-persons/index.ts +0 -97
  62. package/contracts/compliance/dashboard/index.ts +0 -111
  63. package/contracts/compliance/data-records/index.ts +0 -116
  64. package/contracts/compliance/data-rooms/index.ts +0 -113
  65. package/contracts/compliance/default-theme-configs/index.ts +0 -95
  66. package/contracts/compliance/disbursement/index.ts +0 -165
  67. package/contracts/compliance/disbursement-approval-users/index.ts +0 -84
  68. package/contracts/compliance/disbursement-transactions/index.ts +0 -37
  69. package/contracts/compliance/domain-filters/index.ts +0 -117
  70. package/contracts/compliance/email-themes/index.ts +0 -284
  71. package/contracts/compliance/escrow-accounts/index.ts +0 -85
  72. package/contracts/compliance/exchange-api-keys/index.ts +0 -129
  73. package/contracts/compliance/exchange-imports/index.ts +0 -137
  74. package/contracts/compliance/files/index.ts +0 -267
  75. package/contracts/compliance/files-public/index.ts +0 -188
  76. package/contracts/compliance/health/index.ts +0 -26
  77. package/contracts/compliance/index.ts +0 -147
  78. package/contracts/compliance/individuals/index.ts +0 -57
  79. package/contracts/compliance/investor-accounts/index.ts +0 -141
  80. package/contracts/compliance/invites/index.ts +0 -137
  81. package/contracts/compliance/issuer-bank-accounts/index.ts +0 -81
  82. package/contracts/compliance/issuer-payment-methods/index.ts +0 -81
  83. package/contracts/compliance/issuers/index.ts +0 -97
  84. package/contracts/compliance/job-items/index.ts +0 -58
  85. package/contracts/compliance/jobs/index.ts +0 -59
  86. package/contracts/compliance/kyb/index.ts +0 -54
  87. package/contracts/compliance/kyc/index.ts +0 -77
  88. package/contracts/compliance/legal-entities/index.ts +0 -57
  89. package/contracts/compliance/login-histories/index.ts +0 -37
  90. package/contracts/compliance/notes/index.ts +0 -69
  91. package/contracts/compliance/notion-databases/index.ts +0 -107
  92. package/contracts/compliance/notion-pages/index.ts +0 -105
  93. package/contracts/compliance/offering-reports/index.ts +0 -149
  94. package/contracts/compliance/offerings/index.ts +0 -233
  95. package/contracts/compliance/pages/index.ts +0 -178
  96. package/contracts/compliance/rejection-reasons/index.ts +0 -32
  97. package/contracts/compliance/review/index.ts +0 -169
  98. package/contracts/compliance/roles/index.ts +0 -34
  99. package/contracts/compliance/secondary-customers/index.ts +0 -77
  100. package/contracts/compliance/secondary-orders/index.ts +0 -60
  101. package/contracts/compliance/secondary-trades/index.ts +0 -100
  102. package/contracts/compliance/secure-requests/index.ts +0 -54
  103. package/contracts/compliance/signer/index.ts +0 -369
  104. package/contracts/compliance/site-links/index.ts +0 -128
  105. package/contracts/compliance/site-settings/index.ts +0 -669
  106. package/contracts/compliance/sites/index.ts +0 -56
  107. package/contracts/compliance/state-machine/index.ts +0 -94
  108. package/contracts/compliance/tasks/index.ts +0 -91
  109. package/contracts/compliance/third-parties/index.ts +0 -52
  110. package/contracts/compliance/trade-line-items/index.ts +0 -59
  111. package/contracts/compliance/trades/index.ts +0 -230
  112. package/contracts/compliance/transactions/index.ts +0 -161
  113. package/contracts/compliance/user-manuals/index.ts +0 -271
  114. package/contracts/compliance/user-settings/index.ts +0 -189
  115. package/contracts/compliance/users/index.ts +0 -200
  116. package/contracts/compliance/webhooks/index.ts +0 -41
  117. package/contracts/compliance-apikey/accounts/index.ts +0 -58
  118. package/contracts/compliance-apikey/index.ts +0 -14
  119. package/contracts/index.ts +0 -6
  120. package/contracts/investors/account-contacts/index.ts +0 -58
  121. package/contracts/investors/aic/index.ts +0 -59
  122. package/contracts/investors/assets/index.ts +0 -61
  123. package/contracts/investors/auth/index.ts +0 -116
  124. package/contracts/investors/bonus-tiers/index.ts +0 -37
  125. package/contracts/investors/contact-us/index.ts +0 -48
  126. package/contracts/investors/data-records/index.ts +0 -113
  127. package/contracts/investors/data-rooms/index.ts +0 -96
  128. package/contracts/investors/files/index.ts +0 -167
  129. package/contracts/investors/files-public/index.ts +0 -185
  130. package/contracts/investors/index.ts +0 -72
  131. package/contracts/investors/individuals/index.ts +0 -99
  132. package/contracts/investors/investor-accounts/index.ts +0 -110
  133. package/contracts/investors/issuers/index.ts +0 -30
  134. package/contracts/investors/legal-entities/index.ts +0 -93
  135. package/contracts/investors/notes/index.ts +0 -69
  136. package/contracts/investors/offerings/index.ts +0 -93
  137. package/contracts/investors/pages/index.ts +0 -88
  138. package/contracts/investors/payment-methods/index.ts +0 -149
  139. package/contracts/investors/portfolios/index.ts +0 -53
  140. package/contracts/investors/sites/index.ts +0 -96
  141. package/contracts/investors/tasks/index.ts +0 -111
  142. package/contracts/investors/trades/index.ts +0 -114
  143. package/contracts/investors/trusted-contacts/index.ts +0 -93
  144. package/contracts/investors/user-manuals/index.ts +0 -62
  145. package/contracts/investors/user-settings/index.ts +0 -170
  146. package/contracts/investors/users/index.ts +0 -45
  147. package/contracts/investors/webhooks/index.ts +0 -30
  148. package/contracts/issuers/account-contacts/index.ts +0 -76
  149. package/contracts/issuers/account-integrations/index.ts +0 -97
  150. package/contracts/issuers/accounts/index.ts +0 -97
  151. package/contracts/issuers/activities/index.ts +0 -54
  152. package/contracts/issuers/aic/index.ts +0 -39
  153. package/contracts/issuers/api-key-logs/index.ts +0 -53
  154. package/contracts/issuers/api-keys/index.ts +0 -93
  155. package/contracts/issuers/assets/index.ts +0 -122
  156. package/contracts/issuers/auth/index.ts +0 -152
  157. package/contracts/issuers/bonus-tiers/index.ts +0 -55
  158. package/contracts/issuers/contact-us/index.ts +0 -48
  159. package/contracts/issuers/covered-persons/index.ts +0 -136
  160. package/contracts/issuers/dashboard/index.ts +0 -72
  161. package/contracts/issuers/data-records/index.ts +0 -257
  162. package/contracts/issuers/data-rooms/index.ts +0 -134
  163. package/contracts/issuers/disbursement-approval-users/index.ts +0 -82
  164. package/contracts/issuers/disbursement-transactions/index.ts +0 -53
  165. package/contracts/issuers/disbursements/index.ts +0 -153
  166. package/contracts/issuers/email-themes/index.ts +0 -242
  167. package/contracts/issuers/escrow-accounts/index.ts +0 -81
  168. package/contracts/issuers/exchange-api-keys/index.ts +0 -144
  169. package/contracts/issuers/files/index.ts +0 -166
  170. package/contracts/issuers/files-public/index.ts +0 -166
  171. package/contracts/issuers/health/index.ts +0 -24
  172. package/contracts/issuers/index.ts +0 -112
  173. package/contracts/issuers/investor-accounts/index.ts +0 -148
  174. package/contracts/issuers/invites/index.ts +0 -129
  175. package/contracts/issuers/issuer/index.ts +0 -94
  176. package/contracts/issuers/issuer-bank-accounts/index.ts +0 -81
  177. package/contracts/issuers/issuer-payment-methods/index.ts +0 -136
  178. package/contracts/issuers/kyc/index.ts +0 -38
  179. package/contracts/issuers/login-histories/index.ts +0 -51
  180. package/contracts/issuers/notes/index.ts +0 -69
  181. package/contracts/issuers/offerings/index.ts +0 -206
  182. package/contracts/issuers/pages/index.ts +0 -138
  183. package/contracts/issuers/payment-methods/index.ts +0 -61
  184. package/contracts/issuers/portfolios/index.ts +0 -36
  185. package/contracts/issuers/rejection-reasons/index.ts +0 -32
  186. package/contracts/issuers/review/index.ts +0 -63
  187. package/contracts/issuers/secondary-customers/index.ts +0 -55
  188. package/contracts/issuers/secondary-orders/index.ts +0 -57
  189. package/contracts/issuers/secondary-trades/index.ts +0 -57
  190. package/contracts/issuers/secure-requests/index.ts +0 -34
  191. package/contracts/issuers/site-links/index.ts +0 -116
  192. package/contracts/issuers/site-settings/index.ts +0 -585
  193. package/contracts/issuers/sites/index.ts +0 -32
  194. package/contracts/issuers/tasks/index.ts +0 -111
  195. package/contracts/issuers/trades/index.ts +0 -132
  196. package/contracts/issuers/transactions/index.ts +0 -158
  197. package/contracts/issuers/user-manuals/index.ts +0 -62
  198. package/contracts/issuers/user-settings/index.ts +0 -170
  199. package/contracts/issuers/users/index.ts +0 -126
  200. package/contracts/secondaries/accounts/index.ts +0 -58
  201. package/contracts/secondaries/index.ts +0 -23
  202. package/contracts/secondaries/secondary-customers/index.ts +0 -55
  203. package/contracts/secondaries/secondary-issuers/index.ts +0 -94
  204. package/contracts/secondaries/secondary-orders/index.ts +0 -56
  205. package/contracts/secondaries/secondary-securities/index.ts +0 -95
  206. package/contracts/secondaries/secondary-trades/index.ts +0 -56
@@ -152,6 +152,10 @@ export const PutIssuerZod = z
152
152
  .lazy(() => fileIdSchema)
153
153
  .optional()
154
154
  .nullable(),
155
+ formationDocumentFileId: z
156
+ .lazy(() => fileIdSchema)
157
+ .optional()
158
+ .nullable(),
155
159
  coverArtId: z
156
160
  .lazy(() => fileIdSchema)
157
161
  .optional()
@@ -188,6 +192,11 @@ export const IIssuer = IBaseEntity.extend({
188
192
  accountId: z.string(),
189
193
  account: AccountZod.optional().nullable(),
190
194
  ss4LetterFileId: z.string().nullable(),
195
+ formationDocumentFileId: z.string().nullable(),
196
+ formationDocument: z
197
+ .lazy(() => FileZod)
198
+ .nullable()
199
+ .optional(),
191
200
  status: z
192
201
  .nativeEnum(IssuerStatus)
193
202
  .openapi({ example: IssuerStatus.SUBMITTED }),
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { IBaseEntity } from './entity.types';
3
3
  import {
4
4
  dateSchema,
5
+ EmailSchema,
5
6
  IPaginationMeta,
6
7
  KYBStatus,
7
8
  SanctionsStatus,
@@ -138,7 +139,7 @@ export const PostLegalEntitySchema = z
138
139
  investorAccountId: investorAccountIdSchema,
139
140
  name: CompanyNameSchema,
140
141
  ein: EINSchema,
141
- email: z.string().email().optional(),
142
+ email: EmailSchema.optional(),
142
143
  companyType: CompanyTypeSchema.optional(),
143
144
  phone: PhoneZodSchema.openapi({ example: '+12124567890' }).optional(),
144
145
  dateOfIncorporation: z.lazy(() => dateSchema).optional(),
@@ -153,7 +154,7 @@ export const UpdateLegalEntitySchema = z
153
154
  id: legalEntityIdSchema.optional(),
154
155
  name: CompanyNameSchema.optional(),
155
156
  ein: EINSchema,
156
- email: z.string().email().optional(),
157
+ email: EmailSchema.optional(),
157
158
  companyType: CompanyTypeSchema.optional(),
158
159
  phone: PhoneZodSchema.openapi({ example: '+12124567890' }).optional(),
159
160
  dateOfIncorporation: z.lazy(() => dateSchema).optional(),
@@ -434,3 +434,37 @@ export type EmailThemeSetting = z.infer<typeof EmailThemeSettingZod>;
434
434
 
435
435
  export const EmailThemeSettingsZod = z.array(EmailThemeSettingZod);
436
436
  export type EmailThemeSettingsZod = z.infer<typeof EmailThemeSettingsZod>;
437
+
438
+ /**
439
+ * Response from a successful email send operation.
440
+ * Based on SendGrid SDK's ClientResponse structure.
441
+ */
442
+ export const EmailSendResponseSchema = z.object({
443
+ /** HTTP status code from SendGrid (e.g., 202 for accepted) */
444
+ statusCode: z.number(),
445
+ /** SendGrid's x-message-id header for tracking emails in their dashboard */
446
+ messageId: z.string().optional(),
447
+ /** Full response headers, available for debugging if needed */
448
+ headers: z.record(z.string(), z.unknown()).optional(),
449
+ });
450
+ export type EmailSendResponse = z.infer<typeof EmailSendResponseSchema>;
451
+
452
+ /**
453
+ * Error from a failed email send operation.
454
+ * Fields are optional because error shapes vary depending on failure type
455
+ * (network errors vs SendGrid API errors).
456
+ */
457
+ export const EmailSendErrorSchema = z.object({
458
+ /** Human-readable error message */
459
+ message: z.string(),
460
+ /** Network-level error code (e.g., 'ECONNREFUSED', 'ETIMEDOUT') */
461
+ code: z.string().optional(),
462
+ /** HTTP status code if request reached SendGrid (e.g., 400, 401, 403, 413, 500) */
463
+ statusCode: z.number().optional(),
464
+ /**
465
+ * SendGrid's error response body. Structure: { errors: [{ message, field, help, id }] }
466
+ * @see https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send#responses
467
+ */
468
+ response: z.unknown().optional(),
469
+ });
470
+ export type EmailSendError = z.infer<typeof EmailSendErrorSchema>;
@@ -79,7 +79,7 @@ export const PostNoteBody = z.object({
79
79
  export type PostNoteBody = z.infer<typeof PostNoteBody>;
80
80
 
81
81
  export const CompliancePostNoteBody = PostNoteBody.extend({
82
- accountId: accountIdSchema,
82
+ accountId: accountIdSchema.nullable().default(null),
83
83
  });
84
84
 
85
85
  export type CompliancePostNoteBody = z.infer<typeof CompliancePostNoteBody>;
@@ -1,58 +1,554 @@
1
1
  import { extendZodWithOpenApi } from '@anatine/zod-openapi';
2
2
  import { z } from 'zod';
3
3
  import { TypeID } from 'typeid-js';
4
- import { accountIdSchema } from './account.types';
4
+ import { IBaseEntity } from './entity.types';
5
+ import { IPaginationMeta, StringToBooleanSchema } from './common.types';
6
+ import {
7
+ ActivityTypeAction,
8
+ ActivityTypeCategory,
9
+ ActivityTypeResource,
10
+ } from './activity.types';
5
11
 
6
12
  extendZodWithOpenApi(z);
7
- export enum NotificationChannel {
13
+
14
+ export enum NotificationChannelType {
8
15
  EMAIL = 'EMAIL',
9
16
  SLACK = 'SLACK',
17
+ WEBHOOK = 'WEBHOOK',
10
18
  }
11
- export enum NotificationType {
12
- COMPLIANCE_TRADE_ALERT = 'COMPLIANCE_TRADE_ALERT',
13
- TRADE_ALERT = 'TRADE_ALERT',
19
+
20
+ export enum NotificationRecordStatus {
21
+ PENDING = 'PENDING',
22
+ SENT = 'SENT',
23
+ FAILED = 'FAILED',
14
24
  }
15
25
 
16
- export const notificationIdSchema = z.string().refine(
26
+ export type TriggerExclusion = {
27
+ category?: ActivityTypeCategory;
28
+ resource?: ActivityTypeResource;
29
+ action?: ActivityTypeAction;
30
+ };
31
+
32
+ // Excluded notifications
33
+ export const EXCLUDED_NOTIFICATION_TRIGGERS: TriggerExclusion[] = [
34
+ // { action: ActivityTypeAction.RE_SIGN },
35
+ // { action: ActivityTypeAction.RESET_2FA },
36
+ // { action: ActivityTypeAction.LOGIN },
37
+ // { action: ActivityTypeAction.RESET_PASSWORD },
38
+ // { action: ActivityTypeAction.TWO_FACTOR_LOGIN },
39
+ // { action: ActivityTypeAction.FORGOT_PASSWORD },
40
+ // { resource: ActivityTypeResource.USERS },
41
+ // { resource: ActivityTypeResource.THEME_SETTINGS },
42
+ ];
43
+
44
+ export const ISSUER_ALLOWED_CATEGORIES: ActivityTypeCategory[] = [
45
+ ActivityTypeCategory.ISSUER,
46
+ ];
47
+
48
+ /**
49
+ * Checks if an activity type should be excluded from notifications.
50
+ *
51
+ * Each exclusion pattern can specify any combination of category, resource, and action.
52
+ * Omitted fields act as wildcards (match any value).
53
+ *
54
+ * @example
55
+ * // Excludes ALL activities with action RE_SIGN (any category, any resource)
56
+ * { action: ActivityTypeAction.RE_SIGN }
57
+ *
58
+ * @example
59
+ * // Excludes ALL USERS activities (any category, any action)
60
+ * { resource: ActivityTypeResource.USERS }
61
+ *
62
+ * @example
63
+ * // Excludes only COMPLIANCE + TRADES + CREATE (all three must match)
64
+ * { category: ActivityTypeCategory.COMPLIANCE, resource: ActivityTypeResource.TRADES, action: ActivityTypeAction.CREATE }
65
+ *
66
+ * @param category - The activity type category to check
67
+ * @param resource - The activity type resource to check
68
+ * @param action - The activity type action to check
69
+ * @param exclusions - Array of exclusion patterns (defaults to EXCLUDED_NOTIFICATION_TRIGGERS)
70
+ * @returns true if the activity type matches any exclusion pattern
71
+ */
72
+ export function isTriggerExcluded(
73
+ category: string,
74
+ resource: string,
75
+ action: string,
76
+ exclusions: TriggerExclusion[] = EXCLUDED_NOTIFICATION_TRIGGERS,
77
+ ): boolean {
78
+ return exclusions.some((exclusion) => {
79
+ const categoryMatch =
80
+ !exclusion.category || exclusion.category === category;
81
+ const resourceMatch =
82
+ !exclusion.resource || exclusion.resource === resource;
83
+ const actionMatch = !exclusion.action || exclusion.action === action;
84
+ return categoryMatch && resourceMatch && actionMatch;
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Checks if a category is allowed for notification triggers.
90
+ *
91
+ * Used to restrict which activity type categories a user can subscribe to.
92
+ * For example, issuers can only subscribe to ISSUER category triggers.
93
+ *
94
+ * @param category - The activity type category to check
95
+ * @param allowedCategories - Optional array of allowed categories. If empty or undefined, all categories are allowed.
96
+ * @returns true if the category is allowed (or if no restrictions are set)
97
+ */
98
+ export function isCategoryAllowed(
99
+ category: string,
100
+ allowedCategories?: ActivityTypeCategory[],
101
+ ): boolean {
102
+ if (!allowedCategories || allowedCategories.length === 0) {
103
+ return true;
104
+ }
105
+ return allowedCategories.includes(category as ActivityTypeCategory);
106
+ }
107
+
108
+ export const notificationChannelIdSchema = z.string().refine(
17
109
  (value) => {
18
110
  try {
19
111
  const tid = TypeID.fromString(value);
20
- return tid.getType() === 'notification';
112
+ return tid.getType() === 'notification_channel';
21
113
  } catch {
22
114
  return false;
23
115
  }
24
116
  },
25
117
  {
26
118
  message:
27
- 'Invalid notification ID format. Must be a valid TypeID with "notification" prefix.',
119
+ 'Invalid notification channel ID format. Must be a valid TypeID with "notification_channel" prefix.',
28
120
  },
29
121
  );
30
122
 
31
- export const SendNotificationZod = z.object({
32
- message: z.string(),
123
+ export const notificationChannelTriggerIdSchema = z.string().refine(
124
+ (value) => {
125
+ try {
126
+ const tid = TypeID.fromString(value);
127
+ return tid.getType() === 'notification_channel_trigger';
128
+ } catch {
129
+ return false;
130
+ }
131
+ },
132
+ {
133
+ message:
134
+ 'Invalid notification channel trigger ID format. Must be a valid TypeID with "notification_channel_trigger" prefix.',
135
+ },
136
+ );
137
+
138
+ export const notificationRecordIdSchema = z.string().refine(
139
+ (value) => {
140
+ try {
141
+ const tid = TypeID.fromString(value);
142
+ return tid.getType() === 'notification_record';
143
+ } catch {
144
+ return false;
145
+ }
146
+ },
147
+ {
148
+ message:
149
+ 'Invalid notification record ID format. Must be a valid TypeID with "notification_record" prefix.',
150
+ },
151
+ );
152
+
153
+ export const EmailChannelSettingsSchema = z.object({
154
+ recipients: z
155
+ .array(z.string().email())
156
+ .min(1)
157
+ .max(100, 'At most 100 recipients are allowed'),
158
+ subjectTemplate: z.string().optional(),
159
+ });
160
+ export type EmailChannelSettings = z.infer<typeof EmailChannelSettingsSchema>;
161
+
162
+ export const SlackChannelSettingsSchema = z
163
+ .object({
164
+ webhookUrl: z.string().url().optional(),
165
+ channelEmail: z.string().email().optional(),
166
+ })
167
+ .refine((data) => data.webhookUrl || data.channelEmail, {
168
+ message: 'Either webhookUrl or channelEmail is required',
169
+ });
170
+ export type SlackChannelSettings = z.infer<typeof SlackChannelSettingsSchema>;
171
+
172
+ export const WebhookChannelSettingsSchema = z.object({
173
+ url: z.string().url(),
174
+ method: z.enum(['POST', 'PUT']),
175
+ headers: z.record(z.string()).optional(),
176
+ authType: z.enum(['none', 'bearer', 'basic', 'api_key']).default('none'),
177
+ authValue: z.string().optional(),
178
+ });
179
+ export type WebhookChannelSettings = z.infer<
180
+ typeof WebhookChannelSettingsSchema
181
+ >;
182
+
183
+ export const NotificationChannelSettingsSchema = z.discriminatedUnion('type', [
184
+ z.object({
185
+ type: z.literal(NotificationChannelType.EMAIL),
186
+ config: EmailChannelSettingsSchema,
187
+ }),
188
+ z.object({
189
+ type: z.literal(NotificationChannelType.SLACK),
190
+ config: SlackChannelSettingsSchema,
191
+ }),
192
+ z.object({
193
+ type: z.literal(NotificationChannelType.WEBHOOK),
194
+ config: WebhookChannelSettingsSchema,
195
+ }),
196
+ ]);
197
+ export type NotificationChannelSettings = z.infer<
198
+ typeof NotificationChannelSettingsSchema
199
+ >;
200
+
201
+ export const EmailNotificationPayloadSchema = z.object({
202
+ to: z.array(z.string().email()),
33
203
  subject: z.string(),
34
- buttonText: z.string(),
35
- link: z.string(),
36
- debug: z.boolean().default(false),
37
- notificationChannel: z.nativeEnum(NotificationChannel),
38
- channelName: z.string(),
39
- channelEmail: z.string().email(),
40
- notificationType: z.nativeEnum(NotificationType),
204
+ body: z.string(),
205
+ html: z.string().optional(),
206
+ });
207
+ export type EmailNotificationPayload = z.infer<
208
+ typeof EmailNotificationPayloadSchema
209
+ >;
210
+
211
+ /**
212
+ * Payload for Slack notifications sent via Incoming Webhooks.
213
+ *
214
+ * @property webhookUrl - Slack Incoming Webhook URL
215
+ * @property channelEmail - Slack channel email integration (alternative delivery method)
216
+ * @property message - Plain text message (also serves as fallback for notifications)
217
+ * @property blocks - Optional Slack Block Kit elements for rich message formatting.
218
+ * Blocks allow structured layouts with headers, sections, images, buttons, etc.
219
+ * @see https://docs.slack.dev/messaging/creating-interactive-messages
220
+ */
221
+ export const SlackNotificationPayloadSchema = z.object({
222
+ webhookUrl: z.string().url().optional(),
223
+ channelEmail: z.string().email().optional(),
224
+ message: z.string(),
225
+ blocks: z.array(z.record(z.unknown())).optional(),
226
+ });
227
+ export type SlackNotificationPayload = z.infer<
228
+ typeof SlackNotificationPayloadSchema
229
+ >;
230
+
231
+ export const WebhookNotificationPayloadSchema = z.object({
232
+ url: z.string().url(),
233
+ method: z.enum(['POST', 'PUT']),
234
+ headers: z.record(z.string()),
235
+ body: z.record(z.unknown()),
41
236
  });
42
- export type SendNotificationZod = z.infer<typeof SendNotificationZod>;
237
+ export type WebhookNotificationPayload = z.infer<
238
+ typeof WebhookNotificationPayloadSchema
239
+ >;
240
+
241
+ export const NotificationPayloadSchema = z.discriminatedUnion('type', [
242
+ z.object({
243
+ type: z.literal(NotificationChannelType.EMAIL),
244
+ data: EmailNotificationPayloadSchema,
245
+ }),
246
+ z.object({
247
+ type: z.literal(NotificationChannelType.SLACK),
248
+ data: SlackNotificationPayloadSchema,
249
+ }),
250
+ z.object({
251
+ type: z.literal(NotificationChannelType.WEBHOOK),
252
+ data: WebhookNotificationPayloadSchema,
253
+ }),
254
+ ]);
255
+ export type NotificationPayload = z.infer<typeof NotificationPayloadSchema>;
43
256
 
44
- const SlackSettingsSchema = z.object({
45
- channelName: z.string().min(1).max(50),
46
- channelEmail: z.string().email(),
257
+ export const EmailNotificationResponseSchema = z.object({
258
+ messageId: z.string().optional(),
259
+ accepted: z.array(z.string()).optional(),
260
+ rejected: z.array(z.string()).optional(),
47
261
  });
262
+ export type EmailNotificationResponse = z.infer<
263
+ typeof EmailNotificationResponseSchema
264
+ >;
48
265
 
49
- export const PostNotificationZod = z.object({
50
- accountId: accountIdSchema,
51
- notificationChannel: z.literal(NotificationChannel.SLACK), // Only Slack for now
52
- notificationType: z.nativeEnum(NotificationType),
53
- enabled: z.boolean().default(true),
54
- debug: z.boolean().default(false),
55
- settings: SlackSettingsSchema,
266
+ export const SlackNotificationResponseSchema = z.object({
267
+ ok: z.boolean().optional(),
268
+ error: z.string().optional(),
269
+ responseBody: z.string().optional(),
56
270
  });
271
+ export type SlackNotificationResponse = z.infer<
272
+ typeof SlackNotificationResponseSchema
273
+ >;
57
274
 
58
- export type PostNotificationZod = z.infer<typeof PostNotificationZod>;
275
+ export const WebhookNotificationResponseSchema = z.object({
276
+ statusCode: z.number(),
277
+ headers: z.record(z.string()).optional(),
278
+ body: z.unknown().optional(),
279
+ });
280
+ export type WebhookNotificationResponse = z.infer<
281
+ typeof WebhookNotificationResponseSchema
282
+ >;
283
+
284
+ export const NotificationResponseSchema = z.discriminatedUnion('type', [
285
+ z.object({
286
+ type: z.literal(NotificationChannelType.EMAIL),
287
+ data: EmailNotificationResponseSchema,
288
+ }),
289
+ z.object({
290
+ type: z.literal(NotificationChannelType.SLACK),
291
+ data: SlackNotificationResponseSchema,
292
+ }),
293
+ z.object({
294
+ type: z.literal(NotificationChannelType.WEBHOOK),
295
+ data: WebhookNotificationResponseSchema,
296
+ }),
297
+ ]);
298
+ export type NotificationResponse = z.infer<typeof NotificationResponseSchema>;
299
+
300
+ export const INotificationChannelZod = IBaseEntity.extend({
301
+ id: notificationChannelIdSchema.openapi({
302
+ example: 'notification_channel_01j5y5ghx8fvc83dmx3pznq7hv',
303
+ }),
304
+ accountSettingsId: z.string().openapi({
305
+ example: 'account_setting_01j5y5ghx8fvc83dmx3pznq7hv',
306
+ }),
307
+ channelType: z.nativeEnum(NotificationChannelType).openapi({
308
+ example: NotificationChannelType.SLACK,
309
+ }),
310
+ name: z.string().openapi({
311
+ example: 'Compliance Alerts',
312
+ }),
313
+ enabled: z.boolean().openapi({
314
+ example: true,
315
+ }),
316
+ settings: NotificationChannelSettingsSchema.openapi({
317
+ example: {
318
+ type: NotificationChannelType.SLACK,
319
+ config: {
320
+ webhookUrl: 'https://hooks.slack.com/test',
321
+ },
322
+ },
323
+ }),
324
+ });
325
+ export type INotificationChannelZod = z.infer<typeof INotificationChannelZod>;
326
+
327
+ export const INotificationChannelTriggerZod = IBaseEntity.extend({
328
+ id: notificationChannelTriggerIdSchema.openapi({
329
+ example: 'notification_channel_trigger_01j5y5ghx8fvc83dmx3pznq7hv',
330
+ }),
331
+ notificationChannelId: notificationChannelIdSchema.openapi({
332
+ example: 'notification_channel_01j5y5ghx8fvc83dmx3pznq7hv',
333
+ }),
334
+ activityTypeId: z.string().openapi({
335
+ example: 'activity_type_01j5y5ghx8fvc83dmx3pznq7hv',
336
+ }),
337
+ enabled: z.boolean().openapi({
338
+ example: true,
339
+ }),
340
+ });
341
+ export type INotificationChannelTriggerZod = z.infer<
342
+ typeof INotificationChannelTriggerZod
343
+ >;
344
+
345
+ export const IAvailableTriggerZod = z.object({
346
+ activityTypeId: z.string().openapi({
347
+ example: 'activity_type_01j5y5ghx8fvc83dmx3pznq7hv',
348
+ }),
349
+ category: z.string().openapi({
350
+ example: 'COMPLIANCE',
351
+ }),
352
+ resource: z.string().openapi({
353
+ example: 'TRADES',
354
+ }),
355
+ action: z.string().openapi({
356
+ example: 'CREATE',
357
+ }),
358
+ enabled: z.boolean().openapi({
359
+ description: 'Whether the trigger is enabled for this activity type',
360
+ example: false,
361
+ }),
362
+ trigger: INotificationChannelTriggerZod.nullable().openapi({
363
+ description: 'The configured trigger, or null if not configured',
364
+ }),
365
+ });
366
+ export type IAvailableTriggerZod = z.infer<typeof IAvailableTriggerZod>;
367
+
368
+ export const IAvailableTriggersResponse = z.object({
369
+ items: z.array(IAvailableTriggerZod),
370
+ });
371
+ export type IAvailableTriggersResponse = z.infer<
372
+ typeof IAvailableTriggersResponse
373
+ >;
374
+
375
+ export const INotificationRecordZod = IBaseEntity.extend({
376
+ id: notificationRecordIdSchema.openapi({
377
+ example: 'notification_record_01j5y5ghx8fvc83dmx3pznq7hv',
378
+ }),
379
+ notificationChannelId: notificationChannelIdSchema.openapi({
380
+ example: 'notification_channel_01j5y5ghx8fvc83dmx3pznq7hv',
381
+ }),
382
+ activityId: z.string().nullable().openapi({
383
+ example: 'activity_01j5y5ghx8fvc83dmx3pznq7hv',
384
+ }),
385
+ targetTable: z.string().openapi({
386
+ example: 'trades',
387
+ }),
388
+ targetId: z.string().openapi({
389
+ example: 'trade_01j5y5ghx8fvc83dmx3pznq7hv',
390
+ }),
391
+ status: z.nativeEnum(NotificationRecordStatus).openapi({
392
+ example: NotificationRecordStatus.SENT,
393
+ }),
394
+ payload: NotificationPayloadSchema,
395
+ response: NotificationResponseSchema.nullable(),
396
+ errorMessage: z.string().nullable().openapi({
397
+ example: null,
398
+ }),
399
+ retryCount: z.number().openapi({
400
+ example: 0,
401
+ }),
402
+ sentAt: z.date().nullable().openapi({
403
+ example: new Date(),
404
+ }),
405
+ });
406
+ export type INotificationRecordZod = z.infer<typeof INotificationRecordZod>;
407
+
408
+ export const CreateChannelInputSchema = z
409
+ .object({
410
+ accountSettingsId: z.string(),
411
+ channelType: z.nativeEnum(NotificationChannelType),
412
+ name: z
413
+ .string()
414
+ .min(1, 'Name is required')
415
+ .max(255, 'Name must be less than 255 characters'),
416
+ enabled: z.boolean().optional(),
417
+ settings: NotificationChannelSettingsSchema,
418
+ })
419
+ .refine((data) => data.channelType === data.settings.type, {
420
+ message: 'Channel type must match settings type',
421
+ path: ['settings', 'type'],
422
+ });
423
+ export type CreateChannelInput = z.infer<typeof CreateChannelInputSchema>;
424
+
425
+ export const IssuerCreateChannelInputSchema = z
426
+ .object({
427
+ channelType: z.nativeEnum(NotificationChannelType),
428
+ name: z
429
+ .string()
430
+ .min(1, 'Name is required')
431
+ .max(255, 'Name must be less than 255 characters'),
432
+ enabled: z.boolean().optional(),
433
+ settings: NotificationChannelSettingsSchema,
434
+ })
435
+ .refine((data) => data.channelType === data.settings.type, {
436
+ message: 'Channel type must match settings type',
437
+ path: ['settings', 'type'],
438
+ });
439
+ export type IssuerCreateChannelInput = z.infer<
440
+ typeof IssuerCreateChannelInputSchema
441
+ >;
442
+
443
+ export const UpdateChannelInputSchema = z.object({
444
+ name: z.string().optional(),
445
+ enabled: z.boolean().optional(),
446
+ settings: NotificationChannelSettingsSchema.optional(),
447
+ });
448
+ export type UpdateChannelInput = z.infer<typeof UpdateChannelInputSchema>;
449
+
450
+ export const CreateTriggerInputSchema = z.object({
451
+ notificationChannelId: notificationChannelIdSchema,
452
+ activityTypeId: z.string(),
453
+ enabled: z.boolean().optional(),
454
+ });
455
+ export type CreateTriggerInput = z.infer<typeof CreateTriggerInputSchema>;
456
+
457
+ export const IPaginatedNotificationChannel = z.object({
458
+ items: z.array(INotificationChannelZod),
459
+ meta: IPaginationMeta,
460
+ });
461
+ export type IPaginatedNotificationChannel = z.infer<
462
+ typeof IPaginatedNotificationChannel
463
+ >;
464
+
465
+ export const IPaginatedNotificationChannelTrigger = z.object({
466
+ items: z.array(INotificationChannelTriggerZod),
467
+ meta: IPaginationMeta,
468
+ });
469
+ export type IPaginatedNotificationChannelTrigger = z.infer<
470
+ typeof IPaginatedNotificationChannelTrigger
471
+ >;
472
+
473
+ export const IPaginatedNotificationRecord = z.object({
474
+ items: z.array(INotificationRecordZod),
475
+ meta: IPaginationMeta,
476
+ });
477
+ export type IPaginatedNotificationRecord = z.infer<
478
+ typeof IPaginatedNotificationRecord
479
+ >;
480
+
481
+ export const NotificationChannelFilters = z.object({
482
+ accountSettingsId: z.string().optional(),
483
+ channelType: z.nativeEnum(NotificationChannelType).optional(),
484
+ enabled: StringToBooleanSchema.optional(),
485
+ });
486
+ export type NotificationChannelFilters = z.infer<
487
+ typeof NotificationChannelFilters
488
+ >;
489
+
490
+ export const NotificationChannelTriggerFilters = z.object({
491
+ notificationChannelId: notificationChannelIdSchema.optional(),
492
+ activityTypeId: z.string().optional(),
493
+ enabled: StringToBooleanSchema.optional(),
494
+ });
495
+ export type NotificationChannelTriggerFilters = z.infer<
496
+ typeof NotificationChannelTriggerFilters
497
+ >;
498
+
499
+ export const NotificationRecordFilters = z.object({
500
+ notificationChannelId: notificationChannelIdSchema.optional(),
501
+ status: z.nativeEnum(NotificationRecordStatus).optional(),
502
+ });
503
+ export type NotificationRecordFilters = z.infer<
504
+ typeof NotificationRecordFilters
505
+ >;
506
+
507
+ export const notificationChannelsInclude = z.enum([
508
+ 'triggers',
509
+ 'accountSettings',
510
+ 'notificationRecords',
511
+ ]);
512
+
513
+ export const NotificationChannelsIncludeQuery = z.object({
514
+ include: z
515
+ .string()
516
+ .optional()
517
+ .transform((str) => (str ? str.split(',') : []))
518
+ .refine(
519
+ (includes) =>
520
+ includes.every((include) =>
521
+ notificationChannelsInclude.options.includes(include as any),
522
+ ),
523
+ {
524
+ message: `Invalid include value. Valid values are: ${notificationChannelsInclude.options.join(', ')}`,
525
+ },
526
+ ),
527
+ });
528
+ export type NotificationChannelsIncludeQuery = z.infer<
529
+ typeof NotificationChannelsIncludeQuery
530
+ >;
531
+
532
+ export const notificationRecordsInclude = z.enum([
533
+ 'notificationChannel',
534
+ 'activity',
535
+ ]);
536
+
537
+ export const NotificationRecordsIncludeQuery = z.object({
538
+ include: z
539
+ .string()
540
+ .optional()
541
+ .transform((str) => (str ? str.split(',') : []))
542
+ .refine(
543
+ (includes) =>
544
+ includes.every((include) =>
545
+ notificationRecordsInclude.options.includes(include as any),
546
+ ),
547
+ {
548
+ message: `Invalid include value. Valid values are: ${notificationRecordsInclude.options.join(', ')}`,
549
+ },
550
+ ),
551
+ });
552
+ export type NotificationRecordsIncludeQuery = z.infer<
553
+ typeof NotificationRecordsIncludeQuery
554
+ >;