@scalar/workspace-store 0.62.0 → 0.63.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/CHANGELOG.md +12 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -0
- package/dist/helpers/detect-changes-proxy.d.ts +7 -6
- package/dist/helpers/detect-changes-proxy.d.ts.map +1 -1
- package/dist/helpers/detect-changes-proxy.js +83 -38
- package/dist/helpers/document-revision.d.ts +26 -0
- package/dist/helpers/document-revision.d.ts.map +1 -0
- package/dist/helpers/document-revision.js +47 -0
- package/dist/helpers/get-resolved-ref-deep.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref-deep.js +25 -13
- package/dist/helpers/get-resolved-ref.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref.js +61 -3
- package/dist/helpers/unpack-proxy.d.ts +10 -0
- package/dist/helpers/unpack-proxy.d.ts.map +1 -1
- package/dist/helpers/unpack-proxy.js +15 -0
- package/dist/request-example/builder/helpers/get-example-from-schema.d.ts.map +1 -1
- package/dist/request-example/builder/helpers/get-example-from-schema.js +21 -1
- package/dist/resolve.d.ts +10 -2
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +9 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @scalar/workspace-store
|
|
2
2
|
|
|
3
|
+
## 0.63.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10261](https://github.com/scalar/scalar/pull/10261): Add `getDocumentRevision(document)`, a counter the store bumps on every write to a document. A consumer caching a derivation of a schema node can validate the entry against it in constant time, instead of walking the subtree to see whether anything moved. It reads the same from any view of the document, including one with the reactive and detect-changes proxies stripped for reads, and returns 0 for a document no store tracks.
|
|
8
|
+
- [#10261](https://github.com/scalar/scalar/pull/10261): Type the result of `resolve.schema` as read-only. A resolved schema is the document's own node or a shallow merge over it, so writing to it writes into the document behind the store's back; every change belongs in a store mutation, and a caller that needs a modified shape copies what it needs first. No in-repo consumer had to change.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- [#10261](https://github.com/scalar/scalar/pull/10261): Make rendering from the store cheaper: the detect-changes proxy no longer allocates a path on every property read, `getResolvedRefDeep` stops deep-unpacking every node it visits, `resolve.schema` builds its composed typebox schema once, and `getExampleFromSchema` builds its options cache key once per call instead of once per node.
|
|
13
|
+
- [#10261](https://github.com/scalar/scalar/pull/10261): Follow chains of references when resolving. A reference can point at a second reference — `resolve()` on a static or SSR workspace leaves the component behind as a `{ $ref: '#/x-ext/<hash>', $global: true }` stub with the content under `x-ext` — so `getResolvedRef` and `getResolvedRefDeep` now hop through references that carry nothing but a `$ref` until they reach the node itself, instead of handing back the stub. A reference that carries keywords of its own stays its own hop, since it is a schema in its own right.
|
|
14
|
+
|
|
3
15
|
## 0.62.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,YAAY,EAAU,MAAM,2BAA2B,CAAA;AAErE,OAAO,EAAE,KAAK,UAAU,EAAe,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAQ7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,iBAAiB,CAAA;AACjE,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createAuthStore } from './entities/auth/index.js';
|
|
|
18
18
|
import { createHistoryStore } from './entities/history/index.js';
|
|
19
19
|
import { deepClone } from './helpers/deep-clone.js';
|
|
20
20
|
import { createDetectChangesProxy } from './helpers/detect-changes-proxy.js';
|
|
21
|
+
import { bumpDocumentRevision } from './helpers/document-revision.js';
|
|
21
22
|
import { safeAssign } from './helpers/general.js';
|
|
22
23
|
import { getFetch } from './helpers/get-fetch.js';
|
|
23
24
|
import { mergeObjects } from './helpers/merge-object.js';
|
|
@@ -219,6 +220,9 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
219
220
|
info: { title: '', version: '' },
|
|
220
221
|
'x-scalar-original-document-hash': '',
|
|
221
222
|
};
|
|
223
|
+
// Every write through the store passes here, which is what makes the revision a
|
|
224
|
+
// complete record of the document changing.
|
|
225
|
+
bumpDocumentRevision(document);
|
|
222
226
|
const event = {
|
|
223
227
|
type: 'documents',
|
|
224
228
|
documentName,
|
|
@@ -244,6 +248,7 @@ export const createWorkspaceStore = (workspaceProps) => {
|
|
|
244
248
|
info: { title: '', version: '' },
|
|
245
249
|
'x-scalar-original-document-hash': '',
|
|
246
250
|
};
|
|
251
|
+
bumpDocumentRevision(document);
|
|
247
252
|
// Active document changed
|
|
248
253
|
const event = {
|
|
249
254
|
type: 'documents',
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
type OnBeforeChangeHook = (path: string[], value?: unknown) => void;
|
|
2
2
|
type OnAfterChangeHook = (path: string[], value?: unknown) => void;
|
|
3
|
+
type Options = {
|
|
4
|
+
hooks: Partial<{
|
|
5
|
+
onBeforeChange: OnBeforeChangeHook;
|
|
6
|
+
onAfterChange: OnAfterChangeHook;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
3
9
|
/**
|
|
4
10
|
* createDetectChangesProxy - Creates a proxy for an object or array that detects and triggers hooks on changes.
|
|
5
11
|
*
|
|
@@ -23,12 +29,7 @@ type OnAfterChangeHook = (path: string[], value?: unknown) => void;
|
|
|
23
29
|
* @param args Internal: proxy cache and current property path (used for recursion)
|
|
24
30
|
* @returns The proxied object/array with change detection capabilities
|
|
25
31
|
*/
|
|
26
|
-
export declare const createDetectChangesProxy: <T>(target: T, options?: {
|
|
27
|
-
hooks: Partial<{
|
|
28
|
-
onBeforeChange: OnBeforeChangeHook;
|
|
29
|
-
onAfterChange: OnAfterChangeHook;
|
|
30
|
-
}>;
|
|
31
|
-
}, args?: {
|
|
32
|
+
export declare const createDetectChangesProxy: <T>(target: T, options?: Options, args?: {
|
|
32
33
|
/** Cache for storing proxies */
|
|
33
34
|
proxyCache: WeakMap<object, unknown>;
|
|
34
35
|
/** Path for the target */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-changes-proxy.d.ts","sourceRoot":"","sources":["../../src/helpers/detect-changes-proxy.ts"],"names":[],"mappings":"AAKA,KAAK,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AACnE,KAAK,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AAElE
|
|
1
|
+
{"version":3,"file":"detect-changes-proxy.d.ts","sourceRoot":"","sources":["../../src/helpers/detect-changes-proxy.ts"],"names":[],"mappings":"AAKA,KAAK,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AACnE,KAAK,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AAElE,KAAK,OAAO,GAAG;IACb,KAAK,EAAE,OAAO,CAAC;QACb,cAAc,EAAE,kBAAkB,CAAA;QAClC,aAAa,EAAE,iBAAiB,CAAA;KACjC,CAAC,CAAA;CACH,CAAA;AA+HD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EACxC,QAAQ,CAAC,EACT,UAAU,OAAO,EACjB,OAAM;IACJ,gCAAgC;IAChC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,0BAA0B;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;CAIf,KACA,CAAyE,CAAA;AAE5E,eAAO,MAAM,0BAA0B,GAAI,KAAK,OAAO,KAAG,OAMzD,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,KAAK,CAAC,KAAG,CAWpD,CAAA"}
|
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
2
|
const isDetectChangesProxy = Symbol('isDetectChangesProxy');
|
|
3
3
|
const detectChangesProxyTarget = Symbol('detectChangesProxyTarget');
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
export const createDetectChangesProxy = (target, options, args = {
|
|
28
|
-
proxyCache: new WeakMap(),
|
|
29
|
-
path: [],
|
|
30
|
-
}) => {
|
|
4
|
+
/** Build the `string[]` a hook expects, from the chain of links above the property being written. */
|
|
5
|
+
const materializePath = (parent, prop) => {
|
|
6
|
+
let depth = 1;
|
|
7
|
+
for (let link = parent; link !== undefined; link = link.parent) {
|
|
8
|
+
depth++;
|
|
9
|
+
}
|
|
10
|
+
const path = new Array(depth);
|
|
11
|
+
path[--depth] = prop;
|
|
12
|
+
for (let link = parent; link !== undefined; link = link.parent) {
|
|
13
|
+
path[--depth] = link.key;
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
};
|
|
17
|
+
/** Turn the caller-supplied starting path into the link chain the proxies carry. */
|
|
18
|
+
const toPathLink = (path) => {
|
|
19
|
+
let link = undefined;
|
|
20
|
+
for (const key of path) {
|
|
21
|
+
link = { parent: link, key };
|
|
22
|
+
}
|
|
23
|
+
return link;
|
|
24
|
+
};
|
|
25
|
+
const createProxy = (target, options, proxyCache, pathLink) => {
|
|
31
26
|
// Only wrap objects or arrays
|
|
32
27
|
if (!isObject(target) && !Array.isArray(target)) {
|
|
33
28
|
return target;
|
|
34
29
|
}
|
|
35
30
|
// Return cached proxy if already created for this target
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const cached = proxyCache.get(target);
|
|
32
|
+
if (cached !== undefined) {
|
|
33
|
+
return cached;
|
|
38
34
|
}
|
|
39
35
|
const proxy = new Proxy(target, {
|
|
40
36
|
get(target, prop, receiver) {
|
|
@@ -46,34 +42,83 @@ export const createDetectChangesProxy = (target, options, args = {
|
|
|
46
42
|
if (prop === detectChangesProxyTarget) {
|
|
47
43
|
return target;
|
|
48
44
|
}
|
|
49
|
-
// Recursively wrap property values in the detect changes proxy
|
|
50
45
|
const value = Reflect.get(target, prop, receiver);
|
|
46
|
+
// Primitives and functions are handed back untouched, which is the common case on a read.
|
|
47
|
+
if (value === null || typeof value !== 'object') {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
// A value wrapped earlier keeps its proxy, so nothing below this point runs for a repeat read.
|
|
51
|
+
const cachedChild = proxyCache.get(value);
|
|
52
|
+
if (cachedChild !== undefined) {
|
|
53
|
+
return cachedChild;
|
|
54
|
+
}
|
|
51
55
|
if (isDetectChangesProxyObject(value)) {
|
|
52
56
|
return value;
|
|
53
57
|
}
|
|
54
|
-
|
|
58
|
+
// Recursively wrap property values in the detect changes proxy
|
|
59
|
+
return createProxy(value, options, proxyCache, { parent: pathLink, key: String(prop) });
|
|
55
60
|
},
|
|
56
61
|
set(target, prop, value, receiver) {
|
|
57
|
-
const
|
|
62
|
+
const onBeforeChange = options?.hooks?.onBeforeChange;
|
|
63
|
+
const onAfterChange = options?.hooks?.onAfterChange;
|
|
64
|
+
// Both hooks receive the same array, because `client.ts` mutates it in `onAfterChange`.
|
|
65
|
+
const path = onBeforeChange || onAfterChange ? materializePath(pathLink, String(prop)) : undefined;
|
|
58
66
|
// Call before-change hook if provided
|
|
59
|
-
|
|
67
|
+
if (path) {
|
|
68
|
+
onBeforeChange?.(path, value);
|
|
69
|
+
}
|
|
60
70
|
const result = Reflect.set(target, prop, value, receiver);
|
|
61
71
|
// Call after-change hook if provided
|
|
62
|
-
|
|
72
|
+
if (path) {
|
|
73
|
+
onAfterChange?.(path, value);
|
|
74
|
+
}
|
|
63
75
|
return result;
|
|
64
76
|
},
|
|
65
77
|
deleteProperty(target, prop) {
|
|
66
|
-
const
|
|
67
|
-
options?.hooks?.
|
|
78
|
+
const onBeforeChange = options?.hooks?.onBeforeChange;
|
|
79
|
+
const onAfterChange = options?.hooks?.onAfterChange;
|
|
80
|
+
const path = onBeforeChange || onAfterChange ? materializePath(pathLink, String(prop)) : undefined;
|
|
81
|
+
if (path) {
|
|
82
|
+
onBeforeChange?.(path);
|
|
83
|
+
}
|
|
68
84
|
const result = Reflect.deleteProperty(target, prop);
|
|
69
|
-
|
|
85
|
+
if (path) {
|
|
86
|
+
onAfterChange?.(path);
|
|
87
|
+
}
|
|
70
88
|
return result;
|
|
71
89
|
},
|
|
72
90
|
});
|
|
73
91
|
// Cache the proxy for this target
|
|
74
|
-
|
|
92
|
+
proxyCache.set(target, proxy);
|
|
75
93
|
return proxy;
|
|
76
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* createDetectChangesProxy - Creates a proxy for an object or array that detects and triggers hooks on changes.
|
|
97
|
+
*
|
|
98
|
+
* This proxy enables detection of set operations, triggering optional hooks (onBeforeChange, onAfterChange) with the path and value changed.
|
|
99
|
+
* The proxy can be applied recursively to all nested objects/arrays, and caches proxies to prevent creating multiple proxies for the same object.
|
|
100
|
+
*
|
|
101
|
+
* Example usage:
|
|
102
|
+
*
|
|
103
|
+
* const obj = { foo: 1, bar: { baz: 2 } };
|
|
104
|
+
* const proxy = createDetectChangesProxy(obj, {
|
|
105
|
+
* hooks: {
|
|
106
|
+
* onBeforeChange: (path, value) => console.log('Before', path, value),
|
|
107
|
+
* onAfterChange: (path, value) => console.log('After', path, value),
|
|
108
|
+
* }
|
|
109
|
+
* });
|
|
110
|
+
* proxy.foo = 42; // Console: Before ['foo'] '42', After ['foo'] '42'
|
|
111
|
+
* proxy.bar.baz = 99; // Console: Before ['bar', 'baz'] '99', After ['bar', 'baz'] '99'
|
|
112
|
+
*
|
|
113
|
+
* @param target The target object or array to wrap in a proxy
|
|
114
|
+
* @param options Optional: hooks for change detection
|
|
115
|
+
* @param args Internal: proxy cache and current property path (used for recursion)
|
|
116
|
+
* @returns The proxied object/array with change detection capabilities
|
|
117
|
+
*/
|
|
118
|
+
export const createDetectChangesProxy = (target, options, args = {
|
|
119
|
+
proxyCache: new WeakMap(),
|
|
120
|
+
path: [],
|
|
121
|
+
}) => createProxy(target, options, args.proxyCache, toPathLink(args.path));
|
|
77
122
|
export const isDetectChangesProxyObject = (obj) => {
|
|
78
123
|
return (typeof obj === 'object' &&
|
|
79
124
|
obj !== null &&
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records a write against a document.
|
|
3
|
+
*
|
|
4
|
+
* Called from the store's change hooks, which see every mutation made through the store.
|
|
5
|
+
*/
|
|
6
|
+
export declare const bumpDocumentRevision: (document: unknown) => void;
|
|
7
|
+
/**
|
|
8
|
+
* How many writes the store has recorded against a document, for a consumer caching derivations of its
|
|
9
|
+
* nodes: the number changes whenever anything in the document does, so a cache entry taken at one
|
|
10
|
+
* revision is known to be stale at the next, in constant time and without walking the document.
|
|
11
|
+
*
|
|
12
|
+
* Returns 0 for a document no store tracks — a plain or magic-proxied document on the server, say —
|
|
13
|
+
* which is also what an untouched document reads, so a cache validating against it simply never sees a
|
|
14
|
+
* change. Documents that are mutated outside the store are the caller's problem either way: nothing
|
|
15
|
+
* observes those writes.
|
|
16
|
+
*
|
|
17
|
+
* The number is meaningful only against itself. It counts writes, not versions, and a single edit can
|
|
18
|
+
* move it by more than one.
|
|
19
|
+
*
|
|
20
|
+
* It is a plain number, not a reactive source: reading it inside a Vue `computed` or `effect` tracks
|
|
21
|
+
* nothing, so that computed will not re-run when the number moves. Use it to validate a cache entry at
|
|
22
|
+
* the point of use — alongside whatever already makes the surrounding computed re-run — rather than as
|
|
23
|
+
* the thing a computed depends on.
|
|
24
|
+
*/
|
|
25
|
+
export declare const getDocumentRevision: (document: unknown) => number;
|
|
26
|
+
//# sourceMappingURL=document-revision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-revision.d.ts","sourceRoot":"","sources":["../../src/helpers/document-revision.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU,OAAO,KAAG,IAOxD,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,OAAO,KAAG,MAOvD,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { unpackProxyShallow } from '../helpers/unpack-proxy.js';
|
|
2
|
+
/**
|
|
3
|
+
* How many writes the store has recorded against each document, keyed by the raw document object.
|
|
4
|
+
*
|
|
5
|
+
* Keyed on the raw object rather than on a proxy, because the writer and the reader hold different
|
|
6
|
+
* views of the same document: the store writes through `reactive(detectChanges(overrides(magic(raw))))`,
|
|
7
|
+
* while a reader may hold any inner layer — the API reference strips the outer two for schema reads.
|
|
8
|
+
* `unpackProxyShallow` lands on the same object from any of them.
|
|
9
|
+
*/
|
|
10
|
+
const revisions = new WeakMap();
|
|
11
|
+
/**
|
|
12
|
+
* Records a write against a document.
|
|
13
|
+
*
|
|
14
|
+
* Called from the store's change hooks, which see every mutation made through the store.
|
|
15
|
+
*/
|
|
16
|
+
export const bumpDocumentRevision = (document) => {
|
|
17
|
+
const raw = unpackProxyShallow(document);
|
|
18
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
revisions.set(raw, (revisions.get(raw) ?? 0) + 1);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* How many writes the store has recorded against a document, for a consumer caching derivations of its
|
|
25
|
+
* nodes: the number changes whenever anything in the document does, so a cache entry taken at one
|
|
26
|
+
* revision is known to be stale at the next, in constant time and without walking the document.
|
|
27
|
+
*
|
|
28
|
+
* Returns 0 for a document no store tracks — a plain or magic-proxied document on the server, say —
|
|
29
|
+
* which is also what an untouched document reads, so a cache validating against it simply never sees a
|
|
30
|
+
* change. Documents that are mutated outside the store are the caller's problem either way: nothing
|
|
31
|
+
* observes those writes.
|
|
32
|
+
*
|
|
33
|
+
* The number is meaningful only against itself. It counts writes, not versions, and a single edit can
|
|
34
|
+
* move it by more than one.
|
|
35
|
+
*
|
|
36
|
+
* It is a plain number, not a reactive source: reading it inside a Vue `computed` or `effect` tracks
|
|
37
|
+
* nothing, so that computed will not re-run when the number moves. Use it to validate a cache entry at
|
|
38
|
+
* the point of use — alongside whatever already makes the surrounding computed re-run — rather than as
|
|
39
|
+
* the thing a computed depends on.
|
|
40
|
+
*/
|
|
41
|
+
export const getDocumentRevision = (document) => {
|
|
42
|
+
const raw = unpackProxyShallow(document);
|
|
43
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
return revisions.get(raw) ?? 0;
|
|
47
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-resolved-ref-deep.d.ts","sourceRoot":"","sources":["../../src/helpers/get-resolved-ref-deep.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,kDAAkD,CAAA;AAMnG,KAAK,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAC1F,KAAK,SAAS,CAAC,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3C;;;;GAIG;AACH,KAAK,eAAe,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,GAC9C,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC5B,eAAe,CAAC,CAAC,CAAC,EAAE,GACpB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC5G,CAAC,GACL,WAAW,CAAC,CAAC,CAAC,SAAS,MAAM,GAC3B;KACG,CAAC,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,GAC/D,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,GACjD,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACvC,GACD,WAAW,CAAC,CAAC,CAAC,CAAA;AAEpB;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,KAAG,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"get-resolved-ref-deep.d.ts","sourceRoot":"","sources":["../../src/helpers/get-resolved-ref-deep.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,kDAAkD,CAAA;AAMnG,KAAK,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAC1F,KAAK,SAAS,CAAC,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3C;;;;GAIG;AACH,KAAK,eAAe,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,GAC9C,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC5B,eAAe,CAAC,CAAC,CAAC,EAAE,GACpB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC5G,CAAC,GACL,WAAW,CAAC,CAAC,CAAC,SAAS,MAAM,GAC3B;KACG,CAAC,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,GAC/D,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,GACjD,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACvC,GACD,WAAW,CAAC,CAAC,CAAC,CAAA;AAEpB;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,KAAG,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,CAyE/F,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
2
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
3
|
-
import {
|
|
3
|
+
import { unpackProxyShallow } from '@scalar/workspace-store/helpers/unpack-proxy';
|
|
4
4
|
/**
|
|
5
5
|
* Recursively resolves all $ref objects in a data structure to their actual values.
|
|
6
6
|
* Traverses through objects, arrays, and nested structures to find and resolve
|
|
@@ -17,7 +17,9 @@ export const getResolvedRefDeep = (node) => {
|
|
|
17
17
|
if (!isObject(current) && !Array.isArray(current)) {
|
|
18
18
|
return current;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
// Identity only: the raw object keys the caches below and is never read from or returned, so the
|
|
21
|
+
// outermost proxies come off and the node's own properties are left alone.
|
|
22
|
+
const rawValue = unpackProxyShallow(current);
|
|
21
23
|
// We don't have to recurse into the same object again
|
|
22
24
|
// This helps us having to manually remove the tracked node after we recurse into the tree
|
|
23
25
|
if (cachedResults.has(rawValue)) {
|
|
@@ -30,31 +32,41 @@ export const getResolvedRefDeep = (node) => {
|
|
|
30
32
|
// Track visited nodes
|
|
31
33
|
visited.add(rawValue);
|
|
32
34
|
if ('$ref' in current) {
|
|
35
|
+
// `getResolvedRef` follows a chain of pure pass-through references, so a component that resolve()
|
|
36
|
+
// left behind as a `$global` stub reaches the node the stub points at rather than the stub.
|
|
33
37
|
const resolved = getResolvedRef(current);
|
|
34
38
|
const result = resolveNode(resolved);
|
|
35
39
|
// Preserve keywords declared alongside the `$ref`. A `$ref` may carry siblings that specialize the
|
|
36
40
|
// target — most notably the `$defs`/`$dynamicAnchor` binding used to express a generic schema like
|
|
37
41
|
// `Paginated<Planet>`. Per OpenAPI 3.1, siblings of a `$ref` override the resolved value, so we merge
|
|
38
42
|
// them on top. Without this the dynamic-ref binding is dropped and `$dynamicRef` cannot resolve.
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
let merged = undefined;
|
|
44
|
+
if (isObject(result)) {
|
|
45
|
+
for (const key of Object.keys(current)) {
|
|
46
|
+
if (key === '$ref' || key === '$ref-value') {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
merged ??= { ...result };
|
|
50
|
+
merged[key] = resolveNode(current[key]);
|
|
44
51
|
}
|
|
45
|
-
cachedResults.set(rawValue, merged);
|
|
46
|
-
return merged;
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const value = merged ?? result;
|
|
54
|
+
cachedResults.set(rawValue, value);
|
|
55
|
+
return value;
|
|
50
56
|
}
|
|
51
57
|
// For arrays
|
|
52
58
|
if (Array.isArray(current)) {
|
|
53
|
-
const result = current.
|
|
59
|
+
const result = new Array(current.length);
|
|
60
|
+
for (let index = 0; index < current.length; index++) {
|
|
61
|
+
result[index] = resolveNode(current[index]);
|
|
62
|
+
}
|
|
54
63
|
cachedResults.set(rawValue, result);
|
|
55
64
|
return result;
|
|
56
65
|
}
|
|
57
|
-
const result =
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const key of Object.keys(current)) {
|
|
68
|
+
result[key] = resolveNode(current[key]);
|
|
69
|
+
}
|
|
58
70
|
cachedResults.set(rawValue, result);
|
|
59
71
|
return result;
|
|
60
72
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-resolved-ref.d.ts","sourceRoot":"","sources":["../../src/helpers/get-resolved-ref.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,SAAS,CAAC,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"get-resolved-ref.d.ts","sourceRoot":"","sources":["../../src/helpers/get-resolved-ref.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,SAAS,CAAC,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AA0ElD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,IAclE,CAAA;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EACzC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EACrB,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,MAAM,GACzC,IAAI,GAAG,MAAM,CAAA;AAChB,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAAA;AACtF,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;AAW7E;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA"}
|
|
@@ -1,7 +1,64 @@
|
|
|
1
1
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
|
+
/**
|
|
3
|
+
* Keys the store writes on an externalized stub for its own bookkeeping.
|
|
4
|
+
*
|
|
5
|
+
* They describe the stub — whether it is shared across documents, whether its chunk has loaded — not the
|
|
6
|
+
* node it points at.
|
|
7
|
+
*/
|
|
8
|
+
const STUB_BOOKKEEPING_KEYS = new Set(['$global', '$status']);
|
|
9
|
+
const isReferenceNode = (value) => typeof value === 'object' && value !== null && '$ref' in value;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a reference is pure indirection: a `$ref` and the store's own bookkeeping, nothing more.
|
|
12
|
+
*
|
|
13
|
+
* A reference that carries anything else is a schema in its own right — an `$id` opens a schema
|
|
14
|
+
* resource, `$defs` and `$dynamicAnchor` bind a generic's type parameter, and `description` annotates
|
|
15
|
+
* the target — so it stays its own hop and the caller resolves it as it descends, the way it always
|
|
16
|
+
* has. Collapsing such a node into the one it points at merges two schema resources into one, and the
|
|
17
|
+
* `$dynamicRef` in the inner one then has no outer scope left to bind against.
|
|
18
|
+
*/
|
|
19
|
+
const isPassThroughReference = (node) => {
|
|
20
|
+
for (const key of Object.keys(node)) {
|
|
21
|
+
if (key !== '$ref' && key !== '$ref-value' && !STUB_BOOKKEEPING_KEYS.has(key)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Follow `$ref-value` onward for as long as it lands on another reference that is pure indirection.
|
|
29
|
+
*
|
|
30
|
+
* A reference can point at a second reference. `resolve()` on a static or SSR workspace leaves exactly
|
|
31
|
+
* that behind: the component stays in the document as a `{ $ref: '#/x-ext/<hash>', $global: true }` stub
|
|
32
|
+
* and the content lives under `x-ext`, so `#/components/schemas/User` reaches the schema in two hops.
|
|
33
|
+
* Stopping at the first hop hands consumers the stub, a node with no `type` and no `properties`, which
|
|
34
|
+
* renders as an empty, non-expandable schema.
|
|
35
|
+
*
|
|
36
|
+
* Stops at a reference that has not been resolved yet and hands that node back, which is what a single
|
|
37
|
+
* hop onto an unresolved reference produces today. `seen` terminates a reference cycle on the node it
|
|
38
|
+
* comes back around to rather than looping.
|
|
39
|
+
*
|
|
40
|
+
* @param value - The value the first hop produced.
|
|
41
|
+
* @param seen - References already crossed, including the node the chain started at.
|
|
42
|
+
*/
|
|
43
|
+
const followPassThroughReferences = (value, seen) => {
|
|
44
|
+
let current = value;
|
|
45
|
+
while (isReferenceNode(current) && isPassThroughReference(current) && !seen.has(current)) {
|
|
46
|
+
const next = current['$ref-value'];
|
|
47
|
+
if (next === undefined) {
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
seen.add(current);
|
|
51
|
+
current = next;
|
|
52
|
+
}
|
|
53
|
+
return current;
|
|
54
|
+
};
|
|
2
55
|
const defaultTransform = (node) => {
|
|
3
56
|
// Unresolved references have no value; callers must account for that state.
|
|
4
|
-
|
|
57
|
+
const value = node['$ref-value'];
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return followPassThroughReferences(value, new Set([node]));
|
|
5
62
|
};
|
|
6
63
|
/**
|
|
7
64
|
* Transform for getResolvedRef that merges sibling properties of a $ref wrapper
|
|
@@ -10,15 +67,16 @@ const defaultTransform = (node) => {
|
|
|
10
67
|
*/
|
|
11
68
|
export const mergeSiblingReferences = (node) => {
|
|
12
69
|
const { '$ref-value': value, ...rest } = node;
|
|
70
|
+
const target = value === undefined ? undefined : followPassThroughReferences(value, new Set([node]));
|
|
13
71
|
// A reference can land on something that is not a record: a pointer that aims at a string
|
|
14
72
|
// (`$ref: '#/info/title'`), one that was never resolved, or one whose target is an array. Spreading
|
|
15
73
|
// any of those copies it index by index, so a reference to a title becomes `{ 0: 'G', 1: 'a', … }`
|
|
16
74
|
// and every consumer downstream treats those digits as real properties. There is nothing to merge
|
|
17
75
|
// siblings onto in that case, so only the siblings survive.
|
|
18
|
-
if (!isObject(
|
|
76
|
+
if (!isObject(target)) {
|
|
19
77
|
return rest;
|
|
20
78
|
}
|
|
21
|
-
return { ...
|
|
79
|
+
return { ...target, ...rest };
|
|
22
80
|
};
|
|
23
81
|
export function getResolvedRef(node, transform = defaultTransform) {
|
|
24
82
|
if (typeof node === 'object' && node !== null && '$ref' in node) {
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
* @param depth - Optional, limits recursion depth. `null` means unlimited depth (default is 1).
|
|
14
14
|
* @returns - A plain object or array with all proxies removed up to the specified depth.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Strips the known proxies (Vue reactivity, overrides, detect-changes, magic) from a value without
|
|
18
|
+
* touching its properties, returning the raw object underneath.
|
|
19
|
+
*
|
|
20
|
+
* Use this when the raw object is wanted as an identity — a cache key or a cycle guard — rather than as
|
|
21
|
+
* data. `unpackProxyObject` walks the value's own properties and writes each one back, which costs a
|
|
22
|
+
* read and a write per property and mutates the object it unpacks; callers that only compare identities
|
|
23
|
+
* pay for neither.
|
|
24
|
+
*/
|
|
25
|
+
export declare const unpackProxyShallow: <T>(input: T) => T;
|
|
16
26
|
export declare const unpackProxyObject: <T>(input: T, { depth }?: {
|
|
17
27
|
depth?: number | null;
|
|
18
28
|
}) => T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unpack-proxy.d.ts","sourceRoot":"","sources":["../../src/helpers/unpack-proxy.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,YAAe;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,KAAG,CAsE9F,CAAA"}
|
|
1
|
+
{"version":3,"file":"unpack-proxy.d.ts","sourceRoot":"","sources":["../../src/helpers/unpack-proxy.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,CAMhD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,YAAe;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,KAAG,CAsE9F,CAAA"}
|
|
@@ -17,6 +17,21 @@ import { unpackOverridesProxy } from '../helpers/overrides-proxy.js';
|
|
|
17
17
|
* @param depth - Optional, limits recursion depth. `null` means unlimited depth (default is 1).
|
|
18
18
|
* @returns - A plain object or array with all proxies removed up to the specified depth.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Strips the known proxies (Vue reactivity, overrides, detect-changes, magic) from a value without
|
|
22
|
+
* touching its properties, returning the raw object underneath.
|
|
23
|
+
*
|
|
24
|
+
* Use this when the raw object is wanted as an identity — a cache key or a cycle guard — rather than as
|
|
25
|
+
* data. `unpackProxyObject` walks the value's own properties and writes each one back, which costs a
|
|
26
|
+
* read and a write per property and mutates the object it unpacks; callers that only compare identities
|
|
27
|
+
* pay for neither.
|
|
28
|
+
*/
|
|
29
|
+
export const unpackProxyShallow = (input) => {
|
|
30
|
+
if (typeof input !== 'object' || input === null) {
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
return unpackDetectChangesProxy(toRaw(getRaw(unpackOverridesProxy(input))));
|
|
34
|
+
};
|
|
20
35
|
export const unpackProxyObject = (input, { depth = 0 } = {}) => {
|
|
21
36
|
// Internal DFS helper to recursively strip all known proxies (Vue, overrides, detect-changes, magic proxies)
|
|
22
37
|
const dfs = (value, currentDepth = 0) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-example-from-schema.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/helpers/get-example-from-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAqD,MAAM,uBAAuB,CAAA;AAG5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AA+wB1E,KAAK,2BAA2B,GAAG;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,qDAAqD;IACrD,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C,CAAA;
|
|
1
|
+
{"version":3,"file":"get-example-from-schema.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/helpers/get-example-from-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAqD,MAAM,uBAAuB,CAAA;AAG5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AA+wB1E,KAAK,2BAA2B,GAAG;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,qDAAqD;IACrD,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C,CAAA;AAuPD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,YAAY,EACpB,UAAU,2BAA2B,EACrC,iEAOG,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,YAAY,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACrB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,qGAAqG;IACrG,YAAY,EAAE,YAAY,CAAA;CAC3B,CAAM,KACN,OAmNF,CAAA"}
|
|
@@ -646,6 +646,26 @@ const createOptionsCacheKey = (options) => JSON.stringify({
|
|
|
646
646
|
? Object.entries(options.compositionSelection).sort(([a], [b]) => a.localeCompare(b))
|
|
647
647
|
: undefined,
|
|
648
648
|
});
|
|
649
|
+
/** Sentinel for "nothing memoized yet", since `undefined` is itself a valid options value. */
|
|
650
|
+
const NO_OPTIONS = Symbol('NO_OPTIONS');
|
|
651
|
+
/** The options object `lastOptionsKey` was built from. */
|
|
652
|
+
let lastOptions = NO_OPTIONS;
|
|
653
|
+
let lastOptionsKey = '';
|
|
654
|
+
/**
|
|
655
|
+
* The options half of the result-cache key, built once per top-level call instead of once per node.
|
|
656
|
+
*
|
|
657
|
+
* `createOptionsCacheKey` serializes the whole options object, and every schema the walk enters needs
|
|
658
|
+
* the same string. The walk is synchronous and passes the same `options` object down, so rebuilding at
|
|
659
|
+
* level 0, or whenever the identity changes, is enough: a caller that mutates its options object in
|
|
660
|
+
* place still gets a fresh key on its next top-level call, exactly as before.
|
|
661
|
+
*/
|
|
662
|
+
const getOptionsCacheKey = (options, level) => {
|
|
663
|
+
if (level === 0 || options !== lastOptions) {
|
|
664
|
+
lastOptionsKey = createOptionsCacheKey(options);
|
|
665
|
+
lastOptions = options;
|
|
666
|
+
}
|
|
667
|
+
return lastOptionsKey;
|
|
668
|
+
};
|
|
649
669
|
/** Stand-in for a truncated schema whose shape cannot be read off the document. */
|
|
650
670
|
const MAX_DEPTH_EXCEEDED = '[Max Depth Exceeded]';
|
|
651
671
|
/** How long a chain of composition wrappers may be unwrapped before the stack becomes the concern. */
|
|
@@ -867,7 +887,7 @@ export const getExampleFromSchema = (schema, options, { level = 0, parentSchema,
|
|
|
867
887
|
}
|
|
868
888
|
seen.add(targetValue);
|
|
869
889
|
/** Make the cache key unique per options and schema path */
|
|
870
|
-
const cacheKey =
|
|
890
|
+
const cacheKey = getOptionsCacheKey(options, level) + (schemaPath.length > 0 ? `:path:${schemaPath.join('.')}` : '');
|
|
871
891
|
// Check cache first for performance - avoid recomputing the same schema (skipped under a dynamic scope)
|
|
872
892
|
if (!skipCache) {
|
|
873
893
|
const cached = resultCache.get(targetValue)?.get(cacheKey);
|
package/dist/resolve.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { MaybeRefSchemaObject, SchemaObject } from './schemas/v3.2/strict/schema.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* A resolved schema is a read-only view.
|
|
4
|
+
*
|
|
5
|
+
* What comes back is either the document's own node or a shallow merge over it, so the nested values
|
|
6
|
+
* are the document's either way and a write reaches the document without going through the store. The
|
|
7
|
+
* `Readonly` says so to the compiler at the one level where a write is cheap to make by accident; a
|
|
8
|
+
* caller with a change to make copies what it needs, or goes through a store mutation.
|
|
9
|
+
*/
|
|
10
|
+
type ResolvedSchema<T> = T extends undefined ? undefined : Readonly<SchemaObject & {
|
|
3
11
|
$ref?: string;
|
|
4
|
-
}
|
|
12
|
+
}>;
|
|
5
13
|
export declare const resolve: {
|
|
6
14
|
schema: <T extends MaybeRefSchemaObject | undefined>(schema: T) => ResolvedSchema<T>;
|
|
7
15
|
};
|
package/dist/resolve.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAEtF,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAEtF;;;;;;;GAOG;AACH,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAWrG,eAAO,MAAM,OAAO;aACT,CAAC,SAAS,oBAAoB,GAAG,SAAS,UAAU,CAAC,KAAG,cAAc,CAAC,CAAC,CAAC;CAQnF,CAAA"}
|
package/dist/resolve.js
CHANGED
|
@@ -3,12 +3,20 @@ import { getResolvedRef, mergeSiblingReferences } from './helpers/get-resolved-r
|
|
|
3
3
|
import { compose } from './schemas/compose.js';
|
|
4
4
|
import { coerceValue } from './schemas/typebox-coerce.js';
|
|
5
5
|
import { SchemaObjectSchema } from './schemas/v3.2/strict/openapi-document.js';
|
|
6
|
+
/**
|
|
7
|
+
* The coercion target: a schema object that may still carry the `$ref` it was resolved from.
|
|
8
|
+
*
|
|
9
|
+
* `Type.Composite` merges every property of `SchemaObjectSchema` to build this, so it belongs at module
|
|
10
|
+
* scope. `resolve.schema` runs once per property of every schema a render walks, and rebuilding the
|
|
11
|
+
* composite per call dominated that walk.
|
|
12
|
+
*/
|
|
13
|
+
const resolvedSchemaSchema = compose(SchemaObjectSchema, Type.Object({ $ref: Type.Optional(Type.String()) }));
|
|
6
14
|
export const resolve = {
|
|
7
15
|
schema: (schema) => {
|
|
8
16
|
if (schema === undefined) {
|
|
9
17
|
return undefined;
|
|
10
18
|
}
|
|
11
19
|
const resoled = getResolvedRef(schema, mergeSiblingReferences);
|
|
12
|
-
return coerceValue(
|
|
20
|
+
return coerceValue(resolvedSchemaSchema, resoled);
|
|
13
21
|
},
|
|
14
22
|
};
|