@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/CHANGELOG.md +20 -0
- package/README.md +7 -2
- package/dist/index.cjs +200 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +154 -39
- package/dist/index.d.ts +154 -39
- package/dist/index.mjs +200 -115
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (!
|
|
444
|
-
|
|
445
|
-
|
|
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:
|
|
466
|
-
nextCursor: hasMoreInSource
|
|
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
|
|
2708
|
-
|
|
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
|
-
|
|
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
|
|
2844
|
-
search:
|
|
2845
|
-
cursor:
|
|
2864
|
+
context,
|
|
2865
|
+
search: position.search,
|
|
2866
|
+
cursor: position.pageCursor ?? void 0
|
|
2846
2867
|
}) : void 0
|
|
2847
2868
|
);
|
|
2848
2869
|
return {
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
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
|
-
|
|
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,
|
|
2914
|
+
function selectActions(leaf, page, multiple) {
|
|
2882
2915
|
const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
|
|
2883
|
-
if (searchMode &&
|
|
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 (
|
|
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,
|
|
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:
|
|
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
|
-
...
|
|
2951
|
+
...page.position.search !== void 0 ? { search: page.position.search } : {},
|
|
2916
2952
|
...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
|
|
2917
|
-
|
|
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
|
-
|
|
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,
|
|
2966
|
-
|
|
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,
|
|
3179
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 === "
|
|
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.
|
|
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
|
-
|
|
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.
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
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.
|
|
3420
|
+
delete state.pagination;
|
|
3362
3421
|
return advance(ctx, state);
|
|
3363
3422
|
}
|
|
3364
3423
|
async function refine(ctx, state, leaf, path, action) {
|
|
3365
|
-
const
|
|
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.
|
|
3434
|
+
delete state.pagination;
|
|
3380
3435
|
return advance(ctx, state);
|
|
3381
3436
|
}
|
|
3382
3437
|
}
|
|
3383
3438
|
const context = await resolveContext(leaf, state.resolved);
|
|
3384
|
-
|
|
3385
|
-
|
|
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,
|
|
3445
|
+
question: selectQuestion(leaf, path, state.resolved, page, context)
|
|
3393
3446
|
}
|
|
3394
3447
|
};
|
|
3395
3448
|
} catch (error) {
|
|
3396
|
-
state.
|
|
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) {
|