@expofp/resolve 3.11.12 → 3.12.1
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/dist/resolve.d.ts +21 -0
- package/dist/resolve.js +88 -11
- package/dist/types.d.ts +7 -0
- package/package.json +2 -2
package/dist/resolve.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
import { type Ref, type ResolveOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a `$ref` to its document. Non-ref values pass through (they ARE the
|
|
4
|
+
* value). A ref carrying a `process` hook has it applied to the raw document —
|
|
5
|
+
* that is the whole extension surface; this package knows nothing about
|
|
6
|
+
* schemas or assets. The result is deep-frozen and memoized by `$ref` for the
|
|
7
|
+
* sync {@link getResolved}; repeated resolves of the same ref reuse the fetch
|
|
8
|
+
* cache, so they cost no extra network round-trip. The memo is keyed by URL
|
|
9
|
+
* alone — resolving the same URL through refs with different `process` hooks
|
|
10
|
+
* settles whichever ran last.
|
|
11
|
+
*/
|
|
2
12
|
export declare function resolve<T>(data: Ref<T> | T, options?: ResolveOptions): Promise<T>;
|
|
13
|
+
/**
|
|
14
|
+
* The settled value of an already-resolved `$ref`, synchronously. Throws when
|
|
15
|
+
* the ref has not settled (never resolved, still in flight, or failed) — a
|
|
16
|
+
* boot-ordering mistake that should surface loudly. Non-ref values pass
|
|
17
|
+
* through. Probe with {@link isResolved} where absence is expected.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getResolved<T>(data: Ref<T> | T): T;
|
|
20
|
+
/** Whether {@link getResolved} would succeed. Non-ref values are always "resolved". */
|
|
21
|
+
export declare function isResolved(data: unknown): boolean;
|
|
22
|
+
/** Drop every cached fetch and settled value (tests; long-lived kiosk refreshes). */
|
|
23
|
+
export declare function clearResolveCache(): void;
|
|
3
24
|
//# sourceMappingURL=resolve.d.ts.map
|
package/dist/resolve.js
CHANGED
|
@@ -1,17 +1,94 @@
|
|
|
1
|
-
import { importJson } from '@expofp/utils';
|
|
1
|
+
import { deepFreeze, importJsAsJson, importJson } from '@expofp/utils';
|
|
2
2
|
const globalResolveCache = new Map();
|
|
3
|
+
/** Settled results by `$ref` — the sync {@link getResolved} reads these. */
|
|
4
|
+
const settledValues = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a `$ref` to its document. Non-ref values pass through (they ARE the
|
|
7
|
+
* value). A ref carrying a `process` hook has it applied to the raw document —
|
|
8
|
+
* that is the whole extension surface; this package knows nothing about
|
|
9
|
+
* schemas or assets. The result is deep-frozen and memoized by `$ref` for the
|
|
10
|
+
* sync {@link getResolved}; repeated resolves of the same ref reuse the fetch
|
|
11
|
+
* cache, so they cost no extra network round-trip. The memo is keyed by URL
|
|
12
|
+
* alone — resolving the same URL through refs with different `process` hooks
|
|
13
|
+
* settles whichever ran last.
|
|
14
|
+
*/
|
|
3
15
|
// TODO: use json pointer lib to resolve within documents
|
|
4
16
|
export async function resolve(data, options) {
|
|
5
|
-
|
|
6
|
-
fetchCache: options?.refCache ?? globalResolveCache,
|
|
7
|
-
forceFetch: options?.forceFetch,
|
|
8
|
-
signal: options?.signal,
|
|
9
|
-
importCallback: options?.importCallback,
|
|
10
|
-
};
|
|
11
|
-
if (typeof data !== 'object' || data === null || !('$ref' in data)) {
|
|
17
|
+
if (!isRefShaped(data)) {
|
|
12
18
|
return data;
|
|
13
|
-
// throw new Error('resolve: data is not a $ref object');
|
|
14
19
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
20
|
+
const ref = data.$ref;
|
|
21
|
+
// Legacy `.js` refs (`data.js`, `fp.svg.js`, …) are scripts that assign
|
|
22
|
+
// browser globals rather than being JSON documents. Evaluate them and
|
|
23
|
+
// return the assigned globals.
|
|
24
|
+
const raw = await (isJsRef(ref)
|
|
25
|
+
? resolveJsRef(ref, options)
|
|
26
|
+
: importJson(ref, {
|
|
27
|
+
fetchCache: options?.refCache ?? globalResolveCache,
|
|
28
|
+
forceFetch: options?.forceFetch,
|
|
29
|
+
signal: options?.signal,
|
|
30
|
+
importCallback: options?.importCallback,
|
|
31
|
+
}));
|
|
32
|
+
const value = data.process ? await data.process(raw, ref) : raw;
|
|
33
|
+
deepFreeze(value);
|
|
34
|
+
settledValues.set(ref, value);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The settled value of an already-resolved `$ref`, synchronously. Throws when
|
|
39
|
+
* the ref has not settled (never resolved, still in flight, or failed) — a
|
|
40
|
+
* boot-ordering mistake that should surface loudly. Non-ref values pass
|
|
41
|
+
* through. Probe with {@link isResolved} where absence is expected.
|
|
42
|
+
*/
|
|
43
|
+
export function getResolved(data) {
|
|
44
|
+
if (!isRefShaped(data)) {
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
if (!settledValues.has(data.$ref)) {
|
|
48
|
+
throw new Error(`resolve: $ref not resolved yet: ${data.$ref}`);
|
|
49
|
+
}
|
|
50
|
+
return settledValues.get(data.$ref);
|
|
51
|
+
}
|
|
52
|
+
/** Whether {@link getResolved} would succeed. Non-ref values are always "resolved". */
|
|
53
|
+
export function isResolved(data) {
|
|
54
|
+
return isRefShaped(data) ? settledValues.has(data.$ref) : true;
|
|
55
|
+
}
|
|
56
|
+
/** Drop every cached fetch and settled value (tests; long-lived kiosk refreshes). */
|
|
57
|
+
export function clearResolveCache() {
|
|
58
|
+
globalResolveCache.clear();
|
|
59
|
+
settledValues.clear();
|
|
60
|
+
}
|
|
61
|
+
function isRefShaped(data) {
|
|
62
|
+
return (typeof data === 'object' && data !== null && '$ref' in data && typeof data.$ref === 'string');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Matches a `.js` path — bare (`…/data.js`) or with a query/hash
|
|
66
|
+
* (`…/data.js?v=1`). Anchored to the `.js` extension, not a substring test, so
|
|
67
|
+
* `.json` refs are left alone.
|
|
68
|
+
*/
|
|
69
|
+
function isJsRef(ref) {
|
|
70
|
+
return /\.js(\?|#|$)/i.test(ref);
|
|
71
|
+
}
|
|
72
|
+
/** Evaluate a legacy `.js` ref to its globals, deduped through the ref cache. */
|
|
73
|
+
function resolveJsRef(ref, options) {
|
|
74
|
+
const cache = options?.refCache ?? globalResolveCache;
|
|
75
|
+
const cacheKey = '__loadJs__' + ref;
|
|
76
|
+
let promise = options?.forceFetch ? undefined : cache.get(cacheKey);
|
|
77
|
+
if (!promise) {
|
|
78
|
+
const fetched = importJsAsJson(ref, { signal: options?.signal });
|
|
79
|
+
cache.set(cacheKey, fetched);
|
|
80
|
+
// A rejection must not poison the URL for the cache's life (one transient
|
|
81
|
+
// fetch drop would replay as a permanent failure) — evict it so the next
|
|
82
|
+
// resolve retries. Guarded: a forceFetch may have replaced the entry.
|
|
83
|
+
fetched.catch(() => {
|
|
84
|
+
if (cache.get(cacheKey) === fetched)
|
|
85
|
+
cache.delete(cacheKey);
|
|
86
|
+
});
|
|
87
|
+
promise = fetched;
|
|
88
|
+
}
|
|
89
|
+
// Fire importCallback once per resolve call (after load), mirroring importJson.
|
|
90
|
+
return promise.then((result) => {
|
|
91
|
+
options?.importCallback?.(ref);
|
|
92
|
+
return result;
|
|
93
|
+
});
|
|
17
94
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export interface Ref<T> {
|
|
2
2
|
$ref: string;
|
|
3
|
+
/**
|
|
4
|
+
* Post-resolution hook: turns the raw fetched/evaluated document into the
|
|
5
|
+
* value to memoize (validate, absolutize resource URLs, …). Attached in
|
|
6
|
+
* code by whoever mints the ref — `JSON.stringify` drops it, so serialized
|
|
7
|
+
* refs stay plain `{ $ref }` data.
|
|
8
|
+
*/
|
|
9
|
+
process?: (raw: unknown, refUrl: string) => unknown;
|
|
3
10
|
__type?: T;
|
|
4
11
|
}
|
|
5
12
|
export type RefTarget<R> = R extends Ref<infer T> ? T : never;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/resolve",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ExpoFP SDK internal: asset and resource resolution",
|
|
6
6
|
"homepage": "https://developer.expofp.com/",
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"tslib": "^2.3.0",
|
|
30
|
-
"@expofp/utils": "3.
|
|
30
|
+
"@expofp/utils": "3.12.1"
|
|
31
31
|
}
|
|
32
32
|
}
|