@lovelaces-io/storyteller 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -197,8 +197,138 @@ function readClockTime(timestamp) {
197
197
  return timePart.length === 8 ? timePart : timestamp;
198
198
  }
199
199
 
200
- // src/normalize.ts
200
+ // src/redaction.ts
201
201
  var REDACTED = "[redacted]";
202
+ var RULE_GROUPS = [
203
+ {
204
+ // Vendor key formats
205
+ anchor: /sk[-_]|rk_|gh[opusr]_|github_pat_|glpat-|npm_|xox[abprs]-|AIza|SG\.|AKIA|ASIA|eyJ|PRIVATE KEY/,
206
+ rules: [
207
+ // Stripe secret and restricted keys
208
+ { pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}\b/g, replacement: REDACTED },
209
+ // OpenAI, Anthropic and similar `sk-` keys
210
+ { pattern: /\bsk-(?:[A-Za-z0-9]+-)*[A-Za-z0-9_-]{16,}/g, replacement: REDACTED },
211
+ // GitHub tokens, classic and fine-grained
212
+ { pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{16,}\b/g, replacement: REDACTED },
213
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: REDACTED },
214
+ // GitLab, npm, Slack, Google, SendGrid, AWS access key ids
215
+ { pattern: /\bglpat-[A-Za-z0-9_-]{16,}\b/g, replacement: REDACTED },
216
+ { pattern: /\bnpm_[A-Za-z0-9]{30,}\b/g, replacement: REDACTED },
217
+ { pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, replacement: REDACTED },
218
+ { pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g, replacement: REDACTED },
219
+ { pattern: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replacement: REDACTED },
220
+ { pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: REDACTED },
221
+ // JSON Web Tokens
222
+ { pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, replacement: REDACTED },
223
+ // PEM private keys, whole block
224
+ { pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, replacement: REDACTED }
225
+ ]
226
+ },
227
+ {
228
+ // Authorization header values: keep the scheme, drop the credential
229
+ anchor: /\b(?:Bearer|Basic|Token|Digest) /,
230
+ rules: [{ pattern: /\b(Bearer|Basic|Token|Digest)\s+[A-Za-z0-9._~+/=-]{12,}/g, replacement: `$1 ${REDACTED}` }]
231
+ },
232
+ {
233
+ // URLs: a password in the userinfo, a secret in the query string
234
+ anchor: /:\/\/|[?&]/,
235
+ rules: [
236
+ { pattern: /(:\/\/[^\s/:@]+:)[^\s@/]+(@)/g, replacement: `$1${REDACTED}$2` },
237
+ {
238
+ pattern: /([?&](?:token|access_token|refresh_token|id_token|api_key|apikey|api-key|key|secret|client_secret|password|passwd|pwd|signature|sig|auth|authorization)=)[^&\s#]+/gi,
239
+ replacement: `$1${REDACTED}`
240
+ }
241
+ ]
242
+ }
243
+ ];
244
+ var STRONG_KEY_HINT = /password|passwd|passphrase|secret|apikey|privatekey|credential|authorization|accesskey|sessionid|cookie|refreshtoken|accesstoken|idtoken|clientkey|signingkey|encryptionkey|masterkey|servicekey/;
245
+ var WEAK_KEY_HINT = /token|key|auth|bearer|cert|pwd/;
246
+ var keyHints = /* @__PURE__ */ new Map();
247
+ var KEY_HINT_CACHE_LIMIT = 4096;
248
+ function hintFor(key) {
249
+ const cached = keyHints.get(key);
250
+ if (cached !== void 0) return cached;
251
+ const reduced = normalizeKeyForMatching(key);
252
+ const hint = STRONG_KEY_HINT.test(reduced) ? "strong" : WEAK_KEY_HINT.test(reduced) ? "weak" : "none";
253
+ if (keyHints.size >= KEY_HINT_CACHE_LIMIT) keyHints.clear();
254
+ keyHints.set(key, hint);
255
+ return hint;
256
+ }
257
+ var MIN_STRONG_LENGTH = 4;
258
+ var MIN_ENTROPY_LENGTH = 16;
259
+ var MIN_ENTROPY_BITS = 3;
260
+ var MIN_STRICT_LENGTH = 32;
261
+ function normalizeKeyForMatching(key) {
262
+ return key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
263
+ }
264
+ function entropyOf(text) {
265
+ const counts = /* @__PURE__ */ new Map();
266
+ for (const char of text) counts.set(char, (counts.get(char) ?? 0) + 1);
267
+ let bits = 0;
268
+ for (const count of counts.values()) {
269
+ const p = count / text.length;
270
+ bits -= p * Math.log2(p);
271
+ }
272
+ return bits;
273
+ }
274
+ function looksRandom(value, minimumLength) {
275
+ if (value.length < minimumLength || /\s/.test(value)) return false;
276
+ if (/^[0-9a-f-]+$/i.test(value) || /^[0-9.-]+$/.test(value)) return false;
277
+ return entropyOf(value) >= MIN_ENTROPY_BITS;
278
+ }
279
+ function keyLooksSecret(key, value) {
280
+ if (typeof value !== "string") return false;
281
+ const hint = hintFor(key);
282
+ if (hint === "strong") return value.length >= MIN_STRONG_LENGTH;
283
+ if (hint === "weak") return looksRandom(value, MIN_ENTROPY_LENGTH);
284
+ return false;
285
+ }
286
+ var ANY_ANCHOR = new RegExp(RULE_GROUPS.map((group) => group.anchor.source).join("|"));
287
+ function redactString(value, strictness = "balanced") {
288
+ if (strictness === "off" || value.length < 8) return value;
289
+ let result = value;
290
+ if (!ANY_ANCHOR.test(value)) return strictness === "strict" ? redactRandomRuns(value) : value;
291
+ for (const group of RULE_GROUPS) {
292
+ if (!group.anchor.test(result)) continue;
293
+ for (const { pattern, replacement } of group.rules) {
294
+ pattern.lastIndex = 0;
295
+ result = result.replace(pattern, replacement);
296
+ }
297
+ }
298
+ return strictness === "strict" ? redactRandomRuns(result) : result;
299
+ }
300
+ function redactRandomRuns(text) {
301
+ return text.replace(
302
+ /(?<![A-Za-z0-9+/=_-])[A-Za-z0-9+/_-]{32,}={0,2}(?![A-Za-z0-9+/=_-])/g,
303
+ (run) => looksRandom(run, MIN_STRICT_LENGTH) && characterClasses(run) >= 3 ? REDACTED : run
304
+ );
305
+ }
306
+ function characterClasses(text) {
307
+ return [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((test) => test.test(text)).length;
308
+ }
309
+ function redactJson(value, options = {}) {
310
+ const strictness = options.strictness ?? "balanced";
311
+ const keys = options.redactKeys;
312
+ const walk = (node) => {
313
+ if (typeof node === "string") return redactString(node, strictness);
314
+ if (Array.isArray(node)) return node.map(walk);
315
+ if (node && typeof node === "object") {
316
+ const out = {};
317
+ for (const [key, child] of Object.entries(node)) {
318
+ if (keys?.has(normalizeKeyForMatching(key)) || strictness !== "off" && keyLooksSecret(key, child)) {
319
+ out[key] = REDACTED;
320
+ } else {
321
+ out[key] = walk(child);
322
+ }
323
+ }
324
+ return out;
325
+ }
326
+ return node;
327
+ };
328
+ return walk(value);
329
+ }
330
+
331
+ // src/normalize.ts
202
332
  var DEFAULT_REDACT_KEYS = [
203
333
  "password",
204
334
  "passphrase",
@@ -229,7 +359,8 @@ function normalizeValue(input, options = {}) {
229
359
  redactKeys: new Set(
230
360
  (options.redactKeys ?? DEFAULT_REDACT_KEYS).map(normalizeKeyForMatching)
231
361
  ),
232
- redact: options.redact ?? true
362
+ redact: options.redact ?? true,
363
+ redactValues: options.redactValues ?? "balanced"
233
364
  };
234
365
  try {
235
366
  return normalizeUnknown(input, resolved, 0, "$", /* @__PURE__ */ new Map());
@@ -246,7 +377,8 @@ function normalizeError(rawError, options = {}) {
246
377
  redactKeys: new Set(
247
378
  (options.redactKeys ?? DEFAULT_REDACT_KEYS).map(normalizeKeyForMatching)
248
379
  ),
249
- redact: options.redact ?? true
380
+ redact: options.redact ?? true,
381
+ redactValues: options.redactValues ?? "balanced"
250
382
  };
251
383
  return normalizeErrorInternal(rawError, resolved, 0);
252
384
  }
@@ -254,7 +386,7 @@ function normalizeErrorInternal(rawError, options, causeDepth) {
254
386
  if (!(rawError instanceof Error)) {
255
387
  if (isPlainRecord(rawError)) {
256
388
  const record = rawError;
257
- const message = typeof record["message"] === "string" ? record["message"] : void 0;
389
+ const message = typeof record["message"] === "string" ? redactText(record["message"], options) : void 0;
258
390
  const name = typeof record["name"] === "string" ? record["name"] : void 0;
259
391
  if (message !== void 0 || name !== void 0) {
260
392
  return {
@@ -263,14 +395,14 @@ function normalizeErrorInternal(rawError, options, causeDepth) {
263
395
  };
264
396
  }
265
397
  }
266
- return { message: safeStringify(rawError, options.maxStringLength) };
398
+ return { message: redactText(safeStringify(rawError, options.maxStringLength), options) };
267
399
  }
268
400
  const normalized = {
269
401
  name: rawError.name,
270
- message: rawError.message
402
+ message: redactText(rawError.message, options)
271
403
  };
272
404
  if (rawError.stack !== void 0) {
273
- normalized.stack = truncateString(rawError.stack, options.maxStringLength);
405
+ normalized.stack = truncateString(redactText(rawError.stack, options), options.maxStringLength);
274
406
  }
275
407
  const cause = rawError.cause;
276
408
  if (cause !== void 0) {
@@ -292,7 +424,7 @@ function normalizeUnknown(value, options, depth, path, ancestors) {
292
424
  if (value === null) return null;
293
425
  const valueType = typeof value;
294
426
  if (valueType === "string") {
295
- return truncateString(value, options.maxStringLength);
427
+ return truncateString(redactText(value, options), options.maxStringLength);
296
428
  }
297
429
  if (valueType === "number") {
298
430
  return Number.isFinite(value) ? value : String(value);
@@ -486,10 +618,13 @@ function redactOrNormalize(key, value, options, depth, path, ancestors) {
486
618
  if (options.redact && options.redactKeys.has(normalizeKeyForMatching(key))) {
487
619
  return REDACTED;
488
620
  }
621
+ if (options.redact && options.redactValues !== "off" && keyLooksSecret(key, value)) {
622
+ return REDACTED;
623
+ }
489
624
  return normalizeUnknown(value, options, depth, path, ancestors);
490
625
  }
491
- function normalizeKeyForMatching(key) {
492
- return key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
626
+ function redactText(value, options) {
627
+ return options.redact ? redactString(value, options.redactValues) : value;
493
628
  }
494
629
  function readConstructorName(value) {
495
630
  try {
@@ -1160,16 +1295,293 @@ function dbAudience(insert) {
1160
1295
  return {
1161
1296
  name: "db",
1162
1297
  hears: ["story"],
1163
- accepts: (emission) => emission.kind === "story" && (emission.level === "Warning" || emission.level === "Error"),
1164
- hear: async (emission) => {
1165
- if (emission.kind !== "story") return;
1166
- await insert(emission);
1298
+ accepts: (event) => event.level === "Warning" || event.level === "Error",
1299
+ hear: async (event) => {
1300
+ await insert(event);
1301
+ }
1302
+ };
1303
+ }
1304
+
1305
+ // src/store/storyStore.ts
1306
+ var DEFAULT_KEY_SET = new Set(DEFAULT_REDACT_KEYS.map(normalizeKeyForMatching));
1307
+ function toStoredStory(event, options = {}) {
1308
+ const { timestamp, level, title, storyId, parentStoryId, origin, notes, durationMs, droppedEmissions, error } = event;
1309
+ const strictness = options.redactValues ?? "balanced";
1310
+ const scrub = (value) => strictness === "off" ? clone(value) : redactJson(clone(value), { redactKeys: DEFAULT_KEY_SET, strictness });
1311
+ const record = {
1312
+ timestamp,
1313
+ level,
1314
+ title: scrub(title),
1315
+ storyId: storyId ?? generateStoryId(),
1316
+ notes: scrub(notes)
1317
+ };
1318
+ if (parentStoryId !== void 0) record.parentStoryId = parentStoryId;
1319
+ if (origin !== void 0) record.origin = scrub(origin);
1320
+ if (durationMs !== void 0) record.durationMs = durationMs;
1321
+ if (droppedEmissions !== void 0) record.droppedEmissions = droppedEmissions;
1322
+ if (error !== void 0) record.error = scrub(error);
1323
+ return record;
1324
+ }
1325
+ function clone(value) {
1326
+ return JSON.parse(JSON.stringify(value));
1327
+ }
1328
+ function generateStoryId() {
1329
+ const random = globalThis.crypto?.randomUUID;
1330
+ if (random) return random.call(globalThis.crypto);
1331
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1332
+ }
1333
+ function storySearchText(story) {
1334
+ const parts = [story.title];
1335
+ for (const note of story.notes) {
1336
+ parts.push(note.note);
1337
+ for (const value of [note.who, note.what, note.where]) {
1338
+ if (typeof value === "string") parts.push(value);
1167
1339
  }
1340
+ if (note.error?.message) parts.push(note.error.message);
1341
+ }
1342
+ if (story.error?.message) parts.push(story.error.message);
1343
+ return parts.join("\n");
1344
+ }
1345
+ function flattenOrigin(origin) {
1346
+ const flat = {};
1347
+ if (!origin) return flat;
1348
+ for (const key of ["who", "what", "where"]) {
1349
+ const value = origin[key];
1350
+ if (value === void 0 || value === null) continue;
1351
+ flat[key] = typeof value === "object" ? JSON.stringify(value) : String(value);
1352
+ }
1353
+ return flat;
1354
+ }
1355
+ function canonicalRow(event) {
1356
+ const story = toStoredStory(event);
1357
+ const origin = flattenOrigin(story.origin);
1358
+ return {
1359
+ story_id: story.storyId,
1360
+ parent_story_id: story.parentStoryId ?? null,
1361
+ timestamp: story.timestamp,
1362
+ level: story.level,
1363
+ title: story.title,
1364
+ origin_who: origin.who ?? null,
1365
+ origin_what: origin.what ?? null,
1366
+ origin_where: origin.where ?? null,
1367
+ duration_ms: story.durationMs ?? null,
1368
+ error_message: story.error?.message ?? null,
1369
+ notes: JSON.stringify(story.notes),
1370
+ search_text: storySearchText(story),
1371
+ record: JSON.stringify(story)
1168
1372
  };
1169
1373
  }
1374
+ function matchesQuery(story, query = {}) {
1375
+ if (query.since && Date.parse(story.timestamp) < query.since.getTime()) return false;
1376
+ if (query.until && Date.parse(story.timestamp) >= query.until.getTime()) return false;
1377
+ if (query.level !== void 0) {
1378
+ const levels = Array.isArray(query.level) ? query.level : [query.level];
1379
+ if (!levels.includes(story.level)) return false;
1380
+ }
1381
+ if (query.minimumLevel && !meetsLevel(story.level, query.minimumLevel)) return false;
1382
+ if (query.parentStoryId !== void 0 && story.parentStoryId !== query.parentStoryId) return false;
1383
+ if (query.slowerThanMs !== void 0 && !((story.durationMs ?? -1) > query.slowerThanMs)) return false;
1384
+ if (query.failed !== void 0) {
1385
+ const failed = story.level === "Error" || story.error !== void 0;
1386
+ if (failed !== query.failed) return false;
1387
+ }
1388
+ if (query.about) {
1389
+ if (!storySearchText(story).toLowerCase().includes(query.about.toLowerCase())) return false;
1390
+ }
1391
+ if (query.from) {
1392
+ const origin = Object.values(flattenOrigin(story.origin)).join("\n").toLowerCase();
1393
+ if (!origin.includes(query.from.toLowerCase())) return false;
1394
+ }
1395
+ return true;
1396
+ }
1397
+ function applyQuery(stories2, query = {}) {
1398
+ const matched = [];
1399
+ let position = 0;
1400
+ for (const story of stories2) {
1401
+ if (matchesQuery(story, query)) matched.push({ story, position });
1402
+ position++;
1403
+ }
1404
+ const direction = query.order === "oldest" ? 1 : -1;
1405
+ matched.sort((a, b) => {
1406
+ const byTime = Date.parse(a.story.timestamp) - Date.parse(b.story.timestamp);
1407
+ return (byTime !== 0 ? byTime : a.position - b.position) * direction;
1408
+ });
1409
+ const offset = Math.max(0, query.offset ?? 0);
1410
+ const end = query.limit !== void 0 ? offset + Math.max(0, query.limit) : void 0;
1411
+ return matched.slice(offset, end).map((entry) => entry.story);
1412
+ }
1413
+
1414
+ // src/store/memoryStore.ts
1415
+ function memoryStore(options = {}) {
1416
+ const capacity = Math.max(1, options.capacity ?? 1e4);
1417
+ const stories2 = /* @__PURE__ */ new Map();
1418
+ return {
1419
+ get size() {
1420
+ return stories2.size;
1421
+ },
1422
+ clear() {
1423
+ stories2.clear();
1424
+ },
1425
+ async append(event) {
1426
+ const record = toStoredStory(event);
1427
+ stories2.delete(record.storyId);
1428
+ stories2.set(record.storyId, record);
1429
+ while (stories2.size > capacity) {
1430
+ const oldest = stories2.keys().next().value;
1431
+ if (oldest === void 0) break;
1432
+ stories2.delete(oldest);
1433
+ }
1434
+ },
1435
+ async get(storyId) {
1436
+ return stories2.get(storyId);
1437
+ },
1438
+ async query(criteria = {}) {
1439
+ return applyQuery(stories2.values(), criteria);
1440
+ },
1441
+ async children(parentStoryId) {
1442
+ return applyQuery(stories2.values(), { parentStoryId, order: "oldest" });
1443
+ },
1444
+ async prune(before) {
1445
+ const boundary = before.getTime();
1446
+ let removed = 0;
1447
+ for (const [id, story] of stories2) {
1448
+ if (Date.parse(story.timestamp) < boundary) {
1449
+ stories2.delete(id);
1450
+ removed++;
1451
+ }
1452
+ }
1453
+ return removed;
1454
+ }
1455
+ };
1456
+ }
1457
+
1458
+ // src/store/storeAudience.ts
1459
+ function storeAudience(store, options = {}) {
1460
+ return {
1461
+ name: options.name ?? "store",
1462
+ hears: ["story"],
1463
+ accepts: (event) => (options.level === void 0 || meetsLevel(event.level, options.level)) && (options.accepts === void 0 || options.accepts(event)),
1464
+ hear: async (event) => {
1465
+ await store.append(event);
1466
+ }
1467
+ };
1468
+ }
1469
+
1470
+ // src/store/stories.ts
1471
+ var UNITS = {
1472
+ ms: 1,
1473
+ s: 1e3,
1474
+ m: 6e4,
1475
+ h: 36e5,
1476
+ d: 864e5,
1477
+ w: 6048e5
1478
+ };
1479
+ function parseDuration(input) {
1480
+ if (typeof input === "number") {
1481
+ if (!Number.isFinite(input) || input < 0) throw new RangeError(`Not a duration: ${input}`);
1482
+ return input;
1483
+ }
1484
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)\s*$/i.exec(input);
1485
+ if (!match) throw new RangeError(`Not a duration: "${input}" (expected e.g. "30s", "5m", "24h", "7d", "2w")`);
1486
+ return Number(match[1]) * UNITS[match[2].toLowerCase()];
1487
+ }
1488
+ var StoryQueryBuilder = class _StoryQueryBuilder {
1489
+ constructor(store, query, now) {
1490
+ this.store = store;
1491
+ this.query = query;
1492
+ this.now = now;
1493
+ }
1494
+ with(patch) {
1495
+ return new _StoryQueryBuilder(this.store, { ...this.query, ...patch }, this.now);
1496
+ }
1497
+ instant(input) {
1498
+ return input instanceof Date ? input : new Date(this.now().getTime() - parseDuration(input));
1499
+ }
1500
+ /** Title, note text, scalar context or error message mentions this */
1501
+ about(text) {
1502
+ return this.with({ about: text });
1503
+ }
1504
+ /** The origin — who, what or where — mentions this */
1505
+ from(origin) {
1506
+ return this.with({ from: origin });
1507
+ }
1508
+ /** Exactly this level. Accepts the aliases: `"info"`, `"warn"`, `"oops"`. */
1509
+ level(level) {
1510
+ return this.with({ level: toStoryLevel(level) });
1511
+ }
1512
+ /** This level or worse */
1513
+ atLeast(level) {
1514
+ return this.with({ minimumLevel: toStoryLevel(level) });
1515
+ }
1516
+ /** Carried an error, or closed at Error level */
1517
+ failing() {
1518
+ return this.with({ failed: true });
1519
+ }
1520
+ /** Neither an error nor closed at Error level */
1521
+ succeeding() {
1522
+ return this.with({ failed: false });
1523
+ }
1524
+ /** Took longer than this: `"5s"`, `"2m"`, or milliseconds */
1525
+ slowerThan(duration) {
1526
+ return this.with({ slowerThanMs: parseDuration(duration) });
1527
+ }
1528
+ /** Began within this long ago (`"24h"`), or at or after this date */
1529
+ since(when) {
1530
+ return this.with({ since: this.instant(when) });
1531
+ }
1532
+ /** Began before this long ago, or before this date */
1533
+ until(when) {
1534
+ return this.with({ until: this.instant(when) });
1535
+ }
1536
+ /** The chapters of this story */
1537
+ under(parentStoryId) {
1538
+ return this.with({ parentStoryId });
1539
+ }
1540
+ /** Most recent first — the default */
1541
+ newest() {
1542
+ return this.with({ order: "newest" });
1543
+ }
1544
+ /** Earliest first */
1545
+ oldest() {
1546
+ return this.with({ order: "oldest" });
1547
+ }
1548
+ /** At most this many */
1549
+ limit(count) {
1550
+ return this.with({ limit: count });
1551
+ }
1552
+ /** Skip this many first */
1553
+ skip(count) {
1554
+ return this.with({ offset: count });
1555
+ }
1556
+ /** The structured criteria this question compiles to — what an adapter receives */
1557
+ toQuery() {
1558
+ return { ...this.query };
1559
+ }
1560
+ /** Every matching story */
1561
+ all() {
1562
+ return this.store.query(this.toQuery());
1563
+ }
1564
+ /** The first match, or nothing */
1565
+ async first() {
1566
+ const [story] = await this.store.query({ ...this.query, limit: 1 });
1567
+ return story;
1568
+ }
1569
+ /** How many match, ignoring paging */
1570
+ async count() {
1571
+ const { limit: _limit, offset: _offset, ...unpaged } = this.query;
1572
+ return (await this.store.query(unpaged)).length;
1573
+ }
1574
+ /** An awaited builder resolves to `.all()` */
1575
+ then(onFulfilled, onRejected) {
1576
+ return this.all().then(onFulfilled, onRejected);
1577
+ }
1578
+ };
1579
+ function stories(store, options = {}) {
1580
+ return new StoryQueryBuilder(store, {}, options.now ?? (() => /* @__PURE__ */ new Date()));
1581
+ }
1170
1582
 
1171
1583
  // src/report/writeStoryReport.ts
1172
- function writeStoryReport(stories, options = {}) {
1584
+ function writeStoryReport(stories2, options = {}) {
1173
1585
  const {
1174
1586
  timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
1175
1587
  locale = "en-US",
@@ -1178,10 +1590,10 @@ function writeStoryReport(stories, options = {}) {
1178
1590
  showData = true,
1179
1591
  colors = true
1180
1592
  } = options;
1181
- if (!stories.length) {
1593
+ if (!stories2.length) {
1182
1594
  return "Storyteller Report\n\n(no stories)\n";
1183
1595
  }
1184
- const sorted = [...stories].sort(
1596
+ const sorted = [...stories2].sort(
1185
1597
  (storyA, storyB) => Date.parse(storyA.timestamp) - Date.parse(storyB.timestamp)
1186
1598
  );
1187
1599
  const dateFormatter = new Intl.DateTimeFormat(locale, {
@@ -1273,23 +1685,34 @@ export {
1273
1685
  AudienceRegistry,
1274
1686
  DEFAULT_REDACT_KEYS,
1275
1687
  REDACTED,
1688
+ StoryQueryBuilder,
1276
1689
  Storyteller,
1690
+ applyQuery,
1691
+ canonicalRow,
1277
1692
  consoleAudience,
1278
1693
  dbAudience,
1694
+ flattenOrigin,
1279
1695
  formatDuration,
1280
1696
  formatOrigin,
1281
1697
  formatStory,
1282
1698
  getLevelColor,
1699
+ matchesQuery,
1283
1700
  meetsLevel,
1701
+ memoryStore,
1284
1702
  ndjsonAudience,
1285
1703
  normalizeError,
1286
1704
  normalizeValue,
1705
+ parseDuration,
1287
1706
  readEnvironmentValue,
1288
1707
  resolveColors,
1289
1708
  resolveMinimumLevel,
1290
1709
  resolveOutputFormat,
1710
+ storeAudience,
1711
+ stories,
1712
+ storySearchText,
1291
1713
  summarizeContext,
1292
1714
  summarizeStory,
1715
+ toStoredStory,
1293
1716
  toStoryLevel,
1294
1717
  useStoryteller,
1295
1718
  writeStoryReport