capdag 0.152.345 → 0.157.363
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/build-browser.js +15 -15
- package/{cap-graph-renderer.js → cap-fab-renderer.js} +78 -78
- package/capdag.js +152 -42
- package/capdag.test.js +72 -46
- package/package.json +2 -2
package/capdag.js
CHANGED
|
@@ -3142,13 +3142,13 @@ class CapArgumentValue {
|
|
|
3142
3142
|
|
|
3143
3143
|
|
|
3144
3144
|
// ============================================================================
|
|
3145
|
-
//
|
|
3145
|
+
// CAPFAB - Directed graph of capability conversions
|
|
3146
3146
|
// ============================================================================
|
|
3147
3147
|
|
|
3148
3148
|
/**
|
|
3149
3149
|
* An edge in the capability graph representing a conversion from one media URN to another.
|
|
3150
3150
|
*/
|
|
3151
|
-
class
|
|
3151
|
+
class CapFabEdge {
|
|
3152
3152
|
/**
|
|
3153
3153
|
* @param {string} fromUrn - The input media URN
|
|
3154
3154
|
* @param {string} toUrn - The output media URN
|
|
@@ -3168,7 +3168,7 @@ class CapGraphEdge {
|
|
|
3168
3168
|
/**
|
|
3169
3169
|
* Statistics about a capability graph.
|
|
3170
3170
|
*/
|
|
3171
|
-
class
|
|
3171
|
+
class CapFabStats {
|
|
3172
3172
|
/**
|
|
3173
3173
|
* @param {number} nodeCount - Number of unique media URN nodes
|
|
3174
3174
|
* @param {number} edgeCount - Number of edges (capabilities)
|
|
@@ -3187,7 +3187,7 @@ class CapGraphStats {
|
|
|
3187
3187
|
* A directed graph where nodes are media URNs and edges are capabilities.
|
|
3188
3188
|
* This graph enables discovering conversion paths between different media formats.
|
|
3189
3189
|
*/
|
|
3190
|
-
class
|
|
3190
|
+
class CapFab {
|
|
3191
3191
|
constructor() {
|
|
3192
3192
|
this.edges = [];
|
|
3193
3193
|
this.outgoing = new Map(); // fromUrn -> edge indices
|
|
@@ -3211,7 +3211,7 @@ class CapGraph {
|
|
|
3211
3211
|
|
|
3212
3212
|
// Create edge
|
|
3213
3213
|
const edgeIndex = this.edges.length;
|
|
3214
|
-
const edge = new
|
|
3214
|
+
const edge = new CapFabEdge(fromUrn, toUrn, cap, registryName, specificity);
|
|
3215
3215
|
this.edges.push(edge);
|
|
3216
3216
|
|
|
3217
3217
|
// Update outgoing index
|
|
@@ -3237,7 +3237,7 @@ class CapGraph {
|
|
|
3237
3237
|
|
|
3238
3238
|
/**
|
|
3239
3239
|
* Get all edges in the graph.
|
|
3240
|
-
* @returns {
|
|
3240
|
+
* @returns {CapFabEdge[]}
|
|
3241
3241
|
*/
|
|
3242
3242
|
getEdges() {
|
|
3243
3243
|
return [...this.edges];
|
|
@@ -3247,7 +3247,7 @@ class CapGraph {
|
|
|
3247
3247
|
* Get all edges where the provided URN satisfies the edge's input requirement.
|
|
3248
3248
|
* Uses conformsTo-based matching instead of exact string matching.
|
|
3249
3249
|
* @param {string} urn - The media URN
|
|
3250
|
-
* @returns {
|
|
3250
|
+
* @returns {CapFabEdge[]}
|
|
3251
3251
|
*/
|
|
3252
3252
|
getOutgoing(urn) {
|
|
3253
3253
|
// Use TaggedUrn matching: find all edges where the provided URN (instance)
|
|
@@ -3268,7 +3268,7 @@ class CapGraph {
|
|
|
3268
3268
|
/**
|
|
3269
3269
|
* Get all edges targeting a media URN.
|
|
3270
3270
|
* @param {string} urn - The media URN
|
|
3271
|
-
* @returns {
|
|
3271
|
+
* @returns {CapFabEdge[]}
|
|
3272
3272
|
*/
|
|
3273
3273
|
getIncoming(urn) {
|
|
3274
3274
|
const indices = this.incoming.get(urn) || [];
|
|
@@ -3289,7 +3289,7 @@ class CapGraph {
|
|
|
3289
3289
|
* Get all direct edges from one URN to another, sorted by specificity (highest first).
|
|
3290
3290
|
* @param {string} fromUrn - The source media URN
|
|
3291
3291
|
* @param {string} toUrn - The target media URN
|
|
3292
|
-
* @returns {
|
|
3292
|
+
* @returns {CapFabEdge[]}
|
|
3293
3293
|
*/
|
|
3294
3294
|
getDirectEdges(fromUrn, toUrn) {
|
|
3295
3295
|
const edges = this.getOutgoing(fromUrn).filter(edge => edge.toUrn === toUrn);
|
|
@@ -3338,7 +3338,7 @@ class CapGraph {
|
|
|
3338
3338
|
* Find the shortest conversion path from one URN to another.
|
|
3339
3339
|
* @param {string} fromUrn - The source media URN
|
|
3340
3340
|
* @param {string} toUrn - The target media URN
|
|
3341
|
-
* @returns {
|
|
3341
|
+
* @returns {CapFabEdge[]|null} Array of edges representing the path, or null if no path exists
|
|
3342
3342
|
*/
|
|
3343
3343
|
findPath(fromUrn, toUrn) {
|
|
3344
3344
|
if (fromUrn === toUrn) {
|
|
@@ -3393,7 +3393,7 @@ class CapGraph {
|
|
|
3393
3393
|
* @param {string} fromUrn - The source media URN
|
|
3394
3394
|
* @param {string} toUrn - The target media URN
|
|
3395
3395
|
* @param {number} maxDepth - Maximum path length to search
|
|
3396
|
-
* @returns {
|
|
3396
|
+
* @returns {CapFabEdge[][]} Array of paths (each path is an array of edges)
|
|
3397
3397
|
*/
|
|
3398
3398
|
findAllPaths(fromUrn, toUrn, maxDepth) {
|
|
3399
3399
|
if (!this.nodes.has(fromUrn) || !this.nodes.has(toUrn)) {
|
|
@@ -3447,7 +3447,7 @@ class CapGraph {
|
|
|
3447
3447
|
* @param {string} fromUrn - The source media URN
|
|
3448
3448
|
* @param {string} toUrn - The target media URN
|
|
3449
3449
|
* @param {number} maxDepth - Maximum path length to search
|
|
3450
|
-
* @returns {
|
|
3450
|
+
* @returns {CapFabEdge[]|null} Array of edges representing the best path, or null if no path exists
|
|
3451
3451
|
*/
|
|
3452
3452
|
findBestPath(fromUrn, toUrn, maxDepth) {
|
|
3453
3453
|
const allPaths = this.findAllPaths(fromUrn, toUrn, maxDepth);
|
|
@@ -3488,10 +3488,10 @@ class CapGraph {
|
|
|
3488
3488
|
|
|
3489
3489
|
/**
|
|
3490
3490
|
* Get statistics about the graph.
|
|
3491
|
-
* @returns {
|
|
3491
|
+
* @returns {CapFabStats}
|
|
3492
3492
|
*/
|
|
3493
3493
|
stats() {
|
|
3494
|
-
return new
|
|
3494
|
+
return new CapFabStats(
|
|
3495
3495
|
this.nodes.size,
|
|
3496
3496
|
this.edges.length,
|
|
3497
3497
|
this.outgoing.size,
|
|
@@ -3613,6 +3613,54 @@ class CartridgeCapGroup {
|
|
|
3613
3613
|
}
|
|
3614
3614
|
}
|
|
3615
3615
|
|
|
3616
|
+
// =============================================================================
|
|
3617
|
+
// Cartridge registry slug
|
|
3618
|
+
// =============================================================================
|
|
3619
|
+
//
|
|
3620
|
+
// Deterministic mapping from a registry URL to a top-level folder
|
|
3621
|
+
// name under the cartridges install root. Mirrors
|
|
3622
|
+
// capdag::cartridge_slug byte-for-byte: SHA-256 of the URL bytes,
|
|
3623
|
+
// lowercase hex, first 16 chars. The literal string "dev" is
|
|
3624
|
+
// reserved for dev cartridges that have no registry.
|
|
3625
|
+
//
|
|
3626
|
+
// JS uses Web Crypto's SubtleCrypto for SHA-256. The function is
|
|
3627
|
+
// async because `crypto.subtle.digest` returns a Promise; consumers
|
|
3628
|
+
// in synchronous contexts must await.
|
|
3629
|
+
|
|
3630
|
+
const DEV_SLUG = "dev";
|
|
3631
|
+
const SLUG_HEX_LEN = 16;
|
|
3632
|
+
|
|
3633
|
+
/**
|
|
3634
|
+
* Compute the on-disk slug for a registry URL.
|
|
3635
|
+
*
|
|
3636
|
+
* @param {string|null|undefined} registryUrl - The registry URL, or
|
|
3637
|
+
* null/undefined for dev installs.
|
|
3638
|
+
* @returns {Promise<string>} The slug — `DEV_SLUG` for null,
|
|
3639
|
+
* otherwise the first 16 lowercase-hex characters of
|
|
3640
|
+
* sha256(registryUrl).
|
|
3641
|
+
*/
|
|
3642
|
+
async function slugForRegistryUrl(registryUrl) {
|
|
3643
|
+
if (registryUrl === null || registryUrl === undefined) {
|
|
3644
|
+
return DEV_SLUG;
|
|
3645
|
+
}
|
|
3646
|
+
const bytes = new TextEncoder().encode(registryUrl);
|
|
3647
|
+
// Web Crypto exposes SHA-256 via crypto.subtle. Node 16+ exposes
|
|
3648
|
+
// it through globalThis.crypto.subtle.
|
|
3649
|
+
const digestBuffer = await crypto.subtle.digest("SHA-256", bytes);
|
|
3650
|
+
const digestBytes = new Uint8Array(digestBuffer);
|
|
3651
|
+
let hex = "";
|
|
3652
|
+
for (const b of digestBytes) {
|
|
3653
|
+
hex += b.toString(16).padStart(2, "0");
|
|
3654
|
+
}
|
|
3655
|
+
return hex.slice(0, SLUG_HEX_LEN);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
function isRegistrySlug(s) {
|
|
3659
|
+
return typeof s === "string"
|
|
3660
|
+
&& s.length === SLUG_HEX_LEN
|
|
3661
|
+
&& /^[0-9a-f]+$/.test(s);
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3616
3664
|
/**
|
|
3617
3665
|
* Cartridge information from registry
|
|
3618
3666
|
*/
|
|
@@ -3640,6 +3688,16 @@ class CartridgeInfo {
|
|
|
3640
3688
|
throw new Error(`CartridgeInfo ${data.id || '?'}: invalid or missing channel '${data.channel}'`);
|
|
3641
3689
|
}
|
|
3642
3690
|
this.channel = data.channel;
|
|
3691
|
+
// Registry URL: verbatim string the registry was fetched from.
|
|
3692
|
+
// Required and non-empty — every CartridgeInfo carries the URL of
|
|
3693
|
+
// the registry that served it so downstream consumers can build
|
|
3694
|
+
// the (registryUrl, channel, id) identity tuple without
|
|
3695
|
+
// re-deriving it. The registry transformer stamps this onto every
|
|
3696
|
+
// entry at flatten time.
|
|
3697
|
+
if (typeof data.registryUrl !== 'string' || data.registryUrl.length === 0) {
|
|
3698
|
+
throw new Error(`CartridgeInfo ${data.id || '?'}: registryUrl is required and must be a non-empty string`);
|
|
3699
|
+
}
|
|
3700
|
+
this.registryUrl = data.registryUrl;
|
|
3643
3701
|
}
|
|
3644
3702
|
|
|
3645
3703
|
/** All caps flattened across all cap_groups, deduplicated by URN */
|
|
@@ -3688,9 +3746,13 @@ class CartridgeInfo {
|
|
|
3688
3746
|
}
|
|
3689
3747
|
|
|
3690
3748
|
/**
|
|
3691
|
-
* Cartridge suggestion for a missing cap.
|
|
3692
|
-
*
|
|
3693
|
-
* the
|
|
3749
|
+
* Cartridge suggestion for a missing cap.
|
|
3750
|
+
*
|
|
3751
|
+
* `(registryUrl, channel, cartridgeId)` is the suggesting
|
|
3752
|
+
* cartridge's full identity — installs of the same id from
|
|
3753
|
+
* different registries × channels are independent records and the
|
|
3754
|
+
* client keeps both visible. `registryUrl` is required and
|
|
3755
|
+
* non-empty; suggestions never come from dev installs.
|
|
3694
3756
|
*/
|
|
3695
3757
|
class CartridgeSuggestion {
|
|
3696
3758
|
constructor(data) {
|
|
@@ -3706,25 +3768,29 @@ class CartridgeSuggestion {
|
|
|
3706
3768
|
throw new Error(`CartridgeSuggestion: invalid or missing channel '${data.channel}'`);
|
|
3707
3769
|
}
|
|
3708
3770
|
this.channel = data.channel;
|
|
3771
|
+
if (typeof data.registryUrl !== 'string' || data.registryUrl.length === 0) {
|
|
3772
|
+
throw new Error("CartridgeSuggestion: registryUrl is required and must be a non-empty string");
|
|
3773
|
+
}
|
|
3774
|
+
this.registryUrl = data.registryUrl;
|
|
3709
3775
|
}
|
|
3710
3776
|
}
|
|
3711
3777
|
|
|
3712
3778
|
/**
|
|
3713
3779
|
* Cartridge registry cache entry. The cartridges map is keyed by
|
|
3714
|
-
* `<channel>:<id>` so the same id can independently
|
|
3715
|
-
* channels.
|
|
3780
|
+
* `<registryUrl>:<channel>:<id>` so the same id can independently
|
|
3781
|
+
* coexist across multiple registries × both channels.
|
|
3716
3782
|
*/
|
|
3717
3783
|
class CartridgeRepoCache {
|
|
3718
3784
|
constructor(repoUrl) {
|
|
3719
|
-
this.cartridges = new Map(); // "<channel>:<id>" -> CartridgeInfo
|
|
3720
|
-
this.capToCartridges = new Map(); // cap_urn -> [{channel, id}]
|
|
3785
|
+
this.cartridges = new Map(); // "<registryUrl>:<channel>:<id>" -> CartridgeInfo
|
|
3786
|
+
this.capToCartridges = new Map(); // cap_urn -> [{registryUrl, channel, id}]
|
|
3721
3787
|
this.lastUpdated = Date.now();
|
|
3722
3788
|
this.repoUrl = repoUrl;
|
|
3723
3789
|
}
|
|
3724
3790
|
}
|
|
3725
3791
|
|
|
3726
|
-
function _cacheKey(channel, id) {
|
|
3727
|
-
return `${channel}:${id}`;
|
|
3792
|
+
function _cacheKey(registryUrl, channel, id) {
|
|
3793
|
+
return `${registryUrl}:${channel}:${id}`;
|
|
3728
3794
|
}
|
|
3729
3795
|
|
|
3730
3796
|
/**
|
|
@@ -3756,6 +3822,21 @@ class CartridgeRepoClient {
|
|
|
3756
3822
|
if (data.schemaVersion !== '5.0') {
|
|
3757
3823
|
throw new Error(`Cartridge registry from ${repoUrl} has schemaVersion '${data.schemaVersion}'; required: 5.0`);
|
|
3758
3824
|
}
|
|
3825
|
+
// Self-referential check: the manifest declares its own URL via
|
|
3826
|
+
// `registryUrl`. It must match the URL we just fetched from
|
|
3827
|
+
// byte-for-byte — a mismatch is a manifest-corruption signal
|
|
3828
|
+
// (publisher wrote the wrong URL, or manifest is being served
|
|
3829
|
+
// from an unexpected mirror). Identity downstream depends on
|
|
3830
|
+
// this string; refuse to ingest on mismatch.
|
|
3831
|
+
if (typeof data.registryUrl !== 'string' || data.registryUrl.length === 0) {
|
|
3832
|
+
throw new Error(`Cartridge registry from ${repoUrl}: missing required top-level 'registryUrl' field`);
|
|
3833
|
+
}
|
|
3834
|
+
if (data.registryUrl !== repoUrl) {
|
|
3835
|
+
throw new Error(
|
|
3836
|
+
`Cartridge registry from ${repoUrl}: declared registryUrl '${data.registryUrl}' ` +
|
|
3837
|
+
`does not match the URL it was fetched from. These must match byte-for-byte.`
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3759
3840
|
if (!data.channels || typeof data.channels !== 'object') {
|
|
3760
3841
|
throw new Error(`Cartridge registry from ${repoUrl}: missing channels object`);
|
|
3761
3842
|
}
|
|
@@ -3771,7 +3852,12 @@ class CartridgeRepoClient {
|
|
|
3771
3852
|
...c,
|
|
3772
3853
|
id,
|
|
3773
3854
|
version: c.latestVersion,
|
|
3774
|
-
channel
|
|
3855
|
+
channel,
|
|
3856
|
+
// Stamp registryUrl onto every entry — verbatim from the
|
|
3857
|
+
// registry self-reference (which we just verified equals
|
|
3858
|
+
// the fetched URL). Identity comparison downstream is
|
|
3859
|
+
// byte equality.
|
|
3860
|
+
registryUrl: data.registryUrl
|
|
3775
3861
|
}));
|
|
3776
3862
|
}
|
|
3777
3863
|
}
|
|
@@ -3794,7 +3880,10 @@ class CartridgeRepoClient {
|
|
|
3794
3880
|
const cache = new CartridgeRepoCache(repoUrl);
|
|
3795
3881
|
|
|
3796
3882
|
for (const cartridge of cartridges) {
|
|
3797
|
-
cache.cartridges.set(
|
|
3883
|
+
cache.cartridges.set(
|
|
3884
|
+
_cacheKey(cartridge.registryUrl, cartridge.channel, cartridge.id),
|
|
3885
|
+
cartridge
|
|
3886
|
+
);
|
|
3798
3887
|
|
|
3799
3888
|
for (const cap of cartridge.allCaps()) {
|
|
3800
3889
|
const normalized = CapUrn.fromString(cap.urn).toString();
|
|
@@ -3802,6 +3891,7 @@ class CartridgeRepoClient {
|
|
|
3802
3891
|
cache.capToCartridges.set(normalized, []);
|
|
3803
3892
|
}
|
|
3804
3893
|
cache.capToCartridges.get(normalized).push({
|
|
3894
|
+
registryUrl: cartridge.registryUrl,
|
|
3805
3895
|
channel: cartridge.channel,
|
|
3806
3896
|
id: cartridge.id
|
|
3807
3897
|
});
|
|
@@ -3866,7 +3956,7 @@ class CartridgeRepoClient {
|
|
|
3866
3956
|
if (!refs) continue;
|
|
3867
3957
|
|
|
3868
3958
|
for (const ref of refs) {
|
|
3869
|
-
const cartridge = cache.cartridges.get(_cacheKey(ref.channel, ref.id));
|
|
3959
|
+
const cartridge = cache.cartridges.get(_cacheKey(ref.registryUrl, ref.channel, ref.id));
|
|
3870
3960
|
if (!cartridge) continue;
|
|
3871
3961
|
|
|
3872
3962
|
const capInfo = cartridge.allCaps().find(c => {
|
|
@@ -3891,7 +3981,8 @@ class CartridgeRepoClient {
|
|
|
3891
3981
|
latestVersion: cartridge.version,
|
|
3892
3982
|
repoUrl: cache.repoUrl,
|
|
3893
3983
|
pageUrl: pageUrl,
|
|
3894
|
-
channel: cartridge.channel
|
|
3984
|
+
channel: cartridge.channel,
|
|
3985
|
+
registryUrl: cartridge.registryUrl
|
|
3895
3986
|
}));
|
|
3896
3987
|
}
|
|
3897
3988
|
}
|
|
@@ -3928,16 +4019,23 @@ class CartridgeRepoClient {
|
|
|
3928
4019
|
}
|
|
3929
4020
|
|
|
3930
4021
|
/**
|
|
3931
|
-
* Get cartridge info by `(channel, id)`.
|
|
3932
|
-
* the same id can independently exist
|
|
3933
|
-
*
|
|
4022
|
+
* Get cartridge info by `(registryUrl, channel, id)`. All three
|
|
4023
|
+
* are required — the same id can independently exist across
|
|
4024
|
+
* multiple registries × both channels with different metadata.
|
|
4025
|
+
* Returns `null` when not found. `registryUrl` is the verbatim
|
|
4026
|
+
* URL the cache was indexed under.
|
|
3934
4027
|
*/
|
|
3935
|
-
getCartridge(channel, cartridgeId) {
|
|
4028
|
+
getCartridge(registryUrl, channel, cartridgeId) {
|
|
4029
|
+
if (typeof registryUrl !== 'string' || registryUrl.length === 0) {
|
|
4030
|
+
throw new Error('getCartridge: registryUrl must be a non-empty string');
|
|
4031
|
+
}
|
|
3936
4032
|
if (channel !== 'release' && channel !== 'nightly') {
|
|
3937
4033
|
throw new Error(`Invalid channel '${channel}' — must be 'release' or 'nightly'`);
|
|
3938
4034
|
}
|
|
3939
|
-
const key = _cacheKey(channel, cartridgeId);
|
|
3940
|
-
|
|
4035
|
+
const key = _cacheKey(registryUrl, channel, cartridgeId);
|
|
4036
|
+
// Cache outer key is also the registry URL, so look up directly.
|
|
4037
|
+
const cache = this.caches.get(registryUrl);
|
|
4038
|
+
if (cache) {
|
|
3941
4039
|
const cartridge = cache.cartridges.get(key);
|
|
3942
4040
|
if (cartridge) {
|
|
3943
4041
|
return cartridge;
|
|
@@ -4001,6 +4099,9 @@ class CartridgeRepoServer {
|
|
|
4001
4099
|
if (this.registry.schemaVersion !== '5.0') {
|
|
4002
4100
|
throw new Error(`Unsupported registry schema version: ${this.registry.schemaVersion}. Required: 5.0`);
|
|
4003
4101
|
}
|
|
4102
|
+
if (typeof this.registry.registryUrl !== 'string' || this.registry.registryUrl.length === 0) {
|
|
4103
|
+
throw new Error('Registry must have a non-empty top-level `registryUrl` field (self-referential URL)');
|
|
4104
|
+
}
|
|
4004
4105
|
const channels = this.registry.channels;
|
|
4005
4106
|
if (!channels || typeof channels !== 'object') {
|
|
4006
4107
|
throw new Error('Registry must have a channels object');
|
|
@@ -4082,7 +4183,11 @@ class CartridgeRepoServer {
|
|
|
4082
4183
|
tags: cartridge.tags,
|
|
4083
4184
|
versions: cartridge.versions,
|
|
4084
4185
|
availableVersions,
|
|
4085
|
-
channel
|
|
4186
|
+
channel,
|
|
4187
|
+
// Stamp the manifest's self-referential URL onto every entry —
|
|
4188
|
+
// verbatim from the registry. Identity comparison downstream is
|
|
4189
|
+
// byte equality.
|
|
4190
|
+
registryUrl: this.registry.registryUrl
|
|
4086
4191
|
};
|
|
4087
4192
|
}
|
|
4088
4193
|
|
|
@@ -5078,16 +5183,16 @@ class MachineBuilder {
|
|
|
5078
5183
|
}
|
|
5079
5184
|
|
|
5080
5185
|
/**
|
|
5081
|
-
* Add a linear chain of edges from
|
|
5186
|
+
* Add a linear chain of edges from CapFabEdge[] (from CapFab.findAllPaths).
|
|
5082
5187
|
*
|
|
5083
|
-
* Each
|
|
5188
|
+
* Each CapFabEdge has fromUrn, toUrn, and cap (with cap.urn).
|
|
5084
5189
|
* This converts the path into a series of MachineEdges.
|
|
5085
5190
|
*
|
|
5086
|
-
* @param {
|
|
5191
|
+
* @param {CapFabEdge[]} capFabEdges - Array of CapFabEdge from pathfinding
|
|
5087
5192
|
* @returns {MachineBuilder} this (for chaining)
|
|
5088
5193
|
*/
|
|
5089
|
-
|
|
5090
|
-
for (const edge of
|
|
5194
|
+
addCapFabPath(capFabEdges) {
|
|
5195
|
+
for (const edge of capFabEdges) {
|
|
5091
5196
|
const source = MediaUrn.fromString(edge.fromUrn);
|
|
5092
5197
|
const target = MediaUrn.fromString(edge.toUrn);
|
|
5093
5198
|
this._edges.push(new MachineEdge([source], edge.cap.urn, target, false));
|
|
@@ -5396,9 +5501,9 @@ module.exports = {
|
|
|
5396
5501
|
mediaUrnForType,
|
|
5397
5502
|
modelAvailabilityUrn,
|
|
5398
5503
|
modelPathUrn,
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5504
|
+
CapFabEdge,
|
|
5505
|
+
CapFabStats,
|
|
5506
|
+
CapFab,
|
|
5402
5507
|
StdinSource,
|
|
5403
5508
|
StdinSourceKind,
|
|
5404
5509
|
// Cartridge Repository
|
|
@@ -5409,6 +5514,11 @@ module.exports = {
|
|
|
5409
5514
|
CartridgeRepoClient,
|
|
5410
5515
|
CartridgeRepoServer,
|
|
5411
5516
|
CartridgeChannel,
|
|
5517
|
+
// Registry slug
|
|
5518
|
+
DEV_SLUG,
|
|
5519
|
+
SLUG_HEX_LEN,
|
|
5520
|
+
slugForRegistryUrl,
|
|
5521
|
+
isRegistrySlug,
|
|
5412
5522
|
// Machine notation
|
|
5413
5523
|
MachineSyntaxError,
|
|
5414
5524
|
MachineSyntaxErrorCodes,
|