@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/dist/index.mjs
CHANGED
|
@@ -924,7 +924,8 @@ function isSdkPage(value) {
|
|
|
924
924
|
}
|
|
925
925
|
function createPageFunction(coreFn, {
|
|
926
926
|
sdk,
|
|
927
|
-
adaptPage
|
|
927
|
+
adaptPage,
|
|
928
|
+
finalizePage
|
|
928
929
|
}) {
|
|
929
930
|
const functionName = coreFn.name + "Page";
|
|
930
931
|
const namedFunctions = {
|
|
@@ -937,7 +938,7 @@ function createPageFunction(coreFn, {
|
|
|
937
938
|
`${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
939
|
);
|
|
939
940
|
}
|
|
940
|
-
return page;
|
|
941
|
+
return finalizePage ? finalizePage(page) : page;
|
|
941
942
|
} catch (error) {
|
|
942
943
|
throw normalizeError(
|
|
943
944
|
error,
|
|
@@ -956,9 +957,14 @@ function createPaginatedFunction(coreFn, options) {
|
|
|
956
957
|
defaultPageSize,
|
|
957
958
|
adaptPage,
|
|
958
959
|
annotator,
|
|
960
|
+
finalizePage,
|
|
959
961
|
getDeprecation
|
|
960
962
|
} = options;
|
|
961
|
-
const pageFunction = createPageFunction(coreFn, {
|
|
963
|
+
const pageFunction = createPageFunction(coreFn, {
|
|
964
|
+
sdk,
|
|
965
|
+
adaptPage,
|
|
966
|
+
finalizePage
|
|
967
|
+
});
|
|
962
968
|
const functionName = name || coreFn.name;
|
|
963
969
|
const namedFunctions = {
|
|
964
970
|
[functionName]: function(callOptions) {
|
|
@@ -1484,6 +1490,7 @@ function defineMethod(config) {
|
|
|
1484
1490
|
importBindings: deps.bindings,
|
|
1485
1491
|
inputSchema: config.inputSchema,
|
|
1486
1492
|
skipInputValidation: config.skipInputValidation,
|
|
1493
|
+
skipOutputValidation: config.skipOutputValidation,
|
|
1487
1494
|
meta: collectLeafMeta(config),
|
|
1488
1495
|
resolvers: config.resolvers,
|
|
1489
1496
|
formatter: config.formatter,
|
|
@@ -1590,6 +1597,29 @@ function declareMethod(config) {
|
|
|
1590
1597
|
}
|
|
1591
1598
|
};
|
|
1592
1599
|
}
|
|
1600
|
+
function declareOptionalMethod(config) {
|
|
1601
|
+
const { name, namespace } = parseId(config.id);
|
|
1602
|
+
const id = makeId(name, namespace);
|
|
1603
|
+
return {
|
|
1604
|
+
pluginType: "method",
|
|
1605
|
+
name,
|
|
1606
|
+
namespace,
|
|
1607
|
+
id,
|
|
1608
|
+
standIn: true,
|
|
1609
|
+
optional: true,
|
|
1610
|
+
imports: [],
|
|
1611
|
+
importBindings: [],
|
|
1612
|
+
run: () => {
|
|
1613
|
+
throw new Error(
|
|
1614
|
+
`Plugin "${id}" is an optional stand-in (declareOptionalMethod) with no implementation. Its binding is \`undefined\` unless a real plugin is registered under this id.`
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
// Requires nothing (phantom carrier `<never, never>`): a consumer that
|
|
1618
|
+
// imports it still passes `createSdk`'s completeness check unprovided. The
|
|
1619
|
+
// `optional: true` literal drives `PluginSurface` to type the binding
|
|
1620
|
+
// `| undefined`.
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1593
1623
|
function defineProperty(config) {
|
|
1594
1624
|
const deps = normalizeImports(config.imports);
|
|
1595
1625
|
return {
|
|
@@ -1635,6 +1665,11 @@ function declareOptionalProperty(config) {
|
|
|
1635
1665
|
// import binding is still typed `TValue | undefined` from the descriptor.
|
|
1636
1666
|
};
|
|
1637
1667
|
}
|
|
1668
|
+
function declareDefault({
|
|
1669
|
+
plugin
|
|
1670
|
+
}) {
|
|
1671
|
+
return { ...plugin, defaultSource: plugin };
|
|
1672
|
+
}
|
|
1638
1673
|
function defineHook(config) {
|
|
1639
1674
|
const deps = normalizeImports(config.imports);
|
|
1640
1675
|
return {
|
|
@@ -1936,6 +1971,97 @@ var getRegistryPlugin = defineMethod({
|
|
|
1936
1971
|
run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
|
|
1937
1972
|
});
|
|
1938
1973
|
|
|
1974
|
+
// src/utils/output-policy.ts
|
|
1975
|
+
function isRecord(value) {
|
|
1976
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1977
|
+
}
|
|
1978
|
+
function diffDroppedPaths(raw, parsed, prefix = "") {
|
|
1979
|
+
const paths = [];
|
|
1980
|
+
walkDroppedPaths(raw, parsed, prefix, paths);
|
|
1981
|
+
return paths;
|
|
1982
|
+
}
|
|
1983
|
+
function walkDroppedPaths(raw, parsed, prefix, out) {
|
|
1984
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
1985
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1986
|
+
const length = Math.min(raw.length, parsed.length);
|
|
1987
|
+
for (let index = 0; index < length; index++) {
|
|
1988
|
+
const elementPaths = [];
|
|
1989
|
+
walkDroppedPaths(raw[index], parsed[index], `${prefix}[]`, elementPaths);
|
|
1990
|
+
for (const path of elementPaths) {
|
|
1991
|
+
if (seen.has(path)) continue;
|
|
1992
|
+
seen.add(path);
|
|
1993
|
+
out.push(path);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
if (isRecord(raw) && isRecord(parsed)) {
|
|
1999
|
+
for (const key of Object.keys(raw)) {
|
|
2000
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2001
|
+
if (!(key in parsed)) {
|
|
2002
|
+
out.push(path);
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
walkDroppedPaths(raw[key], parsed[key], path, out);
|
|
2006
|
+
}
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
function parseOutput(schema, value, policy, locator) {
|
|
2011
|
+
const result = schema.safeParse(value);
|
|
2012
|
+
if (result.success) return result.data;
|
|
2013
|
+
const issues = result.error.issues.map((issue) => {
|
|
2014
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "data";
|
|
2015
|
+
return `${path}: ${issue.message}`;
|
|
2016
|
+
});
|
|
2017
|
+
const subject = policy.methodName ? ` for "${policy.methodName}"` : "";
|
|
2018
|
+
const at = locator ? ` at ${locator}` : "";
|
|
2019
|
+
throw createCoreError(
|
|
2020
|
+
{
|
|
2021
|
+
code: CoreErrorCode.Validation,
|
|
2022
|
+
message: `Output validation failed${subject}${at}:
|
|
2023
|
+
${issues.join("\n ")}
|
|
2024
|
+
|
|
2025
|
+
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.`,
|
|
2026
|
+
details: { zodErrors: result.error.issues, output: value }
|
|
2027
|
+
},
|
|
2028
|
+
policy.adaptError
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
function applyItemOutputPolicy(result, policy) {
|
|
2032
|
+
const schema = policy.outputSchema;
|
|
2033
|
+
if (!schema || policy.skipOutputValidation) return result;
|
|
2034
|
+
if (!isRecord(result) || !("data" in result)) return result;
|
|
2035
|
+
const data = parseOutput(schema, result.data, policy);
|
|
2036
|
+
const next = { ...result, data };
|
|
2037
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2038
|
+
const droppedPaths = diffDroppedPaths(result.data, data);
|
|
2039
|
+
if (droppedPaths.length > 0) {
|
|
2040
|
+
next.meta = withOutputValidation(result.meta, droppedPaths);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return next;
|
|
2044
|
+
}
|
|
2045
|
+
function withOutputValidation(existing, droppedPaths) {
|
|
2046
|
+
const base = isRecord(existing) ? existing : {};
|
|
2047
|
+
return { ...base, outputValidation: { droppedPaths } };
|
|
2048
|
+
}
|
|
2049
|
+
function applyListOutputPolicy(page, policy) {
|
|
2050
|
+
const schema = policy.outputSchema;
|
|
2051
|
+
if (!schema || policy.skipOutputValidation) return page;
|
|
2052
|
+
const data = page.data.map(
|
|
2053
|
+
(item, index) => parseOutput(schema, item, policy, `data[${index}]`)
|
|
2054
|
+
);
|
|
2055
|
+
const next = { ...page, data };
|
|
2056
|
+
if (policy.includeOutputValidationDroppedPaths) {
|
|
2057
|
+
const droppedPaths = diffDroppedPaths(page.data, data);
|
|
2058
|
+
if (droppedPaths.length > 0) {
|
|
2059
|
+
next.meta = { ...page.meta, outputValidation: { droppedPaths } };
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
return next;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1939
2065
|
// src/model/materialize.ts
|
|
1940
2066
|
var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
|
|
1941
2067
|
CORE_OPTIONS_ID
|
|
@@ -1999,6 +2125,9 @@ function edgesOf(plugin) {
|
|
|
1999
2125
|
function isStandIn(plugin) {
|
|
2000
2126
|
return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
|
|
2001
2127
|
}
|
|
2128
|
+
function isDefault(plugin) {
|
|
2129
|
+
return (plugin.pluginType === "method" || plugin.pluginType === "property") && plugin.defaultSource !== void 0;
|
|
2130
|
+
}
|
|
2002
2131
|
function topoOrder(descriptors) {
|
|
2003
2132
|
const order = [];
|
|
2004
2133
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2013,28 +2142,89 @@ function topoOrder(descriptors) {
|
|
|
2013
2142
|
return order;
|
|
2014
2143
|
}
|
|
2015
2144
|
function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
|
|
2145
|
+
const rank = (plugin) => isStandIn(plugin) ? 0 : isDefault(plugin) ? 1 : 2;
|
|
2146
|
+
const allNodes = [];
|
|
2147
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2148
|
+
const collect = (plugin) => {
|
|
2149
|
+
if (materialized.has(plugin.id) || seen.has(plugin)) return;
|
|
2150
|
+
seen.add(plugin);
|
|
2151
|
+
allNodes.push(plugin);
|
|
2152
|
+
for (const edge of edgesOf(plugin)) collect(edge);
|
|
2153
|
+
};
|
|
2154
|
+
collect(root);
|
|
2155
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2156
|
+
const candidatesById = /* @__PURE__ */ new Map();
|
|
2157
|
+
for (const node of allNodes) {
|
|
2158
|
+
childrenOf.set(
|
|
2159
|
+
node,
|
|
2160
|
+
edgesOf(node).filter((edge) => seen.has(edge))
|
|
2161
|
+
);
|
|
2162
|
+
const candidates = candidatesById.get(node.id);
|
|
2163
|
+
if (candidates) candidates.push(node);
|
|
2164
|
+
else candidatesById.set(node.id, [node]);
|
|
2165
|
+
}
|
|
2166
|
+
const live = new Set(allNodes);
|
|
2167
|
+
for (; ; ) {
|
|
2168
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2169
|
+
if (live.has(root)) reachable.add(root);
|
|
2170
|
+
const queue = reachable.has(root) ? [root] : [];
|
|
2171
|
+
while (queue.length) {
|
|
2172
|
+
const node = queue.pop();
|
|
2173
|
+
for (const child of childrenOf.get(node) ?? []) {
|
|
2174
|
+
if (reachable.has(child)) continue;
|
|
2175
|
+
reachable.add(child);
|
|
2176
|
+
if (live.has(child)) queue.push(child);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
let changed = false;
|
|
2180
|
+
for (const node of live) {
|
|
2181
|
+
if (!reachable.has(node)) {
|
|
2182
|
+
live.delete(node);
|
|
2183
|
+
changed = true;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
for (const candidates of candidatesById.values()) {
|
|
2187
|
+
let maxRank = -1;
|
|
2188
|
+
for (const candidate of candidates) {
|
|
2189
|
+
if (live.has(candidate)) maxRank = Math.max(maxRank, rank(candidate));
|
|
2190
|
+
}
|
|
2191
|
+
if (maxRank < 0) continue;
|
|
2192
|
+
for (const candidate of candidates) {
|
|
2193
|
+
if (live.has(candidate) && rank(candidate) < maxRank) {
|
|
2194
|
+
live.delete(candidate);
|
|
2195
|
+
changed = true;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
if (!changed) break;
|
|
2200
|
+
}
|
|
2016
2201
|
const byId = /* @__PURE__ */ new Map();
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2202
|
+
const conflictedDefaults = /* @__PURE__ */ new Set();
|
|
2203
|
+
const isOptional = (plugin) => "optional" in plugin && plugin.optional === true;
|
|
2204
|
+
for (const [id, candidates] of candidatesById) {
|
|
2205
|
+
const liveCandidates = candidates.filter(
|
|
2206
|
+
(candidate) => live.has(candidate)
|
|
2207
|
+
);
|
|
2208
|
+
const winner = liveCandidates.find((candidate) => !isOptional(candidate)) ?? liveCandidates[0];
|
|
2209
|
+
if (!winner) continue;
|
|
2210
|
+
if (liveCandidates.length > 1) {
|
|
2211
|
+
const winnerRank = rank(winner);
|
|
2212
|
+
if (winnerRank === 2) {
|
|
2024
2213
|
throw new Error(
|
|
2025
|
-
`createSdk: duplicate plugin id "${
|
|
2214
|
+
`createSdk: duplicate plugin id "${id}". Two different plugins registered under the same id.`
|
|
2026
2215
|
);
|
|
2027
2216
|
}
|
|
2028
|
-
if (
|
|
2029
|
-
|
|
2030
|
-
|
|
2217
|
+
if (winnerRank === 1) {
|
|
2218
|
+
const sources = new Set(
|
|
2219
|
+
liveCandidates.map(
|
|
2220
|
+
(candidate) => candidate.defaultSource
|
|
2221
|
+
)
|
|
2222
|
+
);
|
|
2223
|
+
if (sources.size > 1) conflictedDefaults.add(id);
|
|
2031
2224
|
}
|
|
2032
|
-
return;
|
|
2033
2225
|
}
|
|
2034
|
-
byId.set(
|
|
2035
|
-
|
|
2036
|
-
};
|
|
2037
|
-
visit(root);
|
|
2226
|
+
byId.set(id, winner);
|
|
2227
|
+
}
|
|
2038
2228
|
if (configuration) {
|
|
2039
2229
|
for (const [id, value] of Object.entries(configuration)) {
|
|
2040
2230
|
const existing = byId.get(id);
|
|
@@ -2085,6 +2275,14 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
|
|
|
2085
2275
|
);
|
|
2086
2276
|
}
|
|
2087
2277
|
}
|
|
2278
|
+
for (const id of conflictedDefaults) {
|
|
2279
|
+
const winner = byId.get(id);
|
|
2280
|
+
if (winner && isDefault(winner)) {
|
|
2281
|
+
throw new Error(
|
|
2282
|
+
`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.`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2088
2286
|
return byId;
|
|
2089
2287
|
}
|
|
2090
2288
|
function bindValue({
|
|
@@ -2408,6 +2606,7 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2408
2606
|
const plugins = context.plugins;
|
|
2409
2607
|
for (const [id, descriptor] of descriptors) {
|
|
2410
2608
|
if (descriptor.pluginType !== "method") continue;
|
|
2609
|
+
if (isStandIn(descriptor)) continue;
|
|
2411
2610
|
const out = normalizeOutput(descriptor.output);
|
|
2412
2611
|
const entry = {
|
|
2413
2612
|
pluginType: "method",
|
|
@@ -2460,6 +2659,16 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2460
2659
|
const sdk = { context };
|
|
2461
2660
|
const methodAnnotator = descriptor.annotator;
|
|
2462
2661
|
const boundAnnotator = methodAnnotator ? (input) => methodAnnotator({ input }) : void 0;
|
|
2662
|
+
const outputPolicy = () => {
|
|
2663
|
+
const core = resolveCoreOptions(context);
|
|
2664
|
+
return {
|
|
2665
|
+
outputSchema: descriptor.meta?.outputSchema,
|
|
2666
|
+
skipOutputValidation: descriptor.skipOutputValidation,
|
|
2667
|
+
includeOutputValidationDroppedPaths: core?.includeOutputValidationDroppedPaths,
|
|
2668
|
+
methodName: descriptor.name,
|
|
2669
|
+
adaptError: core?.adaptError
|
|
2670
|
+
};
|
|
2671
|
+
};
|
|
2463
2672
|
if (out.type === "list") {
|
|
2464
2673
|
entry.value = createPaginatedFunction(
|
|
2465
2674
|
fold(callRun),
|
|
@@ -2470,11 +2679,15 @@ function buildMethodEntries(descriptors, context, states) {
|
|
|
2470
2679
|
defaultPageSize: out.defaultPageSize,
|
|
2471
2680
|
adaptPage: out.adaptPage,
|
|
2472
2681
|
annotator: boundAnnotator,
|
|
2682
|
+
// Validate + strip each item against the item `outputSchema`
|
|
2683
|
+
// (item mode's sibling); dropped paths surface as `[].x` in the page's
|
|
2684
|
+
// `meta`, unioned across items.
|
|
2685
|
+
finalizePage: (page) => applyListOutputPolicy(page, outputPolicy()),
|
|
2473
2686
|
getDeprecation: () => entry.meta?.deprecation
|
|
2474
2687
|
}
|
|
2475
2688
|
);
|
|
2476
2689
|
} else if (out.type === "item") {
|
|
2477
|
-
const itemCore = async (input, ctx) => callRun(input, ctx);
|
|
2690
|
+
const itemCore = async (input, ctx) => applyItemOutputPolicy(await callRun(input, ctx), outputPolicy());
|
|
2478
2691
|
entry.value = createFunction(
|
|
2479
2692
|
fold(itemCore),
|
|
2480
2693
|
{
|
|
@@ -4190,7 +4403,9 @@ export {
|
|
|
4190
4403
|
createSdk,
|
|
4191
4404
|
createValidator,
|
|
4192
4405
|
dangerousContextPlugin,
|
|
4406
|
+
declareDefault,
|
|
4193
4407
|
declareMethod,
|
|
4408
|
+
declareOptionalMethod,
|
|
4194
4409
|
declareOptionalProperty,
|
|
4195
4410
|
declarePlugin,
|
|
4196
4411
|
declareProperty,
|