@shware/workflow 5.0.1 → 6.1.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 (190) hide show
  1. package/README.md +224 -53
  2. package/dist/aws/index.cjs +1 -1
  3. package/dist/aws/index.mjs +1 -1
  4. package/dist/client.cjs +68 -4
  5. package/dist/client.cjs.map +1 -1
  6. package/dist/client.d.cts +39 -3
  7. package/dist/client.d.cts.map +1 -1
  8. package/dist/client.d.mts +39 -3
  9. package/dist/client.d.mts.map +1 -1
  10. package/dist/client.mjs +68 -4
  11. package/dist/client.mjs.map +1 -1
  12. package/dist/cloudflare/index.cjs +4 -4
  13. package/dist/cloudflare/index.mjs +4 -4
  14. package/dist/cloudflare/runner.cjs +1 -1
  15. package/dist/cloudflare/runner.d.cts +1 -1
  16. package/dist/cloudflare/runner.d.mts +1 -1
  17. package/dist/cloudflare/runner.mjs +1 -1
  18. package/dist/config.cjs +24 -3
  19. package/dist/config.cjs.map +1 -1
  20. package/dist/config.d.cts +15 -9
  21. package/dist/config.d.cts.map +1 -1
  22. package/dist/config.d.mts +15 -9
  23. package/dist/config.d.mts.map +1 -1
  24. package/dist/config.mjs +25 -4
  25. package/dist/config.mjs.map +1 -1
  26. package/dist/contract.cjs +11 -0
  27. package/dist/contract.cjs.map +1 -1
  28. package/dist/contract.d.cts +28 -7
  29. package/dist/contract.d.cts.map +1 -1
  30. package/dist/contract.d.mts +28 -7
  31. package/dist/contract.d.mts.map +1 -1
  32. package/dist/contract.mjs +11 -0
  33. package/dist/contract.mjs.map +1 -1
  34. package/dist/dsl/bundle.cjs +4 -3
  35. package/dist/dsl/bundle.cjs.map +1 -1
  36. package/dist/dsl/bundle.d.cts +5 -1
  37. package/dist/dsl/bundle.d.cts.map +1 -1
  38. package/dist/dsl/bundle.d.mts +5 -1
  39. package/dist/dsl/bundle.d.mts.map +1 -1
  40. package/dist/dsl/bundle.mjs +4 -3
  41. package/dist/dsl/bundle.mjs.map +1 -1
  42. package/dist/dsl/flow.cjs +2 -1
  43. package/dist/dsl/flow.cjs.map +1 -1
  44. package/dist/dsl/flow.d.cts +1 -1
  45. package/dist/dsl/flow.d.cts.map +1 -1
  46. package/dist/dsl/flow.d.mts +1 -1
  47. package/dist/dsl/flow.d.mts.map +1 -1
  48. package/dist/dsl/flow.mjs +2 -1
  49. package/dist/dsl/flow.mjs.map +1 -1
  50. package/dist/dsl/workflow.cjs +2 -2
  51. package/dist/dsl/workflow.mjs +2 -2
  52. package/dist/email-sender.cjs +3 -2
  53. package/dist/email-sender.cjs.map +1 -1
  54. package/dist/email-sender.d.cts +5 -4
  55. package/dist/email-sender.d.cts.map +1 -1
  56. package/dist/email-sender.d.mts +5 -4
  57. package/dist/email-sender.d.mts.map +1 -1
  58. package/dist/email-sender.mjs +3 -2
  59. package/dist/email-sender.mjs.map +1 -1
  60. package/dist/email.cjs +66 -11
  61. package/dist/email.cjs.map +1 -1
  62. package/dist/email.d.cts +72 -12
  63. package/dist/email.d.cts.map +1 -1
  64. package/dist/email.d.mts +72 -12
  65. package/dist/email.d.mts.map +1 -1
  66. package/dist/email.mjs +63 -12
  67. package/dist/email.mjs.map +1 -1
  68. package/dist/engine/condition.cjs.map +1 -1
  69. package/dist/engine/condition.d.cts.map +1 -1
  70. package/dist/engine/condition.d.mts.map +1 -1
  71. package/dist/engine/condition.mjs.map +1 -1
  72. package/dist/engine/index.cjs +1 -1
  73. package/dist/engine/index.mjs +1 -1
  74. package/dist/engine/interpreter.cjs +21 -4
  75. package/dist/engine/interpreter.cjs.map +1 -1
  76. package/dist/engine/interpreter.mjs +21 -4
  77. package/dist/engine/interpreter.mjs.map +1 -1
  78. package/dist/engine/ports.d.cts +14 -2
  79. package/dist/engine/ports.d.cts.map +1 -1
  80. package/dist/engine/ports.d.mts +14 -2
  81. package/dist/engine/ports.d.mts.map +1 -1
  82. package/dist/host/channels.cjs +1 -1
  83. package/dist/host/channels.mjs +1 -1
  84. package/dist/host/execute.cjs +2 -2
  85. package/dist/host/execute.d.cts +1 -1
  86. package/dist/host/execute.d.mts +1 -1
  87. package/dist/host/execute.mjs +2 -2
  88. package/dist/host/http.cjs +75 -5
  89. package/dist/host/http.cjs.map +1 -1
  90. package/dist/host/http.d.cts +2 -2
  91. package/dist/host/http.d.cts.map +1 -1
  92. package/dist/host/http.d.mts +2 -2
  93. package/dist/host/http.d.mts.map +1 -1
  94. package/dist/host/http.mjs +76 -6
  95. package/dist/host/http.mjs.map +1 -1
  96. package/dist/host/ingest.cjs +173 -4
  97. package/dist/host/ingest.cjs.map +1 -1
  98. package/dist/host/ingest.d.cts +99 -0
  99. package/dist/host/ingest.d.cts.map +1 -1
  100. package/dist/host/ingest.d.mts +99 -0
  101. package/dist/host/ingest.d.mts.map +1 -1
  102. package/dist/host/ingest.mjs +167 -5
  103. package/dist/host/ingest.mjs.map +1 -1
  104. package/dist/host/journey-host.cjs +10 -8
  105. package/dist/host/journey-host.cjs.map +1 -1
  106. package/dist/host/journey-host.mjs +10 -8
  107. package/dist/host/journey-host.mjs.map +1 -1
  108. package/dist/index.cjs +8 -2
  109. package/dist/index.d.cts +5 -5
  110. package/dist/index.d.mts +5 -5
  111. package/dist/index.mjs +5 -5
  112. package/dist/ir.cjs +11 -2
  113. package/dist/ir.cjs.map +1 -1
  114. package/dist/ir.d.cts +17 -0
  115. package/dist/ir.d.cts.map +1 -1
  116. package/dist/ir.d.mts +17 -0
  117. package/dist/ir.d.mts.map +1 -1
  118. package/dist/ir.mjs +11 -3
  119. package/dist/ir.mjs.map +1 -1
  120. package/dist/react-email.cjs +6 -5
  121. package/dist/react-email.cjs.map +1 -1
  122. package/dist/react-email.d.cts.map +1 -1
  123. package/dist/react-email.d.mts.map +1 -1
  124. package/dist/react-email.mjs +6 -5
  125. package/dist/react-email.mjs.map +1 -1
  126. package/dist/reporting/observer.cjs +4 -1
  127. package/dist/reporting/observer.cjs.map +1 -1
  128. package/dist/reporting/observer.d.cts +2 -1
  129. package/dist/reporting/observer.d.cts.map +1 -1
  130. package/dist/reporting/observer.d.mts +2 -1
  131. package/dist/reporting/observer.d.mts.map +1 -1
  132. package/dist/reporting/observer.mjs +4 -1
  133. package/dist/reporting/observer.mjs.map +1 -1
  134. package/dist/reporting/tracking.cjs +31 -10
  135. package/dist/reporting/tracking.cjs.map +1 -1
  136. package/dist/reporting/tracking.d.cts +15 -6
  137. package/dist/reporting/tracking.d.cts.map +1 -1
  138. package/dist/reporting/tracking.d.mts +15 -6
  139. package/dist/reporting/tracking.d.mts.map +1 -1
  140. package/dist/reporting/tracking.mjs +31 -11
  141. package/dist/reporting/tracking.mjs.map +1 -1
  142. package/dist/store/facts.cjs +3 -3
  143. package/dist/store/facts.cjs.map +1 -1
  144. package/dist/store/facts.d.cts +2 -2
  145. package/dist/store/facts.d.cts.map +1 -1
  146. package/dist/store/facts.d.mts +2 -2
  147. package/dist/store/facts.d.mts.map +1 -1
  148. package/dist/store/facts.mjs +3 -3
  149. package/dist/store/facts.mjs.map +1 -1
  150. package/dist/store/index.cjs +1 -1
  151. package/dist/store/index.cjs.map +1 -1
  152. package/dist/store/index.d.cts +44 -13
  153. package/dist/store/index.d.cts.map +1 -1
  154. package/dist/store/index.d.mts +44 -13
  155. package/dist/store/index.d.mts.map +1 -1
  156. package/dist/store/index.mjs +1 -1
  157. package/dist/store/index.mjs.map +1 -1
  158. package/dist/store/postgres-reporting.cjs +30 -10
  159. package/dist/store/postgres-reporting.cjs.map +1 -1
  160. package/dist/store/postgres-reporting.d.cts +4 -2
  161. package/dist/store/postgres-reporting.d.cts.map +1 -1
  162. package/dist/store/postgres-reporting.d.mts +4 -2
  163. package/dist/store/postgres-reporting.d.mts.map +1 -1
  164. package/dist/store/postgres-reporting.mjs +30 -10
  165. package/dist/store/postgres-reporting.mjs.map +1 -1
  166. package/dist/store/postgres.cjs +39 -3
  167. package/dist/store/postgres.cjs.map +1 -1
  168. package/dist/store/postgres.d.cts +4 -2
  169. package/dist/store/postgres.d.cts.map +1 -1
  170. package/dist/store/postgres.d.mts +4 -2
  171. package/dist/store/postgres.d.mts.map +1 -1
  172. package/dist/store/postgres.mjs +40 -4
  173. package/dist/store/postgres.mjs.map +1 -1
  174. package/dist/store/tables.cjs +75 -11
  175. package/dist/store/tables.cjs.map +1 -1
  176. package/dist/store/tables.d.cts +17 -1
  177. package/dist/store/tables.d.cts.map +1 -1
  178. package/dist/store/tables.d.mts +17 -1
  179. package/dist/store/tables.d.mts.map +1 -1
  180. package/dist/store/tables.mjs +74 -12
  181. package/dist/store/tables.mjs.map +1 -1
  182. package/dist/templates.cjs +60 -7
  183. package/dist/templates.cjs.map +1 -1
  184. package/dist/templates.d.cts +101 -13
  185. package/dist/templates.d.cts.map +1 -1
  186. package/dist/templates.d.mts +101 -13
  187. package/dist/templates.d.mts.map +1 -1
  188. package/dist/templates.mjs +61 -9
  189. package/dist/templates.mjs.map +1 -1
  190. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { ScalarIR } from "./ir.mjs";
2
+ import { Contract, Topic } from "./contract.mjs";
2
3
  import { EmailAddress } from "./email.mjs";
3
4
  import { Block, KnownBlock } from "@slack/types";
4
5
  import { RESTPostAPIWebhookWithTokenJSONBody } from "discord-api-types/v10";
@@ -77,16 +78,70 @@ export interface EmailMetadata {
77
78
  /** This template's unsubscribe link; left out, the config's `email.unsubscribeUrl` (if any). */
78
79
  readonly unsubscribeUrl?: string | undefined;
79
80
  }
80
- export interface EmailTemplate<S extends PropsSchema = PropsSchema> extends TemplateBase<S>, EmailMetadata {
81
+ /**
82
+ * What kind of message a template is, by type: a `topic` the contract declares (a user can opt
83
+ * out of it, per channel), or `transactional: true` (a receipt, a code, an invite: sent whatever
84
+ * the user opted out of). One of the two, never neither.
85
+ *
86
+ * The slots nothing real fits are string-literal types that read as the error: an editor
87
+ * reports a mistake at the property itself ("Type 'true' is not assignable to type 'give
88
+ * either …'"), and the third member catches a template that says neither, with the property
89
+ * it reports missing naming both options. Written out in full (no aliases): TypeScript prints
90
+ * an alias by name, and the point is the text.
91
+ */
92
+ export type TemplateTopic<T extends string = string> = {
93
+ readonly topic: T;
94
+ readonly transactional?: "give either 'topic' or 'transactional: true', not both";
95
+ } | {
96
+ readonly transactional: true;
97
+ readonly topic?: "a topic the contract declares, or none: 'topic' and 'transactional: true' are exclusive";
98
+ } | {
99
+ readonly 'topic | transactional': "every template says what it is: 'topic: <id>' (one the contract declares) or 'transactional: true'";
100
+ readonly topic?: never;
101
+ readonly transactional?: never;
102
+ };
103
+ /**
104
+ * The email form of `TemplateTopic`: a topic email carries the unsubscribe link and the
105
+ * `List-Unsubscribe` headers; a transactional one has no link — so no `unsubscribeUrl` either,
106
+ * which the type says in its own words.
107
+ */
108
+ export type EmailTemplateTopic<T extends string = string> = {
109
+ readonly topic: T;
110
+ readonly transactional?: "give either 'topic' or 'transactional: true', not both";
111
+ readonly unsubscribeUrl?: string;
112
+ } | {
113
+ readonly transactional: true;
114
+ readonly topic?: "a topic the contract declares, or none: 'topic' and 'transactional: true' are exclusive";
115
+ readonly unsubscribeUrl?: "a transactional email has no unsubscribe link, so it takes no 'unsubscribeUrl'";
116
+ } | {
117
+ readonly 'topic | transactional': "every template says what it is: 'topic: <id>' (one the contract declares) or 'transactional: true'";
118
+ readonly topic?: never;
119
+ readonly transactional?: never;
120
+ readonly unsubscribeUrl?: string;
121
+ };
122
+ /**
123
+ * `T` is the template's topic (`never` for a transactional one): a registry typed by the
124
+ * contract's topics (`defineConfig({ contract, templates })`) accepts only templates whose topic
125
+ * the contract declares.
126
+ */
127
+ export interface EmailTemplate<S extends PropsSchema = PropsSchema, T extends string = string> extends TemplateBase<S>, EmailMetadata {
81
128
  readonly kind: 'email-template';
82
129
  readonly version: 1;
83
130
  readonly subject: string;
131
+ /** The subscription topic the email belongs to; null for a transactional email. */
132
+ readonly topic: T | null;
84
133
  /** The email body: a component over the parsed props (rendered by `@shware/workflow/react-email`). */
85
134
  readonly component: (props: Readonly<core.output<S>>) => unknown;
86
135
  }
87
- export interface PushTemplate<S extends PropsSchema = PropsSchema> extends TemplateBase<S> {
136
+ /**
137
+ * `T` is the template's topic (`never` for a transactional one): a registry typed by the
138
+ * contract's topics accepts only push templates whose topic the contract declares.
139
+ */
140
+ export interface PushTemplate<S extends PropsSchema = PropsSchema, T extends string = string> extends TemplateBase<S> {
88
141
  readonly kind: 'push-template';
89
142
  readonly version: 1;
143
+ /** The subscription topic the push belongs to; null for a transactional push. */
144
+ readonly topic: T | null;
90
145
  /** The notification, from the parsed props. Synchronous and pure; its result is checked before sending. */
91
146
  readonly content: (props: Readonly<core.output<S>>) => PushContent;
92
147
  }
@@ -112,19 +167,20 @@ export interface SlackTemplate<S extends PropsSchema = PropsSchema, Env = Record
112
167
  }
113
168
  /** Strips a Promise return out of the accepted callback types: async content is rejected at the type level. */
114
169
  type Sync<T> = T extends Promise<unknown> ? never : T;
115
- export interface EmailTemplateDefinition<S extends PropsSchema> {
170
+ export interface EmailTemplateBase<S extends PropsSchema> {
116
171
  id: string;
117
172
  title: string;
118
173
  description?: string;
119
174
  from: EmailAddress;
120
175
  replyTo?: EmailAddress;
121
- unsubscribeUrl?: string;
122
176
  props: S;
123
177
  subject: string;
124
178
  preview: NoInfer<core.input<S>>;
125
179
  component: (props: NoInfer<Readonly<core.output<S>>>) => unknown;
126
180
  }
127
- export interface PushTemplateDefinition<S extends PropsSchema, R> {
181
+ /** `emailTemplate`'s argument: the metadata, the props schema and the body, plus `topic` or `transactional: true`. */
182
+ export type EmailTemplateDefinition<S extends PropsSchema, T extends string = string> = EmailTemplateBase<S> & EmailTemplateTopic<T>;
183
+ export interface PushTemplateBase<S extends PropsSchema, R> {
128
184
  id: string;
129
185
  title: string;
130
186
  description?: string;
@@ -132,6 +188,8 @@ export interface PushTemplateDefinition<S extends PropsSchema, R> {
132
188
  preview: NoInfer<core.input<S>>;
133
189
  content: (props: NoInfer<Readonly<core.output<S>>>) => Sync<R> & PushContent;
134
190
  }
191
+ /** `pushTemplate`'s argument: the props schema and the notification, plus `topic` or `transactional: true`. */
192
+ export type PushTemplateDefinition<S extends PropsSchema, R, T extends string = string> = PushTemplateBase<S, R> & TemplateTopic<T>;
135
193
  export interface WebhookTemplateDefinition<S extends PropsSchema, Env, C> {
136
194
  id: string;
137
195
  title: string;
@@ -147,12 +205,40 @@ export declare function discordTemplate<S extends PropsSchema, Env = Record<stri
147
205
  /** Declare a Slack webhook template. Throws when `props` is unsupported or `preview` does not parse. */
148
206
  export declare function slackTemplate<S extends PropsSchema, Env = Record<string, unknown>>(definition: WebhookTemplateDefinition<S, Env, SlackContent>): SlackTemplate<S, Env>;
149
207
  /**
150
- * Declare an email template. Throws when `from` is missing or invalid, `replyTo` or `unsubscribeUrl`
151
- * is invalid, `props` uses an unsupported schema or `preview` does not parse.
208
+ * Declare an email template: with a `topic` the contract declares, or `transactional: true`
209
+ * (then `T` is `never`, and the template's `topic` is null). Throws when neither (or both) is
210
+ * given, a transactional template carries an `unsubscribeUrl`, `from` is missing or invalid,
211
+ * `replyTo` or `unsubscribeUrl` is invalid, `props` uses an unsupported schema or `preview` does
212
+ * not parse. Whether the topic is one the contract declares is checked by `compileConfig`,
213
+ * where the contract is at hand — or at the template itself by `templatesFor(contract)`'s
214
+ * `emailTemplate`, the form to prefer in a project with a contract.
152
215
  */
153
- export declare function emailTemplate<S extends PropsSchema>(definition: EmailTemplateDefinition<S>): EmailTemplate<S>;
154
- /** Declare a push template. Throws when `props` uses an unsupported schema or `preview` does not parse. */
155
- export declare function pushTemplate<S extends PropsSchema, R extends PushContent>(definition: PushTemplateDefinition<S, R>): PushTemplate<S>;
216
+ export declare function emailTemplate<S extends PropsSchema, T extends string = never>(definition: EmailTemplateDefinition<S, T>): EmailTemplate<S, T>;
217
+ /**
218
+ * Declare a push template: with a `topic` the contract declares (the user can opt out of it in
219
+ * the app — App Store Review Guideline 4.5.4 requires that for marketing pushes), or
220
+ * `transactional: true` (then `T` is `never`, and the template's `topic` is null). Throws when
221
+ * neither (or both) is given, `props` uses an unsupported schema or `preview` does not parse.
222
+ * Whether the topic is one the contract declares is checked by `compileConfig`, where the
223
+ * contract is at hand — or at the template itself by `templatesFor(contract)`'s `pushTemplate`.
224
+ */
225
+ export declare function pushTemplate<S extends PropsSchema, R extends PushContent, T extends string = never>(definition: PushTemplateDefinition<S, R, T>): PushTemplate<S, T>;
226
+ /**
227
+ * The template factories bound to a contract: the same `emailTemplate` and `pushTemplate`, with
228
+ * the topic typed by the contract's `subscriptions.topics` — so a topic the contract does not
229
+ * declare is an error at the template's own `topic` line (in the editor, and when the module
230
+ * loads), not a type chain at the config's `templates` where the registry meets the contract.
231
+ * The form to use wherever a contract exists:
232
+ *
233
+ * const { emailTemplate, pushTemplate } = templatesFor(contract);
234
+ *
235
+ * The bare `emailTemplate` / `pushTemplate` stay for a project without a contract (transactional
236
+ * templates only) and for the compile, which checks every template again.
237
+ */
238
+ export declare function templatesFor<C extends Contract>(contract: C): {
239
+ emailTemplate: <S extends PropsSchema, T extends Topic<C> = never>(definition: EmailTemplateDefinition<S, T>) => EmailTemplate<S, T>;
240
+ pushTemplate: <S extends PropsSchema, R extends PushContent, T extends Topic<C> = never>(definition: PushTemplateDefinition<S, R, T>) => PushTemplate<S, T>;
241
+ };
156
242
  export declare function isEmailTemplate(value: unknown): value is EmailTemplate;
157
243
  export declare function isPushTemplate(value: unknown): value is PushTemplate;
158
244
  export declare function isDiscordTemplate(value: unknown): value is DiscordTemplate;
@@ -167,9 +253,11 @@ export declare function isTemplate(value: unknown): value is AnyTemplate;
167
253
  * plain object whose property names are JavaScript names only — a template is identified by its
168
254
  * own `id` (`indexTemplates`).
169
255
  */
170
- export interface TemplateRegistry {
171
- readonly email?: Readonly<Record<string, EmailTemplate>>;
172
- readonly push?: Readonly<Record<string, PushTemplate>>;
256
+ export interface TemplateRegistry<T extends string = string> {
257
+ /** Email templates; typed by the contract's topics under `defineConfig({ contract, templates })`. */
258
+ readonly email?: Readonly<Record<string, EmailTemplate<PropsSchema, T>>>;
259
+ /** Push templates; typed by the contract's topics the same way. */
260
+ readonly push?: Readonly<Record<string, PushTemplate<PropsSchema, T>>>;
173
261
  /** Discord webhook templates, each typed by the host's Env its `webhook` reads. */
174
262
  readonly discord?: Readonly<Record<string, DiscordTemplate<PropsSchema, never>>>;
175
263
  readonly slack?: Readonly<Record<string, SlackTemplate<PropsSchema, never>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.mts","names":[],"sources":["../src/templates.ts"],"mappings":"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"}
1
+ {"version":3,"file":"templates.d.mts","names":[],"sources":["../src/templates.ts"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { assertEmailMetadata } from "./email.mjs";
1
+ import { assertEmailMetadata, assertTemplateTopic } from "./email.mjs";
2
2
  import * as z from "zod/mini";
3
3
  // src/templates.ts
4
4
  /** Declare a Discord webhook template. Throws when `props` is unsupported or `preview` does not parse. */
@@ -24,31 +24,83 @@ function slackTemplate(definition) {
24
24
  };
25
25
  }
26
26
  /**
27
- * Declare an email template. Throws when `from` is missing or invalid, `replyTo` or `unsubscribeUrl`
28
- * is invalid, `props` uses an unsupported schema or `preview` does not parse.
27
+ * Declare an email template: with a `topic` the contract declares, or `transactional: true`
28
+ * (then `T` is `never`, and the template's `topic` is null). Throws when neither (or both) is
29
+ * given, a transactional template carries an `unsubscribeUrl`, `from` is missing or invalid,
30
+ * `replyTo` or `unsubscribeUrl` is invalid, `props` uses an unsupported schema or `preview` does
31
+ * not parse. Whether the topic is one the contract declares is checked by `compileConfig`,
32
+ * where the contract is at hand — or at the template itself by `templatesFor(contract)`'s
33
+ * `emailTemplate`, the form to prefer in a project with a contract.
29
34
  */
30
35
  function emailTemplate(definition) {
36
+ return declareEmail(definition, void 0);
37
+ }
38
+ /**
39
+ * Declare a push template: with a `topic` the contract declares (the user can opt out of it in
40
+ * the app — App Store Review Guideline 4.5.4 requires that for marketing pushes), or
41
+ * `transactional: true` (then `T` is `never`, and the template's `topic` is null). Throws when
42
+ * neither (or both) is given, `props` uses an unsupported schema or `preview` does not parse.
43
+ * Whether the topic is one the contract declares is checked by `compileConfig`, where the
44
+ * contract is at hand — or at the template itself by `templatesFor(contract)`'s `pushTemplate`.
45
+ */
46
+ function pushTemplate(definition) {
47
+ return declarePush(definition, void 0);
48
+ }
49
+ /**
50
+ * The template factories bound to a contract: the same `emailTemplate` and `pushTemplate`, with
51
+ * the topic typed by the contract's `subscriptions.topics` — so a topic the contract does not
52
+ * declare is an error at the template's own `topic` line (in the editor, and when the module
53
+ * loads), not a type chain at the config's `templates` where the registry meets the contract.
54
+ * The form to use wherever a contract exists:
55
+ *
56
+ * const { emailTemplate, pushTemplate } = templatesFor(contract);
57
+ *
58
+ * The bare `emailTemplate` / `pushTemplate` stay for a project without a contract (transactional
59
+ * templates only) and for the compile, which checks every template again.
60
+ */
61
+ function templatesFor(contract) {
62
+ return {
63
+ emailTemplate: (definition) => declareEmail(definition, contract),
64
+ pushTemplate: (definition) => declarePush(definition, contract)
65
+ };
66
+ }
67
+ function declareEmail(definition, contract) {
31
68
  assertTemplateId(definition.id, "emailTemplate");
32
- assertEmailMetadata(definition, `emailTemplate('${definition.id}')`);
69
+ const where = `emailTemplate('${definition.id}')`;
70
+ assertEmailMetadata(definition, where);
71
+ assertDeclaredTopic(definition, where, contract);
33
72
  assertSupportedSchema(definition.props, definition.title);
34
73
  parsePreview(definition.props, definition.preview, "email", definition.title);
74
+ const { topic, transactional, ...rest } = definition;
35
75
  return {
36
76
  kind: "email-template",
37
77
  version: 1,
38
- ...definition
78
+ ...rest,
79
+ topic: topic ?? null
39
80
  };
40
81
  }
41
- /** Declare a push template. Throws when `props` uses an unsupported schema or `preview` does not parse. */
42
- function pushTemplate(definition) {
82
+ function declarePush(definition, contract) {
43
83
  assertTemplateId(definition.id, "pushTemplate");
84
+ const where = `pushTemplate('${definition.id}')`;
85
+ assertTemplateTopic(definition, where, "push");
86
+ assertDeclaredTopic(definition, where, contract);
44
87
  assertSupportedSchema(definition.props, definition.title);
45
88
  parsePreview(definition.props, definition.preview, "push", definition.title);
89
+ const { topic, transactional, ...rest } = definition;
46
90
  return {
47
91
  kind: "push-template",
48
92
  version: 1,
49
- ...definition
93
+ ...rest,
94
+ topic: topic ?? null
50
95
  };
51
96
  }
97
+ /** With a contract at hand (`templatesFor`), a topic it does not declare fails here, naming the list. */
98
+ function assertDeclaredTopic(definition, where, contract) {
99
+ const { topic } = definition;
100
+ if (contract === void 0 || typeof topic !== "string" || contract.topics.includes(topic)) return;
101
+ const list = contract.topics.length === 0 ? "the contract declares no topics (defineContract({ subscriptions: { topics: [...] } }))" : `the contract declares ${contract.topics.map((t) => `'${t}'`).join(", ")}`;
102
+ throw new TypeError(`${where}: topic '${topic}' is not declared — ${list}`);
103
+ }
52
104
  function assertTemplateId(id, factory) {
53
105
  if (!isTemplateId(id)) throw new TypeError(`${factory}(): 'id' must be a non-empty string`);
54
106
  }
@@ -280,6 +332,6 @@ function registryValidator(registry) {
280
332
  return props;
281
333
  } };
282
334
  }
283
- export { CHANNEL_OF, TemplateValidationError, assertSupportedSchema, discordTemplate, emailTemplate, indexTemplates, isDiscordTemplate, isEmailTemplate, isPushTemplate, isSlackTemplate, isTemplate, lookupTemplate, parseTemplateProps, pushTemplate, registryValidator, renderDiscordContent, renderPushContent, renderSlackContent, slackTemplate };
335
+ export { CHANNEL_OF, TemplateValidationError, assertSupportedSchema, discordTemplate, emailTemplate, indexTemplates, isDiscordTemplate, isEmailTemplate, isPushTemplate, isSlackTemplate, isTemplate, lookupTemplate, parseTemplateProps, pushTemplate, registryValidator, renderDiscordContent, renderPushContent, renderSlackContent, slackTemplate, templatesFor };
284
336
 
285
337
  //# sourceMappingURL=templates.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"templates.mjs","names":[],"sources":["../src/templates.ts"],"sourcesContent":["import type { Block, KnownBlock } from '@slack/types';\nimport type { RESTPostAPIWebhookWithTokenJSONBody } from 'discord-api-types/v10';\nimport * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport { type EmailAddress, assertEmailMetadata } from './email';\nimport type { ScalarIR } from './ir';\n\n/**\n * Template definitions — `emailTemplate`, `pushTemplate`, `discordTemplate` and `slackTemplate`. One\n * object per template, identified by its own `id` (unique per channel): workflows take the\n * definition itself (`.email(welcome, props)`), the config registers it, and the IR, the manifest,\n * the senders and the studio all find it by that id. Its Zod `props` schema is the only source of the\n * props type: the DSL's `.email()/.push()` arguments, the `preview`, and the\n * `component`/`content` parameter are all derived from it, and every render\n * parses the props against it first (types alone check nothing at run time).\n *\n * Kept free of React and of any renderer: the definitions are plain data plus\n * a function, so the DSL, the Worker and the studio can all import them; the\n * React rendering lives in `@shware/workflow/react-email`.\n *\n * Supported schemas (v1): a strict object (`z.strictObject`) whose fields are\n * string / number / boolean — optionally as enums or literals, optionally\n * `.optional()`, with the built-in checks (min, email, …). That is exactly\n * what IR message props can carry, and it keeps input and output identical, so\n * producer, studio and sender never disagree about a transformed value.\n * Anything else (nested objects, arrays, null, default/transform/coerce,\n * custom refinements) is rejected when the template is declared.\n */\n\n/** A strict Zod object schema of scalar fields — the accepted shape of `props`. */\nexport type PropsSchema = core.$ZodObject;\n\n/** What a push template's `content` returns: the notification itself. */\nexport interface PushContent {\n title: string;\n body: string;\n /** Rich image URL (iOS attachment / Android BigPicture). */\n image?: string;\n /** Where a tap leads: the page a browser push opens (the site's root when absent). */\n url?: string;\n}\n\n/** A Discord webhook message (`discord-api-types`): content, embeds, username, allowed mentions… */\nexport type DiscordContent = RESTPostAPIWebhookWithTokenJSONBody;\n\n/** A Slack incoming-webhook message: the fallback `text` and, usually, Block Kit `blocks`. */\nexport interface SlackContent {\n /** Shown in notifications and wherever blocks cannot be; required even with blocks. */\n text: string;\n blocks?: (KnownBlock | Block)[];\n username?: string;\n icon_emoji?: string;\n icon_url?: string;\n mrkdwn?: boolean;\n unfurl_links?: boolean;\n unfurl_media?: boolean;\n}\n\n/** The channels a template can be declared for. */\nexport type TemplateChannel = 'email' | 'push' | 'discord' | 'slack';\n\ninterface TemplateBase<S extends PropsSchema> {\n /**\n * Wire identity, unique per channel: message nodes, the template manifest, every send and the\n * studio's preview links name the template by it. Not something to rename for readability —\n * `title` is; the property a registry holds it under is only a JavaScript name.\n */\n readonly id: string;\n /** Display name in the studio (not the email subject, not the notification title). */\n readonly title: string;\n readonly description?: string | undefined;\n readonly props: S;\n /** Sample props for the studio; parsed against `props` when the template is declared. */\n readonly preview: core.input<S>;\n}\n\n/**\n * Who an email comes from and where it leads: declared on each template, never inherited from a\n * global sender. Metadata, not props — the component never receives it; its layout reads the\n * resolved unsubscribe link with `useEmailContext()` (`@shware/workflow/email-context`).\n */\nexport interface EmailMetadata {\n readonly from: EmailAddress;\n /** Where replies go; left out, the email has no Reply-To (it is not `from`, and not inherited). */\n readonly replyTo?: EmailAddress | undefined;\n /** This template's unsubscribe link; left out, the config's `email.unsubscribeUrl` (if any). */\n readonly unsubscribeUrl?: string | undefined;\n}\n\nexport interface EmailTemplate<S extends PropsSchema = PropsSchema>\n extends TemplateBase<S>, EmailMetadata {\n readonly kind: 'email-template';\n readonly version: 1;\n readonly subject: string;\n /** The email body: a component over the parsed props (rendered by `@shware/workflow/react-email`). */\n readonly component: (props: Readonly<core.output<S>>) => unknown;\n}\n\nexport interface PushTemplate<S extends PropsSchema = PropsSchema> extends TemplateBase<S> {\n readonly kind: 'push-template';\n readonly version: 1;\n /** The notification, from the parsed props. Synchronous and pure; its result is checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => PushContent;\n}\n\n/**\n * A message posted to a team channel through an incoming webhook (Discord, Slack). The webhook\n * URL is a secret, so it lives in the Worker's environment and the template reads it from there\n * at send time: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`, with `Env` the host's\n * zod-validated environment (`journeyWorker({ env: schema })`). The studio never calls it.\n */\ninterface WebhookTemplateBase<S extends PropsSchema, Env> extends TemplateBase<S> {\n readonly version: 1;\n // A method, so a template typed by the host's Env still fits a registry of any Env\n webhook(env: Env): string | undefined;\n}\n\nexport interface DiscordTemplate<\n S extends PropsSchema = PropsSchema,\n Env = Record<string, unknown>,\n> extends WebhookTemplateBase<S, Env> {\n readonly kind: 'discord-template';\n /** The message, from the parsed props. Synchronous and pure; checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => DiscordContent;\n}\n\nexport interface SlackTemplate<\n S extends PropsSchema = PropsSchema,\n Env = Record<string, unknown>,\n> extends WebhookTemplateBase<S, Env> {\n readonly kind: 'slack-template';\n /** The message, from the parsed props. Synchronous and pure; checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => SlackContent;\n}\n\n/** Strips a Promise return out of the accepted callback types: async content is rejected at the type level. */\ntype Sync<T> = T extends Promise<unknown> ? never : T;\n\nexport interface EmailTemplateDefinition<S extends PropsSchema> {\n id: string;\n title: string;\n description?: string;\n from: EmailAddress;\n replyTo?: EmailAddress;\n unsubscribeUrl?: string;\n props: S;\n subject: string;\n preview: NoInfer<core.input<S>>;\n component: (props: NoInfer<Readonly<core.output<S>>>) => unknown;\n}\n\nexport interface PushTemplateDefinition<S extends PropsSchema, R> {\n id: string;\n title: string;\n description?: string;\n props: S;\n preview: NoInfer<core.input<S>>;\n content: (props: NoInfer<Readonly<core.output<S>>>) => Sync<R> & PushContent;\n}\n\nexport interface WebhookTemplateDefinition<S extends PropsSchema, Env, C> {\n id: string;\n title: string;\n description?: string;\n props: S;\n preview: NoInfer<core.input<S>>;\n /** The webhook URL, read from the Worker's environment when a message is sent. */\n webhook: (env: Env) => string | undefined;\n content: (props: NoInfer<Readonly<core.output<S>>>) => Sync<C>;\n}\n\n/** Declare a Discord webhook template. Throws when `props` is unsupported or `preview` does not parse. */\nexport function discordTemplate<S extends PropsSchema, Env = Record<string, unknown>>(\n definition: WebhookTemplateDefinition<S, Env, DiscordContent>\n): DiscordTemplate<S, Env> {\n assertTemplateId(definition.id, 'discordTemplate');\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'discord', definition.title);\n return { kind: 'discord-template', version: 1, ...definition };\n}\n\n/** Declare a Slack webhook template. Throws when `props` is unsupported or `preview` does not parse. */\nexport function slackTemplate<S extends PropsSchema, Env = Record<string, unknown>>(\n definition: WebhookTemplateDefinition<S, Env, SlackContent>\n): SlackTemplate<S, Env> {\n assertTemplateId(definition.id, 'slackTemplate');\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'slack', definition.title);\n return { kind: 'slack-template', version: 1, ...definition };\n}\n\n/**\n * Declare an email template. Throws when `from` is missing or invalid, `replyTo` or `unsubscribeUrl`\n * is invalid, `props` uses an unsupported schema or `preview` does not parse.\n */\nexport function emailTemplate<S extends PropsSchema>(\n definition: EmailTemplateDefinition<S>\n): EmailTemplate<S> {\n assertTemplateId(definition.id, 'emailTemplate');\n assertEmailMetadata(definition, `emailTemplate('${definition.id}')`);\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'email', definition.title);\n return { kind: 'email-template', version: 1, ...definition };\n}\n\n/** Declare a push template. Throws when `props` uses an unsupported schema or `preview` does not parse. */\nexport function pushTemplate<S extends PropsSchema, R extends PushContent>(\n definition: PushTemplateDefinition<S, R>\n): PushTemplate<S> {\n assertTemplateId(definition.id, 'pushTemplate');\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'push', definition.title);\n return { kind: 'push-template', version: 1, ...definition } as unknown as PushTemplate<S>;\n}\n\nfunction assertTemplateId(id: unknown, factory: string): void {\n if (!isTemplateId(id)) {\n throw new TypeError(`${factory}(): 'id' must be a non-empty string`);\n }\n}\n\nfunction isTemplateId(id: unknown): id is string {\n return typeof id === 'string' && id.trim() !== '';\n}\n\nexport function isEmailTemplate(value: unknown): value is EmailTemplate {\n return isMarked(value, 'email-template');\n}\n\nexport function isPushTemplate(value: unknown): value is PushTemplate {\n return isMarked(value, 'push-template');\n}\n\nexport function isDiscordTemplate(value: unknown): value is DiscordTemplate {\n return isMarked(value, 'discord-template');\n}\n\nexport function isSlackTemplate(value: unknown): value is SlackTemplate {\n return isMarked(value, 'slack-template');\n}\n\n/** Any declared template. */\nexport type AnyTemplate = EmailTemplate | PushTemplate | DiscordTemplate | SlackTemplate;\n\nexport const CHANNEL_OF: Record<AnyTemplate['kind'], TemplateChannel> = {\n 'email-template': 'email',\n 'push-template': 'push',\n 'discord-template': 'discord',\n 'slack-template': 'slack',\n};\n\nfunction isMarked(value: unknown, kind: string): boolean {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === kind &&\n (value as { version?: unknown }).version === 1\n );\n}\n\n/** Any template definition, whatever its channel. */\nexport function isTemplate(value: unknown): value is AnyTemplate {\n return (\n typeof value === 'object' &&\n value !== null &&\n Object.hasOwn(CHANNEL_OF, (value as { kind?: unknown }).kind as string) &&\n (value as { version?: unknown }).version === 1\n );\n}\n\n/* --------------------------------- registry -------------------------------- */\n\n/**\n * The template registry: per channel, the definitions the project registers. Each channel is a\n * plain object whose property names are JavaScript names only — a template is identified by its\n * own `id` (`indexTemplates`).\n */\nexport interface TemplateRegistry {\n readonly email?: Readonly<Record<string, EmailTemplate>>;\n readonly push?: Readonly<Record<string, PushTemplate>>;\n /** Discord webhook templates, each typed by the host's Env its `webhook` reads. */\n readonly discord?: Readonly<Record<string, DiscordTemplate<PropsSchema, never>>>;\n readonly slack?: Readonly<Record<string, SlackTemplate<PropsSchema, never>>>;\n}\n\n/** The template a channel's registry holds. */\nexport interface ChannelTemplate {\n email: EmailTemplate;\n push: PushTemplate;\n discord: DiscordTemplate;\n slack: SlackTemplate;\n}\n\nconst IS_TEMPLATE: Record<TemplateChannel, (value: unknown) => boolean> = {\n email: isEmailTemplate,\n push: isPushTemplate,\n discord: isDiscordTemplate,\n slack: isSlackTemplate,\n};\n\n/**\n * A channel's registry by template id — how everything that turns an id back into a definition\n * (the compiler, the validator, the renderers, the senders, the studio) looks it up. Throws when\n * an entry is not a template of the channel, has no id, or shares its id with another entry: an id\n * is registered once per channel, so two registrations (even of the same object) are an error,\n * not a silent last-wins. Insertion order is the registry's.\n */\nexport function indexTemplates<C extends TemplateChannel>(\n channel: C,\n registry: Readonly<Record<string, unknown>> | undefined\n): ReadonlyMap<string, ChannelTemplate[C]> {\n const index = new Map<string, ChannelTemplate[C]>();\n const paths = new Map<string, string>();\n for (const [name, entry] of Object.entries(registry ?? {})) {\n const path = `templates.${channel}.${name}`;\n if (!IS_TEMPLATE[channel](entry)) {\n throw new TypeError(`${path} is not a ${channel} template`);\n }\n const id = (entry as AnyTemplate).id;\n if (!isTemplateId(id)) throw new TypeError(`${path} has no id`);\n const previous = paths.get(id);\n if (previous !== undefined) {\n throw new TypeError(\n `${channel} template id '${id}' is registered twice: ${previous} and ${path}`\n );\n }\n paths.set(id, path);\n index.set(id, entry as ChannelTemplate[C]);\n }\n return index;\n}\n\n/** A registered template by id; throws naming the channel and id when there is none. */\nexport function lookupTemplate<T>(index: ReadonlyMap<string, T>, channel: string, id: string): T {\n const template = index.get(id);\n if (template === undefined) throw new Error(`${channel} template '${id}' is not registered`);\n return template;\n}\n\n/* ------------------------------- validation -------------------------------- */\n\n/** One failed field, with the dotted path the studio and the logs show. */\nexport interface TemplateIssue {\n path: string;\n message: string;\n}\n\n/**\n * A template refused its input (`phase: 'props'` — the component/content was\n * never called) or its content function returned something that is not a\n * notification (`phase: 'content'` — called, but nothing is sent). Never a\n * provider failure: a sender must not read it as a dead device token.\n */\nexport class TemplateValidationError extends Error {\n override readonly name = 'TemplateValidationError';\n constructor(\n readonly channel: TemplateChannel,\n readonly template: string,\n readonly phase: 'props' | 'content',\n readonly issues: TemplateIssue[]\n ) {\n super(\n `${channel} template '${template}' ${phase === 'props' ? 'rejected its props' : 'returned invalid content'}: ${issues\n .map((i) => `${i.path || '(root)'} ${i.message}`)\n .join('; ')}`\n );\n }\n}\n\nfunction issuesOf(error: core.$ZodError): TemplateIssue[] {\n return error.issues.map((issue) => ({\n path: issue.path.map(String).join('.'),\n message: issue.message,\n }));\n}\n\n/**\n * Parse message props against a template's schema and return the parsed value\n * — the only value anything downstream may use. Throws TemplateValidationError.\n */\nexport function parseTemplateProps<S extends PropsSchema>(\n template: EmailTemplate<S> | PushTemplate<S> | DiscordTemplate<S> | SlackTemplate<S>,\n props: Record<string, unknown>\n): core.output<S> {\n const channel = CHANNEL_OF[template.kind];\n const result = z.safeParse(template.props, props);\n if (!result.success) {\n throw new TemplateValidationError(channel, template.id, 'props', issuesOf(result.error));\n }\n return result.data;\n}\n\nconst pushContentSchema = z.strictObject({\n title: z.string(),\n body: z.string(),\n image: z.optional(z.string()),\n url: z.optional(z.string()),\n});\n\n/** Run a push template's content over parsed props and check the notification it returns. */\nexport function renderPushContent(\n template: PushTemplate,\n props: Record<string, unknown>\n): PushContent {\n const parsed = parseTemplateProps(template, props);\n const output: unknown = template.content(parsed);\n // A Promise (async content smuggled past the types) is an object without title/body: refused here\n const result = z.safeParse(pushContentSchema, output);\n if (!result.success) {\n throw new TemplateValidationError('push', template.id, 'content', issuesOf(result.error));\n }\n return result.data;\n}\n\n/**\n * What a Discord webhook accepts at all: something to show (content, an embed, a poll or\n * components) and content within Discord's 2000 characters. The rest of the body is Discord's to\n * judge; its types come from `discord-api-types`.\n */\nconst discordContentSchema = z.looseObject({\n content: z.optional(z.string().check(z.maxLength(2000))),\n embeds: z.optional(z.array(z.looseObject({}))),\n});\n\nconst slackContentSchema = z.looseObject({\n text: z.string().check(z.minLength(1)),\n blocks: z.optional(z.array(z.looseObject({ type: z.string() }))),\n});\n\n/** Run a Discord template's content over parsed props and check the message it returns. */\nexport function renderDiscordContent(\n template: DiscordTemplate,\n props: Record<string, unknown>\n): DiscordContent {\n const output: unknown = template.content(parseTemplateProps(template, props));\n const result = z.safeParse(discordContentSchema, output);\n const empty =\n result.success &&\n !result.data.content &&\n (result.data.embeds?.length ?? 0) === 0 &&\n (output as { poll?: unknown; components?: unknown[] }).poll === undefined &&\n ((output as { components?: unknown[] }).components?.length ?? 0) === 0;\n if (!result.success || empty) {\n const issues = result.success\n ? [{ path: '', message: 'a Discord message needs content, an embed, a poll or components' }]\n : issuesOf(result.error);\n throw new TemplateValidationError('discord', template.id, 'content', issues);\n }\n return output as DiscordContent;\n}\n\n/** Run a Slack template's content over parsed props and check the message it returns. */\nexport function renderSlackContent(\n template: SlackTemplate,\n props: Record<string, unknown>\n): SlackContent {\n const output: unknown = template.content(parseTemplateProps(template, props));\n const result = z.safeParse(slackContentSchema, output);\n if (!result.success) {\n throw new TemplateValidationError('slack', template.id, 'content', issuesOf(result.error));\n }\n return output as SlackContent;\n}\n\nfunction parsePreview(\n schema: PropsSchema,\n preview: unknown,\n channel: TemplateChannel,\n title: string\n) {\n const result = z.safeParse(schema, preview);\n if (!result.success) {\n throw new TemplateValidationError(\n channel,\n title,\n 'props',\n issuesOf(result.error).map((i) => Object.assign(i, { message: `preview: ${i.message}` }))\n );\n }\n}\n\n/* ---------------------------- supported schemas ---------------------------- */\n\nconst SCALAR_TYPES = new Set(['string', 'number', 'boolean', 'enum']);\n\n/** Throws when `schema` is outside the v1 subset (see the header). */\nexport function assertSupportedSchema(schema: unknown, where: string): void {\n const fail = (reason: string): never => {\n throw new TypeError(`template '${where}': unsupported props schema — ${reason}`);\n };\n const def = (schema as { _zod?: { def?: core.$ZodTypeDef } } | null | undefined)?._zod?.def;\n if (def?.type !== 'object') fail('props must be a z.strictObject({...})');\n const object = def as core.$ZodObjectDef;\n if (object.catchall?._zod.def.type !== 'never') {\n fail(\n 'props must be strict (z.strictObject): unknown fields would be dropped or passed through'\n );\n }\n rejectCustomChecks(object, where, fail);\n for (const [field, value] of Object.entries(object.shape)) {\n let fieldDef = value._zod.def;\n if (fieldDef.type === 'optional') {\n // The wrapper carries checks of its own (`.optional().overwrite(...)`): refuse them too\n rejectCustomChecks(fieldDef, `${where}.${field}`, fail);\n fieldDef = (fieldDef as core.$ZodOptionalDef).innerType._zod.def;\n }\n if (fieldDef.type === 'literal') {\n const values = (fieldDef as core.$ZodLiteralDef<core.util.Literal>).values;\n if (!values.every((v) => ['string', 'number', 'boolean'].includes(typeof v))) {\n fail(`field '${field}' is a literal of a non-scalar value`);\n }\n } else if (!SCALAR_TYPES.has(fieldDef.type)) {\n fail(\n `field '${field}' is ${fieldDef.type}; only string, number, boolean, enum and literal fields (optionally .optional()) are supported`\n );\n }\n if ((fieldDef as { coerce?: boolean }).coerce === true) {\n fail(`field '${field}' coerces its input (z.coerce), which would change what the props mean`);\n }\n rejectCustomChecks(fieldDef, `${where}.${field}`, fail);\n }\n}\n\n/**\n * Checks the subset refuses: `custom` runs arbitrary code (`.refine()`), and\n * `overwrite` changes the value (`.overwrite()`, `.trim()`, `.toLowerCase()`…)\n * — the props are parsed more than once on the way to a send (the validator,\n * then the renderer), so a value-changing check would apply twice and the\n * email would differ from the preview.\n */\nfunction rejectCustomChecks(def: core.$ZodTypeDef, where: string, fail: (reason: string) => never) {\n for (const check of def.checks ?? []) {\n if (check._zod.def.check === 'custom') {\n fail(`'${where}' uses a custom refinement, which the template subset does not support`);\n }\n if (check._zod.def.check === 'overwrite') {\n fail(\n `'${where}' changes the value (.overwrite, .trim, .toLowerCase, …), which the template subset does not support`\n );\n }\n }\n}\n\n/** The scalar props a template accepts, as the IR resolves them before parsing. */\nexport type ResolvedProps = Record<string, ScalarIR | undefined>;\n\n/**\n * The engine's MessageValidator over the template registries: the message's\n * props are parsed by its template (the parsed value is what gets sent); a\n * push's content is also run once and checked, so an invalid notification\n * stops the send before any device; Discord and Slack content likewise. Channels without a\n * registry here (sms, …) pass through to their app-supplied senders.\n */\nexport function registryValidator(registry: TemplateRegistry): {\n validate(message: { channel: string; template: string; props: ResolvedProps }): ResolvedProps;\n} {\n const email = indexTemplates('email', registry.email);\n const push = indexTemplates('push', registry.push);\n const discord = indexTemplates('discord', registry.discord);\n const slack = indexTemplates('slack', registry.slack);\n return {\n validate({ channel, template, props }) {\n if (channel === 'email') {\n const entry = lookupTemplate(email, 'email', template);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'push') {\n const entry = lookupTemplate(push, 'push', template);\n renderPushContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'discord') {\n const entry = lookupTemplate(discord, 'discord', template);\n renderDiscordContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'slack') {\n const entry = lookupTemplate(slack, 'slack', template);\n renderSlackContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n return props;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"templates.mjs","names":[],"sources":["../src/templates.ts"],"sourcesContent":["import type { Block, KnownBlock } from '@slack/types';\nimport type { RESTPostAPIWebhookWithTokenJSONBody } from 'discord-api-types/v10';\nimport * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract, Topic } from './contract';\nimport { type EmailAddress, assertEmailMetadata, assertTemplateTopic } from './email';\nimport type { ScalarIR } from './ir';\n\n/**\n * Template definitions — `emailTemplate`, `pushTemplate`, `discordTemplate` and `slackTemplate`. One\n * object per template, identified by its own `id` (unique per channel): workflows take the\n * definition itself (`.email(welcome, props)`), the config registers it, and the IR, the manifest,\n * the senders and the studio all find it by that id. Its Zod `props` schema is the only source of the\n * props type: the DSL's `.email()/.push()` arguments, the `preview`, and the\n * `component`/`content` parameter are all derived from it, and every render\n * parses the props against it first (types alone check nothing at run time).\n *\n * Kept free of React and of any renderer: the definitions are plain data plus\n * a function, so the DSL, the Worker and the studio can all import them; the\n * React rendering lives in `@shware/workflow/react-email`.\n *\n * Supported schemas (v1): a strict object (`z.strictObject`) whose fields are\n * string / number / boolean — optionally as enums or literals, optionally\n * `.optional()`, with the built-in checks (min, email, …). That is exactly\n * what IR message props can carry, and it keeps input and output identical, so\n * producer, studio and sender never disagree about a transformed value.\n * Anything else (nested objects, arrays, null, default/transform/coerce,\n * custom refinements) is rejected when the template is declared.\n */\n\n/** A strict Zod object schema of scalar fields — the accepted shape of `props`. */\nexport type PropsSchema = core.$ZodObject;\n\n/** What a push template's `content` returns: the notification itself. */\nexport interface PushContent {\n title: string;\n body: string;\n /** Rich image URL (iOS attachment / Android BigPicture). */\n image?: string;\n /** Where a tap leads: the page a browser push opens (the site's root when absent). */\n url?: string;\n}\n\n/** A Discord webhook message (`discord-api-types`): content, embeds, username, allowed mentions… */\nexport type DiscordContent = RESTPostAPIWebhookWithTokenJSONBody;\n\n/** A Slack incoming-webhook message: the fallback `text` and, usually, Block Kit `blocks`. */\nexport interface SlackContent {\n /** Shown in notifications and wherever blocks cannot be; required even with blocks. */\n text: string;\n blocks?: (KnownBlock | Block)[];\n username?: string;\n icon_emoji?: string;\n icon_url?: string;\n mrkdwn?: boolean;\n unfurl_links?: boolean;\n unfurl_media?: boolean;\n}\n\n/** The channels a template can be declared for. */\nexport type TemplateChannel = 'email' | 'push' | 'discord' | 'slack';\n\ninterface TemplateBase<S extends PropsSchema> {\n /**\n * Wire identity, unique per channel: message nodes, the template manifest, every send and the\n * studio's preview links name the template by it. Not something to rename for readability —\n * `title` is; the property a registry holds it under is only a JavaScript name.\n */\n readonly id: string;\n /** Display name in the studio (not the email subject, not the notification title). */\n readonly title: string;\n readonly description?: string | undefined;\n readonly props: S;\n /** Sample props for the studio; parsed against `props` when the template is declared. */\n readonly preview: core.input<S>;\n}\n\n/**\n * Who an email comes from and where it leads: declared on each template, never inherited from a\n * global sender. Metadata, not props — the component never receives it; its layout reads the\n * resolved unsubscribe link with `useEmailContext()` (`@shware/workflow/email-context`).\n */\nexport interface EmailMetadata {\n readonly from: EmailAddress;\n /** Where replies go; left out, the email has no Reply-To (it is not `from`, and not inherited). */\n readonly replyTo?: EmailAddress | undefined;\n /** This template's unsubscribe link; left out, the config's `email.unsubscribeUrl` (if any). */\n readonly unsubscribeUrl?: string | undefined;\n}\n\n/**\n * What kind of message a template is, by type: a `topic` the contract declares (a user can opt\n * out of it, per channel), or `transactional: true` (a receipt, a code, an invite: sent whatever\n * the user opted out of). One of the two, never neither.\n *\n * The slots nothing real fits are string-literal types that read as the error: an editor\n * reports a mistake at the property itself (\"Type 'true' is not assignable to type 'give\n * either …'\"), and the third member catches a template that says neither, with the property\n * it reports missing naming both options. Written out in full (no aliases): TypeScript prints\n * an alias by name, and the point is the text.\n */\nexport type TemplateTopic<T extends string = string> =\n | {\n readonly topic: T;\n readonly transactional?: \"give either 'topic' or 'transactional: true', not both\";\n }\n | {\n readonly transactional: true;\n readonly topic?: \"a topic the contract declares, or none: 'topic' and 'transactional: true' are exclusive\";\n }\n | {\n readonly 'topic | transactional': \"every template says what it is: 'topic: <id>' (one the contract declares) or 'transactional: true'\";\n readonly topic?: never;\n readonly transactional?: never;\n };\n\n/**\n * The email form of `TemplateTopic`: a topic email carries the unsubscribe link and the\n * `List-Unsubscribe` headers; a transactional one has no link — so no `unsubscribeUrl` either,\n * which the type says in its own words.\n */\nexport type EmailTemplateTopic<T extends string = string> =\n | {\n readonly topic: T;\n readonly transactional?: \"give either 'topic' or 'transactional: true', not both\";\n readonly unsubscribeUrl?: string;\n }\n | {\n readonly transactional: true;\n readonly topic?: \"a topic the contract declares, or none: 'topic' and 'transactional: true' are exclusive\";\n readonly unsubscribeUrl?: \"a transactional email has no unsubscribe link, so it takes no 'unsubscribeUrl'\";\n }\n | {\n readonly 'topic | transactional': \"every template says what it is: 'topic: <id>' (one the contract declares) or 'transactional: true'\";\n readonly topic?: never;\n readonly transactional?: never;\n readonly unsubscribeUrl?: string;\n };\n\n/**\n * `T` is the template's topic (`never` for a transactional one): a registry typed by the\n * contract's topics (`defineConfig({ contract, templates })`) accepts only templates whose topic\n * the contract declares.\n */\nexport interface EmailTemplate<S extends PropsSchema = PropsSchema, T extends string = string>\n extends TemplateBase<S>, EmailMetadata {\n readonly kind: 'email-template';\n readonly version: 1;\n readonly subject: string;\n /** The subscription topic the email belongs to; null for a transactional email. */\n readonly topic: T | null;\n /** The email body: a component over the parsed props (rendered by `@shware/workflow/react-email`). */\n readonly component: (props: Readonly<core.output<S>>) => unknown;\n}\n\n/**\n * `T` is the template's topic (`never` for a transactional one): a registry typed by the\n * contract's topics accepts only push templates whose topic the contract declares.\n */\nexport interface PushTemplate<\n S extends PropsSchema = PropsSchema,\n T extends string = string,\n> extends TemplateBase<S> {\n readonly kind: 'push-template';\n readonly version: 1;\n /** The subscription topic the push belongs to; null for a transactional push. */\n readonly topic: T | null;\n /** The notification, from the parsed props. Synchronous and pure; its result is checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => PushContent;\n}\n\n/**\n * A message posted to a team channel through an incoming webhook (Discord, Slack). The webhook\n * URL is a secret, so it lives in the Worker's environment and the template reads it from there\n * at send time: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`, with `Env` the host's\n * zod-validated environment (`journeyWorker({ env: schema })`). The studio never calls it.\n */\ninterface WebhookTemplateBase<S extends PropsSchema, Env> extends TemplateBase<S> {\n readonly version: 1;\n // A method, so a template typed by the host's Env still fits a registry of any Env\n webhook(env: Env): string | undefined;\n}\n\nexport interface DiscordTemplate<\n S extends PropsSchema = PropsSchema,\n Env = Record<string, unknown>,\n> extends WebhookTemplateBase<S, Env> {\n readonly kind: 'discord-template';\n /** The message, from the parsed props. Synchronous and pure; checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => DiscordContent;\n}\n\nexport interface SlackTemplate<\n S extends PropsSchema = PropsSchema,\n Env = Record<string, unknown>,\n> extends WebhookTemplateBase<S, Env> {\n readonly kind: 'slack-template';\n /** The message, from the parsed props. Synchronous and pure; checked before sending. */\n readonly content: (props: Readonly<core.output<S>>) => SlackContent;\n}\n\n/** Strips a Promise return out of the accepted callback types: async content is rejected at the type level. */\ntype Sync<T> = T extends Promise<unknown> ? never : T;\n\nexport interface EmailTemplateBase<S extends PropsSchema> {\n id: string;\n title: string;\n description?: string;\n from: EmailAddress;\n replyTo?: EmailAddress;\n props: S;\n subject: string;\n preview: NoInfer<core.input<S>>;\n component: (props: NoInfer<Readonly<core.output<S>>>) => unknown;\n}\n\n/** `emailTemplate`'s argument: the metadata, the props schema and the body, plus `topic` or `transactional: true`. */\nexport type EmailTemplateDefinition<\n S extends PropsSchema,\n T extends string = string,\n> = EmailTemplateBase<S> & EmailTemplateTopic<T>;\n\nexport interface PushTemplateBase<S extends PropsSchema, R> {\n id: string;\n title: string;\n description?: string;\n props: S;\n preview: NoInfer<core.input<S>>;\n content: (props: NoInfer<Readonly<core.output<S>>>) => Sync<R> & PushContent;\n}\n\n/** `pushTemplate`'s argument: the props schema and the notification, plus `topic` or `transactional: true`. */\nexport type PushTemplateDefinition<\n S extends PropsSchema,\n R,\n T extends string = string,\n> = PushTemplateBase<S, R> & TemplateTopic<T>;\n\nexport interface WebhookTemplateDefinition<S extends PropsSchema, Env, C> {\n id: string;\n title: string;\n description?: string;\n props: S;\n preview: NoInfer<core.input<S>>;\n /** The webhook URL, read from the Worker's environment when a message is sent. */\n webhook: (env: Env) => string | undefined;\n content: (props: NoInfer<Readonly<core.output<S>>>) => Sync<C>;\n}\n\n/** Declare a Discord webhook template. Throws when `props` is unsupported or `preview` does not parse. */\nexport function discordTemplate<S extends PropsSchema, Env = Record<string, unknown>>(\n definition: WebhookTemplateDefinition<S, Env, DiscordContent>\n): DiscordTemplate<S, Env> {\n assertTemplateId(definition.id, 'discordTemplate');\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'discord', definition.title);\n return { kind: 'discord-template', version: 1, ...definition };\n}\n\n/** Declare a Slack webhook template. Throws when `props` is unsupported or `preview` does not parse. */\nexport function slackTemplate<S extends PropsSchema, Env = Record<string, unknown>>(\n definition: WebhookTemplateDefinition<S, Env, SlackContent>\n): SlackTemplate<S, Env> {\n assertTemplateId(definition.id, 'slackTemplate');\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'slack', definition.title);\n return { kind: 'slack-template', version: 1, ...definition };\n}\n\n/**\n * Declare an email template: with a `topic` the contract declares, or `transactional: true`\n * (then `T` is `never`, and the template's `topic` is null). Throws when neither (or both) is\n * given, a transactional template carries an `unsubscribeUrl`, `from` is missing or invalid,\n * `replyTo` or `unsubscribeUrl` is invalid, `props` uses an unsupported schema or `preview` does\n * not parse. Whether the topic is one the contract declares is checked by `compileConfig`,\n * where the contract is at hand — or at the template itself by `templatesFor(contract)`'s\n * `emailTemplate`, the form to prefer in a project with a contract.\n */\nexport function emailTemplate<S extends PropsSchema, T extends string = never>(\n definition: EmailTemplateDefinition<S, T>\n): EmailTemplate<S, T> {\n return declareEmail(definition, undefined);\n}\n\n/**\n * Declare a push template: with a `topic` the contract declares (the user can opt out of it in\n * the app — App Store Review Guideline 4.5.4 requires that for marketing pushes), or\n * `transactional: true` (then `T` is `never`, and the template's `topic` is null). Throws when\n * neither (or both) is given, `props` uses an unsupported schema or `preview` does not parse.\n * Whether the topic is one the contract declares is checked by `compileConfig`, where the\n * contract is at hand — or at the template itself by `templatesFor(contract)`'s `pushTemplate`.\n */\nexport function pushTemplate<\n S extends PropsSchema,\n R extends PushContent,\n T extends string = never,\n>(definition: PushTemplateDefinition<S, R, T>): PushTemplate<S, T> {\n return declarePush(definition, undefined);\n}\n\n/**\n * The template factories bound to a contract: the same `emailTemplate` and `pushTemplate`, with\n * the topic typed by the contract's `subscriptions.topics` — so a topic the contract does not\n * declare is an error at the template's own `topic` line (in the editor, and when the module\n * loads), not a type chain at the config's `templates` where the registry meets the contract.\n * The form to use wherever a contract exists:\n *\n * const { emailTemplate, pushTemplate } = templatesFor(contract);\n *\n * The bare `emailTemplate` / `pushTemplate` stay for a project without a contract (transactional\n * templates only) and for the compile, which checks every template again.\n */\nexport function templatesFor<C extends Contract>(\n contract: C\n): {\n // Properties, not methods: the factories are meant to be destructured\n emailTemplate: <S extends PropsSchema, T extends Topic<C> = never>(\n definition: EmailTemplateDefinition<S, T>\n ) => EmailTemplate<S, T>;\n pushTemplate: <S extends PropsSchema, R extends PushContent, T extends Topic<C> = never>(\n definition: PushTemplateDefinition<S, R, T>\n ) => PushTemplate<S, T>;\n} {\n return {\n emailTemplate: (definition) => declareEmail(definition, contract),\n pushTemplate: (definition) => declarePush(definition, contract),\n };\n}\n\nfunction declareEmail<S extends PropsSchema, T extends string>(\n definition: EmailTemplateDefinition<S, T>,\n contract: Contract | undefined\n): EmailTemplate<S, T> {\n assertTemplateId(definition.id, 'emailTemplate');\n const where = `emailTemplate('${definition.id}')`;\n assertEmailMetadata(definition, where);\n assertDeclaredTopic(definition, where, contract);\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'email', definition.title);\n const { topic, transactional, ...rest } = definition as EmailTemplateBase<S> & {\n topic?: T;\n transactional?: true;\n unsubscribeUrl?: string;\n };\n void transactional;\n return { kind: 'email-template', version: 1, ...rest, topic: topic ?? null };\n}\n\nfunction declarePush<S extends PropsSchema, R extends PushContent, T extends string>(\n definition: PushTemplateDefinition<S, R, T>,\n contract: Contract | undefined\n): PushTemplate<S, T> {\n assertTemplateId(definition.id, 'pushTemplate');\n const where = `pushTemplate('${definition.id}')`;\n assertTemplateTopic(definition, where, 'push');\n assertDeclaredTopic(definition, where, contract);\n assertSupportedSchema(definition.props, definition.title);\n parsePreview(definition.props, definition.preview, 'push', definition.title);\n const { topic, transactional, ...rest } = definition as PushTemplateBase<S, R> & {\n topic?: T;\n transactional?: true;\n };\n void transactional;\n return {\n kind: 'push-template',\n version: 1,\n ...rest,\n topic: topic ?? null,\n } as unknown as PushTemplate<S, T>;\n}\n\n/** With a contract at hand (`templatesFor`), a topic it does not declare fails here, naming the list. */\nfunction assertDeclaredTopic(\n definition: { topic?: unknown },\n where: string,\n contract: Contract | undefined\n): void {\n const { topic } = definition;\n // `includes`, not the predicate: narrowing `string` by a `topic is string` leaves `never`\n if (contract === undefined || typeof topic !== 'string' || contract.topics.includes(topic))\n return;\n const list =\n contract.topics.length === 0\n ? 'the contract declares no topics (defineContract({ subscriptions: { topics: [...] } }))'\n : `the contract declares ${contract.topics.map((t) => `'${t}'`).join(', ')}`;\n throw new TypeError(`${where}: topic '${topic}' is not declared — ${list}`);\n}\n\nfunction assertTemplateId(id: unknown, factory: string): void {\n if (!isTemplateId(id)) {\n throw new TypeError(`${factory}(): 'id' must be a non-empty string`);\n }\n}\n\nfunction isTemplateId(id: unknown): id is string {\n return typeof id === 'string' && id.trim() !== '';\n}\n\nexport function isEmailTemplate(value: unknown): value is EmailTemplate {\n return isMarked(value, 'email-template');\n}\n\nexport function isPushTemplate(value: unknown): value is PushTemplate {\n return isMarked(value, 'push-template');\n}\n\nexport function isDiscordTemplate(value: unknown): value is DiscordTemplate {\n return isMarked(value, 'discord-template');\n}\n\nexport function isSlackTemplate(value: unknown): value is SlackTemplate {\n return isMarked(value, 'slack-template');\n}\n\n/** Any declared template. */\nexport type AnyTemplate = EmailTemplate | PushTemplate | DiscordTemplate | SlackTemplate;\n\nexport const CHANNEL_OF: Record<AnyTemplate['kind'], TemplateChannel> = {\n 'email-template': 'email',\n 'push-template': 'push',\n 'discord-template': 'discord',\n 'slack-template': 'slack',\n};\n\nfunction isMarked(value: unknown, kind: string): boolean {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === kind &&\n (value as { version?: unknown }).version === 1\n );\n}\n\n/** Any template definition, whatever its channel. */\nexport function isTemplate(value: unknown): value is AnyTemplate {\n return (\n typeof value === 'object' &&\n value !== null &&\n Object.hasOwn(CHANNEL_OF, (value as { kind?: unknown }).kind as string) &&\n (value as { version?: unknown }).version === 1\n );\n}\n\n/* --------------------------------- registry -------------------------------- */\n\n/**\n * The template registry: per channel, the definitions the project registers. Each channel is a\n * plain object whose property names are JavaScript names only — a template is identified by its\n * own `id` (`indexTemplates`).\n */\nexport interface TemplateRegistry<T extends string = string> {\n /** Email templates; typed by the contract's topics under `defineConfig({ contract, templates })`. */\n readonly email?: Readonly<Record<string, EmailTemplate<PropsSchema, T>>>;\n /** Push templates; typed by the contract's topics the same way. */\n readonly push?: Readonly<Record<string, PushTemplate<PropsSchema, T>>>;\n /** Discord webhook templates, each typed by the host's Env its `webhook` reads. */\n readonly discord?: Readonly<Record<string, DiscordTemplate<PropsSchema, never>>>;\n readonly slack?: Readonly<Record<string, SlackTemplate<PropsSchema, never>>>;\n}\n\n/** The template a channel's registry holds. */\nexport interface ChannelTemplate {\n email: EmailTemplate;\n push: PushTemplate;\n discord: DiscordTemplate;\n slack: SlackTemplate;\n}\n\nconst IS_TEMPLATE: Record<TemplateChannel, (value: unknown) => boolean> = {\n email: isEmailTemplate,\n push: isPushTemplate,\n discord: isDiscordTemplate,\n slack: isSlackTemplate,\n};\n\n/**\n * A channel's registry by template id — how everything that turns an id back into a definition\n * (the compiler, the validator, the renderers, the senders, the studio) looks it up. Throws when\n * an entry is not a template of the channel, has no id, or shares its id with another entry: an id\n * is registered once per channel, so two registrations (even of the same object) are an error,\n * not a silent last-wins. Insertion order is the registry's.\n */\nexport function indexTemplates<C extends TemplateChannel>(\n channel: C,\n registry: Readonly<Record<string, unknown>> | undefined\n): ReadonlyMap<string, ChannelTemplate[C]> {\n const index = new Map<string, ChannelTemplate[C]>();\n const paths = new Map<string, string>();\n for (const [name, entry] of Object.entries(registry ?? {})) {\n const path = `templates.${channel}.${name}`;\n if (!IS_TEMPLATE[channel](entry)) {\n throw new TypeError(`${path} is not a ${channel} template`);\n }\n const id = (entry as AnyTemplate).id;\n if (!isTemplateId(id)) throw new TypeError(`${path} has no id`);\n const previous = paths.get(id);\n if (previous !== undefined) {\n throw new TypeError(\n `${channel} template id '${id}' is registered twice: ${previous} and ${path}`\n );\n }\n paths.set(id, path);\n index.set(id, entry as ChannelTemplate[C]);\n }\n return index;\n}\n\n/** A registered template by id; throws naming the channel and id when there is none. */\nexport function lookupTemplate<T>(index: ReadonlyMap<string, T>, channel: string, id: string): T {\n const template = index.get(id);\n if (template === undefined) throw new Error(`${channel} template '${id}' is not registered`);\n return template;\n}\n\n/* ------------------------------- validation -------------------------------- */\n\n/** One failed field, with the dotted path the studio and the logs show. */\nexport interface TemplateIssue {\n path: string;\n message: string;\n}\n\n/**\n * A template refused its input (`phase: 'props'` — the component/content was\n * never called) or its content function returned something that is not a\n * notification (`phase: 'content'` — called, but nothing is sent). Never a\n * provider failure: a sender must not read it as a dead device token.\n */\nexport class TemplateValidationError extends Error {\n override readonly name = 'TemplateValidationError';\n constructor(\n readonly channel: TemplateChannel,\n readonly template: string,\n readonly phase: 'props' | 'content',\n readonly issues: TemplateIssue[]\n ) {\n super(\n `${channel} template '${template}' ${phase === 'props' ? 'rejected its props' : 'returned invalid content'}: ${issues\n .map((i) => `${i.path || '(root)'} ${i.message}`)\n .join('; ')}`\n );\n }\n}\n\nfunction issuesOf(error: core.$ZodError): TemplateIssue[] {\n return error.issues.map((issue) => ({\n path: issue.path.map(String).join('.'),\n message: issue.message,\n }));\n}\n\n/**\n * Parse message props against a template's schema and return the parsed value\n * — the only value anything downstream may use. Throws TemplateValidationError.\n */\nexport function parseTemplateProps<S extends PropsSchema>(\n template: EmailTemplate<S> | PushTemplate<S> | DiscordTemplate<S> | SlackTemplate<S>,\n props: Record<string, unknown>\n): core.output<S> {\n const channel = CHANNEL_OF[template.kind];\n const result = z.safeParse(template.props, props);\n if (!result.success) {\n throw new TemplateValidationError(channel, template.id, 'props', issuesOf(result.error));\n }\n return result.data;\n}\n\nconst pushContentSchema = z.strictObject({\n title: z.string(),\n body: z.string(),\n image: z.optional(z.string()),\n url: z.optional(z.string()),\n});\n\n/** Run a push template's content over parsed props and check the notification it returns. */\nexport function renderPushContent(\n template: PushTemplate,\n props: Record<string, unknown>\n): PushContent {\n const parsed = parseTemplateProps(template, props);\n const output: unknown = template.content(parsed);\n // A Promise (async content smuggled past the types) is an object without title/body: refused here\n const result = z.safeParse(pushContentSchema, output);\n if (!result.success) {\n throw new TemplateValidationError('push', template.id, 'content', issuesOf(result.error));\n }\n return result.data;\n}\n\n/**\n * What a Discord webhook accepts at all: something to show (content, an embed, a poll or\n * components) and content within Discord's 2000 characters. The rest of the body is Discord's to\n * judge; its types come from `discord-api-types`.\n */\nconst discordContentSchema = z.looseObject({\n content: z.optional(z.string().check(z.maxLength(2000))),\n embeds: z.optional(z.array(z.looseObject({}))),\n});\n\nconst slackContentSchema = z.looseObject({\n text: z.string().check(z.minLength(1)),\n blocks: z.optional(z.array(z.looseObject({ type: z.string() }))),\n});\n\n/** Run a Discord template's content over parsed props and check the message it returns. */\nexport function renderDiscordContent(\n template: DiscordTemplate,\n props: Record<string, unknown>\n): DiscordContent {\n const output: unknown = template.content(parseTemplateProps(template, props));\n const result = z.safeParse(discordContentSchema, output);\n const empty =\n result.success &&\n !result.data.content &&\n (result.data.embeds?.length ?? 0) === 0 &&\n (output as { poll?: unknown; components?: unknown[] }).poll === undefined &&\n ((output as { components?: unknown[] }).components?.length ?? 0) === 0;\n if (!result.success || empty) {\n const issues = result.success\n ? [{ path: '', message: 'a Discord message needs content, an embed, a poll or components' }]\n : issuesOf(result.error);\n throw new TemplateValidationError('discord', template.id, 'content', issues);\n }\n return output as DiscordContent;\n}\n\n/** Run a Slack template's content over parsed props and check the message it returns. */\nexport function renderSlackContent(\n template: SlackTemplate,\n props: Record<string, unknown>\n): SlackContent {\n const output: unknown = template.content(parseTemplateProps(template, props));\n const result = z.safeParse(slackContentSchema, output);\n if (!result.success) {\n throw new TemplateValidationError('slack', template.id, 'content', issuesOf(result.error));\n }\n return output as SlackContent;\n}\n\nfunction parsePreview(\n schema: PropsSchema,\n preview: unknown,\n channel: TemplateChannel,\n title: string\n) {\n const result = z.safeParse(schema, preview);\n if (!result.success) {\n throw new TemplateValidationError(\n channel,\n title,\n 'props',\n issuesOf(result.error).map((i) => Object.assign(i, { message: `preview: ${i.message}` }))\n );\n }\n}\n\n/* ---------------------------- supported schemas ---------------------------- */\n\nconst SCALAR_TYPES = new Set(['string', 'number', 'boolean', 'enum']);\n\n/** Throws when `schema` is outside the v1 subset (see the header). */\nexport function assertSupportedSchema(schema: unknown, where: string): void {\n const fail = (reason: string): never => {\n throw new TypeError(`template '${where}': unsupported props schema — ${reason}`);\n };\n const def = (schema as { _zod?: { def?: core.$ZodTypeDef } } | null | undefined)?._zod?.def;\n if (def?.type !== 'object') fail('props must be a z.strictObject({...})');\n const object = def as core.$ZodObjectDef;\n if (object.catchall?._zod.def.type !== 'never') {\n fail(\n 'props must be strict (z.strictObject): unknown fields would be dropped or passed through'\n );\n }\n rejectCustomChecks(object, where, fail);\n for (const [field, value] of Object.entries(object.shape)) {\n let fieldDef = value._zod.def;\n if (fieldDef.type === 'optional') {\n // The wrapper carries checks of its own (`.optional().overwrite(...)`): refuse them too\n rejectCustomChecks(fieldDef, `${where}.${field}`, fail);\n fieldDef = (fieldDef as core.$ZodOptionalDef).innerType._zod.def;\n }\n if (fieldDef.type === 'literal') {\n const values = (fieldDef as core.$ZodLiteralDef<core.util.Literal>).values;\n if (!values.every((v) => ['string', 'number', 'boolean'].includes(typeof v))) {\n fail(`field '${field}' is a literal of a non-scalar value`);\n }\n } else if (!SCALAR_TYPES.has(fieldDef.type)) {\n fail(\n `field '${field}' is ${fieldDef.type}; only string, number, boolean, enum and literal fields (optionally .optional()) are supported`\n );\n }\n if ((fieldDef as { coerce?: boolean }).coerce === true) {\n fail(`field '${field}' coerces its input (z.coerce), which would change what the props mean`);\n }\n rejectCustomChecks(fieldDef, `${where}.${field}`, fail);\n }\n}\n\n/**\n * Checks the subset refuses: `custom` runs arbitrary code (`.refine()`), and\n * `overwrite` changes the value (`.overwrite()`, `.trim()`, `.toLowerCase()`…)\n * — the props are parsed more than once on the way to a send (the validator,\n * then the renderer), so a value-changing check would apply twice and the\n * email would differ from the preview.\n */\nfunction rejectCustomChecks(def: core.$ZodTypeDef, where: string, fail: (reason: string) => never) {\n for (const check of def.checks ?? []) {\n if (check._zod.def.check === 'custom') {\n fail(`'${where}' uses a custom refinement, which the template subset does not support`);\n }\n if (check._zod.def.check === 'overwrite') {\n fail(\n `'${where}' changes the value (.overwrite, .trim, .toLowerCase, …), which the template subset does not support`\n );\n }\n }\n}\n\n/** The scalar props a template accepts, as the IR resolves them before parsing. */\nexport type ResolvedProps = Record<string, ScalarIR | undefined>;\n\n/**\n * The engine's MessageValidator over the template registries: the message's\n * props are parsed by its template (the parsed value is what gets sent); a\n * push's content is also run once and checked, so an invalid notification\n * stops the send before any device; Discord and Slack content likewise. Channels without a\n * registry here (sms, …) pass through to their app-supplied senders.\n */\nexport function registryValidator(registry: TemplateRegistry): {\n validate(message: { channel: string; template: string; props: ResolvedProps }): ResolvedProps;\n} {\n const email = indexTemplates('email', registry.email);\n const push = indexTemplates('push', registry.push);\n const discord = indexTemplates('discord', registry.discord);\n const slack = indexTemplates('slack', registry.slack);\n return {\n validate({ channel, template, props }) {\n if (channel === 'email') {\n const entry = lookupTemplate(email, 'email', template);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'push') {\n const entry = lookupTemplate(push, 'push', template);\n renderPushContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'discord') {\n const entry = lookupTemplate(discord, 'discord', template);\n renderDiscordContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n if (channel === 'slack') {\n const entry = lookupTemplate(slack, 'slack', template);\n renderSlackContent(entry, props);\n return parseTemplateProps(entry, props) as ResolvedProps;\n }\n return props;\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/workflow",
3
- "version": "5.0.1",
3
+ "version": "6.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ShwareHQ/shware-sdk.git"