@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 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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/events",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Shared event envelopes, local subscriptions, and webhook delivery for Hasna open-source apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",