@scalar/helpers 0.2.12 → 0.2.16
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/CHANGELOG.md +24 -0
- package/dist/consts/content-types.d.ts +15 -0
- package/dist/consts/content-types.d.ts.map +1 -0
- package/dist/consts/content-types.js +15 -0
- package/dist/consts/content-types.js.map +7 -0
- package/dist/general/extract-config-secrets.d.ts +8 -0
- package/dist/general/extract-config-secrets.d.ts.map +1 -0
- package/dist/general/extract-config-secrets.js +31 -0
- package/dist/general/extract-config-secrets.js.map +7 -0
- package/dist/object/circular-to-refs.d.ts +24 -0
- package/dist/object/circular-to-refs.d.ts.map +1 -0
- package/dist/object/circular-to-refs.js +290 -0
- package/dist/object/circular-to-refs.js.map +7 -0
- package/dist/url/extract-server-from-path.d.ts +24 -0
- package/dist/url/extract-server-from-path.d.ts.map +1 -0
- package/dist/url/extract-server-from-path.js +32 -0
- package/dist/url/extract-server-from-path.js.map +7 -0
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @scalar/helpers
|
|
2
2
|
|
|
3
|
+
## 0.2.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#8248](https://github.com/scalar/scalar/pull/8248): fix: local storage migration script
|
|
8
|
+
|
|
9
|
+
## 0.2.15
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#8212](https://github.com/scalar/scalar/pull/8212): chore: version bump
|
|
14
|
+
|
|
15
|
+
## 0.2.14
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#8207](https://github.com/scalar/scalar/pull/8207): chore: version bump
|
|
20
|
+
|
|
21
|
+
## 0.2.13
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#7826](https://github.com/scalar/scalar/pull/7826): feat: added migrator for v1 local storage to v2 indexdb
|
|
26
|
+
|
|
3
27
|
## 0.2.12
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content types that we automatically add in the client
|
|
3
|
+
*/
|
|
4
|
+
export declare const CONTENT_TYPES: {
|
|
5
|
+
readonly 'multipart/form-data': "Multipart Form";
|
|
6
|
+
readonly 'application/x-www-form-urlencoded': "Form URL Encoded";
|
|
7
|
+
readonly 'application/octet-stream': "Binary File";
|
|
8
|
+
readonly 'application/json': "JSON";
|
|
9
|
+
readonly 'application/xml': "XML";
|
|
10
|
+
readonly 'application/yaml': "YAML";
|
|
11
|
+
readonly 'application/edn': "EDN";
|
|
12
|
+
readonly other: "Other";
|
|
13
|
+
readonly none: "None";
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=content-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-types.d.ts","sourceRoot":"","sources":["../../src/consts/content-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;CAUhB,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const CONTENT_TYPES = {
|
|
2
|
+
"multipart/form-data": "Multipart Form",
|
|
3
|
+
"application/x-www-form-urlencoded": "Form URL Encoded",
|
|
4
|
+
"application/octet-stream": "Binary File",
|
|
5
|
+
"application/json": "JSON",
|
|
6
|
+
"application/xml": "XML",
|
|
7
|
+
"application/yaml": "YAML",
|
|
8
|
+
"application/edn": "EDN",
|
|
9
|
+
"other": "Other",
|
|
10
|
+
"none": "None"
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
CONTENT_TYPES
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=content-types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/consts/content-types.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Content types that we automatically add in the client\n */\nexport const CONTENT_TYPES = {\n 'multipart/form-data': 'Multipart Form',\n 'application/x-www-form-urlencoded': 'Form URL Encoded',\n 'application/octet-stream': 'Binary File',\n 'application/json': 'JSON',\n 'application/xml': 'XML',\n 'application/yaml': 'YAML',\n 'application/edn': 'EDN',\n 'other': 'Other',\n 'none': 'None',\n} as const\n"],
|
|
5
|
+
"mappings": "AAGO,MAAM,gBAAgB;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,QAAQ;AACV;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts secret fields from the config or the old schemes
|
|
3
|
+
* Maps original field names to their x-scalar-secret extension equivalents.
|
|
4
|
+
*/
|
|
5
|
+
export declare const extractConfigSecrets: (input: Record<string, unknown>) => Record<string, string>;
|
|
6
|
+
/** Removes all secret fields from the input object */
|
|
7
|
+
export declare const removeSecretFields: (input: Record<string, unknown>) => Record<string, unknown>;
|
|
8
|
+
//# sourceMappingURL=extract-config-secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-config-secrets.d.ts","sourceRoot":"","sources":["../../src/general/extract-config-secrets.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAOnF,CAAA;AAOR,sDAAsD;AACtD,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAMlF,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { objectEntries } from "../object/object-entries.js";
|
|
2
|
+
const SECRET_FIELD_MAPPINGS = {
|
|
3
|
+
clientSecret: "x-scalar-secret-client-secret",
|
|
4
|
+
password: "x-scalar-secret-password",
|
|
5
|
+
token: "x-scalar-secret-token",
|
|
6
|
+
username: "x-scalar-secret-username",
|
|
7
|
+
value: "x-scalar-secret-token",
|
|
8
|
+
"x-scalar-client-id": "x-scalar-secret-client-id",
|
|
9
|
+
"x-scalar-redirect-uri": "x-scalar-secret-redirect-uri"
|
|
10
|
+
};
|
|
11
|
+
const extractConfigSecrets = (input) => objectEntries(SECRET_FIELD_MAPPINGS).reduce((result, [field, secretField]) => {
|
|
12
|
+
const value = input[field];
|
|
13
|
+
if (value && typeof value === "string") {
|
|
14
|
+
result[secretField] = value;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}, {});
|
|
18
|
+
const SECRETS_SET = new Set(
|
|
19
|
+
objectEntries(SECRET_FIELD_MAPPINGS).flatMap(([oldSecret, newSecret]) => [oldSecret, newSecret])
|
|
20
|
+
);
|
|
21
|
+
const removeSecretFields = (input) => objectEntries(input).reduce((result, [key, value]) => {
|
|
22
|
+
if (!SECRETS_SET.has(key)) {
|
|
23
|
+
result[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}, {});
|
|
27
|
+
export {
|
|
28
|
+
extractConfigSecrets,
|
|
29
|
+
removeSecretFields
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=extract-config-secrets.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/general/extract-config-secrets.ts"],
|
|
4
|
+
"sourcesContent": ["import { objectEntries } from '../object/object-entries'\n\n/**\n * Mapping of field names to their corresponding x-scalar-secret extension names.\n */\nconst SECRET_FIELD_MAPPINGS = {\n clientSecret: 'x-scalar-secret-client-secret',\n password: 'x-scalar-secret-password',\n token: 'x-scalar-secret-token',\n username: 'x-scalar-secret-username',\n value: 'x-scalar-secret-token',\n 'x-scalar-client-id': 'x-scalar-secret-client-id',\n 'x-scalar-redirect-uri': 'x-scalar-secret-redirect-uri',\n} as const\n\n/**\n * Extracts secret fields from the config or the old schemes\n * Maps original field names to their x-scalar-secret extension equivalents.\n */\nexport const extractConfigSecrets = (input: Record<string, unknown>): Record<string, string> =>\n objectEntries(SECRET_FIELD_MAPPINGS).reduce<Record<string, string>>((result, [field, secretField]) => {\n const value = input[field]\n if (value && typeof value === 'string') {\n result[secretField] = value\n }\n return result\n }, {})\n\n/** Set of all secret fields */\nconst SECRETS_SET = new Set<string>(\n objectEntries(SECRET_FIELD_MAPPINGS).flatMap(([oldSecret, newSecret]) => [oldSecret, newSecret]),\n)\n\n/** Removes all secret fields from the input object */\nexport const removeSecretFields = (input: Record<string, unknown>): Record<string, unknown> =>\n objectEntries(input).reduce<Record<string, unknown>>((result, [key, value]) => {\n if (!SECRETS_SET.has(key)) {\n result[key] = value\n }\n return result\n }, {})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,qBAAqB;AAK9B,MAAM,wBAAwB;AAAA,EAC5B,cAAc;AAAA,EACd,UAAU;AAAA,EACV,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AAAA,EACP,sBAAsB;AAAA,EACtB,yBAAyB;AAC3B;AAMO,MAAM,uBAAuB,CAAC,UACnC,cAAc,qBAAqB,EAAE,OAA+B,CAAC,QAAQ,CAAC,OAAO,WAAW,MAAM;AACpG,QAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,WAAO,WAAW,IAAI;AAAA,EACxB;AACA,SAAO;AACT,GAAG,CAAC,CAAC;AAGP,MAAM,cAAc,IAAI;AAAA,EACtB,cAAc,qBAAqB,EAAE,QAAQ,CAAC,CAAC,WAAW,SAAS,MAAM,CAAC,WAAW,SAAS,CAAC;AACjG;AAGO,MAAM,qBAAqB,CAAC,UACjC,cAAc,KAAK,EAAE,OAAgC,CAAC,QAAQ,CAAC,KAAK,KAAK,MAAM;AAC7E,MAAI,CAAC,YAAY,IAAI,GAAG,GAAG;AACzB,WAAO,GAAG,IAAI;AAAA,EAChB;AACA,SAAO;AACT,GAAG,CAAC,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects and breaks circular JavaScript object references in an OpenAPI document tree.
|
|
3
|
+
*
|
|
4
|
+
* The legacy API client (via openapi-parser's dereference) resolved all $refs inline:
|
|
5
|
+
* it deleted the $ref key and copied every property from the resolved target directly
|
|
6
|
+
* onto the object. For self-referencing or mutually-referencing schemas this created
|
|
7
|
+
* circular JS object graphs that cannot be serialized to JSON or validated by TypeBox.
|
|
8
|
+
*
|
|
9
|
+
* This function walks the object tree depth-first, detects cycles via reference identity,
|
|
10
|
+
* extracts each circular component into the appropriate components section (schemas,
|
|
11
|
+
* responses, parameters, etc.) with a generated name, and replaces ALL occurrences
|
|
12
|
+
* (both first and back-references) with **only** `$ref` and any extra properties
|
|
13
|
+
* (no extra schema properties) so that TypeBox's Value.Cast picks the reference branch
|
|
14
|
+
* of the schemaOrReference union.
|
|
15
|
+
*
|
|
16
|
+
* Returns a new (deep-cloned) document with all cycles replaced. When no circular
|
|
17
|
+
* references exist, the output is structurally identical to the input.
|
|
18
|
+
*
|
|
19
|
+
* @param document - The OpenAPI document to process
|
|
20
|
+
* @param extraProps - Extra properties to add as siblings to the $ref (e.g., '$ref-value')
|
|
21
|
+
* @returns A new document with circular references replaced by $ref pointers
|
|
22
|
+
*/
|
|
23
|
+
export declare const circularToRefs: (document: Record<string, unknown>, extraProps?: Record<string, unknown>) => Record<string, unknown>;
|
|
24
|
+
//# sourceMappingURL=circular-to-refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circular-to-refs.d.ts","sourceRoot":"","sources":["../../src/object/circular-to-refs.ts"],"names":[],"mappings":"AAyFA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,aAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,KACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAwWxB,CAAA"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const COMPONENT_PREFIXES = {
|
|
2
|
+
schemas: "CircularSchema",
|
|
3
|
+
responses: "CircularResponse",
|
|
4
|
+
parameters: "CircularParameter",
|
|
5
|
+
examples: "CircularExample",
|
|
6
|
+
requestBodies: "CircularRequestBody",
|
|
7
|
+
headers: "CircularHeader",
|
|
8
|
+
securitySchemes: "CircularSecurityScheme",
|
|
9
|
+
links: "CircularLink",
|
|
10
|
+
callbacks: "CircularCallback",
|
|
11
|
+
pathItems: "CircularPathItem"
|
|
12
|
+
};
|
|
13
|
+
const COMPONENT_TYPES = Object.keys(COMPONENT_PREFIXES);
|
|
14
|
+
const KEY_TO_CONTEXT = {
|
|
15
|
+
responses: "responses",
|
|
16
|
+
parameters: "parameters",
|
|
17
|
+
requestBody: "requestBodies",
|
|
18
|
+
headers: "headers",
|
|
19
|
+
examples: "examples",
|
|
20
|
+
links: "links",
|
|
21
|
+
callbacks: "callbacks",
|
|
22
|
+
securitySchemes: "securitySchemes",
|
|
23
|
+
schema: "schemas",
|
|
24
|
+
items: "schemas",
|
|
25
|
+
additionalProperties: "schemas",
|
|
26
|
+
allOf: "schemas",
|
|
27
|
+
oneOf: "schemas",
|
|
28
|
+
anyOf: "schemas",
|
|
29
|
+
not: "schemas",
|
|
30
|
+
properties: "schemas",
|
|
31
|
+
patternProperties: "schemas",
|
|
32
|
+
get: "pathItems",
|
|
33
|
+
put: "pathItems",
|
|
34
|
+
post: "pathItems",
|
|
35
|
+
delete: "pathItems",
|
|
36
|
+
options: "pathItems",
|
|
37
|
+
head: "pathItems",
|
|
38
|
+
patch: "pathItems",
|
|
39
|
+
trace: "pathItems"
|
|
40
|
+
};
|
|
41
|
+
const NON_REFERENCE_CONTAINER_KEYS = /* @__PURE__ */ new Set(["properties", "patternProperties", "responses"]);
|
|
42
|
+
const getContextForKey = (key) => {
|
|
43
|
+
const context = KEY_TO_CONTEXT[key];
|
|
44
|
+
if (context !== void 0) {
|
|
45
|
+
return context;
|
|
46
|
+
}
|
|
47
|
+
if (key.includes("{$") || key.charCodeAt(0) === 47) {
|
|
48
|
+
return "pathItems";
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
const circularToRefs = (document, extraProps = {}) => {
|
|
53
|
+
const circularMeta = /* @__PURE__ */ new Map();
|
|
54
|
+
const objectContext = /* @__PURE__ */ new Map();
|
|
55
|
+
const existingComponentNames = /* @__PURE__ */ new Map();
|
|
56
|
+
const nonReferenceContainers = /* @__PURE__ */ new WeakSet();
|
|
57
|
+
const nonReferenceContainerInfo = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
const counters = {
|
|
59
|
+
schemas: 0,
|
|
60
|
+
responses: 0,
|
|
61
|
+
parameters: 0,
|
|
62
|
+
examples: 0,
|
|
63
|
+
requestBodies: 0,
|
|
64
|
+
headers: 0,
|
|
65
|
+
securitySchemes: 0,
|
|
66
|
+
links: 0,
|
|
67
|
+
callbacks: 0,
|
|
68
|
+
pathItems: 0
|
|
69
|
+
};
|
|
70
|
+
const scanExistingComponents = (doc) => {
|
|
71
|
+
const components = doc.components;
|
|
72
|
+
if (!components) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const componentType of COMPONENT_TYPES) {
|
|
76
|
+
const section = components[componentType];
|
|
77
|
+
if (!section) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
for (const [name, value] of Object.entries(section)) {
|
|
81
|
+
if (value !== null && typeof value === "object") {
|
|
82
|
+
existingComponentNames.set(value, { type: componentType, name });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const identifyCircularObjects = (value, ancestors, context) => {
|
|
88
|
+
if (value === null || typeof value !== "object") {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const obj = value;
|
|
92
|
+
if (ancestors.has(obj)) {
|
|
93
|
+
if (nonReferenceContainers.has(obj)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!circularMeta.has(obj)) {
|
|
97
|
+
const existing = existingComponentNames.get(obj);
|
|
98
|
+
if (existing) {
|
|
99
|
+
circularMeta.set(obj, existing);
|
|
100
|
+
} else {
|
|
101
|
+
const componentType = objectContext.get(obj) ?? context;
|
|
102
|
+
const count = ++counters[componentType];
|
|
103
|
+
circularMeta.set(obj, {
|
|
104
|
+
type: componentType,
|
|
105
|
+
name: `${COMPONENT_PREFIXES[componentType]}${count}`
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!objectContext.has(obj)) {
|
|
112
|
+
objectContext.set(obj, context);
|
|
113
|
+
}
|
|
114
|
+
ancestors.add(obj);
|
|
115
|
+
if (Array.isArray(obj)) {
|
|
116
|
+
for (const item of obj) {
|
|
117
|
+
identifyCircularObjects(item, ancestors, context);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const record = obj;
|
|
121
|
+
for (const key of Object.keys(record)) {
|
|
122
|
+
const child = record[key];
|
|
123
|
+
if (NON_REFERENCE_CONTAINER_KEYS.has(key) && child !== null && typeof child === "object" && !Array.isArray(child)) {
|
|
124
|
+
nonReferenceContainers.add(child);
|
|
125
|
+
nonReferenceContainerInfo.set(child, {
|
|
126
|
+
key,
|
|
127
|
+
owner: obj
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const newContext = getContextForKey(key) ?? context;
|
|
131
|
+
identifyCircularObjects(child, ancestors, newContext);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ancestors.delete(obj);
|
|
135
|
+
};
|
|
136
|
+
const extractedComponents = /* @__PURE__ */ new Map();
|
|
137
|
+
const hasExtraProps = Object.keys(extraProps).length > 0;
|
|
138
|
+
const createRefObject = (meta) => {
|
|
139
|
+
const ref = { $ref: `#/components/${meta.type}/${meta.name}` };
|
|
140
|
+
return hasExtraProps ? { ...ref, ...extraProps } : ref;
|
|
141
|
+
};
|
|
142
|
+
const getOrCreateExtractedSection = (type) => {
|
|
143
|
+
const existing = extractedComponents.get(type);
|
|
144
|
+
if (existing !== void 0) {
|
|
145
|
+
return existing;
|
|
146
|
+
}
|
|
147
|
+
const section = /* @__PURE__ */ new Map();
|
|
148
|
+
extractedComponents.set(type, section);
|
|
149
|
+
return section;
|
|
150
|
+
};
|
|
151
|
+
const ensureMeta = (obj, fallbackType) => {
|
|
152
|
+
const existingMeta = circularMeta.get(obj);
|
|
153
|
+
if (existingMeta !== void 0) {
|
|
154
|
+
return existingMeta;
|
|
155
|
+
}
|
|
156
|
+
const existingName = existingComponentNames.get(obj);
|
|
157
|
+
if (existingName !== void 0) {
|
|
158
|
+
circularMeta.set(obj, existingName);
|
|
159
|
+
return existingName;
|
|
160
|
+
}
|
|
161
|
+
const count = ++counters[fallbackType];
|
|
162
|
+
const meta = {
|
|
163
|
+
type: fallbackType,
|
|
164
|
+
name: `${COMPONENT_PREFIXES[fallbackType]}${count}`
|
|
165
|
+
};
|
|
166
|
+
circularMeta.set(obj, meta);
|
|
167
|
+
return meta;
|
|
168
|
+
};
|
|
169
|
+
const createLiftedRefForContainer = (container, clonedContainer) => {
|
|
170
|
+
const info = nonReferenceContainerInfo.get(container);
|
|
171
|
+
if (info === void 0) {
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
if (info.key === "properties" || info.key === "patternProperties") {
|
|
175
|
+
const meta = ensureMeta(info.owner, "schemas");
|
|
176
|
+
return createRefObject(meta);
|
|
177
|
+
}
|
|
178
|
+
const responses = container;
|
|
179
|
+
for (const [key, value] of Object.entries(responses)) {
|
|
180
|
+
if (value === null || typeof value !== "object" || value === container) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const responseObject = value;
|
|
184
|
+
const meta = ensureMeta(responseObject, "responses");
|
|
185
|
+
if (!existingComponentNames.has(responseObject)) {
|
|
186
|
+
const section = getOrCreateExtractedSection(meta.type);
|
|
187
|
+
if (!section.has(meta.name)) {
|
|
188
|
+
const clonedValue = clonedContainer?.[key];
|
|
189
|
+
if (clonedValue !== void 0) {
|
|
190
|
+
section.set(meta.name, clonedValue);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return createRefObject(meta);
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
};
|
|
198
|
+
const cloneNode = (obj, visited, context) => {
|
|
199
|
+
return Array.isArray(obj) ? cloneArray(obj, visited, context) : cloneObject(obj, visited, context);
|
|
200
|
+
};
|
|
201
|
+
const cloneNodeWithVisitTracking = (obj, visited, context) => {
|
|
202
|
+
visited.add(obj);
|
|
203
|
+
const cloned = cloneNode(obj, visited, context);
|
|
204
|
+
visited.delete(obj);
|
|
205
|
+
return cloned;
|
|
206
|
+
};
|
|
207
|
+
const cloneWithRefs = (value, visited, context) => {
|
|
208
|
+
if (value === null || typeof value !== "object") {
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
const obj = value;
|
|
212
|
+
const meta = circularMeta.get(obj);
|
|
213
|
+
if (meta !== void 0) {
|
|
214
|
+
if (visited.has(obj)) {
|
|
215
|
+
return createRefObject(meta);
|
|
216
|
+
}
|
|
217
|
+
const cloned2 = cloneNodeWithVisitTracking(obj, visited, context);
|
|
218
|
+
if (existingComponentNames.has(obj)) {
|
|
219
|
+
return cloned2;
|
|
220
|
+
}
|
|
221
|
+
const section2 = getOrCreateExtractedSection(meta.type);
|
|
222
|
+
if (!section2.has(meta.name)) {
|
|
223
|
+
section2.set(meta.name, cloned2);
|
|
224
|
+
}
|
|
225
|
+
return createRefObject(meta);
|
|
226
|
+
}
|
|
227
|
+
if (visited.has(obj)) {
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
const cloned = cloneNodeWithVisitTracking(obj, visited, context);
|
|
231
|
+
const lateMeta = circularMeta.get(obj);
|
|
232
|
+
if (lateMeta === void 0 || existingComponentNames.has(obj)) {
|
|
233
|
+
return cloned;
|
|
234
|
+
}
|
|
235
|
+
const section = getOrCreateExtractedSection(lateMeta.type);
|
|
236
|
+
if (!section.has(lateMeta.name)) {
|
|
237
|
+
section.set(lateMeta.name, cloned);
|
|
238
|
+
}
|
|
239
|
+
return createRefObject(lateMeta);
|
|
240
|
+
};
|
|
241
|
+
const cloneArray = (arr, visited, context) => {
|
|
242
|
+
const result2 = [];
|
|
243
|
+
for (const item of arr) {
|
|
244
|
+
const clonedItem = cloneWithRefs(item, visited, context);
|
|
245
|
+
if (clonedItem !== void 0) {
|
|
246
|
+
result2.push(clonedItem);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result2;
|
|
250
|
+
};
|
|
251
|
+
const cloneObject = (obj, visited, context) => {
|
|
252
|
+
const record = obj;
|
|
253
|
+
const result2 = {};
|
|
254
|
+
for (const key of Object.keys(record)) {
|
|
255
|
+
const newContext = getContextForKey(key) ?? context;
|
|
256
|
+
const value = record[key];
|
|
257
|
+
const clonedValue = cloneWithRefs(value, visited, newContext);
|
|
258
|
+
if (clonedValue !== void 0) {
|
|
259
|
+
result2[key] = clonedValue;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (value === obj && nonReferenceContainers.has(obj)) {
|
|
263
|
+
const liftedRef = createLiftedRefForContainer(obj, result2);
|
|
264
|
+
if (liftedRef !== void 0) {
|
|
265
|
+
result2[key] = liftedRef;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return result2;
|
|
270
|
+
};
|
|
271
|
+
scanExistingComponents(document);
|
|
272
|
+
identifyCircularObjects(document, /* @__PURE__ */ new Set(), "schemas");
|
|
273
|
+
const result = cloneWithRefs(document, /* @__PURE__ */ new Set(), "schemas");
|
|
274
|
+
if (extractedComponents.size > 0) {
|
|
275
|
+
const components = result.components ?? {};
|
|
276
|
+
for (const [componentType, items] of extractedComponents) {
|
|
277
|
+
const section = components[componentType] ?? {};
|
|
278
|
+
for (const [name, component] of items) {
|
|
279
|
+
section[name] = component;
|
|
280
|
+
}
|
|
281
|
+
components[componentType] = section;
|
|
282
|
+
}
|
|
283
|
+
result.components = components;
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
};
|
|
287
|
+
export {
|
|
288
|
+
circularToRefs
|
|
289
|
+
};
|
|
290
|
+
//# sourceMappingURL=circular-to-refs.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/object/circular-to-refs.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * OpenAPI component types that can contain circular references.\n * Each type maps to a section in `#/components/{type}`.\n */\nconst COMPONENT_PREFIXES = {\n schemas: 'CircularSchema',\n responses: 'CircularResponse',\n parameters: 'CircularParameter',\n examples: 'CircularExample',\n requestBodies: 'CircularRequestBody',\n headers: 'CircularHeader',\n securitySchemes: 'CircularSecurityScheme',\n links: 'CircularLink',\n callbacks: 'CircularCallback',\n pathItems: 'CircularPathItem',\n} as const\n\ntype ComponentType = keyof typeof COMPONENT_PREFIXES\nconst COMPONENT_TYPES = Object.keys(COMPONENT_PREFIXES) as ComponentType[]\n\n/**\n * Lookup table for OpenAPI keys that change the component context.\n * Using an object lookup is faster than a switch statement for many keys.\n */\nconst KEY_TO_CONTEXT: Readonly<Record<string, ComponentType>> = {\n responses: 'responses',\n parameters: 'parameters',\n requestBody: 'requestBodies',\n headers: 'headers',\n examples: 'examples',\n links: 'links',\n callbacks: 'callbacks',\n securitySchemes: 'securitySchemes',\n schema: 'schemas',\n items: 'schemas',\n additionalProperties: 'schemas',\n allOf: 'schemas',\n oneOf: 'schemas',\n anyOf: 'schemas',\n not: 'schemas',\n properties: 'schemas',\n patternProperties: 'schemas',\n get: 'pathItems',\n put: 'pathItems',\n post: 'pathItems',\n delete: 'pathItems',\n options: 'pathItems',\n head: 'pathItems',\n patch: 'pathItems',\n trace: 'pathItems',\n}\n\n/**\n * These container objects must remain maps in OpenAPI and cannot be replaced by a Reference Object.\n * When a cycle points to one of these containers, we lift the ref to a legal parent object.\n */\nconst NON_REFERENCE_CONTAINER_KEYS = new Set(['properties', 'patternProperties', 'responses'])\n\n/**\n * Determines the component context for a given OpenAPI key.\n * Returns the appropriate ComponentType or null to inherit parent context.\n */\nconst getContextForKey = (key: string): ComponentType | null => {\n // Fast path: direct lookup for known keys\n const context = KEY_TO_CONTEXT[key]\n if (context !== undefined) {\n return context\n }\n\n // Callback path expressions (keys containing '{$') or path templates have pathItem values\n if (key.includes('{$') || key.charCodeAt(0) === 47 /* '/' */) {\n return 'pathItems'\n }\n\n // No context change \u2014 inherit from parent\n return null\n}\n\n/** Metadata about a circular object: its component type and generated name */\ntype CircularMeta = {\n readonly type: ComponentType\n readonly name: string\n}\n\ntype NonReferenceContainerInfo = {\n readonly key: 'properties' | 'patternProperties' | 'responses'\n readonly owner: object\n}\n\n/**\n * Detects and breaks circular JavaScript object references in an OpenAPI document tree.\n *\n * The legacy API client (via openapi-parser's dereference) resolved all $refs inline:\n * it deleted the $ref key and copied every property from the resolved target directly\n * onto the object. For self-referencing or mutually-referencing schemas this created\n * circular JS object graphs that cannot be serialized to JSON or validated by TypeBox.\n *\n * This function walks the object tree depth-first, detects cycles via reference identity,\n * extracts each circular component into the appropriate components section (schemas,\n * responses, parameters, etc.) with a generated name, and replaces ALL occurrences\n * (both first and back-references) with **only** `$ref` and any extra properties\n * (no extra schema properties) so that TypeBox's Value.Cast picks the reference branch\n * of the schemaOrReference union.\n *\n * Returns a new (deep-cloned) document with all cycles replaced. When no circular\n * references exist, the output is structurally identical to the input.\n *\n * @param document - The OpenAPI document to process\n * @param extraProps - Extra properties to add as siblings to the $ref (e.g., '$ref-value')\n * @returns A new document with circular references replaced by $ref pointers\n */\nexport const circularToRefs = (\n document: Record<string, unknown>,\n extraProps: Record<string, unknown> = {},\n): Record<string, unknown> => {\n /**\n * Phase 1 structures:\n * - circularMeta: Maps circular objects to their component type and generated name\n * - objectContext: Records the context when an object is first encountered\n * - existingComponentNames: Maps objects to their existing component names (if in components section)\n * - counters: Tracks unique naming counters per component type\n */\n const circularMeta = new Map<object, CircularMeta>()\n const objectContext = new Map<object, ComponentType>()\n const existingComponentNames = new Map<object, { type: ComponentType; name: string }>()\n const nonReferenceContainers = new WeakSet<object>()\n const nonReferenceContainerInfo = new WeakMap<object, NonReferenceContainerInfo>()\n const counters: Record<ComponentType, number> = {\n schemas: 0,\n responses: 0,\n parameters: 0,\n examples: 0,\n requestBodies: 0,\n headers: 0,\n securitySchemes: 0,\n links: 0,\n callbacks: 0,\n pathItems: 0,\n }\n\n /**\n * Pre-scan: Map existing components to their names.\n * This allows us to reference existing schemas instead of creating duplicates.\n */\n const scanExistingComponents = (doc: Record<string, unknown>): void => {\n const components = doc.components as Record<string, unknown> | undefined\n if (!components) {\n return\n }\n\n for (const componentType of COMPONENT_TYPES) {\n const section = components[componentType] as Record<string, unknown> | undefined\n if (!section) {\n continue\n }\n\n for (const [name, value] of Object.entries(section)) {\n if (value !== null && typeof value === 'object') {\n existingComponentNames.set(value as object, { type: componentType, name })\n }\n }\n }\n }\n\n /**\n * Phase 1: Identify all circular objects in the tree.\n *\n * We traverse depth-first, tracking ancestors in the current path. When we\n * encounter an object already in the ancestor set, we've found a cycle.\n * We record the circular object with its context and a generated name.\n *\n * @param value - Current value being traversed\n * @param ancestors - Set of objects in the current traversal path\n * @param context - Current OpenAPI component context\n */\n const identifyCircularObjects = (value: unknown, ancestors: Set<object>, context: ComponentType): void => {\n // Primitives and null cannot be circular\n if (value === null || typeof value !== 'object') {\n return\n }\n\n const obj = value as object\n\n // Cycle detected: this object is already in our ancestor chain\n if (ancestors.has(obj)) {\n // Keep container maps as maps; we will lift refs on their entries during cloning.\n if (nonReferenceContainers.has(obj)) {\n return\n }\n\n // Only register once \u2014 use existing name if available, otherwise generate new one\n if (!circularMeta.has(obj)) {\n const existing = existingComponentNames.get(obj)\n if (existing) {\n // Use the existing component name\n circularMeta.set(obj, existing)\n } else {\n // Generate a new name\n const componentType = objectContext.get(obj) ?? context\n const count = ++counters[componentType]\n circularMeta.set(obj, {\n type: componentType,\n name: `${COMPONENT_PREFIXES[componentType]}${count}`,\n })\n }\n }\n return\n }\n\n // Record context on first encounter for accurate categorization\n if (!objectContext.has(obj)) {\n objectContext.set(obj, context)\n }\n\n // Add to ancestor chain and recurse\n ancestors.add(obj)\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n identifyCircularObjects(item, ancestors, context)\n }\n } else {\n // Objects: check each property, updating context as needed\n const record = obj as Record<string, unknown>\n for (const key of Object.keys(record)) {\n const child = record[key]\n if (\n NON_REFERENCE_CONTAINER_KEYS.has(key) &&\n child !== null &&\n typeof child === 'object' &&\n !Array.isArray(child)\n ) {\n nonReferenceContainers.add(child as object)\n nonReferenceContainerInfo.set(child as object, {\n key: key as NonReferenceContainerInfo['key'],\n owner: obj,\n })\n }\n\n const newContext = getContextForKey(key) ?? context\n identifyCircularObjects(child, ancestors, newContext)\n }\n }\n\n // Remove from ancestor chain when backtracking\n ancestors.delete(obj)\n }\n\n /** Stores processed (cycle-free) definitions by type and name */\n const extractedComponents = new Map<ComponentType, Map<string, unknown>>()\n\n /** Precompute whether we have extra props to avoid repeated Object.keys calls */\n const hasExtraProps = Object.keys(extraProps).length > 0\n\n /** Creates a $ref object pointing to the extracted component */\n const createRefObject = (meta: CircularMeta): Record<string, unknown> => {\n const ref = { $ref: `#/components/${meta.type}/${meta.name}` }\n return hasExtraProps ? { ...ref, ...extraProps } : ref\n }\n\n /** Gets or creates the target section for extracted components. */\n const getOrCreateExtractedSection = (type: ComponentType): Map<string, unknown> => {\n const existing = extractedComponents.get(type)\n if (existing !== undefined) {\n return existing\n }\n\n const section = new Map<string, unknown>()\n extractedComponents.set(type, section)\n return section\n }\n\n /** Ensures an object has component metadata so it can be referenced legally. */\n const ensureMeta = (obj: object, fallbackType: ComponentType): CircularMeta => {\n const existingMeta = circularMeta.get(obj)\n if (existingMeta !== undefined) {\n return existingMeta\n }\n\n const existingName = existingComponentNames.get(obj)\n if (existingName !== undefined) {\n circularMeta.set(obj, existingName)\n return existingName\n }\n\n const count = ++counters[fallbackType]\n const meta: CircularMeta = {\n type: fallbackType,\n name: `${COMPONENT_PREFIXES[fallbackType]}${count}`,\n }\n circularMeta.set(obj, meta)\n return meta\n }\n\n /** Lifts illegal container self-cycles to a legal reference target. */\n const createLiftedRefForContainer = (\n container: object,\n clonedContainer?: Record<string, unknown>,\n ): Record<string, unknown> | undefined => {\n const info = nonReferenceContainerInfo.get(container)\n if (info === undefined) {\n return undefined\n }\n\n if (info.key === 'properties' || info.key === 'patternProperties') {\n const meta = ensureMeta(info.owner, 'schemas')\n return createRefObject(meta)\n }\n\n // responses map values must be Response Object or Reference Object.\n // Lift self-cycle to the first concrete response entry.\n const responses = container as Record<string, unknown>\n for (const [key, value] of Object.entries(responses)) {\n if (value === null || typeof value !== 'object' || value === container) {\n continue\n }\n\n const responseObject = value as object\n const meta = ensureMeta(responseObject, 'responses')\n if (!existingComponentNames.has(responseObject)) {\n const section = getOrCreateExtractedSection(meta.type)\n if (!section.has(meta.name)) {\n const clonedValue = clonedContainer?.[key]\n if (clonedValue !== undefined) {\n section.set(meta.name, clonedValue)\n }\n }\n }\n\n return createRefObject(meta)\n }\n\n return undefined\n }\n\n const cloneNode = (obj: object, visited: Set<object>, context: ComponentType): unknown => {\n return Array.isArray(obj) ? cloneArray(obj, visited, context) : cloneObject(obj, visited, context)\n }\n\n const cloneNodeWithVisitTracking = (obj: object, visited: Set<object>, context: ComponentType): unknown => {\n // Keep visit bookkeeping in one place so all clone paths handle cycles consistently.\n visited.add(obj)\n const cloned = cloneNode(obj, visited, context)\n visited.delete(obj)\n return cloned\n }\n\n /**\n * Phase 2: Create a deep clone with circular references replaced by $refs.\n * For circular objects, we extract to components and return $ref.\n * For non-circular objects, we simply deep clone.\n */\n const cloneWithRefs = (value: unknown, visited: Set<object>, context: ComponentType): unknown => {\n if (value === null || typeof value !== 'object') {\n return value\n }\n\n const obj = value as object\n const meta = circularMeta.get(obj)\n\n // If this is a circular object and we're already visiting it, return a $ref to break the cycle\n if (meta !== undefined) {\n if (visited.has(obj)) {\n return createRefObject(meta)\n }\n\n const cloned = cloneNodeWithVisitTracking(obj, visited, context)\n if (existingComponentNames.has(obj)) {\n // Do not extract \u2014 it already exists in components.\n // Just clone it in place and replace circular back-references with $ref.\n return cloned\n }\n\n const section = getOrCreateExtractedSection(meta.type)\n if (!section.has(meta.name)) {\n // Store the extracted clone once; later occurrences can directly return a $ref.\n section.set(meta.name, cloned)\n }\n\n return createRefObject(meta)\n }\n\n // Non-circular object: track traversal path to handle cycles that are intentionally\n // not extracted (for container maps where we lift refs to legal targets).\n if (visited.has(obj)) {\n return undefined\n }\n\n const cloned = cloneNodeWithVisitTracking(obj, visited, context)\n\n // This object may become referenceable while cloning children (e.g. lifting\n // a container self-cycle to the parent object). If so, extract it now.\n const lateMeta = circularMeta.get(obj)\n if (lateMeta === undefined || existingComponentNames.has(obj)) {\n // Existing components keep their inline structure, but still have back-refs normalized.\n return cloned\n }\n\n const section = getOrCreateExtractedSection(lateMeta.type)\n if (!section.has(lateMeta.name)) {\n section.set(lateMeta.name, cloned)\n }\n\n return createRefObject(lateMeta)\n }\n\n /**\n * Clones an array, recursively processing each element.\n */\n const cloneArray = (arr: unknown[], visited: Set<object>, context: ComponentType): unknown[] => {\n const result: unknown[] = []\n for (const item of arr) {\n const clonedItem = cloneWithRefs(item, visited, context)\n if (clonedItem !== undefined) {\n result.push(clonedItem)\n }\n }\n return result\n }\n\n /**\n * Clones an object, recursively processing each property.\n * Updates context based on property keys.\n */\n const cloneObject = (obj: object, visited: Set<object>, context: ComponentType): Record<string, unknown> => {\n const record = obj as Record<string, unknown>\n const result: Record<string, unknown> = {}\n\n for (const key of Object.keys(record)) {\n const newContext = getContextForKey(key) ?? context\n const value = record[key]\n const clonedValue = cloneWithRefs(value, visited, newContext)\n if (clonedValue !== undefined) {\n result[key] = clonedValue\n continue\n }\n\n // If a value loops back to its parent container map, lift to a legal reference target.\n if (value === obj && nonReferenceContainers.has(obj)) {\n const liftedRef = createLiftedRefForContainer(obj, result)\n if (liftedRef !== undefined) {\n result[key] = liftedRef\n }\n }\n }\n\n return result\n }\n\n // Execute Pre-scan: map existing components\n scanExistingComponents(document)\n\n // Execute Phase 1: identify all circular objects\n identifyCircularObjects(document, new Set(), 'schemas')\n\n // Execute Phase 2: clone with refs\n const result = cloneWithRefs(document, new Set(), 'schemas') as Record<string, unknown>\n\n // Inject extracted components into the result document\n if (extractedComponents.size > 0) {\n const components = (result.components ?? {}) as Record<string, unknown>\n\n for (const [componentType, items] of extractedComponents) {\n const section = (components[componentType] ?? {}) as Record<string, unknown>\n for (const [name, component] of items) {\n section[name] = component\n }\n components[componentType] = section\n }\n\n result.components = components\n }\n\n return result\n}\n"],
|
|
5
|
+
"mappings": "AAIA,MAAM,qBAAqB;AAAA,EACzB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,eAAe;AAAA,EACf,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,OAAO;AAAA,EACP,WAAW;AAAA,EACX,WAAW;AACb;AAGA,MAAM,kBAAkB,OAAO,KAAK,kBAAkB;AAMtD,MAAM,iBAA0D;AAAA,EAC9D,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,sBAAsB;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AACT;AAMA,MAAM,+BAA+B,oBAAI,IAAI,CAAC,cAAc,qBAAqB,WAAW,CAAC;AAM7F,MAAM,mBAAmB,CAAC,QAAsC;AAE9D,QAAM,UAAU,eAAe,GAAG;AAClC,MAAI,YAAY,QAAW;AACzB,WAAO;AAAA,EACT;AAGA,MAAI,IAAI,SAAS,IAAI,KAAK,IAAI,WAAW,CAAC,MAAM,IAAc;AAC5D,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAmCO,MAAM,iBAAiB,CAC5B,UACA,aAAsC,CAAC,MACX;AAQ5B,QAAM,eAAe,oBAAI,IAA0B;AACnD,QAAM,gBAAgB,oBAAI,IAA2B;AACrD,QAAM,yBAAyB,oBAAI,IAAmD;AACtF,QAAM,yBAAyB,oBAAI,QAAgB;AACnD,QAAM,4BAA4B,oBAAI,QAA2C;AACjF,QAAM,WAA0C;AAAA,IAC9C,SAAS;AAAA,IACT,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,eAAe;AAAA,IACf,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AAMA,QAAM,yBAAyB,CAAC,QAAuC;AACrE,UAAM,aAAa,IAAI;AACvB,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAEA,eAAW,iBAAiB,iBAAiB;AAC3C,YAAM,UAAU,WAAW,aAAa;AACxC,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AAEA,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,YAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,iCAAuB,IAAI,OAAiB,EAAE,MAAM,eAAe,KAAK,CAAC;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAaA,QAAM,0BAA0B,CAAC,OAAgB,WAAwB,YAAiC;AAExG,QAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C;AAAA,IACF;AAEA,UAAM,MAAM;AAGZ,QAAI,UAAU,IAAI,GAAG,GAAG;AAEtB,UAAI,uBAAuB,IAAI,GAAG,GAAG;AACnC;AAAA,MACF;AAGA,UAAI,CAAC,aAAa,IAAI,GAAG,GAAG;AAC1B,cAAM,WAAW,uBAAuB,IAAI,GAAG;AAC/C,YAAI,UAAU;AAEZ,uBAAa,IAAI,KAAK,QAAQ;AAAA,QAChC,OAAO;AAEL,gBAAM,gBAAgB,cAAc,IAAI,GAAG,KAAK;AAChD,gBAAM,QAAQ,EAAE,SAAS,aAAa;AACtC,uBAAa,IAAI,KAAK;AAAA,YACpB,MAAM;AAAA,YACN,MAAM,GAAG,mBAAmB,aAAa,CAAC,GAAG,KAAK;AAAA,UACpD,CAAC;AAAA,QACH;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,CAAC,cAAc,IAAI,GAAG,GAAG;AAC3B,oBAAc,IAAI,KAAK,OAAO;AAAA,IAChC;AAGA,cAAU,IAAI,GAAG;AAEjB,QAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,iBAAW,QAAQ,KAAK;AACtB,gCAAwB,MAAM,WAAW,OAAO;AAAA,MAClD;AAAA,IACF,OAAO;AAEL,YAAM,SAAS;AACf,iBAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,cAAM,QAAQ,OAAO,GAAG;AACxB,YACE,6BAA6B,IAAI,GAAG,KACpC,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,GACpB;AACA,iCAAuB,IAAI,KAAe;AAC1C,oCAA0B,IAAI,OAAiB;AAAA,YAC7C;AAAA,YACA,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AAEA,cAAM,aAAa,iBAAiB,GAAG,KAAK;AAC5C,gCAAwB,OAAO,WAAW,UAAU;AAAA,MACtD;AAAA,IACF;AAGA,cAAU,OAAO,GAAG;AAAA,EACtB;AAGA,QAAM,sBAAsB,oBAAI,IAAyC;AAGzE,QAAM,gBAAgB,OAAO,KAAK,UAAU,EAAE,SAAS;AAGvD,QAAM,kBAAkB,CAAC,SAAgD;AACvE,UAAM,MAAM,EAAE,MAAM,gBAAgB,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG;AAC7D,WAAO,gBAAgB,EAAE,GAAG,KAAK,GAAG,WAAW,IAAI;AAAA,EACrD;AAGA,QAAM,8BAA8B,CAAC,SAA8C;AACjF,UAAM,WAAW,oBAAoB,IAAI,IAAI;AAC7C,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,oBAAI,IAAqB;AACzC,wBAAoB,IAAI,MAAM,OAAO;AACrC,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,CAAC,KAAa,iBAA8C;AAC7E,UAAM,eAAe,aAAa,IAAI,GAAG;AACzC,QAAI,iBAAiB,QAAW;AAC9B,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,uBAAuB,IAAI,GAAG;AACnD,QAAI,iBAAiB,QAAW;AAC9B,mBAAa,IAAI,KAAK,YAAY;AAClC,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,EAAE,SAAS,YAAY;AACrC,UAAM,OAAqB;AAAA,MACzB,MAAM;AAAA,MACN,MAAM,GAAG,mBAAmB,YAAY,CAAC,GAAG,KAAK;AAAA,IACnD;AACA,iBAAa,IAAI,KAAK,IAAI;AAC1B,WAAO;AAAA,EACT;AAGA,QAAM,8BAA8B,CAClC,WACA,oBACwC;AACxC,UAAM,OAAO,0BAA0B,IAAI,SAAS;AACpD,QAAI,SAAS,QAAW;AACtB,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,QAAQ,gBAAgB,KAAK,QAAQ,qBAAqB;AACjE,YAAM,OAAO,WAAW,KAAK,OAAO,SAAS;AAC7C,aAAO,gBAAgB,IAAI;AAAA,IAC7B;AAIA,UAAM,YAAY;AAClB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,UAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,UAAU,WAAW;AACtE;AAAA,MACF;AAEA,YAAM,iBAAiB;AACvB,YAAM,OAAO,WAAW,gBAAgB,WAAW;AACnD,UAAI,CAAC,uBAAuB,IAAI,cAAc,GAAG;AAC/C,cAAM,UAAU,4BAA4B,KAAK,IAAI;AACrD,YAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,GAAG;AAC3B,gBAAM,cAAc,kBAAkB,GAAG;AACzC,cAAI,gBAAgB,QAAW;AAC7B,oBAAQ,IAAI,KAAK,MAAM,WAAW;AAAA,UACpC;AAAA,QACF;AAAA,MACF;AAEA,aAAO,gBAAgB,IAAI;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,CAAC,KAAa,SAAsB,YAAoC;AACxF,WAAO,MAAM,QAAQ,GAAG,IAAI,WAAW,KAAK,SAAS,OAAO,IAAI,YAAY,KAAK,SAAS,OAAO;AAAA,EACnG;AAEA,QAAM,6BAA6B,CAAC,KAAa,SAAsB,YAAoC;AAEzG,YAAQ,IAAI,GAAG;AACf,UAAM,SAAS,UAAU,KAAK,SAAS,OAAO;AAC9C,YAAQ,OAAO,GAAG;AAClB,WAAO;AAAA,EACT;AAOA,QAAM,gBAAgB,CAAC,OAAgB,SAAsB,YAAoC;AAC/F,QAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,aAAO;AAAA,IACT;AAEA,UAAM,MAAM;AACZ,UAAM,OAAO,aAAa,IAAI,GAAG;AAGjC,QAAI,SAAS,QAAW;AACtB,UAAI,QAAQ,IAAI,GAAG,GAAG;AACpB,eAAO,gBAAgB,IAAI;AAAA,MAC7B;AAEA,YAAMA,UAAS,2BAA2B,KAAK,SAAS,OAAO;AAC/D,UAAI,uBAAuB,IAAI,GAAG,GAAG;AAGnC,eAAOA;AAAA,MACT;AAEA,YAAMC,WAAU,4BAA4B,KAAK,IAAI;AACrD,UAAI,CAACA,SAAQ,IAAI,KAAK,IAAI,GAAG;AAE3B,QAAAA,SAAQ,IAAI,KAAK,MAAMD,OAAM;AAAA,MAC/B;AAEA,aAAO,gBAAgB,IAAI;AAAA,IAC7B;AAIA,QAAI,QAAQ,IAAI,GAAG,GAAG;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,2BAA2B,KAAK,SAAS,OAAO;AAI/D,UAAM,WAAW,aAAa,IAAI,GAAG;AACrC,QAAI,aAAa,UAAa,uBAAuB,IAAI,GAAG,GAAG;AAE7D,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,4BAA4B,SAAS,IAAI;AACzD,QAAI,CAAC,QAAQ,IAAI,SAAS,IAAI,GAAG;AAC/B,cAAQ,IAAI,SAAS,MAAM,MAAM;AAAA,IACnC;AAEA,WAAO,gBAAgB,QAAQ;AAAA,EACjC;AAKA,QAAM,aAAa,CAAC,KAAgB,SAAsB,YAAsC;AAC9F,UAAME,UAAoB,CAAC;AAC3B,eAAW,QAAQ,KAAK;AACtB,YAAM,aAAa,cAAc,MAAM,SAAS,OAAO;AACvD,UAAI,eAAe,QAAW;AAC5B,QAAAA,QAAO,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AACA,WAAOA;AAAA,EACT;AAMA,QAAM,cAAc,CAAC,KAAa,SAAsB,YAAoD;AAC1G,UAAM,SAAS;AACf,UAAMA,UAAkC,CAAC;AAEzC,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,YAAM,aAAa,iBAAiB,GAAG,KAAK;AAC5C,YAAM,QAAQ,OAAO,GAAG;AACxB,YAAM,cAAc,cAAc,OAAO,SAAS,UAAU;AAC5D,UAAI,gBAAgB,QAAW;AAC7B,QAAAA,QAAO,GAAG,IAAI;AACd;AAAA,MACF;AAGA,UAAI,UAAU,OAAO,uBAAuB,IAAI,GAAG,GAAG;AACpD,cAAM,YAAY,4BAA4B,KAAKA,OAAM;AACzD,YAAI,cAAc,QAAW;AAC3B,UAAAA,QAAO,GAAG,IAAI;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,WAAOA;AAAA,EACT;AAGA,yBAAuB,QAAQ;AAG/B,0BAAwB,UAAU,oBAAI,IAAI,GAAG,SAAS;AAGtD,QAAM,SAAS,cAAc,UAAU,oBAAI,IAAI,GAAG,SAAS;AAG3D,MAAI,oBAAoB,OAAO,GAAG;AAChC,UAAM,aAAc,OAAO,cAAc,CAAC;AAE1C,eAAW,CAAC,eAAe,KAAK,KAAK,qBAAqB;AACxD,YAAM,UAAW,WAAW,aAAa,KAAK,CAAC;AAC/C,iBAAW,CAAC,MAAM,SAAS,KAAK,OAAO;AACrC,gBAAQ,IAAI,IAAI;AAAA,MAClB;AACA,iBAAW,aAAa,IAAI;AAAA,IAC9B;AAEA,WAAO,aAAa;AAAA,EACtB;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": ["cloned", "section", "result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the server from a string, used to check for servers in paths during migration
|
|
3
|
+
*
|
|
4
|
+
* @param path - The URL string to parse. If no protocol is provided, the URL API will throw an error.
|
|
5
|
+
* @returns A tuple of [origin, remainingPath] or null if the input is empty, whitespace-only, or invalid.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* extractServer('https://api.example.com/v1/users?id=123')
|
|
9
|
+
* // Returns: ['https://api.example.com', '/v1/users?id=123']
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* extractServer('/users')
|
|
13
|
+
* // Returns: null
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* extractServer('/users')
|
|
17
|
+
* // Returns: null
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* extractServer('//api.example.com/v1/users')
|
|
21
|
+
* // Returns: ['//api.example.com', '/v1/users']
|
|
22
|
+
*/
|
|
23
|
+
export declare const extractServerFromPath: (path?: string) => [string, string] | null;
|
|
24
|
+
//# sourceMappingURL=extract-server-from-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-server-from-path.d.ts","sourceRoot":"","sources":["../../src/url/extract-server-from-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAS,KAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAmCpE,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const extractServerFromPath = (path = "") => {
|
|
2
|
+
if (!path.trim()) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
if (path.startsWith("//")) {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(`https:${path}`);
|
|
8
|
+
if (url.origin === "null") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const origin = url.origin.replace(/^https?:/, "");
|
|
12
|
+
const remainingPath = decodeURIComponent(url.pathname) + url.search + url.hash;
|
|
13
|
+
return [origin, remainingPath];
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(path);
|
|
20
|
+
if (url.origin === "null") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const remainingPath = decodeURIComponent(url.pathname) + url.search + url.hash;
|
|
24
|
+
return [url.origin, remainingPath];
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
extractServerFromPath
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=extract-server-from-path.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/url/extract-server-from-path.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Extracts the server from a string, used to check for servers in paths during migration\n *\n * @param path - The URL string to parse. If no protocol is provided, the URL API will throw an error.\n * @returns A tuple of [origin, remainingPath] or null if the input is empty, whitespace-only, or invalid.\n *\n * @example\n * extractServer('https://api.example.com/v1/users?id=123')\n * // Returns: ['https://api.example.com', '/v1/users?id=123']\n *\n * @example\n * extractServer('/users')\n * // Returns: null\n *\n * @example\n * extractServer('/users')\n * // Returns: null\n *\n * @example\n * extractServer('//api.example.com/v1/users')\n * // Returns: ['//api.example.com', '/v1/users']\n */\nexport const extractServerFromPath = (path = ''): [string, string] | null => {\n if (!path.trim()) {\n return null\n }\n\n /** Handle protocol-relative URLs (e.g., \"//api.example.com\") */\n if (path.startsWith('//')) {\n try {\n /** Use dummy protocol to parse, then strip it */\n const url = new URL(`https:${path}`)\n if (url.origin === 'null') {\n return null\n }\n const origin = url.origin.replace(/^https?:/, '')\n\n /** Decode pathname to preserve OpenAPI template variables like {userId} */\n const remainingPath = decodeURIComponent(url.pathname) + url.search + url.hash\n return [origin, remainingPath]\n } catch {\n return null\n }\n }\n\n try {\n const url = new URL(path)\n /** URL API returns \"null\" for file:// and other invalid protocols */\n if (url.origin === 'null') {\n return null\n }\n /** Decode pathname to preserve OpenAPI template variables like {userId} */\n const remainingPath = decodeURIComponent(url.pathname) + url.search + url.hash\n return [url.origin, remainingPath]\n } catch {\n return null\n }\n}\n"],
|
|
5
|
+
"mappings": "AAsBO,MAAM,wBAAwB,CAAC,OAAO,OAAgC;AAC3E,MAAI,CAAC,KAAK,KAAK,GAAG;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,WAAW,IAAI,GAAG;AACzB,QAAI;AAEF,YAAM,MAAM,IAAI,IAAI,SAAS,IAAI,EAAE;AACnC,UAAI,IAAI,WAAW,QAAQ;AACzB,eAAO;AAAA,MACT;AACA,YAAM,SAAS,IAAI,OAAO,QAAQ,YAAY,EAAE;AAGhD,YAAM,gBAAgB,mBAAmB,IAAI,QAAQ,IAAI,IAAI,SAAS,IAAI;AAC1E,aAAO,CAAC,QAAQ,aAAa;AAAA,IAC/B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI;AAExB,QAAI,IAAI,WAAW,QAAQ;AACzB,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgB,mBAAmB,IAAI,QAAQ,IAAI,IAAI,SAAS,IAAI;AAC1E,WAAO,CAAC,IAAI,QAAQ,aAAa;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"helpers",
|
|
15
15
|
"js"
|
|
16
16
|
],
|
|
17
|
-
"version": "0.2.
|
|
17
|
+
"version": "0.2.16",
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=20"
|
|
20
20
|
},
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"types": "./dist/array/*.d.ts",
|
|
28
28
|
"default": "./dist/array/*.js"
|
|
29
29
|
},
|
|
30
|
+
"./consts/*": {
|
|
31
|
+
"import": "./dist/consts/*.js",
|
|
32
|
+
"types": "./dist/consts/*.d.ts",
|
|
33
|
+
"default": "./dist/consts/*.js"
|
|
34
|
+
},
|
|
30
35
|
"./crypto/*": {
|
|
31
36
|
"import": "./dist/crypto/*.js",
|
|
32
37
|
"types": "./dist/crypto/*.d.ts",
|