@lovelaces-io/storyteller 0.1.0 → 0.2.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/README.md +72 -73
- package/dist/index.cjs +227 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -36
- package/dist/index.d.ts +119 -36
- package/dist/index.js +225 -174
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/audiences/consoleAudience.ts","../src/utils.ts","../src/formatting.ts","../src/storyteller.ts","../src/useStoryteller.ts","../src/audiences/dbAudience.ts","../src/report/writeStoryReport.ts"],"sourcesContent":["export * from \"./storyteller\";\nexport * from \"./formatting\";\nexport * from \"./useStoryteller\";\nexport * from \"./audiences/consoleAudience\";\nexport * from \"./audiences/dbAudience\";\nexport * from \"./report/writeStoryReport\";\nexport { ANSI, getLevelColor, formatOrigin } from \"./utils\";\n","import type { AudienceMember } from \"../storyteller\";\n\n/**\n * Create an audience that prints stories to the console with color-coded grouped output.\n * Registered by default on every Storyteller instance.\n *\n * @example\n * ```ts\n * // Already included — but you can re-add after removing:\n * story.audience.add(consoleAudience());\n * ```\n */\nexport function consoleAudience(): AudienceMember {\n return {\n name: \"console\",\n hear: (event) => {\n const prefix = \"Storyteller\";\n\n const style =\n event.level === \"Information\"\n ? \"color:#16a34a;font-weight:600\"\n : event.level === \"Warning\"\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\n if (event.level === \"Information\") {\n console.log(header, payload);\n } else if (event.level === \"Warning\") {\n console.warn(header, payload);\n } else {\n console.error(header, payload);\n }\n\n console.groupEnd();\n },\n };\n}\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[90m\",\n};\n\n/** Map a story level to its corresponding ANSI terminal color */\nexport function getLevelColor(level: StoryLevel): string {\n if (level === \"Information\") return ANSI.green;\n if (level === \"Warning\") 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\n // Show well-known keys first in a natural order, then any additional fields\n const priorityKeys = [\"app\", \"service\", \"page\", \"component\"];\n const priorityParts = priorityKeys\n .filter((key) => whereRecord[key] != null)\n .map((key) => String(whereRecord[key]));\n const extraParts = Object.entries(whereRecord)\n .filter(([key, value]) => !priorityKeys.includes(key) && value != null)\n .map(([_, value]) => String(value));\n\n const parts = [...priorityParts, ...extraParts];\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 {\n StoryEventBase,\n StoryNote,\n ReportNote,\n StoryReport,\n FormattedReport,\n ReportOptions,\n} from \"./storyteller\";\nimport { ANSI, getLevelColor, formatOrigin, colorizeJsonSections } from \"./utils\";\n\n/**\n * Format a story event into a human-readable report with optional colors.\n *\n * @param story - The story event to format\n * @param options - Formatting options (timezone, locale, detail level, colors)\n * @returns A FormattedReport with both text (for display) and data (structured)\n *\n * @example\n * ```ts\n * const report = formatStory(event, { colors: false, detail: \"brief\" });\n * console.log(report.text);\n * ```\n */\nexport function formatStory(\n story: StoryEventBase,\n options: ReportOptions = {}\n): FormattedReport {\n const {\n timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,\n locale = \"en-US\",\n detail = \"normal\",\n noteLimit = 50,\n showData = true,\n colors = 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 // Notes are already sorted if coming from buildEvent; sort again for standalone use\n const orderedNotes = [...story.notes].sort(\n (noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)\n );\n\n // Use pre-computed durationMs from the event if available, otherwise compute\n const durationMs = story.durationMs ?? calculateNoteDuration(orderedNotes);\n const duration = durationMs != null ? formatDuration(durationMs) : undefined;\n\n const slicedNotes = orderedNotes.slice(0, noteLimit);\n const reportNotes: ReportNote[] = slicedNotes.map((note) => ({\n timestamp: note.timestamp,\n when: timeFormatter.format(new Date(note.timestamp)),\n note: note.note,\n text: formatNoteText(note, detail),\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: StoryReport = {\n title: story.title,\n level: story.level,\n when: dateTimeFormatter.format(new Date(story.timestamp)),\n ...(durationMs != null ? { durationMs } : {}),\n ...(duration ? { duration } : {}),\n ...(story.origin ? { origin: story.origin } : {}),\n notes: reportNotes,\n ...(story.error ? { error: story.error } : {}),\n };\n\n const lines = buildReportText(story, data, reportNotes, orderedNotes, {\n colors,\n detail,\n showData,\n duration,\n });\n\n return { text: lines.join(\"\\n\"), data };\n}\n\n/** Build the human-readable text lines for a story report */\nfunction buildReportText(\n story: StoryEventBase,\n data: StoryReport,\n reportNotes: ReportNote[],\n orderedNotes: StoryNote[],\n options: { colors: boolean; detail: string; showData: boolean; duration?: string | undefined }\n): string[] {\n const levelColor = getLevelColor(story.level);\n const label = (text: string) =>\n options.colors ? `${levelColor}${text}${ANSI.reset}` : text;\n const originLabel = formatOrigin(story.origin);\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}${options.duration ? ` (${options.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 (options.detail !== \"brief\" && reportNotes.length) {\n lines.push(`${label(\"Notes\")}:`);\n for (const note of reportNotes) {\n lines.push(` ${note.when} — ${note.text}`);\n }\n if (orderedNotes.length > reportNotes.length) {\n lines.push(` … (${orderedNotes.length - reportNotes.length} more)`);\n }\n }\n\n if (options.showData) {\n lines.push(`${label(\"Data\")}:`);\n const json = JSON.stringify(data, null, 2);\n if (options.colors) {\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 lines;\n}\n\n/** Calculate the duration between the first and last note in a sequence */\nfunction calculateNoteDuration(notes: StoryNote[]): number | undefined {\n if (notes.length <= 1) return undefined;\n\n const startTime = Date.parse(notes[0]!.timestamp);\n const endTime = Date.parse(notes[notes.length - 1]!.timestamp);\n\n return Number.isFinite(startTime) && Number.isFinite(endTime)\n ? Math.max(0, endTime - startTime)\n : undefined;\n}\n\n/** Convert milliseconds into a human-readable duration string */\nexport function 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/** @deprecated Use formatStory instead */\nexport const summarizeStory = formatStory;\n\n/** Format a note's text with optional context details when detail level 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 for (const [key, value] of Object.entries(what)) {\n if (value != null) details.push(`${key}=${String(value)}`);\n }\n }\n if (typeof where === \"string\") {\n details.push(`where=${where}`);\n } else if (where) {\n for (const [key, value] of Object.entries(where)) {\n if (value != null) details.push(`${key}=${String(value)}`);\n }\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 { consoleAudience } from \"./audiences/consoleAudience\";\nimport { formatStory } from \"./formatting\";\n\n/** Internal method identifiers — mapped to human-readable labels in the stored record */\ntype StoryMethod = \"tell\" | \"warn\" | \"oops\";\n\n/** Human-readable level labels stored in story records */\nexport type StoryLevel = \"Information\" | \"Warning\" | \"Error\";\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 durationMs?: number;\n\n error?: StoryError;\n};\n\nexport type ReportOptions = {\n timezone?: string;\n locale?: string;\n detail?: \"brief\" | \"normal\" | \"full\";\n noteLimit?: number;\n showData?: boolean;\n colors?: boolean;\n};\n\n/** @deprecated Use ReportOptions instead */\nexport type StorySummaryOptions = ReportOptions;\n\nexport type PreviewOptions = ReportOptions & {\n title?: string;\n level?: StoryLevel;\n error?: unknown;\n};\n\n/** @deprecated Use PreviewOptions instead */\nexport type StoryPreviewOptions = PreviewOptions;\n\nexport type ReportNote = {\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\n/** @deprecated Use ReportNote instead */\nexport type StorySummaryNote = ReportNote;\n\nexport type StoryReport = {\n title: string;\n level: StoryLevel;\n when: string;\n durationMs?: number;\n duration?: string;\n origin?: StoryEventBase[\"origin\"];\n notes: ReportNote[];\n error?: StoryError;\n};\n\n/** @deprecated Use StoryReport instead */\nexport type StorySummaryData = StoryReport;\n\nexport type FormattedReport = {\n text: string;\n data: StoryReport;\n};\n\n/** @deprecated Use FormattedReport instead */\nexport type StorySummary = FormattedReport;\n\nexport type StoryEvent = StoryEventBase & {\n summarize: (options?: ReportOptions) => FormattedReport;\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 /** Check if an audience member is registered by name */\n has(name: string) {\n return this.members.has(name);\n }\n\n /** List the names of all registered audience members */\n names() {\n return [...this.members.keys()];\n }\n}\n\n/**\n * Collects timestamped notes and emits them as a single structured story event.\n *\n * @example\n * ```ts\n * const story = new Storyteller({ origin: { who: \"api-server\" } });\n * story.note(\"Request received\", { what: { path: \"/checkout\" } });\n * story.note(\"Validated cart\");\n * story.tell(\"Checkout started\");\n * ```\n */\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 /**\n * Add a timestamped note to the current story.\n * @param text - What happened\n * @param data - Optional context: who did it, what was involved, where it happened, any error\n * @returns `this` for chaining\n *\n * @example\n * ```ts\n * story.note(\"Card charged\", { what: { amount: \"$42\" }, where: \"stripe\" });\n * ```\n */\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 /** Preview the current notes as a formatted report without emitting or clearing them */\n summarize(options: PreviewOptions = {}) {\n const {\n title = \"Story preview\",\n level = \"Information\",\n error,\n ...reportOptions\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 formatStory(event, reportOptions);\n }\n\n /** Tell a success story — everything went well */\n tell(title: string) {\n return this.createDelivery(\"tell\", title);\n }\n\n /** Tell a cautionary story — something was off but it's handled */\n warn(title: string) {\n return this.createDelivery(\"warn\", title);\n }\n\n /** Tell an error story — something broke. Pass the error for automatic normalization. */\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(method: StoryMethod, title: string, error?: unknown) {\n const event = this.buildEvent(method, 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(method: StoryMethod, title: string, error?: unknown): StoryEvent {\n const now = new Date().toISOString();\n const level = methodToLevel(method);\n\n // Sort notes chronologically so the record tells the story in order\n const sortedNotes = [...this.notes].sort(\n (noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)\n );\n\n this.notes = [];\n\n // Compute duration from first to last note\n const durationMs = calculateNoteDuration(sortedNotes).durationMs;\n\n const event: StoryEventBase = {\n timestamp: now,\n level,\n title,\n ...(this.origin ? { origin: this.origin } : {}),\n notes: sortedNotes,\n ...(durationMs != null ? { durationMs } : {}),\n ...(error ? { error: normalizeError(error) } : {}),\n };\n\n const eventWithSummary = event as StoryEvent;\n Object.defineProperty(eventWithSummary, \"summarize\", {\n value: (options?: ReportOptions) => formatStory(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/** Map internal method names to human-readable level labels */\nfunction methodToLevel(method: StoryMethod): StoryLevel {\n if (method === \"tell\") return \"Information\";\n if (method === \"warn\") return \"Warning\";\n return \"Error\";\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","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/**\n * Get or create a shared Storyteller instance for cross-component logging.\n * First call creates the instance; subsequent calls return the same one.\n *\n * @param options.origin - Origin context for the shared instance\n * @param options.reset - Create a fresh instance (useful in tests)\n *\n * @example\n * ```ts\n * // Same instance everywhere in your app\n * const story = useStoryteller({ origin: { who: \"worker\" } });\n * ```\n */\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/**\n * Create an audience that stores warn and oops stories via your insert function.\n * Tell-level events are filtered out to reduce noise — only warnings and errors are persisted.\n *\n * Note: if the insert function throws, the error is silently caught by the delivery\n * pipeline (Promise.allSettled). Wrap your insert with try/catch to handle failures.\n *\n * @param insert - Function that receives the story event and stores it\n *\n * @example\n * ```ts\n * story.audience.add(\n * dbAudience(async (event) => {\n * await db.insert(\"story_logs\", event);\n * })\n * );\n * ```\n */\nexport function dbAudience(insert: (event: StoryEvent) => Promise<void> | void): AudienceMember {\n return {\n name: \"db\",\n accepts: (event) => event.level === \"Warning\" || event.level === \"Error\",\n hear: async (event) => {\n await insert(event);\n },\n };\n}\n","import type { StoryEventBase } from \"../storyteller\";\nimport { formatStory } from \"../formatting\";\nimport { ANSI, getLevelColor, formatOrigin, colorizeJsonSections } from \"../utils\";\n\nexport type StoryReportOptions = {\n timezone?: string;\n locale?: string;\n detail?: \"brief\" | \"normal\" | \"full\";\n noteLimit?: number;\n showData?: boolean;\n colors?: 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 detail = \"normal\",\n noteLimit = 50,\n showData = true,\n colors = 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 report = formatStory(story, {\n timezone,\n locale,\n detail,\n noteLimit,\n colors,\n });\n const { data } = report;\n const originLabel = formatOrigin(story.origin);\n const levelColor = getLevelColor(story.level);\n const label = (text: string) =>\n colors ? `${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 (detail !== \"brief\" && data.notes.length) {\n lines.push(` ${label(\"Notes\")}:`);\n\n for (const reportNote of data.notes) {\n lines.push(` ${reportNote.when} — ${reportNote.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 (colors) {\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":["calculateNoteDuration"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** Human-readable level labels stored in story records */
|
|
2
|
+
type StoryLevel = "Information" | "Warning" | "Error";
|
|
2
3
|
type StoryContextValue = Record<string, unknown> | string;
|
|
3
4
|
type StoryError = {
|
|
4
5
|
name?: string;
|
|
@@ -24,22 +25,27 @@ type StoryEventBase = {
|
|
|
24
25
|
where?: StoryContextValue;
|
|
25
26
|
};
|
|
26
27
|
notes: StoryNote[];
|
|
28
|
+
durationMs?: number;
|
|
27
29
|
error?: StoryError;
|
|
28
30
|
};
|
|
29
|
-
type
|
|
31
|
+
type ReportOptions = {
|
|
30
32
|
timezone?: string;
|
|
31
33
|
locale?: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
detail?: "brief" | "normal" | "full";
|
|
35
|
+
noteLimit?: number;
|
|
34
36
|
showData?: boolean;
|
|
35
|
-
|
|
37
|
+
colors?: boolean;
|
|
36
38
|
};
|
|
37
|
-
|
|
39
|
+
/** @deprecated Use ReportOptions instead */
|
|
40
|
+
type StorySummaryOptions = ReportOptions;
|
|
41
|
+
type PreviewOptions = ReportOptions & {
|
|
38
42
|
title?: string;
|
|
39
43
|
level?: StoryLevel;
|
|
40
44
|
error?: unknown;
|
|
41
45
|
};
|
|
42
|
-
|
|
46
|
+
/** @deprecated Use PreviewOptions instead */
|
|
47
|
+
type StoryPreviewOptions = PreviewOptions;
|
|
48
|
+
type ReportNote = {
|
|
43
49
|
timestamp: string;
|
|
44
50
|
when: string;
|
|
45
51
|
note: string;
|
|
@@ -49,22 +55,28 @@ type StorySummaryNote = {
|
|
|
49
55
|
where?: StoryContextValue;
|
|
50
56
|
error?: StoryError;
|
|
51
57
|
};
|
|
52
|
-
|
|
58
|
+
/** @deprecated Use ReportNote instead */
|
|
59
|
+
type StorySummaryNote = ReportNote;
|
|
60
|
+
type StoryReport = {
|
|
53
61
|
title: string;
|
|
54
62
|
level: StoryLevel;
|
|
55
63
|
when: string;
|
|
56
64
|
durationMs?: number;
|
|
57
65
|
duration?: string;
|
|
58
66
|
origin?: StoryEventBase["origin"];
|
|
59
|
-
notes:
|
|
67
|
+
notes: ReportNote[];
|
|
60
68
|
error?: StoryError;
|
|
61
69
|
};
|
|
62
|
-
|
|
70
|
+
/** @deprecated Use StoryReport instead */
|
|
71
|
+
type StorySummaryData = StoryReport;
|
|
72
|
+
type FormattedReport = {
|
|
63
73
|
text: string;
|
|
64
|
-
data:
|
|
74
|
+
data: StoryReport;
|
|
65
75
|
};
|
|
76
|
+
/** @deprecated Use FormattedReport instead */
|
|
77
|
+
type StorySummary = FormattedReport;
|
|
66
78
|
type StoryEvent = StoryEventBase & {
|
|
67
|
-
summarize: (options?:
|
|
79
|
+
summarize: (options?: ReportOptions) => FormattedReport;
|
|
68
80
|
};
|
|
69
81
|
type AudienceMember = {
|
|
70
82
|
name: string;
|
|
@@ -88,8 +100,22 @@ declare class AudienceRegistry {
|
|
|
88
100
|
getAll(): AudienceMember[];
|
|
89
101
|
/** Return only the audience members matching the given names */
|
|
90
102
|
getOnly(names: string[]): AudienceMember[];
|
|
103
|
+
/** Check if an audience member is registered by name */
|
|
104
|
+
has(name: string): boolean;
|
|
105
|
+
/** List the names of all registered audience members */
|
|
106
|
+
names(): string[];
|
|
91
107
|
}
|
|
92
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Collects timestamped notes and emits them as a single structured story event.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const story = new Storyteller({ origin: { who: "api-server" } });
|
|
114
|
+
* story.note("Request received", { what: { path: "/checkout" } });
|
|
115
|
+
* story.note("Validated cart");
|
|
116
|
+
* story.tell("Checkout started");
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
93
119
|
declare class Storyteller {
|
|
94
120
|
readonly audience: AudienceRegistry;
|
|
95
121
|
private readonly origin?;
|
|
@@ -98,21 +124,31 @@ declare class Storyteller {
|
|
|
98
124
|
origin?: StoryEventBase["origin"];
|
|
99
125
|
audiences?: AudienceMember[];
|
|
100
126
|
});
|
|
101
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Add a timestamped note to the current story.
|
|
129
|
+
* @param text - What happened
|
|
130
|
+
* @param data - Optional context: who did it, what was involved, where it happened, any error
|
|
131
|
+
* @returns `this` for chaining
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* story.note("Card charged", { what: { amount: "$42" }, where: "stripe" });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
102
138
|
note(text: string, data?: NoteData): this;
|
|
103
139
|
/** Clear all accumulated notes without emitting a story */
|
|
104
140
|
reset(): this;
|
|
105
|
-
/**
|
|
106
|
-
summarize(options?:
|
|
107
|
-
/**
|
|
141
|
+
/** Preview the current notes as a formatted report without emitting or clearing them */
|
|
142
|
+
summarize(options?: PreviewOptions): FormattedReport;
|
|
143
|
+
/** Tell a success story — everything went well */
|
|
108
144
|
tell(title: string): {
|
|
109
145
|
to: (...names: string[]) => void;
|
|
110
146
|
};
|
|
111
|
-
/**
|
|
147
|
+
/** Tell a cautionary story — something was off but it's handled */
|
|
112
148
|
warn(title: string): {
|
|
113
149
|
to: (...names: string[]) => void;
|
|
114
150
|
};
|
|
115
|
-
/**
|
|
151
|
+
/** Tell an error story — something broke. Pass the error for automatic normalization. */
|
|
116
152
|
oops(title: string, error?: unknown): {
|
|
117
153
|
to: (...names: string[]) => void;
|
|
118
154
|
};
|
|
@@ -123,29 +159,84 @@ declare class Storyteller {
|
|
|
123
159
|
/** Deliver a story event to matching audience members */
|
|
124
160
|
private deliver;
|
|
125
161
|
}
|
|
126
|
-
|
|
127
|
-
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format a story event into a human-readable report with optional colors.
|
|
165
|
+
*
|
|
166
|
+
* @param story - The story event to format
|
|
167
|
+
* @param options - Formatting options (timezone, locale, detail level, colors)
|
|
168
|
+
* @returns A FormattedReport with both text (for display) and data (structured)
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const report = formatStory(event, { colors: false, detail: "brief" });
|
|
173
|
+
* console.log(report.text);
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
declare function formatStory(story: StoryEventBase, options?: ReportOptions): FormattedReport;
|
|
177
|
+
/** Convert milliseconds into a human-readable duration string */
|
|
178
|
+
declare function formatDuration(milliseconds: number): string;
|
|
179
|
+
/** @deprecated Use formatStory instead */
|
|
180
|
+
declare const summarizeStory: typeof formatStory;
|
|
128
181
|
|
|
129
182
|
type StorytellerSharedOptions = {
|
|
130
183
|
origin?: StoryEventBase["origin"];
|
|
131
184
|
reset?: boolean;
|
|
132
185
|
};
|
|
133
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Get or create a shared Storyteller instance for cross-component logging.
|
|
188
|
+
* First call creates the instance; subsequent calls return the same one.
|
|
189
|
+
*
|
|
190
|
+
* @param options.origin - Origin context for the shared instance
|
|
191
|
+
* @param options.reset - Create a fresh instance (useful in tests)
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* // Same instance everywhere in your app
|
|
196
|
+
* const story = useStoryteller({ origin: { who: "worker" } });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
134
199
|
declare function useStoryteller(options?: StorytellerSharedOptions): Storyteller;
|
|
135
200
|
|
|
136
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Create an audience that prints stories to the console with color-coded grouped output.
|
|
203
|
+
* Registered by default on every Storyteller instance.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* // Already included — but you can re-add after removing:
|
|
208
|
+
* story.audience.add(consoleAudience());
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
137
211
|
declare function consoleAudience(): AudienceMember;
|
|
138
212
|
|
|
139
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* Create an audience that stores warn and oops stories via your insert function.
|
|
215
|
+
* Tell-level events are filtered out to reduce noise — only warnings and errors are persisted.
|
|
216
|
+
*
|
|
217
|
+
* Note: if the insert function throws, the error is silently caught by the delivery
|
|
218
|
+
* pipeline (Promise.allSettled). Wrap your insert with try/catch to handle failures.
|
|
219
|
+
*
|
|
220
|
+
* @param insert - Function that receives the story event and stores it
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* story.audience.add(
|
|
225
|
+
* dbAudience(async (event) => {
|
|
226
|
+
* await db.insert("story_logs", event);
|
|
227
|
+
* })
|
|
228
|
+
* );
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
140
231
|
declare function dbAudience(insert: (event: StoryEvent) => Promise<void> | void): AudienceMember;
|
|
141
232
|
|
|
142
233
|
type StoryReportOptions = {
|
|
143
234
|
timezone?: string;
|
|
144
235
|
locale?: string;
|
|
145
|
-
|
|
146
|
-
|
|
236
|
+
detail?: "brief" | "normal" | "full";
|
|
237
|
+
noteLimit?: number;
|
|
147
238
|
showData?: boolean;
|
|
148
|
-
|
|
239
|
+
colors?: boolean;
|
|
149
240
|
};
|
|
150
241
|
/** Generate a formatted report from an array of story events, grouped by day */
|
|
151
242
|
declare function writeStoryReport(stories: StoryEventBase[], options?: StoryReportOptions): string;
|
|
@@ -163,13 +254,5 @@ declare const ANSI: {
|
|
|
163
254
|
declare function getLevelColor(level: StoryLevel): string;
|
|
164
255
|
/** Format an origin context into a human-readable path like "app / page / component" */
|
|
165
256
|
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
257
|
|
|
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,
|
|
258
|
+
export { ANSI, type AudienceMember, type FormattedReport, type PreviewOptions, type ReportNote, type ReportOptions, type StoryContextValue, type StoryError, type StoryEvent, type StoryEventBase, type StoryLevel, type StoryNote, type StoryPreviewOptions, type StoryReport, type StoryReportOptions, type StorySummary, type StorySummaryData, type StorySummaryNote, type StorySummaryOptions, Storyteller, consoleAudience, dbAudience, formatDuration, formatOrigin, formatStory, getLevelColor, summarizeStory, useStoryteller, writeStoryReport };
|