@fgv/ts-res 5.0.1-1 → 5.0.1-10

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 (196) 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/ts-res.d.ts +15 -15
  152. package/dist/tsdoc-metadata.json +1 -1
  153. package/lib/index.browser.d.ts +24 -0
  154. package/lib/index.browser.js +116 -0
  155. package/lib/packlets/common/helpers/resources.d.ts +1 -1
  156. package/lib/packlets/common/helpers/resources.js +3 -2
  157. package/lib/packlets/common/validate/conditions.js +14 -14
  158. package/lib/packlets/common/validate/resources.js +7 -7
  159. package/lib/packlets/conditions/conditionSet.js +1 -1
  160. package/lib/packlets/config/common.js +1 -0
  161. package/lib/packlets/config/configInitFactory.js +1 -0
  162. package/lib/packlets/config/index.browser.d.ts +7 -0
  163. package/lib/packlets/config/index.browser.js +74 -0
  164. package/lib/packlets/config/systemConfiguration.js +1 -0
  165. package/lib/packlets/context/convert/decls.js +1 -1
  166. package/lib/packlets/import/importers/collectionImporter.js +1 -1
  167. package/lib/packlets/import/importers/index.browser.d.ts +4 -0
  168. package/lib/packlets/import/importers/index.browser.js +45 -0
  169. package/lib/packlets/import/importers/jsonImporter.js +1 -1
  170. package/lib/packlets/import/index.browser.d.ts +7 -0
  171. package/lib/packlets/import/index.browser.js +74 -0
  172. package/lib/packlets/qualifier-types/helpers.js +5 -4
  173. package/lib/packlets/qualifier-types/languageQualifierType.d.ts +4 -4
  174. package/lib/packlets/qualifier-types/literalQualifierType.d.ts +4 -4
  175. package/lib/packlets/qualifier-types/literalQualifierType.js +1 -1
  176. package/lib/packlets/qualifier-types/literalValueHierarchy.js +7 -6
  177. package/lib/packlets/qualifier-types/qualifierType.d.ts +5 -5
  178. package/lib/packlets/qualifier-types/qualifierType.js +1 -0
  179. package/lib/packlets/qualifier-types/territoryQualifierType.d.ts +4 -4
  180. package/lib/packlets/qualifier-types/territoryQualifierType.js +1 -1
  181. package/lib/packlets/resource-json/helpers.js +4 -0
  182. package/lib/packlets/resource-types/helpers.js +1 -0
  183. package/lib/packlets/resource-types/resourceType.js +1 -0
  184. package/lib/packlets/resources/candidateValue.js +1 -0
  185. package/lib/packlets/resources/candidateValueCollector.js +4 -2
  186. package/lib/packlets/resources/deltaGenerator.js +8 -10
  187. package/lib/packlets/resources/resourceBuilder.js +4 -0
  188. package/lib/packlets/resources/resourceManagerBuilder.js +6 -0
  189. package/lib/packlets/runtime/compiledResourceCollection.js +1 -0
  190. package/lib/packlets/runtime/context/simpleContextQualifierProvider.js +4 -3
  191. package/lib/packlets/runtime/resource-tree/resourceTreeChildren.js +3 -0
  192. package/lib/packlets/runtime/resourceResolver.js +2 -4
  193. package/lib/packlets/runtime/resourceTreeResolver.js +2 -0
  194. package/lib/packlets/zip-archive/index.browser.d.ts +27 -0
  195. package/lib/packlets/zip-archive/index.browser.js +102 -0
  196. package/package.json +34 -20
@@ -0,0 +1,481 @@
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, fail, succeed } from '@fgv/ts-utils';
23
+ import { isJsonObject } from '@fgv/ts-json-base';
24
+ import { JsonEditor } from '@fgv/ts-json';
25
+ import { NoMatch } from '../common';
26
+ import { ValidatingSimpleContextQualifierProvider } from './context';
27
+ import { ConditionSetResolutionResult } from './conditionSetResolutionResult';
28
+ /**
29
+ * High-performance runtime resource resolver with O(1) condition caching.
30
+ * Resolves resources for a given context by evaluating conditions against qualifier values
31
+ * and caching results for optimal performance.
32
+ * @public
33
+ */
34
+ export class ResourceResolver {
35
+ /**
36
+ * The readonly qualifier collector that provides qualifier implementations.
37
+ */
38
+ get qualifiers() {
39
+ return this.contextQualifierProvider.qualifiers;
40
+ }
41
+ /**
42
+ * The resource IDs that this resolver can resolve.
43
+ */
44
+ get resourceIds() {
45
+ return this.resourceManager.resourceIds;
46
+ }
47
+ /**
48
+ * The cache array for resolved conditions, indexed by condition index for O(1) lookup.
49
+ * Each entry stores the resolved {@link Runtime.IConditionMatchResult | condition match result} for
50
+ * the corresponding condition.
51
+ */
52
+ get conditionCache() {
53
+ return this._conditionCache;
54
+ }
55
+ /**
56
+ * The cache array for resolved condition sets, indexed by condition set index for O(1) lookup.
57
+ * Each entry stores the resolved ConditionSetResolutionResult for the corresponding condition set.
58
+ */
59
+ get conditionSetCache() {
60
+ return this._conditionSetCache;
61
+ }
62
+ /**
63
+ * The cache array for resolved decisions, indexed by decision index for O(1) lookup.
64
+ * Each entry stores the resolved DecisionResolutionResult for the corresponding decision.
65
+ */
66
+ get decisionCache() {
67
+ return this._decisionCache;
68
+ }
69
+ /**
70
+ * Constructor for a {@link Runtime.ResourceResolver | ResourceResolver} object.
71
+ * @param params - {@link Runtime.IResourceResolverCreateParams | Parameters} used to create the resolver.
72
+ */
73
+ constructor(params) {
74
+ var _a, _b;
75
+ this.resourceManager = params.resourceManager;
76
+ this.qualifierTypes = params.qualifierTypes;
77
+ this.contextQualifierProvider = params.contextQualifierProvider;
78
+ this.options = {
79
+ suppressNullAsDelete: (_b = (_a = params.options) === null || _a === void 0 ? void 0 : _a.suppressNullAsDelete) !== null && _b !== void 0 ? _b : false
80
+ };
81
+ // Initialize condition cache array with size matching the condition collector
82
+ const conditionCollectorSize = this.resourceManager.conditions.size;
83
+ this._conditionCache = new Array(conditionCollectorSize);
84
+ // Initialize condition set cache array with size matching the condition set collector
85
+ const conditionSetCollectorSize = this.resourceManager.conditionSets.size;
86
+ this._conditionSetCache = new Array(conditionSetCollectorSize);
87
+ // Initialize decision cache array with size matching the decision collector
88
+ const decisionCollectorSize = this.resourceManager.decisions.size;
89
+ this._decisionCache = new Array(decisionCollectorSize);
90
+ this._listener = params.listener;
91
+ }
92
+ /**
93
+ * Creates a new {@link Runtime.ResourceResolver | ResourceResolver} object.
94
+ * @param params - {@link Runtime.IResourceResolverCreateParams | Parameters} used to create the resolver.
95
+ * @returns `Success` with the new {@link Runtime.ResourceResolver | ResourceResolver} object if successful,
96
+ * or `Failure` with an error message if not.
97
+ * @public
98
+ */
99
+ static create(params) {
100
+ return captureResult(() => new ResourceResolver(params));
101
+ }
102
+ /**
103
+ * Resolves a condition by evaluating it against the current context.
104
+ * Uses O(1) caching based on the condition's globally unique sequential index.
105
+ * @param condition - The {@link Conditions.Condition | condition} to resolve.
106
+ * @returns `Success` with the {@link QualifierMatchScore | match score} if successful,
107
+ * or `Failure` with an error message if the condition cannot be resolved.
108
+ * @public
109
+ */
110
+ resolveCondition(condition) {
111
+ var _a, _b, _c;
112
+ // Get the condition's index for cache lookup
113
+ const conditionIndex = condition.index;
114
+ if (conditionIndex === undefined) {
115
+ return fail(`Condition "${condition.key}" does not have a valid index`);
116
+ }
117
+ // Check cache first for O(1) lookup
118
+ const cachedResult = this._conditionCache[conditionIndex];
119
+ if (cachedResult !== undefined) {
120
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheHit('condition', conditionIndex);
121
+ return succeed(cachedResult);
122
+ }
123
+ // Resolve the condition by getting qualifier value and evaluating with qualifier type
124
+ const score = this.contextQualifierProvider
125
+ .get(condition.qualifier)
126
+ .onSuccess((qualifierValue) => {
127
+ // Evaluate the condition using the qualifier type's matching logic
128
+ return succeed(condition.qualifier.type.matches(condition.value, qualifierValue, condition.operator));
129
+ })
130
+ .onFailure((err) => {
131
+ var _a;
132
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onContextError(condition.qualifier.name, err);
133
+ return fail(err);
134
+ })
135
+ .orDefault(NoMatch);
136
+ const priority = condition.priority;
137
+ const scoreAsDefault = (_b = condition.scoreAsDefault) !== null && _b !== void 0 ? _b : NoMatch;
138
+ const matchResult = score > NoMatch
139
+ ? { score, priority, matchType: 'match' }
140
+ : scoreAsDefault > NoMatch
141
+ ? { score: scoreAsDefault, priority, matchType: 'matchAsDefault' }
142
+ : { score: NoMatch, priority, matchType: 'noMatch' };
143
+ // Cache the resolved value for future O(1) lookup
144
+ this._conditionCache[conditionIndex] = matchResult;
145
+ (_c = this._listener) === null || _c === void 0 ? void 0 : _c.onCacheMiss('condition', conditionIndex);
146
+ return succeed(matchResult);
147
+ }
148
+ /**
149
+ * Resolves a condition set by evaluating all its constituent conditions against the current context.
150
+ * Uses O(1) caching based on the condition set's globally unique sequential index.
151
+ * @param conditionSet - The {@link Conditions.ConditionSet | condition set} to resolve.
152
+ * @returns `Success` with the {@link Runtime.ConditionSetResolutionResult | resolution result} if successful,
153
+ * or `Failure` with an error message if the condition set cannot be resolved.
154
+ * @public
155
+ */
156
+ resolveConditionSet(conditionSet) {
157
+ var _a, _b;
158
+ // Get the condition set's index for cache lookup
159
+ const conditionSetIndex = conditionSet.index;
160
+ if (conditionSetIndex === undefined) {
161
+ return fail(`ConditionSet "${conditionSet.key}" does not have a valid index`);
162
+ }
163
+ // Check cache first for O(1) lookup
164
+ const cachedResult = this._conditionSetCache[conditionSetIndex];
165
+ if (cachedResult !== undefined) {
166
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheHit('conditionSet', conditionSetIndex);
167
+ return succeed(cachedResult);
168
+ }
169
+ // Resolve all conditions in the condition set
170
+ const conditions = [];
171
+ let matchType = 'match';
172
+ for (const condition of conditionSet.conditions) {
173
+ const { value: conditionResult, message: conditionMessage } = this.resolveCondition(condition);
174
+ /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
175
+ if (conditionMessage !== undefined) {
176
+ (_b = this._listener) === null || _b === void 0 ? void 0 : _b.onCacheError('conditionSet', conditionSetIndex);
177
+ return fail(`Failed to resolve condition "${condition.key}": ${conditionMessage}`);
178
+ }
179
+ conditions.push(conditionResult);
180
+ if (conditionResult.matchType === 'noMatch') {
181
+ // Cache the failure result
182
+ return ConditionSetResolutionResult.create('noMatch', conditions)
183
+ .onSuccess((result) => {
184
+ var _a;
185
+ this._conditionSetCache[conditionSetIndex] = result;
186
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheMiss('conditionSet', conditionSetIndex);
187
+ return succeed(result);
188
+ })
189
+ .onFailure((err) => {
190
+ var _a;
191
+ /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
192
+ this._conditionSetCache[conditionSetIndex] = undefined;
193
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheError('conditionSet', conditionSetIndex);
194
+ return fail(`${conditionSetIndex}: error creating condition set resolution result: ${err}`);
195
+ });
196
+ }
197
+ /* c8 ignore next 3 - edge case: matchAsDefault fallback logic rarely triggered */
198
+ if (conditionResult.matchType === 'matchAsDefault') {
199
+ matchType = 'matchAsDefault';
200
+ }
201
+ }
202
+ // Cache the successful result
203
+ return ConditionSetResolutionResult.create(matchType, conditions)
204
+ .onSuccess((result) => {
205
+ var _a;
206
+ this._conditionSetCache[conditionSetIndex] = result;
207
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheMiss('conditionSet', conditionSetIndex);
208
+ return succeed(result);
209
+ })
210
+ .onFailure((err) => {
211
+ var _a;
212
+ /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
213
+ this._conditionSetCache[conditionSetIndex] = undefined;
214
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheError('conditionSet', conditionSetIndex);
215
+ return fail(`${conditionSetIndex}: error creating condition set resolution result: ${err}`);
216
+ });
217
+ }
218
+ /**
219
+ * Resolves a decision by evaluating all its constituent condition sets against the current context.
220
+ * Uses O(1) caching based on the decision's globally unique sequential index.
221
+ * @param decision - The {@link Decisions.AbstractDecision | abstract decision} to resolve.
222
+ * @returns `Success` with the {@link Runtime.DecisionResolutionResult | resolution result} if successful,
223
+ * or `Failure` with an error message if the decision cannot be resolved.
224
+ * @public
225
+ */
226
+ resolveDecision(decision) {
227
+ var _a, _b, _c;
228
+ // Get the decision's index for cache lookup
229
+ const decisionIndex = decision.index;
230
+ if (decisionIndex === undefined) {
231
+ return fail(`Decision "${decision.key}" does not have a valid index`);
232
+ }
233
+ // Check cache first for O(1) lookup
234
+ const cachedResult = this._decisionCache[decisionIndex];
235
+ if (cachedResult !== undefined) {
236
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheHit('decision', decisionIndex);
237
+ return succeed(cachedResult);
238
+ }
239
+ // Resolve all condition sets in the decision
240
+ const matchingInstanceResults = [];
241
+ const matchingDefaultInstanceResults = [];
242
+ for (let instanceIndex = 0; instanceIndex < decision.candidates.length; instanceIndex++) {
243
+ const candidate = decision.candidates[instanceIndex];
244
+ const conditionSetResult = this.resolveConditionSet(candidate.conditionSet);
245
+ /* c8 ignore next 4 - defensive in depth */
246
+ if (conditionSetResult.isFailure()) {
247
+ (_b = this._listener) === null || _b === void 0 ? void 0 : _b.onCacheError('decision', decisionIndex);
248
+ return fail(`${decision.key}: Failed to resolve condition set": ${conditionSetResult.message}`);
249
+ }
250
+ const resolution = conditionSetResult.value;
251
+ // Only include condition sets that match
252
+ if (resolution.matchType === 'match') {
253
+ matchingInstanceResults.push({
254
+ index: instanceIndex,
255
+ result: resolution
256
+ });
257
+ }
258
+ else if (resolution.matchType === 'matchAsDefault') {
259
+ matchingDefaultInstanceResults.push({
260
+ index: instanceIndex,
261
+ result: resolution
262
+ });
263
+ }
264
+ // If condition set doesn't match, we simply skip it and continue (don't fail the decision)
265
+ }
266
+ // Sort by condition set resolution priority using the proper comparison logic
267
+ matchingInstanceResults.sort((a, b) => ConditionSetResolutionResult.compare(a.result, b.result));
268
+ matchingDefaultInstanceResults.sort((a, b) => ConditionSetResolutionResult.compare(a.result, b.result));
269
+ // Extract just the instance indices in priority order
270
+ const instanceIndices = matchingInstanceResults.map((item) => item.index);
271
+ const defaultInstanceIndices = matchingDefaultInstanceResults.map((item) => item.index);
272
+ // Cache the successful result
273
+ const successResult = {
274
+ success: true,
275
+ instanceIndices,
276
+ defaultInstanceIndices
277
+ };
278
+ this._decisionCache[decisionIndex] = successResult;
279
+ (_c = this._listener) === null || _c === void 0 ? void 0 : _c.onCacheMiss('decision', decisionIndex);
280
+ return succeed(successResult);
281
+ }
282
+ resolveResource(idOrResource) {
283
+ /* c8 ignore next 4 - defensive coding: string resource resolution should use direct resource calls */
284
+ if (typeof idOrResource === 'string') {
285
+ return this.resourceManager
286
+ .getBuiltResource(idOrResource)
287
+ .onSuccess((resource) => this.resolveResource(resource));
288
+ }
289
+ const resource = idOrResource;
290
+ // Get the abstract decision from the resource's concrete decision
291
+ const abstractDecision = resource.decision.baseDecision;
292
+ // Resolve the decision to get candidate indices in priority order
293
+ const decisionResult = this.resolveDecision(abstractDecision);
294
+ /* c8 ignore next 3 - defense in depth almost impossible to hit */
295
+ if (decisionResult.isFailure()) {
296
+ return fail(`Failed to resolve decision for resource "${resource.id}": ${decisionResult.message}`);
297
+ }
298
+ const resolution = decisionResult.value;
299
+ // Check if any candidates matched (regular or default)
300
+ if (!resolution.success ||
301
+ (resolution.instanceIndices.length === 0 && resolution.defaultInstanceIndices.length === 0)) {
302
+ return fail(`No matching candidates found for resource "${resource.id}"`);
303
+ }
304
+ // Prefer regular matches over default matches
305
+ const candidateIndex = resolution.instanceIndices.length > 0
306
+ ? resolution.instanceIndices[0] // Best regular match
307
+ : resolution.defaultInstanceIndices[0]; // Best default match
308
+ if (candidateIndex >= resource.candidates.length) {
309
+ return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
310
+ }
311
+ const bestCandidate = resource.candidates[candidateIndex];
312
+ return succeed(bestCandidate);
313
+ }
314
+ resolveAllResourceCandidates(idOrResource) {
315
+ if (typeof idOrResource === 'string') {
316
+ return this.resourceManager
317
+ .getBuiltResource(idOrResource)
318
+ .onSuccess((resource) => this.resolveAllResourceCandidates(resource));
319
+ }
320
+ const resource = idOrResource;
321
+ // Get the abstract decision from the resource's concrete decision
322
+ const abstractDecision = resource.decision.baseDecision;
323
+ // Resolve the decision to get candidate indices in priority order
324
+ const decisionResult = this.resolveDecision(abstractDecision);
325
+ /* c8 ignore next 3 - defense in depth almost impossible to hit */
326
+ if (decisionResult.isFailure()) {
327
+ return fail(`Failed to resolve decision for resource "${resource.id}": ${decisionResult.message}`);
328
+ }
329
+ const resolution = decisionResult.value;
330
+ // Check if any candidates matched (regular or default)
331
+ if (!resolution.success ||
332
+ (resolution.instanceIndices.length === 0 && resolution.defaultInstanceIndices.length === 0)) {
333
+ return fail(`No matching candidates found for resource "${resource.id}"`);
334
+ }
335
+ // Get all matching candidates: regular matches first, then default matches
336
+ const candidates = [];
337
+ // Add all regular matches first (already sorted by priority)
338
+ for (const candidateIndex of resolution.instanceIndices) {
339
+ if (candidateIndex >= resource.candidates.length) {
340
+ return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
341
+ }
342
+ const candidate = resource.candidates[candidateIndex];
343
+ candidates.push(candidate);
344
+ }
345
+ // Add all default matches after regular matches (already sorted by priority)
346
+ for (const candidateIndex of resolution.defaultInstanceIndices) {
347
+ /* c8 ignore next 3 - defense in depth should not happen */
348
+ if (candidateIndex >= resource.candidates.length) {
349
+ return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
350
+ }
351
+ const candidate = resource.candidates[candidateIndex];
352
+ candidates.push(candidate);
353
+ }
354
+ return succeed(candidates);
355
+ }
356
+ resolveComposedResourceValue(idOrResource) {
357
+ /* c8 ignore next 4 - defensive coding: string resource resolution should use direct resource calls */
358
+ if (typeof idOrResource === 'string') {
359
+ return this.resourceManager
360
+ .getBuiltResource(idOrResource)
361
+ .onSuccess((resource) => this.resolveComposedResourceValue(resource));
362
+ }
363
+ const resource = idOrResource;
364
+ return this.resolveAllResourceCandidates(resource).onSuccess((candidates) => {
365
+ /* c8 ignore next 3 - defense in depth should never occur */
366
+ if (candidates.length === 0) {
367
+ return fail(`${resource.id}: No matching candidates found.`);
368
+ }
369
+ // Find the first full candidate and collect all partial candidates above it
370
+ let fullCandidateIndex = -1;
371
+ const partialCandidates = [];
372
+ for (let i = 0; i < candidates.length; i++) {
373
+ const candidate = candidates[i];
374
+ if (!candidate.isPartial) {
375
+ // Found the first full candidate
376
+ fullCandidateIndex = i;
377
+ break;
378
+ }
379
+ else {
380
+ // Collect partial candidates (these are in ascending priority order)
381
+ partialCandidates.unshift(candidate);
382
+ }
383
+ }
384
+ // If no full candidate found, use the last candidate as the base
385
+ const baseCandidateIndex = fullCandidateIndex >= 0 ? fullCandidateIndex : candidates.length - 1;
386
+ const baseCandidate = candidates[baseCandidateIndex];
387
+ // If there are no partial candidates to merge, but null-as-delete is enabled,
388
+ // still process through JsonEditor to handle null properties in the base candidate
389
+ if (partialCandidates.length === 0) {
390
+ if (this.options.suppressNullAsDelete || !isJsonObject(baseCandidate.json)) {
391
+ return succeed(baseCandidate.json);
392
+ }
393
+ // Process single candidate through JsonEditor to apply null-as-delete
394
+ const editor = JsonEditor.create({
395
+ merge: {
396
+ arrayMergeBehavior: 'replace',
397
+ nullAsDelete: true
398
+ }
399
+ }).orThrow(); // Should never fail with valid options
400
+ return editor
401
+ .mergeObjectsInPlace({}, [baseCandidate.json])
402
+ .withErrorFormat((err) => `${resource.id}: Composition failed: ${err}`);
403
+ }
404
+ const allCandidates = [
405
+ baseCandidate.json,
406
+ ...partialCandidates.map((candidate) => candidate.json)
407
+ ].filter((v) => isJsonObject(v));
408
+ /* c8 ignore next 3 - defensive check: non-object values in resource candidates should be prevented at validation time */
409
+ if (allCandidates.length !== partialCandidates.length + 1) {
410
+ return fail(`${resource.id}: Unable to compose non-object candidate values.`);
411
+ }
412
+ // Create JsonEditor with array replacement behavior and null-as-delete for resource composition
413
+ const editor = JsonEditor.create({
414
+ merge: {
415
+ arrayMergeBehavior: 'replace',
416
+ nullAsDelete: !this.options.suppressNullAsDelete
417
+ }
418
+ }).orThrow(); // Should never fail with valid options
419
+ return editor
420
+ .mergeObjectsInPlace({}, allCandidates)
421
+ .withErrorFormat((err) => `${resource.id}: Composition failed: ${err}`);
422
+ });
423
+ }
424
+ /**
425
+ * {@inheritDoc IResourceResolver.withContext}
426
+ */
427
+ withContext(context) {
428
+ const { resourceManager, qualifierTypes, options } = this;
429
+ return ValidatingSimpleContextQualifierProvider.create({
430
+ qualifiers: this.qualifiers,
431
+ qualifierValues: context
432
+ }).onSuccess((contextQualifierProvider) => {
433
+ return ResourceResolver.create({
434
+ resourceManager,
435
+ qualifierTypes,
436
+ options,
437
+ contextQualifierProvider
438
+ });
439
+ });
440
+ }
441
+ /**
442
+ * Clears all caches (condition, condition set, and decision), forcing all cached items
443
+ * to be re-evaluated on next access. This should be called when the context changes and cached
444
+ * results are no longer valid.
445
+ * @public
446
+ */
447
+ clearConditionCache() {
448
+ var _a, _b, _c;
449
+ this._conditionCache.fill(undefined);
450
+ this._conditionSetCache.fill(undefined);
451
+ this._decisionCache.fill(undefined);
452
+ (_a = this._listener) === null || _a === void 0 ? void 0 : _a.onCacheClear('condition');
453
+ (_b = this._listener) === null || _b === void 0 ? void 0 : _b.onCacheClear('conditionSet');
454
+ (_c = this._listener) === null || _c === void 0 ? void 0 : _c.onCacheClear('decision');
455
+ }
456
+ /**
457
+ * Gets the current size of the condition cache array.
458
+ * @returns The size of the condition cache array.
459
+ * @public
460
+ */
461
+ get conditionCacheSize() {
462
+ return this._conditionCache.length;
463
+ }
464
+ /**
465
+ * Gets the current size of the condition set cache array.
466
+ * @returns The size of the condition set cache array.
467
+ * @public
468
+ */
469
+ get conditionSetCacheSize() {
470
+ return this._conditionSetCache.length;
471
+ }
472
+ /**
473
+ * Gets the current size of the decision cache array.
474
+ * @returns The size of the decision cache array.
475
+ * @public
476
+ */
477
+ get decisionCacheSize() {
478
+ return this._decisionCache.length;
479
+ }
480
+ }
481
+ //# sourceMappingURL=resourceResolver.js.map