@demokit-ai/core 0.0.1

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,565 @@
1
+ /**
2
+ * Session state management for DemoKit
3
+ *
4
+ * Provides in-memory storage for mutable state during a demo session.
5
+ * State persists across requests but resets on page refresh.
6
+ */
7
+ /**
8
+ * Session state interface for storing and retrieving demo session data
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // In a fixture handler
13
+ * 'POST /api/users': ({ body, session }) => {
14
+ * const users = session.get<User[]>('users') || []
15
+ * const newUser = { id: crypto.randomUUID(), ...body }
16
+ * session.set('users', [...users, newUser])
17
+ * return newUser
18
+ * }
19
+ *
20
+ * 'GET /api/users': ({ session }) => {
21
+ * return session.get<User[]>('users') || []
22
+ * }
23
+ * ```
24
+ */
25
+ interface SessionState {
26
+ /**
27
+ * Get a value from the session state
28
+ * @param key - The key to retrieve
29
+ * @returns The value if it exists, undefined otherwise
30
+ */
31
+ get<T>(key: string): T | undefined;
32
+ /**
33
+ * Set a value in the session state
34
+ * @param key - The key to set
35
+ * @param value - The value to store
36
+ */
37
+ set<T>(key: string, value: T): void;
38
+ /**
39
+ * Delete a value from the session state
40
+ * @param key - The key to delete
41
+ */
42
+ delete(key: string): void;
43
+ /**
44
+ * Clear all session state
45
+ */
46
+ clear(): void;
47
+ /**
48
+ * Get all keys in the session state
49
+ * @returns Array of all keys
50
+ */
51
+ keys(): string[];
52
+ /**
53
+ * Check if a key exists in the session state
54
+ * @param key - The key to check
55
+ * @returns True if the key exists
56
+ */
57
+ has(key: string): boolean;
58
+ /**
59
+ * Get the number of items in the session state
60
+ * @returns The number of items
61
+ */
62
+ size(): number;
63
+ }
64
+ /**
65
+ * Create a new session state instance
66
+ *
67
+ * Session state is purely in-memory and resets when the page is refreshed.
68
+ * This is intentional - demo sessions should start fresh each time.
69
+ *
70
+ * @returns A new SessionState instance
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const session = createSessionState()
75
+ *
76
+ * session.set('cart', [{ id: '1', quantity: 2 }])
77
+ * const cart = session.get<CartItem[]>('cart')
78
+ *
79
+ * session.clear() // Reset all state
80
+ * ```
81
+ */
82
+ declare function createSessionState(): SessionState;
83
+
84
+ /**
85
+ * Configuration for creating a demo interceptor
86
+ */
87
+ interface DemoKitConfig {
88
+ /**
89
+ * Map of URL patterns to fixture handlers
90
+ * Patterns support :param syntax for URL parameters and * for wildcards
91
+ * @example
92
+ * {
93
+ * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
94
+ * 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
95
+ * 'POST /api/users': ({ body }) => ({ id: 'new', ...body }),
96
+ * }
97
+ */
98
+ fixtures: FixtureMap;
99
+ /**
100
+ * localStorage key for persisting demo mode state
101
+ * @default 'demokit-mode'
102
+ */
103
+ storageKey?: string;
104
+ /**
105
+ * Callback invoked when demo mode is enabled
106
+ */
107
+ onEnable?: () => void;
108
+ /**
109
+ * Callback invoked when demo mode is disabled
110
+ */
111
+ onDisable?: () => void;
112
+ /**
113
+ * Whether to start with demo mode enabled
114
+ * If not provided, will read from localStorage
115
+ * @default false
116
+ */
117
+ initialEnabled?: boolean;
118
+ /**
119
+ * Base URL to use for relative URL parsing
120
+ * @default 'http://localhost'
121
+ */
122
+ baseUrl?: string;
123
+ }
124
+ /**
125
+ * Map of URL patterns to fixture handlers
126
+ * Pattern format: "METHOD /path/:param"
127
+ */
128
+ type FixtureMap = Record<string, FixtureHandler>;
129
+ /**
130
+ * A fixture handler can be:
131
+ * - A static value (object, array, primitive)
132
+ * - A function that receives request context and returns a value
133
+ * - An async function for dynamic fixtures
134
+ */
135
+ type FixtureHandler = unknown | ((context: RequestContext) => unknown) | ((context: RequestContext) => Promise<unknown>);
136
+ /**
137
+ * Context provided to fixture handler functions
138
+ */
139
+ interface RequestContext {
140
+ /**
141
+ * The full URL of the request
142
+ */
143
+ url: string;
144
+ /**
145
+ * HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
146
+ */
147
+ method: string;
148
+ /**
149
+ * URL parameters extracted from the pattern
150
+ * @example For pattern 'GET /api/users/:id' and URL '/api/users/123',
151
+ * params would be { id: '123' }
152
+ */
153
+ params: Record<string, string>;
154
+ /**
155
+ * Query string parameters
156
+ */
157
+ searchParams: URLSearchParams;
158
+ /**
159
+ * Parsed request body (for POST, PUT, PATCH requests)
160
+ */
161
+ body?: unknown;
162
+ /**
163
+ * Request headers
164
+ */
165
+ headers: Headers;
166
+ /**
167
+ * Session state for storing mutable data across requests
168
+ * Resets when the page is refreshed
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * // Store data in a POST handler
173
+ * 'POST /api/users': ({ body, session }) => {
174
+ * const users = session.get<User[]>('users') || []
175
+ * const newUser = { id: crypto.randomUUID(), ...body }
176
+ * session.set('users', [...users, newUser])
177
+ * return newUser
178
+ * }
179
+ *
180
+ * // Retrieve data in a GET handler
181
+ * 'GET /api/users': ({ session }) => {
182
+ * return session.get<User[]>('users') || []
183
+ * }
184
+ * ```
185
+ */
186
+ session: SessionState;
187
+ }
188
+ /**
189
+ * The demo interceptor instance returned by createDemoInterceptor
190
+ */
191
+ interface DemoInterceptor {
192
+ /**
193
+ * Enable demo mode - all matching fetches will return fixture data
194
+ */
195
+ enable(): void;
196
+ /**
197
+ * Disable demo mode - fetches will pass through to the real API
198
+ */
199
+ disable(): void;
200
+ /**
201
+ * Check if demo mode is currently enabled
202
+ */
203
+ isEnabled(): boolean;
204
+ /**
205
+ * Toggle demo mode state and return the new state
206
+ */
207
+ toggle(): boolean;
208
+ /**
209
+ * Replace all fixtures with a new fixture map
210
+ */
211
+ setFixtures(fixtures: FixtureMap): void;
212
+ /**
213
+ * Add or update a single fixture pattern
214
+ */
215
+ addFixture(pattern: string, handler: FixtureHandler): void;
216
+ /**
217
+ * Remove a fixture pattern
218
+ */
219
+ removeFixture(pattern: string): void;
220
+ /**
221
+ * Reset the session state, clearing all stored data
222
+ * Call this to manually reset the demo session without page refresh
223
+ */
224
+ resetSession(): void;
225
+ /**
226
+ * Get the current session state instance
227
+ * Useful for inspecting or manipulating session state directly
228
+ */
229
+ getSession(): SessionState;
230
+ /**
231
+ * Clean up the interceptor - restores original fetch
232
+ * Call this when unmounting or cleaning up
233
+ */
234
+ destroy(): void;
235
+ }
236
+ /**
237
+ * Result of URL pattern matching
238
+ */
239
+ interface MatchResult {
240
+ /**
241
+ * Whether the pattern matched the URL
242
+ */
243
+ matched: boolean;
244
+ /**
245
+ * Extracted URL parameters
246
+ */
247
+ params: Record<string, string>;
248
+ }
249
+ /**
250
+ * Parsed URL pattern
251
+ */
252
+ interface ParsedPattern {
253
+ /**
254
+ * HTTP method from the pattern
255
+ */
256
+ method: string;
257
+ /**
258
+ * Regex to match the path
259
+ */
260
+ pathPattern: RegExp;
261
+ /**
262
+ * Names of parameters in order of appearance
263
+ */
264
+ paramNames: string[];
265
+ }
266
+
267
+ /**
268
+ * Create a demo interceptor that patches fetch to return mock data
269
+ *
270
+ * @param config - Configuration including fixtures and options
271
+ * @returns Demo interceptor instance with enable/disable controls
272
+ *
273
+ * @example
274
+ * const demo = createDemoInterceptor({
275
+ * fixtures: {
276
+ * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
277
+ * 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
278
+ * 'POST /api/users': ({ body }) => ({ id: 'new', ...body }),
279
+ * }
280
+ * })
281
+ *
282
+ * demo.enable() // All matching fetches return mock data
283
+ * demo.disable() // Back to real API
284
+ */
285
+ declare function createDemoInterceptor(config: DemoKitConfig): DemoInterceptor;
286
+
287
+ /**
288
+ * Parse a URL pattern into its components
289
+ *
290
+ * @param pattern - Pattern in format "METHOD /path/:param"
291
+ * @returns Parsed pattern with method, regex, and param names
292
+ *
293
+ * @example
294
+ * parseUrlPattern('GET /api/users/:id')
295
+ * // { method: 'GET', pathPattern: /^\/api\/users\/([^/]+)$/, paramNames: ['id'] }
296
+ *
297
+ * parseUrlPattern('GET /api/projects/*')
298
+ * // { method: 'GET', pathPattern: /^\/api\/projects\/.*$/, paramNames: [] }
299
+ */
300
+ declare function parseUrlPattern(pattern: string): ParsedPattern;
301
+ /**
302
+ * Match a request against a fixture pattern
303
+ *
304
+ * @param pattern - Fixture pattern (e.g., "GET /api/users/:id")
305
+ * @param method - HTTP method of the request
306
+ * @param pathname - URL pathname of the request
307
+ * @returns Match result with extracted params, or null if no match
308
+ *
309
+ * @example
310
+ * matchUrl('GET /api/users/:id', 'GET', '/api/users/123')
311
+ * // { matched: true, params: { id: '123' } }
312
+ *
313
+ * matchUrl('GET /api/users/:id', 'POST', '/api/users/123')
314
+ * // null (method doesn't match)
315
+ *
316
+ * matchUrl('GET /api/users/:id', 'GET', '/api/projects/123')
317
+ * // null (path doesn't match)
318
+ */
319
+ declare function matchUrl(pattern: string, method: string, pathname: string): MatchResult | null;
320
+ /**
321
+ * Find the first matching pattern from a fixture map
322
+ *
323
+ * @param fixtures - Map of patterns to fixtures
324
+ * @param method - HTTP method of the request
325
+ * @param pathname - URL pathname of the request
326
+ * @returns Tuple of [pattern, match result] or null if no match
327
+ */
328
+ declare function findMatchingPattern(fixtures: Record<string, unknown>, method: string, pathname: string): [string, MatchResult] | null;
329
+ /**
330
+ * Clear the pattern cache (useful for testing)
331
+ */
332
+ declare function clearPatternCache(): void;
333
+ /**
334
+ * A query key element can be a string, number, or object
335
+ */
336
+ type QueryKeyElement = string | number | boolean | null | undefined | Record<string, unknown>;
337
+ /**
338
+ * A query key is an array of elements (like TanStack Query uses)
339
+ */
340
+ type QueryKey = readonly QueryKeyElement[];
341
+ /**
342
+ * Result of query key matching
343
+ */
344
+ interface QueryKeyMatchResult {
345
+ /**
346
+ * Whether the pattern matched the query key
347
+ */
348
+ matched: boolean;
349
+ /**
350
+ * Extracted parameters from :param placeholders
351
+ */
352
+ params: Record<string, unknown>;
353
+ }
354
+ /**
355
+ * Match a query key against a pattern
356
+ *
357
+ * Supports:
358
+ * - Exact string/number matching: ['users'] matches ['users']
359
+ * - Parameter extraction with :param syntax: ['users', ':id'] matches ['users', '123']
360
+ * - Object matching with params: ['users', { id: ':id' }] matches ['users', { id: '123' }]
361
+ * - Wildcard matching with '*': ['users', '*'] matches ['users', 'anything']
362
+ *
363
+ * @param queryKey - The actual query key to match
364
+ * @param pattern - The pattern to match against
365
+ * @returns Match result with extracted params, or null if no match
366
+ *
367
+ * @example
368
+ * // Exact match
369
+ * matchQueryKey(['users'], ['users'])
370
+ * // { matched: true, params: {} }
371
+ *
372
+ * // Parameter extraction from string
373
+ * matchQueryKey(['users', '123'], ['users', ':id'])
374
+ * // { matched: true, params: { id: '123' } }
375
+ *
376
+ * // Parameter extraction from object
377
+ * matchQueryKey(['users', { id: '123', status: 'active' }], ['users', { id: ':userId' }])
378
+ * // { matched: true, params: { userId: '123' } }
379
+ *
380
+ * // Wildcard matching
381
+ * matchQueryKey(['users', 'anything'], ['users', '*'])
382
+ * // { matched: true, params: {} }
383
+ *
384
+ * // No match - different length
385
+ * matchQueryKey(['users'], ['users', 'list'])
386
+ * // null
387
+ */
388
+ declare function matchQueryKey(queryKey: QueryKey, pattern: QueryKey): QueryKeyMatchResult | null;
389
+ /**
390
+ * Find the first matching pattern from a map of query key patterns
391
+ *
392
+ * @param patterns - Map of serialized patterns to values
393
+ * @param queryKey - The query key to match
394
+ * @returns Tuple of [pattern, value, match result] or null if no match
395
+ *
396
+ * @example
397
+ * const patterns = {
398
+ * 'users': { data: [] },
399
+ * 'users/:id': (params) => ({ id: params.id }),
400
+ * }
401
+ * findMatchingQueryKeyPattern(patterns, ['users', '123'])
402
+ * // [['users', ':id'], { params: { id: '123' } }]
403
+ */
404
+ declare function findMatchingQueryKeyPattern<T>(patterns: Map<QueryKey, T>, queryKey: QueryKey): [QueryKey, T, QueryKeyMatchResult] | null;
405
+
406
+ /**
407
+ * Shared demo state interface that can be used across frameworks
408
+ * This provides a consistent API for managing demo mode state
409
+ */
410
+ interface DemoState {
411
+ /**
412
+ * Whether demo mode is currently enabled
413
+ */
414
+ enabled: boolean;
415
+ /**
416
+ * The currently active scenario name, if any
417
+ * Scenarios allow grouping fixtures for different demo flows
418
+ */
419
+ scenario: string | null;
420
+ /**
421
+ * The active fixture map for the current scenario
422
+ */
423
+ fixtures: FixtureMap;
424
+ /**
425
+ * Timestamp when demo mode was last enabled
426
+ * Useful for session timeout logic
427
+ */
428
+ enabledAt: number | null;
429
+ }
430
+ /**
431
+ * Create initial demo state
432
+ *
433
+ * @param initialEnabled - Whether demo mode should start enabled
434
+ * @param fixtures - Initial fixture map
435
+ * @returns A new DemoState object
436
+ *
437
+ * @example
438
+ * const state = createDemoState(false, {
439
+ * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
440
+ * })
441
+ */
442
+ declare function createDemoState(initialEnabled?: boolean, fixtures?: FixtureMap): DemoState;
443
+ /**
444
+ * Options for creating a demo state store
445
+ */
446
+ interface DemoStateStoreOptions {
447
+ /**
448
+ * Initial enabled state
449
+ * @default false
450
+ */
451
+ initialEnabled?: boolean;
452
+ /**
453
+ * Initial fixtures
454
+ * @default {}
455
+ */
456
+ fixtures?: FixtureMap;
457
+ /**
458
+ * Available scenarios with their fixtures
459
+ */
460
+ scenarios?: Record<string, FixtureMap>;
461
+ /**
462
+ * Callback when state changes
463
+ */
464
+ onChange?: (state: DemoState) => void;
465
+ }
466
+ /**
467
+ * A minimal state store for demo mode
468
+ * Framework adapters can use this or integrate with their own state management
469
+ */
470
+ interface DemoStateStore {
471
+ /**
472
+ * Get the current state
473
+ */
474
+ getState(): DemoState;
475
+ /**
476
+ * Enable demo mode
477
+ */
478
+ enable(): void;
479
+ /**
480
+ * Disable demo mode
481
+ */
482
+ disable(): void;
483
+ /**
484
+ * Toggle demo mode
485
+ */
486
+ toggle(): boolean;
487
+ /**
488
+ * Set the active scenario
489
+ * @param name - Scenario name (or null to clear)
490
+ */
491
+ setScenario(name: string | null): void;
492
+ /**
493
+ * Update fixtures (merges with existing)
494
+ */
495
+ updateFixtures(fixtures: FixtureMap): void;
496
+ /**
497
+ * Replace all fixtures
498
+ */
499
+ setFixtures(fixtures: FixtureMap): void;
500
+ /**
501
+ * Subscribe to state changes
502
+ * @returns Unsubscribe function
503
+ */
504
+ subscribe(listener: (state: DemoState) => void): () => void;
505
+ }
506
+ /**
507
+ * Create a demo state store
508
+ *
509
+ * This is a minimal, framework-agnostic state store that can be used
510
+ * directly or wrapped by framework-specific adapters (React, Vue, etc.)
511
+ *
512
+ * @param options - Store configuration options
513
+ * @returns A DemoStateStore instance
514
+ *
515
+ * @example
516
+ * const store = createDemoStateStore({
517
+ * initialEnabled: false,
518
+ * fixtures: {
519
+ * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
520
+ * },
521
+ * scenarios: {
522
+ * 'empty-state': { 'GET /api/users': () => [] },
523
+ * 'error-state': { 'GET /api/users': () => { throw new Error('API Error') } },
524
+ * },
525
+ * })
526
+ *
527
+ * // Subscribe to changes
528
+ * const unsubscribe = store.subscribe((state) => {
529
+ * console.log('Demo mode:', state.enabled)
530
+ * })
531
+ *
532
+ * // Enable demo mode
533
+ * store.enable()
534
+ *
535
+ * // Switch scenario
536
+ * store.setScenario('empty-state')
537
+ */
538
+ declare function createDemoStateStore(options?: DemoStateStoreOptions): DemoStateStore;
539
+
540
+ /**
541
+ * Default localStorage key for demo mode state
542
+ */
543
+ declare const DEFAULT_STORAGE_KEY = "demokit-mode";
544
+ /**
545
+ * Load demo mode state from localStorage
546
+ *
547
+ * @param key - localStorage key to use
548
+ * @returns Current demo mode state, or false if not set or unavailable
549
+ */
550
+ declare function loadDemoState(key?: string): boolean;
551
+ /**
552
+ * Save demo mode state to localStorage
553
+ *
554
+ * @param key - localStorage key to use
555
+ * @param enabled - Whether demo mode is enabled
556
+ */
557
+ declare function saveDemoState(key: string | undefined, enabled: boolean): void;
558
+ /**
559
+ * Clear demo mode state from localStorage
560
+ *
561
+ * @param key - localStorage key to use
562
+ */
563
+ declare function clearDemoState(key?: string): void;
564
+
565
+ export { DEFAULT_STORAGE_KEY, type DemoInterceptor, type DemoKitConfig, type DemoState, type DemoStateStore, type DemoStateStoreOptions, type FixtureHandler, type FixtureMap, type MatchResult, type ParsedPattern, type QueryKey, type QueryKeyElement, type QueryKeyMatchResult, type RequestContext, type SessionState, clearDemoState, clearPatternCache, createDemoInterceptor, createDemoState, createDemoStateStore, createSessionState, findMatchingPattern, findMatchingQueryKeyPattern, loadDemoState, matchQueryKey, matchUrl, parseUrlPattern, saveDemoState };