capdag 1.470.4 → 1.474.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/README.md +6 -0
- package/capdag.js +494 -31
- package/machine-parser.js +103 -323
- package/package.json +2 -2
package/capdag.js
CHANGED
|
@@ -2062,9 +2062,10 @@ class MediaDef {
|
|
|
2062
2062
|
* @param {Object|null} validation - Optional validation rules (min, max, min_length, max_length, pattern, allowed_values)
|
|
2063
2063
|
* @param {Object|null} metadata - Optional metadata (arbitrary key-value pairs for display/categorization)
|
|
2064
2064
|
* @param {string[]} extensions - File extensions for storing this media type (e.g., ['pdf'], ['jpg', 'jpeg'])
|
|
2065
|
-
* @param {string|null} documentation - Optional long-form markdown documentation. Rendered in media info panels, the cap navigator, capdag-dot-com, and anywhere else a rich-text explanation of the media def is useful.
|
|
2065
|
+
* @param {string|null} documentation - Optional long-form markdown documentation. Rendered in media info panels, the cap navigator, capdag-dot-com, and anywhere else a rich-text explanation of the media def is useful. Only what is specific to this media def: what the dims it was built from mean is documented with each dim.
|
|
2066
|
+
* @param {DimRef[]} dims - The dims this media def was built from, and the value chosen from each, in the order its source references them. Each dim is published on its own (`FabricRegistryClient.getDim`); a reader shows its documentation beside `documentation`. Empty when built from no dim.
|
|
2066
2067
|
*/
|
|
2067
|
-
constructor(contentType, profile = null, schema = null, title = null, description = null, mediaUrn = null, validation = null, metadata = null, extensions = [], documentation = null) {
|
|
2068
|
+
constructor(contentType, profile = null, schema = null, title = null, description = null, mediaUrn = null, validation = null, metadata = null, extensions = [], documentation = null, dims = []) {
|
|
2068
2069
|
this.contentType = contentType;
|
|
2069
2070
|
this.profile = profile;
|
|
2070
2071
|
this.schema = schema;
|
|
@@ -2075,6 +2076,7 @@ class MediaDef {
|
|
|
2075
2076
|
this.metadata = metadata;
|
|
2076
2077
|
this.extensions = extensions;
|
|
2077
2078
|
this.documentation = documentation;
|
|
2079
|
+
this.dims = dims;
|
|
2078
2080
|
}
|
|
2079
2081
|
|
|
2080
2082
|
/**
|
|
@@ -2224,7 +2226,7 @@ function resolveMediaUrn(mediaUrn, mediaDefs = []) {
|
|
|
2224
2226
|
const def = mediaDefs.find(spec => spec.urn === mediaUrn);
|
|
2225
2227
|
|
|
2226
2228
|
if (def) {
|
|
2227
|
-
// Object form: { urn, media_type, title, profile_uri?, schema?, description?, documentation?, validation?, metadata?, extensions? }
|
|
2229
|
+
// Object form: { urn, media_type, title, profile_uri?, schema?, description?, documentation?, dims?, validation?, metadata?, extensions? }
|
|
2228
2230
|
const mediaType = def.media_type || def.mediaType;
|
|
2229
2231
|
const profileUri = def.profile_uri || def.profileUri || null;
|
|
2230
2232
|
const schema = def.schema || null;
|
|
@@ -2240,6 +2242,9 @@ function resolveMediaUrn(mediaUrn, mediaDefs = []) {
|
|
|
2240
2242
|
const validation = def.validation || null;
|
|
2241
2243
|
const metadata = def.metadata || null;
|
|
2242
2244
|
const extensions = Array.isArray(def.extensions) ? def.extensions : [];
|
|
2245
|
+
// The dims it was built from, each entry held to the dim reference
|
|
2246
|
+
// keys and each dim named once.
|
|
2247
|
+
const dims = dimRefsFromJSON(def.dims, `media def '${mediaUrn}'`);
|
|
2243
2248
|
|
|
2244
2249
|
if (!mediaType) {
|
|
2245
2250
|
throw new MediaDefError(
|
|
@@ -2248,7 +2253,7 @@ function resolveMediaUrn(mediaUrn, mediaDefs = []) {
|
|
|
2248
2253
|
);
|
|
2249
2254
|
}
|
|
2250
2255
|
|
|
2251
|
-
return new MediaDef(mediaType, profileUri, schema, title, description, mediaUrn, validation, metadata, extensions, documentation);
|
|
2256
|
+
return new MediaDef(mediaType, profileUri, schema, title, description, mediaUrn, validation, metadata, extensions, documentation, dims);
|
|
2252
2257
|
}
|
|
2253
2258
|
}
|
|
2254
2259
|
|
|
@@ -3023,7 +3028,16 @@ class Cap {
|
|
|
3023
3028
|
this.aliases = aliases;
|
|
3024
3029
|
this.is_abstract = false;
|
|
3025
3030
|
this.cap_description = capDescription;
|
|
3031
|
+
// Only what is specific to this cap: what the dims it was built from mean
|
|
3032
|
+
// is documented with each dim (see `dims`).
|
|
3026
3033
|
this.documentation = documentation;
|
|
3034
|
+
// The dims this cap was built from, and the value chosen from each
|
|
3035
|
+
// (Array of DimRef), in the order its source references them. Each dim is
|
|
3036
|
+
// published on its own (`FabricRegistryClient.getDim`) with its
|
|
3037
|
+
// documentation and each value's; a reader shows that beside
|
|
3038
|
+
// `documentation`. Empty when the cap was built from no dim. Descriptive
|
|
3039
|
+
// only: matching and dispatch never read it.
|
|
3040
|
+
this.dims = [];
|
|
3027
3041
|
this.metadata = metadata || {};
|
|
3028
3042
|
this.args = []; // Array of CapArg - unified argument format
|
|
3029
3043
|
// Every output the cap declares (Array of CapOutput). Exactly one is the
|
|
@@ -3317,6 +3331,7 @@ class Cap {
|
|
|
3317
3331
|
this.is_abstract === other.is_abstract &&
|
|
3318
3332
|
this.cap_description === other.cap_description &&
|
|
3319
3333
|
this.documentation === other.documentation &&
|
|
3334
|
+
JSON.stringify(this.dims.map(d => d.toJSON())) === JSON.stringify(other.dims.map(d => d.toJSON())) &&
|
|
3320
3335
|
JSON.stringify(this.metadata) === JSON.stringify(other.metadata) &&
|
|
3321
3336
|
JSON.stringify(this.args.map(a => a.toJSON())) === JSON.stringify(other.args.map(a => a.toJSON())) &&
|
|
3322
3337
|
JSON.stringify(this.outputs.map(o => o.toJSON())) === JSON.stringify(other.outputs.map(o => o.toJSON())) &&
|
|
@@ -3355,6 +3370,12 @@ class Cap {
|
|
|
3355
3370
|
result.documentation = this.documentation;
|
|
3356
3371
|
}
|
|
3357
3372
|
|
|
3373
|
+
// Emit `dims` only when the cap was built from some; absent means none,
|
|
3374
|
+
// exactly as the registry publishes it.
|
|
3375
|
+
if (this.dims.length > 0) {
|
|
3376
|
+
result.dims = this.dims.map(d => d.toJSON());
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3358
3379
|
if (this.metadata_json !== null && this.metadata_json !== undefined) {
|
|
3359
3380
|
result.metadata_json = this.metadata_json;
|
|
3360
3381
|
}
|
|
@@ -3393,6 +3414,9 @@ class Cap {
|
|
|
3393
3414
|
const cap = new Cap(urn, json.title, json.aliases, json.cap_description, json.metadata, json.metadata_json, documentation);
|
|
3394
3415
|
cap.is_abstract = json.abstract === true;
|
|
3395
3416
|
cap.version = (typeof json.version === 'number' && json.version !== 0) ? json.version : 0;
|
|
3417
|
+
// A dim is named at most once: a definition cannot have been built with
|
|
3418
|
+
// two values of one dim.
|
|
3419
|
+
cap.dims = dimRefsFromJSON(json.dims, `cap '${urn}'`);
|
|
3396
3420
|
// Parse args (new format)
|
|
3397
3421
|
if (json.args && Array.isArray(json.args)) {
|
|
3398
3422
|
cap.args = json.args.map(a => CapArg.fromJSON(a));
|
|
@@ -5564,6 +5588,23 @@ class CartridgeSuggestion {
|
|
|
5564
5588
|
* `<registryUrl>:<channel>:<id>` so the same id can independently
|
|
5565
5589
|
* coexist across multiple registries × both channels.
|
|
5566
5590
|
*/
|
|
5591
|
+
/**
|
|
5592
|
+
* The schema revision of the cartridge registry this build reads
|
|
5593
|
+
* (`schemas/cartridge-registry/v7.json`): its cap groups carry each
|
|
5594
|
+
* cartridge's cap definitions exactly as its manifest declares them,
|
|
5595
|
+
* including the dims each was built from.
|
|
5596
|
+
*/
|
|
5597
|
+
const CARTRIDGE_REGISTRY_SCHEMA_VERSION = '7.0';
|
|
5598
|
+
|
|
5599
|
+
/**
|
|
5600
|
+
* The cartridge registry regime version this build speaks
|
|
5601
|
+
* (`schemas/cartridge-registry/registry-version.txt`), served at
|
|
5602
|
+
* `/v<version>/manifest`. v0 is the pre-versioning legacy at the bare
|
|
5603
|
+
* `/manifest` path; a registry of any other version is refused, never
|
|
5604
|
+
* reinterpreted.
|
|
5605
|
+
*/
|
|
5606
|
+
const CARTRIDGE_REGISTRY_VERSION = 3;
|
|
5607
|
+
|
|
5567
5608
|
class CartridgeRepoCache {
|
|
5568
5609
|
constructor(repoUrl) {
|
|
5569
5610
|
this.cartridges = new Map(); // "<registryUrl>:<channel>:<id>" -> CartridgeInfo
|
|
@@ -5587,7 +5628,8 @@ class CartridgeRepoClient {
|
|
|
5587
5628
|
}
|
|
5588
5629
|
|
|
5589
5630
|
/**
|
|
5590
|
-
* Fetch a
|
|
5631
|
+
* Fetch a channel-partitioned registry (schema
|
|
5632
|
+
* CARTRIDGE_REGISTRY_SCHEMA_VERSION) from a URL and flatten
|
|
5591
5633
|
* to a list of `CartridgeInfo`, one per `(channel, id)` pair.
|
|
5592
5634
|
*/
|
|
5593
5635
|
async fetchRegistry(repoUrl) {
|
|
@@ -5603,14 +5645,13 @@ class CartridgeRepoClient {
|
|
|
5603
5645
|
}
|
|
5604
5646
|
|
|
5605
5647
|
const data = await response.json();
|
|
5606
|
-
if (data.schemaVersion !==
|
|
5607
|
-
throw new Error(`Cartridge registry from ${repoUrl} has schemaVersion '${data.schemaVersion}'; required:
|
|
5648
|
+
if (data.schemaVersion !== CARTRIDGE_REGISTRY_SCHEMA_VERSION) {
|
|
5649
|
+
throw new Error(`Cartridge registry from ${repoUrl} has schemaVersion '${data.schemaVersion}'; required: ${CARTRIDGE_REGISTRY_SCHEMA_VERSION}`);
|
|
5608
5650
|
}
|
|
5609
|
-
// Cartridge registry regime version.
|
|
5610
|
-
//
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
throw new Error(`Cartridge registry from ${repoUrl} has registryVersion '${data.registryVersion}'; this build speaks v1`);
|
|
5651
|
+
// Cartridge registry regime version (CARTRIDGE_REGISTRY_VERSION). A
|
|
5652
|
+
// manifest from a different regime version is rejected, not reinterpreted.
|
|
5653
|
+
if (data.registryVersion !== CARTRIDGE_REGISTRY_VERSION) {
|
|
5654
|
+
throw new Error(`Cartridge registry from ${repoUrl} has registryVersion '${data.registryVersion}'; this build speaks v${CARTRIDGE_REGISTRY_VERSION}`);
|
|
5614
5655
|
}
|
|
5615
5656
|
// A cartridge registry must declare the fabric its caps resolve against. The
|
|
5616
5657
|
// client cross-checks this equals its own fabric (so every registry it uses
|
|
@@ -5877,7 +5918,8 @@ function _validChannel(c) {
|
|
|
5877
5918
|
}
|
|
5878
5919
|
|
|
5879
5920
|
/**
|
|
5880
|
-
* Reads a
|
|
5921
|
+
* Reads a channel-partitioned cartridge registry (schema
|
|
5922
|
+
* CARTRIDGE_REGISTRY_SCHEMA_VERSION). Both `release`
|
|
5881
5923
|
* and `nightly` channels are always present (possibly empty); every
|
|
5882
5924
|
* `CartridgeInfo` returned carries the channel it came from so consumers
|
|
5883
5925
|
* can render the release/nightly distinction without re-deriving.
|
|
@@ -5892,11 +5934,11 @@ class CartridgeRepoServer {
|
|
|
5892
5934
|
if (!this.registry) {
|
|
5893
5935
|
throw new Error('Registry is required');
|
|
5894
5936
|
}
|
|
5895
|
-
if (this.registry.schemaVersion !==
|
|
5896
|
-
throw new Error(`Unsupported registry schema version: ${this.registry.schemaVersion}. Required:
|
|
5937
|
+
if (this.registry.schemaVersion !== CARTRIDGE_REGISTRY_SCHEMA_VERSION) {
|
|
5938
|
+
throw new Error(`Unsupported registry schema version: ${this.registry.schemaVersion}. Required: ${CARTRIDGE_REGISTRY_SCHEMA_VERSION}`);
|
|
5897
5939
|
}
|
|
5898
|
-
if (this.registry.registryVersion !==
|
|
5899
|
-
throw new Error(`Unsupported cartridge registry version: ${this.registry.registryVersion}. This build speaks
|
|
5940
|
+
if (this.registry.registryVersion !== CARTRIDGE_REGISTRY_VERSION) {
|
|
5941
|
+
throw new Error(`Unsupported cartridge registry version: ${this.registry.registryVersion}. This build speaks v${CARTRIDGE_REGISTRY_VERSION}.`);
|
|
5900
5942
|
}
|
|
5901
5943
|
if (typeof this.registry.fabricRegistryUrl !== 'string' || this.registry.fabricRegistryUrl.length === 0) {
|
|
5902
5944
|
throw new Error('Registry must declare a non-empty `fabricRegistryUrl` (the fabric its caps resolve against)');
|
|
@@ -11072,7 +11114,7 @@ Machine.prototype.toMermaid = function toMermaid(aliases = null) {
|
|
|
11072
11114
|
};
|
|
11073
11115
|
|
|
11074
11116
|
// ============================================================================
|
|
11075
|
-
// The fabric registry: versioned definitions of caps, media and
|
|
11117
|
+
// The fabric registry: versioned definitions of caps, media, aliases and dims
|
|
11076
11118
|
// ============================================================================
|
|
11077
11119
|
|
|
11078
11120
|
/**
|
|
@@ -11117,8 +11159,8 @@ const LOAD_ALL_AT_ONCE = 16;
|
|
|
11117
11159
|
|
|
11118
11160
|
/**
|
|
11119
11161
|
* Read a manifest as the registry serves it, refusing one that is not whole:
|
|
11120
|
-
* `version` and `previous` are whole numbers, and `caps`, `media`
|
|
11121
|
-
* `
|
|
11162
|
+
* `version` and `previous` are whole numbers, and `caps`, `media`, `aliases`
|
|
11163
|
+
* and `dims` map each name to a positive per-definition version.
|
|
11122
11164
|
*/
|
|
11123
11165
|
function manifestFromJSON(obj, expectedVersion) {
|
|
11124
11166
|
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
@@ -11132,7 +11174,7 @@ function manifestFromJSON(obj, expectedVersion) {
|
|
|
11132
11174
|
throw new FabricRegistryError(FabricRegistryErrorCodes.PARSE,
|
|
11133
11175
|
`manifest fetched as v${expectedVersion} reports version ${obj.version}`);
|
|
11134
11176
|
}
|
|
11135
|
-
for (const section of ['caps', 'media', 'aliases']) {
|
|
11177
|
+
for (const section of ['caps', 'media', 'aliases', 'dims']) {
|
|
11136
11178
|
const held = obj[section];
|
|
11137
11179
|
if (held === null || typeof held !== 'object' || Array.isArray(held)) {
|
|
11138
11180
|
throw new FabricRegistryError(FabricRegistryErrorCodes.PARSE, `manifest v${expectedVersion} has no '${section}' map`);
|
|
@@ -11144,7 +11186,7 @@ function manifestFromJSON(obj, expectedVersion) {
|
|
|
11144
11186
|
}
|
|
11145
11187
|
}
|
|
11146
11188
|
}
|
|
11147
|
-
return new Manifest(obj.version, obj.previous, { ...obj.caps }, { ...obj.media }, { ...obj.aliases });
|
|
11189
|
+
return new Manifest(obj.version, obj.previous, { ...obj.caps }, { ...obj.media }, { ...obj.aliases }, { ...obj.dims });
|
|
11148
11190
|
}
|
|
11149
11191
|
|
|
11150
11192
|
/**
|
|
@@ -11163,9 +11205,16 @@ function mediaDefFromStored(obj, expectedUrn, expectedVersion) {
|
|
|
11163
11205
|
throw refused(`reports version ${JSON.stringify(obj.version)} but the manifest pins ${expectedVersion}`);
|
|
11164
11206
|
}
|
|
11165
11207
|
if (obj.extensions !== undefined && !Array.isArray(obj.extensions)) throw refused("has 'extensions' that are not a list");
|
|
11208
|
+
let dims;
|
|
11209
|
+
try {
|
|
11210
|
+
dims = dimRefsFromJSON(obj.dims, `media def '${expectedUrn}'`);
|
|
11211
|
+
} catch (error) {
|
|
11212
|
+
throw refused(`has dims this capdag cannot hold: ${error.message}`);
|
|
11213
|
+
}
|
|
11166
11214
|
const def = new MediaDef(obj.media_type, obj.profile_uri || null, obj.schema === undefined ? null : obj.schema,
|
|
11167
11215
|
obj.title, obj.description || null, expectedUrn, obj.validation || null, obj.metadata || null,
|
|
11168
|
-
obj.extensions || [], typeof obj.documentation === 'string' && obj.documentation.length > 0 ? obj.documentation : null
|
|
11216
|
+
obj.extensions || [], typeof obj.documentation === 'string' && obj.documentation.length > 0 ? obj.documentation : null,
|
|
11217
|
+
dims);
|
|
11169
11218
|
def.version = obj.version;
|
|
11170
11219
|
return def;
|
|
11171
11220
|
}
|
|
@@ -11200,12 +11249,41 @@ function capFromStored(obj, expectedUrn, expectedVersion) {
|
|
|
11200
11249
|
return cap;
|
|
11201
11250
|
}
|
|
11202
11251
|
|
|
11252
|
+
/**
|
|
11253
|
+
* A dim body as a registry served it, for the dim `name` at `defver`: parsed
|
|
11254
|
+
* (unknown fields refused), named and versioned as requested, and holding
|
|
11255
|
+
* (`StoredDim.validate`).
|
|
11256
|
+
* @returns {StoredDim}
|
|
11257
|
+
*/
|
|
11258
|
+
function checkedDim(obj, name, defver) {
|
|
11259
|
+
let dim;
|
|
11260
|
+
try {
|
|
11261
|
+
dim = StoredDim.fromJSON(obj);
|
|
11262
|
+
} catch (error) {
|
|
11263
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.PARSE, `Failed to parse dim '${name}': ${error.message}`);
|
|
11264
|
+
}
|
|
11265
|
+
if (dim.name !== name) {
|
|
11266
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.PARSE,
|
|
11267
|
+
`dim object name '${dim.name}' does not match requested name '${name}'`);
|
|
11268
|
+
}
|
|
11269
|
+
if (dim.version !== defver) {
|
|
11270
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.PARSE,
|
|
11271
|
+
`dim '${dim.name}' object reports version ${dim.version} but manifest pins defver ${defver}`);
|
|
11272
|
+
}
|
|
11273
|
+
try {
|
|
11274
|
+
dim.validate();
|
|
11275
|
+
} catch (error) {
|
|
11276
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.VALIDATION, error.message);
|
|
11277
|
+
}
|
|
11278
|
+
return dim;
|
|
11279
|
+
}
|
|
11280
|
+
|
|
11203
11281
|
/**
|
|
11204
11282
|
* A client of the fabric registry, pinned to one manifest version.
|
|
11205
11283
|
*
|
|
11206
|
-
* The manifest names every cap URN, media URN and
|
|
11207
|
-
* the version of each definition; each definition is one object at
|
|
11208
|
-
* `<base>/{caps,media,aliases}/<sha256 of its canonical name>/<version>.json`.
|
|
11284
|
+
* The manifest names every cap URN, media URN, alias and dim of its snapshot
|
|
11285
|
+
* with the version of each definition; each definition is one object at
|
|
11286
|
+
* `<base>/{caps,media,aliases,dims}/<sha256 of its canonical name>/<version>.json`.
|
|
11209
11287
|
* Nothing outside the pinned manifest is fetched: a URN it does not name is
|
|
11210
11288
|
* not found, never read from another snapshot.
|
|
11211
11289
|
*
|
|
@@ -11214,7 +11292,10 @@ function capFromStored(obj, expectedUrn, expectedVersion) {
|
|
|
11214
11292
|
* parsed against (`capDefinition`, `mediaDefinition`, `aliasTarget`) and the
|
|
11215
11293
|
* alias source a machine is written with (`displayAliasForUrn`). A cap is
|
|
11216
11294
|
* cached only once every media URN it names is: a cap whose media cannot be
|
|
11217
|
-
* read is not held at all.
|
|
11295
|
+
* read is not held at all. A cap or media definition is likewise cached only
|
|
11296
|
+
* once every dim it names (`dims`) is, at the version the manifest pins,
|
|
11297
|
+
* with the value it names — so a reader holding a definition can always
|
|
11298
|
+
* read the documentation of the dims it was built from (`getCachedDim`).
|
|
11218
11299
|
*/
|
|
11219
11300
|
class FabricRegistryClient {
|
|
11220
11301
|
/**
|
|
@@ -11238,6 +11319,7 @@ class FabricRegistryClient {
|
|
|
11238
11319
|
this._caps = new Map();
|
|
11239
11320
|
this._media = new Map();
|
|
11240
11321
|
this._aliases = new Map();
|
|
11322
|
+
this._dims = new Map();
|
|
11241
11323
|
// A fetch under way, by what it fetches: one asked for again while it
|
|
11242
11324
|
// runs is the same fetch.
|
|
11243
11325
|
this._pending = new Map();
|
|
@@ -11325,6 +11407,7 @@ class FabricRegistryClient {
|
|
|
11325
11407
|
const defver = await this._defver('media', canonical, `media definition '${canonical}'`);
|
|
11326
11408
|
const url = `${this._baseUrl}/media/${await sha256Hex(canonical)}/${defver}.json`;
|
|
11327
11409
|
const def = mediaDefFromStored(await this._get(url, `media definition '${canonical}'`), canonical, defver);
|
|
11410
|
+
await this._ensureDimsCached(`media def '${canonical}'`, def.dims);
|
|
11328
11411
|
this._media.set(canonical, def);
|
|
11329
11412
|
return def;
|
|
11330
11413
|
});
|
|
@@ -11361,6 +11444,8 @@ class FabricRegistryClient {
|
|
|
11361
11444
|
}
|
|
11362
11445
|
for (const output of cap.getOutputs()) name(output.media_urn);
|
|
11363
11446
|
for (const media of named) await this.getMediaDef(media);
|
|
11447
|
+
// Every dim the cap names, at its pinned version, with the value named.
|
|
11448
|
+
await this._ensureDimsCached(`cap '${canonical}'`, cap.dims);
|
|
11364
11449
|
this._caps.set(canonical, cap);
|
|
11365
11450
|
return cap;
|
|
11366
11451
|
}
|
|
@@ -11400,6 +11485,88 @@ class FabricRegistryClient {
|
|
|
11400
11485
|
return alias;
|
|
11401
11486
|
}
|
|
11402
11487
|
|
|
11488
|
+
/**
|
|
11489
|
+
* A dim's definition — its title, documentation and values, each value
|
|
11490
|
+
* with its own — by name (held first, then the registry), at the version
|
|
11491
|
+
* the pinned manifest names.
|
|
11492
|
+
* @param {string} name
|
|
11493
|
+
* @returns {Promise<StoredDim>}
|
|
11494
|
+
*/
|
|
11495
|
+
async getDim(name) {
|
|
11496
|
+
if (!isDimName(name)) {
|
|
11497
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.VALIDATION, `'${name}' is not a dim name`);
|
|
11498
|
+
}
|
|
11499
|
+
if (this._dims.has(name)) return this._dims.get(name);
|
|
11500
|
+
return this._once(`dim ${name}`, async () => this._fetchDim(name, await this._defver('dims', name, `dim '${name}'`)));
|
|
11501
|
+
}
|
|
11502
|
+
|
|
11503
|
+
async _fetchDim(name, defver) {
|
|
11504
|
+
const url = `${this._baseUrl}/dims/${await sha256Hex(name)}/${defver}.json`;
|
|
11505
|
+
const dim = checkedDim(await this._get(url, `dim '${name}'`), name, defver);
|
|
11506
|
+
this._dims.set(name, dim);
|
|
11507
|
+
return dim;
|
|
11508
|
+
}
|
|
11509
|
+
|
|
11510
|
+
/**
|
|
11511
|
+
* The held dim of that name, or null. Every dim a held cap or media
|
|
11512
|
+
* definition names is held with it.
|
|
11513
|
+
* @param {string} name
|
|
11514
|
+
* @returns {StoredDim|null}
|
|
11515
|
+
*/
|
|
11516
|
+
getCachedDim(name) {
|
|
11517
|
+
return this._dims.get(name) || null;
|
|
11518
|
+
}
|
|
11519
|
+
|
|
11520
|
+
/**
|
|
11521
|
+
* The version the pinned manifest names for a dim, without fetching its
|
|
11522
|
+
* body.
|
|
11523
|
+
* @param {string} name
|
|
11524
|
+
* @returns {Promise<number>}
|
|
11525
|
+
*/
|
|
11526
|
+
async dimDefverFor(name) {
|
|
11527
|
+
return this._defver('dims', name, `dim '${name}'`);
|
|
11528
|
+
}
|
|
11529
|
+
|
|
11530
|
+
/**
|
|
11531
|
+
* Insert a dim into the cache, keyed by its name: the seam a host or a
|
|
11532
|
+
* test seeds dims through without a registry behind it.
|
|
11533
|
+
* @param {StoredDim} dim
|
|
11534
|
+
*/
|
|
11535
|
+
insertCachedDimForTest(dim) {
|
|
11536
|
+
this._dims.set(dim.name, dim);
|
|
11537
|
+
}
|
|
11538
|
+
|
|
11539
|
+
/**
|
|
11540
|
+
* Make sure every dim a definition names is held, fetching the ones that
|
|
11541
|
+
* are not at the version the manifest pins, and that each names a value
|
|
11542
|
+
* the dim declares. `owner` names the definition in errors. Nothing about
|
|
11543
|
+
* the definition itself is cached here: its fetcher caches it only once
|
|
11544
|
+
* this succeeds. That each dim is named once was held when the definition
|
|
11545
|
+
* was parsed.
|
|
11546
|
+
*/
|
|
11547
|
+
async _ensureDimsCached(owner, dims) {
|
|
11548
|
+
for (const r of dims) {
|
|
11549
|
+
let dim = this._dims.get(r.dim);
|
|
11550
|
+
if (dim === undefined) {
|
|
11551
|
+
const defver = (await this.manifest()).dims[r.dim];
|
|
11552
|
+
if (defver === undefined) {
|
|
11553
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.NOT_FOUND,
|
|
11554
|
+
`${owner} names dim '${r.dim}', which is not in manifest v${this._manifestVersion}`);
|
|
11555
|
+
}
|
|
11556
|
+
try {
|
|
11557
|
+
dim = await this._once(`dim ${r.dim}`, () => this._fetchDim(r.dim, defver));
|
|
11558
|
+
} catch (error) {
|
|
11559
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.NOT_FOUND,
|
|
11560
|
+
`${owner} names dim '${r.dim}', which could not be fetched: ${error.message}`);
|
|
11561
|
+
}
|
|
11562
|
+
}
|
|
11563
|
+
if (dim.value(r.value) === null) {
|
|
11564
|
+
throw new FabricRegistryError(FabricRegistryErrorCodes.VALIDATION,
|
|
11565
|
+
`${owner} names value '${r.value}' of dim '${r.dim}', which the dim does not declare`);
|
|
11566
|
+
}
|
|
11567
|
+
}
|
|
11568
|
+
}
|
|
11569
|
+
|
|
11403
11570
|
async _aliasTargetOfKind(name, kind) {
|
|
11404
11571
|
const alias = await this.getAlias(name);
|
|
11405
11572
|
if (classifyAliasTarget(alias.target) !== kind) {
|
|
@@ -11500,6 +11667,7 @@ class FabricRegistryClient {
|
|
|
11500
11667
|
this._caps.clear();
|
|
11501
11668
|
this._media.clear();
|
|
11502
11669
|
this._aliases.clear();
|
|
11670
|
+
this._dims.clear();
|
|
11503
11671
|
this._pending.clear();
|
|
11504
11672
|
}
|
|
11505
11673
|
}
|
|
@@ -11619,21 +11787,302 @@ class StoredAlias {
|
|
|
11619
11787
|
}
|
|
11620
11788
|
}
|
|
11621
11789
|
|
|
11790
|
+
// ===========================================================================
|
|
11791
|
+
// Dims (the axes definitions are built along)
|
|
11792
|
+
// ===========================================================================
|
|
11793
|
+
//
|
|
11794
|
+
// A dim (a language, a file type, a quantisation, …) is a fabric definition
|
|
11795
|
+
// like a cap, a media def or an alias: published on its own at
|
|
11796
|
+
// `dims/<sha256-of-name>/<defver>.json` and pinned by a manifest's `dims`
|
|
11797
|
+
// map. Its body says what the dim is and which values it may take, each with
|
|
11798
|
+
// its own documentation.
|
|
11799
|
+
//
|
|
11800
|
+
// A cap or media definition names the dims it was built from, and the value
|
|
11801
|
+
// chosen from each, in its `dims` (DimRef). Its own `documentation` is only
|
|
11802
|
+
// what is specific to it; what a dim and a value mean is read from the dim.
|
|
11803
|
+
// A reader shows both.
|
|
11804
|
+
|
|
11805
|
+
const DIM_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
11806
|
+
/** The keys of an entry of a definition's `dims`. */
|
|
11807
|
+
const DIM_REF_KEYS = Object.freeze(['dim', 'value']);
|
|
11808
|
+
/** The keys of a tag a dim value contributes. */
|
|
11809
|
+
const DIM_TAG_KEYS = Object.freeze(['key', 'value']);
|
|
11810
|
+
/** The keys of a dim value (fabric/dim.schema.json). */
|
|
11811
|
+
const DIM_VALUE_KEYS = Object.freeze(['name', 'title', 'tags', 'documentation', 'media_type', 'extensions']);
|
|
11812
|
+
/** The keys of a dim body (fabric/dim.schema.json). */
|
|
11813
|
+
const DIM_KEYS = Object.freeze(['name', 'version', 'title', 'documentation', 'values']);
|
|
11814
|
+
|
|
11815
|
+
const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11816
|
+
|
|
11817
|
+
/**
|
|
11818
|
+
* Whether `name` is a dim name: `^[a-z][a-z0-9-]*$`.
|
|
11819
|
+
* @param {string} name
|
|
11820
|
+
* @returns {boolean}
|
|
11821
|
+
*/
|
|
11822
|
+
function isDimName(name) {
|
|
11823
|
+
return typeof name === 'string' && DIM_NAME_RE.test(name);
|
|
11824
|
+
}
|
|
11825
|
+
|
|
11826
|
+
/**
|
|
11827
|
+
* One dim a definition was built from, and the value chosen from it. The
|
|
11828
|
+
* entries of a definition's `dims`, in the order its source references them.
|
|
11829
|
+
*
|
|
11830
|
+
* Unknown fields are refused, as for arguments and outputs: a key this
|
|
11831
|
+
* library does not know would be dropped when the definition is written back
|
|
11832
|
+
* out, and the copy would no longer say what the registry said.
|
|
11833
|
+
*/
|
|
11834
|
+
class DimRef {
|
|
11835
|
+
/**
|
|
11836
|
+
* @param {string} dim - The dim's name
|
|
11837
|
+
* @param {string} value - The name of the value chosen from that dim
|
|
11838
|
+
*/
|
|
11839
|
+
constructor(dim, value) {
|
|
11840
|
+
this.dim = dim;
|
|
11841
|
+
this.value = value;
|
|
11842
|
+
}
|
|
11843
|
+
|
|
11844
|
+
toJSON() {
|
|
11845
|
+
return { dim: this.dim, value: this.value };
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
/** @returns {DimRef} */
|
|
11849
|
+
static fromJSON(json) {
|
|
11850
|
+
if (!isPlainObject(json)) {
|
|
11851
|
+
throw new Error(`a dim reference is an object, not ${JSON.stringify(json)}`);
|
|
11852
|
+
}
|
|
11853
|
+
refuseUnknownDefinitionKeys(json, DIM_REF_KEYS, 'dim reference');
|
|
11854
|
+
for (const key of DIM_REF_KEYS) {
|
|
11855
|
+
if (typeof json[key] !== 'string') {
|
|
11856
|
+
throw new Error(`dim reference is missing field \`${key}\``);
|
|
11857
|
+
}
|
|
11858
|
+
}
|
|
11859
|
+
return new DimRef(json.dim, json.value);
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
|
|
11863
|
+
/**
|
|
11864
|
+
* A definition's `dims` names each dim at most once, and only by dim and
|
|
11865
|
+
* value names. Returns why not, naming the offending entry, or null when it
|
|
11866
|
+
* holds.
|
|
11867
|
+
* @param {DimRef[]} dims
|
|
11868
|
+
* @returns {string|null}
|
|
11869
|
+
*/
|
|
11870
|
+
function checkDimRefs(dims) {
|
|
11871
|
+
const seen = new Set();
|
|
11872
|
+
for (const r of dims) {
|
|
11873
|
+
if (!isDimName(r.dim)) return `'${r.dim}' is not a dim name`;
|
|
11874
|
+
if (typeof r.value !== 'string' || r.value.length === 0) return `dim '${r.dim}' is named without a value`;
|
|
11875
|
+
if (seen.has(r.dim)) return `dim '${r.dim}' is named more than once`;
|
|
11876
|
+
seen.add(r.dim);
|
|
11877
|
+
}
|
|
11878
|
+
return null;
|
|
11879
|
+
}
|
|
11880
|
+
|
|
11881
|
+
/**
|
|
11882
|
+
* A definition's `dims` as its JSON carries them: absent is none; otherwise
|
|
11883
|
+
* a list of dim references, each dim named once. `what` names the definition
|
|
11884
|
+
* in the error.
|
|
11885
|
+
* @returns {DimRef[]}
|
|
11886
|
+
*/
|
|
11887
|
+
function dimRefsFromJSON(json, what) {
|
|
11888
|
+
if (json === undefined) return [];
|
|
11889
|
+
if (!Array.isArray(json)) {
|
|
11890
|
+
throw new Error(`${what}: 'dims' is a list of dim references`);
|
|
11891
|
+
}
|
|
11892
|
+
const dims = json.map(d => DimRef.fromJSON(d));
|
|
11893
|
+
const wrong = checkDimRefs(dims);
|
|
11894
|
+
if (wrong !== null) throw new Error(`${what} dims: ${wrong}`);
|
|
11895
|
+
return dims;
|
|
11896
|
+
}
|
|
11897
|
+
|
|
11898
|
+
/** A tag a dim value contributes to the URN of every definition built with it. `value` is `*` for a marker tag. */
|
|
11899
|
+
class DimTag {
|
|
11900
|
+
constructor(key, value) {
|
|
11901
|
+
this.key = key;
|
|
11902
|
+
this.value = value;
|
|
11903
|
+
}
|
|
11904
|
+
|
|
11905
|
+
toJSON() {
|
|
11906
|
+
return { key: this.key, value: this.value };
|
|
11907
|
+
}
|
|
11908
|
+
|
|
11909
|
+
/** @returns {DimTag} */
|
|
11910
|
+
static fromJSON(json) {
|
|
11911
|
+
if (!isPlainObject(json)) throw new Error(`a dim tag is an object, not ${JSON.stringify(json)}`);
|
|
11912
|
+
refuseUnknownDefinitionKeys(json, DIM_TAG_KEYS, 'dim tag');
|
|
11913
|
+
for (const key of DIM_TAG_KEYS) {
|
|
11914
|
+
if (typeof json[key] !== 'string') throw new Error(`dim tag is missing field \`${key}\``);
|
|
11915
|
+
}
|
|
11916
|
+
return new DimTag(json.key, json.value);
|
|
11917
|
+
}
|
|
11918
|
+
}
|
|
11919
|
+
|
|
11920
|
+
/** One value a dim may take. */
|
|
11921
|
+
class StoredDimValue {
|
|
11922
|
+
/**
|
|
11923
|
+
* @param {string} name - The value's name, unique within the dim
|
|
11924
|
+
* @param {string} title - Display-friendly title of the value
|
|
11925
|
+
* @param {DimTag[]} tags - The tags the value contributes; at least one
|
|
11926
|
+
* @param {string|null} documentation - Markdown documentation of what choosing this value means
|
|
11927
|
+
* @param {string|null} mediaType - The MIME media type the value gives the media defs built with it
|
|
11928
|
+
* @param {string[]} extensions - The file extensions the value gives the media defs built with it
|
|
11929
|
+
*/
|
|
11930
|
+
constructor(name, title, tags, documentation = null, mediaType = null, extensions = []) {
|
|
11931
|
+
this.name = name;
|
|
11932
|
+
this.title = title;
|
|
11933
|
+
this.tags = tags;
|
|
11934
|
+
this.documentation = documentation;
|
|
11935
|
+
this.media_type = mediaType;
|
|
11936
|
+
this.extensions = extensions;
|
|
11937
|
+
}
|
|
11938
|
+
|
|
11939
|
+
toJSON() {
|
|
11940
|
+
const result = { name: this.name, title: this.title, tags: this.tags.map(t => t.toJSON()) };
|
|
11941
|
+
if (this.documentation !== null) result.documentation = this.documentation;
|
|
11942
|
+
if (this.media_type !== null) result.media_type = this.media_type;
|
|
11943
|
+
if (this.extensions.length > 0) result.extensions = this.extensions;
|
|
11944
|
+
return result;
|
|
11945
|
+
}
|
|
11946
|
+
|
|
11947
|
+
/** @returns {StoredDimValue} */
|
|
11948
|
+
static fromJSON(json) {
|
|
11949
|
+
if (!isPlainObject(json)) throw new Error(`a dim value is an object, not ${JSON.stringify(json)}`);
|
|
11950
|
+
refuseUnknownDefinitionKeys(json, DIM_VALUE_KEYS, 'dim value');
|
|
11951
|
+
for (const key of ['name', 'title']) {
|
|
11952
|
+
if (typeof json[key] !== 'string') throw new Error(`dim value is missing field \`${key}\``);
|
|
11953
|
+
}
|
|
11954
|
+
if (!Array.isArray(json.tags)) throw new Error(`dim value '${json.name}' is missing field \`tags\``);
|
|
11955
|
+
for (const key of ['documentation', 'media_type']) {
|
|
11956
|
+
if (json[key] !== undefined && typeof json[key] !== 'string') {
|
|
11957
|
+
throw new Error(`dim value '${json.name}': '${key}' is a string`);
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
if (json.extensions !== undefined && !(Array.isArray(json.extensions) && json.extensions.every(e => typeof e === 'string'))) {
|
|
11961
|
+
throw new Error(`dim value '${json.name}': 'extensions' is a list of strings`);
|
|
11962
|
+
}
|
|
11963
|
+
return new StoredDimValue(json.name, json.title, json.tags.map(t => DimTag.fromJSON(t)),
|
|
11964
|
+
json.documentation === undefined ? null : json.documentation,
|
|
11965
|
+
json.media_type === undefined ? null : json.media_type,
|
|
11966
|
+
json.extensions === undefined ? [] : [...json.extensions]);
|
|
11967
|
+
}
|
|
11968
|
+
}
|
|
11969
|
+
|
|
11970
|
+
const DimErrorCodes = Object.freeze({
|
|
11971
|
+
INVALID_NAME: 'InvalidName',
|
|
11972
|
+
NO_VALUES: 'NoValues',
|
|
11973
|
+
DUPLICATE_VALUE: 'DuplicateValue',
|
|
11974
|
+
VALUE_WITHOUT_TAGS: 'ValueWithoutTags',
|
|
11975
|
+
});
|
|
11976
|
+
|
|
11977
|
+
/** Why a dim body is not one this library can hold. */
|
|
11978
|
+
class DimError extends Error {
|
|
11979
|
+
constructor(code, message) {
|
|
11980
|
+
super(message);
|
|
11981
|
+
this.name = 'DimError';
|
|
11982
|
+
this.code = code;
|
|
11983
|
+
}
|
|
11984
|
+
}
|
|
11985
|
+
|
|
11986
|
+
/**
|
|
11987
|
+
* Stored dim definition. Mirrors fabric/dim.schema.json on the wire and is
|
|
11988
|
+
* the body published at `dims/<sha256-of-name>/<defver>.json`.
|
|
11989
|
+
*/
|
|
11990
|
+
class StoredDim {
|
|
11991
|
+
/**
|
|
11992
|
+
* @param {string} name - The dim's name, unique across the snapshot
|
|
11993
|
+
* @param {number} version - Per-definition version; dims exist only in the versioned regime
|
|
11994
|
+
* @param {string} title - Display-friendly title of the dim
|
|
11995
|
+
* @param {string} documentation - Markdown documentation of what the dim is
|
|
11996
|
+
* @param {StoredDimValue[]} values - The values the dim may take, in the order the dim declares them
|
|
11997
|
+
*/
|
|
11998
|
+
constructor(name, version, title, documentation, values) {
|
|
11999
|
+
this.name = name;
|
|
12000
|
+
this.version = version;
|
|
12001
|
+
this.title = title;
|
|
12002
|
+
this.documentation = documentation;
|
|
12003
|
+
this.values = values;
|
|
12004
|
+
}
|
|
12005
|
+
|
|
12006
|
+
/**
|
|
12007
|
+
* The value named `name`, if the dim declares it.
|
|
12008
|
+
* @returns {StoredDimValue|null}
|
|
12009
|
+
*/
|
|
12010
|
+
value(name) {
|
|
12011
|
+
return this.values.find(v => v.name === name) || null;
|
|
12012
|
+
}
|
|
12013
|
+
|
|
12014
|
+
/**
|
|
12015
|
+
* Hold the body to what dim.schema.json says beyond its field types: a
|
|
12016
|
+
* dim name, at least one value, value names unique, every value with a tag.
|
|
12017
|
+
* @throws {DimError}
|
|
12018
|
+
*/
|
|
12019
|
+
validate() {
|
|
12020
|
+
if (!isDimName(this.name)) {
|
|
12021
|
+
throw new DimError(DimErrorCodes.INVALID_NAME,
|
|
12022
|
+
`dim name '${this.name}' is not a dim name (lowercase letters, digits and '-', starting with a letter)`);
|
|
12023
|
+
}
|
|
12024
|
+
if (this.values.length === 0) {
|
|
12025
|
+
throw new DimError(DimErrorCodes.NO_VALUES, `dim '${this.name}' declares no values`);
|
|
12026
|
+
}
|
|
12027
|
+
const seen = new Set();
|
|
12028
|
+
for (const v of this.values) {
|
|
12029
|
+
if (seen.has(v.name)) {
|
|
12030
|
+
throw new DimError(DimErrorCodes.DUPLICATE_VALUE, `dim '${this.name}' declares value '${v.name}' more than once`);
|
|
12031
|
+
}
|
|
12032
|
+
seen.add(v.name);
|
|
12033
|
+
if (v.tags.length === 0) {
|
|
12034
|
+
throw new DimError(DimErrorCodes.VALUE_WITHOUT_TAGS, `dim '${this.name}' value '${v.name}' contributes no tag`);
|
|
12035
|
+
}
|
|
12036
|
+
}
|
|
12037
|
+
}
|
|
12038
|
+
|
|
12039
|
+
toJSON() {
|
|
12040
|
+
return {
|
|
12041
|
+
name: this.name,
|
|
12042
|
+
version: this.version,
|
|
12043
|
+
title: this.title,
|
|
12044
|
+
documentation: this.documentation,
|
|
12045
|
+
values: this.values.map(v => v.toJSON()),
|
|
12046
|
+
};
|
|
12047
|
+
}
|
|
12048
|
+
|
|
12049
|
+
/**
|
|
12050
|
+
* Read a dim body, refusing a field this library does not know.
|
|
12051
|
+
* @returns {StoredDim}
|
|
12052
|
+
*/
|
|
12053
|
+
static fromJSON(json) {
|
|
12054
|
+
if (!isPlainObject(json)) throw new Error(`a dim is an object, not ${JSON.stringify(json)}`);
|
|
12055
|
+
refuseUnknownDefinitionKeys(json, DIM_KEYS, 'dim');
|
|
12056
|
+
for (const key of ['name', 'title', 'documentation']) {
|
|
12057
|
+
if (typeof json[key] !== 'string') throw new Error(`dim is missing field \`${key}\``);
|
|
12058
|
+
}
|
|
12059
|
+
if (!Number.isInteger(json.version) || json.version < 0) {
|
|
12060
|
+
throw new Error(`dim '${json.name}' has no whole \`version\``);
|
|
12061
|
+
}
|
|
12062
|
+
if (!Array.isArray(json.values)) throw new Error(`dim '${json.name}' is missing field \`values\``);
|
|
12063
|
+
return new StoredDim(json.name, json.version, json.title, json.documentation,
|
|
12064
|
+
json.values.map(v => StoredDimValue.fromJSON(v)));
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
|
|
11622
12068
|
/**
|
|
11623
12069
|
* A versioned registry snapshot. Mirrors fabric/manifest.schema.json:
|
|
11624
|
-
* { version, previous, caps, media, aliases } where each map is
|
|
11625
|
-
* name/urn -> defver.
|
|
12070
|
+
* { version, previous, caps, media, aliases, dims } where each map is
|
|
12071
|
+
* name/urn -> defver. `dims` is required: every dim a cap or media def of
|
|
12072
|
+
* the snapshot names is in it, and a manifest without it is not one this
|
|
12073
|
+
* library reads.
|
|
11626
12074
|
*/
|
|
11627
12075
|
class Manifest {
|
|
11628
|
-
constructor(version, previous, caps = {}, media = {}, aliases = {}) {
|
|
12076
|
+
constructor(version, previous, caps = {}, media = {}, aliases = {}, dims = {}) {
|
|
11629
12077
|
this.version = version;
|
|
11630
12078
|
this.previous = previous;
|
|
11631
12079
|
this.caps = caps;
|
|
11632
12080
|
this.media = media;
|
|
11633
12081
|
this.aliases = aliases;
|
|
12082
|
+
this.dims = dims;
|
|
11634
12083
|
}
|
|
11635
12084
|
static empty(version) {
|
|
11636
|
-
return new Manifest(version, Math.max(0, version - 1), {}, {}, {});
|
|
12085
|
+
return new Manifest(version, Math.max(0, version - 1), {}, {}, {}, {});
|
|
11637
12086
|
}
|
|
11638
12087
|
toJSON() {
|
|
11639
12088
|
return {
|
|
@@ -11642,15 +12091,20 @@ class Manifest {
|
|
|
11642
12091
|
caps: this.caps,
|
|
11643
12092
|
media: this.media,
|
|
11644
12093
|
aliases: this.aliases,
|
|
12094
|
+
dims: this.dims,
|
|
11645
12095
|
};
|
|
11646
12096
|
}
|
|
11647
12097
|
static fromJSON(obj) {
|
|
12098
|
+
if (!isPlainObject(obj.dims)) {
|
|
12099
|
+
throw new Error(`manifest v${obj.version} has no 'dims' map`);
|
|
12100
|
+
}
|
|
11648
12101
|
return new Manifest(
|
|
11649
12102
|
obj.version,
|
|
11650
12103
|
obj.previous,
|
|
11651
12104
|
obj.caps || {},
|
|
11652
12105
|
obj.media || {},
|
|
11653
12106
|
obj.aliases || {},
|
|
12107
|
+
{ ...obj.dims },
|
|
11654
12108
|
);
|
|
11655
12109
|
}
|
|
11656
12110
|
}
|
|
@@ -11680,6 +12134,15 @@ export {
|
|
|
11680
12134
|
selectDisplayAlias,
|
|
11681
12135
|
StoredAlias,
|
|
11682
12136
|
Manifest,
|
|
12137
|
+
// Dims
|
|
12138
|
+
isDimName,
|
|
12139
|
+
checkDimRefs,
|
|
12140
|
+
DimRef,
|
|
12141
|
+
DimTag,
|
|
12142
|
+
StoredDimValue,
|
|
12143
|
+
StoredDim,
|
|
12144
|
+
DimError,
|
|
12145
|
+
DimErrorCodes,
|
|
11683
12146
|
CapUrn,
|
|
11684
12147
|
CapKind,
|
|
11685
12148
|
CapEffect,
|