@scalar/workspace-store 0.62.0 → 0.64.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 +36 -0
- package/README.md +88 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +217 -135
- package/dist/helpers/chunk-index.d.ts +95 -0
- package/dist/helpers/chunk-index.d.ts.map +1 -0
- package/dist/helpers/chunk-index.js +139 -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/external-examples.d.ts +17 -0
- package/dist/helpers/external-examples.d.ts.map +1 -0
- package/dist/helpers/external-examples.js +50 -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/operation-examples.d.ts +6 -0
- package/dist/helpers/operation-examples.d.ts.map +1 -0
- package/dist/helpers/operation-examples.js +45 -0
- 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/helpers/use-external-examples.d.ts +15 -0
- package/dist/helpers/use-external-examples.d.ts.map +1 -0
- package/dist/helpers/use-external-examples.js +55 -0
- package/dist/mutators/operation/parameters.d.ts.map +1 -1
- package/dist/mutators/operation/parameters.js +28 -9
- package/dist/plugins/bundler/index.d.ts +5 -2
- package/dist/plugins/bundler/index.d.ts.map +1 -1
- package/dist/plugins/bundler/index.js +12 -3
- package/dist/request-example/builder/header/is-param-disabled.d.ts +2 -2
- package/dist/request-example/builder/header/is-param-disabled.d.ts.map +1 -1
- package/dist/request-example/builder/header/is-param-disabled.js +5 -4
- 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/request-example/builder/helpers/get-example.d.ts +2 -0
- package/dist/request-example/builder/helpers/get-example.d.ts.map +1 -1
- package/dist/request-example/builder/helpers/get-example.js +9 -7
- package/dist/request-example/context/headers.d.ts.map +1 -1
- package/dist/request-example/context/headers.js +3 -4
- package/dist/resolve.d.ts +10 -2
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +9 -1
- package/dist/schemas/v3.2/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/v3.2/strict/openapi-document.js +17 -1
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +34 -9
- package/package.json +3 -3
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { isHttpMethod } from '@scalar/helpers/http/is-http-method';
|
|
2
|
+
import { isObject } from '@scalar/helpers/object/is-object';
|
|
3
|
+
import { escapeJsonPointer } from '@scalar/json-magic/helpers/escape-json-pointer';
|
|
4
|
+
import { encodeChunkName } from '../helpers/encode-chunk-name.js';
|
|
5
|
+
/**
|
|
6
|
+
* The extension key a compact sparse document carries its chunk index under.
|
|
7
|
+
*
|
|
8
|
+
* It exists on the wire only: the client expands the index back into per-node references while the
|
|
9
|
+
* document is ingested and removes the key, so nothing downstream ever sees it.
|
|
10
|
+
*/
|
|
11
|
+
export const CHUNK_INDEX_KEY = 'x-scalar-chunk-index';
|
|
12
|
+
/** Stands in for an externalized operation on a compact path item. */
|
|
13
|
+
const OPERATION_PLACEHOLDER = 0;
|
|
14
|
+
/** Matches a `{slot}` in a template, or a doubled brace standing for a literal one. */
|
|
15
|
+
const TEMPLATE_SLOT = /\{\{|\}\}|\{(\w+)\}/g;
|
|
16
|
+
/**
|
|
17
|
+
* Fills the `{slot}`s of a chunk-reference template.
|
|
18
|
+
*
|
|
19
|
+
* `{{` and `}}` stand for literal braces, so text the writer inlined into a template — a document
|
|
20
|
+
* name, a base URL — may contain braces without being read back as a slot. An unknown slot is left
|
|
21
|
+
* alone rather than blanked, so a template from a newer writer fails visibly instead of resolving
|
|
22
|
+
* to the wrong chunk.
|
|
23
|
+
*/
|
|
24
|
+
export const fillChunkRef = (template, values = {}) => template.replace(TEMPLATE_SLOT, (match, slot) => slot === undefined ? match.charAt(0) : (values[slot] ?? match));
|
|
25
|
+
/** Escapes text inlined into a template verbatim, so its braces are not read back as slots. */
|
|
26
|
+
const escapeTemplateLiteral = (text) => text.replaceAll('{', '{{').replaceAll('}', '}}');
|
|
27
|
+
const identity = (value) => value;
|
|
28
|
+
/**
|
|
29
|
+
* How each template slot is spelled, per mode.
|
|
30
|
+
*
|
|
31
|
+
* Static references name files, so their variable segments go through `encodeChunkName` — the same
|
|
32
|
+
* call `generateWorkspaceChunks` makes when it writes those files. SSR references name pointers
|
|
33
|
+
* into the store's in-memory assets, which `get()` looks up with JSON Pointer segments, so only the
|
|
34
|
+
* path is escaped. A method is never encoded in either mode: it is written as it was read, and
|
|
35
|
+
* `isHttpMethod` keeps it to letters.
|
|
36
|
+
*
|
|
37
|
+
* Both sides read the table from here. A reader that encoded a segment differently would build a
|
|
38
|
+
* reference to a chunk that does not exist.
|
|
39
|
+
*/
|
|
40
|
+
const SLOT_ENCODERS = {
|
|
41
|
+
static: { type: encodeChunkName, name: encodeChunkName, path: encodeChunkName, method: identity },
|
|
42
|
+
ssr: { type: identity, name: identity, path: escapeJsonPointer, method: identity },
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Builds the reference templates for one document.
|
|
46
|
+
*
|
|
47
|
+
* Filled, these produce exactly the references `externalizeComponentReferences` and
|
|
48
|
+
* `externalizePathReferences` write, so a client that expands the index lands on the same chunk.
|
|
49
|
+
*/
|
|
50
|
+
export const chunkRefTemplates = (meta) => {
|
|
51
|
+
if (meta.mode === 'ssr') {
|
|
52
|
+
const base = `${escapeTemplateLiteral(meta.baseUrl)}/${escapeTemplateLiteral(meta.name)}`;
|
|
53
|
+
return {
|
|
54
|
+
components: `${base}/components/{type}/{name}#`,
|
|
55
|
+
operations: `${base}/operations/{path}/{method}#`,
|
|
56
|
+
navigation: `${base}/navigation#`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const base = `./chunks/${escapeTemplateLiteral(encodeChunkName(meta.name))}`;
|
|
60
|
+
return {
|
|
61
|
+
components: `${base}/components/{type}/{name}.json#`,
|
|
62
|
+
operations: `${base}/operations/{path}/{method}.json#`,
|
|
63
|
+
navigation: `${base}/navigation.json#`,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/** The reference a lazily loaded chunk is reached through. */
|
|
67
|
+
export const chunkReference = (ref) => ({ '$ref': ref, $global: true });
|
|
68
|
+
/**
|
|
69
|
+
* Compacts a sparse document's `components` and `paths` into an index.
|
|
70
|
+
*
|
|
71
|
+
* Takes the sections the externalizers produced rather than the document itself, so the index is
|
|
72
|
+
* built from the very references it replaces and the two cannot come to describe different sets of
|
|
73
|
+
* chunks.
|
|
74
|
+
*/
|
|
75
|
+
export const buildChunkIndex = ({ mode, refs, components, paths, }) => ({
|
|
76
|
+
mode,
|
|
77
|
+
refs,
|
|
78
|
+
components: Object.fromEntries(Object.entries(components).map(([type, names]) => [type, Object.keys(names)])),
|
|
79
|
+
paths: Object.fromEntries(Object.entries(paths).map(([path, pathItem]) => [
|
|
80
|
+
path,
|
|
81
|
+
Object.fromEntries(Object.entries(pathItem).map(([key, value]) => [key, isHttpMethod(key) ? OPERATION_PLACEHOLDER : value])),
|
|
82
|
+
])),
|
|
83
|
+
});
|
|
84
|
+
/** Whether a value is shaped like an index this build knows how to expand. */
|
|
85
|
+
const isChunkIndex = (value) => isObject(value) &&
|
|
86
|
+
(value['mode'] === 'static' || value['mode'] === 'ssr') &&
|
|
87
|
+
isObject(value['refs']) &&
|
|
88
|
+
isObject(value['components']) &&
|
|
89
|
+
isObject(value['paths']);
|
|
90
|
+
/**
|
|
91
|
+
* Expands a compact sparse document into the one a non-compact server store would have sent.
|
|
92
|
+
*
|
|
93
|
+
* Mutates the document in place and drops the index key, so what the rest of the store sees is an
|
|
94
|
+
* ordinary sparse document: `resolve()`, the bundler and anything enumerating `paths` or
|
|
95
|
+
* `components` are looking at the shape they always have.
|
|
96
|
+
*
|
|
97
|
+
* A document without an index is left alone, which is every document a non-compact server store or
|
|
98
|
+
* an author produces.
|
|
99
|
+
*
|
|
100
|
+
* @param document - The document to expand, mutated in place
|
|
101
|
+
* @returns Whether an index was found and expanded
|
|
102
|
+
*/
|
|
103
|
+
export const expandChunkIndex = (document) => {
|
|
104
|
+
if (!isObject(document) || document[CHUNK_INDEX_KEY] === undefined) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const index = document[CHUNK_INDEX_KEY];
|
|
108
|
+
if (!isChunkIndex(index)) {
|
|
109
|
+
// The key is dropped rather than passed on: it is not part of any document a consumer should
|
|
110
|
+
// see, and the sections it stands for are missing either way, so there is nothing a partial
|
|
111
|
+
// expansion could recover.
|
|
112
|
+
delete document[CHUNK_INDEX_KEY];
|
|
113
|
+
console.warn(`Ignoring an unreadable "${CHUNK_INDEX_KEY}"; this document's chunks cannot be resolved.`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const encoders = SLOT_ENCODERS[index.mode];
|
|
117
|
+
// Define own properties so document keys such as `__proto__` remain data, without invoking
|
|
118
|
+
// inherited setters or changing the prototype of any expanded object.
|
|
119
|
+
const components = Object.fromEntries(Object.entries(index.components).map(([type, names]) => [
|
|
120
|
+
type,
|
|
121
|
+
Object.fromEntries(names.map((name) => [
|
|
122
|
+
name,
|
|
123
|
+
chunkReference(fillChunkRef(index.refs.components, { type: encoders.type(type), name: encoders.name(name) })),
|
|
124
|
+
])),
|
|
125
|
+
]));
|
|
126
|
+
const paths = Object.fromEntries(Object.entries(index.paths).map(([path, pathItem]) => [
|
|
127
|
+
path,
|
|
128
|
+
Object.fromEntries(Object.entries(pathItem).map(([key, value]) => [
|
|
129
|
+
key,
|
|
130
|
+
isHttpMethod(key)
|
|
131
|
+
? chunkReference(fillChunkRef(index.refs.operations, { path: encoders.path(path), method: encoders.method(key) }))
|
|
132
|
+
: value,
|
|
133
|
+
])),
|
|
134
|
+
]));
|
|
135
|
+
document['components'] = components;
|
|
136
|
+
document['paths'] = paths;
|
|
137
|
+
delete document[CHUNK_INDEX_KEY];
|
|
138
|
+
return true;
|
|
139
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type LoaderPlugin } from '@scalar/json-magic/bundle';
|
|
2
|
+
import { fetchUrls } from '@scalar/json-magic/bundle/plugins/browser';
|
|
3
|
+
import type { ExampleObject } from '../schemas/v3.2/strict/openapi-document.js';
|
|
4
|
+
/** Download state lives outside the document so reading an example never creates an edit. */
|
|
5
|
+
export type ExternalExampleState = {
|
|
6
|
+
status: 'idle' | 'loading' | 'loaded' | 'error';
|
|
7
|
+
value?: unknown;
|
|
8
|
+
load: () => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/** A resolver belongs to one document revision and its configured transport. */
|
|
11
|
+
export type ExternalExampleResolver = (example: ExampleObject) => ExternalExampleState;
|
|
12
|
+
/** Cache external payloads and in-flight requests without changing authored examples. */
|
|
13
|
+
export declare const createExternalExampleResolver: (options?: Parameters<typeof fetchUrls>[0] & {
|
|
14
|
+
origin?: string;
|
|
15
|
+
fileLoader?: LoaderPlugin;
|
|
16
|
+
}) => ExternalExampleResolver;
|
|
17
|
+
//# sourceMappingURL=external-examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-examples.d.ts","sourceRoot":"","sources":["../../src/helpers/external-examples.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAwB,MAAM,2BAA2B,CAAA;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAE3E,6FAA6F;AAC7F,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B,CAAA;AAED,gFAAgF;AAChF,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK,oBAAoB,CAAA;AAEtF,yFAAyF;AACzF,eAAO,MAAM,6BAA6B,GACxC,UAAS,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,YAAY,CAAA;CAAO,KAC7F,uBAuCF,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { resolveReferencePath } from '@scalar/json-magic/bundle';
|
|
2
|
+
import { fetchUrls } from '@scalar/json-magic/bundle/plugins/browser';
|
|
3
|
+
import { shallowReactive } from 'vue';
|
|
4
|
+
/** Cache external payloads and in-flight requests without changing authored examples. */
|
|
5
|
+
export const createExternalExampleResolver = (options = {}) => {
|
|
6
|
+
const loader = fetchUrls({ ...options, limit: 10 });
|
|
7
|
+
const cache = new Map();
|
|
8
|
+
return (example) => {
|
|
9
|
+
if (example.value !== undefined || !example.externalValue) {
|
|
10
|
+
return { status: 'loaded', value: example.value, load: () => Promise.resolve() };
|
|
11
|
+
}
|
|
12
|
+
const url = resolveReferencePath(options.origin ?? '', example.externalValue) ?? example.externalValue;
|
|
13
|
+
const cached = cache.get(url);
|
|
14
|
+
if (cached)
|
|
15
|
+
return cached;
|
|
16
|
+
let pending;
|
|
17
|
+
const state = shallowReactive({
|
|
18
|
+
status: 'idle',
|
|
19
|
+
load: () => {
|
|
20
|
+
if (pending)
|
|
21
|
+
return pending;
|
|
22
|
+
if (state.status === 'loaded')
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
state.status = 'loading';
|
|
25
|
+
pending = (async () => {
|
|
26
|
+
try {
|
|
27
|
+
const selectedLoader = [loader, options.fileLoader].find((candidate) => candidate?.validate(url));
|
|
28
|
+
const result = await selectedLoader?.exec(url);
|
|
29
|
+
if (result?.ok) {
|
|
30
|
+
state.value = result.data === undefined ? result.raw : result.data;
|
|
31
|
+
state.status = 'loaded';
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
state.status = 'error';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
state.status = 'error';
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
pending = undefined;
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
return pending;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
cache.set(url, state);
|
|
48
|
+
return state;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -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"}
|