@zapier/kitcore 0.6.0 → 0.8.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,69 +423,71 @@ async function* paginateBuffered(pageFunction, pageOptions) {
422
423
  }
423
424
  }
424
425
  var paginate = paginateBuffered;
425
- function concatPaginated({
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
+ }
448
+ async function concatLists({
426
449
  sources,
427
- dedupe,
428
- pageSize = 100
450
+ pageSize = 100,
451
+ cursor
429
452
  }) {
430
453
  if (sources.length === 0) {
431
- const empty = { data: [] };
432
- return Object.assign(Promise.resolve(empty), {
433
- [Symbol.asyncIterator]: async function* () {
434
- yield empty;
435
- }
436
- });
454
+ return { data: [] };
437
455
  }
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;
456
+ const pageFunction = async (options) => {
457
+ let { index, cursor: listCursor } = decodeConcatCursor(options.cursor);
458
+ while (index < sources.length) {
459
+ const page = await sources[index]({ cursor: listCursor });
460
+ const hasMoreInList = page.nextCursor != null;
461
+ if (page.data.length === 0 && !hasMoreInList) {
462
+ index++;
463
+ listCursor = void 0;
451
464
  continue;
452
465
  }
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
466
  return {
465
- data: items,
466
- nextCursor: hasMoreInSource || hasMoreSources ? "__has_more__" : void 0
467
+ data: page.data,
468
+ nextCursor: hasMoreInList ? encodeConcatCursor(index, page.nextCursor) : index < sources.length - 1 ? encodeConcatCursor(index + 1, void 0) : void 0
467
469
  };
468
470
  }
469
471
  return { data: [] };
470
472
  };
471
- const iterator = paginateBuffered(pageFunction, { pageSize });
472
- const firstPagePromise = iterator.next().then((result) => {
473
- if (result.done) {
474
- return { data: [] };
475
- }
476
- return result.value;
477
- });
478
- return Object.assign(firstPagePromise, {
479
- [Symbol.asyncIterator]: async function* () {
480
- yield await firstPagePromise;
481
- for await (const page of { [Symbol.asyncIterator]: () => iterator }) {
482
- yield page;
483
- }
484
- }
485
- });
473
+ const result = await paginateBuffered(pageFunction, {
474
+ pageSize,
475
+ cursor
476
+ }).next();
477
+ return result.done ? { data: [] } : result.value;
478
+ }
479
+ function concatPaginated({
480
+ sources,
481
+ pageSize,
482
+ cursor
483
+ }) {
484
+ logDeprecation("concatPaginated() is deprecated. Use concatLists() instead.");
485
+ return concatLists({ sources, pageSize, cursor });
486
486
  }
487
487
  function toIterable(source) {
488
+ logDeprecation(
489
+ "toIterable() is deprecated. Call .pages() on the paginated result instead."
490
+ );
488
491
  return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
489
492
  }
490
493
 
@@ -839,6 +842,13 @@ function createPaginatedFunction(coreFn, options) {
839
842
  [Symbol.asyncIterator]() {
840
843
  return pageStream;
841
844
  },
845
+ pages: function() {
846
+ return {
847
+ [Symbol.asyncIterator]() {
848
+ return pageStream;
849
+ }
850
+ };
851
+ },
842
852
  items: function() {
843
853
  return {
844
854
  [Symbol.asyncIterator]: async function* () {
@@ -1351,6 +1361,7 @@ function defineResolver(config) {
1351
1361
  getContext: config.getContext,
1352
1362
  listItems: config.listItems,
1353
1363
  prompt: config.prompt,
1364
+ validate: config.validate,
1354
1365
  tryResolveWithoutPrompt: config.tryResolveWithoutPrompt,
1355
1366
  tryResolveFromSearch: config.tryResolveFromSearch
1356
1367
  };
@@ -2009,6 +2020,7 @@ function bindResolver(resolver, plugins) {
2009
2020
  const {
2010
2021
  getContext: getContext2,
2011
2022
  listItems,
2023
+ validate,
2012
2024
  tryResolveWithoutPrompt,
2013
2025
  tryResolveFromSearch
2014
2026
  } = resolver;
@@ -2022,6 +2034,9 @@ function bindResolver(resolver, plugins) {
2022
2034
  };
2023
2035
  if (getContext2)
2024
2036
  bound.getContext = ({ input }) => getContext2({ imports, input });
2037
+ if (validate) {
2038
+ bound.validate = ({ value, input, context }) => validate({ imports, value, input, context });
2039
+ }
2025
2040
  if (tryResolveWithoutPrompt) {
2026
2041
  bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
2027
2042
  }
@@ -2701,14 +2716,14 @@ function coerce(leaf, raw) {
2701
2716
  }
2702
2717
  async function validationError(leaf, value, state) {
2703
2718
  if (leaf.resolver?.type !== "dynamic") return null;
2719
+ const validate = leaf.resolver.validate;
2720
+ if (!validate) return null;
2704
2721
  const context = await resolveContext(leaf, state.resolved);
2705
- const config = leaf.resolver.prompt?.({
2706
- items: state.listing?.items ?? [],
2722
+ const verdict = await validate({
2723
+ value,
2707
2724
  input: mergeInput(state.resolved, leaf.extraInput),
2708
2725
  context
2709
2726
  });
2710
- if (!config?.validate) return null;
2711
- const verdict = config.validate(value);
2712
2727
  if (verdict === true) return null;
2713
2728
  return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
2714
2729
  }
@@ -2834,20 +2849,36 @@ async function resolveContext(leaf, input) {
2834
2849
  input: mergeInput(input, leaf.extraInput)
2835
2850
  });
2836
2851
  }
2837
- async function fetchListing(leaf, input, opts = {}) {
2852
+ function firstPagePosition(opts = {}) {
2853
+ return {
2854
+ ...opts.search !== void 0 ? { search: opts.search } : {},
2855
+ pageCursor: null,
2856
+ previousCursors: [],
2857
+ generation: opts.generation ?? 0
2858
+ };
2859
+ }
2860
+ async function fetchListing(leaf, input, position, context) {
2861
+ if (leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search" && position.search === void 0) {
2862
+ return { position, items: [] };
2863
+ }
2838
2864
  const page = await firstPage(
2839
2865
  leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
2840
2866
  input: mergeInput(input, leaf.extraInput),
2841
- context: opts.context,
2842
- search: opts.search,
2843
- cursor: opts.cursor
2867
+ context,
2868
+ search: position.search,
2869
+ cursor: position.pageCursor ?? void 0
2844
2870
  }) : void 0
2845
2871
  );
2846
2872
  return {
2847
- items: [...opts.priorItems ?? [], ...page.data],
2848
- cursor: page.nextCursor,
2849
- search: opts.search,
2850
- exhausted: page.nextCursor == null
2873
+ position,
2874
+ items: page.data,
2875
+ ...page.nextCursor != null ? { nextCursor: page.nextCursor } : {}
2876
+ };
2877
+ }
2878
+ function toPagination(page) {
2879
+ return {
2880
+ position: page.position,
2881
+ ...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {}
2851
2882
  };
2852
2883
  }
2853
2884
 
@@ -2869,23 +2900,32 @@ var AFFORDANCE = {
2869
2900
  description: "Filter the options by a search term",
2870
2901
  supply: "term"
2871
2902
  },
2872
- more: { action: "more", description: "Load more options" },
2903
+ nextPage: {
2904
+ action: "next_page",
2905
+ description: "Fetch the next page of options"
2906
+ },
2907
+ previousPage: {
2908
+ action: "previous_page",
2909
+ description: "Return to the previous page of options"
2910
+ },
2873
2911
  skip: { action: "skip", description: "Omit this optional parameter" },
2874
2912
  add: { action: "add", description: "Add another item" },
2875
2913
  done: { action: "done", description: "Finish the list" },
2876
2914
  retry: { action: "retry", description: "Retry loading the options" },
2877
2915
  cancel: { action: "cancel", description: "Cancel resolution" }
2878
2916
  };
2879
- function selectActions(leaf, listing, multiple) {
2917
+ function selectActions(leaf, page, multiple) {
2880
2918
  const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2881
- if (searchMode && listing.search === void 0 && listing.items.length === 0) {
2919
+ if (searchMode && page.position.search === void 0 && page.items.length === 0) {
2882
2920
  const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
2883
2921
  if (!leaf.required) actions2.push(AFFORDANCE.skip);
2884
2922
  return actions2;
2885
2923
  }
2886
2924
  const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
2887
2925
  if (searchMode) actions.push(AFFORDANCE.search);
2888
- if (listing.cursor) actions.push(AFFORDANCE.more);
2926
+ if (page.nextCursor) actions.push(AFFORDANCE.nextPage);
2927
+ if (page.position.previousCursors.length > 0)
2928
+ actions.push(AFFORDANCE.previousPage);
2889
2929
  if (!leaf.required) actions.push(AFFORDANCE.skip);
2890
2930
  return actions;
2891
2931
  }
@@ -2893,16 +2933,17 @@ function labeledMessage(leaf) {
2893
2933
  if (!leaf.label) return void 0;
2894
2934
  return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
2895
2935
  }
2896
- function selectQuestion(leaf, input, listing, context) {
2936
+ function selectQuestion(leaf, path, input, page, context) {
2897
2937
  const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
2898
2938
  const config = resolver?.prompt?.({
2899
- items: listing.items,
2939
+ items: page.items,
2900
2940
  input: mergeInput(input, leaf.extraInput),
2901
2941
  context
2902
2942
  });
2903
2943
  const multiple = config?.type === "checkbox";
2904
2944
  return {
2905
2945
  type: "select",
2946
+ path,
2906
2947
  // A labeled field's title beats the resolver's message: per-field
2907
2948
  // resolvers are shared across fields (one choices-fetcher for every
2908
2949
  // field), so only the leaf knows which field is being asked.
@@ -2910,9 +2951,13 @@ function selectQuestion(leaf, input, listing, context) {
2910
2951
  choices: (config?.choices ?? []).map(toChoice),
2911
2952
  ...multiple ? { multiple: true } : {},
2912
2953
  ...config?.notes?.length ? { notes: config.notes } : {},
2913
- ...listing.search !== void 0 ? { search: listing.search } : {},
2954
+ ...page.position.search !== void 0 ? { search: page.position.search } : {},
2914
2955
  ...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
2915
- actions: selectActions(leaf, listing, multiple)
2956
+ page: {
2957
+ generation: page.position.generation,
2958
+ index: page.position.previousCursors.length
2959
+ },
2960
+ actions: selectActions(leaf, page, multiple)
2916
2961
  };
2917
2962
  }
2918
2963
  function toControllerError(error) {
@@ -2934,6 +2979,7 @@ function failedResult(state, name, error) {
2934
2979
  error: toControllerError(error),
2935
2980
  question: {
2936
2981
  type: "select",
2982
+ path: state.current ?? [],
2937
2983
  message: `Could not load options for ${name}.`,
2938
2984
  choices: [],
2939
2985
  actions: [AFFORDANCE.retry, AFFORDANCE.cancel]
@@ -2941,27 +2987,15 @@ function failedResult(state, name, error) {
2941
2987
  }
2942
2988
  };
2943
2989
  }
2944
- async function buildQuestion(leaf, input) {
2990
+ async function buildQuestion(leaf, path, input) {
2945
2991
  const optional = !leaf.required;
2946
2992
  const resolver = leaf.resolver;
2947
2993
  if (resolver?.type === "dynamic") {
2948
2994
  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 });
2995
+ const page = await fetchListing(leaf, input, firstPagePosition(), context);
2962
2996
  return {
2963
- question: selectQuestion(leaf, input, listing, context),
2964
- listing
2997
+ question: selectQuestion(leaf, path, input, page, context),
2998
+ pagination: toPagination(page)
2965
2999
  };
2966
3000
  }
2967
3001
  if (leaf.staticChoices) {
@@ -2970,6 +3004,7 @@ async function buildQuestion(leaf, input) {
2970
3004
  return {
2971
3005
  question: {
2972
3006
  type: "select",
3007
+ path,
2973
3008
  message: `Select ${leaf.name}:`,
2974
3009
  choices: leaf.staticChoices,
2975
3010
  actions: actions2
@@ -2983,6 +3018,7 @@ async function buildQuestion(leaf, input) {
2983
3018
  return {
2984
3019
  question: {
2985
3020
  type: "input",
3021
+ path,
2986
3022
  // The optional marker makes Enter-to-pass discoverable on a bare
2987
3023
  // parameter; a labeled field carries its marker via labeledMessage.
2988
3024
  message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
@@ -2997,6 +3033,7 @@ function collectionQuestion(t) {
2997
3033
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
2998
3034
  return {
2999
3035
  type: "collection",
3036
+ path: t.path,
3000
3037
  message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
3001
3038
  container: "array",
3002
3039
  count: t.count,
@@ -3010,6 +3047,7 @@ function collectionQuestion(t) {
3010
3047
  function objectGateQuestion(path) {
3011
3048
  return {
3012
3049
  type: "collection",
3050
+ path,
3013
3051
  message: `Add ${path[path.length - 1]}?`,
3014
3052
  container: "object",
3015
3053
  actions: [
@@ -3021,9 +3059,10 @@ function objectGateQuestion(path) {
3021
3059
  ]
3022
3060
  };
3023
3061
  }
3024
- function optionalsGateQuestion(pending) {
3062
+ function optionalsGateQuestion(path, pending) {
3025
3063
  return {
3026
3064
  type: "collection",
3065
+ path,
3027
3066
  // The prompt and its context ride separately so a host renders the info
3028
3067
  // line above the confirm without composing any text of its own.
3029
3068
  message: "Would you like to configure optional fields?",
@@ -3173,8 +3212,12 @@ async function askLeaf(state, path, leaf, opts = {}) {
3173
3212
  state.current = path;
3174
3213
  delete state.gate;
3175
3214
  try {
3176
- const { question, listing } = await buildQuestion(leaf, state.resolved);
3177
- state.listing = listing;
3215
+ const { question, pagination } = await buildQuestion(
3216
+ leaf,
3217
+ path,
3218
+ state.resolved
3219
+ );
3220
+ state.pagination = pagination;
3178
3221
  return {
3179
3222
  state,
3180
3223
  result: {
@@ -3184,7 +3227,7 @@ async function askLeaf(state, path, leaf, opts = {}) {
3184
3227
  }
3185
3228
  };
3186
3229
  } catch (error) {
3187
- state.listing = { items: [], exhausted: false };
3230
+ state.pagination = failedPagination(void 0, firstPagePosition());
3188
3231
  return failedResult(state, leaf.name, error);
3189
3232
  }
3190
3233
  }
@@ -3194,13 +3237,13 @@ async function advance(ctx, state) {
3194
3237
  if (!target) {
3195
3238
  delete state.current;
3196
3239
  delete state.gate;
3197
- delete state.listing;
3240
+ delete state.pagination;
3198
3241
  return { state, result: finalize(ctx, state.resolved) };
3199
3242
  }
3200
3243
  if (target.kind === "array") {
3201
3244
  state.current = target.path;
3202
3245
  state.gate = "array";
3203
- delete state.listing;
3246
+ delete state.pagination;
3204
3247
  return {
3205
3248
  state,
3206
3249
  result: { status: "ask", question: collectionQuestion(target) }
@@ -3209,7 +3252,7 @@ async function advance(ctx, state) {
3209
3252
  if (target.kind === "object") {
3210
3253
  state.current = target.path;
3211
3254
  state.gate = "entry";
3212
- delete state.listing;
3255
+ delete state.pagination;
3213
3256
  return {
3214
3257
  state,
3215
3258
  result: { status: "ask", question: objectGateQuestion(target.path) }
@@ -3218,12 +3261,12 @@ async function advance(ctx, state) {
3218
3261
  if (target.kind === "optionals") {
3219
3262
  state.current = target.path;
3220
3263
  state.gate = "optionals";
3221
- delete state.listing;
3264
+ delete state.pagination;
3222
3265
  return {
3223
3266
  state,
3224
3267
  result: {
3225
3268
  status: "ask",
3226
- question: optionalsGateQuestion(target.pending)
3269
+ question: optionalsGateQuestion(target.path, target.pending)
3227
3270
  }
3228
3271
  };
3229
3272
  }
@@ -3274,13 +3317,13 @@ async function step(ctx, prior, action) {
3274
3317
  if (action.type === "cancel") {
3275
3318
  delete state.current;
3276
3319
  delete state.gate;
3277
- delete state.listing;
3320
+ delete state.pagination;
3278
3321
  return { state, result: { status: "cancelled" } };
3279
3322
  }
3280
3323
  const path = state.current;
3281
3324
  if (!path) throw new Error("step called with no outstanding question");
3282
3325
  const leaf = await leafAt(ctx, path, state.resolved);
3283
- if (leaf && (action.type === "search" || action.type === "more" || action.type === "retry")) {
3326
+ if (leaf && (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry")) {
3284
3327
  return refine(ctx, state, leaf, path, action);
3285
3328
  }
3286
3329
  if (action.type === "add" || action.type === "done") {
@@ -3292,7 +3335,7 @@ async function step(ctx, prior, action) {
3292
3335
  }
3293
3336
  delete state.current;
3294
3337
  delete state.gate;
3295
- delete state.listing;
3338
+ delete state.pagination;
3296
3339
  if (action.type === "done") {
3297
3340
  settle(state, path);
3298
3341
  return advance(ctx, state);
@@ -3321,23 +3364,44 @@ async function step(ctx, prior, action) {
3321
3364
  case "choose":
3322
3365
  case "custom": {
3323
3366
  if (leaf) {
3324
- const error = await validationError(leaf, action.value, state);
3367
+ let error;
3368
+ try {
3369
+ error = await validationError(leaf, action.value, state);
3370
+ } catch (thrown) {
3371
+ return failedResult(state, leaf.name, thrown);
3372
+ }
3325
3373
  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
- };
3374
+ if (state.pagination && leaf.resolver?.type === "dynamic") {
3375
+ try {
3376
+ const context = await resolveContext(leaf, state.resolved);
3377
+ const page = await fetchListing(
3378
+ leaf,
3379
+ state.resolved,
3380
+ state.pagination.position,
3381
+ context
3382
+ );
3383
+ state.pagination = toPagination(page);
3384
+ return {
3385
+ state,
3386
+ result: {
3387
+ status: "ask",
3388
+ question: selectQuestion(
3389
+ leaf,
3390
+ path,
3391
+ state.resolved,
3392
+ page,
3393
+ context
3394
+ ),
3395
+ error
3396
+ }
3397
+ };
3398
+ } catch (fetchError) {
3399
+ state.pagination = failedPagination(
3400
+ state.pagination,
3401
+ state.pagination.position
3402
+ );
3403
+ return failedResult(state, leaf.name, fetchError);
3404
+ }
3341
3405
  }
3342
3406
  return askLeaf(state, path, leaf, { error });
3343
3407
  }
@@ -3356,15 +3420,11 @@ async function step(ctx, prior, action) {
3356
3420
  throw new Error(`action "${action.type}" is not supported here`);
3357
3421
  }
3358
3422
  delete state.current;
3359
- delete state.listing;
3423
+ delete state.pagination;
3360
3424
  return advance(ctx, state);
3361
3425
  }
3362
3426
  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
- };
3427
+ const position = positionAfter(state.pagination, action);
3368
3428
  try {
3369
3429
  if (action.type === "search") {
3370
3430
  const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
@@ -3374,32 +3434,62 @@ async function refine(ctx, state, leaf, path, action) {
3374
3434
  if (exact) {
3375
3435
  setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3376
3436
  delete state.current;
3377
- delete state.listing;
3437
+ delete state.pagination;
3378
3438
  return advance(ctx, state);
3379
3439
  }
3380
3440
  }
3381
3441
  const context = await resolveContext(leaf, state.resolved);
3382
- state.listing = await fetchListing(leaf, state.resolved, {
3383
- ...attempt,
3384
- context
3385
- });
3442
+ const page = await fetchListing(leaf, state.resolved, position, context);
3443
+ state.pagination = toPagination(page);
3386
3444
  return {
3387
3445
  state,
3388
3446
  result: {
3389
3447
  status: "ask",
3390
- question: selectQuestion(leaf, state.resolved, state.listing, context)
3448
+ question: selectQuestion(leaf, path, state.resolved, page, context)
3391
3449
  }
3392
3450
  };
3393
3451
  } catch (error) {
3394
- state.listing = {
3395
- items: attempt.priorItems,
3396
- search: attempt.search,
3397
- cursor: attempt.cursor,
3398
- exhausted: false
3399
- };
3452
+ state.pagination = failedPagination(state.pagination, position);
3400
3453
  return failedResult(state, leaf.name, error);
3401
3454
  }
3402
3455
  }
3456
+ function failedPagination(pagination, retryPosition) {
3457
+ return {
3458
+ position: pagination?.position ?? firstPagePosition(),
3459
+ ...pagination?.nextCursor !== void 0 ? { nextCursor: pagination.nextCursor } : {},
3460
+ retryPosition
3461
+ };
3462
+ }
3463
+ function positionAfter(pagination, action) {
3464
+ const current = pagination?.position ?? firstPagePosition();
3465
+ switch (action.type) {
3466
+ case "search":
3467
+ return firstPagePosition({
3468
+ search: action.term,
3469
+ generation: current.generation + 1
3470
+ });
3471
+ case "next_page": {
3472
+ if (pagination?.nextCursor == null) return current;
3473
+ return {
3474
+ ...current.search !== void 0 ? { search: current.search } : {},
3475
+ pageCursor: pagination.nextCursor,
3476
+ previousCursors: [...current.previousCursors, current.pageCursor],
3477
+ generation: current.generation
3478
+ };
3479
+ }
3480
+ case "previous_page": {
3481
+ if (current.previousCursors.length === 0) return current;
3482
+ return {
3483
+ ...current.search !== void 0 ? { search: current.search } : {},
3484
+ pageCursor: current.previousCursors[current.previousCursors.length - 1],
3485
+ previousCursors: current.previousCursors.slice(0, -1),
3486
+ generation: current.generation
3487
+ };
3488
+ }
3489
+ case "retry":
3490
+ return pagination?.retryPosition ?? current;
3491
+ }
3492
+ }
3403
3493
 
3404
3494
  // src/model/resolution/controller.ts
3405
3495
  function toJsonSchema(schema) {
@@ -3581,6 +3671,7 @@ export {
3581
3671
  CoreSignal,
3582
3672
  addPlugin,
3583
3673
  composePlugins,
3674
+ concatLists,
3584
3675
  concatPaginated,
3585
3676
  coreOptionsPluginRef,
3586
3677
  createAsyncContext,