@cowliss/cli 0.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 (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/guest/constants-OZrYz4I2.d.ts +42 -0
  4. package/dist/guest/driver-BOe7jBFw.js +5647 -0
  5. package/dist/guest/driver.d.ts +21 -0
  6. package/dist/guest/driver.js +3 -0
  7. package/dist/guest/emails.d.ts +24 -0
  8. package/dist/guest/emails.js +0 -0
  9. package/dist/guest/index-heC1gFE_.d.ts +296 -0
  10. package/dist/guest/journeys-CxuS-mHn.js +571 -0
  11. package/dist/guest/journeys.d.ts +127 -0
  12. package/dist/guest/journeys.js +3 -0
  13. package/dist/guest/prelude.d.ts +1 -0
  14. package/dist/guest/prelude.js +91 -0
  15. package/dist/guest/wasi.d.ts +6 -0
  16. package/dist/guest/wasi.js +43 -0
  17. package/dist/index.js +11235 -0
  18. package/examples/abandoned-checkout/emails/abandoned-checkout.tsx +104 -0
  19. package/examples/abandoned-checkout/journeys/abandoned-checkout.ts +39 -0
  20. package/examples/abandoned-checkout/scenarios/abandoned-checkout.recovered.json +10 -0
  21. package/examples/abandoned-checkout/scenarios/abandoned-checkout.timeout.json +16 -0
  22. package/examples/activity-decay/journeys/activity-decay.ts +36 -0
  23. package/examples/activity-decay/scenarios/activity-decay.json +8 -0
  24. package/examples/cross-app-pitch/emails/cross-app-pitch.tsx +77 -0
  25. package/examples/cross-app-pitch/journeys/cross-app-pitch.ts +30 -0
  26. package/examples/cross-app-pitch/scenarios/cross-app-pitch.json +16 -0
  27. package/examples/winback/emails/winback.tsx +78 -0
  28. package/examples/winback/journeys/winback.ts +33 -0
  29. package/examples/winback/scenarios/winback.json +18 -0
  30. package/package.json +82 -0
  31. package/project/tsconfig.json +18 -0
@@ -0,0 +1,571 @@
1
+ import { z } from "zod";
2
+
3
+ //#region ../../packages/shared/src/constants.ts
4
+ /** The public docs site, for the mail and the pages that point people at it. */
5
+ const DOCS_URL = "https://docs.cowliss.com";
6
+ /**
7
+ * Clerk ID prefix marking an organization-scoped subject. Ingestion API keys
8
+ * must resolve to an org subject; user-scoped keys are rejected.
9
+ */
10
+ const CLERK_ORG_SUBJECT_PREFIX = "org_";
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
+ * Fixed consent purposes for the prototype. Consent is a per-purpose map on
23
+ * the profile, checked at send-step execution time.
24
+ *
25
+ * The two names describe what the recipient agreed to, not the pipe it
26
+ * arrives on: "emails I did not ask for individually" and "my data leaving
27
+ * for somewhere else". Naming them after the channel (`email`, `webhook`)
28
+ * said nothing a recipient could consent to, and transactional mail already
29
+ * bypasses the email purpose, so it was only ever marketing consent.
30
+ */
31
+ const CONSENT_PURPOSES = ["emailMarketing", "dataProcessing"];
32
+ /**
33
+ * The two classes of send, declared on the template rather than passed per
34
+ * call so the class cannot drift between two sends of the same message.
35
+ *
36
+ * Marketing is everything a journey sends on the org's behalf: it carries
37
+ * the RFC 8058 unsubscribe headers and runs the full gate list.
38
+ * Transactional is the developer's own operational mail (a receipt, an
39
+ * export-is-ready notice): it carries neither header and skips consent and
40
+ * the per-user frequency cap, because an unsubscribe link on an invoice is
41
+ * wrong and a receipt must not silently vanish for anyone who once left a
42
+ * newsletter. Suppression, the quota, the sending pause, and the from-domain
43
+ * check still apply to both: those bound cost and protect the shared SES
44
+ * account, and none of them are about what the recipient asked for.
45
+ */
46
+ const SEND_CLASSES = ["marketing", "transactional"];
47
+ /** Per execution: capability calls, journal size, and captured `api.log` lines. */
48
+ const EXECUTION_LIMITS = {
49
+ calls: 1e3,
50
+ journalBytes: 1048576,
51
+ logLines: 100,
52
+ logLineBytes: 1024
53
+ };
54
+ /** Per release: manifest counts, bundle size, and the source tarball. */
55
+ const RELEASE_LIMITS = {
56
+ journeys: 100,
57
+ templates: 200,
58
+ bundleBytes: 2097152,
59
+ sourceBytes: 5242880
60
+ };
61
+ /**
62
+ * Where the docs site serves the `cow.json` JSON Schema (the `$schema` a
63
+ * project file points at). Generated from `cowConfigSchema` by the docs
64
+ * generator; the path is stable because project files link to it.
65
+ */
66
+ const COW_CONFIG_SCHEMA_PATH = "/schemas/cow.json";
67
+ const COW_CONFIG_SCHEMA_URL = `${DOCS_URL}${COW_CONFIG_SCHEMA_PATH}`;
68
+
69
+ //#endregion
70
+ //#region ../../packages/shared/src/journeys-v2/config.ts
71
+ /**
72
+ * `cow.json` (spec: Project layout): the org id and nothing more. The
73
+ * folder name is the project's local name, the environment is always a
74
+ * flag, and auth never lives in the project. Strict, so a typo'd key is a
75
+ * build error rather than a silently ignored setting.
76
+ */
77
+ const cowConfigSchema = z.strictObject({
78
+ $schema: z.url().optional(),
79
+ orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
80
+ /** Overrides the API the CLI talks to; the hosted product needs none. */
81
+ apiUrl: z.url().optional()
82
+ }).meta({
83
+ title: "cow.json",
84
+ description: "A cow project: the organization it deploys to."
85
+ });
86
+
87
+ //#endregion
88
+ //#region ../../packages/shared/src/environments.ts
89
+ /**
90
+ * The environment on the wire: the source's `environment` field, the
91
+ * `X-Cow-Environment` header admin calls select with, and the
92
+ * `environment` column every per-environment DTO carries.
93
+ */
94
+ const environmentSchema = z.enum(ENVIRONMENTS);
95
+ /**
96
+ * The environments a shared definition (a segment, a journey) declares it
97
+ * works on: a non-empty list of distinct values. A definition declaring none
98
+ * would be dead code with a row behind it, and a repeated value is a typo
99
+ * rather than an intent, which is the rule `defineJourney` applies too.
100
+ */
101
+ 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" });
102
+
103
+ //#endregion
104
+ //#region ../../packages/shared/src/journeys-v2/manifest.ts
105
+ /**
106
+ * The release manifest (spec: Build; Push and compile): what `cow build`
107
+ * extracts from a project and `cow push` uploads with the bundles. The
108
+ * server validates it with these schemas, compiles every bundle, and stores
109
+ * the compiled form on the release row.
110
+ */
111
+ /**
112
+ * A journey or template key: the file basename under `journeys/` or
113
+ * `emails/`, kebab-case and unique across the project. Becomes part of the
114
+ * Temporal workflow id and travels in the journey chain, so it stays short.
115
+ */
116
+ const JOURNEY_KEY_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
117
+ const journeyKeySchema = z.string().max(64).regex(JOURNEY_KEY_PATTERN, "key must be kebab-case (a-z, 0-9, hyphens)");
118
+ /**
119
+ * An author's labels on a journey or a template: how the dashboard groups
120
+ * and filters them, and the only grouping there is. Case is kept as
121
+ * written, each entry is trimmed and non-empty, the list is deduplicated,
122
+ * and both ceilings are low on purpose: tags are a handful of words, not a
123
+ * taxonomy. Absent means `[]`.
124
+ *
125
+ * The count is capped on the list as written, before the deduplication, so
126
+ * a 21st entry is an error even when it is a repeat: that keeps `maxItems`
127
+ * in the generated JSON Schema, and an author who wrote 21 tags wants to
128
+ * hear about it.
129
+ */
130
+ 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)]);
131
+ /**
132
+ * A matcher field: one pattern or a non-empty list of them, a list being a
133
+ * disjunction. See `matchesPattern` in ../patterns for the dialect (`*`
134
+ * only) and for why a pattern whose literal prefix is not `system.` never
135
+ * reaches a system event.
136
+ */
137
+ const onePatternSchema = z.string().min(1).max(200, "a pattern must be at most 200 characters");
138
+ const patternSchema = z.union([onePatternSchema, z.array(onePatternSchema).min(1).max(20, "a matcher takes at most 20 patterns")]);
139
+ /**
140
+ * What starts a journey: an event (optionally narrowed to an app id or a
141
+ * list of them) or a segment entry. The registry DTO in `../journeys`
142
+ * reuses it.
143
+ *
144
+ * Both members are strict, so a journey holding the retired `source` key
145
+ * fails to compile a release instead of silently triggering on every app.
146
+ * There is deliberately no pipe filter: a trigger narrows by the app the
147
+ * write is attributed to, the same token a segment definition names.
148
+ */
149
+ const triggerSchema = z.union([z.strictObject({
150
+ event: patternSchema,
151
+ appId: patternSchema.optional()
152
+ }), z.strictObject({ segment: z.string().min(1) })]);
153
+ /** A content address: `sha256:` plus the lowercase hex digest. */
154
+ const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
155
+ /** The names of the capability calls a journey may make. */
156
+ const COMMAND_NAMES = [
157
+ "sleep",
158
+ "waitForEvent",
159
+ "email.send",
160
+ "webhook.send",
161
+ "traits.set",
162
+ "traits.unset",
163
+ "profile.get",
164
+ "profiles.get",
165
+ "events.track",
166
+ "restart"
167
+ ];
168
+ /**
169
+ * One entry of the step spine `cow build` records by running `run` once
170
+ * against a recording stub. Display only, never trusted: a journey's real
171
+ * control flow is whatever its code does at runtime. `detail` names the
172
+ * template, destination, event, or trait key when the call has one.
173
+ */
174
+ const spineEntrySchema = z.object({
175
+ name: z.enum(COMMAND_NAMES),
176
+ detail: z.string().max(200).optional()
177
+ });
178
+ const manifestJourneySchema = z.object({
179
+ key: journeyKeySchema,
180
+ /** The author's labels; the dashboard's only grouping. */
181
+ tags: tagsSchema,
182
+ trigger: triggerSchema,
183
+ purpose: z.enum(CONSENT_PURPOSES),
184
+ /** The author's rollout gate: the journey is active only in these. */
185
+ environments: environmentsSchema,
186
+ spine: z.array(spineEntrySchema),
187
+ bundle: digestSchema
188
+ });
189
+ const manifestTemplateSchema = z.object({
190
+ key: journeyKeySchema,
191
+ /** The author's labels; the dashboard's only grouping. */
192
+ tags: tagsSchema,
193
+ sendClass: z.enum(SEND_CLASSES),
194
+ /** True asks the host to mint a signed `verifyUrl` prop at send time. */
195
+ verifyLink: z.boolean(),
196
+ /** JSON Schema of the template's `props`, converted by `cow build`. */
197
+ propsSchema: z.record(z.string(), z.unknown()),
198
+ bundle: digestSchema
199
+ });
200
+ function uniqueKeys(items, ctx, path) {
201
+ const seen = /* @__PURE__ */ new Set();
202
+ for (const [index, item] of items.entries()) {
203
+ if (seen.has(item.key)) ctx.addIssue({
204
+ code: "custom",
205
+ message: `duplicate ${path} key "${item.key}"`,
206
+ path: [
207
+ path,
208
+ index,
209
+ "key"
210
+ ]
211
+ });
212
+ seen.add(item.key);
213
+ }
214
+ }
215
+ /** What `cow build` writes to `.cow/build/manifest.json` and `cow push` sends. */
216
+ const manifestSchema = z.object({
217
+ protocol: z.literal(1),
218
+ /** The `@cowliss/cli` version the project was built with. */
219
+ sdk: z.string().min(1),
220
+ journeys: z.array(manifestJourneySchema).max(RELEASE_LIMITS.journeys),
221
+ templates: z.array(manifestTemplateSchema).max(RELEASE_LIMITS.templates),
222
+ /** Digest of the gzipped source tarball. */
223
+ source: digestSchema
224
+ }).superRefine((manifest, ctx) => {
225
+ uniqueKeys(manifest.journeys, ctx, "journeys");
226
+ uniqueKeys(manifest.templates, ctx, "templates");
227
+ });
228
+ /**
229
+ * The manifest as the release row stores it once compilation succeeded:
230
+ * the pushed manifest plus the compiled module digest per key, and the
231
+ * digest of the Javy engine plugin the toolchain that compiled them was
232
+ * built from.
233
+ *
234
+ * Journeys and templates are keyed separately because they share a key
235
+ * space: `welcome.ts` and `welcome.tsx` are one journey and the email it
236
+ * sends in every example, and a flat map would let one overwrite the other.
237
+ *
238
+ * `plugin` is a toolchain record, not a linked artifact: modules are
239
+ * statically linked, so the plugin bytes are inside each module. It says
240
+ * which engine compiled the release, which is what a later bug report or a
241
+ * reproducible rebuild needs.
242
+ */
243
+ const compiledManifestSchema = manifestSchema.safeExtend({
244
+ modules: z.object({
245
+ journeys: z.record(journeyKeySchema, digestSchema),
246
+ templates: z.record(journeyKeySchema, digestSchema)
247
+ }),
248
+ plugin: digestSchema
249
+ });
250
+
251
+ //#endregion
252
+ //#region ../../packages/shared/src/journeys-v2/guest.ts
253
+ /**
254
+ * The guest protocol (spec: Guest protocol): the JSON a compiled module
255
+ * reads on stdin and writes on stdout. The sandbox worker parses every byte
256
+ * a guest returns with these schemas before anything acts on it; the guest
257
+ * SDK and the Node simulator produce and consume the same shapes.
258
+ */
259
+ /** A duration as journey code writes it: an ms-style string ("2d") or milliseconds. */
260
+ const durationSchema = z.union([z.string().min(1), z.number().int().nonnegative()]);
261
+ /** The event a journey runs for, or waits on: name, properties, and when. */
262
+ const guestEventSchema = z.object({
263
+ name: z.string().min(1),
264
+ properties: z.record(z.string(), z.unknown()),
265
+ /** Milliseconds since the epoch; the guest's clock starts here. */
266
+ timestamp: z.number().int().nonnegative()
267
+ });
268
+ const properties = z.record(z.string(), z.unknown());
269
+ /**
270
+ * One capability call as the guest suspends on it: the runner dispatches by
271
+ * `name` and validates `args` with this schema before any activity sees
272
+ * them. A name outside the union is rejected, never dispatched.
273
+ *
274
+ * Every `args` is strict, and that is the tenancy boundary made mechanical:
275
+ * a module that returns an `orgId`, an `environment`, or any other field
276
+ * beside the ones a capability takes fails the parse instead of having it
277
+ * quietly dropped. Tenant context comes from the workflow input, never from
278
+ * guest output, and this is where saying so becomes checkable.
279
+ */
280
+ const commandSchema = z.discriminatedUnion("name", [
281
+ z.object({
282
+ name: z.literal("sleep"),
283
+ args: z.strictObject({ duration: durationSchema })
284
+ }),
285
+ z.object({
286
+ name: z.literal("waitForEvent"),
287
+ args: z.strictObject({
288
+ event: patternSchema,
289
+ timeout: durationSchema
290
+ })
291
+ }),
292
+ z.object({
293
+ name: z.literal("email.send"),
294
+ args: z.strictObject({
295
+ template: journeyKeySchema,
296
+ props: properties
297
+ })
298
+ }),
299
+ z.object({
300
+ name: z.literal("webhook.send"),
301
+ args: z.strictObject({
302
+ destination: z.string().min(1),
303
+ payload: properties
304
+ })
305
+ }),
306
+ z.object({
307
+ name: z.literal("traits.set"),
308
+ args: z.strictObject({
309
+ key: z.string().min(1),
310
+ value: z.unknown()
311
+ })
312
+ }),
313
+ z.object({
314
+ name: z.literal("traits.unset"),
315
+ args: z.strictObject({ key: z.string().min(1) })
316
+ }),
317
+ z.object({
318
+ name: z.literal("profile.get"),
319
+ args: z.strictObject({})
320
+ }),
321
+ z.object({
322
+ name: z.literal("profiles.get"),
323
+ args: z.strictObject({ id: z.string().min(1) })
324
+ }),
325
+ z.object({
326
+ name: z.literal("events.track"),
327
+ args: z.strictObject({
328
+ event: z.string().min(1),
329
+ properties
330
+ })
331
+ }),
332
+ z.object({
333
+ name: z.literal("restart"),
334
+ args: z.strictObject({ event: guestEventSchema.optional() })
335
+ })
336
+ ]);
337
+ /** A journaled call's outcome: the value the capability returned, or why it threw. */
338
+ const journalResultSchema = z.discriminatedUnion("ok", [z.object({
339
+ ok: z.literal(true),
340
+ value: z.unknown()
341
+ }), z.object({
342
+ ok: z.literal(false),
343
+ error: z.object({
344
+ code: z.string().min(1),
345
+ message: z.string()
346
+ })
347
+ })]);
348
+ /**
349
+ * One line of the journal the runner carries per execution. `at` is the
350
+ * workflow time the result was recorded, in epoch milliseconds: it is what
351
+ * `Date.now()` returns to journey code between this call and the next.
352
+ */
353
+ const journalEntrySchema = z.object({
354
+ call: commandSchema,
355
+ result: journalResultSchema,
356
+ at: z.number().int().nonnegative()
357
+ });
358
+ const LOG_LEVELS = [
359
+ "debug",
360
+ "info",
361
+ "warn",
362
+ "error"
363
+ ];
364
+ /** One `api.log` line, emitted during the step's new segment only. */
365
+ const logLineSchema = z.object({
366
+ level: z.enum(LOG_LEVELS),
367
+ message: z.string().max(EXECUTION_LIMITS.logLineBytes)
368
+ });
369
+ /** The per-execution caps the guest is told about, so it can fail early. */
370
+ const executionLimitsSchema = z.object({
371
+ calls: z.number().int().positive(),
372
+ journalBytes: z.number().int().positive(),
373
+ logLines: z.number().int().positive(),
374
+ logLineBytes: z.number().int().positive()
375
+ });
376
+ const journeyStepInputSchema = z.object({
377
+ protocol: z.literal(1),
378
+ kind: z.literal("journey"),
379
+ key: journeyKeySchema,
380
+ event: guestEventSchema,
381
+ /** Workflow time at the start of this step, epoch milliseconds. */
382
+ now: z.number().int().nonnegative(),
383
+ /** Seeds `Math.random()`; derived from the execution id. */
384
+ seed: z.string().min(1),
385
+ journal: z.array(journalEntrySchema).max(EXECUTION_LIMITS.calls),
386
+ limits: executionLimitsSchema
387
+ });
388
+ const logs = z.array(logLineSchema).max(EXECUTION_LIMITS.logLines).default([]);
389
+ /** The error codes a guest itself reports; traps are mapped host-side. */
390
+ const GUEST_ERROR_CODES = ["journey_error", "journey_nondeterministic"];
391
+ const journeyStepOutputSchema = z.discriminatedUnion("status", [
392
+ z.object({
393
+ status: z.literal("suspend"),
394
+ call: commandSchema,
395
+ logs
396
+ }),
397
+ z.object({
398
+ status: z.literal("done"),
399
+ logs
400
+ }),
401
+ z.object({
402
+ status: z.literal("error"),
403
+ error: z.object({
404
+ code: z.enum(GUEST_ERROR_CODES),
405
+ message: z.string(),
406
+ stack: z.string().optional()
407
+ }),
408
+ /** Set when a replayed call diverged from the journal: both are named. */
409
+ nondeterministic: z.object({
410
+ expected: commandSchema,
411
+ actual: commandSchema
412
+ }).optional(),
413
+ logs
414
+ })
415
+ ]);
416
+ const templateRenderInputSchema = z.object({
417
+ protocol: z.literal(1),
418
+ kind: z.literal("template"),
419
+ key: journeyKeySchema,
420
+ props: properties
421
+ });
422
+ const templateRenderOutputSchema = z.object({
423
+ subject: z.string(),
424
+ html: z.string(),
425
+ text: z.string()
426
+ });
427
+ /** Asks a module for its own config; the compile step checks it against the push. */
428
+ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
429
+ /**
430
+ * A module's self-report. The host knows which key it ran, so the report
431
+ * carries the config only.
432
+ */
433
+ const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
434
+ trigger: true,
435
+ purpose: true,
436
+ environments: true,
437
+ tags: true
438
+ }).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
439
+ sendClass: true,
440
+ verifyLink: true,
441
+ propsSchema: true,
442
+ tags: true
443
+ }).extend({ kind: z.literal("template") })]);
444
+ /** Everything a driver may be handed on stdin. */
445
+ const guestInputSchema = z.discriminatedUnion("kind", [
446
+ journeyStepInputSchema,
447
+ templateRenderInputSchema,
448
+ manifestInputSchema
449
+ ]);
450
+ /**
451
+ * Execution lifecycle as the runner records it. `waiting` is a suspended
452
+ * step (a timer or a signal wait); `cancelled` is the runner honouring a
453
+ * release cancellation at its next step.
454
+ */
455
+ const EXECUTION_STATUSES = [
456
+ "running",
457
+ "waiting",
458
+ "completed",
459
+ "failed",
460
+ "cancelled"
461
+ ];
462
+ const executionStatusSchema = z.enum(EXECUTION_STATUSES);
463
+
464
+ //#endregion
465
+ //#region ../../packages/shared/src/journeys-v2/sandbox.ts
466
+ /**
467
+ * What the sandbox worker's activities return (spec: Sandbox worker). A
468
+ * guest failure is a value, not a throw: the runner has to journal it and
469
+ * record it on the execution, and a Temporal retry would only reproduce it.
470
+ * Infrastructure failures (a missing binary, a spawn error, Postgres down)
471
+ * throw and retry on the activity's bounded policy.
472
+ */
473
+ /** Every way a guest invocation can fail, as the runner records it. */
474
+ const SANDBOX_FAILURE_CODES = [
475
+ "journey_fuel_exceeded",
476
+ "journey_timeout",
477
+ "journey_memory_exceeded",
478
+ "journey_error",
479
+ "journey_output_invalid"
480
+ ];
481
+ const sandboxFailureCodeSchema = z.enum(SANDBOX_FAILURE_CODES);
482
+
483
+ //#endregion
484
+ //#region ../../packages/shared/src/journeys-v2/scenario.ts
485
+ /**
486
+ * Scenario files for `cow test`: a journey run scripted as JSON and replayed
487
+ * on a virtual clock, so a 2-day journey is asserted in seconds. The shape is deliberately small (a user, some later
488
+ * events, the side effects the run must produce) because a scenario is meant
489
+ * to be written by hand or by an agent, not generated.
490
+ *
491
+ * The schema lives here rather than in packages/journeys so the CLI can reject
492
+ * a bad file before it boots a Temporal environment, and so the docs site can
493
+ * render the format from one source.
494
+ */
495
+ const journeyScenarioSchema = z.object({
496
+ /** The user the instance runs for; traits are what getProfile returns. */
497
+ user: z.object({
498
+ id: z.string().min(1),
499
+ traits: z.record(z.string(), z.unknown()).default({})
500
+ }).default({
501
+ id: "user_1",
502
+ traits: {}
503
+ }),
504
+ /**
505
+ * Later events, each at an offset from instance start: ms-style string
506
+ * ("1h", "2d") or milliseconds. The trigger event itself is delivered
507
+ * automatically at start, exactly as production does.
508
+ */
509
+ events: z.array(z.object({
510
+ at: z.union([z.string().min(1), z.number().int().nonnegative()]),
511
+ event: z.string().min(1)
512
+ })).default([]),
513
+ /**
514
+ * Every side-effect activity call the run must make, in order. `input` is
515
+ * matched as a subset of the actual activity input, so a scenario asserts
516
+ * the fields it cares about and ignores the rest.
517
+ */
518
+ expect: z.array(z.object({
519
+ activity: z.enum([
520
+ "sendEmail",
521
+ "sendWebhook",
522
+ "setTrait",
523
+ "unsetTrait",
524
+ "trackEvent",
525
+ "restart"
526
+ ]),
527
+ input: z.record(z.string(), z.unknown()).optional()
528
+ }))
529
+ });
530
+
531
+ //#endregion
532
+ //#region src/guest/journeys.ts
533
+ /**
534
+ * A capability that failed host-side (an unknown destination, invalid
535
+ * props, a profile that is not there). The failure is journaled, so a
536
+ * journey that catches it takes the same branch on every replay.
537
+ */
538
+ var CapabilityError = class extends Error {
539
+ code;
540
+ constructor(code, message) {
541
+ super(message);
542
+ this.name = "CapabilityError";
543
+ this.code = code;
544
+ }
545
+ };
546
+ /**
547
+ * The config half of a journey, validated with the same schema the release
548
+ * manifest is validated with, so a bad trigger or an unknown purpose fails
549
+ * at build time rather than at the first execution.
550
+ */
551
+ const journeyConfigSchema = manifestJourneySchema.pick({
552
+ trigger: true,
553
+ purpose: true,
554
+ environments: true,
555
+ tags: true
556
+ });
557
+ /** Author a journey. Throws at definition time on an invalid config. */
558
+ function defineJourney(input) {
559
+ return {
560
+ ...journeyConfigSchema.parse({
561
+ trigger: input.trigger,
562
+ purpose: input.purpose,
563
+ environments: input.environments ?? [...ENVIRONMENTS],
564
+ tags: input.tags
565
+ }),
566
+ run: input.run
567
+ };
568
+ }
569
+
570
+ //#endregion
571
+ export { manifestOutputSchema as a, journeyStepOutputSchema as i, defineJourney as n, templateRenderOutputSchema as o, guestInputSchema as r, CapabilityError as t };
@@ -0,0 +1,127 @@
1
+ import { n as Duration, r as GuestEvent, t as Trigger } from "./index-heC1gFE_.js";
2
+ import { n as Environment, t as ConsentPurpose } from "./constants-OZrYz4I2.js";
3
+ //#region src/guest/journeys.d.ts
4
+ /** The event a journey runs for, or the one a `waitForEvent` resolved with. */
5
+ type Event = GuestEvent;
6
+ /**
7
+ * 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 and
9
+ * environment (`api.profiles.get(id)`). A fresh read on every call.
10
+ */
11
+ type Profile = {
12
+ /** The Cowliss-generated profile id (`usr_`). */
13
+ id: string;
14
+ traits: Record<string, unknown>;
15
+ /** Consent state per purpose; the send gates read it host-side too. */
16
+ consent: Record<ConsentPurpose, boolean>;
17
+ identifiers: Record<string, string>;
18
+ /** Names of the segments the profile is currently in. */
19
+ segments: string[];
20
+ };
21
+ /** What `api.email.send` resolves to once the host accepted the send. */
22
+ type EmailSendResult = {
23
+ deliveryId: string;
24
+ status: string;
25
+ };
26
+ /**
27
+ * A capability that failed host-side (an unknown destination, invalid
28
+ * props, a profile that is not there). The failure is journaled, so a
29
+ * journey that catches it takes the same branch on every replay.
30
+ */
31
+ declare class CapabilityError extends Error {
32
+ readonly code: string;
33
+ constructor(code: string, message: string);
34
+ }
35
+ /**
36
+ * The project's templates, keyed by template key. `cow build` writes the
37
+ * real interface into `.cow/types.d.ts` and TypeScript merges it into this
38
+ * one, which is what types `api.email.send`. Empty here on purpose: a
39
+ * project that has not built yet still compiles, with string keys.
40
+ */
41
+ interface CowTemplates {}
42
+ type TemplateKey = keyof CowTemplates extends never ? string : keyof CowTemplates;
43
+ type TemplateProps<Key> = Key extends keyof CowTemplates ? CowTemplates[Key] : Record<string, unknown>;
44
+ /**
45
+ * The capability object a journey's `run` receives. Every method is async
46
+ * and journaled (except `log`), which is what makes a replay reproduce the
47
+ * run exactly: the SDK compares each call against the journal and returns
48
+ * the recorded result instead of doing the work again.
49
+ */
50
+ type Api = {
51
+ /** Durable wait; the runner turns it into a Temporal timer. */
52
+ sleep(duration: Duration): Promise<void>;
53
+ /**
54
+ * The next event matching this pattern, or `null` once the timeout
55
+ * elapses. `*` matches any run of characters, a list of patterns is a
56
+ * disjunction, and the resolved event carries its own `name`, so a wait on
57
+ * a list can branch on which one arrived. A pattern whose literal prefix
58
+ * is not `system.` never resolves with an event Cowliss wrote itself.
59
+ */
60
+ waitForEvent(pattern: string | string[], options: {
61
+ timeout: Duration;
62
+ }): Promise<Event | null>;
63
+ email: {
64
+ send<Key extends TemplateKey>(args: {
65
+ template: Key;
66
+ props: TemplateProps<Key>;
67
+ }): Promise<EmailSendResult>;
68
+ };
69
+ webhook: {
70
+ send(args: {
71
+ destination: string;
72
+ payload: Record<string, unknown>;
73
+ }): Promise<void>;
74
+ };
75
+ traits: {
76
+ /** A `null` value deletes the key (RFC 7386 merge, as identify does). */
77
+ set(key: string, value: unknown): Promise<void>;
78
+ unset(key: string): Promise<void>;
79
+ };
80
+ profile: {
81
+ get(): Promise<Profile>;
82
+ };
83
+ profiles: {
84
+ get(id: string): Promise<Profile>;
85
+ };
86
+ /**
87
+ * Writes an event on the recipient's timeline; may trigger other journeys.
88
+ * The name may not start with `system.`, which Cowliss reserves for its
89
+ * own events: that call throws a CapabilityError instead of writing.
90
+ */
91
+ events: {
92
+ track(name: string, properties: Record<string, unknown>): Promise<void>;
93
+ };
94
+ /** Not journaled; captured per execution and shown on the execution. */
95
+ log(...args: unknown[]): void;
96
+ /**
97
+ * Ends this execution and starts a fresh one under the same workflow id
98
+ * with an empty journal. Never resolves: the execution is over.
99
+ */
100
+ restart(args?: {
101
+ event?: Event;
102
+ }): Promise<never>;
103
+ };
104
+ /**
105
+ * A journey as `defineJourney` returns it: the validated config plus `run`.
106
+ * The manifest path reports the config back to the host, which compares it
107
+ * with what `cow push` uploaded.
108
+ */
109
+ type JourneyConfig = {
110
+ trigger: Trigger;
111
+ purpose: ConsentPurpose;
112
+ /** The author's rollout gate; defaults to every environment. */
113
+ environments: Environment[];
114
+ /** The author's labels; the dashboard's only grouping. Defaults to none. */
115
+ tags: string[];
116
+ };
117
+ type Journey = JourneyConfig & {
118
+ run: (event: Event, api: Api) => Promise<void>;
119
+ };
120
+ /** Author a journey. Throws at definition time on an invalid config. */
121
+ declare function defineJourney(input: Omit<JourneyConfig, "environments" | "tags"> & {
122
+ environments?: Environment[];
123
+ tags?: string[];
124
+ run: (event: Event, api: Api) => Promise<void>;
125
+ }): Journey;
126
+ //#endregion
127
+ export { Api, CapabilityError, CowTemplates, type Duration, EmailSendResult, Event, Journey, JourneyConfig, Profile, type Trigger, defineJourney };
@@ -0,0 +1,3 @@
1
+ import { n as defineJourney, t as CapabilityError } from "./journeys-CxuS-mHn.js";
2
+
3
+ export { CapabilityError, defineJourney };