@lovelaces-io/storyteller 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/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/index.cjs +515 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
var ANSI = {
|
|
3
|
+
reset: "\x1B[0m",
|
|
4
|
+
green: "\x1B[32m",
|
|
5
|
+
yellow: "\x1B[33m",
|
|
6
|
+
red: "\x1B[38;2;250;128;114m",
|
|
7
|
+
grayLight: "\x1B[37m",
|
|
8
|
+
grayDark: "\x1B[37m"
|
|
9
|
+
};
|
|
10
|
+
function getLevelColor(level) {
|
|
11
|
+
if (level === "tell") return ANSI.green;
|
|
12
|
+
if (level === "warn") return ANSI.yellow;
|
|
13
|
+
return ANSI.red;
|
|
14
|
+
}
|
|
15
|
+
function formatOrigin(origin) {
|
|
16
|
+
if (!origin?.where) return;
|
|
17
|
+
if (typeof origin.where === "string") return origin.where;
|
|
18
|
+
const whereRecord = origin.where;
|
|
19
|
+
const parts = [whereRecord.app, whereRecord.service, whereRecord.page, whereRecord.component].filter(Boolean).map(String);
|
|
20
|
+
return parts.length ? parts.join(" / ") : void 0;
|
|
21
|
+
}
|
|
22
|
+
function colorizeJsonSections(json, colors) {
|
|
23
|
+
const lines = json.split("\n");
|
|
24
|
+
let insideNotes = false;
|
|
25
|
+
let bracketDepth = 0;
|
|
26
|
+
return lines.map((line) => {
|
|
27
|
+
if (!insideNotes && line.includes('"notes": [')) {
|
|
28
|
+
insideNotes = true;
|
|
29
|
+
bracketDepth = countBrackets(line);
|
|
30
|
+
return `${colors.notes}${line}${colors.reset}`;
|
|
31
|
+
}
|
|
32
|
+
if (insideNotes) {
|
|
33
|
+
const colored = `${colors.notes}${line}${colors.reset}`;
|
|
34
|
+
bracketDepth += countBrackets(line);
|
|
35
|
+
if (bracketDepth <= 0) insideNotes = false;
|
|
36
|
+
return colored;
|
|
37
|
+
}
|
|
38
|
+
return `${colors.base}${line}${colors.reset}`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function countBrackets(line) {
|
|
42
|
+
const openCount = (line.match(/\[/g) || []).length;
|
|
43
|
+
const closeCount = (line.match(/\]/g) || []).length;
|
|
44
|
+
return openCount - closeCount;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/audiences/consoleAudience.ts
|
|
48
|
+
function consoleAudience() {
|
|
49
|
+
return {
|
|
50
|
+
name: "console",
|
|
51
|
+
hear: (event) => {
|
|
52
|
+
const prefix = "Storyteller";
|
|
53
|
+
const style = event.level === "tell" ? "color:#16a34a;font-weight:600" : event.level === "warn" ? "color:#f59e0b;font-weight:600" : "color:#dc2626;font-weight:600";
|
|
54
|
+
const header = `${prefix}: ${event.title}`;
|
|
55
|
+
console.groupCollapsed(`%c${header}`, style);
|
|
56
|
+
const payload = JSON.stringify(event, null, 2);
|
|
57
|
+
const coloredPayload = event.level === "oops" ? `${ANSI.red}${payload}${ANSI.reset}` : payload;
|
|
58
|
+
if (event.level === "tell") {
|
|
59
|
+
console.log(header, payload);
|
|
60
|
+
} else if (event.level === "warn") {
|
|
61
|
+
console.warn(header, payload);
|
|
62
|
+
} else {
|
|
63
|
+
console.error(header, coloredPayload);
|
|
64
|
+
}
|
|
65
|
+
console.groupEnd();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/storyteller.ts
|
|
71
|
+
var AudienceRegistry = class {
|
|
72
|
+
members = /* @__PURE__ */ new Map();
|
|
73
|
+
/** Register an audience member, replacing any existing member with the same name */
|
|
74
|
+
add(member) {
|
|
75
|
+
this.members.set(member.name, member);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
/** Remove an audience member by name */
|
|
79
|
+
remove(name) {
|
|
80
|
+
this.members.delete(name);
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/** Return all registered audience members */
|
|
84
|
+
getAll() {
|
|
85
|
+
return [...this.members.values()];
|
|
86
|
+
}
|
|
87
|
+
/** Return only the audience members matching the given names */
|
|
88
|
+
getOnly(names) {
|
|
89
|
+
return names.map((name) => this.members.get(name)).filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var Storyteller = class {
|
|
93
|
+
audience = new AudienceRegistry();
|
|
94
|
+
origin;
|
|
95
|
+
notes = [];
|
|
96
|
+
constructor(options) {
|
|
97
|
+
this.origin = options?.origin;
|
|
98
|
+
this.audience.add(consoleAudience());
|
|
99
|
+
options?.audiences?.forEach((audience) => this.audience.add(audience));
|
|
100
|
+
}
|
|
101
|
+
/** Add a timestamped note with optional context (who, what, where, error) */
|
|
102
|
+
note(text, data = {}) {
|
|
103
|
+
this.notes.push({
|
|
104
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
+
note: text,
|
|
106
|
+
...data.who ? { who: data.who } : {},
|
|
107
|
+
...data.what ? { what: data.what } : {},
|
|
108
|
+
...data.where ? { where: data.where } : {},
|
|
109
|
+
...data.error ? { error: normalizeError(data.error) } : {}
|
|
110
|
+
});
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/** Clear all accumulated notes without emitting a story */
|
|
114
|
+
reset() {
|
|
115
|
+
this.notes = [];
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
/** Generate a formatted summary of current notes without emitting or clearing them */
|
|
119
|
+
summarize(options = {}) {
|
|
120
|
+
const {
|
|
121
|
+
title = "Story preview",
|
|
122
|
+
level = "tell",
|
|
123
|
+
error,
|
|
124
|
+
...summaryOptions
|
|
125
|
+
} = options;
|
|
126
|
+
const event = {
|
|
127
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
128
|
+
level,
|
|
129
|
+
title,
|
|
130
|
+
...this.origin ? { origin: this.origin } : {},
|
|
131
|
+
notes: [...this.notes],
|
|
132
|
+
...error ? { error: normalizeError(error) } : {}
|
|
133
|
+
};
|
|
134
|
+
return summarizeStory(event, summaryOptions);
|
|
135
|
+
}
|
|
136
|
+
/** Emit a story at the "tell" level (success / informational) */
|
|
137
|
+
tell(title) {
|
|
138
|
+
return this.createDelivery("tell", title);
|
|
139
|
+
}
|
|
140
|
+
/** Emit a story at the "warn" level (something was off) */
|
|
141
|
+
warn(title) {
|
|
142
|
+
return this.createDelivery("warn", title);
|
|
143
|
+
}
|
|
144
|
+
/** Emit a story at the "oops" level (something broke) with an optional error */
|
|
145
|
+
oops(title, error) {
|
|
146
|
+
return this.createDelivery("oops", title, error);
|
|
147
|
+
}
|
|
148
|
+
/** Build a story event and schedule delivery, returning a handle to override the audience list */
|
|
149
|
+
createDelivery(level, title, error) {
|
|
150
|
+
const event = this.buildEvent(level, title, error);
|
|
151
|
+
let delivered = false;
|
|
152
|
+
let defaultCancelled = false;
|
|
153
|
+
queueMicrotask(() => {
|
|
154
|
+
if (delivered || defaultCancelled) return;
|
|
155
|
+
delivered = true;
|
|
156
|
+
void this.deliver(event);
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
to: (...names) => {
|
|
160
|
+
defaultCancelled = true;
|
|
161
|
+
if (delivered) return;
|
|
162
|
+
delivered = true;
|
|
163
|
+
void this.deliver(event, { only: names });
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Assemble the story event from current notes and clear notes for the next story */
|
|
168
|
+
buildEvent(level, title, error) {
|
|
169
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
170
|
+
const collectedNotes = [...this.notes];
|
|
171
|
+
this.notes = [];
|
|
172
|
+
const event = {
|
|
173
|
+
timestamp: now,
|
|
174
|
+
level,
|
|
175
|
+
title,
|
|
176
|
+
...this.origin ? { origin: this.origin } : {},
|
|
177
|
+
notes: collectedNotes,
|
|
178
|
+
...error ? { error: normalizeError(error) } : {}
|
|
179
|
+
};
|
|
180
|
+
const eventWithSummary = event;
|
|
181
|
+
Object.defineProperty(eventWithSummary, "summarize", {
|
|
182
|
+
value: (options) => summarizeStory(event, options),
|
|
183
|
+
enumerable: false
|
|
184
|
+
});
|
|
185
|
+
return eventWithSummary;
|
|
186
|
+
}
|
|
187
|
+
/** Deliver a story event to matching audience members */
|
|
188
|
+
async deliver(event, options) {
|
|
189
|
+
const targets = options?.only?.length ? this.audience.getOnly(options.only) : this.audience.getAll();
|
|
190
|
+
await Promise.allSettled(
|
|
191
|
+
targets.filter((member) => member.accepts ? member.accepts(event) : true).map((member) => member.hear(event))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
function normalizeError(rawError) {
|
|
196
|
+
if (rawError instanceof Error) {
|
|
197
|
+
const normalized = {
|
|
198
|
+
name: rawError.name,
|
|
199
|
+
message: rawError.message
|
|
200
|
+
};
|
|
201
|
+
if (rawError.stack !== void 0) {
|
|
202
|
+
normalized.stack = rawError.stack;
|
|
203
|
+
}
|
|
204
|
+
const cause = rawError.cause;
|
|
205
|
+
if (cause !== void 0) {
|
|
206
|
+
normalized.cause = cause;
|
|
207
|
+
}
|
|
208
|
+
return normalized;
|
|
209
|
+
}
|
|
210
|
+
return { message: String(rawError) };
|
|
211
|
+
}
|
|
212
|
+
function calculateNoteDuration(notes) {
|
|
213
|
+
if (notes.length <= 1) {
|
|
214
|
+
return {
|
|
215
|
+
durationMs: void 0
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const startTime = Date.parse(notes[0].timestamp);
|
|
219
|
+
const endTime = Date.parse(notes[notes.length - 1].timestamp);
|
|
220
|
+
return {
|
|
221
|
+
durationMs: Number.isFinite(startTime) && Number.isFinite(endTime) ? Math.max(0, endTime - startTime) : void 0
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function summarizeStory(story, options = {}) {
|
|
225
|
+
const {
|
|
226
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
227
|
+
locale = "en-US",
|
|
228
|
+
verbosity = "normal",
|
|
229
|
+
maxNotes = 50,
|
|
230
|
+
showData = true,
|
|
231
|
+
colorize = true
|
|
232
|
+
} = options;
|
|
233
|
+
const dateTimeFormatter = new Intl.DateTimeFormat(locale, {
|
|
234
|
+
timeZone: timezone,
|
|
235
|
+
year: "numeric",
|
|
236
|
+
month: "short",
|
|
237
|
+
day: "2-digit",
|
|
238
|
+
hour: "numeric",
|
|
239
|
+
minute: "2-digit",
|
|
240
|
+
second: "2-digit"
|
|
241
|
+
});
|
|
242
|
+
const timeFormatter = new Intl.DateTimeFormat(locale, {
|
|
243
|
+
timeZone: timezone,
|
|
244
|
+
hour: "numeric",
|
|
245
|
+
minute: "2-digit",
|
|
246
|
+
second: "2-digit"
|
|
247
|
+
});
|
|
248
|
+
const orderedNotes = [...story.notes].sort(
|
|
249
|
+
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)
|
|
250
|
+
);
|
|
251
|
+
const noteTiming = calculateNoteDuration(orderedNotes);
|
|
252
|
+
const originLabel = formatOrigin(story.origin);
|
|
253
|
+
const duration = noteTiming.durationMs != null ? formatDuration(noteTiming.durationMs) : void 0;
|
|
254
|
+
const slicedNotes = orderedNotes.slice(0, maxNotes);
|
|
255
|
+
const summaryNotes = slicedNotes.map((note) => ({
|
|
256
|
+
timestamp: note.timestamp,
|
|
257
|
+
when: timeFormatter.format(new Date(note.timestamp)),
|
|
258
|
+
note: note.note,
|
|
259
|
+
text: formatNoteText(note, verbosity),
|
|
260
|
+
...note.who ? { who: note.who } : {},
|
|
261
|
+
...note.what ? { what: note.what } : {},
|
|
262
|
+
...note.where ? { where: note.where } : {},
|
|
263
|
+
...note.error ? { error: note.error } : {}
|
|
264
|
+
}));
|
|
265
|
+
const data = {
|
|
266
|
+
title: story.title,
|
|
267
|
+
level: story.level,
|
|
268
|
+
when: dateTimeFormatter.format(new Date(story.timestamp)),
|
|
269
|
+
...noteTiming.durationMs != null ? { durationMs: noteTiming.durationMs } : {},
|
|
270
|
+
...duration ? { duration } : {},
|
|
271
|
+
...story.origin ? { origin: story.origin } : {},
|
|
272
|
+
notes: summaryNotes,
|
|
273
|
+
...story.error ? { error: story.error } : {}
|
|
274
|
+
};
|
|
275
|
+
const levelColor = getLevelColor(story.level);
|
|
276
|
+
const label = (text) => colorize ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
277
|
+
const lines = [];
|
|
278
|
+
lines.push(`${label("Story")}: ${story.title}`);
|
|
279
|
+
lines.push(`${label("Level")}: ${story.level}`);
|
|
280
|
+
lines.push(`${label("Time")}: ${data.when}${duration ? ` (${duration})` : ""}`);
|
|
281
|
+
if (originLabel) {
|
|
282
|
+
lines.push(`${label("Origin")}: ${originLabel}`);
|
|
283
|
+
}
|
|
284
|
+
if (story.error) {
|
|
285
|
+
const errorLine = [story.error.name, story.error.message].filter(Boolean).join(": ");
|
|
286
|
+
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
287
|
+
}
|
|
288
|
+
if (verbosity !== "brief" && summaryNotes.length) {
|
|
289
|
+
lines.push(`${label("Notes")}:`);
|
|
290
|
+
for (const note of summaryNotes) {
|
|
291
|
+
lines.push(` ${note.when} \u2014 ${note.text}`);
|
|
292
|
+
}
|
|
293
|
+
if (orderedNotes.length > summaryNotes.length) {
|
|
294
|
+
lines.push(` \u2026 (${orderedNotes.length - summaryNotes.length} more)`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (showData) {
|
|
298
|
+
lines.push(`${label("Data")}:`);
|
|
299
|
+
const json = JSON.stringify(data, null, 2);
|
|
300
|
+
if (colorize) {
|
|
301
|
+
const colored = colorizeJsonSections(json, {
|
|
302
|
+
base: ANSI.grayLight,
|
|
303
|
+
notes: ANSI.grayDark,
|
|
304
|
+
reset: ANSI.reset
|
|
305
|
+
});
|
|
306
|
+
lines.push(...colored);
|
|
307
|
+
} else {
|
|
308
|
+
lines.push(...json.split("\n"));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { text: lines.join("\n"), data };
|
|
312
|
+
}
|
|
313
|
+
function formatDuration(milliseconds) {
|
|
314
|
+
if (milliseconds < 1e3) return `${milliseconds}ms`;
|
|
315
|
+
const seconds = milliseconds / 1e3;
|
|
316
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
317
|
+
const minutes = Math.floor(seconds / 60);
|
|
318
|
+
const remainingSeconds = Math.round(seconds % 60).toString().padStart(2, "0");
|
|
319
|
+
return `${minutes}:${remainingSeconds}m`;
|
|
320
|
+
}
|
|
321
|
+
function formatNoteText(note, verbosity) {
|
|
322
|
+
if (verbosity !== "full") return note.note;
|
|
323
|
+
const details = [];
|
|
324
|
+
const what = note.what;
|
|
325
|
+
const where = note.where;
|
|
326
|
+
if (typeof what === "string") {
|
|
327
|
+
details.push(`what=${what}`);
|
|
328
|
+
} else if (what) {
|
|
329
|
+
if (what.field) details.push(`field=${String(what.field)}`);
|
|
330
|
+
if (what.status) details.push(`status=${String(what.status)}`);
|
|
331
|
+
}
|
|
332
|
+
if (typeof where === "string") {
|
|
333
|
+
details.push(`where=${where}`);
|
|
334
|
+
} else if (where) {
|
|
335
|
+
if (where.component) details.push(`component=${String(where.component)}`);
|
|
336
|
+
}
|
|
337
|
+
if (note.error) {
|
|
338
|
+
const errorLine = [note.error.name, note.error.message].filter(Boolean).join(": ");
|
|
339
|
+
if (errorLine) details.push(`error=${errorLine}`);
|
|
340
|
+
}
|
|
341
|
+
return details.length ? `${note.note} (${details.join(" ")})` : note.note;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/useStoryteller.ts
|
|
345
|
+
var sharedInstance;
|
|
346
|
+
function useStoryteller(options = {}) {
|
|
347
|
+
if (!sharedInstance || options.reset) {
|
|
348
|
+
sharedInstance = new Storyteller({ origin: options.origin });
|
|
349
|
+
return sharedInstance;
|
|
350
|
+
}
|
|
351
|
+
return sharedInstance;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/audiences/dbAudience.ts
|
|
355
|
+
function dbAudience(insert) {
|
|
356
|
+
return {
|
|
357
|
+
name: "db",
|
|
358
|
+
accepts: (event) => event.level === "warn" || event.level === "oops",
|
|
359
|
+
hear: async (event) => {
|
|
360
|
+
await insert(event);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/report/writeStoryReport.ts
|
|
366
|
+
function writeStoryReport(stories, options = {}) {
|
|
367
|
+
const {
|
|
368
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
369
|
+
locale = "en-US",
|
|
370
|
+
verbosity = "normal",
|
|
371
|
+
maxNotesPerStory = 50,
|
|
372
|
+
showData = true,
|
|
373
|
+
colorize = true
|
|
374
|
+
} = options;
|
|
375
|
+
if (!stories.length) {
|
|
376
|
+
return "Storyteller Report\n\n(no stories)\n";
|
|
377
|
+
}
|
|
378
|
+
const sorted = [...stories].sort(
|
|
379
|
+
(storyA, storyB) => Date.parse(storyA.timestamp) - Date.parse(storyB.timestamp)
|
|
380
|
+
);
|
|
381
|
+
const dateFormatter = new Intl.DateTimeFormat(locale, {
|
|
382
|
+
timeZone: timezone,
|
|
383
|
+
year: "numeric",
|
|
384
|
+
month: "short",
|
|
385
|
+
day: "2-digit"
|
|
386
|
+
});
|
|
387
|
+
const firstStory = sorted[0];
|
|
388
|
+
const lastStory = sorted[sorted.length - 1];
|
|
389
|
+
if (!firstStory || !lastStory) {
|
|
390
|
+
return "Storyteller Report\n\n(no stories)\n";
|
|
391
|
+
}
|
|
392
|
+
const lines = [];
|
|
393
|
+
lines.push(`Storyteller Report (${timezone})`);
|
|
394
|
+
lines.push(
|
|
395
|
+
`Range: ${dateFormatter.format(new Date(firstStory.timestamp))} \u2013 ${dateFormatter.format(
|
|
396
|
+
new Date(lastStory.timestamp)
|
|
397
|
+
)}`
|
|
398
|
+
);
|
|
399
|
+
lines.push("");
|
|
400
|
+
const storiesByDay = /* @__PURE__ */ new Map();
|
|
401
|
+
for (const story of sorted) {
|
|
402
|
+
const dayKey = dateFormatter.format(new Date(story.timestamp));
|
|
403
|
+
const dayEvents = storiesByDay.get(dayKey) ?? [];
|
|
404
|
+
dayEvents.push(story);
|
|
405
|
+
storiesByDay.set(dayKey, dayEvents);
|
|
406
|
+
}
|
|
407
|
+
for (const [day, dayStories] of storiesByDay) {
|
|
408
|
+
lines.push(day);
|
|
409
|
+
for (const story of dayStories) {
|
|
410
|
+
const summary = summarizeStory(story, {
|
|
411
|
+
timezone,
|
|
412
|
+
locale,
|
|
413
|
+
verbosity,
|
|
414
|
+
maxNotes: maxNotesPerStory,
|
|
415
|
+
colorize
|
|
416
|
+
});
|
|
417
|
+
const { data } = summary;
|
|
418
|
+
const originLabel = formatOrigin(story.origin);
|
|
419
|
+
const levelColor = getLevelColor(story.level);
|
|
420
|
+
const label = (text) => colorize ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
421
|
+
const duration = data.duration ? ` (${data.duration})` : "";
|
|
422
|
+
lines.push(`${label("Story")}: ${story.title}`);
|
|
423
|
+
lines.push(`${label("Level")}: ${story.level}`);
|
|
424
|
+
lines.push(`${label("Time")}: ${data.when}${duration}`);
|
|
425
|
+
if (originLabel) {
|
|
426
|
+
lines.push(`${label("Origin")}: ${originLabel}`);
|
|
427
|
+
}
|
|
428
|
+
if (data.error) {
|
|
429
|
+
const errorLine = [
|
|
430
|
+
data.error.name,
|
|
431
|
+
data.error.message
|
|
432
|
+
].filter(Boolean).join(": ");
|
|
433
|
+
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
434
|
+
}
|
|
435
|
+
if (verbosity !== "brief" && data.notes.length) {
|
|
436
|
+
lines.push(` ${label("Notes")}:`);
|
|
437
|
+
for (const summaryNote of data.notes) {
|
|
438
|
+
lines.push(` ${summaryNote.when} \u2014 ${summaryNote.text}`);
|
|
439
|
+
}
|
|
440
|
+
if (story.notes.length > data.notes.length) {
|
|
441
|
+
lines.push(
|
|
442
|
+
` \u2026 (${story.notes.length - data.notes.length} more)`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (showData) {
|
|
447
|
+
lines.push(`${label("Data")}:`);
|
|
448
|
+
const json = JSON.stringify(data, null, 2);
|
|
449
|
+
if (colorize) {
|
|
450
|
+
const colored = colorizeJsonSections(json, {
|
|
451
|
+
base: ANSI.grayLight,
|
|
452
|
+
notes: ANSI.grayDark,
|
|
453
|
+
reset: ANSI.reset
|
|
454
|
+
});
|
|
455
|
+
lines.push(...colored);
|
|
456
|
+
} else {
|
|
457
|
+
lines.push(...json.split("\n"));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
lines.push("");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return lines.join("\n").trim() + "\n";
|
|
464
|
+
}
|
|
465
|
+
export {
|
|
466
|
+
ANSI,
|
|
467
|
+
Storyteller,
|
|
468
|
+
colorizeJsonSections,
|
|
469
|
+
consoleAudience,
|
|
470
|
+
countBrackets,
|
|
471
|
+
dbAudience,
|
|
472
|
+
formatOrigin,
|
|
473
|
+
getLevelColor,
|
|
474
|
+
summarizeStory,
|
|
475
|
+
useStoryteller,
|
|
476
|
+
writeStoryReport
|
|
477
|
+
};
|
|
478
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/audiences/consoleAudience.ts","../src/storyteller.ts","../src/useStoryteller.ts","../src/audiences/dbAudience.ts","../src/report/writeStoryReport.ts"],"sourcesContent":["import type { StoryEventBase, StoryLevel } from \"./storyteller\";\n\n/** ANSI escape codes for terminal colorization */\nexport const ANSI = {\n reset: \"\\x1b[0m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n red: \"\\x1b[38;2;250;128;114m\",\n grayLight: \"\\x1b[37m\",\n grayDark: \"\\x1b[37m\",\n};\n\n/** Map a story level to its corresponding ANSI terminal color */\nexport function getLevelColor(level: StoryLevel): string {\n if (level === \"tell\") return ANSI.green;\n if (level === \"warn\") return ANSI.yellow;\n return ANSI.red;\n}\n\n/** Format an origin context into a human-readable path like \"app / page / component\" */\nexport function formatOrigin(origin?: StoryEventBase[\"origin\"]): string | undefined {\n if (!origin?.where) return;\n if (typeof origin.where === \"string\") return origin.where;\n const whereRecord = origin.where as Record<string, unknown>;\n const parts = [whereRecord.app, whereRecord.service, whereRecord.page, whereRecord.component]\n .filter(Boolean)\n .map(String);\n return parts.length ? parts.join(\" / \") : undefined;\n}\n\n/** Colorize JSON output, dimming the notes section for visual hierarchy */\nexport function colorizeJsonSections(\n json: string,\n colors: { base: string; notes: string; reset: string }\n): string[] {\n const lines = json.split(\"\\n\");\n let insideNotes = false;\n let bracketDepth = 0;\n\n return lines.map((line) => {\n if (!insideNotes && line.includes('\"notes\": [')) {\n insideNotes = true;\n bracketDepth = countBrackets(line);\n return `${colors.notes}${line}${colors.reset}`;\n }\n\n if (insideNotes) {\n const colored = `${colors.notes}${line}${colors.reset}`;\n bracketDepth += countBrackets(line);\n if (bracketDepth <= 0) insideNotes = false;\n return colored;\n }\n\n return `${colors.base}${line}${colors.reset}`;\n });\n}\n\n/** Count the net bracket depth change in a line (opening brackets minus closing brackets) */\nexport function countBrackets(line: string): number {\n const openCount = (line.match(/\\[/g) || []).length;\n const closeCount = (line.match(/\\]/g) || []).length;\n return openCount - closeCount;\n}\n","import type { AudienceMember } from \"../storyteller\";\nimport { ANSI } from \"../utils\";\n\n/** Create an audience that logs stories to the browser console with color-coded grouped output */\nexport function consoleAudience(): AudienceMember {\n return {\n name: \"console\",\n hear: (event) => {\n const prefix = \"Storyteller\";\n\n const style =\n event.level === \"tell\"\n ? \"color:#16a34a;font-weight:600\"\n : event.level === \"warn\"\n ? \"color:#f59e0b;font-weight:600\"\n : \"color:#dc2626;font-weight:600\";\n\n const header = `${prefix}: ${event.title}`;\n\n console.groupCollapsed(`%c${header}`, style);\n\n const payload = JSON.stringify(event, null, 2);\n const coloredPayload =\n event.level === \"oops\" ? `${ANSI.red}${payload}${ANSI.reset}` : payload;\n\n if (event.level === \"tell\") {\n console.log(header, payload);\n } else if (event.level === \"warn\") {\n console.warn(header, payload);\n } else {\n console.error(header, coloredPayload);\n }\n\n console.groupEnd();\n },\n };\n}\n","import { consoleAudience } from \"./audiences/consoleAudience\";\nimport { ANSI, getLevelColor, formatOrigin, colorizeJsonSections } from \"./utils\";\n\nexport type StoryLevel = \"tell\" | \"warn\" | \"oops\";\n\nexport type StoryContextValue = Record<string, unknown> | string;\n\nexport type StoryError = {\n name?: string;\n message?: string;\n stack?: string;\n cause?: unknown;\n};\n\nexport type StoryNote = {\n timestamp: string;\n note: string;\n who?: StoryContextValue;\n what?: StoryContextValue;\n where?: StoryContextValue;\n error?: StoryError;\n};\n\nexport type StoryEventBase = {\n timestamp: string;\n level: StoryLevel;\n title: string;\n\n origin?: {\n who?: StoryContextValue;\n what?: StoryContextValue;\n where?: StoryContextValue;\n };\n\n notes: StoryNote[];\n\n error?: StoryError;\n};\n\nexport type StorySummaryOptions = {\n timezone?: string;\n locale?: string;\n verbosity?: \"brief\" | \"normal\" | \"full\";\n maxNotes?: number;\n showData?: boolean;\n colorize?: boolean;\n};\n\nexport type StoryPreviewOptions = StorySummaryOptions & {\n title?: string;\n level?: StoryLevel;\n error?: unknown;\n};\n\nexport type StorySummaryNote = {\n timestamp: string;\n when: string;\n note: string;\n text: string;\n who?: StoryContextValue;\n what?: StoryContextValue;\n where?: StoryContextValue;\n error?: StoryError;\n};\n\nexport type StorySummaryData = {\n title: string;\n level: StoryLevel;\n when: string;\n durationMs?: number;\n duration?: string;\n origin?: StoryEventBase[\"origin\"];\n notes: StorySummaryNote[];\n error?: StoryError;\n};\n\nexport type StorySummary = {\n text: string;\n data: StorySummaryData;\n};\n\nexport type StoryEvent = StoryEventBase & {\n summarize: (options?: StorySummaryOptions) => StorySummary;\n};\n\nexport type AudienceMember = {\n name: string;\n accepts?: (event: StoryEvent) => boolean;\n hear: (event: StoryEvent) => void | Promise<void>;\n};\n\ntype NoteData = {\n who?: StoryContextValue;\n what?: StoryContextValue;\n where?: StoryContextValue;\n error?: unknown;\n};\n\n/** Manages the set of audience members that receive story events */\nclass AudienceRegistry {\n private members = new Map<string, AudienceMember>();\n\n /** Register an audience member, replacing any existing member with the same name */\n add(member: AudienceMember) {\n this.members.set(member.name, member);\n return this;\n }\n\n /** Remove an audience member by name */\n remove(name: string) {\n this.members.delete(name);\n return this;\n }\n\n /** Return all registered audience members */\n getAll() {\n return [...this.members.values()];\n }\n\n /** Return only the audience members matching the given names */\n getOnly(names: string[]) {\n return names.map((name) => this.members.get(name)).filter(Boolean) as AudienceMember[];\n }\n}\n\n/** Core logging class that collects timestamped notes and emits them as structured story events */\nexport class Storyteller {\n public readonly audience = new AudienceRegistry();\n\n private readonly origin?: StoryEventBase[\"origin\"];\n private notes: StoryNote[] = [];\n\n constructor(options?: { origin?: StoryEventBase[\"origin\"]; audiences?: AudienceMember[] }) {\n this.origin = options?.origin;\n\n // Every storyteller gets a console audience by default\n this.audience.add(consoleAudience());\n\n options?.audiences?.forEach((audience) => this.audience.add(audience));\n }\n\n /** Add a timestamped note with optional context (who, what, where, error) */\n note(text: string, data: NoteData = {}) {\n this.notes.push({\n timestamp: new Date().toISOString(),\n note: text,\n ...(data.who ? { who: data.who } : {}),\n ...(data.what ? { what: data.what } : {}),\n ...(data.where ? { where: data.where } : {}),\n ...(data.error ? { error: normalizeError(data.error) } : {}),\n });\n return this;\n }\n\n /** Clear all accumulated notes without emitting a story */\n reset() {\n this.notes = [];\n return this;\n }\n\n /** Generate a formatted summary of current notes without emitting or clearing them */\n summarize(options: StoryPreviewOptions = {}) {\n const {\n title = \"Story preview\",\n level = \"tell\",\n error,\n ...summaryOptions\n } = options;\n const event: StoryEventBase = {\n timestamp: new Date().toISOString(),\n level,\n title,\n ...(this.origin ? { origin: this.origin } : {}),\n notes: [...this.notes],\n ...(error ? { error: normalizeError(error) } : {}),\n };\n\n return summarizeStory(event, summaryOptions);\n }\n\n /** Emit a story at the \"tell\" level (success / informational) */\n tell(title: string) {\n return this.createDelivery(\"tell\", title);\n }\n\n /** Emit a story at the \"warn\" level (something was off) */\n warn(title: string) {\n return this.createDelivery(\"warn\", title);\n }\n\n /** Emit a story at the \"oops\" level (something broke) with an optional error */\n oops(title: string, error?: unknown) {\n return this.createDelivery(\"oops\", title, error);\n }\n\n /** Build a story event and schedule delivery, returning a handle to override the audience list */\n private createDelivery(level: StoryLevel, title: string, error?: unknown) {\n const event = this.buildEvent(level, title, error);\n\n let delivered = false;\n let defaultCancelled = false;\n\n // Delivery is microtask-scheduled so .to() can override synchronously\n queueMicrotask(() => {\n if (delivered || defaultCancelled) return;\n delivered = true;\n void this.deliver(event);\n });\n\n return {\n to: (...names: string[]) => {\n defaultCancelled = true;\n if (delivered) return;\n delivered = true;\n void this.deliver(event, { only: names });\n },\n };\n }\n\n /** Assemble the story event from current notes and clear notes for the next story */\n private buildEvent(level: StoryLevel, title: string, error?: unknown): StoryEvent {\n const now = new Date().toISOString();\n const collectedNotes = [...this.notes];\n\n this.notes = [];\n\n const event: StoryEventBase = {\n timestamp: now,\n level,\n title,\n ...(this.origin ? { origin: this.origin } : {}),\n notes: collectedNotes,\n ...(error ? { error: normalizeError(error) } : {}),\n };\n\n const eventWithSummary = event as StoryEvent;\n Object.defineProperty(eventWithSummary, \"summarize\", {\n value: (options?: StorySummaryOptions) => summarizeStory(event, options),\n enumerable: false,\n });\n\n return eventWithSummary;\n }\n\n /** Deliver a story event to matching audience members */\n private async deliver(event: StoryEvent, options?: { only?: string[] }) {\n const targets = options?.only?.length\n ? this.audience.getOnly(options.only)\n : this.audience.getAll();\n\n await Promise.allSettled(\n targets\n .filter((member) => (member.accepts ? member.accepts(event) : true))\n .map((member) => member.hear(event))\n );\n }\n}\n\n/** Convert an unknown error value into a serializable StoryError object */\nfunction normalizeError(rawError: unknown): StoryError {\n if (rawError instanceof Error) {\n const normalized: StoryError = {\n name: rawError.name,\n message: rawError.message,\n };\n\n if (rawError.stack !== undefined) {\n normalized.stack = rawError.stack;\n }\n\n const cause = (rawError as { cause?: unknown }).cause;\n if (cause !== undefined) {\n normalized.cause = cause;\n }\n\n return normalized;\n }\n\n return { message: String(rawError) };\n}\n\n/** Calculate the duration between the first and last note in a sequence */\nfunction calculateNoteDuration(notes: StoryNote[]) {\n if (notes.length <= 1) {\n return {\n durationMs: undefined as number | undefined,\n };\n }\n\n const startTime = Date.parse(notes[0]!.timestamp);\n const endTime = Date.parse(notes[notes.length - 1]!.timestamp);\n\n return {\n durationMs: Number.isFinite(startTime) && Number.isFinite(endTime)\n ? Math.max(0, endTime - startTime)\n : undefined,\n };\n}\n\n/** Generate a formatted, human-readable summary from a story event */\nexport function summarizeStory(\n story: StoryEventBase,\n options: StorySummaryOptions = {}\n): StorySummary {\n const {\n timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,\n locale = \"en-US\",\n verbosity = \"normal\",\n maxNotes = 50,\n showData = true,\n colorize = true,\n } = options;\n\n const dateTimeFormatter = new Intl.DateTimeFormat(locale, {\n timeZone: timezone,\n year: \"numeric\",\n month: \"short\",\n day: \"2-digit\",\n hour: \"numeric\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n\n const timeFormatter = new Intl.DateTimeFormat(locale, {\n timeZone: timezone,\n hour: \"numeric\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n\n const orderedNotes = [...story.notes].sort(\n (noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)\n );\n const noteTiming = calculateNoteDuration(orderedNotes);\n const originLabel = formatOrigin(story.origin);\n const duration =\n noteTiming.durationMs != null ? formatDuration(noteTiming.durationMs) : undefined;\n\n const slicedNotes = orderedNotes.slice(0, maxNotes);\n const summaryNotes: StorySummaryNote[] = slicedNotes.map((note) => ({\n timestamp: note.timestamp,\n when: timeFormatter.format(new Date(note.timestamp)),\n note: note.note,\n text: formatNoteText(note, verbosity),\n ...(note.who ? { who: note.who } : {}),\n ...(note.what ? { what: note.what } : {}),\n ...(note.where ? { where: note.where } : {}),\n ...(note.error ? { error: note.error } : {}),\n }));\n\n const data: StorySummaryData = {\n title: story.title,\n level: story.level,\n when: dateTimeFormatter.format(new Date(story.timestamp)),\n ...(noteTiming.durationMs != null ? { durationMs: noteTiming.durationMs } : {}),\n ...(duration ? { duration } : {}),\n ...(story.origin ? { origin: story.origin } : {}),\n notes: summaryNotes,\n ...(story.error ? { error: story.error } : {}),\n };\n\n const levelColor = getLevelColor(story.level);\n const label = (text: string) =>\n colorize ? `${levelColor}${text}${ANSI.reset}` : text;\n\n const lines: string[] = [];\n lines.push(`${label(\"Story\")}: ${story.title}`);\n lines.push(`${label(\"Level\")}: ${story.level}`);\n lines.push(`${label(\"Time\")}: ${data.when}${duration ? ` (${duration})` : \"\"}`);\n\n if (originLabel) {\n lines.push(`${label(\"Origin\")}: ${originLabel}`);\n }\n\n if (story.error) {\n const errorLine = [story.error.name, story.error.message]\n .filter(Boolean)\n .join(\": \");\n if (errorLine) lines.push(`${label(\"Error\")}: ${errorLine}`);\n }\n\n if (verbosity !== \"brief\" && summaryNotes.length) {\n lines.push(`${label(\"Notes\")}:`);\n for (const note of summaryNotes) {\n lines.push(` ${note.when} — ${note.text}`);\n }\n if (orderedNotes.length > summaryNotes.length) {\n lines.push(` … (${orderedNotes.length - summaryNotes.length} more)`);\n }\n }\n\n if (showData) {\n lines.push(`${label(\"Data\")}:`);\n const json = JSON.stringify(data, null, 2);\n if (colorize) {\n const colored = colorizeJsonSections(json, {\n base: ANSI.grayLight,\n notes: ANSI.grayDark,\n reset: ANSI.reset,\n });\n lines.push(...colored);\n } else {\n lines.push(...json.split(\"\\n\"));\n }\n }\n\n return { text: lines.join(\"\\n\"), data };\n}\n\n/** Convert milliseconds into a human-readable duration string */\nfunction formatDuration(milliseconds: number): string {\n if (milliseconds < 1000) return `${milliseconds}ms`;\n const seconds = milliseconds / 1000;\n if (seconds < 60) return `${seconds.toFixed(1)}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.round(seconds % 60)\n .toString()\n .padStart(2, \"0\");\n return `${minutes}:${remainingSeconds}m`;\n}\n\n/** Format a note's text with optional context details when verbosity is \"full\" */\nfunction formatNoteText(\n note: StoryNote,\n verbosity: \"brief\" | \"normal\" | \"full\"\n): string {\n if (verbosity !== \"full\") return note.note;\n\n const details: string[] = [];\n const what = note.what;\n const where = note.where;\n\n if (typeof what === \"string\") {\n details.push(`what=${what}`);\n } else if (what) {\n if (what.field) details.push(`field=${String(what.field)}`);\n if (what.status) details.push(`status=${String(what.status)}`);\n }\n if (typeof where === \"string\") {\n details.push(`where=${where}`);\n } else if (where) {\n if (where.component) details.push(`component=${String(where.component)}`);\n }\n if (note.error) {\n const errorLine = [note.error.name, note.error.message]\n .filter(Boolean)\n .join(\": \");\n if (errorLine) details.push(`error=${errorLine}`);\n }\n\n return details.length\n ? `${note.note} (${details.join(\" \")})`\n : note.note;\n}\n","import type { StoryEventBase } from \"./storyteller\";\nimport { Storyteller } from \"./storyteller\";\n\nlet sharedInstance: Storyteller | undefined;\n\ntype StorytellerSharedOptions = {\n origin?: StoryEventBase[\"origin\"];\n reset?: boolean;\n};\n\n/** Return a shared singleton Storyteller instance for cross-component or cross-service logging */\nexport function useStoryteller(\n options: StorytellerSharedOptions = {}\n): Storyteller {\n if (!sharedInstance || options.reset) {\n sharedInstance = new Storyteller({ origin: options.origin });\n return sharedInstance;\n }\n\n return sharedInstance;\n}\n","import type { AudienceMember, StoryEvent } from \"../storyteller\";\n\n/** Create an audience that persists warn and oops stories to a database via the provided insert function */\nexport function dbAudience(insert: (event: StoryEvent) => Promise<void> | void): AudienceMember {\n return {\n name: \"db\",\n accepts: (event) => event.level === \"warn\" || event.level === \"oops\",\n hear: async (event) => {\n await insert(event);\n },\n };\n}\n","import type { StoryEventBase } from \"../storyteller\";\nimport { summarizeStory } from \"../storyteller\";\nimport { ANSI, getLevelColor, formatOrigin, colorizeJsonSections } from \"../utils\";\n\nexport type StoryReportOptions = {\n timezone?: string;\n locale?: string;\n verbosity?: \"brief\" | \"normal\" | \"full\";\n maxNotesPerStory?: number;\n showData?: boolean;\n colorize?: boolean;\n};\n\n/** Generate a formatted report from an array of story events, grouped by day */\nexport function writeStoryReport(\n stories: StoryEventBase[],\n options: StoryReportOptions = {}\n): string {\n const {\n timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,\n locale = \"en-US\",\n verbosity = \"normal\",\n maxNotesPerStory = 50,\n showData = true,\n colorize = true,\n } = options;\n\n if (!stories.length) {\n return \"Storyteller Report\\n\\n(no stories)\\n\";\n }\n\n const sorted = [...stories].sort(\n (storyA, storyB) => Date.parse(storyA.timestamp) - Date.parse(storyB.timestamp)\n );\n\n const dateFormatter = new Intl.DateTimeFormat(locale, {\n timeZone: timezone,\n year: \"numeric\",\n month: \"short\",\n day: \"2-digit\",\n });\n\n const firstStory = sorted[0];\n const lastStory = sorted[sorted.length - 1];\n if (!firstStory || !lastStory) {\n return \"Storyteller Report\\n\\n(no stories)\\n\";\n }\n\n const lines: string[] = [];\n lines.push(`Storyteller Report (${timezone})`);\n lines.push(\n `Range: ${dateFormatter.format(new Date(firstStory.timestamp))} – ${dateFormatter.format(\n new Date(lastStory.timestamp)\n )}`\n );\n lines.push(\"\");\n\n const storiesByDay = new Map<string, StoryEventBase[]>();\n for (const story of sorted) {\n const dayKey = dateFormatter.format(new Date(story.timestamp));\n const dayEvents = storiesByDay.get(dayKey) ?? [];\n dayEvents.push(story);\n storiesByDay.set(dayKey, dayEvents);\n }\n\n for (const [day, dayStories] of storiesByDay) {\n lines.push(day);\n\n for (const story of dayStories) {\n const summary = summarizeStory(story, {\n timezone,\n locale,\n verbosity,\n maxNotes: maxNotesPerStory,\n colorize,\n });\n const { data } = summary;\n const originLabel = formatOrigin(story.origin);\n const levelColor = getLevelColor(story.level);\n const label = (text: string) =>\n colorize ? `${levelColor}${text}${ANSI.reset}` : text;\n\n const duration = data.duration ? ` (${data.duration})` : \"\";\n lines.push(`${label(\"Story\")}: ${story.title}`);\n lines.push(`${label(\"Level\")}: ${story.level}`);\n lines.push(`${label(\"Time\")}: ${data.when}${duration}`);\n\n if (originLabel) {\n lines.push(`${label(\"Origin\")}: ${originLabel}`);\n }\n\n if (data.error) {\n const errorLine = [\n data.error.name,\n data.error.message,\n ]\n .filter(Boolean)\n .join(\": \");\n if (errorLine) lines.push(`${label(\"Error\")}: ${errorLine}`);\n }\n\n if (verbosity !== \"brief\" && data.notes.length) {\n lines.push(` ${label(\"Notes\")}:`);\n\n for (const summaryNote of data.notes) {\n lines.push(` ${summaryNote.when} — ${summaryNote.text}`);\n }\n\n if (story.notes.length > data.notes.length) {\n lines.push(\n ` … (${story.notes.length - data.notes.length} more)`\n );\n }\n }\n\n if (showData) {\n lines.push(`${label(\"Data\")}:`);\n const json = JSON.stringify(data, null, 2);\n if (colorize) {\n const colored = colorizeJsonSections(json, {\n base: ANSI.grayLight,\n notes: ANSI.grayDark,\n reset: ANSI.reset,\n });\n lines.push(...colored);\n } else {\n lines.push(...json.split(\"\\n\"));\n }\n }\n\n lines.push(\"\");\n }\n }\n\n return lines.join(\"\\n\").trim() + \"\\n\";\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lovelaces-io/storyteller",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight TypeScript logging library that treats logs as stories: grouped notes emitted as a single structured event.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"registry": "https://registry.npmjs.org/",
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/lovelaces-io/storyteller.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/lovelaces-io/storyteller#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/lovelaces-io/storyteller/issues"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"logging",
|
|
26
|
+
"logger",
|
|
27
|
+
"structured-logging",
|
|
28
|
+
"storyteller",
|
|
29
|
+
"events",
|
|
30
|
+
"observability",
|
|
31
|
+
"typescript"
|
|
32
|
+
],
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test:console": "npm run build && node ./scripts/consoleTest.mjs",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
49
|
+
"lint": "eslint .",
|
|
50
|
+
"prepack": "npm run build"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^10.0.1",
|
|
54
|
+
"eslint": "^10.1.0",
|
|
55
|
+
"tsup": "^8.0.0",
|
|
56
|
+
"typescript": "^5.6.0",
|
|
57
|
+
"typescript-eslint": "^8.57.1",
|
|
58
|
+
"vitest": "^2.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|