@fgv/ts-res 5.0.0-30 → 5.0.0-31
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/ts-res.d.ts +317 -158
- package/lib/packlets/common/convert.d.ts +5 -0
- package/lib/packlets/common/convert.js +6 -1
- package/lib/packlets/common/helpers/context.js +1 -0
- package/lib/packlets/common/helpers/resources.js +1 -0
- package/lib/packlets/common/validate/conditions.d.ts +17 -1
- package/lib/packlets/common/validate/conditions.js +32 -6
- package/lib/packlets/common/validate/resources.js +1 -0
- package/lib/packlets/conditions/condition.js +1 -0
- package/lib/packlets/conditions/conditionSet.js +1 -0
- package/lib/packlets/import/fsItem.d.ts +2 -1
- package/lib/packlets/import/fsItem.js +2 -1
- package/lib/packlets/import/importManager.d.ts +2 -1
- package/lib/packlets/import/importers/pathImporter.d.ts +2 -1
- package/lib/packlets/import/importers/pathImporter.js +2 -1
- package/lib/packlets/qualifier-types/literalValueHierarchy.js +1 -0
- package/lib/packlets/qualifier-types/territoryQualifierType.js +3 -0
- package/lib/packlets/resources/candidateReducer.js +3 -0
- package/lib/packlets/resources/resourceBuilder.js +1 -0
- package/lib/packlets/resources/resourceManagerBuilder.js +3 -0
- package/lib/packlets/runtime/compiledResourceCollection.js +38 -29
- package/lib/packlets/runtime/conditionSetResolutionResult.js +3 -0
- package/lib/packlets/runtime/context/contextQualifierProvider.d.ts +53 -3
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.d.ts +82 -37
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.js +49 -77
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.d.ts +23 -2
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.js +53 -31
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.d.ts +4 -4
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.js +2 -2
- package/lib/packlets/runtime/resourceResolver.js +5 -0
- package/lib/packlets/zip-archive/types.d.ts +1 -1
- package/lib/packlets/zip-archive/zipArchiveCreator.js +3 -2
- package/package.json +7 -7
|
@@ -67,6 +67,7 @@ const Validate = __importStar(require("../validate"));
|
|
|
67
67
|
* @public
|
|
68
68
|
*/
|
|
69
69
|
function buildContextQualifierToken({ qualifier, value }) {
|
|
70
|
+
/* c8 ignore next 1 - edge case: ternary expression alternate branch rarely hit */
|
|
70
71
|
return Validate.toContextQualifierToken(qualifier ? `${qualifier}=${value}` : value);
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
@@ -36,6 +36,7 @@ const resources_1 = require("../validate/resources");
|
|
|
36
36
|
* @public
|
|
37
37
|
*/
|
|
38
38
|
function splitResourceId(id) {
|
|
39
|
+
/* c8 ignore next 3 - edge case: undefined resource ID should not occur in normal operation */
|
|
39
40
|
if (id === undefined) {
|
|
40
41
|
return (0, ts_utils_1.succeed)([]);
|
|
41
42
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import { ConditionPriority, QualifierName, QualifierTypeName, QualifierIndex, QualifierTypeIndex, ConditionIndex, ConditionSetIndex, QualifierMatchScore, ConditionKey, ConditionSetKey, DecisionKey, DecisionIndex, ConditionSetHash, ConditionOperator, ConditionToken, ConditionSetToken, ContextQualifierToken, ContextToken, QualifierDefaultValueToken, QualifierDefaultValuesToken } from '../conditions';
|
|
2
|
+
import { ConditionPriority, QualifierName, QualifierTypeName, QualifierIndex, QualifierTypeIndex, ConditionIndex, ConditionSetIndex, QualifierMatchScore, QualifierContextValue, ConditionKey, ConditionSetKey, DecisionKey, DecisionIndex, ConditionSetHash, ConditionOperator, ConditionToken, ConditionSetToken, ContextQualifierToken, ContextToken, QualifierDefaultValueToken, QualifierDefaultValuesToken } from '../conditions';
|
|
3
3
|
/**
|
|
4
4
|
* Determines whether a string is a valid qualifier name.
|
|
5
5
|
* @param name - the string to validate
|
|
@@ -117,6 +117,14 @@ export declare function isValidDecisionKey(key: string): key is DecisionKey;
|
|
|
117
117
|
* @public
|
|
118
118
|
*/
|
|
119
119
|
export declare function isValidDecisionIndex(index: number): index is DecisionIndex;
|
|
120
|
+
/**
|
|
121
|
+
* Determines whether a string is a valid qualifier context value.
|
|
122
|
+
* For now, any non-empty string is considered a valid context value.
|
|
123
|
+
* @param value - the string to validate
|
|
124
|
+
* @returns true if the string is a valid qualifier context value, false otherwise.
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
export declare function isValidQualifierContextValue(value: string): value is QualifierContextValue;
|
|
120
128
|
/**
|
|
121
129
|
* Converts a string to a {@link QualifierName} if it is a valid qualifier name.
|
|
122
130
|
* @param name - the string to convert
|
|
@@ -125,6 +133,14 @@ export declare function isValidDecisionIndex(index: number): index is DecisionIn
|
|
|
125
133
|
* @public
|
|
126
134
|
*/
|
|
127
135
|
export declare function toQualifierName(name: string): Result<QualifierName>;
|
|
136
|
+
/**
|
|
137
|
+
* Converts a string to a {@link QualifierContextValue} if it is a valid qualifier context value.
|
|
138
|
+
* @param value - the string to convert
|
|
139
|
+
* @returns `Success` with the converted {@link QualifierContextValue} if successful, or `Failure` with an
|
|
140
|
+
* error message if not.
|
|
141
|
+
* @public
|
|
142
|
+
*/
|
|
143
|
+
export declare function toQualifierContextValue(value: string): Result<QualifierContextValue>;
|
|
128
144
|
/**
|
|
129
145
|
* Converts a number to a {@link QualifierIndex} if it is a valid qualifier index.
|
|
130
146
|
* @param index - the number to convert
|
|
@@ -37,7 +37,9 @@ exports.isValidConditionSetToken = isValidConditionSetToken;
|
|
|
37
37
|
exports.isValidConditionSetHash = isValidConditionSetHash;
|
|
38
38
|
exports.isValidDecisionKey = isValidDecisionKey;
|
|
39
39
|
exports.isValidDecisionIndex = isValidDecisionIndex;
|
|
40
|
+
exports.isValidQualifierContextValue = isValidQualifierContextValue;
|
|
40
41
|
exports.toQualifierName = toQualifierName;
|
|
42
|
+
exports.toQualifierContextValue = toQualifierContextValue;
|
|
41
43
|
exports.toQualifierIndex = toQualifierIndex;
|
|
42
44
|
exports.toQualifierTypeName = toQualifierTypeName;
|
|
43
45
|
exports.toQualifierTypeIndex = toQualifierTypeIndex;
|
|
@@ -182,6 +184,7 @@ function isValidConditionSetKey(key) {
|
|
|
182
184
|
if (key === '') {
|
|
183
185
|
return true;
|
|
184
186
|
}
|
|
187
|
+
/* c8 ignore next 2 - edge case: condition set key validation rarely fails */
|
|
185
188
|
// a condition set key is a `+` separated list of condition keys
|
|
186
189
|
return key.split('+').every(isValidConditionKey);
|
|
187
190
|
}
|
|
@@ -224,6 +227,16 @@ function isValidDecisionKey(key) {
|
|
|
224
227
|
function isValidDecisionIndex(index) {
|
|
225
228
|
return index >= 0;
|
|
226
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Determines whether a string is a valid qualifier context value.
|
|
232
|
+
* For now, any non-empty string is considered a valid context value.
|
|
233
|
+
* @param value - the string to validate
|
|
234
|
+
* @returns true if the string is a valid qualifier context value, false otherwise.
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
237
|
+
function isValidQualifierContextValue(value) {
|
|
238
|
+
return typeof value === 'string' && value.length > 0;
|
|
239
|
+
}
|
|
227
240
|
/**
|
|
228
241
|
* Converts a string to a {@link QualifierName} if it is a valid qualifier name.
|
|
229
242
|
* @param name - the string to convert
|
|
@@ -234,10 +247,23 @@ function isValidDecisionIndex(index) {
|
|
|
234
247
|
function toQualifierName(name) {
|
|
235
248
|
/* c8 ignore next 3 - functional code path tested but coverage intermittently missed */
|
|
236
249
|
if (!isValidQualifierName(name)) {
|
|
237
|
-
return (0, ts_utils_1.fail)(`${name}:
|
|
250
|
+
return (0, ts_utils_1.fail)(`${name}: invalid qualifier name`);
|
|
238
251
|
}
|
|
239
252
|
return (0, ts_utils_1.succeed)(name);
|
|
240
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Converts a string to a {@link QualifierContextValue} if it is a valid qualifier context value.
|
|
256
|
+
* @param value - the string to convert
|
|
257
|
+
* @returns `Success` with the converted {@link QualifierContextValue} if successful, or `Failure` with an
|
|
258
|
+
* error message if not.
|
|
259
|
+
* @public
|
|
260
|
+
*/
|
|
261
|
+
function toQualifierContextValue(value) {
|
|
262
|
+
if (!isValidQualifierContextValue(value)) {
|
|
263
|
+
return (0, ts_utils_1.fail)(`${value}: invalid qualifier context value`);
|
|
264
|
+
}
|
|
265
|
+
return (0, ts_utils_1.succeed)(value);
|
|
266
|
+
}
|
|
241
267
|
/**
|
|
242
268
|
* Converts a number to a {@link QualifierIndex} if it is a valid qualifier index.
|
|
243
269
|
* @param index - the number to convert
|
|
@@ -248,7 +274,7 @@ function toQualifierName(name) {
|
|
|
248
274
|
function toQualifierIndex(index) {
|
|
249
275
|
/* c8 ignore next 3 - coverage is having a bad day */
|
|
250
276
|
if (!isValidQualifierIndex(index)) {
|
|
251
|
-
return (0, ts_utils_1.fail)(`${index}:
|
|
277
|
+
return (0, ts_utils_1.fail)(`${index}: invalid qualifier index`);
|
|
252
278
|
}
|
|
253
279
|
return (0, ts_utils_1.succeed)(index);
|
|
254
280
|
}
|
|
@@ -262,7 +288,7 @@ function toQualifierIndex(index) {
|
|
|
262
288
|
function toQualifierTypeName(name) {
|
|
263
289
|
/* c8 ignore next 3 - functional code path tested but coverage intermittently missed */
|
|
264
290
|
if (!isValidQualifierTypeName(name)) {
|
|
265
|
-
return (0, ts_utils_1.fail)(`${name}:
|
|
291
|
+
return (0, ts_utils_1.fail)(`${name}: invalid qualifier type name`);
|
|
266
292
|
}
|
|
267
293
|
return (0, ts_utils_1.succeed)(name);
|
|
268
294
|
}
|
|
@@ -276,7 +302,7 @@ function toQualifierTypeName(name) {
|
|
|
276
302
|
function toQualifierTypeIndex(index) {
|
|
277
303
|
/* c8 ignore next 3 - functional code path tested but coverage intermittently missed */
|
|
278
304
|
if (!isValidQualifierTypeIndex(index)) {
|
|
279
|
-
return (0, ts_utils_1.fail)(`${index}:
|
|
305
|
+
return (0, ts_utils_1.fail)(`${index}: invalid qualifier type index`);
|
|
280
306
|
}
|
|
281
307
|
return (0, ts_utils_1.succeed)(index);
|
|
282
308
|
}
|
|
@@ -541,7 +567,7 @@ function isValidQualifierDefaultValuesToken(token) {
|
|
|
541
567
|
function toQualifierDefaultValueToken(token) {
|
|
542
568
|
/* c8 ignore next 3 - functional code path tested but coverage intermittently missed */
|
|
543
569
|
if (!isValidQualifierDefaultValueToken(token)) {
|
|
544
|
-
return (0, ts_utils_1.fail)(`${token}:
|
|
570
|
+
return (0, ts_utils_1.fail)(`${token}: invalid qualifier default value token`);
|
|
545
571
|
}
|
|
546
572
|
return (0, ts_utils_1.succeed)(token);
|
|
547
573
|
}
|
|
@@ -555,7 +581,7 @@ function toQualifierDefaultValueToken(token) {
|
|
|
555
581
|
function toQualifierDefaultValuesToken(token) {
|
|
556
582
|
/* c8 ignore next 3 - functional code path tested but coverage intermittently missed */
|
|
557
583
|
if (!isValidQualifierDefaultValuesToken(token)) {
|
|
558
|
-
return (0, ts_utils_1.fail)(`${token}:
|
|
584
|
+
return (0, ts_utils_1.fail)(`${token}: invalid qualifier default values token`);
|
|
559
585
|
}
|
|
560
586
|
return (0, ts_utils_1.succeed)(token);
|
|
561
587
|
}
|
|
@@ -132,6 +132,7 @@ function toResourceName(name) {
|
|
|
132
132
|
* @public
|
|
133
133
|
*/
|
|
134
134
|
function toResourceId(id) {
|
|
135
|
+
/* c8 ignore next 3 - defensive coding: resource ID validation should prevent invalid IDs */
|
|
135
136
|
if (!isValidResourceId(id)) {
|
|
136
137
|
return (0, ts_utils_1.fail)(`${id}: not a valid resource ID.`);
|
|
137
138
|
}
|
|
@@ -43,6 +43,7 @@ class Condition {
|
|
|
43
43
|
*/
|
|
44
44
|
constructor({ qualifier, value, operator, priority, scoreAsDefault, index }) {
|
|
45
45
|
if (scoreAsDefault === undefined && qualifier.defaultValue !== undefined) {
|
|
46
|
+
/* c8 ignore next 4 - edge case: default value matching logic rarely triggered */
|
|
46
47
|
const defaultMatch = qualifier.type.matches(value, qualifier.defaultValue, 'matches');
|
|
47
48
|
if (defaultMatch !== common_1.NoMatch) {
|
|
48
49
|
scoreAsDefault = defaultMatch;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DetailedResult,
|
|
1
|
+
import { DetailedResult, Result } from '@fgv/ts-utils';
|
|
2
|
+
import { FileTree } from '@fgv/ts-json-base';
|
|
2
3
|
import * as Conditions from '../conditions';
|
|
3
4
|
import { IReadOnlyQualifierCollector } from '../qualifiers';
|
|
4
5
|
import { ImportContext } from './importContext';
|
|
@@ -56,6 +56,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
57
|
exports.FsItem = void 0;
|
|
58
58
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
59
|
+
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
59
60
|
const common_1 = require("../common");
|
|
60
61
|
const Conditions = __importStar(require("../conditions"));
|
|
61
62
|
const importContext_1 = require("./importContext");
|
|
@@ -110,7 +111,7 @@ class FsItem {
|
|
|
110
111
|
* that should be silently ignored.
|
|
111
112
|
*/
|
|
112
113
|
static createForPath(importPath, qualifiers, tree) {
|
|
113
|
-
return (0, ts_utils_1.useOrInitialize)(tree, () =>
|
|
114
|
+
return (0, ts_utils_1.useOrInitialize)(tree, () => ts_json_base_1.FileTree.forFilesystem())
|
|
114
115
|
.withDetail('failed', 'succeeded')
|
|
115
116
|
.onSuccess((tree) => {
|
|
116
117
|
return tree
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { FileTree } from '@fgv/ts-json-base';
|
|
2
3
|
import { ResourceManagerBuilder } from '../resources';
|
|
3
4
|
import { ImportContext } from './importContext';
|
|
4
5
|
import { IImportable } from './importable';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DetailedResult, Result
|
|
1
|
+
import { DetailedResult, Result } from '@fgv/ts-utils';
|
|
2
|
+
import { FileTree } from '@fgv/ts-json-base';
|
|
2
3
|
import { ResourceManagerBuilder } from '../../resources';
|
|
3
4
|
import { IImportable } from '../importable';
|
|
4
5
|
import { IImporter, ImporterResultDetail } from './importer';
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.PathImporter = void 0;
|
|
25
25
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
26
|
+
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
26
27
|
const fsItem_1 = require("../fsItem");
|
|
27
28
|
/**
|
|
28
29
|
* {@link Import.Importers.IImporter | Importer} implementation which imports resources from a `FileTree`
|
|
@@ -41,7 +42,7 @@ class PathImporter {
|
|
|
41
42
|
*/
|
|
42
43
|
this.types = ['path'];
|
|
43
44
|
this.qualifiers = params.qualifiers;
|
|
44
|
-
this.tree = (_a = params.tree) !== null && _a !== void 0 ? _a :
|
|
45
|
+
this.tree = (_a = params.tree) !== null && _a !== void 0 ? _a : ts_json_base_1.FileTree.forFilesystem().orThrow();
|
|
45
46
|
this.ignoreFileTypes = (_b = params.ignoreFileTypes) !== null && _b !== void 0 ? _b : [];
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
@@ -106,6 +106,7 @@ class TerritoryQualifierType extends qualifierType_1.QualifierType {
|
|
|
106
106
|
*/
|
|
107
107
|
isValidConditionValue(value) {
|
|
108
108
|
const normalized = this.acceptLowercase ? value.toUpperCase() : value;
|
|
109
|
+
/* c8 ignore next 6 - edge case: invalid territory values and allowed territory filtering rarely hit */
|
|
109
110
|
if (!TerritoryQualifierType.isValidTerritoryConditionValue(normalized)) {
|
|
110
111
|
return false;
|
|
111
112
|
}
|
|
@@ -194,6 +195,7 @@ class TerritoryQualifierType extends qualifierType_1.QualifierType {
|
|
|
194
195
|
if (normalizedCondition === normalizedContext) {
|
|
195
196
|
return common_1.PerfectMatch;
|
|
196
197
|
}
|
|
198
|
+
/* c8 ignore next 8 - edge case: hierarchy matching and fallback logic rarely used */
|
|
197
199
|
if (this.hierarchy) {
|
|
198
200
|
return this.hierarchy.match(normalizedCondition, normalizedContext, __operator);
|
|
199
201
|
}
|
|
@@ -209,6 +211,7 @@ class TerritoryQualifierType extends qualifierType_1.QualifierType {
|
|
|
209
211
|
* @public
|
|
210
212
|
*/
|
|
211
213
|
static isValidTerritoryConditionValue(value, acceptLowercase) {
|
|
214
|
+
/* c8 ignore next 3 - edge case: lowercase validation rarely fails */
|
|
212
215
|
if (acceptLowercase !== true && value !== value.toUpperCase()) {
|
|
213
216
|
return false;
|
|
214
217
|
}
|
|
@@ -132,6 +132,7 @@ class CandidateReducer {
|
|
|
132
132
|
hasCollisions = true;
|
|
133
133
|
}
|
|
134
134
|
else {
|
|
135
|
+
/* c8 ignore next 12 - complex collision handling for partial candidates rarely triggered */
|
|
135
136
|
// Partial reduced candidate - merge into unchanged and suppress reduced
|
|
136
137
|
const mergedJson = CandidateReducer._mergeJsonValues(unchangedCandidate.originalCandidate.json, reducedCandidate.originalCandidate.json);
|
|
137
138
|
// Update unchanged candidate with merged JSON, keeping its isPartial status
|
|
@@ -142,6 +143,7 @@ class CandidateReducer {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
else {
|
|
146
|
+
/* c8 ignore next 15 - complex collision reversion logic rarely triggered in practice */
|
|
145
147
|
// Complex collision case - revert all reduced candidates to unchanged
|
|
146
148
|
for (const candidateInfo of reducedCandidates) {
|
|
147
149
|
// Revert to original conditions and mark as unchanged
|
|
@@ -224,6 +226,7 @@ class CandidateReducer {
|
|
|
224
226
|
reduceCandidate(candidate) {
|
|
225
227
|
var _a;
|
|
226
228
|
const candidateInfo = this._candidateInfos.find((info) => info.originalCandidate === candidate);
|
|
229
|
+
/* c8 ignore next 3 - defensive coding: candidate should always be found if properly managed */
|
|
227
230
|
if (!candidateInfo) {
|
|
228
231
|
return (0, ts_utils_1.fail)(`Candidate not found in reducer state`);
|
|
229
232
|
}
|
|
@@ -103,6 +103,7 @@ class ResourceBuilder {
|
|
|
103
103
|
.getOrAdd(candidate.conditions.toString(), candidate)
|
|
104
104
|
.onSuccess((added, detail) => {
|
|
105
105
|
if (detail === 'exists') {
|
|
106
|
+
/* c8 ignore next 5 - defensive coding: conflicting candidates with same conditions should not occur */
|
|
106
107
|
if (!resourceCandidate_1.ResourceCandidate.equal(added, candidate)) {
|
|
107
108
|
return (0, ts_utils_1.failWithDetail)(`${this.id}: conflicting candidates.`, 'exists');
|
|
108
109
|
}
|
|
@@ -255,6 +255,7 @@ class ResourceManagerBuilder {
|
|
|
255
255
|
addResource(decl) {
|
|
256
256
|
var _a;
|
|
257
257
|
const { value: id, message } = common_1.Validate.toResourceId(decl.id);
|
|
258
|
+
/* c8 ignore next 3 - defensive coding: resource ID validation should prevent invalid IDs */
|
|
258
259
|
if (message !== undefined) {
|
|
259
260
|
return (0, ts_utils_1.failWithDetail)(`${id}: invalid id - ${message}`, 'failure');
|
|
260
261
|
}
|
|
@@ -271,6 +272,7 @@ class ResourceManagerBuilder {
|
|
|
271
272
|
return (0, ts_utils_1.failWithDetail)(`${id}: unable to get or add resource\n${getOrAddMessage}`, detail);
|
|
272
273
|
}
|
|
273
274
|
if (detail === 'exists') {
|
|
275
|
+
/* c8 ignore next 4 - defensive coding: resource type mismatch on existing resources should not occur */
|
|
274
276
|
const { message } = builder.setResourceType(decl.resourceTypeName);
|
|
275
277
|
if (message !== undefined) {
|
|
276
278
|
return (0, ts_utils_1.failWithDetail)(`${id}: unable to set resource type\n${message}`, 'type-mismatch');
|
|
@@ -479,6 +481,7 @@ class ResourceManagerBuilder {
|
|
|
479
481
|
getCompiledResourceCollection(options) {
|
|
480
482
|
// Build resources first to ensure all data is available
|
|
481
483
|
const buildResult = this._performBuild();
|
|
484
|
+
/* c8 ignore next 3 - defensive coding: build failure should not occur after successful validation */
|
|
482
485
|
if (buildResult.isFailure()) {
|
|
483
486
|
return (0, ts_utils_1.fail)(`Failed to build resources: ${buildResult.message}`);
|
|
484
487
|
}
|
|
@@ -264,6 +264,7 @@ class CompiledResourceCollection {
|
|
|
264
264
|
const conditionCollector = conditionCollectorResult.value;
|
|
265
265
|
for (const [index, compiledCondition] of compiled.conditions.entries()) {
|
|
266
266
|
const qualifierResult = qualifiers.getAt(compiledCondition.qualifierIndex);
|
|
267
|
+
/* c8 ignore next 5 - defensive coding: invalid qualifier index should not occur in compiled data */
|
|
267
268
|
if (qualifierResult.isFailure()) {
|
|
268
269
|
errors.addMessage(`Invalid qualifier index ${compiledCondition.qualifierIndex} at condition ${index}: ${qualifierResult.message}`);
|
|
269
270
|
continue;
|
|
@@ -361,6 +362,7 @@ class CompiledResourceCollection {
|
|
|
361
362
|
const decisionCollector = decisionCollectorResult.value;
|
|
362
363
|
for (const [index, compiledDecision] of compiled.decisions.entries()) {
|
|
363
364
|
const referencedConditionSetsResult = (0, ts_utils_1.mapResults)(compiledDecision.conditionSets.map((conditionSetIndex) => conditionSets.getAt(conditionSetIndex)));
|
|
365
|
+
/* c8 ignore next 5 - defensive coding: condition set resolution should not fail with valid compiled data */
|
|
364
366
|
if (referencedConditionSetsResult.isFailure()) {
|
|
365
367
|
errors.addMessage(`Failed to resolve condition sets for decision ${index}: ${referencedConditionSetsResult.message}`);
|
|
366
368
|
continue;
|
|
@@ -394,6 +396,7 @@ class CompiledResourceCollection {
|
|
|
394
396
|
* @returns
|
|
395
397
|
*/
|
|
396
398
|
_getCandidateValue(valueIndex) {
|
|
399
|
+
/* c8 ignore next 3 - defensive coding: candidate value index should be valid in compiled data */
|
|
397
400
|
if (valueIndex < 0 || valueIndex >= this._candidateValues.length) {
|
|
398
401
|
return (0, ts_utils_1.fail)(`Invalid candidate value index ${valueIndex}`);
|
|
399
402
|
}
|
|
@@ -422,31 +425,38 @@ class CompiledResourceCollection {
|
|
|
422
425
|
})
|
|
423
426
|
});
|
|
424
427
|
for (const compiledResource of compiled.resources) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
+
const resourceType = resourceTypes
|
|
429
|
+
.getAt(compiledResource.type)
|
|
430
|
+
.aggregateError(errors, (message) => `Invalid resource type index ${compiledResource.type} for resource ${compiledResource.id}: ${message}`)
|
|
431
|
+
.orDefault();
|
|
432
|
+
/* c8 ignore next 3 - defensive coding: resource type index should be valid in compiled data */
|
|
433
|
+
if (resourceType === undefined) {
|
|
428
434
|
continue;
|
|
429
435
|
}
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
436
|
+
const decision = decisions
|
|
437
|
+
.getAt(compiledResource.decision)
|
|
438
|
+
.aggregateError(errors, (message) => `Invalid decision index ${compiledResource.decision} for resource ${compiledResource.id}: ${message}`)
|
|
439
|
+
.orDefault();
|
|
440
|
+
/* c8 ignore next 3 - defensive coding: decision index should be valid in compiled data */
|
|
441
|
+
if (decision === undefined) {
|
|
434
442
|
continue;
|
|
435
443
|
}
|
|
436
|
-
const decision = decisionResult.value;
|
|
437
444
|
// Build candidates from compiled data
|
|
438
|
-
const
|
|
445
|
+
const candidateDecls = (0, ts_utils_1.mapResults)(compiledResource.candidates.map((compiledCandidate) => this._getCandidateValue(compiledCandidate.valueIndex)
|
|
446
|
+
.aggregateError(errors, (message) => `Failed to convert candidate JSON for resource ${compiledResource.id}: ${message}`)
|
|
447
|
+
.onSuccess((json) => {
|
|
439
448
|
return (0, ts_utils_1.succeed)({
|
|
440
449
|
json,
|
|
441
450
|
isPartial: compiledCandidate.isPartial,
|
|
442
451
|
mergeMethod: compiledCandidate.mergeMethod
|
|
443
452
|
});
|
|
444
|
-
})))
|
|
445
|
-
|
|
446
|
-
|
|
453
|
+
})))
|
|
454
|
+
.aggregateError(errors, (message) => `Failed to convert candidate JSON for resource ${compiledResource.id}: ${message}`)
|
|
455
|
+
.orDefault();
|
|
456
|
+
/* c8 ignore next 3 - defensive coding: candidate JSON conversion should not fail with valid compiled data */
|
|
457
|
+
if (candidateDecls === undefined) {
|
|
447
458
|
continue;
|
|
448
459
|
}
|
|
449
|
-
const candidateDecls = candidateDeclsResult.value;
|
|
450
460
|
// Create minimal candidates that implement IResourceCandidate
|
|
451
461
|
const candidates = candidateDecls.map((candidateDecl) => ({
|
|
452
462
|
json: candidateDecl.json,
|
|
@@ -457,20 +467,21 @@ class CompiledResourceCollection {
|
|
|
457
467
|
conditionSet: baseCandidate.conditionSet,
|
|
458
468
|
value: candidates[idx].json
|
|
459
469
|
}));
|
|
460
|
-
const
|
|
470
|
+
const concreteDecision = decisions_2.ConcreteDecision.create({
|
|
461
471
|
decisions,
|
|
462
472
|
candidates: candidatesWithConditionSets
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
473
|
+
})
|
|
474
|
+
.aggregateError(errors, (message) => `Failed to create concrete decision for resource ${compiledResource.id}: ${message}`)
|
|
475
|
+
.orDefault();
|
|
476
|
+
/* c8 ignore next 3 - defensive coding for ConcreteDecision creation failure */
|
|
477
|
+
if (concreteDecision === undefined) {
|
|
467
478
|
continue;
|
|
468
479
|
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
480
|
+
const name = common_1.Helpers.getNameForResourceId(compiledResource.id)
|
|
481
|
+
.aggregateError(errors, (message) => `Failed to get name for resource ${compiledResource.id}: ${message}`)
|
|
482
|
+
.orDefault();
|
|
483
|
+
/* c8 ignore next 3 - defensive coding: name should be valid in compiled data */
|
|
484
|
+
if (name === undefined) {
|
|
474
485
|
continue;
|
|
475
486
|
}
|
|
476
487
|
// Create minimal resource that implements IResource
|
|
@@ -481,13 +492,11 @@ class CompiledResourceCollection {
|
|
|
481
492
|
decision: concreteDecision,
|
|
482
493
|
candidates
|
|
483
494
|
};
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
errors.addMessage(`Failed to add resource ${compiledResource.id}: ${setResult.message}`);
|
|
488
|
-
}
|
|
495
|
+
resourceMap
|
|
496
|
+
.set(compiledResource.id, resource)
|
|
497
|
+
.aggregateError(errors, (message) => `Failed to add resource ${compiledResource.id}: ${message}`);
|
|
489
498
|
}
|
|
490
|
-
return errors.
|
|
499
|
+
return errors.returnOrReport((0, ts_utils_1.succeed)(resourceMap));
|
|
491
500
|
}
|
|
492
501
|
}
|
|
493
502
|
exports.CompiledResourceCollection = CompiledResourceCollection;
|
|
@@ -72,6 +72,7 @@ class ConditionSetResolutionResult {
|
|
|
72
72
|
return -1;
|
|
73
73
|
}
|
|
74
74
|
else if (a.matchType === 'matchAsDefault') {
|
|
75
|
+
/* c8 ignore next 5 - edge case comparison for matchAsDefault rarely hit in practice */
|
|
75
76
|
if (b.matchType === 'match') {
|
|
76
77
|
return 1;
|
|
77
78
|
}
|
|
@@ -80,6 +81,7 @@ class ConditionSetResolutionResult {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
else {
|
|
84
|
+
/* c8 ignore next 7 - edge case comparison for noMatch rarely hit in practice */
|
|
83
85
|
// a.matchType === 'noMatch'
|
|
84
86
|
if (b.matchType === 'noMatch') {
|
|
85
87
|
return 0;
|
|
@@ -100,6 +102,7 @@ class ConditionSetResolutionResult {
|
|
|
100
102
|
}
|
|
101
103
|
// If priority matches, compare score
|
|
102
104
|
const scoreDiff = matchB.score - matchA.score;
|
|
105
|
+
/* c8 ignore next 3 - edge case: score comparison when priorities are equal */
|
|
103
106
|
if (scoreDiff !== 0) {
|
|
104
107
|
return scoreDiff;
|
|
105
108
|
}
|
|
@@ -2,11 +2,12 @@ import { Result } from '@fgv/ts-utils';
|
|
|
2
2
|
import { QualifierName, QualifierContextValue, QualifierIndex } from '../../common';
|
|
3
3
|
import { IReadOnlyQualifierCollector, Qualifier } from '../../qualifiers';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Base interface for providing qualifier values in an optimized runtime context.
|
|
6
|
+
* Contains common read-only operations shared by both mutable and immutable providers.
|
|
6
7
|
* Acts as a property bag using the Result pattern for qualifier value lookups.
|
|
7
8
|
* @public
|
|
8
9
|
*/
|
|
9
|
-
export interface
|
|
10
|
+
export interface IContextQualifierProviderBase {
|
|
10
11
|
/**
|
|
11
12
|
* Gets a qualifier value by its name, index, or qualifier object.
|
|
12
13
|
* @param nameOrIndexOrQualifier - The {@link QualifierName | qualifier name}, {@link QualifierIndex | index}, or {@link Qualifiers.Qualifier | qualifier object} to look up.
|
|
@@ -39,12 +40,61 @@ export interface IContextQualifierProvider {
|
|
|
39
40
|
*/
|
|
40
41
|
readonly qualifiers: IReadOnlyQualifierCollector;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Read-only interface for providing qualifier values in an optimized runtime context.
|
|
45
|
+
* Explicitly marked as immutable with compile-time type discrimination.
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export interface IReadOnlyContextQualifierProvider extends IContextQualifierProviderBase {
|
|
49
|
+
/**
|
|
50
|
+
* Explicit mutability marker for compile-time type discrimination.
|
|
51
|
+
* Always `false` for read-only providers.
|
|
52
|
+
*/
|
|
53
|
+
readonly mutable: false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mutable interface for providing qualifier values in an optimized runtime context.
|
|
57
|
+
* Extends the base interface with mutation operations and explicit mutability marker.
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export interface IMutableContextQualifierProvider extends IContextQualifierProviderBase {
|
|
61
|
+
/**
|
|
62
|
+
* Explicit mutability marker for compile-time type discrimination.
|
|
63
|
+
* Always `true` for mutable providers.
|
|
64
|
+
*/
|
|
65
|
+
readonly mutable: true;
|
|
66
|
+
/**
|
|
67
|
+
* Sets a qualifier value in this provider.
|
|
68
|
+
* @param name - The {@link QualifierName | qualifier name} to set.
|
|
69
|
+
* @param value - The {@link QualifierContextValue | qualifier context value} to set.
|
|
70
|
+
* @returns `Success` with the set {@link QualifierContextValue | qualifier context value} if successful,
|
|
71
|
+
* or `Failure` with an error message if not.
|
|
72
|
+
*/
|
|
73
|
+
set(name: QualifierName, value: QualifierContextValue): Result<QualifierContextValue>;
|
|
74
|
+
/**
|
|
75
|
+
* Removes a qualifier value from this provider.
|
|
76
|
+
* @param name - The {@link QualifierName | qualifier name} to remove.
|
|
77
|
+
* @returns `Success` with the removed {@link QualifierContextValue | qualifier context value} if successful,
|
|
78
|
+
* or `Failure` with an error message if not found or an error occurs.
|
|
79
|
+
*/
|
|
80
|
+
remove(name: QualifierName): Result<QualifierContextValue>;
|
|
81
|
+
/**
|
|
82
|
+
* Clears all qualifier values from this provider.
|
|
83
|
+
*/
|
|
84
|
+
clear(): void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Union type for context qualifier providers that can be either read-only or mutable.
|
|
88
|
+
* Provides compile-time type discrimination via the `mutable` property.
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
export type IContextQualifierProvider = IReadOnlyContextQualifierProvider | IMutableContextQualifierProvider;
|
|
42
92
|
/**
|
|
43
93
|
* Abstract base class for implementing context qualifier providers.
|
|
44
94
|
* Provides common functionality and enforces the contract for derived classes.
|
|
45
95
|
* @public
|
|
46
96
|
*/
|
|
47
|
-
export declare abstract class ContextQualifierProvider implements
|
|
97
|
+
export declare abstract class ContextQualifierProvider implements IContextQualifierProviderBase {
|
|
48
98
|
/**
|
|
49
99
|
* The readonly qualifier collector that defines and validates the qualifiers for this context.
|
|
50
100
|
*/
|