@zapier/kitcore 0.16.0 → 0.17.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 CHANGED
@@ -1,5 +1,130 @@
1
1
  # @zapier/kitcore
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9cde1f6: **`getRegistry(sdk)` reads an SDK's registry without the SDK surfacing
8
+ anything**, and `createController` accepts any built SDK, so introspection costs
9
+ no `getRegistryPlugin` re-export. Re-export it anyway if you want
10
+ `sdk.getRegistry()` for your own callers; both return the same result.
11
+
12
+ Every accessor reads one registry: the free `getRegistry(sdk)`, a re-exported
13
+ `getRegistry()`, and the one a legacy-bridged SDK surfaces all return the same
14
+ memoized object. It is memoized per SDK and per package filter, and every reader
15
+ shares it, so `functions`, `categories`, and each category's own `functions` are
16
+ frozen. Copy before sorting or splicing. The memo lives on the SDK's context, so a process holding two
17
+ copies of kitcore, one bundled into a head and one loaded directly, shares it
18
+ and invalidates it once. A `createPluginStack()` build has no kitcore context and so no
19
+ memo: there the call forwards to the SDK's own `getRegistry()` and hands back a
20
+ fresh, mutable result each time.
21
+
22
+ **A property that declares `dynamicMembers` must have an object value when the
23
+ SDK is built.** `createSdk` throws otherwise, naming the property. The registry
24
+ reports a member like `apps.{appKey}` only while its root is a live object, and
25
+ that answer is memoized, so a root arriving later would leave the registry
26
+ reporting whatever was true when something first read it. Build the root in
27
+ `setup` and return it from `get`.
28
+
29
+ **`defineOverride(method, { ... })` patches how a surface presents a method.**
30
+ Pass the method itself, or a `declareMethod` stand-in when you want to patch one
31
+ without importing it, so no id is respelled and a rename cannot leave a silent
32
+ no-op behind. `namespace` names the override rather than the target, so two
33
+ surfaces can patch the same method in one graph. `defineMethodOverride({ target })`
34
+ still works and warns.
35
+
36
+ **An override patches presentation only.** Its config takes
37
+ `OverridableMetaFields`: `description`, `categories`, `itemType`, `returnType`,
38
+ `packages`, `experimental`, `deprecation`, and `supportsJsonOutput`. Anything
39
+ else throws, including for JavaScript callers the type does not reach. An
40
+ override changes how a surface describes a method, never what it does. A
41
+ method's declared type is fixed at `defineMethod` and nothing re-checks it
42
+ afterwards, so patching output validation, a host's confirmation prompt, or
43
+ which input a caller can pass would let a call fail against a contract its own
44
+ return type says it satisfies. That rules out `outputSchema`, `confirm`, and
45
+ `aliases`, and `type` too, because the registry derives `confirm` from it.
46
+
47
+ **Provide a declared method or property by reference.**
48
+ `defineProperty(ref, { value })` and `defineMethod(ref, { run })` take a
49
+ `declareProperty` / `declareMethod` stand-in instead of a config that respells
50
+ its `name` and `namespace`, so no id string is repeated and `value` and `run`
51
+ are typed against the declaration. It is also how a graph supplies framework
52
+ options as ordinary properties rather than through `createSdk`'s `configuration`
53
+ channel. The method form covers raw output only.
54
+
55
+ **`requireParameters` accepts an absolute path**, which says exactly which value
56
+ a resolver means. A bare name is convenient and can go wrong: it resolves in the
57
+ resolver's own container first, then the root, so when both hold the name the
58
+ container wins and there is no way to say otherwise. An array
59
+ (`[["nested", "owner"]]`) is a path from the root, so it names one place. It also
60
+ reaches a field inside another parameter, which no bare name can address. A
61
+ dotted string is one key, not a path.
62
+
63
+ **A raw method's `outputSchema` is enforced.** Raw has no envelope, so the schema
64
+ is matched against the whole return, and a method that meant to be
65
+ `output: "item"` and returns `{ data }` fails with an error saying so. It
66
+ validates without reshaping: the caller gets the original object back, extras and
67
+ all. **A raw method carrying a schema for projection only needs
68
+ `skipOutputValidation: true`.**
69
+
70
+ **A caller can skip output validation for one call** by passing
71
+ `skipOutputDataValidation: true` alongside an item or list method's input. An
72
+ author's `skipOutputValidation` wins, so a caller can only ever subtract. Raw
73
+ methods do not take it: their call object is entirely the author's, so the key
74
+ there is ordinary input.
75
+
76
+ **A call runs through BOTH schemas.** The framework's own parameters (`cursor`,
77
+ `pageSize`, `maxItems`, `skipOutputDataValidation`) are held to a minimal floor,
78
+ whatever a method declares and even when it declares no `inputSchema` at all.
79
+ The floor rejects only what the machinery cannot act on: `pageSize` at least 1,
80
+ because a page loop asking for zero items does not terminate, `maxItems` at
81
+ least 0, and `cursor` any string.
82
+
83
+ The method's own `inputSchema` then sees the whole call, so a constraint it
84
+ declares on one of those parameters still applies. Write
85
+ `pageSize: z.number().max(100)` and callers get your cap on top of the floor.
86
+ Nothing reads your schema's shape to work this out, so a `.transform()` or
87
+ `.refine()` wrapper cannot hide a declaration.
88
+
89
+ A `strict()` schema is the exception, since it cannot ignore a key it never
90
+ declared. When it rejects a framework parameter by name, that parameter is
91
+ removed and the parse runs again, so the method sees exactly the input it
92
+ described and the error names only the caller's own mistakes.
93
+
94
+ On the way to `run`, the framework's validated value wins for the parameters it
95
+ owns, so a caller asking for `pageSize: 5` gets 5. A list `run` receives the
96
+ `cursor` and `pageSize` of the page it must fetch. `maxItems` and the output
97
+ skip stop at the framework, which is the only thing that can act on them. The
98
+ types say the same: `Parameters<typeof method.run>[0]["input"]` carries the page
99
+ controls and nothing else.
100
+
101
+ **`meta.outputDataValidation` reports what the framework did**, typed
102
+ `OutputDataValidationReport`:
103
+
104
+ ```ts
105
+ | { skipped: true }
106
+ | { skipped: false; droppedPaths: null }
107
+ | { skipped: false; droppedPaths: string[]; instruction: string }
108
+ ```
109
+
110
+ `skipped` says whether the framework parsed, never that the data is unchecked.
111
+ `droppedPaths: null` means nothing was dropped, and `instruction` rides only the
112
+ arm where it is actionable. Reading `droppedPaths` requires narrowing on
113
+ `skipped` first. A report appears only when there is something to say: a caller's
114
+ skip that changed something, or paths dropped with the
115
+ `includeOutputValidationDroppedPaths` core option on.
116
+
117
+ The shape and the instruction wording match the connectors SDK, so a consumer
118
+ moving between them reads one report.
119
+
120
+ `meta.outputValidation` is deprecated in favour of it. It is still written, with
121
+ the same `{ droppedPaths }` shape and the same emission rule, and it will be
122
+ removed in a later release.
123
+
124
+ `StandInId` and `SdkContextCarrier` are exported so a package building on
125
+ kitcore can emit declarations that name them. Nothing asks you to write either
126
+ one yourself.
127
+
3
128
  ## 0.16.0
4
129
 
5
130
  ### Minor Changes
package/README.md CHANGED
@@ -350,21 +350,23 @@ await disposeSdk(sdk, { exitCode: 0 });
350
350
 
351
351
  ## Introspection
352
352
 
353
- Re-export the built-in `getRegistryPlugin` to put a `getRegistry()` method on the SDK. It reports the live surface as plain data (one entry per binding, with the leaf's metadata), which is what drives generated docs, CLI commands, and MCP tools.
353
+ `getRegistry(sdk)` reports the live surface as plain data (one entry per binding, with the leaf's metadata), which is what drives generated docs, CLI commands, and MCP tools. It needs nothing on the SDK itself.
354
354
 
355
355
  ```ts
356
- import { getRegistryPlugin } from "@zapier/kitcore";
356
+ import { getRegistry } from "@zapier/kitcore";
357
357
 
358
- const sdk = createSdk(
359
- definePlugin({ name: "api", exports: [greet, getRegistryPlugin] }),
360
- );
358
+ const sdk = createSdk(definePlugin({ name: "api", exports: [greet] }));
361
359
 
362
- const registry = sdk.getRegistry();
360
+ const registry = getRegistry(sdk);
363
361
  registry.functions; // [{ name, description, inputSchema, ... }]
364
362
  registry.categories; // grouped for menus / docs
365
363
  ```
366
364
 
367
- Because the registry reads the surface at call time, it also reflects anything added later with `addPlugin`.
365
+ Re-export the built-in `getRegistryPlugin` if you also want a `getRegistry()` method on the SDK surface for your own callers. It is a thin shim over the same result, so the two agree. Ids are scoped to a graph, so separate SDKs in one process can each register it; what cannot work is one graph pulling in two copies of kitcore, whose built-ins would then collide on the shared `kitcore/getRegistry` id. Introspection does not depend on the re-export either way.
366
+
367
+ The result is memoized per SDK and per package filter, and `addPlugin` drops it, so a registry read still reflects anything added after the build. Because every reader shares that one object, its `functions` and `categories` arrays are frozen: copy before sorting or filtering in place.
368
+
369
+ That holds for an SDK with a kitcore context, which is anything `createSdk` built. An SDK without one, meaning a `createPluginStack()` build, has no memo to share: `getRegistry(sdk)` forwards to the SDK's own `getRegistry()` and hands back whatever it builds, fresh and mutable on every call.
368
370
 
369
371
  ## Resolving inputs: controllers
370
372