@smthrs/plugin 0.0.0-stage → 1.0.0-rc.3
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 +86 -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 +160 -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 +343 -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 +19 -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 +178 -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 +367 -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 +19 -0
- package/dist/esm/internal/snapshotCacheEnvironment.d.ts.map +1 -0
- package/dist/esm/internal/snapshotCacheEnvironment.js +63 -0
- package/dist/esm/internal/snapshotCacheEnvironment.js.map +1 -0
- package/docs/README.md +131 -0
- package/docs/api.md +522 -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 +71 -0
- package/docs/quickstart.md +151 -0
- package/docs/troubleshooting.md +179 -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 +254 -0
- package/src/Resolve.ts +535 -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 +77 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inert JSON admission for plugin configuration and cache identity.
|
|
3
|
+
*
|
|
4
|
+
* Reflection is descriptor-only: accessors never execute, hostile proxies are
|
|
5
|
+
* refused, and accepted values are detached before they are frozen.
|
|
6
|
+
*
|
|
7
|
+
* @private
|
|
8
|
+
* @since 1.0.0-rc.0
|
|
9
|
+
*/
|
|
10
|
+
import * as BoundedJson from "@smthrs/canonical/BoundedJson";
|
|
11
|
+
/**
|
|
12
|
+
* Strict JSON value accepted by the boundary.
|
|
13
|
+
*
|
|
14
|
+
* @private
|
|
15
|
+
* @since 1.0.0-rc.0
|
|
16
|
+
*/
|
|
17
|
+
export type Json = BoundedJson.Json;
|
|
18
|
+
/**
|
|
19
|
+
* Resource bounds for one admitted tree.
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
* @since 1.0.0-rc.0
|
|
23
|
+
*/
|
|
24
|
+
export type Limits = BoundedJson.StrictLimits;
|
|
25
|
+
/**
|
|
26
|
+
* Successful admission.
|
|
27
|
+
*
|
|
28
|
+
* @private
|
|
29
|
+
* @since 1.0.0-rc.0
|
|
30
|
+
*/
|
|
31
|
+
export interface AdmissionSuccess {
|
|
32
|
+
readonly ok: true;
|
|
33
|
+
readonly value: Json;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Refusal with a stable value path.
|
|
37
|
+
*
|
|
38
|
+
* @private
|
|
39
|
+
* @since 1.0.0-rc.0
|
|
40
|
+
*/
|
|
41
|
+
export interface AdmissionFailure {
|
|
42
|
+
readonly ok: false;
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly complaint: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Result of inert JSON admission.
|
|
48
|
+
*
|
|
49
|
+
* @private
|
|
50
|
+
* @since 1.0.0-rc.0
|
|
51
|
+
*/
|
|
52
|
+
export type Admission = AdmissionSuccess | AdmissionFailure;
|
|
53
|
+
/**
|
|
54
|
+
* Default configuration bounds.
|
|
55
|
+
*
|
|
56
|
+
* @private
|
|
57
|
+
* @since 1.0.0-rc.0
|
|
58
|
+
*/
|
|
59
|
+
export declare const defaultLimits: Limits;
|
|
60
|
+
/**
|
|
61
|
+
* Tests whether a string contains paired UTF-16 surrogate units.
|
|
62
|
+
*
|
|
63
|
+
* @private
|
|
64
|
+
* @since 1.0.0-rc.0
|
|
65
|
+
*/
|
|
66
|
+
export declare const isWellFormedText: (value: string) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Copies one strict JSON tree under explicit resource bounds.
|
|
69
|
+
*
|
|
70
|
+
* @private
|
|
71
|
+
* @since 1.0.0-rc.0
|
|
72
|
+
*/
|
|
73
|
+
export declare const admit: (input: unknown, limits?: Limits) => Admission;
|
|
74
|
+
/**
|
|
75
|
+
* Admits only a JSON record at the root.
|
|
76
|
+
*
|
|
77
|
+
* @private
|
|
78
|
+
* @since 1.0.0-rc.0
|
|
79
|
+
*/
|
|
80
|
+
export declare const record: (input: unknown, limits?: Limits) => AdmissionSuccess | AdmissionFailure;
|
|
81
|
+
/**
|
|
82
|
+
* Merges two independently admitted records, retaining unchanged subtrees.
|
|
83
|
+
* Both operands must come from successful admission, and the patch must be a
|
|
84
|
+
* fresh copy: their trees must be disjoint. Each output branch then comes from
|
|
85
|
+
* at most one location in either tree, preserving the no-repeated-reference
|
|
86
|
+
* invariant without walking retained subtrees. Limits apply to the result.
|
|
87
|
+
*
|
|
88
|
+
* @private
|
|
89
|
+
* @since 1.0.0-rc.0
|
|
90
|
+
*/
|
|
91
|
+
export declare const mergeRecords: (base: {
|
|
92
|
+
readonly [key: string]: Json;
|
|
93
|
+
}, patch: {
|
|
94
|
+
readonly [key: string]: Json;
|
|
95
|
+
}, limits?: Limits) => Admission;
|
|
96
|
+
//# sourceMappingURL=Boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Boundary.d.ts","sourceRoot":"","sources":["../../../src/internal/Boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAA;AAEnC;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,YAAY,CAAA;AAE7C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;IACjB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,MAO1B,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,OAA+B,CAAA;AA4EhF;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,OAAO,EAAE,SAAQ,MAAsB,KAAG,SAatE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GACjB,OAAO,OAAO,EACd,SAAQ,MAAsB,KAC7B,gBAAgB,GAAG,gBAKrB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACtC,OAAO;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACvC,SAAQ,MAAsB,KAC7B,SA4CF,CAAA"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inert JSON admission for plugin configuration and cache identity.
|
|
3
|
+
*
|
|
4
|
+
* Reflection is descriptor-only: accessors never execute, hostile proxies are
|
|
5
|
+
* refused, and accepted values are detached before they are frozen.
|
|
6
|
+
*
|
|
7
|
+
* @private
|
|
8
|
+
* @since 1.0.0-rc.0
|
|
9
|
+
*/
|
|
10
|
+
import * as BoundedJson from "@smthrs/canonical/BoundedJson";
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration bounds.
|
|
13
|
+
*
|
|
14
|
+
* @private
|
|
15
|
+
* @since 1.0.0-rc.0
|
|
16
|
+
*/
|
|
17
|
+
export const defaultLimits = Object.freeze({
|
|
18
|
+
maxBytes: 1024 * 1024,
|
|
19
|
+
maxDepth: 64,
|
|
20
|
+
maxMembers: 4_096,
|
|
21
|
+
maxNodes: 8_192,
|
|
22
|
+
maxStringBytes: 64 * 1024,
|
|
23
|
+
maxKeyBytes: 1_024
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Tests whether a string contains paired UTF-16 surrogate units.
|
|
27
|
+
*
|
|
28
|
+
* @private
|
|
29
|
+
* @since 1.0.0-rc.0
|
|
30
|
+
*/
|
|
31
|
+
export const isWellFormedText = (value) => value.isWellFormed();
|
|
32
|
+
const childPath = (path, key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
|
|
33
|
+
const encodedStringBytes = (value) => BoundedJson.encodedStringBytes(value);
|
|
34
|
+
// Paths contain only well-formed text and JSON-escaped keys. Count their JSON
|
|
35
|
+
// bytes without encoding the whole path, reserving quotes and a truncation marker.
|
|
36
|
+
const boundedPath = (path) => {
|
|
37
|
+
let bytes = 2 + 3;
|
|
38
|
+
let prefix = "";
|
|
39
|
+
for (const character of path) {
|
|
40
|
+
const point = character.codePointAt(0);
|
|
41
|
+
bytes += character === "\"" || character === "\\"
|
|
42
|
+
? 2
|
|
43
|
+
: point <= 0x7f
|
|
44
|
+
? 1
|
|
45
|
+
: point <= 0x7ff
|
|
46
|
+
? 2
|
|
47
|
+
: point <= 0xffff
|
|
48
|
+
? 3
|
|
49
|
+
: 4;
|
|
50
|
+
if (bytes > 192)
|
|
51
|
+
return `${prefix}...`;
|
|
52
|
+
prefix += character;
|
|
53
|
+
}
|
|
54
|
+
return path;
|
|
55
|
+
};
|
|
56
|
+
// Only detached containers constructed here enter this map. Mutable caller
|
|
57
|
+
// objects and Object.freeze alone never establish admission.
|
|
58
|
+
const snapshots = new WeakMap();
|
|
59
|
+
const scalarTotals = (bytes) => ({ bytes, members: 0, nodes: 1, depth: 0 });
|
|
60
|
+
const total = (entries) => {
|
|
61
|
+
const totals = { bytes: 2 + Math.max(0, entries.size - 1), members: entries.size, nodes: 1, depth: 1 };
|
|
62
|
+
for (const entry of entries.values()) {
|
|
63
|
+
totals.bytes += entry.keyBytes + entry.totals.bytes;
|
|
64
|
+
totals.members += entry.totals.members;
|
|
65
|
+
totals.nodes += entry.totals.nodes;
|
|
66
|
+
totals.depth = Math.max(totals.depth, 1 + entry.totals.depth);
|
|
67
|
+
}
|
|
68
|
+
return totals;
|
|
69
|
+
};
|
|
70
|
+
const remember = (value) => {
|
|
71
|
+
const entries = new Map();
|
|
72
|
+
for (const [key, child] of Object.entries(value)) {
|
|
73
|
+
entries.set(key, {
|
|
74
|
+
keyBytes: Array.isArray(value) ? 0 : encodedStringBytes(key) + 1,
|
|
75
|
+
totals: typeof child === "object" && child !== null ?
|
|
76
|
+
snapshots.get(child).totals
|
|
77
|
+
: scalarTotals(typeof child === "string" ? encodedStringBytes(child) : String(child).length)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
snapshots.set(value, { totals: total(entries), entries });
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Copies one strict JSON tree under explicit resource bounds.
|
|
84
|
+
*
|
|
85
|
+
* @private
|
|
86
|
+
* @since 1.0.0-rc.0
|
|
87
|
+
*/
|
|
88
|
+
export const admit = (input, limits = defaultLimits) => {
|
|
89
|
+
const result = BoundedJson.admitStrict(input, limits, {
|
|
90
|
+
ordinaryRecords: true,
|
|
91
|
+
boundedText: true,
|
|
92
|
+
onContainer: remember
|
|
93
|
+
});
|
|
94
|
+
return result.ok ? result : {
|
|
95
|
+
...result,
|
|
96
|
+
path: boundedPath(result.path),
|
|
97
|
+
complaint: result.complaint === "must be dense and have no extra properties"
|
|
98
|
+
? "must be a dense array with no extra properties" :
|
|
99
|
+
result.complaint
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Admits only a JSON record at the root.
|
|
104
|
+
*
|
|
105
|
+
* @private
|
|
106
|
+
* @since 1.0.0-rc.0
|
|
107
|
+
*/
|
|
108
|
+
export const record = (input, limits = defaultLimits) => {
|
|
109
|
+
const admitted = admit(input, limits);
|
|
110
|
+
return admitted.ok && (admitted.value === null || Array.isArray(admitted.value) || typeof admitted.value !== "object")
|
|
111
|
+
? { ok: false, path: "$", complaint: "must be a JSON record" }
|
|
112
|
+
: admitted;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Merges two independently admitted records, retaining unchanged subtrees.
|
|
116
|
+
* Both operands must come from successful admission, and the patch must be a
|
|
117
|
+
* fresh copy: their trees must be disjoint. Each output branch then comes from
|
|
118
|
+
* at most one location in either tree, preserving the no-repeated-reference
|
|
119
|
+
* invariant without walking retained subtrees. Limits apply to the result.
|
|
120
|
+
*
|
|
121
|
+
* @private
|
|
122
|
+
* @since 1.0.0-rc.0
|
|
123
|
+
*/
|
|
124
|
+
export const mergeRecords = (base, patch, limits = defaultLimits) => {
|
|
125
|
+
const merge = (left, right, path) => {
|
|
126
|
+
const entries = new Map(snapshots.get(left).entries);
|
|
127
|
+
const result = { ...left };
|
|
128
|
+
for (const [key, entry] of snapshots.get(right).entries) {
|
|
129
|
+
const previous = left[key];
|
|
130
|
+
const next = right[key];
|
|
131
|
+
if (typeof previous === "object" && previous !== null && !Array.isArray(previous) &&
|
|
132
|
+
typeof next === "object" && next !== null && !Array.isArray(next)) {
|
|
133
|
+
const merged = merge(previous, next, childPath(path, key));
|
|
134
|
+
if (!merged.ok)
|
|
135
|
+
return merged;
|
|
136
|
+
result[key] = merged.value;
|
|
137
|
+
entries.set(key, { keyBytes: entry.keyBytes, totals: snapshots.get(merged.value).totals });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
result[key] = next;
|
|
141
|
+
entries.set(key, entry);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const totals = total(entries);
|
|
145
|
+
const complaint = totals.members > limits.maxMembers ?
|
|
146
|
+
`exceeds the ${limits.maxMembers}-member limit`
|
|
147
|
+
: totals.nodes > limits.maxNodes ?
|
|
148
|
+
`exceeds the ${limits.maxNodes}-node limit`
|
|
149
|
+
: totals.bytes > limits.maxBytes ?
|
|
150
|
+
`exceeds the ${limits.maxBytes}-byte limit`
|
|
151
|
+
: totals.depth > limits.maxDepth ?
|
|
152
|
+
`exceeds the depth limit of ${limits.maxDepth}`
|
|
153
|
+
: undefined;
|
|
154
|
+
if (complaint !== undefined)
|
|
155
|
+
return { ok: false, path: boundedPath(path), complaint };
|
|
156
|
+
Object.freeze(result);
|
|
157
|
+
snapshots.set(result, { totals, entries });
|
|
158
|
+
return { ok: true, value: result };
|
|
159
|
+
};
|
|
160
|
+
return merge(base, patch, "$");
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=Boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Boundary.js","sourceRoot":"","sources":["../../../src/internal/Boundary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Inert JSON admission for plugin configuration and cache identity.\n *\n * Reflection is descriptor-only: accessors never execute, hostile proxies are\n * refused, and accepted values are detached before they are frozen.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport * as BoundedJson from \"@smthrs/canonical/BoundedJson\"\n\n/**\n * Strict JSON value accepted by the boundary.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Json = BoundedJson.Json\n\n/**\n * Resource bounds for one admitted tree.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Limits = BoundedJson.StrictLimits\n\n/**\n * Successful admission.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface AdmissionSuccess {\n readonly ok: true\n readonly value: Json\n}\n\n/**\n * Refusal with a stable value path.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface AdmissionFailure {\n readonly ok: false\n readonly path: string\n readonly complaint: string\n}\n\n/**\n * Result of inert JSON admission.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Admission = AdmissionSuccess | AdmissionFailure\n\n/**\n * Default configuration bounds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const defaultLimits: Limits = Object.freeze({\n maxBytes: 1024 * 1024,\n maxDepth: 64,\n maxMembers: 4_096,\n maxNodes: 8_192,\n maxStringBytes: 64 * 1024,\n maxKeyBytes: 1_024\n})\n\n/**\n * Tests whether a string contains paired UTF-16 surrogate units.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const isWellFormedText = (value: string): boolean => value.isWellFormed()\n\nconst childPath = (path: string, key: string): string =>\n /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`\n\nconst encodedStringBytes = (value: string): number => BoundedJson.encodedStringBytes(value)!\n\n// Paths contain only well-formed text and JSON-escaped keys. Count their JSON\n// bytes without encoding the whole path, reserving quotes and a truncation marker.\nconst boundedPath = (path: string): string => {\n let bytes = 2 + 3\n let prefix = \"\"\n for (const character of path) {\n const point = character.codePointAt(0)!\n bytes += character === \"\\\"\" || character === \"\\\\\"\n ? 2\n : point <= 0x7f\n ? 1\n : point <= 0x7ff\n ? 2\n : point <= 0xffff\n ? 3\n : 4\n if (bytes > 192) return `${prefix}...`\n prefix += character\n }\n return path\n}\n\ninterface Totals {\n bytes: number\n members: number\n nodes: number\n depth: number\n}\n\ninterface Entry {\n readonly keyBytes: number\n readonly totals: Totals\n}\n\ninterface Snapshot {\n readonly totals: Totals\n readonly entries: Map<string, Entry>\n}\n\n// Only detached containers constructed here enter this map. Mutable caller\n// objects and Object.freeze alone never establish admission.\nconst snapshots = new WeakMap<object, Snapshot>()\n\nconst scalarTotals = (bytes: number): Totals => ({ bytes, members: 0, nodes: 1, depth: 0 })\n\nconst total = (entries: ReadonlyMap<string, Entry>): Totals => {\n const totals: Totals = { bytes: 2 + Math.max(0, entries.size - 1), members: entries.size, nodes: 1, depth: 1 }\n for (const entry of entries.values()) {\n totals.bytes += entry.keyBytes + entry.totals.bytes\n totals.members += entry.totals.members\n totals.nodes += entry.totals.nodes\n totals.depth = Math.max(totals.depth, 1 + entry.totals.depth)\n }\n return totals\n}\n\nconst remember = (value: ReadonlyArray<Json> | { readonly [key: string]: Json }): void => {\n const entries = new Map<string, Entry>()\n for (const [key, child] of Object.entries(value)) {\n entries.set(key, {\n keyBytes: Array.isArray(value) ? 0 : encodedStringBytes(key) + 1,\n totals: typeof child === \"object\" && child !== null ?\n snapshots.get(child)!.totals\n : scalarTotals(typeof child === \"string\" ? encodedStringBytes(child) : String(child).length)\n })\n }\n snapshots.set(value, { totals: total(entries), entries })\n}\n\n/**\n * Copies one strict JSON tree under explicit resource bounds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const admit = (input: unknown, limits: Limits = defaultLimits): Admission => {\n const result = BoundedJson.admitStrict(input, limits, {\n ordinaryRecords: true,\n boundedText: true,\n onContainer: remember\n })\n return result.ok ? result : {\n ...result,\n path: boundedPath(result.path),\n complaint: result.complaint === \"must be dense and have no extra properties\"\n ? \"must be a dense array with no extra properties\" :\n result.complaint\n }\n}\n\n/**\n * Admits only a JSON record at the root.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const record = (\n input: unknown,\n limits: Limits = defaultLimits\n): AdmissionSuccess | AdmissionFailure => {\n const admitted = admit(input, limits)\n return admitted.ok && (admitted.value === null || Array.isArray(admitted.value) || typeof admitted.value !== \"object\")\n ? { ok: false, path: \"$\", complaint: \"must be a JSON record\" }\n : admitted\n}\n\n/**\n * Merges two independently admitted records, retaining unchanged subtrees.\n * Both operands must come from successful admission, and the patch must be a\n * fresh copy: their trees must be disjoint. Each output branch then comes from\n * at most one location in either tree, preserving the no-repeated-reference\n * invariant without walking retained subtrees. Limits apply to the result.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const mergeRecords = (\n base: { readonly [key: string]: Json },\n patch: { readonly [key: string]: Json },\n limits: Limits = defaultLimits\n): Admission => {\n const merge = (\n left: { readonly [key: string]: Json },\n right: { readonly [key: string]: Json },\n path: string\n ): Admission => {\n const entries = new Map(snapshots.get(left)!.entries)\n const result = { ...left }\n for (const [key, entry] of snapshots.get(right)!.entries) {\n const previous = left[key]\n const next = right[key]!\n if (\n typeof previous === \"object\" && previous !== null && !Array.isArray(previous) &&\n typeof next === \"object\" && next !== null && !Array.isArray(next)\n ) {\n const merged = merge(\n previous as { readonly [key: string]: Json },\n next as { readonly [key: string]: Json },\n childPath(path, key)\n )\n if (!merged.ok) return merged\n result[key] = merged.value\n entries.set(key, { keyBytes: entry.keyBytes, totals: snapshots.get(merged.value as object)!.totals })\n } else {\n result[key] = next\n entries.set(key, entry)\n }\n }\n const totals = total(entries)\n const complaint = totals.members > limits.maxMembers ?\n `exceeds the ${limits.maxMembers}-member limit`\n : totals.nodes > limits.maxNodes ?\n `exceeds the ${limits.maxNodes}-node limit`\n : totals.bytes > limits.maxBytes ?\n `exceeds the ${limits.maxBytes}-byte limit`\n : totals.depth > limits.maxDepth ?\n `exceeds the depth limit of ${limits.maxDepth}`\n : undefined\n if (complaint !== undefined) return { ok: false, path: boundedPath(path), complaint }\n Object.freeze(result)\n snapshots.set(result, { totals, entries })\n return { ok: true, value: result }\n }\n return merge(base, patch, \"$\")\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect layer composition for a resolved plugin list.
|
|
3
|
+
*
|
|
4
|
+
* @private
|
|
5
|
+
* @since 1.0.0-rc.0
|
|
6
|
+
*/
|
|
7
|
+
import { Action } from "@smthrs/flow";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import type { FlowsPlugin } from "../Plugin.ts";
|
|
10
|
+
import { PluginError } from "../PluginError.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Merges plugin layers left-to-right, wrapping each build failure as
|
|
13
|
+
* `layer_failed`, then seals the cache environment: it is supplied beneath
|
|
14
|
+
* the plugin layers and overrides anything they output, and an absent
|
|
15
|
+
* declaration stays absent.
|
|
16
|
+
*
|
|
17
|
+
* @private
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
*/
|
|
20
|
+
export declare const mergePluginLayers: <H>(plugins: ReadonlyArray<FlowsPlugin<H>>, cacheEnvironment: Action.CacheEnvironment | undefined) => Layer.Layer<any, PluginError, any>;
|
|
21
|
+
//# sourceMappingURL=mergePluginLayers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergePluginLayers.d.ts","sourceRoot":"","sources":["../../../src/internal/mergePluginLayers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,SAAS,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EACtC,kBAAkB,MAAM,CAAC,gBAAgB,GAAG,SAAS,KACpD,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,CA+BnC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect layer composition for a resolved plugin list.
|
|
3
|
+
*
|
|
4
|
+
* @private
|
|
5
|
+
* @since 1.0.0-rc.0
|
|
6
|
+
*/
|
|
7
|
+
import { Action } from "@smthrs/flow";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import { PluginError } from "../PluginError.js";
|
|
11
|
+
/**
|
|
12
|
+
* Merges plugin layers left-to-right, wrapping each build failure as
|
|
13
|
+
* `layer_failed`, then seals the cache environment: it is supplied beneath
|
|
14
|
+
* the plugin layers and overrides anything they output, and an absent
|
|
15
|
+
* declaration stays absent.
|
|
16
|
+
*
|
|
17
|
+
* @private
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
*/
|
|
20
|
+
export const mergePluginLayers = (plugins, cacheEnvironment) => {
|
|
21
|
+
const layers = plugins.flatMap((plugin) => plugin.layer
|
|
22
|
+
? [
|
|
23
|
+
Layer.catchCause(plugin.layer, (cause) => Layer.effectDiscard(Effect.fail(new PluginError({
|
|
24
|
+
code: "layer_failed",
|
|
25
|
+
message: `plugin "${plugin.name}" failed to build its layer`,
|
|
26
|
+
plugin: plugin.name,
|
|
27
|
+
cause
|
|
28
|
+
}))))
|
|
29
|
+
]
|
|
30
|
+
: []);
|
|
31
|
+
const merged = (layers.length === 0
|
|
32
|
+
? Layer.empty
|
|
33
|
+
: layers.reduce((accumulated, next) => Layer.provideMerge(next, accumulated)));
|
|
34
|
+
// The sealed environment is provided beneath the plugin layers so they can
|
|
35
|
+
// read it, then merged last so it overrides any plugin output. An absent
|
|
36
|
+
// declaration is sealed as `undefined`, so no plugin layer can declare or
|
|
37
|
+
// replace the composition's cache identity.
|
|
38
|
+
const environment = Layer.succeed(Action.CurrentCacheEnvironment)(cacheEnvironment);
|
|
39
|
+
return Layer.merge(Layer.provideMerge(merged, environment), environment);
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=mergePluginLayers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergePluginLayers.js","sourceRoot":"","sources":["../../../src/internal/mergePluginLayers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAAsC,EACtC,gBAAqD,EACjB,EAAE;IACtC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CACxC,MAAM,CAAC,KAAK;QACV,CAAC,CAAC;YACA,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACvC,KAAK,CAAC,aAAa,CACjB,MAAM,CAAC,IAAI,CACT,IAAI,WAAW,CAAC;gBACd,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,WAAW,MAAM,CAAC,IAAI,6BAA6B;gBAC5D,MAAM,EAAE,MAAM,CAAC,IAAI;gBACnB,KAAK;aACN,CAAC,CACH,CACF,CAAuC;SAC3C;QACD,CAAC,CAAC,EAAE,CACP,CAAA;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACjC,CAAC,CAAC,KAAK,CAAC,KAAK;QACb,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAuC,CAAA;IACtH,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,gBAAgB,CAIjF,CAAA;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAA;AAC1E,CAAC,CAAA","sourcesContent":["/**\n * Effect layer composition for a resolved plugin list.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport { Action } from \"@smthrs/flow\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport type { FlowsPlugin } from \"../Plugin.ts\"\nimport { PluginError } from \"../PluginError.ts\"\n\n/**\n * Merges plugin layers left-to-right, wrapping each build failure as\n * `layer_failed`, then seals the cache environment: it is supplied beneath\n * the plugin layers and overrides anything they output, and an absent\n * declaration stays absent.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const mergePluginLayers = <H>(\n plugins: ReadonlyArray<FlowsPlugin<H>>,\n cacheEnvironment: Action.CacheEnvironment | undefined\n): Layer.Layer<any, PluginError, any> => {\n const layers = plugins.flatMap((plugin) =>\n plugin.layer\n ? [\n Layer.catchCause(plugin.layer, (cause) =>\n Layer.effectDiscard(\n Effect.fail(\n new PluginError({\n code: \"layer_failed\",\n message: `plugin \"${plugin.name}\" failed to build its layer`,\n plugin: plugin.name,\n cause\n })\n )\n )) as Layer.Layer<any, PluginError, any>\n ]\n : []\n )\n const merged = (layers.length === 0\n ? Layer.empty\n : layers.reduce((accumulated, next) => Layer.provideMerge(next, accumulated))) as Layer.Layer<any, PluginError, any>\n // The sealed environment is provided beneath the plugin layers so they can\n // read it, then merged last so it overrides any plugin output. An absent\n // declaration is sealed as `undefined`, so no plugin layer can declare or\n // replace the composition's cache identity.\n const environment = Layer.succeed(Action.CurrentCacheEnvironment)(cacheEnvironment) as unknown as Layer.Layer<\n any,\n PluginError,\n any\n >\n return Layer.merge(Layer.provideMerge(merged, environment), environment)\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache identity admission for a resolved plugin list.
|
|
3
|
+
*
|
|
4
|
+
* @private
|
|
5
|
+
* @since 1.0.0-rc.0
|
|
6
|
+
*/
|
|
7
|
+
import { Action } from "@smthrs/flow";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import type { FlowsPlugin } from "../Plugin.ts";
|
|
10
|
+
import { PluginError } from "../PluginError.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Admits a caller cache environment and prepends one escaped `name@version`
|
|
13
|
+
* identity per plugin, in resolved order. An absent input stays absent.
|
|
14
|
+
*
|
|
15
|
+
* @private
|
|
16
|
+
* @since 1.0.0-rc.0
|
|
17
|
+
*/
|
|
18
|
+
export declare const snapshotCacheEnvironment: <H>(input: unknown, plugins: ReadonlyArray<FlowsPlugin<H>>) => Effect.Effect<Action.CacheEnvironment | undefined, PluginError>;
|
|
19
|
+
//# sourceMappingURL=snapshotCacheEnvironment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotCacheEnvironment.d.ts","sourceRoot":"","sources":["../../../src/internal/snapshotCacheEnvironment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EACxC,OAAO,OAAO,EACd,SAAS,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,KACrC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,GAAG,SAAS,EAAE,WAAW,CAgDhE,CAAA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache identity admission for a resolved plugin list.
|
|
3
|
+
*
|
|
4
|
+
* @private
|
|
5
|
+
* @since 1.0.0-rc.0
|
|
6
|
+
*/
|
|
7
|
+
import { Action } from "@smthrs/flow";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import { PluginError } from "../PluginError.js";
|
|
11
|
+
import * as Boundary from "./Boundary.js";
|
|
12
|
+
const escapePluginIdentityPart = (value) =>
|
|
13
|
+
// Percent must be escaped first so literal escape-looking text remains distinct from the `@` escape added next.
|
|
14
|
+
value.replaceAll("%", "%25").replaceAll("@", "%40");
|
|
15
|
+
/**
|
|
16
|
+
* Admits a caller cache environment and prepends one escaped `name@version`
|
|
17
|
+
* identity per plugin, in resolved order. An absent input stays absent.
|
|
18
|
+
*
|
|
19
|
+
* @private
|
|
20
|
+
* @since 1.0.0-rc.0
|
|
21
|
+
*/
|
|
22
|
+
export const snapshotCacheEnvironment = (input, plugins) => {
|
|
23
|
+
if (input === undefined)
|
|
24
|
+
return Effect.succeed(undefined);
|
|
25
|
+
const pluginIdentities = [];
|
|
26
|
+
for (const plugin of plugins) {
|
|
27
|
+
if (plugin.version === undefined) {
|
|
28
|
+
return Effect.fail(new PluginError({
|
|
29
|
+
code: "cache_environment_invalid",
|
|
30
|
+
message: `cache environment requires a version for plugin "${plugin.name}"`,
|
|
31
|
+
path: "$.version",
|
|
32
|
+
plugin: plugin.name
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
pluginIdentities.push(`${escapePluginIdentityPart(plugin.name)}@${escapePluginIdentityPart(plugin.version)}`);
|
|
36
|
+
}
|
|
37
|
+
const admitted = Boundary.record(input);
|
|
38
|
+
if (!admitted.ok) {
|
|
39
|
+
return Effect.fail(new PluginError({
|
|
40
|
+
code: "cache_environment_invalid",
|
|
41
|
+
message: `cache environment ${admitted.complaint}`,
|
|
42
|
+
path: `$options.cacheEnvironment${admitted.path.slice(1)}`
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
return Schema.decodeUnknownEffect(Action.CacheEnvironment)(admitted.value).pipe(Effect.mapError(() => new PluginError({
|
|
46
|
+
code: "cache_environment_invalid",
|
|
47
|
+
message: "cache environment does not match the complete cache identity schema",
|
|
48
|
+
path: "$options.cacheEnvironment"
|
|
49
|
+
})), Effect.map((environment) => {
|
|
50
|
+
const capabilities = {};
|
|
51
|
+
for (const name of Object.keys(environment.capabilities)) {
|
|
52
|
+
Object.defineProperty(capabilities, name, {
|
|
53
|
+
value: Object.freeze([...environment.capabilities[name]]),
|
|
54
|
+
enumerable: true
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
layers: Object.freeze([...pluginIdentities, ...environment.layers]),
|
|
59
|
+
capabilities: Object.freeze(capabilities)
|
|
60
|
+
});
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=snapshotCacheEnvironment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotCacheEnvironment.js","sourceRoot":"","sources":["../../../src/internal/snapshotCacheEnvironment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC,MAAM,wBAAwB,GAAG,CAAC,KAAa,EAAU,EAAE;AACzD,gHAAgH;AAChH,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;AAErD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,KAAc,EACd,OAAsC,EAC2B,EAAE;IACnE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACzD,MAAM,gBAAgB,GAAkB,EAAE,CAAA;IAC1C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,WAAW,CAAC;gBACd,IAAI,EAAE,2BAA2B;gBACjC,OAAO,EAAE,oDAAoD,MAAM,CAAC,IAAI,GAAG;gBAC3E,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,MAAM,CAAC,IAAI;aACpB,CAAC,CACH,CAAA;QACH,CAAC;QACD,gBAAgB,CAAC,IAAI,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC/G,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACvC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,WAAW,CAAC;YACd,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE,qBAAqB,QAAQ,CAAC,SAAS,EAAE;YAClD,IAAI,EAAE,4BAA4B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;SAC3D,CAAC,CACH,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAC7E,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACnB,IAAI,WAAW,CAAC;QACd,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,qEAAqE;QAC9E,IAAI,EAAE,2BAA2B;KAClC,CAAC,CACH,EACD,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE;QACzB,MAAM,YAAY,GAA0C,EAAE,CAAA;QAC9D,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;YACzD,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,IAAI,EAAE;gBACxC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC,IAAI,CAAE,CAAC,CAAC;gBAC1D,UAAU,EAAE,IAAI;aACjB,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;YACnE,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;SAC1C,CAAC,CAAA;IACJ,CAAC,CAAC,CACH,CAAA;AACH,CAAC,CAAA","sourcesContent":["/**\n * Cache identity admission for a resolved plugin list.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport { Action } from \"@smthrs/flow\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type { FlowsPlugin } from \"../Plugin.ts\"\nimport { PluginError } from \"../PluginError.ts\"\nimport * as Boundary from \"./Boundary.ts\"\n\nconst escapePluginIdentityPart = (value: string): string =>\n // Percent must be escaped first so literal escape-looking text remains distinct from the `@` escape added next.\n value.replaceAll(\"%\", \"%25\").replaceAll(\"@\", \"%40\")\n\n/**\n * Admits a caller cache environment and prepends one escaped `name@version`\n * identity per plugin, in resolved order. An absent input stays absent.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const snapshotCacheEnvironment = <H>(\n input: unknown,\n plugins: ReadonlyArray<FlowsPlugin<H>>\n): Effect.Effect<Action.CacheEnvironment | undefined, PluginError> => {\n if (input === undefined) return Effect.succeed(undefined)\n const pluginIdentities: Array<string> = []\n for (const plugin of plugins) {\n if (plugin.version === undefined) {\n return Effect.fail(\n new PluginError({\n code: \"cache_environment_invalid\",\n message: `cache environment requires a version for plugin \"${plugin.name}\"`,\n path: \"$.version\",\n plugin: plugin.name\n })\n )\n }\n pluginIdentities.push(`${escapePluginIdentityPart(plugin.name)}@${escapePluginIdentityPart(plugin.version)}`)\n }\n const admitted = Boundary.record(input)\n if (!admitted.ok) {\n return Effect.fail(\n new PluginError({\n code: \"cache_environment_invalid\",\n message: `cache environment ${admitted.complaint}`,\n path: `$options.cacheEnvironment${admitted.path.slice(1)}`\n })\n )\n }\n return Schema.decodeUnknownEffect(Action.CacheEnvironment)(admitted.value).pipe(\n Effect.mapError(() =>\n new PluginError({\n code: \"cache_environment_invalid\",\n message: \"cache environment does not match the complete cache identity schema\",\n path: \"$options.cacheEnvironment\"\n })\n ),\n Effect.map((environment) => {\n const capabilities: Record<string, ReadonlyArray<string>> = {}\n for (const name of Object.keys(environment.capabilities)) {\n Object.defineProperty(capabilities, name, {\n value: Object.freeze([...environment.capabilities[name]!]),\n enumerable: true\n })\n }\n return Object.freeze({\n layers: Object.freeze([...pluginIdentities, ...environment.layers]),\n capabilities: Object.freeze(capabilities)\n })\n })\n )\n}\n"]}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
|
|
55
|
+
|
|
56
|
+
Node.js 26.4.0 or later is required. For the runtime requirements, the import
|
|
57
|
+
forms, and the subpaths the export map blocks, see
|
|
58
|
+
[Installation](./installation.md).
|
|
59
|
+
|
|
60
|
+
## The shortest real example
|
|
61
|
+
|
|
62
|
+
A plugin contributes a configuration namespace, and a second hook reads the
|
|
63
|
+
resolved result back:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { type FlowsPlugin, Kernel } from "@smthrs/plugin"
|
|
67
|
+
import { Effect } from "effect"
|
|
68
|
+
|
|
69
|
+
const feature: FlowsPlugin = {
|
|
70
|
+
name: "flows-plugin-feature",
|
|
71
|
+
hooks: {
|
|
72
|
+
config: () => Effect.succeed({ feature: { enabled: true } }),
|
|
73
|
+
configResolved: (config) => Effect.log(`feature enabled: ${config["feature"] !== undefined}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const kernel = await Effect.runPromise(Kernel.make([feature]))
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`Kernel.make` gives you four things:
|
|
81
|
+
|
|
82
|
+
- `kernel.config` is the frozen, deeply copied configuration every plugin
|
|
83
|
+
agreed on.
|
|
84
|
+
- `kernel.plugins` dispatches hooks.
|
|
85
|
+
- `kernel.layer` is every plugin's Effect services merged in resolved order.
|
|
86
|
+
- `kernel.observerErrors` holds the failures that `configResolved` observers
|
|
87
|
+
reported without failing startup.
|
|
88
|
+
|
|
89
|
+
## Where this sits in Smithers
|
|
90
|
+
|
|
91
|
+
This package is the mechanism, not a host: on its own it dispatches only the
|
|
92
|
+
config lifecycle. The shipped host is [`@smthrs/agent`](/api/agent), the
|
|
93
|
+
Smithers agent loop, which augments `FlowsHooks` with three waterfall hooks of
|
|
94
|
+
its own (`cellRegistry`, `cellFlows`, and `cellModelRequest`), passes the
|
|
95
|
+
matching runtime catalog with `target: "harness"`, and dispatches them around
|
|
96
|
+
each frame of an agent cell. If you want to extend the Smithers agent rather
|
|
97
|
+
than build a host of your own, read that package first: its hooks are the ones
|
|
98
|
+
your plugin declares, and this package explains what those hooks promise.
|
|
99
|
+
|
|
100
|
+
Both sit under the `smthrs` command line, [`@smthrs/cli`](/api/cli), which runs
|
|
101
|
+
and inspects the durable flows a Smithers installation is made of. Start there
|
|
102
|
+
for the whole picture.
|
|
103
|
+
|
|
104
|
+
The kernel depends on [`@smthrs/flow`](/api/flow) for the
|
|
105
|
+
`Action.CacheEnvironment` schema used to
|
|
106
|
+
[declare a cache identity](./guides/cache-identity.md), and
|
|
107
|
+
[`@smthrs/canonical`](/api/canonical) for immutable catalog maps.
|
|
108
|
+
|
|
109
|
+
## What this package does not do
|
|
110
|
+
|
|
111
|
+
The kernel is not an engine lifecycle registry. Retry, storage, concurrency,
|
|
112
|
+
cache, wait, journal, and ownership policy stay on the Effect service or
|
|
113
|
+
constructor option that applies them. Configuration refuses the root keys
|
|
114
|
+
`engine`, `retry`, `store`, and `plugins` so that a configuration file cannot
|
|
115
|
+
imply otherwise.
|
|
116
|
+
|
|
117
|
+
## Where to go next
|
|
118
|
+
|
|
119
|
+
- [Installation](./installation.md) for the runtime requirements, the import
|
|
120
|
+
forms, and the subpaths that are not public.
|
|
121
|
+
- [Quickstart](./quickstart.md) to build a host with its own hook, run it, and
|
|
122
|
+
read a typed result.
|
|
123
|
+
- [Hook kinds](./concepts/hook-kinds.md) for what each of the four dispatch
|
|
124
|
+
shapes promises.
|
|
125
|
+
- [Resolution](./concepts/resolution.md) for how a plugin list becomes an
|
|
126
|
+
ordered, immutable catalog.
|
|
127
|
+
- [Configuration](./concepts/configuration.md) for the config waterfall and the
|
|
128
|
+
JSON boundary it enforces.
|
|
129
|
+
- [API reference](./api.md) for every export, with signatures and limits.
|
|
130
|
+
- [Troubleshooting](./troubleshooting.md) for each `PluginError` code, what
|
|
131
|
+
causes it, and what to change.
|