@hasna/events 0.1.13 → 0.1.15
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/LICENSE +198 -13
- package/README.md +274 -26
- package/dist/app-event.js +382 -0
- package/dist/catalog.js +191 -0
- package/dist/cli/index.js +1680 -107
- package/dist/commander.js +882 -92
- package/dist/durable-spool.js +184 -0
- package/dist/durable-worker.js +378 -0
- package/dist/durable.js +2232 -0
- package/dist/index.js +868 -71
- package/dist/storage.js +140 -4
- package/dist/transports.js +36 -7
- package/fixtures/hasna.app_event.v1.json +108 -0
- package/hasna.contract.json +70 -0
- package/package.json +46 -12
- package/schemas/hasna.app_event.v1.json +186 -0
- package/types/app-event.d.ts +130 -0
- package/types/catalog.d.ts +136 -0
- package/{dist → types}/commander.d.ts +14 -0
- package/types/durable-spool.d.ts +30 -0
- package/types/durable-worker.d.ts +27 -0
- package/types/durable.d.ts +112 -0
- package/{dist → types}/index.d.ts +23 -8
- package/types/redaction.d.ts +4 -0
- package/{dist → types}/storage.d.ts +16 -3
- package/{dist → types}/transports.d.ts +8 -1
- package/{dist → types}/types.d.ts +64 -2
- /package/{dist → types}/cli/index.d.ts +0 -0
- /package/{dist → types}/filter-options.d.ts +0 -0
- /package/{dist → types}/filter.d.ts +0 -0
- /package/{dist → types}/signing.d.ts +0 -0
package/dist/storage.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/storage.ts
|
|
3
3
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
4
|
+
import { Buffer } from "buffer";
|
|
4
5
|
import { existsSync } from "fs";
|
|
5
6
|
import { homedir } from "os";
|
|
6
7
|
import { join } from "path";
|
|
7
8
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
8
9
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
10
|
+
var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
|
|
11
|
+
var DEFAULT_EVENT_PAGE_LIMIT = 100;
|
|
12
|
+
var MAX_EVENT_PAGE_LIMIT = 1000;
|
|
9
13
|
function getEventsDataDir(override) {
|
|
10
14
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
11
15
|
}
|
|
@@ -19,11 +23,13 @@ function getActiveEventsDirEnv() {
|
|
|
19
23
|
|
|
20
24
|
class JsonEventsStore {
|
|
21
25
|
dataDir;
|
|
26
|
+
runtime;
|
|
22
27
|
channelsPath;
|
|
23
28
|
eventsPath;
|
|
24
29
|
deliveriesPath;
|
|
25
30
|
constructor(dataDir = getEventsDataDir()) {
|
|
26
31
|
this.dataDir = dataDir;
|
|
32
|
+
this.runtime = localJsonRuntime(dataDir);
|
|
27
33
|
this.channelsPath = join(dataDir, "channels.json");
|
|
28
34
|
this.eventsPath = join(dataDir, "events.json");
|
|
29
35
|
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
@@ -71,13 +77,58 @@ class JsonEventsStore {
|
|
|
71
77
|
await this.writeJson(this.eventsPath, events);
|
|
72
78
|
return event;
|
|
73
79
|
}
|
|
74
|
-
async
|
|
80
|
+
async appendEventOnce(event, options = {}) {
|
|
75
81
|
await this.init();
|
|
76
|
-
|
|
82
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
83
|
+
const dedupe = options.dedupe !== false;
|
|
84
|
+
if (dedupe) {
|
|
85
|
+
const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
|
|
86
|
+
if (existing) {
|
|
87
|
+
return {
|
|
88
|
+
event: existing,
|
|
89
|
+
stored: false,
|
|
90
|
+
deduped: true,
|
|
91
|
+
identity: { id: existing.id, dedupeKey: existing.dedupeKey }
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
events.push(event);
|
|
96
|
+
await this.writeJson(this.eventsPath, events);
|
|
97
|
+
return {
|
|
98
|
+
event,
|
|
99
|
+
stored: true,
|
|
100
|
+
deduped: false,
|
|
101
|
+
identity: { id: event.id, dedupeKey: event.dedupeKey }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async listEvents(options = {}) {
|
|
105
|
+
await this.init();
|
|
106
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
107
|
+
return queryEvents(events, options);
|
|
108
|
+
}
|
|
109
|
+
async listEventsPage(options = {}) {
|
|
110
|
+
await this.init();
|
|
111
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
112
|
+
const queried = queryEvents(events, {
|
|
113
|
+
eventId: options.eventId,
|
|
114
|
+
source: options.source,
|
|
115
|
+
type: options.type
|
|
116
|
+
});
|
|
117
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
118
|
+
const limit = normalizeEventPageLimit(options.limit);
|
|
119
|
+
const pageEvents = queried.slice(offset, offset + limit);
|
|
120
|
+
const nextOffset = offset + pageEvents.length;
|
|
121
|
+
const hasMore = nextOffset < queried.length;
|
|
122
|
+
return {
|
|
123
|
+
events: pageEvents,
|
|
124
|
+
cursor: options.cursor,
|
|
125
|
+
nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
|
|
126
|
+
hasMore
|
|
127
|
+
};
|
|
77
128
|
}
|
|
78
129
|
async findEventByIdentity(identity) {
|
|
79
130
|
const events = await this.listEvents();
|
|
80
|
-
return events
|
|
131
|
+
return findEventByIdentity(events, identity);
|
|
81
132
|
}
|
|
82
133
|
async appendDelivery(result) {
|
|
83
134
|
await this.init();
|
|
@@ -128,6 +179,83 @@ class JsonEventsStore {
|
|
|
128
179
|
});
|
|
129
180
|
}
|
|
130
181
|
}
|
|
182
|
+
function localJsonRuntime(dataDir = getEventsDataDir()) {
|
|
183
|
+
return {
|
|
184
|
+
mode: "local-files",
|
|
185
|
+
name: "json-events-store",
|
|
186
|
+
remote: false,
|
|
187
|
+
localFiles: true,
|
|
188
|
+
localSqlite: false,
|
|
189
|
+
postgres: false,
|
|
190
|
+
s3: false,
|
|
191
|
+
aws: false,
|
|
192
|
+
durable: true,
|
|
193
|
+
idempotency: "best-effort-local",
|
|
194
|
+
replayCursors: true,
|
|
195
|
+
description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function encodeLocalJsonEventCursor(offset, options = {}) {
|
|
199
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
200
|
+
throw new Error(`Invalid event cursor offset: ${offset}`);
|
|
201
|
+
const payload = {
|
|
202
|
+
offset,
|
|
203
|
+
eventId: options.eventId,
|
|
204
|
+
source: options.source,
|
|
205
|
+
type: options.type
|
|
206
|
+
};
|
|
207
|
+
return `${LOCAL_JSON_EVENT_CURSOR_PREFIX}${Buffer.from(JSON.stringify(payload), "utf-8").toString("base64url")}`;
|
|
208
|
+
}
|
|
209
|
+
function decodeLocalJsonEventCursor(cursor, options = {}) {
|
|
210
|
+
if (!cursor)
|
|
211
|
+
return 0;
|
|
212
|
+
if (!cursor.startsWith(LOCAL_JSON_EVENT_CURSOR_PREFIX))
|
|
213
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
214
|
+
const rawPayload = cursor.slice(LOCAL_JSON_EVENT_CURSOR_PREFIX.length);
|
|
215
|
+
let payload;
|
|
216
|
+
try {
|
|
217
|
+
payload = JSON.parse(Buffer.from(rawPayload, "base64url").toString("utf-8"));
|
|
218
|
+
} catch {
|
|
219
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
220
|
+
}
|
|
221
|
+
const offset = payload.offset;
|
|
222
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
223
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
224
|
+
assertCursorFilter("eventId", payload.eventId, options.eventId);
|
|
225
|
+
assertCursorFilter("source", payload.source, options.source);
|
|
226
|
+
assertCursorFilter("type", payload.type, options.type);
|
|
227
|
+
return offset;
|
|
228
|
+
}
|
|
229
|
+
function normalizeEventPageLimit(limit) {
|
|
230
|
+
if (limit === undefined)
|
|
231
|
+
return DEFAULT_EVENT_PAGE_LIMIT;
|
|
232
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
233
|
+
throw new Error(`Event page limit must be a positive integer, got ${limit}`);
|
|
234
|
+
return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
|
|
235
|
+
}
|
|
236
|
+
function queryEvents(events, options) {
|
|
237
|
+
let rows = events;
|
|
238
|
+
if (options.eventId)
|
|
239
|
+
rows = rows.filter((event) => event.id === options.eventId);
|
|
240
|
+
if (options.source)
|
|
241
|
+
rows = rows.filter((event) => event.source === options.source);
|
|
242
|
+
if (options.type)
|
|
243
|
+
rows = rows.filter((event) => event.type === options.type);
|
|
244
|
+
if (options.cursor) {
|
|
245
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
246
|
+
rows = rows.slice(offset);
|
|
247
|
+
}
|
|
248
|
+
if (options.limit !== undefined)
|
|
249
|
+
rows = rows.slice(0, normalizeEventPageLimit(options.limit));
|
|
250
|
+
return rows;
|
|
251
|
+
}
|
|
252
|
+
function assertCursorFilter(name, cursorValue, optionValue) {
|
|
253
|
+
if (cursorValue !== optionValue)
|
|
254
|
+
throw new Error(`Local JSON event cursor ${name} filter mismatch`);
|
|
255
|
+
}
|
|
256
|
+
function findEventByIdentity(events, identity) {
|
|
257
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
258
|
+
}
|
|
131
259
|
async function getEventsStatus(dataDir) {
|
|
132
260
|
const store = new JsonEventsStore(dataDir);
|
|
133
261
|
await store.init();
|
|
@@ -144,6 +272,7 @@ async function getEventsStatus(dataDir) {
|
|
|
144
272
|
service: "events",
|
|
145
273
|
schemaVersion: "1.0",
|
|
146
274
|
dataDir: store.dataDir,
|
|
275
|
+
storage: store.runtime,
|
|
147
276
|
env: {
|
|
148
277
|
primary: HASNA_EVENTS_DIR_ENV,
|
|
149
278
|
fallback: HASNA_EVENTS_HOME_ENV,
|
|
@@ -175,10 +304,17 @@ function statusFile(dataDir, fileName, records) {
|
|
|
175
304
|
return { path, exists: existsSync(path), records };
|
|
176
305
|
}
|
|
177
306
|
export {
|
|
307
|
+
normalizeEventPageLimit,
|
|
308
|
+
localJsonRuntime,
|
|
178
309
|
getEventsStatus,
|
|
179
310
|
getEventsDataDir,
|
|
180
311
|
getActiveEventsDirEnv,
|
|
312
|
+
encodeLocalJsonEventCursor,
|
|
313
|
+
decodeLocalJsonEventCursor,
|
|
314
|
+
MAX_EVENT_PAGE_LIMIT,
|
|
315
|
+
LOCAL_JSON_EVENT_CURSOR_PREFIX,
|
|
181
316
|
JsonEventsStore,
|
|
182
317
|
HASNA_EVENTS_HOME_ENV,
|
|
183
|
-
HASNA_EVENTS_DIR_ENV
|
|
318
|
+
HASNA_EVENTS_DIR_ENV,
|
|
319
|
+
DEFAULT_EVENT_PAGE_LIMIT
|
|
184
320
|
};
|
package/dist/transports.js
CHANGED
|
@@ -42,21 +42,27 @@ function now() {
|
|
|
42
42
|
function truncate(value, max = 4096) {
|
|
43
43
|
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
44
44
|
}
|
|
45
|
-
function buildWebhookRequest(event, channel) {
|
|
45
|
+
function buildWebhookRequest(event, channel, options = {}) {
|
|
46
46
|
if (!channel.webhook)
|
|
47
47
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
48
|
+
for (const name of Object.keys(channel.webhook.headers ?? {})) {
|
|
49
|
+
if (/^x-hasna-/i.test(name)) {
|
|
50
|
+
throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
48
53
|
const body = JSON.stringify(event);
|
|
49
|
-
const timestamp =
|
|
54
|
+
const timestamp = options.timestamp ?? new Date().toISOString();
|
|
50
55
|
const headers = {
|
|
51
56
|
"Content-Type": "application/json",
|
|
52
57
|
"User-Agent": "@hasna/events",
|
|
53
58
|
"X-Hasna-Event-Id": event.id,
|
|
54
59
|
"X-Hasna-Event-Type": event.type,
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
...channel.webhook.headers,
|
|
61
|
+
"X-Hasna-Timestamp": timestamp
|
|
57
62
|
};
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const secret = options.secret ?? channel.webhook.secret;
|
|
64
|
+
if (secret) {
|
|
65
|
+
headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
|
|
60
66
|
}
|
|
61
67
|
return { body, headers };
|
|
62
68
|
}
|
|
@@ -64,7 +70,21 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
64
70
|
if (!channel.webhook)
|
|
65
71
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
66
72
|
const startedAt = now();
|
|
67
|
-
|
|
73
|
+
let secret = channel.webhook.secret;
|
|
74
|
+
if (channel.webhook.secretRef) {
|
|
75
|
+
if (!options.secretResolver) {
|
|
76
|
+
return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
secret = await options.secretResolver(channel.webhook.secretRef);
|
|
80
|
+
} catch {
|
|
81
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
82
|
+
}
|
|
83
|
+
if (!secret)
|
|
84
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
85
|
+
}
|
|
86
|
+
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
87
|
+
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
68
88
|
const controller = new AbortController;
|
|
69
89
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
70
90
|
try {
|
|
@@ -96,6 +116,15 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
96
116
|
clearTimeout(timeout);
|
|
97
117
|
}
|
|
98
118
|
}
|
|
119
|
+
function failedAttempt(startedAt, error) {
|
|
120
|
+
return {
|
|
121
|
+
attempt: 1,
|
|
122
|
+
status: "failed",
|
|
123
|
+
startedAt,
|
|
124
|
+
completedAt: now(),
|
|
125
|
+
error
|
|
126
|
+
};
|
|
127
|
+
}
|
|
99
128
|
async function dispatchCommand(event, channel) {
|
|
100
129
|
if (!channel.command)
|
|
101
130
|
throw new Error(`Channel ${channel.id} has no command config`);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"event_id": "evt_fixture_todo_completed_01",
|
|
3
|
+
"event_type": "todos.task.completed",
|
|
4
|
+
"schema_version": "hasna.app_event.v1",
|
|
5
|
+
"source": {
|
|
6
|
+
"app": "open-todos",
|
|
7
|
+
"version": "0.0.0-fixture",
|
|
8
|
+
"machine": "fixture.local"
|
|
9
|
+
},
|
|
10
|
+
"occurred_at": "2026-07-29T12:00:00.000Z",
|
|
11
|
+
"severity": "notice",
|
|
12
|
+
"idempotency": {
|
|
13
|
+
"dedupe_key": "fixture:interop-project:todo-42:completed:v1",
|
|
14
|
+
"replay_safe": true
|
|
15
|
+
},
|
|
16
|
+
"correlation": {
|
|
17
|
+
"correlation_id": "corr_fixture_interop_project_01",
|
|
18
|
+
"causation_id": "evt_fixture_todo_created_01"
|
|
19
|
+
},
|
|
20
|
+
"subject": {
|
|
21
|
+
"kind": "task",
|
|
22
|
+
"id": "todo_fixture_42",
|
|
23
|
+
"uri": "hasna://todos/tasks/todo_fixture_42"
|
|
24
|
+
},
|
|
25
|
+
"actor": {
|
|
26
|
+
"kind": "service",
|
|
27
|
+
"id": "open-todos",
|
|
28
|
+
"name": "Todos fixture producer"
|
|
29
|
+
},
|
|
30
|
+
"project_mappings": {
|
|
31
|
+
"canonical_id": "project_fixture_interop",
|
|
32
|
+
"slug": "interop-fixture",
|
|
33
|
+
"repository": "https://example.invalid/hasna/interop-fixture",
|
|
34
|
+
"workspace": "workspace_fixture_interop",
|
|
35
|
+
"external_ids": {
|
|
36
|
+
"todos": "project_fixture_interop",
|
|
37
|
+
"conversations": "conversation_fixture_17",
|
|
38
|
+
"mementos": "memento_fixture_09",
|
|
39
|
+
"projects": "project_fixture_interop",
|
|
40
|
+
"codewith": "mailbox_fixture_interop"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"summary": "Interop fixture task completed successfully.",
|
|
44
|
+
"data": {
|
|
45
|
+
"task_status": "completed",
|
|
46
|
+
"task_title": "Verify app-event interoperability",
|
|
47
|
+
"conversation_id": "conversation_fixture_17",
|
|
48
|
+
"memento_id": "memento_fixture_09",
|
|
49
|
+
"mailbox_topic": "project.activity",
|
|
50
|
+
"render_hint": {
|
|
51
|
+
"component": "StatusBadge",
|
|
52
|
+
"tone": "success"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"resource_refs": [
|
|
56
|
+
{
|
|
57
|
+
"kind": "task",
|
|
58
|
+
"id": "todo_fixture_42",
|
|
59
|
+
"uri": "hasna://todos/tasks/todo_fixture_42",
|
|
60
|
+
"source_package": "@hasna/todos",
|
|
61
|
+
"external_id": "todo_fixture_42"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"kind": "conversation",
|
|
65
|
+
"id": "conversation_fixture_17",
|
|
66
|
+
"uri": "hasna://conversations/conversation_fixture_17"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"kind": "memento",
|
|
70
|
+
"id": "memento_fixture_09",
|
|
71
|
+
"uri": "hasna://mementos/memento_fixture_09"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"kind": "render",
|
|
75
|
+
"id": "project_fixture_interop_status",
|
|
76
|
+
"uri": "hasna://projects/project_fixture_interop/render/status"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"evidence_refs": [
|
|
80
|
+
{
|
|
81
|
+
"kind": "test_result",
|
|
82
|
+
"id": "evidence_fixture_interop_01",
|
|
83
|
+
"uri": "artifact://interop-fixture/test-report.json",
|
|
84
|
+
"redaction": "none"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"sensitivity": {
|
|
88
|
+
"classification": "internal",
|
|
89
|
+
"contains_personal_data": false
|
|
90
|
+
},
|
|
91
|
+
"redaction": {
|
|
92
|
+
"state": "none",
|
|
93
|
+
"fields": [],
|
|
94
|
+
"safe_for_logs": true
|
|
95
|
+
},
|
|
96
|
+
"delivery": {
|
|
97
|
+
"intent": "notification",
|
|
98
|
+
"mode": "at_least_once",
|
|
99
|
+
"targets": [
|
|
100
|
+
"todos",
|
|
101
|
+
"conversations",
|
|
102
|
+
"mementos",
|
|
103
|
+
"projects-json-render",
|
|
104
|
+
"codewith-mailbox"
|
|
105
|
+
],
|
|
106
|
+
"agent_conversation_injection": false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "hasna.service_contract.v1",
|
|
3
|
+
"name": "events",
|
|
4
|
+
"class": "library",
|
|
5
|
+
"contractVersion": "v1",
|
|
6
|
+
"kitVersion": "0.8.5",
|
|
7
|
+
"description": "Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps. Library class: SDK exports are the primary surface and the local `events` CLI is the operator seam. The default compatibility store remains user-owned JSON; opt-in durable delivery uses a Node-safe filesystem spool plus a Bun SQLite outbox under the same caller-selected data directory. The package provisions no remote infrastructure.",
|
|
8
|
+
"bins": [
|
|
9
|
+
"events"
|
|
10
|
+
],
|
|
11
|
+
"hosting": [
|
|
12
|
+
"user-hosted"
|
|
13
|
+
],
|
|
14
|
+
"serviceSurfaces": [
|
|
15
|
+
{
|
|
16
|
+
"name": "package-sdk",
|
|
17
|
+
"kind": "sdk",
|
|
18
|
+
"status": "supported",
|
|
19
|
+
"authMode": "none",
|
|
20
|
+
"exportSubpath": "."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "durable-spool-sdk",
|
|
24
|
+
"kind": "sdk",
|
|
25
|
+
"status": "supported",
|
|
26
|
+
"authMode": "none",
|
|
27
|
+
"exportSubpath": "./durable-spool"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "durable-delivery-sdk",
|
|
31
|
+
"kind": "sdk",
|
|
32
|
+
"status": "supported",
|
|
33
|
+
"authMode": "none",
|
|
34
|
+
"exportSubpath": "./durable"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "durable-worker-sdk",
|
|
38
|
+
"kind": "sdk",
|
|
39
|
+
"status": "supported",
|
|
40
|
+
"authMode": "none",
|
|
41
|
+
"exportSubpath": "./durable-worker"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "events-cli",
|
|
45
|
+
"kind": "cli",
|
|
46
|
+
"status": "supported",
|
|
47
|
+
"bin": "events",
|
|
48
|
+
"authMode": "local-only"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"metadata": {
|
|
52
|
+
"release": {
|
|
53
|
+
"artifactScan": {
|
|
54
|
+
"script": "artifact-scan"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"conformance": {
|
|
58
|
+
"waivedSurfaces": [
|
|
59
|
+
{
|
|
60
|
+
"kind": "api",
|
|
61
|
+
"reason": "Embedded library plus a local CLI; it owns no HTTP service boundary and ships no serve bin."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"kind": "mcp",
|
|
65
|
+
"reason": "Embedded library; MCP execution belongs to the applications that consume these envelopes."
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,53 +1,85 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/events",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
|
-
"types": "./
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
9
|
"events": "dist/cli/index.js",
|
|
10
10
|
"hasna-events": "dist/cli/index.js"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"types": "./
|
|
14
|
+
"types": "./types/index.d.ts",
|
|
15
15
|
"import": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./storage": {
|
|
18
|
-
"types": "./
|
|
18
|
+
"types": "./types/storage.d.ts",
|
|
19
19
|
"import": "./dist/storage.js"
|
|
20
20
|
},
|
|
21
|
+
"./durable": {
|
|
22
|
+
"types": "./types/durable.d.ts",
|
|
23
|
+
"import": "./dist/durable.js"
|
|
24
|
+
},
|
|
25
|
+
"./durable-spool": {
|
|
26
|
+
"types": "./types/durable-spool.d.ts",
|
|
27
|
+
"import": "./dist/durable-spool.js"
|
|
28
|
+
},
|
|
29
|
+
"./durable-worker": {
|
|
30
|
+
"types": "./types/durable-worker.d.ts",
|
|
31
|
+
"import": "./dist/durable-worker.js"
|
|
32
|
+
},
|
|
21
33
|
"./signing": {
|
|
22
|
-
"types": "./
|
|
34
|
+
"types": "./types/signing.d.ts",
|
|
23
35
|
"import": "./dist/signing.js"
|
|
24
36
|
},
|
|
25
37
|
"./filter": {
|
|
26
|
-
"types": "./
|
|
38
|
+
"types": "./types/filter.d.ts",
|
|
27
39
|
"import": "./dist/filter.js"
|
|
28
40
|
},
|
|
29
41
|
"./transports": {
|
|
30
|
-
"types": "./
|
|
42
|
+
"types": "./types/transports.d.ts",
|
|
31
43
|
"import": "./dist/transports.js"
|
|
32
44
|
},
|
|
45
|
+
"./catalog": {
|
|
46
|
+
"types": "./types/catalog.d.ts",
|
|
47
|
+
"import": "./dist/catalog.js"
|
|
48
|
+
},
|
|
49
|
+
"./app-event": {
|
|
50
|
+
"types": "./types/app-event.d.ts",
|
|
51
|
+
"import": "./dist/app-event.js"
|
|
52
|
+
},
|
|
53
|
+
"./schemas/hasna.app_event.v1.json": "./schemas/hasna.app_event.v1.json",
|
|
54
|
+
"./fixtures/hasna.app_event.v1.json": "./fixtures/hasna.app_event.v1.json",
|
|
33
55
|
"./commander": {
|
|
34
|
-
"types": "./
|
|
56
|
+
"types": "./types/commander.d.ts",
|
|
35
57
|
"import": "./dist/commander.js"
|
|
36
58
|
},
|
|
37
59
|
"./cli": {
|
|
38
|
-
"types": "./
|
|
60
|
+
"types": "./types/cli/index.d.ts",
|
|
39
61
|
"import": "./dist/cli/index.js"
|
|
40
62
|
}
|
|
41
63
|
},
|
|
42
64
|
"files": [
|
|
43
65
|
"dist",
|
|
66
|
+
"types",
|
|
67
|
+
"schemas",
|
|
68
|
+
"fixtures",
|
|
44
69
|
"README.md",
|
|
45
|
-
"LICENSE"
|
|
70
|
+
"LICENSE",
|
|
71
|
+
"hasna.contract.json"
|
|
46
72
|
],
|
|
47
73
|
"scripts": {
|
|
48
|
-
"build": "
|
|
74
|
+
"build": "bun run build:runtime && bun run build:types",
|
|
75
|
+
"build:runtime": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun && bun build src/index.ts src/storage.ts src/signing.ts src/filter.ts src/transports.ts src/types.ts src/commander.ts src/catalog.ts src/app-event.ts src/durable.ts src/durable-worker.ts --root src --outdir dist --target bun && bun build src/durable-spool.ts --root src --outdir dist --target node",
|
|
76
|
+
"build:types": "rm -rf types && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir types",
|
|
77
|
+
"generated-artifacts:check": "bun run build && test -z \"$(git status --porcelain --untracked-files=all -- dist types)\"",
|
|
78
|
+
"contract:check": "contracts repo-conformance .",
|
|
79
|
+
"artifact-scan": "bun scripts/artifact-scan.ts",
|
|
49
80
|
"typecheck": "tsc --noEmit",
|
|
50
81
|
"test": "bun test",
|
|
82
|
+
"prepack": "bun run build && bun run artifact-scan",
|
|
51
83
|
"prepublishOnly": "bun run test && bun run build"
|
|
52
84
|
},
|
|
53
85
|
"keywords": [
|
|
@@ -71,7 +103,8 @@
|
|
|
71
103
|
"url": "https://github.com/hasna/events/issues"
|
|
72
104
|
},
|
|
73
105
|
"engines": {
|
|
74
|
-
"bun": ">=1.0.0"
|
|
106
|
+
"bun": ">=1.0.0",
|
|
107
|
+
"node": ">=20.0.0"
|
|
75
108
|
},
|
|
76
109
|
"author": "Hasna",
|
|
77
110
|
"license": "Apache-2.0",
|
|
@@ -79,6 +112,7 @@
|
|
|
79
112
|
"commander": "^13.1.0"
|
|
80
113
|
},
|
|
81
114
|
"devDependencies": {
|
|
115
|
+
"@hasna/contracts": "0.8.5",
|
|
82
116
|
"@types/bun": "latest",
|
|
83
117
|
"typescript": "^5.7.3"
|
|
84
118
|
}
|