@zapier/kitcore 0.10.1 → 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 +27 -0
- package/README.md +63 -5
- package/dist/index.cjs +486 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +252 -26
- package/dist/index.d.ts +252 -26
- package/dist/index.mjs +484 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
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
|
+
|
|
18
|
+
## 0.11.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 60892be: Added an explicit per-call annotation channel for attaching and reading per-invocation metadata:
|
|
23
|
+
- A method's `run` bag now includes the live `callContext` plus an `annotate(metadata)` function for recording per-invocation metadata onto the call's annotation bag.
|
|
24
|
+
- The `onMethodStart` / `onMethodEnd` hook context now carries `callId`, the call's `annotations`, and a `callOrigin` field (`"surface"` | `"internal"`), so observers can read a call's correlation id, annotations, and origin directly.
|
|
25
|
+
- `CallContext` gains a `callOrigin` field (`"surface"` | `"internal"`) that marks framework-internal calls, letting observers distinguish user calls from framework machinery (e.g. to keep framework calls out of telemetry). It is inherited by child calls, so a delegated subtree shares its root's origin.
|
|
26
|
+
- `defineHook` gains an optional `annotator` for deriving annotations from a call's input before `onMethodStart`. Multiple plugins' annotators compose (merged right-additively into the call's annotation bag), so annotation is contributed through the plugin graph rather than a single handler.
|
|
27
|
+
- `defineMethod` gains an optional pre-run `annotator` whose result merges into the call's annotations before `run`.
|
|
28
|
+
- Exported the `CallContext`, `Annotations`, and `CallOrigin` types, plus the `MethodAnnotator`, `HookAnnotator`, and `ComposedAnnotator` annotator function types.
|
|
29
|
+
|
|
3
30
|
## 0.10.1
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ There are four kinds of plugin, and an SDK is just a materialized plugin:
|
|
|
44
44
|
|
|
45
45
|
- **Method** (`defineMethod`) — a leaf that _is_ a function.
|
|
46
46
|
- **Property** (`defineProperty`) — a leaf that _is_ a value.
|
|
47
|
-
- **Hook** (`defineHook`) — a leaf contributing cross-cutting behavior (see [Hooks](#hooks-wrap-and-
|
|
47
|
+
- **Hook** (`defineHook`) — a leaf contributing cross-cutting behavior (see [Hooks](#hooks-wrap-observe-and-annotate)).
|
|
48
48
|
- **Aggregate** (`definePlugin`) — re-exports other plugins under binding names.
|
|
49
49
|
|
|
50
50
|
A plugin declares `imports` (an array of the plugins it depends on) and receives them as a flat `imports` bag, with each import bound under its own name. The two ends share the word the way ES modules do: `import { x } from "y"` is the declaration, `x` is the binding.
|
|
@@ -120,6 +120,26 @@ sdk.next(); // 2
|
|
|
120
120
|
|
|
121
121
|
`setup` can read `imports`, so state can be built from dependencies. Because state lives behind `setup`, several small "state plugins" can each own a slice instead of one monolith.
|
|
122
122
|
|
|
123
|
+
## Per-call context and annotations
|
|
124
|
+
|
|
125
|
+
Every method `run` bag includes a live `callContext` with the invocation's `callId`, `depth`, `callOrigin`, and `annotations`. Each method invocation gets its own annotation bag; a delegated child shares its root's call ID and origin but starts with empty annotations.
|
|
126
|
+
|
|
127
|
+
Use the run bag's `annotate(fields)` helper for metadata learned during execution. For metadata available from raw input, a method can declare a synchronous `annotator`; it runs before validation, `onMethodStart`, and `run`, so its `input` is `unknown` and must be narrowed before fields are read.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const runJob = defineMethod({
|
|
131
|
+
name: "runJob",
|
|
132
|
+
annotator: () => ({ operationType: "write" }),
|
|
133
|
+
run: async ({ input, callContext, annotate }) => {
|
|
134
|
+
const worker = await selectWorker(input);
|
|
135
|
+
annotate({ worker });
|
|
136
|
+
return executeJob(input, callContext.annotations);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Both forms merge into the same bag. `annotator` supplies fields before the method starts; `annotate` adds fields as the method runs.
|
|
142
|
+
|
|
123
143
|
## Properties
|
|
124
144
|
|
|
125
145
|
A property is a value rather than a function. It can be a static `value`, or a **live** `get` re-derived on every read, with an optional `setup` building the state `get` returns. So `setup` + `get` mirrors a method's `setup` + `run`.
|
|
@@ -217,9 +237,9 @@ const fetchMethod = defineMethod({
|
|
|
217
237
|
sdk.fetch("https://example.com", { method: "GET" });
|
|
218
238
|
```
|
|
219
239
|
|
|
220
|
-
## Hooks: wrap and
|
|
240
|
+
## Hooks: wrap, observe, and annotate
|
|
221
241
|
|
|
222
|
-
Cross-cutting behavior lives in a **hook** (`defineHook`), a fourth kind of leaf with
|
|
242
|
+
Cross-cutting behavior lives in a **hook** (`defineHook`), a fourth kind of leaf with three faces.
|
|
223
243
|
|
|
224
244
|
`wrap` is targeted middleware. An entry is keyed by the import binding it wraps and receives `{ imports, next, input }`. It must preserve the target's contract (enforced by the types), so it cannot change the public signature.
|
|
225
245
|
|
|
@@ -260,6 +280,17 @@ const telemetry = defineHook({
|
|
|
260
280
|
});
|
|
261
281
|
```
|
|
262
282
|
|
|
283
|
+
`annotator` adds cross-cutting annotations before `onMethodStart`. It is a synchronous mapper over `{ methodName, input, state }`; like method annotators, it receives raw pre-validation input. Multiple hook annotators compose in graph order, later fields win on collision, and the method's own annotator runs last. Each contributor is best-effort, so a thrown annotation error never breaks the method or suppresses other contributors.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const classifyOperations = defineHook({
|
|
287
|
+
name: "classifyOperations",
|
|
288
|
+
annotator: ({ methodName }) => ({ methodCategory: classify(methodName) }),
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Hook annotators run only for outermost surface-origin calls. They are suppressed for nested and internal-origin calls, and for methods called from an observer or another hook annotator. A method's own annotator still runs for every invocation, including those suppressed cases, so method behavior does not depend on how the method was reached.
|
|
293
|
+
|
|
263
294
|
A hook joins the graph like any other plugin: import or export it from an aggregate.
|
|
264
295
|
|
|
265
296
|
## Dependency injection
|
|
@@ -412,9 +443,36 @@ await controller.listChoices({
|
|
|
412
443
|
// { data: [{ label: "Slack", value: "slack" }], nextCursor }
|
|
413
444
|
```
|
|
414
445
|
|
|
415
|
-
##
|
|
446
|
+
## Plugin references
|
|
447
|
+
|
|
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.
|
|
416
474
|
|
|
417
|
-
|
|
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.
|
|
418
476
|
|
|
419
477
|
## Namespaces
|
|
420
478
|
|