@zapier/kitcore 0.0.0 → 0.5.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 +96 -0
- package/LICENSE +2 -0
- package/README.md +426 -2
- package/dist/index.cjs +3744 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +3383 -0
- package/dist/index.d.ts +3383 -0
- package/dist/index.mjs +3646 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -5
- package/index.mjs +0 -3
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3646 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/utils/string-utils.ts
|
|
5
|
+
function toTitleCase(input) {
|
|
6
|
+
return input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").replace(/\s+/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
7
|
+
}
|
|
8
|
+
function toSnakeCase(input) {
|
|
9
|
+
let result = input.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s\-]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
10
|
+
if (/^[0-9]/.test(result)) {
|
|
11
|
+
result = "_" + result;
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
function pluralize(word) {
|
|
16
|
+
if (/s$/i.test(word)) return word;
|
|
17
|
+
if (/[bcdfghjklmnpqrstvwxz]y$/i.test(word)) {
|
|
18
|
+
return word.slice(0, -1) + "ies";
|
|
19
|
+
}
|
|
20
|
+
return word + "s";
|
|
21
|
+
}
|
|
22
|
+
function pluralizeLastWord(title) {
|
|
23
|
+
const words = title.split(" ");
|
|
24
|
+
return [...words.slice(0, -1), pluralize(words[words.length - 1])].join(" ");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/registry.ts
|
|
28
|
+
function resolveCategoryDefinition(ref) {
|
|
29
|
+
const def = typeof ref === "string" ? { key: ref } : ref;
|
|
30
|
+
const title = def.title ?? toTitleCase(def.key);
|
|
31
|
+
return {
|
|
32
|
+
key: def.key,
|
|
33
|
+
title,
|
|
34
|
+
titlePlural: def.titlePlural ?? pluralizeLastWord(title)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function canonicalInputSchema(schema) {
|
|
38
|
+
if (schema instanceof z.ZodUnion) {
|
|
39
|
+
return schema.options[0];
|
|
40
|
+
}
|
|
41
|
+
return schema;
|
|
42
|
+
}
|
|
43
|
+
function buildRegistry({
|
|
44
|
+
sdk,
|
|
45
|
+
meta,
|
|
46
|
+
formatters,
|
|
47
|
+
boundResolvers,
|
|
48
|
+
positional,
|
|
49
|
+
packageFilter
|
|
50
|
+
}) {
|
|
51
|
+
const definitionsByKey = /* @__PURE__ */ new Map();
|
|
52
|
+
const objectDeclaredKeys = /* @__PURE__ */ new Set();
|
|
53
|
+
for (const m of Object.values(meta)) {
|
|
54
|
+
for (const ref of m.categories ?? []) {
|
|
55
|
+
const key2 = typeof ref === "string" ? ref : ref.key;
|
|
56
|
+
if (typeof ref === "object") {
|
|
57
|
+
objectDeclaredKeys.add(key2);
|
|
58
|
+
definitionsByKey.set(key2, resolveCategoryDefinition(ref));
|
|
59
|
+
} else if (!objectDeclaredKeys.has(key2)) {
|
|
60
|
+
definitionsByKey.set(key2, resolveCategoryDefinition(ref));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!definitionsByKey.has("other")) {
|
|
65
|
+
definitionsByKey.set("other", resolveCategoryDefinition("other"));
|
|
66
|
+
}
|
|
67
|
+
const knownCategories = Array.from(definitionsByKey.keys());
|
|
68
|
+
const functions = Object.keys(meta).filter((key2) => {
|
|
69
|
+
const property = sdk[key2];
|
|
70
|
+
if (typeof property === "function") return true;
|
|
71
|
+
const [rootKey] = key2.split(".");
|
|
72
|
+
const rootProperty = sdk[rootKey];
|
|
73
|
+
return typeof rootProperty === "object" && rootProperty !== null;
|
|
74
|
+
}).map((key2) => {
|
|
75
|
+
const m = meta[key2];
|
|
76
|
+
return {
|
|
77
|
+
name: key2,
|
|
78
|
+
description: m.description,
|
|
79
|
+
type: m.type,
|
|
80
|
+
itemType: m.itemType,
|
|
81
|
+
returnType: m.returnType,
|
|
82
|
+
inputSchema: canonicalInputSchema(m.inputSchema),
|
|
83
|
+
inputParameters: m.inputParameters,
|
|
84
|
+
outputSchema: m.outputSchema,
|
|
85
|
+
positional: positional?.[key2],
|
|
86
|
+
categories: (m.categories ?? []).map(
|
|
87
|
+
(c) => typeof c === "string" ? c : c.key
|
|
88
|
+
),
|
|
89
|
+
resolvers: m.resolvers,
|
|
90
|
+
boundResolvers: boundResolvers?.[key2],
|
|
91
|
+
formatter: formatters?.[key2],
|
|
92
|
+
experimental: m.experimental,
|
|
93
|
+
packages: m.packages,
|
|
94
|
+
confirm: m.confirm ?? (m.type === "delete" ? "delete" : void 0),
|
|
95
|
+
deprecation: m.deprecation,
|
|
96
|
+
aliases: m.aliases,
|
|
97
|
+
supportsJsonOutput: m.supportsJsonOutput ?? true
|
|
98
|
+
};
|
|
99
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
+
const filteredFunctions = packageFilter ? functions.filter((f) => !f.packages || f.packages.includes(packageFilter)) : functions;
|
|
101
|
+
const filteredCategories = knownCategories.slice().sort((a, b) => {
|
|
102
|
+
if (a === "other") return 1;
|
|
103
|
+
if (b === "other") return -1;
|
|
104
|
+
return definitionsByKey.get(a).title.localeCompare(definitionsByKey.get(b).title);
|
|
105
|
+
}).map((categoryKey) => {
|
|
106
|
+
const categoryFunctions = filteredFunctions.filter(
|
|
107
|
+
(f) => f.categories.includes(categoryKey) || categoryKey === "other" && !f.categories.some((c) => knownCategories.includes(c))
|
|
108
|
+
).map((f) => f.name).sort();
|
|
109
|
+
const def = definitionsByKey.get(categoryKey);
|
|
110
|
+
return {
|
|
111
|
+
key: categoryKey,
|
|
112
|
+
title: def.title,
|
|
113
|
+
titlePlural: def.titlePlural,
|
|
114
|
+
functions: categoryFunctions
|
|
115
|
+
};
|
|
116
|
+
}).filter((category) => category.functions.length > 0);
|
|
117
|
+
return { functions: filteredFunctions, categories: filteredCategories };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/utils/build-hooks.ts
|
|
121
|
+
function composeVoid(existing, added) {
|
|
122
|
+
if (!existing) return added;
|
|
123
|
+
if (!added) return existing;
|
|
124
|
+
return (ctx) => {
|
|
125
|
+
existing(ctx);
|
|
126
|
+
added(ctx);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function buildHooks(existing, added) {
|
|
130
|
+
const result = {};
|
|
131
|
+
const start2 = composeVoid(existing.onMethodStart, added.onMethodStart);
|
|
132
|
+
if (start2) result.onMethodStart = start2;
|
|
133
|
+
const end = composeVoid(existing.onMethodEnd, added.onMethodEnd);
|
|
134
|
+
if (end) result.onMethodEnd = end;
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/utils/logging.ts
|
|
139
|
+
function createDeprecationLogger(tag) {
|
|
140
|
+
const loggedDeprecations = /* @__PURE__ */ new Set();
|
|
141
|
+
return {
|
|
142
|
+
logDeprecation(message) {
|
|
143
|
+
if (loggedDeprecations.has(message)) return;
|
|
144
|
+
loggedDeprecations.add(message);
|
|
145
|
+
console.warn(`[${tag}] Deprecation: ${message}`);
|
|
146
|
+
},
|
|
147
|
+
resetDeprecationWarnings() {
|
|
148
|
+
loggedDeprecations.clear();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
var { logDeprecation, resetDeprecationWarnings } = createDeprecationLogger("core");
|
|
153
|
+
|
|
154
|
+
// src/types/errors.ts
|
|
155
|
+
var CORE_ERROR_SYMBOL = Symbol.for("kitcore.error");
|
|
156
|
+
var CoreErrorCode = {
|
|
157
|
+
Validation: "VALIDATION_ERROR",
|
|
158
|
+
Unknown: "UNKNOWN_ERROR"
|
|
159
|
+
};
|
|
160
|
+
var CoreError = class extends Error {
|
|
161
|
+
constructor(message, options = {}) {
|
|
162
|
+
super(message);
|
|
163
|
+
this.name = "CoreError";
|
|
164
|
+
if (options.statusCode !== void 0) this.statusCode = options.statusCode;
|
|
165
|
+
if (options.errors !== void 0) this.errors = options.errors;
|
|
166
|
+
if (options.cause !== void 0) this.cause = options.cause;
|
|
167
|
+
if (options.response !== void 0) this.response = options.response;
|
|
168
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function createCoreError(options, adaptError) {
|
|
172
|
+
const error = adaptError?.(options) ?? new CoreError(options.message, { cause: options.cause });
|
|
173
|
+
Object.defineProperty(error, CORE_ERROR_SYMBOL, {
|
|
174
|
+
value: true,
|
|
175
|
+
enumerable: false,
|
|
176
|
+
configurable: true,
|
|
177
|
+
writable: false
|
|
178
|
+
});
|
|
179
|
+
Object.defineProperty(error, "coreCode", {
|
|
180
|
+
value: options.code,
|
|
181
|
+
enumerable: false,
|
|
182
|
+
configurable: true,
|
|
183
|
+
writable: false
|
|
184
|
+
});
|
|
185
|
+
return error;
|
|
186
|
+
}
|
|
187
|
+
function isCoreError(value) {
|
|
188
|
+
return Boolean(
|
|
189
|
+
value && typeof value === "object" && value[CORE_ERROR_SYMBOL] === true
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function getCoreErrorCode(value) {
|
|
193
|
+
if (!isCoreError(value)) return void 0;
|
|
194
|
+
return value.coreCode;
|
|
195
|
+
}
|
|
196
|
+
function getCoreErrorCause(value) {
|
|
197
|
+
if (!isCoreError(value)) return void 0;
|
|
198
|
+
return value.cause;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/utils/pagination-utils.ts
|
|
202
|
+
var CURSOR_VERSION = 1;
|
|
203
|
+
var CURSOR_SOURCE = {
|
|
204
|
+
API: "api",
|
|
205
|
+
SDK: "sdk"
|
|
206
|
+
};
|
|
207
|
+
function encodeBase64(str) {
|
|
208
|
+
return btoa(
|
|
209
|
+
Array.from(
|
|
210
|
+
new TextEncoder().encode(str),
|
|
211
|
+
(b) => String.fromCharCode(b)
|
|
212
|
+
).join("")
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function decodeBase64(str) {
|
|
216
|
+
return new TextDecoder().decode(
|
|
217
|
+
Uint8Array.from(atob(str), (c) => c.charCodeAt(0))
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
function encodeApiCursor(cursor) {
|
|
221
|
+
const envelope = {
|
|
222
|
+
v: CURSOR_VERSION,
|
|
223
|
+
source: CURSOR_SOURCE.API,
|
|
224
|
+
cursor
|
|
225
|
+
};
|
|
226
|
+
return encodeBase64(JSON.stringify(envelope));
|
|
227
|
+
}
|
|
228
|
+
function encodeSdkCursor(offset, cursor) {
|
|
229
|
+
const envelope = {
|
|
230
|
+
v: CURSOR_VERSION,
|
|
231
|
+
source: CURSOR_SOURCE.SDK,
|
|
232
|
+
cursor,
|
|
233
|
+
offset
|
|
234
|
+
};
|
|
235
|
+
return encodeBase64(JSON.stringify(envelope));
|
|
236
|
+
}
|
|
237
|
+
function decodeIncomingCursor(incoming) {
|
|
238
|
+
if (!incoming) {
|
|
239
|
+
return { offset: 0, cursor: void 0 };
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const decoded = decodeBase64(incoming);
|
|
243
|
+
const envelope = JSON.parse(decoded);
|
|
244
|
+
if (envelope.v !== CURSOR_VERSION) {
|
|
245
|
+
return { offset: 0, cursor: incoming };
|
|
246
|
+
}
|
|
247
|
+
if (envelope.source === CURSOR_SOURCE.SDK) {
|
|
248
|
+
return { offset: envelope.offset ?? 0, cursor: envelope.cursor };
|
|
249
|
+
}
|
|
250
|
+
if (envelope.source === CURSOR_SOURCE.API) {
|
|
251
|
+
return { offset: 0, cursor: envelope.cursor };
|
|
252
|
+
}
|
|
253
|
+
return { offset: 0, cursor: incoming };
|
|
254
|
+
} catch {
|
|
255
|
+
return { offset: 0, cursor: incoming };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function createPrefixedCursor(prefix, cursor) {
|
|
259
|
+
if (!cursor) {
|
|
260
|
+
return `${prefix}::`;
|
|
261
|
+
}
|
|
262
|
+
return `${prefix}::${cursor}`;
|
|
263
|
+
}
|
|
264
|
+
function splitPrefixedCursor(cursor, prefixes) {
|
|
265
|
+
if (!cursor) {
|
|
266
|
+
return [void 0, void 0];
|
|
267
|
+
}
|
|
268
|
+
const [prefix, ...rest] = cursor.split("::");
|
|
269
|
+
if (prefixes && !prefixes.includes(prefix)) {
|
|
270
|
+
return [void 0, cursor];
|
|
271
|
+
}
|
|
272
|
+
cursor = rest.join("::");
|
|
273
|
+
if (!cursor) {
|
|
274
|
+
return [prefix, void 0];
|
|
275
|
+
}
|
|
276
|
+
return [prefix, cursor];
|
|
277
|
+
}
|
|
278
|
+
async function* paginateMaxItemsWithUnencodedCursor(pageFunction, pageOptions) {
|
|
279
|
+
let cursor = pageOptions?.cursor;
|
|
280
|
+
let totalItemsYielded = 0;
|
|
281
|
+
const maxItems = pageOptions?.maxItems;
|
|
282
|
+
const pageSize = pageOptions?.pageSize;
|
|
283
|
+
do {
|
|
284
|
+
const options = {
|
|
285
|
+
...pageOptions || {},
|
|
286
|
+
cursor,
|
|
287
|
+
pageSize: maxItems !== void 0 && pageSize !== void 0 ? Math.min(pageSize, maxItems) : pageSize
|
|
288
|
+
};
|
|
289
|
+
const page = await pageFunction(options);
|
|
290
|
+
if (maxItems !== void 0) {
|
|
291
|
+
const remainingItems = maxItems - totalItemsYielded;
|
|
292
|
+
if (page.data.length >= remainingItems) {
|
|
293
|
+
yield {
|
|
294
|
+
...page,
|
|
295
|
+
data: page.data.slice(0, remainingItems),
|
|
296
|
+
nextCursor: void 0
|
|
297
|
+
};
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
yield page;
|
|
302
|
+
totalItemsYielded += page.data.length;
|
|
303
|
+
cursor = page.nextCursor;
|
|
304
|
+
} while (cursor);
|
|
305
|
+
}
|
|
306
|
+
async function* paginateMaxItems(pageFunction, pageOptions) {
|
|
307
|
+
const { cursor } = decodeIncomingCursor(pageOptions?.cursor);
|
|
308
|
+
const options = {
|
|
309
|
+
...pageOptions || {},
|
|
310
|
+
cursor
|
|
311
|
+
};
|
|
312
|
+
for await (const page of paginateMaxItemsWithUnencodedCursor(
|
|
313
|
+
pageFunction,
|
|
314
|
+
options
|
|
315
|
+
)) {
|
|
316
|
+
yield {
|
|
317
|
+
...page,
|
|
318
|
+
nextCursor: page.nextCursor ? encodeApiCursor(page.nextCursor) : void 0
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function* paginateBuffered(pageFunction, pageOptions) {
|
|
323
|
+
const pageSize = pageOptions?.pageSize;
|
|
324
|
+
const { offset: cursorOffset, cursor: initialCursor } = decodeIncomingCursor(
|
|
325
|
+
pageOptions?.cursor
|
|
326
|
+
);
|
|
327
|
+
const requestedMaxItems = pageOptions?.maxItems;
|
|
328
|
+
const options = {
|
|
329
|
+
...pageOptions || {},
|
|
330
|
+
cursor: initialCursor,
|
|
331
|
+
// SDK cursors can carry an offset into a raw backend page. Since maxItems
|
|
332
|
+
// is expected to be relative to the resumed position, we add that offset
|
|
333
|
+
// so raw pagination still yields enough items after offset slicing.
|
|
334
|
+
maxItems: requestedMaxItems !== void 0 && cursorOffset > 0 ? requestedMaxItems + cursorOffset : requestedMaxItems
|
|
335
|
+
};
|
|
336
|
+
if (!pageSize) {
|
|
337
|
+
for await (const page of paginateMaxItemsWithUnencodedCursor(
|
|
338
|
+
pageFunction,
|
|
339
|
+
options
|
|
340
|
+
)) {
|
|
341
|
+
yield {
|
|
342
|
+
...page,
|
|
343
|
+
nextCursor: page.nextCursor ? encodeApiCursor(page.nextCursor) : void 0
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
let bufferedPages = [];
|
|
349
|
+
let isFirstPage = true;
|
|
350
|
+
let rawCursor;
|
|
351
|
+
for await (let page of paginateMaxItemsWithUnencodedCursor(
|
|
352
|
+
pageFunction,
|
|
353
|
+
options
|
|
354
|
+
)) {
|
|
355
|
+
const nextRawCursor = page.nextCursor;
|
|
356
|
+
if (isFirstPage) {
|
|
357
|
+
isFirstPage = false;
|
|
358
|
+
if (cursorOffset) {
|
|
359
|
+
page = {
|
|
360
|
+
...page,
|
|
361
|
+
data: page.data.slice(cursorOffset)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const bufferedLength = bufferedPages.reduce(
|
|
366
|
+
(acc, p) => acc + p.data.length,
|
|
367
|
+
0
|
|
368
|
+
);
|
|
369
|
+
if (bufferedLength + page.data.length < pageSize) {
|
|
370
|
+
bufferedPages.push(page);
|
|
371
|
+
rawCursor = nextRawCursor;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const bufferedItems = bufferedPages.map((p) => p.data).flat();
|
|
375
|
+
const allItems = [...bufferedItems, ...page.data];
|
|
376
|
+
const pageItems = allItems.slice(0, pageSize);
|
|
377
|
+
const remainingItems = allItems.slice(pageItems.length);
|
|
378
|
+
if (remainingItems.length === 0) {
|
|
379
|
+
yield {
|
|
380
|
+
...page,
|
|
381
|
+
data: pageItems,
|
|
382
|
+
nextCursor: nextRawCursor ? encodeApiCursor(nextRawCursor) : void 0
|
|
383
|
+
};
|
|
384
|
+
bufferedPages = [];
|
|
385
|
+
rawCursor = nextRawCursor;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
yield {
|
|
389
|
+
...page,
|
|
390
|
+
data: pageItems,
|
|
391
|
+
nextCursor: encodeSdkCursor(
|
|
392
|
+
page.data.length - remainingItems.length,
|
|
393
|
+
rawCursor
|
|
394
|
+
)
|
|
395
|
+
};
|
|
396
|
+
while (remainingItems.length > pageSize) {
|
|
397
|
+
const chunkItems = remainingItems.splice(0, pageSize);
|
|
398
|
+
yield {
|
|
399
|
+
...page,
|
|
400
|
+
data: chunkItems,
|
|
401
|
+
nextCursor: encodeSdkCursor(
|
|
402
|
+
page.data.length - remainingItems.length,
|
|
403
|
+
rawCursor
|
|
404
|
+
)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
bufferedPages = [
|
|
408
|
+
{
|
|
409
|
+
...page,
|
|
410
|
+
data: remainingItems
|
|
411
|
+
}
|
|
412
|
+
];
|
|
413
|
+
rawCursor = nextRawCursor;
|
|
414
|
+
}
|
|
415
|
+
if (bufferedPages.length > 0) {
|
|
416
|
+
const lastBufferedPage = bufferedPages.slice(-1)[0];
|
|
417
|
+
const bufferedItems = bufferedPages.map((p) => p.data).flat();
|
|
418
|
+
yield {
|
|
419
|
+
...lastBufferedPage,
|
|
420
|
+
data: bufferedItems
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
var paginate = paginateBuffered;
|
|
425
|
+
function concatPaginated({
|
|
426
|
+
sources,
|
|
427
|
+
dedupe,
|
|
428
|
+
pageSize = 100
|
|
429
|
+
}) {
|
|
430
|
+
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
|
+
});
|
|
437
|
+
}
|
|
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;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
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
|
+
return {
|
|
465
|
+
data: items,
|
|
466
|
+
nextCursor: hasMoreInSource || hasMoreSources ? "__has_more__" : void 0
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return { data: [] };
|
|
470
|
+
};
|
|
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
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function toIterable(source) {
|
|
488
|
+
return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/utils/validation.ts
|
|
492
|
+
var parseOrThrow = (schema, input, { adaptError } = {}) => {
|
|
493
|
+
const result = schema.safeParse(input);
|
|
494
|
+
if (!result.success) {
|
|
495
|
+
const errorMessages = result.error.issues.map((issue) => {
|
|
496
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "input";
|
|
497
|
+
return `${path}: ${issue.message}`;
|
|
498
|
+
});
|
|
499
|
+
throw createCoreError(
|
|
500
|
+
{
|
|
501
|
+
code: CoreErrorCode.Validation,
|
|
502
|
+
message: `Validation failed:
|
|
503
|
+
${errorMessages.join("\n ")}`,
|
|
504
|
+
details: {
|
|
505
|
+
zodErrors: result.error.issues,
|
|
506
|
+
input
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
adaptError
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
return result.data;
|
|
513
|
+
};
|
|
514
|
+
function createValidator(schema, { adaptError } = {}) {
|
|
515
|
+
return function validateFn(input) {
|
|
516
|
+
return parseOrThrow(schema, input, { adaptError });
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
var validateOptions = (schema, options, { adaptError } = {}) => parseOrThrow(schema, options, { adaptError });
|
|
520
|
+
|
|
521
|
+
// src/utils/async-context.ts
|
|
522
|
+
import {
|
|
523
|
+
AsyncLocalStorage
|
|
524
|
+
} from "async_hooks";
|
|
525
|
+
function createAsyncContext() {
|
|
526
|
+
let store = null;
|
|
527
|
+
try {
|
|
528
|
+
store = new AsyncLocalStorage();
|
|
529
|
+
} catch {
|
|
530
|
+
store = null;
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
available: store !== null,
|
|
534
|
+
run(value, fn) {
|
|
535
|
+
return store ? store.run(value, fn) : fn();
|
|
536
|
+
},
|
|
537
|
+
get() {
|
|
538
|
+
return store?.getStore();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/utils/method-scope.ts
|
|
544
|
+
var scope = createAsyncContext();
|
|
545
|
+
function getCurrentScope() {
|
|
546
|
+
return scope.get();
|
|
547
|
+
}
|
|
548
|
+
function getCurrentDepth() {
|
|
549
|
+
return getCurrentScope()?.depth ?? 0;
|
|
550
|
+
}
|
|
551
|
+
function isNestedMethodCall() {
|
|
552
|
+
if (!scope.available) return true;
|
|
553
|
+
const store = scope.get();
|
|
554
|
+
return store !== void 0 && store.depth > 0;
|
|
555
|
+
}
|
|
556
|
+
var observerReentrancy = 0;
|
|
557
|
+
function runIsolatedObserver(fn) {
|
|
558
|
+
observerReentrancy++;
|
|
559
|
+
try {
|
|
560
|
+
fn();
|
|
561
|
+
} catch {
|
|
562
|
+
} finally {
|
|
563
|
+
observerReentrancy--;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function isInsideObserver() {
|
|
567
|
+
return observerReentrancy > 0;
|
|
568
|
+
}
|
|
569
|
+
function runInMethodScope(fn) {
|
|
570
|
+
if (!scope.available) return fn();
|
|
571
|
+
const currentDepth = scope.get()?.depth ?? -1;
|
|
572
|
+
return scope.run({ depth: currentDepth + 1 }, fn);
|
|
573
|
+
}
|
|
574
|
+
var runWithTelemetryContext = runInMethodScope;
|
|
575
|
+
var isTelemetryNested = isNestedMethodCall;
|
|
576
|
+
|
|
577
|
+
// src/utils/core-options.ts
|
|
578
|
+
function defaultLogDeprecation({
|
|
579
|
+
methodName,
|
|
580
|
+
deprecation
|
|
581
|
+
}) {
|
|
582
|
+
logDeprecation(`${methodName}() is deprecated. ${deprecation.message}`);
|
|
583
|
+
}
|
|
584
|
+
var CORE_OPTIONS_ID = "kitcore/coreOptions";
|
|
585
|
+
|
|
586
|
+
// src/utils/function-utils.ts
|
|
587
|
+
function resolveCoreOptions(context) {
|
|
588
|
+
const entry = context.plugins?.[CORE_OPTIONS_ID];
|
|
589
|
+
if (entry) {
|
|
590
|
+
return entry.getValue ? entry.getValue() : entry.value;
|
|
591
|
+
}
|
|
592
|
+
return context.core;
|
|
593
|
+
}
|
|
594
|
+
var INTERNAL_CALL = Symbol("kitcore.internalCall");
|
|
595
|
+
function signalDeprecation(context, methodName, getDeprecation) {
|
|
596
|
+
if (isInsideObserver()) return;
|
|
597
|
+
const deprecation = getDeprecation?.();
|
|
598
|
+
if (!deprecation?.message) return;
|
|
599
|
+
const warning = {
|
|
600
|
+
type: "deprecation",
|
|
601
|
+
methodName,
|
|
602
|
+
deprecation
|
|
603
|
+
};
|
|
604
|
+
const handler = resolveCoreOptions(context)?.logDeprecation ?? defaultLogDeprecation;
|
|
605
|
+
runIsolatedObserver(() => handler(warning));
|
|
606
|
+
}
|
|
607
|
+
function normalizeError(error, adaptError) {
|
|
608
|
+
if (error instanceof Error) return error;
|
|
609
|
+
const message = typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : String(error);
|
|
610
|
+
return createCoreError(
|
|
611
|
+
{
|
|
612
|
+
code: CoreErrorCode.Unknown,
|
|
613
|
+
message,
|
|
614
|
+
cause: error
|
|
615
|
+
},
|
|
616
|
+
adaptError
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
function createFunction(coreFn, options) {
|
|
620
|
+
const { sdk, schema, name, getDeprecation } = options;
|
|
621
|
+
const functionName = name || coreFn.name;
|
|
622
|
+
const namedFunctions = {
|
|
623
|
+
[functionName]: async function(callOptions) {
|
|
624
|
+
if (arguments[1] !== INTERNAL_CALL) {
|
|
625
|
+
signalDeprecation(sdk.context, functionName, getDeprecation);
|
|
626
|
+
}
|
|
627
|
+
return runInMethodScope(async () => {
|
|
628
|
+
const startTime = Date.now();
|
|
629
|
+
const normalizedOptions = callOptions ?? {};
|
|
630
|
+
const args = [normalizedOptions];
|
|
631
|
+
const depth = getCurrentDepth();
|
|
632
|
+
const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
|
|
633
|
+
const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
|
|
634
|
+
hooks?.onMethodStart?.({
|
|
635
|
+
methodName: functionName,
|
|
636
|
+
args,
|
|
637
|
+
isPaginated: false,
|
|
638
|
+
depth
|
|
639
|
+
});
|
|
640
|
+
try {
|
|
641
|
+
let result;
|
|
642
|
+
if (schema) {
|
|
643
|
+
const validatedOptions = validateOptions(
|
|
644
|
+
schema,
|
|
645
|
+
normalizedOptions,
|
|
646
|
+
{
|
|
647
|
+
adaptError
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
result = await coreFn({
|
|
651
|
+
...normalizedOptions,
|
|
652
|
+
...validatedOptions
|
|
653
|
+
});
|
|
654
|
+
} else {
|
|
655
|
+
result = await coreFn(normalizedOptions);
|
|
656
|
+
}
|
|
657
|
+
hooks?.onMethodEnd?.({
|
|
658
|
+
methodName: functionName,
|
|
659
|
+
args,
|
|
660
|
+
isPaginated: false,
|
|
661
|
+
depth,
|
|
662
|
+
durationMs: Date.now() - startTime
|
|
663
|
+
});
|
|
664
|
+
return result;
|
|
665
|
+
} catch (error) {
|
|
666
|
+
const normalizedError = normalizeError(error, adaptError);
|
|
667
|
+
hooks?.onMethodEnd?.({
|
|
668
|
+
methodName: functionName,
|
|
669
|
+
args,
|
|
670
|
+
isPaginated: false,
|
|
671
|
+
depth,
|
|
672
|
+
durationMs: Date.now() - startTime,
|
|
673
|
+
error: normalizedError
|
|
674
|
+
});
|
|
675
|
+
throw normalizedError;
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
return namedFunctions[functionName];
|
|
681
|
+
}
|
|
682
|
+
function createRawFunction(coreFn, options) {
|
|
683
|
+
const { sdk, name, schema, positional, getDeprecation } = options;
|
|
684
|
+
return function(rawInput) {
|
|
685
|
+
if (arguments[1] !== INTERNAL_CALL) {
|
|
686
|
+
signalDeprecation(sdk.context, name, getDeprecation);
|
|
687
|
+
}
|
|
688
|
+
return runInMethodScope(() => {
|
|
689
|
+
const startTime = Date.now();
|
|
690
|
+
const depth = getCurrentDepth();
|
|
691
|
+
const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
|
|
692
|
+
const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
|
|
693
|
+
const input = schema ? rawInput ?? {} : rawInput;
|
|
694
|
+
const record = input;
|
|
695
|
+
const args = positional ? positional.filter((key2) => record?.[key2] !== void 0).map((key2) => record?.[key2]) : [input];
|
|
696
|
+
hooks?.onMethodStart?.({
|
|
697
|
+
methodName: name,
|
|
698
|
+
args,
|
|
699
|
+
isPaginated: false,
|
|
700
|
+
depth
|
|
701
|
+
});
|
|
702
|
+
const fireEnd = (error) => {
|
|
703
|
+
hooks?.onMethodEnd?.({
|
|
704
|
+
methodName: name,
|
|
705
|
+
args,
|
|
706
|
+
isPaginated: false,
|
|
707
|
+
depth,
|
|
708
|
+
durationMs: Date.now() - startTime,
|
|
709
|
+
...error ? { error } : {}
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
try {
|
|
713
|
+
const parsed = schema ? validateOptions(schema, input, { adaptError }) : input;
|
|
714
|
+
const result = coreFn(parsed);
|
|
715
|
+
if (result !== null && typeof result === "object" && typeof result.then === "function") {
|
|
716
|
+
return result.then(
|
|
717
|
+
(value) => {
|
|
718
|
+
fireEnd();
|
|
719
|
+
return value;
|
|
720
|
+
},
|
|
721
|
+
(error) => {
|
|
722
|
+
fireEnd(
|
|
723
|
+
error instanceof Error ? error : new Error(String(error))
|
|
724
|
+
);
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
fireEnd();
|
|
730
|
+
return result;
|
|
731
|
+
} catch (error) {
|
|
732
|
+
fireEnd(error instanceof Error ? error : new Error(String(error)));
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function isSdkPage(value) {
|
|
739
|
+
if (typeof value !== "object" || value === null) return false;
|
|
740
|
+
const page = value;
|
|
741
|
+
if (!Array.isArray(page.data)) return false;
|
|
742
|
+
if (page.nextCursor !== void 0 && typeof page.nextCursor !== "string") {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
return Object.keys(page).every((k) => k === "data" || k === "nextCursor");
|
|
746
|
+
}
|
|
747
|
+
function createPageFunction(coreFn, {
|
|
748
|
+
sdk,
|
|
749
|
+
adaptPage
|
|
750
|
+
}) {
|
|
751
|
+
const functionName = coreFn.name + "Page";
|
|
752
|
+
const namedFunctions = {
|
|
753
|
+
[functionName]: async function(options) {
|
|
754
|
+
try {
|
|
755
|
+
const response = await coreFn(options);
|
|
756
|
+
const page = adaptPage ? adaptPage(response) : response;
|
|
757
|
+
if (!isSdkPage(page)) {
|
|
758
|
+
throw new Error(
|
|
759
|
+
`${functionName}: paginated result must be exactly { data: TItem[], nextCursor? } (produced by the handler or its \`adaptPage\`); got keys [${page && typeof page === "object" ? Object.keys(page).join(", ") : typeof page}]. If the handler returns a raw shape, set \`adaptPage\` to translate it; if \`adaptPage\` already runs, it must return only \`data\`/\`nextCursor\`.`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
return page;
|
|
763
|
+
} catch (error) {
|
|
764
|
+
throw normalizeError(
|
|
765
|
+
error,
|
|
766
|
+
resolveCoreOptions(sdk.context)?.adaptError
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
return namedFunctions[functionName];
|
|
772
|
+
}
|
|
773
|
+
function createPaginatedFunction(coreFn, options) {
|
|
774
|
+
const { sdk, schema, name, defaultPageSize, adaptPage, getDeprecation } = options;
|
|
775
|
+
const pageFunction = createPageFunction(coreFn, { sdk, adaptPage });
|
|
776
|
+
const functionName = name || coreFn.name;
|
|
777
|
+
const namedFunctions = {
|
|
778
|
+
[functionName]: function(callOptions) {
|
|
779
|
+
if (arguments[1] !== INTERNAL_CALL) {
|
|
780
|
+
signalDeprecation(sdk.context, functionName, getDeprecation);
|
|
781
|
+
}
|
|
782
|
+
return runInMethodScope(() => {
|
|
783
|
+
const startTime = Date.now();
|
|
784
|
+
const normalizedOptions = callOptions ?? {};
|
|
785
|
+
const args = [normalizedOptions];
|
|
786
|
+
const depth = getCurrentDepth();
|
|
787
|
+
const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
|
|
788
|
+
const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
|
|
789
|
+
hooks?.onMethodStart?.({
|
|
790
|
+
methodName: functionName,
|
|
791
|
+
args,
|
|
792
|
+
isPaginated: true,
|
|
793
|
+
depth
|
|
794
|
+
});
|
|
795
|
+
try {
|
|
796
|
+
const validatedOptions = {
|
|
797
|
+
...normalizedOptions,
|
|
798
|
+
...schema ? createValidator(schema, { adaptError })(normalizedOptions) : normalizedOptions
|
|
799
|
+
};
|
|
800
|
+
const pageSize = validatedOptions.pageSize ?? defaultPageSize;
|
|
801
|
+
const optimizedOptions = {
|
|
802
|
+
...validatedOptions,
|
|
803
|
+
pageSize
|
|
804
|
+
};
|
|
805
|
+
const iterator = paginate(pageFunction, optimizedOptions);
|
|
806
|
+
const firstPagePromise = iterator.next().then((result) => {
|
|
807
|
+
if (result.done) {
|
|
808
|
+
throw new Error("Paginate should always iterate at least once");
|
|
809
|
+
}
|
|
810
|
+
return result.value;
|
|
811
|
+
});
|
|
812
|
+
if (hooks?.onMethodEnd) {
|
|
813
|
+
firstPagePromise.then(() => {
|
|
814
|
+
hooks.onMethodEnd({
|
|
815
|
+
methodName: functionName,
|
|
816
|
+
args,
|
|
817
|
+
isPaginated: true,
|
|
818
|
+
depth,
|
|
819
|
+
durationMs: Date.now() - startTime
|
|
820
|
+
});
|
|
821
|
+
}).catch((error) => {
|
|
822
|
+
hooks.onMethodEnd({
|
|
823
|
+
methodName: functionName,
|
|
824
|
+
args,
|
|
825
|
+
isPaginated: true,
|
|
826
|
+
depth,
|
|
827
|
+
durationMs: Date.now() - startTime,
|
|
828
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
const pageStream = async function* () {
|
|
833
|
+
yield await firstPagePromise;
|
|
834
|
+
for await (const page of iterator) {
|
|
835
|
+
yield page;
|
|
836
|
+
}
|
|
837
|
+
}();
|
|
838
|
+
return Object.assign(firstPagePromise, {
|
|
839
|
+
[Symbol.asyncIterator]() {
|
|
840
|
+
return pageStream;
|
|
841
|
+
},
|
|
842
|
+
items: function() {
|
|
843
|
+
return {
|
|
844
|
+
[Symbol.asyncIterator]: async function* () {
|
|
845
|
+
for await (const page of pageStream) {
|
|
846
|
+
for (const item of page.data) {
|
|
847
|
+
yield item;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
} catch (error) {
|
|
855
|
+
const normalizedError = normalizeError(error, adaptError);
|
|
856
|
+
hooks?.onMethodEnd?.({
|
|
857
|
+
methodName: functionName,
|
|
858
|
+
args,
|
|
859
|
+
isPaginated: true,
|
|
860
|
+
depth,
|
|
861
|
+
durationMs: Date.now() - startTime,
|
|
862
|
+
error: normalizedError
|
|
863
|
+
});
|
|
864
|
+
throw normalizedError;
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
return namedFunctions[functionName];
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/utils/plugin-utils.ts
|
|
873
|
+
function createPluginMethod(sdk, config) {
|
|
874
|
+
logDeprecation(
|
|
875
|
+
"createPluginMethod() is deprecated. Author methods with defineMethod instead."
|
|
876
|
+
);
|
|
877
|
+
const { name, inputSchema, handler, ...metaFields } = config;
|
|
878
|
+
const namedHandlers = {
|
|
879
|
+
[name]: async function(options) {
|
|
880
|
+
return handler({ sdk, options });
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const wrappedFn = createFunction(namedHandlers[name], {
|
|
884
|
+
sdk,
|
|
885
|
+
schema: inputSchema
|
|
886
|
+
});
|
|
887
|
+
return {
|
|
888
|
+
[name]: wrappedFn,
|
|
889
|
+
context: {
|
|
890
|
+
meta: {
|
|
891
|
+
[name]: {
|
|
892
|
+
...metaFields,
|
|
893
|
+
...inputSchema ? { inputSchema } : {}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function createPaginatedPluginMethod(sdk, config) {
|
|
900
|
+
logDeprecation(
|
|
901
|
+
'createPaginatedPluginMethod() is deprecated. Author list methods with defineMethod output "list" instead.'
|
|
902
|
+
);
|
|
903
|
+
const {
|
|
904
|
+
name,
|
|
905
|
+
inputSchema,
|
|
906
|
+
handler,
|
|
907
|
+
adaptPage,
|
|
908
|
+
defaultPageSize,
|
|
909
|
+
...metaFields
|
|
910
|
+
} = config;
|
|
911
|
+
const namedHandlers = {
|
|
912
|
+
[name]: function(options) {
|
|
913
|
+
return handler({ sdk, options });
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
const wrappedFn = createPaginatedFunction(namedHandlers[name], {
|
|
917
|
+
sdk,
|
|
918
|
+
schema: inputSchema,
|
|
919
|
+
name,
|
|
920
|
+
defaultPageSize,
|
|
921
|
+
adaptPage
|
|
922
|
+
});
|
|
923
|
+
return {
|
|
924
|
+
[name]: wrappedFn,
|
|
925
|
+
context: {
|
|
926
|
+
meta: {
|
|
927
|
+
[name]: {
|
|
928
|
+
...metaFields,
|
|
929
|
+
...inputSchema ? { inputSchema } : {}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function splitPluginContribution(result) {
|
|
936
|
+
const { context, ...rootKeys } = result;
|
|
937
|
+
const { meta, hooks, ...contextRest } = context ?? {};
|
|
938
|
+
return {
|
|
939
|
+
rootKeys,
|
|
940
|
+
meta: meta ?? {},
|
|
941
|
+
hooks: hooks ?? {},
|
|
942
|
+
contextRest
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
var RESERVED_ROOT_KEYS = /* @__PURE__ */ new Set([
|
|
946
|
+
"context",
|
|
947
|
+
"getRegistry"
|
|
948
|
+
]);
|
|
949
|
+
function hasOwn(obj, key2) {
|
|
950
|
+
return Object.prototype.hasOwnProperty.call(obj, key2);
|
|
951
|
+
}
|
|
952
|
+
function setOwn(target, key2, value) {
|
|
953
|
+
Object.defineProperty(target, key2, {
|
|
954
|
+
value,
|
|
955
|
+
enumerable: true,
|
|
956
|
+
configurable: true,
|
|
957
|
+
writable: true
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
function checkCollisions(target, source, kind, callerLabel, override) {
|
|
961
|
+
if (kind === "root key") {
|
|
962
|
+
checkRootKeyCollisions(target, Object.keys(source), override, callerLabel);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
for (const key2 of Object.keys(source)) {
|
|
966
|
+
if (!override && hasOwn(target, key2)) {
|
|
967
|
+
throw new Error(
|
|
968
|
+
`${callerLabel}: duplicate ${kind} "${key2}". If the override is intentional, pass { override: true } in the options.`
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
function checkRootKeyCollisions(target, keys, override, callerLabel) {
|
|
974
|
+
for (const key2 of keys) {
|
|
975
|
+
if (RESERVED_ROOT_KEYS.has(key2)) {
|
|
976
|
+
throw new Error(
|
|
977
|
+
`${callerLabel}: plugin attempted to register reserved root key "${key2}". The SDK uses this key for its own accessor; rename the plugin's method.`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
if (!override && hasOwn(target, key2)) {
|
|
981
|
+
throw new Error(
|
|
982
|
+
`${callerLabel}: duplicate root key "${key2}". If the override is intentional, pass { override: true } in the options.`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function applyOwnProperties(target, source) {
|
|
988
|
+
for (const key2 of Object.keys(source)) {
|
|
989
|
+
setOwn(target, key2, source[key2]);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function createPluginAccumulator(initialProperties = {}, initialContext = {}) {
|
|
993
|
+
const initialMeta = initialContext.meta ?? {};
|
|
994
|
+
const initialHooks = initialContext.hooks ?? {};
|
|
995
|
+
const context = {
|
|
996
|
+
...initialContext,
|
|
997
|
+
meta: { ...initialMeta },
|
|
998
|
+
hooks: { ...initialHooks }
|
|
999
|
+
};
|
|
1000
|
+
const view = { ...initialProperties, context };
|
|
1001
|
+
return { view, context };
|
|
1002
|
+
}
|
|
1003
|
+
function mergeContribution(propertiesTarget, contextTarget, contribution, options) {
|
|
1004
|
+
checkCollisions(
|
|
1005
|
+
propertiesTarget,
|
|
1006
|
+
contribution.rootKeys,
|
|
1007
|
+
"root key",
|
|
1008
|
+
options.callerLabel,
|
|
1009
|
+
options.override
|
|
1010
|
+
);
|
|
1011
|
+
checkCollisions(
|
|
1012
|
+
contextTarget.meta,
|
|
1013
|
+
contribution.meta,
|
|
1014
|
+
"context.meta key",
|
|
1015
|
+
options.callerLabel,
|
|
1016
|
+
options.override
|
|
1017
|
+
);
|
|
1018
|
+
checkCollisions(
|
|
1019
|
+
contextTarget,
|
|
1020
|
+
contribution.contextRest,
|
|
1021
|
+
"context key",
|
|
1022
|
+
options.callerLabel,
|
|
1023
|
+
options.override
|
|
1024
|
+
);
|
|
1025
|
+
applyOwnProperties(propertiesTarget, contribution.rootKeys);
|
|
1026
|
+
applyOwnProperties(contextTarget.meta, contribution.meta);
|
|
1027
|
+
applyOwnProperties(contextTarget, contribution.contextRest);
|
|
1028
|
+
contextTarget.hooks = buildHooks(contextTarget.hooks, contribution.hooks);
|
|
1029
|
+
}
|
|
1030
|
+
function applyPluginContribution(acc, contribution, options) {
|
|
1031
|
+
mergeContribution(acc.view, acc.context, contribution, options);
|
|
1032
|
+
}
|
|
1033
|
+
function wrapAsSdk(properties, context) {
|
|
1034
|
+
const sdk = {
|
|
1035
|
+
...properties,
|
|
1036
|
+
context,
|
|
1037
|
+
getRegistry(qopts) {
|
|
1038
|
+
return buildRegistry({
|
|
1039
|
+
sdk,
|
|
1040
|
+
meta: context.meta,
|
|
1041
|
+
packageFilter: qopts?.package
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
return sdk;
|
|
1046
|
+
}
|
|
1047
|
+
function wrapAccumulatorAsSdk(acc) {
|
|
1048
|
+
const { context: _ctx, ...rootKeys } = acc.view;
|
|
1049
|
+
return wrapAsSdk(
|
|
1050
|
+
rootKeys,
|
|
1051
|
+
acc.context
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
function applyPluginToSdk(sdk, plugin, options) {
|
|
1055
|
+
const context = sdk.context;
|
|
1056
|
+
const contribution = splitPluginContribution(
|
|
1057
|
+
plugin(sdk)
|
|
1058
|
+
);
|
|
1059
|
+
mergeContribution(sdk, context, contribution, {
|
|
1060
|
+
callerLabel: "addPlugin",
|
|
1061
|
+
override: options.override === true
|
|
1062
|
+
});
|
|
1063
|
+
return contribution;
|
|
1064
|
+
}
|
|
1065
|
+
function resolveStack(head) {
|
|
1066
|
+
const entries = [];
|
|
1067
|
+
let node = head;
|
|
1068
|
+
while (node) {
|
|
1069
|
+
entries.unshift({ apply: node.entry, override: node.override });
|
|
1070
|
+
node = node.prev;
|
|
1071
|
+
}
|
|
1072
|
+
return entries;
|
|
1073
|
+
}
|
|
1074
|
+
function composeStackHooks(hooks) {
|
|
1075
|
+
let composed = {};
|
|
1076
|
+
for (const h of hooks) composed = buildHooks(composed, h);
|
|
1077
|
+
return composed;
|
|
1078
|
+
}
|
|
1079
|
+
function collapseStackEntries(entries, callerLabel) {
|
|
1080
|
+
return (outerSdk) => {
|
|
1081
|
+
const { context: outerContext, ...outerProperties } = outerSdk ?? {};
|
|
1082
|
+
const viewAcc = createPluginAccumulator(outerProperties, outerContext);
|
|
1083
|
+
const contribsAcc = createPluginAccumulator();
|
|
1084
|
+
const hooks = [];
|
|
1085
|
+
for (const { apply, override } of entries) {
|
|
1086
|
+
const contribution = splitPluginContribution(
|
|
1087
|
+
apply(viewAcc.view)
|
|
1088
|
+
);
|
|
1089
|
+
const hookless = { ...contribution, hooks: {} };
|
|
1090
|
+
applyPluginContribution(viewAcc, hookless, { callerLabel, override });
|
|
1091
|
+
applyPluginContribution(contribsAcc, hookless, { callerLabel, override });
|
|
1092
|
+
hooks.push(contribution.hooks);
|
|
1093
|
+
}
|
|
1094
|
+
const stackHooks = composeStackHooks(hooks);
|
|
1095
|
+
viewAcc.context.hooks = buildHooks(viewAcc.context.hooks, stackHooks);
|
|
1096
|
+
contribsAcc.context.hooks = stackHooks;
|
|
1097
|
+
const { context: _ignored, ...contributedRoot } = contribsAcc.view;
|
|
1098
|
+
return {
|
|
1099
|
+
...contributedRoot,
|
|
1100
|
+
context: contribsAcc.context
|
|
1101
|
+
};
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function buildStackAccumulator(head, callerLabel) {
|
|
1105
|
+
const entries = resolveStack(head);
|
|
1106
|
+
const acc = createPluginAccumulator();
|
|
1107
|
+
const hooks = [];
|
|
1108
|
+
for (const { apply, override } of entries) {
|
|
1109
|
+
const contribution = splitPluginContribution(
|
|
1110
|
+
apply(acc.view)
|
|
1111
|
+
);
|
|
1112
|
+
applyPluginContribution(
|
|
1113
|
+
acc,
|
|
1114
|
+
{ ...contribution, hooks: {} },
|
|
1115
|
+
{ callerLabel, override }
|
|
1116
|
+
);
|
|
1117
|
+
hooks.push(contribution.hooks);
|
|
1118
|
+
}
|
|
1119
|
+
acc.context.hooks = composeStackHooks(hooks);
|
|
1120
|
+
return acc;
|
|
1121
|
+
}
|
|
1122
|
+
function composePlugins(...plugins) {
|
|
1123
|
+
logDeprecation(
|
|
1124
|
+
"composePlugins(...) is deprecated. Use createPluginStack().use(a).use(b).use(c).toPlugin({ name }) instead. The stack carries the same collision-detection and hook-composition behavior and supports per-step { override: true } for intentional duplicates."
|
|
1125
|
+
);
|
|
1126
|
+
let head = null;
|
|
1127
|
+
for (const plugin of plugins) {
|
|
1128
|
+
head = { entry: plugin, override: false, prev: head };
|
|
1129
|
+
}
|
|
1130
|
+
const entries = resolveStack(head);
|
|
1131
|
+
return collapseStackEntries(entries, "composePlugins");
|
|
1132
|
+
}
|
|
1133
|
+
function createPluginStack() {
|
|
1134
|
+
logDeprecation(
|
|
1135
|
+
"createPluginStack() is deprecated. Compose with definePlugin and build with createSdk instead."
|
|
1136
|
+
);
|
|
1137
|
+
return buildPluginStack(null, "createPluginStack");
|
|
1138
|
+
}
|
|
1139
|
+
function buildPluginStack(head, callerLabel) {
|
|
1140
|
+
const stack = {
|
|
1141
|
+
use(plugin, options) {
|
|
1142
|
+
const next = {
|
|
1143
|
+
entry: plugin,
|
|
1144
|
+
override: options?.override === true,
|
|
1145
|
+
prev: head
|
|
1146
|
+
};
|
|
1147
|
+
return buildPluginStack(next, callerLabel);
|
|
1148
|
+
},
|
|
1149
|
+
toPlugin() {
|
|
1150
|
+
const entries = resolveStack(head);
|
|
1151
|
+
return collapseStackEntries(entries, callerLabel);
|
|
1152
|
+
},
|
|
1153
|
+
toSdk() {
|
|
1154
|
+
return wrapAccumulatorAsSdk(
|
|
1155
|
+
buildStackAccumulator(head, callerLabel)
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
return stack;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// src/model/shared.ts
|
|
1163
|
+
function parseId(id) {
|
|
1164
|
+
const at = id.lastIndexOf("/");
|
|
1165
|
+
return at === -1 ? { name: id, namespace: void 0 } : { name: id.slice(at + 1), namespace: id.slice(0, at) };
|
|
1166
|
+
}
|
|
1167
|
+
function makeId(name, namespace, kind = "leaf") {
|
|
1168
|
+
validateName(name, kind);
|
|
1169
|
+
if (namespace !== void 0) validateNamespace(namespace);
|
|
1170
|
+
return namespace ? `${namespace}/${name}` : name;
|
|
1171
|
+
}
|
|
1172
|
+
var NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1173
|
+
var SEGMENT_RE = /^@?[A-Za-z0-9._-]+$/;
|
|
1174
|
+
function validateName(name, kind) {
|
|
1175
|
+
if (name === "") throw new Error("Plugin name must not be empty.");
|
|
1176
|
+
if (kind === "leaf") {
|
|
1177
|
+
if (!NAME_RE.test(name)) {
|
|
1178
|
+
throw new Error(
|
|
1179
|
+
`Plugin name "${name}" must be a valid JS identifier (it is the binding name).`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
} else if (!SEGMENT_RE.test(name)) {
|
|
1183
|
+
throw new Error(
|
|
1184
|
+
`Plugin name "${name}" must be package-like (letters, digits, ".", "_", "-", optional leading "@") with no "/".`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function validateNamespace(namespace) {
|
|
1189
|
+
if (namespace === "") throw new Error("Plugin namespace must not be empty.");
|
|
1190
|
+
for (const segment of namespace.split("/")) {
|
|
1191
|
+
if (!SEGMENT_RE.test(segment)) {
|
|
1192
|
+
throw new Error(
|
|
1193
|
+
`Plugin namespace "${namespace}" is invalid: each "/"-separated segment must be package-like (letters, digits, ".", "_", "-", optional leading "@").`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/model/types.ts
|
|
1200
|
+
var LEAF_META_KEYS = [
|
|
1201
|
+
"description",
|
|
1202
|
+
"categories",
|
|
1203
|
+
"type",
|
|
1204
|
+
"itemType",
|
|
1205
|
+
"returnType",
|
|
1206
|
+
"outputSchema",
|
|
1207
|
+
"inputParameters",
|
|
1208
|
+
"packages",
|
|
1209
|
+
"experimental",
|
|
1210
|
+
"confirm",
|
|
1211
|
+
"deprecation",
|
|
1212
|
+
"aliases",
|
|
1213
|
+
"supportsJsonOutput"
|
|
1214
|
+
];
|
|
1215
|
+
|
|
1216
|
+
// src/model/define.ts
|
|
1217
|
+
function normalizeImports(deps) {
|
|
1218
|
+
if (!deps) return { plugins: [], bindings: [] };
|
|
1219
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1220
|
+
const bindings = [];
|
|
1221
|
+
const add = (binding, id, optional) => {
|
|
1222
|
+
const priorId = seen.get(binding);
|
|
1223
|
+
if (priorId !== void 0 && priorId !== id) {
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`Import binding "${binding}" is declared twice. Two different plugins ("${priorId}" and "${id}") bind the same name; wrap one in selectExports to rename it.`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
if (priorId === void 0) {
|
|
1229
|
+
seen.set(binding, id);
|
|
1230
|
+
bindings.push(optional ? { binding, id, optional } : { binding, id });
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
for (const plugin of deps) {
|
|
1234
|
+
if (plugin.pluginType === "aggregate") {
|
|
1235
|
+
for (const [binding, child] of Object.entries(plugin.exports)) {
|
|
1236
|
+
add(binding, child.id);
|
|
1237
|
+
}
|
|
1238
|
+
} else if (plugin.pluginType === "hook") {
|
|
1239
|
+
} else {
|
|
1240
|
+
add(plugin.name, plugin.id, plugin.optional);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return { plugins: deps, bindings };
|
|
1244
|
+
}
|
|
1245
|
+
function collectLeafMeta(config) {
|
|
1246
|
+
let meta;
|
|
1247
|
+
for (const key2 of LEAF_META_KEYS) {
|
|
1248
|
+
if (config[key2] !== void 0) (meta ?? (meta = {}))[key2] = config[key2];
|
|
1249
|
+
}
|
|
1250
|
+
return meta;
|
|
1251
|
+
}
|
|
1252
|
+
function formatDynamicMemberName(path) {
|
|
1253
|
+
return path.map((seg) => typeof seg === "string" ? seg : `{${seg.param}}`).join(".");
|
|
1254
|
+
}
|
|
1255
|
+
function collectDynamicMembers(members) {
|
|
1256
|
+
if (!members?.length) return void 0;
|
|
1257
|
+
return members.map((member) => {
|
|
1258
|
+
const root = member.path[0];
|
|
1259
|
+
if (typeof root !== "string") {
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
"defineProperty: a dynamicMember path must start with a literal segment (the owning binding), not a { param }."
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
const leaf = collectLeafMeta(member) ?? {};
|
|
1265
|
+
return {
|
|
1266
|
+
name: formatDynamicMemberName(member.path),
|
|
1267
|
+
rootBinding: root,
|
|
1268
|
+
meta: member.inputSchema ? { ...leaf, inputSchema: member.inputSchema } : leaf
|
|
1269
|
+
};
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
function defineMethod(config) {
|
|
1273
|
+
const deps = normalizeImports(config.imports);
|
|
1274
|
+
return {
|
|
1275
|
+
pluginType: "method",
|
|
1276
|
+
name: config.name,
|
|
1277
|
+
namespace: config.namespace,
|
|
1278
|
+
id: makeId(config.name, config.namespace),
|
|
1279
|
+
imports: deps.plugins,
|
|
1280
|
+
importBindings: deps.bindings,
|
|
1281
|
+
inputSchema: config.inputSchema,
|
|
1282
|
+
skipInputValidation: config.skipInputValidation,
|
|
1283
|
+
meta: collectLeafMeta(config),
|
|
1284
|
+
resolvers: config.resolvers,
|
|
1285
|
+
formatter: config.formatter,
|
|
1286
|
+
output: config.output,
|
|
1287
|
+
positional: config.positional,
|
|
1288
|
+
setup: config.setup,
|
|
1289
|
+
dispose: config.dispose,
|
|
1290
|
+
run: config.run
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function defineMethodOverride(config) {
|
|
1294
|
+
const { target, namespace, ...rest } = config;
|
|
1295
|
+
return {
|
|
1296
|
+
pluginType: "method-override",
|
|
1297
|
+
name: `override:${target}`,
|
|
1298
|
+
id: namespace ? `${namespace}/override:${target}` : `override:${target}`,
|
|
1299
|
+
target,
|
|
1300
|
+
imports: [],
|
|
1301
|
+
importBindings: [],
|
|
1302
|
+
meta: collectLeafMeta(rest)
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function defineResolver(config) {
|
|
1306
|
+
const deps = normalizeImports(config.imports);
|
|
1307
|
+
const base = { imports: deps.plugins, importBindings: deps.bindings };
|
|
1308
|
+
const gates = {
|
|
1309
|
+
requireParameters: config.requireParameters
|
|
1310
|
+
};
|
|
1311
|
+
switch (config.type) {
|
|
1312
|
+
case "static":
|
|
1313
|
+
return {
|
|
1314
|
+
...base,
|
|
1315
|
+
...gates,
|
|
1316
|
+
type: "static",
|
|
1317
|
+
inputType: config.inputType,
|
|
1318
|
+
placeholder: config.placeholder
|
|
1319
|
+
};
|
|
1320
|
+
case "constant":
|
|
1321
|
+
return { ...base, ...gates, type: "constant", value: config.value };
|
|
1322
|
+
case "info":
|
|
1323
|
+
return { ...base, type: "info", text: config.text ?? "" };
|
|
1324
|
+
case "object":
|
|
1325
|
+
return {
|
|
1326
|
+
...base,
|
|
1327
|
+
...gates,
|
|
1328
|
+
type: "object",
|
|
1329
|
+
properties: config.properties,
|
|
1330
|
+
definitions: config.definitions,
|
|
1331
|
+
getProperties: config.getProperties
|
|
1332
|
+
};
|
|
1333
|
+
case "array":
|
|
1334
|
+
return {
|
|
1335
|
+
...base,
|
|
1336
|
+
...gates,
|
|
1337
|
+
type: "array",
|
|
1338
|
+
items: config.items,
|
|
1339
|
+
minItems: config.minItems,
|
|
1340
|
+
maxItems: config.maxItems,
|
|
1341
|
+
itemValueType: config.itemValueType,
|
|
1342
|
+
definitions: config.definitions
|
|
1343
|
+
};
|
|
1344
|
+
default:
|
|
1345
|
+
return {
|
|
1346
|
+
...base,
|
|
1347
|
+
...gates,
|
|
1348
|
+
type: "dynamic",
|
|
1349
|
+
inputType: config.inputType,
|
|
1350
|
+
placeholder: config.placeholder,
|
|
1351
|
+
getContext: config.getContext,
|
|
1352
|
+
listItems: config.listItems,
|
|
1353
|
+
prompt: config.prompt,
|
|
1354
|
+
tryResolveWithoutPrompt: config.tryResolveWithoutPrompt,
|
|
1355
|
+
tryResolveFromSearch: config.tryResolveFromSearch
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function defineFormatter(config) {
|
|
1360
|
+
const deps = normalizeImports(config.imports);
|
|
1361
|
+
return {
|
|
1362
|
+
imports: deps.plugins,
|
|
1363
|
+
importBindings: deps.bindings,
|
|
1364
|
+
getContext: config.getContext,
|
|
1365
|
+
format: config.format
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function declareMethod(config) {
|
|
1369
|
+
const { name, namespace } = parseId(config.id);
|
|
1370
|
+
const id = makeId(name, namespace);
|
|
1371
|
+
return {
|
|
1372
|
+
pluginType: "method",
|
|
1373
|
+
name,
|
|
1374
|
+
namespace,
|
|
1375
|
+
id,
|
|
1376
|
+
standIn: true,
|
|
1377
|
+
imports: [],
|
|
1378
|
+
importBindings: [],
|
|
1379
|
+
run: () => {
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
`Plugin "${id}" is a stand-in (declareMethod) with no implementation. Register the real plugin under this id.`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
function defineProperty(config) {
|
|
1387
|
+
const deps = normalizeImports(config.imports);
|
|
1388
|
+
return {
|
|
1389
|
+
pluginType: "property",
|
|
1390
|
+
name: config.name,
|
|
1391
|
+
namespace: config.namespace,
|
|
1392
|
+
id: makeId(config.name, config.namespace),
|
|
1393
|
+
imports: deps.plugins,
|
|
1394
|
+
importBindings: deps.bindings,
|
|
1395
|
+
setup: config.setup,
|
|
1396
|
+
dispose: config.dispose,
|
|
1397
|
+
value: config.value,
|
|
1398
|
+
get: config.get,
|
|
1399
|
+
meta: collectLeafMeta(config),
|
|
1400
|
+
dynamicMembers: collectDynamicMembers(config.dynamicMembers)
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function declareProperty(config) {
|
|
1404
|
+
const { name, namespace } = parseId(config.id);
|
|
1405
|
+
return {
|
|
1406
|
+
pluginType: "property",
|
|
1407
|
+
name,
|
|
1408
|
+
namespace,
|
|
1409
|
+
id: makeId(name, namespace),
|
|
1410
|
+
standIn: true,
|
|
1411
|
+
imports: [],
|
|
1412
|
+
importBindings: []
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
function declareOptionalProperty(config) {
|
|
1416
|
+
const { name, namespace } = parseId(config.id);
|
|
1417
|
+
return {
|
|
1418
|
+
pluginType: "property",
|
|
1419
|
+
name,
|
|
1420
|
+
namespace,
|
|
1421
|
+
id: makeId(name, namespace),
|
|
1422
|
+
standIn: true,
|
|
1423
|
+
optional: true,
|
|
1424
|
+
imports: [],
|
|
1425
|
+
importBindings: []
|
|
1426
|
+
// Requires nothing (phantom carrier `<never, never>`): a consumer that
|
|
1427
|
+
// imports it still passes `createSdk`'s completeness check unprovided. The
|
|
1428
|
+
// import binding is still typed `TValue | undefined` from the descriptor.
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function defineHook(config) {
|
|
1432
|
+
const deps = normalizeImports(config.imports);
|
|
1433
|
+
return {
|
|
1434
|
+
pluginType: "hook",
|
|
1435
|
+
name: config.name,
|
|
1436
|
+
namespace: config.namespace,
|
|
1437
|
+
id: makeId(config.name, config.namespace),
|
|
1438
|
+
imports: deps.plugins,
|
|
1439
|
+
importBindings: deps.bindings,
|
|
1440
|
+
setup: config.setup,
|
|
1441
|
+
dispose: config.dispose,
|
|
1442
|
+
wrap: config.wrap,
|
|
1443
|
+
observe: config.observe
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
function declarePlugin(config) {
|
|
1447
|
+
const { name, namespace } = parseId(config.id);
|
|
1448
|
+
return {
|
|
1449
|
+
pluginType: "aggregate",
|
|
1450
|
+
name,
|
|
1451
|
+
namespace,
|
|
1452
|
+
id: makeId(name, namespace, "aggregate"),
|
|
1453
|
+
standIn: true,
|
|
1454
|
+
imports: [],
|
|
1455
|
+
importBindings: [],
|
|
1456
|
+
exports: normalizeExports(config.exports)
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function definePlugin(fnOrConfig) {
|
|
1460
|
+
if (typeof fnOrConfig === "function") {
|
|
1461
|
+
logDeprecation(
|
|
1462
|
+
"definePlugin(fn) (the function form) is deprecated. Author plugins with defineMethod/defineProperty/definePlugin({ ... }) instead."
|
|
1463
|
+
);
|
|
1464
|
+
return fnOrConfig;
|
|
1465
|
+
}
|
|
1466
|
+
const config = fnOrConfig;
|
|
1467
|
+
const deps = normalizeImports(config.imports);
|
|
1468
|
+
return {
|
|
1469
|
+
pluginType: "aggregate",
|
|
1470
|
+
name: config.name,
|
|
1471
|
+
namespace: config.namespace,
|
|
1472
|
+
id: makeId(config.name, config.namespace, "aggregate"),
|
|
1473
|
+
// A re-export synthetic (`selectExports` / `omitExports`) is flattened by
|
|
1474
|
+
// `normalizeExports` into bare bindings, which drops its own `imports:
|
|
1475
|
+
// [source]`. That edge is how `omitExports` keeps an omitted (unbound) leaf
|
|
1476
|
+
// materialized + addressable by id, so preserve every exported aggregate's
|
|
1477
|
+
// imports as extra reachability edges here (bindings unaffected).
|
|
1478
|
+
imports: [...deps.plugins, ...exportedAggregateImports(config.exports)],
|
|
1479
|
+
importBindings: deps.bindings,
|
|
1480
|
+
exports: normalizeExports(config.exports)
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
function exportedAggregateImports(exports) {
|
|
1484
|
+
if (!exports) return [];
|
|
1485
|
+
const out = [];
|
|
1486
|
+
for (const element of exports) {
|
|
1487
|
+
if (element.pluginType === "aggregate") out.push(...element.imports);
|
|
1488
|
+
}
|
|
1489
|
+
return out;
|
|
1490
|
+
}
|
|
1491
|
+
function normalizeExports(exports) {
|
|
1492
|
+
if (!exports) return {};
|
|
1493
|
+
const out = {};
|
|
1494
|
+
const add = (binding, leaf) => {
|
|
1495
|
+
const existing = out[binding];
|
|
1496
|
+
if (existing && existing.id !== leaf.id) {
|
|
1497
|
+
throw new Error(
|
|
1498
|
+
`definePlugin: duplicate export binding "${binding}". Two different plugins ("${existing.id}" and "${leaf.id}") bind the same name; wrap one in selectExports to rename it.`
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
out[binding] = leaf;
|
|
1502
|
+
};
|
|
1503
|
+
for (const element of exports) {
|
|
1504
|
+
if (element.pluginType === "aggregate") {
|
|
1505
|
+
for (const [binding, child] of Object.entries(element.exports)) {
|
|
1506
|
+
add(binding, child);
|
|
1507
|
+
}
|
|
1508
|
+
} else {
|
|
1509
|
+
add(element.name, element);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return out;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// src/model/exports.ts
|
|
1516
|
+
var selectSeq = 0;
|
|
1517
|
+
function selectExports(source, ...specs) {
|
|
1518
|
+
const selected = {};
|
|
1519
|
+
const pick = (binding, fromName) => {
|
|
1520
|
+
const child = source.exports[fromName];
|
|
1521
|
+
if (!child) {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`selectExports: "${source.id}" has no export "${fromName}".`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
selected[binding] = child;
|
|
1527
|
+
};
|
|
1528
|
+
for (const spec of specs) {
|
|
1529
|
+
if (typeof spec === "string") {
|
|
1530
|
+
pick(spec, spec);
|
|
1531
|
+
} else {
|
|
1532
|
+
for (const [newName, fromName] of Object.entries(spec)) {
|
|
1533
|
+
pick(newName, fromName);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const id = `${source.id}#select:${selectSeq++}`;
|
|
1538
|
+
return {
|
|
1539
|
+
pluginType: "aggregate",
|
|
1540
|
+
name: makeId(`select`, source.name, "aggregate"),
|
|
1541
|
+
id,
|
|
1542
|
+
// Depend on the source so it is materialized; the selected bindings resolve
|
|
1543
|
+
// to the source's own leaves (kept identity).
|
|
1544
|
+
imports: [source],
|
|
1545
|
+
importBindings: [],
|
|
1546
|
+
exports: selected
|
|
1547
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
function omitExports(source, omit) {
|
|
1551
|
+
const omitSet = new Set(omit);
|
|
1552
|
+
for (const name of omit) {
|
|
1553
|
+
if (!(name in source.exports)) {
|
|
1554
|
+
throw new Error(`omitExports: "${source.id}" has no export "${name}".`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const kept = {};
|
|
1558
|
+
for (const [binding, child] of Object.entries(source.exports)) {
|
|
1559
|
+
if (!omitSet.has(binding)) kept[binding] = child;
|
|
1560
|
+
}
|
|
1561
|
+
return {
|
|
1562
|
+
pluginType: "aggregate",
|
|
1563
|
+
name: makeId(`omit`, source.name, "aggregate"),
|
|
1564
|
+
id: `${source.id}#omit:${selectSeq++}`,
|
|
1565
|
+
imports: [source],
|
|
1566
|
+
importBindings: [],
|
|
1567
|
+
exports: kept
|
|
1568
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// src/model/legacy.ts
|
|
1573
|
+
function fromFunctionPlugin(fn, config) {
|
|
1574
|
+
logDeprecation(
|
|
1575
|
+
"fromFunctionPlugin() is deprecated. Author plugins with defineMethod/definePlugin instead."
|
|
1576
|
+
);
|
|
1577
|
+
return {
|
|
1578
|
+
pluginType: "legacy",
|
|
1579
|
+
name: config.name,
|
|
1580
|
+
namespace: config.namespace,
|
|
1581
|
+
id: makeId(config.name, config.namespace, "aggregate"),
|
|
1582
|
+
imports: [],
|
|
1583
|
+
importBindings: [],
|
|
1584
|
+
run: fn
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function defineLegacyMerge(args) {
|
|
1588
|
+
logDeprecation(
|
|
1589
|
+
"defineLegacyMerge() is deprecated. Build directly with createSdk(root, { configuration }) instead."
|
|
1590
|
+
);
|
|
1591
|
+
return {
|
|
1592
|
+
pluginType: "legacy-merge",
|
|
1593
|
+
name: args.name,
|
|
1594
|
+
namespace: args.namespace,
|
|
1595
|
+
id: makeId(args.name, args.namespace, "aggregate"),
|
|
1596
|
+
legacy: fromFunctionPlugin(args.legacy, {
|
|
1597
|
+
name: args.name,
|
|
1598
|
+
namespace: args.namespace
|
|
1599
|
+
}),
|
|
1600
|
+
plugin: args.plugin
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function legacyGraphEntry(name, value, pluginMeta) {
|
|
1604
|
+
const { inputSchema, ...rest } = pluginMeta ?? {};
|
|
1605
|
+
const meta = Object.keys(rest).length ? rest : void 0;
|
|
1606
|
+
if (typeof value === "function") {
|
|
1607
|
+
return {
|
|
1608
|
+
pluginType: "method",
|
|
1609
|
+
name,
|
|
1610
|
+
value,
|
|
1611
|
+
chain: [],
|
|
1612
|
+
...inputSchema ? { inputSchema } : {},
|
|
1613
|
+
...meta ? { meta } : {}
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
return { pluginType: "property", name, value, ...meta ? { meta } : {} };
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// src/model/builtins.ts
|
|
1620
|
+
import { z as z2 } from "zod";
|
|
1621
|
+
|
|
1622
|
+
// src/model/registry-support.ts
|
|
1623
|
+
function adaptLegacyFormatter(legacy, sdk) {
|
|
1624
|
+
const legacyFetch = legacy.fetch;
|
|
1625
|
+
return {
|
|
1626
|
+
getContext: legacyFetch ? async ({ items, input, context }) => {
|
|
1627
|
+
let ctx = context;
|
|
1628
|
+
for (const item of items) {
|
|
1629
|
+
ctx = await legacyFetch(sdk, input, item, ctx);
|
|
1630
|
+
}
|
|
1631
|
+
return ctx;
|
|
1632
|
+
} : void 0,
|
|
1633
|
+
format: ({ item, context }) => legacy.format(item, context)
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function normalizeFormatter(entry, sdk) {
|
|
1637
|
+
if (entry.pluginType !== "method") return void 0;
|
|
1638
|
+
if (entry.formatter) return entry.formatter;
|
|
1639
|
+
const legacy = entry.meta?.formatter;
|
|
1640
|
+
return legacy ? adaptLegacyFormatter(legacy, sdk) : void 0;
|
|
1641
|
+
}
|
|
1642
|
+
function normalizeBoundResolvers(entry) {
|
|
1643
|
+
if (entry.pluginType !== "method") return void 0;
|
|
1644
|
+
return entry.resolvers;
|
|
1645
|
+
}
|
|
1646
|
+
function methodPositional(entry) {
|
|
1647
|
+
if (entry.pluginType !== "method") return void 0;
|
|
1648
|
+
return entry.positional;
|
|
1649
|
+
}
|
|
1650
|
+
function pluginEntryMeta(entry) {
|
|
1651
|
+
if (entry.pluginType === "method" && entry.meta) {
|
|
1652
|
+
return entry.inputSchema ? { ...entry.meta, inputSchema: entry.inputSchema } : entry.meta;
|
|
1653
|
+
}
|
|
1654
|
+
if (entry.pluginType === "property" && entry.meta) return entry.meta;
|
|
1655
|
+
return void 0;
|
|
1656
|
+
}
|
|
1657
|
+
function foldDynamicMembers(entry, surfaceBindings, meta) {
|
|
1658
|
+
if (entry.pluginType !== "property" || !entry.dynamicMembers) return;
|
|
1659
|
+
for (const member of entry.dynamicMembers) {
|
|
1660
|
+
if (!surfaceBindings.has(member.rootBinding)) {
|
|
1661
|
+
throw new Error(
|
|
1662
|
+
`dynamicMember "${member.name}": its root "${member.rootBinding}" is not a surfaced member. A dynamic member's path must start with a real binding.`
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
meta[member.name] = member.meta;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
function collectSurfaceProjection(context, formatterSdk) {
|
|
1669
|
+
const meta = {};
|
|
1670
|
+
const entries = {};
|
|
1671
|
+
for (const [binding, id] of Object.entries(context.surface)) {
|
|
1672
|
+
const entry = context.plugins[id];
|
|
1673
|
+
if (!entry || entry.pluginType === "aggregate") continue;
|
|
1674
|
+
entries[binding] = entry;
|
|
1675
|
+
const m = pluginEntryMeta(entry);
|
|
1676
|
+
if (m) meta[binding] = m;
|
|
1677
|
+
}
|
|
1678
|
+
const surfaceBindings = new Set(Object.keys(context.surface));
|
|
1679
|
+
for (const entry of Object.values(entries)) {
|
|
1680
|
+
foldDynamicMembers(entry, surfaceBindings, meta);
|
|
1681
|
+
}
|
|
1682
|
+
const formatters = {};
|
|
1683
|
+
const boundResolvers = {};
|
|
1684
|
+
const positional = {};
|
|
1685
|
+
for (const [binding, entry] of Object.entries(entries)) {
|
|
1686
|
+
const f = normalizeFormatter(entry, formatterSdk);
|
|
1687
|
+
if (f) formatters[binding] = f;
|
|
1688
|
+
const r = normalizeBoundResolvers(entry);
|
|
1689
|
+
if (r) boundResolvers[binding] = r;
|
|
1690
|
+
const p = methodPositional(entry);
|
|
1691
|
+
if (p) positional[binding] = p;
|
|
1692
|
+
}
|
|
1693
|
+
return { meta, formatters, boundResolvers, positional };
|
|
1694
|
+
}
|
|
1695
|
+
function buildSurfaceRegistry(context, packageFilter) {
|
|
1696
|
+
const surface = {};
|
|
1697
|
+
for (const [binding, id] of Object.entries(context.surface)) {
|
|
1698
|
+
const entry = context.plugins[id];
|
|
1699
|
+
if (!entry || entry.pluginType === "aggregate") continue;
|
|
1700
|
+
surface[binding] = entry.pluginType === "property" && entry.getValue ? entry.getValue() : entry.value;
|
|
1701
|
+
}
|
|
1702
|
+
return buildRegistry({
|
|
1703
|
+
sdk: surface,
|
|
1704
|
+
...collectSurfaceProjection(context, surface),
|
|
1705
|
+
packageFilter
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/model/builtins.ts
|
|
1710
|
+
var coreOptionsPluginRef = declareOptionalProperty({ id: CORE_OPTIONS_ID });
|
|
1711
|
+
var dangerousContextPlugin = {
|
|
1712
|
+
pluginType: "property",
|
|
1713
|
+
name: "context",
|
|
1714
|
+
namespace: "kitcore",
|
|
1715
|
+
id: "kitcore/context",
|
|
1716
|
+
imports: [],
|
|
1717
|
+
importBindings: [],
|
|
1718
|
+
privileged: true
|
|
1719
|
+
};
|
|
1720
|
+
var getRegistryPlugin = defineMethod({
|
|
1721
|
+
name: "getRegistry",
|
|
1722
|
+
namespace: "kitcore",
|
|
1723
|
+
imports: [dangerousContextPlugin],
|
|
1724
|
+
inputSchema: z2.object({ package: z2.string().optional() }).optional(),
|
|
1725
|
+
run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
// src/model/materialize.ts
|
|
1729
|
+
var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
|
|
1730
|
+
CORE_OPTIONS_ID
|
|
1731
|
+
]);
|
|
1732
|
+
function normalizeOutput(output) {
|
|
1733
|
+
if (output === void 0) return { type: "raw" };
|
|
1734
|
+
if (typeof output === "string") return { type: output };
|
|
1735
|
+
return output;
|
|
1736
|
+
}
|
|
1737
|
+
var CONTEXT = Symbol.for("kitcore.context");
|
|
1738
|
+
function getContext(sdk) {
|
|
1739
|
+
return sdk[CONTEXT];
|
|
1740
|
+
}
|
|
1741
|
+
function isResolverRef(value) {
|
|
1742
|
+
return "ref" in value;
|
|
1743
|
+
}
|
|
1744
|
+
function nestedResolvers(resolver) {
|
|
1745
|
+
const out = [];
|
|
1746
|
+
if (resolver.type === "object") {
|
|
1747
|
+
for (const field of Object.values(resolver.properties ?? {})) {
|
|
1748
|
+
if (!isResolverRef(field.resolver)) out.push(field.resolver);
|
|
1749
|
+
}
|
|
1750
|
+
out.push(...Object.values(resolver.definitions ?? {}));
|
|
1751
|
+
} else if (resolver.type === "array") {
|
|
1752
|
+
if (!isResolverRef(resolver.items)) out.push(resolver.items);
|
|
1753
|
+
out.push(...Object.values(resolver.definitions ?? {}));
|
|
1754
|
+
}
|
|
1755
|
+
return out;
|
|
1756
|
+
}
|
|
1757
|
+
function resolverImportEdges(resolver) {
|
|
1758
|
+
const out = [...resolver.imports];
|
|
1759
|
+
for (const child of nestedResolvers(resolver)) {
|
|
1760
|
+
out.push(...resolverImportEdges(child));
|
|
1761
|
+
}
|
|
1762
|
+
return out;
|
|
1763
|
+
}
|
|
1764
|
+
function methodAttachmentEdges(plugin) {
|
|
1765
|
+
const out = [];
|
|
1766
|
+
if (plugin.resolvers) {
|
|
1767
|
+
for (const resolver of Object.values(plugin.resolvers)) {
|
|
1768
|
+
out.push(...resolverImportEdges(resolver));
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (plugin.formatter) out.push(...plugin.formatter.imports);
|
|
1772
|
+
return out;
|
|
1773
|
+
}
|
|
1774
|
+
function edgesOf(plugin) {
|
|
1775
|
+
if (plugin.pluginType === "aggregate") {
|
|
1776
|
+
return [...Object.values(plugin.exports), ...plugin.imports];
|
|
1777
|
+
}
|
|
1778
|
+
if (plugin.pluginType === "method") {
|
|
1779
|
+
return [...plugin.imports, ...methodAttachmentEdges(plugin)];
|
|
1780
|
+
}
|
|
1781
|
+
return plugin.imports;
|
|
1782
|
+
}
|
|
1783
|
+
function isStandIn(plugin) {
|
|
1784
|
+
return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
|
|
1785
|
+
}
|
|
1786
|
+
function topoOrder(descriptors) {
|
|
1787
|
+
const order = [];
|
|
1788
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1789
|
+
const visit = (id) => {
|
|
1790
|
+
if (visited.has(id)) return;
|
|
1791
|
+
visited.add(id);
|
|
1792
|
+
const descriptor = descriptors.get(id);
|
|
1793
|
+
if (descriptor) for (const edge of edgesOf(descriptor)) visit(edge.id);
|
|
1794
|
+
order.push(id);
|
|
1795
|
+
};
|
|
1796
|
+
for (const id of descriptors.keys()) visit(id);
|
|
1797
|
+
return order;
|
|
1798
|
+
}
|
|
1799
|
+
function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
|
|
1800
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1801
|
+
const visit = (plugin) => {
|
|
1802
|
+
if (materialized.has(plugin.id)) return;
|
|
1803
|
+
const existing = byId.get(plugin.id);
|
|
1804
|
+
if (existing === plugin) return;
|
|
1805
|
+
if (existing) {
|
|
1806
|
+
const bothReal = !isStandIn(existing) && !isStandIn(plugin);
|
|
1807
|
+
if (bothReal) {
|
|
1808
|
+
throw new Error(
|
|
1809
|
+
`createSdk: duplicate plugin id "${plugin.id}". Two different plugins registered under the same id.`
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
if (isStandIn(existing) && !isStandIn(plugin)) {
|
|
1813
|
+
byId.set(plugin.id, plugin);
|
|
1814
|
+
for (const edge of edgesOf(plugin)) visit(edge);
|
|
1815
|
+
}
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
byId.set(plugin.id, plugin);
|
|
1819
|
+
for (const edge of edgesOf(plugin)) visit(edge);
|
|
1820
|
+
};
|
|
1821
|
+
visit(root);
|
|
1822
|
+
if (configuration) {
|
|
1823
|
+
for (const [id, value] of Object.entries(configuration)) {
|
|
1824
|
+
const existing = byId.get(id);
|
|
1825
|
+
if (!existing) {
|
|
1826
|
+
if (FRAMEWORK_CONFIGURATION_IDS.has(id)) {
|
|
1827
|
+
const { name, namespace } = parseId(id);
|
|
1828
|
+
byId.set(id, {
|
|
1829
|
+
pluginType: "property",
|
|
1830
|
+
name,
|
|
1831
|
+
namespace,
|
|
1832
|
+
id,
|
|
1833
|
+
imports: [],
|
|
1834
|
+
importBindings: [],
|
|
1835
|
+
value
|
|
1836
|
+
});
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
throw new Error(
|
|
1840
|
+
`createSdk: configuration id "${id}" matches no plugin in the graph. An injected value must satisfy a property stand-in reachable from the root.`
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
if (existing.pluginType !== "property") {
|
|
1844
|
+
throw new Error(
|
|
1845
|
+
`createSdk: configuration id "${id}" resolves to a "${existing.pluginType}" plugin; only property values can be injected.`
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
if (!isStandIn(existing)) {
|
|
1849
|
+
throw new Error(
|
|
1850
|
+
`createSdk: configuration id "${id}" collides with a registered provider. A property is either injected or provided by a plugin, not both.`
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
byId.set(id, {
|
|
1854
|
+
pluginType: "property",
|
|
1855
|
+
name: existing.name,
|
|
1856
|
+
namespace: existing.namespace,
|
|
1857
|
+
id,
|
|
1858
|
+
imports: [],
|
|
1859
|
+
importBindings: [],
|
|
1860
|
+
value
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
for (const [id, plugin] of byId) {
|
|
1865
|
+
if (isStandIn(plugin)) {
|
|
1866
|
+
if ("optional" in plugin && plugin.optional) continue;
|
|
1867
|
+
throw new Error(
|
|
1868
|
+
`createSdk: missing dependency "${id}". A plugin depends on it (via a stand-in) but no implementation was registered.`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return byId;
|
|
1873
|
+
}
|
|
1874
|
+
function bindValue(target, key2, entry, callType = "surface") {
|
|
1875
|
+
if (entry.pluginType === "property" && entry.getValue) {
|
|
1876
|
+
Object.defineProperty(target, key2, {
|
|
1877
|
+
get: entry.getValue,
|
|
1878
|
+
enumerable: true,
|
|
1879
|
+
configurable: true
|
|
1880
|
+
});
|
|
1881
|
+
} else {
|
|
1882
|
+
const value = callType === "internal" && entry.pluginType === "method" ? entry.internalValue ?? entry.value : entry.value;
|
|
1883
|
+
Object.defineProperty(target, key2, {
|
|
1884
|
+
value,
|
|
1885
|
+
writable: true,
|
|
1886
|
+
enumerable: true,
|
|
1887
|
+
configurable: true
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function buildSurface(context, ...maps) {
|
|
1892
|
+
const sdk = {};
|
|
1893
|
+
for (const map of maps) {
|
|
1894
|
+
Object.defineProperties(sdk, Object.getOwnPropertyDescriptors(map));
|
|
1895
|
+
}
|
|
1896
|
+
sdk.context = context;
|
|
1897
|
+
sdk[CONTEXT] = context;
|
|
1898
|
+
return sdk;
|
|
1899
|
+
}
|
|
1900
|
+
function buildImports(plugins, importBindings) {
|
|
1901
|
+
const imports = {};
|
|
1902
|
+
for (const { binding, id, optional } of importBindings) {
|
|
1903
|
+
const entry = plugins[id];
|
|
1904
|
+
if (!entry && optional) {
|
|
1905
|
+
Object.defineProperty(imports, binding, {
|
|
1906
|
+
value: void 0,
|
|
1907
|
+
writable: true,
|
|
1908
|
+
enumerable: true,
|
|
1909
|
+
configurable: true
|
|
1910
|
+
});
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
bindValue(imports, binding, entry, "internal");
|
|
1914
|
+
}
|
|
1915
|
+
return imports;
|
|
1916
|
+
}
|
|
1917
|
+
function mirrorLegacyRootKeys(context, rootKeys, meta) {
|
|
1918
|
+
const exports = {};
|
|
1919
|
+
for (const [name, value] of Object.entries(rootKeys)) {
|
|
1920
|
+
context.plugins[name] = legacyGraphEntry(name, value, meta[name]);
|
|
1921
|
+
exports[name] = value;
|
|
1922
|
+
}
|
|
1923
|
+
return exports;
|
|
1924
|
+
}
|
|
1925
|
+
function recordExportSurface(context, exports) {
|
|
1926
|
+
for (const [binding, child] of Object.entries(exports)) {
|
|
1927
|
+
context.surface[binding] = child.id;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
function materialize(descriptors, context) {
|
|
1931
|
+
const states = /* @__PURE__ */ new Map();
|
|
1932
|
+
runLegacyPass(descriptors, context);
|
|
1933
|
+
buildMethodEntries(descriptors, context, states);
|
|
1934
|
+
buildEagerArtifacts(descriptors, context, states);
|
|
1935
|
+
bindAttachments(descriptors, context);
|
|
1936
|
+
resolveAggregates(descriptors, context);
|
|
1937
|
+
assembleMiddleware(descriptors, context, states);
|
|
1938
|
+
assembleHooks(descriptors, context, states);
|
|
1939
|
+
applyMethodOverrides(descriptors, context);
|
|
1940
|
+
return context.plugins;
|
|
1941
|
+
}
|
|
1942
|
+
function applyMethodOverride(context, override) {
|
|
1943
|
+
const entry = context.plugins[override.target];
|
|
1944
|
+
if (!entry) {
|
|
1945
|
+
throw new Error(
|
|
1946
|
+
`defineMethodOverride: no method "${override.target}" to override. Include the target method in the SDK build.`
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
if (entry.pluginType !== "method") {
|
|
1950
|
+
throw new Error(
|
|
1951
|
+
`defineMethodOverride: "${override.target}" is a ${entry.pluginType}, not a method; only methods can be overridden.`
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
entry.meta = { ...entry.meta, ...override.meta };
|
|
1955
|
+
}
|
|
1956
|
+
function applyMethodOverrides(descriptors, context) {
|
|
1957
|
+
for (const descriptor of descriptors.values()) {
|
|
1958
|
+
if (descriptor.pluginType !== "method-override") continue;
|
|
1959
|
+
applyMethodOverride(context, descriptor);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function bindResolver(resolver, plugins) {
|
|
1963
|
+
switch (resolver.type) {
|
|
1964
|
+
case "static":
|
|
1965
|
+
return {
|
|
1966
|
+
type: "static",
|
|
1967
|
+
requireParameters: resolver.requireParameters,
|
|
1968
|
+
inputType: resolver.inputType,
|
|
1969
|
+
placeholder: resolver.placeholder
|
|
1970
|
+
};
|
|
1971
|
+
case "constant":
|
|
1972
|
+
return {
|
|
1973
|
+
type: "constant",
|
|
1974
|
+
value: resolver.value,
|
|
1975
|
+
requireParameters: resolver.requireParameters
|
|
1976
|
+
};
|
|
1977
|
+
case "info":
|
|
1978
|
+
return { type: "info", text: resolver.text };
|
|
1979
|
+
case "object": {
|
|
1980
|
+
const imports = buildImports(plugins, resolver.importBindings);
|
|
1981
|
+
const bound = {
|
|
1982
|
+
type: "object",
|
|
1983
|
+
requireParameters: resolver.requireParameters
|
|
1984
|
+
};
|
|
1985
|
+
if (resolver.properties)
|
|
1986
|
+
bound.properties = bindFields(resolver.properties, plugins);
|
|
1987
|
+
if (resolver.definitions)
|
|
1988
|
+
bound.definitions = bindDefinitions(resolver.definitions, plugins);
|
|
1989
|
+
const { getProperties } = resolver;
|
|
1990
|
+
if (getProperties)
|
|
1991
|
+
bound.getProperties = ({ input }) => getProperties({ imports, input });
|
|
1992
|
+
return bound;
|
|
1993
|
+
}
|
|
1994
|
+
case "array": {
|
|
1995
|
+
const bound = {
|
|
1996
|
+
type: "array",
|
|
1997
|
+
requireParameters: resolver.requireParameters,
|
|
1998
|
+
minItems: resolver.minItems,
|
|
1999
|
+
maxItems: resolver.maxItems,
|
|
2000
|
+
itemValueType: resolver.itemValueType,
|
|
2001
|
+
items: isResolverRef(resolver.items) ? resolver.items : bindResolver(resolver.items, plugins)
|
|
2002
|
+
};
|
|
2003
|
+
if (resolver.definitions)
|
|
2004
|
+
bound.definitions = bindDefinitions(resolver.definitions, plugins);
|
|
2005
|
+
return bound;
|
|
2006
|
+
}
|
|
2007
|
+
case "dynamic": {
|
|
2008
|
+
const imports = buildImports(plugins, resolver.importBindings);
|
|
2009
|
+
const {
|
|
2010
|
+
getContext: getContext2,
|
|
2011
|
+
listItems,
|
|
2012
|
+
tryResolveWithoutPrompt,
|
|
2013
|
+
tryResolveFromSearch
|
|
2014
|
+
} = resolver;
|
|
2015
|
+
const bound = {
|
|
2016
|
+
type: "dynamic",
|
|
2017
|
+
requireParameters: resolver.requireParameters,
|
|
2018
|
+
inputType: resolver.inputType,
|
|
2019
|
+
placeholder: resolver.placeholder,
|
|
2020
|
+
prompt: resolver.prompt,
|
|
2021
|
+
listItems: ({ input, context, search, cursor }) => listItems({ imports, input, context, search, cursor })
|
|
2022
|
+
};
|
|
2023
|
+
if (getContext2)
|
|
2024
|
+
bound.getContext = ({ input }) => getContext2({ imports, input });
|
|
2025
|
+
if (tryResolveWithoutPrompt) {
|
|
2026
|
+
bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
|
|
2027
|
+
}
|
|
2028
|
+
if (tryResolveFromSearch) {
|
|
2029
|
+
bound.tryResolveFromSearch = ({ input, search }) => tryResolveFromSearch({ imports, input, search });
|
|
2030
|
+
}
|
|
2031
|
+
return bound;
|
|
2032
|
+
}
|
|
2033
|
+
default: {
|
|
2034
|
+
const unhandled = resolver;
|
|
2035
|
+
throw new Error(
|
|
2036
|
+
`unhandled resolver kind: ${unhandled.type}`
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
function bindFields(fields, plugins) {
|
|
2042
|
+
const out = {};
|
|
2043
|
+
for (const [key2, field] of Object.entries(fields)) {
|
|
2044
|
+
out[key2] = {
|
|
2045
|
+
...field,
|
|
2046
|
+
resolver: isResolverRef(field.resolver) ? field.resolver : bindResolver(field.resolver, plugins)
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
return out;
|
|
2050
|
+
}
|
|
2051
|
+
function bindDefinitions(definitions, plugins) {
|
|
2052
|
+
const out = {};
|
|
2053
|
+
for (const [key2, def] of Object.entries(definitions)) {
|
|
2054
|
+
out[key2] = bindResolver(def, plugins);
|
|
2055
|
+
}
|
|
2056
|
+
return out;
|
|
2057
|
+
}
|
|
2058
|
+
function bindFormatter(formatter, plugins) {
|
|
2059
|
+
const imports = buildImports(plugins, formatter.importBindings);
|
|
2060
|
+
const bound = { format: formatter.format };
|
|
2061
|
+
const { getContext: getContext2 } = formatter;
|
|
2062
|
+
if (getContext2)
|
|
2063
|
+
bound.getContext = ({ items, input, context }) => getContext2({ imports, items, input, context });
|
|
2064
|
+
return bound;
|
|
2065
|
+
}
|
|
2066
|
+
function bindAttachments(descriptors, context) {
|
|
2067
|
+
const plugins = context.plugins;
|
|
2068
|
+
for (const [id, descriptor] of descriptors) {
|
|
2069
|
+
if (descriptor.pluginType !== "method") continue;
|
|
2070
|
+
const entry = plugins[id];
|
|
2071
|
+
if (!entry || entry.pluginType !== "method") continue;
|
|
2072
|
+
if (descriptor.resolvers) {
|
|
2073
|
+
const bound = {};
|
|
2074
|
+
for (const [param, resolver] of Object.entries(descriptor.resolvers)) {
|
|
2075
|
+
bound[param] = bindResolver(resolver, plugins);
|
|
2076
|
+
}
|
|
2077
|
+
entry.resolvers = bound;
|
|
2078
|
+
}
|
|
2079
|
+
if (descriptor.formatter) {
|
|
2080
|
+
entry.formatter = bindFormatter(descriptor.formatter, plugins);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function runLegacyPass(descriptors, context) {
|
|
2085
|
+
const plugins = context.plugins;
|
|
2086
|
+
const compatView = new Proxy(
|
|
2087
|
+
{},
|
|
2088
|
+
{
|
|
2089
|
+
get: (_target, prop) => {
|
|
2090
|
+
if (prop === "context") return context;
|
|
2091
|
+
const entry = plugins[prop];
|
|
2092
|
+
return entry?.value;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
);
|
|
2096
|
+
for (const id of topoOrder(descriptors)) {
|
|
2097
|
+
const descriptor = descriptors.get(id);
|
|
2098
|
+
if (!descriptor || descriptor.pluginType !== "legacy") continue;
|
|
2099
|
+
const { rootKeys, meta, hooks, contextRest } = splitPluginContribution(
|
|
2100
|
+
descriptor.run(compatView)
|
|
2101
|
+
);
|
|
2102
|
+
Object.assign(context.meta, meta);
|
|
2103
|
+
Object.assign(context, contextRest);
|
|
2104
|
+
context.hooks = buildHooks(context.hooks, hooks);
|
|
2105
|
+
const exports = mirrorLegacyRootKeys(context, rootKeys, meta);
|
|
2106
|
+
if (!("getRegistry" in exports)) {
|
|
2107
|
+
let getRegistry2 = function(options) {
|
|
2108
|
+
const sdk = this ?? exports;
|
|
2109
|
+
const projection = collectSurfaceProjection(context, sdk);
|
|
2110
|
+
Object.assign(projection.meta, context.meta);
|
|
2111
|
+
return buildRegistry({
|
|
2112
|
+
sdk,
|
|
2113
|
+
...projection,
|
|
2114
|
+
packageFilter: options?.package
|
|
2115
|
+
});
|
|
2116
|
+
};
|
|
2117
|
+
var getRegistry = getRegistry2;
|
|
2118
|
+
exports.getRegistry = getRegistry2;
|
|
2119
|
+
plugins.getRegistry = {
|
|
2120
|
+
pluginType: "method",
|
|
2121
|
+
name: "getRegistry",
|
|
2122
|
+
value: getRegistry2,
|
|
2123
|
+
chain: []
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
plugins[id] = { pluginType: "aggregate", name: descriptor.name, exports };
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function buildMethodEntries(descriptors, context, states) {
|
|
2130
|
+
const plugins = context.plugins;
|
|
2131
|
+
for (const [id, descriptor] of descriptors) {
|
|
2132
|
+
if (descriptor.pluginType !== "method") continue;
|
|
2133
|
+
const out = normalizeOutput(descriptor.output);
|
|
2134
|
+
const entry = {
|
|
2135
|
+
pluginType: "method",
|
|
2136
|
+
name: descriptor.name,
|
|
2137
|
+
chain: [],
|
|
2138
|
+
inputSchema: descriptor.inputSchema,
|
|
2139
|
+
// Derive the presentation type from the output mode when the author did
|
|
2140
|
+
// not set one; an explicit meta.type (e.g. "create") still wins.
|
|
2141
|
+
meta: out.type === "raw" || descriptor.meta?.type ? descriptor.meta : { ...descriptor.meta, type: out.type },
|
|
2142
|
+
output: out,
|
|
2143
|
+
// Replaced below; never called.
|
|
2144
|
+
value: () => void 0
|
|
2145
|
+
};
|
|
2146
|
+
const callRun = (input) => descriptor.run({
|
|
2147
|
+
imports: buildImports(plugins, descriptor.importBindings),
|
|
2148
|
+
state: states.get(id),
|
|
2149
|
+
input
|
|
2150
|
+
});
|
|
2151
|
+
const fold = (coreFn) => (input) => {
|
|
2152
|
+
let next = coreFn;
|
|
2153
|
+
for (const wrap of entry.chain) {
|
|
2154
|
+
const inner = next;
|
|
2155
|
+
next = (i) => wrap.run({
|
|
2156
|
+
imports: buildImports(plugins, wrap.owner.importBindings),
|
|
2157
|
+
next: inner,
|
|
2158
|
+
input: i,
|
|
2159
|
+
// Overwritten by the chain item's own closure with the owning
|
|
2160
|
+
// hook's setup state.
|
|
2161
|
+
state: void 0
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
return next(input);
|
|
2165
|
+
};
|
|
2166
|
+
const sdk = { context };
|
|
2167
|
+
if (out.type === "list") {
|
|
2168
|
+
entry.value = createPaginatedFunction(
|
|
2169
|
+
fold(callRun),
|
|
2170
|
+
{
|
|
2171
|
+
sdk,
|
|
2172
|
+
schema: descriptor.inputSchema,
|
|
2173
|
+
name: descriptor.name,
|
|
2174
|
+
defaultPageSize: out.defaultPageSize,
|
|
2175
|
+
adaptPage: out.adaptPage,
|
|
2176
|
+
getDeprecation: () => entry.meta?.deprecation
|
|
2177
|
+
}
|
|
2178
|
+
);
|
|
2179
|
+
} else if (out.type === "item") {
|
|
2180
|
+
const itemCore = async (input) => ({
|
|
2181
|
+
data: await callRun(input)
|
|
2182
|
+
});
|
|
2183
|
+
entry.value = createFunction(
|
|
2184
|
+
fold(itemCore),
|
|
2185
|
+
{
|
|
2186
|
+
sdk,
|
|
2187
|
+
schema: descriptor.inputSchema,
|
|
2188
|
+
name: descriptor.name,
|
|
2189
|
+
getDeprecation: () => entry.meta?.deprecation
|
|
2190
|
+
}
|
|
2191
|
+
);
|
|
2192
|
+
} else {
|
|
2193
|
+
entry.value = createRawFunction(
|
|
2194
|
+
(input) => fold(callRun)(input),
|
|
2195
|
+
{
|
|
2196
|
+
sdk,
|
|
2197
|
+
name: descriptor.name,
|
|
2198
|
+
schema: descriptor.skipInputValidation ? void 0 : descriptor.inputSchema,
|
|
2199
|
+
positional: descriptor.positional,
|
|
2200
|
+
// The boundary reads the deprecation LIVE off the entry, so a
|
|
2201
|
+
// deprecation merged after build (defineMethodOverride, addPlugin)
|
|
2202
|
+
// fires too.
|
|
2203
|
+
getDeprecation: () => entry.meta?.deprecation
|
|
2204
|
+
}
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
const canonicalValue = entry.value;
|
|
2208
|
+
if (descriptor.positional) {
|
|
2209
|
+
const names = descriptor.positional;
|
|
2210
|
+
const pack = (args) => {
|
|
2211
|
+
const packed = {};
|
|
2212
|
+
names.forEach((name, i) => {
|
|
2213
|
+
if (i < args.length) packed[name] = args[i];
|
|
2214
|
+
});
|
|
2215
|
+
return packed;
|
|
2216
|
+
};
|
|
2217
|
+
entry.value = (...args) => canonicalValue(pack(args));
|
|
2218
|
+
entry.internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
|
|
2219
|
+
entry.positional = names;
|
|
2220
|
+
} else {
|
|
2221
|
+
entry.internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
|
|
2222
|
+
}
|
|
2223
|
+
plugins[id] = entry;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
function buildEagerArtifacts(descriptors, context, states) {
|
|
2227
|
+
const plugins = context.plugins;
|
|
2228
|
+
const built = /* @__PURE__ */ new Set();
|
|
2229
|
+
const building = /* @__PURE__ */ new Set();
|
|
2230
|
+
const ensureBuilt = (id) => {
|
|
2231
|
+
if (built.has(id)) return;
|
|
2232
|
+
const descriptor = descriptors.get(id);
|
|
2233
|
+
if (!descriptor || descriptor.pluginType === "aggregate" || descriptor.pluginType === "legacy" || descriptor.pluginType === "method-override" || isStandIn(descriptor)) {
|
|
2234
|
+
built.add(id);
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
if (building.has(id)) {
|
|
2238
|
+
throw new Error(`createSdk: dependency cycle at "${id}".`);
|
|
2239
|
+
}
|
|
2240
|
+
building.add(id);
|
|
2241
|
+
for (const { id: depId } of descriptor.importBindings) ensureBuilt(depId);
|
|
2242
|
+
const recordDisposer = () => {
|
|
2243
|
+
const dispose = descriptor.dispose;
|
|
2244
|
+
if (!dispose) return;
|
|
2245
|
+
context.disposers?.push({
|
|
2246
|
+
id,
|
|
2247
|
+
dispose: (input) => dispose({
|
|
2248
|
+
imports: buildImports(plugins, descriptor.importBindings),
|
|
2249
|
+
state: states.get(id),
|
|
2250
|
+
input
|
|
2251
|
+
})
|
|
2252
|
+
});
|
|
2253
|
+
};
|
|
2254
|
+
if (descriptor.pluginType === "hook") {
|
|
2255
|
+
states.set(
|
|
2256
|
+
id,
|
|
2257
|
+
descriptor.setup ? descriptor.setup({
|
|
2258
|
+
imports: buildImports(plugins, descriptor.importBindings)
|
|
2259
|
+
}) : void 0
|
|
2260
|
+
);
|
|
2261
|
+
recordDisposer();
|
|
2262
|
+
building.delete(id);
|
|
2263
|
+
built.add(id);
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (descriptor.pluginType === "method") {
|
|
2267
|
+
states.set(
|
|
2268
|
+
id,
|
|
2269
|
+
descriptor.setup ? descriptor.setup({
|
|
2270
|
+
imports: buildImports(plugins, descriptor.importBindings)
|
|
2271
|
+
}) : void 0
|
|
2272
|
+
);
|
|
2273
|
+
} else {
|
|
2274
|
+
states.set(
|
|
2275
|
+
id,
|
|
2276
|
+
descriptor.setup ? descriptor.setup({
|
|
2277
|
+
imports: buildImports(plugins, descriptor.importBindings)
|
|
2278
|
+
}) : void 0
|
|
2279
|
+
);
|
|
2280
|
+
if (descriptor.privileged) {
|
|
2281
|
+
plugins[id] = {
|
|
2282
|
+
pluginType: "property",
|
|
2283
|
+
name: descriptor.name,
|
|
2284
|
+
value: context,
|
|
2285
|
+
meta: descriptor.meta,
|
|
2286
|
+
dynamicMembers: descriptor.dynamicMembers
|
|
2287
|
+
};
|
|
2288
|
+
} else if (descriptor.get) {
|
|
2289
|
+
const get = descriptor.get;
|
|
2290
|
+
const importBindings = descriptor.importBindings;
|
|
2291
|
+
plugins[id] = {
|
|
2292
|
+
pluginType: "property",
|
|
2293
|
+
name: descriptor.name,
|
|
2294
|
+
getValue: () => get({
|
|
2295
|
+
imports: buildImports(plugins, importBindings),
|
|
2296
|
+
state: states.get(id)
|
|
2297
|
+
}),
|
|
2298
|
+
meta: descriptor.meta,
|
|
2299
|
+
dynamicMembers: descriptor.dynamicMembers
|
|
2300
|
+
};
|
|
2301
|
+
} else {
|
|
2302
|
+
plugins[id] = {
|
|
2303
|
+
pluginType: "property",
|
|
2304
|
+
name: descriptor.name,
|
|
2305
|
+
value: descriptor.value,
|
|
2306
|
+
meta: descriptor.meta,
|
|
2307
|
+
dynamicMembers: descriptor.dynamicMembers
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
recordDisposer();
|
|
2312
|
+
building.delete(id);
|
|
2313
|
+
built.add(id);
|
|
2314
|
+
};
|
|
2315
|
+
for (const id of descriptors.keys()) ensureBuilt(id);
|
|
2316
|
+
}
|
|
2317
|
+
function resolvePlugin(sdk, ref) {
|
|
2318
|
+
const entry = getContext(sdk).plugins[ref.id];
|
|
2319
|
+
if (!entry) {
|
|
2320
|
+
if (ref.optional) {
|
|
2321
|
+
return void 0;
|
|
2322
|
+
}
|
|
2323
|
+
throw new Error(
|
|
2324
|
+
`resolvePlugin: plugin "${ref.id}" is not materialized on the SDK.`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
if (entry.pluginType === "property" && entry.getValue) {
|
|
2328
|
+
return entry.getValue();
|
|
2329
|
+
}
|
|
2330
|
+
if (entry.pluginType === "method" && entry.internalValue) {
|
|
2331
|
+
return entry.internalValue;
|
|
2332
|
+
}
|
|
2333
|
+
return entry.value;
|
|
2334
|
+
}
|
|
2335
|
+
var CoreDisposeError = class extends Error {
|
|
2336
|
+
constructor(errors) {
|
|
2337
|
+
super(`disposeSdk: ${errors.length} dispose callback(s) failed.`);
|
|
2338
|
+
this.name = "CoreDisposeError";
|
|
2339
|
+
this.errors = errors;
|
|
2340
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
function disposeSdk(sdk, input) {
|
|
2344
|
+
const context = getContext(sdk);
|
|
2345
|
+
if (context.disposed) return context.disposed;
|
|
2346
|
+
const disposers = context.disposers ?? [];
|
|
2347
|
+
context.disposed = (async () => {
|
|
2348
|
+
const errors = [];
|
|
2349
|
+
for (let i = disposers.length - 1; i >= 0; i--) {
|
|
2350
|
+
try {
|
|
2351
|
+
await disposers[i].dispose(input);
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
errors.push(error);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (errors.length > 0) throw new CoreDisposeError(errors);
|
|
2357
|
+
})();
|
|
2358
|
+
return context.disposed;
|
|
2359
|
+
}
|
|
2360
|
+
function resolveAggregates(descriptors, context) {
|
|
2361
|
+
const plugins = context.plugins;
|
|
2362
|
+
for (const [id, descriptor] of descriptors) {
|
|
2363
|
+
if (descriptor.pluginType !== "aggregate") continue;
|
|
2364
|
+
const exports = {};
|
|
2365
|
+
for (const [binding, child] of Object.entries(descriptor.exports)) {
|
|
2366
|
+
bindValue(exports, binding, plugins[child.id]);
|
|
2367
|
+
}
|
|
2368
|
+
plugins[id] = { pluginType: "aggregate", name: descriptor.name, exports };
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
function assembleMiddleware(descriptors, context, states) {
|
|
2372
|
+
const plugins = context.plugins;
|
|
2373
|
+
for (const id of topoOrder(descriptors)) {
|
|
2374
|
+
const descriptor = descriptors.get(id);
|
|
2375
|
+
if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.wrap) {
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
for (const [targetBinding, fn] of Object.entries(descriptor.wrap)) {
|
|
2379
|
+
const edge = descriptor.importBindings.find(
|
|
2380
|
+
(b) => b.binding === targetBinding
|
|
2381
|
+
);
|
|
2382
|
+
if (!edge) {
|
|
2383
|
+
throw new Error(
|
|
2384
|
+
`createSdk: wrap target "${targetBinding}" in hook "${id}" is not a direct dependency. A wrap target must be a declared import of the wrapping hook.`
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
const target = plugins[edge.id];
|
|
2388
|
+
if (!target || target.pluginType !== "method") {
|
|
2389
|
+
throw new Error(
|
|
2390
|
+
`createSdk: wrap target "${targetBinding}" in hook "${id}" does not resolve to a method.`
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
if (target.output?.type === "list") {
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
`createSdk: wrap target "${targetBinding}" in hook "${id}" resolves to a list-output method, which does not support wrapping yet.`
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
target.chain.push({
|
|
2399
|
+
run: (bag) => fn({ ...bag, state: states.get(id) }),
|
|
2400
|
+
owner: descriptor
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function assembleHooks(descriptors, context, states) {
|
|
2406
|
+
const plugins = context.plugins;
|
|
2407
|
+
for (const id of topoOrder(descriptors)) {
|
|
2408
|
+
const descriptor = descriptors.get(id);
|
|
2409
|
+
if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.observe) {
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
const { observe } = descriptor;
|
|
2413
|
+
const imports = buildImports(plugins, descriptor.importBindings);
|
|
2414
|
+
const state = states.get(id);
|
|
2415
|
+
const contributed = {};
|
|
2416
|
+
if (observe.onMethodStart) {
|
|
2417
|
+
const onStart = observe.onMethodStart;
|
|
2418
|
+
contributed.onMethodStart = (input) => {
|
|
2419
|
+
runIsolatedObserver(() => onStart({ imports, input, state }));
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
if (observe.onMethodEnd) {
|
|
2423
|
+
const onEnd = observe.onMethodEnd;
|
|
2424
|
+
contributed.onMethodEnd = (input) => {
|
|
2425
|
+
runIsolatedObserver(() => onEnd({ imports, input, state }));
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
context.hooks = buildHooks(context.hooks, contributed);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
function createSdk(root, options) {
|
|
2432
|
+
const context = {
|
|
2433
|
+
plugins: {},
|
|
2434
|
+
meta: {},
|
|
2435
|
+
hooks: {},
|
|
2436
|
+
surface: {},
|
|
2437
|
+
disposers: []
|
|
2438
|
+
};
|
|
2439
|
+
if (root.pluginType === "legacy-merge") {
|
|
2440
|
+
const { legacy, plugin } = root;
|
|
2441
|
+
const collectRoot = {
|
|
2442
|
+
pluginType: "aggregate",
|
|
2443
|
+
name: root.name,
|
|
2444
|
+
id: `${root.id}:merge`,
|
|
2445
|
+
imports: [legacy, plugin],
|
|
2446
|
+
importBindings: [],
|
|
2447
|
+
exports: {}
|
|
2448
|
+
};
|
|
2449
|
+
const plugins2 = materialize(
|
|
2450
|
+
collectPlugins(collectRoot, void 0, options?.configuration),
|
|
2451
|
+
context
|
|
2452
|
+
);
|
|
2453
|
+
const legacyExports = plugins2[legacy.id].exports;
|
|
2454
|
+
let pluginSurface;
|
|
2455
|
+
if (plugin.pluginType === "aggregate") {
|
|
2456
|
+
pluginSurface = plugins2[plugin.id].exports;
|
|
2457
|
+
} else {
|
|
2458
|
+
pluginSurface = {};
|
|
2459
|
+
bindValue(pluginSurface, plugin.name, plugins2[plugin.id]);
|
|
2460
|
+
}
|
|
2461
|
+
for (const key2 of Object.keys(legacyExports)) context.surface[key2] = key2;
|
|
2462
|
+
if (plugin.pluginType === "aggregate") {
|
|
2463
|
+
recordExportSurface(context, plugin.exports);
|
|
2464
|
+
} else {
|
|
2465
|
+
context.surface[plugin.name] = plugin.id;
|
|
2466
|
+
}
|
|
2467
|
+
return buildSurface(context, legacyExports, pluginSurface);
|
|
2468
|
+
}
|
|
2469
|
+
const plugins = materialize(
|
|
2470
|
+
collectPlugins(root, void 0, options?.configuration),
|
|
2471
|
+
context
|
|
2472
|
+
);
|
|
2473
|
+
if (root.pluginType === "method" || root.pluginType === "property") {
|
|
2474
|
+
context.surface[root.name] = root.id;
|
|
2475
|
+
const sdk = buildSurface(context);
|
|
2476
|
+
bindValue(sdk, root.name, plugins[root.id]);
|
|
2477
|
+
return sdk;
|
|
2478
|
+
}
|
|
2479
|
+
if (root.pluginType === "aggregate")
|
|
2480
|
+
recordExportSurface(context, root.exports);
|
|
2481
|
+
return buildSurface(context, plugins[root.id].exports);
|
|
2482
|
+
}
|
|
2483
|
+
function addModelPlugin(sdk, plugin, options = {}) {
|
|
2484
|
+
const override = options.override === true;
|
|
2485
|
+
const context = getContext(sdk);
|
|
2486
|
+
const surfaceKeys = plugin.pluginType === "aggregate" ? Object.keys(plugin.exports) : plugin.pluginType === "hook" ? [] : [plugin.name];
|
|
2487
|
+
checkRootKeyCollisions(sdk, surfaceKeys, override, "addPlugin");
|
|
2488
|
+
const materialized = new Set(Object.keys(context.plugins));
|
|
2489
|
+
if (override && materialized.has(plugin.id)) {
|
|
2490
|
+
throw new Error(
|
|
2491
|
+
`addPlugin: cannot override already-materialized plugin "${plugin.id}" on the incremental path. Rebuild the SDK with the replacement via createSdk.`
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
materialize(collectPlugins(plugin, materialized), context);
|
|
2495
|
+
if (plugin.pluginType === "hook") return;
|
|
2496
|
+
const entry = context.plugins[plugin.id];
|
|
2497
|
+
if (entry.pluginType === "aggregate") {
|
|
2498
|
+
Object.defineProperties(
|
|
2499
|
+
sdk,
|
|
2500
|
+
Object.getOwnPropertyDescriptors(entry.exports)
|
|
2501
|
+
);
|
|
2502
|
+
for (const [binding, child] of Object.entries(
|
|
2503
|
+
plugin.exports
|
|
2504
|
+
)) {
|
|
2505
|
+
context.surface[binding] = child.id;
|
|
2506
|
+
}
|
|
2507
|
+
} else {
|
|
2508
|
+
bindValue(sdk, plugin.name, entry);
|
|
2509
|
+
context.surface[plugin.name] = plugin.id;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
function addPlugin(sdk, plugin, options) {
|
|
2513
|
+
if (typeof plugin === "function") {
|
|
2514
|
+
const record = sdk;
|
|
2515
|
+
const contribution = applyPluginToSdk(
|
|
2516
|
+
record,
|
|
2517
|
+
plugin,
|
|
2518
|
+
options ?? {}
|
|
2519
|
+
);
|
|
2520
|
+
const context = record[CONTEXT];
|
|
2521
|
+
if (context) {
|
|
2522
|
+
mirrorLegacyRootKeys(context, contribution.rootKeys, contribution.meta);
|
|
2523
|
+
for (const name of Object.keys(contribution.rootKeys)) {
|
|
2524
|
+
context.surface[name] = name;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
if (plugin.pluginType === "method-override") {
|
|
2530
|
+
applyMethodOverride(
|
|
2531
|
+
getContext(sdk),
|
|
2532
|
+
plugin
|
|
2533
|
+
);
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
addModelPlugin(
|
|
2537
|
+
sdk,
|
|
2538
|
+
plugin,
|
|
2539
|
+
options ?? {}
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// src/model/resolution/controller.ts
|
|
2544
|
+
import { z as z4 } from "zod";
|
|
2545
|
+
|
|
2546
|
+
// src/types/signals.ts
|
|
2547
|
+
var CORE_SIGNAL_SYMBOL = Symbol.for("kitcore.signal");
|
|
2548
|
+
var CoreSignal = class extends Error {
|
|
2549
|
+
constructor(message) {
|
|
2550
|
+
super(message);
|
|
2551
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
2552
|
+
Object.defineProperty(this, CORE_SIGNAL_SYMBOL, {
|
|
2553
|
+
value: true,
|
|
2554
|
+
enumerable: false,
|
|
2555
|
+
configurable: true,
|
|
2556
|
+
writable: false
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
function isCoreSignal(value) {
|
|
2561
|
+
return Boolean(
|
|
2562
|
+
value && typeof value === "object" && value[CORE_SIGNAL_SYMBOL] === true
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
var CoreCancelledSignal = class extends CoreSignal {
|
|
2566
|
+
constructor(message = "resolution cancelled") {
|
|
2567
|
+
super(message);
|
|
2568
|
+
this.name = "CoreCancelledSignal";
|
|
2569
|
+
this.code = "CANCELLED";
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
|
|
2573
|
+
// src/model/resolution/plan.ts
|
|
2574
|
+
import { z as z3 } from "zod";
|
|
2575
|
+
function unwrap(schema) {
|
|
2576
|
+
let inner = schema;
|
|
2577
|
+
let required = true;
|
|
2578
|
+
for (; ; ) {
|
|
2579
|
+
if (inner instanceof z3.ZodOptional) {
|
|
2580
|
+
required = false;
|
|
2581
|
+
inner = inner._zod.def.innerType;
|
|
2582
|
+
} else if (inner instanceof z3.ZodDefault) {
|
|
2583
|
+
required = false;
|
|
2584
|
+
inner = inner._zod.def.innerType;
|
|
2585
|
+
} else if (inner instanceof z3.ZodNullable) {
|
|
2586
|
+
inner = inner._zod.def.innerType;
|
|
2587
|
+
} else {
|
|
2588
|
+
break;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
return { inner, required };
|
|
2592
|
+
}
|
|
2593
|
+
function valueTypeOf(inner) {
|
|
2594
|
+
if (inner instanceof z3.ZodString) return "string";
|
|
2595
|
+
if (inner instanceof z3.ZodNumber) return "number";
|
|
2596
|
+
if (inner instanceof z3.ZodBoolean) return "boolean";
|
|
2597
|
+
if (inner instanceof z3.ZodEnum) return "string";
|
|
2598
|
+
if (inner instanceof z3.ZodArray) return "array";
|
|
2599
|
+
if (inner instanceof z3.ZodObject) return "object";
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
function staticChoicesOf(inner) {
|
|
2603
|
+
if (inner instanceof z3.ZodEnum) {
|
|
2604
|
+
const values = inner.options;
|
|
2605
|
+
return values.map((value) => ({ label: value, value }));
|
|
2606
|
+
}
|
|
2607
|
+
return void 0;
|
|
2608
|
+
}
|
|
2609
|
+
function objectShape(schema) {
|
|
2610
|
+
const { inner } = schema ? unwrap(schema) : { inner: void 0 };
|
|
2611
|
+
if (inner instanceof z3.ZodObject) {
|
|
2612
|
+
return inner.shape;
|
|
2613
|
+
}
|
|
2614
|
+
return void 0;
|
|
2615
|
+
}
|
|
2616
|
+
function topoOrder2(specs) {
|
|
2617
|
+
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
2618
|
+
const placed = /* @__PURE__ */ new Set();
|
|
2619
|
+
const ordered = [];
|
|
2620
|
+
const isReady = (spec) => spec.requires.every((r) => !byName.has(r) || placed.has(r));
|
|
2621
|
+
for (; ; ) {
|
|
2622
|
+
const next = specs.find((s) => !placed.has(s.name) && isReady(s));
|
|
2623
|
+
if (!next) break;
|
|
2624
|
+
ordered.push(next);
|
|
2625
|
+
placed.add(next.name);
|
|
2626
|
+
}
|
|
2627
|
+
for (const spec of specs) if (!placed.has(spec.name)) ordered.push(spec);
|
|
2628
|
+
return ordered;
|
|
2629
|
+
}
|
|
2630
|
+
function planParameters(entry) {
|
|
2631
|
+
const shape = objectShape(entry.inputSchema);
|
|
2632
|
+
const resolvers = entry.boundResolvers ?? {};
|
|
2633
|
+
const names = shape ? [
|
|
2634
|
+
...Object.keys(shape),
|
|
2635
|
+
...Object.keys(resolvers).filter(
|
|
2636
|
+
(name) => !(name in shape) && resolvers[name].type === "constant"
|
|
2637
|
+
)
|
|
2638
|
+
] : Object.keys(resolvers);
|
|
2639
|
+
const specs = names.map((name) => {
|
|
2640
|
+
const field = shape?.[name];
|
|
2641
|
+
const { inner, required } = field ? unwrap(field) : { inner: void 0, required: false };
|
|
2642
|
+
const resolver = resolvers[name];
|
|
2643
|
+
return {
|
|
2644
|
+
name,
|
|
2645
|
+
required,
|
|
2646
|
+
valueType: inner ? valueTypeOf(inner) : void 0,
|
|
2647
|
+
staticChoices: inner ? staticChoicesOf(inner) : void 0,
|
|
2648
|
+
resolver,
|
|
2649
|
+
requires: resolver?.requireParameters ?? []
|
|
2650
|
+
};
|
|
2651
|
+
});
|
|
2652
|
+
const declared = shape ? specs.filter((s) => s.name in shape) : specs;
|
|
2653
|
+
const scan = [
|
|
2654
|
+
...shape ? specs.filter((s) => !(s.name in shape)) : [],
|
|
2655
|
+
...declared.filter((s) => s.required),
|
|
2656
|
+
...declared.filter((s) => !s.required)
|
|
2657
|
+
];
|
|
2658
|
+
return { parameters: topoOrder2(scan) };
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/model/resolution/engine.ts
|
|
2662
|
+
function getAtPath(root, path) {
|
|
2663
|
+
let node = root;
|
|
2664
|
+
for (const seg of path) {
|
|
2665
|
+
if (node == null || typeof node !== "object") return void 0;
|
|
2666
|
+
node = node[seg];
|
|
2667
|
+
}
|
|
2668
|
+
return node;
|
|
2669
|
+
}
|
|
2670
|
+
function setAtPath(root, path, value) {
|
|
2671
|
+
let node = root;
|
|
2672
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
2673
|
+
const seg = path[i];
|
|
2674
|
+
if (node[seg] == null || typeof node[seg] !== "object") node[seg] = {};
|
|
2675
|
+
node = node[seg];
|
|
2676
|
+
}
|
|
2677
|
+
node[path[path.length - 1]] = value;
|
|
2678
|
+
}
|
|
2679
|
+
var key = (path) => path.join(".");
|
|
2680
|
+
function isSettled(state, path) {
|
|
2681
|
+
return state.settled.includes(key(path));
|
|
2682
|
+
}
|
|
2683
|
+
function remember(state, k) {
|
|
2684
|
+
if (!state.settled.includes(k)) state.settled.push(k);
|
|
2685
|
+
}
|
|
2686
|
+
function settle(state, path) {
|
|
2687
|
+
remember(state, key(path));
|
|
2688
|
+
}
|
|
2689
|
+
function clone(state) {
|
|
2690
|
+
return JSON.parse(JSON.stringify(state));
|
|
2691
|
+
}
|
|
2692
|
+
function coerce(leaf, raw) {
|
|
2693
|
+
if (typeof raw !== "string") return raw;
|
|
2694
|
+
if (leaf.valueType === "number") {
|
|
2695
|
+
const n = Number(raw);
|
|
2696
|
+
return raw.trim() !== "" && !Number.isNaN(n) ? n : raw;
|
|
2697
|
+
}
|
|
2698
|
+
if (leaf.valueType === "boolean") {
|
|
2699
|
+
if (raw === "true") return true;
|
|
2700
|
+
if (raw === "false") return false;
|
|
2701
|
+
}
|
|
2702
|
+
return raw;
|
|
2703
|
+
}
|
|
2704
|
+
async function validationError(leaf, value, state) {
|
|
2705
|
+
if (leaf.resolver?.type !== "dynamic") return null;
|
|
2706
|
+
const context = await resolveContext(leaf, state.resolved);
|
|
2707
|
+
const config = leaf.resolver.prompt?.({
|
|
2708
|
+
items: state.listing?.items ?? [],
|
|
2709
|
+
input: mergeInput(state.resolved, leaf.extraInput),
|
|
2710
|
+
context
|
|
2711
|
+
});
|
|
2712
|
+
if (!config?.validate) return null;
|
|
2713
|
+
const verdict = config.validate(value);
|
|
2714
|
+
if (verdict === true) return null;
|
|
2715
|
+
return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
|
|
2716
|
+
}
|
|
2717
|
+
function isRef(r) {
|
|
2718
|
+
return typeof r === "object" && r !== null && "ref" in r;
|
|
2719
|
+
}
|
|
2720
|
+
function toLeaf(name, field, definitions) {
|
|
2721
|
+
let resolver;
|
|
2722
|
+
let extraInput;
|
|
2723
|
+
if (isRef(field.resolver)) {
|
|
2724
|
+
resolver = definitions?.[field.resolver.ref];
|
|
2725
|
+
extraInput = field.resolver.input;
|
|
2726
|
+
} else {
|
|
2727
|
+
resolver = field.resolver;
|
|
2728
|
+
}
|
|
2729
|
+
return {
|
|
2730
|
+
name,
|
|
2731
|
+
required: field.required ?? false,
|
|
2732
|
+
label: field.label,
|
|
2733
|
+
resolver,
|
|
2734
|
+
extraInput,
|
|
2735
|
+
// Carry the field's value type so nested answers coerce (number/boolean)
|
|
2736
|
+
// the same way top-level params do. Without this a nested `z.number()`
|
|
2737
|
+
// field's typed answer stays a string and fails final validation.
|
|
2738
|
+
valueType: field.valueType,
|
|
2739
|
+
requires: resolver?.requireParameters ?? []
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
async function objectChildren(resolver, input) {
|
|
2743
|
+
if (resolver.properties) {
|
|
2744
|
+
return Object.entries(resolver.properties).map(
|
|
2745
|
+
([name, field]) => toLeaf(name, field, resolver.definitions)
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
if (!resolver.getProperties) return [];
|
|
2749
|
+
const fields = await resolver.getProperties({ input });
|
|
2750
|
+
return Object.entries(fields).map(([name, field]) => {
|
|
2751
|
+
if (!isRef(field.resolver) && (field.resolver.imports?.length ?? 0) > 0) {
|
|
2752
|
+
throw new Error(
|
|
2753
|
+
`dynamic object field "${name}" inlines an import-bearing resolver; use { ref } into the object's definitions so its imports bind`
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
return toLeaf(name, field, resolver.definitions);
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
function arrayItem(resolver) {
|
|
2760
|
+
const items = resolver.items;
|
|
2761
|
+
const valueType = resolver.itemValueType;
|
|
2762
|
+
if (isRef(items)) {
|
|
2763
|
+
return {
|
|
2764
|
+
name: "",
|
|
2765
|
+
required: true,
|
|
2766
|
+
resolver: resolver.definitions?.[items.ref],
|
|
2767
|
+
extraInput: items.input,
|
|
2768
|
+
valueType,
|
|
2769
|
+
requires: []
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
return {
|
|
2773
|
+
name: "",
|
|
2774
|
+
required: true,
|
|
2775
|
+
resolver: items,
|
|
2776
|
+
valueType,
|
|
2777
|
+
requires: []
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function autoSettles(resolver) {
|
|
2781
|
+
return resolver.type === "constant" || resolver.type === "info";
|
|
2782
|
+
}
|
|
2783
|
+
function mergeInput(input, extra) {
|
|
2784
|
+
return extra ? { ...input, ...extra } : input;
|
|
2785
|
+
}
|
|
2786
|
+
async function childrenAt(ctx, path, resolved) {
|
|
2787
|
+
if (path.length === 0) return ctx.parameters;
|
|
2788
|
+
const leaf = await leafAt(ctx, path, resolved);
|
|
2789
|
+
if (!leaf?.resolver || leaf.resolver.type !== "object") return [];
|
|
2790
|
+
return objectChildren(leaf.resolver, mergeInput(resolved, leaf.extraInput));
|
|
2791
|
+
}
|
|
2792
|
+
async function leafAt(ctx, path, resolved) {
|
|
2793
|
+
let children = ctx.parameters;
|
|
2794
|
+
let leaf;
|
|
2795
|
+
for (let i = 0; i < path.length; i++) {
|
|
2796
|
+
const seg = path[i];
|
|
2797
|
+
if (typeof seg === "number") {
|
|
2798
|
+
if (leaf?.resolver?.type !== "array") return void 0;
|
|
2799
|
+
leaf = arrayItem(leaf.resolver);
|
|
2800
|
+
} else {
|
|
2801
|
+
leaf = children.find((c) => c.name === seg);
|
|
2802
|
+
if (!leaf) return void 0;
|
|
2803
|
+
}
|
|
2804
|
+
if (i < path.length - 1 && typeof path[i + 1] === "string") {
|
|
2805
|
+
if (leaf?.resolver?.type !== "object") return void 0;
|
|
2806
|
+
children = await objectChildren(
|
|
2807
|
+
leaf.resolver,
|
|
2808
|
+
mergeInput(resolved, leaf.extraInput)
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return leaf;
|
|
2813
|
+
}
|
|
2814
|
+
async function arrayInfoAt(ctx, path, resolved) {
|
|
2815
|
+
const leaf = await leafAt(ctx, path, resolved);
|
|
2816
|
+
const resolver = leaf?.resolver;
|
|
2817
|
+
if (resolver?.type !== "array") {
|
|
2818
|
+
throw new Error(`expected an array resolver at "${key(path)}"`);
|
|
2819
|
+
}
|
|
2820
|
+
return {
|
|
2821
|
+
min: resolver.minItems ?? 0,
|
|
2822
|
+
max: resolver.maxItems ?? Infinity,
|
|
2823
|
+
item: { ...arrayItem(resolver), name: String(path[path.length - 1]) }
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
async function firstPage(result) {
|
|
2827
|
+
const page = await result;
|
|
2828
|
+
return {
|
|
2829
|
+
data: Array.isArray(page?.data) ? page.data : [],
|
|
2830
|
+
nextCursor: page?.nextCursor
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
async function resolveContext(leaf, input) {
|
|
2834
|
+
if (leaf.resolver?.type !== "dynamic") return void 0;
|
|
2835
|
+
return leaf.resolver.getContext?.({
|
|
2836
|
+
input: mergeInput(input, leaf.extraInput)
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
async function fetchListing(leaf, input, opts = {}) {
|
|
2840
|
+
const page = await firstPage(
|
|
2841
|
+
leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
|
|
2842
|
+
input: mergeInput(input, leaf.extraInput),
|
|
2843
|
+
context: opts.context,
|
|
2844
|
+
search: opts.search,
|
|
2845
|
+
cursor: opts.cursor
|
|
2846
|
+
}) : void 0
|
|
2847
|
+
);
|
|
2848
|
+
return {
|
|
2849
|
+
items: [...opts.priorItems ?? [], ...page.data],
|
|
2850
|
+
cursor: page.nextCursor,
|
|
2851
|
+
search: opts.search,
|
|
2852
|
+
exhausted: page.nextCursor == null
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/model/resolution/questions.ts
|
|
2857
|
+
function toChoice(c) {
|
|
2858
|
+
const label = "label" in c ? c.label : c.name;
|
|
2859
|
+
const hint = Array.isArray(c.hint) ? c.hint.join(", ") : c.hint;
|
|
2860
|
+
return { label, value: String(c.value), hint };
|
|
2861
|
+
}
|
|
2862
|
+
var AFFORDANCE = {
|
|
2863
|
+
choose: { action: "choose", description: "Pick one of the listed options" },
|
|
2864
|
+
custom: {
|
|
2865
|
+
action: "custom",
|
|
2866
|
+
description: "Provide a value directly",
|
|
2867
|
+
supply: "value"
|
|
2868
|
+
},
|
|
2869
|
+
search: {
|
|
2870
|
+
action: "search",
|
|
2871
|
+
description: "Filter the options by a search term",
|
|
2872
|
+
supply: "term"
|
|
2873
|
+
},
|
|
2874
|
+
more: { action: "more", description: "Load more options" },
|
|
2875
|
+
skip: { action: "skip", description: "Omit this optional parameter" },
|
|
2876
|
+
add: { action: "add", description: "Add another item" },
|
|
2877
|
+
done: { action: "done", description: "Finish the list" },
|
|
2878
|
+
retry: { action: "retry", description: "Retry loading the options" },
|
|
2879
|
+
cancel: { action: "cancel", description: "Cancel resolution" }
|
|
2880
|
+
};
|
|
2881
|
+
function selectActions(leaf, listing, multiple) {
|
|
2882
|
+
const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
|
|
2883
|
+
if (searchMode && listing.search === void 0 && listing.items.length === 0) {
|
|
2884
|
+
const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
|
|
2885
|
+
if (!leaf.required) actions2.push(AFFORDANCE.skip);
|
|
2886
|
+
return actions2;
|
|
2887
|
+
}
|
|
2888
|
+
const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
|
|
2889
|
+
if (searchMode) actions.push(AFFORDANCE.search);
|
|
2890
|
+
if (listing.cursor) actions.push(AFFORDANCE.more);
|
|
2891
|
+
if (!leaf.required) actions.push(AFFORDANCE.skip);
|
|
2892
|
+
return actions;
|
|
2893
|
+
}
|
|
2894
|
+
function labeledMessage(leaf) {
|
|
2895
|
+
if (!leaf.label) return void 0;
|
|
2896
|
+
return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
|
|
2897
|
+
}
|
|
2898
|
+
function selectQuestion(leaf, input, listing, context) {
|
|
2899
|
+
const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
|
|
2900
|
+
const config = resolver?.prompt?.({
|
|
2901
|
+
items: listing.items,
|
|
2902
|
+
input: mergeInput(input, leaf.extraInput),
|
|
2903
|
+
context
|
|
2904
|
+
});
|
|
2905
|
+
const multiple = config?.type === "checkbox";
|
|
2906
|
+
return {
|
|
2907
|
+
type: "select",
|
|
2908
|
+
// A labeled field's title beats the resolver's message: per-field
|
|
2909
|
+
// resolvers are shared across fields (one choices-fetcher for every
|
|
2910
|
+
// field), so only the leaf knows which field is being asked.
|
|
2911
|
+
message: labeledMessage(leaf) ?? config?.message ?? `Select ${leaf.name}:`,
|
|
2912
|
+
choices: (config?.choices ?? []).map(toChoice),
|
|
2913
|
+
...multiple ? { multiple: true } : {},
|
|
2914
|
+
...config?.notes?.length ? { notes: config.notes } : {},
|
|
2915
|
+
...listing.search !== void 0 ? { search: listing.search } : {},
|
|
2916
|
+
...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
|
|
2917
|
+
actions: selectActions(leaf, listing, multiple)
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
function toControllerError(error) {
|
|
2921
|
+
if (error instanceof Error) {
|
|
2922
|
+
const code = error.code;
|
|
2923
|
+
return {
|
|
2924
|
+
name: error.name,
|
|
2925
|
+
message: error.message,
|
|
2926
|
+
...typeof code === "string" ? { code } : {}
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
return { name: "Error", message: String(error) };
|
|
2930
|
+
}
|
|
2931
|
+
function failedResult(state, name, error) {
|
|
2932
|
+
return {
|
|
2933
|
+
state,
|
|
2934
|
+
result: {
|
|
2935
|
+
status: "failed",
|
|
2936
|
+
error: toControllerError(error),
|
|
2937
|
+
question: {
|
|
2938
|
+
type: "select",
|
|
2939
|
+
message: `Could not load options for ${name}.`,
|
|
2940
|
+
choices: [],
|
|
2941
|
+
actions: [AFFORDANCE.retry, AFFORDANCE.cancel]
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
async function buildQuestion(leaf, input) {
|
|
2947
|
+
const optional = !leaf.required;
|
|
2948
|
+
const resolver = leaf.resolver;
|
|
2949
|
+
if (resolver?.type === "dynamic") {
|
|
2950
|
+
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 });
|
|
2964
|
+
return {
|
|
2965
|
+
question: selectQuestion(leaf, input, listing, context),
|
|
2966
|
+
listing
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
if (leaf.staticChoices) {
|
|
2970
|
+
const actions2 = [AFFORDANCE.choose];
|
|
2971
|
+
if (optional) actions2.push(AFFORDANCE.skip);
|
|
2972
|
+
return {
|
|
2973
|
+
question: {
|
|
2974
|
+
type: "select",
|
|
2975
|
+
message: `Select ${leaf.name}:`,
|
|
2976
|
+
choices: leaf.staticChoices,
|
|
2977
|
+
actions: actions2
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
const textSource = resolver?.type === "static" ? resolver : void 0;
|
|
2982
|
+
const inputType = textSource?.inputType && textSource.inputType !== "search" ? textSource.inputType : "text";
|
|
2983
|
+
const actions = [AFFORDANCE.custom];
|
|
2984
|
+
if (optional) actions.push(AFFORDANCE.skip);
|
|
2985
|
+
return {
|
|
2986
|
+
question: {
|
|
2987
|
+
type: "input",
|
|
2988
|
+
// The optional marker makes Enter-to-pass discoverable on a bare
|
|
2989
|
+
// parameter; a labeled field carries its marker via labeledMessage.
|
|
2990
|
+
message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
|
|
2991
|
+
inputType,
|
|
2992
|
+
placeholder: textSource?.placeholder,
|
|
2993
|
+
actions
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
function collectionQuestion(t) {
|
|
2998
|
+
const actions = [AFFORDANCE.add];
|
|
2999
|
+
if (t.count >= t.min) actions.push(AFFORDANCE.done);
|
|
3000
|
+
return {
|
|
3001
|
+
type: "collection",
|
|
3002
|
+
message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
|
|
3003
|
+
container: "array",
|
|
3004
|
+
count: t.count,
|
|
3005
|
+
min: t.min,
|
|
3006
|
+
// An unbounded array's max is Infinity, which JSON.stringify turns to null;
|
|
3007
|
+
// omit it so the question round-trips across the wall as plain data.
|
|
3008
|
+
...Number.isFinite(t.max) ? { max: t.max } : {},
|
|
3009
|
+
actions
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
function objectGateQuestion(path) {
|
|
3013
|
+
return {
|
|
3014
|
+
type: "collection",
|
|
3015
|
+
message: `Add ${path[path.length - 1]}?`,
|
|
3016
|
+
container: "object",
|
|
3017
|
+
actions: [
|
|
3018
|
+
{
|
|
3019
|
+
action: "add",
|
|
3020
|
+
description: "Provide values for these fields"
|
|
3021
|
+
},
|
|
3022
|
+
{ action: "done", description: "Skip these fields" }
|
|
3023
|
+
]
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
function optionalsGateQuestion(pending) {
|
|
3027
|
+
return {
|
|
3028
|
+
type: "collection",
|
|
3029
|
+
// The prompt and its context ride separately so a host renders the info
|
|
3030
|
+
// line above the confirm without composing any text of its own.
|
|
3031
|
+
message: "Would you like to configure optional fields?",
|
|
3032
|
+
description: `There are ${pending.length} optional field(s) available.`,
|
|
3033
|
+
container: "object",
|
|
3034
|
+
// The gated fields' projection, so a smart host can show WHAT `add` would
|
|
3035
|
+
// walk (or render a form section) instead of a blind yes/no.
|
|
3036
|
+
fields: pending.map((leaf) => ({
|
|
3037
|
+
key: leaf.name,
|
|
3038
|
+
...leaf.label ? { label: leaf.label } : {},
|
|
3039
|
+
...leaf.valueType ? { valueType: leaf.valueType } : {}
|
|
3040
|
+
})),
|
|
3041
|
+
actions: [
|
|
3042
|
+
{ action: "add", description: "Configure the optional fields" },
|
|
3043
|
+
{ action: "done", description: "Skip the optional fields" }
|
|
3044
|
+
]
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/model/resolution/walk.ts
|
|
3049
|
+
function finalize(ctx, resolved) {
|
|
3050
|
+
if (!ctx.schema) return { status: "done", value: resolved };
|
|
3051
|
+
const parsed = ctx.schema.safeParse(resolved);
|
|
3052
|
+
if (parsed.success) {
|
|
3053
|
+
return { status: "done", value: parsed.data };
|
|
3054
|
+
}
|
|
3055
|
+
const issues = parsed.error.issues.map((i) => ({
|
|
3056
|
+
parameter: i.path.map(String).join(".") || void 0,
|
|
3057
|
+
message: i.message
|
|
3058
|
+
}));
|
|
3059
|
+
return { status: "invalid", issues };
|
|
3060
|
+
}
|
|
3061
|
+
var optionalsMarker = (path) => `${key(path)}?optionals`;
|
|
3062
|
+
async function findInArray(ctx, state, path) {
|
|
3063
|
+
if (isSettled(state, path)) return null;
|
|
3064
|
+
if (getAtPath(state.resolved, path) == null)
|
|
3065
|
+
setAtPath(state.resolved, path, []);
|
|
3066
|
+
if (!state.interactive) {
|
|
3067
|
+
settle(state, path);
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
const { min, max, item } = await arrayInfoAt(ctx, path, state.resolved);
|
|
3071
|
+
const items = getAtPath(state.resolved, path);
|
|
3072
|
+
const len = items.length;
|
|
3073
|
+
const itemType = item.resolver?.type;
|
|
3074
|
+
if (len > 0 && (itemType === "object" || itemType === "array")) {
|
|
3075
|
+
const itemPath = [...path, len - 1];
|
|
3076
|
+
if (!isSettled(state, itemPath)) {
|
|
3077
|
+
const inner = await findNext(ctx, state, itemPath);
|
|
3078
|
+
if (inner) return inner;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (len < min) return descendItem(ctx, state, path, len, item);
|
|
3082
|
+
if (len < max) return { kind: "array", path, count: len, min, max };
|
|
3083
|
+
settle(state, path);
|
|
3084
|
+
return null;
|
|
3085
|
+
}
|
|
3086
|
+
function seedItemSlot(state, itemPath, item) {
|
|
3087
|
+
const type = item.resolver?.type;
|
|
3088
|
+
if (type === "object") {
|
|
3089
|
+
setAtPath(state.resolved, itemPath, {});
|
|
3090
|
+
return "object";
|
|
3091
|
+
}
|
|
3092
|
+
if (type === "array") {
|
|
3093
|
+
setAtPath(state.resolved, itemPath, []);
|
|
3094
|
+
return "array";
|
|
3095
|
+
}
|
|
3096
|
+
return "leaf";
|
|
3097
|
+
}
|
|
3098
|
+
async function descendItem(ctx, state, arrayPath, index, item) {
|
|
3099
|
+
const itemPath = [...arrayPath, index];
|
|
3100
|
+
const kind = seedItemSlot(state, itemPath, item);
|
|
3101
|
+
if (kind === "object") return findNext(ctx, state, itemPath);
|
|
3102
|
+
if (kind === "array") return findInArray(ctx, state, itemPath);
|
|
3103
|
+
return { kind: "leaf", path: itemPath, leaf: item };
|
|
3104
|
+
}
|
|
3105
|
+
async function findNext(ctx, state, path = []) {
|
|
3106
|
+
const container = getAtPath(state.resolved, path) ?? {};
|
|
3107
|
+
const children = await childrenAt(ctx, path, state.resolved);
|
|
3108
|
+
const inObject = path.length > 0;
|
|
3109
|
+
const ordered = inObject ? [
|
|
3110
|
+
...children.filter((c) => c.required),
|
|
3111
|
+
...children.filter((c) => !c.required)
|
|
3112
|
+
] : children;
|
|
3113
|
+
const asksUser = (c) => c.resolver ? !autoSettles(c.resolver) : c.required;
|
|
3114
|
+
const isPendingChild = (c) => container[c.name] === void 0 && !isSettled(state, [...path, c.name]);
|
|
3115
|
+
const hasAskableRequired = children.some((c) => c.required && asksUser(c));
|
|
3116
|
+
for (const leaf of ordered) {
|
|
3117
|
+
const childPath = [...path, leaf.name];
|
|
3118
|
+
if (!leaf.requires.every(
|
|
3119
|
+
(r) => container[r] !== void 0 || getAtPath(state.resolved, [r]) !== void 0 || isSettled(state, [...path, r]) || isSettled(state, [r])
|
|
3120
|
+
)) {
|
|
3121
|
+
continue;
|
|
3122
|
+
}
|
|
3123
|
+
const inArrayItem = path.some((segment) => typeof segment === "number");
|
|
3124
|
+
if (inObject && !inArrayItem && state.interactive && hasAskableRequired && !leaf.required && asksUser(leaf) && isPendingChild(leaf) && !state.settled.includes(optionalsMarker(path)) && !children.some((c) => c.required && asksUser(c) && isPendingChild(c))) {
|
|
3125
|
+
const pending = ordered.filter(
|
|
3126
|
+
(c) => !c.required && asksUser(c) && isPendingChild(c)
|
|
3127
|
+
);
|
|
3128
|
+
return { kind: "optionals", path, pending };
|
|
3129
|
+
}
|
|
3130
|
+
if (leaf.resolver?.type === "object") {
|
|
3131
|
+
if (isSettled(state, childPath)) continue;
|
|
3132
|
+
if (getAtPath(state.resolved, childPath) == null) {
|
|
3133
|
+
if (!leaf.required && !inArrayItem) {
|
|
3134
|
+
if (!state.interactive) {
|
|
3135
|
+
settle(state, childPath);
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
return { kind: "object", path: childPath, leaf };
|
|
3139
|
+
}
|
|
3140
|
+
setAtPath(state.resolved, childPath, {});
|
|
3141
|
+
}
|
|
3142
|
+
const inner = await findNext(ctx, state, childPath);
|
|
3143
|
+
if (inner) return inner;
|
|
3144
|
+
if (!leaf.required) {
|
|
3145
|
+
const value = getAtPath(state.resolved, childPath);
|
|
3146
|
+
if (value !== null && typeof value === "object" && Object.keys(value).length === 0) {
|
|
3147
|
+
const grandchildren = await childrenAt(
|
|
3148
|
+
ctx,
|
|
3149
|
+
childPath,
|
|
3150
|
+
state.resolved
|
|
3151
|
+
);
|
|
3152
|
+
const pending = grandchildren.some(
|
|
3153
|
+
(c) => value[c.name] === void 0 && !isSettled(state, [...childPath, c.name])
|
|
3154
|
+
);
|
|
3155
|
+
if (!pending) {
|
|
3156
|
+
delete container[leaf.name];
|
|
3157
|
+
settle(state, childPath);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
continue;
|
|
3162
|
+
}
|
|
3163
|
+
if (leaf.resolver?.type === "array") {
|
|
3164
|
+
const inner = await findInArray(ctx, state, childPath);
|
|
3165
|
+
if (inner) return inner;
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
if (container[leaf.name] !== void 0 || isSettled(state, childPath))
|
|
3169
|
+
continue;
|
|
3170
|
+
return { kind: "leaf", path: childPath, leaf };
|
|
3171
|
+
}
|
|
3172
|
+
return null;
|
|
3173
|
+
}
|
|
3174
|
+
async function askLeaf(state, path, leaf, opts = {}) {
|
|
3175
|
+
state.current = path;
|
|
3176
|
+
delete state.gate;
|
|
3177
|
+
try {
|
|
3178
|
+
const { question, listing } = await buildQuestion(leaf, state.resolved);
|
|
3179
|
+
state.listing = listing;
|
|
3180
|
+
return {
|
|
3181
|
+
state,
|
|
3182
|
+
result: {
|
|
3183
|
+
status: "ask",
|
|
3184
|
+
question,
|
|
3185
|
+
...opts.error ? { error: opts.error } : {}
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
3188
|
+
} catch (error) {
|
|
3189
|
+
state.listing = { items: [], exhausted: false };
|
|
3190
|
+
return failedResult(state, leaf.name, error);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
async function advance(ctx, state) {
|
|
3194
|
+
for (; ; ) {
|
|
3195
|
+
const target = await findNext(ctx, state);
|
|
3196
|
+
if (!target) {
|
|
3197
|
+
delete state.current;
|
|
3198
|
+
delete state.gate;
|
|
3199
|
+
delete state.listing;
|
|
3200
|
+
return { state, result: finalize(ctx, state.resolved) };
|
|
3201
|
+
}
|
|
3202
|
+
if (target.kind === "array") {
|
|
3203
|
+
state.current = target.path;
|
|
3204
|
+
state.gate = "array";
|
|
3205
|
+
delete state.listing;
|
|
3206
|
+
return {
|
|
3207
|
+
state,
|
|
3208
|
+
result: { status: "ask", question: collectionQuestion(target) }
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
if (target.kind === "object") {
|
|
3212
|
+
state.current = target.path;
|
|
3213
|
+
state.gate = "entry";
|
|
3214
|
+
delete state.listing;
|
|
3215
|
+
return {
|
|
3216
|
+
state,
|
|
3217
|
+
result: { status: "ask", question: objectGateQuestion(target.path) }
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
if (target.kind === "optionals") {
|
|
3221
|
+
state.current = target.path;
|
|
3222
|
+
state.gate = "optionals";
|
|
3223
|
+
delete state.listing;
|
|
3224
|
+
return {
|
|
3225
|
+
state,
|
|
3226
|
+
result: {
|
|
3227
|
+
status: "ask",
|
|
3228
|
+
question: optionalsGateQuestion(target.pending)
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
const { path, leaf } = target;
|
|
3233
|
+
const resolver = leaf.resolver;
|
|
3234
|
+
if (resolver && autoSettles(resolver)) {
|
|
3235
|
+
if (resolver.type === "constant") {
|
|
3236
|
+
setAtPath(state.resolved, path, resolver.value);
|
|
3237
|
+
}
|
|
3238
|
+
settle(state, path);
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
|
|
3242
|
+
input: mergeInput(state.resolved, leaf.extraInput)
|
|
3243
|
+
}) : void 0;
|
|
3244
|
+
if (auto) {
|
|
3245
|
+
if (auto.resolvedValue !== void 0)
|
|
3246
|
+
setAtPath(state.resolved, path, auto.resolvedValue);
|
|
3247
|
+
settle(state, path);
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
if (!state.interactive) {
|
|
3251
|
+
if (!leaf.required) {
|
|
3252
|
+
settle(state, path);
|
|
3253
|
+
continue;
|
|
3254
|
+
}
|
|
3255
|
+
} else if (!leaf.required && !leaf.resolver) {
|
|
3256
|
+
settle(state, path);
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
return askLeaf(state, path, leaf);
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
async function start(ctx, input = {}, interactive = true) {
|
|
3263
|
+
return advance(ctx, {
|
|
3264
|
+
method: ctx.method,
|
|
3265
|
+
resolved: { ...input },
|
|
3266
|
+
settled: [],
|
|
3267
|
+
interactive
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
async function step(ctx, prior, action) {
|
|
3271
|
+
const state = clone(prior);
|
|
3272
|
+
if (action.type === "cancel") {
|
|
3273
|
+
delete state.current;
|
|
3274
|
+
delete state.gate;
|
|
3275
|
+
delete state.listing;
|
|
3276
|
+
return { state, result: { status: "cancelled" } };
|
|
3277
|
+
}
|
|
3278
|
+
const path = state.current;
|
|
3279
|
+
if (!path) throw new Error("step called with no outstanding question");
|
|
3280
|
+
const leaf = await leafAt(ctx, path, state.resolved);
|
|
3281
|
+
if (leaf && (action.type === "search" || action.type === "more" || action.type === "retry")) {
|
|
3282
|
+
return refine(ctx, state, leaf, path, action);
|
|
3283
|
+
}
|
|
3284
|
+
if (action.type === "add" || action.type === "done") {
|
|
3285
|
+
const gate = state.gate;
|
|
3286
|
+
if (!gate) {
|
|
3287
|
+
throw new Error(
|
|
3288
|
+
`action "${action.type}" is not supported here: no container decision is outstanding`
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
delete state.current;
|
|
3292
|
+
delete state.gate;
|
|
3293
|
+
delete state.listing;
|
|
3294
|
+
if (action.type === "done") {
|
|
3295
|
+
settle(state, path);
|
|
3296
|
+
return advance(ctx, state);
|
|
3297
|
+
}
|
|
3298
|
+
if (gate === "entry") {
|
|
3299
|
+
setAtPath(state.resolved, path, {});
|
|
3300
|
+
return advance(ctx, state);
|
|
3301
|
+
}
|
|
3302
|
+
if (gate === "optionals") {
|
|
3303
|
+
remember(state, optionalsMarker(path));
|
|
3304
|
+
return advance(ctx, state);
|
|
3305
|
+
}
|
|
3306
|
+
const items = getAtPath(state.resolved, path) ?? [];
|
|
3307
|
+
const { item } = await arrayInfoAt(ctx, path, state.resolved);
|
|
3308
|
+
const itemPath = [...path, items.length];
|
|
3309
|
+
if (seedItemSlot(state, itemPath, item) === "leaf")
|
|
3310
|
+
return askLeaf(state, itemPath, item);
|
|
3311
|
+
return advance(ctx, state);
|
|
3312
|
+
}
|
|
3313
|
+
if (state.gate) {
|
|
3314
|
+
throw new Error(
|
|
3315
|
+
`action "${action.type}" is not supported here: a container decision (${state.gate}) is outstanding`
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
switch (action.type) {
|
|
3319
|
+
case "choose":
|
|
3320
|
+
case "custom": {
|
|
3321
|
+
if (leaf) {
|
|
3322
|
+
const error = await validationError(leaf, action.value, state);
|
|
3323
|
+
if (error) {
|
|
3324
|
+
if (state.listing && leaf.resolver?.type === "dynamic") {
|
|
3325
|
+
const context = await resolveContext(leaf, state.resolved);
|
|
3326
|
+
return {
|
|
3327
|
+
state,
|
|
3328
|
+
result: {
|
|
3329
|
+
status: "ask",
|
|
3330
|
+
question: selectQuestion(
|
|
3331
|
+
leaf,
|
|
3332
|
+
state.resolved,
|
|
3333
|
+
state.listing,
|
|
3334
|
+
context
|
|
3335
|
+
),
|
|
3336
|
+
error
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
return askLeaf(state, path, leaf, { error });
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
setAtPath(
|
|
3344
|
+
state.resolved,
|
|
3345
|
+
path,
|
|
3346
|
+
leaf ? coerce(leaf, action.value) : action.value
|
|
3347
|
+
);
|
|
3348
|
+
break;
|
|
3349
|
+
}
|
|
3350
|
+
case "skip":
|
|
3351
|
+
settle(state, path);
|
|
3352
|
+
break;
|
|
3353
|
+
default:
|
|
3354
|
+
throw new Error(`action "${action.type}" is not supported here`);
|
|
3355
|
+
}
|
|
3356
|
+
delete state.current;
|
|
3357
|
+
delete state.listing;
|
|
3358
|
+
return advance(ctx, state);
|
|
3359
|
+
}
|
|
3360
|
+
async function refine(ctx, state, leaf, path, action) {
|
|
3361
|
+
const attempt = action.type === "search" ? { search: action.term, cursor: void 0, priorItems: [] } : {
|
|
3362
|
+
search: state.listing?.search,
|
|
3363
|
+
cursor: state.listing?.cursor,
|
|
3364
|
+
priorItems: state.listing?.items ?? []
|
|
3365
|
+
};
|
|
3366
|
+
try {
|
|
3367
|
+
if (action.type === "search") {
|
|
3368
|
+
const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
|
|
3369
|
+
input: mergeInput(state.resolved, leaf.extraInput),
|
|
3370
|
+
search: action.term
|
|
3371
|
+
}) : void 0;
|
|
3372
|
+
if (exact) {
|
|
3373
|
+
setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
|
|
3374
|
+
delete state.current;
|
|
3375
|
+
delete state.listing;
|
|
3376
|
+
return advance(ctx, state);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
const context = await resolveContext(leaf, state.resolved);
|
|
3380
|
+
state.listing = await fetchListing(leaf, state.resolved, {
|
|
3381
|
+
...attempt,
|
|
3382
|
+
context
|
|
3383
|
+
});
|
|
3384
|
+
return {
|
|
3385
|
+
state,
|
|
3386
|
+
result: {
|
|
3387
|
+
status: "ask",
|
|
3388
|
+
question: selectQuestion(leaf, state.resolved, state.listing, context)
|
|
3389
|
+
}
|
|
3390
|
+
};
|
|
3391
|
+
} catch (error) {
|
|
3392
|
+
state.listing = {
|
|
3393
|
+
items: attempt.priorItems,
|
|
3394
|
+
search: attempt.search,
|
|
3395
|
+
cursor: attempt.cursor,
|
|
3396
|
+
exhausted: false
|
|
3397
|
+
};
|
|
3398
|
+
return failedResult(state, leaf.name, error);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
// src/model/resolution/controller.ts
|
|
3403
|
+
function toJsonSchema(schema) {
|
|
3404
|
+
if (!schema) return void 0;
|
|
3405
|
+
try {
|
|
3406
|
+
return z4.toJSONSchema(schema);
|
|
3407
|
+
} catch {
|
|
3408
|
+
return void 0;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
function projectSummary(entry) {
|
|
3412
|
+
return {
|
|
3413
|
+
name: entry.name,
|
|
3414
|
+
...entry.description ? { description: entry.description } : {},
|
|
3415
|
+
...entry.categories?.length ? { categories: entry.categories } : {}
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
function projectMethod(entry) {
|
|
3419
|
+
const inputProperties = toJsonSchema(entry.inputSchema)?.properties;
|
|
3420
|
+
const parameters = {};
|
|
3421
|
+
for (const spec of planParameters(entry).parameters) {
|
|
3422
|
+
const dynamic = spec.resolver?.type === "dynamic" ? spec.resolver : void 0;
|
|
3423
|
+
parameters[spec.name] = {
|
|
3424
|
+
required: spec.required,
|
|
3425
|
+
dynamic: Boolean(dynamic),
|
|
3426
|
+
...dynamic?.inputType === "search" ? { searchable: true } : {},
|
|
3427
|
+
...inputProperties?.[spec.name] ? { schema: inputProperties[spec.name] } : {},
|
|
3428
|
+
...spec.staticChoices ? { choices: spec.staticChoices } : {},
|
|
3429
|
+
...spec.requires.length ? { requireParameters: spec.requires } : {}
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
const output = toJsonSchema(entry.outputSchema);
|
|
3433
|
+
return {
|
|
3434
|
+
name: entry.name,
|
|
3435
|
+
...entry.description ? { description: entry.description } : {},
|
|
3436
|
+
...entry.categories?.length ? { categories: entry.categories } : {},
|
|
3437
|
+
parameters,
|
|
3438
|
+
...entry.positional?.length ? { positional: entry.positional } : {},
|
|
3439
|
+
...output ? { output } : {}
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
function createController(sdk) {
|
|
3443
|
+
function entryFor(method) {
|
|
3444
|
+
const entry = sdk.getRegistry().functions.find((f) => f.name === method);
|
|
3445
|
+
if (!entry) throw new Error(`unknown method "${method}"`);
|
|
3446
|
+
return entry;
|
|
3447
|
+
}
|
|
3448
|
+
function contextFor(method) {
|
|
3449
|
+
const entry = entryFor(method);
|
|
3450
|
+
return {
|
|
3451
|
+
method,
|
|
3452
|
+
schema: entry.inputSchema,
|
|
3453
|
+
parameters: planParameters(entry).parameters
|
|
3454
|
+
};
|
|
3455
|
+
}
|
|
3456
|
+
const start2 = ({ method, input, interactive }) => start(contextFor(method), input, interactive);
|
|
3457
|
+
const step2 = ({ state, action }) => step(contextFor(state.method), state, action);
|
|
3458
|
+
const resolve = async ({
|
|
3459
|
+
method,
|
|
3460
|
+
input,
|
|
3461
|
+
answer,
|
|
3462
|
+
interactive
|
|
3463
|
+
}) => {
|
|
3464
|
+
const ctx = contextFor(method);
|
|
3465
|
+
let { state, result } = await start(ctx, input, interactive);
|
|
3466
|
+
while (result.status === "ask" || result.status === "failed") {
|
|
3467
|
+
const action = await answer({ state, result });
|
|
3468
|
+
({ state, result } = await step(ctx, state, action));
|
|
3469
|
+
}
|
|
3470
|
+
if (result.status === "done") return result.value;
|
|
3471
|
+
if (result.status === "cancelled") {
|
|
3472
|
+
throw new CoreCancelledSignal(`resolution cancelled for "${method}"`);
|
|
3473
|
+
}
|
|
3474
|
+
const detail = result.issues.map((i) => i.parameter ? `${i.parameter}: ${i.message}` : i.message).join("; ");
|
|
3475
|
+
throw new Error(`invalid input for "${method}": ${detail}`);
|
|
3476
|
+
};
|
|
3477
|
+
const listMethods = () => ({
|
|
3478
|
+
data: sdk.getRegistry().functions.map(projectSummary)
|
|
3479
|
+
});
|
|
3480
|
+
const getMethod = ({ method }) => ({
|
|
3481
|
+
data: projectMethod(entryFor(method))
|
|
3482
|
+
});
|
|
3483
|
+
const listChoices = async ({
|
|
3484
|
+
method,
|
|
3485
|
+
parameter,
|
|
3486
|
+
input = {},
|
|
3487
|
+
search,
|
|
3488
|
+
cursor
|
|
3489
|
+
}) => {
|
|
3490
|
+
const spec = contextFor(method).parameters.find(
|
|
3491
|
+
(p) => p.name === parameter
|
|
3492
|
+
);
|
|
3493
|
+
const dynamic = spec?.resolver?.type === "dynamic" ? spec.resolver : void 0;
|
|
3494
|
+
if (!dynamic) return { data: [] };
|
|
3495
|
+
const context = await dynamic.getContext?.({ input });
|
|
3496
|
+
const page = await firstPage(
|
|
3497
|
+
dynamic.listItems({ input, context, search, cursor })
|
|
3498
|
+
);
|
|
3499
|
+
const config = dynamic.prompt?.({ items: page.data, input, context });
|
|
3500
|
+
const data = (config?.choices ?? []).map(toChoice);
|
|
3501
|
+
return { data, nextCursor: page.nextCursor };
|
|
3502
|
+
};
|
|
3503
|
+
return { resolve, start: start2, step: step2, listMethods, getMethod, listChoices };
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/utils/core-plugin.ts
|
|
3507
|
+
function createCorePlugin(options) {
|
|
3508
|
+
logDeprecation(
|
|
3509
|
+
"createCorePlugin() is deprecated. Inject the options under CORE_OPTIONS_ID via createSdk's configuration instead."
|
|
3510
|
+
);
|
|
3511
|
+
return () => ({
|
|
3512
|
+
context: {
|
|
3513
|
+
core: options
|
|
3514
|
+
}
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
// src/utils/schema-utils.ts
|
|
3519
|
+
import { z as z5 } from "zod";
|
|
3520
|
+
function getOutputSchema(inputSchema) {
|
|
3521
|
+
return inputSchema._zod.def.outputSchema;
|
|
3522
|
+
}
|
|
3523
|
+
function withOutputSchema(inputSchema, outputSchema) {
|
|
3524
|
+
Object.assign(inputSchema._zod.def, {
|
|
3525
|
+
outputSchema
|
|
3526
|
+
});
|
|
3527
|
+
return inputSchema;
|
|
3528
|
+
}
|
|
3529
|
+
function withResolver(schema, config) {
|
|
3530
|
+
schema._zod.def.resolverMeta = config;
|
|
3531
|
+
return schema;
|
|
3532
|
+
}
|
|
3533
|
+
function getSchemaDescription(schema) {
|
|
3534
|
+
return schema.description;
|
|
3535
|
+
}
|
|
3536
|
+
function getFieldDescriptions(schema) {
|
|
3537
|
+
const descriptions = {};
|
|
3538
|
+
const shape = schema.shape;
|
|
3539
|
+
for (const [key2, fieldSchema] of Object.entries(shape)) {
|
|
3540
|
+
if (fieldSchema instanceof z5.ZodType && fieldSchema.description) {
|
|
3541
|
+
descriptions[key2] = fieldSchema.description;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
return descriptions;
|
|
3545
|
+
}
|
|
3546
|
+
function withPositional(schema) {
|
|
3547
|
+
Object.assign(schema._zod.def, {
|
|
3548
|
+
positionalMeta: { positional: true }
|
|
3549
|
+
});
|
|
3550
|
+
return schema;
|
|
3551
|
+
}
|
|
3552
|
+
function schemaHasPositionalMeta(schema) {
|
|
3553
|
+
return "positionalMeta" in schema._zod.def;
|
|
3554
|
+
}
|
|
3555
|
+
function isPositional(schema) {
|
|
3556
|
+
if (schemaHasPositionalMeta(schema) && schema._zod.def.positionalMeta?.positional) {
|
|
3557
|
+
return true;
|
|
3558
|
+
}
|
|
3559
|
+
if (schema instanceof z5.ZodOptional) {
|
|
3560
|
+
return isPositional(schema._zod.def.innerType);
|
|
3561
|
+
}
|
|
3562
|
+
if (schema instanceof z5.ZodDefault) {
|
|
3563
|
+
return isPositional(schema._zod.def.innerType);
|
|
3564
|
+
}
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
function openEnum(values, description) {
|
|
3568
|
+
return z5.union([z5.enum(values), z5.string()]).describe(description);
|
|
3569
|
+
}
|
|
3570
|
+
export {
|
|
3571
|
+
CONTEXT,
|
|
3572
|
+
CORE_ERROR_SYMBOL,
|
|
3573
|
+
CORE_OPTIONS_ID,
|
|
3574
|
+
CORE_SIGNAL_SYMBOL,
|
|
3575
|
+
CoreCancelledSignal,
|
|
3576
|
+
CoreDisposeError,
|
|
3577
|
+
CoreError,
|
|
3578
|
+
CoreErrorCode,
|
|
3579
|
+
CoreSignal,
|
|
3580
|
+
addPlugin,
|
|
3581
|
+
composePlugins,
|
|
3582
|
+
concatPaginated,
|
|
3583
|
+
coreOptionsPluginRef,
|
|
3584
|
+
createAsyncContext,
|
|
3585
|
+
createController,
|
|
3586
|
+
createCoreError,
|
|
3587
|
+
createCorePlugin,
|
|
3588
|
+
createDeprecationLogger,
|
|
3589
|
+
createFunction,
|
|
3590
|
+
createPaginatedFunction,
|
|
3591
|
+
createPaginatedPluginMethod,
|
|
3592
|
+
createPluginMethod,
|
|
3593
|
+
createPluginStack,
|
|
3594
|
+
createPrefixedCursor,
|
|
3595
|
+
createSdk,
|
|
3596
|
+
createValidator,
|
|
3597
|
+
dangerousContextPlugin,
|
|
3598
|
+
declareMethod,
|
|
3599
|
+
declareOptionalProperty,
|
|
3600
|
+
declarePlugin,
|
|
3601
|
+
declareProperty,
|
|
3602
|
+
decodeIncomingCursor,
|
|
3603
|
+
defaultLogDeprecation,
|
|
3604
|
+
defineFormatter,
|
|
3605
|
+
defineHook,
|
|
3606
|
+
defineLegacyMerge,
|
|
3607
|
+
defineMethod,
|
|
3608
|
+
defineMethodOverride,
|
|
3609
|
+
definePlugin,
|
|
3610
|
+
defineProperty,
|
|
3611
|
+
defineResolver,
|
|
3612
|
+
disposeSdk,
|
|
3613
|
+
fromFunctionPlugin,
|
|
3614
|
+
getContext,
|
|
3615
|
+
getCoreErrorCause,
|
|
3616
|
+
getCoreErrorCode,
|
|
3617
|
+
getCurrentDepth,
|
|
3618
|
+
getCurrentScope,
|
|
3619
|
+
getFieldDescriptions,
|
|
3620
|
+
getOutputSchema,
|
|
3621
|
+
getRegistryPlugin,
|
|
3622
|
+
getSchemaDescription,
|
|
3623
|
+
isCoreError,
|
|
3624
|
+
isCoreSignal,
|
|
3625
|
+
isNestedMethodCall,
|
|
3626
|
+
isPositional,
|
|
3627
|
+
isTelemetryNested,
|
|
3628
|
+
omitExports,
|
|
3629
|
+
openEnum,
|
|
3630
|
+
paginate,
|
|
3631
|
+
paginateBuffered,
|
|
3632
|
+
paginateMaxItems,
|
|
3633
|
+
resolvePlugin,
|
|
3634
|
+
runInMethodScope,
|
|
3635
|
+
runWithTelemetryContext,
|
|
3636
|
+
selectExports,
|
|
3637
|
+
splitPrefixedCursor,
|
|
3638
|
+
toIterable,
|
|
3639
|
+
toSnakeCase,
|
|
3640
|
+
toTitleCase,
|
|
3641
|
+
validateOptions,
|
|
3642
|
+
withOutputSchema,
|
|
3643
|
+
withPositional,
|
|
3644
|
+
withResolver
|
|
3645
|
+
};
|
|
3646
|
+
//# sourceMappingURL=index.mjs.map
|