@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/audiences/consoleAudience.ts","../src/storyteller.ts","../src/useStoryteller.ts","../src/audiences/dbAudience.ts","../src/report/writeStoryReport.ts"],"sourcesContent":["export * from \"./storyteller\";\nexport * from \"./useStoryteller\";\nexport * from \"./audiences/consoleAudience\";\nexport * from \"./audiences/dbAudience\";\nexport * from \"./report/writeStoryReport\";\nexport * from \"./utils\";\n","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/dist/index.d.cts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
type StoryLevel = "tell" | "warn" | "oops";
|
|
2
|
+
type StoryContextValue = Record<string, unknown> | string;
|
|
3
|
+
type StoryError = {
|
|
4
|
+
name?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
stack?: string;
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
};
|
|
9
|
+
type StoryNote = {
|
|
10
|
+
timestamp: string;
|
|
11
|
+
note: string;
|
|
12
|
+
who?: StoryContextValue;
|
|
13
|
+
what?: StoryContextValue;
|
|
14
|
+
where?: StoryContextValue;
|
|
15
|
+
error?: StoryError;
|
|
16
|
+
};
|
|
17
|
+
type StoryEventBase = {
|
|
18
|
+
timestamp: string;
|
|
19
|
+
level: StoryLevel;
|
|
20
|
+
title: string;
|
|
21
|
+
origin?: {
|
|
22
|
+
who?: StoryContextValue;
|
|
23
|
+
what?: StoryContextValue;
|
|
24
|
+
where?: StoryContextValue;
|
|
25
|
+
};
|
|
26
|
+
notes: StoryNote[];
|
|
27
|
+
error?: StoryError;
|
|
28
|
+
};
|
|
29
|
+
type StorySummaryOptions = {
|
|
30
|
+
timezone?: string;
|
|
31
|
+
locale?: string;
|
|
32
|
+
verbosity?: "brief" | "normal" | "full";
|
|
33
|
+
maxNotes?: number;
|
|
34
|
+
showData?: boolean;
|
|
35
|
+
colorize?: boolean;
|
|
36
|
+
};
|
|
37
|
+
type StoryPreviewOptions = StorySummaryOptions & {
|
|
38
|
+
title?: string;
|
|
39
|
+
level?: StoryLevel;
|
|
40
|
+
error?: unknown;
|
|
41
|
+
};
|
|
42
|
+
type StorySummaryNote = {
|
|
43
|
+
timestamp: string;
|
|
44
|
+
when: string;
|
|
45
|
+
note: string;
|
|
46
|
+
text: string;
|
|
47
|
+
who?: StoryContextValue;
|
|
48
|
+
what?: StoryContextValue;
|
|
49
|
+
where?: StoryContextValue;
|
|
50
|
+
error?: StoryError;
|
|
51
|
+
};
|
|
52
|
+
type StorySummaryData = {
|
|
53
|
+
title: string;
|
|
54
|
+
level: StoryLevel;
|
|
55
|
+
when: string;
|
|
56
|
+
durationMs?: number;
|
|
57
|
+
duration?: string;
|
|
58
|
+
origin?: StoryEventBase["origin"];
|
|
59
|
+
notes: StorySummaryNote[];
|
|
60
|
+
error?: StoryError;
|
|
61
|
+
};
|
|
62
|
+
type StorySummary = {
|
|
63
|
+
text: string;
|
|
64
|
+
data: StorySummaryData;
|
|
65
|
+
};
|
|
66
|
+
type StoryEvent = StoryEventBase & {
|
|
67
|
+
summarize: (options?: StorySummaryOptions) => StorySummary;
|
|
68
|
+
};
|
|
69
|
+
type AudienceMember = {
|
|
70
|
+
name: string;
|
|
71
|
+
accepts?: (event: StoryEvent) => boolean;
|
|
72
|
+
hear: (event: StoryEvent) => void | Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
type NoteData = {
|
|
75
|
+
who?: StoryContextValue;
|
|
76
|
+
what?: StoryContextValue;
|
|
77
|
+
where?: StoryContextValue;
|
|
78
|
+
error?: unknown;
|
|
79
|
+
};
|
|
80
|
+
/** Manages the set of audience members that receive story events */
|
|
81
|
+
declare class AudienceRegistry {
|
|
82
|
+
private members;
|
|
83
|
+
/** Register an audience member, replacing any existing member with the same name */
|
|
84
|
+
add(member: AudienceMember): this;
|
|
85
|
+
/** Remove an audience member by name */
|
|
86
|
+
remove(name: string): this;
|
|
87
|
+
/** Return all registered audience members */
|
|
88
|
+
getAll(): AudienceMember[];
|
|
89
|
+
/** Return only the audience members matching the given names */
|
|
90
|
+
getOnly(names: string[]): AudienceMember[];
|
|
91
|
+
}
|
|
92
|
+
/** Core logging class that collects timestamped notes and emits them as structured story events */
|
|
93
|
+
declare class Storyteller {
|
|
94
|
+
readonly audience: AudienceRegistry;
|
|
95
|
+
private readonly origin?;
|
|
96
|
+
private notes;
|
|
97
|
+
constructor(options?: {
|
|
98
|
+
origin?: StoryEventBase["origin"];
|
|
99
|
+
audiences?: AudienceMember[];
|
|
100
|
+
});
|
|
101
|
+
/** Add a timestamped note with optional context (who, what, where, error) */
|
|
102
|
+
note(text: string, data?: NoteData): this;
|
|
103
|
+
/** Clear all accumulated notes without emitting a story */
|
|
104
|
+
reset(): this;
|
|
105
|
+
/** Generate a formatted summary of current notes without emitting or clearing them */
|
|
106
|
+
summarize(options?: StoryPreviewOptions): StorySummary;
|
|
107
|
+
/** Emit a story at the "tell" level (success / informational) */
|
|
108
|
+
tell(title: string): {
|
|
109
|
+
to: (...names: string[]) => void;
|
|
110
|
+
};
|
|
111
|
+
/** Emit a story at the "warn" level (something was off) */
|
|
112
|
+
warn(title: string): {
|
|
113
|
+
to: (...names: string[]) => void;
|
|
114
|
+
};
|
|
115
|
+
/** Emit a story at the "oops" level (something broke) with an optional error */
|
|
116
|
+
oops(title: string, error?: unknown): {
|
|
117
|
+
to: (...names: string[]) => void;
|
|
118
|
+
};
|
|
119
|
+
/** Build a story event and schedule delivery, returning a handle to override the audience list */
|
|
120
|
+
private createDelivery;
|
|
121
|
+
/** Assemble the story event from current notes and clear notes for the next story */
|
|
122
|
+
private buildEvent;
|
|
123
|
+
/** Deliver a story event to matching audience members */
|
|
124
|
+
private deliver;
|
|
125
|
+
}
|
|
126
|
+
/** Generate a formatted, human-readable summary from a story event */
|
|
127
|
+
declare function summarizeStory(story: StoryEventBase, options?: StorySummaryOptions): StorySummary;
|
|
128
|
+
|
|
129
|
+
type StorytellerSharedOptions = {
|
|
130
|
+
origin?: StoryEventBase["origin"];
|
|
131
|
+
reset?: boolean;
|
|
132
|
+
};
|
|
133
|
+
/** Return a shared singleton Storyteller instance for cross-component or cross-service logging */
|
|
134
|
+
declare function useStoryteller(options?: StorytellerSharedOptions): Storyteller;
|
|
135
|
+
|
|
136
|
+
/** Create an audience that logs stories to the browser console with color-coded grouped output */
|
|
137
|
+
declare function consoleAudience(): AudienceMember;
|
|
138
|
+
|
|
139
|
+
/** Create an audience that persists warn and oops stories to a database via the provided insert function */
|
|
140
|
+
declare function dbAudience(insert: (event: StoryEvent) => Promise<void> | void): AudienceMember;
|
|
141
|
+
|
|
142
|
+
type StoryReportOptions = {
|
|
143
|
+
timezone?: string;
|
|
144
|
+
locale?: string;
|
|
145
|
+
verbosity?: "brief" | "normal" | "full";
|
|
146
|
+
maxNotesPerStory?: number;
|
|
147
|
+
showData?: boolean;
|
|
148
|
+
colorize?: boolean;
|
|
149
|
+
};
|
|
150
|
+
/** Generate a formatted report from an array of story events, grouped by day */
|
|
151
|
+
declare function writeStoryReport(stories: StoryEventBase[], options?: StoryReportOptions): string;
|
|
152
|
+
|
|
153
|
+
/** ANSI escape codes for terminal colorization */
|
|
154
|
+
declare const ANSI: {
|
|
155
|
+
reset: string;
|
|
156
|
+
green: string;
|
|
157
|
+
yellow: string;
|
|
158
|
+
red: string;
|
|
159
|
+
grayLight: string;
|
|
160
|
+
grayDark: string;
|
|
161
|
+
};
|
|
162
|
+
/** Map a story level to its corresponding ANSI terminal color */
|
|
163
|
+
declare function getLevelColor(level: StoryLevel): string;
|
|
164
|
+
/** Format an origin context into a human-readable path like "app / page / component" */
|
|
165
|
+
declare function formatOrigin(origin?: StoryEventBase["origin"]): string | undefined;
|
|
166
|
+
/** Colorize JSON output, dimming the notes section for visual hierarchy */
|
|
167
|
+
declare function colorizeJsonSections(json: string, colors: {
|
|
168
|
+
base: string;
|
|
169
|
+
notes: string;
|
|
170
|
+
reset: string;
|
|
171
|
+
}): string[];
|
|
172
|
+
/** Count the net bracket depth change in a line (opening brackets minus closing brackets) */
|
|
173
|
+
declare function countBrackets(line: string): number;
|
|
174
|
+
|
|
175
|
+
export { ANSI, type AudienceMember, type StoryContextValue, type StoryError, type StoryEvent, type StoryEventBase, type StoryLevel, type StoryNote, type StoryPreviewOptions, type StoryReportOptions, type StorySummary, type StorySummaryData, type StorySummaryNote, type StorySummaryOptions, Storyteller, colorizeJsonSections, consoleAudience, countBrackets, dbAudience, formatOrigin, getLevelColor, summarizeStory, useStoryteller, writeStoryReport };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
type StoryLevel = "tell" | "warn" | "oops";
|
|
2
|
+
type StoryContextValue = Record<string, unknown> | string;
|
|
3
|
+
type StoryError = {
|
|
4
|
+
name?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
stack?: string;
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
};
|
|
9
|
+
type StoryNote = {
|
|
10
|
+
timestamp: string;
|
|
11
|
+
note: string;
|
|
12
|
+
who?: StoryContextValue;
|
|
13
|
+
what?: StoryContextValue;
|
|
14
|
+
where?: StoryContextValue;
|
|
15
|
+
error?: StoryError;
|
|
16
|
+
};
|
|
17
|
+
type StoryEventBase = {
|
|
18
|
+
timestamp: string;
|
|
19
|
+
level: StoryLevel;
|
|
20
|
+
title: string;
|
|
21
|
+
origin?: {
|
|
22
|
+
who?: StoryContextValue;
|
|
23
|
+
what?: StoryContextValue;
|
|
24
|
+
where?: StoryContextValue;
|
|
25
|
+
};
|
|
26
|
+
notes: StoryNote[];
|
|
27
|
+
error?: StoryError;
|
|
28
|
+
};
|
|
29
|
+
type StorySummaryOptions = {
|
|
30
|
+
timezone?: string;
|
|
31
|
+
locale?: string;
|
|
32
|
+
verbosity?: "brief" | "normal" | "full";
|
|
33
|
+
maxNotes?: number;
|
|
34
|
+
showData?: boolean;
|
|
35
|
+
colorize?: boolean;
|
|
36
|
+
};
|
|
37
|
+
type StoryPreviewOptions = StorySummaryOptions & {
|
|
38
|
+
title?: string;
|
|
39
|
+
level?: StoryLevel;
|
|
40
|
+
error?: unknown;
|
|
41
|
+
};
|
|
42
|
+
type StorySummaryNote = {
|
|
43
|
+
timestamp: string;
|
|
44
|
+
when: string;
|
|
45
|
+
note: string;
|
|
46
|
+
text: string;
|
|
47
|
+
who?: StoryContextValue;
|
|
48
|
+
what?: StoryContextValue;
|
|
49
|
+
where?: StoryContextValue;
|
|
50
|
+
error?: StoryError;
|
|
51
|
+
};
|
|
52
|
+
type StorySummaryData = {
|
|
53
|
+
title: string;
|
|
54
|
+
level: StoryLevel;
|
|
55
|
+
when: string;
|
|
56
|
+
durationMs?: number;
|
|
57
|
+
duration?: string;
|
|
58
|
+
origin?: StoryEventBase["origin"];
|
|
59
|
+
notes: StorySummaryNote[];
|
|
60
|
+
error?: StoryError;
|
|
61
|
+
};
|
|
62
|
+
type StorySummary = {
|
|
63
|
+
text: string;
|
|
64
|
+
data: StorySummaryData;
|
|
65
|
+
};
|
|
66
|
+
type StoryEvent = StoryEventBase & {
|
|
67
|
+
summarize: (options?: StorySummaryOptions) => StorySummary;
|
|
68
|
+
};
|
|
69
|
+
type AudienceMember = {
|
|
70
|
+
name: string;
|
|
71
|
+
accepts?: (event: StoryEvent) => boolean;
|
|
72
|
+
hear: (event: StoryEvent) => void | Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
type NoteData = {
|
|
75
|
+
who?: StoryContextValue;
|
|
76
|
+
what?: StoryContextValue;
|
|
77
|
+
where?: StoryContextValue;
|
|
78
|
+
error?: unknown;
|
|
79
|
+
};
|
|
80
|
+
/** Manages the set of audience members that receive story events */
|
|
81
|
+
declare class AudienceRegistry {
|
|
82
|
+
private members;
|
|
83
|
+
/** Register an audience member, replacing any existing member with the same name */
|
|
84
|
+
add(member: AudienceMember): this;
|
|
85
|
+
/** Remove an audience member by name */
|
|
86
|
+
remove(name: string): this;
|
|
87
|
+
/** Return all registered audience members */
|
|
88
|
+
getAll(): AudienceMember[];
|
|
89
|
+
/** Return only the audience members matching the given names */
|
|
90
|
+
getOnly(names: string[]): AudienceMember[];
|
|
91
|
+
}
|
|
92
|
+
/** Core logging class that collects timestamped notes and emits them as structured story events */
|
|
93
|
+
declare class Storyteller {
|
|
94
|
+
readonly audience: AudienceRegistry;
|
|
95
|
+
private readonly origin?;
|
|
96
|
+
private notes;
|
|
97
|
+
constructor(options?: {
|
|
98
|
+
origin?: StoryEventBase["origin"];
|
|
99
|
+
audiences?: AudienceMember[];
|
|
100
|
+
});
|
|
101
|
+
/** Add a timestamped note with optional context (who, what, where, error) */
|
|
102
|
+
note(text: string, data?: NoteData): this;
|
|
103
|
+
/** Clear all accumulated notes without emitting a story */
|
|
104
|
+
reset(): this;
|
|
105
|
+
/** Generate a formatted summary of current notes without emitting or clearing them */
|
|
106
|
+
summarize(options?: StoryPreviewOptions): StorySummary;
|
|
107
|
+
/** Emit a story at the "tell" level (success / informational) */
|
|
108
|
+
tell(title: string): {
|
|
109
|
+
to: (...names: string[]) => void;
|
|
110
|
+
};
|
|
111
|
+
/** Emit a story at the "warn" level (something was off) */
|
|
112
|
+
warn(title: string): {
|
|
113
|
+
to: (...names: string[]) => void;
|
|
114
|
+
};
|
|
115
|
+
/** Emit a story at the "oops" level (something broke) with an optional error */
|
|
116
|
+
oops(title: string, error?: unknown): {
|
|
117
|
+
to: (...names: string[]) => void;
|
|
118
|
+
};
|
|
119
|
+
/** Build a story event and schedule delivery, returning a handle to override the audience list */
|
|
120
|
+
private createDelivery;
|
|
121
|
+
/** Assemble the story event from current notes and clear notes for the next story */
|
|
122
|
+
private buildEvent;
|
|
123
|
+
/** Deliver a story event to matching audience members */
|
|
124
|
+
private deliver;
|
|
125
|
+
}
|
|
126
|
+
/** Generate a formatted, human-readable summary from a story event */
|
|
127
|
+
declare function summarizeStory(story: StoryEventBase, options?: StorySummaryOptions): StorySummary;
|
|
128
|
+
|
|
129
|
+
type StorytellerSharedOptions = {
|
|
130
|
+
origin?: StoryEventBase["origin"];
|
|
131
|
+
reset?: boolean;
|
|
132
|
+
};
|
|
133
|
+
/** Return a shared singleton Storyteller instance for cross-component or cross-service logging */
|
|
134
|
+
declare function useStoryteller(options?: StorytellerSharedOptions): Storyteller;
|
|
135
|
+
|
|
136
|
+
/** Create an audience that logs stories to the browser console with color-coded grouped output */
|
|
137
|
+
declare function consoleAudience(): AudienceMember;
|
|
138
|
+
|
|
139
|
+
/** Create an audience that persists warn and oops stories to a database via the provided insert function */
|
|
140
|
+
declare function dbAudience(insert: (event: StoryEvent) => Promise<void> | void): AudienceMember;
|
|
141
|
+
|
|
142
|
+
type StoryReportOptions = {
|
|
143
|
+
timezone?: string;
|
|
144
|
+
locale?: string;
|
|
145
|
+
verbosity?: "brief" | "normal" | "full";
|
|
146
|
+
maxNotesPerStory?: number;
|
|
147
|
+
showData?: boolean;
|
|
148
|
+
colorize?: boolean;
|
|
149
|
+
};
|
|
150
|
+
/** Generate a formatted report from an array of story events, grouped by day */
|
|
151
|
+
declare function writeStoryReport(stories: StoryEventBase[], options?: StoryReportOptions): string;
|
|
152
|
+
|
|
153
|
+
/** ANSI escape codes for terminal colorization */
|
|
154
|
+
declare const ANSI: {
|
|
155
|
+
reset: string;
|
|
156
|
+
green: string;
|
|
157
|
+
yellow: string;
|
|
158
|
+
red: string;
|
|
159
|
+
grayLight: string;
|
|
160
|
+
grayDark: string;
|
|
161
|
+
};
|
|
162
|
+
/** Map a story level to its corresponding ANSI terminal color */
|
|
163
|
+
declare function getLevelColor(level: StoryLevel): string;
|
|
164
|
+
/** Format an origin context into a human-readable path like "app / page / component" */
|
|
165
|
+
declare function formatOrigin(origin?: StoryEventBase["origin"]): string | undefined;
|
|
166
|
+
/** Colorize JSON output, dimming the notes section for visual hierarchy */
|
|
167
|
+
declare function colorizeJsonSections(json: string, colors: {
|
|
168
|
+
base: string;
|
|
169
|
+
notes: string;
|
|
170
|
+
reset: string;
|
|
171
|
+
}): string[];
|
|
172
|
+
/** Count the net bracket depth change in a line (opening brackets minus closing brackets) */
|
|
173
|
+
declare function countBrackets(line: string): number;
|
|
174
|
+
|
|
175
|
+
export { ANSI, type AudienceMember, type StoryContextValue, type StoryError, type StoryEvent, type StoryEventBase, type StoryLevel, type StoryNote, type StoryPreviewOptions, type StoryReportOptions, type StorySummary, type StorySummaryData, type StorySummaryNote, type StorySummaryOptions, Storyteller, colorizeJsonSections, consoleAudience, countBrackets, dbAudience, formatOrigin, getLevelColor, summarizeStory, useStoryteller, writeStoryReport };
|