@zudojs/openapi 1.1.0 → 1.2.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/README.md CHANGED
@@ -101,9 +101,12 @@ manager.toUIResponse({ specUrl: "/openapi.json", assetsBaseUrl: "/vendor/swagger
101
101
  ```
102
102
 
103
103
  Caller-supplied text is escaped, and input that would break out of the page is
104
- refused rather than mangled: a `javascript:` or `vbscript:` URL throws, an
105
- empty `specUrl` throws, and `customCss` containing `</style>` throws — that
106
- sequence ends the style block and lets the rest be parsed as HTML.
104
+ refused rather than mangled: a `javascript:` or `vbscript:` URL throws (the
105
+ scheme is read after stripping the control characters browsers ignore, so
106
+ `java\nscript:` is caught too), a `data:` URL that is not an image throws
107
+ (the assets base lands in `<script src>`), an empty `specUrl` throws, and
108
+ `customCss` containing `</style>` throws — that sequence ends the style block
109
+ and lets the rest be parsed as HTML.
107
110
 
108
111
  ## Branding
109
112
 
@@ -180,9 +183,16 @@ produces
180
183
 
181
184
  Objects, arrays, enums, literals, unions, discriminated unions,
182
185
  intersections, records, tuples, sets, optionals, nullables, defaults,
183
- refinements, transforms, lazy schemas and the coercion wrappers are all
184
- converted, along with string and number constraints (`min`, `max`, `length`,
185
- `pattern`, `format`, `int`, `multipleOf`, `gt`, `lt`).
186
+ refinements, transforms, lazy schemas, bigints and the coercion wrappers are
187
+ all converted, along with string and number constraints (`min`, `max`,
188
+ `length`, `pattern`, `format`, `int`, `multipleOf`, `gt`, `lt`). Constraints
189
+ on a coercing schema (`coerce.number().int().min(1)`) are carried through.
190
+
191
+ A property is listed in `required` exactly when the object parser rejects
192
+ its absence: fields wrapped in `optional`, fields with a `default`, and
193
+ `any` / `unknown` fields are left out, and `.required()` forces every key
194
+ back on. A default supplied as a factory (`.default(() => new Date())`) is
195
+ invoked once and its value emitted.
186
196
 
187
197
  Anything that cannot be expressed exactly produces a **warning** rather than a
188
198
  silent `{}`:
@@ -259,7 +269,10 @@ The validator checks:
259
269
  - that path templates and `in: "path"` parameters agree in both directions —
260
270
  the classic "`{id}` is in the path but nowhere in `parameters`" mistake
261
271
  - that path parameters are marked required, and that no parameter is declared
262
- twice
272
+ twice in one list (an operation-level parameter may override a path-level
273
+ one with the same name and location)
274
+ - that no two paths are identical apart from their template parameter names
275
+ (`/users/{id}` next to `/users/{userId}`)
263
276
  - `operationId` uniqueness and length
264
277
  - that every `security` requirement names a scheme declared in
265
278
  `components.securitySchemes` — a typo there yields a document that _looks_
@@ -57,6 +57,8 @@ export declare class OpenAPIManager {
57
57
  private readonly cacheTtlMs;
58
58
  private readonly now;
59
59
  private readonly logo;
60
+ /** True when `branding` was a caller-supplied logo rather than the default. */
61
+ private readonly customLogo;
60
62
  private cachedDocument?;
61
63
  private cachedAt;
62
64
  /** Whether the cached document was produced by a validating generate. */
@@ -21,6 +21,8 @@ export class OpenAPIManager {
21
21
  cacheTtlMs;
22
22
  now;
23
23
  logo;
24
+ /** True when `branding` was a caller-supplied logo rather than the default. */
25
+ customLogo;
24
26
  cachedDocument;
25
27
  cachedAt = 0;
26
28
  /** Whether the cached document was produced by a validating generate. */
@@ -44,6 +46,8 @@ export class OpenAPIManager {
44
46
  : options.branding === true || options.branding === undefined
45
47
  ? zudoLogo()
46
48
  : options.branding;
49
+ this.customLogo =
50
+ typeof options.branding === "object" && options.branding !== null;
47
51
  if (options.info)
48
52
  this.registry.setInfo(options.info);
49
53
  for (const server of options.servers ?? [])
@@ -124,6 +128,11 @@ export class OpenAPIManager {
124
128
  * Safe to call repeatedly: routes are replaced rather than re-added.
125
129
  */
126
130
  generate(validate = false) {
131
+ // The scanner is the source of truth for routes. Re-setting on top of
132
+ // the previous route set kept routes that had since been removed or
133
+ // hidden, so `removeRoute()` had no effect once a document had been
134
+ // generated.
135
+ this.registry.clearRoutes();
127
136
  for (const route of this.scanner.scan()) {
128
137
  this.registry.setRoute(route);
129
138
  }
@@ -234,11 +243,16 @@ export class OpenAPIManager {
234
243
  ? `${info.title} · API reference`
235
244
  : undefined,
236
245
  ...options,
246
+ // Precedence: an explicit page logo, then the manager's `branding`
247
+ // (a custom logo is used as-is; `false` renders none), then the
248
+ // page's own default wordmark.
237
249
  logo: options.logo !== undefined
238
250
  ? options.logo
239
251
  : this.logo === undefined
240
252
  ? false
241
- : undefined,
253
+ : this.customLogo
254
+ ? this.logo
255
+ : undefined,
242
256
  });
243
257
  return Object.freeze({
244
258
  status: 200,
@@ -35,6 +35,15 @@ export declare class OpenAPIRegistryImpl implements OpenAPIRegistry {
35
35
  * `generate()` twice failed.
36
36
  */
37
37
  setRoute(route: OpenAPIRoute): void;
38
+ /** Removes a route. Returns whether one was registered. */
39
+ removeRoute(method: string, path: string): boolean;
40
+ /**
41
+ * Drops every registered route while keeping components, servers, tags
42
+ * and security. `OpenAPIManager.generate()` rebuilds the route set from
43
+ * its scanner on each call; without this a route removed from the
44
+ * scanner lived on in the registry and in every later document.
45
+ */
46
+ clearRoutes(): void;
38
47
  private static register;
39
48
  registerSchema(name: string, schema: OpenAPISchema): void;
40
49
  registerResponse(name: string, response: OpenAPIResponse): void;
@@ -77,6 +77,19 @@ export class OpenAPIRegistryImpl {
77
77
  }),
78
78
  });
79
79
  }
80
+ /** Removes a route. Returns whether one was registered. */
81
+ removeRoute(method, path) {
82
+ return this.routes.delete(OpenAPIRegistryImpl.routeKey({ method, path }));
83
+ }
84
+ /**
85
+ * Drops every registered route while keeping components, servers, tags
86
+ * and security. `OpenAPIManager.generate()` rebuilds the route set from
87
+ * its scanner on each call; without this a route removed from the
88
+ * scanner lived on in the registry and in every later document.
89
+ */
90
+ clearRoutes() {
91
+ this.routes.clear();
92
+ }
80
93
  static register(map, section, name, value) {
81
94
  if (map.has(name)) {
82
95
  throw new OpenAPIComponentConflictError(section, name);
@@ -14,14 +14,24 @@ import type { OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
14
14
  * array `_config.itemSchema`, `_config.min`, `_config.max`, `_config.length`
15
15
  * string `_config.min|max|length|pattern|format`
16
16
  * number `_config.min|max|int|gt|lt|multipleOf`
17
+ * coerce.string / coerce.number
18
+ * `_constraints` — the wrapped StringSchema / NumberSchema that
19
+ * carries the `_config` above
17
20
  * union `_schemas` intersection `_left` / `_right`
18
21
  * enum `_values` literal `_expected`
19
22
  * optional `_inner` nullable `_inner`
20
- * default `_inner`, `_defaultValue` refine `_inner`
21
- * transform `_base` lazy `_factory` / `_inner`
23
+ * default `_inner`, `_defaultValue` (a value or a factory function)
24
+ * refine `_inner`
25
+ * transform `_inner` (`schema.transform(...)`, TransformModifierSchema)
26
+ * or `_base` (the standalone TransformSchema class)
27
+ * lazy `_factory` / `_inner`
22
28
  * record `_keySchema`, `_valueSchema` tuple `_schemas`
23
29
  * map `_keySchema`, `_valueSchema` set `_valueSchema`
24
30
  * metadata `_metadata` (description, example, title, deprecated)
31
+ *
32
+ * Object parsing accepts a missing key when the field schema is one of
33
+ * `optional`, `default`, `any` or `unknown` (`ACCEPTS_UNDEFINED` in
34
+ * schemaObject.core.ts), so exactly those are left out of `required`.
25
35
  */
26
36
  export interface SchemaConversionResult {
27
37
  readonly schema: OpenAPISchema;
@@ -33,8 +33,50 @@ function isSchemaLike(value) {
33
33
  value !== null &&
34
34
  typeof value._type === "string");
35
35
  }
36
- function isOptionalSchema(value) {
37
- return isSchemaLike(value) && value._type === "optional";
36
+ /**
37
+ * Field schemas for which `ObjectSchema` accepts a missing key. Mirrors
38
+ * `ACCEPTS_UNDEFINED` in `@zudojs/schema`: a field with a default is filled
39
+ * in when absent, so documenting it as `required` publishes a contract
40
+ * stricter than the code that validates against it.
41
+ */
42
+ const ACCEPTS_MISSING_KEY = new Set([
43
+ "optional",
44
+ "default",
45
+ "any",
46
+ "unknown",
47
+ ]);
48
+ function acceptsMissingKey(value) {
49
+ return isSchemaLike(value) && ACCEPTS_MISSING_KEY.has(value._type);
50
+ }
51
+ /**
52
+ * The schema a `coerce.*` wrapper delegates its constraints to.
53
+ *
54
+ * `s.coerce.number().int().min(1)` keeps `int` and `min` on the wrapped
55
+ * `NumberSchema` under `_constraints`, not on the wrapper's own `_config`;
56
+ * reading the wrapper alone yields a bare `{ type: "number" }`.
57
+ */
58
+ function coercionTarget(schema) {
59
+ const constraints = schema["_constraints"];
60
+ return isSchemaLike(constraints) ? constraints : schema;
61
+ }
62
+ /**
63
+ * Resolves a `default` schema's value, which may be a factory function.
64
+ *
65
+ * A factory (`.default(() => new Date())`) is invoked once for the document.
66
+ * Emitting the function itself produces a `default` that `JSON.stringify`
67
+ * silently drops and the YAML serializer renders as source text.
68
+ */
69
+ function resolveDefaultValue(schema, state) {
70
+ const raw = schema["_defaultValue"];
71
+ if (typeof raw !== "function")
72
+ return raw;
73
+ try {
74
+ return raw();
75
+ }
76
+ catch (error) {
77
+ state.warnings.push(`A \`default\` factory threw and its value was omitted: ${error instanceof Error ? error.message : String(error)}`);
78
+ return undefined;
79
+ }
38
80
  }
39
81
  function extractMeta(schema) {
40
82
  const meta = schema._metadata;
@@ -225,14 +267,17 @@ function convertSchemaNode(schema, state) {
225
267
  const c = config(schema);
226
268
  switch (schema._type) {
227
269
  case "string":
228
- case "coerce.string":
229
270
  return convertString(schema, state);
271
+ case "coerce.string":
272
+ return convertString(coercionTarget(schema), state);
230
273
  case "number":
231
- case "coerce.number":
232
274
  return convertNumber(schema, state);
275
+ case "coerce.number":
276
+ return convertNumber(coercionTarget(schema), state);
233
277
  case "boolean":
234
278
  case "coerce.boolean":
235
279
  return { type: "boolean" };
280
+ case "bigint":
236
281
  case "coerce.bigint":
237
282
  return { type: "string", format: "int64" };
238
283
  case "null":
@@ -256,10 +301,11 @@ function convertSchemaNode(schema, state) {
256
301
  const required = [];
257
302
  for (const [key, value] of Object.entries(shape)) {
258
303
  defineProperty(properties, key, convertNode(value, state));
259
- // A field is required unless it is wrapped in `optional`. An explicit
304
+ // A field is required unless the object parser accepts its absence
305
+ // (`optional`, `default`, `any`, `unknown`). An explicit
260
306
  // `requiredKeys` set (from `.required()`) forces it back on.
261
307
  const forced = requiredKeys instanceof Set && requiredKeys.has(key);
262
- if (forced || !isOptionalSchema(value))
308
+ if (forced || !acceptsMissingKey(value))
263
309
  required.push(key);
264
310
  }
265
311
  const unknownKeys = c["unknownKeys"];
@@ -403,7 +449,7 @@ function convertSchemaNode(schema, state) {
403
449
  return applyNullable(convertInner(schema["_inner"], state, "nullable"), state.version);
404
450
  case "default": {
405
451
  const inner = convertInner(schema["_inner"], state, "default");
406
- const defaultValue = schema["_defaultValue"];
452
+ const defaultValue = resolveDefaultValue(schema, state);
407
453
  return defaultValue === undefined
408
454
  ? inner
409
455
  : { ...inner, default: defaultValue };
@@ -413,7 +459,10 @@ function convertSchemaNode(schema, state) {
413
459
  // inner shape is still the right description of the data.
414
460
  return convertInner(schema["_inner"], state, "refine");
415
461
  case "transform":
416
- return convertInner(schema["_base"], state, "transform");
462
+ // `schema.transform(fn)` / `s.transform(schema, fn)` build a
463
+ // TransformModifierSchema, whose source is `_inner`; the standalone
464
+ // TransformSchema class names it `_base`.
465
+ return convertInner(schema["_inner"] ?? schema["_base"], state, "transform");
417
466
  case "lazy": {
418
467
  const resolved = schema["_inner"] ?? resolveLazy(schema, state);
419
468
  if (resolved === undefined)
@@ -31,8 +31,30 @@ const YAML_RESERVED = new Set([
31
31
  ]);
32
32
  /** A bare (unquoted) YAML scalar may not start with these. */
33
33
  const YAML_UNSAFE_START = /^[-?:,[\]{}#&*!|>'"%@`\s]/;
34
- const YAML_UNSAFE_ANYWHERE = /[:#\n\r\t]|: |\s#/;
35
- const YAML_NUMERIC = /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/;
34
+ // Control characters have no plain-scalar form; `JSON.stringify` escapes them.
35
+ const YAML_UNSAFE_ANYWHERE = /[:#\u0000-\u001f\u007f]|: |\s#/;
36
+ const YAML_NUMERIC = /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/;
37
+ /**
38
+ * Other strings a YAML parser resolves to a non-string: the infinity and
39
+ * not-a-number forms, hexadecimal, octal and binary integers (1.1 and 1.2
40
+ * core schema), digit groups with `_`, sexagesimal numbers, timestamps
41
+ * and dates (1.1), the `=` value key and the `<<` merge key. A `version:
42
+ * 2024-01-01` or an enum value of `0x1F` came back from the parser as a
43
+ * date or the number 31.
44
+ */
45
+ const YAML_SPECIAL_SCALARS = [
46
+ /^[-+]?\.(?:inf|nan)$/i,
47
+ /^[-+]?0x[0-9a-f_]+$/i,
48
+ /^[-+]?0o?[0-7_]+$/i,
49
+ /^[-+]?0b[01_]+$/i,
50
+ /^[-+]?[0-9][0-9_]*(?:\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?$/,
51
+ /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$/,
52
+ /^\d{4}-\d{1,2}-\d{1,2}(?:[Tt ]|$)/,
53
+ /^(?:=|<<)$/,
54
+ ];
55
+ function isSpecialScalar(value) {
56
+ return YAML_SPECIAL_SCALARS.some((pattern) => pattern.test(value));
57
+ }
36
58
  function quoteYamlString(value) {
37
59
  // Double quotes with JSON escaping is always valid YAML and needs no
38
60
  // decision about block scalars or line folding.
@@ -43,6 +65,7 @@ function yamlScalar(value) {
43
65
  YAML_UNSAFE_START.test(value) ||
44
66
  YAML_UNSAFE_ANYWHERE.test(value) ||
45
67
  YAML_NUMERIC.test(value) ||
68
+ isSpecialScalar(value) ||
46
69
  value !== value.trim()) {
47
70
  return quoteYamlString(value);
48
71
  }
@@ -38,11 +38,25 @@ function jsLiteral(value) {
38
38
  .replace(/\u2028/g, "\\u2028")
39
39
  .replace(/\u2029/g, "\\u2029");
40
40
  }
41
- /** Rejects URLs that could execute script when placed in `src`/`href`. */
41
+ /**
42
+ * Rejects URLs that could execute script when placed in `src`/`href`.
43
+ *
44
+ * The scheme is read after stripping ASCII control characters, because the
45
+ * URL parser browsers apply does the same: `java\nscript:alert(1)` is a
46
+ * `javascript:` URL to every browser and nothing to a regex that only
47
+ * looks at the string as written. `data:` is accepted for images only —
48
+ * the assets base is interpolated into `<script src>`, where a
49
+ * `data:text/javascript,` base would run inline.
50
+ */
42
51
  function safeUrl(value) {
43
52
  const trimmed = value.trim();
44
- if (/^\s*(javascript|vbscript):/i.test(trimmed)) {
45
- throw new TypeError(`Refusing to render a "${trimmed.split(":")[0]}:" URL`);
53
+ const normalized = trimmed.replace(/[\u0000-\u0020\u007f]/g, "");
54
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(normalized)?.[1]?.toLowerCase();
55
+ if (scheme === "javascript" || scheme === "vbscript") {
56
+ throw new TypeError(`Refusing to render a "${scheme}:" URL`);
57
+ }
58
+ if (scheme === "data" && !/^data:image\//i.test(normalized)) {
59
+ throw new TypeError('Refusing to render a non-image "data:" URL; only data:image/* is allowed.');
46
60
  }
47
61
  // Attributes are always double-quoted here, so a single quote (common in
48
62
  // data URIs) can stay as-is.
@@ -118,14 +132,14 @@ export function renderOpenAPIUI(options) {
118
132
  const title = options.title ?? DEFAULT_TITLE;
119
133
  const renderer = options.renderer ?? "swagger";
120
134
  if (renderer === "redoc") {
121
- const base = (options.assetsBaseUrl ?? REDOC_ASSETS).replace(/\/+$/, "");
135
+ const base = (options.assetsBaseUrl ?? REDOC_ASSETS).replace(/(?<!\/)\/+$/, "");
122
136
  return (head(options, title, "") +
123
137
  `<body>${header(options, title)}` +
124
138
  `<redoc spec-url="${safeUrl(options.specUrl)}" hide-hostname></redoc>` +
125
139
  `<script src="${safeUrl(base)}/redoc.standalone.js"></script>` +
126
140
  `${footer()}</body></html>`);
127
141
  }
128
- const base = (options.assetsBaseUrl ?? SWAGGER_ASSETS).replace(/\/+$/, "");
142
+ const base = (options.assetsBaseUrl ?? SWAGGER_ASSETS).replace(/(?<!\/)\/+$/, "");
129
143
  const config = {
130
144
  url: options.specUrl,
131
145
  dom_id: "#zudo-openapi",
@@ -1,5 +1,5 @@
1
1
  import { OpenAPIValidationError } from "../openApiErrors/openApiError.types.js";
2
- import { MAX_OPERATION_ID_LENGTH, RESPONSE_KEY_PATTERN, SUPPORTED_OPENAPI_VERSIONS, } from "../openApiConstants/openApiConstants.core.js";
2
+ import { MAX_OPERATION_ID_LENGTH, PATH_TEMPLATE_PARAMETER, RESPONSE_KEY_PATTERN, SUPPORTED_OPENAPI_VERSIONS, } from "../openApiConstants/openApiConstants.core.js";
3
3
  import { extractPathParameters } from "../openApiRouting/routeConverter.core.js";
4
4
  import { unescapeJsonPointerSegment } from "../openApiSchema/references.core.js";
5
5
  const OPERATIONS = [
@@ -77,9 +77,22 @@ export class OpenAPIValidatorImpl {
77
77
  }
78
78
  }
79
79
  validatePaths(document, collector) {
80
+ /** Paths seen so far, keyed by their template with parameter names erased. */
81
+ const shapes = new Map();
80
82
  for (const [path, pathItem] of Object.entries(document.paths ?? {})) {
81
83
  if (!pathItem)
82
84
  continue;
85
+ // "/users/{id}" and "/users/{userId}" have the same hierarchy and
86
+ // differ only in template names; the specification forbids both
87
+ // existing, since a router cannot tell them apart.
88
+ const shape = path.replace(PATH_TEMPLATE_PARAMETER, "{}");
89
+ const twin = shapes.get(shape);
90
+ if (twin !== undefined && twin !== path) {
91
+ error(collector, ["paths", path], `Path "${path}" is identical to "${twin}" apart from its template parameter names; such paths must not both exist.`);
92
+ }
93
+ else {
94
+ shapes.set(shape, path);
95
+ }
83
96
  if (!path.startsWith("/")) {
84
97
  error(collector, ["paths", path], `Path "${path}" must start with "/".`);
85
98
  }
@@ -121,28 +134,33 @@ export class OpenAPIValidatorImpl {
121
134
  operation.operationId.length > MAX_OPERATION_ID_LENGTH) {
122
135
  error(collector, [...base, "operationId"], `operationId "${operation.operationId}" exceeds the maximum length of ${MAX_OPERATION_ID_LENGTH}.`);
123
136
  }
124
- const parameters = [
125
- ...(pathItem.parameters ?? []),
126
- ...(operation.parameters ?? []),
127
- ];
128
- const seen = new Set();
129
- for (const parameter of parameters) {
130
- if (!parameter.name) {
131
- error(collector, [...base, "parameters"], "Every parameter must have a name.");
132
- continue;
133
- }
134
- const key = `${parameter.in}:${parameter.name}`;
135
- if (seen.has(key)) {
136
- error(collector, [...base, "parameters"], `Duplicate parameter "${parameter.name}" in "${parameter.in}".`);
137
- }
138
- seen.add(key);
139
- if (parameter.in === "path" && parameter.required !== true) {
140
- error(collector, [...base, "parameters"], `Path parameter "${parameter.name}" must be required.`);
137
+ // Uniqueness is per list: the specification forbids duplicates within
138
+ // the path item's list and within the operation's list, while an
139
+ // operation-level parameter *overrides* a path-level one with the same
140
+ // name and location. Treating that override as a duplicate rejected
141
+ // legal documents.
142
+ const parameters = new Map();
143
+ for (const list of [pathItem.parameters, operation.parameters]) {
144
+ const seen = new Set();
145
+ for (const parameter of list ?? []) {
146
+ if (!parameter.name) {
147
+ error(collector, [...base, "parameters"], "Every parameter must have a name.");
148
+ continue;
149
+ }
150
+ const key = `${parameter.in}:${parameter.name}`;
151
+ if (seen.has(key)) {
152
+ error(collector, [...base, "parameters"], `Duplicate parameter "${parameter.name}" in "${parameter.in}".`);
153
+ }
154
+ seen.add(key);
155
+ parameters.set(key, parameter);
156
+ if (parameter.in === "path" && parameter.required !== true) {
157
+ error(collector, [...base, "parameters"], `Path parameter "${parameter.name}" must be required.`);
158
+ }
141
159
  }
142
160
  }
143
161
  // The classic OpenAPI mistake: a templated path with no matching
144
162
  // parameter, or a path parameter that no template slot refers to.
145
- const declaredPathParameters = new Set(parameters
163
+ const declaredPathParameters = new Set([...parameters.values()]
146
164
  .filter((parameter) => parameter.in === "path")
147
165
  .map((parameter) => parameter.name));
148
166
  for (const name of templateParameters) {
@@ -309,7 +327,10 @@ function resolvePointer(document, ref) {
309
327
  current = current[index];
310
328
  continue;
311
329
  }
312
- if (!(segment in current))
330
+ // Own properties only: `in` walks the prototype chain, so a reference
331
+ // to `#/components/schemas/constructor` resolved to
332
+ // `Object.prototype.constructor` and validated as present.
333
+ if (!Object.prototype.hasOwnProperty.call(current, segment))
313
334
  return false;
314
335
  current = current[segment];
315
336
  }
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@zudojs/openapi",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "OpenAPI 3.0 and 3.1 specification generation, validation, and serialization for Zudojs applications.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "module": "./dist/index.js",
@@ -22,7 +26,7 @@
22
26
  "!dist/.tsbuildinfo"
23
27
  ],
24
28
  "dependencies": {
25
- "@zudojs/errors": "1.0.0"
29
+ "@zudojs/errors": "1.0.1"
26
30
  },
27
31
  "devDependencies": {
28
32
  "@types/node": "^26.4.1",