@shipworthy/openapi-parser 0.0.1 → 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/yaml.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Array as Arr, pipe, Predicate, Record as Rec, String as Str } from "effect";
1
2
  import * as Yaml from "effect/encoding/Yaml";
2
3
  const blockScalar = /^[|>](?:[1-9]?[+-]?|[+-]?[1-9]?)$/;
3
4
  /**
@@ -5,8 +6,8 @@ const blockScalar = /^[|>](?:[1-9]?[+-]?|[+-]?[1-9]?)$/;
5
6
  * OpenAPI fixtures in this repo use both, so expand those forms before parsing.
6
7
  */
7
8
  export function parseYamlDocument(source) {
8
- const text = source.charCodeAt(0) === 0xfeff ? source.slice(1) : source;
9
- if (text.trim().length === 0)
9
+ const text = Str.startsWith("\uFEFF")(source) ? Str.slice(1)(source) : source;
10
+ if (isBlank(text))
10
11
  return null;
11
12
  return applyMergeKeys(Yaml.parse(expandCompactNestedSequences(deepenShallowBlockScalars(relaxIndentTabs(text)))));
12
13
  }
@@ -16,12 +17,12 @@ export function parseYamlDocument(source) {
16
17
  * drop the explicit indicator so auto-detection keeps the scalar text.
17
18
  */
18
19
  function deepenShallowBlockScalars(source) {
19
- const lines = source.replace(/\r\n?/g, "\n").split("\n");
20
+ const lines = pipe(source, Str.replace(/\r\n?/g, "\n"), Str.split("\n"));
20
21
  const output = [];
21
22
  for (let index = 0; index < lines.length; index++) {
22
23
  const line = lines[index] ?? "";
23
24
  const indent = leadingSpaces(line);
24
- const text = stripComment(line.slice(indent)).trimEnd();
25
+ const text = pipe(line, Str.slice(indent), stripComment, Str.trimEnd);
25
26
  if (!isExplicitBlockScalar(text) || !hasShallowContent(lines, index, indent)) {
26
27
  output.push(line);
27
28
  continue;
@@ -30,49 +31,49 @@ function deepenShallowBlockScalars(source) {
30
31
  index++;
31
32
  while (index < lines.length) {
32
33
  const next = lines[index] ?? "";
33
- if (next.trim().length > 0 && leadingSpaces(next) < indent)
34
+ if (!isBlank(next) && leadingSpaces(next) < indent)
34
35
  break;
35
- output.push(next.trim().length === 0 ? next : ` ${next}`);
36
+ output.push(isBlank(next) ? next : ` ${next}`);
36
37
  index++;
37
38
  }
38
39
  index--;
39
40
  }
40
- return output.join("\n");
41
+ return Arr.join(output, "\n");
41
42
  }
42
43
  function isExplicitBlockScalar(text) {
43
44
  return /^[|>](?:[+-]?[1-9]|[1-9][+-])$/.test(scalarToken(text));
44
45
  }
45
46
  function scalarToken(text) {
46
47
  const separator = mappingSeparator(text);
47
- const token = separator === -1 ? text : text.slice(separator + 1).trim();
48
- if (token === "-" || token.startsWith("- ")) {
49
- const item = token.slice(1).trimStart();
48
+ const token = separator === -1 ? text : afterIndex(text, separator);
49
+ if (token === "-" || Str.startsWith("- ")(token)) {
50
+ const item = pipe(token, Str.slice(1), Str.trimStart);
50
51
  const nested = mappingSeparator(item);
51
- return nested === -1 ? item : item.slice(nested + 1).trim();
52
+ return nested === -1 ? item : afterIndex(item, nested);
52
53
  }
53
54
  return token;
54
55
  }
55
56
  function hasShallowContent(lines, header, indent) {
56
57
  for (let index = header + 1; index < lines.length; index++) {
57
58
  const line = lines[index] ?? "";
58
- if (line.trim().length === 0)
59
+ if (isBlank(line))
59
60
  continue;
60
61
  return leadingSpaces(line) <= indent;
61
62
  }
62
63
  return false;
63
64
  }
64
65
  function dropExplicitIndent(line) {
65
- return line.replace(/([|>])(\+|-)?[1-9](\+|-)?(\s*(?:#.*)?)?$/, (_, bar, before, after, tail) => `${bar}${before ?? ""}${after ?? ""}${tail ?? ""}`);
66
+ return Str.replace(/([|>])(\+|-)?[1-9](\+|-)?(\s*(?:#.*)?)?$/, "$1$2$3$4")(line);
66
67
  }
67
- /** Effect rejects a tab after leading spaces, including inside block scalars. */
68
+ /**
69
+ * Effect rejects a tab after leading spaces, including inside block scalars.
70
+ * Replace each tab in a line's leading run of spaces then tabs with two spaces.
71
+ */
68
72
  function relaxIndentTabs(source) {
69
- return source.replace(/^( *)\t+/gm, (match, spaces) => spaces + " ".repeat(match.length - spaces.length));
73
+ return Str.replace(/(?<=^ *\t*)\t/gm, " ")(source);
70
74
  }
71
75
  export function expandCompactNestedSequences(source) {
72
- const lines = source
73
- .replace(/^\uFEFF/, "")
74
- .replace(/\r\n?/g, "\n")
75
- .split("\n");
76
+ const lines = pipe(source, Str.replace(/^\uFEFF/, ""), Str.replace(/\r\n?/g, "\n"), Str.split("\n"));
76
77
  const output = [];
77
78
  let blockIndent = -1;
78
79
  for (const line of lines) {
@@ -80,32 +81,32 @@ export function expandCompactNestedSequences(source) {
80
81
  while (true) {
81
82
  const indent = leadingSpaces(raw);
82
83
  if (blockIndent >= 0) {
83
- if (raw.trim().length > 0 && indent <= blockIndent)
84
+ if (!isBlank(raw) && indent <= blockIndent)
84
85
  blockIndent = -1;
85
86
  else
86
87
  break;
87
88
  }
88
- const text = stripComment(raw.slice(indent)).trimEnd();
89
+ const text = pipe(raw, Str.slice(indent), stripComment, Str.trimEnd);
89
90
  const compact = compactRest(raw, indent);
90
91
  if (compact === undefined) {
91
92
  if (isBlockScalar(text))
92
93
  blockIndent = indent;
93
94
  break;
94
95
  }
95
- output.push(`${" ".repeat(indent)}-`);
96
- raw = `${" ".repeat(indent + 2)}${compact}`;
96
+ output.push(`${Str.repeat(indent)(" ")}-`);
97
+ raw = `${Str.repeat(indent + 2)(" ")}${compact}`;
97
98
  }
98
99
  output.push(raw);
99
100
  }
100
- return output.join("\n");
101
+ return Arr.join(output, "\n");
101
102
  }
102
103
  function compactRest(raw, indent) {
103
- const body = raw.slice(indent);
104
- if (!body.startsWith("- "))
104
+ const body = Str.slice(indent)(raw);
105
+ if (!Str.startsWith("- ")(body))
105
106
  return undefined;
106
- const rest = body.slice(2);
107
- const text = stripComment(rest).trimEnd();
108
- if (text === "-" || text.startsWith("- "))
107
+ const rest = Str.slice(2)(body);
108
+ const text = Str.trimEnd(stripComment(rest));
109
+ if (text === "-" || Str.startsWith("- ")(text))
109
110
  return rest;
110
111
  return undefined;
111
112
  }
@@ -114,60 +115,60 @@ function isBlockScalar(text) {
114
115
  return true;
115
116
  const separator = mappingSeparator(text);
116
117
  if (separator !== -1)
117
- return blockScalar.test(text.slice(separator + 1).trim());
118
- if (text === "-" || text.startsWith("- ")) {
119
- const item = text.slice(1).trimStart();
118
+ return blockScalar.test(afterIndex(text, separator));
119
+ if (text === "-" || Str.startsWith("- ")(text)) {
120
+ const item = pipe(text, Str.slice(1), Str.trimStart);
120
121
  const nested = mappingSeparator(item);
121
122
  if (nested === -1)
122
123
  return blockScalar.test(item);
123
- return blockScalar.test(item.slice(nested + 1).trim());
124
+ return blockScalar.test(afterIndex(item, nested));
124
125
  }
125
126
  return false;
126
127
  }
127
128
  function applyMergeKeys(value, seen = new Set()) {
128
- if (typeof value !== "object" || value === null || seen.has(value))
129
+ if (!Predicate.isObjectOrArray(value) || seen.has(value))
129
130
  return value;
130
131
  seen.add(value);
131
- if (Array.isArray(value)) {
132
+ if (Arr.isArray(value)) {
132
133
  for (let index = 0; index < value.length; index++)
133
134
  value[index] = applyMergeKeys(value[index], seen);
134
135
  return value;
135
136
  }
136
137
  const record = value;
137
- for (const key of Object.keys(record))
138
+ for (const key of Rec.keys(record))
138
139
  record[key] = applyMergeKeys(record[key], seen);
139
- if (!Object.keys(record).some(isMergeKey))
140
+ if (!Arr.some(Rec.keys(record), isMergeKey))
140
141
  return record;
141
142
  const next = {};
142
- for (const key of Object.keys(record)) {
143
+ for (const key of Rec.keys(record)) {
143
144
  if (!isMergeKey(key)) {
144
145
  next[key] = record[key];
145
146
  continue;
146
147
  }
147
148
  const source = record[key];
148
- const entries = Array.isArray(source) ? source : [source];
149
+ const entries = Arr.isArray(source) ? source : [source];
149
150
  for (const entry of entries) {
150
- if (typeof entry !== "object" || entry === null || Array.isArray(entry))
151
+ if (!Predicate.isObject(entry))
151
152
  continue;
152
- for (const [mergedKey, mergedValue] of Object.entries(entry)) {
153
- if (isMergeKey(mergedKey) || Object.hasOwn(next, mergedKey))
153
+ for (const [mergedKey, mergedValue] of Rec.toEntries(entry)) {
154
+ if (isMergeKey(mergedKey) || Rec.has(next, mergedKey))
154
155
  continue;
155
156
  next[mergedKey] = mergedValue;
156
157
  }
157
158
  }
158
159
  }
159
- for (const key of Object.keys(record))
160
+ for (const key of Rec.keys(record))
160
161
  delete record[key];
161
162
  Object.assign(record, next);
162
163
  return record;
163
164
  }
164
165
  function isMergeKey(key) {
165
- const text = key.trim();
166
+ const text = Str.trim(key);
166
167
  return text === "<<" || /^!!\S+\s+<<$/.test(text) || /^!<[^>]+>\s+<<$/.test(text);
167
168
  }
168
169
  function leadingSpaces(raw) {
169
170
  let index = 0;
170
- while (raw.charCodeAt(index) === 32)
171
+ while (raw[index] === " ")
171
172
  index++;
172
173
  return index;
173
174
  }
@@ -230,6 +231,9 @@ const findIndicator = (input, indicator) => {
230
231
  };
231
232
  const stripComment = (input) => {
232
233
  const index = findIndicator(input, "#");
233
- return index === -1 ? input : input.slice(0, index).trimEnd();
234
+ return index === -1 ? input : pipe(input, Str.slice(0, index), Str.trimEnd);
234
235
  };
235
236
  const mappingSeparator = (input) => findIndicator(input, ":");
237
+ /** The trimmed text after `index`. */
238
+ const afterIndex = (input, index) => pipe(input, Str.slice(index + 1), Str.trim);
239
+ const isBlank = (input) => Str.isEmpty(Str.trim(input));
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@shipworthy/openapi-parser",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "dependencies": {
8
- "@effect/platform-node": "^4.0.0"
9
- },
10
- "peerDependencies": {
11
- "effect": "^4.0.0"
8
+ "@effect/platform-node": "4.1.4",
9
+ "@effect/platform-node-shared": "4.1.4",
10
+ "effect": "4.1.4"
12
11
  },
13
12
  "description": "Parse, bundle, and upgrade OpenAPI documents with Effect schemas.",
14
13
  "license": "MIT",
@@ -34,12 +33,11 @@
34
33
  "build": "rm -rf dist && tsc -p tsconfig.build.json"
35
34
  },
36
35
  "devDependencies": {
37
- "@effect/vitest": "4.0.0",
36
+ "@effect/vitest": "4.1.4",
38
37
  "@shipworthy/typescript-config": "0.0.1",
39
- "@types/node": "^24.10.3",
40
- "effect": "^4.0.0",
38
+ "@types/node": "^26.6.6",
41
39
  "typescript": "^7.0.2",
42
- "vitest": "5.0.3"
40
+ "vitest": "5.1.0"
43
41
  },
44
42
  "engines": {
45
43
  "node": ">=22.19.0"