@zapier/kitcore 0.11.0 → 0.12.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 +15 -0
- package/README.md +29 -2
- package/dist/index.cjs +237 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +98 -4
- package/dist/index.d.ts +98 -4
- package/dist/index.mjs +235 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @zapier/kitcore
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 281513d: Added `declareDefault`, a helper that imports the capability a plugin provides and defaults to that plugin when nothing else provides its id. Unlike `declareMethod` / `declareProperty`, it carries a real plugin, so its id, type, and kind come from the plugin (one kind-agnostic helper, no method/property split). At materialization an explicit provider of the same id preempts the default with no duplicate-id error, a lone default is used, and two different defaults for one id error only when nothing else provides it.
|
|
8
|
+
|
|
9
|
+
Added `declareOptionalMethod`, the method twin of `declareOptionalProperty`: an optional stand-in for a method registered elsewhere. When nothing provides the id it is not a missing dependency; the import binds `undefined` (typed `callable | undefined`), so a consumer can reference a foreign method that may or may not be present and call it as `imports.method?.(...)`.
|
|
10
|
+
|
|
11
|
+
- 87e634f: A method's `outputSchema` is now enforced at runtime instead of being documentation-only: the framework validates and strips the method's output against it, an `item` method's `{ data }` envelope as a whole and a `list` method's page element by element. A failure names the method and routes through the head's `adaptError`.
|
|
12
|
+
- `skipOutputValidation` on `defineMethod` opts a method out, the output-side partner of `skipInputValidation`. Set it when a schema is meant for projection only (registry, CLI, MCP, docs) and must not shape the response.
|
|
13
|
+
- The `includeOutputValidationDroppedPaths` core option reports what the strip removed, as `meta.outputValidation.droppedPaths`. Off by default; turn it on while reconciling a schema against what an API actually returns, so an over-narrow schema surfaces as data loss instead of looking like the API stopped returning a field.
|
|
14
|
+
- `ResponseMeta` types that sidecar. An `item` method's declared return is now `{ data, meta? }` and `SdkPage` gains the same optional `meta`, so a report reads the same whichever mode produced it. Both stay framework-set: a handler or `adaptPage` returns only `data` / `nextCursor`.
|
|
15
|
+
|
|
16
|
+
Existing methods that declare an `outputSchema` need to either confirm it matches what the API returns or set `skipOutputValidation: true`, since a schema that was previously ignored now shapes the response.
|
|
17
|
+
|
|
3
18
|
## 0.11.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -443,9 +443,36 @@ await controller.listChoices({
|
|
|
443
443
|
// { data: [{ label: "Slack", value: "slack" }], nextCursor }
|
|
444
444
|
```
|
|
445
445
|
|
|
446
|
-
##
|
|
446
|
+
## Plugin references
|
|
447
447
|
|
|
448
|
-
|
|
448
|
+
A plugin _references_ a capability supplied elsewhere by its id, without providing it. Use `declareMethod` / `declareProperty` for a single leaf, or `declarePlugin` for a whole module (its export surface declared as leaf references). A reference binds, at `createSdk`, to whatever real plugin is registered under the same id. You reference by `id` (`namespace/name`, or a bare name), with the contract as explicit type arguments (`declareMethod<"fetch", Input, Output>({ id: "fetch" })`), because the id must be a literal the dependency ledger can read; the binding is the id's last segment.
|
|
449
|
+
|
|
450
|
+
Two flavors, by what happens when nothing provides the id:
|
|
451
|
+
|
|
452
|
+
- **Required** (`declareMethod` / `declareProperty`): an unsatisfied reference is a **compile-time** error (with a runtime backstop). Use when the capability must be present.
|
|
453
|
+
- **Optional** (`declareOptionalProperty` / `declareOptionalMethod`): binds `undefined` instead of failing, so the consumer handles absence in code (`{ ...DEFAULTS, ...imports.config }` for a value, `imports.track?.(...)` for a method). Use to reference a _foreign_ capability userland may or may not import, without claiming its slot.
|
|
454
|
+
|
|
455
|
+
## Defaults
|
|
456
|
+
|
|
457
|
+
When you _own_ a capability slot and can ship a working implementation, register it as a default with `declareDefault({ plugin })` rather than referencing it. A default is a real, single node in the graph, so it works out of the box, hooks and middleware can wrap it, and an explicit provider of the same id silently preempts it (that is the seam for userland or another plugin to replace it).
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
import { declareDefault } from "@zapier/kitcore";
|
|
461
|
+
|
|
462
|
+
const httpPlugin = defineMethod({
|
|
463
|
+
name: "http",
|
|
464
|
+
// Ship a working default pipeline stage; userland can wrap or replace it.
|
|
465
|
+
imports: [declareDefault({ plugin: defaultAuthStagePlugin })],
|
|
466
|
+
run: ({ imports }) => imports.authStage(/* ... */),
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Default versus optional reference is an ownership question:
|
|
471
|
+
|
|
472
|
+
- **Own the slot** → default. It is yours to provide, so provide the fallback.
|
|
473
|
+
- **Don't own it** → optional reference. Point at it without claiming it.
|
|
474
|
+
|
|
475
|
+
The collision rules follow from ownership: two plugins defaulting one id to _different_ implementations conflict (both claimed the slot), while two optional references to the same foreign id never conflict (neither did). So a default on an id you do not own is a latent conflict, triggered the moment a second plugin does the same.
|
|
449
476
|
|
|
450
477
|
## Namespaces
|
|
451
478
|
|
package/dist/index.cjs
CHANGED
|
@@ -49,7 +49,9 @@ __export(index_exports, {
|
|
|
49
49
|
createSdk: () => createSdk,
|
|
50
50
|
createValidator: () => createValidator,
|
|
51
51
|
dangerousContextPlugin: () => dangerousContextPlugin,
|
|
52
|
+
declareDefault: () => declareDefault,
|
|
52
53
|
declareMethod: () => declareMethod,
|
|
54
|
+
declareOptionalMethod: () => declareOptionalMethod,
|
|
53
55
|
declareOptionalProperty: () => declareOptionalProperty,
|
|
54
56
|
declarePlugin: () => declarePlugin,
|
|
55
57
|
declareProperty: () => declareProperty,
|
|
@@ -1024,7 +1026,8 @@ function isSdkPage(value) {
|
|
|
1024
1026
|
}
|
|
1025
1027
|
function createPageFunction(coreFn, {
|
|
1026
1028
|
sdk,
|
|
1027
|
-
adaptPage
|
|
1029
|
+
adaptPage,
|
|
1030
|
+
finalizePage
|
|
1028
1031
|
}) {
|
|
1029
1032
|
const functionName = coreFn.name + "Page";
|
|
1030
1033
|
const namedFunctions = {
|
|
@@ -1037,7 +1040,7 @@ function createPageFunction(coreFn, {
|
|
|
1037
1040
|
`${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\`.`
|
|
1038
1041
|
);
|
|
1039
1042
|
}
|
|
1040
|
-
return page;
|
|
1043
|
+
return finalizePage ? finalizePage(page) : page;
|
|
1041
1044
|
} catch (error) {
|
|
1042
1045
|
throw normalizeError(
|
|
1043
1046
|
error,
|
|
@@ -1056,9 +1059,14 @@ function createPaginatedFunction(coreFn, options) {
|
|
|
1056
1059
|
defaultPageSize,
|
|
1057
1060
|
adaptPage,
|
|
1058
1061
|
annotator,
|
|
1062
|
+
finalizePage,
|
|
1059
1063
|
getDeprecation
|
|
1060
1064
|
} = options;
|
|
1061
|
-
const pageFunction = createPageFunction(coreFn, {
|
|
1065
|
+
const pageFunction = createPageFunction(coreFn, {
|
|
1066
|
+
sdk,
|
|
1067
|
+
adaptPage,
|
|
1068
|
+
finalizePage
|
|
1069
|
+
});
|
|
1062
1070
|
const functionName = name || coreFn.name;
|
|
1063
1071
|
const namedFunctions = {
|
|
1064
1072
|
[functionName]: function(callOptions) {
|
|
@@ -1584,6 +1592,7 @@ function defineMethod(config) {
|
|
|
1584
1592
|
importBindings: deps.bindings,
|
|
1585
1593
|
inputSchema: config.inputSchema,
|
|
1586
1594
|
skipInputValidation: config.skipInputValidation,
|
|
1595
|
+
skipOutputValidation: config.skipOutputValidation,
|
|
1587
1596
|
meta: collectLeafMeta(config),
|
|
1588
1597
|
resolvers: config.resolvers,
|
|
1589
1598
|
formatter: config.formatter,
|
|
@@ -1690,6 +1699,29 @@ function declareMethod(config) {
|
|
|
1690
1699
|
}
|
|
1691
1700
|
};
|
|
1692
1701
|
}
|
|
1702
|
+
function declareOptionalMethod(config) {
|
|
1703
|
+
const { name, namespace } = parseId(config.id);
|
|
1704
|
+
const id = makeId(name, namespace);
|
|
1705
|
+
return {
|
|
1706
|
+
pluginType: "method",
|
|
1707
|
+
name,
|
|
1708
|
+
namespace,
|
|
1709
|
+
id,
|
|
1710
|
+
standIn: true,
|
|
1711
|
+
optional: true,
|
|
1712
|
+
imports: [],
|
|
1713
|
+
importBindings: [],
|
|
1714
|
+
run: () => {
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
`Plugin "${id}" is an optional stand-in (declareOptionalMethod) with no implementation. Its binding is \`undefined\` unless a real plugin is registered under this id.`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
// Requires nothing (phantom carrier `<never, never>`): a consumer that
|
|
1720
|
+
// imports it still passes `createSdk`'s completeness check unprovided. The
|
|
1721
|
+
// `optional: true` literal drives `PluginSurface` to type the binding
|
|
1722
|
+
// `| undefined`.
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1693
1725
|
function defineProperty(config) {
|
|
1694
1726
|
const deps = normalizeImports(config.imports);
|
|
1695
1727
|
return {
|
|
@@ -1735,6 +1767,11 @@ function declareOptionalProperty(config) {
|
|
|
1735
1767
|
// import binding is still typed `TValue | undefined` from the descriptor.
|
|
1736
1768
|
};
|
|
1737
1769
|
}
|
|
1770
|
+
function declareDefault({
|
|
1771
|
+
plugin
|
|
1772
|
+
}) {
|
|
1773
|
+
return { ...plugin, defaultSource: plugin };
|
|
1774
|
+
}
|
|
1738
1775
|
function defineHook(config) {
|
|
1739
1776
|
const deps = normalizeImports(config.imports);
|
|
1740
1777
|
return {
|
|
@@ -2036,6 +2073,97 @@ var getRegistryPlugin = defineMethod({
|
|
|
2036
2073
|
run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
|
|
2037
2074
|
});
|
|
2038
2075
|
|
|
2076
|
+
// src/utils/output-policy.ts
|
|
2077
|
+
function isRecord(value) {
|
|
2078
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2079
|
+
}
|
|
2080
|
+
function diffDroppedPaths(raw, parsed, prefix = "") {
|
|
2081
|
+
const paths = [];
|
|
2082
|
+
walkDroppedPaths(raw, parsed, prefix, paths);
|
|
2083
|
+
return paths;
|
|
2084
|
+
}
|
|
2085
|
+
function walkDroppedPaths(raw, parsed, prefix, out) {
|
|
2086
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
2087
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2088
|
+
const length = Math.min(raw.length, parsed.length);
|
|
2089
|
+
for (let index = 0; index < length; index++) {
|
|
2090
|
+
const elementPaths = [];
|
|
2091
|
+
walkDroppedPaths(raw[index], parsed[index], `${prefix}[]`, elementPaths);
|
|
2092
|
+
for (const path of elementPaths) {
|
|
2093
|
+
if (seen.has(path)) continue;
|
|
2094
|
+
seen.add(path);
|
|
2095
|
+
out.push(path);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (isRecord(raw) && isRecord(parsed)) {
|
|
2101
|
+
for (const key of Object.keys(raw)) {
|
|
2102
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2103
|
+
if (!(key in parsed)) {
|
|
2104
|
+
out.push(path);
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
walkDroppedPaths(raw[key], parsed[key], path, out);
|
|
2108
|
+
}
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function parseOutput(schema, value, policy, locator) {
|
|
2113
|
+
const result = schema.safeParse(value);
|
|
2114
|
+
if (result.success) return result.data;
|
|
2115
|
+
const issues = result.error.issues.map((issue) => {
|
|
2116
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "data";
|
|
2117
|
+
return `${path}: ${issue.message}`;
|
|
2118
|
+
});
|
|
2119
|
+
const subject = policy.methodName ? ` for "${policy.methodName}"` : "";
|
|
2120
|
+
const at = locator ? ` at ${locator}` : "";
|
|
2121
|
+
throw createCoreError(
|
|
2122
|
+
{
|
|
2123
|
+
code: CoreErrorCode.Validation,
|
|
2124
|
+
message: `Output validation failed${subject}${at}:
|
|
2125
|
+
${issues.join("\n ")}
|
|
2126
|
+
|
|
2127
|
+
The response does not match the method's \`outputSchema\`. Correct the schema, or set \`skipOutputValidation: true\` on the method to pass the response through unvalidated.`,
|
|
2128
|
+
details: { zodErrors: result.error.issues, output: value }
|
|
2129
|
+
},
|
|
2130
|
+
policy.adaptError
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
function applyItemOutputPolicy(result, policy) {
|
|
2134
|
+
const schema = policy.outputSchema;
|
|
2135
|
+
if (!schema || policy.skipOutputValidation) return result;
|
|
2136
|
+
if (!isRecord(result) || !("data" in result)) return result;
|
|
2137
|
+
const data = parseOutput(schema, result.data, policy);
|
|
2138
|
+
const next = { ...result, data };
|
|
2139
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2140
|
+
const droppedPaths = diffDroppedPaths(result.data, data);
|
|
2141
|
+
if (droppedPaths.length > 0) {
|
|
2142
|
+
next.meta = withOutputValidation(result.meta, droppedPaths);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
return next;
|
|
2146
|
+
}
|
|
2147
|
+
function withOutputValidation(existing, droppedPaths) {
|
|
2148
|
+
const base = isRecord(existing) ? existing : {};
|
|
2149
|
+
return { ...base, outputValidation: { droppedPaths } };
|
|
2150
|
+
}
|
|
2151
|
+
function applyListOutputPolicy(page, policy) {
|
|
2152
|
+
const schema = policy.outputSchema;
|
|
2153
|
+
if (!schema || policy.skipOutputValidation) return page;
|
|
2154
|
+
const data = page.data.map(
|
|
2155
|
+
(item, index) => parseOutput(schema, item, policy, `data[${index}]`)
|
|
2156
|
+
);
|
|
2157
|
+
const next = { ...page, data };
|
|
2158
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2159
|
+
const droppedPaths = diffDroppedPaths(page.data, data);
|
|
2160
|
+
if (droppedPaths.length > 0) {
|
|
2161
|
+
next.meta = { ...page.meta, outputValidation: { droppedPaths } };
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return next;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2039
2167
|
// src/model/materialize.ts
|
|
2040
2168
|
var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
|
|
2041
2169
|
CORE_OPTIONS_ID
|
|
@@ -2099,6 +2227,9 @@ function edgesOf(plugin) {
|
|
|
2099
2227
|
function isStandIn(plugin) {
|
|
2100
2228
|
return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
|
|
2101
2229
|
}
|
|
2230
|
+
function isDefault(plugin) {
|
|
2231
|
+
return (plugin.pluginType === "method" || plugin.pluginType === "property") && plugin.defaultSource !== void 0;
|
|
2232
|
+
}
|
|
2102
2233
|
function topoOrder(descriptors) {
|
|
2103
2234
|
const order = [];
|
|
2104
2235
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2113,28 +2244,89 @@ function topoOrder(descriptors) {
|
|
|
2113
2244
|
return order;
|
|
2114
2245
|
}
|
|
2115
2246
|
function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
|
|
2247
|
+
const rank = (plugin) => isStandIn(plugin) ? 0 : isDefault(plugin) ? 1 : 2;
|
|
2248
|
+
const allNodes = [];
|
|
2249
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2250
|
+
const collect = (plugin) => {
|
|
2251
|
+
if (materialized.has(plugin.id) || seen.has(plugin)) return;
|
|
2252
|
+
seen.add(plugin);
|
|
2253
|
+
allNodes.push(plugin);
|
|
2254
|
+
for (const edge of edgesOf(plugin)) collect(edge);
|
|
2255
|
+
};
|
|
2256
|
+
collect(root);
|
|
2257
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2258
|
+
const candidatesById = /* @__PURE__ */ new Map();
|
|
2259
|
+
for (const node of allNodes) {
|
|
2260
|
+
childrenOf.set(
|
|
2261
|
+
node,
|
|
2262
|
+
edgesOf(node).filter((edge) => seen.has(edge))
|
|
2263
|
+
);
|
|
2264
|
+
const candidates = candidatesById.get(node.id);
|
|
2265
|
+
if (candidates) candidates.push(node);
|
|
2266
|
+
else candidatesById.set(node.id, [node]);
|
|
2267
|
+
}
|
|
2268
|
+
const live = new Set(allNodes);
|
|
2269
|
+
for (; ; ) {
|
|
2270
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2271
|
+
if (live.has(root)) reachable.add(root);
|
|
2272
|
+
const queue = reachable.has(root) ? [root] : [];
|
|
2273
|
+
while (queue.length) {
|
|
2274
|
+
const node = queue.pop();
|
|
2275
|
+
for (const child of childrenOf.get(node) ?? []) {
|
|
2276
|
+
if (reachable.has(child)) continue;
|
|
2277
|
+
reachable.add(child);
|
|
2278
|
+
if (live.has(child)) queue.push(child);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
let changed = false;
|
|
2282
|
+
for (const node of live) {
|
|
2283
|
+
if (!reachable.has(node)) {
|
|
2284
|
+
live.delete(node);
|
|
2285
|
+
changed = true;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
for (const candidates of candidatesById.values()) {
|
|
2289
|
+
let maxRank = -1;
|
|
2290
|
+
for (const candidate of candidates) {
|
|
2291
|
+
if (live.has(candidate)) maxRank = Math.max(maxRank, rank(candidate));
|
|
2292
|
+
}
|
|
2293
|
+
if (maxRank < 0) continue;
|
|
2294
|
+
for (const candidate of candidates) {
|
|
2295
|
+
if (live.has(candidate) && rank(candidate) < maxRank) {
|
|
2296
|
+
live.delete(candidate);
|
|
2297
|
+
changed = true;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
if (!changed) break;
|
|
2302
|
+
}
|
|
2116
2303
|
const byId = /* @__PURE__ */ new Map();
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2304
|
+
const conflictedDefaults = /* @__PURE__ */ new Set();
|
|
2305
|
+
const isOptional = (plugin) => "optional" in plugin && plugin.optional === true;
|
|
2306
|
+
for (const [id, candidates] of candidatesById) {
|
|
2307
|
+
const liveCandidates = candidates.filter(
|
|
2308
|
+
(candidate) => live.has(candidate)
|
|
2309
|
+
);
|
|
2310
|
+
const winner = liveCandidates.find((candidate) => !isOptional(candidate)) ?? liveCandidates[0];
|
|
2311
|
+
if (!winner) continue;
|
|
2312
|
+
if (liveCandidates.length > 1) {
|
|
2313
|
+
const winnerRank = rank(winner);
|
|
2314
|
+
if (winnerRank === 2) {
|
|
2124
2315
|
throw new Error(
|
|
2125
|
-
`createSdk: duplicate plugin id "${
|
|
2316
|
+
`createSdk: duplicate plugin id "${id}". Two different plugins registered under the same id.`
|
|
2126
2317
|
);
|
|
2127
2318
|
}
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
|
|
2319
|
+
if (winnerRank === 1) {
|
|
2320
|
+
const sources = new Set(
|
|
2321
|
+
liveCandidates.map(
|
|
2322
|
+
(candidate) => candidate.defaultSource
|
|
2323
|
+
)
|
|
2324
|
+
);
|
|
2325
|
+
if (sources.size > 1) conflictedDefaults.add(id);
|
|
2131
2326
|
}
|
|
2132
|
-
return;
|
|
2133
2327
|
}
|
|
2134
|
-
byId.set(
|
|
2135
|
-
|
|
2136
|
-
};
|
|
2137
|
-
visit(root);
|
|
2328
|
+
byId.set(id, winner);
|
|
2329
|
+
}
|
|
2138
2330
|
if (configuration) {
|
|
2139
2331
|
for (const [id, value] of Object.entries(configuration)) {
|
|
2140
2332
|
const existing = byId.get(id);
|
|
@@ -2185,6 +2377,14 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
|
|
|
2185
2377
|
);
|
|
2186
2378
|
}
|
|
2187
2379
|
}
|
|
2380
|
+
for (const id of conflictedDefaults) {
|
|
2381
|
+
const winner = byId.get(id);
|
|
2382
|
+
if (winner && isDefault(winner)) {
|
|
2383
|
+
throw new Error(
|
|
2384
|
+
`createSdk: conflicting defaults for "${id}". Two different plugins were declared as defaults for the same id and nothing else provides it. Register an explicit (non-default) plugin for this id to choose the winner, or give the implementations distinct ids if they are meant to coexist.`
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2188
2388
|
return byId;
|
|
2189
2389
|
}
|
|
2190
2390
|
function bindValue({
|
|
@@ -2508,6 +2708,7 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2508
2708
|
const plugins = context.plugins;
|
|
2509
2709
|
for (const [id, descriptor] of descriptors) {
|
|
2510
2710
|
if (descriptor.pluginType !== "method") continue;
|
|
2711
|
+
if (isStandIn(descriptor)) continue;
|
|
2511
2712
|
const out = normalizeOutput(descriptor.output);
|
|
2512
2713
|
const entry = {
|
|
2513
2714
|
pluginType: "method",
|
|
@@ -2560,6 +2761,16 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2560
2761
|
const sdk = { context };
|
|
2561
2762
|
const methodAnnotator = descriptor.annotator;
|
|
2562
2763
|
const boundAnnotator = methodAnnotator ? (input) => methodAnnotator({ input }) : void 0;
|
|
2764
|
+
const outputPolicy = () => {
|
|
2765
|
+
const core = resolveCoreOptions(context);
|
|
2766
|
+
return {
|
|
2767
|
+
outputSchema: descriptor.meta?.outputSchema,
|
|
2768
|
+
skipOutputValidation: descriptor.skipOutputValidation,
|
|
2769
|
+
includeOutputValidationDroppedPaths: core?.includeOutputValidationDroppedPaths,
|
|
2770
|
+
methodName: descriptor.name,
|
|
2771
|
+
adaptError: core?.adaptError
|
|
2772
|
+
};
|
|
2773
|
+
};
|
|
2563
2774
|
if (out.type === "list") {
|
|
2564
2775
|
entry.value = createPaginatedFunction(
|
|
2565
2776
|
fold(callRun),
|
|
@@ -2570,11 +2781,15 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2570
2781
|
defaultPageSize: out.defaultPageSize,
|
|
2571
2782
|
adaptPage: out.adaptPage,
|
|
2572
2783
|
annotator: boundAnnotator,
|
|
2784
|
+
// Validate + strip each item against the item `outputSchema`
|
|
2785
|
+
// (item mode's sibling); dropped paths surface as `[].x` in the page's
|
|
2786
|
+
// `meta`, unioned across items.
|
|
2787
|
+
finalizePage: (page) => applyListOutputPolicy(page, outputPolicy()),
|
|
2573
2788
|
getDeprecation: () => entry.meta?.deprecation
|
|
2574
2789
|
}
|
|
2575
2790
|
);
|
|
2576
2791
|
} else if (out.type === "item") {
|
|
2577
|
-
const itemCore = async (input, ctx) => callRun(input, ctx);
|
|
2792
|
+
const itemCore = async (input, ctx) => applyItemOutputPolicy(await callRun(input, ctx), outputPolicy());
|
|
2578
2793
|
entry.value = createFunction(
|
|
2579
2794
|
fold(itemCore),
|
|
2580
2795
|
{
|
|
@@ -4291,7 +4506,9 @@ function createCorePlugin(options) {
|
|
|
4291
4506
|
createSdk,
|
|
4292
4507
|
createValidator,
|
|
4293
4508
|
dangerousContextPlugin,
|
|
4509
|
+
declareDefault,
|
|
4294
4510
|
declareMethod,
|
|
4511
|
+
declareOptionalMethod,
|
|
4295
4512
|
declareOptionalProperty,
|
|
4296
4513
|
declarePlugin,
|
|
4297
4514
|
declareProperty,
|