@shipworthy/openapi-parser 0.0.2 → 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/dist/parsers.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect, Predicate } from "effect";
1
+ import { Effect, Predicate, Schema, String as Str } from "effect";
2
2
  import { ParseError, ParserFailure } from "./errors.js";
3
3
  import { promisePlugin } from "./plugins.js";
4
4
  import { extensionOf } from "./url.js";
@@ -7,19 +7,18 @@ function decodeData(data) {
7
7
  if (data === undefined)
8
8
  return "";
9
9
  const text = typeof data === "string" ? data : new TextDecoder().decode(data);
10
- return text.replace(/^\uFEFF/, "");
10
+ return Str.replace(/^\uFEFF/, "")(text);
11
11
  }
12
+ const decodeJsonText = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
12
13
  function parseJsonText(text) {
13
- if (text.trim().length === 0)
14
- return null;
15
- return JSON.parse(text);
14
+ return Str.isEmpty(Str.trim(text)) ? Effect.succeed(null) : decodeJsonText(text);
16
15
  }
17
16
  export const parseText = Effect.fnUntraced(function* (source) {
18
17
  const text = decodeData(source);
19
- const first = text.trimStart()[0];
18
+ const first = Str.trimStart(text)[0];
20
19
  const yaml = () => Effect.try({ try: () => parseYamlDocument(text), catch: parseFailure });
21
20
  if (first === "{" || first === "[") {
22
- return yield* Effect.try({ try: () => parseJsonText(text), catch: parseFailure }).pipe(Effect.catch(yaml));
21
+ return yield* parseJsonText(text).pipe(Effect.catch(yaml));
23
22
  }
24
23
  return yield* yaml();
25
24
  });
@@ -45,20 +44,10 @@ function jsonParser() {
45
44
  order: 100,
46
45
  can: (file) => extensionOf(file.url) === ".json" || extensionOf(file.url) === "",
47
46
  run(file) {
48
- return Effect.try({
49
- try: () => parseJson(file),
50
- catch: (cause) => new ParserFailure({
51
- message: cause instanceof Error ? cause.message : "Parser Error",
52
- source: file.url,
53
- cause,
54
- }),
55
- });
47
+ return parseJsonText(decodeData(file.data)).pipe(Effect.mapError((cause) => new ParserFailure({ message: cause.message, source: file.url, cause })));
56
48
  },
57
49
  };
58
50
  }
59
- function parseJson(file) {
60
- return parseJsonText(decodeData(file.data));
61
- }
62
51
  function yamlParser() {
63
52
  return {
64
53
  order: 200,
package/dist/pipeline.js CHANGED
@@ -1,4 +1,4 @@
1
- import { JsonPointer, JsonSchema, Predicate } from "effect";
1
+ import { Array as Arr, Formatter, JsonPointer, JsonSchema, Predicate, Record as Rec, String as Str, } from "effect";
2
2
  import { getAt, joinPointer } from "./pointer.js";
3
3
  import { componentSchemas, schemaSlots } from "./upgraders/3.0-to-3.1.js";
4
4
  import { bundleFailure, circularAliasFailure, invalidPointerFailure, missingPointerFailure, } from "./errors.js";
@@ -37,7 +37,7 @@ export function runSchemaPipeline(document, mode, hooks) {
37
37
  }
38
38
  const others = schemaSlots(document, false);
39
39
  const slotSchemas = [];
40
- const booleanSlots = others.map((slot) => {
40
+ const booleanSlots = Arr.map(others, (slot) => {
41
41
  const value = slot.parent[slot.key];
42
42
  return typeof value === "boolean" ? value : undefined;
43
43
  });
@@ -48,10 +48,10 @@ export function runSchemaPipeline(document, mode, hooks) {
48
48
  slotSchemas.push(normalized.schema);
49
49
  }
50
50
  const recursive = recursiveNames(definitions);
51
- const rootDefs = new Set(Object.keys(asRecord(document.$defs) ?? {}));
51
+ const rootDefs = new Set(Rec.keys(asRecord(document.$defs) ?? {}));
52
52
  const aliases = new Map();
53
53
  const resolving = new Set();
54
- for (const name of Object.keys(definitions)) {
54
+ for (const name of Rec.keys(definitions)) {
55
55
  definitions[name] = resolveValue(definitions[name], definitions, rootDefs, recursive, aliases, resolving, mode, hooks, `#/$defs/${name}`);
56
56
  }
57
57
  for (let index = 0; index < slotSchemas.length; index++) {
@@ -61,9 +61,9 @@ export function runSchemaPipeline(document, mode, hooks) {
61
61
  slot.parent[slot.key] = slotSchemas[index];
62
62
  }
63
63
  const parked = [];
64
- for (const [name, schema] of Object.entries(definitions))
64
+ for (const [name, schema] of Rec.toEntries(definitions))
65
65
  park(schema, "definition", name, [], parked);
66
- slotSchemas.forEach((schema, index) => park(schema, "schema", String(index), [], parked));
66
+ Arr.forEach(slotSchemas, (schema, index) => park(schema, "schema", String(index), [], parked));
67
67
  const schemas = (slotSchemas.length > 0 ? slotSchemas : [{}]);
68
68
  const converted = JsonSchema.toMultiDocumentOpenApi3_1({
69
69
  dialect: "draft-2020-12",
@@ -79,7 +79,7 @@ export function runSchemaPipeline(document, mode, hooks) {
79
79
  if (value !== undefined)
80
80
  slot.parent[slot.key] = value;
81
81
  }
82
- others.forEach((slot, index) => {
82
+ Arr.forEach(others, (slot, index) => {
83
83
  slot.parent[slot.key] = booleanSlots[index] ?? converted.schemas[index];
84
84
  });
85
85
  rewriteLeftoverDefinitionRefs(document);
@@ -100,20 +100,20 @@ function normalizeSchema(value) {
100
100
  if (typeof record.$id === "string")
101
101
  return normalized;
102
102
  const extracted = asRecord(normalized.definitions);
103
- if (!extracted || Object.keys(extracted).length === 0)
103
+ if (!extracted || Rec.isEmptyRecord(extracted))
104
104
  return normalized;
105
105
  const schema = asRecord(normalized.schema) ?? {};
106
106
  schema.$defs = { ...asRecord(schema.$defs), ...extracted };
107
107
  return { dialect: normalized.dialect, schema, definitions: {} };
108
108
  }
109
109
  function mergeDefinitions(pool, extra, owner, order) {
110
- for (const [name, schema] of Object.entries(extra)) {
111
- if (!Object.hasOwn(pool, name)) {
110
+ for (const [name, schema] of Rec.toEntries(extra)) {
111
+ if (!Rec.has(pool, name)) {
112
112
  pool[name] = schema;
113
113
  rememberDefinition(order, name);
114
114
  continue;
115
115
  }
116
- if (JSON.stringify(pool[name]) === JSON.stringify(schema))
116
+ if (Formatter.formatJson(pool[name]) === Formatter.formatJson(schema))
117
117
  continue;
118
118
  const unique = uniqueName(pool, name);
119
119
  pool[unique] = schema;
@@ -132,18 +132,18 @@ function orderDefinitions(definitions, order) {
132
132
  return definitions;
133
133
  const output = {};
134
134
  for (const name of order) {
135
- if (Object.hasOwn(source, name))
135
+ if (Rec.has(source, name))
136
136
  output[name] = source[name];
137
137
  }
138
- for (const name of Object.keys(source)) {
139
- if (!Object.hasOwn(output, name))
138
+ for (const name of Rec.keys(source)) {
139
+ if (!Rec.has(output, name))
140
140
  output[name] = source[name];
141
141
  }
142
142
  return output;
143
143
  }
144
144
  function uniqueName(pool, name) {
145
145
  let suffix = 2;
146
- while (Object.hasOwn(pool, `${name}_${suffix}`))
146
+ while (Rec.has(pool, `${name}_${suffix}`))
147
147
  suffix += 1;
148
148
  return `${name}_${suffix}`;
149
149
  }
@@ -153,12 +153,12 @@ function rewriteDefName(schema, from, to) {
153
153
  return;
154
154
  if (record.$ref === `#/$defs/${from}`)
155
155
  record.$ref = `#/$defs/${to}`;
156
- for (const value of Object.values(record))
156
+ for (const value of Rec.values(record))
157
157
  rewriteDefName(value, from, to);
158
158
  }
159
159
  function recursiveNames(definitions) {
160
- const names = Object.keys(definitions);
161
- const dependencies = new Map(names.map((name) => [name, collectDefRefs(definitions[name])]));
160
+ const names = Rec.keys(definitions);
161
+ const dependencies = new Map(Arr.map(names, (name) => [name, collectDefRefs(definitions[name])]));
162
162
  const recursive = new Set();
163
163
  const state = new Map();
164
164
  const stack = [];
@@ -167,7 +167,7 @@ function recursiveNames(definitions) {
167
167
  if (current === 1) {
168
168
  const start = stack.indexOf(name);
169
169
  const cycle = stack.slice(start);
170
- if (cycle.every((item) => isPureRef(definitions[item]))) {
170
+ if (Arr.every(cycle, (item) => isPureRef(definitions[item]))) {
171
171
  throw circularAliasFailure(name);
172
172
  }
173
173
  for (const item of cycle)
@@ -190,27 +190,28 @@ function recursiveNames(definitions) {
190
190
  return recursive;
191
191
  }
192
192
  function collectDefRefs(value, found = new Set(), seen = new Set()) {
193
- if (typeof value !== "object" || value === null || seen.has(value))
193
+ if (!Predicate.isObjectOrArray(value) || seen.has(value))
194
194
  return found;
195
195
  seen.add(value);
196
- if (Array.isArray(value)) {
196
+ if (Arr.isArray(value)) {
197
197
  for (const item of value)
198
198
  collectDefRefs(item, found, seen);
199
199
  return found;
200
200
  }
201
- const ref = value.$ref;
201
+ const record = value;
202
+ const ref = record.$ref;
202
203
  if (typeof ref === "string") {
203
204
  const key = referenceKey(ref);
204
205
  if (key)
205
206
  found.add(key);
206
207
  }
207
- for (const child of Object.values(value))
208
+ for (const child of Rec.values(record))
208
209
  collectDefRefs(child, found, seen);
209
210
  return found;
210
211
  }
211
212
  const rootDefRef = (name) => `#/root-defs/${JsonPointer.escapeToken(name)}`;
212
213
  function resolveValue(value, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, pointer) {
213
- if (Array.isArray(value)) {
214
+ if (Arr.isArray(value)) {
214
215
  for (let index = 0; index < value.length; index++) {
215
216
  value[index] = resolveValue(value[index], definitions, rootDefs, recursive, aliases, resolving, mode, hooks, joinPointer(pointer, String(index)));
216
217
  }
@@ -221,7 +222,7 @@ function resolveValue(value, definitions, rootDefs, recursive, aliases, resolvin
221
222
  return value;
222
223
  if (typeof record.$ref === "string") {
223
224
  const key = referenceKey(record.$ref);
224
- if (key && !Object.hasOwn(definitions, key)) {
225
+ if (key && !Rec.has(definitions, key)) {
225
226
  if (rootDefs.has(key)) {
226
227
  record.$ref = rootDefRef(key);
227
228
  }
@@ -245,7 +246,7 @@ function resolveValue(value, definitions, rootDefs, recursive, aliases, resolvin
245
246
  resolving.delete(final);
246
247
  const siblings = { ...record };
247
248
  delete siblings.$ref;
248
- const result = Object.keys(siblings).length === 0
249
+ const result = Rec.isEmptyRecord(siblings)
249
250
  ? resolved
250
251
  : {
251
252
  allOf: [
@@ -258,7 +259,7 @@ function resolveValue(value, definitions, rootDefs, recursive, aliases, resolvin
258
259
  }
259
260
  }
260
261
  }
261
- for (const [key, child] of Object.entries(record)) {
262
+ for (const [key, child] of Rec.toEntries(record)) {
262
263
  record[key] = resolveValue(child, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, joinPointer(pointer, key));
263
264
  }
264
265
  return record;
@@ -274,7 +275,7 @@ function finalName(name, definitions, aliases) {
274
275
  const schema = definitions[name];
275
276
  if (isPureRef(schema)) {
276
277
  const key = referenceKey(schema.$ref);
277
- if (key && Object.hasOwn(definitions, key)) {
278
+ if (key && Rec.has(definitions, key)) {
278
279
  const resolved = finalName(key, definitions, aliases);
279
280
  aliases.set(name, resolved);
280
281
  return resolved;
@@ -285,10 +286,10 @@ function finalName(name, definitions, aliases) {
285
286
  }
286
287
  function isPureRef(value) {
287
288
  const record = asRecord(value);
288
- return Boolean(record && typeof record.$ref === "string" && Object.keys(record).length === 1);
289
+ return Boolean(record && typeof record.$ref === "string" && Rec.size(record) === 1);
289
290
  }
290
291
  function referenceKey(ref) {
291
- if (!ref.startsWith("#"))
292
+ if (!Str.startsWith("#")(ref))
292
293
  return undefined;
293
294
  const tokens = JsonPointer.parseUriFragment(ref);
294
295
  if (tokens === undefined)
@@ -300,7 +301,7 @@ function park(value, scope, id, pointer, saved) {
300
301
  if (!record)
301
302
  return;
302
303
  for (const key of ANNOTATIONS) {
303
- if (!Object.hasOwn(record, key))
304
+ if (!Rec.has(record, key))
304
305
  continue;
305
306
  saved.push({ scope, id, pointer: [...pointer], key, value: record[key] });
306
307
  delete record[key];
@@ -308,15 +309,16 @@ function park(value, scope, id, pointer, saved) {
308
309
  for (const key of NESTED_SINGLE)
309
310
  park(record[key], scope, id, [...pointer, key], saved);
310
311
  for (const key of NESTED_ARRAY) {
311
- if (!Array.isArray(record[key]))
312
+ const items = record[key];
313
+ if (!Arr.isArray(items))
312
314
  continue;
313
- record[key].forEach((item, index) => park(item, scope, id, [...pointer, key, String(index)], saved));
315
+ Arr.forEach(items, (item, index) => park(item, scope, id, [...pointer, key, String(index)], saved));
314
316
  }
315
317
  for (const key of NESTED_MAP) {
316
318
  const map = asRecord(record[key]);
317
319
  if (!map)
318
320
  continue;
319
- for (const child of Object.keys(map))
321
+ for (const child of Rec.keys(map))
320
322
  park(map[child], scope, id, [...pointer, key, child], saved);
321
323
  }
322
324
  }
@@ -334,7 +336,7 @@ function restore(converted, saved) {
334
336
  function descend(value, pointer) {
335
337
  let current = value;
336
338
  for (const token of pointer) {
337
- if (Array.isArray(current)) {
339
+ if (Arr.isArray(current)) {
338
340
  current = current[Number(token)];
339
341
  continue;
340
342
  }
@@ -347,7 +349,7 @@ function descend(value, pointer) {
347
349
  }
348
350
  function restoreRootDefs(document) {
349
351
  const visit = (value) => {
350
- if (Array.isArray(value)) {
352
+ if (Arr.isArray(value)) {
351
353
  for (const item of value)
352
354
  visit(item);
353
355
  return;
@@ -355,19 +357,19 @@ function restoreRootDefs(document) {
355
357
  const record = asRecord(value);
356
358
  if (!record)
357
359
  return;
358
- if (typeof record.$ref === "string" && record.$ref.startsWith("#/root-defs/")) {
359
- const name = record.$ref.slice("#/root-defs/".length);
360
+ if (typeof record.$ref === "string" && Str.startsWith("#/root-defs/")(record.$ref)) {
361
+ const name = Str.slice("#/root-defs/".length)(record.$ref);
360
362
  record.$ref = joinPointer("#/$defs", JsonPointer.unescapeToken(name));
361
363
  }
362
- for (const child of Object.values(record))
364
+ for (const child of Rec.values(record))
363
365
  visit(child);
364
366
  };
365
367
  visit(document);
366
368
  }
367
369
  function rewriteLeftoverDefinitionRefs(document) {
368
- const names = new Set(Object.keys(asRecord(asRecord(document.components)?.schemas) ?? {}));
370
+ const names = new Set(Rec.keys(asRecord(asRecord(document.components)?.schemas) ?? {}));
369
371
  const visit = (value) => {
370
- if (Array.isArray(value)) {
372
+ if (Arr.isArray(value)) {
371
373
  for (const item of value)
372
374
  visit(item);
373
375
  return;
@@ -375,14 +377,14 @@ function rewriteLeftoverDefinitionRefs(document) {
375
377
  const record = asRecord(value);
376
378
  if (!record)
377
379
  return;
378
- if (typeof record.$ref === "string" && record.$ref.startsWith("#/$defs/")) {
380
+ if (typeof record.$ref === "string" && Str.startsWith("#/$defs/")(record.$ref)) {
379
381
  const tokens = JsonPointer.parseUriFragment(record.$ref);
380
382
  const name = tokens?.[0] === "$defs" ? tokens[1] : undefined;
381
383
  if (name && names.has(name)) {
382
384
  record.$ref = joinPointer("#/components/schemas", [name, ...(tokens?.slice(2) ?? [])]);
383
385
  }
384
386
  }
385
- for (const child of Object.values(record))
387
+ for (const child of Rec.values(record))
386
388
  visit(child);
387
389
  };
388
390
  visit(document);
@@ -390,7 +392,7 @@ function rewriteLeftoverDefinitionRefs(document) {
390
392
  export function inlineRemaining(document, recursive, hooks) {
391
393
  const visiting = new Set();
392
394
  const walk = (value, parent, key, pointer = "#") => {
393
- if (Array.isArray(value)) {
395
+ if (Arr.isArray(value)) {
394
396
  for (let index = 0; index < value.length; index++) {
395
397
  walk(value[index], value, String(index), joinPointer(pointer, String(index)));
396
398
  }
@@ -399,7 +401,7 @@ export function inlineRemaining(document, recursive, hooks) {
399
401
  const record = asRecord(value);
400
402
  if (!record)
401
403
  return;
402
- if (typeof record.$ref === "string" && record.$ref.startsWith("#") && parent && key) {
404
+ if (typeof record.$ref === "string" && Str.startsWith("#")(record.$ref) && parent && key) {
403
405
  const ref = record.$ref;
404
406
  const name = componentSchemaName(ref);
405
407
  if (name && recursive.has(name)) {
@@ -419,7 +421,7 @@ export function inlineRemaining(document, recursive, hooks) {
419
421
  hooks?.onDereference?.(pointer, copy, parent, key);
420
422
  return;
421
423
  }
422
- for (const [childKey, child] of Object.entries(record)) {
424
+ for (const [childKey, child] of Rec.toEntries(record)) {
423
425
  walk(child, record, childKey, joinPointer(pointer, childKey));
424
426
  }
425
427
  };
package/dist/pointer.d.ts CHANGED
@@ -1,6 +1,9 @@
1
+ import { Option } from "effect";
1
2
  export declare function parsePointer(pointer: string, source?: string): string[];
2
3
  /** Join pointer tokens without percent-encoding, so `$` stays literal. */
3
4
  export declare function joinPointer(base: string, tokens: string | readonly string[]): string;
5
+ /** Reads the member or array element that a JSON Pointer token names (RFC 6901). */
6
+ export declare function childAt(value: unknown, token: string): Option.Option<unknown>;
4
7
  export declare function getAt(root: unknown, pointer: string, source?: string): unknown;
5
8
  export declare function setAt(root: object, pointer: string, value: unknown): void;
6
9
  /** Copy enumerable data, including cycles, and drop non-enumerable parser symbols. */
package/dist/pointer.js CHANGED
@@ -1,11 +1,11 @@
1
- import { JsonPointer } from "effect";
1
+ import { Array as Arr, JsonPointer, Option, pipe, Predicate, Record as Rec, String as Str, } from "effect";
2
2
  import { invalidPointerFailure, missingPointerFailure } from "./errors.js";
3
3
  const unsafeTokens = new Set(["__proto__", "constructor", "prototype"]);
4
4
  export function parsePointer(pointer, source = pointer) {
5
- const hash = pointer.includes("#") ? pointer.slice(pointer.indexOf("#")) : pointer;
5
+ const hash = pipe(pointer, Str.indexOf("#"), Option.match({ onNone: () => pointer, onSome: (index) => Str.slice(index)(pointer) }));
6
6
  if (hash === "" || hash === "#")
7
7
  return [];
8
- const fragment = hash.startsWith("#") ? hash : `#${hash}`;
8
+ const fragment = Str.startsWith("#")(hash) ? hash : `#${hash}`;
9
9
  const tokens = JsonPointer.parseUriFragment(fragment) ?? splitPointer(fragment);
10
10
  if (tokens === undefined)
11
11
  throw invalidPointerFailure(fragment, source);
@@ -13,46 +13,49 @@ export function parsePointer(pointer, source = pointer) {
13
13
  }
14
14
  /** Effect's URI-fragment parser rejects spaces. JSON Pointer tokens may contain them. */
15
15
  function splitPointer(fragment) {
16
- const body = fragment.startsWith("#") ? fragment.slice(1) : fragment;
16
+ const body = Str.startsWith("#")(fragment) ? Str.slice(1)(fragment) : fragment;
17
17
  if (body === "")
18
18
  return [];
19
- if (!body.startsWith("/"))
19
+ if (!Str.startsWith("/")(body))
20
20
  return undefined;
21
- return body
22
- .slice(1)
23
- .split("/")
24
- .map((token) => token.replace(/~1/g, "/").replace(/~0/g, "~"));
21
+ return pipe(body, Str.slice(1), Str.split("/"), Arr.map((token) => pipe(token, Str.replace(/~1/g, "/"), Str.replace(/~0/g, "~"))));
25
22
  }
26
23
  /** Join pointer tokens without percent-encoding, so `$` stays literal. */
27
24
  export function joinPointer(base, tokens) {
28
25
  const parts = typeof tokens === "string" ? [tokens] : tokens;
29
- let pointer = base.includes("#") ? base : `${base}#`;
26
+ let pointer = Str.includes("#")(base) ? base : `${base}#`;
30
27
  for (const token of parts)
31
28
  pointer += `/${JsonPointer.escapeToken(token)}`;
32
29
  return pointer;
33
30
  }
31
+ const arrayIndex = /^(?:0|[1-9]\d*)$/;
32
+ /** Reads the member or array element that a JSON Pointer token names (RFC 6901). */
33
+ export function childAt(value, token) {
34
+ if (Arr.isArray(value)) {
35
+ return arrayIndex.test(token) ? Arr.get(value, Number(token)) : Option.none();
36
+ }
37
+ return Predicate.isObject(value) ? Rec.get(value, token) : Option.none();
38
+ }
34
39
  export function getAt(root, pointer, source = pointer) {
35
40
  const tokens = parsePointer(pointer, source);
36
41
  let current = root;
37
42
  for (const token of tokens) {
38
- if (current === null || typeof current !== "object") {
43
+ const child = childAt(current, token);
44
+ if (Option.isNone(child))
39
45
  throw missingPointerFailure(token, source, pointer);
40
- }
41
- if (!Object.hasOwn(current, token))
42
- throw missingPointerFailure(token, source, pointer);
43
- current = current[token];
46
+ current = child.value;
44
47
  }
45
48
  return current;
46
49
  }
47
50
  export function setAt(root, pointer, value) {
48
51
  const tokens = parsePointer(pointer, pointer);
49
- if (tokens.length === 0 || tokens.some((token) => unsafeTokens.has(token)))
52
+ if (tokens.length === 0 || Arr.some(tokens, (token) => unsafeTokens.has(token)))
50
53
  return;
51
54
  let current = root;
52
55
  for (let index = 0; index < tokens.length - 1; index++) {
53
56
  const token = tokens[index];
54
57
  const next = current[token];
55
- if (next === null || typeof next !== "object" || Array.isArray(next))
58
+ if (!Predicate.isObject(next))
56
59
  current[token] = {};
57
60
  current = current[token];
58
61
  }
@@ -60,21 +63,22 @@ export function setAt(root, pointer, value) {
60
63
  }
61
64
  /** Copy enumerable data, including cycles, and drop non-enumerable parser symbols. */
62
65
  export function cloneData(value, seen = new Map()) {
63
- if (typeof value !== "object" || value === null)
66
+ if (!Predicate.isObjectOrArray(value))
64
67
  return value;
65
68
  const cached = seen.get(value);
66
69
  if (cached !== undefined)
67
70
  return cached;
68
- if (Array.isArray(value)) {
71
+ if (Arr.isArray(value)) {
69
72
  const copy = [];
70
73
  seen.set(value, copy);
71
74
  for (const item of value)
72
75
  copy.push(cloneData(item, seen));
73
76
  return copy;
74
77
  }
78
+ const source = value;
75
79
  const copy = {};
76
- seen.set(value, copy);
77
- for (const key of Object.keys(value))
78
- copy[key] = cloneData(value[key], seen);
80
+ seen.set(source, copy);
81
+ for (const key of Rec.keys(source))
82
+ copy[key] = cloneData(source[key], seen);
79
83
  return copy;
80
84
  }
package/dist/run.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Array, Cache, Effect, Predicate, Ref, Schema } from "effect";
1
+ import { Array, Cache, Effect, Option, Predicate, Record as Rec, Ref, Schema, String as Str, } from "effect";
2
2
  import { ParserFailure, groupFailure, isLeafFailure, missingPointerFailure, syncFailure, } from "./errors.js";
3
3
  import { hoistExternalRefs } from "./hoist.js";
4
4
  import { loadDocument, makeContext } from "./loader.js";
5
5
  import { Options } from "./plugins.js";
6
- import { cloneData, joinPointer, parsePointer } from "./pointer.js";
6
+ import { childAt, cloneData, joinPointer, parsePointer } from "./pointer.js";
7
7
  import { inlineRemaining, runSchemaPipeline } from "./pipeline.js";
8
8
  import { isOpenApi3, upgradeOpenApi3_0 } from "./upgraders/3.0-to-3.1.js";
9
9
  import { getHash, isHttp, normalizeRef, resolveAgainst, stripHash } from "./url.js";
@@ -112,7 +112,7 @@ function isBundledRef(record) {
112
112
  const ref = record.$ref;
113
113
  if (typeof ref !== "string" || !isHttp(ref))
114
114
  return true;
115
- return Object.keys(record).every((key) => referenceKeys.has(key));
115
+ return Array.every(Rec.keys(record), (key) => referenceKeys.has(key));
116
116
  }
117
117
  function* externalRefs(node, kept) {
118
118
  const seen = new Set();
@@ -128,14 +128,14 @@ function* externalRefs(node, kept) {
128
128
  }
129
129
  const record = current;
130
130
  if (typeof record.$ref === "string" &&
131
- !record.$ref.startsWith("#") &&
131
+ !Str.startsWith("#")(record.$ref) &&
132
132
  !kept.has(record.$ref) &&
133
133
  isBundledRef(record)) {
134
134
  yield record.$ref;
135
135
  }
136
136
  if (typeof record.externalValue === "string" && !kept.has(record.externalValue))
137
137
  yield record.externalValue;
138
- stack.push(...Object.values(record));
138
+ stack.push(...Rec.values(record));
139
139
  }
140
140
  }
141
141
  function absolutize(node, base, ids, keep, kept, seen = new Set()) {
@@ -153,7 +153,7 @@ function absolutize(node, base, ids, keep, kept, seen = new Set()) {
153
153
  next = resolveAgainst(base, record.$id);
154
154
  ids.set(stripHash(next), record);
155
155
  }
156
- if (typeof record.$ref === "string" && !record.$ref.startsWith("#")) {
156
+ if (typeof record.$ref === "string" && !Str.startsWith("#")(record.$ref)) {
157
157
  if (keep(record.$ref))
158
158
  kept.add(record.$ref);
159
159
  else
@@ -164,8 +164,8 @@ function absolutize(node, base, ids, keep, kept, seen = new Set()) {
164
164
  ? discriminator.mapping
165
165
  : undefined;
166
166
  if (mapping) {
167
- for (const [key, value] of Object.entries(mapping)) {
168
- if (typeof value !== "string" || value.startsWith("#"))
167
+ for (const [key, value] of Rec.toEntries(mapping)) {
168
+ if (typeof value !== "string" || Str.startsWith("#")(value))
169
169
  continue;
170
170
  if (keep(value))
171
171
  kept.add(value);
@@ -179,7 +179,7 @@ function absolutize(node, base, ids, keep, kept, seen = new Set()) {
179
179
  else
180
180
  record.externalValue = resolveAgainst(base, record.externalValue);
181
181
  }
182
- for (const [key, value] of Object.entries(record)) {
182
+ for (const [key, value] of Rec.toEntries(record)) {
183
183
  if (key === "$ref" || key === "$id")
184
184
  continue;
185
185
  absolutize(value, next, ids, keep, kept, seen);
@@ -193,7 +193,7 @@ function rewriteIdRefs(root, ids) {
193
193
  pointers.set(url, pointer);
194
194
  }
195
195
  walkRecords(root, (record) => {
196
- if (typeof record.$ref !== "string" || record.$ref.startsWith("#"))
196
+ if (typeof record.$ref !== "string" || Str.startsWith("#")(record.$ref))
197
197
  return;
198
198
  const url = stripHash(record.$ref);
199
199
  const pointer = pointers.get(url);
@@ -225,8 +225,8 @@ function rewriteMappings(root, placed, ids) {
225
225
  : undefined;
226
226
  if (!mapping)
227
227
  return;
228
- for (const [key, value] of Object.entries(mapping)) {
229
- if (typeof value !== "string" || value.startsWith("#"))
228
+ for (const [key, value] of Rec.toEntries(mapping)) {
229
+ if (typeof value !== "string" || Str.startsWith("#")(value))
230
230
  continue;
231
231
  const url = stripHash(value);
232
232
  const hash = getHash(value);
@@ -242,7 +242,7 @@ function rewriteMappings(root, placed, ids) {
242
242
  function findPointer(root, target, pointer = "#", seen = new Set()) {
243
243
  if (root === target)
244
244
  return pointer;
245
- if (typeof root !== "object" || root === null || seen.has(root))
245
+ if (!Predicate.isObjectOrArray(root) || seen.has(root))
246
246
  return undefined;
247
247
  seen.add(root);
248
248
  if (Array.isArray(root)) {
@@ -253,7 +253,7 @@ function findPointer(root, target, pointer = "#", seen = new Set()) {
253
253
  }
254
254
  return undefined;
255
255
  }
256
- for (const [key, value] of Object.entries(root)) {
256
+ for (const [key, value] of Rec.toEntries(root)) {
257
257
  const found = findPointer(value, target, joinPointer(pointer, key), seen);
258
258
  if (found)
259
259
  return found;
@@ -271,7 +271,7 @@ function walkRecords(node, visit, seen = new Set()) {
271
271
  }
272
272
  const record = node;
273
273
  visit(record);
274
- for (const value of Object.values(record))
274
+ for (const value of Rec.values(record))
275
275
  walkRecords(value, visit, seen);
276
276
  }
277
277
  const inlineExternalValues = Effect.fnUntraced(function* (root, cache, keep, kept, options, failures, recorded) {
@@ -304,10 +304,10 @@ function valueAt(document, hash) {
304
304
  const tokens = parsePointer(hash);
305
305
  let current = document;
306
306
  for (const token of tokens) {
307
- if (typeof current !== "object" || current === null || !Object.hasOwn(current, token)) {
307
+ const child = childAt(current, token);
308
+ if (Option.isNone(child))
308
309
  throw missingPointerFailure(token, hash);
309
- }
310
- current = current[token];
310
+ current = child.value;
311
311
  }
312
312
  return current;
313
313
  }
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import { Schema, String as Str } from "effect";
2
2
  import { openApiMap } from "./object.js";
3
3
  import { OpenApiPathItem } from "./path-item.js";
4
- export const OpenApiCallback = openApiMap(Schema.String.check(Schema.makeFilter((key) => !key.startsWith("x-"))), Schema.suspend(() => OpenApiPathItem));
4
+ export const OpenApiCallback = openApiMap(Schema.String.check(Schema.makeFilter((key) => !Str.startsWith("x-")(key))), Schema.suspend(() => OpenApiPathItem));
@@ -1,10 +1,10 @@
1
- import { Schema } from "effect";
1
+ import { Record as Rec, Schema } from "effect";
2
2
  import { openApiObject } from "./object.js";
3
3
  export const OpenApiExample = openApiObject({
4
4
  summary: Schema.optionalKey(Schema.String),
5
5
  description: Schema.optionalKey(Schema.String),
6
6
  value: Schema.optionalKey(Schema.Unknown),
7
7
  externalValue: Schema.optionalKey(Schema.String),
8
- }).check(Schema.makeFilter((value) => Object.hasOwn(value, "value") && value.externalValue !== undefined
8
+ }).check(Schema.makeFilter((value) => Rec.has(value, "value") && value.externalValue !== undefined
9
9
  ? "Example value and externalValue are mutually exclusive"
10
10
  : undefined));
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import { Record as Rec, Schema } from "effect";
2
2
  import { OpenApiExample } from "./example.js";
3
3
  import { OpenApiMediaType } from "./media-type.js";
4
4
  import { openApiObject } from "./object.js";
@@ -19,10 +19,10 @@ export const OpenApiHeader = openApiObject(fields).check(Schema.makeFilter((valu
19
19
  if ((value.schema === undefined) === (value.content === undefined)) {
20
20
  return "A header must have exactly one of schema or content";
21
21
  }
22
- if (value.content !== undefined && Object.keys(value.content).length !== 1) {
22
+ if (value.content !== undefined && Rec.size(value.content) !== 1) {
23
23
  return "Header content must contain exactly one media type";
24
24
  }
25
- if (Object.hasOwn(value, "example") && value.examples !== undefined) {
25
+ if (Rec.has(value, "example") && value.examples !== undefined) {
26
26
  return "Header example and examples are mutually exclusive";
27
27
  }
28
28
  return undefined;