@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.
@@ -0,0 +1,29 @@
1
+ import { e as StoryStore } from '../stories-BgN4BSoZ.cjs';
2
+ export { O as CanonicalRow, $ as DurationInput, Q as StoredStory, a0 as StoriesOptions, T as StoryQuery, a3 as StoryQueryBuilder, U as ToStoredStoryOptions, V as applyQuery, W as canonicalRow, Y as matchesQuery, a1 as parseDuration, a2 as stories, Z as storySearchText, _ as toStoredStory } from '../stories-BgN4BSoZ.cjs';
3
+
4
+ type FileStore = StoryStore & {
5
+ /** Where the stories are */
6
+ readonly path: string;
7
+ /** Resolves once every write queued so far has reached the file — call before exiting */
8
+ flush(): Promise<void>;
9
+ };
10
+ /**
11
+ * Append-only: `append` writes one line, reads parse the file, `prune`
12
+ * rewrites it without the old stories. Simple on purpose. Right for a
13
+ * single process and a few hundred thousand stories; past that, an adapter
14
+ * with an index is the next step, and it answers the same queries.
15
+ *
16
+ * Writes are serialized so two appends cannot interleave and a prune cannot
17
+ * race an append. A line that does not parse — a crash mid-write — is skipped,
18
+ * never fatal: a store that cannot read its own file is not much of a store.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { fileStore } from "@lovelaces-io/storyteller/store/file";
23
+ * const stories = fileStore("./stories.jsonl");
24
+ * story.audience.add(storeAudience(stories));
25
+ * ```
26
+ */
27
+ declare function fileStore(path: string): FileStore;
28
+
29
+ export { type FileStore, StoryStore, fileStore };
@@ -0,0 +1,29 @@
1
+ import { e as StoryStore } from '../stories-BgN4BSoZ.js';
2
+ export { O as CanonicalRow, $ as DurationInput, Q as StoredStory, a0 as StoriesOptions, T as StoryQuery, a3 as StoryQueryBuilder, U as ToStoredStoryOptions, V as applyQuery, W as canonicalRow, Y as matchesQuery, a1 as parseDuration, a2 as stories, Z as storySearchText, _ as toStoredStory } from '../stories-BgN4BSoZ.js';
3
+
4
+ type FileStore = StoryStore & {
5
+ /** Where the stories are */
6
+ readonly path: string;
7
+ /** Resolves once every write queued so far has reached the file — call before exiting */
8
+ flush(): Promise<void>;
9
+ };
10
+ /**
11
+ * Append-only: `append` writes one line, reads parse the file, `prune`
12
+ * rewrites it without the old stories. Simple on purpose. Right for a
13
+ * single process and a few hundred thousand stories; past that, an adapter
14
+ * with an index is the next step, and it answers the same queries.
15
+ *
16
+ * Writes are serialized so two appends cannot interleave and a prune cannot
17
+ * race an append. A line that does not parse — a crash mid-write — is skipped,
18
+ * never fatal: a store that cannot read its own file is not much of a store.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { fileStore } from "@lovelaces-io/storyteller/store/file";
23
+ * const stories = fileStore("./stories.jsonl");
24
+ * story.audience.add(storeAudience(stories));
25
+ * ```
26
+ */
27
+ declare function fileStore(path: string): FileStore;
28
+
29
+ export { type FileStore, StoryStore, fileStore };
@@ -0,0 +1,478 @@
1
+ // src/redaction.ts
2
+ var REDACTED = "[redacted]";
3
+ var RULE_GROUPS = [
4
+ {
5
+ // Vendor key formats
6
+ anchor: /sk[-_]|rk_|gh[opusr]_|github_pat_|glpat-|npm_|xox[abprs]-|AIza|SG\.|AKIA|ASIA|eyJ|PRIVATE KEY/,
7
+ rules: [
8
+ // Stripe secret and restricted keys
9
+ { pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}\b/g, replacement: REDACTED },
10
+ // OpenAI, Anthropic and similar `sk-` keys
11
+ { pattern: /\bsk-(?:[A-Za-z0-9]+-)*[A-Za-z0-9_-]{16,}/g, replacement: REDACTED },
12
+ // GitHub tokens, classic and fine-grained
13
+ { pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{16,}\b/g, replacement: REDACTED },
14
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: REDACTED },
15
+ // GitLab, npm, Slack, Google, SendGrid, AWS access key ids
16
+ { pattern: /\bglpat-[A-Za-z0-9_-]{16,}\b/g, replacement: REDACTED },
17
+ { pattern: /\bnpm_[A-Za-z0-9]{30,}\b/g, replacement: REDACTED },
18
+ { pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, replacement: REDACTED },
19
+ { pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g, replacement: REDACTED },
20
+ { pattern: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replacement: REDACTED },
21
+ { pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: REDACTED },
22
+ // JSON Web Tokens
23
+ { pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, replacement: REDACTED },
24
+ // PEM private keys, whole block
25
+ { pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, replacement: REDACTED }
26
+ ]
27
+ },
28
+ {
29
+ // Authorization header values: keep the scheme, drop the credential
30
+ anchor: /\b(?:Bearer|Basic|Token|Digest) /,
31
+ rules: [{ pattern: /\b(Bearer|Basic|Token|Digest)\s+[A-Za-z0-9._~+/=-]{12,}/g, replacement: `$1 ${REDACTED}` }]
32
+ },
33
+ {
34
+ // URLs: a password in the userinfo, a secret in the query string
35
+ anchor: /:\/\/|[?&]/,
36
+ rules: [
37
+ { pattern: /(:\/\/[^\s/:@]+:)[^\s@/]+(@)/g, replacement: `$1${REDACTED}$2` },
38
+ {
39
+ 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,
40
+ replacement: `$1${REDACTED}`
41
+ }
42
+ ]
43
+ }
44
+ ];
45
+ var STRONG_KEY_HINT = /password|passwd|passphrase|secret|apikey|privatekey|credential|authorization|accesskey|sessionid|cookie|refreshtoken|accesstoken|idtoken|clientkey|signingkey|encryptionkey|masterkey|servicekey/;
46
+ var WEAK_KEY_HINT = /token|key|auth|bearer|cert|pwd/;
47
+ var keyHints = /* @__PURE__ */ new Map();
48
+ var KEY_HINT_CACHE_LIMIT = 4096;
49
+ function hintFor(key) {
50
+ const cached = keyHints.get(key);
51
+ if (cached !== void 0) return cached;
52
+ const reduced = normalizeKeyForMatching(key);
53
+ const hint = STRONG_KEY_HINT.test(reduced) ? "strong" : WEAK_KEY_HINT.test(reduced) ? "weak" : "none";
54
+ if (keyHints.size >= KEY_HINT_CACHE_LIMIT) keyHints.clear();
55
+ keyHints.set(key, hint);
56
+ return hint;
57
+ }
58
+ var MIN_STRONG_LENGTH = 4;
59
+ var MIN_ENTROPY_LENGTH = 16;
60
+ var MIN_ENTROPY_BITS = 3;
61
+ var MIN_STRICT_LENGTH = 32;
62
+ function normalizeKeyForMatching(key) {
63
+ return key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
64
+ }
65
+ function entropyOf(text) {
66
+ const counts = /* @__PURE__ */ new Map();
67
+ for (const char of text) counts.set(char, (counts.get(char) ?? 0) + 1);
68
+ let bits = 0;
69
+ for (const count of counts.values()) {
70
+ const p = count / text.length;
71
+ bits -= p * Math.log2(p);
72
+ }
73
+ return bits;
74
+ }
75
+ function looksRandom(value, minimumLength) {
76
+ if (value.length < minimumLength || /\s/.test(value)) return false;
77
+ if (/^[0-9a-f-]+$/i.test(value) || /^[0-9.-]+$/.test(value)) return false;
78
+ return entropyOf(value) >= MIN_ENTROPY_BITS;
79
+ }
80
+ function keyLooksSecret(key, value) {
81
+ if (typeof value !== "string") return false;
82
+ const hint = hintFor(key);
83
+ if (hint === "strong") return value.length >= MIN_STRONG_LENGTH;
84
+ if (hint === "weak") return looksRandom(value, MIN_ENTROPY_LENGTH);
85
+ return false;
86
+ }
87
+ var ANY_ANCHOR = new RegExp(RULE_GROUPS.map((group) => group.anchor.source).join("|"));
88
+ function redactString(value, strictness = "balanced") {
89
+ if (strictness === "off" || value.length < 8) return value;
90
+ let result = value;
91
+ if (!ANY_ANCHOR.test(value)) return strictness === "strict" ? redactRandomRuns(value) : value;
92
+ for (const group of RULE_GROUPS) {
93
+ if (!group.anchor.test(result)) continue;
94
+ for (const { pattern, replacement } of group.rules) {
95
+ pattern.lastIndex = 0;
96
+ result = result.replace(pattern, replacement);
97
+ }
98
+ }
99
+ return strictness === "strict" ? redactRandomRuns(result) : result;
100
+ }
101
+ function redactRandomRuns(text) {
102
+ return text.replace(
103
+ /(?<![A-Za-z0-9+/=_-])[A-Za-z0-9+/_-]{32,}={0,2}(?![A-Za-z0-9+/=_-])/g,
104
+ (run) => looksRandom(run, MIN_STRICT_LENGTH) && characterClasses(run) >= 3 ? REDACTED : run
105
+ );
106
+ }
107
+ function characterClasses(text) {
108
+ return [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((test) => test.test(text)).length;
109
+ }
110
+ function redactJson(value, options = {}) {
111
+ const strictness = options.strictness ?? "balanced";
112
+ const keys = options.redactKeys;
113
+ const walk = (node) => {
114
+ if (typeof node === "string") return redactString(node, strictness);
115
+ if (Array.isArray(node)) return node.map(walk);
116
+ if (node && typeof node === "object") {
117
+ const out = {};
118
+ for (const [key, child] of Object.entries(node)) {
119
+ if (keys?.has(normalizeKeyForMatching(key)) || strictness !== "off" && keyLooksSecret(key, child)) {
120
+ out[key] = REDACTED;
121
+ } else {
122
+ out[key] = walk(child);
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ return node;
128
+ };
129
+ return walk(value);
130
+ }
131
+
132
+ // src/normalize.ts
133
+ var DEFAULT_REDACT_KEYS = [
134
+ "password",
135
+ "passphrase",
136
+ "token",
137
+ "secret",
138
+ "apiKey",
139
+ "accessKey",
140
+ "authorization",
141
+ "auth",
142
+ "cookie",
143
+ "sessionId",
144
+ "privateKey",
145
+ "clientSecret",
146
+ "refreshToken"
147
+ ];
148
+
149
+ // src/environment.ts
150
+ var LEVEL_RANK = {
151
+ Information: 0,
152
+ Warning: 1,
153
+ Error: 2
154
+ };
155
+ var LEVEL_ALIASES = {
156
+ info: "Information",
157
+ information: "Information",
158
+ tell: "Information",
159
+ warn: "Warning",
160
+ warning: "Warning",
161
+ oops: "Error",
162
+ error: "Error"
163
+ };
164
+ function toStoryLevel(input) {
165
+ if (!input) return "Information";
166
+ return LEVEL_ALIASES[String(input).toLowerCase()] ?? "Information";
167
+ }
168
+ function meetsLevel(level, minimum) {
169
+ return LEVEL_RANK[level] >= LEVEL_RANK[minimum];
170
+ }
171
+
172
+ // src/store/storyStore.ts
173
+ var DEFAULT_KEY_SET = new Set(DEFAULT_REDACT_KEYS.map(normalizeKeyForMatching));
174
+ function toStoredStory(event, options = {}) {
175
+ const { timestamp, level, title, storyId, parentStoryId, origin, notes, durationMs, droppedEmissions, error } = event;
176
+ const strictness = options.redactValues ?? "balanced";
177
+ const scrub = (value) => strictness === "off" ? clone(value) : redactJson(clone(value), { redactKeys: DEFAULT_KEY_SET, strictness });
178
+ const record = {
179
+ timestamp,
180
+ level,
181
+ title: scrub(title),
182
+ storyId: storyId ?? generateStoryId(),
183
+ notes: scrub(notes)
184
+ };
185
+ if (parentStoryId !== void 0) record.parentStoryId = parentStoryId;
186
+ if (origin !== void 0) record.origin = scrub(origin);
187
+ if (durationMs !== void 0) record.durationMs = durationMs;
188
+ if (droppedEmissions !== void 0) record.droppedEmissions = droppedEmissions;
189
+ if (error !== void 0) record.error = scrub(error);
190
+ return record;
191
+ }
192
+ function clone(value) {
193
+ return JSON.parse(JSON.stringify(value));
194
+ }
195
+ function generateStoryId() {
196
+ const random = globalThis.crypto?.randomUUID;
197
+ if (random) return random.call(globalThis.crypto);
198
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
199
+ }
200
+ function storySearchText(story) {
201
+ const parts = [story.title];
202
+ for (const note of story.notes) {
203
+ parts.push(note.note);
204
+ for (const value of [note.who, note.what, note.where]) {
205
+ if (typeof value === "string") parts.push(value);
206
+ }
207
+ if (note.error?.message) parts.push(note.error.message);
208
+ }
209
+ if (story.error?.message) parts.push(story.error.message);
210
+ return parts.join("\n");
211
+ }
212
+ function flattenOrigin(origin) {
213
+ const flat = {};
214
+ if (!origin) return flat;
215
+ for (const key of ["who", "what", "where"]) {
216
+ const value = origin[key];
217
+ if (value === void 0 || value === null) continue;
218
+ flat[key] = typeof value === "object" ? JSON.stringify(value) : String(value);
219
+ }
220
+ return flat;
221
+ }
222
+ function canonicalRow(event) {
223
+ const story = toStoredStory(event);
224
+ const origin = flattenOrigin(story.origin);
225
+ return {
226
+ story_id: story.storyId,
227
+ parent_story_id: story.parentStoryId ?? null,
228
+ timestamp: story.timestamp,
229
+ level: story.level,
230
+ title: story.title,
231
+ origin_who: origin.who ?? null,
232
+ origin_what: origin.what ?? null,
233
+ origin_where: origin.where ?? null,
234
+ duration_ms: story.durationMs ?? null,
235
+ error_message: story.error?.message ?? null,
236
+ notes: JSON.stringify(story.notes),
237
+ search_text: storySearchText(story),
238
+ record: JSON.stringify(story)
239
+ };
240
+ }
241
+ function matchesQuery(story, query = {}) {
242
+ if (query.since && Date.parse(story.timestamp) < query.since.getTime()) return false;
243
+ if (query.until && Date.parse(story.timestamp) >= query.until.getTime()) return false;
244
+ if (query.level !== void 0) {
245
+ const levels = Array.isArray(query.level) ? query.level : [query.level];
246
+ if (!levels.includes(story.level)) return false;
247
+ }
248
+ if (query.minimumLevel && !meetsLevel(story.level, query.minimumLevel)) return false;
249
+ if (query.parentStoryId !== void 0 && story.parentStoryId !== query.parentStoryId) return false;
250
+ if (query.slowerThanMs !== void 0 && !((story.durationMs ?? -1) > query.slowerThanMs)) return false;
251
+ if (query.failed !== void 0) {
252
+ const failed = story.level === "Error" || story.error !== void 0;
253
+ if (failed !== query.failed) return false;
254
+ }
255
+ if (query.about) {
256
+ if (!storySearchText(story).toLowerCase().includes(query.about.toLowerCase())) return false;
257
+ }
258
+ if (query.from) {
259
+ const origin = Object.values(flattenOrigin(story.origin)).join("\n").toLowerCase();
260
+ if (!origin.includes(query.from.toLowerCase())) return false;
261
+ }
262
+ return true;
263
+ }
264
+ function applyQuery(stories2, query = {}) {
265
+ const matched = [];
266
+ let position = 0;
267
+ for (const story of stories2) {
268
+ if (matchesQuery(story, query)) matched.push({ story, position });
269
+ position++;
270
+ }
271
+ const direction = query.order === "oldest" ? 1 : -1;
272
+ matched.sort((a, b) => {
273
+ const byTime = Date.parse(a.story.timestamp) - Date.parse(b.story.timestamp);
274
+ return (byTime !== 0 ? byTime : a.position - b.position) * direction;
275
+ });
276
+ const offset = Math.max(0, query.offset ?? 0);
277
+ const end = query.limit !== void 0 ? offset + Math.max(0, query.limit) : void 0;
278
+ return matched.slice(offset, end).map((entry) => entry.story);
279
+ }
280
+
281
+ // src/store/fileStore.ts
282
+ import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
283
+ import { dirname } from "path";
284
+ function fileStore(path) {
285
+ let queue = Promise.resolve();
286
+ const serialized = (work) => {
287
+ const next = queue.then(work, work);
288
+ queue = next.catch(() => void 0);
289
+ return next;
290
+ };
291
+ async function readAll() {
292
+ let text;
293
+ try {
294
+ text = await readFile(path, "utf8");
295
+ } catch (error) {
296
+ if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
297
+ throw error;
298
+ }
299
+ const stories2 = /* @__PURE__ */ new Map();
300
+ for (const line of text.split("\n")) {
301
+ if (!line.trim()) continue;
302
+ try {
303
+ const story = JSON.parse(line);
304
+ if (typeof story?.storyId === "string" && typeof story.timestamp === "string") {
305
+ stories2.delete(story.storyId);
306
+ stories2.set(story.storyId, story);
307
+ }
308
+ } catch {
309
+ }
310
+ }
311
+ return stories2;
312
+ }
313
+ return {
314
+ path,
315
+ async flush() {
316
+ await queue;
317
+ },
318
+ async append(event) {
319
+ const line = `${JSON.stringify(toStoredStory(event))}
320
+ `;
321
+ await serialized(async () => {
322
+ await mkdir(dirname(path), { recursive: true });
323
+ await appendFile(path, line, "utf8");
324
+ });
325
+ },
326
+ async get(storyId) {
327
+ return (await readAll()).get(storyId);
328
+ },
329
+ async query(criteria = {}) {
330
+ return applyQuery((await readAll()).values(), criteria);
331
+ },
332
+ async children(parentStoryId) {
333
+ return applyQuery((await readAll()).values(), { parentStoryId, order: "oldest" });
334
+ },
335
+ async prune(before) {
336
+ return serialized(async () => {
337
+ const stories2 = await readAll();
338
+ const boundary = before.getTime();
339
+ const kept = [];
340
+ let removed = 0;
341
+ for (const story of stories2.values()) {
342
+ if (Date.parse(story.timestamp) < boundary) removed++;
343
+ else kept.push(story);
344
+ }
345
+ if (removed === 0) return 0;
346
+ const temporary = `${path}.${process.pid}.tmp`;
347
+ await writeFile(temporary, kept.map((story) => JSON.stringify(story)).join("\n") + (kept.length ? "\n" : ""), "utf8");
348
+ await rename(temporary, path);
349
+ return removed;
350
+ });
351
+ }
352
+ };
353
+ }
354
+
355
+ // src/store/stories.ts
356
+ var UNITS = {
357
+ ms: 1,
358
+ s: 1e3,
359
+ m: 6e4,
360
+ h: 36e5,
361
+ d: 864e5,
362
+ w: 6048e5
363
+ };
364
+ function parseDuration(input) {
365
+ if (typeof input === "number") {
366
+ if (!Number.isFinite(input) || input < 0) throw new RangeError(`Not a duration: ${input}`);
367
+ return input;
368
+ }
369
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)\s*$/i.exec(input);
370
+ if (!match) throw new RangeError(`Not a duration: "${input}" (expected e.g. "30s", "5m", "24h", "7d", "2w")`);
371
+ return Number(match[1]) * UNITS[match[2].toLowerCase()];
372
+ }
373
+ var StoryQueryBuilder = class _StoryQueryBuilder {
374
+ constructor(store, query, now) {
375
+ this.store = store;
376
+ this.query = query;
377
+ this.now = now;
378
+ }
379
+ with(patch) {
380
+ return new _StoryQueryBuilder(this.store, { ...this.query, ...patch }, this.now);
381
+ }
382
+ instant(input) {
383
+ return input instanceof Date ? input : new Date(this.now().getTime() - parseDuration(input));
384
+ }
385
+ /** Title, note text, scalar context or error message mentions this */
386
+ about(text) {
387
+ return this.with({ about: text });
388
+ }
389
+ /** The origin — who, what or where — mentions this */
390
+ from(origin) {
391
+ return this.with({ from: origin });
392
+ }
393
+ /** Exactly this level. Accepts the aliases: `"info"`, `"warn"`, `"oops"`. */
394
+ level(level) {
395
+ return this.with({ level: toStoryLevel(level) });
396
+ }
397
+ /** This level or worse */
398
+ atLeast(level) {
399
+ return this.with({ minimumLevel: toStoryLevel(level) });
400
+ }
401
+ /** Carried an error, or closed at Error level */
402
+ failing() {
403
+ return this.with({ failed: true });
404
+ }
405
+ /** Neither an error nor closed at Error level */
406
+ succeeding() {
407
+ return this.with({ failed: false });
408
+ }
409
+ /** Took longer than this: `"5s"`, `"2m"`, or milliseconds */
410
+ slowerThan(duration) {
411
+ return this.with({ slowerThanMs: parseDuration(duration) });
412
+ }
413
+ /** Began within this long ago (`"24h"`), or at or after this date */
414
+ since(when) {
415
+ return this.with({ since: this.instant(when) });
416
+ }
417
+ /** Began before this long ago, or before this date */
418
+ until(when) {
419
+ return this.with({ until: this.instant(when) });
420
+ }
421
+ /** The chapters of this story */
422
+ under(parentStoryId) {
423
+ return this.with({ parentStoryId });
424
+ }
425
+ /** Most recent first — the default */
426
+ newest() {
427
+ return this.with({ order: "newest" });
428
+ }
429
+ /** Earliest first */
430
+ oldest() {
431
+ return this.with({ order: "oldest" });
432
+ }
433
+ /** At most this many */
434
+ limit(count) {
435
+ return this.with({ limit: count });
436
+ }
437
+ /** Skip this many first */
438
+ skip(count) {
439
+ return this.with({ offset: count });
440
+ }
441
+ /** The structured criteria this question compiles to — what an adapter receives */
442
+ toQuery() {
443
+ return { ...this.query };
444
+ }
445
+ /** Every matching story */
446
+ all() {
447
+ return this.store.query(this.toQuery());
448
+ }
449
+ /** The first match, or nothing */
450
+ async first() {
451
+ const [story] = await this.store.query({ ...this.query, limit: 1 });
452
+ return story;
453
+ }
454
+ /** How many match, ignoring paging */
455
+ async count() {
456
+ const { limit: _limit, offset: _offset, ...unpaged } = this.query;
457
+ return (await this.store.query(unpaged)).length;
458
+ }
459
+ /** An awaited builder resolves to `.all()` */
460
+ then(onFulfilled, onRejected) {
461
+ return this.all().then(onFulfilled, onRejected);
462
+ }
463
+ };
464
+ function stories(store, options = {}) {
465
+ return new StoryQueryBuilder(store, {}, options.now ?? (() => /* @__PURE__ */ new Date()));
466
+ }
467
+ export {
468
+ StoryQueryBuilder,
469
+ applyQuery,
470
+ canonicalRow,
471
+ fileStore,
472
+ matchesQuery,
473
+ parseDuration,
474
+ stories,
475
+ storySearchText,
476
+ toStoredStory
477
+ };
478
+ //# sourceMappingURL=file.js.map