@fgv/ts-res 5.0.0-21 → 5.0.0-22
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 +156 -120
- package/dist/ts-res.d.ts +85 -14
- package/lib/packlets/common/resources.d.ts +26 -1
- package/lib/packlets/resource-types/config/convert.js +3 -1
- package/lib/packlets/resource-types/config/json.d.ts +2 -0
- package/lib/packlets/resource-types/jsonResourceType.d.ts +6 -6
- package/lib/packlets/resource-types/jsonResourceType.js +3 -10
- package/lib/packlets/resource-types/resourceType.d.ts +16 -8
- package/lib/packlets/resource-types/resourceType.js +20 -16
- package/lib/packlets/runtime/resourceResolver.d.ts +39 -1
- package/lib/packlets/runtime/resourceResolver.js +45 -28
- package/lib/test/unit/resource-types/derivedResourceType.test.d.ts +2 -0
- package/lib/test/unit/resource-types/testDerivedResourceType.d.ts +78 -0
- package/lib/test/unit/resource-types/testDerivedResourceType.js +110 -0
- package/lib/test/unit/runtime/resourceResolver.coverage.test.d.ts +2 -0
- package/lib/test/unit/runtime/resourceResolver.newFeatures.test.d.ts +2 -0
- package/package.json +7 -7
- package/src/packlets/common/resources.ts +28 -1
- package/src/packlets/resource-types/config/convert.ts +3 -1
- package/src/packlets/resource-types/config/json.ts +3 -0
- package/src/packlets/resource-types/jsonResourceType.ts +9 -11
- package/src/packlets/resource-types/resourceType.ts +57 -24
- package/src/packlets/runtime/resourceResolver.ts +92 -6
|
@@ -37,8 +37,8 @@ class JsonResourceType extends resourceType_1.ResourceType {
|
|
|
37
37
|
* @param key - The key for the new {@link ResourceTypes.JsonResourceType | JsonResourceType} instance.
|
|
38
38
|
* @param index - Optional index for the new {@link ResourceTypes.JsonResourceType | JsonResourceType} instance.
|
|
39
39
|
*/
|
|
40
|
-
constructor(key, index) {
|
|
41
|
-
super(key, index);
|
|
40
|
+
constructor(key, index, template) {
|
|
41
|
+
super(key, index, template);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Factory method to create a new {@link ResourceTypes.JsonResourceType | JsonResourceType} instance.
|
|
@@ -49,7 +49,7 @@ class JsonResourceType extends resourceType_1.ResourceType {
|
|
|
49
49
|
static create(params) {
|
|
50
50
|
var _a;
|
|
51
51
|
return common_1.Convert.resourceTypeName.convert((_a = params === null || params === void 0 ? void 0 : params.key) !== null && _a !== void 0 ? _a : 'json').onSuccess((key) => {
|
|
52
|
-
return (0, ts_utils_1.captureResult)(() => new JsonResourceType(key, params === null || params === void 0 ? void 0 : params.index));
|
|
52
|
+
return (0, ts_utils_1.captureResult)(() => new JsonResourceType(key, params === null || params === void 0 ? void 0 : params.index, params === null || params === void 0 ? void 0 : params.template));
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
@@ -61,13 +61,6 @@ class JsonResourceType extends resourceType_1.ResourceType {
|
|
|
61
61
|
validate(json, __completeness) {
|
|
62
62
|
return ts_json_base_1.Converters.jsonObject.convert(json);
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Gets the default template value for a JSON resource type.
|
|
66
|
-
* @returns An empty object as the default JSON value
|
|
67
|
-
*/
|
|
68
|
-
getDefaultTemplateValue() {
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
64
|
}
|
|
72
65
|
exports.JsonResourceType = JsonResourceType;
|
|
73
66
|
//# sourceMappingURL=jsonResourceType.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JsonObject, JsonValue } from '@fgv/ts-json-base';
|
|
2
2
|
import { ICollectible, Result } from '@fgv/ts-utils';
|
|
3
|
-
import { CandidateCompleteness, ResourceTypeIndex, ResourceTypeName, ResourceValueMergeMethod, ResourceId } from '../common';
|
|
3
|
+
import { CandidateCompleteness, ResourceTypeIndex, ResourceTypeName, ResourceValueMergeMethod, ResourceId, IResourceResolver } from '../common';
|
|
4
4
|
import * as ResourceJson from '../resource-json';
|
|
5
5
|
/**
|
|
6
6
|
* Parameters used to validate a {@link ResourceJson.Json.ILooseResourceCandidateDecl | resource candidate declaration}.
|
|
@@ -86,11 +86,14 @@ export interface IResourceType<T = unknown> extends ICollectible<ResourceTypeNam
|
|
|
86
86
|
/**
|
|
87
87
|
* Creates a template for a new resource of this type.
|
|
88
88
|
* The template provides a default structure for creating new resource instances.
|
|
89
|
-
* @param resourceId - The id for the new resource
|
|
90
|
-
* @
|
|
89
|
+
* @param resourceId - The id for the new resource.
|
|
90
|
+
* @param init - An optional initial value for the resource.
|
|
91
|
+
* @param resolver - An optional resource resolver that can be used to create the template.
|
|
92
|
+
* @param conditions - An optional set of conditions that must be met for the resource to be selected.
|
|
93
|
+
* @returns A loose resource declaration with default values for this resource type.
|
|
91
94
|
* @public
|
|
92
95
|
*/
|
|
93
|
-
createTemplate(resourceId: ResourceId): ResourceJson.Json.ILooseResourceDecl
|
|
96
|
+
createTemplate(resourceId: ResourceId, init?: JsonValue, conditions?: ResourceJson.Json.ConditionSetDecl, resolver?: IResourceResolver): Result<ResourceJson.Json.ILooseResourceDecl>;
|
|
94
97
|
}
|
|
95
98
|
/**
|
|
96
99
|
* Abstract base class for resource types which are responsible for
|
|
@@ -100,6 +103,7 @@ export interface IResourceType<T = unknown> extends ICollectible<ResourceTypeNam
|
|
|
100
103
|
*/
|
|
101
104
|
export declare abstract class ResourceType<T = unknown> implements IResourceType<T> {
|
|
102
105
|
private _collectible;
|
|
106
|
+
private _template;
|
|
103
107
|
/**
|
|
104
108
|
* {@inheritdoc ResourceTypes.IResourceType.key}
|
|
105
109
|
*/
|
|
@@ -108,7 +112,7 @@ export declare abstract class ResourceType<T = unknown> implements IResourceType
|
|
|
108
112
|
* {@inheritdoc ResourceTypes.IResourceType.index}
|
|
109
113
|
*/
|
|
110
114
|
get index(): ResourceTypeIndex | undefined;
|
|
111
|
-
protected constructor(key: ResourceTypeName, index?: number);
|
|
115
|
+
protected constructor(key: ResourceTypeName, index?: number, template?: JsonObject);
|
|
112
116
|
/**
|
|
113
117
|
* Validates properties of a {@link ResourceJson.Json.ILooseResourceCandidateDecl | resource candidate declaration} for
|
|
114
118
|
* a resource instance value.
|
|
@@ -163,15 +167,19 @@ export declare abstract class ResourceType<T = unknown> implements IResourceType
|
|
|
163
167
|
* Default implementation provides a basic template.
|
|
164
168
|
* Subclasses can override to provide type-specific templates.
|
|
165
169
|
* @param resourceId - The id for the new resource
|
|
170
|
+
* @param init - An optional initial value for the resource.
|
|
171
|
+
* @param conditions - An optional set of conditions that must be met for the resource to be selected.
|
|
172
|
+
* @param resolver - An optional resource resolver that can be used to create the template.
|
|
166
173
|
* @returns A loose resource declaration with default values for this resource type
|
|
167
174
|
* @public
|
|
168
175
|
*/
|
|
169
|
-
createTemplate(resourceId: ResourceId): ResourceJson.Json.ILooseResourceDecl
|
|
176
|
+
createTemplate(resourceId: ResourceId, init?: JsonValue, conditions?: ResourceJson.Json.ConditionSetDecl, resolver?: IResourceResolver): Result<ResourceJson.Json.ILooseResourceDecl>;
|
|
170
177
|
/**
|
|
171
178
|
* Gets the default template value for this resource type.
|
|
172
179
|
* Subclasses should override this to provide type-specific default values.
|
|
173
180
|
* @returns The default JSON value for a new resource of this type
|
|
181
|
+
* @public
|
|
174
182
|
*/
|
|
175
|
-
|
|
183
|
+
getDefaultTemplateCandidate(json?: JsonValue, conditions?: ResourceJson.Json.ConditionSetDecl, __resolver?: IResourceResolver): Result<ResourceJson.Json.IChildResourceCandidateDecl>;
|
|
176
184
|
}
|
|
177
185
|
//# sourceMappingURL=resourceType.d.ts.map
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.ResourceType = void 0;
|
|
25
|
+
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
25
26
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
26
27
|
const common_1 = require("../common");
|
|
27
28
|
/**
|
|
@@ -43,13 +44,14 @@ class ResourceType {
|
|
|
43
44
|
get index() {
|
|
44
45
|
return this._collectible.index;
|
|
45
46
|
}
|
|
46
|
-
constructor(key, index) {
|
|
47
|
+
constructor(key, index, template) {
|
|
47
48
|
this._collectible = new ts_utils_1.Collections.Collectible({
|
|
48
49
|
key,
|
|
49
50
|
/* c8 ignore next 1 - coverage having a rough time */
|
|
50
51
|
index: index !== undefined ? common_1.Validate.toResourceTypeIndex(index).orThrow() : undefined,
|
|
51
52
|
indexConverter: common_1.Convert.resourceTypeIndex
|
|
52
53
|
});
|
|
54
|
+
this._template = template !== null && template !== void 0 ? template : {};
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
57
|
* Sets the index for this resource type. Once set, the index cannot be changed.
|
|
@@ -62,32 +64,34 @@ class ResourceType {
|
|
|
62
64
|
* Default implementation provides a basic template.
|
|
63
65
|
* Subclasses can override to provide type-specific templates.
|
|
64
66
|
* @param resourceId - The id for the new resource
|
|
67
|
+
* @param init - An optional initial value for the resource.
|
|
68
|
+
* @param conditions - An optional set of conditions that must be met for the resource to be selected.
|
|
69
|
+
* @param resolver - An optional resource resolver that can be used to create the template.
|
|
65
70
|
* @returns A loose resource declaration with default values for this resource type
|
|
66
71
|
* @public
|
|
67
72
|
*/
|
|
68
|
-
createTemplate(resourceId) {
|
|
69
|
-
return {
|
|
73
|
+
createTemplate(resourceId, init, conditions, resolver) {
|
|
74
|
+
return this.getDefaultTemplateCandidate(init, conditions, resolver).onSuccess((candidate) => (0, ts_utils_1.succeed)({
|
|
70
75
|
id: resourceId,
|
|
71
76
|
resourceTypeName: this.key,
|
|
72
|
-
candidates: [
|
|
73
|
-
|
|
74
|
-
json: this.getDefaultTemplateValue(),
|
|
75
|
-
conditions: undefined,
|
|
76
|
-
isPartial: false,
|
|
77
|
-
mergeMethod: 'replace'
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
};
|
|
77
|
+
candidates: [candidate]
|
|
78
|
+
}));
|
|
81
79
|
}
|
|
82
80
|
/**
|
|
83
81
|
* Gets the default template value for this resource type.
|
|
84
82
|
* Subclasses should override this to provide type-specific default values.
|
|
85
83
|
* @returns The default JSON value for a new resource of this type
|
|
84
|
+
* @public
|
|
86
85
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
getDefaultTemplateCandidate(json, conditions, __resolver) {
|
|
87
|
+
json = json !== null && json !== void 0 ? json : this._template;
|
|
88
|
+
if (!(0, ts_json_base_1.isJsonObject)(json)) {
|
|
89
|
+
return (0, ts_utils_1.fail)(`${this.key}: Invalid initial value "${json}" must be JSON object.`);
|
|
90
|
+
}
|
|
91
|
+
const candidate = Object.assign(Object.assign({ json }, (conditions ? { conditions } : {})), { mergeMethod: 'replace' });
|
|
92
|
+
return this.validate(json, 'full')
|
|
93
|
+
.onSuccess((json) => (0, ts_utils_1.succeed)(Object.assign(Object.assign({}, candidate), { isPartial: false })))
|
|
94
|
+
.onFailure(() => this.validate(json, 'partial').onSuccess((json) => (0, ts_utils_1.succeed)(Object.assign(Object.assign({}, candidate), { isPartial: true }))));
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
97
|
exports.ResourceType = ResourceType;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
2
|
import { JsonValue } from '@fgv/ts-json-base';
|
|
3
|
+
import { IResourceResolver } from '../common';
|
|
3
4
|
import { Condition, ConditionSet } from '../conditions';
|
|
4
5
|
import { AbstractDecision } from '../decisions';
|
|
5
6
|
import { ReadOnlyQualifierTypeCollector } from '../qualifier-types';
|
|
@@ -7,6 +8,7 @@ import { IContextQualifierProvider } from './context';
|
|
|
7
8
|
import { IResourceManager, IResource, IResourceCandidate } from './iResourceManager';
|
|
8
9
|
import { ConditionSetResolutionResult, IConditionMatchResult } from './conditionSetResolutionResult';
|
|
9
10
|
import { IResourceResolverCacheListener } from './cacheListener';
|
|
11
|
+
import { IReadOnlyQualifierCollector } from '../qualifiers';
|
|
10
12
|
/**
|
|
11
13
|
* Represents the cached result of resolving a decision.
|
|
12
14
|
* Contains either a failure indicator or a list of instance indices for matching condition sets,
|
|
@@ -68,7 +70,7 @@ export interface IResourceResolverCreateParams {
|
|
|
68
70
|
* and caching results for optimal performance.
|
|
69
71
|
* @public
|
|
70
72
|
*/
|
|
71
|
-
export declare class ResourceResolver {
|
|
73
|
+
export declare class ResourceResolver implements IResourceResolver {
|
|
72
74
|
/**
|
|
73
75
|
* The resource manager that defines available resources and provides condition access.
|
|
74
76
|
*/
|
|
@@ -85,6 +87,10 @@ export declare class ResourceResolver {
|
|
|
85
87
|
* The configuration options for this resource resolver.
|
|
86
88
|
*/
|
|
87
89
|
readonly options: IResourceResolverOptions;
|
|
90
|
+
/**
|
|
91
|
+
* The readonly qualifier collector that provides qualifier implementations.
|
|
92
|
+
*/
|
|
93
|
+
get qualifiers(): IReadOnlyQualifierCollector;
|
|
88
94
|
/**
|
|
89
95
|
* The cache array for resolved conditions, indexed by condition index for O(1) lookup.
|
|
90
96
|
* Each entry stores the resolved {@link Runtime.IConditionMatchResult | condition match result} for
|
|
@@ -169,6 +175,15 @@ export declare class ResourceResolver {
|
|
|
169
175
|
* @public
|
|
170
176
|
*/
|
|
171
177
|
resolveResource(resource: IResource): Result<IResourceCandidate>;
|
|
178
|
+
/**
|
|
179
|
+
* Resolves a resource by finding the best matching candidate.
|
|
180
|
+
* Uses the resource's associated decision to determine the best match based on the current context.
|
|
181
|
+
* @param resource - The string id of the resource to resolve.
|
|
182
|
+
* @returns `Success` with the best matching candidate if successful,
|
|
183
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
184
|
+
* @public
|
|
185
|
+
*/
|
|
186
|
+
resolveResource(resource: string): Result<IResourceCandidate>;
|
|
172
187
|
/**
|
|
173
188
|
* Resolves all matching resource candidates in priority order.
|
|
174
189
|
* Uses the resource's associated decision to determine all matching candidates based on the current context.
|
|
@@ -178,6 +193,15 @@ export declare class ResourceResolver {
|
|
|
178
193
|
* @public
|
|
179
194
|
*/
|
|
180
195
|
resolveAllResourceCandidates(resource: IResource): Result<ReadonlyArray<IResourceCandidate>>;
|
|
196
|
+
/**
|
|
197
|
+
* Resolves all matching resource candidates in priority order.
|
|
198
|
+
* Uses the resource's associated decision to determine all matching candidates based on the current context.
|
|
199
|
+
* @param resource - The string id of the resource to resolve.
|
|
200
|
+
* @returns `Success` with an array of all matching candidates in priority order if successful,
|
|
201
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
202
|
+
* @public
|
|
203
|
+
*/
|
|
204
|
+
resolveAllResourceCandidates(resource: string): Result<ReadonlyArray<IResourceCandidate>>;
|
|
181
205
|
/**
|
|
182
206
|
* Resolves a resource to a composed value by merging matching candidates according to their merge methods.
|
|
183
207
|
* Starting from the highest priority candidates, finds the first "full" candidate and merges all higher
|
|
@@ -188,6 +212,20 @@ export declare class ResourceResolver {
|
|
|
188
212
|
* @public
|
|
189
213
|
*/
|
|
190
214
|
resolveComposedResourceValue(resource: IResource): Result<JsonValue>;
|
|
215
|
+
/**
|
|
216
|
+
* Resolves a resource to a composed value by merging matching candidates according to their merge methods.
|
|
217
|
+
* Starting from the highest priority candidates, finds the first "full" candidate and merges all higher
|
|
218
|
+
* priority "partial" candidates into it in ascending order of priority.
|
|
219
|
+
* @param resource - The string id of the resource to resolve.
|
|
220
|
+
* @returns `Success` with the composed JsonValue if successful,
|
|
221
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
222
|
+
* @public
|
|
223
|
+
*/
|
|
224
|
+
resolveComposedResourceValue(resource: string): Result<JsonValue>;
|
|
225
|
+
/**
|
|
226
|
+
* {@inheritDoc IResourceResolver.withContext}
|
|
227
|
+
*/
|
|
228
|
+
withContext(context: Record<string, string>): Result<ResourceResolver>;
|
|
191
229
|
/**
|
|
192
230
|
* Clears all caches (condition, condition set, and decision), forcing all cached items
|
|
193
231
|
* to be re-evaluated on next access. This should be called when the context changes and cached
|
|
@@ -26,6 +26,7 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
26
26
|
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
27
27
|
const ts_json_1 = require("@fgv/ts-json");
|
|
28
28
|
const common_1 = require("../common");
|
|
29
|
+
const context_1 = require("./context");
|
|
29
30
|
const conditionSetResolutionResult_1 = require("./conditionSetResolutionResult");
|
|
30
31
|
/**
|
|
31
32
|
* High-performance runtime resource resolver with O(1) condition caching.
|
|
@@ -34,6 +35,12 @@ const conditionSetResolutionResult_1 = require("./conditionSetResolutionResult")
|
|
|
34
35
|
* @public
|
|
35
36
|
*/
|
|
36
37
|
class ResourceResolver {
|
|
38
|
+
/**
|
|
39
|
+
* The readonly qualifier collector that provides qualifier implementations.
|
|
40
|
+
*/
|
|
41
|
+
get qualifiers() {
|
|
42
|
+
return this.contextQualifierProvider.qualifiers;
|
|
43
|
+
}
|
|
37
44
|
/**
|
|
38
45
|
* The cache array for resolved conditions, indexed by condition index for O(1) lookup.
|
|
39
46
|
* Each entry stores the resolved {@link Runtime.IConditionMatchResult | condition match result} for
|
|
@@ -268,15 +275,13 @@ class ResourceResolver {
|
|
|
268
275
|
(_c = this._listener) === null || _c === void 0 ? void 0 : _c.onCacheMiss('decision', decisionIndex);
|
|
269
276
|
return (0, ts_utils_1.succeed)(successResult);
|
|
270
277
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
*/
|
|
279
|
-
resolveResource(resource) {
|
|
278
|
+
resolveResource(idOrResource) {
|
|
279
|
+
if (typeof idOrResource === 'string') {
|
|
280
|
+
return this.resourceManager
|
|
281
|
+
.getBuiltResource(idOrResource)
|
|
282
|
+
.onSuccess((resource) => this.resolveResource(resource));
|
|
283
|
+
}
|
|
284
|
+
const resource = idOrResource;
|
|
280
285
|
// Get the abstract decision from the resource's concrete decision
|
|
281
286
|
const abstractDecision = resource.decision.baseDecision;
|
|
282
287
|
// Resolve the decision to get candidate indices in priority order
|
|
@@ -301,15 +306,13 @@ class ResourceResolver {
|
|
|
301
306
|
const bestCandidate = resource.candidates[candidateIndex];
|
|
302
307
|
return (0, ts_utils_1.succeed)(bestCandidate);
|
|
303
308
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
resolveAllResourceCandidates(resource) {
|
|
309
|
+
resolveAllResourceCandidates(idOrResource) {
|
|
310
|
+
if (typeof idOrResource === 'string') {
|
|
311
|
+
return this.resourceManager
|
|
312
|
+
.getBuiltResource(idOrResource)
|
|
313
|
+
.onSuccess((resource) => this.resolveAllResourceCandidates(resource));
|
|
314
|
+
}
|
|
315
|
+
const resource = idOrResource;
|
|
313
316
|
// Get the abstract decision from the resource's concrete decision
|
|
314
317
|
const abstractDecision = resource.decision.baseDecision;
|
|
315
318
|
// Resolve the decision to get candidate indices in priority order
|
|
@@ -345,16 +348,13 @@ class ResourceResolver {
|
|
|
345
348
|
}
|
|
346
349
|
return (0, ts_utils_1.succeed)(candidates);
|
|
347
350
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* @public
|
|
356
|
-
*/
|
|
357
|
-
resolveComposedResourceValue(resource) {
|
|
351
|
+
resolveComposedResourceValue(idOrResource) {
|
|
352
|
+
if (typeof idOrResource === 'string') {
|
|
353
|
+
return this.resourceManager
|
|
354
|
+
.getBuiltResource(idOrResource)
|
|
355
|
+
.onSuccess((resource) => this.resolveComposedResourceValue(resource));
|
|
356
|
+
}
|
|
357
|
+
const resource = idOrResource;
|
|
358
358
|
return this.resolveAllResourceCandidates(resource).onSuccess((candidates) => {
|
|
359
359
|
/* c8 ignore next 3 - defense in depth should never occur */
|
|
360
360
|
if (candidates.length === 0) {
|
|
@@ -415,6 +415,23 @@ class ResourceResolver {
|
|
|
415
415
|
.withErrorFormat((err) => `${resource.id}: Composition failed: ${err}`);
|
|
416
416
|
});
|
|
417
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* {@inheritDoc IResourceResolver.withContext}
|
|
420
|
+
*/
|
|
421
|
+
withContext(context) {
|
|
422
|
+
const { resourceManager, qualifierTypes, options } = this;
|
|
423
|
+
return context_1.ValidatingSimpleContextQualifierProvider.create({
|
|
424
|
+
qualifiers: this.qualifiers,
|
|
425
|
+
qualifierValues: context
|
|
426
|
+
}).onSuccess((contextQualifierProvider) => {
|
|
427
|
+
return ResourceResolver.create({
|
|
428
|
+
resourceManager,
|
|
429
|
+
qualifierTypes,
|
|
430
|
+
options,
|
|
431
|
+
contextQualifierProvider
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
}
|
|
418
435
|
/**
|
|
419
436
|
* Clears all caches (condition, condition set, and decision), forcing all cached items
|
|
420
437
|
* to be re-evaluated on next access. This should be called when the context changes and cached
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { JsonObject, JsonValue } from '@fgv/ts-json-base';
|
|
2
|
+
import { Result } from '@fgv/ts-utils';
|
|
3
|
+
import { CandidateCompleteness, ResourceTypeName, ResourceId, IResourceResolver } from '../../../packlets/common';
|
|
4
|
+
import { IResourceCandidateValidationProperties, ResourceType } from '../../../packlets/resource-types/resourceType';
|
|
5
|
+
import * as ResourceJson from '../../../packlets/resource-json';
|
|
6
|
+
/**
|
|
7
|
+
* Parameters to create a TestDerivedResourceType instance.
|
|
8
|
+
*/
|
|
9
|
+
export interface ITestDerivedResourceTypeCreateParams {
|
|
10
|
+
/**
|
|
11
|
+
* Optional key for the new TestDerivedResourceType instance.
|
|
12
|
+
* Defaults to 'test-derived'.
|
|
13
|
+
*/
|
|
14
|
+
key?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional index for the new TestDerivedResourceType instance.
|
|
17
|
+
*/
|
|
18
|
+
index?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Optional template for new instances.
|
|
21
|
+
*/
|
|
22
|
+
template?: JsonObject;
|
|
23
|
+
/**
|
|
24
|
+
* Optional resource ID to use as a template source.
|
|
25
|
+
* When provided, the resource resolver will be used to look up
|
|
26
|
+
* this resource and use its value as the template.
|
|
27
|
+
*/
|
|
28
|
+
templateResourceId?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Test data structure for the derived resource type
|
|
32
|
+
*/
|
|
33
|
+
export interface ITestData {
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
metadata?: Record<string, JsonValue>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Partial test data structure for the derived resource type
|
|
40
|
+
*/
|
|
41
|
+
export interface IPartialTestData {
|
|
42
|
+
title?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
metadata?: Record<string, JsonValue>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A derived resource type for testing that demonstrates:
|
|
48
|
+
* 1. Custom validation logic
|
|
49
|
+
* 2. Using a resource resolver to look up template values
|
|
50
|
+
* 3. Type-specific defaults and error handling
|
|
51
|
+
*/
|
|
52
|
+
export declare class TestDerivedResourceType extends ResourceType<ITestData> {
|
|
53
|
+
private _templateResourceId?;
|
|
54
|
+
protected constructor(key: ResourceTypeName, index?: number, template?: JsonObject, templateResourceId?: string);
|
|
55
|
+
/**
|
|
56
|
+
* Factory method to create a new TestDerivedResourceType instance.
|
|
57
|
+
*/
|
|
58
|
+
static create(params?: ITestDerivedResourceTypeCreateParams): Result<TestDerivedResourceType>;
|
|
59
|
+
/**
|
|
60
|
+
* Validates a resource candidate declaration with custom logic.
|
|
61
|
+
*/
|
|
62
|
+
validateDeclaration(props: IResourceCandidateValidationProperties): Result<ITestData | IPartialTestData>;
|
|
63
|
+
/**
|
|
64
|
+
* Validates JSON for this resource type with custom business logic.
|
|
65
|
+
*/
|
|
66
|
+
validate(json: JsonValue, completeness: CandidateCompleteness): Result<IPartialTestData>;
|
|
67
|
+
validate(json: JsonValue, completeness: 'full'): Result<ITestData>;
|
|
68
|
+
validate(json: JsonValue, completeness: 'partial'): Result<IPartialTestData>;
|
|
69
|
+
/**
|
|
70
|
+
* Override to demonstrate looking up template values from another resource.
|
|
71
|
+
*/
|
|
72
|
+
getDefaultTemplateCandidate(json?: JsonValue, conditions?: ResourceJson.Json.ConditionSetDecl, resolver?: IResourceResolver): Result<ResourceJson.Json.IChildResourceCandidateDecl>;
|
|
73
|
+
/**
|
|
74
|
+
* Override createTemplate to add additional validation for the derived type.
|
|
75
|
+
*/
|
|
76
|
+
createTemplate(resourceId: ResourceId, init?: JsonValue, conditions?: ResourceJson.Json.ConditionSetDecl, resolver?: IResourceResolver): Result<ResourceJson.Json.ILooseResourceDecl>;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=testDerivedResourceType.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025 Erik Fortune
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
* in the Software without restriction, including without limitation the rights
|
|
8
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
* furnished to do so, subject to the following conditions:
|
|
11
|
+
*
|
|
12
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software.
|
|
14
|
+
*
|
|
15
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
* SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.TestDerivedResourceType = void 0;
|
|
25
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
26
|
+
const common_1 = require("../../../packlets/common");
|
|
27
|
+
// eslint-disable-next-line @rushstack/packlets/mechanics
|
|
28
|
+
const resourceType_1 = require("../../../packlets/resource-types/resourceType");
|
|
29
|
+
/**
|
|
30
|
+
* A derived resource type for testing that demonstrates:
|
|
31
|
+
* 1. Custom validation logic
|
|
32
|
+
* 2. Using a resource resolver to look up template values
|
|
33
|
+
* 3. Type-specific defaults and error handling
|
|
34
|
+
*/
|
|
35
|
+
class TestDerivedResourceType extends resourceType_1.ResourceType {
|
|
36
|
+
constructor(key, index, template, templateResourceId) {
|
|
37
|
+
super(key, index, template);
|
|
38
|
+
this._templateResourceId = templateResourceId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Factory method to create a new TestDerivedResourceType instance.
|
|
42
|
+
*/
|
|
43
|
+
static create(params) {
|
|
44
|
+
var _a;
|
|
45
|
+
return common_1.Convert.resourceTypeName.convert((_a = params === null || params === void 0 ? void 0 : params.key) !== null && _a !== void 0 ? _a : 'test-derived').onSuccess((key) => {
|
|
46
|
+
return (0, ts_utils_1.captureResult)(() => new TestDerivedResourceType(key, params === null || params === void 0 ? void 0 : params.index, params === null || params === void 0 ? void 0 : params.template, params === null || params === void 0 ? void 0 : params.templateResourceId));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validates a resource candidate declaration with custom logic.
|
|
51
|
+
*/
|
|
52
|
+
validateDeclaration(props) {
|
|
53
|
+
// Custom validation: merge method must be 'replace' for full candidates
|
|
54
|
+
if (props.completeness === 'full' && props.mergeMethod !== 'replace') {
|
|
55
|
+
return (0, ts_utils_1.fail)(`${this.key}: Full candidates must use 'replace' merge method, got '${props.mergeMethod}'`);
|
|
56
|
+
}
|
|
57
|
+
return this.validate(props.json, props.completeness);
|
|
58
|
+
}
|
|
59
|
+
validate(json, completeness) {
|
|
60
|
+
if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
|
61
|
+
return (0, ts_utils_1.fail)(`${this.key}: Expected JSON object, got ${typeof json}`);
|
|
62
|
+
}
|
|
63
|
+
const obj = json;
|
|
64
|
+
// Validate required title field for full resources
|
|
65
|
+
if (completeness === 'full' && (typeof obj.title !== 'string' || !obj.title.trim())) {
|
|
66
|
+
return (0, ts_utils_1.fail)(`${this.key}: 'title' field is required and must be a non-empty string`);
|
|
67
|
+
}
|
|
68
|
+
// Validate optional description
|
|
69
|
+
if (obj.description !== undefined && typeof obj.description !== 'string') {
|
|
70
|
+
return (0, ts_utils_1.fail)(`${this.key}: 'description' field must be a string if provided`);
|
|
71
|
+
}
|
|
72
|
+
// Validate optional metadata
|
|
73
|
+
if (obj.metadata !== undefined) {
|
|
74
|
+
if (typeof obj.metadata !== 'object' || obj.metadata === null || Array.isArray(obj.metadata)) {
|
|
75
|
+
return (0, ts_utils_1.fail)(`${this.key}: 'metadata' field must be an object if provided`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const result = Object.assign(Object.assign(Object.assign({}, (obj.title && { title: obj.title })), (obj.description && { description: obj.description })), (obj.metadata && { metadata: obj.metadata }));
|
|
79
|
+
return (0, ts_utils_1.succeed)(result);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Override to demonstrate looking up template values from another resource.
|
|
83
|
+
*/
|
|
84
|
+
getDefaultTemplateCandidate(json, conditions, resolver) {
|
|
85
|
+
// If a template resource ID is configured and resolver is available, try to resolve it
|
|
86
|
+
if (this._templateResourceId && resolver && !json) {
|
|
87
|
+
return resolver
|
|
88
|
+
.resolveComposedResourceValue(this._templateResourceId)
|
|
89
|
+
.onFailure((error) => (0, ts_utils_1.fail)(`${this.key}: Failed to resolve template resource '${this._templateResourceId}': ${error}`))
|
|
90
|
+
.onSuccess((resolvedValue) => {
|
|
91
|
+
// Use the resolved value as our JSON template
|
|
92
|
+
return super.getDefaultTemplateCandidate(resolvedValue, conditions, resolver);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Use provided JSON or fall back to base implementation
|
|
96
|
+
return super.getDefaultTemplateCandidate(json, conditions, resolver);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Override createTemplate to add additional validation for the derived type.
|
|
100
|
+
*/
|
|
101
|
+
createTemplate(resourceId, init, conditions, resolver) {
|
|
102
|
+
// Additional validation: warn if trying to use resolver functionality without a resolver
|
|
103
|
+
if (this._templateResourceId && !resolver) {
|
|
104
|
+
return (0, ts_utils_1.fail)(`${this.key}: Template resource ID '${this._templateResourceId}' is configured but no resolver provided`);
|
|
105
|
+
}
|
|
106
|
+
return super.createTemplate(resourceId, init, conditions, resolver);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.TestDerivedResourceType = TestDerivedResourceType;
|
|
110
|
+
//# sourceMappingURL=testDerivedResourceType.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-res",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-22",
|
|
4
4
|
"description": "Multi-dimensional Resource Runtime",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "dist/ts-res.d.ts",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"@rushstack/heft-node-rig": "~2.9.3",
|
|
45
45
|
"@types/heft-jest": "1.0.6",
|
|
46
46
|
"eslint-plugin-tsdoc": "~0.4.0",
|
|
47
|
-
"@fgv/ts-utils-jest": "5.0.0-
|
|
48
|
-
"@fgv/ts-extras": "5.0.0-
|
|
47
|
+
"@fgv/ts-utils-jest": "5.0.0-22",
|
|
48
|
+
"@fgv/ts-extras": "5.0.0-22"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"luxon": "^3.7.1",
|
|
52
52
|
"fflate": "~0.8.2",
|
|
53
|
-
"@fgv/ts-
|
|
54
|
-
"@fgv/ts-
|
|
55
|
-
"@fgv/ts-
|
|
56
|
-
"@fgv/ts-json": "5.0.0-
|
|
53
|
+
"@fgv/ts-bcp47": "5.0.0-22",
|
|
54
|
+
"@fgv/ts-utils": "5.0.0-22",
|
|
55
|
+
"@fgv/ts-json-base": "5.0.0-22",
|
|
56
|
+
"@fgv/ts-json": "5.0.0-22"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "heft build --clean",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { JsonValue } from '@fgv/ts-json-base';
|
|
24
|
+
import { Brand, Result } from '@fgv/ts-utils';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Branded string representing a validated resource id. A resource ID
|
|
@@ -76,3 +77,29 @@ export const allResourceValueMergeMethods: ResourceValueMergeMethod[] = ['augmen
|
|
|
76
77
|
* @public
|
|
77
78
|
*/
|
|
78
79
|
export type CandidateCompleteness = 'full' | 'partial';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Minimal resource resolver
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
export interface IResourceResolver {
|
|
86
|
+
/**
|
|
87
|
+
* Resolves a resource to a composed value by merging matching candidates according to their merge methods.
|
|
88
|
+
* Starting from the highest priority candidates, finds the first "full" candidate and merges all higher
|
|
89
|
+
* priority "partial" candidates into it in ascending order of priority.
|
|
90
|
+
* @param resource - The string id of the resource to resolve.
|
|
91
|
+
* @returns `Success` with the composed JsonValue if successful,
|
|
92
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
resolveComposedResourceValue(resource: string): Result<JsonValue>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates a new {@link IResourceResolver | resource resolver} with the given context.
|
|
99
|
+
* @param context - The context to use for the new resource resolver.
|
|
100
|
+
* @returns `Success` with the new resource resolver if successful,
|
|
101
|
+
* or `Failure` with an error message if the context is invalid.
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
withContext(context: Record<string, string>): Result<IResourceResolver>;
|
|
105
|
+
}
|