@smthrs/plugin 0.0.0-stage → 1.0.0-rc.12
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 +79 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/dist/cjs/Config.d.ts +129 -0
- package/dist/cjs/Config.d.ts.map +1 -0
- package/dist/cjs/Config.js +115 -0
- package/dist/cjs/Config.js.map +7 -0
- package/dist/cjs/Hooks.d.ts +176 -0
- package/dist/cjs/Hooks.d.ts.map +1 -0
- package/dist/cjs/Hooks.js +34 -0
- package/dist/cjs/Hooks.js.map +7 -0
- package/dist/cjs/Kernel.d.ts +81 -0
- package/dist/cjs/Kernel.d.ts.map +1 -0
- package/dist/cjs/Kernel.js +57 -0
- package/dist/cjs/Kernel.js.map +7 -0
- package/dist/cjs/Plugin.d.ts +71 -0
- package/dist/cjs/Plugin.d.ts.map +1 -0
- package/dist/cjs/Plugin.js +25 -0
- package/dist/cjs/Plugin.js.map +7 -0
- package/dist/cjs/PluginError.d.ts +50 -0
- package/dist/cjs/PluginError.d.ts.map +1 -0
- package/dist/cjs/PluginError.js +58 -0
- package/dist/cjs/PluginError.js.map +7 -0
- package/dist/cjs/Plugins.d.ts +97 -0
- package/dist/cjs/Plugins.d.ts.map +1 -0
- package/dist/cjs/Plugins.js +169 -0
- package/dist/cjs/Plugins.js.map +7 -0
- package/dist/cjs/Resolve.d.ts +128 -0
- package/dist/cjs/Resolve.d.ts.map +1 -0
- package/dist/cjs/Resolve.js +346 -0
- package/dist/cjs/Resolve.js.map +7 -0
- package/dist/cjs/index.d.ts +83 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +45 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Boundary.d.ts +96 -0
- package/dist/cjs/internal/Boundary.d.ts.map +1 -0
- package/dist/cjs/internal/Boundary.js +129 -0
- package/dist/cjs/internal/Boundary.js.map +7 -0
- package/dist/cjs/internal/mergePluginLayers.d.ts +21 -0
- package/dist/cjs/internal/mergePluginLayers.d.ts.map +1 -0
- package/dist/cjs/internal/mergePluginLayers.js +57 -0
- package/dist/cjs/internal/mergePluginLayers.js.map +7 -0
- package/dist/cjs/internal/snapshotCacheEnvironment.d.ts +23 -0
- package/dist/cjs/internal/snapshotCacheEnvironment.d.ts.map +1 -0
- package/dist/cjs/internal/snapshotCacheEnvironment.js +92 -0
- package/dist/cjs/internal/snapshotCacheEnvironment.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Config.d.ts +129 -0
- package/dist/esm/Config.d.ts.map +1 -0
- package/dist/esm/Config.js +160 -0
- package/dist/esm/Config.js.map +1 -0
- package/dist/esm/Hooks.d.ts +176 -0
- package/dist/esm/Hooks.d.ts.map +1 -0
- package/dist/esm/Hooks.js +56 -0
- package/dist/esm/Hooks.js.map +1 -0
- package/dist/esm/Kernel.d.ts +81 -0
- package/dist/esm/Kernel.d.ts.map +1 -0
- package/dist/esm/Kernel.js +58 -0
- package/dist/esm/Kernel.js.map +1 -0
- package/dist/esm/Plugin.d.ts +71 -0
- package/dist/esm/Plugin.d.ts.map +1 -0
- package/dist/esm/Plugin.js +20 -0
- package/dist/esm/Plugin.js.map +1 -0
- package/dist/esm/PluginError.d.ts +50 -0
- package/dist/esm/PluginError.d.ts.map +1 -0
- package/dist/esm/PluginError.js +53 -0
- package/dist/esm/PluginError.js.map +1 -0
- package/dist/esm/Plugins.d.ts +97 -0
- package/dist/esm/Plugins.d.ts.map +1 -0
- package/dist/esm/Plugins.js +190 -0
- package/dist/esm/Plugins.js.map +1 -0
- package/dist/esm/Resolve.d.ts +128 -0
- package/dist/esm/Resolve.d.ts.map +1 -0
- package/dist/esm/Resolve.js +369 -0
- package/dist/esm/Resolve.js.map +1 -0
- package/dist/esm/index.d.ts +83 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +48 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Boundary.d.ts +96 -0
- package/dist/esm/internal/Boundary.d.ts.map +1 -0
- package/dist/esm/internal/Boundary.js +162 -0
- package/dist/esm/internal/Boundary.js.map +1 -0
- package/dist/esm/internal/mergePluginLayers.d.ts +21 -0
- package/dist/esm/internal/mergePluginLayers.d.ts.map +1 -0
- package/dist/esm/internal/mergePluginLayers.js +41 -0
- package/dist/esm/internal/mergePluginLayers.js.map +1 -0
- package/dist/esm/internal/snapshotCacheEnvironment.d.ts +23 -0
- package/dist/esm/internal/snapshotCacheEnvironment.d.ts.map +1 -0
- package/dist/esm/internal/snapshotCacheEnvironment.js +64 -0
- package/dist/esm/internal/snapshotCacheEnvironment.js.map +1 -0
- package/docs/README.md +133 -0
- package/docs/api.md +524 -0
- package/docs/concepts/configuration.md +111 -0
- package/docs/concepts/hook-kinds.md +107 -0
- package/docs/concepts/resolution.md +132 -0
- package/docs/guides/cache-identity.md +106 -0
- package/docs/guides/contribute-services.md +84 -0
- package/docs/guides/host-your-own-hooks.md +129 -0
- package/docs/guides/order-handlers.md +76 -0
- package/docs/guides/select-plugins.md +72 -0
- package/docs/guides/testing.md +149 -0
- package/docs/installation.md +75 -0
- package/docs/quickstart.md +152 -0
- package/docs/troubleshooting.md +180 -0
- package/package.json +179 -3
- package/src/Config.ts +207 -0
- package/src/Hooks.ts +212 -0
- package/src/Kernel.ts +119 -0
- package/src/Plugin.ts +80 -0
- package/src/PluginError.ts +62 -0
- package/src/Plugins.ts +271 -0
- package/src/Resolve.ts +539 -0
- package/src/index.ts +91 -0
- package/src/internal/Boundary.ts +252 -0
- package/src/internal/mergePluginLayers.ts +57 -0
- package/src/internal/snapshotCacheEnvironment.ts +78 -0
package/docs/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@smthrs/plugin"
|
|
3
|
+
description: "A typed, bounded plugin kernel for Effect programs: Vite-style hooks and ordering, a config waterfall, and a resolution boundary that copies every value a caller hands it."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`@smthrs/plugin` lets a TypeScript program accept plugins written by other
|
|
7
|
+
people. You name the extension points your program offers, a plugin author
|
|
8
|
+
hands you a plain record of handlers, and the kernel turns that list into an
|
|
9
|
+
ordered, immutable catalog you dispatch against.
|
|
10
|
+
|
|
11
|
+
It is Vite's plugin model, typed end to end and bounded at every edge: a plugin
|
|
12
|
+
is a plain record, a hook is an ordinary [Effect](https://effect.website),
|
|
13
|
+
ordering follows Vite's rules exactly, and no value a caller passes in survives
|
|
14
|
+
as a live reference into dispatch.
|
|
15
|
+
|
|
16
|
+
## What it solves
|
|
17
|
+
|
|
18
|
+
An extensible program has to answer five questions before its first plugin
|
|
19
|
+
runs: which plugins are selected, what order their handlers run in, what a
|
|
20
|
+
handler may return, what a failing handler does to the caller, and whether a
|
|
21
|
+
plugin can change any of that after startup. This package answers all five, and
|
|
22
|
+
refuses the input that would leave one of them undecided:
|
|
23
|
+
|
|
24
|
+
- **Selection** is the `apply` field: a host target, or a predicate over the
|
|
25
|
+
configuration.
|
|
26
|
+
- **Order** is Vite's rule kept verbatim. `enforce` sorts the plugin list once,
|
|
27
|
+
and a per-hook `order` re-partitions it for a single hook.
|
|
28
|
+
- **Result and failure** come from the hook's kind. `sequential`, `parallel`,
|
|
29
|
+
`first`, and `waterfall` each promise something different, and the kind is
|
|
30
|
+
checked in the type system and again against the host's runtime catalog.
|
|
31
|
+
- **Change after startup** is not possible. Resolution copies every plugin
|
|
32
|
+
record, hook object, and configuration value it accepts, so dispatch never
|
|
33
|
+
reads memory a caller still holds.
|
|
34
|
+
|
|
35
|
+
Reach for it when you are the host: you have a composition, you want other
|
|
36
|
+
people's code inside it at named points, and you would rather refuse a
|
|
37
|
+
malformed plugin at startup with an error code and a value path than debug it
|
|
38
|
+
in production.
|
|
39
|
+
|
|
40
|
+
## The hook catalog is yours
|
|
41
|
+
|
|
42
|
+
The kernel owns one catalog of its own, the two-step config lifecycle (`config`
|
|
43
|
+
and `configResolved`). Every other hook belongs to a host. You declare your
|
|
44
|
+
hooks with TypeScript module augmentation, hand the kernel the matching runtime
|
|
45
|
+
catalog, and dispatch that catalog and nothing else. The `FlowsHooks` interface
|
|
46
|
+
is open for augmentation and closed for dispatch, so another host augmenting
|
|
47
|
+
the same interface still dispatches only the hooks it supplied.
|
|
48
|
+
|
|
49
|
+
A plugin author writes a record with a `name` and some `hooks`. There is no base
|
|
50
|
+
class, no registration call, and no inheritance.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @smthrs/plugin@next effect
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Node.js 26.4.0 or later is required. For the runtime requirements, the import
|
|
59
|
+
forms, and the subpaths the export map blocks, see
|
|
60
|
+
[Installation](./installation.md).
|
|
61
|
+
|
|
62
|
+
## The shortest real example
|
|
63
|
+
|
|
64
|
+
A plugin contributes a configuration namespace, and a second hook reads the
|
|
65
|
+
resolved result back:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { type FlowsPlugin, Kernel } from "@smthrs/plugin"
|
|
69
|
+
import { Effect } from "effect"
|
|
70
|
+
|
|
71
|
+
const feature: FlowsPlugin = {
|
|
72
|
+
name: "flows-plugin-feature",
|
|
73
|
+
hooks: {
|
|
74
|
+
config: () => Effect.succeed({ feature: { enabled: true } }),
|
|
75
|
+
configResolved: (config) => Effect.log(`feature enabled: ${config["feature"] !== undefined}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const kernel = await Effect.runPromise(Kernel.make([feature]))
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`Kernel.make` gives you four things:
|
|
83
|
+
|
|
84
|
+
- `kernel.config` is the frozen, deeply copied configuration every plugin
|
|
85
|
+
agreed on.
|
|
86
|
+
- `kernel.plugins` dispatches hooks.
|
|
87
|
+
- `kernel.layer` is every plugin's Effect services merged in resolved order.
|
|
88
|
+
- `kernel.observerErrors` holds the failures that `configResolved` observers
|
|
89
|
+
reported without failing startup.
|
|
90
|
+
|
|
91
|
+
## Where this sits in Smithers
|
|
92
|
+
|
|
93
|
+
This package is the mechanism, not a host: on its own it dispatches only the
|
|
94
|
+
config lifecycle. The shipped host is [`@smthrs/agent`](/api/agent), the
|
|
95
|
+
Smithers agent loop, which augments `FlowsHooks` with three waterfall hooks of
|
|
96
|
+
its own (`cellRegistry`, `cellFlows`, and `cellModelRequest`), passes the
|
|
97
|
+
matching runtime catalog with `target: "harness"`, and dispatches them around
|
|
98
|
+
each frame of an agent cell. If you want to extend the Smithers agent rather
|
|
99
|
+
than build a host of your own, read that package first: its hooks are the ones
|
|
100
|
+
your plugin declares, and this package explains what those hooks promise.
|
|
101
|
+
|
|
102
|
+
Both sit under the `smthrs` command line, [`@smthrs/cli`](/api/cli), which runs
|
|
103
|
+
and inspects the durable flows a Smithers installation is made of. Start there
|
|
104
|
+
for the whole picture.
|
|
105
|
+
|
|
106
|
+
The kernel depends on [`@smthrs/flow`](/api/flow) for the
|
|
107
|
+
`Action.CacheEnvironment` schema used to
|
|
108
|
+
[declare a cache identity](./guides/cache-identity.md), and
|
|
109
|
+
[`@smthrs/canonical`](/api/canonical) for immutable catalog maps.
|
|
110
|
+
|
|
111
|
+
## What this package does not do
|
|
112
|
+
|
|
113
|
+
The kernel is not an engine lifecycle registry. Retry, storage, concurrency,
|
|
114
|
+
cache, wait, journal, and ownership policy stay on the Effect service or
|
|
115
|
+
constructor option that applies them. Configuration refuses the root keys
|
|
116
|
+
`engine`, `retry`, `store`, and `plugins` so that a configuration file cannot
|
|
117
|
+
imply otherwise.
|
|
118
|
+
|
|
119
|
+
## Where to go next
|
|
120
|
+
|
|
121
|
+
- [Installation](./installation.md) for the runtime requirements, the import
|
|
122
|
+
forms, and the subpaths that are not public.
|
|
123
|
+
- [Quickstart](./quickstart.md) to build a host with its own hook, run it, and
|
|
124
|
+
read a typed result.
|
|
125
|
+
- [Hook kinds](./concepts/hook-kinds.md) for what each of the four dispatch
|
|
126
|
+
shapes promises.
|
|
127
|
+
- [Resolution](./concepts/resolution.md) for how a plugin list becomes an
|
|
128
|
+
ordered, immutable catalog.
|
|
129
|
+
- [Configuration](./concepts/configuration.md) for the config waterfall and the
|
|
130
|
+
JSON boundary it enforces.
|
|
131
|
+
- [API reference](./api.md) for every export, with signatures and limits.
|
|
132
|
+
- [Troubleshooting](./troubleshooting.md) for each `PluginError` code, what
|
|
133
|
+
causes it, and what to change.
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "API reference"
|
|
3
|
+
description: "Every export of @smthrs/plugin: the plugin and hook declarations, the Kernel, Plugins, Resolve, and Config modules, the PluginError codes, and the published limits."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Import the declarations from the root and the four stateful modules either as
|
|
7
|
+
namespaces from the root or from their own subpaths:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { engineHooks, type FlowsPlugin, make, PluginError } from "@smthrs/plugin"
|
|
11
|
+
import * as Config from "@smthrs/plugin/Config"
|
|
12
|
+
import * as Kernel from "@smthrs/plugin/Kernel"
|
|
13
|
+
import * as Plugins from "@smthrs/plugin/Plugins"
|
|
14
|
+
import * as Resolve from "@smthrs/plugin/Resolve"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Every function that can refuse returns `Effect<A, PluginError>`, except
|
|
18
|
+
`Config.merge` and `Config.deepFreeze`, which are synchronous and throw a
|
|
19
|
+
`PluginError`.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Kernel, make } from "@smthrs/plugin"
|
|
25
|
+
|
|
26
|
+
const editor = make({
|
|
27
|
+
name: "flows-plugin-editor",
|
|
28
|
+
hooks: { config: () => Effect.succeed({ editor: { readOnly: false } }) }
|
|
29
|
+
})
|
|
30
|
+
const kernel = await Effect.runPromise(Kernel.make([editor], {}, { target: "harness" }))
|
|
31
|
+
console.log(kernel.config) // { editor: { readOnly: false } }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Root exports
|
|
35
|
+
|
|
36
|
+
| Export | Kind | Summary |
|
|
37
|
+
| ------------------------------------------------------------------------ | ------------------ | --------------------------------------------------------------------- |
|
|
38
|
+
| `FlowsHooks` | interface | The augmentable hook catalog. Declares `config` and `configResolved`. |
|
|
39
|
+
| `Apply`, `FlowsPlugin`, `PluginInput`, `make` | from `Plugin` | The plugin record and its constructor. |
|
|
40
|
+
| Hook kinds and type-level helpers, `engineHooks`, `handlerOf`, `orderOf` | from `Hooks` | The hook surface. |
|
|
41
|
+
| `PluginErrorCode`, `PluginError` | from `PluginError` | The single typed failure. |
|
|
42
|
+
| `Config`, `Kernel`, `Plugins`, `Resolve` | namespaces | The four modules. |
|
|
43
|
+
|
|
44
|
+
### FlowsHooks
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface FlowsHooks {
|
|
48
|
+
readonly config: WaterfallHook<(config: FlowsConfig) => Effect.Effect<Partial<FlowsConfig> | void, any, never>>
|
|
49
|
+
readonly configResolved: ParallelHook<(config: ResolvedConfig) => Effect.Effect<void, any, never>>
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Declared in the entry point so that `declare module "@smthrs/plugin"` can
|
|
54
|
+
augment it. Open for augmentation, closed for dispatch: the kernel dispatches
|
|
55
|
+
only the config lifecycle, and a host supplies and dispatches its own catalog
|
|
56
|
+
over the same augmented interface.
|
|
57
|
+
|
|
58
|
+
Both startup hooks are context-free. `Kernel.make` runs them before any plugin
|
|
59
|
+
layer is built and supplies no services of its own, so no externally supplied
|
|
60
|
+
service is available during startup. A handler that needs one provides it
|
|
61
|
+
inside the hook with `Effect.provide`. A plugin literal whose `config` hook
|
|
62
|
+
requires a service fails to compile against `FlowsHooks`; a host whose startup
|
|
63
|
+
hooks do require services declares a separate hook interface, and
|
|
64
|
+
`Kernel.make<H>` then carries the requirement as `Kernel.StartupContext<H>`.
|
|
65
|
+
|
|
66
|
+
## Plugin
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
type Apply = "engine" | "harness" | ((config: FlowsConfig) => boolean)
|
|
70
|
+
|
|
71
|
+
interface FlowsPlugin<H = FlowsHooks> {
|
|
72
|
+
readonly name: string
|
|
73
|
+
readonly version?: string | undefined
|
|
74
|
+
readonly enforce?: "pre" | "post" | undefined
|
|
75
|
+
readonly apply?: Apply | undefined
|
|
76
|
+
readonly layer?: Layer.Layer<never, any, any> | undefined
|
|
77
|
+
readonly hooks?: Partial<H> | undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type PluginInput<H = FlowsHooks> =
|
|
81
|
+
| FlowsPlugin<H>
|
|
82
|
+
| false
|
|
83
|
+
| null
|
|
84
|
+
| undefined
|
|
85
|
+
| ReadonlyArray<PluginInput<H>>
|
|
86
|
+
|
|
87
|
+
const make: <H = FlowsHooks>(plugin: FlowsPlugin<H>) => FlowsPlugin<H>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Field | Meaning |
|
|
91
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `name` | Required and unique among selected plugins. Compared as an exact Unicode string, at most 256 UTF-16 code units, never empty or whitespace only. Convention: `flows-plugin-<thing>`. |
|
|
93
|
+
| `version` | Semantic identity folded into sealed cache keys. Required when the host declares a `cacheEnvironment`; refused as empty or malformed text. |
|
|
94
|
+
| `enforce` | Ordering group. Omitted means normal. |
|
|
95
|
+
| `apply` | Conditional inclusion. A literal names the host target; a predicate receives the pre-resolution configuration and returns a boolean. |
|
|
96
|
+
| `layer` | Services this plugin contributes to the composition. Must be an Effect `Layer`. |
|
|
97
|
+
| `hooks` | Typed hook entries. Only keys declared in `H` compile, and only names in the host's runtime catalog resolve. |
|
|
98
|
+
|
|
99
|
+
`make` is an identity function that pins a plugin literal to `FlowsPlugin<H>`,
|
|
100
|
+
so an excess or misspelled hook key fails at the definition site.
|
|
101
|
+
`PluginInput` accepts falsy entries and arrays nested up to
|
|
102
|
+
`maximumPluginDepth`, which is what makes a preset an ordinary function that
|
|
103
|
+
returns plugins.
|
|
104
|
+
|
|
105
|
+
## Hooks
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
type HookKind = "sequential" | "parallel" | "first" | "waterfall"
|
|
109
|
+
|
|
110
|
+
interface HookObject<F> {
|
|
111
|
+
readonly order?: "pre" | "post" | undefined
|
|
112
|
+
readonly handler: F
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type HookEntry<K extends HookKind, F> = (F | HookObject<F>) & HookMeta<K, F>
|
|
116
|
+
|
|
117
|
+
type SequentialHook<F> = HookEntry<"sequential", F>
|
|
118
|
+
type ParallelHook<F> = HookEntry<"parallel", F>
|
|
119
|
+
type FirstHook<F> = HookEntry<"first", F>
|
|
120
|
+
type WaterfallHook<F> = HookEntry<"waterfall", F>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`HookMeta<K, F>` is a phantom carrier: it records the kind and the handler type
|
|
124
|
+
in the type system and adds nothing at runtime. A hook entry is either the bare
|
|
125
|
+
handler or the `{ order, handler }` object; `undefined` and `null` are refused.
|
|
126
|
+
|
|
127
|
+
Type-level helpers, used by the dispatcher's signatures and available to hosts:
|
|
128
|
+
|
|
129
|
+
| Helper | Extracts |
|
|
130
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `KindOf<T>` | The declared kind of a hook entry type. |
|
|
132
|
+
| `HandlerOf<T>` | The handler function type. |
|
|
133
|
+
| `KeysOfKind<H, K>` | The hook names of kind `K` in interface `H`. |
|
|
134
|
+
| `ArgsOf<T>` | The positional argument tuple the handler accepts. |
|
|
135
|
+
| `ReturnOf<T>` | The Effect the handler returns. |
|
|
136
|
+
| `SuccessOf<T>` | That Effect's success value. |
|
|
137
|
+
| `ContextOf<T>` | That Effect's required context. |
|
|
138
|
+
| `HookCatalog<H>` | The runtime catalog for `H`: a name declared in `H` must carry the kind its type declares; other names admit any kind. |
|
|
139
|
+
|
|
140
|
+
Values:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const engineHooks: Readonly<{ readonly config: "waterfall"; readonly configResolved: "parallel" }>
|
|
144
|
+
const handlerOf: (entry: unknown) => (...args: Array<any>) => unknown
|
|
145
|
+
const orderOf: (entry: unknown) => "pre" | "post" | undefined
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`engineHooks` is the frozen runtime catalog for the shared configuration hooks,
|
|
149
|
+
and the catalog `Resolve.resolve` checks against when a host supplies none.
|
|
150
|
+
Spread it into your own catalog to keep the config lifecycle available.
|
|
151
|
+
`handlerOf` and `orderOf` normalize either hook entry form.
|
|
152
|
+
|
|
153
|
+
## Kernel
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
interface Kernel<H = FlowsHooks> {
|
|
157
|
+
readonly plugins: Plugins.Service<H>
|
|
158
|
+
readonly config: ResolvedConfig
|
|
159
|
+
readonly layer: Layer.Layer<any, PluginError, any>
|
|
160
|
+
readonly observerErrors: ReadonlyArray<PluginError>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type ConfigContext<H> = ContextOf<H["config"]>
|
|
164
|
+
type StartupContext<H> = ConfigContext<H> | ContextOf<H["configResolved"]>
|
|
165
|
+
|
|
166
|
+
const make: <H = FlowsHooks>(
|
|
167
|
+
input: PluginInput<NoInfer<H>>,
|
|
168
|
+
config?: FlowsConfig,
|
|
169
|
+
options?: Omit<Resolve.Options<NoInfer<H>>, "config">
|
|
170
|
+
) => Effect.Effect<Kernel<H>, PluginError, StartupContext<H>>
|
|
171
|
+
|
|
172
|
+
const runConfig: <H = FlowsHooks>(
|
|
173
|
+
plugins: Plugins.Service<H>,
|
|
174
|
+
config: FlowsConfig
|
|
175
|
+
) => Effect.Effect<ResolvedConfig, PluginError, ConfigContext<H>>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`StartupContext<H>` is `never` for `FlowsHooks`, whose startup hooks are
|
|
179
|
+
context-free, so `Kernel.make([...])` runs with `Effect.runPromise` as is. For a
|
|
180
|
+
separate hook interface whose `config` or `configResolved` handlers require
|
|
181
|
+
services, the requirement is the startup Effect's context and the caller
|
|
182
|
+
provides it before running; the kernel never supplies startup services itself.
|
|
183
|
+
|
|
184
|
+
`make` performs startup in this order:
|
|
185
|
+
|
|
186
|
+
1. Snapshot the positional `config`.
|
|
187
|
+
2. Resolve the plugin list against `options`.
|
|
188
|
+
3. Run the `config` waterfall, deep-merging each returned patch.
|
|
189
|
+
4. Decode and freeze the result into a `ResolvedConfig`.
|
|
190
|
+
5. Run `configResolved` observers in parallel, collecting their failures into
|
|
191
|
+
`observerErrors` rather than failing.
|
|
192
|
+
6. Merge the selected plugins' layers, left to right in resolved order.
|
|
193
|
+
|
|
194
|
+
The positional `config` is the kernel's only pre-resolution configuration
|
|
195
|
+
source. `Options.config` is excluded from the options type so a caller cannot
|
|
196
|
+
declare a second one, and a `config` key smuggled past the type fails at
|
|
197
|
+
runtime with `invalid_plugin` at `$options.config`, the same refusal an
|
|
198
|
+
unknown option key gets. `runConfig` exposes step 3 and step 4 on their own, for a
|
|
199
|
+
host that resolved its plugin list separately. Before dispatching the first
|
|
200
|
+
hook, it validates and copies the supplied config into a detached, recursively
|
|
201
|
+
frozen snapshot. Invalid or accessor-bearing input fails with `config_invalid`
|
|
202
|
+
before any hook runs, without executing accessors. The final waterfall result
|
|
203
|
+
is admitted again through `Config.resolve`.
|
|
204
|
+
|
|
205
|
+
`observerErrors` is a return value, not a log: a `configResolved` failure never
|
|
206
|
+
fails startup, so a host that ignores the array cannot tell a working observer
|
|
207
|
+
from a broken one.
|
|
208
|
+
|
|
209
|
+
## Plugins
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
interface Service<H = FlowsHooks> {
|
|
213
|
+
readonly resolved: Resolved<H>
|
|
214
|
+
readonly handlers: (hook: string) => ReadonlyArray<HandlerRecord>
|
|
215
|
+
|
|
216
|
+
readonly sequential: <K extends KeysOfKind<H, "sequential">>(
|
|
217
|
+
hook: K,
|
|
218
|
+
...args: ArgsOf<H[K]>
|
|
219
|
+
) => Effect.Effect<ReadonlyArray<SuccessOf<H[K]>>, PluginError, ContextOf<H[K]>>
|
|
220
|
+
|
|
221
|
+
readonly parallel: <K extends KeysOfKind<H, "parallel">>(
|
|
222
|
+
hook: K,
|
|
223
|
+
...args: ArgsOf<H[K]>
|
|
224
|
+
) => Effect.Effect<ReadonlyArray<PluginError>, never, ContextOf<H[K]>>
|
|
225
|
+
|
|
226
|
+
readonly first: <K extends KeysOfKind<H, "first">>(
|
|
227
|
+
hook: K,
|
|
228
|
+
...args: ArgsOf<H[K]>
|
|
229
|
+
) => Effect.Effect<SuccessOf<H[K]>, PluginError, ContextOf<H[K]>>
|
|
230
|
+
|
|
231
|
+
readonly waterfall: <K extends KeysOfKind<H, "waterfall">>(
|
|
232
|
+
hook: K,
|
|
233
|
+
initial: ArgsOf<H[K]>[0],
|
|
234
|
+
merge: (previous: ArgsOf<H[K]>[0], patch: Exclude<SuccessOf<H[K]>, void>) => ArgsOf<H[K]>[0]
|
|
235
|
+
) => Effect.Effect<ArgsOf<H[K]>[0], PluginError, ContextOf<H[K]>>
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const make: <H = FlowsHooks>(resolved: Resolved<H>) => Service<H>
|
|
239
|
+
const makeNoop: <H = FlowsHooks>() => Service<H>
|
|
240
|
+
|
|
241
|
+
class Plugins extends Context.Service<Plugins, Service>()("flows/plugin/Plugins") {}
|
|
242
|
+
const layer: (resolved: Resolved) => Layer.Layer<Plugins>
|
|
243
|
+
const layerNoop: Layer.Layer<Plugins>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Dispatch semantics, one row per kind:
|
|
247
|
+
|
|
248
|
+
| Method | Runs | Returns | On handler failure |
|
|
249
|
+
| ------------ | --------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
250
|
+
| `sequential` | Every handler, in resolved order, one at a time. | Each handler's success value, in resolved order. | Fails the caller with `hook_failed` and stops. |
|
|
251
|
+
| `parallel` | Every handler, bounded by `Resolved.parallelConcurrency`. | The failures, in resolved handler order. Success values are discarded. | Never fails the caller. |
|
|
252
|
+
| `first` | Handlers in order until one returns `Option.some`. | That `Option`, or `Option.none()`. | Fails with `hook_failed`; a non-`Option` value fails with `invalid_hook_result`. |
|
|
253
|
+
| `waterfall` | Every handler, threading the merged value. | The final value. | Fails with `hook_failed` and stops. |
|
|
254
|
+
|
|
255
|
+
Every method first compares its kind with the runtime catalog the resolution
|
|
256
|
+
admitted (`Resolved.kinds`). A hook the catalog declares with a different kind
|
|
257
|
+
is refused before any handler runs: `sequential`, `first`, and `waterfall` fail
|
|
258
|
+
with `hook_kind_mismatch`, and `parallel` returns that one error. A hook name
|
|
259
|
+
absent from the catalog dispatches nothing, unless a hand-built `Resolved`
|
|
260
|
+
registered handlers under it; those are refused the same way, because no kind
|
|
261
|
+
was declared for them. The typed signatures make the mismatch unreachable
|
|
262
|
+
without a cast; the runtime check covers the cast.
|
|
263
|
+
|
|
264
|
+
Handlers must return an Effect. Synchronous throws, Effect failures and defects,
|
|
265
|
+
and non-Effect results (including `undefined`, scalars, and Promises) become
|
|
266
|
+
`hook_failed` with the plugin and hook names. Non-Effect error messages include
|
|
267
|
+
the result's JavaScript `typeof`. Parallel dispatch collects these errors and
|
|
268
|
+
continues running sibling observers, including with `parallelConcurrency: 1`.
|
|
269
|
+
`Kernel.make` succeeds and reports them in `observerErrors`. Sequential dispatch
|
|
270
|
+
fails with the attributed error and skips later handlers.
|
|
271
|
+
|
|
272
|
+
A waterfall handler whose Effect succeeds with `undefined` leaves the value
|
|
273
|
+
unchanged: the merge function is not called. A merge that throws a `PluginError` keeps that
|
|
274
|
+
error's code and path and gains the handler's attribution; a merge that throws
|
|
275
|
+
anything else becomes `config_invalid` attributed to the handler.
|
|
276
|
+
|
|
277
|
+
Cancellation is fiber interruption through scope closure. Nothing threads an
|
|
278
|
+
`AbortSignal`. Interrupting a dispatch interrupts the handler in flight, runs
|
|
279
|
+
its finalizers, and skips the handlers that had not started.
|
|
280
|
+
|
|
281
|
+
The `Plugins` service tag, `layer`, and `layerNoop` hold one dispatcher over the
|
|
282
|
+
process-wide augmented `FlowsHooks`. A host typed against a separate hook
|
|
283
|
+
interface holds its `Service<H>` directly and uses none of the three.
|
|
284
|
+
|
|
285
|
+
## Resolve
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
interface HandlerRecord {
|
|
289
|
+
readonly plugin: string
|
|
290
|
+
readonly hook: string
|
|
291
|
+
readonly handler: (...args: Array<any>) => unknown
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface Resolved<H = FlowsHooks> {
|
|
295
|
+
readonly plugins: ReadonlyArray<FlowsPlugin<H>>
|
|
296
|
+
readonly handlers: ReadonlyMap<string, ReadonlyArray<HandlerRecord>>
|
|
297
|
+
readonly kinds: Readonly<Record<string, HookKind>>
|
|
298
|
+
readonly parallelConcurrency: number
|
|
299
|
+
readonly cacheEnvironment?: Action.CacheEnvironment | undefined
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface Options<H = FlowsHooks> {
|
|
303
|
+
readonly config?: FlowsConfig | undefined
|
|
304
|
+
readonly target?: "engine" | "harness" | undefined
|
|
305
|
+
readonly hooks?: HookCatalog<H> | undefined
|
|
306
|
+
readonly cacheEnvironment?: Action.CacheEnvironment | undefined
|
|
307
|
+
readonly parallelConcurrency?: number | undefined
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const resolve: <H = FlowsHooks>(
|
|
311
|
+
input: PluginInput<NoInfer<H>>,
|
|
312
|
+
options?: Options<NoInfer<H>>,
|
|
313
|
+
configOverride?: FlowsConfig
|
|
314
|
+
) => Effect.Effect<Resolved<H>, PluginError>
|
|
315
|
+
|
|
316
|
+
const layer: <H>(resolved: Resolved<H>) => Layer.Layer<any, PluginError, any>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
| Option | Default | Meaning |
|
|
320
|
+
| --------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
321
|
+
| `config` | `{}` | Pre-resolution configuration tested by `apply` predicates. `Kernel.make` supplies it positionally and omits this field. Supplying both `config` and `configOverride` fails with `invalid_plugin` at `$options.config`. |
|
|
322
|
+
| `target` | `"engine"` | The host whose literal `apply` selectors are active. |
|
|
323
|
+
| `hooks` | `engineHooks` | The hook names and kinds this host recognizes, typed as `HookCatalog<H>`. The `unknown_hook` guard checks against it, the frozen copy is `Resolved.kinds`, and `config` or `configResolved` labelled with any kind but `waterfall` or `parallel` fails with `hook_kind_mismatch` at `$options.hooks.<name>`. |
|
|
324
|
+
| `cacheEnvironment` | absent | Complete composition identity for sealed activity keys. Requires a `version` on every selected plugin. |
|
|
325
|
+
| `parallelConcurrency` | `16` | Maximum observers run at once. A positive safe integer through 256. |
|
|
326
|
+
|
|
327
|
+
`resolve` is once-only and everything it returns is a copy the kernel owns.
|
|
328
|
+
Plugin records and hook objects are snapshotted, handler records and ordered
|
|
329
|
+
arrays are frozen, and `handlers` is a read-only map facade with no `set`,
|
|
330
|
+
`delete`, or `clear`. Reflection is descriptor-only: accessors never execute,
|
|
331
|
+
exotic prototypes are refused, and a hostile proxy observes a bounded number of
|
|
332
|
+
traps whose results are copied rather than retained.
|
|
333
|
+
|
|
334
|
+
Order of the checks: options, flatten, validate every record, filter by `apply`,
|
|
335
|
+
check hook names against the catalog for selected plugins, reject duplicate
|
|
336
|
+
names, order, then decode the cache environment. Structure is validated before
|
|
337
|
+
filtering, so exclusion cannot hide a malformed plugin; catalog names are
|
|
338
|
+
checked after, so a shared preset carrying harness-only hooks still resolves
|
|
339
|
+
under an engine kernel.
|
|
340
|
+
|
|
341
|
+
Ordering follows Vite exactly. `enforce` sorts the plugin list once into pre,
|
|
342
|
+
normal, and post, stably within each group. The per-hook `order` then
|
|
343
|
+
re-partitions that list for one hook, so `{ order: "pre", handler }` runs ahead
|
|
344
|
+
of every normal-order handler even when its plugin is `enforce: "post"`. Ties
|
|
345
|
+
keep resolved plugin order.
|
|
346
|
+
|
|
347
|
+
`layer` merges every selected plugin's layer left to right, so an earlier
|
|
348
|
+
plugin's services are visible to a later plugin's layer, and the later plugin
|
|
349
|
+
wins when two provide the same tag. A layer that fails during acquisition
|
|
350
|
+
becomes `layer_failed` with the plugin's name. `layer` also seals
|
|
351
|
+
`Action.CurrentCacheEnvironment`: plugin layers read the declared environment,
|
|
352
|
+
or `undefined` when none was declared, and a plugin layer that provides its own
|
|
353
|
+
environment is overridden.
|
|
354
|
+
|
|
355
|
+
### Limits
|
|
356
|
+
|
|
357
|
+
| Constant | Value | Refusal |
|
|
358
|
+
| ---------------------------- | ----- | ----------------------------- |
|
|
359
|
+
| `maximumPlugins` | 256 | `resource_limit` |
|
|
360
|
+
| `maximumHandlers` | 1,024 | `resource_limit` |
|
|
361
|
+
| `maximumPluginInputNodes` | 4,096 | `resource_limit` |
|
|
362
|
+
| `maximumPluginDepth` | 64 | `resource_limit` |
|
|
363
|
+
| `maximumParallelConcurrency` | 256 | `resource_limit` |
|
|
364
|
+
| `maximumPluginNameLength` | 256 | `invalid_plugin` |
|
|
365
|
+
| `defaultParallelConcurrency` | 16 | none, it is the default value |
|
|
366
|
+
|
|
367
|
+
A `resource_limit` carries the path of the offending entry or preset array, and
|
|
368
|
+
the handler bound counts the handlers the kernel dispatches. Plugin, version,
|
|
369
|
+
and hook names are a separate check, not a budget: they are control-free,
|
|
370
|
+
well-formed strings within `maximumPluginNameLength` that are neither empty nor
|
|
371
|
+
entirely whitespace, and a name that is not fails with `invalid_plugin` at its
|
|
372
|
+
own path even when every budget is free.
|
|
373
|
+
|
|
374
|
+
## Config
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
const ConfigValue: Schema.Json
|
|
378
|
+
type ConfigValue = typeof ConfigValue.Type
|
|
379
|
+
|
|
380
|
+
const FlowsConfig: Schema.Codec<Readonly<Record<string, ConfigValue>>, unknown>
|
|
381
|
+
type FlowsConfig = typeof FlowsConfig.Type
|
|
382
|
+
|
|
383
|
+
const ResolvedConfig: typeof FlowsConfig
|
|
384
|
+
type ResolvedConfig = typeof ResolvedConfig.Type
|
|
385
|
+
|
|
386
|
+
const defaults: ResolvedConfig
|
|
387
|
+
|
|
388
|
+
const merge: (base: FlowsConfig, patch: unknown) => FlowsConfig
|
|
389
|
+
const deepFreeze: <A extends ConfigValue>(value: A) => A
|
|
390
|
+
const snapshot: (config: unknown) => Effect.Effect<FlowsConfig, PluginError>
|
|
391
|
+
const resolve: (config: unknown) => Effect.Effect<ResolvedConfig, PluginError>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
| Export | Behavior |
|
|
395
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
396
|
+
| `defaults` | The frozen empty configuration. Engine policy is deliberately not defaulted here. |
|
|
397
|
+
| `merge` | Copies and deep-merges a patch over a base. Records merge key by key; every other JSON value replaces wholesale. Raw operands are admitted; unchanged snapshot subtrees are reused. Cached totals enforce result bounds. Throws `PluginError` with code `config_invalid`. |
|
|
398
|
+
| `deepFreeze` | Copies and recursively freezes one JSON value without retaining caller-owned objects. Throws `PluginError`. |
|
|
399
|
+
| `snapshot` | Admits a raw pre-resolution configuration as an immutable snapshot. |
|
|
400
|
+
| `resolve` | Decodes the post-waterfall configuration into its final immutable form. |
|
|
401
|
+
|
|
402
|
+
`FlowsConfig` and `ResolvedConfig` decode through the same admission contract as
|
|
403
|
+
`snapshot` and `resolve`. Decoding raw input detaches and recursively freezes it;
|
|
404
|
+
invalid input produces a schema error. `Schema.is` recognizes admitted snapshots,
|
|
405
|
+
not mutable JSON shapes. Encoding accepts admitted snapshots and returns their
|
|
406
|
+
JSON value. The TypeScript types remain read-only namespace maps.
|
|
407
|
+
|
|
408
|
+
Known snapshots can be reused by `snapshot`, `resolve`, and `merge`. Freezing an
|
|
409
|
+
object yourself does not establish admission. A merge copies each changed record
|
|
410
|
+
and retains unchanged frozen subtrees. It fully admits and detaches patch data,
|
|
411
|
+
including references to previous snapshots, so patches cannot introduce shared
|
|
412
|
+
references. Cached byte, member, node, and depth totals enforce result bounds.
|
|
413
|
+
Admission work scales with the initial tree plus patch data; copying changed
|
|
414
|
+
records also scales with their immediate key counts.
|
|
415
|
+
|
|
416
|
+
Configuration is a plugin-owned JSON namespace map. The root keys `engine`,
|
|
417
|
+
`retry`, `store`, and `plugins` are refused, because the kernel does not apply
|
|
418
|
+
those policies.
|
|
419
|
+
|
|
420
|
+
Refused with `config_invalid`, at the offending path, without executing user
|
|
421
|
+
accessors: cycles, repeated object references, sparse arrays, accessors, symbol
|
|
422
|
+
keys, exotic prototypes, non-finite numbers, unpaired surrogates, `undefined`
|
|
423
|
+
members, and the prototype-control keys `__proto__`, `constructor`, and
|
|
424
|
+
`prototype`. `undefined` is refused rather than dropped, so
|
|
425
|
+
`{ endpoint: undefined }` fails at `$.endpoint` instead of resolving to `{}`.
|
|
426
|
+
Omit the key, or write `null`.
|
|
427
|
+
|
|
428
|
+
### Limits
|
|
429
|
+
|
|
430
|
+
| Constant | Value |
|
|
431
|
+
| ---------------------- | ----------------------- |
|
|
432
|
+
| `maximumConfigBytes` | 1 MiB of encoded JSON |
|
|
433
|
+
| `maximumConfigDepth` | 64 container edges |
|
|
434
|
+
| `maximumConfigMembers` | 4,096 aggregate members |
|
|
435
|
+
| `maximumConfigNodes` | 8,192 aggregate values |
|
|
436
|
+
|
|
437
|
+
Strings are limited to 64 KiB of encoded JSON and keys to 1 KiB.
|
|
438
|
+
|
|
439
|
+
## Cache identity
|
|
440
|
+
|
|
441
|
+
`Options.cacheEnvironment` is optional. Omitting it keeps sealed activity keys
|
|
442
|
+
local to the run and leaves plugin `version` optional. Supplying it requires the
|
|
443
|
+
complete `Action.CacheEnvironment` schema and a bounded, non-empty `version` on
|
|
444
|
+
every selected plugin.
|
|
445
|
+
|
|
446
|
+
Resolution copies and freezes every layer entry, capability record, and
|
|
447
|
+
capability array, then prepends each selected `name@version` in resolved order.
|
|
448
|
+
Both halves percent-escape `%` and then `@`, so the identity is injective:
|
|
449
|
+
`a@b` at version `c` and `a` at version `b@c` produce different layer entries
|
|
450
|
+
instead of one shared sealed identity. A scoped name reads as
|
|
451
|
+
`%40scope/name@1.0.0`. A versionless composition fails with
|
|
452
|
+
`cache_environment_invalid` instead of declaring an ambiguous cross-run
|
|
453
|
+
identity. Invalid or mutable caller data never reaches
|
|
454
|
+
`Action.CurrentCacheEnvironment`, and no plugin layer can declare or replace
|
|
455
|
+
it.
|
|
456
|
+
|
|
457
|
+
## PluginError
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
const PluginErrorCode: Schema.Literals<[
|
|
461
|
+
"duplicate_name",
|
|
462
|
+
"unknown_hook",
|
|
463
|
+
"hook_kind_mismatch",
|
|
464
|
+
"invalid_plugin",
|
|
465
|
+
"apply_failed",
|
|
466
|
+
"config_invalid",
|
|
467
|
+
"cache_environment_invalid",
|
|
468
|
+
"invalid_hook_result",
|
|
469
|
+
"resource_limit",
|
|
470
|
+
"hook_failed",
|
|
471
|
+
"layer_failed"
|
|
472
|
+
]>
|
|
473
|
+
type PluginErrorCode = typeof PluginErrorCode.Type
|
|
474
|
+
|
|
475
|
+
class PluginError extends Schema.TaggedError<PluginError>()("flows/plugin/PluginError", {
|
|
476
|
+
code: PluginErrorCode,
|
|
477
|
+
message: Schema.String,
|
|
478
|
+
plugin: Schema.optional(Schema.String),
|
|
479
|
+
hook: Schema.optional(Schema.String),
|
|
480
|
+
path: Schema.optional(Schema.String),
|
|
481
|
+
cause: Schema.optional(Schema.Unknown)
|
|
482
|
+
}) {}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Every startup and dispatch refusal uses this one error. `plugin` and `hook` are
|
|
486
|
+
attribution, `path` is a diagnostic dollar-path into the refused value, and
|
|
487
|
+
`cause` carries a failing handler's, layer's, or waterfall merge's original
|
|
488
|
+
failure. Match on `code`; the messages are prose.
|
|
489
|
+
|
|
490
|
+
`cause` is not redacted. It holds whatever the plugin failed or threw with,
|
|
491
|
+
which can include credentials or config values. Keep it in process for
|
|
492
|
+
diagnostics, and redact it before a host logs, persists, or transmits a
|
|
493
|
+
`PluginError`.
|
|
494
|
+
|
|
495
|
+
A dollar-path is not an RFC 6901 JSON Pointer: `$` is the input, `.config` an
|
|
496
|
+
identifier property, `[2]` an array index, and `["not an identifier"]` any other
|
|
497
|
+
key, JSON-quoted. So `$[2].hooks.config` is the third plugin's `config` entry
|
|
498
|
+
and `$options.parallelConcurrency` is an option.
|
|
499
|
+
|
|
500
|
+
| Code | Meaning |
|
|
501
|
+
| --------------------------- | ------------------------------------------------------------------------------ |
|
|
502
|
+
| `duplicate_name` | Two selected plugins have the same exact name. |
|
|
503
|
+
| `unknown_hook` | A plugin declares a hook absent from the host catalog. |
|
|
504
|
+
| `hook_kind_mismatch` | A catalog kind disagrees with the kind the kernel or a dispatch method runs. |
|
|
505
|
+
| `invalid_plugin` | A plugin, preset, option, or hook entry has an invalid runtime shape. |
|
|
506
|
+
| `apply_failed` | An `apply` predicate threw. Its raw failure is not retained. |
|
|
507
|
+
| `config_invalid` | Config is not bounded strict JSON or uses a reserved policy key. |
|
|
508
|
+
| `cache_environment_invalid` | Cache identity is malformed or incomplete. |
|
|
509
|
+
| `invalid_hook_result` | A first hook returned a value other than `Option`. |
|
|
510
|
+
| `resource_limit` | Preset, plugin, handler, or concurrency work exceeds a published bound. |
|
|
511
|
+
| `hook_failed` | A hook Effect failed, defected, threw synchronously, or returned a non-Effect. |
|
|
512
|
+
| `layer_failed` | A validated plugin layer failed during acquisition. |
|
|
513
|
+
|
|
514
|
+
`configResolved` is a lossy observer boundary. Its failures do not fail kernel
|
|
515
|
+
startup; `Kernel.observerErrors` returns them for redacted logging by the host.
|
|
516
|
+
What to change for each code is in [Troubleshooting](./troubleshooting.md).
|
|
517
|
+
|
|
518
|
+
## Related packages
|
|
519
|
+
|
|
520
|
+
The shipped hook catalog is the Smithers agent loop in
|
|
521
|
+
[`@smthrs/agent`](/api/agent), which augments `FlowsHooks` with three
|
|
522
|
+
waterfalls. `Action.CacheEnvironment` comes from
|
|
523
|
+
[`@smthrs/flow`](/api/flow). The other runtime dependency,
|
|
524
|
+
[`@smthrs/canonical`](/api/canonical), supplies immutable catalog maps.
|