@scalar/workspace-store 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/package.json +6 -2
- package/.turbo/turbo-build.log +0 -13
- package/esbuild.ts +0 -8
- package/src/client.test.ts +0 -409
- package/src/client.ts +0 -254
- package/src/helpers/general.ts +0 -30
- package/src/helpers/json-path-utils.test.ts +0 -13
- package/src/helpers/json-path-utils.ts +0 -38
- package/src/helpers/proxy.test.ts +0 -61
- package/src/helpers/proxy.ts +0 -213
- package/src/schemas/callback.ts +0 -13
- package/src/schemas/components.ts +0 -36
- package/src/schemas/contact.ts +0 -11
- package/src/schemas/discriminator.ts +0 -13
- package/src/schemas/encoding.ts +0 -17
- package/src/schemas/example.ts +0 -17
- package/src/schemas/external-documentation.ts +0 -9
- package/src/schemas/header.ts +0 -19
- package/src/schemas/info.ts +0 -23
- package/src/schemas/license.ts +0 -11
- package/src/schemas/link.ts +0 -24
- package/src/schemas/media-type.ts +0 -21
- package/src/schemas/oauth-flow.ts +0 -13
- package/src/schemas/oauthflows.ts +0 -16
- package/src/schemas/openapi-document.ts +0 -34
- package/src/schemas/operation-without-callback.ts +0 -37
- package/src/schemas/parameter.ts +0 -26
- package/src/schemas/path-item.ts +0 -35
- package/src/schemas/paths.ts +0 -11
- package/src/schemas/reference.ts +0 -20
- package/src/schemas/request-body.ts +0 -12
- package/src/schemas/response.ts +0 -16
- package/src/schemas/responses.ts +0 -14
- package/src/schemas/schema.ts +0 -26
- package/src/schemas/security-requirement.ts +0 -16
- package/src/schemas/security-scheme.ts +0 -58
- package/src/schemas/server-variable.ts +0 -11
- package/src/schemas/server-workspace.ts +0 -36
- package/src/schemas/server.ts +0 -12
- package/src/schemas/tag.ts +0 -12
- package/src/schemas/xml.ts +0 -19
- package/src/schemas.ts +0 -6
- package/src/server.test.ts +0 -470
- package/src/server.ts +0 -341
- package/test/helpers.ts +0 -16
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -8
- package/vite.config.ts +0 -9
package/src/client.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { reactive, toRaw } from 'vue'
|
|
2
|
-
import type { WorkspaceMeta, WorkspaceDocumentMeta, Workspace } from './schemas/server-workspace'
|
|
3
|
-
import { createMagicProxy } from './helpers/proxy'
|
|
4
|
-
import { isObject } from '@/helpers/general'
|
|
5
|
-
import { getValueByPath } from '@/helpers/json-path-utils'
|
|
6
|
-
import { bundle } from '@scalar/openapi-parser'
|
|
7
|
-
import { fetchUrls } from '@scalar/openapi-parser/utils/bundle/plugins/fetch-urls'
|
|
8
|
-
|
|
9
|
-
type WorkspaceDocumentMetaInput = { meta?: WorkspaceDocumentMeta; name: string }
|
|
10
|
-
type WorkspaceDocumentInput =
|
|
11
|
-
| ({ document: Record<string, unknown> } & WorkspaceDocumentMetaInput)
|
|
12
|
-
| ({ url: string } & WorkspaceDocumentMetaInput)
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Resolves a workspace document from various input sources (URL, local file, or direct document object).
|
|
16
|
-
*
|
|
17
|
-
* @param workspaceDocument - The document input to resolve, which can be:
|
|
18
|
-
* - A URL to fetch the document from
|
|
19
|
-
* - A local file path to read the document from
|
|
20
|
-
* - A direct document object
|
|
21
|
-
* @returns A promise that resolves to an object containing:
|
|
22
|
-
* - ok: boolean indicating if the resolution was successful
|
|
23
|
-
* - data: The resolved document data
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* // Resolve from URL
|
|
27
|
-
* const urlDoc = await loadDocument({ name: 'api', url: 'https://api.example.com/openapi.json' })
|
|
28
|
-
*
|
|
29
|
-
* // Resolve from local file
|
|
30
|
-
* const fileDoc = await loadDocument({ name: 'local', path: './openapi.json' })
|
|
31
|
-
*
|
|
32
|
-
* // Resolve direct document
|
|
33
|
-
* const directDoc = await loadDocument({
|
|
34
|
-
* name: 'inline',
|
|
35
|
-
* document: { openapi: '3.0.0', paths: {} }
|
|
36
|
-
* })
|
|
37
|
-
*/
|
|
38
|
-
async function loadDocument(workspaceDocument: WorkspaceDocumentInput) {
|
|
39
|
-
if ('url' in workspaceDocument) {
|
|
40
|
-
return fetchUrls().exec(workspaceDocument.url)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
ok: true as const,
|
|
45
|
-
data: workspaceDocument.document,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Creates a reactive workspace store that manages documents and their metadata.
|
|
51
|
-
* The store provides functionality for accessing, updating, and resolving document references.
|
|
52
|
-
*
|
|
53
|
-
* @param workspaceProps - Configuration object for the workspace
|
|
54
|
-
* @param workspaceProps.meta - Optional metadata for the workspace
|
|
55
|
-
* @param workspaceProps.documents - Optional record of documents to initialize the workspace with
|
|
56
|
-
* @returns An object containing methods and getters for managing the workspace
|
|
57
|
-
* @deprecated Use `createWorkspaceStore` instead.
|
|
58
|
-
*/
|
|
59
|
-
export function createWorkspaceStoreSync(workspaceProps?: {
|
|
60
|
-
meta?: WorkspaceMeta
|
|
61
|
-
}) {
|
|
62
|
-
// Create a reactive workspace object with proxied documents
|
|
63
|
-
// Each document is wrapped in a proxy to enable reactive updates and reference resolution
|
|
64
|
-
const workspace = reactive({
|
|
65
|
-
...workspaceProps?.meta,
|
|
66
|
-
documents: {},
|
|
67
|
-
/**
|
|
68
|
-
* Returns the currently active document from the workspace.
|
|
69
|
-
* The active document is determined by the 'x-scalar-active-document' metadata field,
|
|
70
|
-
* falling back to the first document in the workspace if no active document is specified.
|
|
71
|
-
*
|
|
72
|
-
* @returns The active document or undefined if no document is found
|
|
73
|
-
*/
|
|
74
|
-
get activeDocument(): (typeof workspace.documents)[number] | undefined {
|
|
75
|
-
const activeDocumentKey = workspace['x-scalar-active-document'] ?? Object.keys(workspace.documents)[0] ?? ''
|
|
76
|
-
return workspace.documents[activeDocumentKey]
|
|
77
|
-
},
|
|
78
|
-
}) as Workspace
|
|
79
|
-
|
|
80
|
-
// Cache to track visited nodes during reference resolution to prevent bundling the same subtree multiple times
|
|
81
|
-
// This is needed because we are doing partial bundle operations
|
|
82
|
-
const visitedNodesCache = new Set()
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
/**
|
|
86
|
-
* Returns the raw (non-reactive) workspace object
|
|
87
|
-
*/
|
|
88
|
-
get rawWorkspace() {
|
|
89
|
-
return toRaw(workspace)
|
|
90
|
-
},
|
|
91
|
-
/**
|
|
92
|
-
* Returns the reactive workspace object with an additional activeDocument getter
|
|
93
|
-
*/
|
|
94
|
-
get workspace() {
|
|
95
|
-
return workspace
|
|
96
|
-
},
|
|
97
|
-
/**
|
|
98
|
-
* Updates a specific metadata field in the workspace
|
|
99
|
-
* @param key - The metadata field to update
|
|
100
|
-
* @param value - The new value for the field
|
|
101
|
-
* @example
|
|
102
|
-
* // Update the workspace title
|
|
103
|
-
* update('x-scalar-active-document', 'document-name')
|
|
104
|
-
*/
|
|
105
|
-
update<K extends keyof WorkspaceMeta>(key: K, value: WorkspaceMeta[K]) {
|
|
106
|
-
// @ts-ignore
|
|
107
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
108
|
-
throw new Error('Invalid key: cannot modify prototype')
|
|
109
|
-
}
|
|
110
|
-
Object.assign(workspace, { [key]: value })
|
|
111
|
-
},
|
|
112
|
-
/**
|
|
113
|
-
* Updates a specific metadata field in a document
|
|
114
|
-
* @param name - The name of the document to update ('active' or a specific document name)
|
|
115
|
-
* @param key - The metadata field to update
|
|
116
|
-
* @param value - The new value for the field
|
|
117
|
-
* @throws Error if the specified document doesn't exist
|
|
118
|
-
* @example
|
|
119
|
-
* // Update the auth of the active document
|
|
120
|
-
* updateDocument('active', 'x-scalar-active-auth', 'Bearer')
|
|
121
|
-
* // Update the auth of a specific document
|
|
122
|
-
* updateDocument('document-name', 'x-scalar-active-auth', 'Bearer')
|
|
123
|
-
*/
|
|
124
|
-
updateDocument<K extends keyof WorkspaceDocumentMeta>(
|
|
125
|
-
name: 'active' | (string & {}),
|
|
126
|
-
key: K,
|
|
127
|
-
value: WorkspaceDocumentMeta[K],
|
|
128
|
-
) {
|
|
129
|
-
const currentDocument =
|
|
130
|
-
workspace.documents[
|
|
131
|
-
name === 'active'
|
|
132
|
-
? (workspace['x-scalar-active-document'] ?? Object.keys(workspace.documents)[0] ?? '')
|
|
133
|
-
: name
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
if (!currentDocument) {
|
|
137
|
-
throw 'Please select a valid document'
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
Object.assign(currentDocument, { [key]: value })
|
|
141
|
-
},
|
|
142
|
-
/**
|
|
143
|
-
* Resolves a reference in the active document by following the provided path and resolving any external $ref references.
|
|
144
|
-
* This method traverses the document structure following the given path and resolves any $ref references it encounters.
|
|
145
|
-
* During resolution, it sets a loading status and updates the reference with the resolved content.
|
|
146
|
-
*
|
|
147
|
-
* @param path - Array of strings representing the path to the reference (e.g. ['paths', '/users', 'get', 'responses', '200'])
|
|
148
|
-
* @throws Error if the path is invalid or empty
|
|
149
|
-
* @example
|
|
150
|
-
* // Resolve a reference in the active document
|
|
151
|
-
* resolve(['paths', '/users', 'get', 'responses', '200'])
|
|
152
|
-
*/
|
|
153
|
-
resolve: async (path: string[]) => {
|
|
154
|
-
const activeDocument = workspace.activeDocument
|
|
155
|
-
|
|
156
|
-
const target = getValueByPath(activeDocument, path)
|
|
157
|
-
|
|
158
|
-
if (!isObject(target)) {
|
|
159
|
-
console.error(
|
|
160
|
-
`Invalid path provided for resolution. Path: [${path.join(', ')}]. Found value of type: ${typeof target}. Expected an object.`,
|
|
161
|
-
)
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Bundle the target document with the active document as root, resolving any external references
|
|
166
|
-
// and tracking resolution status through hooks
|
|
167
|
-
return bundle(target, {
|
|
168
|
-
root: activeDocument,
|
|
169
|
-
treeShake: false,
|
|
170
|
-
plugins: [fetchUrls()],
|
|
171
|
-
urlMap: false,
|
|
172
|
-
hooks: {
|
|
173
|
-
onResolveStart: (node) => {
|
|
174
|
-
node['$status'] = 'loading'
|
|
175
|
-
},
|
|
176
|
-
onResolveError: (node) => {
|
|
177
|
-
node['$status'] = 'error'
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
visitedNodes: visitedNodesCache,
|
|
181
|
-
})
|
|
182
|
-
},
|
|
183
|
-
/**
|
|
184
|
-
* Adds a new document to the workspace
|
|
185
|
-
* @param document - The document content to add. This should be a valid OpenAPI/Swagger document or other supported format
|
|
186
|
-
* @param meta - Metadata for the document, including its name and other properties defined in WorkspaceDocumentMeta
|
|
187
|
-
* @example
|
|
188
|
-
* // Add a new OpenAPI document to the workspace
|
|
189
|
-
* store.addDocument({
|
|
190
|
-
* name: 'name',
|
|
191
|
-
* document: {
|
|
192
|
-
* openapi: '3.0.0',
|
|
193
|
-
* info: { title: 'title' },
|
|
194
|
-
* },
|
|
195
|
-
* meta: {
|
|
196
|
-
* 'x-scalar-active-auth': 'Bearer',
|
|
197
|
-
* 'x-scalar-active-server': 'production'
|
|
198
|
-
* }
|
|
199
|
-
* })
|
|
200
|
-
*/
|
|
201
|
-
addDocument: async (input: WorkspaceDocumentInput) => {
|
|
202
|
-
const { name, meta } = input
|
|
203
|
-
|
|
204
|
-
const resolve = await loadDocument(input)
|
|
205
|
-
|
|
206
|
-
if (!resolve.ok || !isObject(resolve.data)) {
|
|
207
|
-
console.error(`Can not load the document '${name}'`)
|
|
208
|
-
workspace.documents[name] = {
|
|
209
|
-
...meta,
|
|
210
|
-
}
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
workspace.documents[name] = createMagicProxy({ ...(resolve.data as Record<string, unknown>), ...meta })
|
|
215
|
-
},
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Creates a reactive workspace store that manages documents and their metadata.
|
|
221
|
-
* The store provides functionality for accessing, updating, and resolving document references.
|
|
222
|
-
*
|
|
223
|
-
* @param workspaceProps - Configuration object for the workspace
|
|
224
|
-
* @param workspaceProps.meta - Optional metadata for the workspace
|
|
225
|
-
* @param workspaceProps.documents - Optional record of documents to initialize the workspace with
|
|
226
|
-
* @returns An object containing methods and getters for managing the workspace
|
|
227
|
-
* @example
|
|
228
|
-
* // Create a workspace store with metadata and documents
|
|
229
|
-
* const store = await createWorkspaceStore({
|
|
230
|
-
* meta: {
|
|
231
|
-
* name: 'My Workspace',
|
|
232
|
-
* description: 'A workspace for my API'
|
|
233
|
-
* },
|
|
234
|
-
* documents: [
|
|
235
|
-
* {
|
|
236
|
-
* name: 'petstore',
|
|
237
|
-
* document: {
|
|
238
|
-
* openapi: '3.0.0',
|
|
239
|
-
* info: { title: 'Petstore API' }
|
|
240
|
-
* }
|
|
241
|
-
* }
|
|
242
|
-
* ]
|
|
243
|
-
* })
|
|
244
|
-
*/
|
|
245
|
-
export async function createWorkspaceStore(workspaceProps?: {
|
|
246
|
-
meta?: WorkspaceMeta
|
|
247
|
-
documents?: WorkspaceDocumentInput[]
|
|
248
|
-
}) {
|
|
249
|
-
const store = createWorkspaceStoreSync({ meta: workspaceProps?.meta })
|
|
250
|
-
|
|
251
|
-
await Promise.all(workspaceProps?.documents?.map((it) => store.addDocument(it)) ?? [])
|
|
252
|
-
|
|
253
|
-
return store
|
|
254
|
-
}
|
package/src/helpers/general.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export type UnknownObject = Record<string, unknown>
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns true if the value is a non-null object (but not an array).
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* isObject({}) // true
|
|
9
|
-
* isObject([]) // false
|
|
10
|
-
* isObject(null) // false
|
|
11
|
-
* ```
|
|
12
|
-
*/
|
|
13
|
-
export function isObject(value: unknown): value is UnknownObject {
|
|
14
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Checks if a string is a local reference (starts with #)
|
|
19
|
-
* @param value - The reference string to check
|
|
20
|
-
* @returns true if the string is a local reference, false otherwise
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* isLocalRef('#/components/schemas/User') // true
|
|
24
|
-
* isLocalRef('https://example.com/schema.json') // false
|
|
25
|
-
* isLocalRef('./local-schema.json') // false
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export function isLocalRef(value: string): boolean {
|
|
29
|
-
return value.startsWith('#')
|
|
30
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { parseJsonPointer } from '@/helpers/json-path-utils'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
describe('parseJsonPointer', () => {
|
|
5
|
-
test.each([
|
|
6
|
-
['#/users/name', ['users', 'name']],
|
|
7
|
-
['#/', []],
|
|
8
|
-
['', []],
|
|
9
|
-
['users/name', ['users', 'name']],
|
|
10
|
-
])('should correctly parse json pointers', (a, b) => {
|
|
11
|
-
expect(parseJsonPointer(a)).toEqual(b)
|
|
12
|
-
})
|
|
13
|
-
})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses a JSON Pointer string into an array of path segments
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```ts
|
|
6
|
-
* parseJsonPointer('#/components/schemas/User')
|
|
7
|
-
*
|
|
8
|
-
* ['components', 'schemas', 'User']
|
|
9
|
-
* ```
|
|
10
|
-
*/
|
|
11
|
-
export function parseJsonPointer(pointer: string): string[] {
|
|
12
|
-
return (
|
|
13
|
-
pointer
|
|
14
|
-
// Split on '/'
|
|
15
|
-
.split('/')
|
|
16
|
-
// Remove the leading '#' if present
|
|
17
|
-
.filter((segment, index) => (index !== 0 || segment !== '#') && segment)
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Retrieves a nested value from the source document using a path array
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* getValueByPath(document, ['components', 'schemas', 'User'])
|
|
27
|
-
*
|
|
28
|
-
* { id: '123', name: 'John Doe' }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function getValueByPath(obj: any, pointer: string[]): unknown {
|
|
32
|
-
return pointer.reduce((acc, part) => {
|
|
33
|
-
if (acc === undefined || acc === null) {
|
|
34
|
-
return undefined
|
|
35
|
-
}
|
|
36
|
-
return acc[part]
|
|
37
|
-
}, obj)
|
|
38
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { createMagicProxy } from '@/helpers/proxy'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
describe('createMagicProxy', () => {
|
|
5
|
-
test('should correctly proxy internal refs', () => {
|
|
6
|
-
const input = {
|
|
7
|
-
a: 'hello',
|
|
8
|
-
b: {
|
|
9
|
-
'$ref': '#/a',
|
|
10
|
-
},
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const result = createMagicProxy(input)
|
|
14
|
-
|
|
15
|
-
expect(result.b).toBe('hello')
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('should correctly proxy deep nested refs', () => {
|
|
19
|
-
const input = {
|
|
20
|
-
a: {
|
|
21
|
-
b: {
|
|
22
|
-
c: {
|
|
23
|
-
d: {
|
|
24
|
-
prop: 'hello',
|
|
25
|
-
},
|
|
26
|
-
e: {
|
|
27
|
-
'$ref': '#/a/b/c/d',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const result = createMagicProxy(input) as any
|
|
35
|
-
expect(result.a.b.c.e.prop).toBe('hello')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('should correctly proxy multi refs', () => {
|
|
39
|
-
const input = {
|
|
40
|
-
a: {
|
|
41
|
-
b: {
|
|
42
|
-
c: {
|
|
43
|
-
prop: 'hello',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
e: {
|
|
48
|
-
f: {
|
|
49
|
-
$ref: '#/a/b/c/prop',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
d: {
|
|
53
|
-
$ref: '#/e/f',
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const result = createMagicProxy(input)
|
|
58
|
-
|
|
59
|
-
expect(result.d).toBe('hello')
|
|
60
|
-
})
|
|
61
|
-
})
|
package/src/helpers/proxy.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { isReactive, toRaw } from 'vue'
|
|
2
|
-
import { getValueByPath, parseJsonPointer } from './json-path-utils'
|
|
3
|
-
import { isLocalRef, isObject } from './general'
|
|
4
|
-
import type { UnknownObject } from './general'
|
|
5
|
-
|
|
6
|
-
export const TARGET_SYMBOL = Symbol('target')
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Creates a proxy handler that automatically resolves JSON references ($ref) in an object.
|
|
10
|
-
* The handler intercepts property access, assignment, and property enumeration to automatically
|
|
11
|
-
* resolve any $ref references to their target values in the source document.
|
|
12
|
-
*
|
|
13
|
-
* @param sourceDocument - The source document containing the reference targets
|
|
14
|
-
* @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies
|
|
15
|
-
* @returns A proxy handler that automatically resolves $ref references
|
|
16
|
-
*/
|
|
17
|
-
function createProxyHandler(
|
|
18
|
-
sourceDocument: UnknownObject,
|
|
19
|
-
resolvedProxyCache?: WeakMap<object, UnknownObject>,
|
|
20
|
-
): ProxyHandler<UnknownObject> {
|
|
21
|
-
return {
|
|
22
|
-
get(target, property, receiver) {
|
|
23
|
-
if (property === TARGET_SYMBOL) {
|
|
24
|
-
return target
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (property === '__isProxy') {
|
|
28
|
-
return true
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const value = Reflect.get(target, property, receiver)
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Recursively resolves nested references in an object.
|
|
35
|
-
* If the value is not an object, returns it as is.
|
|
36
|
-
* If the value has a $ref property:
|
|
37
|
-
* - For local references: resolves the reference and continues resolving nested refs
|
|
38
|
-
* - For all other objects: creates a proxy for lazy resolution
|
|
39
|
-
*/
|
|
40
|
-
const deepResolveNestedRefs = (value: unknown) => {
|
|
41
|
-
if (!isObject(value)) {
|
|
42
|
-
return value
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if ('$ref' in value) {
|
|
46
|
-
const ref = value.$ref as string
|
|
47
|
-
|
|
48
|
-
if (isLocalRef(ref)) {
|
|
49
|
-
const referencePath = parseJsonPointer(ref)
|
|
50
|
-
const resolvedValue = getValueByPath(sourceDocument, referencePath)
|
|
51
|
-
|
|
52
|
-
return deepResolveNestedRefs(resolvedValue)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return createMagicProxy(value, sourceDocument, resolvedProxyCache)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return deepResolveNestedRefs(value)
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
set(target: UnknownObject, property: string, newValue: unknown, receiver: UnknownObject) {
|
|
63
|
-
const rawTarget = isReactive(target) ? toRaw(target) : target
|
|
64
|
-
const currentValue = rawTarget[property]
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
isObject(currentValue) &&
|
|
68
|
-
'$ref' in currentValue &&
|
|
69
|
-
typeof currentValue.$ref === 'string' &&
|
|
70
|
-
isLocalRef(currentValue.$ref)
|
|
71
|
-
) {
|
|
72
|
-
const referencePath = parseJsonPointer(currentValue.$ref)
|
|
73
|
-
const targetObject = getValueByPath(sourceDocument, referencePath.slice(0, -1)) as UnknownObject
|
|
74
|
-
const lastPathSegment = referencePath[referencePath.length - 1]
|
|
75
|
-
|
|
76
|
-
if (targetObject && lastPathSegment) {
|
|
77
|
-
targetObject[lastPathSegment] = newValue
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
Reflect.set(rawTarget, property, newValue, receiver)
|
|
81
|
-
}
|
|
82
|
-
return true
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
has(target: UnknownObject, key: string) {
|
|
86
|
-
if (typeof key === 'string' && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
87
|
-
const referencePath = parseJsonPointer(target['$ref'])
|
|
88
|
-
const resolvedValue = getValueByPath(sourceDocument, referencePath) as UnknownObject
|
|
89
|
-
|
|
90
|
-
return resolvedValue ? key in resolvedValue : false
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return key in target
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
ownKeys(target: UnknownObject) {
|
|
97
|
-
if ('$ref' in target && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
98
|
-
const referencePath = parseJsonPointer(target['$ref'])
|
|
99
|
-
const resolvedValue = getValueByPath(sourceDocument, referencePath)
|
|
100
|
-
|
|
101
|
-
return resolvedValue ? Reflect.ownKeys(resolvedValue) : []
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return Reflect.ownKeys(target)
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
getOwnPropertyDescriptor(target: UnknownObject, key: string) {
|
|
108
|
-
if ('$ref' in target && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
109
|
-
const referencePath = parseJsonPointer(target['$ref'])
|
|
110
|
-
const resolvedValue = getValueByPath(sourceDocument, referencePath)
|
|
111
|
-
|
|
112
|
-
if (resolvedValue) {
|
|
113
|
-
return Object.getOwnPropertyDescriptor(resolvedValue, key)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return Object.getOwnPropertyDescriptor(target, key)
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Creates a proxy that automatically resolves JSON references ($ref) in an object.
|
|
124
|
-
* The proxy intercepts property access and automatically resolves any $ref references
|
|
125
|
-
* to their target values in the source document.
|
|
126
|
-
*
|
|
127
|
-
* @param targetObject - The object to create a proxy for
|
|
128
|
-
* @param sourceDocument - The source document containing the reference targets (defaults to targetObject)
|
|
129
|
-
* @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies
|
|
130
|
-
* @returns A proxy that automatically resolves $ref references
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* // Basic usage with local references
|
|
134
|
-
* const doc = {
|
|
135
|
-
* components: {
|
|
136
|
-
* schemas: {
|
|
137
|
-
* User: { type: 'object', properties: { name: { type: 'string' } } }
|
|
138
|
-
* }
|
|
139
|
-
* },
|
|
140
|
-
* paths: {
|
|
141
|
-
* '/users': {
|
|
142
|
-
* get: {
|
|
143
|
-
* responses: {
|
|
144
|
-
* 200: {
|
|
145
|
-
* content: {
|
|
146
|
-
* 'application/json': {
|
|
147
|
-
* schema: { $ref: '#/components/schemas/User' }
|
|
148
|
-
* }
|
|
149
|
-
* }
|
|
150
|
-
* }
|
|
151
|
-
* }
|
|
152
|
-
* }
|
|
153
|
-
* }
|
|
154
|
-
* }
|
|
155
|
-
* }
|
|
156
|
-
*
|
|
157
|
-
* const proxy = createMagicProxy(doc)
|
|
158
|
-
* // Accessing the schema will automatically resolve the $ref
|
|
159
|
-
* console.log(proxy.paths['/users'].get.responses[200].content['application/json'].schema)
|
|
160
|
-
* // Output: { type: 'object', properties: { name: { type: 'string' } } }
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* // Using with a cache to prevent duplicate proxies
|
|
164
|
-
* const cache = new WeakMap()
|
|
165
|
-
* const proxy1 = createMagicProxy(doc, doc, cache)
|
|
166
|
-
* const proxy2 = createMagicProxy(doc, doc, cache)
|
|
167
|
-
* // proxy1 and proxy2 are the same instance due to caching
|
|
168
|
-
* console.log(proxy1 === proxy2) // true
|
|
169
|
-
*/
|
|
170
|
-
export function createMagicProxy<T extends UnknownObject>(
|
|
171
|
-
targetObject: T,
|
|
172
|
-
sourceDocument: T = targetObject,
|
|
173
|
-
resolvedProxyCache?: WeakMap<object, T>,
|
|
174
|
-
): T {
|
|
175
|
-
if (!isObject(targetObject)) {
|
|
176
|
-
return targetObject
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const rawTarget = isReactive(targetObject) ? toRaw(targetObject) : targetObject
|
|
180
|
-
|
|
181
|
-
// check for cached results
|
|
182
|
-
if (resolvedProxyCache?.has(rawTarget)) {
|
|
183
|
-
const cachedValue = resolvedProxyCache.get(rawTarget)
|
|
184
|
-
|
|
185
|
-
if (cachedValue) {
|
|
186
|
-
return cachedValue
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Create a handler with the correct context
|
|
191
|
-
const handler = createProxyHandler(sourceDocument, resolvedProxyCache)
|
|
192
|
-
const proxy = new Proxy<T>(rawTarget, handler)
|
|
193
|
-
|
|
194
|
-
if (resolvedProxyCache) {
|
|
195
|
-
resolvedProxyCache.set(rawTarget, proxy)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return proxy
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Gets the raw (non-proxied) version of an object created by createMagicProxy.
|
|
203
|
-
* This is useful when you need to access the original object without the magic proxy wrapper.
|
|
204
|
-
*
|
|
205
|
-
* @param obj - The magic proxy object to get the raw version of
|
|
206
|
-
* @returns The raw version of the object
|
|
207
|
-
* @example
|
|
208
|
-
* const proxy = createMagicProxy({ foo: { $ref: '#/bar' } })
|
|
209
|
-
* const raw = getRaw(proxy) // { foo: { $ref: '#/bar' } }
|
|
210
|
-
*/
|
|
211
|
-
export function getRaw(obj: UnknownObject) {
|
|
212
|
-
return (obj as { [TARGET_SYMBOL]: UnknownObject })[TARGET_SYMBOL]
|
|
213
|
-
}
|
package/src/schemas/callback.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Type } from '@sinclair/typebox'
|
|
2
|
-
import { PathItemObject } from './path-item'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A map of possible out-of band callbacks related to the parent operation. Each value in the map is a Path Item Object that describes a set of requests that may be initiated by the API provider and the expected responses. The key value used to identify the Path Item Object is an expression, evaluated at runtime, that identifies a URL to use for the callback operation.
|
|
6
|
-
*
|
|
7
|
-
* To describe incoming requests from the API provider independent from another API call, use the webhooks field.
|
|
8
|
-
*/
|
|
9
|
-
export const CallbackObject = Type.Record(
|
|
10
|
-
Type.String(),
|
|
11
|
-
/** A Path Item Object used to define a callback request and expected responses. A complete example is available. */
|
|
12
|
-
PathItemObject,
|
|
13
|
-
)
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Type } from '@sinclair/typebox'
|
|
2
|
-
import { SchemaObject } from './schema'
|
|
3
|
-
import { ResponseObject } from './response'
|
|
4
|
-
import { ReferenceObject } from './reference'
|
|
5
|
-
import { ParameterObject } from './parameter'
|
|
6
|
-
import { ExampleObject } from './example'
|
|
7
|
-
import { RequestBodyObject } from './request-body'
|
|
8
|
-
import { SecuritySchemeObject } from './security-scheme'
|
|
9
|
-
import { LinkObject } from './link'
|
|
10
|
-
import { CallbackObject } from './callback'
|
|
11
|
-
import { PathItemObject } from './path-item'
|
|
12
|
-
import { HeaderObject } from './header'
|
|
13
|
-
|
|
14
|
-
/** Holds a set of reusable objects for different aspects of the OAS. All objects defined within the Components Object will have no effect on the API unless they are explicitly referenced from outside the Components Object. */
|
|
15
|
-
export const ComponentsObject = Type.Object({
|
|
16
|
-
/** An object to hold reusable Schema Objects. */
|
|
17
|
-
schemas: Type.Optional(Type.Record(Type.String(), SchemaObject)),
|
|
18
|
-
/** An object to hold reusable Response Objects. */
|
|
19
|
-
responses: Type.Optional(Type.Record(Type.String(), Type.Union([ResponseObject, ReferenceObject]))),
|
|
20
|
-
/** An object to hold reusable Parameter Objects. */
|
|
21
|
-
parameters: Type.Optional(Type.Record(Type.String(), Type.Union([ParameterObject, ReferenceObject]))),
|
|
22
|
-
/** An object to hold reusable Example Objects. */
|
|
23
|
-
examples: Type.Optional(Type.Record(Type.String(), Type.Union([ExampleObject, ReferenceObject]))),
|
|
24
|
-
/** An object to hold reusable Request Body Objects. */
|
|
25
|
-
requestBodies: Type.Optional(Type.Record(Type.String(), Type.Union([RequestBodyObject, ReferenceObject]))),
|
|
26
|
-
/** An object to hold reusable Header Objects. */
|
|
27
|
-
headers: Type.Optional(Type.Record(Type.String(), Type.Union([HeaderObject, ReferenceObject]))),
|
|
28
|
-
/** An object to hold reusable Security Scheme Objects. */
|
|
29
|
-
securitySchemes: Type.Optional(Type.Record(Type.String(), Type.Union([SecuritySchemeObject, ReferenceObject]))),
|
|
30
|
-
/** An object to hold reusable Link Objects. */
|
|
31
|
-
links: Type.Optional(Type.Record(Type.String(), Type.Union([LinkObject, ReferenceObject]))),
|
|
32
|
-
/** An object to hold reusable Callback Objects. */
|
|
33
|
-
callbacks: Type.Optional(Type.Record(Type.String(), Type.Union([CallbackObject, ReferenceObject]))),
|
|
34
|
-
/** An object to hold reusable Path Item Objects. */
|
|
35
|
-
pathItems: Type.Optional(Type.Record(Type.String(), PathItemObject)),
|
|
36
|
-
})
|
package/src/schemas/contact.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Type } from '@sinclair/typebox'
|
|
2
|
-
|
|
3
|
-
/** Contact information for the exposed API. */
|
|
4
|
-
export const ContactObject = Type.Object({
|
|
5
|
-
/** The identifying name of the contact person/organization. */
|
|
6
|
-
name: Type.Optional(Type.String()),
|
|
7
|
-
/** The URI for the contact information. This MUST be in the form of a URI. */
|
|
8
|
-
url: Type.Optional(Type.String()),
|
|
9
|
-
/** The email address of the contact person/organization. This MUST be in the form of an email address. */
|
|
10
|
-
email: Type.Optional(Type.String()),
|
|
11
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Type } from '@sinclair/typebox'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* When request bodies or response payloads may be one of a number of different schemas, a Discriminator Object gives a hint about the expected schema of the document. This hint can be used to aid in serialization, deserialization, and validation. The Discriminator Object does this by implicitly or explicitly associating the possible values of a named property with alternative schemas.
|
|
5
|
-
*
|
|
6
|
-
* Note that discriminator MUST NOT change the validation outcome of the schema.
|
|
7
|
-
*/
|
|
8
|
-
export const DiscriminatorObject = Type.Object({
|
|
9
|
-
/** REQUIRED. The name of the property in the payload that will hold the discriminating value. This property SHOULD be required in the payload schema, as the behavior when the property is absent is undefined. */
|
|
10
|
-
propertyName: Type.String(),
|
|
11
|
-
/** An object to hold mappings between payload values and schema names or URI references. */
|
|
12
|
-
mapping: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
13
|
-
})
|