@zapier/zapier-sdk 0.13.6 → 0.13.8

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 (155) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/api/client.d.ts.map +1 -1
  3. package/dist/api/client.js +5 -5
  4. package/dist/api/client.test.d.ts +2 -0
  5. package/dist/api/client.test.d.ts.map +1 -0
  6. package/dist/api/client.test.js +80 -0
  7. package/dist/api/index.d.ts +1 -0
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +3 -1
  10. package/dist/api/schemas.d.ts +20 -20
  11. package/dist/api/types.d.ts +2 -0
  12. package/dist/api/types.d.ts.map +1 -1
  13. package/dist/auth.d.ts +3 -0
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.test.d.ts +2 -0
  16. package/dist/auth.test.d.ts.map +1 -0
  17. package/dist/auth.test.js +102 -0
  18. package/dist/constants.d.ts +4 -4
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +4 -4
  21. package/dist/index.cjs +89 -21
  22. package/dist/index.d.mts +21 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/index.mjs +88 -22
  27. package/dist/plugins/api/index.d.ts.map +1 -1
  28. package/dist/plugins/api/index.js +4 -1
  29. package/dist/plugins/eventEmission/index.d.ts +2 -0
  30. package/dist/plugins/eventEmission/index.d.ts.map +1 -1
  31. package/dist/plugins/eventEmission/index.js +35 -9
  32. package/dist/plugins/eventEmission/index.test.js +100 -0
  33. package/dist/schemas/Action.d.ts +2 -2
  34. package/dist/schemas/Auth.d.ts +4 -4
  35. package/dist/schemas/Field.d.ts +10 -10
  36. package/dist/sdk.test.js +121 -1
  37. package/dist/types/sdk.d.ts +3 -0
  38. package/dist/types/sdk.d.ts.map +1 -1
  39. package/dist/utils/url-utils.d.ts +19 -0
  40. package/dist/utils/url-utils.d.ts.map +1 -0
  41. package/dist/utils/url-utils.js +62 -0
  42. package/dist/utils/url-utils.test.d.ts +2 -0
  43. package/dist/utils/url-utils.test.d.ts.map +1 -0
  44. package/dist/utils/url-utils.test.js +103 -0
  45. package/package.json +8 -3
  46. package/src/api/auth.ts +0 -28
  47. package/src/api/client.ts +0 -491
  48. package/src/api/debug.test.ts +0 -76
  49. package/src/api/debug.ts +0 -154
  50. package/src/api/index.ts +0 -90
  51. package/src/api/polling.test.ts +0 -405
  52. package/src/api/polling.ts +0 -253
  53. package/src/api/schemas.ts +0 -465
  54. package/src/api/types.ts +0 -152
  55. package/src/auth.ts +0 -72
  56. package/src/constants.ts +0 -16
  57. package/src/index.ts +0 -111
  58. package/src/plugins/api/index.ts +0 -43
  59. package/src/plugins/apps/index.ts +0 -203
  60. package/src/plugins/apps/schemas.ts +0 -64
  61. package/src/plugins/eventEmission/builders.ts +0 -115
  62. package/src/plugins/eventEmission/index.test.ts +0 -169
  63. package/src/plugins/eventEmission/index.ts +0 -294
  64. package/src/plugins/eventEmission/transport.test.ts +0 -214
  65. package/src/plugins/eventEmission/transport.ts +0 -135
  66. package/src/plugins/eventEmission/types.ts +0 -58
  67. package/src/plugins/eventEmission/utils.ts +0 -121
  68. package/src/plugins/fetch/index.ts +0 -83
  69. package/src/plugins/fetch/schemas.ts +0 -37
  70. package/src/plugins/findFirstAuthentication/index.test.ts +0 -209
  71. package/src/plugins/findFirstAuthentication/index.ts +0 -68
  72. package/src/plugins/findFirstAuthentication/schemas.ts +0 -47
  73. package/src/plugins/findUniqueAuthentication/index.test.ts +0 -197
  74. package/src/plugins/findUniqueAuthentication/index.ts +0 -77
  75. package/src/plugins/findUniqueAuthentication/schemas.ts +0 -49
  76. package/src/plugins/getAction/index.test.ts +0 -239
  77. package/src/plugins/getAction/index.ts +0 -75
  78. package/src/plugins/getAction/schemas.ts +0 -41
  79. package/src/plugins/getApp/index.test.ts +0 -181
  80. package/src/plugins/getApp/index.ts +0 -60
  81. package/src/plugins/getApp/schemas.ts +0 -33
  82. package/src/plugins/getAuthentication/index.test.ts +0 -294
  83. package/src/plugins/getAuthentication/index.ts +0 -95
  84. package/src/plugins/getAuthentication/schemas.ts +0 -38
  85. package/src/plugins/getProfile/index.ts +0 -60
  86. package/src/plugins/getProfile/schemas.ts +0 -24
  87. package/src/plugins/listActions/index.test.ts +0 -526
  88. package/src/plugins/listActions/index.ts +0 -132
  89. package/src/plugins/listActions/schemas.ts +0 -55
  90. package/src/plugins/listApps/index.test.ts +0 -378
  91. package/src/plugins/listApps/index.ts +0 -159
  92. package/src/plugins/listApps/schemas.ts +0 -41
  93. package/src/plugins/listAuthentications/index.test.ts +0 -739
  94. package/src/plugins/listAuthentications/index.ts +0 -152
  95. package/src/plugins/listAuthentications/schemas.ts +0 -77
  96. package/src/plugins/listInputFieldChoices/index.test.ts +0 -653
  97. package/src/plugins/listInputFieldChoices/index.ts +0 -173
  98. package/src/plugins/listInputFieldChoices/schemas.ts +0 -125
  99. package/src/plugins/listInputFields/index.test.ts +0 -439
  100. package/src/plugins/listInputFields/index.ts +0 -294
  101. package/src/plugins/listInputFields/schemas.ts +0 -68
  102. package/src/plugins/manifest/index.test.ts +0 -776
  103. package/src/plugins/manifest/index.ts +0 -461
  104. package/src/plugins/manifest/schemas.ts +0 -60
  105. package/src/plugins/registry/index.ts +0 -160
  106. package/src/plugins/request/index.test.ts +0 -333
  107. package/src/plugins/request/index.ts +0 -105
  108. package/src/plugins/request/schemas.ts +0 -69
  109. package/src/plugins/runAction/index.test.ts +0 -388
  110. package/src/plugins/runAction/index.ts +0 -215
  111. package/src/plugins/runAction/schemas.ts +0 -60
  112. package/src/resolvers/actionKey.ts +0 -37
  113. package/src/resolvers/actionType.ts +0 -34
  114. package/src/resolvers/appKey.ts +0 -7
  115. package/src/resolvers/authenticationId.ts +0 -54
  116. package/src/resolvers/index.ts +0 -11
  117. package/src/resolvers/inputFieldKey.ts +0 -70
  118. package/src/resolvers/inputs.ts +0 -69
  119. package/src/schemas/Action.ts +0 -52
  120. package/src/schemas/App.ts +0 -45
  121. package/src/schemas/Auth.ts +0 -59
  122. package/src/schemas/Field.ts +0 -169
  123. package/src/schemas/Run.ts +0 -40
  124. package/src/schemas/UserProfile.ts +0 -60
  125. package/src/sdk.test.ts +0 -212
  126. package/src/sdk.ts +0 -178
  127. package/src/types/domain.test.ts +0 -50
  128. package/src/types/domain.ts +0 -66
  129. package/src/types/errors.ts +0 -278
  130. package/src/types/events.ts +0 -43
  131. package/src/types/functions.ts +0 -28
  132. package/src/types/optional-zapier-sdk-cli-login.d.ts +0 -37
  133. package/src/types/plugin.ts +0 -125
  134. package/src/types/properties.ts +0 -80
  135. package/src/types/sdk.ts +0 -111
  136. package/src/types/telemetry-events.ts +0 -85
  137. package/src/utils/array-utils.test.ts +0 -131
  138. package/src/utils/array-utils.ts +0 -41
  139. package/src/utils/domain-utils.test.ts +0 -433
  140. package/src/utils/domain-utils.ts +0 -267
  141. package/src/utils/file-utils.test.ts +0 -73
  142. package/src/utils/file-utils.ts +0 -94
  143. package/src/utils/function-utils.test.ts +0 -141
  144. package/src/utils/function-utils.ts +0 -245
  145. package/src/utils/pagination-utils.test.ts +0 -620
  146. package/src/utils/pagination-utils.ts +0 -242
  147. package/src/utils/schema-utils.ts +0 -207
  148. package/src/utils/string-utils.test.ts +0 -45
  149. package/src/utils/string-utils.ts +0 -54
  150. package/src/utils/validation.test.ts +0 -51
  151. package/src/utils/validation.ts +0 -44
  152. package/tsconfig.build.json +0 -18
  153. package/tsconfig.json +0 -20
  154. package/tsconfig.tsbuildinfo +0 -1
  155. package/tsup.config.ts +0 -23
@@ -1,620 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { paginate, paginateMaxItems } from "./pagination-utils";
3
-
4
- // Test data set - simulating a large dataset with cursor-based pagination
5
- export const ALL_ITEMS = Array.from({ length: 25 }, (_, i) => ({
6
- id: i + 1,
7
- name: `Item ${i + 1}`,
8
- }));
9
-
10
- // Test page function that implements cursor-based pagination logic
11
- export async function listTestItems(options: {
12
- pageSize?: number;
13
- cursor?: string;
14
- filter?: string;
15
- maxItems?: number;
16
- }) {
17
- const { pageSize = 5, cursor, filter, maxItems } = options;
18
-
19
- // Cap pageSize at 10 to simulate real API limits
20
- let actualPageSize = Math.min(pageSize, 10);
21
-
22
- // If maxItems hint is provided, further limit the page size for optimization
23
- if (maxItems !== undefined && maxItems > 0) {
24
- actualPageSize = Math.min(actualPageSize, maxItems);
25
- }
26
-
27
- // Filter items if filter is provided
28
- let items = ALL_ITEMS;
29
- if (filter) {
30
- items = items.filter((item) =>
31
- item.name.toLowerCase().includes(filter.toLowerCase()),
32
- );
33
- }
34
-
35
- // Find starting position based on cursor
36
- let startIndex = 0;
37
- if (cursor) {
38
- // Cursor format: "cursor-{itemId}"
39
- const cursorId = parseInt(cursor.replace("cursor-", ""));
40
- startIndex = items.findIndex((item) => item.id > cursorId);
41
- if (startIndex === -1) {
42
- // No more items after cursor
43
- return { data: [], nextCursor: undefined };
44
- }
45
- }
46
-
47
- // Get page of items
48
- const pageItems = items.slice(startIndex, startIndex + actualPageSize);
49
-
50
- // Determine next cursor
51
- let nextCursor: string | undefined;
52
- if (startIndex + actualPageSize < items.length && pageItems.length > 0) {
53
- const lastItem = pageItems[pageItems.length - 1];
54
- nextCursor = `cursor-${lastItem.id}`;
55
- }
56
-
57
- return {
58
- data: pageItems,
59
- nextCursor,
60
- };
61
- }
62
-
63
- describe("paginate", () => {
64
- it("should paginate through multiple pages", async () => {
65
- const pageIterator = paginate(listTestItems, { pageSize: 3 });
66
-
67
- const pages = [];
68
- for await (const page of pageIterator) {
69
- pages.push(page);
70
- }
71
-
72
- // With 25 items and pageSize 3, should have 9 pages (8 full + 1 partial)
73
- expect(pages).toHaveLength(9);
74
-
75
- // Check first page
76
- expect(pages[0]).toEqual({
77
- data: ALL_ITEMS.slice(0, 3),
78
- nextCursor: "cursor-3",
79
- });
80
-
81
- // Check second page (cursor should be passed correctly)
82
- expect(pages[1]).toEqual({
83
- data: ALL_ITEMS.slice(3, 6),
84
- nextCursor: "cursor-6",
85
- });
86
-
87
- // Check last page (should have no nextCursor)
88
- expect(pages[8]).toEqual({
89
- data: ALL_ITEMS.slice(24, 25),
90
- nextCursor: undefined,
91
- });
92
-
93
- // Verify we got all 25 items across all pages
94
- const allItems = pages.flatMap((page) => page.data);
95
- expect(allItems).toHaveLength(25);
96
- });
97
-
98
- it("should cap pageSize at 10 when unbuffered", async () => {
99
- // Use a page size larger than 10, should get capped
100
- const pageIterator = paginateMaxItems(listTestItems, { pageSize: 50 });
101
-
102
- const pages = [];
103
- for await (const page of pageIterator) {
104
- pages.push(page);
105
- }
106
-
107
- // Should get 3 pages: 10+10+5=25 (pageSize capped at 10)
108
- expect(pages).toHaveLength(3);
109
- expect(pages[0].data).toHaveLength(10); // First page capped at 10
110
- expect(pages[1].data).toHaveLength(10); // Second page capped at 10
111
- expect(pages[2].data).toHaveLength(5); // Last page has remaining 5
112
- expect(pages[2].nextCursor).toBeUndefined(); // Last page has no nextCursor
113
- });
114
-
115
- it("should not cap pageSize when buffered", async () => {
116
- const pageIterator = paginate(listTestItems, { pageSize: 50 });
117
-
118
- for await (const page of pageIterator) {
119
- expect(page.data).toHaveLength(25);
120
- break;
121
- }
122
- });
123
-
124
- it("should not cap pageSize when buffered", async () => {
125
- const pageIterator = paginate(listTestItems, { pageSize: 15 });
126
-
127
- const pages = [];
128
- for await (const page of pageIterator) {
129
- pages.push(page);
130
- }
131
-
132
- expect(pages).toHaveLength(2);
133
- expect(pages[0].data).toHaveLength(15);
134
- expect(pages[1].data).toHaveLength(10);
135
- });
136
-
137
- it("should continue from pseudo cursor", async () => {
138
- const pageIterator = paginate(listTestItems, { pageSize: 15 });
139
-
140
- const pages = [];
141
- for await (const page of pageIterator) {
142
- pages.push(page);
143
- break;
144
- }
145
-
146
- const nextPageIterator = paginate(listTestItems, {
147
- pageSize: 15,
148
- cursor: pages[0].nextCursor,
149
- });
150
-
151
- for await (const page of nextPageIterator) {
152
- pages.push(page);
153
- }
154
-
155
- expect(pages).toHaveLength(2);
156
- expect(pages[0].data).toHaveLength(15);
157
- expect(pages[1].data).toHaveLength(10);
158
- });
159
-
160
- it("should handle filtered results", async () => {
161
- // Filter for items containing "1" (should match Item 1, Item 10-19, Item 21)
162
- const pageIterator = paginate(listTestItems, {
163
- pageSize: 4,
164
- filter: "1",
165
- });
166
-
167
- const pages = [];
168
- for await (const page of pageIterator) {
169
- pages.push(page);
170
- }
171
-
172
- // Should have multiple pages due to pageSize: 4
173
- expect(pages.length).toBeGreaterThan(1);
174
-
175
- // All items should contain "1"
176
- const allItems = pages.flatMap((page) => page.data);
177
- expect(allItems.every((item) => item.name.includes("1"))).toBe(true);
178
-
179
- // Should include items like Item 1, Item 10, Item 11, etc.
180
- expect(allItems).toContainEqual(ALL_ITEMS[0]); // Item 1
181
- expect(allItems).toContainEqual(ALL_ITEMS[9]); // Item 10
182
- expect(allItems).toContainEqual(ALL_ITEMS[20]); // Item 21
183
- });
184
-
185
- it("should handle empty results", async () => {
186
- // Filter that matches no items
187
- const pageIterator = paginate(listTestItems, {
188
- pageSize: 10,
189
- filter: "nonexistent",
190
- });
191
-
192
- const pages = [];
193
- for await (const page of pageIterator) {
194
- pages.push(page);
195
- }
196
-
197
- expect(pages).toHaveLength(1);
198
- expect(pages[0]).toEqual({
199
- data: [],
200
- nextCursor: undefined,
201
- });
202
- });
203
-
204
- it("should pass through errors from the page function", async () => {
205
- // Create a page function that throws an error
206
- const errorPageFunction = async () => {
207
- throw new Error("Page function failed");
208
- };
209
-
210
- const pageIterator = paginate(errorPageFunction, {});
211
-
212
- let reachedUnreachableCode: any = undefined;
213
-
214
- await expect(async () => {
215
- for await (const page of pageIterator) {
216
- // Should never reach here due to error
217
- reachedUnreachableCode = page;
218
- }
219
- }).rejects.toThrow("Page function failed");
220
-
221
- // Verify we never reached the unreachable code
222
- expect(reachedUnreachableCode).toBeUndefined();
223
- });
224
-
225
- it("should start pagination from provided cursor", async () => {
226
- // Test that pagination can start from a specific cursor position
227
- const pageIterator = paginate(listTestItems, {
228
- pageSize: 3,
229
- cursor: "cursor-5", // Start after item 5
230
- });
231
-
232
- const pages = [];
233
- for await (const page of pageIterator) {
234
- pages.push(page);
235
- if (pages.length >= 2) break; // Just test first 2 pages
236
- }
237
-
238
- expect(pages).toHaveLength(2);
239
-
240
- // First page should start after cursor-5 (from item 6)
241
- expect(pages[0].data[0]).toEqual(ALL_ITEMS[5]); // Item 6
242
-
243
- // Second page should continue properly
244
- expect(pages[1].data[0]).toEqual(ALL_ITEMS[8]); // Item 9
245
- });
246
-
247
- it("should allow breaking out of pagination early", async () => {
248
- // Test that users can break out of the iterator manually
249
- const pageIterator = paginate(listTestItems, { pageSize: 3 });
250
-
251
- const pages = [];
252
- for await (const page of pageIterator) {
253
- pages.push(page);
254
-
255
- // Break after getting 3 pages
256
- if (pages.length >= 3) {
257
- break;
258
- }
259
- }
260
-
261
- // Should have stopped at exactly 3 pages with 9 items (3+3+3=9)
262
- const allItems = pages.flatMap((page) => page.data);
263
- expect(allItems).toHaveLength(9);
264
- expect(pages).toHaveLength(3);
265
-
266
- // Should be first 9 items
267
- expect(allItems).toEqual(ALL_ITEMS.slice(0, 9));
268
- });
269
-
270
- describe("conditional types for optional options", () => {
271
- it("should work with function that takes no options", async () => {
272
- async function fetchWithNoOptions(_options?: {
273
- maxItems?: number;
274
- cursor?: string;
275
- }): Promise<{
276
- data: number[];
277
- nextCursor?: string;
278
- }> {
279
- return { data: [1, 2, 3] };
280
- }
281
-
282
- const pages: number[] = [];
283
- for await (const page of paginate(fetchWithNoOptions)) {
284
- pages.push(...page.data);
285
- }
286
-
287
- expect(pages).toEqual([1, 2, 3]);
288
- });
289
-
290
- it("should work with function that takes optional options", async () => {
291
- async function fetchWithOptionalOptions(options?: {
292
- limit?: number;
293
- cursor?: string;
294
- maxItems?: number;
295
- }): Promise<{ data: string[]; nextCursor?: string }> {
296
- return { data: [`page-${options?.cursor || "1"}`] };
297
- }
298
-
299
- const pages: string[] = [];
300
- for await (const page of paginate(fetchWithOptionalOptions, {
301
- limit: 5,
302
- })) {
303
- pages.push(...page.data);
304
- }
305
-
306
- expect(pages).toEqual(["page-1"]);
307
- });
308
-
309
- it("should work with function that takes only cursor", async () => {
310
- async function fetchWithOnlyCursor(_options: {
311
- cursor?: string;
312
- maxItems?: number;
313
- }): Promise<{ data: boolean[]; nextCursor?: string }> {
314
- return { data: [true] };
315
- }
316
-
317
- const pages: boolean[] = [];
318
- for await (const page of paginate(fetchWithOnlyCursor, {
319
- cursor: "start",
320
- })) {
321
- pages.push(...page.data);
322
- }
323
-
324
- expect(pages).toEqual([true]);
325
- });
326
-
327
- it("should work when baseOptions is omitted entirely", async () => {
328
- async function fetchSimple(_options?: {
329
- cursor?: string;
330
- maxItems?: number;
331
- }): Promise<{
332
- data: string[];
333
- nextCursor?: string;
334
- }> {
335
- return { data: ["simple"] };
336
- }
337
-
338
- const pages: string[] = [];
339
- for await (const page of paginate(fetchSimple)) {
340
- pages.push(...page.data);
341
- }
342
-
343
- expect(pages).toEqual(["simple"]);
344
- });
345
- });
346
-
347
- describe("maxItems functionality", () => {
348
- it("should limit total items across pages", async () => {
349
- const items: typeof ALL_ITEMS = [];
350
-
351
- for await (const page of paginate(listTestItems, {
352
- pageSize: 3,
353
- maxItems: 7,
354
- })) {
355
- items.push(...page.data);
356
- }
357
-
358
- expect(items).toHaveLength(7);
359
- expect(items).toEqual(ALL_ITEMS.slice(0, 7));
360
- });
361
-
362
- it("should truncate the final page when maxItems is exceeded", async () => {
363
- // Create a function that ignores maxItems hint to force truncation
364
-
365
- async function fetchIgnoringMaxItems(options: {
366
- pageSize?: number;
367
- cursor?: string;
368
- maxItems?: number;
369
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
370
- return listTestItems({
371
- pageSize: options.pageSize,
372
- cursor: options.cursor,
373
- });
374
- }
375
-
376
- const pageData: { data: typeof ALL_ITEMS; nextCursor?: string }[] = [];
377
-
378
- for await (const page of paginate(fetchIgnoringMaxItems, {
379
- pageSize: 3,
380
- maxItems: 7,
381
- })) {
382
- pageData.push(page);
383
- }
384
-
385
- // Should have 3 pages: [3 items], [3 items], [1 item (truncated)]
386
- expect(pageData).toHaveLength(3);
387
- expect(pageData[0].data).toHaveLength(3);
388
- expect(pageData[1].data).toHaveLength(3);
389
- expect(pageData[2].data).toHaveLength(1); // Truncated page
390
- expect(pageData[2].nextCursor).toBeUndefined(); // Should clear cursor
391
- });
392
-
393
- it("should stop early when maxItems is exactly a page boundary", async () => {
394
- const items: typeof ALL_ITEMS = [];
395
-
396
- for await (const page of paginate(listTestItems, {
397
- pageSize: 3,
398
- maxItems: 6,
399
- })) {
400
- items.push(...page.data);
401
- }
402
-
403
- expect(items).toHaveLength(6);
404
- expect(items).toEqual(ALL_ITEMS.slice(0, 6));
405
- });
406
-
407
- it("should handle maxItems of 0", async () => {
408
- const items: typeof ALL_ITEMS = [];
409
-
410
- for await (const page of paginate(listTestItems, {
411
- pageSize: 3,
412
- maxItems: 0,
413
- })) {
414
- items.push(...page.data);
415
- }
416
-
417
- expect(items).toHaveLength(0);
418
- });
419
-
420
- it("should work normally when maxItems is larger than total items", async () => {
421
- const items: typeof ALL_ITEMS = [];
422
-
423
- for await (const page of paginate(listTestItems, {
424
- pageSize: 3,
425
- maxItems: 100,
426
- })) {
427
- items.push(...page.data);
428
- }
429
-
430
- // Should get all items since limit is higher than total
431
- expect(items).toEqual(ALL_ITEMS);
432
- });
433
-
434
- it("should work with maxItems and other options combined", async () => {
435
- async function fetchWithLimit(options: {
436
- limit: number;
437
- cursor?: string;
438
- maxItems?: number;
439
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
440
- const start = parseInt(options.cursor || "0");
441
- let limit = options.limit;
442
-
443
- // Apply maxItems hint for optimization
444
- if (options.maxItems !== undefined && options.maxItems > 0) {
445
- limit = Math.min(limit, options.maxItems);
446
- }
447
-
448
- const items = ALL_ITEMS.slice(start, start + limit);
449
- const nextCursor =
450
- start + limit < ALL_ITEMS.length
451
- ? (start + limit).toString()
452
- : undefined;
453
- return { data: items, nextCursor };
454
- }
455
-
456
- const items: typeof ALL_ITEMS = [];
457
-
458
- for await (const page of paginate(fetchWithLimit, {
459
- limit: 2,
460
- maxItems: 5,
461
- })) {
462
- items.push(...page.data);
463
- }
464
-
465
- expect(items).toHaveLength(5);
466
- expect(items).toEqual(ALL_ITEMS.slice(0, 5));
467
- });
468
-
469
- it("should preserve original page structure except for truncation", async () => {
470
- // Create a function that ignores maxItems hint to force truncation
471
- async function fetchIgnoringMaxItems(options: {
472
- pageSize?: number;
473
- cursor?: string;
474
- maxItems?: number;
475
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
476
- const pageSize = options.pageSize || 3;
477
- const start = options.cursor
478
- ? parseInt(options.cursor.replace("cursor-", "")) + 1
479
- : 0;
480
- const items = ALL_ITEMS.slice(start, start + pageSize);
481
- const nextCursor =
482
- start + pageSize <= ALL_ITEMS.length
483
- ? `cursor-${ALL_ITEMS[start + pageSize - 1].id}`
484
- : undefined;
485
- return { data: items, nextCursor };
486
- }
487
-
488
- let pageCount = 0;
489
-
490
- for await (const page of paginate(fetchIgnoringMaxItems, {
491
- pageSize: 3,
492
- maxItems: 7,
493
- })) {
494
- pageCount++;
495
-
496
- // All pages should have the expected structure
497
- expect(page).toHaveProperty("data");
498
- expect(Array.isArray(page.data)).toBe(true);
499
-
500
- if (pageCount === 3) {
501
- // Final truncated page
502
- expect(page.data).toHaveLength(1);
503
- expect(page.nextCursor).toBeUndefined();
504
- } else {
505
- // Regular pages
506
- expect(page.data).toHaveLength(3);
507
- expect(typeof page.nextCursor).toBe("string");
508
- }
509
- }
510
-
511
- expect(pageCount).toBe(3);
512
- });
513
-
514
- it("should handle API that ignores pageSize and returns more items on first page", async () => {
515
- // Simulate an API that ignores pageSize and always returns 10 items per page
516
- async function apiIgnoresPageSize(options: {
517
- pageSize?: number;
518
- cursor?: string;
519
- maxItems?: number;
520
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
521
- return listTestItems({
522
- cursor: options.cursor,
523
- pageSize: 10,
524
- });
525
- }
526
-
527
- const pages: { data: typeof ALL_ITEMS; nextCursor?: string }[] = [];
528
-
529
- // User requests pageSize: 3, but API returns 10 per page
530
- for await (const page of paginate(apiIgnoresPageSize, {
531
- pageSize: 3,
532
- })) {
533
- pages.push(page);
534
- // Only get first page to test the behavior
535
- break;
536
- }
537
-
538
- // Should get 1 page with only 3 items (paginate enforces pageSize limit)
539
- expect(pages).toHaveLength(1);
540
- expect(pages[0].data).toHaveLength(3);
541
- expect(pages[0].data).toEqual(ALL_ITEMS.slice(0, 3));
542
- // Cursor should still be present since there are more items available
543
- expect(pages[0].nextCursor).toBeDefined();
544
- });
545
-
546
- it("should truncate oversized API response to pageSize", async () => {
547
- // API always returns 10 items, but user wants pageSize: 3
548
- async function apiReturns10Items(options: {
549
- pageSize?: number;
550
- cursor?: string;
551
- maxItems?: number;
552
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
553
- return listTestItems({
554
- cursor: options.cursor,
555
- pageSize: 10,
556
- });
557
- }
558
-
559
- const pages: { data: typeof ALL_ITEMS; nextCursor?: string }[] = [];
560
-
561
- for await (const page of paginate(apiReturns10Items, {
562
- pageSize: 3,
563
- })) {
564
- pages.push(page);
565
- }
566
-
567
- // Should truncate each 10-item API response to 3 items per page
568
- expect(pages).toHaveLength(9);
569
- expect(pages[0].data).toHaveLength(3);
570
- expect(pages[1].data).toHaveLength(3);
571
- expect(pages[8].data).toHaveLength(1);
572
-
573
- // Verify items are from different API calls
574
- expect(pages[0].data).toEqual(ALL_ITEMS.slice(0, 3)); // Items 1-3 from first API call
575
- expect(pages[1].data).toEqual(ALL_ITEMS.slice(3, 6)); // Items 11-13 from second API call
576
- expect(pages[8].data).toEqual(ALL_ITEMS.slice(24, 25)); // Items 21-23 from third API call
577
-
578
- // All pages should have nextCursor since API has more data
579
- expect(pages[0].nextCursor).toBe('["$$offset$$",3,null]');
580
- expect(pages[8].nextCursor).toBeUndefined();
581
- });
582
-
583
- it("should handle pageSize enforcement with maxItems limit", async () => {
584
- // API returns 10 items, user wants pageSize: 3, maxItems: 7
585
- async function apiReturns10Items(options: {
586
- pageSize?: number;
587
- cursor?: string;
588
- maxItems?: number;
589
- }): Promise<{ data: typeof ALL_ITEMS; nextCursor?: string }> {
590
- return listTestItems({
591
- cursor: options.cursor,
592
- pageSize: 10,
593
- });
594
- }
595
-
596
- const pages: { data: typeof ALL_ITEMS; nextCursor?: string }[] = [];
597
-
598
- for await (const page of paginate(apiReturns10Items, {
599
- pageSize: 3,
600
- maxItems: 7,
601
- })) {
602
- pages.push(page);
603
- }
604
-
605
- // Should get: [3, 3, 1] items (total 7, respecting maxItems)
606
- expect(pages).toHaveLength(3);
607
- expect(pages[0].data).toHaveLength(3);
608
- expect(pages[1].data).toHaveLength(3);
609
- expect(pages[2].data).toHaveLength(1); // Truncated to respect maxItems
610
-
611
- // Last page should have no nextCursor (maxItems reached)
612
- expect(pages[2].nextCursor).toBeUndefined();
613
-
614
- // Verify total items
615
- const allItems = pages.flatMap((p) => p.data);
616
- expect(allItems).toHaveLength(7);
617
- expect(allItems).toEqual(ALL_ITEMS.slice(0, 7));
618
- });
619
- });
620
- });