@zapier/kitcore 0.11.0 → 0.13.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 +23 -0
- package/README.md +29 -2
- package/dist/index.cjs +248 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +120 -4
- package/dist/index.d.ts +120 -4
- package/dist/index.mjs +245 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @zapier/kitcore
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bc7405e: `@zapier/kitcore`: added `NegatableMetadata` and a `getNegatable` reader for marking optional boolean schema fields where omission is distinct from false (omitting means "use the server default" or "keep the current value"). Tooling built on kitcore can read the marker to offer an explicit disable affordance — for example, the CLI generates a `--disabled` flag from it.
|
|
8
|
+
|
|
9
|
+
`@zapier/zapier-sdk`: the `publishWorkflowVersion` and `publishWorkflowDraft` `enabled` parameter descriptions now state what omitting the parameter does (uses the server default / preserves the current state) without naming CLI-specific flags. `NegatableMetadata` and `getNegatable` are re-exported from `@zapier/kitcore` for schema-metadata consumers.
|
|
10
|
+
|
|
11
|
+
## 0.12.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
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?.(...)`.
|
|
18
|
+
|
|
19
|
+
- 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`.
|
|
20
|
+
- `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.
|
|
21
|
+
- 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.
|
|
22
|
+
- `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`.
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
3
26
|
## 0.11.0
|
|
4
27
|
|
|
5
28
|
### 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,
|
|
@@ -71,6 +73,7 @@ __export(index_exports, {
|
|
|
71
73
|
getCurrentDepth: () => getCurrentDepth,
|
|
72
74
|
getCurrentScope: () => getCurrentScope,
|
|
73
75
|
getFieldDescriptions: () => getFieldDescriptions,
|
|
76
|
+
getNegatable: () => getNegatable,
|
|
74
77
|
getOutputSchema: () => getOutputSchema,
|
|
75
78
|
getRegistryPlugin: () => getRegistryPlugin,
|
|
76
79
|
getSchemaDescription: () => getSchemaDescription,
|
|
@@ -178,6 +181,15 @@ function isPositional(schema) {
|
|
|
178
181
|
}
|
|
179
182
|
return false;
|
|
180
183
|
}
|
|
184
|
+
function getNegatable(schema) {
|
|
185
|
+
const negatable = schema.meta?.()?.negatable;
|
|
186
|
+
if (negatable === true) return true;
|
|
187
|
+
if (typeof negatable === "string" && negatable.length > 0) return negatable;
|
|
188
|
+
if (schema instanceof import_zod.z.ZodOptional || schema instanceof import_zod.z.ZodDefault) {
|
|
189
|
+
return getNegatable(schema._zod.def.innerType);
|
|
190
|
+
}
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
181
193
|
function openEnum(values, description) {
|
|
182
194
|
return import_zod.z.union([import_zod.z.enum(values), import_zod.z.string()]).describe(description);
|
|
183
195
|
}
|
|
@@ -1024,7 +1036,8 @@ function isSdkPage(value) {
|
|
|
1024
1036
|
}
|
|
1025
1037
|
function createPageFunction(coreFn, {
|
|
1026
1038
|
sdk,
|
|
1027
|
-
adaptPage
|
|
1039
|
+
adaptPage,
|
|
1040
|
+
finalizePage
|
|
1028
1041
|
}) {
|
|
1029
1042
|
const functionName = coreFn.name + "Page";
|
|
1030
1043
|
const namedFunctions = {
|
|
@@ -1037,7 +1050,7 @@ function createPageFunction(coreFn, {
|
|
|
1037
1050
|
`${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
1051
|
);
|
|
1039
1052
|
}
|
|
1040
|
-
return page;
|
|
1053
|
+
return finalizePage ? finalizePage(page) : page;
|
|
1041
1054
|
} catch (error) {
|
|
1042
1055
|
throw normalizeError(
|
|
1043
1056
|
error,
|
|
@@ -1056,9 +1069,14 @@ function createPaginatedFunction(coreFn, options) {
|
|
|
1056
1069
|
defaultPageSize,
|
|
1057
1070
|
adaptPage,
|
|
1058
1071
|
annotator,
|
|
1072
|
+
finalizePage,
|
|
1059
1073
|
getDeprecation
|
|
1060
1074
|
} = options;
|
|
1061
|
-
const pageFunction = createPageFunction(coreFn, {
|
|
1075
|
+
const pageFunction = createPageFunction(coreFn, {
|
|
1076
|
+
sdk,
|
|
1077
|
+
adaptPage,
|
|
1078
|
+
finalizePage
|
|
1079
|
+
});
|
|
1062
1080
|
const functionName = name || coreFn.name;
|
|
1063
1081
|
const namedFunctions = {
|
|
1064
1082
|
[functionName]: function(callOptions) {
|
|
@@ -1584,6 +1602,7 @@ function defineMethod(config) {
|
|
|
1584
1602
|
importBindings: deps.bindings,
|
|
1585
1603
|
inputSchema: config.inputSchema,
|
|
1586
1604
|
skipInputValidation: config.skipInputValidation,
|
|
1605
|
+
skipOutputValidation: config.skipOutputValidation,
|
|
1587
1606
|
meta: collectLeafMeta(config),
|
|
1588
1607
|
resolvers: config.resolvers,
|
|
1589
1608
|
formatter: config.formatter,
|
|
@@ -1690,6 +1709,29 @@ function declareMethod(config) {
|
|
|
1690
1709
|
}
|
|
1691
1710
|
};
|
|
1692
1711
|
}
|
|
1712
|
+
function declareOptionalMethod(config) {
|
|
1713
|
+
const { name, namespace } = parseId(config.id);
|
|
1714
|
+
const id = makeId(name, namespace);
|
|
1715
|
+
return {
|
|
1716
|
+
pluginType: "method",
|
|
1717
|
+
name,
|
|
1718
|
+
namespace,
|
|
1719
|
+
id,
|
|
1720
|
+
standIn: true,
|
|
1721
|
+
optional: true,
|
|
1722
|
+
imports: [],
|
|
1723
|
+
importBindings: [],
|
|
1724
|
+
run: () => {
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
`Plugin "${id}" is an optional stand-in (declareOptionalMethod) with no implementation. Its binding is \`undefined\` unless a real plugin is registered under this id.`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
// Requires nothing (phantom carrier `<never, never>`): a consumer that
|
|
1730
|
+
// imports it still passes `createSdk`'s completeness check unprovided. The
|
|
1731
|
+
// `optional: true` literal drives `PluginSurface` to type the binding
|
|
1732
|
+
// `| undefined`.
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1693
1735
|
function defineProperty(config) {
|
|
1694
1736
|
const deps = normalizeImports(config.imports);
|
|
1695
1737
|
return {
|
|
@@ -1735,6 +1777,11 @@ function declareOptionalProperty(config) {
|
|
|
1735
1777
|
// import binding is still typed `TValue | undefined` from the descriptor.
|
|
1736
1778
|
};
|
|
1737
1779
|
}
|
|
1780
|
+
function declareDefault({
|
|
1781
|
+
plugin
|
|
1782
|
+
}) {
|
|
1783
|
+
return { ...plugin, defaultSource: plugin };
|
|
1784
|
+
}
|
|
1738
1785
|
function defineHook(config) {
|
|
1739
1786
|
const deps = normalizeImports(config.imports);
|
|
1740
1787
|
return {
|
|
@@ -2036,6 +2083,97 @@ var getRegistryPlugin = defineMethod({
|
|
|
2036
2083
|
run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
|
|
2037
2084
|
});
|
|
2038
2085
|
|
|
2086
|
+
// src/utils/output-policy.ts
|
|
2087
|
+
function isRecord(value) {
|
|
2088
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2089
|
+
}
|
|
2090
|
+
function diffDroppedPaths(raw, parsed, prefix = "") {
|
|
2091
|
+
const paths = [];
|
|
2092
|
+
walkDroppedPaths(raw, parsed, prefix, paths);
|
|
2093
|
+
return paths;
|
|
2094
|
+
}
|
|
2095
|
+
function walkDroppedPaths(raw, parsed, prefix, out) {
|
|
2096
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
2097
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2098
|
+
const length = Math.min(raw.length, parsed.length);
|
|
2099
|
+
for (let index = 0; index < length; index++) {
|
|
2100
|
+
const elementPaths = [];
|
|
2101
|
+
walkDroppedPaths(raw[index], parsed[index], `${prefix}[]`, elementPaths);
|
|
2102
|
+
for (const path of elementPaths) {
|
|
2103
|
+
if (seen.has(path)) continue;
|
|
2104
|
+
seen.add(path);
|
|
2105
|
+
out.push(path);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
if (isRecord(raw) && isRecord(parsed)) {
|
|
2111
|
+
for (const key of Object.keys(raw)) {
|
|
2112
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2113
|
+
if (!(key in parsed)) {
|
|
2114
|
+
out.push(path);
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
walkDroppedPaths(raw[key], parsed[key], path, out);
|
|
2118
|
+
}
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
function parseOutput(schema, value, policy, locator) {
|
|
2123
|
+
const result = schema.safeParse(value);
|
|
2124
|
+
if (result.success) return result.data;
|
|
2125
|
+
const issues = result.error.issues.map((issue) => {
|
|
2126
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "data";
|
|
2127
|
+
return `${path}: ${issue.message}`;
|
|
2128
|
+
});
|
|
2129
|
+
const subject = policy.methodName ? ` for "${policy.methodName}"` : "";
|
|
2130
|
+
const at = locator ? ` at ${locator}` : "";
|
|
2131
|
+
throw createCoreError(
|
|
2132
|
+
{
|
|
2133
|
+
code: CoreErrorCode.Validation,
|
|
2134
|
+
message: `Output validation failed${subject}${at}:
|
|
2135
|
+
${issues.join("\n ")}
|
|
2136
|
+
|
|
2137
|
+
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.`,
|
|
2138
|
+
details: { zodErrors: result.error.issues, output: value }
|
|
2139
|
+
},
|
|
2140
|
+
policy.adaptError
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
function applyItemOutputPolicy(result, policy) {
|
|
2144
|
+
const schema = policy.outputSchema;
|
|
2145
|
+
if (!schema || policy.skipOutputValidation) return result;
|
|
2146
|
+
if (!isRecord(result) || !("data" in result)) return result;
|
|
2147
|
+
const data = parseOutput(schema, result.data, policy);
|
|
2148
|
+
const next = { ...result, data };
|
|
2149
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2150
|
+
const droppedPaths = diffDroppedPaths(result.data, data);
|
|
2151
|
+
if (droppedPaths.length > 0) {
|
|
2152
|
+
next.meta = withOutputValidation(result.meta, droppedPaths);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
return next;
|
|
2156
|
+
}
|
|
2157
|
+
function withOutputValidation(existing, droppedPaths) {
|
|
2158
|
+
const base = isRecord(existing) ? existing : {};
|
|
2159
|
+
return { ...base, outputValidation: { droppedPaths } };
|
|
2160
|
+
}
|
|
2161
|
+
function applyListOutputPolicy(page, policy) {
|
|
2162
|
+
const schema = policy.outputSchema;
|
|
2163
|
+
if (!schema || policy.skipOutputValidation) return page;
|
|
2164
|
+
const data = page.data.map(
|
|
2165
|
+
(item, index) => parseOutput(schema, item, policy, `data[${index}]`)
|
|
2166
|
+
);
|
|
2167
|
+
const next = { ...page, data };
|
|
2168
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2169
|
+
const droppedPaths = diffDroppedPaths(page.data, data);
|
|
2170
|
+
if (droppedPaths.length > 0) {
|
|
2171
|
+
next.meta = { ...page.meta, outputValidation: { droppedPaths } };
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
return next;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2039
2177
|
// src/model/materialize.ts
|
|
2040
2178
|
var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
|
|
2041
2179
|
CORE_OPTIONS_ID
|
|
@@ -2099,6 +2237,9 @@ function edgesOf(plugin) {
|
|
|
2099
2237
|
function isStandIn(plugin) {
|
|
2100
2238
|
return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
|
|
2101
2239
|
}
|
|
2240
|
+
function isDefault(plugin) {
|
|
2241
|
+
return (plugin.pluginType === "method" || plugin.pluginType === "property") && plugin.defaultSource !== void 0;
|
|
2242
|
+
}
|
|
2102
2243
|
function topoOrder(descriptors) {
|
|
2103
2244
|
const order = [];
|
|
2104
2245
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2113,28 +2254,89 @@ function topoOrder(descriptors) {
|
|
|
2113
2254
|
return order;
|
|
2114
2255
|
}
|
|
2115
2256
|
function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
|
|
2257
|
+
const rank = (plugin) => isStandIn(plugin) ? 0 : isDefault(plugin) ? 1 : 2;
|
|
2258
|
+
const allNodes = [];
|
|
2259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2260
|
+
const collect = (plugin) => {
|
|
2261
|
+
if (materialized.has(plugin.id) || seen.has(plugin)) return;
|
|
2262
|
+
seen.add(plugin);
|
|
2263
|
+
allNodes.push(plugin);
|
|
2264
|
+
for (const edge of edgesOf(plugin)) collect(edge);
|
|
2265
|
+
};
|
|
2266
|
+
collect(root);
|
|
2267
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2268
|
+
const candidatesById = /* @__PURE__ */ new Map();
|
|
2269
|
+
for (const node of allNodes) {
|
|
2270
|
+
childrenOf.set(
|
|
2271
|
+
node,
|
|
2272
|
+
edgesOf(node).filter((edge) => seen.has(edge))
|
|
2273
|
+
);
|
|
2274
|
+
const candidates = candidatesById.get(node.id);
|
|
2275
|
+
if (candidates) candidates.push(node);
|
|
2276
|
+
else candidatesById.set(node.id, [node]);
|
|
2277
|
+
}
|
|
2278
|
+
const live = new Set(allNodes);
|
|
2279
|
+
for (; ; ) {
|
|
2280
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2281
|
+
if (live.has(root)) reachable.add(root);
|
|
2282
|
+
const queue = reachable.has(root) ? [root] : [];
|
|
2283
|
+
while (queue.length) {
|
|
2284
|
+
const node = queue.pop();
|
|
2285
|
+
for (const child of childrenOf.get(node) ?? []) {
|
|
2286
|
+
if (reachable.has(child)) continue;
|
|
2287
|
+
reachable.add(child);
|
|
2288
|
+
if (live.has(child)) queue.push(child);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
let changed = false;
|
|
2292
|
+
for (const node of live) {
|
|
2293
|
+
if (!reachable.has(node)) {
|
|
2294
|
+
live.delete(node);
|
|
2295
|
+
changed = true;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
for (const candidates of candidatesById.values()) {
|
|
2299
|
+
let maxRank = -1;
|
|
2300
|
+
for (const candidate of candidates) {
|
|
2301
|
+
if (live.has(candidate)) maxRank = Math.max(maxRank, rank(candidate));
|
|
2302
|
+
}
|
|
2303
|
+
if (maxRank < 0) continue;
|
|
2304
|
+
for (const candidate of candidates) {
|
|
2305
|
+
if (live.has(candidate) && rank(candidate) < maxRank) {
|
|
2306
|
+
live.delete(candidate);
|
|
2307
|
+
changed = true;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
if (!changed) break;
|
|
2312
|
+
}
|
|
2116
2313
|
const byId = /* @__PURE__ */ new Map();
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2314
|
+
const conflictedDefaults = /* @__PURE__ */ new Set();
|
|
2315
|
+
const isOptional = (plugin) => "optional" in plugin && plugin.optional === true;
|
|
2316
|
+
for (const [id, candidates] of candidatesById) {
|
|
2317
|
+
const liveCandidates = candidates.filter(
|
|
2318
|
+
(candidate) => live.has(candidate)
|
|
2319
|
+
);
|
|
2320
|
+
const winner = liveCandidates.find((candidate) => !isOptional(candidate)) ?? liveCandidates[0];
|
|
2321
|
+
if (!winner) continue;
|
|
2322
|
+
if (liveCandidates.length > 1) {
|
|
2323
|
+
const winnerRank = rank(winner);
|
|
2324
|
+
if (winnerRank === 2) {
|
|
2124
2325
|
throw new Error(
|
|
2125
|
-
`createSdk: duplicate plugin id "${
|
|
2326
|
+
`createSdk: duplicate plugin id "${id}". Two different plugins registered under the same id.`
|
|
2126
2327
|
);
|
|
2127
2328
|
}
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
|
|
2329
|
+
if (winnerRank === 1) {
|
|
2330
|
+
const sources = new Set(
|
|
2331
|
+
liveCandidates.map(
|
|
2332
|
+
(candidate) => candidate.defaultSource
|
|
2333
|
+
)
|
|
2334
|
+
);
|
|
2335
|
+
if (sources.size > 1) conflictedDefaults.add(id);
|
|
2131
2336
|
}
|
|
2132
|
-
return;
|
|
2133
2337
|
}
|
|
2134
|
-
byId.set(
|
|
2135
|
-
|
|
2136
|
-
};
|
|
2137
|
-
visit(root);
|
|
2338
|
+
byId.set(id, winner);
|
|
2339
|
+
}
|
|
2138
2340
|
if (configuration) {
|
|
2139
2341
|
for (const [id, value] of Object.entries(configuration)) {
|
|
2140
2342
|
const existing = byId.get(id);
|
|
@@ -2185,6 +2387,14 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
|
|
|
2185
2387
|
);
|
|
2186
2388
|
}
|
|
2187
2389
|
}
|
|
2390
|
+
for (const id of conflictedDefaults) {
|
|
2391
|
+
const winner = byId.get(id);
|
|
2392
|
+
if (winner && isDefault(winner)) {
|
|
2393
|
+
throw new Error(
|
|
2394
|
+
`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.`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2188
2398
|
return byId;
|
|
2189
2399
|
}
|
|
2190
2400
|
function bindValue({
|
|
@@ -2508,6 +2718,7 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2508
2718
|
const plugins = context.plugins;
|
|
2509
2719
|
for (const [id, descriptor] of descriptors) {
|
|
2510
2720
|
if (descriptor.pluginType !== "method") continue;
|
|
2721
|
+
if (isStandIn(descriptor)) continue;
|
|
2511
2722
|
const out = normalizeOutput(descriptor.output);
|
|
2512
2723
|
const entry = {
|
|
2513
2724
|
pluginType: "method",
|
|
@@ -2560,6 +2771,16 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2560
2771
|
const sdk = { context };
|
|
2561
2772
|
const methodAnnotator = descriptor.annotator;
|
|
2562
2773
|
const boundAnnotator = methodAnnotator ? (input) => methodAnnotator({ input }) : void 0;
|
|
2774
|
+
const outputPolicy = () => {
|
|
2775
|
+
const core = resolveCoreOptions(context);
|
|
2776
|
+
return {
|
|
2777
|
+
outputSchema: descriptor.meta?.outputSchema,
|
|
2778
|
+
skipOutputValidation: descriptor.skipOutputValidation,
|
|
2779
|
+
includeOutputValidationDroppedPaths: core?.includeOutputValidationDroppedPaths,
|
|
2780
|
+
methodName: descriptor.name,
|
|
2781
|
+
adaptError: core?.adaptError
|
|
2782
|
+
};
|
|
2783
|
+
};
|
|
2563
2784
|
if (out.type === "list") {
|
|
2564
2785
|
entry.value = createPaginatedFunction(
|
|
2565
2786
|
fold(callRun),
|
|
@@ -2570,11 +2791,15 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2570
2791
|
defaultPageSize: out.defaultPageSize,
|
|
2571
2792
|
adaptPage: out.adaptPage,
|
|
2572
2793
|
annotator: boundAnnotator,
|
|
2794
|
+
// Validate + strip each item against the item `outputSchema`
|
|
2795
|
+
// (item mode's sibling); dropped paths surface as `[].x` in the page's
|
|
2796
|
+
// `meta`, unioned across items.
|
|
2797
|
+
finalizePage: (page) => applyListOutputPolicy(page, outputPolicy()),
|
|
2573
2798
|
getDeprecation: () => entry.meta?.deprecation
|
|
2574
2799
|
}
|
|
2575
2800
|
);
|
|
2576
2801
|
} else if (out.type === "item") {
|
|
2577
|
-
const itemCore = async (input, ctx) => callRun(input, ctx);
|
|
2802
|
+
const itemCore = async (input, ctx) => applyItemOutputPolicy(await callRun(input, ctx), outputPolicy());
|
|
2578
2803
|
entry.value = createFunction(
|
|
2579
2804
|
fold(itemCore),
|
|
2580
2805
|
{
|
|
@@ -4291,7 +4516,9 @@ function createCorePlugin(options) {
|
|
|
4291
4516
|
createSdk,
|
|
4292
4517
|
createValidator,
|
|
4293
4518
|
dangerousContextPlugin,
|
|
4519
|
+
declareDefault,
|
|
4294
4520
|
declareMethod,
|
|
4521
|
+
declareOptionalMethod,
|
|
4295
4522
|
declareOptionalProperty,
|
|
4296
4523
|
declarePlugin,
|
|
4297
4524
|
declareProperty,
|
|
@@ -4313,6 +4540,7 @@ function createCorePlugin(options) {
|
|
|
4313
4540
|
getCurrentDepth,
|
|
4314
4541
|
getCurrentScope,
|
|
4315
4542
|
getFieldDescriptions,
|
|
4543
|
+
getNegatable,
|
|
4316
4544
|
getOutputSchema,
|
|
4317
4545
|
getRegistryPlugin,
|
|
4318
4546
|
getSchemaDescription,
|