@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/dist/index.mjs
CHANGED
|
@@ -76,6 +76,15 @@ function isPositional(schema) {
|
|
|
76
76
|
}
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
|
+
function getNegatable(schema) {
|
|
80
|
+
const negatable = schema.meta?.()?.negatable;
|
|
81
|
+
if (negatable === true) return true;
|
|
82
|
+
if (typeof negatable === "string" && negatable.length > 0) return negatable;
|
|
83
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodDefault) {
|
|
84
|
+
return getNegatable(schema._zod.def.innerType);
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
79
88
|
function openEnum(values, description) {
|
|
80
89
|
return z.union([z.enum(values), z.string()]).describe(description);
|
|
81
90
|
}
|
|
@@ -924,7 +933,8 @@ function isSdkPage(value) {
|
|
|
924
933
|
}
|
|
925
934
|
function createPageFunction(coreFn, {
|
|
926
935
|
sdk,
|
|
927
|
-
adaptPage
|
|
936
|
+
adaptPage,
|
|
937
|
+
finalizePage
|
|
928
938
|
}) {
|
|
929
939
|
const functionName = coreFn.name + "Page";
|
|
930
940
|
const namedFunctions = {
|
|
@@ -937,7 +947,7 @@ function createPageFunction(coreFn, {
|
|
|
937
947
|
`${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\`.`
|
|
938
948
|
);
|
|
939
949
|
}
|
|
940
|
-
return page;
|
|
950
|
+
return finalizePage ? finalizePage(page) : page;
|
|
941
951
|
} catch (error) {
|
|
942
952
|
throw normalizeError(
|
|
943
953
|
error,
|
|
@@ -956,9 +966,14 @@ function createPaginatedFunction(coreFn, options) {
|
|
|
956
966
|
defaultPageSize,
|
|
957
967
|
adaptPage,
|
|
958
968
|
annotator,
|
|
969
|
+
finalizePage,
|
|
959
970
|
getDeprecation
|
|
960
971
|
} = options;
|
|
961
|
-
const pageFunction = createPageFunction(coreFn, {
|
|
972
|
+
const pageFunction = createPageFunction(coreFn, {
|
|
973
|
+
sdk,
|
|
974
|
+
adaptPage,
|
|
975
|
+
finalizePage
|
|
976
|
+
});
|
|
962
977
|
const functionName = name || coreFn.name;
|
|
963
978
|
const namedFunctions = {
|
|
964
979
|
[functionName]: function(callOptions) {
|
|
@@ -1484,6 +1499,7 @@ function defineMethod(config) {
|
|
|
1484
1499
|
importBindings: deps.bindings,
|
|
1485
1500
|
inputSchema: config.inputSchema,
|
|
1486
1501
|
skipInputValidation: config.skipInputValidation,
|
|
1502
|
+
skipOutputValidation: config.skipOutputValidation,
|
|
1487
1503
|
meta: collectLeafMeta(config),
|
|
1488
1504
|
resolvers: config.resolvers,
|
|
1489
1505
|
formatter: config.formatter,
|
|
@@ -1590,6 +1606,29 @@ function declareMethod(config) {
|
|
|
1590
1606
|
}
|
|
1591
1607
|
};
|
|
1592
1608
|
}
|
|
1609
|
+
function declareOptionalMethod(config) {
|
|
1610
|
+
const { name, namespace } = parseId(config.id);
|
|
1611
|
+
const id = makeId(name, namespace);
|
|
1612
|
+
return {
|
|
1613
|
+
pluginType: "method",
|
|
1614
|
+
name,
|
|
1615
|
+
namespace,
|
|
1616
|
+
id,
|
|
1617
|
+
standIn: true,
|
|
1618
|
+
optional: true,
|
|
1619
|
+
imports: [],
|
|
1620
|
+
importBindings: [],
|
|
1621
|
+
run: () => {
|
|
1622
|
+
throw new Error(
|
|
1623
|
+
`Plugin "${id}" is an optional stand-in (declareOptionalMethod) with no implementation. Its binding is \`undefined\` unless a real plugin is registered under this id.`
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
// Requires nothing (phantom carrier `<never, never>`): a consumer that
|
|
1627
|
+
// imports it still passes `createSdk`'s completeness check unprovided. The
|
|
1628
|
+
// `optional: true` literal drives `PluginSurface` to type the binding
|
|
1629
|
+
// `| undefined`.
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1593
1632
|
function defineProperty(config) {
|
|
1594
1633
|
const deps = normalizeImports(config.imports);
|
|
1595
1634
|
return {
|
|
@@ -1635,6 +1674,11 @@ function declareOptionalProperty(config) {
|
|
|
1635
1674
|
// import binding is still typed `TValue | undefined` from the descriptor.
|
|
1636
1675
|
};
|
|
1637
1676
|
}
|
|
1677
|
+
function declareDefault({
|
|
1678
|
+
plugin
|
|
1679
|
+
}) {
|
|
1680
|
+
return { ...plugin, defaultSource: plugin };
|
|
1681
|
+
}
|
|
1638
1682
|
function defineHook(config) {
|
|
1639
1683
|
const deps = normalizeImports(config.imports);
|
|
1640
1684
|
return {
|
|
@@ -1936,6 +1980,97 @@ var getRegistryPlugin = defineMethod({
|
|
|
1936
1980
|
run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
|
|
1937
1981
|
});
|
|
1938
1982
|
|
|
1983
|
+
// src/utils/output-policy.ts
|
|
1984
|
+
function isRecord(value) {
|
|
1985
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1986
|
+
}
|
|
1987
|
+
function diffDroppedPaths(raw, parsed, prefix = "") {
|
|
1988
|
+
const paths = [];
|
|
1989
|
+
walkDroppedPaths(raw, parsed, prefix, paths);
|
|
1990
|
+
return paths;
|
|
1991
|
+
}
|
|
1992
|
+
function walkDroppedPaths(raw, parsed, prefix, out) {
|
|
1993
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
1994
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1995
|
+
const length = Math.min(raw.length, parsed.length);
|
|
1996
|
+
for (let index = 0; index < length; index++) {
|
|
1997
|
+
const elementPaths = [];
|
|
1998
|
+
walkDroppedPaths(raw[index], parsed[index], `${prefix}[]`, elementPaths);
|
|
1999
|
+
for (const path of elementPaths) {
|
|
2000
|
+
if (seen.has(path)) continue;
|
|
2001
|
+
seen.add(path);
|
|
2002
|
+
out.push(path);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (isRecord(raw) && isRecord(parsed)) {
|
|
2008
|
+
for (const key of Object.keys(raw)) {
|
|
2009
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2010
|
+
if (!(key in parsed)) {
|
|
2011
|
+
out.push(path);
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
walkDroppedPaths(raw[key], parsed[key], path, out);
|
|
2015
|
+
}
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function parseOutput(schema, value, policy, locator) {
|
|
2020
|
+
const result = schema.safeParse(value);
|
|
2021
|
+
if (result.success) return result.data;
|
|
2022
|
+
const issues = result.error.issues.map((issue) => {
|
|
2023
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "data";
|
|
2024
|
+
return `${path}: ${issue.message}`;
|
|
2025
|
+
});
|
|
2026
|
+
const subject = policy.methodName ? ` for "${policy.methodName}"` : "";
|
|
2027
|
+
const at = locator ? ` at ${locator}` : "";
|
|
2028
|
+
throw createCoreError(
|
|
2029
|
+
{
|
|
2030
|
+
code: CoreErrorCode.Validation,
|
|
2031
|
+
message: `Output validation failed${subject}${at}:
|
|
2032
|
+
${issues.join("\n ")}
|
|
2033
|
+
|
|
2034
|
+
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.`,
|
|
2035
|
+
details: { zodErrors: result.error.issues, output: value }
|
|
2036
|
+
},
|
|
2037
|
+
policy.adaptError
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
function applyItemOutputPolicy(result, policy) {
|
|
2041
|
+
const schema = policy.outputSchema;
|
|
2042
|
+
if (!schema || policy.skipOutputValidation) return result;
|
|
2043
|
+
if (!isRecord(result) || !("data" in result)) return result;
|
|
2044
|
+
const data = parseOutput(schema, result.data, policy);
|
|
2045
|
+
const next = { ...result, data };
|
|
2046
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2047
|
+
const droppedPaths = diffDroppedPaths(result.data, data);
|
|
2048
|
+
if (droppedPaths.length > 0) {
|
|
2049
|
+
next.meta = withOutputValidation(result.meta, droppedPaths);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return next;
|
|
2053
|
+
}
|
|
2054
|
+
function withOutputValidation(existing, droppedPaths) {
|
|
2055
|
+
const base = isRecord(existing) ? existing : {};
|
|
2056
|
+
return { ...base, outputValidation: { droppedPaths } };
|
|
2057
|
+
}
|
|
2058
|
+
function applyListOutputPolicy(page, policy) {
|
|
2059
|
+
const schema = policy.outputSchema;
|
|
2060
|
+
if (!schema || policy.skipOutputValidation) return page;
|
|
2061
|
+
const data = page.data.map(
|
|
2062
|
+
(item, index) => parseOutput(schema, item, policy, `data[${index}]`)
|
|
2063
|
+
);
|
|
2064
|
+
const next = { ...page, data };
|
|
2065
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2066
|
+
const droppedPaths = diffDroppedPaths(page.data, data);
|
|
2067
|
+
if (droppedPaths.length > 0) {
|
|
2068
|
+
next.meta = { ...page.meta, outputValidation: { droppedPaths } };
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return next;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1939
2074
|
// src/model/materialize.ts
|
|
1940
2075
|
var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
|
|
1941
2076
|
CORE_OPTIONS_ID
|
|
@@ -1999,6 +2134,9 @@ function edgesOf(plugin) {
|
|
|
1999
2134
|
function isStandIn(plugin) {
|
|
2000
2135
|
return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
|
|
2001
2136
|
}
|
|
2137
|
+
function isDefault(plugin) {
|
|
2138
|
+
return (plugin.pluginType === "method" || plugin.pluginType === "property") && plugin.defaultSource !== void 0;
|
|
2139
|
+
}
|
|
2002
2140
|
function topoOrder(descriptors) {
|
|
2003
2141
|
const order = [];
|
|
2004
2142
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2013,28 +2151,89 @@ function topoOrder(descriptors) {
|
|
|
2013
2151
|
return order;
|
|
2014
2152
|
}
|
|
2015
2153
|
function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
|
|
2154
|
+
const rank = (plugin) => isStandIn(plugin) ? 0 : isDefault(plugin) ? 1 : 2;
|
|
2155
|
+
const allNodes = [];
|
|
2156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2157
|
+
const collect = (plugin) => {
|
|
2158
|
+
if (materialized.has(plugin.id) || seen.has(plugin)) return;
|
|
2159
|
+
seen.add(plugin);
|
|
2160
|
+
allNodes.push(plugin);
|
|
2161
|
+
for (const edge of edgesOf(plugin)) collect(edge);
|
|
2162
|
+
};
|
|
2163
|
+
collect(root);
|
|
2164
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2165
|
+
const candidatesById = /* @__PURE__ */ new Map();
|
|
2166
|
+
for (const node of allNodes) {
|
|
2167
|
+
childrenOf.set(
|
|
2168
|
+
node,
|
|
2169
|
+
edgesOf(node).filter((edge) => seen.has(edge))
|
|
2170
|
+
);
|
|
2171
|
+
const candidates = candidatesById.get(node.id);
|
|
2172
|
+
if (candidates) candidates.push(node);
|
|
2173
|
+
else candidatesById.set(node.id, [node]);
|
|
2174
|
+
}
|
|
2175
|
+
const live = new Set(allNodes);
|
|
2176
|
+
for (; ; ) {
|
|
2177
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2178
|
+
if (live.has(root)) reachable.add(root);
|
|
2179
|
+
const queue = reachable.has(root) ? [root] : [];
|
|
2180
|
+
while (queue.length) {
|
|
2181
|
+
const node = queue.pop();
|
|
2182
|
+
for (const child of childrenOf.get(node) ?? []) {
|
|
2183
|
+
if (reachable.has(child)) continue;
|
|
2184
|
+
reachable.add(child);
|
|
2185
|
+
if (live.has(child)) queue.push(child);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
let changed = false;
|
|
2189
|
+
for (const node of live) {
|
|
2190
|
+
if (!reachable.has(node)) {
|
|
2191
|
+
live.delete(node);
|
|
2192
|
+
changed = true;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
for (const candidates of candidatesById.values()) {
|
|
2196
|
+
let maxRank = -1;
|
|
2197
|
+
for (const candidate of candidates) {
|
|
2198
|
+
if (live.has(candidate)) maxRank = Math.max(maxRank, rank(candidate));
|
|
2199
|
+
}
|
|
2200
|
+
if (maxRank < 0) continue;
|
|
2201
|
+
for (const candidate of candidates) {
|
|
2202
|
+
if (live.has(candidate) && rank(candidate) < maxRank) {
|
|
2203
|
+
live.delete(candidate);
|
|
2204
|
+
changed = true;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (!changed) break;
|
|
2209
|
+
}
|
|
2016
2210
|
const byId = /* @__PURE__ */ new Map();
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2211
|
+
const conflictedDefaults = /* @__PURE__ */ new Set();
|
|
2212
|
+
const isOptional = (plugin) => "optional" in plugin && plugin.optional === true;
|
|
2213
|
+
for (const [id, candidates] of candidatesById) {
|
|
2214
|
+
const liveCandidates = candidates.filter(
|
|
2215
|
+
(candidate) => live.has(candidate)
|
|
2216
|
+
);
|
|
2217
|
+
const winner = liveCandidates.find((candidate) => !isOptional(candidate)) ?? liveCandidates[0];
|
|
2218
|
+
if (!winner) continue;
|
|
2219
|
+
if (liveCandidates.length > 1) {
|
|
2220
|
+
const winnerRank = rank(winner);
|
|
2221
|
+
if (winnerRank === 2) {
|
|
2024
2222
|
throw new Error(
|
|
2025
|
-
`createSdk: duplicate plugin id "${
|
|
2223
|
+
`createSdk: duplicate plugin id "${id}". Two different plugins registered under the same id.`
|
|
2026
2224
|
);
|
|
2027
2225
|
}
|
|
2028
|
-
if (
|
|
2029
|
-
|
|
2030
|
-
|
|
2226
|
+
if (winnerRank === 1) {
|
|
2227
|
+
const sources = new Set(
|
|
2228
|
+
liveCandidates.map(
|
|
2229
|
+
(candidate) => candidate.defaultSource
|
|
2230
|
+
)
|
|
2231
|
+
);
|
|
2232
|
+
if (sources.size > 1) conflictedDefaults.add(id);
|
|
2031
2233
|
}
|
|
2032
|
-
return;
|
|
2033
2234
|
}
|
|
2034
|
-
byId.set(
|
|
2035
|
-
|
|
2036
|
-
};
|
|
2037
|
-
visit(root);
|
|
2235
|
+
byId.set(id, winner);
|
|
2236
|
+
}
|
|
2038
2237
|
if (configuration) {
|
|
2039
2238
|
for (const [id, value] of Object.entries(configuration)) {
|
|
2040
2239
|
const existing = byId.get(id);
|
|
@@ -2085,6 +2284,14 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
|
|
|
2085
2284
|
);
|
|
2086
2285
|
}
|
|
2087
2286
|
}
|
|
2287
|
+
for (const id of conflictedDefaults) {
|
|
2288
|
+
const winner = byId.get(id);
|
|
2289
|
+
if (winner && isDefault(winner)) {
|
|
2290
|
+
throw new Error(
|
|
2291
|
+
`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.`
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2088
2295
|
return byId;
|
|
2089
2296
|
}
|
|
2090
2297
|
function bindValue({
|
|
@@ -2408,6 +2615,7 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2408
2615
|
const plugins = context.plugins;
|
|
2409
2616
|
for (const [id, descriptor] of descriptors) {
|
|
2410
2617
|
if (descriptor.pluginType !== "method") continue;
|
|
2618
|
+
if (isStandIn(descriptor)) continue;
|
|
2411
2619
|
const out = normalizeOutput(descriptor.output);
|
|
2412
2620
|
const entry = {
|
|
2413
2621
|
pluginType: "method",
|
|
@@ -2460,6 +2668,16 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2460
2668
|
const sdk = { context };
|
|
2461
2669
|
const methodAnnotator = descriptor.annotator;
|
|
2462
2670
|
const boundAnnotator = methodAnnotator ? (input) => methodAnnotator({ input }) : void 0;
|
|
2671
|
+
const outputPolicy = () => {
|
|
2672
|
+
const core = resolveCoreOptions(context);
|
|
2673
|
+
return {
|
|
2674
|
+
outputSchema: descriptor.meta?.outputSchema,
|
|
2675
|
+
skipOutputValidation: descriptor.skipOutputValidation,
|
|
2676
|
+
includeOutputValidationDroppedPaths: core?.includeOutputValidationDroppedPaths,
|
|
2677
|
+
methodName: descriptor.name,
|
|
2678
|
+
adaptError: core?.adaptError
|
|
2679
|
+
};
|
|
2680
|
+
};
|
|
2463
2681
|
if (out.type === "list") {
|
|
2464
2682
|
entry.value = createPaginatedFunction(
|
|
2465
2683
|
fold(callRun),
|
|
@@ -2470,11 +2688,15 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2470
2688
|
defaultPageSize: out.defaultPageSize,
|
|
2471
2689
|
adaptPage: out.adaptPage,
|
|
2472
2690
|
annotator: boundAnnotator,
|
|
2691
|
+
// Validate + strip each item against the item `outputSchema`
|
|
2692
|
+
// (item mode's sibling); dropped paths surface as `[].x` in the page's
|
|
2693
|
+
// `meta`, unioned across items.
|
|
2694
|
+
finalizePage: (page) => applyListOutputPolicy(page, outputPolicy()),
|
|
2473
2695
|
getDeprecation: () => entry.meta?.deprecation
|
|
2474
2696
|
}
|
|
2475
2697
|
);
|
|
2476
2698
|
} else if (out.type === "item") {
|
|
2477
|
-
const itemCore = async (input, ctx) => callRun(input, ctx);
|
|
2699
|
+
const itemCore = async (input, ctx) => applyItemOutputPolicy(await callRun(input, ctx), outputPolicy());
|
|
2478
2700
|
entry.value = createFunction(
|
|
2479
2701
|
fold(itemCore),
|
|
2480
2702
|
{
|
|
@@ -4190,7 +4412,9 @@ export {
|
|
|
4190
4412
|
createSdk,
|
|
4191
4413
|
createValidator,
|
|
4192
4414
|
dangerousContextPlugin,
|
|
4415
|
+
declareDefault,
|
|
4193
4416
|
declareMethod,
|
|
4417
|
+
declareOptionalMethod,
|
|
4194
4418
|
declareOptionalProperty,
|
|
4195
4419
|
declarePlugin,
|
|
4196
4420
|
declareProperty,
|
|
@@ -4212,6 +4436,7 @@ export {
|
|
|
4212
4436
|
getCurrentDepth,
|
|
4213
4437
|
getCurrentScope,
|
|
4214
4438
|
getFieldDescriptions,
|
|
4439
|
+
getNegatable,
|
|
4215
4440
|
getOutputSchema,
|
|
4216
4441
|
getRegistryPlugin,
|
|
4217
4442
|
getSchemaDescription,
|