@cowliss/cli 0.3.0 → 0.5.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 +16 -9
- package/dist/guest/{driver-M6LQVMr2.js → driver-14FnzM-h.js} +18 -15
- 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-heC1gFE_.d.ts → index-BDj02EsQ.d.ts} +12 -15
- package/dist/guest/{journeys-BcbIxxI-.js → journeys-C8j8Sqvl.js} +216 -116
- package/dist/guest/journeys.d.ts +42 -16
- package/dist/guest/journeys.js +1 -1
- package/dist/guest/wasi.js +1 -1
- package/dist/index.js +2185 -1544
- package/examples/abandoned-checkout/journeys/abandoned-checkout.ts +2 -1
- package/examples/abandoned-checkout/scenarios/abandoned-checkout.timeout.json +2 -1
- package/examples/activity-decay/journeys/activity-decay.ts +1 -0
- package/examples/cross-app-pitch/journeys/cross-app-pitch.ts +5 -1
- package/examples/cross-app-pitch/scenarios/cross-app-pitch.json +2 -1
- package/examples/winback/journeys/winback.ts +2 -1
- package/examples/winback/scenarios/winback.json +4 -1
- package/package.json +3 -1
- package/dist/guest/constants-OZrYz4I2.d.ts +0 -42
|
@@ -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,65 +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/journeys-v2/config.ts
|
|
71
|
-
/**
|
|
72
|
-
* A project's name: what `cow init` asked for, and what the `projects` row
|
|
73
|
-
* stores. Lives here rather than beside the release DTOs because `cow.json`
|
|
74
|
-
* is the file a developer types it into; `createProjectBodySchema` reuses it.
|
|
75
|
-
*/
|
|
76
|
-
const projectNameSchema = z.string().trim().min(1).max(200);
|
|
77
|
-
/**
|
|
78
|
-
* `cow.json` (spec: Project layout): the org, and which of its projects this
|
|
79
|
-
* directory is. The environment is always a flag, and auth never lives in
|
|
80
|
-
* the project. Strict, so a typo'd key is a build error rather than a
|
|
81
|
-
* silently ignored setting.
|
|
82
|
-
*/
|
|
83
|
-
const cowConfigSchema = z.strictObject({
|
|
84
|
-
$schema: z.url().optional(),
|
|
85
|
-
orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
|
|
86
|
-
/**
|
|
87
|
-
* Which project of the org this directory pushes to. One org can hold
|
|
88
|
-
* several, one per repo, each with its own release sequence and its own
|
|
89
|
-
* slice of the deployed journeys, so every project says which it is.
|
|
90
|
-
*/
|
|
91
|
-
project: projectNameSchema,
|
|
92
|
-
/** Overrides the API the CLI talks to; the hosted product needs none. */
|
|
93
|
-
apiUrl: z.url().optional(),
|
|
94
|
-
/**
|
|
95
|
-
* Overrides the dashboard `cow login` opens. Paired with `apiUrl`: a
|
|
96
|
-
* login against one Cowliss's dashboard yields a token the other's API
|
|
97
|
-
* rejects, so a config that names an API names its dashboard too.
|
|
98
|
-
*/
|
|
99
|
-
webUrl: z.url().optional()
|
|
100
|
-
}).meta({
|
|
101
|
-
title: "cow.json",
|
|
102
|
-
description: "A cow project: the organization and the project it deploys to."
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
//#endregion
|
|
106
|
-
//#region ../../packages/shared/src/environments.ts
|
|
107
|
-
/**
|
|
108
|
-
* The environment on the wire: the source's `environment` field, the
|
|
109
|
-
* `X-Cow-Environment` header admin calls select with, and the
|
|
110
|
-
* `environment` column every per-environment DTO carries.
|
|
111
|
-
*/
|
|
112
|
-
const environmentSchema = z.enum(ENVIRONMENTS);
|
|
113
|
-
/**
|
|
114
|
-
* The environments a shared definition (a segment, a journey) declares it
|
|
115
|
-
* works on: a non-empty list of distinct values. A definition declaring none
|
|
116
|
-
* would be dead code with a row behind it, and a repeated value is a typo
|
|
117
|
-
* rather than an intent, which is the rule `defineJourney` applies too.
|
|
118
|
-
*/
|
|
119
|
-
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" });
|
|
120
|
-
|
|
121
59
|
//#endregion
|
|
122
60
|
//#region ../../packages/shared/src/journeys-v2/manifest.ts
|
|
123
61
|
/**
|
|
124
|
-
* The
|
|
62
|
+
* The pushed manifest (spec: Build; Push and compile): what `cow build`
|
|
125
63
|
* extracts from a project and `cow push` uploads with the bundles. The
|
|
126
|
-
* server validates it with these schemas, compiles every bundle
|
|
127
|
-
*
|
|
64
|
+
* server validates it with these schemas, compiles every bundle whose key
|
|
65
|
+
* changed, and stores that entry on the version it creates.
|
|
128
66
|
*/
|
|
129
67
|
/**
|
|
130
68
|
* A journey or template key: the file basename under `journeys/` or
|
|
@@ -147,6 +85,50 @@ const journeyKeySchema = z.string().max(64).regex(JOURNEY_KEY_PATTERN, "key must
|
|
|
147
85
|
*/
|
|
148
86
|
const tagsSchema = z.array(z.string().trim().min(1).max(50, "a tag must be at most 50 characters")).max(20, "a journey or template takes at most 20 tags").default([]).transform((tags) => [...new Set(tags)]);
|
|
149
87
|
/**
|
|
88
|
+
* A consent purpose key: camelCase, matching the fixed `emailMarketing` and
|
|
89
|
+
* `dataProcessing`. Purposes are keys in the `consent` map a customer reads
|
|
90
|
+
* on their own profile, which is why they are not the kebab-case of a
|
|
91
|
+
* journey key.
|
|
92
|
+
*/
|
|
93
|
+
const CONSENT_PURPOSE_KEY_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
|
94
|
+
/**
|
|
95
|
+
* One purpose key wherever a key is named rather than declared: a journey's
|
|
96
|
+
* `purpose`, and the keys of the consent patch an identify carries. The set
|
|
97
|
+
* a key is checked against is the org's declared rows, which no schema can
|
|
98
|
+
* see, so validation here is the shape only; the deploy and the ingestion
|
|
99
|
+
* write refuse a key the org has not declared.
|
|
100
|
+
*/
|
|
101
|
+
const consentPurposeKeySchema = z.string().max(50, "a purpose key must be at most 50 characters").regex(CONSENT_PURPOSE_KEY_PATTERN, "a consent purpose key must be camelCase (a letter first, then letters and digits)");
|
|
102
|
+
/**
|
|
103
|
+
* One purpose a project declares in `cow.json` (spec: Decisions). A declared
|
|
104
|
+
* purpose is marketing-class and sits under the `emailMarketing` umbrella,
|
|
105
|
+
* so `denied` is the only default it may carry: the purpose is absent on
|
|
106
|
+
* every profile that already exists, and a granted default would answer for
|
|
107
|
+
* all of them at once.
|
|
108
|
+
*
|
|
109
|
+
* The field stays required rather than disappearing, so every `cow.json` and
|
|
110
|
+
* every stored manifest written before this still parses, and the column
|
|
111
|
+
* behind it still holds `granted` for the seeded `dataProcessing` row: this
|
|
112
|
+
* is a refusal at declaration time, not a narrower storage shape.
|
|
113
|
+
*
|
|
114
|
+
* The two fixed purposes cannot be declared. They are seeded for every org
|
|
115
|
+
* and owned by no project, so a project redeclaring one would be renaming
|
|
116
|
+
* the master switch every other project's journeys hang off.
|
|
117
|
+
*/
|
|
118
|
+
const declaredPurposeSchema = z.strictObject({
|
|
119
|
+
key: consentPurposeKeySchema.refine((key) => !CONSENT_PURPOSES.includes(key), `${CONSENT_PURPOSES.join(" and ")} are fixed purposes and cannot be declared`),
|
|
120
|
+
/** What the dashboard and the account modal render beside the switch. */
|
|
121
|
+
label: z.string().trim().min(1).max(50, "a purpose label must be at most 50 characters"),
|
|
122
|
+
/** What the purpose means for a profile whose map does not answer it. */
|
|
123
|
+
default: z.literal("denied", "a declared purpose's \"default\" must be \"denied\": nobody has answered it yet, and a granted default would opt every profile you already have into it")
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* The purposes one project declares. Capped low the way tags are: a purpose
|
|
127
|
+
* is a category a recipient reads on a preferences switch, not a taxonomy.
|
|
128
|
+
* Absent means the project declares none, which is every project today.
|
|
129
|
+
*/
|
|
130
|
+
const purposesSchema = z.array(declaredPurposeSchema).max(20, "a project declares at most 20 consent purposes");
|
|
131
|
+
/**
|
|
150
132
|
* A matcher field: one pattern or a non-empty list of them, a list being a
|
|
151
133
|
* disjunction. See `matchesPattern` in ../patterns for the dialect (`*`
|
|
152
134
|
* only) and for why a pattern whose literal prefix is not `system.` never
|
|
@@ -160,7 +142,7 @@ const patternSchema = z.union([onePatternSchema, z.array(onePatternSchema).min(1
|
|
|
160
142
|
* reuses it.
|
|
161
143
|
*
|
|
162
144
|
* Both members are strict, so a journey holding the retired `source` key
|
|
163
|
-
* fails to compile
|
|
145
|
+
* fails to compile instead of silently triggering on every app.
|
|
164
146
|
* There is deliberately no pipe filter: a trigger narrows by the app the
|
|
165
147
|
* write is attributed to, the same token a segment definition names.
|
|
166
148
|
*/
|
|
@@ -168,14 +150,29 @@ const triggerSchema = z.union([z.strictObject({
|
|
|
168
150
|
event: patternSchema,
|
|
169
151
|
appId: patternSchema.optional()
|
|
170
152
|
}), z.strictObject({ segment: z.string().min(1) })]);
|
|
153
|
+
/**
|
|
154
|
+
* A destination's name: the token a journey addresses it by, in a
|
|
155
|
+
* `send.webhook` call or a journey's `senderIdentity`.
|
|
156
|
+
*
|
|
157
|
+
* Defined here rather than beside the destinations contract, and imported
|
|
158
|
+
* from here by it, because a journey manifest names one and the guest layer
|
|
159
|
+
* is bundled into every tenant module: importing it the other way round
|
|
160
|
+
* would pull the Drizzle destinations table into all of them.
|
|
161
|
+
*/
|
|
162
|
+
const destinationNameSchema = z.string().trim().min(1, "name is required").max(100, "name must be at most 100 characters");
|
|
171
163
|
/** A content address: `sha256:` plus the lowercase hex digest. */
|
|
172
164
|
const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
|
|
173
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* The names of the capability calls a journey may make. Grouped by verb and
|
|
167
|
+
* then channel (`send.email`, not `email.send`), because the verb is the
|
|
168
|
+
* thing a journey author is choosing between and the name should read the
|
|
169
|
+
* way the code is written.
|
|
170
|
+
*/
|
|
174
171
|
const COMMAND_NAMES = [
|
|
175
172
|
"sleep",
|
|
176
173
|
"waitForEvent",
|
|
177
|
-
"email
|
|
178
|
-
"webhook
|
|
174
|
+
"send.email",
|
|
175
|
+
"send.webhook",
|
|
179
176
|
"traits.set",
|
|
180
177
|
"traits.unset",
|
|
181
178
|
"profile.get",
|
|
@@ -184,23 +181,65 @@ const COMMAND_NAMES = [
|
|
|
184
181
|
"restart"
|
|
185
182
|
];
|
|
186
183
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
184
|
+
* What a spine entry can be: every capability call, plus the control flow
|
|
185
|
+
* `cow build` reads off the journey's source: an `if` (a condition, with
|
|
186
|
+
* `steps` and `otherwise`), a `loop` (with `steps` as its body), and the `end`
|
|
187
|
+
* of a path.
|
|
191
188
|
*/
|
|
189
|
+
const SPINE_ENTRY_NAMES = [
|
|
190
|
+
...COMMAND_NAMES,
|
|
191
|
+
"if",
|
|
192
|
+
"loop",
|
|
193
|
+
"end"
|
|
194
|
+
];
|
|
192
195
|
const spineEntrySchema = z.object({
|
|
193
|
-
name: z.enum(
|
|
194
|
-
detail: z.string().max(200).optional()
|
|
195
|
-
|
|
196
|
+
name: z.enum(SPINE_ENTRY_NAMES),
|
|
197
|
+
detail: z.string().max(200).optional(),
|
|
198
|
+
/**
|
|
199
|
+
* The sender identity a `send.email` call named for itself, overriding
|
|
200
|
+
* the journey's own. Present only when the author wrote one on the call,
|
|
201
|
+
* which is what lets the deploy warning and the journey detail page name
|
|
202
|
+
* the override without re-reading the code.
|
|
203
|
+
*/
|
|
204
|
+
senderIdentity: z.string().max(100).optional(),
|
|
205
|
+
/** A `waitForEvent` timeout, as the author wrote it. */
|
|
206
|
+
timeout: z.string().max(50).optional(),
|
|
207
|
+
get steps() {
|
|
208
|
+
return z.array(spineEntrySchema).optional();
|
|
209
|
+
},
|
|
210
|
+
get otherwise() {
|
|
211
|
+
return z.array(spineEntrySchema).optional();
|
|
212
|
+
}
|
|
213
|
+
}).meta({ id: "JourneySpineEntry" });
|
|
214
|
+
/**
|
|
215
|
+
* One journey in a stored manifest. A plain (non-strict) object on purpose:
|
|
216
|
+
* a manifest pushed before the author's rollout gate went away still carries
|
|
217
|
+
* `environments`, and every stored manifest has to keep parsing for good
|
|
218
|
+
* (ADR 0011). Zod strips the key, and the journey runs wherever the
|
|
219
|
+
* per-environment enabled flag says it runs, which is the one gate there is.
|
|
220
|
+
*/
|
|
196
221
|
const manifestJourneySchema = z.object({
|
|
197
222
|
key: journeyKeySchema,
|
|
198
223
|
/** The author's labels; the dashboard's only grouping. */
|
|
199
224
|
tags: tagsSchema,
|
|
200
225
|
trigger: triggerSchema,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
/**
|
|
227
|
+
* A fixed purpose or one the project declares. Checked against the org's
|
|
228
|
+
* declared set at push time, not here: a manifest is built and pushed
|
|
229
|
+
* without ever reaching the org whose rows say what exists.
|
|
230
|
+
*/
|
|
231
|
+
purpose: consentPurposeKeySchema,
|
|
232
|
+
/**
|
|
233
|
+
* The sender identity every `send.email` in this journey goes out as,
|
|
234
|
+
* unless the call names its own. A name, never a `dst_` id: an org has one
|
|
235
|
+
* row per environment, so an id would send in production and fail in
|
|
236
|
+
* development, which is the one thing a journey must not do.
|
|
237
|
+
*
|
|
238
|
+
* Optional here and required at `defineJourney`, exactly like `purposes`: a
|
|
239
|
+
* version pushed before the field existed carries none and its stored
|
|
240
|
+
* manifest still parses. The author's build is where the error is useful.
|
|
241
|
+
*/
|
|
242
|
+
senderIdentity: destinationNameSchema.optional(),
|
|
204
243
|
spine: z.array(spineEntrySchema),
|
|
205
244
|
bundle: digestSchema
|
|
206
245
|
});
|
|
@@ -230,40 +269,87 @@ function uniqueKeys(items, ctx, path) {
|
|
|
230
269
|
seen.add(item.key);
|
|
231
270
|
}
|
|
232
271
|
}
|
|
233
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* What `cow build` writes to `.cow/build/manifest.json`, and the shape a
|
|
274
|
+
* push row stores for good, entry by entry, on the versions it creates.
|
|
275
|
+
*
|
|
276
|
+
* `protocol` is any positive integer rather than the current constant on
|
|
277
|
+
* purpose: a version is immutable and an execution stays pinned to the one
|
|
278
|
+
* it started on, so the day the protocol is bumped every stored push must
|
|
279
|
+
* still parse, or the runner and every API response the client validates
|
|
280
|
+
* break at once for any org with history. The literal lives at push time
|
|
281
|
+
* only (`createPushBodySchema`), which is where a stale CLI is the
|
|
282
|
+
* developer's own fixable problem, and the runner refuses a version built
|
|
283
|
+
* for another protocol with a message naming both numbers.
|
|
284
|
+
*/
|
|
234
285
|
const manifestSchema = z.object({
|
|
235
|
-
protocol: z.
|
|
286
|
+
protocol: z.number().int().positive(),
|
|
236
287
|
/** The `@cowliss/cli` version the project was built with. */
|
|
237
288
|
sdk: z.string().min(1),
|
|
238
|
-
journeys: z.array(manifestJourneySchema).max(
|
|
239
|
-
templates: z.array(manifestTemplateSchema).max(
|
|
289
|
+
journeys: z.array(manifestJourneySchema).max(PUSH_LIMITS.journeys),
|
|
290
|
+
templates: z.array(manifestTemplateSchema).max(PUSH_LIMITS.templates),
|
|
291
|
+
/**
|
|
292
|
+
* The consent purposes this project declares, copied from `cow.json`.
|
|
293
|
+
* Optional rather than defaulted: a push made before purposes existed
|
|
294
|
+
* carries none, and its stored manifest still parses.
|
|
295
|
+
*/
|
|
296
|
+
purposes: purposesSchema.optional(),
|
|
240
297
|
/** Digest of the gzipped source tarball. */
|
|
241
298
|
source: digestSchema
|
|
242
299
|
}).superRefine((manifest, ctx) => {
|
|
243
300
|
uniqueKeys(manifest.journeys, ctx, "journeys");
|
|
244
301
|
uniqueKeys(manifest.templates, ctx, "templates");
|
|
302
|
+
uniqueKeys(manifest.purposes ?? [], ctx, "purposes");
|
|
245
303
|
});
|
|
246
304
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* Journeys and templates are keyed separately because they share a key
|
|
253
|
-
* space: `welcome.ts` and `welcome.tsx` are one journey and the email it
|
|
254
|
-
* sends in every example, and a flat map would let one overwrite the other.
|
|
255
|
-
*
|
|
256
|
-
* `plugin` is a toolchain record, not a linked artifact: modules are
|
|
257
|
-
* statically linked, so the plugin bytes are inside each module. It says
|
|
258
|
-
* which engine compiled the release, which is what a later bug report or a
|
|
259
|
-
* reproducible rebuild needs.
|
|
305
|
+
* One entry of a stored manifest: what a version is a snapshot of. Journeys
|
|
306
|
+
* and templates are kept apart because they share a key space (`welcome.ts`
|
|
307
|
+
* and `welcome.tsx` are one journey and the email it sends in every
|
|
308
|
+
* example), and the version's own `kind` says which of the two this is.
|
|
260
309
|
*/
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
310
|
+
const versionManifestSchema = z.union([manifestJourneySchema, manifestTemplateSchema]);
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region ../../packages/shared/src/journeys-v2/config.ts
|
|
314
|
+
/**
|
|
315
|
+
* A project's name: what `cow init` asked for, and what the `projects` row
|
|
316
|
+
* stores. Lives here rather than beside the release DTOs because `cow.json`
|
|
317
|
+
* is the file a developer types it into; `createProjectBodySchema` reuses it.
|
|
318
|
+
*/
|
|
319
|
+
const projectNameSchema = z.string().trim().min(1).max(200);
|
|
320
|
+
/**
|
|
321
|
+
* `cow.json` (spec: Project layout): the org, and which of its projects this
|
|
322
|
+
* directory is. The environment is always a flag, and auth never lives in
|
|
323
|
+
* the project. Strict, so a typo'd key is a build error rather than a
|
|
324
|
+
* silently ignored setting.
|
|
325
|
+
*/
|
|
326
|
+
const cowConfigSchema = z.strictObject({
|
|
327
|
+
$schema: z.url().optional(),
|
|
328
|
+
orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
|
|
329
|
+
/**
|
|
330
|
+
* Which project of the org this directory pushes to. One org can hold
|
|
331
|
+
* several, one per repo, each with its own release sequence and its own
|
|
332
|
+
* slice of the deployed journeys, so every project says which it is.
|
|
333
|
+
*/
|
|
334
|
+
project: projectNameSchema,
|
|
335
|
+
/**
|
|
336
|
+
* The consent purposes this project declares. They are org-wide, so two
|
|
337
|
+
* projects declaring one key must agree on its label and default or the
|
|
338
|
+
* deploy is refused; a deploy adds and updates them and never deletes
|
|
339
|
+
* one, because profiles hold answers against them.
|
|
340
|
+
*/
|
|
341
|
+
purposes: purposesSchema.optional(),
|
|
342
|
+
/** Overrides the API the CLI talks to; the hosted product needs none. */
|
|
343
|
+
apiUrl: z.url().optional(),
|
|
344
|
+
/**
|
|
345
|
+
* Overrides the dashboard `cow login` opens. Paired with `apiUrl`: a
|
|
346
|
+
* login against one Cowliss's dashboard yields a token the other's API
|
|
347
|
+
* rejects, so a config that names an API names its dashboard too.
|
|
348
|
+
*/
|
|
349
|
+
webUrl: z.url().optional()
|
|
350
|
+
}).meta({
|
|
351
|
+
title: "cow.json",
|
|
352
|
+
description: "A cow project: the organization and the project it pushes to."
|
|
267
353
|
});
|
|
268
354
|
|
|
269
355
|
//#endregion
|
|
@@ -308,14 +394,21 @@ const commandSchema = z.discriminatedUnion("name", [
|
|
|
308
394
|
})
|
|
309
395
|
}),
|
|
310
396
|
z.object({
|
|
311
|
-
name: z.literal("email
|
|
397
|
+
name: z.literal("send.email"),
|
|
312
398
|
args: z.strictObject({
|
|
313
399
|
template: journeyKeySchema,
|
|
314
|
-
props: properties
|
|
400
|
+
props: properties,
|
|
401
|
+
/**
|
|
402
|
+
* Which sender identity this mail leaves as, by name. Required, and
|
|
403
|
+
* the guest SDK fills in the journey's own when the call does not name
|
|
404
|
+
* one, so the host has one resolution path and never has to read the
|
|
405
|
+
* manifest to find a sender.
|
|
406
|
+
*/
|
|
407
|
+
senderIdentity: destinationNameSchema
|
|
315
408
|
})
|
|
316
409
|
}),
|
|
317
410
|
z.object({
|
|
318
|
-
name: z.literal("webhook
|
|
411
|
+
name: z.literal("send.webhook"),
|
|
319
412
|
args: z.strictObject({
|
|
320
413
|
destination: z.string().min(1),
|
|
321
414
|
payload: properties
|
|
@@ -392,7 +485,7 @@ const executionLimitsSchema = z.object({
|
|
|
392
485
|
logLineBytes: z.number().int().positive()
|
|
393
486
|
});
|
|
394
487
|
const journeyStepInputSchema = z.object({
|
|
395
|
-
protocol: z.literal(
|
|
488
|
+
protocol: z.literal(2),
|
|
396
489
|
kind: z.literal("journey"),
|
|
397
490
|
key: journeyKeySchema,
|
|
398
491
|
event: guestEventSchema,
|
|
@@ -432,7 +525,7 @@ const journeyStepOutputSchema = z.discriminatedUnion("status", [
|
|
|
432
525
|
})
|
|
433
526
|
]);
|
|
434
527
|
const templateRenderInputSchema = z.object({
|
|
435
|
-
protocol: z.literal(
|
|
528
|
+
protocol: z.literal(2),
|
|
436
529
|
kind: z.literal("template"),
|
|
437
530
|
key: journeyKeySchema,
|
|
438
531
|
props: properties
|
|
@@ -451,7 +544,7 @@ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
|
|
|
451
544
|
const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
|
|
452
545
|
trigger: true,
|
|
453
546
|
purpose: true,
|
|
454
|
-
|
|
547
|
+
senderIdentity: true,
|
|
455
548
|
tags: true
|
|
456
549
|
}).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
|
|
457
550
|
sendClass: true,
|
|
@@ -563,22 +656,29 @@ var CapabilityError = class extends Error {
|
|
|
563
656
|
};
|
|
564
657
|
/**
|
|
565
658
|
* The config half of a journey, validated with the same schema the release
|
|
566
|
-
* manifest is validated with, so a bad trigger or
|
|
567
|
-
* at build time rather than at the first execution.
|
|
659
|
+
* manifest is validated with, so a bad trigger or a malformed purpose key
|
|
660
|
+
* fails at build time rather than at the first execution. Whether the org
|
|
661
|
+
* declares that purpose is a question only the deploy can answer.
|
|
568
662
|
*/
|
|
569
663
|
const journeyConfigSchema = manifestJourneySchema.pick({
|
|
570
664
|
trigger: true,
|
|
571
665
|
purpose: true,
|
|
572
|
-
|
|
666
|
+
senderIdentity: true,
|
|
573
667
|
tags: true
|
|
574
|
-
});
|
|
575
|
-
/**
|
|
668
|
+
}).extend({ senderIdentity: destinationNameSchema });
|
|
669
|
+
/**
|
|
670
|
+
* Author a journey. Throws at definition time on an invalid config.
|
|
671
|
+
*
|
|
672
|
+
* There is no environment gate here: a journey runs wherever it is enabled,
|
|
673
|
+
* and nowhere else (ADR 0011). A source that still passes `environments` is
|
|
674
|
+
* a typecheck error in `cow build`, which is where the author can fix it.
|
|
675
|
+
*/
|
|
576
676
|
function defineJourney(input) {
|
|
577
677
|
return {
|
|
578
678
|
...journeyConfigSchema.parse({
|
|
579
679
|
trigger: input.trigger,
|
|
580
680
|
purpose: input.purpose,
|
|
581
|
-
|
|
681
|
+
senderIdentity: input.senderIdentity,
|
|
582
682
|
tags: input.tags
|
|
583
683
|
}),
|
|
584
684
|
run: input.run
|
package/dist/guest/journeys.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { n as Duration, r as GuestEvent, t as Trigger } from "./index-
|
|
2
|
-
import { n as Environment, t as ConsentPurpose } from "./constants-OZrYz4I2.js";
|
|
1
|
+
import { n as Duration, r as GuestEvent, t as Trigger } from "./index-BDj02EsQ.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;
|
|
@@ -13,12 +12,12 @@ type Profile = {
|
|
|
13
12
|
id: string;
|
|
14
13
|
traits: Record<string, unknown>;
|
|
15
14
|
/** Consent state per purpose; the send gates read it host-side too. */
|
|
16
|
-
consent: Record<
|
|
15
|
+
consent: Record<string, boolean>;
|
|
17
16
|
identifiers: Record<string, string>;
|
|
18
17
|
/** Names of the segments the profile is currently in. */
|
|
19
18
|
segments: string[];
|
|
20
19
|
};
|
|
21
|
-
/** What `api.email
|
|
20
|
+
/** What `api.send.email` resolves to once the host accepted the send. */
|
|
22
21
|
type EmailSendResult = {
|
|
23
22
|
deliveryId: string;
|
|
24
23
|
status: string;
|
|
@@ -35,7 +34,7 @@ declare class CapabilityError extends Error {
|
|
|
35
34
|
/**
|
|
36
35
|
* The project's templates, keyed by template key. `cow build` writes the
|
|
37
36
|
* real interface into `.cow/types.d.ts` and TypeScript merges it into this
|
|
38
|
-
* one, which is what types `api.email
|
|
37
|
+
* one, which is what types `api.send.email`. Empty here on purpose: a
|
|
39
38
|
* project that has not built yet still compiles, with string keys.
|
|
40
39
|
*/
|
|
41
40
|
interface CowTemplates {}
|
|
@@ -60,14 +59,22 @@ type Api = {
|
|
|
60
59
|
waitForEvent(pattern: string | string[], options: {
|
|
61
60
|
timeout: Duration;
|
|
62
61
|
}): Promise<Event | null>;
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/**
|
|
63
|
+
* The two things a journey sends. Grouped by the verb rather than by the
|
|
64
|
+
* channel, because the verb is what an author is choosing between.
|
|
65
|
+
*/
|
|
66
|
+
send: {
|
|
67
|
+
email<Key extends TemplateKey>(args: {
|
|
65
68
|
template: Key;
|
|
66
69
|
props: TemplateProps<Key>;
|
|
70
|
+
/**
|
|
71
|
+
* Send this one mail from a different sender identity than the
|
|
72
|
+
* journey's own. A name the organization has configured, resolved in
|
|
73
|
+
* the environment the execution is running in.
|
|
74
|
+
*/
|
|
75
|
+
senderIdentity?: string;
|
|
67
76
|
}): Promise<EmailSendResult>;
|
|
68
|
-
|
|
69
|
-
webhook: {
|
|
70
|
-
send(args: {
|
|
77
|
+
webhook(args: {
|
|
71
78
|
destination: string;
|
|
72
79
|
payload: Record<string, unknown>;
|
|
73
80
|
}): Promise<void>;
|
|
@@ -108,18 +115,37 @@ type Api = {
|
|
|
108
115
|
*/
|
|
109
116
|
type JourneyConfig = {
|
|
110
117
|
trigger: Trigger;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
/**
|
|
119
|
+
* A fixed purpose (`emailMarketing`, `dataProcessing`) or one this
|
|
120
|
+
* project declares in `cow.json`. The key's shape is checked here; that
|
|
121
|
+
* the org declares it is checked when the release is deployed.
|
|
122
|
+
*/
|
|
123
|
+
purpose: string;
|
|
124
|
+
/**
|
|
125
|
+
* The sender identity every `api.send.email` in this journey goes out as:
|
|
126
|
+
* the name of one your organization has configured, or `cowliss-default`
|
|
127
|
+
* for the address Cowliss gives you with nothing to set up. A single send
|
|
128
|
+
* can override it.
|
|
129
|
+
*
|
|
130
|
+
* Required, and named here rather than in a settings page, so the address
|
|
131
|
+
* a journey sends from is readable in the journey's own source. It is
|
|
132
|
+
* resolved per environment, which is why it is a name and not an id.
|
|
133
|
+
*/
|
|
134
|
+
senderIdentity: string;
|
|
114
135
|
/** The author's labels; the dashboard's only grouping. Defaults to none. */
|
|
115
136
|
tags: string[];
|
|
116
137
|
};
|
|
117
138
|
type Journey = JourneyConfig & {
|
|
118
139
|
run: (event: Event, api: Api) => Promise<void>;
|
|
119
140
|
};
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Author a journey. Throws at definition time on an invalid config.
|
|
143
|
+
*
|
|
144
|
+
* There is no environment gate here: a journey runs wherever it is enabled,
|
|
145
|
+
* and nowhere else (ADR 0011). A source that still passes `environments` is
|
|
146
|
+
* a typecheck error in `cow build`, which is where the author can fix it.
|
|
147
|
+
*/
|
|
148
|
+
declare function defineJourney(input: Omit<JourneyConfig, "tags"> & {
|
|
123
149
|
tags?: string[];
|
|
124
150
|
run: (event: Event, api: Api) => Promise<void>;
|
|
125
151
|
}): Journey;
|
package/dist/guest/journeys.js
CHANGED
package/dist/guest/wasi.js
CHANGED