@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
@@ -24,26 +24,46 @@ __export(paginated_lists_exports, {
24
24
  PaginatedList: () => PaginatedList
25
25
  });
26
26
  module.exports = __toCommonJS(paginated_lists_exports);
27
- var import_arrays = require("./arrays.js");
28
27
  var import_errors = require("./errors.js");
29
28
  var PaginatedList = class _PaginatedList {
30
29
  // Implementations
30
+ /** Returns the cursor pointing to the start of the list (before any items). */
31
31
  getFirstCursor() {
32
32
  return this._getFirstCursor();
33
33
  }
34
+ /** Returns the cursor pointing to the end of the list (after all items). */
34
35
  getLastCursor() {
35
36
  return this._getLastCursor();
36
37
  }
38
+ /** Compares two items according to the given orderBy. Returns negative if a < b, 0 if equal, positive if a > b. */
37
39
  compare(orderBy, a, b) {
38
40
  return this._compare(orderBy, a, b);
39
41
  }
42
+ /**
43
+ * Fetches items moving forward ('next') or backward ('prev') from the given cursor.
44
+ *
45
+ * Respects `limitPrecision`: 'exact' guarantees the exact limit, 'at-least'/'at-most'/'approximate'
46
+ * allow flexibility for performance. Returns items, boundary flags, and a new cursor.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * // Get 5 items after the start
51
+ * const result = await list.nextOrPrev('next', { cursor: list.getFirstCursor(), limit: 5, filter: {}, orderBy: 'asc', limitPrecision: 'exact' });
52
+ * // result.items.length === 5 (or less if list has fewer items)
53
+ * // result.isFirst === true (started at first cursor)
54
+ * // result.isLast === true if we got all remaining items
55
+ *
56
+ * // Continue from where we left off
57
+ * const more = await list.nextOrPrev('next', { cursor: result.cursor, limit: 5, ... });
58
+ * ```
59
+ */
40
60
  async nextOrPrev(type, options) {
41
61
  let result = [];
42
62
  let includesFirst = false;
43
63
  let includesLast = false;
44
64
  let cursor = options.cursor;
45
65
  let limitRemaining = options.limit;
46
- while (limitRemaining > 0 || type === "next" && includesLast || type === "prev" && includesFirst) {
66
+ while (limitRemaining > 0 && (type !== "next" || !includesLast) && (type !== "prev" || !includesFirst)) {
47
67
  const iterationRes = await this._nextOrPrev(type, {
48
68
  cursor,
49
69
  limit: options.limit,
@@ -71,21 +91,23 @@ var PaginatedList = class _PaginatedList {
71
91
  if (type === "next") {
72
92
  result = result.slice(0, options.limit);
73
93
  includesLast = false;
74
- if (options.limit > 0) cursor = result[result.length - 1].itemCursor;
94
+ if (options.limit > 0) cursor = result[result.length - 1].nextCursor;
75
95
  } else {
76
96
  result = result.slice(result.length - options.limit);
77
97
  includesFirst = false;
78
- if (options.limit > 0) cursor = result[0].itemCursor;
98
+ if (options.limit > 0) cursor = result[0].prevCursor;
79
99
  }
80
100
  }
81
101
  return { items: result, isFirst: includesFirst, isLast: includesLast, cursor };
82
102
  }
103
+ /** Fetches items after the given cursor (forward pagination). */
83
104
  async next({ after, ...rest }) {
84
105
  return await this.nextOrPrev("next", {
85
106
  ...rest,
86
107
  cursor: after
87
108
  });
88
109
  }
110
+ /** Fetches items before the given cursor (backward pagination). */
89
111
  async prev({ before, ...rest }) {
90
112
  return await this.nextOrPrev("prev", {
91
113
  ...rest,
@@ -93,6 +115,27 @@ var PaginatedList = class _PaginatedList {
93
115
  });
94
116
  }
95
117
  // Utility methods below
118
+ /**
119
+ * Transforms this list by mapping each item to zero or more new items.
120
+ *
121
+ * Note that the sort order must be preserved after the operation; the flat-mapped list will not be sorted automatically.
122
+ *
123
+ * @param itemMapper - Maps each item (with its cursor) to an array of new items
124
+ * @param compare - Comparison function for the new item type
125
+ * @param newCursorFromOldCursor/oldCursorFromNewCursor - Cursor conversion functions
126
+ * @param estimateItemsToFetch - Estimates how many source items to fetch for a given limit
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * // Expand orders into line items (1 order -> N line items)
131
+ * const lineItems = ordersList.flatMap({
132
+ * itemMapper: ({ item: order }) => order.lineItems.map((li, i) => ({ item: li, prevCursor: `${order.id}-${i}`, nextCursor: `${order.id}-${i + 1}` })),
133
+ * compare: (_, a, b) => a.createdAt - b.createdAt,
134
+ * estimateItemsToFetch: ({ limit }) => Math.ceil(limit / 3), // avg 3 items per order
135
+ * // ... cursor converters
136
+ * });
137
+ * ```
138
+ */
96
139
  flatMap(options) {
97
140
  const that = this;
98
141
  class FlatMapPaginatedList extends _PaginatedList {
@@ -129,10 +172,27 @@ var PaginatedList = class _PaginatedList {
129
172
  }
130
173
  return new FlatMapPaginatedList();
131
174
  }
175
+ /**
176
+ * Transforms each item in the list. Requires a reverse mapper for comparison delegation.
177
+ *
178
+ * @param itemMapper - Transforms each item
179
+ * @param oldItemFromNewItem - Reverse-maps new items back to old items (for comparison)
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * // Convert User objects to UserDTO
184
+ * const userDtos = usersList.map({
185
+ * itemMapper: (user) => ({ id: user.id, displayName: user.name }),
186
+ * oldItemFromNewItem: (dto) => fullUsers.get(dto.id)!, // for comparison
187
+ * oldFilterFromNewFilter: (f) => f,
188
+ * oldOrderByFromNewOrderBy: (o) => o,
189
+ * });
190
+ * ```
191
+ */
132
192
  map(options) {
133
193
  return this.flatMap({
134
194
  itemMapper: (itemEntry, filter, orderBy) => {
135
- return [{ item: options.itemMapper(itemEntry.item), itemCursor: itemEntry.itemCursor }];
195
+ return [{ item: options.itemMapper(itemEntry.item), prevCursor: itemEntry.prevCursor, nextCursor: itemEntry.nextCursor }];
136
196
  },
137
197
  compare: (orderBy, a, b) => this.compare(options.oldOrderByFromNewOrderBy(orderBy), options.oldItemFromNewItem(a), options.oldItemFromNewItem(b)),
138
198
  newCursorFromOldCursor: (cursor) => cursor,
@@ -142,6 +202,22 @@ var PaginatedList = class _PaginatedList {
142
202
  estimateItemsToFetch: (options2) => options2.limit
143
203
  });
144
204
  }
205
+ /**
206
+ * Filters items in the list. Requires an estimate function since filtering may reduce output.
207
+ *
208
+ * @param filter - Predicate to include/exclude items
209
+ * @param estimateItemsToFetch - Estimates how many source items to fetch (accounts for filter selectivity)
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * // Filter to only active users
214
+ * const activeUsers = usersList.filter({
215
+ * filter: (user, filterOpts) => user.isActive && user.role === filterOpts.role,
216
+ * oldFilterFromNewFilter: (f) => ({}), // original list has no filter
217
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // expect ~50% active
218
+ * });
219
+ * ```
220
+ */
145
221
  filter(options) {
146
222
  return this.flatMap({
147
223
  itemMapper: (itemEntry, filter, orderBy) => options.filter(itemEntry.item, filter) ? [itemEntry] : [],
@@ -153,6 +229,20 @@ var PaginatedList = class _PaginatedList {
153
229
  estimateItemsToFetch: (o) => options.estimateItemsToFetch(o)
154
230
  });
155
231
  }
232
+ /**
233
+ * Adds an additional filter constraint while preserving the original filter type.
234
+ * Shorthand for `filter()` that intersects Filter with AddedFilter.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * // Add a "verified" filter on top of existing filters
239
+ * const verifiedUsers = usersList.addFilter({
240
+ * filter: (user, f) => user.emailVerified,
241
+ * estimateItemsToFetch: ({ limit }) => limit * 2, // ~50% are verified
242
+ * });
243
+ * // verifiedUsers filter type is Filter
244
+ * ```
245
+ */
156
246
  addFilter(options) {
157
247
  return this.filter({
158
248
  filter: (item, filter) => options.filter(item, filter),
@@ -160,6 +250,20 @@ var PaginatedList = class _PaginatedList {
160
250
  estimateItemsToFetch: (o) => options.estimateItemsToFetch(o)
161
251
  });
162
252
  }
253
+ /**
254
+ * Merges multiple paginated lists into one, interleaving items by sort order.
255
+ * All lists must use the same compare function.
256
+ *
257
+ * The merged cursor is a JSON-encoded array of individual list cursors.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * // Merge users from multiple sources into a unified feed
262
+ * const allUsers = PaginatedList.merge(internalUsers, externalUsers, partnerUsers);
263
+ * const page = await allUsers.next({ after: allUsers.getFirstCursor(), limit: 20, ... });
264
+ * // page.items contains interleaved items from all sources, sorted by orderBy
265
+ * ```
266
+ */
163
267
  static merge(...lists) {
164
268
  class MergePaginatedList extends _PaginatedList {
165
269
  _getFirstCursor() {
@@ -171,7 +275,7 @@ var PaginatedList = class _PaginatedList {
171
275
  _compare(orderBy, a, b) {
172
276
  const listsResults = lists.map((list) => list.compare(orderBy, a, b));
173
277
  if (!listsResults.every((result) => result === listsResults[0])) {
174
- throw new import_errors.StackAssertionError("Lists have different compare results; make sure that they use the same compare function", { lists, listsResults });
278
+ throw new import_errors.StackAssertionError("Lists have different compare results; make sure that they use the same compare function", { lists, listsResults, orderBy, a, b });
175
279
  }
176
280
  return listsResults[0];
177
281
  }
@@ -188,20 +292,40 @@ var PaginatedList = class _PaginatedList {
188
292
  }));
189
293
  const combinedItems = fetchedLists.flatMap((list, i) => list.items.map((itemEntry) => ({ itemEntry, listIndex: i })));
190
294
  const sortedItems = [...combinedItems].sort((a, b) => this._compare(orderBy, a.itemEntry.item, b.itemEntry.item));
191
- const lastCursorForEachList = sortedItems.reduce((acc, item) => {
192
- acc[item.listIndex] = item.itemEntry.itemCursor;
193
- return acc;
194
- }, (0, import_arrays.range)(lists.length).map((i) => cursors[i]));
295
+ const sortedItemsWithMergedCursors = [];
296
+ const curCursors = [...cursors];
297
+ for (const item of type === "next" ? sortedItems : sortedItems.reverse()) {
298
+ const lastCursors = [...curCursors];
299
+ curCursors[item.listIndex] = type === "next" ? item.itemEntry.nextCursor : item.itemEntry.prevCursor;
300
+ sortedItemsWithMergedCursors.push({
301
+ item: item.itemEntry.item,
302
+ prevCursor: type === "next" ? JSON.stringify(lastCursors) : JSON.stringify(curCursors),
303
+ nextCursor: type === "next" ? JSON.stringify(curCursors) : JSON.stringify(lastCursors)
304
+ });
305
+ }
306
+ if (type === "prev") {
307
+ sortedItemsWithMergedCursors.reverse();
308
+ }
195
309
  return {
196
- items: sortedItems.map((item) => item.itemEntry),
197
- isFirst: sortedItems.every((item) => item.listIndex === 0),
198
- isLast: sortedItems.every((item) => item.listIndex === lists.length - 1),
199
- cursor: JSON.stringify(lastCursorForEachList)
310
+ items: sortedItemsWithMergedCursors,
311
+ isFirst: fetchedLists.every((list) => list.isFirst),
312
+ isLast: fetchedLists.every((list) => list.isLast),
313
+ cursor: JSON.stringify(curCursors)
200
314
  };
201
315
  }
202
316
  }
203
317
  return new MergePaginatedList();
204
318
  }
319
+ /**
320
+ * Returns an empty paginated list that always returns no items.
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * const empty = PaginatedList.empty();
325
+ * const result = await empty.next({ after: empty.getFirstCursor(), limit: 10, ... });
326
+ * // result = { items: [], isFirst: true, isLast: true, cursor: "first" }
327
+ * ```
328
+ */
205
329
  static empty() {
206
330
  class EmptyPaginatedList extends _PaginatedList {
207
331
  _getFirstCursor() {
@@ -226,10 +350,10 @@ var ArrayPaginatedList = class extends PaginatedList {
226
350
  this.array = array;
227
351
  }
228
352
  _getFirstCursor() {
229
- return "0";
353
+ return "before-0";
230
354
  }
231
355
  _getLastCursor() {
232
- return `${this.array.length - 1}`;
356
+ return `before-${this.array.length}`;
233
357
  }
234
358
  _compare(orderBy, a, b) {
235
359
  return orderBy(a, b);
@@ -237,14 +361,20 @@ var ArrayPaginatedList = class extends PaginatedList {
237
361
  async _nextOrPrev(type, options) {
238
362
  const filteredArray = this.array.filter(options.filter);
239
363
  const sortedArray = [...filteredArray].sort((a, b) => this._compare(options.orderBy, a, b));
240
- const itemEntriesArray = sortedArray.map((item, index) => ({ item, itemCursor: `${index}` }));
241
- const oldCursor = Number(options.cursor);
242
- const newCursor = Math.max(0, Math.min(this.array.length - 1, oldCursor + (type === "next" ? 1 : -1) * options.limit));
364
+ const itemEntriesArray = sortedArray.map((item, index) => ({
365
+ item,
366
+ prevCursor: `before-${index}`,
367
+ nextCursor: `before-${index + 1}`
368
+ }));
369
+ const oldCursor = Number(options.cursor.replace("before-", ""));
370
+ const clampedOldCursor = Math.max(0, Math.min(sortedArray.length, oldCursor));
371
+ const newCursor = Math.max(0, Math.min(sortedArray.length, clampedOldCursor + (type === "next" ? 1 : -1) * options.limit));
372
+ const slicedItemEntriesArray = itemEntriesArray.slice(Math.min(clampedOldCursor, newCursor), Math.max(clampedOldCursor, newCursor));
243
373
  return {
244
- items: itemEntriesArray.slice(Math.min(oldCursor, newCursor), Math.max(oldCursor, newCursor)),
245
- isFirst: oldCursor === 0 || newCursor === 0,
246
- isLast: oldCursor === this.array.length - 1 || newCursor === this.array.length - 1,
247
- cursor: `${newCursor}`
374
+ items: slicedItemEntriesArray,
375
+ isFirst: clampedOldCursor === 0 || newCursor === 0,
376
+ isLast: clampedOldCursor === sortedArray.length || newCursor === sortedArray.length,
377
+ cursor: `before-${newCursor}`
248
378
  };
249
379
  }
250
380
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/paginated-lists.tsx"],"sourcesContent":["import { range } from \"./arrays\";\nimport { StackAssertionError } from \"./errors\";\n\ntype QueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> =\n & {\n filter: Filter,\n orderBy: OrderBy,\n limit: number,\n /**\n * Whether the limit should be treated as an exact value, or an approximate value.\n *\n * If set to 'exact', less items will only be returned if the list item is the first or last item.\n *\n * If set to 'at-least' or 'approximate', the implementation may decide to return more items than the limit requested if doing so comes at no (or negligible) extra cost.\n *\n * If set to 'at-most' or 'approximate', the implementation may decide to return less items than the limit requested if requesting more items would come at a non-negligible extra cost. In this case, if limit > 0, the implementation must still make progress towards the end of the list and the returned cursor must be different from the one passed in.\n *\n * Defaults to 'exact'.\n */\n limitPrecision: 'exact' | 'at-least' | 'at-most' | 'approximate',\n }\n & ([Type] extends [never] ? unknown\n : [Type] extends ['next'] ? { after: Cursor }\n : [Type] extends ['prev'] ? { before: Cursor }\n : { cursor: Cursor });\n\ntype ImplQueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> = QueryOptions<Type, Cursor, Filter, OrderBy> & { limitPrecision: 'approximate' }\n\ntype QueryResult<Item, Cursor> = { items: { item: Item, itemCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\ntype ImplQueryResult<Item, Cursor> = { items: { item: Item, itemCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\nexport abstract class PaginatedList<\n Item,\n Cursor extends string,\n Filter extends unknown,\n OrderBy extends unknown,\n> {\n // Abstract methods\n\n protected abstract _getFirstCursor(): Cursor;\n protected abstract _getLastCursor(): Cursor;\n protected abstract _compare(orderBy: OrderBy, a: Item, b: Item): number;\n protected abstract _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<ImplQueryResult<Item, Cursor>>;\n\n // Implementations\n public getFirstCursor(): Cursor { return this._getFirstCursor(); }\n public getLastCursor(): Cursor { return this._getLastCursor(); }\n public compare(orderBy: OrderBy, a: Item, b: Item): number { return this._compare(orderBy, a, b); }\n\n async nextOrPrev(type: 'next' | 'prev', options: QueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n let result: { item: Item, itemCursor: Cursor }[] = [];\n let includesFirst = false;\n let includesLast = false;\n let cursor = options.cursor;\n let limitRemaining = options.limit;\n while (limitRemaining > 0 || (type === \"next\" && includesLast) || (type === \"prev\" && includesFirst)) {\n const iterationRes = await this._nextOrPrev(type, {\n cursor,\n limit: options.limit,\n limitPrecision: \"approximate\",\n filter: options.filter,\n orderBy: options.orderBy,\n });\n result[type === \"next\" ? \"push\" : \"unshift\"](...iterationRes.items);\n limitRemaining -= iterationRes.items.length;\n includesFirst ||= iterationRes.isFirst;\n includesLast ||= iterationRes.isLast;\n cursor = iterationRes.cursor;\n if ([\"approximate\", \"at-most\"].includes(options.limitPrecision)) break;\n }\n\n // Assert that the result is sorted\n for (let i = 1; i < result.length; i++) {\n if (this._compare(options.orderBy, result[i].item, result[i - 1].item) < 0) {\n throw new StackAssertionError(\"Paginated list result is not sorted; something is wrong with the implementation\", {\n i,\n options,\n result,\n });\n }\n }\n\n if ([\"exact\", \"at-most\"].includes(options.limitPrecision) && result.length > options.limit) {\n if (type === \"next\") {\n result = result.slice(0, options.limit);\n includesLast = false;\n if (options.limit > 0) cursor = result[result.length - 1].itemCursor;\n } else {\n result = result.slice(result.length - options.limit);\n includesFirst = false;\n if (options.limit > 0) cursor = result[0].itemCursor;\n }\n }\n return { items: result, isFirst: includesFirst, isLast: includesLast, cursor };\n }\n public async next({ after, ...rest }: QueryOptions<'next', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"next\", {\n ...rest,\n cursor: after,\n });\n }\n public async prev({ before, ...rest }: QueryOptions<'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"prev\", {\n ...rest,\n cursor: before,\n });\n }\n\n // Utility methods below\n\n flatMap<Item2, Cursor2 extends string, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (itemEntry: { item: Item, itemCursor: Cursor }, filter: Filter2, orderBy: OrderBy2) => { item: Item2, itemCursor: Cursor2 }[],\n compare: (orderBy: OrderBy2, a: Item2, b: Item2) => number,\n newCursorFromOldCursor: (cursor: Cursor) => Cursor2,\n oldCursorFromNewCursor: (cursor: Cursor2) => Cursor,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy2, limit: number }) => number,\n }): PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n const that = this;\n class FlatMapPaginatedList extends PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n override _getFirstCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getFirstCursor()); }\n override _getLastCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getLastCursor()); }\n\n override _compare(orderBy: OrderBy2, a: Item2, b: Item2): number {\n return options.compare(orderBy, a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', Cursor2, Filter2, OrderBy2>) {\n const estimatedItems = options.estimateItemsToFetch({ limit, filter, orderBy });\n const original = await that.nextOrPrev(type, {\n limit: estimatedItems,\n limitPrecision: \"approximate\",\n cursor: options.oldCursorFromNewCursor(cursor),\n filter: options.oldFilterFromNewFilter(filter),\n orderBy: options.oldOrderByFromNewOrderBy(orderBy),\n });\n const mapped = original.items.flatMap(itemEntry => options.itemMapper(\n itemEntry,\n filter,\n orderBy,\n ));\n return {\n items: mapped,\n isFirst: original.isFirst,\n isLast: original.isLast,\n cursor: options.newCursorFromOldCursor(original.cursor),\n };\n }\n }\n return new FlatMapPaginatedList();\n }\n\n map<Item2, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (item: Item) => Item2,\n oldItemFromNewItem: (item: Item2) => Item,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n }): PaginatedList<Item2, Cursor, Filter2, OrderBy2> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => {\n return [{ item: options.itemMapper(itemEntry.item), itemCursor: itemEntry.itemCursor }];\n },\n compare: (orderBy, a, b) => this.compare(options.oldOrderByFromNewOrderBy(orderBy), options.oldItemFromNewItem(a), options.oldItemFromNewItem(b)),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => options.oldOrderByFromNewOrderBy(orderBy),\n estimateItemsToFetch: (options) => options.limit,\n });\n }\n\n filter<Filter2 extends unknown>(options: {\n filter: (item: Item, filter: Filter2) => boolean,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter2, OrderBy> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => (options.filter(itemEntry.item, filter) ? [itemEntry] : []),\n compare: (orderBy, a, b) => this.compare(orderBy, a, b),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => orderBy,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n addFilter<AddedFilter extends unknown>(options: {\n filter: (item: Item, filter: Filter & AddedFilter) => boolean,\n estimateItemsToFetch: (options: { filter: Filter & AddedFilter, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter & AddedFilter, OrderBy> {\n return this.filter({\n filter: (item, filter) => options.filter(item, filter),\n oldFilterFromNewFilter: (filter) => filter,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n static merge<\n Item,\n Filter extends unknown,\n OrderBy extends unknown,\n >(\n ...lists: PaginatedList<Item, any, Filter, OrderBy>[]\n ): PaginatedList<Item, string, Filter, OrderBy> {\n class MergePaginatedList extends PaginatedList<Item, string, Filter, OrderBy> {\n override _getFirstCursor() { return JSON.stringify(lists.map(list => list.getFirstCursor())); }\n override _getLastCursor() { return JSON.stringify(lists.map(list => list.getLastCursor())); }\n override _compare(orderBy: OrderBy, a: Item, b: Item): number {\n const listsResults = lists.map(list => list.compare(orderBy, a, b));\n if (!listsResults.every(result => result === listsResults[0])) {\n throw new StackAssertionError(\"Lists have different compare results; make sure that they use the same compare function\", { lists, listsResults });\n }\n return listsResults[0];\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', \"first\" | \"last\" | `[${string}]`, Filter, OrderBy>) {\n const cursors = JSON.parse(cursor);\n const fetchedLists = await Promise.all(lists.map(async (list, i) => {\n return await list.nextOrPrev(type, {\n limit,\n filter,\n orderBy,\n cursor: cursors[i],\n limitPrecision: \"at-least\",\n });\n }));\n const combinedItems = fetchedLists.flatMap((list, i) => list.items.map((itemEntry) => ({ itemEntry, listIndex: i })));\n const sortedItems = [...combinedItems].sort((a, b) => this._compare(orderBy, a.itemEntry.item, b.itemEntry.item));\n const lastCursorForEachList = sortedItems.reduce((acc, item) => {\n acc[item.listIndex] = item.itemEntry.itemCursor;\n return acc;\n }, range(lists.length).map((i) => cursors[i]));\n return {\n items: sortedItems.map((item) => item.itemEntry),\n isFirst: sortedItems.every((item) => item.listIndex === 0),\n isLast: sortedItems.every((item) => item.listIndex === lists.length - 1),\n cursor: JSON.stringify(lastCursorForEachList),\n };\n }\n }\n return new MergePaginatedList();\n }\n\n static empty() {\n class EmptyPaginatedList extends PaginatedList<never, \"first\" | \"last\", any, any> {\n override _getFirstCursor() { return \"first\" as const; }\n override _getLastCursor() { return \"last\" as const; }\n override _compare(orderBy: any, a: any, b: any): number {\n return 0;\n }\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', string, any, any>) {\n return { items: [], isFirst: true, isLast: true, cursor: \"first\" as const };\n }\n }\n return new EmptyPaginatedList();\n }\n}\n\nexport class ArrayPaginatedList<Item> extends PaginatedList<Item, `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number> {\n constructor(private readonly array: Item[]) {\n super();\n }\n\n override _getFirstCursor() { return \"0\" as const; }\n override _getLastCursor() { return `${this.array.length - 1}` as const; }\n override _compare(orderBy: (a: Item, b: Item) => number, a: Item, b: Item): number {\n return orderBy(a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', `${number}`, (item: Item) => boolean, (a: Item, b: Item) => number>) {\n const filteredArray = this.array.filter(options.filter);\n const sortedArray = [...filteredArray].sort((a, b) => this._compare(options.orderBy, a, b));\n const itemEntriesArray = sortedArray.map((item, index) => ({ item, itemCursor: `${index}` as const }));\n const oldCursor = Number(options.cursor);\n const newCursor = Math.max(0, Math.min(this.array.length - 1, oldCursor + (type === \"next\" ? 1 : -1) * options.limit));\n return {\n items: itemEntriesArray.slice(Math.min(oldCursor, newCursor), Math.max(oldCursor, newCursor)),\n isFirst: oldCursor === 0 || newCursor === 0,\n isLast: oldCursor === this.array.length - 1 || newCursor === this.array.length - 1,\n cursor: `${newCursor}` as const,\n };\n }\n}\n"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../../src/utils/paginated-lists.tsx"],"sourcesContent":["import { StackAssertionError } from \"./errors\";\n\ntype QueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> =\n & {\n filter: Filter,\n orderBy: OrderBy,\n limit: number,\n /**\n * Whether the limit should be treated as an exact value, or an approximate value.\n *\n * If set to 'exact', less items will only be returned if the list item is the first or last item.\n *\n * If set to 'at-least' or 'approximate', the implementation may decide to return more items than the limit requested if doing so comes at no (or negligible) extra cost.\n *\n * If set to 'at-most' or 'approximate', the implementation may decide to return less items than the limit requested if requesting more items would come at a non-negligible extra cost. In this case, if limit > 0, the implementation must still make progress towards the end of the list and the returned cursor must be different from the one passed in.\n *\n * Defaults to 'exact'.\n */\n limitPrecision: 'exact' | 'at-least' | 'at-most' | 'approximate',\n }\n & ([Type] extends [never] ? unknown\n : [Type] extends ['next'] ? { after: Cursor }\n : [Type] extends ['prev'] ? { before: Cursor }\n : { cursor: Cursor });\n\ntype ImplQueryOptions<Type extends 'next' | 'prev', Cursor, Filter, OrderBy> = QueryOptions<Type, Cursor, Filter, OrderBy> & { limitPrecision: 'approximate' }\n\ntype QueryResult<Item, Cursor> = { items: { item: Item, prevCursor: Cursor, nextCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\ntype ImplQueryResult<Item, Cursor> = { items: { item: Item, prevCursor: Cursor, nextCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }\n\n/**\n * Abstract base class for cursor-based pagination over any ordered data source.\n *\n * Subclasses implement `_nextOrPrev` to fetch items in one direction. This class handles\n * limit enforcement, sorting validation, and provides `map`, `filter`, `flatMap`, and `merge` utilities.\n *\n * @template Item - The type of items in the list\n * @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.\n * @template Filter - Query filter type\n * @template OrderBy - Sort order specification type\n *\n * @example\n * ```ts\n * // Basic usage: paginate through users\n * const users = new MyUserList();\n * const first10 = await users.next({ after: users.getFirstCursor(), limit: 10, filter: {}, orderBy: 'name', limitPrecision: 'exact' });\n * // first10 = { items: [...], isFirst: true, isLast: false, cursor: \"cursor-after-item-10\" }\n *\n * const next10 = await users.next({ after: first10.cursor, limit: 10, filter: {}, orderBy: 'name', limitPrecision: 'exact' });\n * // Continues from where we left off\n * ```\n */\nexport abstract class PaginatedList<\n Item,\n Cursor extends string,\n Filter extends unknown,\n OrderBy extends unknown,\n> {\n // Abstract methods\n\n protected abstract _getFirstCursor(): Cursor;\n protected abstract _getLastCursor(): Cursor;\n protected abstract _compare(orderBy: OrderBy, a: Item, b: Item): number;\n protected abstract _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<ImplQueryResult<Item, Cursor>>;\n\n // Implementations\n\n /** Returns the cursor pointing to the start of the list (before any items). */\n public getFirstCursor(): Cursor { return this._getFirstCursor(); }\n\n /** Returns the cursor pointing to the end of the list (after all items). */\n public getLastCursor(): Cursor { return this._getLastCursor(); }\n\n /** Compares two items according to the given orderBy. Returns negative if a < b, 0 if equal, positive if a > b. */\n public compare(orderBy: OrderBy, a: Item, b: Item): number { return this._compare(orderBy, a, b); }\n\n /**\n * Fetches items moving forward ('next') or backward ('prev') from the given cursor.\n *\n * Respects `limitPrecision`: 'exact' guarantees the exact limit, 'at-least'/'at-most'/'approximate'\n * allow flexibility for performance. Returns items, boundary flags, and a new cursor.\n *\n * @example\n * ```ts\n * // Get 5 items after the start\n * const result = await list.nextOrPrev('next', { cursor: list.getFirstCursor(), limit: 5, filter: {}, orderBy: 'asc', limitPrecision: 'exact' });\n * // result.items.length === 5 (or less if list has fewer items)\n * // result.isFirst === true (started at first cursor)\n * // result.isLast === true if we got all remaining items\n *\n * // Continue from where we left off\n * const more = await list.nextOrPrev('next', { cursor: result.cursor, limit: 5, ... });\n * ```\n */\n async nextOrPrev(type: 'next' | 'prev', options: QueryOptions<'next' | 'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n let result: { item: Item, prevCursor: Cursor, nextCursor: Cursor }[] = [];\n let includesFirst = false;\n let includesLast = false;\n let cursor = options.cursor;\n let limitRemaining = options.limit;\n while (limitRemaining > 0 && (type !== \"next\" || !includesLast) && (type !== \"prev\" || !includesFirst)) {\n const iterationRes = await this._nextOrPrev(type, {\n cursor,\n limit: options.limit,\n limitPrecision: \"approximate\",\n filter: options.filter,\n orderBy: options.orderBy,\n });\n result[type === \"next\" ? \"push\" : \"unshift\"](...iterationRes.items);\n limitRemaining -= iterationRes.items.length;\n includesFirst ||= iterationRes.isFirst;\n includesLast ||= iterationRes.isLast;\n cursor = iterationRes.cursor;\n if ([\"approximate\", \"at-most\"].includes(options.limitPrecision)) break;\n }\n\n // Assert that the result is sorted\n for (let i = 1; i < result.length; i++) {\n if (this._compare(options.orderBy, result[i].item, result[i - 1].item) < 0) {\n throw new StackAssertionError(\"Paginated list result is not sorted; something is wrong with the implementation\", {\n i,\n options,\n result,\n });\n }\n }\n\n if ([\"exact\", \"at-most\"].includes(options.limitPrecision) && result.length > options.limit) {\n if (type === \"next\") {\n result = result.slice(0, options.limit);\n includesLast = false;\n if (options.limit > 0) cursor = result[result.length - 1].nextCursor;\n } else {\n result = result.slice(result.length - options.limit);\n includesFirst = false;\n if (options.limit > 0) cursor = result[0].prevCursor;\n }\n }\n return { items: result, isFirst: includesFirst, isLast: includesLast, cursor };\n }\n /** Fetches items after the given cursor (forward pagination). */\n public async next({ after, ...rest }: QueryOptions<'next', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"next\", {\n ...rest,\n cursor: after,\n });\n }\n\n /** Fetches items before the given cursor (backward pagination). */\n public async prev({ before, ...rest }: QueryOptions<'prev', Cursor, Filter, OrderBy>): Promise<QueryResult<Item, Cursor>> {\n return await this.nextOrPrev(\"prev\", {\n ...rest,\n cursor: before,\n });\n }\n\n // Utility methods below\n\n /**\n * Transforms this list by mapping each item to zero or more new items.\n *\n * Note that the sort order must be preserved after the operation; the flat-mapped list will not be sorted automatically.\n *\n * @param itemMapper - Maps each item (with its cursor) to an array of new items\n * @param compare - Comparison function for the new item type\n * @param newCursorFromOldCursor/oldCursorFromNewCursor - Cursor conversion functions\n * @param estimateItemsToFetch - Estimates how many source items to fetch for a given limit\n *\n * @example\n * ```ts\n * // Expand orders into line items (1 order -> N line items)\n * const lineItems = ordersList.flatMap({\n * itemMapper: ({ item: order }) => order.lineItems.map((li, i) => ({ item: li, prevCursor: `${order.id}-${i}`, nextCursor: `${order.id}-${i + 1}` })),\n * compare: (_, a, b) => a.createdAt - b.createdAt,\n * estimateItemsToFetch: ({ limit }) => Math.ceil(limit / 3), // avg 3 items per order\n * // ... cursor converters\n * });\n * ```\n */\n flatMap<Item2, Cursor2 extends string, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (itemEntry: { item: Item, prevCursor: Cursor, nextCursor: Cursor }, filter: Filter2, orderBy: OrderBy2) => { item: Item2, prevCursor: Cursor2, nextCursor: Cursor2 }[],\n compare: (orderBy: OrderBy2, a: Item2, b: Item2) => number,\n newCursorFromOldCursor: (cursor: Cursor) => Cursor2,\n oldCursorFromNewCursor: (cursor: Cursor2) => Cursor,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy2, limit: number }) => number,\n }): PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n const that = this;\n class FlatMapPaginatedList extends PaginatedList<Item2, Cursor2, Filter2, OrderBy2> {\n override _getFirstCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getFirstCursor()); }\n override _getLastCursor(): Cursor2 { return options.newCursorFromOldCursor(that.getLastCursor()); }\n\n override _compare(orderBy: OrderBy2, a: Item2, b: Item2): number {\n return options.compare(orderBy, a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', Cursor2, Filter2, OrderBy2>) {\n const estimatedItems = options.estimateItemsToFetch({ limit, filter, orderBy });\n const original = await that.nextOrPrev(type, {\n limit: estimatedItems,\n limitPrecision: \"approximate\",\n cursor: options.oldCursorFromNewCursor(cursor),\n filter: options.oldFilterFromNewFilter(filter),\n orderBy: options.oldOrderByFromNewOrderBy(orderBy),\n });\n const mapped = original.items.flatMap(itemEntry => options.itemMapper(\n itemEntry,\n filter,\n orderBy,\n ));\n return {\n items: mapped,\n isFirst: original.isFirst,\n isLast: original.isLast,\n cursor: options.newCursorFromOldCursor(original.cursor),\n };\n }\n }\n return new FlatMapPaginatedList();\n }\n\n /**\n * Transforms each item in the list. Requires a reverse mapper for comparison delegation.\n *\n * @param itemMapper - Transforms each item\n * @param oldItemFromNewItem - Reverse-maps new items back to old items (for comparison)\n *\n * @example\n * ```ts\n * // Convert User objects to UserDTO\n * const userDtos = usersList.map({\n * itemMapper: (user) => ({ id: user.id, displayName: user.name }),\n * oldItemFromNewItem: (dto) => fullUsers.get(dto.id)!, // for comparison\n * oldFilterFromNewFilter: (f) => f,\n * oldOrderByFromNewOrderBy: (o) => o,\n * });\n * ```\n */\n map<Item2, Filter2 extends unknown, OrderBy2 extends unknown>(options: {\n itemMapper: (item: Item) => Item2,\n oldItemFromNewItem: (item: Item2) => Item,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n oldOrderByFromNewOrderBy: (orderBy: OrderBy2) => OrderBy,\n }): PaginatedList<Item2, Cursor, Filter2, OrderBy2> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => {\n return [{ item: options.itemMapper(itemEntry.item), prevCursor: itemEntry.prevCursor, nextCursor: itemEntry.nextCursor }];\n },\n compare: (orderBy, a, b) => this.compare(options.oldOrderByFromNewOrderBy(orderBy), options.oldItemFromNewItem(a), options.oldItemFromNewItem(b)),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => options.oldOrderByFromNewOrderBy(orderBy),\n estimateItemsToFetch: (options) => options.limit,\n });\n }\n\n /**\n * Filters items in the list. Requires an estimate function since filtering may reduce output.\n *\n * @param filter - Predicate to include/exclude items\n * @param estimateItemsToFetch - Estimates how many source items to fetch (accounts for filter selectivity)\n *\n * @example\n * ```ts\n * // Filter to only active users\n * const activeUsers = usersList.filter({\n * filter: (user, filterOpts) => user.isActive && user.role === filterOpts.role,\n * oldFilterFromNewFilter: (f) => ({}), // original list has no filter\n * estimateItemsToFetch: ({ limit }) => limit * 2, // expect ~50% active\n * });\n * ```\n */\n filter<Filter2 extends unknown>(options: {\n filter: (item: Item, filter: Filter2) => boolean,\n oldFilterFromNewFilter: (filter: Filter2) => Filter,\n estimateItemsToFetch: (options: { filter: Filter2, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter2, OrderBy> {\n return this.flatMap({\n itemMapper: (itemEntry, filter, orderBy) => (options.filter(itemEntry.item, filter) ? [itemEntry] : []),\n compare: (orderBy, a, b) => this.compare(orderBy, a, b),\n newCursorFromOldCursor: (cursor) => cursor,\n oldCursorFromNewCursor: (cursor) => cursor,\n oldFilterFromNewFilter: (filter) => options.oldFilterFromNewFilter(filter),\n oldOrderByFromNewOrderBy: (orderBy) => orderBy,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n /**\n * Adds an additional filter constraint while preserving the original filter type.\n * Shorthand for `filter()` that intersects Filter with AddedFilter.\n *\n * @example\n * ```ts\n * // Add a \"verified\" filter on top of existing filters\n * const verifiedUsers = usersList.addFilter({\n * filter: (user, f) => user.emailVerified,\n * estimateItemsToFetch: ({ limit }) => limit * 2, // ~50% are verified\n * });\n * // verifiedUsers filter type is Filter\n * ```\n */\n addFilter<AddedFilter extends unknown>(options: {\n filter: (item: Item, filter: Filter & AddedFilter) => boolean,\n estimateItemsToFetch: (options: { filter: Filter & AddedFilter, orderBy: OrderBy, limit: number }) => number,\n }): PaginatedList<Item, Cursor, Filter & AddedFilter, OrderBy> {\n return this.filter({\n filter: (item, filter) => options.filter(item, filter),\n oldFilterFromNewFilter: (filter) => filter,\n estimateItemsToFetch: (o) => options.estimateItemsToFetch(o),\n });\n }\n\n /**\n * Merges multiple paginated lists into one, interleaving items by sort order.\n * All lists must use the same compare function.\n *\n * The merged cursor is a JSON-encoded array of individual list cursors.\n *\n * @example\n * ```ts\n * // Merge users from multiple sources into a unified feed\n * const allUsers = PaginatedList.merge(internalUsers, externalUsers, partnerUsers);\n * const page = await allUsers.next({ after: allUsers.getFirstCursor(), limit: 20, ... });\n * // page.items contains interleaved items from all sources, sorted by orderBy\n * ```\n */\n static merge<\n Item,\n Filter extends unknown,\n OrderBy extends unknown,\n >(\n ...lists: PaginatedList<Item, any, Filter, OrderBy>[]\n ): PaginatedList<Item, string, Filter, OrderBy> {\n class MergePaginatedList extends PaginatedList<Item, string, Filter, OrderBy> {\n override _getFirstCursor() { return JSON.stringify(lists.map(list => list.getFirstCursor())); }\n override _getLastCursor() { return JSON.stringify(lists.map(list => list.getLastCursor())); }\n override _compare(orderBy: OrderBy, a: Item, b: Item): number {\n const listsResults = lists.map(list => list.compare(orderBy, a, b));\n if (!listsResults.every(result => result === listsResults[0])) {\n throw new StackAssertionError(\"Lists have different compare results; make sure that they use the same compare function\", { lists, listsResults, orderBy, a, b });\n }\n return listsResults[0];\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', \"first\" | \"last\" | `[${string}]`, Filter, OrderBy>) {\n const cursors = JSON.parse(cursor);\n const fetchedLists = await Promise.all(lists.map(async (list, i) => {\n return await list.nextOrPrev(type, {\n limit,\n filter,\n orderBy,\n cursor: cursors[i],\n limitPrecision: \"at-least\",\n });\n }));\n const combinedItems = fetchedLists.flatMap((list, i) => list.items.map((itemEntry) => ({ itemEntry, listIndex: i })));\n const sortedItems = [...combinedItems].sort((a, b) => this._compare(orderBy, a.itemEntry.item, b.itemEntry.item));\n\n const sortedItemsWithMergedCursors: { item: Item, prevCursor: string, nextCursor: string }[] = [];\n const curCursors = [...cursors];\n // When going backward, we iterate in reverse order to correctly build cursors,\n // but we need to return items in ascending order\n for (const item of (type === 'next' ? sortedItems : sortedItems.reverse())) {\n const lastCursors = [...curCursors];\n curCursors[item.listIndex] = type === 'next' ? item.itemEntry.nextCursor : item.itemEntry.prevCursor;\n sortedItemsWithMergedCursors.push({\n item: item.itemEntry.item,\n prevCursor: type === 'next' ? JSON.stringify(lastCursors) : JSON.stringify(curCursors),\n nextCursor: type === 'next' ? JSON.stringify(curCursors) : JSON.stringify(lastCursors),\n });\n }\n\n // When going backward, reverse the result to maintain ascending order\n if (type === 'prev') {\n sortedItemsWithMergedCursors.reverse();\n }\n\n return {\n items: sortedItemsWithMergedCursors,\n isFirst: fetchedLists.every((list) => list.isFirst),\n isLast: fetchedLists.every((list) => list.isLast),\n cursor: JSON.stringify(curCursors),\n };\n }\n }\n return new MergePaginatedList();\n }\n\n /**\n * Returns an empty paginated list that always returns no items.\n *\n * @example\n * ```ts\n * const empty = PaginatedList.empty();\n * const result = await empty.next({ after: empty.getFirstCursor(), limit: 10, ... });\n * // result = { items: [], isFirst: true, isLast: true, cursor: \"first\" }\n * ```\n */\n static empty() {\n class EmptyPaginatedList extends PaginatedList<never, \"first\" | \"last\", any, any> {\n override _getFirstCursor() { return \"first\" as const; }\n override _getLastCursor() { return \"last\" as const; }\n override _compare(orderBy: any, a: any, b: any): number {\n return 0;\n }\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', string, any, any>) {\n return { items: [], isFirst: true, isLast: true, cursor: \"first\" as const };\n }\n }\n return new EmptyPaginatedList();\n }\n}\n\n/**\n * A simple in-memory paginated list backed by an array.\n *\n * Filter is a predicate function, OrderBy is a comparator function.\n * Cursors are in the format \"before-{index}\" representing the position before that index.\n *\n * Note: This implementation re-filters and re-sorts the entire array on each query,\n * so it's only suitable for small datasets.\n *\n * @example\n * ```ts\n * const numbers = new ArrayPaginatedList([5, 2, 8, 1, 9, 3]);\n * const page = await numbers.next({\n * after: \"before-0\",\n * limit: 3,\n * filter: (n) => n > 2,\n * orderBy: (a, b) => a - b,\n * limitPrecision: 'exact',\n * });\n * // page.items = [{ item: 3, prevCursor: \"before-0\", nextCursor: \"before-1\" }, { item: 5, prevCursor: \"before-1\", nextCursor: \"before-2\" }, ...]\n * ```\n */\nexport class ArrayPaginatedList<Item> extends PaginatedList<Item, `before-${number}`, (item: Item) => boolean, (a: Item, b: Item) => number> {\n constructor(private readonly array: Item[]) {\n super();\n }\n\n override _getFirstCursor() { return \"before-0\" as const; }\n override _getLastCursor() { return `before-${this.array.length}` as const; }\n override _compare(orderBy: (a: Item, b: Item) => number, a: Item, b: Item): number {\n return orderBy(a, b);\n }\n\n override async _nextOrPrev(type: 'next' | 'prev', options: ImplQueryOptions<'next' | 'prev', `before-${number}`, (item: Item) => boolean, (a: Item, b: Item) => number>) {\n // First filter and sort the entire array, THEN slice and assign cursors\n // This ensures pagination happens in the sorted/filtered result space\n const filteredArray = this.array.filter(options.filter);\n const sortedArray = [...filteredArray].sort((a, b) => this._compare(options.orderBy, a, b));\n\n // Assign cursors based on position in sorted/filtered result\n const itemEntriesArray = sortedArray.map((item, index) => ({\n item,\n prevCursor: `before-${index}` as `before-${number}`,\n nextCursor: `before-${index + 1}` as `before-${number}`,\n }));\n\n // Calculate slice boundaries based on cursor position in the sorted result\n const oldCursor = Number(options.cursor.replace(\"before-\", \"\"));\n const clampedOldCursor = Math.max(0, Math.min(sortedArray.length, oldCursor));\n const newCursor = Math.max(0, Math.min(sortedArray.length, clampedOldCursor + (type === \"next\" ? 1 : -1) * options.limit));\n\n const slicedItemEntriesArray = itemEntriesArray.slice(Math.min(clampedOldCursor, newCursor), Math.max(clampedOldCursor, newCursor));\n\n return {\n items: slicedItemEntriesArray,\n isFirst: clampedOldCursor === 0 || newCursor === 0,\n isLast: clampedOldCursor === sortedArray.length || newCursor === sortedArray.length,\n cursor: `before-${newCursor}` as const,\n };\n }\n}\n\n"],"mappings":"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","names":["options"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }