@zapier/kitcore 0.5.1 → 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
  }
@@ -2177,9 +2189,7 @@ function buildMethodEntries(descriptors, context, states) {
2177
2189
  }
2178
2190
  );
2179
2191
  } else if (out.type === "item") {
2180
- const itemCore = async (input) => ({
2181
- data: await callRun(input)
2182
- });
2192
+ const itemCore = async (input) => callRun(input);
2183
2193
  entry.value = createFunction(
2184
2194
  fold(itemCore),
2185
2195
  {
@@ -2703,14 +2713,14 @@ function coerce(leaf, raw) {
2703
2713
  }
2704
2714
  async function validationError(leaf, value, state) {
2705
2715
  if (leaf.resolver?.type !== "dynamic") return null;
2716
+ const validate = leaf.resolver.validate;
2717
+ if (!validate) return null;
2706
2718
  const context = await resolveContext(leaf, state.resolved);
2707
- const config = leaf.resolver.prompt?.({
2708
- items: state.listing?.items ?? [],
2719
+ const verdict = await validate({
2720
+ value,
2709
2721
  input: mergeInput(state.resolved, leaf.extraInput),
2710
2722
  context
2711
2723
  });
2712
- if (!config?.validate) return null;
2713
- const verdict = config.validate(value);
2714
2724
  if (verdict === true) return null;
2715
2725
  return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
2716
2726
  }
@@ -2836,20 +2846,36 @@ async function resolveContext(leaf, input) {
2836
2846
  input: mergeInput(input, leaf.extraInput)
2837
2847
  });
2838
2848
  }
2839
- 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
+ }
2840
2861
  const page = await firstPage(
2841
2862
  leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
2842
2863
  input: mergeInput(input, leaf.extraInput),
2843
- context: opts.context,
2844
- search: opts.search,
2845
- cursor: opts.cursor
2864
+ context,
2865
+ search: position.search,
2866
+ cursor: position.pageCursor ?? void 0
2846
2867
  }) : void 0
2847
2868
  );
2848
2869
  return {
2849
- items: [...opts.priorItems ?? [], ...page.data],
2850
- cursor: page.nextCursor,
2851
- search: opts.search,
2852
- 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 } : {}
2853
2879
  };
2854
2880
  }
2855
2881
 
@@ -2871,23 +2897,32 @@ var AFFORDANCE = {
2871
2897
  description: "Filter the options by a search term",
2872
2898
  supply: "term"
2873
2899
  },
2874
- 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
+ },
2875
2908
  skip: { action: "skip", description: "Omit this optional parameter" },
2876
2909
  add: { action: "add", description: "Add another item" },
2877
2910
  done: { action: "done", description: "Finish the list" },
2878
2911
  retry: { action: "retry", description: "Retry loading the options" },
2879
2912
  cancel: { action: "cancel", description: "Cancel resolution" }
2880
2913
  };
2881
- function selectActions(leaf, listing, multiple) {
2914
+ function selectActions(leaf, page, multiple) {
2882
2915
  const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2883
- if (searchMode && listing.search === void 0 && listing.items.length === 0) {
2916
+ if (searchMode && page.position.search === void 0 && page.items.length === 0) {
2884
2917
  const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
2885
2918
  if (!leaf.required) actions2.push(AFFORDANCE.skip);
2886
2919
  return actions2;
2887
2920
  }
2888
2921
  const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
2889
2922
  if (searchMode) actions.push(AFFORDANCE.search);
2890
- 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);
2891
2926
  if (!leaf.required) actions.push(AFFORDANCE.skip);
2892
2927
  return actions;
2893
2928
  }
@@ -2895,16 +2930,17 @@ function labeledMessage(leaf) {
2895
2930
  if (!leaf.label) return void 0;
2896
2931
  return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
2897
2932
  }
2898
- function selectQuestion(leaf, input, listing, context) {
2933
+ function selectQuestion(leaf, path, input, page, context) {
2899
2934
  const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
2900
2935
  const config = resolver?.prompt?.({
2901
- items: listing.items,
2936
+ items: page.items,
2902
2937
  input: mergeInput(input, leaf.extraInput),
2903
2938
  context
2904
2939
  });
2905
2940
  const multiple = config?.type === "checkbox";
2906
2941
  return {
2907
2942
  type: "select",
2943
+ path,
2908
2944
  // A labeled field's title beats the resolver's message: per-field
2909
2945
  // resolvers are shared across fields (one choices-fetcher for every
2910
2946
  // field), so only the leaf knows which field is being asked.
@@ -2912,9 +2948,13 @@ function selectQuestion(leaf, input, listing, context) {
2912
2948
  choices: (config?.choices ?? []).map(toChoice),
2913
2949
  ...multiple ? { multiple: true } : {},
2914
2950
  ...config?.notes?.length ? { notes: config.notes } : {},
2915
- ...listing.search !== void 0 ? { search: listing.search } : {},
2951
+ ...page.position.search !== void 0 ? { search: page.position.search } : {},
2916
2952
  ...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
2917
- 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)
2918
2958
  };
2919
2959
  }
2920
2960
  function toControllerError(error) {
@@ -2936,6 +2976,7 @@ function failedResult(state, name, error) {
2936
2976
  error: toControllerError(error),
2937
2977
  question: {
2938
2978
  type: "select",
2979
+ path: state.current ?? [],
2939
2980
  message: `Could not load options for ${name}.`,
2940
2981
  choices: [],
2941
2982
  actions: [AFFORDANCE.retry, AFFORDANCE.cancel]
@@ -2943,27 +2984,15 @@ function failedResult(state, name, error) {
2943
2984
  }
2944
2985
  };
2945
2986
  }
2946
- async function buildQuestion(leaf, input) {
2987
+ async function buildQuestion(leaf, path, input) {
2947
2988
  const optional = !leaf.required;
2948
2989
  const resolver = leaf.resolver;
2949
2990
  if (resolver?.type === "dynamic") {
2950
2991
  const context = await resolveContext(leaf, input);
2951
- if (resolver.inputType === "search") {
2952
- const listing2 = {
2953
- items: [],
2954
- cursor: void 0,
2955
- search: void 0,
2956
- exhausted: true
2957
- };
2958
- return {
2959
- question: selectQuestion(leaf, input, listing2, context),
2960
- listing: listing2
2961
- };
2962
- }
2963
- const listing = await fetchListing(leaf, input, { context });
2992
+ const page = await fetchListing(leaf, input, firstPagePosition(), context);
2964
2993
  return {
2965
- question: selectQuestion(leaf, input, listing, context),
2966
- listing
2994
+ question: selectQuestion(leaf, path, input, page, context),
2995
+ pagination: toPagination(page)
2967
2996
  };
2968
2997
  }
2969
2998
  if (leaf.staticChoices) {
@@ -2972,6 +3001,7 @@ async function buildQuestion(leaf, input) {
2972
3001
  return {
2973
3002
  question: {
2974
3003
  type: "select",
3004
+ path,
2975
3005
  message: `Select ${leaf.name}:`,
2976
3006
  choices: leaf.staticChoices,
2977
3007
  actions: actions2
@@ -2985,6 +3015,7 @@ async function buildQuestion(leaf, input) {
2985
3015
  return {
2986
3016
  question: {
2987
3017
  type: "input",
3018
+ path,
2988
3019
  // The optional marker makes Enter-to-pass discoverable on a bare
2989
3020
  // parameter; a labeled field carries its marker via labeledMessage.
2990
3021
  message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
@@ -2999,6 +3030,7 @@ function collectionQuestion(t) {
2999
3030
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
3000
3031
  return {
3001
3032
  type: "collection",
3033
+ path: t.path,
3002
3034
  message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
3003
3035
  container: "array",
3004
3036
  count: t.count,
@@ -3012,6 +3044,7 @@ function collectionQuestion(t) {
3012
3044
  function objectGateQuestion(path) {
3013
3045
  return {
3014
3046
  type: "collection",
3047
+ path,
3015
3048
  message: `Add ${path[path.length - 1]}?`,
3016
3049
  container: "object",
3017
3050
  actions: [
@@ -3023,9 +3056,10 @@ function objectGateQuestion(path) {
3023
3056
  ]
3024
3057
  };
3025
3058
  }
3026
- function optionalsGateQuestion(pending) {
3059
+ function optionalsGateQuestion(path, pending) {
3027
3060
  return {
3028
3061
  type: "collection",
3062
+ path,
3029
3063
  // The prompt and its context ride separately so a host renders the info
3030
3064
  // line above the confirm without composing any text of its own.
3031
3065
  message: "Would you like to configure optional fields?",
@@ -3175,8 +3209,12 @@ async function askLeaf(state, path, leaf, opts = {}) {
3175
3209
  state.current = path;
3176
3210
  delete state.gate;
3177
3211
  try {
3178
- const { question, listing } = await buildQuestion(leaf, state.resolved);
3179
- state.listing = listing;
3212
+ const { question, pagination } = await buildQuestion(
3213
+ leaf,
3214
+ path,
3215
+ state.resolved
3216
+ );
3217
+ state.pagination = pagination;
3180
3218
  return {
3181
3219
  state,
3182
3220
  result: {
@@ -3186,7 +3224,7 @@ async function askLeaf(state, path, leaf, opts = {}) {
3186
3224
  }
3187
3225
  };
3188
3226
  } catch (error) {
3189
- state.listing = { items: [], exhausted: false };
3227
+ state.pagination = failedPagination(void 0, firstPagePosition());
3190
3228
  return failedResult(state, leaf.name, error);
3191
3229
  }
3192
3230
  }
@@ -3196,13 +3234,13 @@ async function advance(ctx, state) {
3196
3234
  if (!target) {
3197
3235
  delete state.current;
3198
3236
  delete state.gate;
3199
- delete state.listing;
3237
+ delete state.pagination;
3200
3238
  return { state, result: finalize(ctx, state.resolved) };
3201
3239
  }
3202
3240
  if (target.kind === "array") {
3203
3241
  state.current = target.path;
3204
3242
  state.gate = "array";
3205
- delete state.listing;
3243
+ delete state.pagination;
3206
3244
  return {
3207
3245
  state,
3208
3246
  result: { status: "ask", question: collectionQuestion(target) }
@@ -3211,7 +3249,7 @@ async function advance(ctx, state) {
3211
3249
  if (target.kind === "object") {
3212
3250
  state.current = target.path;
3213
3251
  state.gate = "entry";
3214
- delete state.listing;
3252
+ delete state.pagination;
3215
3253
  return {
3216
3254
  state,
3217
3255
  result: { status: "ask", question: objectGateQuestion(target.path) }
@@ -3220,12 +3258,12 @@ async function advance(ctx, state) {
3220
3258
  if (target.kind === "optionals") {
3221
3259
  state.current = target.path;
3222
3260
  state.gate = "optionals";
3223
- delete state.listing;
3261
+ delete state.pagination;
3224
3262
  return {
3225
3263
  state,
3226
3264
  result: {
3227
3265
  status: "ask",
3228
- question: optionalsGateQuestion(target.pending)
3266
+ question: optionalsGateQuestion(target.path, target.pending)
3229
3267
  }
3230
3268
  };
3231
3269
  }
@@ -3276,13 +3314,13 @@ async function step(ctx, prior, action) {
3276
3314
  if (action.type === "cancel") {
3277
3315
  delete state.current;
3278
3316
  delete state.gate;
3279
- delete state.listing;
3317
+ delete state.pagination;
3280
3318
  return { state, result: { status: "cancelled" } };
3281
3319
  }
3282
3320
  const path = state.current;
3283
3321
  if (!path) throw new Error("step called with no outstanding question");
3284
3322
  const leaf = await leafAt(ctx, path, state.resolved);
3285
- 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")) {
3286
3324
  return refine(ctx, state, leaf, path, action);
3287
3325
  }
3288
3326
  if (action.type === "add" || action.type === "done") {
@@ -3294,7 +3332,7 @@ async function step(ctx, prior, action) {
3294
3332
  }
3295
3333
  delete state.current;
3296
3334
  delete state.gate;
3297
- delete state.listing;
3335
+ delete state.pagination;
3298
3336
  if (action.type === "done") {
3299
3337
  settle(state, path);
3300
3338
  return advance(ctx, state);
@@ -3323,23 +3361,44 @@ async function step(ctx, prior, action) {
3323
3361
  case "choose":
3324
3362
  case "custom": {
3325
3363
  if (leaf) {
3326
- 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
+ }
3327
3370
  if (error) {
3328
- if (state.listing && leaf.resolver?.type === "dynamic") {
3329
- const context = await resolveContext(leaf, state.resolved);
3330
- return {
3331
- state,
3332
- result: {
3333
- status: "ask",
3334
- question: selectQuestion(
3335
- leaf,
3336
- state.resolved,
3337
- state.listing,
3338
- context
3339
- ),
3340
- error
3341
- }
3342
- };
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
+ }
3343
3402
  }
3344
3403
  return askLeaf(state, path, leaf, { error });
3345
3404
  }
@@ -3358,15 +3417,11 @@ async function step(ctx, prior, action) {
3358
3417
  throw new Error(`action "${action.type}" is not supported here`);
3359
3418
  }
3360
3419
  delete state.current;
3361
- delete state.listing;
3420
+ delete state.pagination;
3362
3421
  return advance(ctx, state);
3363
3422
  }
3364
3423
  async function refine(ctx, state, leaf, path, action) {
3365
- const attempt = action.type === "search" ? { search: action.term, cursor: void 0, priorItems: [] } : {
3366
- search: state.listing?.search,
3367
- cursor: state.listing?.cursor,
3368
- priorItems: state.listing?.items ?? []
3369
- };
3424
+ const position = positionAfter(state.pagination, action);
3370
3425
  try {
3371
3426
  if (action.type === "search") {
3372
3427
  const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
@@ -3376,32 +3431,62 @@ async function refine(ctx, state, leaf, path, action) {
3376
3431
  if (exact) {
3377
3432
  setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3378
3433
  delete state.current;
3379
- delete state.listing;
3434
+ delete state.pagination;
3380
3435
  return advance(ctx, state);
3381
3436
  }
3382
3437
  }
3383
3438
  const context = await resolveContext(leaf, state.resolved);
3384
- state.listing = await fetchListing(leaf, state.resolved, {
3385
- ...attempt,
3386
- context
3387
- });
3439
+ const page = await fetchListing(leaf, state.resolved, position, context);
3440
+ state.pagination = toPagination(page);
3388
3441
  return {
3389
3442
  state,
3390
3443
  result: {
3391
3444
  status: "ask",
3392
- question: selectQuestion(leaf, state.resolved, state.listing, context)
3445
+ question: selectQuestion(leaf, path, state.resolved, page, context)
3393
3446
  }
3394
3447
  };
3395
3448
  } catch (error) {
3396
- state.listing = {
3397
- items: attempt.priorItems,
3398
- search: attempt.search,
3399
- cursor: attempt.cursor,
3400
- exhausted: false
3401
- };
3449
+ state.pagination = failedPagination(state.pagination, position);
3402
3450
  return failedResult(state, leaf.name, error);
3403
3451
  }
3404
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
+ }
3405
3490
 
3406
3491
  // src/model/resolution/controller.ts
3407
3492
  function toJsonSchema(schema) {