@hasna/events 0.1.13 → 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 +95 -3
- package/dist/catalog.d.ts +136 -0
- package/dist/catalog.js +191 -0
- package/dist/cli/index.js +266 -28
- package/dist/commander.d.ts +14 -0
- package/dist/commander.js +434 -47
- package/dist/index.d.ts +21 -6
- package/dist/index.js +406 -26
- package/dist/storage.d.ts +16 -3
- package/dist/storage.js +140 -4
- package/dist/types.d.ts +55 -2
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -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,6 +97,51 @@ 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
|
+
|
|
61
145
|
## OpenAutomations Trigger Ingress
|
|
62
146
|
|
|
63
147
|
`@hasna/events` is trigger ingress for OpenAutomations. It records and delivers
|
|
@@ -306,18 +390,26 @@ events events emit ticket.created \
|
|
|
306
390
|
|
|
307
391
|
events events list --limit 20
|
|
308
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
|
|
309
395
|
events events replay --dry-run
|
|
310
396
|
```
|
|
311
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
|
+
|
|
312
404
|
Machine-readable status:
|
|
313
405
|
|
|
314
406
|
```bash
|
|
315
407
|
events status --json
|
|
316
408
|
```
|
|
317
409
|
|
|
318
|
-
The status contract reports event, channel, delivery, file, and
|
|
319
|
-
only. It does not include event payloads, webhook signing
|
|
320
|
-
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.
|
|
321
413
|
|
|
322
414
|
Use `--json` for script-friendly output and `--dir <path>` for isolated data.
|
|
323
415
|
|
|
@@ -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;
|
package/dist/catalog.js
ADDED
|
@@ -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
|
+
};
|