@outfitter/state 0.1.0-rc.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,667 @@
1
+ import { NotFoundError, Result, StorageError, ValidationError } from "@outfitter/contracts";
2
+ /**
3
+ * A pagination cursor representing a position in a result set.
4
+ *
5
+ * Cursors are immutable (frozen) objects that encapsulate pagination state.
6
+ * They are intentionally opaque to prevent direct manipulation - use
7
+ * {@link advanceCursor} to create a new cursor with an updated position.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const result = createCursor({
12
+ * position: 0,
13
+ * metadata: { query: "status:open" },
14
+ * ttl: 3600000, // 1 hour
15
+ * });
16
+ *
17
+ * if (result.isOk()) {
18
+ * const cursor = result.value;
19
+ * console.log(cursor.id); // UUID
20
+ * console.log(cursor.position); // 0
21
+ * console.log(cursor.expiresAt); // Unix timestamp
22
+ * }
23
+ * ```
24
+ */
25
+ interface Cursor {
26
+ /** Unique identifier for this cursor (UUID format) */
27
+ readonly id: string;
28
+ /** Current position/offset in the result set (zero-based) */
29
+ readonly position: number;
30
+ /** Optional user-defined metadata associated with this cursor */
31
+ readonly metadata?: Record<string, unknown>;
32
+ /** Time-to-live in milliseconds (optional, omitted if cursor never expires) */
33
+ readonly ttl?: number;
34
+ /** Unix timestamp (ms) when this cursor expires (computed from createdAt + ttl) */
35
+ readonly expiresAt?: number;
36
+ /** Unix timestamp (ms) when this cursor was created */
37
+ readonly createdAt: number;
38
+ }
39
+ /**
40
+ * Options for creating a pagination cursor.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // Minimal options (ID auto-generated, no TTL)
45
+ * const opts1: CreateCursorOptions = { position: 0 };
46
+ *
47
+ * // Full options with custom ID, metadata, and TTL
48
+ * const opts2: CreateCursorOptions = {
49
+ * id: "my-cursor-id",
50
+ * position: 50,
51
+ * metadata: { query: "status:open", pageSize: 25 },
52
+ * ttl: 30 * 60 * 1000, // 30 minutes
53
+ * };
54
+ * ```
55
+ */
56
+ interface CreateCursorOptions {
57
+ /** Custom cursor ID (UUID generated if not provided) */
58
+ id?: string;
59
+ /** Starting position in the result set (must be non-negative) */
60
+ position: number;
61
+ /** User-defined metadata to associate with the cursor */
62
+ metadata?: Record<string, unknown>;
63
+ /** Time-to-live in milliseconds (cursor never expires if omitted) */
64
+ ttl?: number;
65
+ }
66
+ /**
67
+ * A store for managing pagination cursors.
68
+ *
69
+ * Cursor stores handle storage, retrieval, and expiration of cursors.
70
+ * Expired cursors are automatically excluded from `get()` and `has()` operations.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const store = createCursorStore();
75
+ *
76
+ * // Store a cursor
77
+ * const cursor = createCursor({ position: 0 });
78
+ * if (cursor.isOk()) {
79
+ * store.set(cursor.value);
80
+ * }
81
+ *
82
+ * // Retrieve by ID
83
+ * const result = store.get("cursor-id");
84
+ * if (result.isOk()) {
85
+ * console.log(result.value.position);
86
+ * }
87
+ *
88
+ * // Cleanup expired cursors
89
+ * const pruned = store.prune();
90
+ * console.log(`Removed ${pruned} expired cursors`);
91
+ * ```
92
+ */
93
+ interface CursorStore {
94
+ /**
95
+ * Save or update a cursor in the store.
96
+ * @param cursor - The cursor to store (replaces existing if same ID)
97
+ */
98
+ set(cursor: Cursor): void;
99
+ /**
100
+ * Retrieve a cursor by ID.
101
+ * @param id - The cursor ID to look up
102
+ * @returns Result with cursor or NotFoundError (also returned for expired cursors)
103
+ */
104
+ get(id: string): Result<Cursor, InstanceType<typeof NotFoundError>>;
105
+ /**
106
+ * Check if a cursor exists and is not expired.
107
+ * @param id - The cursor ID to check
108
+ * @returns True if cursor exists and is valid, false otherwise
109
+ */
110
+ has(id: string): boolean;
111
+ /**
112
+ * Delete a cursor by ID.
113
+ * @param id - The cursor ID to delete (no-op if not found)
114
+ */
115
+ delete(id: string): void;
116
+ /**
117
+ * Remove all cursors from the store.
118
+ */
119
+ clear(): void;
120
+ /**
121
+ * List all cursor IDs in the store (including expired).
122
+ * @returns Array of cursor IDs
123
+ */
124
+ list(): string[];
125
+ /**
126
+ * Remove all expired cursors from the store.
127
+ * @returns Number of cursors that were pruned
128
+ */
129
+ prune(): number;
130
+ }
131
+ /**
132
+ * A cursor store with namespace isolation.
133
+ *
134
+ * Scoped stores prefix all cursor IDs with the scope name, preventing
135
+ * collisions between different contexts (e.g., "issues" vs "pull-requests").
136
+ *
137
+ * Scopes can be nested: creating a scoped store from another scoped store
138
+ * produces IDs like "parent:child:cursor-id".
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const store = createCursorStore();
143
+ * const issueStore = createScopedStore(store, "issues");
144
+ * const prStore = createScopedStore(store, "prs");
145
+ *
146
+ * // These don't conflict - different namespaces
147
+ * issueStore.set(cursor1); // Stored as "issues:abc123"
148
+ * prStore.set(cursor2); // Stored as "prs:abc123"
149
+ *
150
+ * // Clear only affects the scope
151
+ * issueStore.clear(); // Only clears issue cursors
152
+ * ```
153
+ */
154
+ interface ScopedStore extends CursorStore {
155
+ /**
156
+ * Get the full scope path for this store.
157
+ * @returns Scope string (e.g., "parent:child" for nested scopes)
158
+ */
159
+ getScope(): string;
160
+ }
161
+ /**
162
+ * Options for creating a persistent cursor store.
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const options: PersistentStoreOptions = {
167
+ * path: "/home/user/.config/myapp/cursors.json",
168
+ * };
169
+ *
170
+ * const store = await createPersistentStore(options);
171
+ * ```
172
+ */
173
+ interface PersistentStoreOptions {
174
+ /** Absolute file path for cursor persistence (JSON format) */
175
+ path: string;
176
+ }
177
+ /**
178
+ * A cursor store that persists to disk and survives process restarts.
179
+ *
180
+ * Persistent stores use atomic writes (temp file + rename) to prevent
181
+ * corruption. They automatically load existing data on initialization
182
+ * and handle corrupted files gracefully by starting empty.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const store = await createPersistentStore({
187
+ * path: "~/.config/myapp/cursors.json",
188
+ * });
189
+ *
190
+ * // Use like any cursor store
191
+ * store.set(cursor);
192
+ *
193
+ * // Flush to disk before exit
194
+ * await store.flush();
195
+ *
196
+ * // Cleanup resources
197
+ * store.dispose();
198
+ * ```
199
+ */
200
+ interface PersistentStore extends CursorStore {
201
+ /**
202
+ * Flush all in-memory cursors to disk.
203
+ * Uses atomic write (temp file + rename) to prevent corruption.
204
+ * @returns Promise that resolves when write is complete
205
+ */
206
+ flush(): Promise<void>;
207
+ /**
208
+ * Dispose of the store and cleanup resources.
209
+ * Call this when the store is no longer needed.
210
+ */
211
+ dispose(): void;
212
+ }
213
+ /**
214
+ * Create a new pagination cursor.
215
+ *
216
+ * Cursors are immutable and frozen. To update position, use {@link advanceCursor}.
217
+ * If no ID is provided, a UUID is generated automatically.
218
+ *
219
+ * @param options - Cursor creation options including position, optional ID, metadata, and TTL
220
+ * @returns Result containing frozen Cursor or ValidationError if position is negative
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * // Basic cursor at position 0
225
+ * const result = createCursor({ position: 0 });
226
+ *
227
+ * // Cursor with metadata and TTL
228
+ * const result = createCursor({
229
+ * position: 0,
230
+ * metadata: { filter: "active" },
231
+ * ttl: 3600000, // 1 hour
232
+ * });
233
+ *
234
+ * if (result.isOk()) {
235
+ * store.set(result.value);
236
+ * }
237
+ * ```
238
+ */
239
+ declare function createCursor(options: CreateCursorOptions): Result<Cursor, InstanceType<typeof ValidationError>>;
240
+ /**
241
+ * Advance a cursor to a new position, returning a new immutable cursor.
242
+ *
243
+ * The original cursor is not modified. All properties (ID, metadata, TTL,
244
+ * expiresAt, createdAt) are preserved in the new cursor.
245
+ *
246
+ * @param cursor - The cursor to advance (not modified)
247
+ * @param newPosition - The new position value (typically cursor.position + pageSize)
248
+ * @returns A new frozen Cursor with the updated position
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * const cursor = createCursor({ position: 0 });
253
+ * if (cursor.isOk()) {
254
+ * // Advance by page size of 25
255
+ * const nextPage = advanceCursor(cursor.value, cursor.value.position + 25);
256
+ *
257
+ * console.log(cursor.value.position); // 0 (unchanged)
258
+ * console.log(nextPage.position); // 25
259
+ *
260
+ * // Store the advanced cursor
261
+ * store.set(nextPage);
262
+ * }
263
+ * ```
264
+ */
265
+ declare function advanceCursor(cursor: Cursor, newPosition: number): Cursor;
266
+ /**
267
+ * Check if a cursor has expired based on its TTL.
268
+ *
269
+ * Cursors without a TTL (no `expiresAt` property) never expire and
270
+ * this function will always return `false` for them.
271
+ *
272
+ * @param cursor - The cursor to check for expiration
273
+ * @returns `true` if cursor has expired, `false` if still valid or has no TTL
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const cursor = createCursor({ position: 0, ttl: 1000 }); // 1 second TTL
278
+ *
279
+ * if (cursor.isOk()) {
280
+ * console.log(isExpired(cursor.value)); // false (just created)
281
+ *
282
+ * // Wait 2 seconds...
283
+ * await new Promise(resolve => setTimeout(resolve, 2000));
284
+ *
285
+ * console.log(isExpired(cursor.value)); // true (expired)
286
+ * }
287
+ *
288
+ * // Cursors without TTL never expire
289
+ * const eternal = createCursor({ position: 0 });
290
+ * if (eternal.isOk()) {
291
+ * console.log(isExpired(eternal.value)); // always false
292
+ * }
293
+ * ```
294
+ */
295
+ declare function isExpired(cursor: Cursor): boolean;
296
+ /**
297
+ * Encodes a cursor to an opaque URL-safe string.
298
+ *
299
+ * The internal structure is hidden from consumers. The cursor is serialized
300
+ * to JSON and then encoded using URL-safe base64 (RFC 4648 Section 5).
301
+ *
302
+ * @param cursor - Cursor to encode
303
+ * @returns URL-safe base64 encoded string (no +, /, or = characters)
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const cursor = createCursor({ position: 0, metadata: { query: "status:open" } });
308
+ * if (cursor.isOk()) {
309
+ * const encoded = encodeCursor(cursor.value);
310
+ * // encoded is a URL-safe string like "eyJpZCI6IjEyMzQ..."
311
+ *
312
+ * // Can be safely used in URLs, query params, headers
313
+ * const url = `/api/items?cursor=${encoded}`;
314
+ * }
315
+ * ```
316
+ */
317
+ declare function encodeCursor(cursor: Cursor): string;
318
+ /**
319
+ * Decodes an opaque cursor string back to a Cursor.
320
+ *
321
+ * Validates the structure after decoding, ensuring all required fields
322
+ * are present and have correct types.
323
+ *
324
+ * @param encoded - URL-safe base64 encoded cursor
325
+ * @returns Result with decoded Cursor or ValidationError if invalid
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * // Successful decode
330
+ * const result = decodeCursor(encodedString);
331
+ * if (result.isOk()) {
332
+ * console.log(result.value.position);
333
+ * }
334
+ *
335
+ * // Handle invalid input
336
+ * const invalid = decodeCursor("not-a-valid-cursor");
337
+ * if (invalid.isErr()) {
338
+ * console.log(invalid.error.message); // "Invalid cursor: ..."
339
+ * }
340
+ * ```
341
+ */
342
+ declare function decodeCursor(encoded: string): Result<Cursor, InstanceType<typeof ValidationError>>;
343
+ /**
344
+ * Create an in-memory cursor store.
345
+ *
346
+ * The store automatically handles expiration: `get()` and `has()` return
347
+ * not-found/false for expired cursors. Use `prune()` to remove expired
348
+ * cursors from memory.
349
+ *
350
+ * @returns A new cursor store implementing both CursorStore and ScopedStore interfaces
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * const store = createCursorStore();
355
+ *
356
+ * // Create and store a cursor
357
+ * const cursor = createCursor({
358
+ * position: 0,
359
+ * metadata: { query: "status:open" },
360
+ * ttl: 3600000, // 1 hour
361
+ * });
362
+ *
363
+ * if (cursor.isOk()) {
364
+ * store.set(cursor.value);
365
+ *
366
+ * // Retrieve later
367
+ * const result = store.get(cursor.value.id);
368
+ * if (result.isOk()) {
369
+ * console.log(result.value.position);
370
+ * }
371
+ * }
372
+ *
373
+ * // List all cursors
374
+ * console.log(store.list()); // ["cursor-id", ...]
375
+ *
376
+ * // Cleanup expired
377
+ * const pruned = store.prune();
378
+ * ```
379
+ */
380
+ declare function createCursorStore(): CursorStore & ScopedStore;
381
+ /**
382
+ * Create a persistent cursor store that saves to disk.
383
+ *
384
+ * The store loads existing cursors from the file on initialization.
385
+ * Changes are kept in memory until `flush()` is called. Uses atomic
386
+ * writes (temp file + rename) to prevent corruption.
387
+ *
388
+ * If the file is corrupted or invalid JSON, the store starts empty
389
+ * rather than throwing an error.
390
+ *
391
+ * @param options - Persistence options including the file path
392
+ * @returns Promise resolving to a PersistentStore
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * // Create persistent store
397
+ * const store = await createPersistentStore({
398
+ * path: "/home/user/.config/myapp/cursors.json",
399
+ * });
400
+ *
401
+ * // Use like any cursor store
402
+ * const cursor = createCursor({ position: 0 });
403
+ * if (cursor.isOk()) {
404
+ * store.set(cursor.value);
405
+ * }
406
+ *
407
+ * // Flush to disk (call before process exit)
408
+ * await store.flush();
409
+ *
410
+ * // Cleanup when done
411
+ * store.dispose();
412
+ * ```
413
+ *
414
+ * @example
415
+ * ```typescript
416
+ * // Combine with scoped stores for organized persistence
417
+ * const persistent = await createPersistentStore({
418
+ * path: "~/.config/myapp/cursors.json",
419
+ * });
420
+ *
421
+ * const issuesCursors = createScopedStore(persistent, "issues");
422
+ * const prsCursors = createScopedStore(persistent, "prs");
423
+ *
424
+ * // All scopes share the same persistence file
425
+ * await persistent.flush();
426
+ * ```
427
+ */
428
+ declare function createPersistentStore(options: PersistentStoreOptions): Promise<PersistentStore>;
429
+ /**
430
+ * Create a scoped cursor store with namespace isolation.
431
+ *
432
+ * Scoped stores prefix all cursor IDs with the scope name, preventing
433
+ * collisions between different contexts (e.g., "issues" vs "pull-requests").
434
+ *
435
+ * Scopes can be nested: `createScopedStore(scopedStore, "child")` creates
436
+ * IDs like "parent:child:cursor-id".
437
+ *
438
+ * When retrieving cursors, the scope prefix is automatically stripped,
439
+ * so consumers see clean IDs without the namespace prefix.
440
+ *
441
+ * @param store - Parent store to scope (CursorStore or another ScopedStore for nesting)
442
+ * @param scope - Namespace for this scope (will be prefixed to all cursor IDs)
443
+ * @returns ScopedStore with isolated cursor management
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * const store = createCursorStore();
448
+ * const issueStore = createScopedStore(store, "issues");
449
+ * const prStore = createScopedStore(store, "prs");
450
+ *
451
+ * // These don't conflict - different namespaces
452
+ * issueStore.set(cursor1); // Stored as "issues:abc123"
453
+ * prStore.set(cursor2); // Stored as "prs:abc123"
454
+ *
455
+ * // Retrieved cursors have clean IDs
456
+ * const result = issueStore.get("abc123");
457
+ * if (result.isOk()) {
458
+ * result.value.id; // "abc123" (not "issues:abc123")
459
+ * }
460
+ *
461
+ * // Clear only affects the scope
462
+ * issueStore.clear(); // Only clears issue cursors
463
+ * prStore.list(); // PR cursors still exist
464
+ * ```
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * // Nested scopes for hierarchical organization
469
+ * const store = createCursorStore();
470
+ * const githubStore = createScopedStore(store, "github");
471
+ * const issuesStore = createScopedStore(githubStore, "issues");
472
+ *
473
+ * issuesStore.getScope(); // "github:issues"
474
+ *
475
+ * // Cursor stored as "github:issues:cursor-id"
476
+ * issuesStore.set(cursor);
477
+ * ```
478
+ */
479
+ declare function createScopedStore(store: CursorStore | ScopedStore, scope: string): ScopedStore;
480
+ /**
481
+ * Default page size when limit is not specified in cursor metadata.
482
+ */
483
+ declare const DEFAULT_PAGE_LIMIT = 25;
484
+ /**
485
+ * A simple cursor store for pagination operations.
486
+ *
487
+ * This is a simplified interface compared to {@link CursorStore}, designed
488
+ * specifically for pagination helpers. It returns `null` instead of errors
489
+ * for missing cursors, making pagination code more straightforward.
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const store = createPaginationStore();
494
+ *
495
+ * // Store a cursor
496
+ * store.set("my-cursor", cursor);
497
+ *
498
+ * // Retrieve (returns null if not found)
499
+ * const cursor = store.get("my-cursor");
500
+ * if (cursor) {
501
+ * console.log(cursor.position);
502
+ * }
503
+ * ```
504
+ */
505
+ interface PaginationStore {
506
+ /**
507
+ * Get a cursor by ID.
508
+ * @param id - The cursor ID to look up
509
+ * @returns The cursor if found, null otherwise
510
+ */
511
+ get(id: string): Cursor | null;
512
+ /**
513
+ * Store a cursor by ID.
514
+ * @param id - The ID to store under
515
+ * @param cursor - The cursor to store
516
+ */
517
+ set(id: string, cursor: Cursor): void;
518
+ /**
519
+ * Delete a cursor by ID.
520
+ * @param id - The cursor ID to delete
521
+ */
522
+ delete(id: string): void;
523
+ }
524
+ /**
525
+ * Get the default pagination store (module-level singleton).
526
+ *
527
+ * The default store is lazily initialized on first access.
528
+ * Use this when you want cursors to persist across multiple
529
+ * load/save calls within the same process.
530
+ *
531
+ * @returns The default pagination store instance
532
+ *
533
+ * @example
534
+ * ```typescript
535
+ * const store = getDefaultPaginationStore();
536
+ * store.set("cursor-1", cursor);
537
+ *
538
+ * // Later, same store is returned
539
+ * const sameStore = getDefaultPaginationStore();
540
+ * sameStore.get("cursor-1"); // Returns the cursor
541
+ * ```
542
+ */
543
+ declare function getDefaultPaginationStore(): PaginationStore;
544
+ /**
545
+ * Create an in-memory pagination store.
546
+ *
547
+ * This is a simple Map-backed store for cursor persistence.
548
+ * Unlike {@link createCursorStore}, this store does not handle
549
+ * TTL/expiration - it's designed for simple pagination use cases.
550
+ *
551
+ * @returns A new pagination store instance
552
+ *
553
+ * @example
554
+ * ```typescript
555
+ * const store = createPaginationStore();
556
+ *
557
+ * const cursor = createCursor({ position: 0, metadata: { limit: 25 } });
558
+ * if (cursor.isOk()) {
559
+ * store.set(cursor.value.id, cursor.value);
560
+ * const retrieved = store.get(cursor.value.id);
561
+ * }
562
+ * ```
563
+ */
564
+ declare function createPaginationStore(): PaginationStore;
565
+ /**
566
+ * Result of a pagination operation.
567
+ *
568
+ * @typeParam T - The type of items being paginated
569
+ */
570
+ interface PaginationResult<T> {
571
+ /** The items in the current page */
572
+ page: T[];
573
+ /** The cursor for the next page, or null if this is the last page */
574
+ nextCursor: Cursor | null;
575
+ }
576
+ /**
577
+ * Extract a page of items based on cursor position.
578
+ *
579
+ * Uses `cursor.position` as the offset and `cursor.metadata.limit` as
580
+ * the page size (defaults to {@link DEFAULT_PAGE_LIMIT} if not specified).
581
+ *
582
+ * Returns a `nextCursor` for fetching the next page, or `null` if there
583
+ * are no more items (i.e., this is the last page).
584
+ *
585
+ * @typeParam T - The type of items being paginated
586
+ * @param items - The full array of items to paginate
587
+ * @param cursor - Cursor containing position (offset) and optionally limit in metadata
588
+ * @returns Object containing the page slice and next cursor (or null)
589
+ *
590
+ * @example
591
+ * ```typescript
592
+ * const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
593
+ * const cursor = createCursor({
594
+ * position: 0,
595
+ * metadata: { limit: 3 },
596
+ * });
597
+ *
598
+ * if (cursor.isOk()) {
599
+ * const { page, nextCursor } = paginate(items, cursor.value);
600
+ * console.log(page); // [1, 2, 3]
601
+ * console.log(nextCursor?.position); // 3
602
+ *
603
+ * if (nextCursor) {
604
+ * const { page: page2 } = paginate(items, nextCursor);
605
+ * console.log(page2); // [4, 5, 6]
606
+ * }
607
+ * }
608
+ * ```
609
+ */
610
+ declare function paginate<T>(items: T[], cursor: Cursor): PaginationResult<T>;
611
+ /**
612
+ * Load a cursor from a pagination store.
613
+ *
614
+ * Returns `Ok(null)` if the cursor is not found (not an error).
615
+ * This differs from {@link CursorStore.get} which returns a `NotFoundError`.
616
+ *
617
+ * @param id - The cursor ID to load
618
+ * @param store - Optional store to load from (defaults to module-level store)
619
+ * @returns Result containing the cursor or null if not found
620
+ *
621
+ * @example
622
+ * ```typescript
623
+ * // Using default store
624
+ * const result = loadCursor("my-cursor");
625
+ * if (result.isOk()) {
626
+ * if (result.value) {
627
+ * console.log(`Found cursor at position ${result.value.position}`);
628
+ * } else {
629
+ * console.log("Cursor not found, starting fresh");
630
+ * }
631
+ * }
632
+ *
633
+ * // Using custom store
634
+ * const store = createPaginationStore();
635
+ * const result = loadCursor("my-cursor", store);
636
+ * ```
637
+ */
638
+ declare function loadCursor(id: string, store?: PaginationStore): Result<Cursor | null, StorageError>;
639
+ /**
640
+ * Save a cursor to a pagination store.
641
+ *
642
+ * The cursor is stored by its `id` property.
643
+ *
644
+ * @param cursor - The cursor to save
645
+ * @param store - Optional store to save to (defaults to module-level store)
646
+ * @returns Result indicating success or storage error
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * const cursor = createCursor({
651
+ * id: "search-results",
652
+ * position: 50,
653
+ * metadata: { limit: 25, query: "status:open" },
654
+ * });
655
+ *
656
+ * if (cursor.isOk()) {
657
+ * // Save to default store
658
+ * saveCursor(cursor.value);
659
+ *
660
+ * // Or save to custom store
661
+ * const store = createPaginationStore();
662
+ * saveCursor(cursor.value, store);
663
+ * }
664
+ * ```
665
+ */
666
+ declare function saveCursor(cursor: Cursor, store?: PaginationStore): Result<void, StorageError>;
667
+ export { saveCursor, paginate, loadCursor, isExpired, getDefaultPaginationStore, encodeCursor, decodeCursor, createScopedStore, createPersistentStore, createPaginationStore, createCursorStore, createCursor, advanceCursor, ScopedStore, PersistentStoreOptions, PersistentStore, PaginationStore, PaginationResult, DEFAULT_PAGE_LIMIT, CursorStore, Cursor, CreateCursorOptions };