@hasna/events 0.1.7 → 0.1.8
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 +10 -0
- package/dist/cli/index.js +62 -0
- package/dist/commander.js +53 -0
- package/dist/index.js +55 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +55 -0
- package/dist/types.d.ts +41 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,6 +219,16 @@ events events replay --type ticket.created
|
|
|
219
219
|
events events replay --dry-run
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
Machine-readable status:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
events status --json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The status contract reports event, channel, delivery, file, and transport counts
|
|
229
|
+
only. It does not include event payloads, webhook signing secrets, command
|
|
230
|
+
environment values, or channel targets.
|
|
231
|
+
|
|
222
232
|
Use `--json` for script-friendly output and `--dir <path>` for isolated data.
|
|
223
233
|
|
|
224
234
|
## App Integration Pattern
|
package/dist/cli/index.js
CHANGED
|
@@ -62,6 +62,13 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
62
62
|
function getEventsDataDir(override) {
|
|
63
63
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
64
64
|
}
|
|
65
|
+
function getActiveEventsDirEnv() {
|
|
66
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
67
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
68
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
69
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
65
72
|
|
|
66
73
|
class JsonEventsStore {
|
|
67
74
|
dataDir;
|
|
@@ -174,6 +181,52 @@ class JsonEventsStore {
|
|
|
174
181
|
});
|
|
175
182
|
}
|
|
176
183
|
}
|
|
184
|
+
async function getEventsStatus(dataDir) {
|
|
185
|
+
const store = new JsonEventsStore(dataDir);
|
|
186
|
+
await store.init();
|
|
187
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
188
|
+
store.listChannels(),
|
|
189
|
+
store.listEvents(),
|
|
190
|
+
store.listDeliveries()
|
|
191
|
+
]);
|
|
192
|
+
const transports = channels.reduce((counts, channel) => {
|
|
193
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
194
|
+
return counts;
|
|
195
|
+
}, {});
|
|
196
|
+
return {
|
|
197
|
+
service: "events",
|
|
198
|
+
schemaVersion: "1.0",
|
|
199
|
+
dataDir: store.dataDir,
|
|
200
|
+
env: {
|
|
201
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
202
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
203
|
+
active: getActiveEventsDirEnv()
|
|
204
|
+
},
|
|
205
|
+
files: {
|
|
206
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
207
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
208
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
209
|
+
},
|
|
210
|
+
counts: {
|
|
211
|
+
channels: channels.length,
|
|
212
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
213
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
214
|
+
events: events.length,
|
|
215
|
+
deliveries: deliveries.length
|
|
216
|
+
},
|
|
217
|
+
transports,
|
|
218
|
+
safety: {
|
|
219
|
+
includesEventPayloads: false,
|
|
220
|
+
includesWebhookSecrets: false,
|
|
221
|
+
listOutputsRedactSecrets: true,
|
|
222
|
+
statusOutputIsMetadataOnly: true
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function statusFile(dataDir, fileName, records) {
|
|
227
|
+
const path = join(dataDir, fileName);
|
|
228
|
+
return { path, exists: existsSync(path), records };
|
|
229
|
+
}
|
|
177
230
|
|
|
178
231
|
// src/transports.ts
|
|
179
232
|
import { randomUUID } from "crypto";
|
|
@@ -652,6 +705,7 @@ Usage:
|
|
|
652
705
|
${name} [--dir <path>] [--json] webhooks list
|
|
653
706
|
${name} [--dir <path>] [--json] webhooks remove <id>
|
|
654
707
|
${name} [--dir <path>] [--json] webhooks test <id>
|
|
708
|
+
${name} [--dir <path>] [--json] status
|
|
655
709
|
${name} [--dir <path>] [--json] events emit <type>${options.source ? "" : " --source <source>"} [options]
|
|
656
710
|
${name} [--dir <path>] [--json] events list [--limit <n>]
|
|
657
711
|
${name} [--dir <path>] [--json] events replay [--id <event-id>] [--dry-run]
|
|
@@ -712,6 +766,14 @@ async function runEventsCli(argv = process.argv.slice(2), options = {}) {
|
|
|
712
766
|
console.log(version());
|
|
713
767
|
return;
|
|
714
768
|
}
|
|
769
|
+
if (group === "status") {
|
|
770
|
+
const status = await getEventsStatus(parsed.dir);
|
|
771
|
+
output(parsed, status, () => {
|
|
772
|
+
console.log(`events ${status.counts.events} event(s), ${status.counts.channels} channel(s), ${status.counts.deliveries} delivery record(s)`);
|
|
773
|
+
console.log(`dataDir: ${status.dataDir}`);
|
|
774
|
+
});
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
715
777
|
const store = new JsonEventsStore(parsed.dir);
|
|
716
778
|
const client = new EventsClient({ store });
|
|
717
779
|
if (group === "webhooks") {
|
package/dist/commander.js
CHANGED
|
@@ -52,6 +52,13 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
52
52
|
function getEventsDataDir(override) {
|
|
53
53
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
54
54
|
}
|
|
55
|
+
function getActiveEventsDirEnv() {
|
|
56
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
57
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
58
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
59
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
55
62
|
|
|
56
63
|
class JsonEventsStore {
|
|
57
64
|
dataDir;
|
|
@@ -164,6 +171,52 @@ class JsonEventsStore {
|
|
|
164
171
|
});
|
|
165
172
|
}
|
|
166
173
|
}
|
|
174
|
+
async function getEventsStatus(dataDir) {
|
|
175
|
+
const store = new JsonEventsStore(dataDir);
|
|
176
|
+
await store.init();
|
|
177
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
178
|
+
store.listChannels(),
|
|
179
|
+
store.listEvents(),
|
|
180
|
+
store.listDeliveries()
|
|
181
|
+
]);
|
|
182
|
+
const transports = channels.reduce((counts, channel) => {
|
|
183
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
184
|
+
return counts;
|
|
185
|
+
}, {});
|
|
186
|
+
return {
|
|
187
|
+
service: "events",
|
|
188
|
+
schemaVersion: "1.0",
|
|
189
|
+
dataDir: store.dataDir,
|
|
190
|
+
env: {
|
|
191
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
192
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
193
|
+
active: getActiveEventsDirEnv()
|
|
194
|
+
},
|
|
195
|
+
files: {
|
|
196
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
197
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
198
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
199
|
+
},
|
|
200
|
+
counts: {
|
|
201
|
+
channels: channels.length,
|
|
202
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
203
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
204
|
+
events: events.length,
|
|
205
|
+
deliveries: deliveries.length
|
|
206
|
+
},
|
|
207
|
+
transports,
|
|
208
|
+
safety: {
|
|
209
|
+
includesEventPayloads: false,
|
|
210
|
+
includesWebhookSecrets: false,
|
|
211
|
+
listOutputsRedactSecrets: true,
|
|
212
|
+
statusOutputIsMetadataOnly: true
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function statusFile(dataDir, fileName, records) {
|
|
217
|
+
const path = join(dataDir, fileName);
|
|
218
|
+
return { path, exists: existsSync(path), records };
|
|
219
|
+
}
|
|
167
220
|
|
|
168
221
|
// src/signing.ts
|
|
169
222
|
import { createHmac, timingSafeEqual } from "crypto";
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,13 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
52
52
|
function getEventsDataDir(override) {
|
|
53
53
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
54
54
|
}
|
|
55
|
+
function getActiveEventsDirEnv() {
|
|
56
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
57
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
58
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
59
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
55
62
|
|
|
56
63
|
class JsonEventsStore {
|
|
57
64
|
dataDir;
|
|
@@ -164,6 +171,52 @@ class JsonEventsStore {
|
|
|
164
171
|
});
|
|
165
172
|
}
|
|
166
173
|
}
|
|
174
|
+
async function getEventsStatus(dataDir) {
|
|
175
|
+
const store = new JsonEventsStore(dataDir);
|
|
176
|
+
await store.init();
|
|
177
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
178
|
+
store.listChannels(),
|
|
179
|
+
store.listEvents(),
|
|
180
|
+
store.listDeliveries()
|
|
181
|
+
]);
|
|
182
|
+
const transports = channels.reduce((counts, channel) => {
|
|
183
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
184
|
+
return counts;
|
|
185
|
+
}, {});
|
|
186
|
+
return {
|
|
187
|
+
service: "events",
|
|
188
|
+
schemaVersion: "1.0",
|
|
189
|
+
dataDir: store.dataDir,
|
|
190
|
+
env: {
|
|
191
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
192
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
193
|
+
active: getActiveEventsDirEnv()
|
|
194
|
+
},
|
|
195
|
+
files: {
|
|
196
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
197
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
198
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
199
|
+
},
|
|
200
|
+
counts: {
|
|
201
|
+
channels: channels.length,
|
|
202
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
203
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
204
|
+
events: events.length,
|
|
205
|
+
deliveries: deliveries.length
|
|
206
|
+
},
|
|
207
|
+
transports,
|
|
208
|
+
safety: {
|
|
209
|
+
includesEventPayloads: false,
|
|
210
|
+
includesWebhookSecrets: false,
|
|
211
|
+
listOutputsRedactSecrets: true,
|
|
212
|
+
statusOutputIsMetadataOnly: true
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function statusFile(dataDir, fileName, records) {
|
|
217
|
+
const path = join(dataDir, fileName);
|
|
218
|
+
return { path, exists: existsSync(path), records };
|
|
219
|
+
}
|
|
167
220
|
|
|
168
221
|
// src/signing.ts
|
|
169
222
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -558,7 +611,9 @@ export {
|
|
|
558
611
|
redactPaths,
|
|
559
612
|
matchString,
|
|
560
613
|
isTimestampWithinTolerance,
|
|
614
|
+
getEventsStatus,
|
|
561
615
|
getEventsDataDir,
|
|
616
|
+
getActiveEventsDirEnv,
|
|
562
617
|
eventMatchesFilter,
|
|
563
618
|
dispatchWebhook,
|
|
564
619
|
dispatchCommand,
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ChannelConfig, DeliveryResult, EventEnvelope, StoredEventsData } from "./types.js";
|
|
1
|
+
import type { ChannelConfig, DeliveryResult, EventEnvelope, EventsStatus, StoredEventsData } from "./types.js";
|
|
2
2
|
export declare const HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
3
3
|
export declare const HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
4
4
|
export declare function getEventsDataDir(override?: string): string;
|
|
5
|
+
export declare function getActiveEventsDirEnv(): EventsStatus["env"]["active"];
|
|
5
6
|
export interface EventsStore {
|
|
6
7
|
dataDir: string;
|
|
7
8
|
init(): Promise<void>;
|
|
@@ -42,3 +43,4 @@ export declare class JsonEventsStore implements EventsStore {
|
|
|
42
43
|
private readJson;
|
|
43
44
|
private writeJson;
|
|
44
45
|
}
|
|
46
|
+
export declare function getEventsStatus(dataDir?: string): Promise<EventsStatus>;
|
package/dist/storage.js
CHANGED
|
@@ -9,6 +9,13 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
9
9
|
function getEventsDataDir(override) {
|
|
10
10
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
11
11
|
}
|
|
12
|
+
function getActiveEventsDirEnv() {
|
|
13
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
14
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
15
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
16
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
class JsonEventsStore {
|
|
14
21
|
dataDir;
|
|
@@ -121,8 +128,56 @@ class JsonEventsStore {
|
|
|
121
128
|
});
|
|
122
129
|
}
|
|
123
130
|
}
|
|
131
|
+
async function getEventsStatus(dataDir) {
|
|
132
|
+
const store = new JsonEventsStore(dataDir);
|
|
133
|
+
await store.init();
|
|
134
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
135
|
+
store.listChannels(),
|
|
136
|
+
store.listEvents(),
|
|
137
|
+
store.listDeliveries()
|
|
138
|
+
]);
|
|
139
|
+
const transports = channels.reduce((counts, channel) => {
|
|
140
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
141
|
+
return counts;
|
|
142
|
+
}, {});
|
|
143
|
+
return {
|
|
144
|
+
service: "events",
|
|
145
|
+
schemaVersion: "1.0",
|
|
146
|
+
dataDir: store.dataDir,
|
|
147
|
+
env: {
|
|
148
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
149
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
150
|
+
active: getActiveEventsDirEnv()
|
|
151
|
+
},
|
|
152
|
+
files: {
|
|
153
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
154
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
155
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
156
|
+
},
|
|
157
|
+
counts: {
|
|
158
|
+
channels: channels.length,
|
|
159
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
160
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
161
|
+
events: events.length,
|
|
162
|
+
deliveries: deliveries.length
|
|
163
|
+
},
|
|
164
|
+
transports,
|
|
165
|
+
safety: {
|
|
166
|
+
includesEventPayloads: false,
|
|
167
|
+
includesWebhookSecrets: false,
|
|
168
|
+
listOutputsRedactSecrets: true,
|
|
169
|
+
statusOutputIsMetadataOnly: true
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function statusFile(dataDir, fileName, records) {
|
|
174
|
+
const path = join(dataDir, fileName);
|
|
175
|
+
return { path, exists: existsSync(path), records };
|
|
176
|
+
}
|
|
124
177
|
export {
|
|
178
|
+
getEventsStatus,
|
|
125
179
|
getEventsDataDir,
|
|
180
|
+
getActiveEventsDirEnv,
|
|
126
181
|
JsonEventsStore,
|
|
127
182
|
HASNA_EVENTS_HOME_ENV,
|
|
128
183
|
HASNA_EVENTS_DIR_ENV
|
package/dist/types.d.ts
CHANGED
|
@@ -117,3 +117,44 @@ export interface EmitResult<TData extends EventData = EventData> {
|
|
|
117
117
|
deliveries: DeliveryResult[];
|
|
118
118
|
deduped: boolean;
|
|
119
119
|
}
|
|
120
|
+
export interface EventsStatus {
|
|
121
|
+
service: "events";
|
|
122
|
+
schemaVersion: "1.0";
|
|
123
|
+
dataDir: string;
|
|
124
|
+
env: {
|
|
125
|
+
primary: "HASNA_EVENTS_DIR";
|
|
126
|
+
fallback: "HASNA_EVENTS_HOME";
|
|
127
|
+
active: "HASNA_EVENTS_DIR" | "HASNA_EVENTS_HOME" | null;
|
|
128
|
+
};
|
|
129
|
+
files: {
|
|
130
|
+
channels: {
|
|
131
|
+
path: string;
|
|
132
|
+
exists: boolean;
|
|
133
|
+
records: number;
|
|
134
|
+
};
|
|
135
|
+
events: {
|
|
136
|
+
path: string;
|
|
137
|
+
exists: boolean;
|
|
138
|
+
records: number;
|
|
139
|
+
};
|
|
140
|
+
deliveries: {
|
|
141
|
+
path: string;
|
|
142
|
+
exists: boolean;
|
|
143
|
+
records: number;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
counts: {
|
|
147
|
+
channels: number;
|
|
148
|
+
enabledChannels: number;
|
|
149
|
+
disabledChannels: number;
|
|
150
|
+
events: number;
|
|
151
|
+
deliveries: number;
|
|
152
|
+
};
|
|
153
|
+
transports: Record<string, number>;
|
|
154
|
+
safety: {
|
|
155
|
+
includesEventPayloads: false;
|
|
156
|
+
includesWebhookSecrets: false;
|
|
157
|
+
listOutputsRedactSecrets: true;
|
|
158
|
+
statusOutputIsMetadataOnly: true;
|
|
159
|
+
};
|
|
160
|
+
}
|