@kyberonai/trailproof 0.1.0
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/dist/index.cjs +511 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +202 -0
- package/dist/index.d.ts +202 -0
- package/dist/index.js +479 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TrailproofError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "TrailproofError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var ValidationError = class extends TrailproofError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "ValidationError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var StoreError = class extends TrailproofError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "StoreError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var ChainError = class extends TrailproofError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "ChainError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var SignatureError = class extends TrailproofError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "SignatureError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/trailproof.ts
|
|
34
|
+
import { randomUUID } from "crypto";
|
|
35
|
+
|
|
36
|
+
// src/chain.ts
|
|
37
|
+
import { createHash } from "crypto";
|
|
38
|
+
var GENESIS_HASH = "0".repeat(64);
|
|
39
|
+
var EXCLUDED_FIELDS = /* @__PURE__ */ new Set(["hash", "signature"]);
|
|
40
|
+
function canonicalJson(event) {
|
|
41
|
+
const raw = { ...event };
|
|
42
|
+
const cleaned = stripExcluded(raw);
|
|
43
|
+
return stableStringify(cleaned);
|
|
44
|
+
}
|
|
45
|
+
function computeHash(prevHash, event) {
|
|
46
|
+
const payload = prevHash + canonicalJson(event);
|
|
47
|
+
return createHash("sha256").update(payload, "utf-8").digest("hex");
|
|
48
|
+
}
|
|
49
|
+
function stripExcluded(data) {
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const key of Object.keys(data)) {
|
|
52
|
+
if (EXCLUDED_FIELDS.has(key)) continue;
|
|
53
|
+
const value = data[key];
|
|
54
|
+
if (value === null || value === void 0) continue;
|
|
55
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
56
|
+
result[key] = stripExcluded(value);
|
|
57
|
+
} else {
|
|
58
|
+
result[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function stableStringify(obj) {
|
|
64
|
+
if (obj === null || obj === void 0) return "null";
|
|
65
|
+
if (typeof obj === "string") return JSON.stringify(obj);
|
|
66
|
+
if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
|
|
67
|
+
if (Array.isArray(obj)) {
|
|
68
|
+
return "[" + obj.map((item) => stableStringify(item)).join(",") + "]";
|
|
69
|
+
}
|
|
70
|
+
if (typeof obj === "object") {
|
|
71
|
+
const record = obj;
|
|
72
|
+
const keys = Object.keys(record).sort();
|
|
73
|
+
const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(record[k]));
|
|
74
|
+
return "{" + pairs.join(",") + "}";
|
|
75
|
+
}
|
|
76
|
+
return String(obj);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/signer.ts
|
|
80
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
81
|
+
var SIGNATURE_PREFIX = "hmac-sha256:";
|
|
82
|
+
function signEvent(key, event) {
|
|
83
|
+
const canonical = canonicalJson(event);
|
|
84
|
+
const mac = createHmac("sha256", key).update(canonical, "utf-8").digest("hex");
|
|
85
|
+
return SIGNATURE_PREFIX + mac;
|
|
86
|
+
}
|
|
87
|
+
function verifySignature(key, event) {
|
|
88
|
+
if (event.signature == null) {
|
|
89
|
+
throw new SignatureError("Trailproof: missing signature \u2014 event has no signature field");
|
|
90
|
+
}
|
|
91
|
+
if (!event.signature.startsWith(SIGNATURE_PREFIX)) {
|
|
92
|
+
throw new SignatureError(
|
|
93
|
+
"Trailproof: invalid signature format \u2014 expected 'hmac-sha256:' prefix"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const expected = signEvent(key, event);
|
|
97
|
+
const actualBuf = Buffer.from(event.signature, "utf-8");
|
|
98
|
+
const expectedBuf = Buffer.from(expected, "utf-8");
|
|
99
|
+
if (actualBuf.length !== expectedBuf.length || !timingSafeEqual(actualBuf, expectedBuf)) {
|
|
100
|
+
throw new SignatureError("Trailproof: signature mismatch \u2014 HMAC verification failed");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/stores/jsonl.ts
|
|
105
|
+
import { appendFileSync, closeSync, existsSync, openSync, readFileSync, writeSync } from "fs";
|
|
106
|
+
var DEFAULT_LIMIT = 100;
|
|
107
|
+
var FILE_PERMISSIONS = 384;
|
|
108
|
+
var JsonlStore = class {
|
|
109
|
+
path;
|
|
110
|
+
events = [];
|
|
111
|
+
_corruptLines = [];
|
|
112
|
+
constructor(path) {
|
|
113
|
+
this.path = path;
|
|
114
|
+
this.loadExisting();
|
|
115
|
+
}
|
|
116
|
+
/** Append a single event to the JSONL file. */
|
|
117
|
+
append(event) {
|
|
118
|
+
const line = JSON.stringify(event) + "\n";
|
|
119
|
+
try {
|
|
120
|
+
if (!existsSync(this.path)) {
|
|
121
|
+
const fd = openSync(this.path, "wx", FILE_PERMISSIONS);
|
|
122
|
+
writeSync(fd, line, void 0, "utf-8");
|
|
123
|
+
closeSync(fd);
|
|
124
|
+
} else {
|
|
125
|
+
appendFileSync(this.path, line, { encoding: "utf-8" });
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
129
|
+
throw new StoreError(`Trailproof: write failed \u2014 ${message}`);
|
|
130
|
+
}
|
|
131
|
+
this.events.push(event);
|
|
132
|
+
}
|
|
133
|
+
/** Return all events in insertion order. */
|
|
134
|
+
readAll() {
|
|
135
|
+
return [...this.events];
|
|
136
|
+
}
|
|
137
|
+
/** Query events with optional filters and cursor pagination. */
|
|
138
|
+
query(filters) {
|
|
139
|
+
let events = this.events;
|
|
140
|
+
if (filters.cursor != null) {
|
|
141
|
+
const cursorIndex = events.findIndex((e) => e.event_id === filters.cursor);
|
|
142
|
+
if (cursorIndex === -1) {
|
|
143
|
+
return { events: [], next_cursor: null };
|
|
144
|
+
}
|
|
145
|
+
events = events.slice(cursorIndex + 1);
|
|
146
|
+
}
|
|
147
|
+
if (filters.event_type != null) {
|
|
148
|
+
events = events.filter((e) => e.event_type === filters.event_type);
|
|
149
|
+
}
|
|
150
|
+
if (filters.actor_id != null) {
|
|
151
|
+
events = events.filter((e) => e.actor_id === filters.actor_id);
|
|
152
|
+
}
|
|
153
|
+
if (filters.tenant_id != null) {
|
|
154
|
+
events = events.filter((e) => e.tenant_id === filters.tenant_id);
|
|
155
|
+
}
|
|
156
|
+
if (filters.trace_id != null) {
|
|
157
|
+
events = events.filter((e) => e.trace_id === filters.trace_id);
|
|
158
|
+
}
|
|
159
|
+
if (filters.session_id != null) {
|
|
160
|
+
events = events.filter((e) => e.session_id === filters.session_id);
|
|
161
|
+
}
|
|
162
|
+
if (filters.from_time != null) {
|
|
163
|
+
const fromTime = filters.from_time;
|
|
164
|
+
events = events.filter((e) => e.timestamp >= fromTime);
|
|
165
|
+
}
|
|
166
|
+
if (filters.to_time != null) {
|
|
167
|
+
const toTime = filters.to_time;
|
|
168
|
+
events = events.filter((e) => e.timestamp <= toTime);
|
|
169
|
+
}
|
|
170
|
+
const limit = filters.limit ?? DEFAULT_LIMIT;
|
|
171
|
+
if (events.length > limit) {
|
|
172
|
+
const cursorEvent = events[limit - 1];
|
|
173
|
+
const nextCursor = cursorEvent ? cursorEvent.event_id : null;
|
|
174
|
+
events = events.slice(0, limit);
|
|
175
|
+
return { events, next_cursor: nextCursor };
|
|
176
|
+
}
|
|
177
|
+
return { events, next_cursor: null };
|
|
178
|
+
}
|
|
179
|
+
/** Return hash of the last event, or genesis hash if empty. */
|
|
180
|
+
lastHash() {
|
|
181
|
+
if (this.events.length === 0) {
|
|
182
|
+
return GENESIS_HASH;
|
|
183
|
+
}
|
|
184
|
+
const last = this.events[this.events.length - 1];
|
|
185
|
+
return last ? last.hash : GENESIS_HASH;
|
|
186
|
+
}
|
|
187
|
+
/** Return total number of stored events. */
|
|
188
|
+
count() {
|
|
189
|
+
return this.events.length;
|
|
190
|
+
}
|
|
191
|
+
/** Return indices of corrupt lines found during file load. */
|
|
192
|
+
get corruptLines() {
|
|
193
|
+
return [...this._corruptLines];
|
|
194
|
+
}
|
|
195
|
+
/** Load events from an existing JSONL file. */
|
|
196
|
+
loadExisting() {
|
|
197
|
+
if (!existsSync(this.path)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let content;
|
|
201
|
+
try {
|
|
202
|
+
content = readFileSync(this.path, { encoding: "utf-8" });
|
|
203
|
+
} catch (err) {
|
|
204
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
205
|
+
throw new StoreError(`Trailproof: read failed \u2014 ${message}`);
|
|
206
|
+
}
|
|
207
|
+
const lines = content.split("\n");
|
|
208
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
209
|
+
const line = lines[lineNum];
|
|
210
|
+
const stripped = line?.trim();
|
|
211
|
+
if (!stripped) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const data = JSON.parse(stripped);
|
|
216
|
+
if (!isValidTrailEvent(data)) {
|
|
217
|
+
this._corruptLines.push(lineNum);
|
|
218
|
+
console.warn(
|
|
219
|
+
`Trailproof: corrupt line ${lineNum} in ${this.path} \u2014 missing required fields`
|
|
220
|
+
);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
this.events.push(data);
|
|
224
|
+
} catch {
|
|
225
|
+
this._corruptLines.push(lineNum);
|
|
226
|
+
console.warn(`Trailproof: corrupt line ${lineNum} in ${this.path} \u2014 invalid JSON`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function isValidTrailEvent(data) {
|
|
232
|
+
if (typeof data !== "object" || data === null) return false;
|
|
233
|
+
const record = data;
|
|
234
|
+
return typeof record["event_id"] === "string" && typeof record["event_type"] === "string" && typeof record["timestamp"] === "string" && typeof record["actor_id"] === "string" && typeof record["tenant_id"] === "string" && typeof record["payload"] === "object" && record["payload"] !== null && typeof record["prev_hash"] === "string" && typeof record["hash"] === "string";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/stores/memory.ts
|
|
238
|
+
var DEFAULT_LIMIT2 = 100;
|
|
239
|
+
var MemoryStore = class {
|
|
240
|
+
events = [];
|
|
241
|
+
/** Append a single event to the in-memory array. */
|
|
242
|
+
append(event) {
|
|
243
|
+
this.events.push(event);
|
|
244
|
+
}
|
|
245
|
+
/** Return all events in insertion order. */
|
|
246
|
+
readAll() {
|
|
247
|
+
return [...this.events];
|
|
248
|
+
}
|
|
249
|
+
/** Query events with optional filters and cursor pagination. */
|
|
250
|
+
query(filters) {
|
|
251
|
+
let events = this.events;
|
|
252
|
+
if (filters.cursor != null) {
|
|
253
|
+
const cursorIndex = events.findIndex((e) => e.event_id === filters.cursor);
|
|
254
|
+
if (cursorIndex === -1) {
|
|
255
|
+
return { events: [], next_cursor: null };
|
|
256
|
+
}
|
|
257
|
+
events = events.slice(cursorIndex + 1);
|
|
258
|
+
}
|
|
259
|
+
if (filters.event_type != null) {
|
|
260
|
+
events = events.filter((e) => e.event_type === filters.event_type);
|
|
261
|
+
}
|
|
262
|
+
if (filters.actor_id != null) {
|
|
263
|
+
events = events.filter((e) => e.actor_id === filters.actor_id);
|
|
264
|
+
}
|
|
265
|
+
if (filters.tenant_id != null) {
|
|
266
|
+
events = events.filter((e) => e.tenant_id === filters.tenant_id);
|
|
267
|
+
}
|
|
268
|
+
if (filters.trace_id != null) {
|
|
269
|
+
events = events.filter((e) => e.trace_id === filters.trace_id);
|
|
270
|
+
}
|
|
271
|
+
if (filters.session_id != null) {
|
|
272
|
+
events = events.filter((e) => e.session_id === filters.session_id);
|
|
273
|
+
}
|
|
274
|
+
if (filters.from_time != null) {
|
|
275
|
+
const fromTime = filters.from_time;
|
|
276
|
+
events = events.filter((e) => e.timestamp >= fromTime);
|
|
277
|
+
}
|
|
278
|
+
if (filters.to_time != null) {
|
|
279
|
+
const toTime = filters.to_time;
|
|
280
|
+
events = events.filter((e) => e.timestamp <= toTime);
|
|
281
|
+
}
|
|
282
|
+
const limit = filters.limit ?? DEFAULT_LIMIT2;
|
|
283
|
+
if (events.length > limit) {
|
|
284
|
+
const cursorEvent = events[limit - 1];
|
|
285
|
+
const nextCursor = cursorEvent ? cursorEvent.event_id : null;
|
|
286
|
+
events = events.slice(0, limit);
|
|
287
|
+
return { events, next_cursor: nextCursor };
|
|
288
|
+
}
|
|
289
|
+
return { events, next_cursor: null };
|
|
290
|
+
}
|
|
291
|
+
/** Return hash of the last event, or genesis hash if empty. */
|
|
292
|
+
lastHash() {
|
|
293
|
+
if (this.events.length === 0) {
|
|
294
|
+
return GENESIS_HASH;
|
|
295
|
+
}
|
|
296
|
+
const last = this.events[this.events.length - 1];
|
|
297
|
+
return last ? last.hash : GENESIS_HASH;
|
|
298
|
+
}
|
|
299
|
+
/** Return total number of stored events. */
|
|
300
|
+
count() {
|
|
301
|
+
return this.events.length;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/trailproof.ts
|
|
306
|
+
var Trailproof = class {
|
|
307
|
+
/** @internal */
|
|
308
|
+
_store;
|
|
309
|
+
_signingKey;
|
|
310
|
+
defaultTenantId;
|
|
311
|
+
constructor(options = {}) {
|
|
312
|
+
const storeType = options.store ?? "memory";
|
|
313
|
+
this._signingKey = options.signingKey;
|
|
314
|
+
this.defaultTenantId = options.defaultTenantId;
|
|
315
|
+
if (storeType === "memory") {
|
|
316
|
+
this._store = new MemoryStore();
|
|
317
|
+
} else if (storeType === "jsonl") {
|
|
318
|
+
if (!options.path) {
|
|
319
|
+
throw new ValidationError(
|
|
320
|
+
"Trailproof: missing required parameter \u2014 path is required for jsonl store"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
this._store = new JsonlStore(options.path);
|
|
324
|
+
} else {
|
|
325
|
+
throw new ValidationError(`Trailproof: invalid store type \u2014 '${storeType}' is not supported`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Record a new event in the audit trail.
|
|
330
|
+
*
|
|
331
|
+
* Validates required fields, generates event_id and timestamp,
|
|
332
|
+
* computes the hash chain link, and appends to the store.
|
|
333
|
+
*/
|
|
334
|
+
emit(options) {
|
|
335
|
+
const tenantId = options.tenantId || this.defaultTenantId;
|
|
336
|
+
this.validateRequired("event_type", options.eventType);
|
|
337
|
+
this.validateRequired("actor_id", options.actorId);
|
|
338
|
+
this.validateRequired("tenant_id", tenantId);
|
|
339
|
+
const eventId = randomUUID();
|
|
340
|
+
const now = /* @__PURE__ */ new Date();
|
|
341
|
+
const timestamp = now.toISOString().replace(/(\.\d{3})\d*Z$/, "$1Z");
|
|
342
|
+
const prevHash = this._store.lastHash();
|
|
343
|
+
const eventForHash = {
|
|
344
|
+
event_id: eventId,
|
|
345
|
+
event_type: options.eventType,
|
|
346
|
+
timestamp,
|
|
347
|
+
actor_id: options.actorId,
|
|
348
|
+
tenant_id: tenantId,
|
|
349
|
+
payload: options.payload,
|
|
350
|
+
prev_hash: prevHash,
|
|
351
|
+
hash: ""
|
|
352
|
+
};
|
|
353
|
+
const eventHash = computeHash(prevHash, eventForHash);
|
|
354
|
+
let event = {
|
|
355
|
+
event_id: eventId,
|
|
356
|
+
event_type: options.eventType,
|
|
357
|
+
timestamp,
|
|
358
|
+
actor_id: options.actorId,
|
|
359
|
+
tenant_id: tenantId,
|
|
360
|
+
payload: options.payload,
|
|
361
|
+
prev_hash: prevHash,
|
|
362
|
+
hash: eventHash,
|
|
363
|
+
...options.traceId != null ? { trace_id: options.traceId } : {},
|
|
364
|
+
...options.sessionId != null ? { session_id: options.sessionId } : {}
|
|
365
|
+
};
|
|
366
|
+
if (this._signingKey != null) {
|
|
367
|
+
const signature = signEvent(this._signingKey, event);
|
|
368
|
+
event = { ...event, signature };
|
|
369
|
+
}
|
|
370
|
+
this._store.append(event);
|
|
371
|
+
return event;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Query events with optional filters and cursor pagination.
|
|
375
|
+
*
|
|
376
|
+
* All filter parameters are optional. No filters returns all events
|
|
377
|
+
* up to the limit.
|
|
378
|
+
*/
|
|
379
|
+
query(options = {}) {
|
|
380
|
+
const filters = {
|
|
381
|
+
event_type: options.eventType,
|
|
382
|
+
actor_id: options.actorId,
|
|
383
|
+
tenant_id: options.tenantId,
|
|
384
|
+
trace_id: options.traceId,
|
|
385
|
+
session_id: options.sessionId,
|
|
386
|
+
from_time: options.fromTime,
|
|
387
|
+
to_time: options.toTime,
|
|
388
|
+
limit: options.limit,
|
|
389
|
+
cursor: options.cursor
|
|
390
|
+
};
|
|
391
|
+
return this._store.query(filters);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Return all events with the given trace_id, ordered by timestamp.
|
|
395
|
+
*/
|
|
396
|
+
getTrace(traceId) {
|
|
397
|
+
const result = this._store.query({ trace_id: traceId, limit: 1e4 });
|
|
398
|
+
return [...result.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Verify the integrity of the entire hash chain.
|
|
402
|
+
*
|
|
403
|
+
* Walks every event and recomputes its hash. If any event's hash
|
|
404
|
+
* does not match, that index and all subsequent indices are reported
|
|
405
|
+
* as broken (cascading breaks).
|
|
406
|
+
*/
|
|
407
|
+
verify() {
|
|
408
|
+
const events = this._store.readAll();
|
|
409
|
+
const total = events.length;
|
|
410
|
+
if (total === 0) {
|
|
411
|
+
return { intact: true, total: 0, broken: [] };
|
|
412
|
+
}
|
|
413
|
+
const broken = [];
|
|
414
|
+
let prevHash = GENESIS_HASH;
|
|
415
|
+
let chainBroken = false;
|
|
416
|
+
for (let i = 0; i < events.length; i++) {
|
|
417
|
+
const event = events[i];
|
|
418
|
+
if (event == null) continue;
|
|
419
|
+
if (chainBroken) {
|
|
420
|
+
broken.push(i);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (event.signature != null && this._signingKey == null) {
|
|
424
|
+
throw new SignatureError(
|
|
425
|
+
"Trailproof: signature found but no signing key configured \u2014 cannot verify signature"
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const eventForHash = {
|
|
429
|
+
event_id: event.event_id,
|
|
430
|
+
event_type: event.event_type,
|
|
431
|
+
timestamp: event.timestamp,
|
|
432
|
+
actor_id: event.actor_id,
|
|
433
|
+
tenant_id: event.tenant_id,
|
|
434
|
+
payload: event.payload,
|
|
435
|
+
prev_hash: event.prev_hash,
|
|
436
|
+
hash: ""
|
|
437
|
+
};
|
|
438
|
+
const expectedHash = computeHash(prevHash, eventForHash);
|
|
439
|
+
if (event.hash !== expectedHash || event.prev_hash !== prevHash) {
|
|
440
|
+
broken.push(i);
|
|
441
|
+
chainBroken = true;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (this._signingKey != null && event.signature != null) {
|
|
445
|
+
try {
|
|
446
|
+
verifySignature(this._signingKey, event);
|
|
447
|
+
} catch {
|
|
448
|
+
broken.push(i);
|
|
449
|
+
chainBroken = true;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
prevHash = event.hash;
|
|
454
|
+
}
|
|
455
|
+
const intact = broken.length === 0;
|
|
456
|
+
return { intact, total, broken };
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Flush any buffered data to the underlying store.
|
|
460
|
+
*
|
|
461
|
+
* For MemoryStore this is a no-op.
|
|
462
|
+
*/
|
|
463
|
+
flush() {
|
|
464
|
+
}
|
|
465
|
+
validateRequired(fieldName, value) {
|
|
466
|
+
if (value == null || value === "") {
|
|
467
|
+
throw new ValidationError(`Trailproof: missing required field \u2014 ${fieldName} is required`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
export {
|
|
472
|
+
ChainError,
|
|
473
|
+
SignatureError,
|
|
474
|
+
StoreError,
|
|
475
|
+
Trailproof,
|
|
476
|
+
TrailproofError,
|
|
477
|
+
ValidationError
|
|
478
|
+
};
|
|
479
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/trailproof.ts","../src/chain.ts","../src/signer.ts","../src/stores/jsonl.ts","../src/stores/memory.ts"],"sourcesContent":["/**\n * Trailproof error hierarchy.\n */\n\n/** Base error for all Trailproof operations. */\nexport class TrailproofError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TrailproofError\";\n }\n}\n\n/** Raised when event data is invalid (missing or empty required fields). */\nexport class ValidationError extends TrailproofError {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\n/** Raised when a storage operation fails (disk, permissions, corruption). */\nexport class StoreError extends TrailproofError {\n constructor(message: string) {\n super(message);\n this.name = \"StoreError\";\n }\n}\n\n/** Raised when the hash chain is broken. */\nexport class ChainError extends TrailproofError {\n constructor(message: string) {\n super(message);\n this.name = \"ChainError\";\n }\n}\n\n/** Raised when HMAC signature verification fails. */\nexport class SignatureError extends TrailproofError {\n constructor(message: string) {\n super(message);\n this.name = \"SignatureError\";\n }\n}\n","/**\n * Trailproof facade class — the main public API.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { GENESIS_HASH, computeHash } from \"./chain.js\";\nimport { SignatureError, ValidationError } from \"./errors.js\";\nimport { signEvent, verifySignature } from \"./signer.js\";\nimport type { TrailStore } from \"./stores/base.js\";\nimport { JsonlStore } from \"./stores/jsonl.js\";\nimport { MemoryStore } from \"./stores/memory.js\";\nimport type { QueryFilters, QueryResult, TrailEvent, VerifyResult } from \"./types.js\";\n\n/** Options for constructing a Trailproof instance. */\nexport interface TrailproofOptions {\n /** Store type — \"memory\" (default) or \"jsonl\". */\n store?: string;\n /** File path for JSONL store. Required when store=\"jsonl\". */\n path?: string;\n /** Optional HMAC-SHA256 key for event signing. */\n signingKey?: string;\n /** Default tenant_id used when emit() is called without one. */\n defaultTenantId?: string;\n}\n\n/** Options for querying events. */\nexport interface QueryOptions {\n /** Filter by exact event type match. */\n eventType?: string;\n /** Filter by exact actor ID match. */\n actorId?: string;\n /** Filter by exact tenant ID match. */\n tenantId?: string;\n /** Filter by exact trace ID match. */\n traceId?: string;\n /** Filter by exact session ID match. */\n sessionId?: string;\n /** Include events at or after this ISO-8601 timestamp. */\n fromTime?: string;\n /** Include events at or before this ISO-8601 timestamp. */\n toTime?: string;\n /** Maximum number of events to return (default 100). */\n limit?: number;\n /** Resume pagination from this event_id. */\n cursor?: string;\n}\n\n/** Options for emitting a new event. */\nexport interface EmitOptions {\n /** Namespaced event type (e.g., \"memproof.memory.write\"). */\n eventType: string;\n /** Who performed the action. */\n actorId: string;\n /** Domain-specific data (stored opaquely). */\n payload: Record<string, unknown>;\n /** Tenant/org isolation key. Falls back to defaultTenantId. */\n tenantId?: string;\n /** Optional cross-system correlation ID. */\n traceId?: string;\n /** Optional session grouping ID. */\n sessionId?: string;\n}\n\n/**\n * Tamper-evident audit trail using hash chains and optional HMAC signing.\n *\n * The main entry point for recording, querying, and verifying events.\n */\nexport class Trailproof {\n /** @internal */\n readonly _store: TrailStore;\n private readonly _signingKey: string | undefined;\n private readonly defaultTenantId: string | undefined;\n\n constructor(options: TrailproofOptions = {}) {\n const storeType = options.store ?? \"memory\";\n this._signingKey = options.signingKey;\n this.defaultTenantId = options.defaultTenantId;\n\n if (storeType === \"memory\") {\n this._store = new MemoryStore();\n } else if (storeType === \"jsonl\") {\n if (!options.path) {\n throw new ValidationError(\n \"Trailproof: missing required parameter — path is required for jsonl store\",\n );\n }\n this._store = new JsonlStore(options.path);\n } else {\n throw new ValidationError(`Trailproof: invalid store type — '${storeType}' is not supported`);\n }\n }\n\n /**\n * Record a new event in the audit trail.\n *\n * Validates required fields, generates event_id and timestamp,\n * computes the hash chain link, and appends to the store.\n */\n emit(options: EmitOptions): TrailEvent {\n // Resolve tenant_id\n const tenantId = options.tenantId || this.defaultTenantId;\n\n // Validate required fields\n this.validateRequired(\"event_type\", options.eventType);\n this.validateRequired(\"actor_id\", options.actorId);\n this.validateRequired(\"tenant_id\", tenantId);\n\n // Generate auto fields\n const eventId = randomUUID();\n const now = new Date();\n const timestamp = now.toISOString().replace(/(\\.\\d{3})\\d*Z$/, \"$1Z\");\n\n // Get previous hash for chain linking\n const prevHash = this._store.lastHash();\n\n // Build event without hash first (hash field is placeholder)\n const eventForHash: TrailEvent = {\n event_id: eventId,\n event_type: options.eventType,\n timestamp,\n actor_id: options.actorId,\n tenant_id: tenantId as string,\n payload: options.payload,\n prev_hash: prevHash,\n hash: \"\",\n };\n\n // Compute hash\n const eventHash = computeHash(prevHash, eventForHash);\n\n // Build final event with real hash and optional fields\n let event: TrailEvent = {\n event_id: eventId,\n event_type: options.eventType,\n timestamp,\n actor_id: options.actorId,\n tenant_id: tenantId as string,\n payload: options.payload,\n prev_hash: prevHash,\n hash: eventHash,\n ...(options.traceId != null ? { trace_id: options.traceId } : {}),\n ...(options.sessionId != null ? { session_id: options.sessionId } : {}),\n };\n\n // Sign if signing key is configured\n if (this._signingKey != null) {\n const signature = signEvent(this._signingKey, event);\n event = { ...event, signature };\n }\n\n // Append to store\n this._store.append(event);\n\n return event;\n }\n\n /**\n * Query events with optional filters and cursor pagination.\n *\n * All filter parameters are optional. No filters returns all events\n * up to the limit.\n */\n query(options: QueryOptions = {}): QueryResult {\n const filters: QueryFilters = {\n event_type: options.eventType,\n actor_id: options.actorId,\n tenant_id: options.tenantId,\n trace_id: options.traceId,\n session_id: options.sessionId,\n from_time: options.fromTime,\n to_time: options.toTime,\n limit: options.limit,\n cursor: options.cursor,\n };\n return this._store.query(filters);\n }\n\n /**\n * Return all events with the given trace_id, ordered by timestamp.\n */\n getTrace(traceId: string): TrailEvent[] {\n const result = this._store.query({ trace_id: traceId, limit: 10_000 });\n return [...result.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n }\n\n /**\n * Verify the integrity of the entire hash chain.\n *\n * Walks every event and recomputes its hash. If any event's hash\n * does not match, that index and all subsequent indices are reported\n * as broken (cascading breaks).\n */\n verify(): VerifyResult {\n const events = this._store.readAll();\n const total = events.length;\n\n if (total === 0) {\n return { intact: true, total: 0, broken: [] };\n }\n\n const broken: number[] = [];\n let prevHash = GENESIS_HASH;\n let chainBroken = false;\n\n for (let i = 0; i < events.length; i++) {\n const event = events[i];\n if (event == null) continue;\n\n if (chainBroken) {\n broken.push(i);\n continue;\n }\n\n // Check for signature without key\n if (event.signature != null && this._signingKey == null) {\n throw new SignatureError(\n \"Trailproof: signature found but no signing key configured — cannot verify signature\",\n );\n }\n\n // Recompute hash using event with hash=\"\" (as during emit)\n const eventForHash: TrailEvent = {\n event_id: event.event_id,\n event_type: event.event_type,\n timestamp: event.timestamp,\n actor_id: event.actor_id,\n tenant_id: event.tenant_id,\n payload: event.payload,\n prev_hash: event.prev_hash,\n hash: \"\",\n };\n const expectedHash = computeHash(prevHash, eventForHash);\n\n if (event.hash !== expectedHash || event.prev_hash !== prevHash) {\n broken.push(i);\n chainBroken = true;\n continue;\n }\n\n // Verify signature if signing key is configured\n if (this._signingKey != null && event.signature != null) {\n try {\n verifySignature(this._signingKey, event);\n } catch {\n broken.push(i);\n chainBroken = true;\n continue;\n }\n }\n\n prevHash = event.hash;\n }\n\n const intact = broken.length === 0;\n return { intact, total, broken };\n }\n\n /**\n * Flush any buffered data to the underlying store.\n *\n * For MemoryStore this is a no-op.\n */\n flush(): void {\n // MemoryStore has no buffering; JSONL store will override if needed\n }\n\n private validateRequired(fieldName: string, value: string | undefined | null): void {\n if (value == null || value === \"\") {\n throw new ValidationError(`Trailproof: missing required field — ${fieldName} is required`);\n }\n }\n}\n","/**\n * Hash chain engine with canonical JSON serialization.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { TrailEvent } from \"./types.js\";\n\n/** Genesis hash: 64 zero characters. */\nexport const GENESIS_HASH = \"0\".repeat(64);\n\nconst EXCLUDED_FIELDS = new Set([\"hash\", \"signature\"]);\n\n/**\n * Serialize an event to canonical JSON for hashing.\n *\n * Produces deterministic output:\n * - Keys sorted alphabetically (recursive for nested objects)\n * - Compact format: no whitespace\n * - Excludes \"hash\" and \"signature\" fields\n * - Excludes null/undefined fields\n * - UTF-8 encoding\n */\nexport function canonicalJson(event: TrailEvent): string {\n const raw: Record<string, unknown> = { ...event };\n const cleaned = stripExcluded(raw);\n return stableStringify(cleaned);\n}\n\n/**\n * Compute the SHA-256 hash for an event in the chain.\n *\n * hash = SHA-256(prev_hash + canonicalJson(event))\n */\nexport function computeHash(prevHash: string, event: TrailEvent): string {\n const payload = prevHash + canonicalJson(event);\n return createHash(\"sha256\").update(payload, \"utf-8\").digest(\"hex\");\n}\n\n/** Remove excluded fields and null/undefined values, recursing into nested objects. */\nfunction stripExcluded(data: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(data)) {\n if (EXCLUDED_FIELDS.has(key)) continue;\n const value = data[key];\n if (value === null || value === undefined) continue;\n if (typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = stripExcluded(value as Record<string, unknown>);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/** JSON.stringify with sorted keys (recursive), compact format. */\nfunction stableStringify(obj: unknown): string {\n if (obj === null || obj === undefined) return \"null\";\n if (typeof obj === \"string\") return JSON.stringify(obj);\n if (typeof obj === \"number\" || typeof obj === \"boolean\") return String(obj);\n if (Array.isArray(obj)) {\n return \"[\" + obj.map((item) => stableStringify(item)).join(\",\") + \"]\";\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n const pairs = keys.map((k) => JSON.stringify(k) + \":\" + stableStringify(record[k]));\n return \"{\" + pairs.join(\",\") + \"}\";\n }\n return String(obj);\n}\n","/**\n * Optional HMAC-SHA256 event signing and verification.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { canonicalJson } from \"./chain.js\";\nimport { SignatureError } from \"./errors.js\";\nimport type { TrailEvent } from \"./types.js\";\n\nexport const SIGNATURE_PREFIX = \"hmac-sha256:\";\n\n/**\n * Compute an HMAC-SHA256 signature for an event.\n *\n * @param key - The HMAC signing key.\n * @param event - The trail event to sign.\n * @returns Signature string in format \"hmac-sha256:<hex>\".\n */\nexport function signEvent(key: string, event: TrailEvent): string {\n const canonical = canonicalJson(event);\n const mac = createHmac(\"sha256\", key).update(canonical, \"utf-8\").digest(\"hex\");\n return SIGNATURE_PREFIX + mac;\n}\n\n/**\n * Verify the HMAC-SHA256 signature on an event.\n *\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @param key - The HMAC signing key.\n * @param event - The trail event with a signature to verify.\n * @throws SignatureError if the event has no signature or the signature doesn't match.\n */\nexport function verifySignature(key: string, event: TrailEvent): void {\n if (event.signature == null) {\n throw new SignatureError(\"Trailproof: missing signature — event has no signature field\");\n }\n\n if (!event.signature.startsWith(SIGNATURE_PREFIX)) {\n throw new SignatureError(\n \"Trailproof: invalid signature format — expected 'hmac-sha256:' prefix\",\n );\n }\n\n const expected = signEvent(key, event);\n const actualBuf = Buffer.from(event.signature, \"utf-8\");\n const expectedBuf = Buffer.from(expected, \"utf-8\");\n\n if (actualBuf.length !== expectedBuf.length || !timingSafeEqual(actualBuf, expectedBuf)) {\n throw new SignatureError(\"Trailproof: signature mismatch — HMAC verification failed\");\n }\n}\n","/**\n * Append-only JSONL file store for trail events.\n */\n\nimport { appendFileSync, closeSync, existsSync, openSync, readFileSync, writeSync } from \"node:fs\";\nimport { GENESIS_HASH } from \"../chain.js\";\nimport { StoreError } from \"../errors.js\";\nimport type { QueryFilters, QueryResult, TrailEvent } from \"../types.js\";\nimport type { TrailStore } from \"./base.js\";\n\nconst DEFAULT_LIMIT = 100;\nconst FILE_PERMISSIONS = 0o600;\n\n/**\n * Persistent store that writes one JSON object per line to a JSONL file.\n *\n * Append-only. File is created with 0o600 permissions on first write.\n * On init, reads any existing file to recover last_hash and count.\n * Corrupt lines are skipped with a warning.\n */\nexport class JsonlStore implements TrailStore {\n private readonly path: string;\n private events: TrailEvent[] = [];\n private _corruptLines: number[] = [];\n\n constructor(path: string) {\n this.path = path;\n this.loadExisting();\n }\n\n /** Append a single event to the JSONL file. */\n append(event: TrailEvent): void {\n const line = JSON.stringify(event) + \"\\n\";\n try {\n if (!existsSync(this.path)) {\n const fd = openSync(this.path, \"wx\", FILE_PERMISSIONS);\n writeSync(fd, line, undefined, \"utf-8\");\n closeSync(fd);\n } else {\n appendFileSync(this.path, line, { encoding: \"utf-8\" });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new StoreError(`Trailproof: write failed — ${message}`);\n }\n this.events.push(event);\n }\n\n /** Return all events in insertion order. */\n readAll(): TrailEvent[] {\n return [...this.events];\n }\n\n /** Query events with optional filters and cursor pagination. */\n query(filters: QueryFilters): QueryResult {\n let events: TrailEvent[] = this.events;\n\n // Apply cursor: skip events up to and including the cursor event_id\n if (filters.cursor != null) {\n const cursorIndex = events.findIndex((e) => e.event_id === filters.cursor);\n if (cursorIndex === -1) {\n return { events: [], next_cursor: null };\n }\n events = events.slice(cursorIndex + 1);\n }\n\n // Apply filters\n if (filters.event_type != null) {\n events = events.filter((e) => e.event_type === filters.event_type);\n }\n if (filters.actor_id != null) {\n events = events.filter((e) => e.actor_id === filters.actor_id);\n }\n if (filters.tenant_id != null) {\n events = events.filter((e) => e.tenant_id === filters.tenant_id);\n }\n if (filters.trace_id != null) {\n events = events.filter((e) => e.trace_id === filters.trace_id);\n }\n if (filters.session_id != null) {\n events = events.filter((e) => e.session_id === filters.session_id);\n }\n if (filters.from_time != null) {\n const fromTime = filters.from_time;\n events = events.filter((e) => e.timestamp >= fromTime);\n }\n if (filters.to_time != null) {\n const toTime = filters.to_time;\n events = events.filter((e) => e.timestamp <= toTime);\n }\n\n // Apply limit and determine next_cursor\n const limit = filters.limit ?? DEFAULT_LIMIT;\n if (events.length > limit) {\n const cursorEvent = events[limit - 1];\n const nextCursor = cursorEvent ? cursorEvent.event_id : null;\n events = events.slice(0, limit);\n return { events, next_cursor: nextCursor };\n }\n\n return { events, next_cursor: null };\n }\n\n /** Return hash of the last event, or genesis hash if empty. */\n lastHash(): string {\n if (this.events.length === 0) {\n return GENESIS_HASH;\n }\n const last = this.events[this.events.length - 1];\n return last ? last.hash : GENESIS_HASH;\n }\n\n /** Return total number of stored events. */\n count(): number {\n return this.events.length;\n }\n\n /** Return indices of corrupt lines found during file load. */\n get corruptLines(): number[] {\n return [...this._corruptLines];\n }\n\n /** Load events from an existing JSONL file. */\n private loadExisting(): void {\n if (!existsSync(this.path)) {\n return;\n }\n\n let content: string;\n try {\n content = readFileSync(this.path, { encoding: \"utf-8\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new StoreError(`Trailproof: read failed — ${message}`);\n }\n\n const lines = content.split(\"\\n\");\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum];\n const stripped = line?.trim();\n if (!stripped) {\n continue;\n }\n\n try {\n const data: unknown = JSON.parse(stripped);\n if (!isValidTrailEvent(data)) {\n this._corruptLines.push(lineNum);\n console.warn(\n `Trailproof: corrupt line ${lineNum} in ${this.path} — missing required fields`,\n );\n continue;\n }\n this.events.push(data);\n } catch {\n this._corruptLines.push(lineNum);\n console.warn(`Trailproof: corrupt line ${lineNum} in ${this.path} — invalid JSON`);\n }\n }\n }\n}\n\n/** Check that a parsed JSON object has all required TrailEvent fields. */\nfunction isValidTrailEvent(data: unknown): data is TrailEvent {\n if (typeof data !== \"object\" || data === null) return false;\n const record = data as Record<string, unknown>;\n return (\n typeof record[\"event_id\"] === \"string\" &&\n typeof record[\"event_type\"] === \"string\" &&\n typeof record[\"timestamp\"] === \"string\" &&\n typeof record[\"actor_id\"] === \"string\" &&\n typeof record[\"tenant_id\"] === \"string\" &&\n typeof record[\"payload\"] === \"object\" &&\n record[\"payload\"] !== null &&\n typeof record[\"prev_hash\"] === \"string\" &&\n typeof record[\"hash\"] === \"string\"\n );\n}\n","/**\n * In-memory trail event store.\n */\n\nimport { GENESIS_HASH } from \"../chain.js\";\nimport type { QueryFilters, QueryResult, TrailEvent } from \"../types.js\";\nimport type { TrailStore } from \"./base.js\";\n\nconst DEFAULT_LIMIT = 100;\n\n/**\n * In-memory store that keeps events in an array.\n *\n * Events are lost when the process exits. Useful for testing\n * and short-lived applications.\n */\nexport class MemoryStore implements TrailStore {\n private events: TrailEvent[] = [];\n\n /** Append a single event to the in-memory array. */\n append(event: TrailEvent): void {\n this.events.push(event);\n }\n\n /** Return all events in insertion order. */\n readAll(): TrailEvent[] {\n return [...this.events];\n }\n\n /** Query events with optional filters and cursor pagination. */\n query(filters: QueryFilters): QueryResult {\n let events: TrailEvent[] = this.events;\n\n // Apply cursor: skip events up to and including the cursor event_id\n if (filters.cursor != null) {\n const cursorIndex = events.findIndex((e) => e.event_id === filters.cursor);\n if (cursorIndex === -1) {\n return { events: [], next_cursor: null };\n }\n events = events.slice(cursorIndex + 1);\n }\n\n // Apply filters\n if (filters.event_type != null) {\n events = events.filter((e) => e.event_type === filters.event_type);\n }\n if (filters.actor_id != null) {\n events = events.filter((e) => e.actor_id === filters.actor_id);\n }\n if (filters.tenant_id != null) {\n events = events.filter((e) => e.tenant_id === filters.tenant_id);\n }\n if (filters.trace_id != null) {\n events = events.filter((e) => e.trace_id === filters.trace_id);\n }\n if (filters.session_id != null) {\n events = events.filter((e) => e.session_id === filters.session_id);\n }\n if (filters.from_time != null) {\n const fromTime = filters.from_time;\n events = events.filter((e) => e.timestamp >= fromTime);\n }\n if (filters.to_time != null) {\n const toTime = filters.to_time;\n events = events.filter((e) => e.timestamp <= toTime);\n }\n\n // Apply limit and determine next_cursor\n const limit = filters.limit ?? DEFAULT_LIMIT;\n if (events.length > limit) {\n const cursorEvent = events[limit - 1];\n const nextCursor = cursorEvent ? cursorEvent.event_id : null;\n events = events.slice(0, limit);\n return { events, next_cursor: nextCursor };\n }\n\n return { events, next_cursor: null };\n }\n\n /** Return hash of the last event, or genesis hash if empty. */\n lastHash(): string {\n if (this.events.length === 0) {\n return GENESIS_HASH;\n }\n const last = this.events[this.events.length - 1];\n return last ? last.hash : GENESIS_HASH;\n }\n\n /** Return total number of stored events. */\n count(): number {\n return this.events.length;\n }\n}\n"],"mappings":"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","names":["DEFAULT_LIMIT"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kyberonai/trailproof",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tamper-evident audit trail library using hash chains and optional HMAC signing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"lint": "eslint src/ tests/ && prettier --check src/ tests/",
|
|
29
|
+
"lint:fix": "eslint --fix src/ tests/ && prettier --write src/ tests/",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"all": "npm run lint && npm run typecheck && npm run test"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.0.0",
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"eslint": "^9.0.0",
|
|
37
|
+
"prettier": "^3.0.0",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.5.0",
|
|
40
|
+
"typescript-eslint": "^8.0.0",
|
|
41
|
+
"vitest": "^2.0.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"license": "Apache-2.0",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/KyberonAi/trailproof"
|
|
50
|
+
}
|
|
51
|
+
}
|