@fgv/ts-res 5.0.1-8 → 5.0.1

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.
Files changed (162) hide show
  1. package/dist/index.browser.js +53 -0
  2. package/dist/index.js +45 -0
  3. package/dist/packlets/bundle/bundleBuilder.js +130 -0
  4. package/dist/packlets/bundle/bundleLoader.js +101 -0
  5. package/dist/packlets/bundle/bundleNormalizer.js +138 -0
  6. package/dist/packlets/bundle/bundleUtils.js +113 -0
  7. package/dist/packlets/bundle/convert.js +75 -0
  8. package/dist/packlets/bundle/index.js +42 -0
  9. package/dist/packlets/bundle/model.js +23 -0
  10. package/dist/packlets/common/conditions.js +47 -0
  11. package/dist/packlets/common/convert.js +172 -0
  12. package/dist/packlets/common/helpers/conditions.js +66 -0
  13. package/dist/packlets/common/helpers/context.js +70 -0
  14. package/dist/packlets/common/helpers/index.js +26 -0
  15. package/dist/packlets/common/helpers/qualifierDefaultValues.js +66 -0
  16. package/dist/packlets/common/helpers/resources.js +93 -0
  17. package/dist/packlets/common/index.js +28 -0
  18. package/dist/packlets/common/resources.js +27 -0
  19. package/dist/packlets/common/validate/conditions.js +544 -0
  20. package/dist/packlets/common/validate/index.js +26 -0
  21. package/dist/packlets/common/validate/regularExpressions.js +84 -0
  22. package/dist/packlets/common/validate/resources.js +209 -0
  23. package/dist/packlets/conditions/condition.js +252 -0
  24. package/dist/packlets/conditions/conditionCollector.js +69 -0
  25. package/dist/packlets/conditions/conditionDecls.js +23 -0
  26. package/dist/packlets/conditions/conditionSet.js +247 -0
  27. package/dist/packlets/conditions/conditionSetCollector.js +89 -0
  28. package/dist/packlets/conditions/conditionSetDecls.js +23 -0
  29. package/dist/packlets/conditions/conditionToken.js +147 -0
  30. package/dist/packlets/conditions/convert/conditionSetDecls.js +51 -0
  31. package/dist/packlets/conditions/convert/decls.js +75 -0
  32. package/dist/packlets/conditions/convert/index.js +24 -0
  33. package/dist/packlets/conditions/index.js +31 -0
  34. package/dist/packlets/config/common.js +78 -0
  35. package/dist/packlets/config/configInitFactory.js +258 -0
  36. package/dist/packlets/config/convert.js +56 -0
  37. package/dist/packlets/config/index.browser.js +34 -0
  38. package/dist/packlets/config/index.js +28 -0
  39. package/dist/packlets/config/json.js +23 -0
  40. package/dist/packlets/config/predefined/default.js +138 -0
  41. package/dist/packlets/config/predefined/extended.js +190 -0
  42. package/dist/packlets/config/predefined/index.js +25 -0
  43. package/dist/packlets/config/systemConfiguration.js +147 -0
  44. package/dist/packlets/context/contextDecls.js +23 -0
  45. package/dist/packlets/context/contextToken.js +202 -0
  46. package/dist/packlets/context/convert/decls.js +91 -0
  47. package/dist/packlets/context/convert/index.js +23 -0
  48. package/dist/packlets/context/index.js +26 -0
  49. package/dist/packlets/decisions/abstractDecision.js +68 -0
  50. package/dist/packlets/decisions/abstractDecisionCollector.js +90 -0
  51. package/dist/packlets/decisions/candidate.js +76 -0
  52. package/dist/packlets/decisions/common.js +23 -0
  53. package/dist/packlets/decisions/concreteDecision.js +111 -0
  54. package/dist/packlets/decisions/decision.js +126 -0
  55. package/dist/packlets/decisions/index.js +28 -0
  56. package/dist/packlets/import/fsItem.js +134 -0
  57. package/dist/packlets/import/importContext.js +129 -0
  58. package/dist/packlets/import/importManager.js +129 -0
  59. package/dist/packlets/import/importable.js +32 -0
  60. package/dist/packlets/import/importers/collectionImporter.js +118 -0
  61. package/dist/packlets/import/importers/fsItemImporter.js +116 -0
  62. package/dist/packlets/import/importers/importer.js +23 -0
  63. package/dist/packlets/import/importers/index.browser.js +29 -0
  64. package/dist/packlets/import/importers/index.js +27 -0
  65. package/dist/packlets/import/importers/jsonImporter.js +94 -0
  66. package/dist/packlets/import/importers/pathImporter.js +84 -0
  67. package/dist/packlets/import/index.browser.js +34 -0
  68. package/dist/packlets/import/index.js +28 -0
  69. package/dist/packlets/qualifier-types/config/convert.js +124 -0
  70. package/dist/packlets/qualifier-types/config/index.js +25 -0
  71. package/dist/packlets/qualifier-types/config/json.js +32 -0
  72. package/dist/packlets/qualifier-types/convert.js +56 -0
  73. package/dist/packlets/qualifier-types/helpers.js +82 -0
  74. package/dist/packlets/qualifier-types/index.js +32 -0
  75. package/dist/packlets/qualifier-types/languageQualifierType.js +142 -0
  76. package/dist/packlets/qualifier-types/literalQualifierType.js +200 -0
  77. package/dist/packlets/qualifier-types/literalValueHierarchy.js +236 -0
  78. package/dist/packlets/qualifier-types/qualifierType.js +184 -0
  79. package/dist/packlets/qualifier-types/qualifierTypeCollector.js +66 -0
  80. package/dist/packlets/qualifier-types/territoryQualifierType.js +200 -0
  81. package/dist/packlets/qualifiers/convert/decls.js +70 -0
  82. package/dist/packlets/qualifiers/convert/index.js +24 -0
  83. package/dist/packlets/qualifiers/convert/qualifier.js +40 -0
  84. package/dist/packlets/qualifiers/index.js +29 -0
  85. package/dist/packlets/qualifiers/qualifier.js +108 -0
  86. package/dist/packlets/qualifiers/qualifierCollector.js +123 -0
  87. package/dist/packlets/qualifiers/qualifierDecl.js +23 -0
  88. package/dist/packlets/qualifiers/qualifierDefaultValueDecls.js +23 -0
  89. package/dist/packlets/qualifiers/qualifierDefaultValueToken.js +175 -0
  90. package/dist/packlets/resource-json/compiled/common.js +23 -0
  91. package/dist/packlets/resource-json/compiled/convert.js +132 -0
  92. package/dist/packlets/resource-json/compiled/index.js +26 -0
  93. package/dist/packlets/resource-json/compiled/json.js +2 -0
  94. package/dist/packlets/resource-json/convert.js +171 -0
  95. package/dist/packlets/resource-json/helpers.js +241 -0
  96. package/dist/packlets/resource-json/index.js +31 -0
  97. package/dist/packlets/resource-json/json.js +36 -0
  98. package/dist/packlets/resource-json/normalized.js +23 -0
  99. package/dist/packlets/resource-json/resourceDeclCollection.js +88 -0
  100. package/dist/packlets/resource-json/resourceDeclContainer.js +23 -0
  101. package/dist/packlets/resource-json/resourceDeclTree.js +94 -0
  102. package/dist/packlets/resource-types/config/convert.js +35 -0
  103. package/dist/packlets/resource-types/config/index.js +25 -0
  104. package/dist/packlets/resource-types/config/json.js +23 -0
  105. package/dist/packlets/resource-types/helpers.js +42 -0
  106. package/dist/packlets/resource-types/index.js +28 -0
  107. package/dist/packlets/resource-types/jsonResourceType.js +66 -0
  108. package/dist/packlets/resource-types/resourceType.js +95 -0
  109. package/dist/packlets/resource-types/resourceTypeCollector.js +61 -0
  110. package/dist/packlets/resources/candidateReducer.js +246 -0
  111. package/dist/packlets/resources/candidateValue.js +92 -0
  112. package/dist/packlets/resources/candidateValueCollector.js +94 -0
  113. package/dist/packlets/resources/common.js +23 -0
  114. package/dist/packlets/resources/deltaGenerator.js +338 -0
  115. package/dist/packlets/resources/index.js +31 -0
  116. package/dist/packlets/resources/resource.js +207 -0
  117. package/dist/packlets/resources/resourceBuilder.js +183 -0
  118. package/dist/packlets/resources/resourceCandidate.js +216 -0
  119. package/dist/packlets/resources/resourceManagerBuilder.js +890 -0
  120. package/dist/packlets/runtime/cacheListener.js +58 -0
  121. package/dist/packlets/runtime/cacheMetrics.js +149 -0
  122. package/dist/packlets/runtime/compiledResourceCollection.js +473 -0
  123. package/dist/packlets/runtime/conditionSetResolutionResult.js +134 -0
  124. package/dist/packlets/runtime/context/contextQualifierProvider.js +29 -0
  125. package/dist/packlets/runtime/context/contextQualifierProviderValidator.js +146 -0
  126. package/dist/packlets/runtime/context/index.js +26 -0
  127. package/dist/packlets/runtime/context/simpleContextQualifierProvider.js +196 -0
  128. package/dist/packlets/runtime/context/validatingSimpleContextQualifierProvider.js +60 -0
  129. package/dist/packlets/runtime/iResourceManager.js +23 -0
  130. package/dist/packlets/runtime/index.js +34 -0
  131. package/dist/packlets/runtime/resource-tree/common.js +23 -0
  132. package/dist/packlets/runtime/resource-tree/index.js +26 -0
  133. package/dist/packlets/runtime/resource-tree/readOnlyResourceTree.js +216 -0
  134. package/dist/packlets/runtime/resource-tree/resourceTreeChildren.js +97 -0
  135. package/dist/packlets/runtime/resource-tree/resourceTreeChildrenValidator.js +161 -0
  136. package/dist/packlets/runtime/resourceResolver.js +481 -0
  137. package/dist/packlets/runtime/resourceTreeResolver.js +220 -0
  138. package/dist/packlets/runtime/validate.js +47 -0
  139. package/dist/packlets/zip-archive/convert.js +100 -0
  140. package/dist/packlets/zip-archive/index.browser.js +54 -0
  141. package/dist/packlets/zip-archive/index.js +47 -0
  142. package/dist/packlets/zip-archive/json.js +23 -0
  143. package/dist/packlets/zip-archive/types.js +36 -0
  144. package/dist/packlets/zip-archive/zipArchiveCreator.js +191 -0
  145. package/dist/packlets/zip-archive/zipArchiveFormat.js +140 -0
  146. package/dist/packlets/zip-archive/zipArchiveLoader.js +282 -0
  147. package/dist/test/data/sample.json +32 -0
  148. package/dist/test/unit/qualifier-types/testQualifierType.js +76 -0
  149. package/dist/test/unit/resource-types/testDerivedResourceType.js +109 -0
  150. package/dist/test/unit/resources/deltaGenerator.helpers.js +282 -0
  151. package/dist/tsdoc-metadata.json +1 -1
  152. package/lib/index.browser.d.ts +24 -0
  153. package/lib/index.browser.js +116 -0
  154. package/lib/packlets/config/index.browser.d.ts +7 -0
  155. package/lib/packlets/config/index.browser.js +74 -0
  156. package/lib/packlets/import/importers/index.browser.d.ts +4 -0
  157. package/lib/packlets/import/importers/index.browser.js +45 -0
  158. package/lib/packlets/import/index.browser.d.ts +7 -0
  159. package/lib/packlets/import/index.browser.js +74 -0
  160. package/lib/packlets/zip-archive/index.browser.d.ts +27 -0
  161. package/lib/packlets/zip-archive/index.browser.js +102 -0
  162. package/package.json +21 -7
@@ -0,0 +1,32 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import * as Config from './config';
23
+ import * as Convert from './convert';
24
+ export * from './qualifierType';
25
+ export * from './languageQualifierType';
26
+ export * from './literalQualifierType';
27
+ export * from './literalValueHierarchy';
28
+ export * from './territoryQualifierType';
29
+ export * from './qualifierTypeCollector';
30
+ export * from './helpers';
31
+ export { Config, Convert };
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,142 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { captureResult, succeed } from '@fgv/ts-utils';
23
+ import { Bcp47 } from '@fgv/ts-bcp47';
24
+ import { Convert, NoMatch, Validate } from '../common';
25
+ import { QualifierType } from './qualifierType';
26
+ import * as Config from './config';
27
+ import { sanitizeJsonObject } from '@fgv/ts-json-base';
28
+ /**
29
+ * {@link QualifierTypes.QualifierType | Qualifier type} which matches BCP-47 language tags
30
+ * applying {@link https://github.com/ErikFortune/fgv/tree/main/libraries/ts-bcp47#tag-matching | similarity matching}.
31
+ * Accepts a list of language tags in the context by default.
32
+ * @public
33
+ */
34
+ export class LanguageQualifierType extends QualifierType {
35
+ /**
36
+ * Creates a new instance of a {@link QualifierTypes.LanguageQualifierType | language qualifier type}.
37
+ * @param name - Optional name for the qualifier type. Defaults to 'language'.
38
+ * @param allowContextList - Optional flag indicating whether the context can be a
39
+ * list of values. Defaults to `true`.
40
+ * @public
41
+ */
42
+ constructor({ name, allowContextList, index }) {
43
+ allowContextList = allowContextList !== false;
44
+ /* c8 ignore next 2 - coverage intermittently drops these two lines even though they're tested */
45
+ name = name !== null && name !== void 0 ? name : 'language';
46
+ const validated = index ? { index: Convert.qualifierTypeIndex.convert(index).orThrow() } : {};
47
+ super(Object.assign({ name,
48
+ allowContextList }, validated));
49
+ /**
50
+ * {@inheritdoc QualifierTypes.IQualifierType.systemTypeName}
51
+ */
52
+ this.systemTypeName = Convert.qualifierTypeName.convert('language').orThrow();
53
+ }
54
+ /**
55
+ * Creates a new instance of a {@link QualifierTypes.LanguageQualifierType | language qualifier type}.
56
+ * @param params - Optional {@link QualifierTypes.ILanguageQualifierTypeCreateParams | parameters}
57
+ * to use when creating the new instance.
58
+ * @returns `Success` with the new {@link QualifierTypes.LanguageQualifierType | language qualifier type}
59
+ * if successful, `Failure` otherwise.
60
+ */
61
+ static create(params) {
62
+ /* c8 ignore next 1 - coverage seems to intermittently miss the branch even though it's tested */
63
+ params = params !== null && params !== void 0 ? params : {};
64
+ return captureResult(() => new LanguageQualifierType(params));
65
+ }
66
+ /**
67
+ * Creates a new {@link QualifierTypes.LanguageQualifierType | LanguageQualifierType} from a configuration object.
68
+ * @param config - The {@link QualifierTypes.Config.IQualifierTypeConfig | configuration object} containing
69
+ * the name, systemType, and optional language-specific configuration.
70
+ * @returns `Success` with the new {@link QualifierTypes.LanguageQualifierType | LanguageQualifierType}
71
+ * if successful, `Failure` with an error message otherwise.
72
+ * @public
73
+ */
74
+ static createFromConfig(config) {
75
+ var _a;
76
+ return sanitizeJsonObject({
77
+ name: config.name,
78
+ allowContextList: ((_a = config.configuration) === null || _a === void 0 ? void 0 : _a.allowContextList) === true
79
+ }).onSuccess(LanguageQualifierType.create);
80
+ }
81
+ /**
82
+ * {@inheritdoc QualifierTypes.IQualifierType.isValidConditionValue}
83
+ */
84
+ isValidConditionValue(value) {
85
+ return Bcp47.tag(value)
86
+ .onSuccess((tag) => succeed(tag.isValid))
87
+ .orDefault(false);
88
+ }
89
+ /**
90
+ * Gets a {@link QualifierTypes.Config.ISystemLanguageQualifierTypeConfig | strongly typed configuration object}
91
+ * for this qualifier type.
92
+ * @returns `Success` with the configuration if successful, `Failure` with an error message otherwise.
93
+ */
94
+ getConfiguration() {
95
+ return this.getConfigurationJson().onSuccess((json) => Config.Convert.systemLanguageQualifierTypeConfig.convert(json));
96
+ }
97
+ /**
98
+ * {@inheritdoc QualifierTypes.IQualifierType.getConfigurationJson}
99
+ */
100
+ getConfigurationJson() {
101
+ return succeed({
102
+ name: this.name,
103
+ systemType: 'language',
104
+ configuration: {
105
+ allowContextList: this.allowContextList
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * {@inheritdoc QualifierTypes.IQualifierType.validateConfigurationJson}
111
+ */
112
+ validateConfigurationJson(from) {
113
+ return Config.Convert.systemLanguageQualifierTypeConfig.convert(from);
114
+ }
115
+ /**
116
+ * Validates a {@link QualifierTypes.Config.ISystemLanguageQualifierTypeConfig | strongly typed configuration object}
117
+ * for this qualifier type.
118
+ * @param from - The unknown data to validate as a configuration object.
119
+ * @returns `Success` with the validated configuration if successful, `Failure` with an error message otherwise.
120
+ */
121
+ validateConfiguration(from) {
122
+ return Config.Convert.systemLanguageQualifierTypeConfig.convert(from);
123
+ }
124
+ /**
125
+ * Matches a single language condition against a single language context value using
126
+ * {@link https://github.com/ErikFortune/fgv/tree/main/libraries/ts-bcp47#tag-matching | similarity matching}.
127
+ * @param condition - The language condition value to match.
128
+ * @param context - The language context value to match against.
129
+ * @param operator - The operator to use for the match. Must be 'matches'.
130
+ * @returns The match score, or `noMatch` if the match fails.
131
+ */
132
+ _matchOne(condition, context, operator) {
133
+ if (operator === 'matches') {
134
+ const similarity = Bcp47.similarity(condition, context).orDefault(Bcp47.tagSimilarity.none);
135
+ if (similarity > 0.0 && Validate.isValidQualifierMatchScore(similarity)) {
136
+ return similarity;
137
+ }
138
+ }
139
+ return NoMatch;
140
+ }
141
+ }
142
+ //# sourceMappingURL=languageQualifierType.js.map
@@ -0,0 +1,200 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { captureResult, mapResults, fail, succeed } from '@fgv/ts-utils';
23
+ import { Convert, NoMatch, PerfectMatch, Validate } from '../common';
24
+ import { QualifierType } from './qualifierType';
25
+ import { LiteralValueHierarchy } from './literalValueHierarchy';
26
+ import * as Config from './config';
27
+ import { sanitizeJsonObject } from '@fgv/ts-json-base';
28
+ /**
29
+ * A {@link QualifierTypes.QualifierType | qualifier} that matches a literal value,
30
+ * optionally case-sensitive or matching against an ordered list of values at runtime.
31
+ * @public
32
+ */
33
+ export class LiteralQualifierType extends QualifierType {
34
+ /**
35
+ * Constructs a new {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType}.
36
+ * @param name - Optional name for the qualifier type. Defaults to 'literal'.
37
+ * @param caseSensitive - Optional flag indicating whether the match should be
38
+ * case-sensitive. Defaults to `false`.
39
+ * @param allowContextList - Optional flag indicating whether the context can be a
40
+ * list of values. Defaults to `false`.
41
+ * @public
42
+ */
43
+ constructor({ name, caseSensitive, allowContextList, enumeratedValues, hierarchy, index }) {
44
+ allowContextList = allowContextList !== false;
45
+ super({
46
+ name: name !== null && name !== void 0 ? name : 'literal',
47
+ allowContextList,
48
+ index: index !== undefined ? Convert.qualifierTypeIndex.convert(index).orThrow() : undefined
49
+ });
50
+ /**
51
+ * {@inheritdoc QualifierTypes.IQualifierType.systemTypeName}
52
+ */
53
+ this.systemTypeName = Convert.qualifierTypeName.convert('literal').orThrow();
54
+ this.caseSensitive = caseSensitive === true;
55
+ this.enumeratedValues = enumeratedValues
56
+ ? mapResults(Array.from(enumeratedValues).map(LiteralQualifierType.toLiteralConditionValue)).orThrow()
57
+ : undefined;
58
+ if (hierarchy) {
59
+ /* c8 ignore next 5 - defensive coding: enumeratedValues ?? [] fallback enables open values mode */
60
+ this.hierarchy = LiteralValueHierarchy.create({
61
+ values: enumeratedValues !== null && enumeratedValues !== void 0 ? enumeratedValues : [],
62
+ hierarchy: hierarchy
63
+ }).orThrow();
64
+ }
65
+ }
66
+ /**
67
+ * Determines whether a value is a valid condition value for a literal qualifier.
68
+ * The {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType} accepts
69
+ * any identifier as a valid condition value.
70
+ * @param value - The value to validate.
71
+ * @returns `true` if the value is a valid condition value, `false` otherwise.
72
+ */
73
+ isValidConditionValue(value) {
74
+ if (this.enumeratedValues) {
75
+ if (this.caseSensitive) {
76
+ return this.enumeratedValues.includes(value);
77
+ }
78
+ return this.enumeratedValues.some((v) => v.toLowerCase() === value.toLowerCase());
79
+ }
80
+ return LiteralQualifierType.isValidLiteralConditionValue(value);
81
+ }
82
+ /**
83
+ * {@inheritdoc QualifierTypes.IQualifierType.isPotentialMatch}
84
+ */
85
+ isPotentialMatch(conditionValue, contextValue) {
86
+ if (this.isValidConditionValue(conditionValue) && this.isValidContextValue(contextValue)) {
87
+ if (this._matchOne(conditionValue, contextValue, 'matches') !== NoMatch) {
88
+ return true;
89
+ }
90
+ if (this.hierarchy) {
91
+ return this.hierarchy.isAncestor(conditionValue, contextValue);
92
+ }
93
+ }
94
+ return false;
95
+ }
96
+ /**
97
+ * Gets a {@link QualifierTypes.Config.ISystemLiteralQualifierTypeConfig | strongly typed configuration object}
98
+ * for this qualifier type.
99
+ * @returns `Success` with the configuration if successful, `Failure` with an error message otherwise.
100
+ */
101
+ getConfiguration() {
102
+ return this.getConfigurationJson().onSuccess((json) => Config.Convert.systemLiteralQualifierTypeConfig.convert(json));
103
+ }
104
+ /**
105
+ * {@inheritdoc QualifierTypes.IQualifierType.getConfigurationJson}
106
+ */
107
+ getConfigurationJson() {
108
+ const hierarchy = this.hierarchy ? { hierarchy: this.hierarchy.asRecord() } : {};
109
+ const enumeratedValues = this.enumeratedValues
110
+ ? { enumeratedValues: [...this.enumeratedValues] }
111
+ : {};
112
+ return succeed({
113
+ name: this.name,
114
+ systemType: 'literal',
115
+ configuration: Object.assign(Object.assign({ allowContextList: this.allowContextList, caseSensitive: this.caseSensitive }, enumeratedValues), hierarchy)
116
+ });
117
+ }
118
+ /**
119
+ * {@inheritdoc QualifierTypes.IQualifierType.validateConfigurationJson}
120
+ */
121
+ validateConfigurationJson(from) {
122
+ return Config.Convert.systemLiteralQualifierTypeConfig.convert(from);
123
+ }
124
+ /**
125
+ * Validates a {@link QualifierTypes.Config.ISystemLiteralQualifierTypeConfig | strongly typed configuration object}
126
+ * for this qualifier type.
127
+ * @param from - The unknown data to validate as a configuration object.
128
+ * @returns `Success` with the validated configuration if successful, `Failure` with an error message otherwise.
129
+ */
130
+ validateConfiguration(from) {
131
+ return Config.Convert.systemLiteralQualifierTypeConfig.convert(from);
132
+ }
133
+ /**
134
+ * {@inheritdoc QualifierTypes.QualifierType._matchOne}
135
+ */
136
+ _matchOne(condition, context, __operator) {
137
+ if (this.hierarchy) {
138
+ return this.hierarchy.match(condition, context, __operator);
139
+ }
140
+ if (this.caseSensitive) {
141
+ return condition === context ? PerfectMatch : NoMatch;
142
+ }
143
+ else {
144
+ return condition.toLowerCase() === context.toLowerCase() ? PerfectMatch : NoMatch;
145
+ }
146
+ }
147
+ /**
148
+ * Creates a new {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType}.
149
+ * @param params - Optional {@link QualifierTypes.ILiteralQualifierTypeCreateParams | parameters}
150
+ * to use when creating the new instance.
151
+ * @returns `Success` with the new {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType}
152
+ * if successful, `Failure` with an error message otherwise.
153
+ * @public
154
+ */
155
+ static create(params) {
156
+ return captureResult(() => new LiteralQualifierType(params !== null && params !== void 0 ? params : {}));
157
+ }
158
+ /**
159
+ * Creates a new {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType} from a configuration object.
160
+ * @param config - The {@link QualifierTypes.Config.IQualifierTypeConfig | configuration object} containing
161
+ * the name, systemType, and optional literal-specific configuration including case sensitivity,
162
+ * enumerated values, and hierarchy information.
163
+ * @returns `Success` with the new {@link QualifierTypes.LiteralQualifierType | LiteralQualifierType}
164
+ * if successful, `Failure` with an error message otherwise.
165
+ * @public
166
+ */
167
+ static createFromConfig(config) {
168
+ var _a;
169
+ const literalConfig = (_a = config.configuration) !== null && _a !== void 0 ? _a : {};
170
+ return sanitizeJsonObject({
171
+ name: config.name,
172
+ allowContextList: literalConfig.allowContextList === true,
173
+ caseSensitive: literalConfig.caseSensitive,
174
+ enumeratedValues: literalConfig.enumeratedValues,
175
+ hierarchy: literalConfig.hierarchy
176
+ }).onSuccess(LiteralQualifierType.create);
177
+ }
178
+ /**
179
+ * Checks if the given value is a valid literal condition value.
180
+ * @param from - The value to validate.
181
+ * @returns `true` if the value is a valid literal condition value, otherwise `false`.
182
+ * @public
183
+ */
184
+ static isValidLiteralConditionValue(from) {
185
+ return Validate.RegularExpressions.identifier.test(from);
186
+ }
187
+ /**
188
+ * Converts a string to a {@link QualifierConditionValue | literal condition value}.
189
+ * @param from - The string to convert.
190
+ * @returns `Success` with the converted value if valid, or `Failure` with an error
191
+ * message if not.
192
+ * @public
193
+ */
194
+ static toLiteralConditionValue(from) {
195
+ return LiteralQualifierType.isValidLiteralConditionValue(from)
196
+ ? succeed(from)
197
+ : fail(`${from}: invalid literal condition value.`);
198
+ }
199
+ }
200
+ //# sourceMappingURL=literalQualifierType.js.map
@@ -0,0 +1,236 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { captureResult, MessageAggregator, succeed, fail } from '@fgv/ts-utils';
23
+ import { NoMatch, PerfectMatch, Convert as CommonConverters } from '../common';
24
+ /**
25
+ * A class that implements a hierarchy of literal values. The hierarchy is defined as a
26
+ * tree, where each value can have multiple children but only one parent. The root of the
27
+ * tree has no parent. The hierarchy is used to determine the relationship between values
28
+ * when matching conditions and contexts.
29
+ *
30
+ * @remarks
31
+ * The hierarchy can be created in two modes:
32
+ * - **Constrained mode**: When enumerated values are provided, only those values are allowed
33
+ * in the hierarchy and matching operations.
34
+ * - **Open values mode**: When no enumerated values are provided, all values referenced in
35
+ * the hierarchy are automatically collected and used. This allows for flexible hierarchies
36
+ * where any value can be used in matching operations.
37
+ * @public
38
+ */
39
+ export class LiteralValueHierarchy {
40
+ constructor(params) {
41
+ const result = LiteralValueHierarchy._buildValuesFromHierarchy(params);
42
+ this.values = result.orThrow();
43
+ this.isOpenValues = !params.values || params.values.length === 0;
44
+ }
45
+ /**
46
+ * Creates a new {@link QualifierTypes.LiteralValueHierarchy | LiteralValueHierarchy} instance.
47
+ * @param params - The {@link QualifierTypes.ILiteralValueHierarchyCreateParams | parameters}
48
+ * used to create the hierarchy.
49
+ * @returns `Success` with the new hierarchy or `Failure` with an error message.
50
+ */
51
+ static create(params) {
52
+ return captureResult(() => new LiteralValueHierarchy(params));
53
+ }
54
+ /**
55
+ * Checks if a value exists in the hierarchy.
56
+ * @param value - The value to check.
57
+ * @returns `true` if the value exists in the hierarchy, `false` otherwise.
58
+ */
59
+ hasValue(value) {
60
+ return this.values.has(value);
61
+ }
62
+ /**
63
+ * Gets all root values (values with no parent) in the hierarchy.
64
+ * @returns `Success` with an array of root values.
65
+ */
66
+ getRoots() {
67
+ return succeed(Array.from(this.values.values())
68
+ .filter((v) => !v.parent)
69
+ .map((v) => v.name));
70
+ }
71
+ /**
72
+ * Gets all ancestors of a value in the hierarchy.
73
+ * @param value - The value to get ancestors for.
74
+ * @returns `Success` with an array of ancestor values, ordered from immediate parent
75
+ * to root, or `Failure` if the value is not in the hierarchy.
76
+ */
77
+ getAncestors(value) {
78
+ const current = this.values.get(value);
79
+ /* c8 ignore next 3 - functional error case tested but coverage intermittently missed */
80
+ if (!current) {
81
+ return fail(`${value}: not found in hierarchy`);
82
+ }
83
+ const ancestors = [];
84
+ let ancestor = current.parent;
85
+ while (ancestor) {
86
+ ancestors.push(ancestor.name);
87
+ ancestor = ancestor.parent;
88
+ }
89
+ return succeed(ancestors);
90
+ }
91
+ /**
92
+ * Determines if a value is an ancestor of a possible ancestor value.
93
+ * @param value - The value to check.
94
+ * @param possibleAncestor - The possible ancestor value.
95
+ * @returns `true` if the value is an ancestor of the possible ancestor, `false` otherwise.
96
+ */
97
+ isAncestor(value, possibleAncestor) {
98
+ const ancestors = this.getAncestors(value);
99
+ if (ancestors.isSuccess()) {
100
+ return ancestors.value.includes(possibleAncestor);
101
+ }
102
+ /* c8 ignore next 2 - edge case: ancestor validation fallback */
103
+ return false;
104
+ }
105
+ /**
106
+ * Gets all descendants of a value in the hierarchy.
107
+ * @param value - The value to get descendants for.
108
+ * @returns `Success` with an array of descendant values, or `Failure` if the value
109
+ * is not in the hierarchy.
110
+ */
111
+ getDescendants(value) {
112
+ const current = this.values.get(value);
113
+ if (!current) {
114
+ return fail(`${value}: not found in hierarchy`);
115
+ }
116
+ const descendants = [];
117
+ if (current.children) {
118
+ for (const childName of current.children) {
119
+ descendants.push(childName);
120
+ const childDescendants = this.getDescendants(childName);
121
+ if (childDescendants.isSuccess()) {
122
+ descendants.push(...childDescendants.value);
123
+ }
124
+ }
125
+ }
126
+ return succeed(descendants);
127
+ }
128
+ match(condition, context, __operator) {
129
+ var _a;
130
+ // For open hierarchies, skip validation and allow any values
131
+ if (!this.isOpenValues) {
132
+ // Validate that both condition and context exist in the hierarchy
133
+ /* c8 ignore next 3 - functional error case tested but coverage intermittently missed */
134
+ if (!this.values.has(condition)) {
135
+ return NoMatch;
136
+ }
137
+ /* c8 ignore next 3 - functional error case tested but coverage intermittently missed */
138
+ if (!this.values.has(context)) {
139
+ return NoMatch;
140
+ }
141
+ }
142
+ if (condition === context) {
143
+ return PerfectMatch;
144
+ }
145
+ // For open hierarchies, if values aren't in the hierarchy, treat as no match
146
+ // but don't fail validation
147
+ const values = this.values;
148
+ /* c8 ignore next 1 - ? is defense in depth */
149
+ let value = (_a = values.get(context)) === null || _a === void 0 ? void 0 : _a.parent;
150
+ /* c8 ignore next 2 - functional error case tested but coverage intermittently missed */
151
+ if (!value) {
152
+ return NoMatch;
153
+ }
154
+ let score = PerfectMatch;
155
+ while (value) {
156
+ score *= 0.9;
157
+ if (value.name === condition) {
158
+ return CommonConverters.qualifierMatchScore.convert(score).orDefault(NoMatch);
159
+ }
160
+ value = value.parent;
161
+ }
162
+ return NoMatch;
163
+ }
164
+ static _buildValuesFromHierarchy(params) {
165
+ var _a;
166
+ const errors = new MessageAggregator();
167
+ const values = params.values;
168
+ /* c8 ignore next 1 - defense in depth */
169
+ const hierarchy = (_a = params.hierarchy) !== null && _a !== void 0 ? _a : {};
170
+ // If no values are provided, collect all values from the hierarchy
171
+ let allValues;
172
+ if (!values || values.length === 0) {
173
+ const hierarchyValues = new Set();
174
+ // Add all keys and values from the hierarchy
175
+ Object.keys(hierarchy).forEach((key) => hierarchyValues.add(key));
176
+ Object.values(hierarchy).forEach((value) => hierarchyValues.add(value));
177
+ allValues = Array.from(hierarchyValues);
178
+ }
179
+ else {
180
+ allValues = [...values];
181
+ }
182
+ const valueMap = new Map(allValues.map((name) => [name, { name }]));
183
+ if (hierarchy) {
184
+ for (const [valueName, parentName] of Object.entries(hierarchy)) {
185
+ const value = valueMap.get(valueName);
186
+ if (!value) {
187
+ errors.addMessage(`${valueName}: invalid literal value.`);
188
+ continue;
189
+ }
190
+ const parent = valueMap.get(parentName);
191
+ if (!parent) {
192
+ errors.addMessage(`${valueName}: parent ${parentName} is invalid literal value.`);
193
+ continue;
194
+ }
195
+ let ancestor = parent.parent;
196
+ let circular = false;
197
+ const ancestors = [];
198
+ while (ancestor) {
199
+ /* c8 ignore next 1 - defensive coding: complex circular reference detection intermittently missed */
200
+ ancestors.push(ancestor.name);
201
+ /* c8 ignore next 1 - defensive coding: ancestor.parent === value is very difficult to reach */
202
+ if (ancestor.parent === parent || ancestor.parent === value) {
203
+ /* c8 ignore next 4 - defensive coding: circular reference error reporting intermittently missed */
204
+ errors.addMessage(`${valueName}: Circular reference detected: ${ancestors.join('->')}`);
205
+ circular = true;
206
+ break;
207
+ }
208
+ ancestor = ancestor.parent;
209
+ }
210
+ /* c8 ignore next 3 - defensive coding: circular reference handling intermittently missed */
211
+ if (circular) {
212
+ continue;
213
+ }
214
+ value.parent = parent;
215
+ if (parent.children) {
216
+ parent.children.push(value.name);
217
+ }
218
+ else {
219
+ parent.children = [value.name];
220
+ }
221
+ }
222
+ }
223
+ return errors.returnOrReport(succeed(valueMap));
224
+ }
225
+ /**
226
+ * Converts the hierarchy to a record of parent-child relationships.
227
+ * @returns A record of parent-child relationships.
228
+ */
229
+ asRecord() {
230
+ const entries = Array.from(this.values.values())
231
+ .filter((v) => v.parent !== undefined)
232
+ .map((v) => [v.name, v.parent.name]);
233
+ return Object.fromEntries(entries);
234
+ }
235
+ }
236
+ //# sourceMappingURL=literalValueHierarchy.js.map