@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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Troubleshooting"
|
|
3
|
+
description: "Every PluginError code the kernel reports, what causes it, and what to change, plus the failures that are silent by design."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Every refusal this package makes is a `PluginError` with a `code` from a closed
|
|
7
|
+
set. Find the code and read its section. The full error schema is in the
|
|
8
|
+
[API reference](./api.md).
|
|
9
|
+
|
|
10
|
+
Most errors carry `plugin`, `hook`, and `path` alongside the message. Read
|
|
11
|
+
`path` first: it is a diagnostic dollar-path into the value the kernel refused,
|
|
12
|
+
not an RFC 6901 JSON Pointer. `$` is the input, `.config` an identifier
|
|
13
|
+
property, `[2]` an array index, and `["not an identifier"]` any other key,
|
|
14
|
+
JSON-quoted. So `$[2].hooks.config` is the third plugin's `config` entry and
|
|
15
|
+
`$options.parallelConcurrency` is an option.
|
|
16
|
+
|
|
17
|
+
## duplicate_name
|
|
18
|
+
|
|
19
|
+
**What happened.** Two selected plugins have the same name. Names compare as
|
|
20
|
+
exact Unicode strings with no normalization, so this is a real collision, not a
|
|
21
|
+
spelling variant.
|
|
22
|
+
|
|
23
|
+
**What to change.** Rename one, or exclude one with
|
|
24
|
+
[`apply`](./guides/select-plugins.md). The kernel refuses rather than letting
|
|
25
|
+
the last one win, because last-wins turns a duplicated preset into a silently
|
|
26
|
+
dropped plugin.
|
|
27
|
+
|
|
28
|
+
## unknown_hook
|
|
29
|
+
|
|
30
|
+
**What happened.** A selected plugin declares a hook name that is not in the
|
|
31
|
+
host's runtime catalog. The error names the plugin and the hook.
|
|
32
|
+
|
|
33
|
+
**What to change.** If the hook is yours to add, list it in the catalog you pass
|
|
34
|
+
as `Resolve.Options.hooks` and augment `FlowsHooks` to match. See
|
|
35
|
+
[Declare hooks for your host](./guides/host-your-own-hooks.md). If the hook
|
|
36
|
+
belongs to a different host, give the plugin an `apply` selector so this host
|
|
37
|
+
does not select it.
|
|
38
|
+
|
|
39
|
+
Passing no catalog means the shared one: `config` and `configResolved` only.
|
|
40
|
+
|
|
41
|
+
## hook_kind_mismatch
|
|
42
|
+
|
|
43
|
+
**What happened.** The runtime catalog and the kind actually run disagree.
|
|
44
|
+
At startup, `Options.hooks` labelled `config` with a kind other than
|
|
45
|
+
`waterfall`, or `configResolved` with a kind other than `parallel`; the path
|
|
46
|
+
names the entry, such as `$options.hooks.config`. At dispatch, a method was
|
|
47
|
+
called for a hook the catalog declares with a different kind, which only a cast
|
|
48
|
+
around the typed dispatcher can reach, or for a hook a hand-built `Resolved`
|
|
49
|
+
gave handlers without declaring it in `kinds`. In every case no handler ran.
|
|
50
|
+
|
|
51
|
+
**What to change.** Make the catalog agree with the hook interface: spread
|
|
52
|
+
`engineHooks` for the shared hooks and write each of your own hooks with the
|
|
53
|
+
kind its type declares. `HookCatalog<H>` refuses a wrong kind for a declared
|
|
54
|
+
name at compile time.
|
|
55
|
+
|
|
56
|
+
## invalid_plugin
|
|
57
|
+
|
|
58
|
+
**What happened.** A plugin, preset, option, or hook entry has a shape the
|
|
59
|
+
kernel will not accept. Common causes, each reported at its own path:
|
|
60
|
+
|
|
61
|
+
- A hook entry that is `undefined` or `null`. Declarations must be callable, so
|
|
62
|
+
a conditionally built hook is refused rather than skipped.
|
|
63
|
+
- An unknown property on a plugin record or on the options object.
|
|
64
|
+
- A name or version that is empty, whitespace only, longer than 256 UTF-16 code
|
|
65
|
+
units, or not well-formed text.
|
|
66
|
+
- `enforce` or `order` that is not `"pre"` or `"post"`.
|
|
67
|
+
- `apply` that is not `"engine"`, `"harness"`, or a function.
|
|
68
|
+
- `layer` that is not an Effect `Layer`.
|
|
69
|
+
- A preset array that is sparse, cyclic, repeated, or carries extra properties.
|
|
70
|
+
- An accessor, a symbol key, or a non-enumerable property anywhere in the
|
|
71
|
+
record.
|
|
72
|
+
|
|
73
|
+
**What to change.** Read `path`, fix that value. To omit an optional hook, leave
|
|
74
|
+
the key out of the `hooks` record instead of setting it to `undefined`.
|
|
75
|
+
|
|
76
|
+
## apply_failed
|
|
77
|
+
|
|
78
|
+
**What happened.** An `apply` predicate threw. The raw failure is not retained,
|
|
79
|
+
because a predicate runs before the kernel has anywhere safe to put an arbitrary
|
|
80
|
+
value.
|
|
81
|
+
|
|
82
|
+
**What to change.** Make the predicate total. It receives the pre-resolution
|
|
83
|
+
configuration and must return a boolean without reading anything it cannot
|
|
84
|
+
guarantee is there. A predicate that returns a non-boolean fails with
|
|
85
|
+
`invalid_plugin` instead.
|
|
86
|
+
|
|
87
|
+
## config_invalid
|
|
88
|
+
|
|
89
|
+
**What happened.** A configuration value is not bounded strict JSON, or it uses
|
|
90
|
+
a reserved root key. This covers the pre-resolution config, any patch a `config`
|
|
91
|
+
handler returned, and the merged result.
|
|
92
|
+
|
|
93
|
+
**What to change.** Read `path`. The frequent causes:
|
|
94
|
+
|
|
95
|
+
- `$.engine`, `$.retry`, `$.store`, or `$.plugins`: rename the namespace. The
|
|
96
|
+
kernel does not apply those policies and refuses to look like it does.
|
|
97
|
+
- An `undefined` member: omit the key, or write `null`. `{ endpoint: undefined }`
|
|
98
|
+
fails at `$.endpoint` rather than resolving to `{}`.
|
|
99
|
+
- A `Date`, `Map`, class instance, or non-finite number: encode it as JSON, or
|
|
100
|
+
keep it in a service instead of in configuration.
|
|
101
|
+
- `__proto__`, `constructor`, or `prototype` as a key at any depth.
|
|
102
|
+
- A tree over 1 MiB, 64 deep, 4,096 members, or 8,192 values. See
|
|
103
|
+
[Configuration](./concepts/configuration.md) for the whole list.
|
|
104
|
+
|
|
105
|
+
When a `config` handler returned the offending patch, the error also carries
|
|
106
|
+
that plugin and hook.
|
|
107
|
+
|
|
108
|
+
## cache_environment_invalid
|
|
109
|
+
|
|
110
|
+
**What happened.** You declared `cacheEnvironment` and the kernel could not
|
|
111
|
+
build a complete identity from it. Either a selected plugin has no `version`, or
|
|
112
|
+
the environment does not match the `Action.CacheEnvironment` schema.
|
|
113
|
+
|
|
114
|
+
**What to change.** Add a `version` to every selected plugin, or drop
|
|
115
|
+
`cacheEnvironment` and let sealed keys stay run-local. Both are correct; a bare
|
|
116
|
+
plugin name folded into a cross-run cache key is not. See
|
|
117
|
+
[Declare a cache identity](./guides/cache-identity.md).
|
|
118
|
+
|
|
119
|
+
## invalid_hook_result
|
|
120
|
+
|
|
121
|
+
**What happened.** A `first` hook handler returned something other than an
|
|
122
|
+
`Option`. The error names the plugin and the hook.
|
|
123
|
+
|
|
124
|
+
**What to change.** Return `Option.some(value)` to answer and `Option.none()` to
|
|
125
|
+
pass. A `first` hook stops at the first `some`, so returning a bare value would
|
|
126
|
+
make "no opinion" indistinguishable from "the answer is undefined".
|
|
127
|
+
|
|
128
|
+
## resource_limit
|
|
129
|
+
|
|
130
|
+
**What happened.** The plugin input, the handler count, or a concurrency option
|
|
131
|
+
exceeded a published bound: 256 plugins, 1,024 handlers, 4,096 input nodes, 64
|
|
132
|
+
nested preset arrays, or a `parallelConcurrency` outside 1 through 256.
|
|
133
|
+
|
|
134
|
+
**What to change.** Split the composition, or raise nothing: these bounds are
|
|
135
|
+
fixed. A list this large is usually a preset included twice or an array building
|
|
136
|
+
itself in a loop. The [limits table](./concepts/resolution.md) has each bound
|
|
137
|
+
and the export that names it.
|
|
138
|
+
|
|
139
|
+
## hook_failed
|
|
140
|
+
|
|
141
|
+
**What happened.** A handler failed, defected, threw synchronously, or returned
|
|
142
|
+
something that is not an Effect. The error names the plugin and the hook, and
|
|
143
|
+
the original failure is on `cause`.
|
|
144
|
+
|
|
145
|
+
**What to change.** Fix the handler. Where it surfaces depends on the kind: a
|
|
146
|
+
`sequential`, `first`, or `waterfall` hook fails the caller and stops dispatch
|
|
147
|
+
at that handler, while a `parallel` hook returns the error and keeps going. See
|
|
148
|
+
[Hook kinds](./concepts/hook-kinds.md).
|
|
149
|
+
|
|
150
|
+
## layer_failed
|
|
151
|
+
|
|
152
|
+
**What happened.** A plugin's layer failed while it was being built. The error
|
|
153
|
+
names the plugin, with the layer's own failure on `cause`.
|
|
154
|
+
|
|
155
|
+
**What to change.** Fix the layer's acquisition, or make the plugin conditional
|
|
156
|
+
so it is not selected in compositions where its dependencies are absent. The
|
|
157
|
+
failure arrives when something provides `kernel.layer`, not when `Kernel.make`
|
|
158
|
+
returns.
|
|
159
|
+
|
|
160
|
+
## Failures that are silent by design
|
|
161
|
+
|
|
162
|
+
**A `configResolved` handler failed and nothing happened.** Parallel observers
|
|
163
|
+
are lossy on purpose: their failures are collected, not raised. Read
|
|
164
|
+
`kernel.observerErrors` and log them. A composition that ignores that array
|
|
165
|
+
cannot tell a working observer from a broken one.
|
|
166
|
+
|
|
167
|
+
**A plugin was dropped and no error was reported.** Exclusion is not a failure.
|
|
168
|
+
`apply: "harness"` under the default `target: "engine"` drops the plugin
|
|
169
|
+
silently, which is exactly what a shared preset needs. Check
|
|
170
|
+
`kernel.plugins.resolved.plugins` for what was actually selected, and pass
|
|
171
|
+
`{ target: "harness" }` if your host is a harness.
|
|
172
|
+
|
|
173
|
+
**A hook never runs even though it resolved.** The kernel dispatches only the
|
|
174
|
+
config lifecycle. Every other hook runs when the host calls the dispatcher for
|
|
175
|
+
it, so a hook in the catalog that no host code dispatches is a handler nobody
|
|
176
|
+
calls.
|
|
177
|
+
|
|
178
|
+
**Mutating a plugin or a config object after startup changed nothing.**
|
|
179
|
+
Resolution copies everything it accepts. Dispatch reads the kernel's own frozen
|
|
180
|
+
records, never yours. Change the input and resolve again.
|
package/package.json
CHANGED
|
@@ -1,6 +1,182 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smthrs/plugin",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0-rc.12",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"smthrs": {
|
|
7
|
+
"group": "agent"
|
|
8
|
+
},
|
|
9
|
+
"description": "Vite-style typed plugin kernel for flows: hooks, resolution, and config execution",
|
|
10
|
+
"homepage": "https://github.com/smithersai/smithers",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/smithersai/smithers.git",
|
|
14
|
+
"directory": "packages/smithers/agent/plugin"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/smithersai/smithers/issues"
|
|
18
|
+
},
|
|
19
|
+
"tags": [
|
|
20
|
+
"typescript",
|
|
21
|
+
"effect",
|
|
22
|
+
"plugin",
|
|
23
|
+
"hooks"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"typescript",
|
|
27
|
+
"effect",
|
|
28
|
+
"plugin",
|
|
29
|
+
"hooks"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=26.4.0"
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": [],
|
|
35
|
+
"exports": {
|
|
36
|
+
"./package.json": "./package.json",
|
|
37
|
+
".": {
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./dist/esm/index.d.ts",
|
|
40
|
+
"default": "./dist/esm/index.js"
|
|
41
|
+
},
|
|
42
|
+
"require": {
|
|
43
|
+
"types": "./dist/cjs/index.d.ts",
|
|
44
|
+
"default": "./dist/cjs/index.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"./internal/*": null,
|
|
48
|
+
"./*/index": null,
|
|
49
|
+
"./Config": {
|
|
50
|
+
"import": {
|
|
51
|
+
"types": "./dist/esm/Config.d.ts",
|
|
52
|
+
"default": "./dist/esm/Config.js"
|
|
53
|
+
},
|
|
54
|
+
"require": {
|
|
55
|
+
"types": "./dist/cjs/Config.d.ts",
|
|
56
|
+
"default": "./dist/cjs/Config.js"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"./Hooks": {
|
|
60
|
+
"import": {
|
|
61
|
+
"types": "./dist/esm/Hooks.d.ts",
|
|
62
|
+
"default": "./dist/esm/Hooks.js"
|
|
63
|
+
},
|
|
64
|
+
"require": {
|
|
65
|
+
"types": "./dist/cjs/Hooks.d.ts",
|
|
66
|
+
"default": "./dist/cjs/Hooks.js"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"./Kernel": {
|
|
70
|
+
"import": {
|
|
71
|
+
"types": "./dist/esm/Kernel.d.ts",
|
|
72
|
+
"default": "./dist/esm/Kernel.js"
|
|
73
|
+
},
|
|
74
|
+
"require": {
|
|
75
|
+
"types": "./dist/cjs/Kernel.d.ts",
|
|
76
|
+
"default": "./dist/cjs/Kernel.js"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"./Plugin": {
|
|
80
|
+
"import": {
|
|
81
|
+
"types": "./dist/esm/Plugin.d.ts",
|
|
82
|
+
"default": "./dist/esm/Plugin.js"
|
|
83
|
+
},
|
|
84
|
+
"require": {
|
|
85
|
+
"types": "./dist/cjs/Plugin.d.ts",
|
|
86
|
+
"default": "./dist/cjs/Plugin.js"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"./PluginError": {
|
|
90
|
+
"import": {
|
|
91
|
+
"types": "./dist/esm/PluginError.d.ts",
|
|
92
|
+
"default": "./dist/esm/PluginError.js"
|
|
93
|
+
},
|
|
94
|
+
"require": {
|
|
95
|
+
"types": "./dist/cjs/PluginError.d.ts",
|
|
96
|
+
"default": "./dist/cjs/PluginError.js"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"./Plugins": {
|
|
100
|
+
"import": {
|
|
101
|
+
"types": "./dist/esm/Plugins.d.ts",
|
|
102
|
+
"default": "./dist/esm/Plugins.js"
|
|
103
|
+
},
|
|
104
|
+
"require": {
|
|
105
|
+
"types": "./dist/cjs/Plugins.d.ts",
|
|
106
|
+
"default": "./dist/cjs/Plugins.js"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"./Resolve": {
|
|
110
|
+
"import": {
|
|
111
|
+
"types": "./dist/esm/Resolve.d.ts",
|
|
112
|
+
"default": "./dist/esm/Resolve.js"
|
|
113
|
+
},
|
|
114
|
+
"require": {
|
|
115
|
+
"types": "./dist/cjs/Resolve.d.ts",
|
|
116
|
+
"default": "./dist/cjs/Resolve.js"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"./index": {
|
|
120
|
+
"import": {
|
|
121
|
+
"types": "./dist/esm/index.d.ts",
|
|
122
|
+
"default": "./dist/esm/index.js"
|
|
123
|
+
},
|
|
124
|
+
"require": {
|
|
125
|
+
"types": "./dist/cjs/index.d.ts",
|
|
126
|
+
"default": "./dist/cjs/index.js"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"files": [
|
|
131
|
+
"src/**/*.ts",
|
|
132
|
+
"dist/**/*.js",
|
|
133
|
+
"dist/**/*.js.map",
|
|
134
|
+
"dist/**/*.d.ts",
|
|
135
|
+
"dist/**/*.d.ts.map",
|
|
136
|
+
"dist/**/package.json",
|
|
137
|
+
"LICENSE",
|
|
138
|
+
"README.md",
|
|
139
|
+
"CHANGELOG.md",
|
|
140
|
+
"docs/"
|
|
141
|
+
],
|
|
142
|
+
"publishConfig": {
|
|
143
|
+
"access": "public",
|
|
144
|
+
"provenance": true,
|
|
145
|
+
"tag": "next"
|
|
146
|
+
},
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"@smthrs/canonical": "1.0.0-rc.12",
|
|
149
|
+
"@smthrs/flow": "1.0.0-rc.12"
|
|
150
|
+
},
|
|
151
|
+
"devDependencies": {
|
|
152
|
+
"@effect/language-service": "0.87.2",
|
|
153
|
+
"@effect/vitest": "4.0.4",
|
|
154
|
+
"@eslint/js": "9.39.5",
|
|
155
|
+
"@types/node": "26.4.1",
|
|
156
|
+
"@vitest/coverage-v8": "5.0.0",
|
|
157
|
+
"dprint": "0.57.1",
|
|
158
|
+
"effect": "4.0.4",
|
|
159
|
+
"esbuild": "0.28.2",
|
|
160
|
+
"eslint": "9.39.5",
|
|
161
|
+
"eslint-import-resolver-typescript": "4.4.5",
|
|
162
|
+
"eslint-plugin-import": "2.32.0",
|
|
163
|
+
"eslint-plugin-jsdoc": "64.3.4",
|
|
164
|
+
"eslint-plugin-unicorn": "65.0.0",
|
|
165
|
+
"madge": "8.0.0",
|
|
166
|
+
"typescript": "5.9.3",
|
|
167
|
+
"typescript-eslint": "8.69.0",
|
|
168
|
+
"vitest": "5.0.0"
|
|
169
|
+
},
|
|
170
|
+
"peerDependencies": {
|
|
171
|
+
"effect": "^4.0.4"
|
|
172
|
+
},
|
|
173
|
+
"scripts": {
|
|
174
|
+
"lint": "eslint src examples --max-warnings=0 && dprint check",
|
|
175
|
+
"format": "dprint fmt",
|
|
176
|
+
"build": "node scripts/build.mjs",
|
|
177
|
+
"check": "tsc -b tsconfig.json && tsc -p tsconfig.test.json --noEmit",
|
|
178
|
+
"circular": "node scripts/circular.mjs",
|
|
179
|
+
"test": "vitest",
|
|
180
|
+
"coverage": "vitest --coverage"
|
|
181
|
+
}
|
|
6
182
|
}
|
package/src/Config.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded JSON configuration consumed and produced by the plugin kernel.
|
|
3
|
+
*
|
|
4
|
+
* Configuration is an extension namespace, not an engine-policy surface.
|
|
5
|
+
* Durable retry, concurrency, and storage policy stay on their owning Effect
|
|
6
|
+
* services and constructor options. Every accepted value is copied before any
|
|
7
|
+
* plugin observes it, then frozen recursively.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0-rc.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
13
|
+
import * as Schema from "effect/Schema"
|
|
14
|
+
import * as SchemaGetter from "effect/SchemaGetter"
|
|
15
|
+
import * as SchemaIssue from "effect/SchemaIssue"
|
|
16
|
+
import * as Boundary from "./internal/Boundary.ts"
|
|
17
|
+
import { PluginError } from "./PluginError.ts"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Maximum encoded bytes accepted for one plugin configuration.
|
|
21
|
+
*
|
|
22
|
+
* @category limits
|
|
23
|
+
* @since 1.0.0-rc.0
|
|
24
|
+
*/
|
|
25
|
+
export const maximumConfigBytes = Boundary.defaultLimits.maxBytes
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Maximum nesting accepted for one plugin configuration.
|
|
29
|
+
*
|
|
30
|
+
* @category limits
|
|
31
|
+
* @since 1.0.0-rc.0
|
|
32
|
+
*/
|
|
33
|
+
export const maximumConfigDepth = Boundary.defaultLimits.maxDepth
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maximum aggregate members accepted for one plugin configuration.
|
|
37
|
+
*
|
|
38
|
+
* @category limits
|
|
39
|
+
* @since 1.0.0-rc.0
|
|
40
|
+
*/
|
|
41
|
+
export const maximumConfigMembers = Boundary.defaultLimits.maxMembers
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Maximum aggregate values accepted for one plugin configuration.
|
|
45
|
+
*
|
|
46
|
+
* @category limits
|
|
47
|
+
* @since 1.0.0-rc.0
|
|
48
|
+
*/
|
|
49
|
+
export const maximumConfigNodes = Boundary.defaultLimits.maxNodes
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One value accepted in a plugin-owned configuration namespace.
|
|
53
|
+
*
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 1.0.0-rc.0
|
|
56
|
+
*/
|
|
57
|
+
export const ConfigValue = Schema.Json
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One value accepted in a plugin-owned configuration namespace.
|
|
61
|
+
*
|
|
62
|
+
* @category models
|
|
63
|
+
* @since 1.0.0-rc.0
|
|
64
|
+
*/
|
|
65
|
+
export type ConfigValue = typeof ConfigValue.Type
|
|
66
|
+
|
|
67
|
+
const admittedConfigs = new WeakSet<object>()
|
|
68
|
+
|
|
69
|
+
const configurationSchema = Schema.Unknown.pipe(
|
|
70
|
+
Schema.decodeTo(
|
|
71
|
+
Schema.declare<Readonly<Record<string, ConfigValue>>>((value): value is Readonly<Record<string, ConfigValue>> =>
|
|
72
|
+
typeof value === "object" && value !== null && admittedConfigs.has(value)
|
|
73
|
+
),
|
|
74
|
+
{
|
|
75
|
+
decode: SchemaGetter.transformEffect((value) =>
|
|
76
|
+
Effect.try({
|
|
77
|
+
try: () => snapshotRecord(value),
|
|
78
|
+
catch: (cause) => {
|
|
79
|
+
const error = cause as PluginError
|
|
80
|
+
return new SchemaIssue.InvalidValue({ message: `${error.path}: ${error.message}` })
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
),
|
|
84
|
+
encode: SchemaGetter.transform((value) => value)
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Admits pre-resolution configuration as a detached, recursively frozen snapshot.
|
|
91
|
+
*
|
|
92
|
+
* Keys name plugin-owned namespaces. `engine`, `retry`, `store`, and
|
|
93
|
+
* `plugins` are refused because accepting those names would imply policy the
|
|
94
|
+
* plugin kernel does not own or apply.
|
|
95
|
+
*
|
|
96
|
+
* @category models
|
|
97
|
+
* @since 1.0.0-rc.0
|
|
98
|
+
*/
|
|
99
|
+
export const FlowsConfig = configurationSchema
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The decoded form of {@link FlowsConfig}.
|
|
103
|
+
*
|
|
104
|
+
* @category models
|
|
105
|
+
* @since 1.0.0-rc.0
|
|
106
|
+
*/
|
|
107
|
+
export type FlowsConfig = typeof FlowsConfig.Type
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Admits the detached, recursively frozen configuration handed to plugins.
|
|
111
|
+
*
|
|
112
|
+
* @category models
|
|
113
|
+
* @since 1.0.0-rc.0
|
|
114
|
+
*/
|
|
115
|
+
export const ResolvedConfig = configurationSchema
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The decoded form of {@link ResolvedConfig}.
|
|
119
|
+
*
|
|
120
|
+
* @category models
|
|
121
|
+
* @since 1.0.0-rc.0
|
|
122
|
+
*/
|
|
123
|
+
export type ResolvedConfig = typeof ResolvedConfig.Type
|
|
124
|
+
|
|
125
|
+
const reservedRootKeys = new Set(["engine", "retry", "store", "plugins"])
|
|
126
|
+
|
|
127
|
+
const invalid = (path: string, complaint: string): PluginError =>
|
|
128
|
+
new PluginError({
|
|
129
|
+
code: "config_invalid",
|
|
130
|
+
message: `plugin configuration ${complaint}`,
|
|
131
|
+
path
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const snapshotRecord = (input: unknown, reuse = true): Readonly<Record<string, ConfigValue>> => {
|
|
135
|
+
if (reuse && typeof input === "object" && input !== null && admittedConfigs.has(input)) return input as ResolvedConfig
|
|
136
|
+
const admitted = Boundary.record(input)
|
|
137
|
+
if (!admitted.ok) throw invalid(admitted.path, admitted.complaint)
|
|
138
|
+
const record = admitted.value as Readonly<Record<string, ConfigValue>>
|
|
139
|
+
for (const key of Object.keys(record)) {
|
|
140
|
+
if (reservedRootKeys.has(key)) {
|
|
141
|
+
throw invalid(`$.${key}`, "uses a runtime-policy key that the plugin kernel does not own")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
admittedConfigs.add(record)
|
|
145
|
+
return record
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Empty defaults. Engine policy is deliberately not defaulted here.
|
|
150
|
+
*
|
|
151
|
+
* @category constructors
|
|
152
|
+
* @since 1.0.0-rc.0
|
|
153
|
+
*/
|
|
154
|
+
export const defaults: ResolvedConfig = snapshotRecord({})
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Copies and deep-merges a configuration patch over a base configuration.
|
|
158
|
+
*
|
|
159
|
+
* Records merge key-by-key and every other JSON value replaces wholesale.
|
|
160
|
+
* Raw operands are admitted; known snapshots retain unchanged subtrees.
|
|
161
|
+
* Cached subtree totals enforce merged output bounds. Unsafe property names,
|
|
162
|
+
* accessors, cycles, exotic prototypes, and aggregate values outside the
|
|
163
|
+
* resource limits fail with `config_invalid`.
|
|
164
|
+
*
|
|
165
|
+
* @category combinators
|
|
166
|
+
* @since 1.0.0-rc.0
|
|
167
|
+
*/
|
|
168
|
+
export const merge = (base: FlowsConfig, patch: unknown): FlowsConfig => {
|
|
169
|
+
const safeBase = snapshotRecord(base)
|
|
170
|
+
if (patch === undefined) return safeBase
|
|
171
|
+
// Even a known patch is copied so its tree cannot alias the retained base.
|
|
172
|
+
const safePatch = snapshotRecord(patch, false)
|
|
173
|
+
const merged = Boundary.mergeRecords(safeBase, safePatch)
|
|
174
|
+
if (!merged.ok) throw invalid(merged.path, merged.complaint)
|
|
175
|
+
const record = merged.value as ResolvedConfig
|
|
176
|
+
admittedConfigs.add(record)
|
|
177
|
+
return record
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Copies and freezes one JSON value without retaining caller-owned objects.
|
|
182
|
+
*
|
|
183
|
+
* @category utils
|
|
184
|
+
* @since 1.0.0-rc.0
|
|
185
|
+
*/
|
|
186
|
+
export const deepFreeze = <A extends ConfigValue>(value: A): A => {
|
|
187
|
+
const admitted = Boundary.admit(value)
|
|
188
|
+
if (!admitted.ok) throw invalid(admitted.path, admitted.complaint)
|
|
189
|
+
return admitted.value as A
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Admits a raw pre-resolution configuration as an immutable snapshot.
|
|
194
|
+
*
|
|
195
|
+
* @category constructors
|
|
196
|
+
* @since 1.0.0-rc.0
|
|
197
|
+
*/
|
|
198
|
+
export const snapshot = (config: unknown): Effect.Effect<FlowsConfig, PluginError> =>
|
|
199
|
+
Effect.try({ try: () => snapshotRecord(config), catch: (cause) => cause as PluginError })
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Decodes the post-waterfall configuration into its final immutable form.
|
|
203
|
+
*
|
|
204
|
+
* @category constructors
|
|
205
|
+
* @since 1.0.0-rc.0
|
|
206
|
+
*/
|
|
207
|
+
export const resolve = (config: unknown): Effect.Effect<ResolvedConfig, PluginError> => snapshot(config)
|