@shipworthy/openapi-overlay 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shipworthy AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,271 @@
1
- # Temporary Holding Version
1
+ # @shipworthy/openapi-overlay
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Apply OpenAPI Overlay 1.0 and 1.1 documents with Effect v4. Validation schemas are generated
4
+ from the upstream YAML schemas, and action behavior follows the declared feature version. Supports RFC 9535 JSONPath, sequential `update`, `copy`, and `remove`
5
+ actions, recursive object merges, array concatenation/appending, and primitive replacement.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ bun add @shipworthy/openapi-overlay effect@4.1.4
11
+ ```
12
+
13
+ Requires Node.js 22.19.0 or later, or Bun. Ships ESM JavaScript and TypeScript declarations.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { applyOverlay } from "@shipworthy/openapi-overlay";
19
+ import { Effect } from "effect";
20
+
21
+ const document = {
22
+ openapi: "3.1.0",
23
+ info: { title: "Pets", version: "1.0.0" },
24
+ paths: {},
25
+ };
26
+
27
+ const overlay = {
28
+ overlay: "1.1.0",
29
+ info: { title: "Public documentation", version: "1.0.0" },
30
+ actions: [
31
+ { target: "$.info.title", update: "Public Pets" },
32
+ { target: "$", update: { "x-public": true } },
33
+ ],
34
+ };
35
+
36
+ const result = await Effect.runPromise(applyOverlay(document, overlay));
37
+ // result.info.title is "Public Pets"; document is unchanged.
38
+ ```
39
+
40
+ `applyOverlay(document, overlay, options?)` accepts either a parsed object or JSON/YAML text for
41
+ the document and overlay. Options are schema-validated: `removeUnusedComponents` is an optional
42
+ boolean, and unknown options are rejected with `OverlayError`. The options type is inferred
43
+ from its Effect Schema. It returns `Effect.Effect<Schema.MutableJsonObject, OverlayError>` without requiring
44
+ services. Input documents must be JSON-compatible objects. Inputs are validated with Effect
45
+ schemas and copied before applying actions; neither input is mutated, including on failure.
46
+
47
+ `applyOverlayWithWarnings(document, overlay, options?)` accepts the same inputs and returns
48
+ `{ document, warnings }`. Each warning contains the one-based `action` number, `target`, and
49
+ `message`. Unmatched targets and malformed JSONPath target expressions are skipped and
50
+ collected in action order; subsequent actions still run against the preceding result.
51
+ `applyOverlay` returns only the document. Neither function logs warnings.
52
+
53
+ ```ts
54
+ import { applyOverlayWithWarnings } from "@shipworthy/openapi-overlay";
55
+
56
+ const { document: updated, warnings } = await Effect.runPromise(
57
+ applyOverlayWithWarnings(document, overlay),
58
+ );
59
+ ```
60
+
61
+ By default, the function applies overlays without validating the resulting OpenAPI structure.
62
+ It does not load files or fetch URLs. `extends` is descriptive
63
+ metadata; callers choose the document explicitly. To bundle and upgrade first, use
64
+ `bundleDocument` from `@shipworthy/openapi-parser`, then pass the result to `applyOverlay`.
65
+
66
+ ### Filtering operations and removing unused components
67
+
68
+ Use an Overlay removal action to select operations by `operationId`, then opt into pruning:
69
+
70
+ ```ts
71
+ const selected = {
72
+ overlay: "1.1.0",
73
+ info: { title: "Selected operations", version: "1.0.0" },
74
+ actions: [
75
+ {
76
+ target:
77
+ "$.paths.*[?(@.responses && !(@.operationId == 'listPets' || @.operationId == 'getPet'))]",
78
+ remove: true,
79
+ },
80
+ ],
81
+ };
82
+
83
+ const result = await Effect.runPromise(
84
+ applyOverlay(document, selected, { removeUnusedComponents: true }),
85
+ );
86
+ ```
87
+
88
+ This selector identifies operations by their `responses` field. For operations without that
89
+ field, use method-specific selectors. Pruning runs after all actions, removes paths without
90
+ operations, and keeps the transitive component dependencies of remaining paths, webhooks,
91
+ document-level security, and retained root `$defs`. It handles all component categories, recursive schemas,
92
+ discriminator mappings and inheritance, and security scheme names. Example payloads,
93
+ schema defaults, and extension values do not count as references. Empty component groups
94
+ are removed; component extensions are preserved.
95
+
96
+ Pruning requires a bundled OpenAPI 3.1 document with local JSON Pointer references. Bundle
97
+ and upgrade with `bundleDocument` first; the CLI does this automatically. Reachable external
98
+ or anchor references and `$dynamicRef` are rejected. Broken references or links to removed
99
+ operation IDs fail with `OverlayError`. Neither input is mutated. Without the option,
100
+ unused components and paths are retained and no OpenAPI validation is performed.
101
+
102
+ ## Actions
103
+
104
+ - Actions run in order against the result of the previous action. A target that selects no
105
+ nodes succeeds without creating anything. To create a property, target its existing parent
106
+ and merge the new property into it.
107
+ - Object updates merge recursively. Existing properties remain unless replaced or removed.
108
+ Corresponding arrays concatenate; corresponding primitives replace each other, including
109
+ `null`. Other corresponding property categories are incompatible and fail.
110
+ - In 1.1, array targets concatenate an array update or append an object or primitive update as
111
+ one element. Primitive targets require primitive updates.
112
+ - In 1.0, targets must select objects or arrays; primitive targets fail, including for removal.
113
+ A selected array receives the update as one appended entry, including when that entry is an
114
+ array. Update primitive properties through their containing object.
115
+ - In 1.1, `copy` selects exactly one source node from the current document and applies a snapshot of
116
+ its value using the same merge rules. Copies are independent of the source and other targets.
117
+ - In 1.1, multiple update/copy targets must all be objects, all arrays, or all primitives. Different
118
+ primitive types belong to the same category.
119
+ - `remove: true` takes precedence over `update` and `copy`. Array removals use the original
120
+ selected indexes. Removing the document root fails because it has no containing map or array.
121
+ - Supplying both `update` and `copy` on a matching action without `remove: true` fails as
122
+ ambiguous. An action with no modifier has no effect. Duplicate target locations are modified
123
+ once per action.
124
+
125
+ Versions `1.0.x` and `1.1.x` use their respective schemas and action behavior. Patch versions
126
+ do not change the feature set. `copy` and `info.description` are accepted only in 1.1. Both
127
+ versions require at least one action and reject structurally duplicate actions, as specified
128
+ by the upstream schemas. Unknown feature versions fail. YAML is parsed with
129
+ [Effect's YAML 1.2 parser](https://effect.website/docs/v4/api/effect/encoding/Yaml).
130
+ JSON-incompatible values and duplicate YAML mapping keys are rejected. The parser does not
131
+ support compact nested block sequences (`- - value`), multiline flow collections, explicit
132
+ complex keys, tag/directive processing, recursive aliases, or document streams. Use expanded
133
+ block collections for nested YAML; formatted JSON is accepted. Comments and source formatting
134
+ are not retained.
135
+
136
+ ## Preparing a document for generation
137
+
138
+ Apply an overlay before running a generator so the same document changes are available to
139
+ TypeScript types, runtime validators, and clients:
140
+
141
+ ```sh
142
+ openapi overlay openapi.yaml --overlay types.overlay.yaml -o types.openapi.json
143
+ openapi generate typescript -i types.openapi.json -o src/generated
144
+ ```
145
+
146
+ The examples below assume the named schemas and paths exist. Target an existing parent to add
147
+ a property; an unmatched target has no effect. Updates contain literal values, so list property
148
+ and parameter names explicitly.
149
+
150
+ ### Require properties
151
+
152
+ Add property names to `required`. A property's `default` alone does not require its presence
153
+ in generated TypeScript types; the default remains available as documentation.
154
+
155
+ ```yaml
156
+ overlay: 1.1.0
157
+ info: { title: Required pet properties, version: "1" }
158
+ actions:
159
+ - target: $.components.schemas.Pet
160
+ update: { required: [id, name] }
161
+ ```
162
+
163
+ This appends to an existing `required` list. To replace the list, first remove
164
+ `$.components.schemas.Pet.required`, then update its parent with the new list.
165
+
166
+ ### Allow additional keys
167
+
168
+ Set `additionalProperties: true` on the intended object schemas, including empty object
169
+ schemas. The TypeScript generator emits an index signature with `unknown` values.
170
+
171
+ ```yaml
172
+ overlay: 1.1.0
173
+ info: { title: Open object schemas, version: "1" }
174
+ actions:
175
+ - target: $.components.schemas.Pet
176
+ update: { additionalProperties: true }
177
+ - target: $.components.schemas.Metadata
178
+ update: { additionalProperties: true }
179
+ ```
180
+
181
+ Preserve existing schema-valued `additionalProperties` constraints when they should still
182
+ apply. To replace a schema-valued constraint with `true`, remove the `additionalProperties`
183
+ property first, then update its parent; object and boolean values cannot merge.
184
+
185
+ ### Remove deprecated members
186
+
187
+ Use a filter to remove properties or parameters marked `deprecated: true`, and remove
188
+ individual operations by their path and method:
189
+
190
+ ```yaml
191
+ overlay: 1.1.0
192
+ info: { title: Public pet API, version: "1" }
193
+ actions:
194
+ - target: $.components.schemas.Pet.properties[?@.deprecated == true]
195
+ remove: true
196
+ - target: $.components.schemas.Pet.required[?@ == 'legacy']
197
+ remove: true
198
+ - target: $.paths['/pets/{id}'].get.parameters[?@.deprecated == true]
199
+ remove: true
200
+ - target: $.paths['/pets/{id}'].delete
201
+ remove: true
202
+ ```
203
+
204
+ Here `legacy` is a deprecated property listed in `Pet.required`. Remove each corresponding
205
+ `required` entry when removing a property. Filters inspect the selected object, not the target
206
+ of a `$ref`; handle referenced deprecated members explicitly. When removing a component,
207
+ repair or remove references to it as well.
208
+
209
+ ### Declare path parameters
210
+
211
+ Add a required parameter declaration for each placeholder in a path template. This example
212
+ adds `id` to the path item's parameters, making it available to all operations on that path:
213
+
214
+ ```yaml
215
+ overlay: 1.1.0
216
+ info: { title: Pet path parameters, version: "1" }
217
+ actions:
218
+ - target: $.paths['/pets/{id}']
219
+ update:
220
+ parameters:
221
+ - name: id
222
+ in: path
223
+ required: true
224
+ schema: { type: string }
225
+ ```
226
+
227
+ Parameter arrays append on update, so add declarations only for parameters that are missing.
228
+
229
+ ## Schemas and errors
230
+
231
+ The public API exports `applyOverlay`, `applyOverlayWithWarnings`, `OverlayError`, and the Effect schemas `Overlay`,
232
+ `OverlayInfo`, and `OverlayAction`, each with its schema-inferred TypeScript type of the same name.
233
+ Schema objects allow `x-` specification extensions and reject other undeclared fields. The root
234
+ schemas accept the union of supported versions. Import a particular version to validate or type
235
+ its documents directly:
236
+
237
+ ```ts
238
+ import { Overlay, type OverlayAction } from "@shipworthy/openapi-overlay/schemas/1.0";
239
+ // Also available at @shipworthy/openapi-overlay/schemas/1.1.
240
+ ```
241
+
242
+ ```ts
243
+ import { Overlay } from "@shipworthy/openapi-overlay";
244
+ import { Schema } from "effect";
245
+
246
+ const checked = Schema.decodeUnknownEffect(Overlay)({
247
+ overlay: "1.1.0",
248
+ info: { title: "Public documentation", version: "1.0.0" },
249
+ actions: [{ target: "$", update: {} }],
250
+ });
251
+ ```
252
+
253
+ `OverlayError` contains `message` and an optional `cause`. Action failures also contain a
254
+ one-based `action` index and the `target` expression. Catch it with
255
+ `Effect.catchTag("OverlayError", handler)`.
256
+
257
+ ## CLI
258
+
259
+ Install `@shipworthy/openapi-cli` to bundle a local or remote OpenAPI document before overlaying:
260
+
261
+ ```sh
262
+ openapi overlay openapi.yaml --overlay overlay.yaml -o bundled.openapi.json
263
+ openapi overlay openapi.yaml --overlay selected.overlay.yaml --remove-unused-components -o selected.openapi.json
264
+ ```
265
+
266
+ The CLI resolves external references and upgrades OpenAPI 3.0 to 3.1, then applies the overlay
267
+ and writes formatted JSON. See the [CLI documentation](../openapi-cli/README.md).
268
+
269
+ ## License
270
+
271
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,17 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { OverlayError } from "./errors.ts";
3
+ /** Apply actions and collect invalid or unmatched targets without mutating inputs. */
4
+ export declare const applyOverlayWithWarnings: (document: unknown, overlay: unknown, options?: {
5
+ readonly removeUnusedComponents?: boolean | undefined;
6
+ } | undefined) => Effect.Effect<{
7
+ document: Schema.MutableJsonObject;
8
+ warnings: {
9
+ readonly action: number;
10
+ readonly target: string;
11
+ readonly message: string;
12
+ }[];
13
+ }, OverlayError, never>;
14
+ /** Apply version-specific Overlay actions without mutating inputs. */
15
+ export declare const applyOverlay: (document: unknown, overlay: unknown, options?: {
16
+ readonly removeUnusedComponents?: boolean | undefined;
17
+ } | undefined) => Effect.Effect<Schema.MutableJsonObject, OverlayError, never>;
package/dist/apply.js ADDED
@@ -0,0 +1,206 @@
1
+ import { Array as Arr, Effect, Formatter, Option, pipe, Predicate, Record, Result, Schema, String as Str, } from "effect";
2
+ import * as Yaml from "effect/encoding/Yaml";
3
+ import { exec } from "jsonpath-rfc9535";
4
+ import { OverlayError } from "./errors.js";
5
+ import { removeUnusedComponents } from "./prune.js";
6
+ import { Overlay } from "./schemas.js";
7
+ const applyOverlayOptions = Schema.Struct({
8
+ removeUnusedComponents: Schema.optionalKey(Schema.Boolean),
9
+ });
10
+ const overlayWarning = Schema.Struct({
11
+ action: Schema.Int,
12
+ target: Schema.String,
13
+ message: Schema.String,
14
+ });
15
+ function applyIfEnabled(enabled, transform) {
16
+ return Effect.flatMap((document) => Effect.suspend(() => transform(document)).pipe(Effect.when(Effect.succeed(enabled === true)), Effect.map(Option.getOrElse(() => document))));
17
+ }
18
+ const parseInput = Effect.fnUntraced(function* (input, label) {
19
+ if (typeof input !== "string")
20
+ return input;
21
+ return yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown))(input).pipe(Effect.catch(() => Effect.try({
22
+ try: () => Yaml.parse(input),
23
+ catch: (cause) => new OverlayError({ message: `Invalid ${label}: ${String(cause)}`, cause }),
24
+ })));
25
+ });
26
+ function cloneObject(value) {
27
+ return Record.fromEntries(Arr.map(Record.toEntries(value), ([key, item]) => [key, clone(item)]));
28
+ }
29
+ function clone(value) {
30
+ if (Arr.isArray(value))
31
+ return Arr.map(value, clone);
32
+ if (Predicate.isObject(value))
33
+ return cloneObject(value);
34
+ return value;
35
+ }
36
+ function kind(value) {
37
+ if (Arr.isArray(value))
38
+ return "array";
39
+ return Predicate.isObject(value) ? "object" : "primitive";
40
+ }
41
+ function merge(target, update, append = false) {
42
+ if (Arr.isArray(target)) {
43
+ if (!Arr.isArray(update) && !append) {
44
+ throw new TypeError("Incompatible array property");
45
+ }
46
+ const items = Arr.isArray(update) ? update : [update];
47
+ for (const item of items)
48
+ target.push(clone(item));
49
+ return target;
50
+ }
51
+ if (Predicate.isObject(target)) {
52
+ if (kind(update) !== "object")
53
+ throw new TypeError("An object target requires an object update");
54
+ const object = target;
55
+ for (const [key, value] of Record.toEntries(update)) {
56
+ const next = Record.has(object, key)
57
+ ? merge(object[key], value)
58
+ : clone(value);
59
+ Object.defineProperty(target, key, {
60
+ value: next,
61
+ writable: true,
62
+ enumerable: true,
63
+ configurable: true,
64
+ });
65
+ }
66
+ return target;
67
+ }
68
+ if (kind(update) !== "primitive")
69
+ throw new TypeError("A primitive target requires a primitive update");
70
+ return clone(update);
71
+ }
72
+ function select(document, expression) {
73
+ const nodes = [];
74
+ exec(document, expression, (value, path) => {
75
+ let parent = document;
76
+ for (const key of path.slice(0, -1)) {
77
+ parent = Arr.isArray(parent)
78
+ ? parent[Number(key)]
79
+ : parent[key];
80
+ }
81
+ nodes.push({
82
+ value,
83
+ path: [...path],
84
+ parent: path.length === 0 ? undefined : parent,
85
+ key: path.at(-1),
86
+ });
87
+ });
88
+ return nodes;
89
+ }
90
+ function uniqueNodes(nodes) {
91
+ return [
92
+ ...new Map(Arr.map(nodes, (node) => [Formatter.formatJson(node.path), node])).values(),
93
+ ];
94
+ }
95
+ function remove(nodes) {
96
+ if (Arr.some(nodes, (node) => node.parent === undefined)) {
97
+ throw new TypeError("Cannot remove the document root, which has no containing map or array");
98
+ }
99
+ // Descendants first; descending array indexes preserve the original selection.
100
+ const ordered = uniqueNodes(nodes).sort((left, right) => right.path.length - left.path.length ||
101
+ (typeof left.key === "number" && typeof right.key === "number"
102
+ ? right.key - left.key
103
+ : typeof left.key === "number"
104
+ ? -1
105
+ : typeof right.key === "number"
106
+ ? 1
107
+ : 0));
108
+ for (const { parent, key } of ordered) {
109
+ if (Arr.isArray(parent))
110
+ parent.splice(Number(key), 1);
111
+ else if (parent !== undefined && key !== undefined)
112
+ delete parent[key];
113
+ }
114
+ }
115
+ function applyAction(document, action, version, nodes) {
116
+ if (version !== "1.0" && version !== "1.1") {
117
+ throw new TypeError(`Overlay ${version} action behavior is not implemented`);
118
+ }
119
+ if (version === "1.0" && Arr.some(nodes, (node) => kind(node.value) === "primitive")) {
120
+ throw new TypeError("Overlay 1.0 targets must select objects or arrays, not primitives");
121
+ }
122
+ if (action.remove === true) {
123
+ remove(nodes);
124
+ return;
125
+ }
126
+ const hasUpdate = Record.has(action, "update");
127
+ const copy = version === "1.1" && typeof action.copy === "string" ? action.copy : undefined;
128
+ if (hasUpdate && copy !== undefined) {
129
+ throw new TypeError("An action cannot contain both update and copy");
130
+ }
131
+ if (!hasUpdate && copy === undefined)
132
+ return;
133
+ if (version === "1.1" && new Set(Arr.map(nodes, (node) => kind(node.value))).size > 1) {
134
+ throw new TypeError("All selected nodes must be objects, arrays, or primitives of the same category");
135
+ }
136
+ let update = action.update;
137
+ if (copy !== undefined) {
138
+ const sources = select(document, copy);
139
+ if (sources.length !== 1)
140
+ throw new TypeError("copy must select exactly one source node");
141
+ update = clone(sources[0].value);
142
+ }
143
+ for (const node of uniqueNodes(nodes)) {
144
+ if (version === "1.0" && Arr.isArray(node.value)) {
145
+ node.value.push(clone(update));
146
+ continue;
147
+ }
148
+ const value = merge(node.value, update, true);
149
+ if (Arr.isArray(node.parent))
150
+ node.parent[Number(node.key)] = value;
151
+ else if (node.parent !== undefined && node.key !== undefined) {
152
+ Object.defineProperty(node.parent, node.key, {
153
+ value,
154
+ writable: true,
155
+ enumerable: true,
156
+ configurable: true,
157
+ });
158
+ }
159
+ }
160
+ }
161
+ /** Apply actions and collect invalid or unmatched targets without mutating inputs. */
162
+ export const applyOverlayWithWarnings = Effect.fn("applyOverlayWithWarnings")(function* (document, overlay, options = {}) {
163
+ const checkedOptions = yield* Schema.decodeEffect(applyOverlayOptions)(options, {
164
+ onExcessProperty: "error",
165
+ }).pipe(Effect.mapError((cause) => new OverlayError({ message: `Invalid options: ${cause.message}`, cause })));
166
+ const source = yield* Schema.decodeUnknownEffect(Schema.JsonObject)(yield* parseInput(document, "document")).pipe(Effect.mapError((cause) => new OverlayError({ message: `Invalid document: ${cause.message}`, cause })));
167
+ const parsed = yield* Schema.decodeUnknownEffect(Overlay)(yield* parseInput(overlay, "overlay"), {
168
+ onExcessProperty: "error",
169
+ }).pipe(Effect.mapError((cause) => new OverlayError({ message: `Invalid overlay: ${cause.message}`, cause })));
170
+ const result = cloneObject(source);
171
+ const version = pipe(parsed.overlay, Str.split("."), Arr.take(2), Arr.join("."));
172
+ const warnings = yield* Effect.forEach(parsed.actions, Effect.fnUntraced(function* (action, index) {
173
+ const selected = yield* Effect.try({
174
+ try: () => select(result, action.target),
175
+ catch: (cause) => ({
176
+ action: index + 1,
177
+ target: action.target,
178
+ message: `Invalid target: ${String(cause)}`,
179
+ }),
180
+ }).pipe(Effect.result);
181
+ if (Result.isFailure(selected))
182
+ return [selected.failure];
183
+ if (selected.success.length === 0)
184
+ return [
185
+ {
186
+ action: index + 1,
187
+ target: action.target,
188
+ message: "Target matched no nodes",
189
+ },
190
+ ];
191
+ yield* Effect.try({
192
+ try: () => applyAction(result, action, version, selected.success),
193
+ catch: (cause) => new OverlayError({
194
+ message: `Overlay action ${index + 1} (${action.target}): ${String(cause)}`,
195
+ action: index + 1,
196
+ target: action.target,
197
+ cause,
198
+ }),
199
+ });
200
+ return [];
201
+ }));
202
+ const output = yield* Effect.succeed(result).pipe(applyIfEnabled(checkedOptions.removeUnusedComponents, removeUnusedComponents));
203
+ return { document: output, warnings: Arr.flatten(warnings) };
204
+ });
205
+ /** Apply version-specific Overlay actions without mutating inputs. */
206
+ export const applyOverlay = Effect.fn("applyOverlay")((document, overlay, options = {}) => applyOverlayWithWarnings(document, overlay, options).pipe(Effect.map((result) => result.document)));
@@ -0,0 +1,12 @@
1
+ import { Schema } from "effect";
2
+ declare const OverlayError_base: Schema.Class<OverlayError, Schema.TaggedStruct<"OverlayError", {
3
+ readonly message: Schema.String;
4
+ readonly action: Schema.optionalKey<Schema.Int>;
5
+ readonly target: Schema.optionalKey<Schema.String>;
6
+ readonly cause: Schema.optionalKey<Schema.Defect>;
7
+ }>, import("effect/Cause").YieldableError & {
8
+ readonly brand: unique symbol;
9
+ }>;
10
+ export declare class OverlayError extends OverlayError_base {
11
+ }
12
+ export {};
package/dist/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ import { Schema } from "effect";
2
+ export class OverlayError extends Schema.TaggedError()("OverlayError", {
3
+ message: Schema.String,
4
+ action: Schema.optionalKey(Schema.Int),
5
+ target: Schema.optionalKey(Schema.String),
6
+ cause: Schema.optionalKey(Schema.Defect()),
7
+ }) {
8
+ }
@@ -0,0 +1,3 @@
1
+ export { applyOverlay, applyOverlayWithWarnings } from "./apply.ts";
2
+ export { OverlayError } from "./errors.ts";
3
+ export { Overlay, OverlayAction, OverlayInfo } from "./schemas.ts";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { applyOverlay, applyOverlayWithWarnings } from "./apply.js";
2
+ export { OverlayError } from "./errors.js";
3
+ export { Overlay, OverlayAction, OverlayInfo } from "./schemas.js";
@@ -0,0 +1,3 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { OverlayError } from "./errors.ts";
3
+ export declare const removeUnusedComponents: (document: Schema.MutableJsonObject) => Effect.Effect<Schema.MutableJsonObject, OverlayError, never>;
package/dist/prune.js ADDED
@@ -0,0 +1,239 @@
1
+ import { OpenApiCallback, OpenApiExample, OpenApiHeader, OpenApiHttpMethod, OpenApiLink, OpenApiMediaType, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiRequestBody, OpenApiReference, OpenApiResponse, OpenApiSchema, OpenApiSecurityScheme, ParsedOpenApi, } from "@shipworthy/openapi-parser/schemas/3.1";
2
+ import { Array, Effect, JsonPointer, pipe, Predicate, Record, Schema, String } from "effect";
3
+ import { OverlayError } from "./errors.js";
4
+ const methods = OpenApiHttpMethod.literals;
5
+ const schemaWithAllOf = Schema.Struct({ allOf: Schema.Array(OpenApiSchema) });
6
+ const schemaMaps = ["properties", "patternProperties", "$defs", "dependentSchemas"];
7
+ const schemaLists = ["allOf", "anyOf", "oneOf", "prefixItems"];
8
+ const schemaSingles = [
9
+ "additionalProperties",
10
+ "unevaluatedProperties",
11
+ "items",
12
+ "contains",
13
+ "not",
14
+ "if",
15
+ "then",
16
+ "else",
17
+ "propertyNames",
18
+ "unevaluatedItems",
19
+ "contentSchema",
20
+ ];
21
+ function extendsSchema(schema, pointer) {
22
+ return (Schema.is(schemaWithAllOf)(schema) &&
23
+ Array.some(schema.allOf, (child) => Schema.is(OpenApiReference)(child) && child.$ref === pointer));
24
+ }
25
+ function visitMap(values, visit, ignoreExtensions = false) {
26
+ return Effect.forEach(Record.toEntries(values ?? {}), ([key, value]) => ignoreExtensions && pipe(key, String.startsWith("x-")) ? Effect.void : visit(value), { discard: true });
27
+ }
28
+ export const removeUnusedComponents = Effect.fnUntraced(function* (document) {
29
+ const parsed = yield* Schema.decodeUnknownEffect(ParsedOpenApi)(document);
30
+ const used = new Set();
31
+ const followed = new Set();
32
+ const removedPaths = new Set();
33
+ const components = new Map();
34
+ const operationIds = new Set();
35
+ const linkedOperationIds = new Set();
36
+ const inherited = new Set();
37
+ const componentPointer = (category, name) => JsonPointer.formatUriFragment(["components", category, name]);
38
+ const mark = Effect.fnUntraced(function* (category, name) {
39
+ const pointer = componentPointer(category, name);
40
+ if (used.has(pointer))
41
+ return;
42
+ const visit = components.get(pointer);
43
+ if (visit === undefined)
44
+ return yield* new OverlayError({ message: `Missing component ${pointer}` });
45
+ used.add(pointer);
46
+ yield* visit();
47
+ });
48
+ const reference = Effect.fnUntraced(function* (ref, visit) {
49
+ const tokens = pipe(ref, String.startsWith("#/"))
50
+ ? JsonPointer.parseUriFragment(ref)
51
+ : undefined;
52
+ if (tokens === undefined)
53
+ return yield* new OverlayError({
54
+ message: `Component pruning requires bundled local JSON Pointer references: ${ref}`,
55
+ });
56
+ if (tokens[0] === "paths" && tokens[1] !== undefined && removedPaths.has(tokens[1]))
57
+ return yield* new OverlayError({ message: `Reference targets a removed path: ${ref}` });
58
+ const root = parsed;
59
+ const target = Array.reduce(tokens, root, (value, token) => Predicate.isObject(value) && Record.has(value, token) ? value[token] : undefined);
60
+ if (target === undefined)
61
+ return yield* new OverlayError({ message: `Missing reference ${ref}` });
62
+ if (followed.has(ref))
63
+ return;
64
+ followed.add(ref);
65
+ if (tokens[0] === "components" && tokens[1] !== undefined && tokens[2] !== undefined)
66
+ yield* mark(tokens[1], tokens[2]);
67
+ yield* visit(target);
68
+ });
69
+ function referenced(schema, walk) {
70
+ const visit = Effect.fnUntraced(function* (value) {
71
+ if (Predicate.isObject(value) && Predicate.isString(value.$ref)) {
72
+ yield* reference(value.$ref, visit);
73
+ return;
74
+ }
75
+ yield* walk(yield* Schema.decodeUnknownEffect(schema)(value));
76
+ });
77
+ return visit;
78
+ }
79
+ const visitSchema = Effect.fnUntraced(function* (value, componentName) {
80
+ const schema = yield* Schema.decodeUnknownEffect(OpenApiSchema)(value);
81
+ if (typeof schema === "boolean")
82
+ return;
83
+ if (Predicate.isString(schema.$ref))
84
+ yield* reference(schema.$ref, visitSchema);
85
+ if (Predicate.isString(schema.$dynamicRef))
86
+ return yield* new OverlayError({
87
+ message: `Component pruning does not support $dynamicRef: ${schema.$dynamicRef}`,
88
+ });
89
+ yield* Effect.forEach(schemaSingles, (key) => (schema[key] === undefined ? Effect.void : visitSchema(schema[key])), { discard: true });
90
+ yield* Effect.forEach(schemaLists, (key) => {
91
+ const children = schema[key];
92
+ return Array.isArray(children)
93
+ ? Effect.forEach(children, (child) => visitSchema(child), { discard: true })
94
+ : Effect.void;
95
+ }, { discard: true });
96
+ yield* Effect.forEach(schemaMaps, (key) => {
97
+ const children = schema[key];
98
+ return Predicate.isObject(children)
99
+ ? Effect.forEach(Record.values(children), (child) => visitSchema(child), {
100
+ discard: true,
101
+ })
102
+ : Effect.void;
103
+ }, { discard: true });
104
+ const discriminator = schema.discriminator;
105
+ if (Predicate.isObject(discriminator) && componentName !== undefined)
106
+ yield* visitDerivedSchemas(componentName);
107
+ if (Predicate.isObject(discriminator) && Predicate.isObject(discriminator.mapping))
108
+ yield* Effect.forEach(Record.values(discriminator.mapping), (target) => {
109
+ if (!Predicate.isString(target))
110
+ return Effect.void;
111
+ return Record.has(parsed.components?.schemas ?? {}, target)
112
+ ? mark("schemas", target)
113
+ : reference(target, visitSchema);
114
+ }, { discard: true });
115
+ });
116
+ function visitDerivedSchemas(parent) {
117
+ if (inherited.has(parent))
118
+ return Effect.void;
119
+ inherited.add(parent);
120
+ const pointer = componentPointer("schemas", parent);
121
+ return Effect.forEach(Record.toEntries(parsed.components?.schemas ?? {}), ([name, schema]) => {
122
+ if (!extendsSchema(schema, pointer))
123
+ return Effect.void;
124
+ return mark("schemas", name).pipe(Effect.andThen(() => visitDerivedSchemas(name)));
125
+ }, { discard: true });
126
+ }
127
+ const visitExample = referenced(OpenApiExample, () => Effect.void);
128
+ const visitSecurityScheme = referenced(OpenApiSecurityScheme, () => Effect.void);
129
+ const visitHeader = referenced(OpenApiHeader, Effect.fnUntraced(function* (header) {
130
+ if (header.schema !== undefined)
131
+ yield* visitSchema(header.schema);
132
+ yield* visitMap(header.content, visitMediaType);
133
+ yield* visitMap(header.examples, visitExample);
134
+ }));
135
+ const visitParameter = referenced(OpenApiParameter, Effect.fnUntraced(function* (parameter) {
136
+ if (parameter.schema !== undefined)
137
+ yield* visitSchema(parameter.schema);
138
+ yield* visitMap(parameter.content, visitMediaType);
139
+ yield* visitMap(parameter.examples, visitExample);
140
+ }));
141
+ const visitRequestBody = referenced(OpenApiRequestBody, (body) => visitMap(body.content, visitMediaType));
142
+ const visitLink = referenced(OpenApiLink, Effect.fnUntraced(function* (link) {
143
+ if (link.operationId !== undefined)
144
+ linkedOperationIds.add(link.operationId);
145
+ if (link.operationRef !== undefined && pipe(link.operationRef, String.startsWith("#")))
146
+ yield* reference(link.operationRef, visitOperation);
147
+ }));
148
+ const visitResponse = referenced(OpenApiResponse, Effect.fnUntraced(function* (response) {
149
+ yield* visitMap(response.content, visitMediaType);
150
+ yield* visitMap(response.headers, visitHeader);
151
+ yield* visitMap(response.links, visitLink);
152
+ }));
153
+ const visitCallback = referenced(OpenApiCallback, (callback) => visitMap(callback, visitPathItem, true));
154
+ function visitMediaType(value) {
155
+ return Schema.decodeUnknownEffect(OpenApiMediaType)(value).pipe(Effect.flatMap(Effect.fnUntraced(function* (media) {
156
+ if (media.schema !== undefined)
157
+ yield* visitSchema(media.schema);
158
+ yield* visitMap(media.examples, visitExample);
159
+ yield* Effect.forEach(Record.values(media.encoding ?? {}), (encoding) => visitMap(encoding.headers, visitHeader), { discard: true });
160
+ })));
161
+ }
162
+ function visitSecurity(security) {
163
+ return Effect.forEach(security ?? [], (requirement) => Effect.forEach(Record.keys(requirement), (name) => mark("securitySchemes", name), {
164
+ discard: true,
165
+ }), { discard: true });
166
+ }
167
+ function visitOperation(value) {
168
+ return Schema.decodeUnknownEffect(OpenApiOperation)(value).pipe(Effect.flatMap(Effect.fnUntraced(function* (operation) {
169
+ if (operation.operationId !== undefined)
170
+ operationIds.add(operation.operationId);
171
+ yield* Effect.forEach(operation.parameters ?? [], visitParameter, { discard: true });
172
+ if (operation.requestBody !== undefined)
173
+ yield* visitRequestBody(operation.requestBody);
174
+ yield* visitMap(operation.responses, visitResponse, true);
175
+ yield* visitMap(operation.callbacks, visitCallback);
176
+ yield* visitSecurity(operation.security);
177
+ })));
178
+ }
179
+ function visitPathItem(value) {
180
+ return Schema.decodeUnknownEffect(OpenApiPathItem)(value).pipe(Effect.flatMap(Effect.fnUntraced(function* (item) {
181
+ if (item.$ref !== undefined)
182
+ yield* reference(item.$ref, visitPathItem);
183
+ yield* Effect.forEach(item.parameters ?? [], visitParameter, { discard: true });
184
+ yield* Effect.forEach(methods, (method) => (item[method] === undefined ? Effect.void : visitOperation(item[method])), { discard: true });
185
+ })));
186
+ }
187
+ function register(category, values, visit) {
188
+ Array.forEach(Record.toEntries(values ?? {}), ([name, value]) => {
189
+ components.set(componentPointer(category, name), () => visit(value));
190
+ });
191
+ }
192
+ Array.forEach(Record.toEntries(parsed.components?.schemas ?? {}), ([name, schema]) => {
193
+ components.set(componentPointer("schemas", name), () => visitSchema(schema, name));
194
+ });
195
+ register("responses", parsed.components?.responses, visitResponse);
196
+ register("parameters", parsed.components?.parameters, visitParameter);
197
+ register("requestBodies", parsed.components?.requestBodies, visitRequestBody);
198
+ register("headers", parsed.components?.headers, visitHeader);
199
+ register("examples", parsed.components?.examples, visitExample);
200
+ register("links", parsed.components?.links, visitLink);
201
+ register("callbacks", parsed.components?.callbacks, visitCallback);
202
+ register("pathItems", parsed.components?.pathItems, visitPathItem);
203
+ register("securitySchemes", parsed.components?.securitySchemes, visitSecurityScheme);
204
+ const retainedPaths = yield* Effect.forEach(Record.toEntries(parsed.paths ?? {}), Effect.fnUntraced(function* ([path, value]) {
205
+ if (pipe(path, String.startsWith("x-")))
206
+ return;
207
+ const item = yield* Schema.decodeEffect(OpenApiPathItem)(value);
208
+ if (item.$ref !== undefined || Array.some(methods, (method) => item[method] !== undefined))
209
+ return item;
210
+ removedPaths.add(path);
211
+ }));
212
+ yield* Effect.forEach(retainedPaths, (item) => (item === undefined ? Effect.void : visitPathItem(item)), { discard: true });
213
+ yield* visitMap(parsed.webhooks, visitPathItem);
214
+ yield* visitMap(parsed.$defs, (value) => visitSchema(value));
215
+ yield* visitSecurity(parsed.security);
216
+ yield* Effect.forEach(linkedOperationIds, (id) => operationIds.has(id)
217
+ ? Effect.void
218
+ : new OverlayError({ message: `Missing linked operationId ${id}` }), { discard: true });
219
+ const paths = document.paths;
220
+ if (Predicate.isObject(paths) && !Array.isArray(paths))
221
+ Array.forEach(removedPaths, (path) => {
222
+ delete paths[path];
223
+ });
224
+ const maps = document.components;
225
+ if (Predicate.isObject(maps) && !Array.isArray(maps))
226
+ Array.forEach(Record.toEntries(maps), ([category, entries]) => {
227
+ if (pipe(category, String.startsWith("x-")) ||
228
+ !Predicate.isObject(entries) ||
229
+ Array.isArray(entries))
230
+ return;
231
+ Array.forEach(Record.keys(entries), (name) => {
232
+ if (!used.has(componentPointer(category, name)))
233
+ delete entries[name];
234
+ });
235
+ if (Record.keys(entries).length === 0)
236
+ delete maps[category];
237
+ });
238
+ return document;
239
+ }, Effect.mapError((cause) => new OverlayError({ message: `Cannot prune components: ${cause.message}`, cause })));
@@ -0,0 +1,28 @@
1
+ import { Schema } from "effect";
2
+ export declare const OverlayInfo: Schema.StructWithRest<Schema.Struct<{
3
+ readonly title: Schema.String;
4
+ readonly version: Schema.String;
5
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
6
+ export type OverlayInfo = typeof OverlayInfo.Type;
7
+ export declare const OverlayAction: Schema.StructWithRest<Schema.Struct<{
8
+ readonly target: Schema.String;
9
+ readonly description: Schema.optionalKey<Schema.String>;
10
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
11
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
12
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
13
+ export type OverlayAction = typeof OverlayAction.Type;
14
+ export declare const Overlay: Schema.StructWithRest<Schema.Struct<{
15
+ readonly overlay: Schema.String;
16
+ readonly info: Schema.StructWithRest<Schema.Struct<{
17
+ readonly title: Schema.String;
18
+ readonly version: Schema.String;
19
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
20
+ readonly extends: Schema.optionalKey<Schema.String>;
21
+ readonly actions: Schema.$Array<Schema.StructWithRest<Schema.Struct<{
22
+ readonly target: Schema.String;
23
+ readonly description: Schema.optionalKey<Schema.String>;
24
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
25
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
26
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>>;
27
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
28
+ export type Overlay = typeof Overlay.Type;
@@ -0,0 +1,52 @@
1
+ // Generated from schemas/v1.0/schema.yaml (sha256: 209a9755d58f96b5d80bf5a3ed4f6f2b9e77550fa27849cd67bd50bc3eabfa83).
2
+ // Run bun run generate:schemas to regenerate. Do not edit by hand.
3
+ import { Schema } from "effect";
4
+ export const OverlayInfo = Schema.StructWithRest(Schema.Struct({ title: Schema.String, version: Schema.String }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))]).check(Schema.isPropertyNames(Schema.Union([
5
+ Schema.Literals(["title", "version"]),
6
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
7
+ expected: "a string matching the RegExp ^x-",
8
+ })),
9
+ ])).annotate({
10
+ expected: "an object with property names matching the schema",
11
+ identifier: "OverlayInfo",
12
+ }));
13
+ export const OverlayAction = Schema.StructWithRest(Schema.Struct({
14
+ target: Schema.String.check(Schema.isPattern(new globalThis.RegExp("^\\$", "u")).annotate({
15
+ expected: "a string matching the RegExp ^\\$",
16
+ })),
17
+ description: Schema.optionalKey(Schema.String),
18
+ update: Schema.optionalKey(Schema.Union([
19
+ Schema.String,
20
+ Schema.Boolean,
21
+ Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })),
22
+ Schema.Array(Schema.Json.annotate({ expected: "JSON value" })),
23
+ Schema.Finite,
24
+ Schema.Null,
25
+ ])),
26
+ remove: Schema.optionalKey(Schema.Boolean.annotate({ default: false })),
27
+ }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))]).check(Schema.isPropertyNames(Schema.Union([
28
+ Schema.Literals(["target", "description", "update", "remove"]),
29
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
30
+ expected: "a string matching the RegExp ^x-",
31
+ })),
32
+ ])).annotate({
33
+ expected: "an object with property names matching the schema",
34
+ identifier: "OverlayAction",
35
+ }));
36
+ export const Overlay = Schema.StructWithRest(Schema.Struct({
37
+ overlay: Schema.String.check(Schema.isPattern(new globalThis.RegExp("^1\\.0\\.\\d+$", "u")).annotate({
38
+ expected: "a string matching the RegExp ^1\\.0\\.\\d+$",
39
+ })),
40
+ info: OverlayInfo,
41
+ extends: Schema.optionalKey(Schema.String.annotate({ format: "uri-reference" })),
42
+ actions: Schema.Array(OverlayAction)
43
+ .check(Schema.isMinLength(1).annotate({ expected: "a value with a length of at least 1" }))
44
+ .check(Schema.isUnique().annotate({ expected: "an array with unique items" })),
45
+ }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))])
46
+ .annotate({ description: "The description of Overlay v1.0.x documents" })
47
+ .check(Schema.isPropertyNames(Schema.Union([
48
+ Schema.Literals(["overlay", "info", "extends", "actions"]),
49
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
50
+ expected: "a string matching the RegExp ^x-",
51
+ })),
52
+ ])).annotate({ expected: "an object with property names matching the schema" }));
@@ -0,0 +1,32 @@
1
+ import { Schema } from "effect";
2
+ export declare const OverlayInfo: Schema.StructWithRest<Schema.Struct<{
3
+ readonly title: Schema.String;
4
+ readonly version: Schema.String;
5
+ readonly description: Schema.optionalKey<Schema.String>;
6
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
7
+ export type OverlayInfo = typeof OverlayInfo.Type;
8
+ export declare const OverlayAction: Schema.StructWithRest<Schema.Struct<{
9
+ readonly target: Schema.String;
10
+ readonly description: Schema.optionalKey<Schema.String>;
11
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
12
+ readonly copy: Schema.optionalKey<Schema.String>;
13
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
14
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
15
+ export type OverlayAction = typeof OverlayAction.Type;
16
+ export declare const Overlay: Schema.StructWithRest<Schema.Struct<{
17
+ readonly overlay: Schema.String;
18
+ readonly info: Schema.StructWithRest<Schema.Struct<{
19
+ readonly title: Schema.String;
20
+ readonly version: Schema.String;
21
+ readonly description: Schema.optionalKey<Schema.String>;
22
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
23
+ readonly extends: Schema.optionalKey<Schema.String>;
24
+ readonly actions: Schema.$Array<Schema.StructWithRest<Schema.Struct<{
25
+ readonly target: Schema.String;
26
+ readonly description: Schema.optionalKey<Schema.String>;
27
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
28
+ readonly copy: Schema.optionalKey<Schema.String>;
29
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
30
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>>;
31
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
32
+ export type Overlay = typeof Overlay.Type;
@@ -0,0 +1,57 @@
1
+ // Generated from schemas/v1.1/schema.yaml (sha256: c9988e3656d7f9de217654a3fc67b093c009668be357c86f07fd76b260863a51).
2
+ // Run bun run generate:schemas to regenerate. Do not edit by hand.
3
+ import { Schema } from "effect";
4
+ export const OverlayInfo = Schema.StructWithRest(Schema.Struct({
5
+ title: Schema.String,
6
+ version: Schema.String,
7
+ description: Schema.optionalKey(Schema.String),
8
+ }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))]).check(Schema.isPropertyNames(Schema.Union([
9
+ Schema.Literals(["title", "version", "description"]),
10
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
11
+ expected: "a string matching the RegExp ^x-",
12
+ })),
13
+ ])).annotate({
14
+ expected: "an object with property names matching the schema",
15
+ identifier: "OverlayInfo",
16
+ }));
17
+ export const OverlayAction = Schema.StructWithRest(Schema.Struct({
18
+ target: Schema.String.check(Schema.isPattern(new globalThis.RegExp("^\\$", "u")).annotate({
19
+ expected: "a string matching the RegExp ^\\$",
20
+ })),
21
+ description: Schema.optionalKey(Schema.String),
22
+ update: Schema.optionalKey(Schema.Union([
23
+ Schema.String,
24
+ Schema.Boolean,
25
+ Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })),
26
+ Schema.Array(Schema.Json.annotate({ expected: "JSON value" })),
27
+ Schema.Finite,
28
+ Schema.Null,
29
+ ])),
30
+ copy: Schema.optionalKey(Schema.String),
31
+ remove: Schema.optionalKey(Schema.Boolean.annotate({ default: false })),
32
+ }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))]).check(Schema.isPropertyNames(Schema.Union([
33
+ Schema.Literals(["target", "description", "update", "copy", "remove"]),
34
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
35
+ expected: "a string matching the RegExp ^x-",
36
+ })),
37
+ ])).annotate({
38
+ expected: "an object with property names matching the schema",
39
+ identifier: "OverlayAction",
40
+ }));
41
+ export const Overlay = Schema.StructWithRest(Schema.Struct({
42
+ overlay: Schema.String.check(Schema.isPattern(new globalThis.RegExp("^1\\.1\\.\\d+$", "u")).annotate({
43
+ expected: "a string matching the RegExp ^1\\.1\\.\\d+$",
44
+ })),
45
+ info: OverlayInfo,
46
+ extends: Schema.optionalKey(Schema.String.annotate({ format: "uri-reference" })),
47
+ actions: Schema.Array(OverlayAction)
48
+ .check(Schema.isMinLength(1).annotate({ expected: "a value with a length of at least 1" }))
49
+ .check(Schema.isUnique().annotate({ expected: "an array with unique items" })),
50
+ }), [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))])
51
+ .annotate({ description: "The description of Overlay v1.1.x documents" })
52
+ .check(Schema.isPropertyNames(Schema.Union([
53
+ Schema.Literals(["overlay", "info", "extends", "actions"]),
54
+ Schema.String.check(Schema.isPattern(new globalThis.RegExp("^x-", "u")).annotate({
55
+ expected: "a string matching the RegExp ^x-",
56
+ })),
57
+ ])).annotate({ expected: "an object with property names matching the schema" }));
@@ -0,0 +1,53 @@
1
+ import { Schema } from "effect";
2
+ export declare const OverlayInfo: Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
3
+ readonly title: Schema.String;
4
+ readonly version: Schema.String;
5
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>, Schema.StructWithRest<Schema.Struct<{
6
+ readonly title: Schema.String;
7
+ readonly version: Schema.String;
8
+ readonly description: Schema.optionalKey<Schema.String>;
9
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>]>;
10
+ export type OverlayInfo = typeof OverlayInfo.Type;
11
+ export declare const OverlayAction: Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
12
+ readonly target: Schema.String;
13
+ readonly description: Schema.optionalKey<Schema.String>;
14
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
15
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
16
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>, Schema.StructWithRest<Schema.Struct<{
17
+ readonly target: Schema.String;
18
+ readonly description: Schema.optionalKey<Schema.String>;
19
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
20
+ readonly copy: Schema.optionalKey<Schema.String>;
21
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
22
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>]>;
23
+ export type OverlayAction = typeof OverlayAction.Type;
24
+ export declare const Overlay: Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
25
+ readonly overlay: Schema.String;
26
+ readonly info: Schema.StructWithRest<Schema.Struct<{
27
+ readonly title: Schema.String;
28
+ readonly version: Schema.String;
29
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
30
+ readonly extends: Schema.optionalKey<Schema.String>;
31
+ readonly actions: Schema.$Array<Schema.StructWithRest<Schema.Struct<{
32
+ readonly target: Schema.String;
33
+ readonly description: Schema.optionalKey<Schema.String>;
34
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
35
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
36
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>>;
37
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>, Schema.StructWithRest<Schema.Struct<{
38
+ readonly overlay: Schema.String;
39
+ readonly info: Schema.StructWithRest<Schema.Struct<{
40
+ readonly title: Schema.String;
41
+ readonly version: Schema.String;
42
+ readonly description: Schema.optionalKey<Schema.String>;
43
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
44
+ readonly extends: Schema.optionalKey<Schema.String>;
45
+ readonly actions: Schema.$Array<Schema.StructWithRest<Schema.Struct<{
46
+ readonly target: Schema.String;
47
+ readonly description: Schema.optionalKey<Schema.String>;
48
+ readonly update: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>, Schema.Finite, Schema.Null]>>;
49
+ readonly copy: Schema.optionalKey<Schema.String>;
50
+ readonly remove: Schema.optionalKey<Schema.Boolean>;
51
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>>;
52
+ }>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>]>;
53
+ export type Overlay = typeof Overlay.Type;
@@ -0,0 +1,6 @@
1
+ import { Schema } from "effect";
2
+ import * as V10 from "./schemas/1.0.js";
3
+ import * as V11 from "./schemas/1.1.js";
4
+ export const OverlayInfo = Schema.Union([V10.OverlayInfo, V11.OverlayInfo]);
5
+ export const OverlayAction = Schema.Union([V10.OverlayAction, V11.OverlayAction]);
6
+ export const Overlay = Schema.Union([V10.Overlay, V11.Overlay]);
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@shipworthy/openapi-overlay",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "dependencies": {
8
+ "@shipworthy/openapi-parser": "1.0.0",
9
+ "effect": "4.1.4",
10
+ "jsonpath-rfc9535": "1.3.0"
11
+ },
12
+ "description": "Apply versioned OpenAPI Overlay documents with Effect.",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/ShipWorthyAI/openapi.git",
17
+ "directory": "packages/openapi-overlay"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./schemas/1.0": {
26
+ "types": "./dist/schemas/1.0.d.ts",
27
+ "default": "./dist/schemas/1.0.js"
28
+ },
29
+ "./schemas/1.1": {
30
+ "types": "./dist/schemas/1.1.d.ts",
31
+ "default": "./dist/schemas/1.1.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "scripts": {
36
+ "generate:schemas": "bun scripts/generate-schemas.ts",
37
+ "check:schemas": "bun scripts/generate-schemas.ts --check",
38
+ "test": "vitest run",
39
+ "build": "rm -rf dist && tsc -p tsconfig.build.json"
40
+ },
41
+ "devDependencies": {
42
+ "@effect/platform-node": "4.1.4",
43
+ "@effect/vitest": "4.1.4",
44
+ "@shipworthy/typescript-config": "0.0.1",
45
+ "@types/node": "^26.6.6",
46
+ "oxfmt": "^0.78.0",
47
+ "typescript": "^7.0.2",
48
+ "vitest": "5.1.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=22.19.0"
52
+ },
53
+ "main": "./dist/index.js",
54
+ "types": "./dist/index.d.ts",
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }