@hasna/events 0.1.12 → 0.1.14

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hasna/events
2
2
 
3
- Shared event envelopes, subscription config, webhook delivery, and command transports for Hasna open-source apps.
3
+ Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps.
4
4
 
5
5
  This package is local-first. By default it stores JSON files under `~/.hasna/events`:
6
6
 
@@ -10,6 +10,45 @@ This package is local-first. By default it stores JSON files under `~/.hasna/eve
10
10
 
11
11
  Override the data directory with `HASNA_EVENTS_DIR`, `HASNA_EVENTS_HOME`, or the CLI `--dir` flag.
12
12
 
13
+ ## Storage Runtime Contract
14
+
15
+ The default runtime is local JSON files. It does not use local SQLite, remote
16
+ Postgres, S3, AWS infrastructure, or live cloud mutation:
17
+
18
+ ```ts
19
+ import { getEventsStatus } from "@hasna/events";
20
+
21
+ const status = await getEventsStatus();
22
+
23
+ console.log(status.storage);
24
+ // {
25
+ // mode: "local-files",
26
+ // localFiles: true,
27
+ // localSqlite: false,
28
+ // remote: false,
29
+ // postgres: false,
30
+ // s3: false,
31
+ // aws: false,
32
+ // idempotency: "best-effort-local",
33
+ // replayCursors: true
34
+ // }
35
+ ```
36
+
37
+ Cloud-backed stores should implement the same `EventsStore` interface and, for
38
+ durable event-bus use, the optional `appendEventOnce` and `listEventsPage`
39
+ methods. `appendEventOnce` is the storage-layer hook for atomic idempotency by
40
+ `id` or `dedupeKey`, such as a Postgres unique constraint or equivalent
41
+ provider guarantee. `listEventsPage` returns an opaque cursor page for bounded
42
+ replay. The local JSON store implements these hooks for deterministic local
43
+ tests, but its idempotency is best-effort local file behavior, not a
44
+ cross-process database lock.
45
+
46
+ Remote Postgres/S3/AWS adapters should keep credentials and infrastructure
47
+ provisioning outside this package configuration, report their storage mode in
48
+ `status.storage`, and avoid emitting live external deliveries during store
49
+ tests. Creating buckets, databases, secrets, migrations, or production data
50
+ changes is an explicit deployment/approval step, not part of this local runtime.
51
+
13
52
  ## Install
14
53
 
15
54
  ```bash
@@ -58,9 +97,94 @@ Envelope fields are:
58
97
 
59
98
  `source` should be the emitting app or bounded context. `type` should use dot notation such as `ticket.created`, `repo.synced`, or `check.failed`.
60
99
 
100
+ ## Typed Event Catalog (Distribution Events)
101
+
102
+ `@hasna/events/catalog` binds well-known envelope `type` strings to the
103
+ `@hasna/contracts` schema ids their `data` payloads mirror, and provides an
104
+ OPT-IN emit-time validator hook.
105
+
106
+ Distribution event types (`DISTRIBUTION_EVENT_TYPES`):
107
+
108
+ | Event type | Contracts schema (`data` mirror) |
109
+ | --- | --- |
110
+ | `release.published` | `hasna.release.v1` |
111
+ | `release.rollout.started` | `hasna.rollout_record.v1` |
112
+ | `release.rollout.completed` | `hasna.rollout_record.v1` |
113
+ | `release.rollout.failed` | `hasna.rollout_record.v1` |
114
+ | `app.installed` | `hasna.rollout_record.v1` |
115
+ | `announcement.sent` | `hasna.announcement.v1` |
116
+ | `feedback.created` | `hasna.feedback.v1` |
117
+ | `feedback.triaged` | `hasna.feedback.v1` |
118
+
119
+ ```ts
120
+ import { EventsClient, EventTypeCatalog, registerDistributionEventTypes } from "@hasna/events";
121
+
122
+ const catalog = registerDistributionEventTypes(new EventTypeCatalog());
123
+ const client = new EventsClient({ catalog, validateCatalogTypes: true });
124
+
125
+ // Registered type with an invalid payload throws EventValidationError
126
+ // BEFORE the event is stored or delivered.
127
+ await client.emit({
128
+ source: "open-publish",
129
+ type: "release.published",
130
+ data: { appId: "open-todos" },
131
+ });
132
+ ```
133
+
134
+ Validation is fully backward compatible:
135
+
136
+ - It is OFF by default (`validateCatalogTypes` defaults to `false`).
137
+ - Unregistered/free-form event types ALWAYS pass, even when validation is on.
138
+ - A per-emit `validate` option overrides the client setting in both directions.
139
+
140
+ Payload types (`ReleasePublishedData`, `RolloutData`, `AppInstalledData`,
141
+ `AnnouncementSentData`, `FeedbackCreatedData`, `FeedbackTriagedData`) are
142
+ dependency-free structural mirrors of the contracts schemas; this package does
143
+ not depend on `@hasna/contracts` at runtime.
144
+
145
+ ## OpenAutomations Trigger Ingress
146
+
147
+ `@hasna/events` is trigger ingress for OpenAutomations. It records and delivers
148
+ event envelopes, but it does not own durable automation runs, action queues,
149
+ approvals, DLQ state, or replay decisions. `@hasna/automations` consumes the
150
+ same envelope shape and materializes matching events into durable automation
151
+ runs.
152
+
153
+ For automation-triggered events:
154
+
155
+ - set `source` to the emitting app or bounded context
156
+ - set `type` with dot notation, such as `ticket.created`
157
+ - set `subject` when the event describes one stable domain object
158
+ - set `dedupeKey` when the producer has a stable business identity
159
+ - keep `id` stable for the specific emitted envelope
160
+ - put only serializable trigger data in `data`
161
+ - keep secrets out of `data` and `metadata`; pass secret references instead
162
+
163
+ OpenAutomations derives idempotency from `dedupeKey` first and falls back to
164
+ `id` when no dedupe key is present. Replaying events through `events events
165
+ replay` re-delivers envelopes; OpenAutomations is still responsible for deciding
166
+ whether that delivery creates a new run, returns the existing idempotent run, or
167
+ creates an explicit replay request.
168
+
169
+ ## OpenLoops Task Notifications
170
+
171
+ `@hasna/events` is also notification ingress for OpenLoops task-created routes.
172
+ It delivers `todos` envelopes to configured channels, but it does not import
173
+ OpenLoops, create workflow invocations, own admission queue state, run agents,
174
+ or decide worker retry/backpressure policy. OpenLoops is the consumer that
175
+ handles an envelope, dedupes/upserts a work item, admits it when capacity is
176
+ available, and records workflow run manifests under `.hasna/loops/runs`.
177
+
178
+ Replay remains delivery-only. Replaying a `todos.task.created` or
179
+ `task.created` envelope sends the event to matching channels again; OpenLoops
180
+ decides whether that replay is ignored as an already-admitted task, resumes
181
+ existing work, or creates an explicit replay work item.
182
+
61
183
  ## Channels And Filters
62
184
 
63
- Channels are reusable subscriptions. They can be enabled or disabled, filtered by source/type/subject/severity, and configured with transport-specific settings.
185
+ Channels are reusable notification routes. They can be enabled or disabled,
186
+ filtered by source/type/subject/severity, and configured with
187
+ transport-specific settings.
64
188
 
65
189
  ```ts
66
190
  await events.addChannel({
@@ -69,7 +193,7 @@ await events.addChannel({
69
193
  transport: "webhook",
70
194
  filters: [{ type: "ticket.*", severity: ["warning", "error", "critical"] }],
71
195
  webhook: {
72
- url: "https://example.com/webhooks/hasna",
196
+ url: "https://example.com/channels/hasna",
73
197
  secret: process.env.HASNA_WEBHOOK_SECRET,
74
198
  },
75
199
  retry: {
@@ -196,16 +320,16 @@ const events = new EventsClient({
196
320
  The package exposes `events` and `hasna-events`.
197
321
 
198
322
  ```bash
199
- events webhooks add https://example.com/webhooks/hasna \
323
+ events channels add https://example.com/channels/hasna \
200
324
  --id ops \
201
325
  --type "ticket.*" \
202
326
  --secret "$HASNA_WEBHOOK_SECRET" \
203
327
  --retry-attempts 3 \
204
328
  --retry-backoff-ms 500
205
329
 
206
- events webhooks list
207
- events webhooks test ops
208
- events webhooks remove ops
330
+ events channels list
331
+ events channels test ops
332
+ events channels remove ops
209
333
  ```
210
334
 
211
335
  Field filters can match nested `data` or `metadata` values. Plain
@@ -220,7 +344,7 @@ legacy source/type/subject filters. For field paths ending in `_path` or `.path`
220
344
  `*` matches one path segment and `**` matches recursively.
221
345
 
222
346
  ```bash
223
- events webhooks add loops \
347
+ events channels add loops \
224
348
  --id open-source-task-route \
225
349
  --transport command \
226
350
  --source todos \
@@ -237,18 +361,18 @@ events webhooks add loops \
237
361
  --arg todos-task
238
362
 
239
363
  # Command args that begin with dashes can be passed either form:
240
- events webhooks add events --id json-route --transport command --arg --json
241
- events webhooks add events --id json-route --transport command --arg=--json
364
+ events channels add events --id json-route --transport command --arg --json
365
+ events channels add events --id json-route --transport command --arg=--json
242
366
 
243
367
  # For nested CLIs, put child positional args and flags after an explicit delimiter.
244
- events webhooks add events --id nested-route --transport command -- handle todos-task --json
368
+ events channels add events --id nested-route --transport command -- handle todos-task --json
245
369
 
246
- events webhooks match open-source-task-route \
370
+ events channels match open-source-task-route \
247
371
  --source todos \
248
372
  --type task.created \
249
373
  --metadata '{"project_path":"/home/hasna/workspace/hasna/opensource/open-events","route_enabled":true}'
250
374
 
251
- events webhooks test open-source-task-route --honor-filters \
375
+ events channels test open-source-task-route --honor-filters \
252
376
  --source todos \
253
377
  --type task.created \
254
378
  --metadata '{"project_path":"/tmp/outside","route_enabled":true}'
@@ -266,24 +390,32 @@ events events emit ticket.created \
266
390
 
267
391
  events events list --limit 20
268
392
  events events replay --type ticket.created
393
+ events events replay --type ticket.created --dry-run --limit 100
394
+ events events replay --type ticket.created --cursor "$NEXT_CURSOR" --limit 100
269
395
  events events replay --dry-run
270
396
  ```
271
397
 
398
+ Replay cursors are opaque and tied to the same filter set (`--id`, `--source`,
399
+ and `--type`) used to produce them. Use the `nextCursor` returned by the
400
+ previous JSON replay response rather than constructing cursor strings in
401
+ callers. A replay without `--limit` or `--cursor` processes all matching events;
402
+ use those flags when callers need bounded page-by-page replay.
403
+
272
404
  Machine-readable status:
273
405
 
274
406
  ```bash
275
407
  events status --json
276
408
  ```
277
409
 
278
- The status contract reports event, channel, delivery, file, and transport counts
279
- only. It does not include event payloads, webhook signing secrets, command
280
- environment values, or channel targets.
410
+ The status contract reports storage runtime, event, channel, delivery, file, and
411
+ transport metadata only. It does not include event payloads, webhook signing
412
+ secrets, command environment values, or channel targets.
281
413
 
282
414
  Use `--json` for script-friendly output and `--dir <path>` for isolated data.
283
415
 
284
416
  ## App Integration Pattern
285
417
 
286
- Apps should keep event emission near durable state changes and avoid hardcoding app-specific webhooks. The common pattern is:
418
+ Apps should keep event emission near durable state changes and avoid hardcoding app-specific channels. The common pattern is:
287
419
 
288
420
  ```ts
289
421
  import { EventsClient } from "@hasna/events";
@@ -0,0 +1,136 @@
1
+ import type { EventData, EventEnvelope } from "./types.js";
2
+ export interface EventValidationIssue {
3
+ path: string;
4
+ message: string;
5
+ }
6
+ export type EventValidationResult = {
7
+ ok: true;
8
+ } | {
9
+ ok: false;
10
+ issues: EventValidationIssue[];
11
+ };
12
+ export type EventDataValidator = (data: EventData, event: EventEnvelope) => EventValidationResult;
13
+ export interface EventTypeDefinition {
14
+ /** Envelope `type` string this definition binds, e.g. `release.published`. */
15
+ type: string;
16
+ /** `@hasna/contracts` schema id the payload mirrors, e.g. `hasna.release.v1`. */
17
+ contractSchemaId?: string;
18
+ description?: string;
19
+ validate: EventDataValidator;
20
+ }
21
+ export declare class EventValidationError extends Error {
22
+ readonly eventType: string;
23
+ readonly issues: EventValidationIssue[];
24
+ constructor(eventType: string, issues: EventValidationIssue[]);
25
+ }
26
+ export declare class EventTypeCatalog {
27
+ private definitions;
28
+ register(definition: EventTypeDefinition): this;
29
+ unregister(type: string): boolean;
30
+ has(type: string): boolean;
31
+ get(type: string): EventTypeDefinition | undefined;
32
+ list(): EventTypeDefinition[];
33
+ /**
34
+ * Validate an event against its registered definition. Events whose type is
35
+ * NOT registered always pass: free-form types stay untouched.
36
+ */
37
+ validateEvent(event: EventEnvelope): EventValidationResult;
38
+ /** Like {@link validateEvent} but throws {@link EventValidationError}. */
39
+ assertEventValid(event: EventEnvelope): void;
40
+ }
41
+ /** Shared default catalog used by `EventsClient` when none is provided. */
42
+ export declare const defaultEventTypeCatalog: EventTypeCatalog;
43
+ export declare const DISTRIBUTION_EVENT_TYPES: {
44
+ readonly releasePublished: "release.published";
45
+ readonly rolloutStarted: "release.rollout.started";
46
+ readonly rolloutCompleted: "release.rollout.completed";
47
+ readonly rolloutFailed: "release.rollout.failed";
48
+ readonly appInstalled: "app.installed";
49
+ readonly announcementSent: "announcement.sent";
50
+ readonly feedbackCreated: "feedback.created";
51
+ readonly feedbackTriaged: "feedback.triaged";
52
+ };
53
+ export type DistributionEventType = (typeof DISTRIBUTION_EVENT_TYPES)[keyof typeof DISTRIBUTION_EVENT_TYPES];
54
+ /** Contracts schema id each distribution event payload mirrors. */
55
+ export declare const DISTRIBUTION_EVENT_CONTRACT_SCHEMAS: Record<DistributionEventType, string>;
56
+ export type PublishPath = "skill" | "ci" | "backfilled";
57
+ export type RolloutAction = "install" | "update" | "rollback" | "freeze-blocked";
58
+ /** Payload for `release.published`; mirrors `hasna.release.v1` key fields. */
59
+ export type ReleasePublishedData = {
60
+ appId: string;
61
+ package: string;
62
+ version: string;
63
+ gitSha?: string;
64
+ publishedAt?: string;
65
+ publishPath?: PublishPath;
66
+ changelogRef?: string;
67
+ [key: string]: unknown;
68
+ };
69
+ /** Payload for `release.rollout.*`; mirrors `hasna.rollout_record.v1` key fields. */
70
+ export type RolloutData = {
71
+ appId: string;
72
+ package: string;
73
+ version: string;
74
+ machine: string;
75
+ action?: RolloutAction;
76
+ result?: string;
77
+ error?: string;
78
+ [key: string]: unknown;
79
+ };
80
+ /** Payload for `app.installed`; mirrors `hasna.rollout_record.v1` (action install). */
81
+ export type AppInstalledData = {
82
+ appId: string;
83
+ package: string;
84
+ version: string;
85
+ machine: string;
86
+ [key: string]: unknown;
87
+ };
88
+ /** Payload for `announcement.sent`; mirrors `hasna.announcement.v1` key fields. */
89
+ export type AnnouncementSentData = {
90
+ campaignId: string;
91
+ appId?: string;
92
+ audienceId?: string;
93
+ releaseId?: string;
94
+ channels?: string[];
95
+ [key: string]: unknown;
96
+ };
97
+ /** Payload for `feedback.created`. */
98
+ export type FeedbackCreatedData = {
99
+ feedbackId: string;
100
+ appId?: string;
101
+ source?: string;
102
+ summary?: string;
103
+ severity?: string;
104
+ [key: string]: unknown;
105
+ };
106
+ /** Payload for `feedback.triaged`. */
107
+ export type FeedbackTriagedData = {
108
+ feedbackId: string;
109
+ disposition: string;
110
+ appId?: string;
111
+ triagedBy?: string;
112
+ [key: string]: unknown;
113
+ };
114
+ export type DistributionEventDataMap = {
115
+ "release.published": ReleasePublishedData;
116
+ "release.rollout.started": RolloutData;
117
+ "release.rollout.completed": RolloutData;
118
+ "release.rollout.failed": RolloutData;
119
+ "app.installed": AppInstalledData;
120
+ "announcement.sent": AnnouncementSentData;
121
+ "feedback.created": FeedbackCreatedData;
122
+ "feedback.triaged": FeedbackTriagedData;
123
+ };
124
+ export declare const validateReleasePublishedData: EventDataValidator;
125
+ export declare const validateRolloutData: EventDataValidator;
126
+ export declare const validateAppInstalledData: EventDataValidator;
127
+ export declare const validateAnnouncementSentData: EventDataValidator;
128
+ export declare const validateFeedbackCreatedData: EventDataValidator;
129
+ export declare const validateFeedbackTriagedData: EventDataValidator;
130
+ /** Fresh definitions for every distribution event type. */
131
+ export declare function createDistributionEventDefinitions(): EventTypeDefinition[];
132
+ /**
133
+ * Register the distribution event types on a catalog (the shared default
134
+ * catalog when omitted). Opt-in: nothing is registered until this is called.
135
+ */
136
+ export declare function registerDistributionEventTypes(catalog?: EventTypeCatalog): EventTypeCatalog;
@@ -0,0 +1,191 @@
1
+ // @bun
2
+ // src/catalog.ts
3
+ class EventValidationError extends Error {
4
+ eventType;
5
+ issues;
6
+ constructor(eventType, issues) {
7
+ const detail = issues.map((issue) => `${issue.path || "<root>"}: ${issue.message}`).join("; ");
8
+ super(`Event validation failed for type "${eventType}": ${detail}`);
9
+ this.name = "EventValidationError";
10
+ this.eventType = eventType;
11
+ this.issues = issues;
12
+ }
13
+ }
14
+
15
+ class EventTypeCatalog {
16
+ definitions = new Map;
17
+ register(definition) {
18
+ this.definitions.set(definition.type, definition);
19
+ return this;
20
+ }
21
+ unregister(type) {
22
+ return this.definitions.delete(type);
23
+ }
24
+ has(type) {
25
+ return this.definitions.has(type);
26
+ }
27
+ get(type) {
28
+ return this.definitions.get(type);
29
+ }
30
+ list() {
31
+ return [...this.definitions.values()];
32
+ }
33
+ validateEvent(event) {
34
+ const definition = this.definitions.get(event.type);
35
+ if (!definition)
36
+ return { ok: true };
37
+ return definition.validate(event.data, event);
38
+ }
39
+ assertEventValid(event) {
40
+ const result = this.validateEvent(event);
41
+ if (!result.ok) {
42
+ throw new EventValidationError(event.type, result.issues);
43
+ }
44
+ }
45
+ }
46
+ var defaultEventTypeCatalog = new EventTypeCatalog;
47
+ var DISTRIBUTION_EVENT_TYPES = {
48
+ releasePublished: "release.published",
49
+ rolloutStarted: "release.rollout.started",
50
+ rolloutCompleted: "release.rollout.completed",
51
+ rolloutFailed: "release.rollout.failed",
52
+ appInstalled: "app.installed",
53
+ announcementSent: "announcement.sent",
54
+ feedbackCreated: "feedback.created",
55
+ feedbackTriaged: "feedback.triaged"
56
+ };
57
+ var DISTRIBUTION_EVENT_CONTRACT_SCHEMAS = {
58
+ "release.published": "hasna.release.v1",
59
+ "release.rollout.started": "hasna.rollout_record.v1",
60
+ "release.rollout.completed": "hasna.rollout_record.v1",
61
+ "release.rollout.failed": "hasna.rollout_record.v1",
62
+ "app.installed": "hasna.rollout_record.v1",
63
+ "announcement.sent": "hasna.announcement.v1",
64
+ "feedback.created": "hasna.feedback.v1",
65
+ "feedback.triaged": "hasna.feedback.v1"
66
+ };
67
+ var PUBLISH_PATHS = ["skill", "ci", "backfilled"];
68
+ var ROLLOUT_ACTIONS = ["install", "update", "rollback", "freeze-blocked"];
69
+ function requireString(data, key, issues) {
70
+ const value = data[key];
71
+ if (typeof value !== "string" || value.trim().length === 0) {
72
+ issues.push({ path: key, message: "must be a non-empty string" });
73
+ }
74
+ }
75
+ function optionalString(data, key, issues) {
76
+ const value = data[key];
77
+ if (value !== undefined && (typeof value !== "string" || value.trim().length === 0)) {
78
+ issues.push({ path: key, message: "must be a non-empty string when present" });
79
+ }
80
+ }
81
+ function optionalEnum(data, key, allowed, issues) {
82
+ const value = data[key];
83
+ if (value !== undefined && (typeof value !== "string" || !allowed.includes(value))) {
84
+ issues.push({ path: key, message: `must be one of: ${allowed.join(", ")}` });
85
+ }
86
+ }
87
+ function optionalStringArray(data, key, issues) {
88
+ const value = data[key];
89
+ if (value === undefined)
90
+ return;
91
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
92
+ issues.push({ path: key, message: "must be an array of non-empty strings when present" });
93
+ }
94
+ }
95
+ function toResult(issues) {
96
+ return issues.length === 0 ? { ok: true } : { ok: false, issues };
97
+ }
98
+ var validateReleasePublishedData = (data) => {
99
+ const issues = [];
100
+ requireString(data, "appId", issues);
101
+ requireString(data, "package", issues);
102
+ requireString(data, "version", issues);
103
+ optionalString(data, "gitSha", issues);
104
+ optionalString(data, "publishedAt", issues);
105
+ optionalEnum(data, "publishPath", PUBLISH_PATHS, issues);
106
+ return toResult(issues);
107
+ };
108
+ var validateRolloutData = (data, event) => {
109
+ const issues = [];
110
+ requireString(data, "appId", issues);
111
+ requireString(data, "package", issues);
112
+ requireString(data, "version", issues);
113
+ requireString(data, "machine", issues);
114
+ optionalEnum(data, "action", ROLLOUT_ACTIONS, issues);
115
+ if (event.type === "release.rollout.completed" || event.type === "release.rollout.failed") {
116
+ requireString(data, "result", issues);
117
+ }
118
+ return toResult(issues);
119
+ };
120
+ var validateAppInstalledData = (data) => {
121
+ const issues = [];
122
+ requireString(data, "appId", issues);
123
+ requireString(data, "package", issues);
124
+ requireString(data, "version", issues);
125
+ requireString(data, "machine", issues);
126
+ return toResult(issues);
127
+ };
128
+ var validateAnnouncementSentData = (data) => {
129
+ const issues = [];
130
+ requireString(data, "campaignId", issues);
131
+ optionalString(data, "appId", issues);
132
+ optionalString(data, "audienceId", issues);
133
+ optionalString(data, "releaseId", issues);
134
+ optionalStringArray(data, "channels", issues);
135
+ return toResult(issues);
136
+ };
137
+ var validateFeedbackCreatedData = (data) => {
138
+ const issues = [];
139
+ requireString(data, "feedbackId", issues);
140
+ optionalString(data, "appId", issues);
141
+ optionalString(data, "source", issues);
142
+ optionalString(data, "summary", issues);
143
+ return toResult(issues);
144
+ };
145
+ var validateFeedbackTriagedData = (data) => {
146
+ const issues = [];
147
+ requireString(data, "feedbackId", issues);
148
+ requireString(data, "disposition", issues);
149
+ optionalString(data, "appId", issues);
150
+ optionalString(data, "triagedBy", issues);
151
+ return toResult(issues);
152
+ };
153
+ function createDistributionEventDefinitions() {
154
+ const bind = (type, validate, description) => ({
155
+ type,
156
+ contractSchemaId: DISTRIBUTION_EVENT_CONTRACT_SCHEMAS[type],
157
+ description,
158
+ validate
159
+ });
160
+ return [
161
+ bind("release.published", validateReleasePublishedData, "A package version was published"),
162
+ bind("release.rollout.started", validateRolloutData, "A rollout of a release to a machine started"),
163
+ bind("release.rollout.completed", validateRolloutData, "A rollout of a release to a machine completed"),
164
+ bind("release.rollout.failed", validateRolloutData, "A rollout of a release to a machine failed"),
165
+ bind("app.installed", validateAppInstalledData, "An app was installed on a machine"),
166
+ bind("announcement.sent", validateAnnouncementSentData, "An announcement campaign was sent"),
167
+ bind("feedback.created", validateFeedbackCreatedData, "User or agent feedback was captured"),
168
+ bind("feedback.triaged", validateFeedbackTriagedData, "Captured feedback was triaged")
169
+ ];
170
+ }
171
+ function registerDistributionEventTypes(catalog = defaultEventTypeCatalog) {
172
+ for (const definition of createDistributionEventDefinitions()) {
173
+ catalog.register(definition);
174
+ }
175
+ return catalog;
176
+ }
177
+ export {
178
+ validateRolloutData,
179
+ validateReleasePublishedData,
180
+ validateFeedbackTriagedData,
181
+ validateFeedbackCreatedData,
182
+ validateAppInstalledData,
183
+ validateAnnouncementSentData,
184
+ registerDistributionEventTypes,
185
+ defaultEventTypeCatalog,
186
+ createDistributionEventDefinitions,
187
+ EventValidationError,
188
+ EventTypeCatalog,
189
+ DISTRIBUTION_EVENT_TYPES,
190
+ DISTRIBUTION_EVENT_CONTRACT_SCHEMAS
191
+ };