@cowliss/cli 0.4.0 → 0.6.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.
- package/README.md +14 -9
- package/dist/guest/{driver-DaNdNhuF.js → driver-DBt5l1Z5.js} +4 -4
- package/dist/guest/driver.d.ts +1 -1
- package/dist/guest/driver.js +1 -1
- package/dist/guest/emails.d.ts +18 -1
- package/dist/guest/{index-DAGLEHDn.d.ts → index-CXcCEcAg.d.ts} +8 -9
- package/dist/guest/{journeys-B_xg_GnL.js → journeys-BrcKEXz0.js} +103 -117
- package/dist/guest/journeys.d.ts +27 -21
- package/dist/guest/journeys.js +1 -1
- package/dist/guest/wasi.js +1 -1
- package/dist/index.js +1728 -1733
- package/examples/abandoned-checkout/journeys/abandoned-checkout.ts +0 -1
- package/examples/abandoned-checkout/scenarios/abandoned-checkout.timeout.json +1 -2
- package/examples/activity-decay/journeys/activity-decay.ts +0 -1
- package/examples/cross-app-pitch/journeys/cross-app-pitch.ts +4 -4
- package/examples/cross-app-pitch/scenarios/cross-app-pitch.json +1 -1
- package/examples/winback/journeys/winback.ts +0 -1
- package/examples/winback/scenarios/winback.json +1 -1
- package/package.json +1 -1
- package/dist/guest/constants-B0wk-87t.d.ts +0 -30
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ cow whoami
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
`cow login` opens the dashboard and caches a session token. In CI, set
|
|
29
|
-
`
|
|
29
|
+
`COW_PIPELINE_KEY` to a pipeline key instead.
|
|
30
30
|
|
|
31
31
|
## Journeys and emails as code
|
|
32
32
|
|
|
@@ -45,7 +45,6 @@ import { defineJourney } from "@cowliss/cli/journeys";
|
|
|
45
45
|
export default defineJourney({
|
|
46
46
|
trigger: { event: "checkout_started" },
|
|
47
47
|
purpose: "emailMarketing",
|
|
48
|
-
senderIdentity: "cowliss-default",
|
|
49
48
|
run: async (event, api) => {
|
|
50
49
|
const purchased = await api.waitForEvent("purchase_completed", {
|
|
51
50
|
timeout: "24h",
|
|
@@ -70,16 +69,22 @@ cow test abandoned-checkout --scenario scenarios/cart.json
|
|
|
70
69
|
# typecheck and bundle, no API call
|
|
71
70
|
cow build
|
|
72
71
|
|
|
73
|
-
#
|
|
74
|
-
cow
|
|
72
|
+
# upload the project; the server compiles it
|
|
73
|
+
cow push
|
|
75
74
|
|
|
76
|
-
#
|
|
77
|
-
cow
|
|
75
|
+
# what the server holds, and what is running
|
|
76
|
+
cow status
|
|
77
|
+
|
|
78
|
+
# turn it on
|
|
79
|
+
cow enable abandoned-checkout
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
A push creates a new version of every journey and template whose code
|
|
83
|
+
changed, and changes no flag: `cow enable` and `cow disable` are the only
|
|
84
|
+
things that put code in front of anyone. A journey runs its latest version
|
|
85
|
+
that finished compiling, so a push that fails to compile leaves the previous
|
|
86
|
+
one running, and a run that has already started stays on the version it
|
|
87
|
+
started on. To undo, `git revert` and push again.
|
|
83
88
|
|
|
84
89
|
## Admin, and everything else
|
|
85
90
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-
|
|
1
|
+
import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-BrcKEXz0.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createElement } from "react";
|
|
4
4
|
import { renderToStaticMarkup } from "react-dom/server.browser";
|
|
@@ -124,7 +124,8 @@ function createApi(input, journey) {
|
|
|
124
124
|
args: {
|
|
125
125
|
template: args.template,
|
|
126
126
|
props: args.props,
|
|
127
|
-
|
|
127
|
+
from: args.from ?? journey.from,
|
|
128
|
+
replyTo: args.replyTo
|
|
128
129
|
}
|
|
129
130
|
}),
|
|
130
131
|
webhook: (args) => call({
|
|
@@ -5572,8 +5573,7 @@ function readManifest(module) {
|
|
|
5572
5573
|
kind: "journey",
|
|
5573
5574
|
trigger: journey.trigger,
|
|
5574
5575
|
purpose: journey.purpose,
|
|
5575
|
-
|
|
5576
|
-
environments: journey.environments,
|
|
5576
|
+
from: journey.from,
|
|
5577
5577
|
tags: journey.tags
|
|
5578
5578
|
});
|
|
5579
5579
|
}
|
package/dist/guest/driver.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-
|
|
1
|
+
import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-CXcCEcAg.js";
|
|
2
2
|
//#region src/guest/driver.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The in-process driver: JSON in, JSON out (spec: Guest protocol). One
|
package/dist/guest/driver.js
CHANGED
package/dist/guest/emails.d.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
import { n as SendClass } from "./constants-B0wk-87t.js";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { ReactElement } from "react";
|
|
3
|
+
//#region ../../packages/shared/src/constants.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The two classes of send, declared on the template rather than passed per
|
|
6
|
+
* call so the class cannot drift between two sends of the same message.
|
|
7
|
+
*
|
|
8
|
+
* Marketing is everything a journey sends on the org's behalf: it carries
|
|
9
|
+
* the RFC 8058 unsubscribe headers and runs the full gate list.
|
|
10
|
+
* Transactional is the developer's own operational mail (a receipt, an
|
|
11
|
+
* export-is-ready notice): it carries neither header and skips consent and
|
|
12
|
+
* the per-user frequency cap, because an unsubscribe link on an invoice is
|
|
13
|
+
* wrong and a receipt must not silently vanish for anyone who once left a
|
|
14
|
+
* newsletter. Suppression, the quota, the sending pause, and the from-domain
|
|
15
|
+
* check still apply to both: those bound cost and protect the shared SES
|
|
16
|
+
* account, and none of them are about what the recipient asked for.
|
|
17
|
+
*/
|
|
18
|
+
declare const SEND_CLASSES: readonly ["marketing", "transactional"];
|
|
19
|
+
type SendClass = (typeof SEND_CLASSES)[number];
|
|
20
|
+
//#endregion
|
|
4
21
|
//#region src/guest/emails.d.ts
|
|
5
22
|
/** A template's subject line, computed from the props it declares. */
|
|
6
23
|
type Subject<Schema extends z.ZodType> = (props: z.infer<Schema>) => string;
|
|
@@ -34,7 +34,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
34
34
|
args: z.ZodObject<{
|
|
35
35
|
template: z.ZodString;
|
|
36
36
|
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
37
|
-
|
|
37
|
+
from: z.ZodOptional<z.ZodString>;
|
|
38
|
+
replyTo: z.ZodOptional<z.ZodEmail>;
|
|
38
39
|
}, z.core.$strict>;
|
|
39
40
|
}, z.core.$strip>, z.ZodObject<{
|
|
40
41
|
name: z.ZodLiteral<"send.webhook">;
|
|
@@ -124,7 +125,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
124
125
|
args: z.ZodObject<{
|
|
125
126
|
template: z.ZodString;
|
|
126
127
|
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
127
|
-
|
|
128
|
+
from: z.ZodOptional<z.ZodString>;
|
|
129
|
+
replyTo: z.ZodOptional<z.ZodEmail>;
|
|
128
130
|
}, z.core.$strict>;
|
|
129
131
|
}, z.core.$strip>, z.ZodObject<{
|
|
130
132
|
name: z.ZodLiteral<"send.webhook">;
|
|
@@ -183,7 +185,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
183
185
|
args: z.ZodObject<{
|
|
184
186
|
template: z.ZodString;
|
|
185
187
|
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
186
|
-
|
|
188
|
+
from: z.ZodOptional<z.ZodString>;
|
|
189
|
+
replyTo: z.ZodOptional<z.ZodEmail>;
|
|
187
190
|
}, z.core.$strict>;
|
|
188
191
|
}, z.core.$strip>, z.ZodObject<{
|
|
189
192
|
name: z.ZodLiteral<"send.webhook">;
|
|
@@ -257,11 +260,7 @@ declare const manifestOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
257
260
|
segment: z.ZodString;
|
|
258
261
|
}, z.core.$strict>]>;
|
|
259
262
|
purpose: z.ZodString;
|
|
260
|
-
|
|
261
|
-
environments: z.ZodArray<z.ZodEnum<{
|
|
262
|
-
development: "development";
|
|
263
|
-
production: "production";
|
|
264
|
-
}>>;
|
|
263
|
+
from: z.ZodOptional<z.ZodString>;
|
|
265
264
|
kind: z.ZodLiteral<"journey">;
|
|
266
265
|
}, z.core.$strip>, z.ZodObject<{
|
|
267
266
|
tags: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
|
|
@@ -282,7 +281,7 @@ type ManifestOutput = z.infer<typeof manifestOutputSchema>;
|
|
|
282
281
|
* reuses it.
|
|
283
282
|
*
|
|
284
283
|
* Both members are strict, so a journey holding the retired `source` key
|
|
285
|
-
* fails to compile
|
|
284
|
+
* fails to compile instead of silently triggering on every app.
|
|
286
285
|
* There is deliberately no pipe filter: a trigger narrows by the app the
|
|
287
286
|
* write is attributed to, the same token a segment definition names.
|
|
288
287
|
*/
|
|
@@ -9,16 +9,6 @@ const DOCS_URL = "https://docs.cowliss.com";
|
|
|
9
9
|
*/
|
|
10
10
|
const CLERK_ORG_SUBJECT_PREFIX = "org_";
|
|
11
11
|
/**
|
|
12
|
-
* The two fixed environments every org has (spec: Environments). An app
|
|
13
|
-
* belongs to one, so the environment of every write is the app's;
|
|
14
|
-
* profiles, identifiers, events, memberships, journey instances,
|
|
15
|
-
* deliveries, violations, quarantine, the address ledger, and idempotency
|
|
16
|
-
* keys are per environment, while the catalog, sending domains, the
|
|
17
|
-
* suppression mirror, billing, and journey code are shared. A third
|
|
18
|
-
* environment is a one-line change here plus the mirrored db enum.
|
|
19
|
-
*/
|
|
20
|
-
const ENVIRONMENTS = ["development", "production"];
|
|
21
|
-
/**
|
|
22
12
|
* Fixed consent purposes for the prototype. Consent is a per-purpose map on
|
|
23
13
|
* the profile, checked at send-step execution time.
|
|
24
14
|
*
|
|
@@ -51,8 +41,8 @@ const EXECUTION_LIMITS = {
|
|
|
51
41
|
logLines: 100,
|
|
52
42
|
logLineBytes: 1024
|
|
53
43
|
};
|
|
54
|
-
/** Per
|
|
55
|
-
const
|
|
44
|
+
/** Per push: manifest counts, bundle size, and the source tarball. */
|
|
45
|
+
const PUSH_LIMITS = {
|
|
56
46
|
journeys: 100,
|
|
57
47
|
templates: 200,
|
|
58
48
|
bundleBytes: 2097152,
|
|
@@ -66,29 +56,13 @@ const RELEASE_LIMITS = {
|
|
|
66
56
|
const COW_CONFIG_SCHEMA_PATH = "/schemas/cow.json";
|
|
67
57
|
const COW_CONFIG_SCHEMA_URL = `${DOCS_URL}${COW_CONFIG_SCHEMA_PATH}`;
|
|
68
58
|
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region ../../packages/shared/src/environments.ts
|
|
71
|
-
/**
|
|
72
|
-
* The environment on the wire: the source's `environment` field, the
|
|
73
|
-
* `X-Cow-Environment` header admin calls select with, and the
|
|
74
|
-
* `environment` column every per-environment DTO carries.
|
|
75
|
-
*/
|
|
76
|
-
const environmentSchema = z.enum(ENVIRONMENTS);
|
|
77
|
-
/**
|
|
78
|
-
* The environments a shared definition (a segment, a journey) declares it
|
|
79
|
-
* works on: a non-empty list of distinct values. A definition declaring none
|
|
80
|
-
* would be dead code with a row behind it, and a repeated value is a typo
|
|
81
|
-
* rather than an intent, which is the rule `defineJourney` applies too.
|
|
82
|
-
*/
|
|
83
|
-
const environmentsSchema = z.array(environmentSchema).min(1, "at least one environment is required").refine((values) => new Set(values).size === values.length, { message: "environments must not repeat" });
|
|
84
|
-
|
|
85
59
|
//#endregion
|
|
86
60
|
//#region ../../packages/shared/src/journeys-v2/manifest.ts
|
|
87
61
|
/**
|
|
88
|
-
* The
|
|
62
|
+
* The pushed manifest (spec: Build; Push and compile): what `cow build`
|
|
89
63
|
* extracts from a project and `cow push` uploads with the bundles. The
|
|
90
|
-
* server validates it with these schemas, compiles every bundle
|
|
91
|
-
*
|
|
64
|
+
* server validates it with these schemas, compiles every bundle whose key
|
|
65
|
+
* changed, and stores that entry on the version it creates.
|
|
92
66
|
*/
|
|
93
67
|
/**
|
|
94
68
|
* A journey or template key: the file basename under `journeys/` or
|
|
@@ -168,7 +142,7 @@ const patternSchema = z.union([onePatternSchema, z.array(onePatternSchema).min(1
|
|
|
168
142
|
* reuses it.
|
|
169
143
|
*
|
|
170
144
|
* Both members are strict, so a journey holding the retired `source` key
|
|
171
|
-
* fails to compile
|
|
145
|
+
* fails to compile instead of silently triggering on every app.
|
|
172
146
|
* There is deliberately no pipe filter: a trigger narrows by the app the
|
|
173
147
|
* write is attributed to, the same token a segment definition names.
|
|
174
148
|
*/
|
|
@@ -177,15 +151,44 @@ const triggerSchema = z.union([z.strictObject({
|
|
|
177
151
|
appId: patternSchema.optional()
|
|
178
152
|
}), z.strictObject({ segment: z.string().min(1) })]);
|
|
179
153
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
154
|
+
* The address half of a from-header: a local part, an `@`, and a dotted
|
|
155
|
+
* domain. Deliberately narrower than RFC 5322 (no quoted local parts, no
|
|
156
|
+
* address literals): every character it refuses would have to be quoted or
|
|
157
|
+
* escaped to survive a header, and none of them belong in an address a
|
|
158
|
+
* journey sends from.
|
|
159
|
+
*/
|
|
160
|
+
const FROM_ADDRESS = /^[^\s@<>",;]+@([^\s@<>",;.]+(?:\.[^\s@<>",;.]+)+)$/;
|
|
161
|
+
/**
|
|
162
|
+
* Parse a journey's or a send's `from`: `addr@domain`, or
|
|
163
|
+
* `Name <addr@domain>` with the name optionally quoted. Null when it is
|
|
164
|
+
* neither, which is what `cow build` refuses on and what the send-time
|
|
165
|
+
* domain gate turns into a skip.
|
|
182
166
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
|
|
167
|
+
* One parser, and the send facade reuses it. A control character anywhere is
|
|
168
|
+
* a refusal rather than something to strip: it survives quoting and would
|
|
169
|
+
* inject a second header.
|
|
170
|
+
*/
|
|
171
|
+
function parseFromAddress(from) {
|
|
172
|
+
const text = from.trim();
|
|
173
|
+
if (/\p{Cc}/u.test(text)) return null;
|
|
174
|
+
const angled = /^(.*?)\s*<([^<>]*)>$/.exec(text);
|
|
175
|
+
const address = (angled?.[2] ?? text).trim();
|
|
176
|
+
const domain = FROM_ADDRESS.exec(address)?.[1];
|
|
177
|
+
if (!domain) return null;
|
|
178
|
+
const name = (angled?.[1] ?? "").trim().replace(/^"(.*)"$/s, "$1").replace(/\\(.)/g, "$1").trim();
|
|
179
|
+
return {
|
|
180
|
+
...name ? { name } : {},
|
|
181
|
+
address,
|
|
182
|
+
domain: domain.toLowerCase()
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The address a journey or one send leaves as: a string, not the name of a
|
|
187
|
+
* configured row (ADR 0014). The domain is checked against the
|
|
188
|
+
* organization's verified ones at push time and again at send time; the
|
|
189
|
+
* shape is all that is checked here, because it is all a build can know.
|
|
187
190
|
*/
|
|
188
|
-
const
|
|
191
|
+
const fromSchema = z.string().trim().max(200, "from must be at most 200 characters").refine((value) => parseFromAddress(value) !== null, { message: "from must be an address (\"billing@acme.com\") or a name and address (\"Billing <billing@acme.com>\")" });
|
|
189
192
|
/** A content address: `sha256:` plus the lowercase hex digest. */
|
|
190
193
|
const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
|
|
191
194
|
/**
|
|
@@ -221,13 +224,6 @@ const SPINE_ENTRY_NAMES = [
|
|
|
221
224
|
const spineEntrySchema = z.object({
|
|
222
225
|
name: z.enum(SPINE_ENTRY_NAMES),
|
|
223
226
|
detail: z.string().max(200).optional(),
|
|
224
|
-
/**
|
|
225
|
-
* The sender identity a `send.email` call named for itself, overriding
|
|
226
|
-
* the journey's own. Present only when the author wrote one on the call,
|
|
227
|
-
* which is what lets the deploy warning and the journey detail page name
|
|
228
|
-
* the override without re-reading the code.
|
|
229
|
-
*/
|
|
230
|
-
senderIdentity: z.string().max(100).optional(),
|
|
231
227
|
/** A `waitForEvent` timeout, as the author wrote it. */
|
|
232
228
|
timeout: z.string().max(50).optional(),
|
|
233
229
|
get steps() {
|
|
@@ -237,6 +233,13 @@ const spineEntrySchema = z.object({
|
|
|
237
233
|
return z.array(spineEntrySchema).optional();
|
|
238
234
|
}
|
|
239
235
|
}).meta({ id: "JourneySpineEntry" });
|
|
236
|
+
/**
|
|
237
|
+
* One journey in a stored manifest. A plain (non-strict) object on purpose:
|
|
238
|
+
* a manifest pushed before the author's rollout gate went away still
|
|
239
|
+
* carries keys that no longer exist, and every stored manifest has to keep
|
|
240
|
+
* parsing for good (ADR 0011). Zod strips them, and the journey runs when
|
|
241
|
+
* its `enabled` flag says so, which is the one gate there is.
|
|
242
|
+
*/
|
|
240
243
|
const manifestJourneySchema = z.object({
|
|
241
244
|
key: journeyKeySchema,
|
|
242
245
|
/** The author's labels; the dashboard's only grouping. */
|
|
@@ -244,23 +247,17 @@ const manifestJourneySchema = z.object({
|
|
|
244
247
|
trigger: triggerSchema,
|
|
245
248
|
/**
|
|
246
249
|
* A fixed purpose or one the project declares. Checked against the org's
|
|
247
|
-
* declared set at
|
|
250
|
+
* declared set at push time, not here: a manifest is built and pushed
|
|
248
251
|
* without ever reaching the org whose rows say what exists.
|
|
249
252
|
*/
|
|
250
253
|
purpose: consentPurposeKeySchema,
|
|
251
254
|
/**
|
|
252
|
-
* The
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* Optional here and required at `defineJourney`, exactly like `purposes`: a
|
|
258
|
-
* release pushed before the field existed carries none and its stored
|
|
259
|
-
* manifest still parses. The author's build is where the error is useful.
|
|
255
|
+
* The address every `send.email` in this journey goes out as, unless the
|
|
256
|
+
* call names its own. Optional everywhere: an omitted one is the
|
|
257
|
+
* organization's shared fallback address, which is what makes day-one
|
|
258
|
+
* sending zero-config (ADR 0014).
|
|
260
259
|
*/
|
|
261
|
-
|
|
262
|
-
/** The author's rollout gate: the journey is active only in these. */
|
|
263
|
-
environments: environmentsSchema,
|
|
260
|
+
from: fromSchema.optional(),
|
|
264
261
|
spine: z.array(spineEntrySchema),
|
|
265
262
|
bundle: digestSchema
|
|
266
263
|
});
|
|
@@ -292,27 +289,27 @@ function uniqueKeys(items, ctx, path) {
|
|
|
292
289
|
}
|
|
293
290
|
/**
|
|
294
291
|
* What `cow build` writes to `.cow/build/manifest.json`, and the shape a
|
|
295
|
-
*
|
|
292
|
+
* push row stores for good, entry by entry, on the versions it creates.
|
|
296
293
|
*
|
|
297
294
|
* `protocol` is any positive integer rather than the current constant on
|
|
298
|
-
* purpose: a
|
|
299
|
-
* started on, so the day the protocol is bumped every stored
|
|
300
|
-
* still parse, or the runner
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
295
|
+
* purpose: a version is immutable and an execution stays pinned to the one
|
|
296
|
+
* it started on, so the day the protocol is bumped every stored push must
|
|
297
|
+
* still parse, or the runner and every API response the client validates
|
|
298
|
+
* break at once for any org with history. The literal lives at push time
|
|
299
|
+
* only (`createPushBodySchema`), which is where a stale CLI is the
|
|
300
|
+
* developer's own fixable problem, and the runner refuses a version built
|
|
301
|
+
* for another protocol with a message naming both numbers.
|
|
305
302
|
*/
|
|
306
303
|
const manifestSchema = z.object({
|
|
307
304
|
protocol: z.number().int().positive(),
|
|
308
305
|
/** The `@cowliss/cli` version the project was built with. */
|
|
309
306
|
sdk: z.string().min(1),
|
|
310
|
-
journeys: z.array(manifestJourneySchema).max(
|
|
311
|
-
templates: z.array(manifestTemplateSchema).max(
|
|
307
|
+
journeys: z.array(manifestJourneySchema).max(PUSH_LIMITS.journeys),
|
|
308
|
+
templates: z.array(manifestTemplateSchema).max(PUSH_LIMITS.templates),
|
|
312
309
|
/**
|
|
313
310
|
* The consent purposes this project declares, copied from `cow.json`.
|
|
314
|
-
* Optional rather than defaulted: a
|
|
315
|
-
*
|
|
311
|
+
* Optional rather than defaulted: a push made before purposes existed
|
|
312
|
+
* carries none, and its stored manifest still parses.
|
|
316
313
|
*/
|
|
317
314
|
purposes: purposesSchema.optional(),
|
|
318
315
|
/** Digest of the gzipped source tarball. */
|
|
@@ -323,27 +320,12 @@ const manifestSchema = z.object({
|
|
|
323
320
|
uniqueKeys(manifest.purposes ?? [], ctx, "purposes");
|
|
324
321
|
});
|
|
325
322
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
* space: `welcome.ts` and `welcome.tsx` are one journey and the email it
|
|
333
|
-
* sends in every example, and a flat map would let one overwrite the other.
|
|
334
|
-
*
|
|
335
|
-
* `plugin` is a toolchain record, not a linked artifact: modules are
|
|
336
|
-
* statically linked, so the plugin bytes are inside each module. It says
|
|
337
|
-
* which engine compiled the release, which is what a later bug report or a
|
|
338
|
-
* reproducible rebuild needs.
|
|
339
|
-
*/
|
|
340
|
-
const compiledManifestSchema = manifestSchema.safeExtend({
|
|
341
|
-
modules: z.object({
|
|
342
|
-
journeys: z.record(journeyKeySchema, digestSchema),
|
|
343
|
-
templates: z.record(journeyKeySchema, digestSchema)
|
|
344
|
-
}),
|
|
345
|
-
plugin: digestSchema
|
|
346
|
-
});
|
|
323
|
+
* One entry of a stored manifest: what a version is a snapshot of. Journeys
|
|
324
|
+
* and templates are kept apart because they share a key space (`welcome.ts`
|
|
325
|
+
* and `welcome.tsx` are one journey and the email it sends in every
|
|
326
|
+
* example), and the version's own `kind` says which of the two this is.
|
|
327
|
+
*/
|
|
328
|
+
const versionManifestSchema = z.union([manifestJourneySchema, manifestTemplateSchema]);
|
|
347
329
|
|
|
348
330
|
//#endregion
|
|
349
331
|
//#region ../../packages/shared/src/journeys-v2/config.ts
|
|
@@ -355,9 +337,8 @@ const compiledManifestSchema = manifestSchema.safeExtend({
|
|
|
355
337
|
const projectNameSchema = z.string().trim().min(1).max(200);
|
|
356
338
|
/**
|
|
357
339
|
* `cow.json` (spec: Project layout): the org, and which of its projects this
|
|
358
|
-
* directory is.
|
|
359
|
-
*
|
|
360
|
-
* silently ignored setting.
|
|
340
|
+
* directory is. Auth never lives in the project. Strict, so a typo'd key
|
|
341
|
+
* is a build error rather than a silently ignored setting.
|
|
361
342
|
*/
|
|
362
343
|
const cowConfigSchema = z.strictObject({
|
|
363
344
|
$schema: z.url().optional(),
|
|
@@ -385,7 +366,7 @@ const cowConfigSchema = z.strictObject({
|
|
|
385
366
|
webUrl: z.url().optional()
|
|
386
367
|
}).meta({
|
|
387
368
|
title: "cow.json",
|
|
388
|
-
description: "A cow project: the organization and the project it
|
|
369
|
+
description: "A cow project: the organization and the project it pushes to."
|
|
389
370
|
});
|
|
390
371
|
|
|
391
372
|
//#endregion
|
|
@@ -412,8 +393,8 @@ const properties = z.record(z.string(), z.unknown());
|
|
|
412
393
|
* them. A name outside the union is rejected, never dispatched.
|
|
413
394
|
*
|
|
414
395
|
* Every `args` is strict, and that is the tenancy boundary made mechanical:
|
|
415
|
-
* a module that returns an `orgId
|
|
416
|
-
*
|
|
396
|
+
* a module that returns an `orgId` or any other field beside the ones a
|
|
397
|
+
* capability takes fails the parse instead of having it
|
|
417
398
|
* quietly dropped. Tenant context comes from the workflow input, never from
|
|
418
399
|
* guest output, and this is where saying so becomes checkable.
|
|
419
400
|
*/
|
|
@@ -435,12 +416,14 @@ const commandSchema = z.discriminatedUnion("name", [
|
|
|
435
416
|
template: journeyKeySchema,
|
|
436
417
|
props: properties,
|
|
437
418
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* one
|
|
441
|
-
*
|
|
419
|
+
* The address this mail leaves as. The guest SDK fills in the
|
|
420
|
+
* journey's own whenever the call does not name one, so the host has
|
|
421
|
+
* one resolution path and never reads the manifest to find it;
|
|
422
|
+
* absent on both is the organization's shared fallback address.
|
|
442
423
|
*/
|
|
443
|
-
|
|
424
|
+
from: fromSchema.optional(),
|
|
425
|
+
/** Where a reply to this one mail goes, instead of the from-address. */
|
|
426
|
+
replyTo: z.email().optional()
|
|
444
427
|
})
|
|
445
428
|
}),
|
|
446
429
|
z.object({
|
|
@@ -521,7 +504,7 @@ const executionLimitsSchema = z.object({
|
|
|
521
504
|
logLineBytes: z.number().int().positive()
|
|
522
505
|
});
|
|
523
506
|
const journeyStepInputSchema = z.object({
|
|
524
|
-
protocol: z.literal(
|
|
507
|
+
protocol: z.literal(3),
|
|
525
508
|
kind: z.literal("journey"),
|
|
526
509
|
key: journeyKeySchema,
|
|
527
510
|
event: guestEventSchema,
|
|
@@ -561,7 +544,7 @@ const journeyStepOutputSchema = z.discriminatedUnion("status", [
|
|
|
561
544
|
})
|
|
562
545
|
]);
|
|
563
546
|
const templateRenderInputSchema = z.object({
|
|
564
|
-
protocol: z.literal(
|
|
547
|
+
protocol: z.literal(3),
|
|
565
548
|
kind: z.literal("template"),
|
|
566
549
|
key: journeyKeySchema,
|
|
567
550
|
props: properties
|
|
@@ -580,8 +563,7 @@ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
|
|
|
580
563
|
const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
|
|
581
564
|
trigger: true,
|
|
582
565
|
purpose: true,
|
|
583
|
-
|
|
584
|
-
environments: true,
|
|
566
|
+
from: true,
|
|
585
567
|
tags: true
|
|
586
568
|
}).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
|
|
587
569
|
sendClass: true,
|
|
@@ -637,7 +619,7 @@ const sandboxFailureCodeSchema = z.enum(SANDBOX_FAILURE_CODES);
|
|
|
637
619
|
* to be written by hand or by an agent, not generated.
|
|
638
620
|
*
|
|
639
621
|
* The schema lives here rather than in packages/journeys so the CLI can reject
|
|
640
|
-
* a bad file before it boots a Temporal
|
|
622
|
+
* a bad file before it boots a Temporal dev server, and so the docs site can
|
|
641
623
|
* render the format from one source.
|
|
642
624
|
*/
|
|
643
625
|
const journeyScenarioSchema = z.object({
|
|
@@ -679,7 +661,7 @@ const journeyScenarioSchema = z.object({
|
|
|
679
661
|
//#endregion
|
|
680
662
|
//#region src/guest/journeys.ts
|
|
681
663
|
/**
|
|
682
|
-
* A capability that failed host-side (an unknown
|
|
664
|
+
* A capability that failed host-side (an unknown webhook, invalid
|
|
683
665
|
* props, a profile that is not there). The failure is journaled, so a
|
|
684
666
|
* journey that catches it takes the same branch on every replay.
|
|
685
667
|
*/
|
|
@@ -693,25 +675,29 @@ var CapabilityError = class extends Error {
|
|
|
693
675
|
};
|
|
694
676
|
/**
|
|
695
677
|
* The config half of a journey, validated with the same schema the release
|
|
696
|
-
* manifest is validated with, so a bad trigger
|
|
697
|
-
* fails at build time rather than at the
|
|
698
|
-
*
|
|
678
|
+
* manifest is validated with, so a bad trigger, a malformed purpose key, or
|
|
679
|
+
* a `from` that is not an address fails at build time rather than at the
|
|
680
|
+
* first execution. Whether the org declares that purpose, and whether it may
|
|
681
|
+
* send from that domain, are questions only the push can answer.
|
|
699
682
|
*/
|
|
700
683
|
const journeyConfigSchema = manifestJourneySchema.pick({
|
|
701
684
|
trigger: true,
|
|
702
685
|
purpose: true,
|
|
703
|
-
|
|
704
|
-
environments: true,
|
|
686
|
+
from: true,
|
|
705
687
|
tags: true
|
|
706
|
-
})
|
|
707
|
-
/**
|
|
688
|
+
});
|
|
689
|
+
/**
|
|
690
|
+
* Author a journey. Throws at definition time on an invalid config.
|
|
691
|
+
*
|
|
692
|
+
* There is no environment gate here: an organization has one data space, and
|
|
693
|
+
* a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
|
|
694
|
+
*/
|
|
708
695
|
function defineJourney(input) {
|
|
709
696
|
return {
|
|
710
697
|
...journeyConfigSchema.parse({
|
|
711
698
|
trigger: input.trigger,
|
|
712
699
|
purpose: input.purpose,
|
|
713
|
-
|
|
714
|
-
environments: input.environments ?? [...ENVIRONMENTS],
|
|
700
|
+
from: input.from,
|
|
715
701
|
tags: input.tags
|
|
716
702
|
}),
|
|
717
703
|
run: input.run
|
package/dist/guest/journeys.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { n as Duration, r as GuestEvent, t as Trigger } from "./index-
|
|
2
|
-
import { t as Environment } from "./constants-B0wk-87t.js";
|
|
1
|
+
import { n as Duration, r as GuestEvent, t as Trigger } from "./index-CXcCEcAg.js";
|
|
3
2
|
//#region src/guest/journeys.d.ts
|
|
4
3
|
/** The event a journey runs for, or the one a `waitForEvent` resolved with. */
|
|
5
4
|
type Event = GuestEvent;
|
|
6
5
|
/**
|
|
7
6
|
* A profile as the capability layer hands it back: the recipient of this
|
|
8
|
-
* execution (`api.profile.get()`) or another profile in the same org
|
|
9
|
-
*
|
|
7
|
+
* execution (`api.profile.get()`) or another profile in the same org
|
|
8
|
+
* (`api.profiles.get(id)`). A fresh read on every call.
|
|
10
9
|
*/
|
|
11
10
|
type Profile = {
|
|
12
11
|
/** The Cowliss-generated profile id (`usr_`). */
|
|
@@ -24,7 +23,7 @@ type EmailSendResult = {
|
|
|
24
23
|
status: string;
|
|
25
24
|
};
|
|
26
25
|
/**
|
|
27
|
-
* A capability that failed host-side (an unknown
|
|
26
|
+
* A capability that failed host-side (an unknown webhook, invalid
|
|
28
27
|
* props, a profile that is not there). The failure is journaled, so a
|
|
29
28
|
* journey that catches it takes the same branch on every replay.
|
|
30
29
|
*/
|
|
@@ -69,11 +68,16 @@ type Api = {
|
|
|
69
68
|
template: Key;
|
|
70
69
|
props: TemplateProps<Key>;
|
|
71
70
|
/**
|
|
72
|
-
* Send this one mail from a different
|
|
73
|
-
*
|
|
74
|
-
*
|
|
71
|
+
* Send this one mail from a different address than the journey's own:
|
|
72
|
+
* `"billing@acme.com"` or `"Billing <billing@acme.com>"`. The domain
|
|
73
|
+
* must be one your organization has verified, or the send is skipped.
|
|
75
74
|
*/
|
|
76
|
-
|
|
75
|
+
from?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Where a reply to this one mail goes, instead of the from-address.
|
|
78
|
+
* Any address: nothing is sent from it.
|
|
79
|
+
*/
|
|
80
|
+
replyTo?: string;
|
|
77
81
|
}): Promise<EmailSendResult>;
|
|
78
82
|
webhook(args: {
|
|
79
83
|
destination: string;
|
|
@@ -123,27 +127,29 @@ type JourneyConfig = {
|
|
|
123
127
|
*/
|
|
124
128
|
purpose: string;
|
|
125
129
|
/**
|
|
126
|
-
* The
|
|
127
|
-
*
|
|
128
|
-
* for the address Cowliss gives you with nothing to set up. A single send
|
|
130
|
+
* The address every `api.send.email` in this journey goes out as:
|
|
131
|
+
* `"billing@acme.com"` or `"Billing <billing@acme.com>"`. A single send
|
|
129
132
|
* can override it.
|
|
130
133
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
+
* Optional: leave it out and Cowliss sends from the address it gives your
|
|
135
|
+
* organization, with nothing to set up. Named here rather than in a
|
|
136
|
+
* settings page, so the address a journey sends from is readable in the
|
|
137
|
+
* journey's own source.
|
|
134
138
|
*/
|
|
135
|
-
|
|
136
|
-
/** The author's rollout gate; defaults to every environment. */
|
|
137
|
-
environments: Environment[];
|
|
139
|
+
from?: string;
|
|
138
140
|
/** The author's labels; the dashboard's only grouping. Defaults to none. */
|
|
139
141
|
tags: string[];
|
|
140
142
|
};
|
|
141
143
|
type Journey = JourneyConfig & {
|
|
142
144
|
run: (event: Event, api: Api) => Promise<void>;
|
|
143
145
|
};
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Author a journey. Throws at definition time on an invalid config.
|
|
148
|
+
*
|
|
149
|
+
* There is no environment gate here: an organization has one data space, and
|
|
150
|
+
* a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
|
|
151
|
+
*/
|
|
152
|
+
declare function defineJourney(input: Omit<JourneyConfig, "tags"> & {
|
|
147
153
|
tags?: string[];
|
|
148
154
|
run: (event: Event, api: Api) => Promise<void>;
|
|
149
155
|
}): Journey;
|
package/dist/guest/journeys.js
CHANGED
package/dist/guest/wasi.js
CHANGED