@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +88 -0
  3. package/dist/client.d.ts +28 -0
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +217 -135
  6. package/dist/helpers/chunk-index.d.ts +95 -0
  7. package/dist/helpers/chunk-index.d.ts.map +1 -0
  8. package/dist/helpers/chunk-index.js +139 -0
  9. package/dist/helpers/detect-changes-proxy.d.ts +7 -6
  10. package/dist/helpers/detect-changes-proxy.d.ts.map +1 -1
  11. package/dist/helpers/detect-changes-proxy.js +83 -38
  12. package/dist/helpers/document-revision.d.ts +26 -0
  13. package/dist/helpers/document-revision.d.ts.map +1 -0
  14. package/dist/helpers/document-revision.js +47 -0
  15. package/dist/helpers/external-examples.d.ts +17 -0
  16. package/dist/helpers/external-examples.d.ts.map +1 -0
  17. package/dist/helpers/external-examples.js +50 -0
  18. package/dist/helpers/get-resolved-ref-deep.d.ts.map +1 -1
  19. package/dist/helpers/get-resolved-ref-deep.js +25 -13
  20. package/dist/helpers/get-resolved-ref.d.ts.map +1 -1
  21. package/dist/helpers/get-resolved-ref.js +61 -3
  22. package/dist/helpers/operation-examples.d.ts +6 -0
  23. package/dist/helpers/operation-examples.d.ts.map +1 -0
  24. package/dist/helpers/operation-examples.js +45 -0
  25. package/dist/helpers/unpack-proxy.d.ts +10 -0
  26. package/dist/helpers/unpack-proxy.d.ts.map +1 -1
  27. package/dist/helpers/unpack-proxy.js +15 -0
  28. package/dist/helpers/use-external-examples.d.ts +15 -0
  29. package/dist/helpers/use-external-examples.d.ts.map +1 -0
  30. package/dist/helpers/use-external-examples.js +55 -0
  31. package/dist/mutators/operation/parameters.d.ts.map +1 -1
  32. package/dist/mutators/operation/parameters.js +28 -9
  33. package/dist/plugins/bundler/index.d.ts +5 -2
  34. package/dist/plugins/bundler/index.d.ts.map +1 -1
  35. package/dist/plugins/bundler/index.js +12 -3
  36. package/dist/request-example/builder/header/is-param-disabled.d.ts +2 -2
  37. package/dist/request-example/builder/header/is-param-disabled.d.ts.map +1 -1
  38. package/dist/request-example/builder/header/is-param-disabled.js +5 -4
  39. package/dist/request-example/builder/helpers/get-example-from-schema.d.ts.map +1 -1
  40. package/dist/request-example/builder/helpers/get-example-from-schema.js +21 -1
  41. package/dist/request-example/builder/helpers/get-example.d.ts +2 -0
  42. package/dist/request-example/builder/helpers/get-example.d.ts.map +1 -1
  43. package/dist/request-example/builder/helpers/get-example.js +9 -7
  44. package/dist/request-example/context/headers.d.ts.map +1 -1
  45. package/dist/request-example/context/headers.js +3 -4
  46. package/dist/resolve.d.ts +10 -2
  47. package/dist/resolve.d.ts.map +1 -1
  48. package/dist/resolve.js +9 -1
  49. package/dist/schemas/v3.2/strict/openapi-document.d.ts.map +1 -1
  50. package/dist/schemas/v3.2/strict/openapi-document.js +17 -1
  51. package/dist/server.d.ts +16 -0
  52. package/dist/server.d.ts.map +1 -1
  53. package/dist/server.js +34 -9
  54. 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;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EACxC,QAAQ,CAAC,EACT,UAAU;IACR,KAAK,EAAE,OAAO,CAAC;QACb,cAAc,EAAE,kBAAkB,CAAA;QAClC,aAAa,EAAE,iBAAiB,CAAA;KACjC,CAAC,CAAA;CACH,EACD,OAAM;IACJ,gCAAgC;IAChC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,0BAA0B;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;CAIf,KACA,CAoDF,CAAA;AAED,eAAO,MAAM,0BAA0B,GAAI,KAAK,OAAO,KAAG,OAMzD,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,KAAK,CAAC,KAAG,CAWpD,CAAA"}
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
- * createDetectChangesProxy - Creates a proxy for an object or array that detects and triggers hooks on changes.
6
- *
7
- * This proxy enables detection of set operations, triggering optional hooks (onBeforeChange, onAfterChange) with the path and value changed.
8
- * The proxy can be applied recursively to all nested objects/arrays, and caches proxies to prevent creating multiple proxies for the same object.
9
- *
10
- * Example usage:
11
- *
12
- * const obj = { foo: 1, bar: { baz: 2 } };
13
- * const proxy = createDetectChangesProxy(obj, {
14
- * hooks: {
15
- * onBeforeChange: (path, value) => console.log('Before', path, value),
16
- * onAfterChange: (path, value) => console.log('After', path, value),
17
- * }
18
- * });
19
- * proxy.foo = 42; // Console: Before ['foo'] '42', After ['foo'] '42'
20
- * proxy.bar.baz = 99; // Console: Before ['bar', 'baz'] '99', After ['bar', 'baz'] '99'
21
- *
22
- * @param target The target object or array to wrap in a proxy
23
- * @param options Optional: hooks for change detection
24
- * @param args Internal: proxy cache and current property path (used for recursion)
25
- * @returns The proxied object/array with change detection capabilities
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
- if (args.proxyCache.has(target)) {
37
- return args.proxyCache.get(target);
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
- return createDetectChangesProxy(value, options, { ...args, path: [...args.path, String(prop)] });
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 path = [...args.path, String(prop)];
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
- options?.hooks?.onBeforeChange?.(path, value);
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
- options?.hooks?.onAfterChange?.(path, value);
72
+ if (path) {
73
+ onAfterChange?.(path, value);
74
+ }
63
75
  return result;
64
76
  },
65
77
  deleteProperty(target, prop) {
66
- const path = [...args.path, String(prop)];
67
- options?.hooks?.onBeforeChange?.(path);
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
- options?.hooks?.onAfterChange?.(path);
85
+ if (path) {
86
+ onAfterChange?.(path);
87
+ }
70
88
  return result;
71
89
  },
72
90
  });
73
91
  // Cache the proxy for this target
74
- args.proxyCache.set(target, proxy);
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,CA6D/F,CAAA"}
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 { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy';
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
- const rawValue = unpackProxyObject(current, { depth: 1 });
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
- const siblings = Object.entries(current).filter(([key]) => key !== '$ref' && key !== '$ref-value');
40
- if (siblings.length > 0 && isObject(result)) {
41
- const merged = { ...result };
42
- for (const [key, value] of siblings) {
43
- merged[key] = resolveNode(value);
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
- cachedResults.set(rawValue, result);
49
- return result;
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.map(resolveNode);
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 = Object.fromEntries(Object.entries(current).map(([key, value]) => [key, resolveNode(value)]));
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;AAOlD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,IAalE,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
+ {"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"}