@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/CHANGELOG.md +24 -0
- package/dist/index.cjs +225 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +183 -54
- package/dist/index.d.ts +183 -54
- package/dist/index.mjs +224 -133
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @zapier/kitcore
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cec12cd: Added `concatLists`, which lists one page of several paginated lists joined end to end, returning a plain promise of `{ data, nextCursor }`. Pass a page's `nextCursor` to a fresh call to resume the concatenation.
|
|
8
|
+
|
|
9
|
+
Deprecated `concatPaginated` in favor of `concatLists`. It now delegates to `concatLists` and logs a deprecation warning; awaiting it yields the same page, but the page-iterable half of its old return shape is removed.
|
|
10
|
+
|
|
11
|
+
- 769751e: Paginated list results now expose `.pages()`, a plain async iterable over pages. Unlike iterating the result directly (which still works but is deprecated), `.pages()` is not also a promise, so returning it from an `async` function no longer silently collapses it to the first page. `.items()` is unchanged and remains the way to iterate individual items across pages. The `toIterable()` helper is deprecated in favor of `.pages()` and now logs a deprecation warning.
|
|
12
|
+
|
|
13
|
+
## 0.7.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 615fda9: The resolution controller now paginates dynamic selects as a page window: each `select` ask carries ONE page of choices plus a `page: { generation, index }` descriptor, so payloads stay proportional to a page.
|
|
18
|
+
- `next_page` and `previous_page` actions replace `more`.
|
|
19
|
+
- State carries coordinates only: `ControllerPagination` holds the cursor trail, never items, so the host carries cursors back between steps instead of the items it was just sent. An accumulating host appends pages client-side keyed on the question's `path` + `generation`, which increments whenever a `search` restarts the listing.
|
|
20
|
+
- Every question carries `path` (which field it is for), so hosts never read the state.
|
|
21
|
+
- Resolver `validate` is now a top-level callback (`validate({ imports, value, input, context })`), async and able to verify against the source; a thrown validate surfaces as a `failed` result with retry, not a rejection. A `validate` inside the prompt config is a type error (`ResolverPromptConfig` omits it), so it can't silently never run.
|
|
22
|
+
- The `exhausted` flag is removed; end-of-list is the absence of `next_page`.
|
|
23
|
+
- A failed fetch records its position so `retry` replays exactly the page that failed.
|
|
24
|
+
|
|
25
|
+
- e6b22fb: `concatPaginated` now paginates with stateless cursors: each source is called with its own cursor per page (`({ cursor }) => ...`), outgoing cursors encode the source index plus that source's cursor, and a new `cursor` option resumes the stream from a previous page's cursor with no shared in-memory state. The `dedupe` option is removed; sources must be disjoint by construction.
|
|
26
|
+
|
|
3
27
|
## 0.6.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
CoreSignal: () => CoreSignal,
|
|
32
32
|
addPlugin: () => addPlugin,
|
|
33
33
|
composePlugins: () => composePlugins,
|
|
34
|
+
concatLists: () => concatLists,
|
|
34
35
|
concatPaginated: () => concatPaginated,
|
|
35
36
|
coreOptionsPluginRef: () => coreOptionsPluginRef,
|
|
36
37
|
createAsyncContext: () => createAsyncContext,
|
|
@@ -301,7 +302,8 @@ function getCoreErrorCause(value) {
|
|
|
301
302
|
var CURSOR_VERSION = 1;
|
|
302
303
|
var CURSOR_SOURCE = {
|
|
303
304
|
API: "api",
|
|
304
|
-
SDK: "sdk"
|
|
305
|
+
SDK: "sdk",
|
|
306
|
+
CONCAT: "concat"
|
|
305
307
|
};
|
|
306
308
|
function encodeBase64(str) {
|
|
307
309
|
return btoa(
|
|
@@ -521,69 +523,71 @@ async function* paginateBuffered(pageFunction, pageOptions) {
|
|
|
521
523
|
}
|
|
522
524
|
}
|
|
523
525
|
var paginate = paginateBuffered;
|
|
524
|
-
function
|
|
526
|
+
function encodeConcatCursor(index, cursor) {
|
|
527
|
+
const envelope = {
|
|
528
|
+
v: CURSOR_VERSION,
|
|
529
|
+
source: CURSOR_SOURCE.CONCAT,
|
|
530
|
+
index,
|
|
531
|
+
cursor
|
|
532
|
+
};
|
|
533
|
+
return encodeBase64(JSON.stringify(envelope));
|
|
534
|
+
}
|
|
535
|
+
function decodeConcatCursor(incoming) {
|
|
536
|
+
if (!incoming) {
|
|
537
|
+
return { index: 0, cursor: void 0 };
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const envelope = JSON.parse(decodeBase64(incoming));
|
|
541
|
+
if (envelope.v === CURSOR_VERSION && envelope.source === CURSOR_SOURCE.CONCAT && typeof envelope.index === "number") {
|
|
542
|
+
return { index: envelope.index, cursor: envelope.cursor };
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
}
|
|
546
|
+
return { index: 0, cursor: incoming };
|
|
547
|
+
}
|
|
548
|
+
async function concatLists({
|
|
525
549
|
sources,
|
|
526
|
-
|
|
527
|
-
|
|
550
|
+
pageSize = 100,
|
|
551
|
+
cursor
|
|
528
552
|
}) {
|
|
529
553
|
if (sources.length === 0) {
|
|
530
|
-
|
|
531
|
-
return Object.assign(Promise.resolve(empty), {
|
|
532
|
-
[Symbol.asyncIterator]: async function* () {
|
|
533
|
-
yield empty;
|
|
534
|
-
}
|
|
535
|
-
});
|
|
554
|
+
return { data: [] };
|
|
536
555
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (!
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
const next = await currentIterator.next();
|
|
547
|
-
if (next.done) {
|
|
548
|
-
sourceIndex++;
|
|
549
|
-
currentIterator = null;
|
|
556
|
+
const pageFunction = async (options) => {
|
|
557
|
+
let { index, cursor: listCursor } = decodeConcatCursor(options.cursor);
|
|
558
|
+
while (index < sources.length) {
|
|
559
|
+
const page = await sources[index]({ cursor: listCursor });
|
|
560
|
+
const hasMoreInList = page.nextCursor != null;
|
|
561
|
+
if (page.data.length === 0 && !hasMoreInList) {
|
|
562
|
+
index++;
|
|
563
|
+
listCursor = void 0;
|
|
550
564
|
continue;
|
|
551
565
|
}
|
|
552
|
-
let items = next.value.data;
|
|
553
|
-
if (dedupe) {
|
|
554
|
-
if (sourceIndex > 0) {
|
|
555
|
-
items = items.filter((item) => !seen.has(dedupe(item)));
|
|
556
|
-
}
|
|
557
|
-
for (const item of items) {
|
|
558
|
-
seen.add(dedupe(item));
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
const hasMoreInSource = next.value.nextCursor != null;
|
|
562
|
-
const hasMoreSources = sourceIndex < sources.length - 1;
|
|
563
566
|
return {
|
|
564
|
-
data:
|
|
565
|
-
nextCursor:
|
|
567
|
+
data: page.data,
|
|
568
|
+
nextCursor: hasMoreInList ? encodeConcatCursor(index, page.nextCursor) : index < sources.length - 1 ? encodeConcatCursor(index + 1, void 0) : void 0
|
|
566
569
|
};
|
|
567
570
|
}
|
|
568
571
|
return { data: [] };
|
|
569
572
|
};
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
});
|
|
573
|
+
const result = await paginateBuffered(pageFunction, {
|
|
574
|
+
pageSize,
|
|
575
|
+
cursor
|
|
576
|
+
}).next();
|
|
577
|
+
return result.done ? { data: [] } : result.value;
|
|
578
|
+
}
|
|
579
|
+
function concatPaginated({
|
|
580
|
+
sources,
|
|
581
|
+
pageSize,
|
|
582
|
+
cursor
|
|
583
|
+
}) {
|
|
584
|
+
logDeprecation("concatPaginated() is deprecated. Use concatLists() instead.");
|
|
585
|
+
return concatLists({ sources, pageSize, cursor });
|
|
585
586
|
}
|
|
586
587
|
function toIterable(source) {
|
|
588
|
+
logDeprecation(
|
|
589
|
+
"toIterable() is deprecated. Call .pages() on the paginated result instead."
|
|
590
|
+
);
|
|
587
591
|
return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
|
|
588
592
|
}
|
|
589
593
|
|
|
@@ -936,6 +940,13 @@ function createPaginatedFunction(coreFn, options) {
|
|
|
936
940
|
[Symbol.asyncIterator]() {
|
|
937
941
|
return pageStream;
|
|
938
942
|
},
|
|
943
|
+
pages: function() {
|
|
944
|
+
return {
|
|
945
|
+
[Symbol.asyncIterator]() {
|
|
946
|
+
return pageStream;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
},
|
|
939
950
|
items: function() {
|
|
940
951
|
return {
|
|
941
952
|
[Symbol.asyncIterator]: async function* () {
|
|
@@ -1448,6 +1459,7 @@ function defineResolver(config) {
|
|
|
1448
1459
|
getContext: config.getContext,
|
|
1449
1460
|
listItems: config.listItems,
|
|
1450
1461
|
prompt: config.prompt,
|
|
1462
|
+
validate: config.validate,
|
|
1451
1463
|
tryResolveWithoutPrompt: config.tryResolveWithoutPrompt,
|
|
1452
1464
|
tryResolveFromSearch: config.tryResolveFromSearch
|
|
1453
1465
|
};
|
|
@@ -2106,6 +2118,7 @@ function bindResolver(resolver, plugins) {
|
|
|
2106
2118
|
const {
|
|
2107
2119
|
getContext: getContext2,
|
|
2108
2120
|
listItems,
|
|
2121
|
+
validate,
|
|
2109
2122
|
tryResolveWithoutPrompt,
|
|
2110
2123
|
tryResolveFromSearch
|
|
2111
2124
|
} = resolver;
|
|
@@ -2119,6 +2132,9 @@ function bindResolver(resolver, plugins) {
|
|
|
2119
2132
|
};
|
|
2120
2133
|
if (getContext2)
|
|
2121
2134
|
bound.getContext = ({ input }) => getContext2({ imports, input });
|
|
2135
|
+
if (validate) {
|
|
2136
|
+
bound.validate = ({ value, input, context }) => validate({ imports, value, input, context });
|
|
2137
|
+
}
|
|
2122
2138
|
if (tryResolveWithoutPrompt) {
|
|
2123
2139
|
bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
|
|
2124
2140
|
}
|
|
@@ -2798,14 +2814,14 @@ function coerce(leaf, raw) {
|
|
|
2798
2814
|
}
|
|
2799
2815
|
async function validationError(leaf, value, state) {
|
|
2800
2816
|
if (leaf.resolver?.type !== "dynamic") return null;
|
|
2817
|
+
const validate = leaf.resolver.validate;
|
|
2818
|
+
if (!validate) return null;
|
|
2801
2819
|
const context = await resolveContext(leaf, state.resolved);
|
|
2802
|
-
const
|
|
2803
|
-
|
|
2820
|
+
const verdict = await validate({
|
|
2821
|
+
value,
|
|
2804
2822
|
input: mergeInput(state.resolved, leaf.extraInput),
|
|
2805
2823
|
context
|
|
2806
2824
|
});
|
|
2807
|
-
if (!config?.validate) return null;
|
|
2808
|
-
const verdict = config.validate(value);
|
|
2809
2825
|
if (verdict === true) return null;
|
|
2810
2826
|
return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
|
|
2811
2827
|
}
|
|
@@ -2931,20 +2947,36 @@ async function resolveContext(leaf, input) {
|
|
|
2931
2947
|
input: mergeInput(input, leaf.extraInput)
|
|
2932
2948
|
});
|
|
2933
2949
|
}
|
|
2934
|
-
|
|
2950
|
+
function firstPagePosition(opts = {}) {
|
|
2951
|
+
return {
|
|
2952
|
+
...opts.search !== void 0 ? { search: opts.search } : {},
|
|
2953
|
+
pageCursor: null,
|
|
2954
|
+
previousCursors: [],
|
|
2955
|
+
generation: opts.generation ?? 0
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
async function fetchListing(leaf, input, position, context) {
|
|
2959
|
+
if (leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search" && position.search === void 0) {
|
|
2960
|
+
return { position, items: [] };
|
|
2961
|
+
}
|
|
2935
2962
|
const page = await firstPage(
|
|
2936
2963
|
leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
|
|
2937
2964
|
input: mergeInput(input, leaf.extraInput),
|
|
2938
|
-
context
|
|
2939
|
-
search:
|
|
2940
|
-
cursor:
|
|
2965
|
+
context,
|
|
2966
|
+
search: position.search,
|
|
2967
|
+
cursor: position.pageCursor ?? void 0
|
|
2941
2968
|
}) : void 0
|
|
2942
2969
|
);
|
|
2943
2970
|
return {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2971
|
+
position,
|
|
2972
|
+
items: page.data,
|
|
2973
|
+
...page.nextCursor != null ? { nextCursor: page.nextCursor } : {}
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
function toPagination(page) {
|
|
2977
|
+
return {
|
|
2978
|
+
position: page.position,
|
|
2979
|
+
...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {}
|
|
2948
2980
|
};
|
|
2949
2981
|
}
|
|
2950
2982
|
|
|
@@ -2966,23 +2998,32 @@ var AFFORDANCE = {
|
|
|
2966
2998
|
description: "Filter the options by a search term",
|
|
2967
2999
|
supply: "term"
|
|
2968
3000
|
},
|
|
2969
|
-
|
|
3001
|
+
nextPage: {
|
|
3002
|
+
action: "next_page",
|
|
3003
|
+
description: "Fetch the next page of options"
|
|
3004
|
+
},
|
|
3005
|
+
previousPage: {
|
|
3006
|
+
action: "previous_page",
|
|
3007
|
+
description: "Return to the previous page of options"
|
|
3008
|
+
},
|
|
2970
3009
|
skip: { action: "skip", description: "Omit this optional parameter" },
|
|
2971
3010
|
add: { action: "add", description: "Add another item" },
|
|
2972
3011
|
done: { action: "done", description: "Finish the list" },
|
|
2973
3012
|
retry: { action: "retry", description: "Retry loading the options" },
|
|
2974
3013
|
cancel: { action: "cancel", description: "Cancel resolution" }
|
|
2975
3014
|
};
|
|
2976
|
-
function selectActions(leaf,
|
|
3015
|
+
function selectActions(leaf, page, multiple) {
|
|
2977
3016
|
const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
|
|
2978
|
-
if (searchMode &&
|
|
3017
|
+
if (searchMode && page.position.search === void 0 && page.items.length === 0) {
|
|
2979
3018
|
const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
|
|
2980
3019
|
if (!leaf.required) actions2.push(AFFORDANCE.skip);
|
|
2981
3020
|
return actions2;
|
|
2982
3021
|
}
|
|
2983
3022
|
const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
|
|
2984
3023
|
if (searchMode) actions.push(AFFORDANCE.search);
|
|
2985
|
-
if (
|
|
3024
|
+
if (page.nextCursor) actions.push(AFFORDANCE.nextPage);
|
|
3025
|
+
if (page.position.previousCursors.length > 0)
|
|
3026
|
+
actions.push(AFFORDANCE.previousPage);
|
|
2986
3027
|
if (!leaf.required) actions.push(AFFORDANCE.skip);
|
|
2987
3028
|
return actions;
|
|
2988
3029
|
}
|
|
@@ -2990,16 +3031,17 @@ function labeledMessage(leaf) {
|
|
|
2990
3031
|
if (!leaf.label) return void 0;
|
|
2991
3032
|
return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
|
|
2992
3033
|
}
|
|
2993
|
-
function selectQuestion(leaf, input,
|
|
3034
|
+
function selectQuestion(leaf, path, input, page, context) {
|
|
2994
3035
|
const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
|
|
2995
3036
|
const config = resolver?.prompt?.({
|
|
2996
|
-
items:
|
|
3037
|
+
items: page.items,
|
|
2997
3038
|
input: mergeInput(input, leaf.extraInput),
|
|
2998
3039
|
context
|
|
2999
3040
|
});
|
|
3000
3041
|
const multiple = config?.type === "checkbox";
|
|
3001
3042
|
return {
|
|
3002
3043
|
type: "select",
|
|
3044
|
+
path,
|
|
3003
3045
|
// A labeled field's title beats the resolver's message: per-field
|
|
3004
3046
|
// resolvers are shared across fields (one choices-fetcher for every
|
|
3005
3047
|
// field), so only the leaf knows which field is being asked.
|
|
@@ -3007,9 +3049,13 @@ function selectQuestion(leaf, input, listing, context) {
|
|
|
3007
3049
|
choices: (config?.choices ?? []).map(toChoice),
|
|
3008
3050
|
...multiple ? { multiple: true } : {},
|
|
3009
3051
|
...config?.notes?.length ? { notes: config.notes } : {},
|
|
3010
|
-
...
|
|
3052
|
+
...page.position.search !== void 0 ? { search: page.position.search } : {},
|
|
3011
3053
|
...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
|
|
3012
|
-
|
|
3054
|
+
page: {
|
|
3055
|
+
generation: page.position.generation,
|
|
3056
|
+
index: page.position.previousCursors.length
|
|
3057
|
+
},
|
|
3058
|
+
actions: selectActions(leaf, page, multiple)
|
|
3013
3059
|
};
|
|
3014
3060
|
}
|
|
3015
3061
|
function toControllerError(error) {
|
|
@@ -3031,6 +3077,7 @@ function failedResult(state, name, error) {
|
|
|
3031
3077
|
error: toControllerError(error),
|
|
3032
3078
|
question: {
|
|
3033
3079
|
type: "select",
|
|
3080
|
+
path: state.current ?? [],
|
|
3034
3081
|
message: `Could not load options for ${name}.`,
|
|
3035
3082
|
choices: [],
|
|
3036
3083
|
actions: [AFFORDANCE.retry, AFFORDANCE.cancel]
|
|
@@ -3038,27 +3085,15 @@ function failedResult(state, name, error) {
|
|
|
3038
3085
|
}
|
|
3039
3086
|
};
|
|
3040
3087
|
}
|
|
3041
|
-
async function buildQuestion(leaf, input) {
|
|
3088
|
+
async function buildQuestion(leaf, path, input) {
|
|
3042
3089
|
const optional = !leaf.required;
|
|
3043
3090
|
const resolver = leaf.resolver;
|
|
3044
3091
|
if (resolver?.type === "dynamic") {
|
|
3045
3092
|
const context = await resolveContext(leaf, input);
|
|
3046
|
-
|
|
3047
|
-
const listing2 = {
|
|
3048
|
-
items: [],
|
|
3049
|
-
cursor: void 0,
|
|
3050
|
-
search: void 0,
|
|
3051
|
-
exhausted: true
|
|
3052
|
-
};
|
|
3053
|
-
return {
|
|
3054
|
-
question: selectQuestion(leaf, input, listing2, context),
|
|
3055
|
-
listing: listing2
|
|
3056
|
-
};
|
|
3057
|
-
}
|
|
3058
|
-
const listing = await fetchListing(leaf, input, { context });
|
|
3093
|
+
const page = await fetchListing(leaf, input, firstPagePosition(), context);
|
|
3059
3094
|
return {
|
|
3060
|
-
question: selectQuestion(leaf, input,
|
|
3061
|
-
|
|
3095
|
+
question: selectQuestion(leaf, path, input, page, context),
|
|
3096
|
+
pagination: toPagination(page)
|
|
3062
3097
|
};
|
|
3063
3098
|
}
|
|
3064
3099
|
if (leaf.staticChoices) {
|
|
@@ -3067,6 +3102,7 @@ async function buildQuestion(leaf, input) {
|
|
|
3067
3102
|
return {
|
|
3068
3103
|
question: {
|
|
3069
3104
|
type: "select",
|
|
3105
|
+
path,
|
|
3070
3106
|
message: `Select ${leaf.name}:`,
|
|
3071
3107
|
choices: leaf.staticChoices,
|
|
3072
3108
|
actions: actions2
|
|
@@ -3080,6 +3116,7 @@ async function buildQuestion(leaf, input) {
|
|
|
3080
3116
|
return {
|
|
3081
3117
|
question: {
|
|
3082
3118
|
type: "input",
|
|
3119
|
+
path,
|
|
3083
3120
|
// The optional marker makes Enter-to-pass discoverable on a bare
|
|
3084
3121
|
// parameter; a labeled field carries its marker via labeledMessage.
|
|
3085
3122
|
message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
|
|
@@ -3094,6 +3131,7 @@ function collectionQuestion(t) {
|
|
|
3094
3131
|
if (t.count >= t.min) actions.push(AFFORDANCE.done);
|
|
3095
3132
|
return {
|
|
3096
3133
|
type: "collection",
|
|
3134
|
+
path: t.path,
|
|
3097
3135
|
message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
|
|
3098
3136
|
container: "array",
|
|
3099
3137
|
count: t.count,
|
|
@@ -3107,6 +3145,7 @@ function collectionQuestion(t) {
|
|
|
3107
3145
|
function objectGateQuestion(path) {
|
|
3108
3146
|
return {
|
|
3109
3147
|
type: "collection",
|
|
3148
|
+
path,
|
|
3110
3149
|
message: `Add ${path[path.length - 1]}?`,
|
|
3111
3150
|
container: "object",
|
|
3112
3151
|
actions: [
|
|
@@ -3118,9 +3157,10 @@ function objectGateQuestion(path) {
|
|
|
3118
3157
|
]
|
|
3119
3158
|
};
|
|
3120
3159
|
}
|
|
3121
|
-
function optionalsGateQuestion(pending) {
|
|
3160
|
+
function optionalsGateQuestion(path, pending) {
|
|
3122
3161
|
return {
|
|
3123
3162
|
type: "collection",
|
|
3163
|
+
path,
|
|
3124
3164
|
// The prompt and its context ride separately so a host renders the info
|
|
3125
3165
|
// line above the confirm without composing any text of its own.
|
|
3126
3166
|
message: "Would you like to configure optional fields?",
|
|
@@ -3270,8 +3310,12 @@ async function askLeaf(state, path, leaf, opts = {}) {
|
|
|
3270
3310
|
state.current = path;
|
|
3271
3311
|
delete state.gate;
|
|
3272
3312
|
try {
|
|
3273
|
-
const { question,
|
|
3274
|
-
|
|
3313
|
+
const { question, pagination } = await buildQuestion(
|
|
3314
|
+
leaf,
|
|
3315
|
+
path,
|
|
3316
|
+
state.resolved
|
|
3317
|
+
);
|
|
3318
|
+
state.pagination = pagination;
|
|
3275
3319
|
return {
|
|
3276
3320
|
state,
|
|
3277
3321
|
result: {
|
|
@@ -3281,7 +3325,7 @@ async function askLeaf(state, path, leaf, opts = {}) {
|
|
|
3281
3325
|
}
|
|
3282
3326
|
};
|
|
3283
3327
|
} catch (error) {
|
|
3284
|
-
state.
|
|
3328
|
+
state.pagination = failedPagination(void 0, firstPagePosition());
|
|
3285
3329
|
return failedResult(state, leaf.name, error);
|
|
3286
3330
|
}
|
|
3287
3331
|
}
|
|
@@ -3291,13 +3335,13 @@ async function advance(ctx, state) {
|
|
|
3291
3335
|
if (!target) {
|
|
3292
3336
|
delete state.current;
|
|
3293
3337
|
delete state.gate;
|
|
3294
|
-
delete state.
|
|
3338
|
+
delete state.pagination;
|
|
3295
3339
|
return { state, result: finalize(ctx, state.resolved) };
|
|
3296
3340
|
}
|
|
3297
3341
|
if (target.kind === "array") {
|
|
3298
3342
|
state.current = target.path;
|
|
3299
3343
|
state.gate = "array";
|
|
3300
|
-
delete state.
|
|
3344
|
+
delete state.pagination;
|
|
3301
3345
|
return {
|
|
3302
3346
|
state,
|
|
3303
3347
|
result: { status: "ask", question: collectionQuestion(target) }
|
|
@@ -3306,7 +3350,7 @@ async function advance(ctx, state) {
|
|
|
3306
3350
|
if (target.kind === "object") {
|
|
3307
3351
|
state.current = target.path;
|
|
3308
3352
|
state.gate = "entry";
|
|
3309
|
-
delete state.
|
|
3353
|
+
delete state.pagination;
|
|
3310
3354
|
return {
|
|
3311
3355
|
state,
|
|
3312
3356
|
result: { status: "ask", question: objectGateQuestion(target.path) }
|
|
@@ -3315,12 +3359,12 @@ async function advance(ctx, state) {
|
|
|
3315
3359
|
if (target.kind === "optionals") {
|
|
3316
3360
|
state.current = target.path;
|
|
3317
3361
|
state.gate = "optionals";
|
|
3318
|
-
delete state.
|
|
3362
|
+
delete state.pagination;
|
|
3319
3363
|
return {
|
|
3320
3364
|
state,
|
|
3321
3365
|
result: {
|
|
3322
3366
|
status: "ask",
|
|
3323
|
-
question: optionalsGateQuestion(target.pending)
|
|
3367
|
+
question: optionalsGateQuestion(target.path, target.pending)
|
|
3324
3368
|
}
|
|
3325
3369
|
};
|
|
3326
3370
|
}
|
|
@@ -3371,13 +3415,13 @@ async function step(ctx, prior, action) {
|
|
|
3371
3415
|
if (action.type === "cancel") {
|
|
3372
3416
|
delete state.current;
|
|
3373
3417
|
delete state.gate;
|
|
3374
|
-
delete state.
|
|
3418
|
+
delete state.pagination;
|
|
3375
3419
|
return { state, result: { status: "cancelled" } };
|
|
3376
3420
|
}
|
|
3377
3421
|
const path = state.current;
|
|
3378
3422
|
if (!path) throw new Error("step called with no outstanding question");
|
|
3379
3423
|
const leaf = await leafAt(ctx, path, state.resolved);
|
|
3380
|
-
if (leaf && (action.type === "search" || action.type === "
|
|
3424
|
+
if (leaf && (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry")) {
|
|
3381
3425
|
return refine(ctx, state, leaf, path, action);
|
|
3382
3426
|
}
|
|
3383
3427
|
if (action.type === "add" || action.type === "done") {
|
|
@@ -3389,7 +3433,7 @@ async function step(ctx, prior, action) {
|
|
|
3389
3433
|
}
|
|
3390
3434
|
delete state.current;
|
|
3391
3435
|
delete state.gate;
|
|
3392
|
-
delete state.
|
|
3436
|
+
delete state.pagination;
|
|
3393
3437
|
if (action.type === "done") {
|
|
3394
3438
|
settle(state, path);
|
|
3395
3439
|
return advance(ctx, state);
|
|
@@ -3418,23 +3462,44 @@ async function step(ctx, prior, action) {
|
|
|
3418
3462
|
case "choose":
|
|
3419
3463
|
case "custom": {
|
|
3420
3464
|
if (leaf) {
|
|
3421
|
-
|
|
3465
|
+
let error;
|
|
3466
|
+
try {
|
|
3467
|
+
error = await validationError(leaf, action.value, state);
|
|
3468
|
+
} catch (thrown) {
|
|
3469
|
+
return failedResult(state, leaf.name, thrown);
|
|
3470
|
+
}
|
|
3422
3471
|
if (error) {
|
|
3423
|
-
if (state.
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3472
|
+
if (state.pagination && leaf.resolver?.type === "dynamic") {
|
|
3473
|
+
try {
|
|
3474
|
+
const context = await resolveContext(leaf, state.resolved);
|
|
3475
|
+
const page = await fetchListing(
|
|
3476
|
+
leaf,
|
|
3477
|
+
state.resolved,
|
|
3478
|
+
state.pagination.position,
|
|
3479
|
+
context
|
|
3480
|
+
);
|
|
3481
|
+
state.pagination = toPagination(page);
|
|
3482
|
+
return {
|
|
3483
|
+
state,
|
|
3484
|
+
result: {
|
|
3485
|
+
status: "ask",
|
|
3486
|
+
question: selectQuestion(
|
|
3487
|
+
leaf,
|
|
3488
|
+
path,
|
|
3489
|
+
state.resolved,
|
|
3490
|
+
page,
|
|
3491
|
+
context
|
|
3492
|
+
),
|
|
3493
|
+
error
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
} catch (fetchError) {
|
|
3497
|
+
state.pagination = failedPagination(
|
|
3498
|
+
state.pagination,
|
|
3499
|
+
state.pagination.position
|
|
3500
|
+
);
|
|
3501
|
+
return failedResult(state, leaf.name, fetchError);
|
|
3502
|
+
}
|
|
3438
3503
|
}
|
|
3439
3504
|
return askLeaf(state, path, leaf, { error });
|
|
3440
3505
|
}
|
|
@@ -3453,15 +3518,11 @@ async function step(ctx, prior, action) {
|
|
|
3453
3518
|
throw new Error(`action "${action.type}" is not supported here`);
|
|
3454
3519
|
}
|
|
3455
3520
|
delete state.current;
|
|
3456
|
-
delete state.
|
|
3521
|
+
delete state.pagination;
|
|
3457
3522
|
return advance(ctx, state);
|
|
3458
3523
|
}
|
|
3459
3524
|
async function refine(ctx, state, leaf, path, action) {
|
|
3460
|
-
const
|
|
3461
|
-
search: state.listing?.search,
|
|
3462
|
-
cursor: state.listing?.cursor,
|
|
3463
|
-
priorItems: state.listing?.items ?? []
|
|
3464
|
-
};
|
|
3525
|
+
const position = positionAfter(state.pagination, action);
|
|
3465
3526
|
try {
|
|
3466
3527
|
if (action.type === "search") {
|
|
3467
3528
|
const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
|
|
@@ -3471,32 +3532,62 @@ async function refine(ctx, state, leaf, path, action) {
|
|
|
3471
3532
|
if (exact) {
|
|
3472
3533
|
setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
|
|
3473
3534
|
delete state.current;
|
|
3474
|
-
delete state.
|
|
3535
|
+
delete state.pagination;
|
|
3475
3536
|
return advance(ctx, state);
|
|
3476
3537
|
}
|
|
3477
3538
|
}
|
|
3478
3539
|
const context = await resolveContext(leaf, state.resolved);
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
context
|
|
3482
|
-
});
|
|
3540
|
+
const page = await fetchListing(leaf, state.resolved, position, context);
|
|
3541
|
+
state.pagination = toPagination(page);
|
|
3483
3542
|
return {
|
|
3484
3543
|
state,
|
|
3485
3544
|
result: {
|
|
3486
3545
|
status: "ask",
|
|
3487
|
-
question: selectQuestion(leaf, state.resolved,
|
|
3546
|
+
question: selectQuestion(leaf, path, state.resolved, page, context)
|
|
3488
3547
|
}
|
|
3489
3548
|
};
|
|
3490
3549
|
} catch (error) {
|
|
3491
|
-
state.
|
|
3492
|
-
items: attempt.priorItems,
|
|
3493
|
-
search: attempt.search,
|
|
3494
|
-
cursor: attempt.cursor,
|
|
3495
|
-
exhausted: false
|
|
3496
|
-
};
|
|
3550
|
+
state.pagination = failedPagination(state.pagination, position);
|
|
3497
3551
|
return failedResult(state, leaf.name, error);
|
|
3498
3552
|
}
|
|
3499
3553
|
}
|
|
3554
|
+
function failedPagination(pagination, retryPosition) {
|
|
3555
|
+
return {
|
|
3556
|
+
position: pagination?.position ?? firstPagePosition(),
|
|
3557
|
+
...pagination?.nextCursor !== void 0 ? { nextCursor: pagination.nextCursor } : {},
|
|
3558
|
+
retryPosition
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
function positionAfter(pagination, action) {
|
|
3562
|
+
const current = pagination?.position ?? firstPagePosition();
|
|
3563
|
+
switch (action.type) {
|
|
3564
|
+
case "search":
|
|
3565
|
+
return firstPagePosition({
|
|
3566
|
+
search: action.term,
|
|
3567
|
+
generation: current.generation + 1
|
|
3568
|
+
});
|
|
3569
|
+
case "next_page": {
|
|
3570
|
+
if (pagination?.nextCursor == null) return current;
|
|
3571
|
+
return {
|
|
3572
|
+
...current.search !== void 0 ? { search: current.search } : {},
|
|
3573
|
+
pageCursor: pagination.nextCursor,
|
|
3574
|
+
previousCursors: [...current.previousCursors, current.pageCursor],
|
|
3575
|
+
generation: current.generation
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3578
|
+
case "previous_page": {
|
|
3579
|
+
if (current.previousCursors.length === 0) return current;
|
|
3580
|
+
return {
|
|
3581
|
+
...current.search !== void 0 ? { search: current.search } : {},
|
|
3582
|
+
pageCursor: current.previousCursors[current.previousCursors.length - 1],
|
|
3583
|
+
previousCursors: current.previousCursors.slice(0, -1),
|
|
3584
|
+
generation: current.generation
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
case "retry":
|
|
3588
|
+
return pagination?.retryPosition ?? current;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3500
3591
|
|
|
3501
3592
|
// src/model/resolution/controller.ts
|
|
3502
3593
|
function toJsonSchema(schema) {
|
|
@@ -3679,6 +3770,7 @@ function openEnum(values, description) {
|
|
|
3679
3770
|
CoreSignal,
|
|
3680
3771
|
addPlugin,
|
|
3681
3772
|
composePlugins,
|
|
3773
|
+
concatLists,
|
|
3682
3774
|
concatPaginated,
|
|
3683
3775
|
coreOptionsPluginRef,
|
|
3684
3776
|
createAsyncContext,
|