@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.
@@ -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
- type StoryLevel = "tell" | "warn" | "oops";
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 StorySummaryOptions = {
31
+ type ReportOptions = {
30
32
  timezone?: string;
31
33
  locale?: string;
32
- verbosity?: "brief" | "normal" | "full";
33
- maxNotes?: number;
34
+ detail?: "brief" | "normal" | "full";
35
+ noteLimit?: number;
34
36
  showData?: boolean;
35
- colorize?: boolean;
37
+ colors?: boolean;
36
38
  };
37
- type StoryPreviewOptions = StorySummaryOptions & {
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
- type StorySummaryNote = {
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
- type StorySummaryData = {
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: StorySummaryNote[];
67
+ notes: ReportNote[];
60
68
  error?: StoryError;
61
69
  };
62
- type StorySummary = {
70
+ /** @deprecated Use StoryReport instead */
71
+ type StorySummaryData = StoryReport;
72
+ type FormattedReport = {
63
73
  text: string;
64
- data: StorySummaryData;
74
+ data: StoryReport;
65
75
  };
76
+ /** @deprecated Use FormattedReport instead */
77
+ type StorySummary = FormattedReport;
66
78
  type StoryEvent = StoryEventBase & {
67
- summarize: (options?: StorySummaryOptions) => StorySummary;
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
- /** Core logging class that collects timestamped notes and emits them as structured story events */
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
- /** Add a timestamped note with optional context (who, what, where, error) */
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
- /** 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) */
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
- /** Emit a story at the "warn" level (something was off) */
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
- /** Emit a story at the "oops" level (something broke) with an optional error */
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
- /** Generate a formatted, human-readable summary from a story event */
127
- declare function summarizeStory(story: StoryEventBase, options?: StorySummaryOptions): StorySummary;
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
- /** Return a shared singleton Storyteller instance for cross-component or cross-service logging */
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
- /** Create an audience that logs stories to the browser console with color-coded grouped output */
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
- /** Create an audience that persists warn and oops stories to a database via the provided insert function */
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
- verbosity?: "brief" | "normal" | "full";
146
- maxNotesPerStory?: number;
236
+ detail?: "brief" | "normal" | "full";
237
+ noteLimit?: number;
147
238
  showData?: boolean;
148
- colorize?: boolean;
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, colorizeJsonSections, consoleAudience, countBrackets, dbAudience, formatOrigin, getLevelColor, summarizeStory, useStoryteller, writeStoryReport };
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 };