@shipworthy/openapi-parser 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -4,6 +4,10 @@ Load an OpenAPI 3.0 or 3.1 document from a file path, a URL, a JSON or YAML stri
4
4
 
5
5
  The API is Effect-native. Every entry point is an `Effect` or an Effect `Schema` codec, so you need [`effect`](https://www.npmjs.com/package/effect) v4 to run it.
6
6
 
7
+ Bundling preserves OpenAPI 3.1 schema keywords alongside `$ref`, including descriptions,
8
+ constraints, and extensions. It rewrites external references to internal references without
9
+ introducing `allOf` wrappers for their sibling keywords.
10
+
7
11
  ## Installation
8
12
 
9
13
  ```sh
@@ -14,10 +18,9 @@ bun add @shipworthy/openapi-parser effect @effect/platform-node
14
18
 
15
19
  | Requirement | Details |
16
20
  | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
- | `effect` | `^4.0.0`, a peer dependency. Install it yourself so your application and the parser share one copy to run the returned effects and to use the schemas. |
21
+ | `effect` | `^4.0.0`, a regular dependency. Add a compatible version to your own `package.json` so your application and the parser resolve to one copy to run the returned effects and to use the schemas. |
18
22
  | Platform | `@effect/platform-node` `^4.0.0` is a regular dependency. Install it yourself only if your code imports it, as the examples here do for `NodeServices`. The parser imports `node:stream`, `node:url`, `node:path`, `node:dns/promises`, `node:net`, and the `Buffer` global, so it needs Node.js `>=22.19.0` or Bun. It does not run in browsers. |
19
23
  | Module format | ESM only (`"type": "module"`). Ships compiled JavaScript with `.d.ts` declarations; no TypeScript loader or bundler is needed. |
20
- | Peer deps | `effect` `^4.0.0`. |
21
24
 
22
25
  ## Quick start
23
26
 
@@ -354,7 +357,7 @@ None of these settings can be configured through the public API.
354
357
  - Load files with `URL` or `file://` strings. Bare strings are parsed as JSON or YAML text.
355
358
  - Input can be OpenAPI 3.0 or 3.1. Output is always bundled and upgraded OpenAPI 3.1 (`openapi: "3.1.0"` when upgraded from 3.0), with external refs moved into `components`.
356
359
  - Every error is a `Schema.SchemaError`. Read `error.message`.
357
- - Requires `effect` `^4.0.0` (peer dependency), ESM, and Node.js `>=22.19.0` or Bun. Ships compiled JavaScript. It does not run in browsers.
360
+ - Depends on `effect` `^4.0.0`. ESM only, and Node.js `>=22.19.0` or Bun. Ships compiled JavaScript. It does not run in browsers.
358
361
  - Next step in the toolchain: pass the result to `decode` from `@shipworthy/openapi-transformer` to get a `ParsedService` for code generators.
359
362
 
360
363
  ## License
package/dist/pipeline.js CHANGED
@@ -94,7 +94,7 @@ function normalizeSchema(value) {
94
94
  const record = asRecord(value);
95
95
  if (!record)
96
96
  throw bundleFailure("A schema position must contain an object or a boolean");
97
- const normalized = JsonSchema.fromSchemaOpenApi3_1(liftSiblings(record));
97
+ const normalized = JsonSchema.fromSchemaOpenApi3_1(record);
98
98
  // A schema $id is its own resource, so its $defs join the shared pool.
99
99
  // Nested $defs on a schema without $id stay on that schema.
100
100
  if (typeof record.$id === "string")
@@ -156,31 +156,6 @@ function rewriteDefName(schema, from, to) {
156
156
  for (const value of Object.values(record))
157
157
  rewriteDefName(value, from, to);
158
158
  }
159
- function liftSiblings(value) {
160
- if (Array.isArray(value))
161
- return value.map((item) => liftSiblings(item));
162
- const record = asRecord(value);
163
- if (!record)
164
- return value;
165
- const lifted = {};
166
- for (const [key, child] of Object.entries(record))
167
- lifted[key] = liftSiblings(child);
168
- if (typeof lifted.$ref !== "string")
169
- return lifted;
170
- const siblings = { ...lifted };
171
- delete siblings.$ref;
172
- // readOnly/writeOnly stay beside $ref so generators can wrap the reference itself.
173
- const flags = {};
174
- for (const key of ["readOnly", "writeOnly"]) {
175
- if (siblings[key] === true) {
176
- flags[key] = true;
177
- delete siblings[key];
178
- }
179
- }
180
- if (Object.keys(siblings).length === 0)
181
- return { $ref: lifted.$ref, ...flags };
182
- return { allOf: [{ $ref: lifted.$ref }, siblings], ...flags };
183
- }
184
159
  function recursiveNames(definitions) {
185
160
  const names = Object.keys(definitions);
186
161
  const dependencies = new Map(names.map((name) => [name, collectDefRefs(definitions[name])]));
@@ -246,30 +221,42 @@ function resolveValue(value, definitions, rootDefs, recursive, aliases, resolvin
246
221
  return value;
247
222
  if (typeof record.$ref === "string") {
248
223
  const key = referenceKey(record.$ref);
249
- if (!key)
250
- return record;
251
- if (!Object.hasOwn(definitions, key)) {
224
+ if (key && !Object.hasOwn(definitions, key)) {
252
225
  if (rootDefs.has(key)) {
253
226
  record.$ref = rootDefRef(key);
254
- return record;
255
227
  }
256
- throw missingPointerFailure(key, record.$ref, record.$ref);
228
+ else {
229
+ throw missingPointerFailure(key, record.$ref, record.$ref);
230
+ }
257
231
  }
258
- const final = finalName(key, definitions, aliases);
259
- const ref = joinPointer("#/$defs", final);
260
- if (recursive.has(final) || mode === "bundle") {
261
- if (recursive.has(final))
262
- hooks?.onCircular?.(pointer);
263
- record.$ref = ref;
264
- return record;
232
+ else if (key) {
233
+ const final = finalName(key, definitions, aliases);
234
+ const ref = joinPointer("#/$defs", final);
235
+ if (recursive.has(final) || mode === "bundle") {
236
+ if (recursive.has(final))
237
+ hooks?.onCircular?.(pointer);
238
+ record.$ref = ref;
239
+ }
240
+ else {
241
+ if (resolving.has(final))
242
+ throw circularAliasFailure(final);
243
+ resolving.add(final);
244
+ const resolved = resolveValue(structuredClone(definitions[final]), definitions, rootDefs, recursive, aliases, resolving, mode, hooks, ref);
245
+ resolving.delete(final);
246
+ const siblings = { ...record };
247
+ delete siblings.$ref;
248
+ const result = Object.keys(siblings).length === 0
249
+ ? resolved
250
+ : {
251
+ allOf: [
252
+ resolved,
253
+ resolveValue(siblings, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, pointer),
254
+ ],
255
+ };
256
+ hooks?.onDereference?.(pointer, result, record, "$ref");
257
+ return result;
258
+ }
265
259
  }
266
- if (resolving.has(final))
267
- throw circularAliasFailure(final);
268
- resolving.add(final);
269
- const resolved = resolveValue(structuredClone(definitions[final]), definitions, rootDefs, recursive, aliases, resolving, mode, hooks, ref);
270
- resolving.delete(final);
271
- hooks?.onDereference?.(pointer, resolved, record, "$ref");
272
- return resolved;
273
260
  }
274
261
  for (const [key, child] of Object.entries(record)) {
275
262
  record[key] = resolveValue(child, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, joinPointer(pointer, key));
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@shipworthy/openapi-parser",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "dependencies": {
8
- "@effect/platform-node": "^4.0.0"
9
- },
10
- "peerDependencies": {
8
+ "@effect/platform-node": "^4.0.0",
11
9
  "effect": "^4.0.0"
12
10
  },
13
11
  "description": "Parse, bundle, and upgrade OpenAPI documents with Effect schemas.",
@@ -37,7 +35,6 @@
37
35
  "@effect/vitest": "4.0.0",
38
36
  "@shipworthy/typescript-config": "0.0.1",
39
37
  "@types/node": "^24.10.3",
40
- "effect": "^4.0.0",
41
38
  "typescript": "^7.0.2",
42
39
  "vitest": "5.0.3"
43
40
  },