@zapier/kitcore 0.6.0 → 0.7.0

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.
package/dist/index.mjs CHANGED
@@ -202,7 +202,8 @@ function getCoreErrorCause(value) {
202
202
  var CURSOR_VERSION = 1;
203
203
  var CURSOR_SOURCE = {
204
204
  API: "api",
205
- SDK: "sdk"
205
+ SDK: "sdk",
206
+ CONCAT: "concat"
206
207
  };
207
208
  function encodeBase64(str) {
208
209
  return btoa(
@@ -422,10 +423,32 @@ async function* paginateBuffered(pageFunction, pageOptions) {
422
423
  }
423
424
  }
424
425
  var paginate = paginateBuffered;
426
+ function encodeConcatCursor(index, cursor) {
427
+ const envelope = {
428
+ v: CURSOR_VERSION,
429
+ source: CURSOR_SOURCE.CONCAT,
430
+ index,
431
+ cursor
432
+ };
433
+ return encodeBase64(JSON.stringify(envelope));
434
+ }
435
+ function decodeConcatCursor(incoming) {
436
+ if (!incoming) {
437
+ return { index: 0, cursor: void 0 };
438
+ }
439
+ try {
440
+ const envelope = JSON.parse(decodeBase64(incoming));
441
+ if (envelope.v === CURSOR_VERSION && envelope.source === CURSOR_SOURCE.CONCAT && typeof envelope.index === "number") {
442
+ return { index: envelope.index, cursor: envelope.cursor };
443
+ }
444
+ } catch {
445
+ }
446
+ return { index: 0, cursor: incoming };
447
+ }
425
448
  function concatPaginated({
426
449
  sources,
427
- dedupe,
428
- pageSize = 100
450
+ pageSize = 100,
451
+ cursor
429
452
  }) {
430
453
  if (sources.length === 0) {
431
454
  const empty = { data: [] };
@@ -435,40 +458,24 @@ function concatPaginated({
435
458
  }
436
459
  });
437
460
  }
438
- let sourceIndex = 0;
439
- let currentIterator = null;
440
- const seen = /* @__PURE__ */ new Set();
441
- const pageFunction = async (_options) => {
442
- while (sourceIndex < sources.length) {
443
- if (!currentIterator) {
444
- const result = sources[sourceIndex]();
445
- currentIterator = result[Symbol.asyncIterator]();
446
- }
447
- const next = await currentIterator.next();
448
- if (next.done) {
449
- sourceIndex++;
450
- currentIterator = null;
461
+ const pageFunction = async (options) => {
462
+ let { index, cursor: sourceCursor } = decodeConcatCursor(options.cursor);
463
+ while (index < sources.length) {
464
+ const page = await sources[index]({ cursor: sourceCursor });
465
+ const hasMoreInSource = page.nextCursor != null;
466
+ if (page.data.length === 0 && !hasMoreInSource) {
467
+ index++;
468
+ sourceCursor = void 0;
451
469
  continue;
452
470
  }
453
- let items = next.value.data;
454
- if (dedupe) {
455
- if (sourceIndex > 0) {
456
- items = items.filter((item) => !seen.has(dedupe(item)));
457
- }
458
- for (const item of items) {
459
- seen.add(dedupe(item));
460
- }
461
- }
462
- const hasMoreInSource = next.value.nextCursor != null;
463
- const hasMoreSources = sourceIndex < sources.length - 1;
464
471
  return {
465
- data: items,
466
- nextCursor: hasMoreInSource || hasMoreSources ? "__has_more__" : void 0
472
+ data: page.data,
473
+ nextCursor: hasMoreInSource ? encodeConcatCursor(index, page.nextCursor) : index < sources.length - 1 ? encodeConcatCursor(index + 1, void 0) : void 0
467
474
  };
468
475
  }
469
476
  return { data: [] };
470
477
  };
471
- const iterator = paginateBuffered(pageFunction, { pageSize });
478
+ const iterator = paginateBuffered(pageFunction, { pageSize, cursor });
472
479
  const firstPagePromise = iterator.next().then((result) => {
473
480
  if (result.done) {
474
481
  return { data: [] };
@@ -1351,6 +1358,7 @@ function defineResolver(config) {
1351
1358
  getContext: config.getContext,
1352
1359
  listItems: config.listItems,
1353
1360
  prompt: config.prompt,
1361
+ validate: config.validate,
1354
1362
  tryResolveWithoutPrompt: config.tryResolveWithoutPrompt,
1355
1363
  tryResolveFromSearch: config.tryResolveFromSearch
1356
1364
  };
@@ -2009,6 +2017,7 @@ function bindResolver(resolver, plugins) {
2009
2017
  const {
2010
2018
  getContext: getContext2,
2011
2019
  listItems,
2020
+ validate,
2012
2021
  tryResolveWithoutPrompt,
2013
2022
  tryResolveFromSearch
2014
2023
  } = resolver;
@@ -2022,6 +2031,9 @@ function bindResolver(resolver, plugins) {
2022
2031
  };
2023
2032
  if (getContext2)
2024
2033
  bound.getContext = ({ input }) => getContext2({ imports, input });
2034
+ if (validate) {
2035
+ bound.validate = ({ value, input, context }) => validate({ imports, value, input, context });
2036
+ }
2025
2037
  if (tryResolveWithoutPrompt) {
2026
2038
  bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
2027
2039
  }
@@ -2701,14 +2713,14 @@ function coerce(leaf, raw) {
2701
2713
  }
2702
2714
  async function validationError(leaf, value, state) {
2703
2715
  if (leaf.resolver?.type !== "dynamic") return null;
2716
+ const validate = leaf.resolver.validate;
2717
+ if (!validate) return null;
2704
2718
  const context = await resolveContext(leaf, state.resolved);
2705
- const config = leaf.resolver.prompt?.({
2706
- items: state.listing?.items ?? [],
2719
+ const verdict = await validate({
2720
+ value,
2707
2721
  input: mergeInput(state.resolved, leaf.extraInput),
2708
2722
  context
2709
2723
  });
2710
- if (!config?.validate) return null;
2711
- const verdict = config.validate(value);
2712
2724
  if (verdict === true) return null;
2713
2725
  return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
2714
2726
  }
@@ -2834,20 +2846,36 @@ async function resolveContext(leaf, input) {
2834
2846
  input: mergeInput(input, leaf.extraInput)
2835
2847
  });
2836
2848
  }
2837
- async function fetchListing(leaf, input, opts = {}) {
2849
+ function firstPagePosition(opts = {}) {
2850
+ return {
2851
+ ...opts.search !== void 0 ? { search: opts.search } : {},
2852
+ pageCursor: null,
2853
+ previousCursors: [],
2854
+ generation: opts.generation ?? 0
2855
+ };
2856
+ }
2857
+ async function fetchListing(leaf, input, position, context) {
2858
+ if (leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search" && position.search === void 0) {
2859
+ return { position, items: [] };
2860
+ }
2838
2861
  const page = await firstPage(
2839
2862
  leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
2840
2863
  input: mergeInput(input, leaf.extraInput),
2841
- context: opts.context,
2842
- search: opts.search,
2843
- cursor: opts.cursor
2864
+ context,
2865
+ search: position.search,
2866
+ cursor: position.pageCursor ?? void 0
2844
2867
  }) : void 0
2845
2868
  );
2846
2869
  return {
2847
- items: [...opts.priorItems ?? [], ...page.data],
2848
- cursor: page.nextCursor,
2849
- search: opts.search,
2850
- exhausted: page.nextCursor == null
2870
+ position,
2871
+ items: page.data,
2872
+ ...page.nextCursor != null ? { nextCursor: page.nextCursor } : {}
2873
+ };
2874
+ }
2875
+ function toPagination(page) {
2876
+ return {
2877
+ position: page.position,
2878
+ ...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {}
2851
2879
  };
2852
2880
  }
2853
2881
 
@@ -2869,23 +2897,32 @@ var AFFORDANCE = {
2869
2897
  description: "Filter the options by a search term",
2870
2898
  supply: "term"
2871
2899
  },
2872
- more: { action: "more", description: "Load more options" },
2900
+ nextPage: {
2901
+ action: "next_page",
2902
+ description: "Fetch the next page of options"
2903
+ },
2904
+ previousPage: {
2905
+ action: "previous_page",
2906
+ description: "Return to the previous page of options"
2907
+ },
2873
2908
  skip: { action: "skip", description: "Omit this optional parameter" },
2874
2909
  add: { action: "add", description: "Add another item" },
2875
2910
  done: { action: "done", description: "Finish the list" },
2876
2911
  retry: { action: "retry", description: "Retry loading the options" },
2877
2912
  cancel: { action: "cancel", description: "Cancel resolution" }
2878
2913
  };
2879
- function selectActions(leaf, listing, multiple) {
2914
+ function selectActions(leaf, page, multiple) {
2880
2915
  const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2881
- if (searchMode && listing.search === void 0 && listing.items.length === 0) {
2916
+ if (searchMode && page.position.search === void 0 && page.items.length === 0) {
2882
2917
  const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
2883
2918
  if (!leaf.required) actions2.push(AFFORDANCE.skip);
2884
2919
  return actions2;
2885
2920
  }
2886
2921
  const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
2887
2922
  if (searchMode) actions.push(AFFORDANCE.search);
2888
- if (listing.cursor) actions.push(AFFORDANCE.more);
2923
+ if (page.nextCursor) actions.push(AFFORDANCE.nextPage);
2924
+ if (page.position.previousCursors.length > 0)
2925
+ actions.push(AFFORDANCE.previousPage);
2889
2926
  if (!leaf.required) actions.push(AFFORDANCE.skip);
2890
2927
  return actions;
2891
2928
  }
@@ -2893,16 +2930,17 @@ function labeledMessage(leaf) {
2893
2930
  if (!leaf.label) return void 0;
2894
2931
  return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
2895
2932
  }
2896
- function selectQuestion(leaf, input, listing, context) {
2933
+ function selectQuestion(leaf, path, input, page, context) {
2897
2934
  const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
2898
2935
  const config = resolver?.prompt?.({
2899
- items: listing.items,
2936
+ items: page.items,
2900
2937
  input: mergeInput(input, leaf.extraInput),
2901
2938
  context
2902
2939
  });
2903
2940
  const multiple = config?.type === "checkbox";
2904
2941
  return {
2905
2942
  type: "select",
2943
+ path,
2906
2944
  // A labeled field's title beats the resolver's message: per-field
2907
2945
  // resolvers are shared across fields (one choices-fetcher for every
2908
2946
  // field), so only the leaf knows which field is being asked.
@@ -2910,9 +2948,13 @@ function selectQuestion(leaf, input, listing, context) {
2910
2948
  choices: (config?.choices ?? []).map(toChoice),
2911
2949
  ...multiple ? { multiple: true } : {},
2912
2950
  ...config?.notes?.length ? { notes: config.notes } : {},
2913
- ...listing.search !== void 0 ? { search: listing.search } : {},
2951
+ ...page.position.search !== void 0 ? { search: page.position.search } : {},
2914
2952
  ...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
2915
- actions: selectActions(leaf, listing, multiple)
2953
+ page: {
2954
+ generation: page.position.generation,
2955
+ index: page.position.previousCursors.length
2956
+ },
2957
+ actions: selectActions(leaf, page, multiple)
2916
2958
  };
2917
2959
  }
2918
2960
  function toControllerError(error) {
@@ -2934,6 +2976,7 @@ function failedResult(state, name, error) {
2934
2976
  error: toControllerError(error),
2935
2977
  question: {
2936
2978
  type: "select",
2979
+ path: state.current ?? [],
2937
2980
  message: `Could not load options for ${name}.`,
2938
2981
  choices: [],
2939
2982
  actions: [AFFORDANCE.retry, AFFORDANCE.cancel]
@@ -2941,27 +2984,15 @@ function failedResult(state, name, error) {
2941
2984
  }
2942
2985
  };
2943
2986
  }
2944
- async function buildQuestion(leaf, input) {
2987
+ async function buildQuestion(leaf, path, input) {
2945
2988
  const optional = !leaf.required;
2946
2989
  const resolver = leaf.resolver;
2947
2990
  if (resolver?.type === "dynamic") {
2948
2991
  const context = await resolveContext(leaf, input);
2949
- if (resolver.inputType === "search") {
2950
- const listing2 = {
2951
- items: [],
2952
- cursor: void 0,
2953
- search: void 0,
2954
- exhausted: true
2955
- };
2956
- return {
2957
- question: selectQuestion(leaf, input, listing2, context),
2958
- listing: listing2
2959
- };
2960
- }
2961
- const listing = await fetchListing(leaf, input, { context });
2992
+ const page = await fetchListing(leaf, input, firstPagePosition(), context);
2962
2993
  return {
2963
- question: selectQuestion(leaf, input, listing, context),
2964
- listing
2994
+ question: selectQuestion(leaf, path, input, page, context),
2995
+ pagination: toPagination(page)
2965
2996
  };
2966
2997
  }
2967
2998
  if (leaf.staticChoices) {
@@ -2970,6 +3001,7 @@ async function buildQuestion(leaf, input) {
2970
3001
  return {
2971
3002
  question: {
2972
3003
  type: "select",
3004
+ path,
2973
3005
  message: `Select ${leaf.name}:`,
2974
3006
  choices: leaf.staticChoices,
2975
3007
  actions: actions2
@@ -2983,6 +3015,7 @@ async function buildQuestion(leaf, input) {
2983
3015
  return {
2984
3016
  question: {
2985
3017
  type: "input",
3018
+ path,
2986
3019
  // The optional marker makes Enter-to-pass discoverable on a bare
2987
3020
  // parameter; a labeled field carries its marker via labeledMessage.
2988
3021
  message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
@@ -2997,6 +3030,7 @@ function collectionQuestion(t) {
2997
3030
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
2998
3031
  return {
2999
3032
  type: "collection",
3033
+ path: t.path,
3000
3034
  message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
3001
3035
  container: "array",
3002
3036
  count: t.count,
@@ -3010,6 +3044,7 @@ function collectionQuestion(t) {
3010
3044
  function objectGateQuestion(path) {
3011
3045
  return {
3012
3046
  type: "collection",
3047
+ path,
3013
3048
  message: `Add ${path[path.length - 1]}?`,
3014
3049
  container: "object",
3015
3050
  actions: [
@@ -3021,9 +3056,10 @@ function objectGateQuestion(path) {
3021
3056
  ]
3022
3057
  };
3023
3058
  }
3024
- function optionalsGateQuestion(pending) {
3059
+ function optionalsGateQuestion(path, pending) {
3025
3060
  return {
3026
3061
  type: "collection",
3062
+ path,
3027
3063
  // The prompt and its context ride separately so a host renders the info
3028
3064
  // line above the confirm without composing any text of its own.
3029
3065
  message: "Would you like to configure optional fields?",
@@ -3173,8 +3209,12 @@ async function askLeaf(state, path, leaf, opts = {}) {
3173
3209
  state.current = path;
3174
3210
  delete state.gate;
3175
3211
  try {
3176
- const { question, listing } = await buildQuestion(leaf, state.resolved);
3177
- state.listing = listing;
3212
+ const { question, pagination } = await buildQuestion(
3213
+ leaf,
3214
+ path,
3215
+ state.resolved
3216
+ );
3217
+ state.pagination = pagination;
3178
3218
  return {
3179
3219
  state,
3180
3220
  result: {
@@ -3184,7 +3224,7 @@ async function askLeaf(state, path, leaf, opts = {}) {
3184
3224
  }
3185
3225
  };
3186
3226
  } catch (error) {
3187
- state.listing = { items: [], exhausted: false };
3227
+ state.pagination = failedPagination(void 0, firstPagePosition());
3188
3228
  return failedResult(state, leaf.name, error);
3189
3229
  }
3190
3230
  }
@@ -3194,13 +3234,13 @@ async function advance(ctx, state) {
3194
3234
  if (!target) {
3195
3235
  delete state.current;
3196
3236
  delete state.gate;
3197
- delete state.listing;
3237
+ delete state.pagination;
3198
3238
  return { state, result: finalize(ctx, state.resolved) };
3199
3239
  }
3200
3240
  if (target.kind === "array") {
3201
3241
  state.current = target.path;
3202
3242
  state.gate = "array";
3203
- delete state.listing;
3243
+ delete state.pagination;
3204
3244
  return {
3205
3245
  state,
3206
3246
  result: { status: "ask", question: collectionQuestion(target) }
@@ -3209,7 +3249,7 @@ async function advance(ctx, state) {
3209
3249
  if (target.kind === "object") {
3210
3250
  state.current = target.path;
3211
3251
  state.gate = "entry";
3212
- delete state.listing;
3252
+ delete state.pagination;
3213
3253
  return {
3214
3254
  state,
3215
3255
  result: { status: "ask", question: objectGateQuestion(target.path) }
@@ -3218,12 +3258,12 @@ async function advance(ctx, state) {
3218
3258
  if (target.kind === "optionals") {
3219
3259
  state.current = target.path;
3220
3260
  state.gate = "optionals";
3221
- delete state.listing;
3261
+ delete state.pagination;
3222
3262
  return {
3223
3263
  state,
3224
3264
  result: {
3225
3265
  status: "ask",
3226
- question: optionalsGateQuestion(target.pending)
3266
+ question: optionalsGateQuestion(target.path, target.pending)
3227
3267
  }
3228
3268
  };
3229
3269
  }
@@ -3274,13 +3314,13 @@ async function step(ctx, prior, action) {
3274
3314
  if (action.type === "cancel") {
3275
3315
  delete state.current;
3276
3316
  delete state.gate;
3277
- delete state.listing;
3317
+ delete state.pagination;
3278
3318
  return { state, result: { status: "cancelled" } };
3279
3319
  }
3280
3320
  const path = state.current;
3281
3321
  if (!path) throw new Error("step called with no outstanding question");
3282
3322
  const leaf = await leafAt(ctx, path, state.resolved);
3283
- if (leaf && (action.type === "search" || action.type === "more" || action.type === "retry")) {
3323
+ if (leaf && (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry")) {
3284
3324
  return refine(ctx, state, leaf, path, action);
3285
3325
  }
3286
3326
  if (action.type === "add" || action.type === "done") {
@@ -3292,7 +3332,7 @@ async function step(ctx, prior, action) {
3292
3332
  }
3293
3333
  delete state.current;
3294
3334
  delete state.gate;
3295
- delete state.listing;
3335
+ delete state.pagination;
3296
3336
  if (action.type === "done") {
3297
3337
  settle(state, path);
3298
3338
  return advance(ctx, state);
@@ -3321,23 +3361,44 @@ async function step(ctx, prior, action) {
3321
3361
  case "choose":
3322
3362
  case "custom": {
3323
3363
  if (leaf) {
3324
- const error = await validationError(leaf, action.value, state);
3364
+ let error;
3365
+ try {
3366
+ error = await validationError(leaf, action.value, state);
3367
+ } catch (thrown) {
3368
+ return failedResult(state, leaf.name, thrown);
3369
+ }
3325
3370
  if (error) {
3326
- if (state.listing && leaf.resolver?.type === "dynamic") {
3327
- const context = await resolveContext(leaf, state.resolved);
3328
- return {
3329
- state,
3330
- result: {
3331
- status: "ask",
3332
- question: selectQuestion(
3333
- leaf,
3334
- state.resolved,
3335
- state.listing,
3336
- context
3337
- ),
3338
- error
3339
- }
3340
- };
3371
+ if (state.pagination && leaf.resolver?.type === "dynamic") {
3372
+ try {
3373
+ const context = await resolveContext(leaf, state.resolved);
3374
+ const page = await fetchListing(
3375
+ leaf,
3376
+ state.resolved,
3377
+ state.pagination.position,
3378
+ context
3379
+ );
3380
+ state.pagination = toPagination(page);
3381
+ return {
3382
+ state,
3383
+ result: {
3384
+ status: "ask",
3385
+ question: selectQuestion(
3386
+ leaf,
3387
+ path,
3388
+ state.resolved,
3389
+ page,
3390
+ context
3391
+ ),
3392
+ error
3393
+ }
3394
+ };
3395
+ } catch (fetchError) {
3396
+ state.pagination = failedPagination(
3397
+ state.pagination,
3398
+ state.pagination.position
3399
+ );
3400
+ return failedResult(state, leaf.name, fetchError);
3401
+ }
3341
3402
  }
3342
3403
  return askLeaf(state, path, leaf, { error });
3343
3404
  }
@@ -3356,15 +3417,11 @@ async function step(ctx, prior, action) {
3356
3417
  throw new Error(`action "${action.type}" is not supported here`);
3357
3418
  }
3358
3419
  delete state.current;
3359
- delete state.listing;
3420
+ delete state.pagination;
3360
3421
  return advance(ctx, state);
3361
3422
  }
3362
3423
  async function refine(ctx, state, leaf, path, action) {
3363
- const attempt = action.type === "search" ? { search: action.term, cursor: void 0, priorItems: [] } : {
3364
- search: state.listing?.search,
3365
- cursor: state.listing?.cursor,
3366
- priorItems: state.listing?.items ?? []
3367
- };
3424
+ const position = positionAfter(state.pagination, action);
3368
3425
  try {
3369
3426
  if (action.type === "search") {
3370
3427
  const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
@@ -3374,32 +3431,62 @@ async function refine(ctx, state, leaf, path, action) {
3374
3431
  if (exact) {
3375
3432
  setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3376
3433
  delete state.current;
3377
- delete state.listing;
3434
+ delete state.pagination;
3378
3435
  return advance(ctx, state);
3379
3436
  }
3380
3437
  }
3381
3438
  const context = await resolveContext(leaf, state.resolved);
3382
- state.listing = await fetchListing(leaf, state.resolved, {
3383
- ...attempt,
3384
- context
3385
- });
3439
+ const page = await fetchListing(leaf, state.resolved, position, context);
3440
+ state.pagination = toPagination(page);
3386
3441
  return {
3387
3442
  state,
3388
3443
  result: {
3389
3444
  status: "ask",
3390
- question: selectQuestion(leaf, state.resolved, state.listing, context)
3445
+ question: selectQuestion(leaf, path, state.resolved, page, context)
3391
3446
  }
3392
3447
  };
3393
3448
  } catch (error) {
3394
- state.listing = {
3395
- items: attempt.priorItems,
3396
- search: attempt.search,
3397
- cursor: attempt.cursor,
3398
- exhausted: false
3399
- };
3449
+ state.pagination = failedPagination(state.pagination, position);
3400
3450
  return failedResult(state, leaf.name, error);
3401
3451
  }
3402
3452
  }
3453
+ function failedPagination(pagination, retryPosition) {
3454
+ return {
3455
+ position: pagination?.position ?? firstPagePosition(),
3456
+ ...pagination?.nextCursor !== void 0 ? { nextCursor: pagination.nextCursor } : {},
3457
+ retryPosition
3458
+ };
3459
+ }
3460
+ function positionAfter(pagination, action) {
3461
+ const current = pagination?.position ?? firstPagePosition();
3462
+ switch (action.type) {
3463
+ case "search":
3464
+ return firstPagePosition({
3465
+ search: action.term,
3466
+ generation: current.generation + 1
3467
+ });
3468
+ case "next_page": {
3469
+ if (pagination?.nextCursor == null) return current;
3470
+ return {
3471
+ ...current.search !== void 0 ? { search: current.search } : {},
3472
+ pageCursor: pagination.nextCursor,
3473
+ previousCursors: [...current.previousCursors, current.pageCursor],
3474
+ generation: current.generation
3475
+ };
3476
+ }
3477
+ case "previous_page": {
3478
+ if (current.previousCursors.length === 0) return current;
3479
+ return {
3480
+ ...current.search !== void 0 ? { search: current.search } : {},
3481
+ pageCursor: current.previousCursors[current.previousCursors.length - 1],
3482
+ previousCursors: current.previousCursors.slice(0, -1),
3483
+ generation: current.generation
3484
+ };
3485
+ }
3486
+ case "retry":
3487
+ return pagination?.retryPosition ?? current;
3488
+ }
3489
+ }
3403
3490
 
3404
3491
  // src/model/resolution/controller.ts
3405
3492
  function toJsonSchema(schema) {