@stackframe/stack-shared 2.8.55 → 2.8.58

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.
Files changed (154) hide show
  1. package/dist/apps/apps-config.d.mts +7 -1
  2. package/dist/apps/apps-config.d.ts +7 -1
  3. package/dist/apps/apps-config.js +7 -1
  4. package/dist/apps/apps-config.js.map +1 -1
  5. package/dist/config/schema-fuzzer.test.js +3 -0
  6. package/dist/config/schema-fuzzer.test.js.map +1 -1
  7. package/dist/config/schema.d.mts +162 -114
  8. package/dist/config/schema.d.ts +162 -114
  9. package/dist/config/schema.js +7 -0
  10. package/dist/config/schema.js.map +1 -1
  11. package/dist/esm/apps/apps-config.js +7 -1
  12. package/dist/esm/apps/apps-config.js.map +1 -1
  13. package/dist/esm/config/schema-fuzzer.test.js +3 -0
  14. package/dist/esm/config/schema-fuzzer.test.js.map +1 -1
  15. package/dist/esm/config/schema.js +7 -0
  16. package/dist/esm/config/schema.js.map +1 -1
  17. package/dist/esm/interface/admin-interface.js +49 -1
  18. package/dist/esm/interface/admin-interface.js.map +1 -1
  19. package/dist/esm/interface/client-interface.js +13 -4
  20. package/dist/esm/interface/client-interface.js.map +1 -1
  21. package/dist/esm/interface/crud/current-user.js +5 -2
  22. package/dist/esm/interface/crud/current-user.js.map +1 -1
  23. package/dist/esm/interface/crud/email-outbox.js +204 -0
  24. package/dist/esm/interface/crud/email-outbox.js.map +1 -0
  25. package/dist/esm/interface/crud/emails.js +6 -3
  26. package/dist/esm/interface/crud/emails.js.map +1 -1
  27. package/dist/esm/interface/crud/notification-preferences.js +13 -1
  28. package/dist/esm/interface/crud/notification-preferences.js.map +1 -1
  29. package/dist/esm/interface/crud/oauth-providers.js +10 -5
  30. package/dist/esm/interface/crud/oauth-providers.js.map +1 -1
  31. package/dist/esm/interface/crud/projects.js +9 -4
  32. package/dist/esm/interface/crud/projects.js.map +1 -1
  33. package/dist/esm/interface/crud/users.js +9 -2
  34. package/dist/esm/interface/crud/users.js.map +1 -1
  35. package/dist/esm/interface/server-interface.js +16 -0
  36. package/dist/esm/interface/server-interface.js.map +1 -1
  37. package/dist/esm/known-errors.js +45 -1
  38. package/dist/esm/known-errors.js.map +1 -1
  39. package/dist/esm/schema-fields.js +26 -2
  40. package/dist/esm/schema-fields.js.map +1 -1
  41. package/dist/esm/sessions.js +72 -8
  42. package/dist/esm/sessions.js.map +1 -1
  43. package/dist/esm/utils/env.js +13 -2
  44. package/dist/esm/utils/env.js.map +1 -1
  45. package/dist/esm/utils/esbuild.js +50 -21
  46. package/dist/esm/utils/esbuild.js.map +1 -1
  47. package/dist/esm/utils/globals.js +12 -0
  48. package/dist/esm/utils/globals.js.map +1 -1
  49. package/dist/esm/utils/paginated-lists.js +153 -23
  50. package/dist/esm/utils/paginated-lists.js.map +1 -1
  51. package/dist/esm/utils/paginated-lists.test.js +842 -0
  52. package/dist/esm/utils/paginated-lists.test.js.map +1 -0
  53. package/dist/esm/utils/proxies.js +28 -1
  54. package/dist/esm/utils/proxies.js.map +1 -1
  55. package/dist/esm/utils/react.js +7 -3
  56. package/dist/esm/utils/react.js.map +1 -1
  57. package/dist/esm/utils/results.js.map +1 -1
  58. package/dist/index.d.mts +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/interface/admin-interface.d.mts +26 -3
  61. package/dist/interface/admin-interface.d.ts +26 -3
  62. package/dist/interface/admin-interface.js +49 -1
  63. package/dist/interface/admin-interface.js.map +1 -1
  64. package/dist/interface/client-interface.d.mts +5 -0
  65. package/dist/interface/client-interface.d.ts +5 -0
  66. package/dist/interface/client-interface.js +13 -4
  67. package/dist/interface/client-interface.js.map +1 -1
  68. package/dist/interface/crud/current-user.d.mts +23 -6
  69. package/dist/interface/crud/current-user.d.ts +23 -6
  70. package/dist/interface/crud/current-user.js +5 -2
  71. package/dist/interface/crud/current-user.js.map +1 -1
  72. package/dist/interface/crud/email-outbox.d.mts +1075 -0
  73. package/dist/interface/crud/email-outbox.d.ts +1075 -0
  74. package/dist/interface/crud/email-outbox.js +241 -0
  75. package/dist/interface/crud/email-outbox.js.map +1 -0
  76. package/dist/interface/crud/emails.d.mts +5 -34
  77. package/dist/interface/crud/emails.d.ts +5 -34
  78. package/dist/interface/crud/emails.js +6 -3
  79. package/dist/interface/crud/emails.js.map +1 -1
  80. package/dist/interface/crud/notification-preferences.d.mts +12 -0
  81. package/dist/interface/crud/notification-preferences.d.ts +12 -0
  82. package/dist/interface/crud/notification-preferences.js +13 -1
  83. package/dist/interface/crud/notification-preferences.js.map +1 -1
  84. package/dist/interface/crud/oauth-providers.d.mts +5 -0
  85. package/dist/interface/crud/oauth-providers.d.ts +5 -0
  86. package/dist/interface/crud/oauth-providers.js +10 -5
  87. package/dist/interface/crud/oauth-providers.js.map +1 -1
  88. package/dist/interface/crud/project-api-keys.d.mts +1 -1
  89. package/dist/interface/crud/project-api-keys.d.ts +1 -1
  90. package/dist/interface/crud/projects.d.mts +73 -66
  91. package/dist/interface/crud/projects.d.ts +73 -66
  92. package/dist/interface/crud/projects.js +9 -4
  93. package/dist/interface/crud/projects.js.map +1 -1
  94. package/dist/interface/crud/team-member-profiles.d.mts +28 -12
  95. package/dist/interface/crud/team-member-profiles.d.ts +28 -12
  96. package/dist/interface/crud/users.d.mts +38 -6
  97. package/dist/interface/crud/users.d.ts +38 -6
  98. package/dist/interface/crud/users.js +9 -2
  99. package/dist/interface/crud/users.js.map +1 -1
  100. package/dist/interface/server-interface.d.mts +29 -0
  101. package/dist/interface/server-interface.d.ts +29 -0
  102. package/dist/interface/server-interface.js +16 -0
  103. package/dist/interface/server-interface.js.map +1 -1
  104. package/dist/interface/webhooks.d.mts +18 -2
  105. package/dist/interface/webhooks.d.ts +18 -2
  106. package/dist/known-errors.d.mts +14 -1
  107. package/dist/known-errors.d.ts +14 -1
  108. package/dist/known-errors.js +45 -1
  109. package/dist/known-errors.js.map +1 -1
  110. package/dist/schema-fields.d.mts +34 -1
  111. package/dist/schema-fields.d.ts +34 -1
  112. package/dist/schema-fields.js +32 -2
  113. package/dist/schema-fields.js.map +1 -1
  114. package/dist/sessions.d.mts +36 -5
  115. package/dist/sessions.d.ts +36 -5
  116. package/dist/sessions.js +72 -8
  117. package/dist/sessions.js.map +1 -1
  118. package/dist/utils/env.d.mts +2 -1
  119. package/dist/utils/env.d.ts +2 -1
  120. package/dist/utils/env.js +13 -1
  121. package/dist/utils/env.js.map +1 -1
  122. package/dist/utils/esbuild.js +49 -20
  123. package/dist/utils/esbuild.js.map +1 -1
  124. package/dist/utils/globals.d.mts +6 -1
  125. package/dist/utils/globals.d.ts +6 -1
  126. package/dist/utils/globals.js +13 -0
  127. package/dist/utils/globals.js.map +1 -1
  128. package/dist/utils/paginated-lists.d.mts +269 -12
  129. package/dist/utils/paginated-lists.d.ts +269 -12
  130. package/dist/utils/paginated-lists.js +153 -23
  131. package/dist/utils/paginated-lists.js.map +1 -1
  132. package/dist/utils/paginated-lists.test.d.mts +2 -0
  133. package/dist/utils/paginated-lists.test.d.ts +2 -0
  134. package/dist/utils/paginated-lists.test.js +844 -0
  135. package/dist/utils/paginated-lists.test.js.map +1 -0
  136. package/dist/utils/proxies.d.mts +8 -1
  137. package/dist/utils/proxies.d.ts +8 -1
  138. package/dist/utils/proxies.js +30 -2
  139. package/dist/utils/proxies.js.map +1 -1
  140. package/dist/utils/react.d.mts +1 -1
  141. package/dist/utils/react.d.ts +1 -1
  142. package/dist/utils/react.js +7 -3
  143. package/dist/utils/react.js.map +1 -1
  144. package/dist/utils/results.d.mts +5 -5
  145. package/dist/utils/results.d.ts +5 -5
  146. package/dist/utils/results.js.map +1 -1
  147. package/package.json +2 -1
  148. package/CHANGELOG.md +0 -1348
  149. package/dist/esm/interface/crud/config.js +0 -40
  150. package/dist/esm/interface/crud/config.js.map +0 -1
  151. package/dist/interface/crud/config.d.mts +0 -49
  152. package/dist/interface/crud/config.d.ts +0 -49
  153. package/dist/interface/crud/config.js +0 -79
  154. package/dist/interface/crud/config.js.map +0 -1
@@ -27,7 +27,8 @@ type ImplQueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> = Q
27
27
  type QueryResult<Item, Cursor> = {
28
28
  items: {
29
29
  item: Item;
30
- itemCursor: Cursor;
30
+ prevCursor: Cursor;
31
+ nextCursor: Cursor;
31
32
  }[];
32
33
  isFirst: boolean;
33
34
  isLast: boolean;
@@ -36,30 +37,99 @@ type QueryResult<Item, Cursor> = {
36
37
  type ImplQueryResult<Item, Cursor> = {
37
38
  items: {
38
39
  item: Item;
39
- itemCursor: Cursor;
40
+ prevCursor: Cursor;
41
+ nextCursor: Cursor;
40
42
  }[];
41
43
  isFirst: boolean;
42
44
  isLast: boolean;
43
45
  cursor: Cursor;
44
46
  };
47
+ /**
48
+ * Abstract base class for cursor-based pagination over any ordered data source.
49
+ *
50
+ * Subclasses implement `_nextOrPrev` to fetch items in one direction. This class handles
51
+ * limit enforcement, sorting validation, and provides `map`, `filter`, `flatMap`, and `merge` utilities.
52
+ *
53
+ * @template Item - The type of items in the list
54
+ * @template Cursor - A string-based cursor type for position tracking. Cursors are always between two items in the list. Note that cursors may not be stable if the filter or orderBy changes.
55
+ * @template Filter - Query filter type
56
+ * @template OrderBy - Sort order specification type
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // Basic usage: paginate through users
61
+ * const users = new MyUserList();
62
+ * const first10 = await users.next({ after: users.getFirstCursor(), limit: 10, filter: {}, orderBy: 'name', limitPrecision: 'exact' });
63
+ * // first10 = { items: [...], isFirst: true, isLast: false, cursor: "cursor-after-item-10" }
64
+ *
65
+ * const next10 = await users.next({ after: first10.cursor, limit: 10, filter: {}, orderBy: 'name', limitPrecision: 'exact' });
66
+ * // Continues from where we left off
67
+ * ```
68
+ */
45
69
  declare abstract class PaginatedList<Item, Cursor extends string, Filter extends unknown, OrderBy extends unknown> {
46
70
  protected abstract _getFirstCursor(): Cursor;
47
71
  protected abstract _getLastCursor(): Cursor;
48
72
  protected abstract _compare(orderBy: OrderBy, a: Item, b: Item): number;
49
73
  protected abstract _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<ImplQueryResult<Item, Cursor>>;
74
+ /** Returns the cursor pointing to the start of the list (before any items). */
50
75
  getFirstCursor(): Cursor;
76
+ /** Returns the cursor pointing to the end of the list (after all items). */
51
77
  getLastCursor(): Cursor;
78
+ /** Compares two items according to the given orderBy. Returns negative if a < b, 0 if equal, positive if a > b. */
52
79
  compare(orderBy: OrderBy, a: Item, b: Item): number;
80
+ /**
81
+ * Fetches items moving forward ('next') or backward ('prev') from the given cursor.
82
+ *
83
+ * Respects `limitPrecision`: 'exact' guarantees the exact limit, 'at-least'/'at-most'/'approximate'
84
+ * allow flexibility for performance. Returns items, boundary flags, and a new cursor.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // Get 5 items after the start
89
+ * const result = await list.nextOrPrev('next', { cursor: list.getFirstCursor(), limit: 5, filter: {}, orderBy: 'asc', limitPrecision: 'exact' });
90
+ * // result.items.length === 5 (or less if list has fewer items)
91
+ * // result.isFirst === true (started at first cursor)
92
+ * // result.isLast === true if we got all remaining items
93
+ *
94
+ * // Continue from where we left off
95
+ * const more = await list.nextOrPrev('next', { cursor: result.cursor, limit: 5, ... });
96
+ * ```
97
+ */
53
98
  nextOrPrev(type: 'next' | 'prev', options: QueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>>;
99
+ /** Fetches items after the given cursor (forward pagination). */
54
100
  next({ after, ...rest }: QueryOptions<'next', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>>;
101
+ /** Fetches items before the given cursor (backward pagination). */
55
102
  prev({ before, ...rest }: QueryOptions<'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>>;
103
+ /**
104
+ * Transforms this list by mapping each item to zero or more new items.
105
+ *
106
+ * Note that the sort order must be preserved after the operation; the flat-mapped list will not be sorted automatically.
107
+ *
108
+ * @param itemMapper - Maps each item (with its cursor) to an array of new items
109
+ * @param compare - Comparison function for the new item type
110
+ * @param newCursorFromOldCursor/oldCursorFromNewCursor - Cursor conversion functions
111
+ * @param estimateItemsToFetch - Estimates how many source items to fetch for a given limit
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * // Expand orders into line items (1 order -> N line items)
116
+ * const lineItems = ordersList.flatMap({
117
+ * itemMapper: ({ item: order }) => order.lineItems.map((li, i) => ({ item: li, prevCursor: `${order.id}-${i}`, nextCursor: `${order.id}-${i + 1}` })),
118
+ * compare: (_, a, b) => a.createdAt - b.createdAt,
119
+ * estimateItemsToFetch: ({ limit }) => Math.ceil(limit / 3), // avg 3 items per order
120
+ * // ... cursor converters
121
+ * });
122
+ * ```
123
+ */
56
124
  flatMap<Item2, Cursor2 extends string, Filter2 extends unknown, OrderBy2 extends unknown>(options: {
57
125
  itemMapper: (itemEntry: {
58
126
  item: Item;
59
- itemCursor: Cursor;
127
+ prevCursor: Cursor;
128
+ nextCursor: Cursor;
60
129
  }, filter: Filter2, orderBy: OrderBy2) => {
61
130
  item: Item2;
62
- itemCursor: Cursor2;
131
+ prevCursor: Cursor2;
132
+ nextCursor: Cursor2;
63
133
  }[];
64
134
  compare: (orderBy: OrderBy2, a: Item2, b: Item2) => number;
65
135
  newCursorFromOldCursor: (cursor: Cursor) => Cursor2;
@@ -72,12 +142,45 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
72
142
  limit: number;
73
143
  }) => number;
74
144
  }): PaginatedList<Item2, Cursor2, Filter2, OrderBy2>;
145
+ /**
146
+ * Transforms each item in the list. Requires a reverse mapper for comparison delegation.
147
+ *
148
+ * @param itemMapper - Transforms each item
149
+ * @param oldItemFromNewItem - Reverse-maps new items back to old items (for comparison)
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Convert User objects to UserDTO
154
+ * const userDtos = usersList.map({
155
+ * itemMapper: (user) => ({ id: user.id, displayName: user.name }),
156
+ * oldItemFromNewItem: (dto) => fullUsers.get(dto.id)!, // for comparison
157
+ * oldFilterFromNewFilter: (f) => f,
158
+ * oldOrderByFromNewOrderBy: (o) => o,
159
+ * });
160
+ * ```
161
+ */
75
162
  map<Item2, Filter2 extends unknown, OrderBy2 extends unknown>(options: {
76
163
  itemMapper: (item: Item) => Item2;
77
164
  oldItemFromNewItem: (item: Item2) => Item;
78
165
  oldFilterFromNewFilter: (filter: Filter2) => Filter;
79
166
  oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy;
80
167
  }): PaginatedList<Item2, Cursor, Filter2, OrderBy2>;
168
+ /**
169
+ * Filters items in the list. Requires an estimate function since filtering may reduce output.
170
+ *
171
+ * @param filter - Predicate to include/exclude items
172
+ * @param estimateItemsToFetch - Estimates how many source items to fetch (accounts for filter selectivity)
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // Filter to only active users
177
+ * const activeUsers = usersList.filter({
178
+ * filter: (user, filterOpts) => user.isActive && user.role === filterOpts.role,
179
+ * oldFilterFromNewFilter: (f) => ({}), // original list has no filter
180
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // expect ~50% active
181
+ * });
182
+ * ```
183
+ */
81
184
  filter<Filter2 extends unknown>(options: {
82
185
  filter: (item: Item, filter: Filter2) => boolean;
83
186
  oldFilterFromNewFilter: (filter: Filter2) => Filter;
@@ -87,6 +190,20 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
87
190
  limit: number;
88
191
  }) => number;
89
192
  }): PaginatedList<Item, Cursor, Filter2, OrderBy>;
193
+ /**
194
+ * Adds an additional filter constraint while preserving the original filter type.
195
+ * Shorthand for `filter()` that intersects Filter with AddedFilter.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * // Add a "verified" filter on top of existing filters
200
+ * const verifiedUsers = usersList.addFilter({
201
+ * filter: (user, f) => user.emailVerified,
202
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // ~50% are verified
203
+ * });
204
+ * // verifiedUsers filter type is Filter
205
+ * ```
206
+ */
90
207
  addFilter<AddedFilter extends unknown>(options: {
91
208
  filter: (item: Item, filter: Filter & AddedFilter) => boolean;
92
209
  estimateItemsToFetch: (options: {
@@ -95,7 +212,31 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
95
212
  limit: number;
96
213
  }) => number;
97
214
  }): PaginatedList<Item, Cursor, Filter & AddedFilter, OrderBy>;
215
+ /**
216
+ * Merges multiple paginated lists into one, interleaving items by sort order.
217
+ * All lists must use the same compare function.
218
+ *
219
+ * The merged cursor is a JSON-encoded array of individual list cursors.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * // Merge users from multiple sources into a unified feed
224
+ * const allUsers = PaginatedList.merge(internalUsers, externalUsers, partnerUsers);
225
+ * const page = await allUsers.next({ after: allUsers.getFirstCursor(), limit: 20, ... });
226
+ * // page.items contains interleaved items from all sources, sorted by orderBy
227
+ * ```
228
+ */
98
229
  static merge<Item, Filter extends unknown, OrderBy extends unknown>(...lists: PaginatedList<Item, any, Filter, OrderBy>[]): PaginatedList<Item, string, Filter, OrderBy>;
230
+ /**
231
+ * Returns an empty paginated list that always returns no items.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * const empty = PaginatedList.empty();
236
+ * const result = await empty.next({ after: empty.getFirstCursor(), limit: 10, ... });
237
+ * // result = { items: [], isFirst: true, isLast: true, cursor: "first" }
238
+ * ```
239
+ */
99
240
  static empty(): {
100
241
  _getFirstCursor(): "first";
101
242
  _getLastCursor(): "last";
@@ -106,19 +247,65 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
106
247
  isLast: boolean;
107
248
  cursor: "first";
108
249
  }>;
250
+ /** Returns the cursor pointing to the start of the list (before any items). */
109
251
  getFirstCursor(): "first" | "last";
252
+ /** Returns the cursor pointing to the end of the list (after all items). */
110
253
  getLastCursor(): "first" | "last";
254
+ /** Compares two items according to the given orderBy. Returns negative if a < b, 0 if equal, positive if a > b. */
111
255
  compare(orderBy: any, a: never, b: never): number;
256
+ /**
257
+ * Fetches items moving forward ('next') or backward ('prev') from the given cursor.
258
+ *
259
+ * Respects `limitPrecision`: 'exact' guarantees the exact limit, 'at-least'/'at-most'/'approximate'
260
+ * allow flexibility for performance. Returns items, boundary flags, and a new cursor.
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * // Get 5 items after the start
265
+ * const result = await list.nextOrPrev('next', { cursor: list.getFirstCursor(), limit: 5, filter: {}, orderBy: 'asc', limitPrecision: 'exact' });
266
+ * // result.items.length === 5 (or less if list has fewer items)
267
+ * // result.isFirst === true (started at first cursor)
268
+ * // result.isLast === true if we got all remaining items
269
+ *
270
+ * // Continue from where we left off
271
+ * const more = await list.nextOrPrev('next', { cursor: result.cursor, limit: 5, ... });
272
+ * ```
273
+ */
112
274
  nextOrPrev(type: "next" | "prev", options: QueryOptions<"next" | "prev", "first" | "last", any, any>): Promise<QueryResult<never, "first" | "last">>;
275
+ /** Fetches items after the given cursor (forward pagination). */
113
276
  next({ after, ...rest }: QueryOptions<"next", "first" | "last", any, any>): Promise<QueryResult<never, "first" | "last">>;
277
+ /** Fetches items before the given cursor (backward pagination). */
114
278
  prev({ before, ...rest }: QueryOptions<"prev", "first" | "last", any, any>): Promise<QueryResult<never, "first" | "last">>;
279
+ /**
280
+ * Transforms this list by mapping each item to zero or more new items.
281
+ *
282
+ * Note that the sort order must be preserved after the operation; the flat-mapped list will not be sorted automatically.
283
+ *
284
+ * @param itemMapper - Maps each item (with its cursor) to an array of new items
285
+ * @param compare - Comparison function for the new item type
286
+ * @param newCursorFromOldCursor/oldCursorFromNewCursor - Cursor conversion functions
287
+ * @param estimateItemsToFetch - Estimates how many source items to fetch for a given limit
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * // Expand orders into line items (1 order -> N line items)
292
+ * const lineItems = ordersList.flatMap({
293
+ * itemMapper: ({ item: order }) => order.lineItems.map((li, i) => ({ item: li, prevCursor: `${order.id}-${i}`, nextCursor: `${order.id}-${i + 1}` })),
294
+ * compare: (_, a, b) => a.createdAt - b.createdAt,
295
+ * estimateItemsToFetch: ({ limit }) => Math.ceil(limit / 3), // avg 3 items per order
296
+ * // ... cursor converters
297
+ * });
298
+ * ```
299
+ */
115
300
  flatMap<Item2, Cursor2 extends string, Filter2 extends unknown, OrderBy2 extends unknown>(options: {
116
301
  itemMapper: (itemEntry: {
117
302
  item: never;
118
- itemCursor: "first" | "last";
303
+ prevCursor: "first" | "last";
304
+ nextCursor: "first" | "last";
119
305
  }, filter: Filter2, orderBy: OrderBy2) => {
120
306
  item: Item2;
121
- itemCursor: Cursor2;
307
+ prevCursor: Cursor2;
308
+ nextCursor: Cursor2;
122
309
  }[];
123
310
  compare: (orderBy: OrderBy2, a: Item2, b: Item2) => number;
124
311
  newCursorFromOldCursor: (cursor: "first" | "last") => Cursor2;
@@ -131,12 +318,45 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
131
318
  limit: number;
132
319
  }) => number;
133
320
  }): PaginatedList<Item2, Cursor2, Filter2, OrderBy2>;
321
+ /**
322
+ * Transforms each item in the list. Requires a reverse mapper for comparison delegation.
323
+ *
324
+ * @param itemMapper - Transforms each item
325
+ * @param oldItemFromNewItem - Reverse-maps new items back to old items (for comparison)
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * // Convert User objects to UserDTO
330
+ * const userDtos = usersList.map({
331
+ * itemMapper: (user) => ({ id: user.id, displayName: user.name }),
332
+ * oldItemFromNewItem: (dto) => fullUsers.get(dto.id)!, // for comparison
333
+ * oldFilterFromNewFilter: (f) => f,
334
+ * oldOrderByFromNewOrderBy: (o) => o,
335
+ * });
336
+ * ```
337
+ */
134
338
  map<Item2_1, Filter2_1 extends unknown, OrderBy2_1 extends unknown>(options: {
135
339
  itemMapper: (item: never) => Item2_1;
136
340
  oldItemFromNewItem: (item: Item2_1) => never;
137
341
  oldFilterFromNewFilter: (filter: Filter2_1) => any;
138
342
  oldOrderByFromNewOrderBy: (orderBy: OrderBy2_1) => any;
139
343
  }): PaginatedList<Item2_1, "first" | "last", Filter2_1, OrderBy2_1>;
344
+ /**
345
+ * Filters items in the list. Requires an estimate function since filtering may reduce output.
346
+ *
347
+ * @param filter - Predicate to include/exclude items
348
+ * @param estimateItemsToFetch - Estimates how many source items to fetch (accounts for filter selectivity)
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * // Filter to only active users
353
+ * const activeUsers = usersList.filter({
354
+ * filter: (user, filterOpts) => user.isActive && user.role === filterOpts.role,
355
+ * oldFilterFromNewFilter: (f) => ({}), // original list has no filter
356
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // expect ~50% active
357
+ * });
358
+ * ```
359
+ */
140
360
  filter<Filter2_2 extends unknown>(options: {
141
361
  filter: (item: never, filter: Filter2_2) => boolean;
142
362
  oldFilterFromNewFilter: (filter: Filter2_2) => any;
@@ -146,6 +366,20 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
146
366
  limit: number;
147
367
  }) => number;
148
368
  }): PaginatedList<never, "first" | "last", Filter2_2, any>;
369
+ /**
370
+ * Adds an additional filter constraint while preserving the original filter type.
371
+ * Shorthand for `filter()` that intersects Filter with AddedFilter.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * // Add a "verified" filter on top of existing filters
376
+ * const verifiedUsers = usersList.addFilter({
377
+ * filter: (user, f) => user.emailVerified,
378
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // ~50% are verified
379
+ * });
380
+ * // verifiedUsers filter type is Filter
381
+ * ```
382
+ */
149
383
  addFilter<AddedFilter extends unknown>(options: {
150
384
  filter: (item: never, filter: any) => boolean;
151
385
  estimateItemsToFetch: (options: {
@@ -156,20 +390,43 @@ declare abstract class PaginatedList<Item, Cursor extends string, Filter extends
156
390
  }): PaginatedList<never, "first" | "last", any, any>;
157
391
  };
158
392
  }
159
- declare class ArrayPaginatedList<Item> extends PaginatedList<Item, `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number> {
393
+ /**
394
+ * A simple in-memory paginated list backed by an array.
395
+ *
396
+ * Filter is a predicate function, OrderBy is a comparator function.
397
+ * Cursors are in the format "before-{index}" representing the position before that index.
398
+ *
399
+ * Note: This implementation re-filters and re-sorts the entire array on each query,
400
+ * so it's only suitable for small datasets.
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * const numbers = new ArrayPaginatedList([5, 2, 8, 1, 9, 3]);
405
+ * const page = await numbers.next({
406
+ * after: "before-0",
407
+ * limit: 3,
408
+ * filter: (n) => n > 2,
409
+ * orderBy: (a, b) => a - b,
410
+ * limitPrecision: 'exact',
411
+ * });
412
+ * // page.items = [{ item: 3, prevCursor: "before-0", nextCursor: "before-1" }, { item: 5, prevCursor: "before-1", nextCursor: "before-2" }, ...]
413
+ * ```
414
+ */
415
+ declare class ArrayPaginatedList<Item> extends PaginatedList<Item, `before-${number}`, (item: Item) => boolean, (a: Item, b: Item) => number> {
160
416
  private readonly array;
161
417
  constructor(array: Item[]);
162
- _getFirstCursor(): "0";
163
- _getLastCursor(): `${number}`;
418
+ _getFirstCursor(): "before-0";
419
+ _getLastCursor(): `before-${number}`;
164
420
  _compare(orderBy: (a: Item, b: Item) => number, a: Item, b: Item): number;
165
- _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number>): Promise<{
421
+ _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', `before-${number}`, (item: Item) => boolean, (a: Item, b: Item) => number>): Promise<{
166
422
  items: {
167
423
  item: Item;
168
- itemCursor: `${number}`;
424
+ prevCursor: `before-${number}`;
425
+ nextCursor: `before-${number}`;
169
426
  }[];
170
427
  isFirst: boolean;
171
428
  isLast: boolean;
172
- cursor: `${number}`;
429
+ cursor: `before-${number}`;
173
430
  }>;
174
431
  }
175
432