@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
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
/* eslint-disable @rushstack/typedef-var */
|
|
24
24
|
|
|
25
25
|
import { Converters } from '@fgv/ts-utils';
|
|
26
|
+
import { Converters as JsonConverters } from '@fgv/ts-json-base';
|
|
26
27
|
import { IResourceTypeConfig } from './json';
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -32,5 +33,6 @@ import { IResourceTypeConfig } from './json';
|
|
|
32
33
|
*/
|
|
33
34
|
export const resourceTypeConfig = Converters.strictObject<IResourceTypeConfig>({
|
|
34
35
|
name: Converters.string,
|
|
35
|
-
typeName: Converters.string
|
|
36
|
+
typeName: Converters.string,
|
|
37
|
+
template: JsonConverters.jsonObject.optional()
|
|
36
38
|
});
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { JsonObject } from '@fgv/ts-json-base';
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* Configuration for a {@link ResourceTypes.ResourceType | resource type}.
|
|
25
27
|
* @public
|
|
@@ -27,4 +29,5 @@
|
|
|
27
29
|
export interface IResourceTypeConfig {
|
|
28
30
|
name: string;
|
|
29
31
|
typeName: string;
|
|
32
|
+
template?: JsonObject;
|
|
30
33
|
}
|
|
@@ -41,6 +41,12 @@ export interface IJsonResourceTypeCreateParams {
|
|
|
41
41
|
* instance.
|
|
42
42
|
*/
|
|
43
43
|
index?: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Optional template for new instances of {@link ResourceTypes.JsonResourceType | JsonResourceType}
|
|
47
|
+
* resources.
|
|
48
|
+
*/
|
|
49
|
+
template?: JsonObject;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
@@ -54,8 +60,8 @@ export class JsonResourceType extends ResourceType<JsonObject> {
|
|
|
54
60
|
* @param key - The key for the new {@link ResourceTypes.JsonResourceType | JsonResourceType} instance.
|
|
55
61
|
* @param index - Optional index for the new {@link ResourceTypes.JsonResourceType | JsonResourceType} instance.
|
|
56
62
|
*/
|
|
57
|
-
protected constructor(key: ResourceTypeName, index?: number) {
|
|
58
|
-
super(key, index);
|
|
63
|
+
protected constructor(key: ResourceTypeName, index?: number, template?: JsonObject) {
|
|
64
|
+
super(key, index, template);
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/**
|
|
@@ -66,7 +72,7 @@ export class JsonResourceType extends ResourceType<JsonObject> {
|
|
|
66
72
|
*/
|
|
67
73
|
public static create(params?: IJsonResourceTypeCreateParams): Result<JsonResourceType> {
|
|
68
74
|
return Convert.resourceTypeName.convert(params?.key ?? 'json').onSuccess((key) => {
|
|
69
|
-
return captureResult(() => new JsonResourceType(key, params?.index));
|
|
75
|
+
return captureResult(() => new JsonResourceType(key, params?.index, params?.template));
|
|
70
76
|
});
|
|
71
77
|
}
|
|
72
78
|
|
|
@@ -93,12 +99,4 @@ export class JsonResourceType extends ResourceType<JsonObject> {
|
|
|
93
99
|
public validate(json: JsonObject, __completeness?: CandidateCompleteness): Result<JsonObject> {
|
|
94
100
|
return JsonConverters.jsonObject.convert(json);
|
|
95
101
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Gets the default template value for a JSON resource type.
|
|
99
|
-
* @returns An empty object as the default JSON value
|
|
100
|
-
*/
|
|
101
|
-
protected getDefaultTemplateValue(): JsonObject {
|
|
102
|
-
return {};
|
|
103
|
-
}
|
|
104
102
|
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
import { Collections, ICollectible, Result } from '@fgv/ts-utils';
|
|
23
|
+
import { isJsonObject, JsonObject, JsonValue } from '@fgv/ts-json-base';
|
|
24
|
+
import { Collections, fail, ICollectible, Result, succeed } from '@fgv/ts-utils';
|
|
25
25
|
import {
|
|
26
26
|
CandidateCompleteness,
|
|
27
27
|
Convert as CommonConvert,
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
ResourceTypeIndex,
|
|
30
30
|
ResourceTypeName,
|
|
31
31
|
ResourceValueMergeMethod,
|
|
32
|
-
ResourceId
|
|
32
|
+
ResourceId,
|
|
33
|
+
IResourceResolver
|
|
33
34
|
} from '../common';
|
|
34
35
|
import * as ResourceJson from '../resource-json';
|
|
35
36
|
|
|
@@ -128,11 +129,19 @@ export interface IResourceType<T = unknown> extends ICollectible<ResourceTypeNam
|
|
|
128
129
|
/**
|
|
129
130
|
* Creates a template for a new resource of this type.
|
|
130
131
|
* The template provides a default structure for creating new resource instances.
|
|
131
|
-
* @param resourceId - The id for the new resource
|
|
132
|
-
* @
|
|
132
|
+
* @param resourceId - The id for the new resource.
|
|
133
|
+
* @param init - An optional initial value for the resource.
|
|
134
|
+
* @param resolver - An optional resource resolver that can be used to create the template.
|
|
135
|
+
* @param conditions - An optional set of conditions that must be met for the resource to be selected.
|
|
136
|
+
* @returns A loose resource declaration with default values for this resource type.
|
|
133
137
|
* @public
|
|
134
138
|
*/
|
|
135
|
-
createTemplate(
|
|
139
|
+
createTemplate(
|
|
140
|
+
resourceId: ResourceId,
|
|
141
|
+
init?: JsonValue,
|
|
142
|
+
conditions?: ResourceJson.Json.ConditionSetDecl,
|
|
143
|
+
resolver?: IResourceResolver
|
|
144
|
+
): Result<ResourceJson.Json.ILooseResourceDecl>;
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
/**
|
|
@@ -143,6 +152,7 @@ export interface IResourceType<T = unknown> extends ICollectible<ResourceTypeNam
|
|
|
143
152
|
*/
|
|
144
153
|
export abstract class ResourceType<T = unknown> implements IResourceType<T> {
|
|
145
154
|
private _collectible: Collections.Collectible<ResourceTypeName, ResourceTypeIndex>;
|
|
155
|
+
private _template: JsonObject;
|
|
146
156
|
/**
|
|
147
157
|
* {@inheritdoc ResourceTypes.IResourceType.key}
|
|
148
158
|
*/
|
|
@@ -157,13 +167,14 @@ export abstract class ResourceType<T = unknown> implements IResourceType<T> {
|
|
|
157
167
|
return this._collectible.index;
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
protected constructor(key: ResourceTypeName, index?: number) {
|
|
170
|
+
protected constructor(key: ResourceTypeName, index?: number, template?: JsonObject) {
|
|
161
171
|
this._collectible = new Collections.Collectible<ResourceTypeName, ResourceTypeIndex>({
|
|
162
172
|
key,
|
|
163
173
|
/* c8 ignore next 1 - coverage having a rough time */
|
|
164
174
|
index: index !== undefined ? Validate.toResourceTypeIndex(index).orThrow() : undefined,
|
|
165
175
|
indexConverter: CommonConvert.resourceTypeIndex
|
|
166
176
|
});
|
|
177
|
+
this._template = template ?? {};
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
/**
|
|
@@ -228,32 +239,54 @@ export abstract class ResourceType<T = unknown> implements IResourceType<T> {
|
|
|
228
239
|
* Default implementation provides a basic template.
|
|
229
240
|
* Subclasses can override to provide type-specific templates.
|
|
230
241
|
* @param resourceId - The id for the new resource
|
|
242
|
+
* @param init - An optional initial value for the resource.
|
|
243
|
+
* @param conditions - An optional set of conditions that must be met for the resource to be selected.
|
|
244
|
+
* @param resolver - An optional resource resolver that can be used to create the template.
|
|
231
245
|
* @returns A loose resource declaration with default values for this resource type
|
|
232
246
|
* @public
|
|
233
247
|
*/
|
|
234
|
-
public createTemplate(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
public createTemplate(
|
|
249
|
+
resourceId: ResourceId,
|
|
250
|
+
init?: JsonValue,
|
|
251
|
+
conditions?: ResourceJson.Json.ConditionSetDecl,
|
|
252
|
+
resolver?: IResourceResolver
|
|
253
|
+
): Result<ResourceJson.Json.ILooseResourceDecl> {
|
|
254
|
+
return this.getDefaultTemplateCandidate(init, conditions, resolver).onSuccess((candidate) =>
|
|
255
|
+
succeed({
|
|
256
|
+
id: resourceId,
|
|
257
|
+
resourceTypeName: this.key,
|
|
258
|
+
candidates: [candidate]
|
|
259
|
+
})
|
|
260
|
+
);
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
/**
|
|
250
264
|
* Gets the default template value for this resource type.
|
|
251
265
|
* Subclasses should override this to provide type-specific default values.
|
|
252
266
|
* @returns The default JSON value for a new resource of this type
|
|
267
|
+
* @public
|
|
253
268
|
*/
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
269
|
+
public getDefaultTemplateCandidate(
|
|
270
|
+
json?: JsonValue,
|
|
271
|
+
conditions?: ResourceJson.Json.ConditionSetDecl,
|
|
272
|
+
__resolver?: IResourceResolver
|
|
273
|
+
): Result<ResourceJson.Json.IChildResourceCandidateDecl> {
|
|
274
|
+
json = json ?? this._template;
|
|
275
|
+
|
|
276
|
+
if (!isJsonObject(json)) {
|
|
277
|
+
return fail(`${this.key}: Invalid initial value "${json}" must be JSON object.`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const candidate: ResourceJson.Json.IChildResourceCandidateDecl = {
|
|
281
|
+
json,
|
|
282
|
+
...(conditions ? { conditions } : {}),
|
|
283
|
+
mergeMethod: 'replace'
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return this.validate(json, 'full')
|
|
287
|
+
.onSuccess((json) => succeed({ ...candidate, isPartial: false }))
|
|
288
|
+
.onFailure(() =>
|
|
289
|
+
this.validate(json, 'partial').onSuccess((json) => succeed({ ...candidate, isPartial: true }))
|
|
290
|
+
);
|
|
258
291
|
}
|
|
259
292
|
}
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
import { Result, captureResult, fail, succeed } from '@fgv/ts-utils';
|
|
24
24
|
import { JsonValue, JsonObject, isJsonObject } from '@fgv/ts-json-base';
|
|
25
25
|
import { JsonEditor } from '@fgv/ts-json';
|
|
26
|
-
import { NoMatch } from '../common';
|
|
26
|
+
import { IResourceResolver, NoMatch } from '../common';
|
|
27
27
|
import { Condition, ConditionSet } from '../conditions';
|
|
28
28
|
import { AbstractDecision } from '../decisions';
|
|
29
29
|
import { ReadOnlyQualifierTypeCollector } from '../qualifier-types';
|
|
30
|
-
import { IContextQualifierProvider } from './context';
|
|
30
|
+
import { IContextQualifierProvider, ValidatingSimpleContextQualifierProvider } from './context';
|
|
31
31
|
import { IResourceManager, IResource, IResourceCandidate } from './iResourceManager';
|
|
32
32
|
import {
|
|
33
33
|
ConditionMatchType,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
IConditionMatchResult
|
|
36
36
|
} from './conditionSetResolutionResult';
|
|
37
37
|
import { IResourceResolverCacheListener } from './cacheListener';
|
|
38
|
+
import { IReadOnlyQualifierCollector } from '../qualifiers';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Represents the cached result of resolving a decision.
|
|
@@ -100,7 +101,7 @@ export interface IResourceResolverCreateParams {
|
|
|
100
101
|
* and caching results for optimal performance.
|
|
101
102
|
* @public
|
|
102
103
|
*/
|
|
103
|
-
export class ResourceResolver {
|
|
104
|
+
export class ResourceResolver implements IResourceResolver {
|
|
104
105
|
/**
|
|
105
106
|
* The resource manager that defines available resources and provides condition access.
|
|
106
107
|
*/
|
|
@@ -121,6 +122,13 @@ export class ResourceResolver {
|
|
|
121
122
|
*/
|
|
122
123
|
public readonly options: IResourceResolverOptions;
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The readonly qualifier collector that provides qualifier implementations.
|
|
127
|
+
*/
|
|
128
|
+
public get qualifiers(): IReadOnlyQualifierCollector {
|
|
129
|
+
return this.contextQualifierProvider.qualifiers;
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
/**
|
|
125
133
|
* The cache array for resolved conditions, indexed by condition index for O(1) lookup.
|
|
126
134
|
* Each entry stores the resolved {@link Runtime.IConditionMatchResult | condition match result} for
|
|
@@ -412,7 +420,26 @@ export class ResourceResolver {
|
|
|
412
420
|
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
413
421
|
* @public
|
|
414
422
|
*/
|
|
415
|
-
public resolveResource(resource: IResource): Result<IResourceCandidate
|
|
423
|
+
public resolveResource(resource: IResource): Result<IResourceCandidate>;
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Resolves a resource by finding the best matching candidate.
|
|
427
|
+
* Uses the resource's associated decision to determine the best match based on the current context.
|
|
428
|
+
* @param resource - The string id of the resource to resolve.
|
|
429
|
+
* @returns `Success` with the best matching candidate if successful,
|
|
430
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
431
|
+
* @public
|
|
432
|
+
*/
|
|
433
|
+
public resolveResource(resource: string): Result<IResourceCandidate>;
|
|
434
|
+
public resolveResource(idOrResource: string | IResource): Result<IResourceCandidate> {
|
|
435
|
+
if (typeof idOrResource === 'string') {
|
|
436
|
+
return this.resourceManager
|
|
437
|
+
.getBuiltResource(idOrResource)
|
|
438
|
+
.onSuccess((resource) => this.resolveResource(resource));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const resource = idOrResource;
|
|
442
|
+
|
|
416
443
|
// Get the abstract decision from the resource's concrete decision
|
|
417
444
|
const abstractDecision = resource.decision.baseDecision;
|
|
418
445
|
|
|
@@ -455,7 +482,28 @@ export class ResourceResolver {
|
|
|
455
482
|
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
456
483
|
* @public
|
|
457
484
|
*/
|
|
458
|
-
public resolveAllResourceCandidates(resource: IResource): Result<ReadonlyArray<IResourceCandidate
|
|
485
|
+
public resolveAllResourceCandidates(resource: IResource): Result<ReadonlyArray<IResourceCandidate>>;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Resolves all matching resource candidates in priority order.
|
|
489
|
+
* Uses the resource's associated decision to determine all matching candidates based on the current context.
|
|
490
|
+
* @param resource - The string id of the resource to resolve.
|
|
491
|
+
* @returns `Success` with an array of all matching candidates in priority order if successful,
|
|
492
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
493
|
+
* @public
|
|
494
|
+
*/
|
|
495
|
+
public resolveAllResourceCandidates(resource: string): Result<ReadonlyArray<IResourceCandidate>>;
|
|
496
|
+
public resolveAllResourceCandidates(
|
|
497
|
+
idOrResource: string | IResource
|
|
498
|
+
): Result<ReadonlyArray<IResourceCandidate>> {
|
|
499
|
+
if (typeof idOrResource === 'string') {
|
|
500
|
+
return this.resourceManager
|
|
501
|
+
.getBuiltResource(idOrResource)
|
|
502
|
+
.onSuccess((resource) => this.resolveAllResourceCandidates(resource));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const resource = idOrResource;
|
|
506
|
+
|
|
459
507
|
// Get the abstract decision from the resource's concrete decision
|
|
460
508
|
const abstractDecision = resource.decision.baseDecision;
|
|
461
509
|
|
|
@@ -510,7 +558,27 @@ export class ResourceResolver {
|
|
|
510
558
|
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
511
559
|
* @public
|
|
512
560
|
*/
|
|
513
|
-
public resolveComposedResourceValue(resource: IResource): Result<JsonValue
|
|
561
|
+
public resolveComposedResourceValue(resource: IResource): Result<JsonValue>;
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Resolves a resource to a composed value by merging matching candidates according to their merge methods.
|
|
565
|
+
* Starting from the highest priority candidates, finds the first "full" candidate and merges all higher
|
|
566
|
+
* priority "partial" candidates into it in ascending order of priority.
|
|
567
|
+
* @param resource - The string id of the resource to resolve.
|
|
568
|
+
* @returns `Success` with the composed JsonValue if successful,
|
|
569
|
+
* or `Failure` with an error message if no candidates match or resolution fails.
|
|
570
|
+
* @public
|
|
571
|
+
*/
|
|
572
|
+
public resolveComposedResourceValue(resource: string): Result<JsonValue>;
|
|
573
|
+
public resolveComposedResourceValue(idOrResource: string | IResource): Result<JsonValue> {
|
|
574
|
+
if (typeof idOrResource === 'string') {
|
|
575
|
+
return this.resourceManager
|
|
576
|
+
.getBuiltResource(idOrResource)
|
|
577
|
+
.onSuccess((resource) => this.resolveComposedResourceValue(resource));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const resource = idOrResource;
|
|
581
|
+
|
|
514
582
|
return this.resolveAllResourceCandidates(resource).onSuccess((candidates) => {
|
|
515
583
|
/* c8 ignore next 3 - defense in depth should never occur */
|
|
516
584
|
if (candidates.length === 0) {
|
|
@@ -581,6 +649,24 @@ export class ResourceResolver {
|
|
|
581
649
|
});
|
|
582
650
|
}
|
|
583
651
|
|
|
652
|
+
/**
|
|
653
|
+
* {@inheritDoc IResourceResolver.withContext}
|
|
654
|
+
*/
|
|
655
|
+
public withContext(context: Record<string, string>): Result<ResourceResolver> {
|
|
656
|
+
const { resourceManager, qualifierTypes, options } = this;
|
|
657
|
+
return ValidatingSimpleContextQualifierProvider.create({
|
|
658
|
+
qualifiers: this.qualifiers,
|
|
659
|
+
qualifierValues: context
|
|
660
|
+
}).onSuccess((contextQualifierProvider) => {
|
|
661
|
+
return ResourceResolver.create({
|
|
662
|
+
resourceManager,
|
|
663
|
+
qualifierTypes,
|
|
664
|
+
options,
|
|
665
|
+
contextQualifierProvider
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
584
670
|
/**
|
|
585
671
|
* Clears all caches (condition, condition set, and decision), forcing all cached items
|
|
586
672
|
* to be re-evaluated on next access. This should be called when the context changes and cached
|