@fgv/ts-res 5.0.0-22 → 5.0.0-24

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 (288) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +665 -44
  3. package/lib/packlets/common/convert.d.ts +10 -0
  4. package/lib/packlets/common/convert.js +11 -1
  5. package/lib/packlets/common/resources.d.ts +14 -0
  6. package/lib/packlets/common/validate/regularExpressions.d.ts +4 -0
  7. package/lib/packlets/common/validate/regularExpressions.js +5 -1
  8. package/lib/packlets/common/validate/resources.d.ts +35 -1
  9. package/lib/packlets/common/validate/resources.js +54 -0
  10. package/lib/packlets/config/convert.js +1 -1
  11. package/lib/packlets/config/json.d.ts +1 -1
  12. package/lib/packlets/qualifier-types/config/convert.d.ts +22 -1
  13. package/lib/packlets/qualifier-types/config/convert.js +67 -2
  14. package/lib/packlets/qualifier-types/config/json.d.ts +11 -2
  15. package/lib/packlets/qualifier-types/helpers.d.ts +1 -2
  16. package/lib/packlets/qualifier-types/languageQualifierType.d.ts +27 -1
  17. package/lib/packlets/qualifier-types/languageQualifierType.js +75 -0
  18. package/lib/packlets/qualifier-types/literalQualifierType.d.ts +30 -4
  19. package/lib/packlets/qualifier-types/literalQualifierType.js +77 -0
  20. package/lib/packlets/qualifier-types/literalValueHierarchy.d.ts +6 -10
  21. package/lib/packlets/qualifier-types/literalValueHierarchy.js +10 -0
  22. package/lib/packlets/qualifier-types/qualifierType.d.ts +28 -0
  23. package/lib/packlets/qualifier-types/territoryQualifierType.d.ts +28 -4
  24. package/lib/packlets/qualifier-types/territoryQualifierType.js +77 -0
  25. package/lib/packlets/resource-json/compiled/convert.js +2 -1
  26. package/lib/packlets/resource-json/compiled/json.d.ts +6 -2
  27. package/lib/packlets/resource-types/jsonResourceType.d.ts +4 -0
  28. package/lib/packlets/resource-types/jsonResourceType.js +4 -0
  29. package/lib/packlets/resource-types/resourceType.d.ts +4 -0
  30. package/lib/packlets/resources/candidateValue.d.ts +92 -0
  31. package/lib/{test/unit/qualifier-types/testQualifierType.js → packlets/resources/candidateValue.js} +63 -24
  32. package/lib/packlets/resources/candidateValueCollector.d.ts +55 -0
  33. package/lib/packlets/resources/candidateValueCollector.js +96 -0
  34. package/lib/packlets/resources/index.d.ts +2 -0
  35. package/lib/packlets/resources/index.js +2 -0
  36. package/lib/packlets/resources/resource.js +2 -2
  37. package/lib/packlets/resources/resourceBuilder.d.ts +6 -0
  38. package/lib/packlets/resources/resourceBuilder.js +3 -1
  39. package/lib/packlets/resources/resourceCandidate.d.ts +10 -1
  40. package/lib/packlets/resources/resourceCandidate.js +11 -5
  41. package/lib/packlets/resources/resourceManagerBuilder.d.ts +7 -0
  42. package/lib/packlets/resources/resourceManagerBuilder.js +13 -6
  43. package/lib/packlets/runtime/compiledResourceCollection.d.ts +20 -8
  44. package/lib/packlets/runtime/compiledResourceCollection.js +25 -4
  45. package/lib/packlets/runtime/index.d.ts +1 -0
  46. package/lib/packlets/runtime/index.js +1 -0
  47. package/lib/packlets/runtime/resource-tree/common.d.ts +1 -1
  48. package/lib/packlets/runtime/resourceTreeResolver.d.ts +152 -0
  49. package/lib/packlets/runtime/resourceTreeResolver.js +222 -0
  50. package/package.json +12 -12
  51. package/.vscode/4.json +0 -3
  52. package/.vscode/launch.json +0 -16
  53. package/.vscode/settings.json +0 -3
  54. package/dist/ts-res.d.ts +0 -9713
  55. package/dist/tsdoc-metadata.json +0 -11
  56. package/lib/test/data/sample.json +0 -32
  57. package/lib/test/unit/bundle/bundleBuilder.test.d.ts +0 -2
  58. package/lib/test/unit/bundle/bundleLoader.test.d.ts +0 -2
  59. package/lib/test/unit/bundle/bundleUtils.test.d.ts +0 -2
  60. package/lib/test/unit/bundle/convert.test.d.ts +0 -2
  61. package/lib/test/unit/bundle/integration.test.d.ts +0 -2
  62. package/lib/test/unit/bundle/model.test.d.ts +0 -2
  63. package/lib/test/unit/bundle/normalization.test.d.ts +0 -2
  64. package/lib/test/unit/common/conditions.test.d.ts +0 -2
  65. package/lib/test/unit/common/helpers/conditions.test.d.ts +0 -2
  66. package/lib/test/unit/common/helpers/contexts.test.d.ts +0 -2
  67. package/lib/test/unit/common/helpers/qualifierDefaultValues.test.d.ts +0 -2
  68. package/lib/test/unit/common/resources.test.d.ts +0 -2
  69. package/lib/test/unit/conditions/condition.test.d.ts +0 -2
  70. package/lib/test/unit/conditions/conditionCollector.test.d.ts +0 -2
  71. package/lib/test/unit/conditions/conditionScoreAsDefault.test.d.ts +0 -2
  72. package/lib/test/unit/conditions/conditionSet.test.d.ts +0 -2
  73. package/lib/test/unit/conditions/conditionSetCollector.test.d.ts +0 -2
  74. package/lib/test/unit/conditions/conditionToken.test.d.ts +0 -2
  75. package/lib/test/unit/conditions/convert.test.d.ts +0 -2
  76. package/lib/test/unit/config/common.test.d.ts +0 -2
  77. package/lib/test/unit/config/convert.test.d.ts +0 -2
  78. package/lib/test/unit/config/predefined/default.test.d.ts +0 -2
  79. package/lib/test/unit/config/predefined/extended.test.d.ts +0 -2
  80. package/lib/test/unit/config/systemConfiguration.test.d.ts +0 -2
  81. package/lib/test/unit/config/systemConfigurationInitParams.test.d.ts +0 -2
  82. package/lib/test/unit/context/contextToken.test.d.ts +0 -2
  83. package/lib/test/unit/context/convert.test.d.ts +0 -2
  84. package/lib/test/unit/decisions/abstractDecision.test.d.ts +0 -2
  85. package/lib/test/unit/decisions/abstractDecisionCollector.test.d.ts +0 -2
  86. package/lib/test/unit/decisions/candidate.test.d.ts +0 -2
  87. package/lib/test/unit/decisions/concreteDecision.test.d.ts +0 -2
  88. package/lib/test/unit/decisions/decision.test.d.ts +0 -2
  89. package/lib/test/unit/import/collectionImporter.test.d.ts +0 -2
  90. package/lib/test/unit/import/fsItem.test.d.ts +0 -2
  91. package/lib/test/unit/import/fsItemImporter.test.d.ts +0 -2
  92. package/lib/test/unit/import/importContext.test.d.ts +0 -2
  93. package/lib/test/unit/import/importManager.test.d.ts +0 -2
  94. package/lib/test/unit/import/jsonImporter.test.d.ts +0 -2
  95. package/lib/test/unit/import/pathImporter.test.d.ts +0 -2
  96. package/lib/test/unit/qualifier-types/convert.test.d.ts +0 -2
  97. package/lib/test/unit/qualifier-types/helpers.test.d.ts +0 -2
  98. package/lib/test/unit/qualifier-types/languageQualifierType.test.d.ts +0 -2
  99. package/lib/test/unit/qualifier-types/literalQualifierType.test.d.ts +0 -2
  100. package/lib/test/unit/qualifier-types/literalValueHierarchy.test.d.ts +0 -2
  101. package/lib/test/unit/qualifier-types/qualifierType.test.d.ts +0 -2
  102. package/lib/test/unit/qualifier-types/qualifierTypeCollector.test.d.ts +0 -2
  103. package/lib/test/unit/qualifier-types/territoryQualifierType.test.d.ts +0 -2
  104. package/lib/test/unit/qualifier-types/testQualifierType.d.ts +0 -15
  105. package/lib/test/unit/qualifiers/convert.test.d.ts +0 -2
  106. package/lib/test/unit/qualifiers/qualifierCollector.test.d.ts +0 -2
  107. package/lib/test/unit/qualifiers/qualifierDefaultValue.test.d.ts +0 -2
  108. package/lib/test/unit/qualifiers/qualifierDefaultValueToken.test.d.ts +0 -2
  109. package/lib/test/unit/resource-json/convert.test.d.ts +0 -2
  110. package/lib/test/unit/resource-json/nameInheritance.test.d.ts +0 -2
  111. package/lib/test/unit/resource-json/resourceDeclCollection.test.d.ts +0 -2
  112. package/lib/test/unit/resource-json/resourceDeclTree.test.d.ts +0 -2
  113. package/lib/test/unit/resource-types/derivedResourceType.test.d.ts +0 -2
  114. package/lib/test/unit/resource-types/jsonResourceType.test.d.ts +0 -2
  115. package/lib/test/unit/resource-types/resourceTypeCollector.test.d.ts +0 -2
  116. package/lib/test/unit/resource-types/testDerivedResourceType.d.ts +0 -78
  117. package/lib/test/unit/resource-types/testDerivedResourceType.js +0 -110
  118. package/lib/test/unit/resources/candidateReducer.integration.test.d.ts +0 -2
  119. package/lib/test/unit/resources/candidateReducer.test.d.ts +0 -2
  120. package/lib/test/unit/resources/reduceCandidates.test.d.ts +0 -2
  121. package/lib/test/unit/resources/reduceQualifiers.comprehensive.test.d.ts +0 -2
  122. package/lib/test/unit/resources/reduceQualifiers.test.d.ts +0 -2
  123. package/lib/test/unit/resources/resource.test.d.ts +0 -2
  124. package/lib/test/unit/resources/resourceBuilder.test.d.ts +0 -2
  125. package/lib/test/unit/resources/resourceCandidate.test.d.ts +0 -2
  126. package/lib/test/unit/resources/resourceManagerBuilder.clone.test.d.ts +0 -2
  127. package/lib/test/unit/resources/resourceManagerBuilder.getBuiltResourceTree.test.d.ts +0 -2
  128. package/lib/test/unit/resources/resourceManagerBuilder.test.d.ts +0 -2
  129. package/lib/test/unit/runtime/cacheListener.test.d.ts +0 -2
  130. package/lib/test/unit/runtime/cacheMetrics.test.d.ts +0 -2
  131. package/lib/test/unit/runtime/compiledResourceCollection.test.d.ts +0 -2
  132. package/lib/test/unit/runtime/conditionSetResolutionResult.comprehensive.test.d.ts +0 -2
  133. package/lib/test/unit/runtime/conditionSetResolutionResult.test.d.ts +0 -2
  134. package/lib/test/unit/runtime/context/contextQualifierProvider.test.d.ts +0 -2
  135. package/lib/test/unit/runtime/context/contextQualifierProviderValidator.test.d.ts +0 -2
  136. package/lib/test/unit/runtime/context/simpleContextQualifierProvider.test.d.ts +0 -2
  137. package/lib/test/unit/runtime/context/validatingSimpleContextQualifierProvider.test.d.ts +0 -2
  138. package/lib/test/unit/runtime/resource-tree/readOnlyResourceTree.test.d.ts +0 -2
  139. package/lib/test/unit/runtime/resource-tree/resourceTreeChildren.test.d.ts +0 -2
  140. package/lib/test/unit/runtime/resource-tree/resourceTreeChildrenValidator.test.d.ts +0 -2
  141. package/lib/test/unit/runtime/resourceResolver.coverage.test.d.ts +0 -2
  142. package/lib/test/unit/runtime/resourceResolver.defaultResolution.test.d.ts +0 -2
  143. package/lib/test/unit/runtime/resourceResolver.newFeatures.test.d.ts +0 -2
  144. package/lib/test/unit/runtime/resourceResolver.test.d.ts +0 -2
  145. package/lib/test/unit/runtime/validate.test.d.ts +0 -2
  146. package/lib/test/unit/zip-archive/convert.test.d.ts +0 -2
  147. package/lib/test/unit/zip-archive/json.test.d.ts +0 -2
  148. package/lib/test/unit/zip-archive/zipArchiveCreator.test.d.ts +0 -2
  149. package/lib/test/unit/zip-archive/zipArchiveFormat.test.d.ts +0 -2
  150. package/lib/test/unit/zip-archive/zipArchiveIdempotency.test.d.ts +0 -2
  151. package/lib/test/unit/zip-archive/zipArchiveLoader.test.d.ts +0 -2
  152. package/src/index.ts +0 -75
  153. package/src/packlets/bundle/bundleBuilder.ts +0 -159
  154. package/src/packlets/bundle/bundleLoader.ts +0 -157
  155. package/src/packlets/bundle/bundleNormalizer.ts +0 -173
  156. package/src/packlets/bundle/bundleUtils.ts +0 -142
  157. package/src/packlets/bundle/convert.ts +0 -87
  158. package/src/packlets/bundle/index.ts +0 -52
  159. package/src/packlets/bundle/model.ts +0 -138
  160. package/src/packlets/common/conditions.ts +0 -226
  161. package/src/packlets/common/convert.ts +0 -184
  162. package/src/packlets/common/helpers/conditions.ts +0 -84
  163. package/src/packlets/common/helpers/context.ts +0 -88
  164. package/src/packlets/common/helpers/index.ts +0 -26
  165. package/src/packlets/common/helpers/qualifierDefaultValues.ts +0 -91
  166. package/src/packlets/common/helpers/resources.ts +0 -96
  167. package/src/packlets/common/index.ts +0 -30
  168. package/src/packlets/common/resources.ts +0 -105
  169. package/src/packlets/common/validate/conditions.ts +0 -595
  170. package/src/packlets/common/validate/index.ts +0 -27
  171. package/src/packlets/common/validate/regularExpressions.ts +0 -92
  172. package/src/packlets/common/validate/resources.ts +0 -170
  173. package/src/packlets/conditions/condition.ts +0 -351
  174. package/src/packlets/conditions/conditionCollector.ts +0 -103
  175. package/src/packlets/conditions/conditionDecls.ts +0 -50
  176. package/src/packlets/conditions/conditionSet.ts +0 -318
  177. package/src/packlets/conditions/conditionSetCollector.ts +0 -127
  178. package/src/packlets/conditions/conditionSetDecls.ts +0 -41
  179. package/src/packlets/conditions/conditionToken.ts +0 -180
  180. package/src/packlets/conditions/convert/conditionSetDecls.ts +0 -72
  181. package/src/packlets/conditions/convert/decls.ts +0 -95
  182. package/src/packlets/conditions/convert/index.ts +0 -24
  183. package/src/packlets/conditions/index.ts +0 -33
  184. package/src/packlets/config/common.ts +0 -107
  185. package/src/packlets/config/configInitFactory.ts +0 -142
  186. package/src/packlets/config/convert.ts +0 -61
  187. package/src/packlets/config/index.ts +0 -30
  188. package/src/packlets/config/json.ts +0 -43
  189. package/src/packlets/config/predefined/default.ts +0 -150
  190. package/src/packlets/config/predefined/extended.ts +0 -198
  191. package/src/packlets/config/predefined/index.ts +0 -26
  192. package/src/packlets/config/systemConfiguration.ts +0 -217
  193. package/src/packlets/context/contextDecls.ts +0 -76
  194. package/src/packlets/context/contextToken.ts +0 -252
  195. package/src/packlets/context/convert/decls.ts +0 -126
  196. package/src/packlets/context/convert/index.ts +0 -23
  197. package/src/packlets/context/index.ts +0 -27
  198. package/src/packlets/decisions/abstractDecision.ts +0 -87
  199. package/src/packlets/decisions/abstractDecisionCollector.ts +0 -122
  200. package/src/packlets/decisions/candidate.ts +0 -101
  201. package/src/packlets/decisions/common.ts +0 -37
  202. package/src/packlets/decisions/concreteDecision.ts +0 -157
  203. package/src/packlets/decisions/decision.ts +0 -172
  204. package/src/packlets/decisions/index.ts +0 -28
  205. package/src/packlets/import/fsItem.ts +0 -212
  206. package/src/packlets/import/importContext.ts +0 -183
  207. package/src/packlets/import/importManager.ts +0 -187
  208. package/src/packlets/import/importable.ts +0 -107
  209. package/src/packlets/import/importers/collectionImporter.ts +0 -153
  210. package/src/packlets/import/importers/fsItemImporter.ts +0 -154
  211. package/src/packlets/import/importers/importer.ts +0 -58
  212. package/src/packlets/import/importers/index.ts +0 -27
  213. package/src/packlets/import/importers/jsonImporter.ts +0 -133
  214. package/src/packlets/import/importers/pathImporter.ts +0 -134
  215. package/src/packlets/import/index.ts +0 -29
  216. package/src/packlets/qualifier-types/config/convert.ts +0 -108
  217. package/src/packlets/qualifier-types/config/index.ts +0 -27
  218. package/src/packlets/qualifier-types/config/json.ts +0 -126
  219. package/src/packlets/qualifier-types/convert.ts +0 -77
  220. package/src/packlets/qualifier-types/helpers.ts +0 -94
  221. package/src/packlets/qualifier-types/index.ts +0 -34
  222. package/src/packlets/qualifier-types/languageQualifierType.ts +0 -143
  223. package/src/packlets/qualifier-types/literalQualifierType.ts +0 -244
  224. package/src/packlets/qualifier-types/literalValueHierarchy.ts +0 -332
  225. package/src/packlets/qualifier-types/qualifierType.ts +0 -360
  226. package/src/packlets/qualifier-types/qualifierTypeCollector.ts +0 -102
  227. package/src/packlets/qualifier-types/territoryQualifierType.ts +0 -255
  228. package/src/packlets/qualifiers/convert/decls.ts +0 -90
  229. package/src/packlets/qualifiers/convert/index.ts +0 -24
  230. package/src/packlets/qualifiers/convert/qualifier.ts +0 -55
  231. package/src/packlets/qualifiers/index.ts +0 -31
  232. package/src/packlets/qualifiers/qualifier.ts +0 -169
  233. package/src/packlets/qualifiers/qualifierCollector.ts +0 -194
  234. package/src/packlets/qualifiers/qualifierDecl.ts +0 -80
  235. package/src/packlets/qualifiers/qualifierDefaultValueDecls.ts +0 -57
  236. package/src/packlets/qualifiers/qualifierDefaultValueToken.ts +0 -230
  237. package/src/packlets/resource-json/compiled/common.ts +0 -35
  238. package/src/packlets/resource-json/compiled/convert.ts +0 -143
  239. package/src/packlets/resource-json/compiled/index.ts +0 -27
  240. package/src/packlets/resource-json/compiled/json.ts +0 -239
  241. package/src/packlets/resource-json/convert.ts +0 -236
  242. package/src/packlets/resource-json/helpers.ts +0 -284
  243. package/src/packlets/resource-json/index.ts +0 -33
  244. package/src/packlets/resource-json/json.ts +0 -295
  245. package/src/packlets/resource-json/normalized.ts +0 -211
  246. package/src/packlets/resource-json/resourceDeclCollection.ts +0 -117
  247. package/src/packlets/resource-json/resourceDeclContainer.ts +0 -48
  248. package/src/packlets/resource-json/resourceDeclTree.ts +0 -116
  249. package/src/packlets/resource-types/config/convert.ts +0 -38
  250. package/src/packlets/resource-types/config/index.ts +0 -27
  251. package/src/packlets/resource-types/config/json.ts +0 -33
  252. package/src/packlets/resource-types/helpers.ts +0 -44
  253. package/src/packlets/resource-types/index.ts +0 -30
  254. package/src/packlets/resource-types/jsonResourceType.ts +0 -102
  255. package/src/packlets/resource-types/resourceType.ts +0 -292
  256. package/src/packlets/resource-types/resourceTypeCollector.ts +0 -79
  257. package/src/packlets/resources/candidateReducer.ts +0 -331
  258. package/src/packlets/resources/common.ts +0 -114
  259. package/src/packlets/resources/index.ts +0 -28
  260. package/src/packlets/resources/resource.ts +0 -307
  261. package/src/packlets/resources/resourceBuilder.ts +0 -269
  262. package/src/packlets/resources/resourceCandidate.ts +0 -333
  263. package/src/packlets/resources/resourceManagerBuilder.ts +0 -1169
  264. package/src/packlets/runtime/cacheListener.ts +0 -113
  265. package/src/packlets/runtime/cacheMetrics.ts +0 -206
  266. package/src/packlets/runtime/compiledResourceCollection.ts +0 -607
  267. package/src/packlets/runtime/conditionSetResolutionResult.ts +0 -160
  268. package/src/packlets/runtime/context/contextQualifierProvider.ts +0 -117
  269. package/src/packlets/runtime/context/contextQualifierProviderValidator.ts +0 -281
  270. package/src/packlets/runtime/context/index.ts +0 -26
  271. package/src/packlets/runtime/context/simpleContextQualifierProvider.ts +0 -225
  272. package/src/packlets/runtime/context/validatingSimpleContextQualifierProvider.ts +0 -96
  273. package/src/packlets/runtime/iResourceManager.ts +0 -130
  274. package/src/packlets/runtime/index.ts +0 -35
  275. package/src/packlets/runtime/resource-tree/common.ts +0 -126
  276. package/src/packlets/runtime/resource-tree/index.ts +0 -26
  277. package/src/packlets/runtime/resource-tree/readOnlyResourceTree.ts +0 -342
  278. package/src/packlets/runtime/resource-tree/resourceTreeChildren.ts +0 -125
  279. package/src/packlets/runtime/resource-tree/resourceTreeChildrenValidator.ts +0 -187
  280. package/src/packlets/runtime/resourceResolver.ts +0 -712
  281. package/src/packlets/runtime/validate.ts +0 -63
  282. package/src/packlets/zip-archive/convert.ts +0 -121
  283. package/src/packlets/zip-archive/index.ts +0 -76
  284. package/src/packlets/zip-archive/json.ts +0 -91
  285. package/src/packlets/zip-archive/types.ts +0 -140
  286. package/src/packlets/zip-archive/zipArchiveCreator.ts +0 -229
  287. package/src/packlets/zip-archive/zipArchiveFormat.ts +0 -158
  288. package/src/packlets/zip-archive/zipArchiveLoader.ts +0 -370
@@ -1,712 +0,0 @@
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
-
23
- import { Result, captureResult, fail, succeed } from '@fgv/ts-utils';
24
- import { JsonValue, JsonObject, isJsonObject } from '@fgv/ts-json-base';
25
- import { JsonEditor } from '@fgv/ts-json';
26
- import { IResourceResolver, NoMatch } from '../common';
27
- import { Condition, ConditionSet } from '../conditions';
28
- import { AbstractDecision } from '../decisions';
29
- import { ReadOnlyQualifierTypeCollector } from '../qualifier-types';
30
- import { IContextQualifierProvider, ValidatingSimpleContextQualifierProvider } from './context';
31
- import { IResourceManager, IResource, IResourceCandidate } from './iResourceManager';
32
- import {
33
- ConditionMatchType,
34
- ConditionSetResolutionResult,
35
- IConditionMatchResult
36
- } from './conditionSetResolutionResult';
37
- import { IResourceResolverCacheListener } from './cacheListener';
38
- import { IReadOnlyQualifierCollector } from '../qualifiers';
39
-
40
- /**
41
- * Represents the cached result of resolving a decision.
42
- * Contains either a failure indicator or a list of instance indices for matching condition sets,
43
- * ordered by condition set priority.
44
- * @public
45
- */
46
- export type DecisionResolutionResult =
47
- | { success: false }
48
- | { success: true; instanceIndices: ReadonlyArray<number>; defaultInstanceIndices: ReadonlyArray<number> };
49
-
50
- /**
51
- * Options for configuring a {@link Runtime.ResourceResolver | ResourceResolver}.
52
- * @public
53
- */
54
- export interface IResourceResolverOptions {
55
- /**
56
- * Controls whether null values in resource composition should suppress properties
57
- * instead of setting them to null. When true, properties with null values from
58
- * higher-priority partial candidates will be omitted from the final composed resource.
59
- * @defaultValue false
60
- */
61
- suppressNullAsDelete?: boolean;
62
- }
63
-
64
- /**
65
- * Parameters for creating a {@link Runtime.ResourceResolver | ResourceResolver}.
66
- * @public
67
- */
68
- export interface IResourceResolverCreateParams {
69
- /**
70
- * The {@link Runtime.IResourceManager | resource manager} that defines the resources available
71
- * and provides access to qualifiers and conditions.
72
- */
73
- resourceManager: IResourceManager;
74
-
75
- /**
76
- * The {@link QualifierTypes.ReadOnlyQualifierTypeCollector | readonly qualifier type collector}
77
- * that provides qualifier implementations for condition evaluation.
78
- */
79
- qualifierTypes: ReadOnlyQualifierTypeCollector;
80
-
81
- /**
82
- * The {@link Runtime.Context.IContextQualifierProvider | context qualifier provider} that resolves
83
- * qualifier values for the current context.
84
- */
85
- contextQualifierProvider: IContextQualifierProvider;
86
-
87
- /**
88
- * An optional listener for {@link Runtime.ResourceResolver | ResourceResolver} cache activity.
89
- */
90
- listener?: IResourceResolverCacheListener;
91
-
92
- /**
93
- * Optional configuration options for the {@link Runtime.ResourceResolver | ResourceResolver}.
94
- */
95
- options?: IResourceResolverOptions;
96
- }
97
-
98
- /**
99
- * High-performance runtime resource resolver with O(1) condition caching.
100
- * Resolves resources for a given context by evaluating conditions against qualifier values
101
- * and caching results for optimal performance.
102
- * @public
103
- */
104
- export class ResourceResolver implements IResourceResolver {
105
- /**
106
- * The resource manager that defines available resources and provides condition access.
107
- */
108
- public readonly resourceManager: IResourceManager;
109
-
110
- /**
111
- * The readonly qualifier type collector that provides qualifier implementations.
112
- */
113
- public readonly qualifierTypes: ReadOnlyQualifierTypeCollector;
114
-
115
- /**
116
- * The context qualifier provider that resolves qualifier values.
117
- */
118
- public readonly contextQualifierProvider: IContextQualifierProvider;
119
-
120
- /**
121
- * The configuration options for this resource resolver.
122
- */
123
- public readonly options: IResourceResolverOptions;
124
-
125
- /**
126
- * The readonly qualifier collector that provides qualifier implementations.
127
- */
128
- public get qualifiers(): IReadOnlyQualifierCollector {
129
- return this.contextQualifierProvider.qualifiers;
130
- }
131
-
132
- /**
133
- * The cache array for resolved conditions, indexed by condition index for O(1) lookup.
134
- * Each entry stores the resolved {@link Runtime.IConditionMatchResult | condition match result} for
135
- * the corresponding condition.
136
- */
137
- public get conditionCache(): ReadonlyArray<IConditionMatchResult | undefined> {
138
- return this._conditionCache;
139
- }
140
-
141
- /**
142
- * The cache array for resolved condition sets, indexed by condition set index for O(1) lookup.
143
- * Each entry stores the resolved ConditionSetResolutionResult for the corresponding condition set.
144
- */
145
- public get conditionSetCache(): ReadonlyArray<ConditionSetResolutionResult | undefined> {
146
- return this._conditionSetCache;
147
- }
148
-
149
- /**
150
- * The cache array for resolved decisions, indexed by decision index for O(1) lookup.
151
- * Each entry stores the resolved DecisionResolutionResult for the corresponding decision.
152
- */
153
- public get decisionCache(): ReadonlyArray<DecisionResolutionResult | undefined> {
154
- return this._decisionCache;
155
- }
156
-
157
- /**
158
- * Cache array for resolved conditions, indexed by condition index for O(1) lookup.
159
- * Each entry stores the resolved QualifierMatchScore for the corresponding condition.
160
- */
161
- private readonly _conditionCache: Array<IConditionMatchResult | undefined>;
162
-
163
- /**
164
- * Cache array for resolved condition sets, indexed by condition set index for O(1) lookup.
165
- * Each entry stores the resolved ConditionSetResolutionResult for the corresponding condition set.
166
- */
167
- private readonly _conditionSetCache: Array<ConditionSetResolutionResult | undefined>;
168
-
169
- /**
170
- * Cache array for resolved decisions, indexed by decision index for O(1) lookup.
171
- * Each entry stores the resolved DecisionResolutionResult for the corresponding decision.
172
- */
173
- private readonly _decisionCache: Array<DecisionResolutionResult | undefined>;
174
-
175
- /**
176
- * The listener for {@link Runtime.ResourceResolver | ResourceResolver} cache activity.
177
- */
178
- private readonly _listener?: IResourceResolverCacheListener;
179
-
180
- /**
181
- * Constructor for a {@link Runtime.ResourceResolver | ResourceResolver} object.
182
- * @param params - {@link Runtime.IResourceResolverCreateParams | Parameters} used to create the resolver.
183
- */
184
- protected constructor(params: IResourceResolverCreateParams) {
185
- this.resourceManager = params.resourceManager;
186
- this.qualifierTypes = params.qualifierTypes;
187
- this.contextQualifierProvider = params.contextQualifierProvider;
188
- this.options = {
189
- suppressNullAsDelete: params.options?.suppressNullAsDelete ?? false
190
- };
191
-
192
- // Initialize condition cache array with size matching the condition collector
193
- const conditionCollectorSize = this.resourceManager.conditions.size;
194
- this._conditionCache = new Array<IConditionMatchResult | undefined>(conditionCollectorSize);
195
-
196
- // Initialize condition set cache array with size matching the condition set collector
197
- const conditionSetCollectorSize = this.resourceManager.conditionSets.size;
198
- this._conditionSetCache = new Array<ConditionSetResolutionResult | undefined>(conditionSetCollectorSize);
199
-
200
- // Initialize decision cache array with size matching the decision collector
201
- const decisionCollectorSize = this.resourceManager.decisions.size;
202
- this._decisionCache = new Array<DecisionResolutionResult | undefined>(decisionCollectorSize);
203
-
204
- this._listener = params.listener;
205
- }
206
-
207
- /**
208
- * Creates a new {@link Runtime.ResourceResolver | ResourceResolver} object.
209
- * @param params - {@link Runtime.IResourceResolverCreateParams | Parameters} used to create the resolver.
210
- * @returns `Success` with the new {@link Runtime.ResourceResolver | ResourceResolver} object if successful,
211
- * or `Failure` with an error message if not.
212
- * @public
213
- */
214
- public static create(params: IResourceResolverCreateParams): Result<ResourceResolver> {
215
- return captureResult(() => new ResourceResolver(params));
216
- }
217
-
218
- /**
219
- * Resolves a condition by evaluating it against the current context.
220
- * Uses O(1) caching based on the condition's globally unique sequential index.
221
- * @param condition - The {@link Conditions.Condition | condition} to resolve.
222
- * @returns `Success` with the {@link QualifierMatchScore | match score} if successful,
223
- * or `Failure` with an error message if the condition cannot be resolved.
224
- * @public
225
- */
226
- public resolveCondition(condition: Condition): Result<IConditionMatchResult> {
227
- // Get the condition's index for cache lookup
228
- const conditionIndex = condition.index;
229
- if (conditionIndex === undefined) {
230
- return fail(`Condition "${condition.key}" does not have a valid index`);
231
- }
232
-
233
- // Check cache first for O(1) lookup
234
- const cachedResult = this._conditionCache[conditionIndex];
235
- if (cachedResult !== undefined) {
236
- this._listener?.onCacheHit('condition', conditionIndex);
237
- return succeed(cachedResult);
238
- }
239
-
240
- // Resolve the condition by getting qualifier value and evaluating with qualifier type
241
- const score = this.contextQualifierProvider
242
- .get(condition.qualifier)
243
- .onSuccess((qualifierValue) => {
244
- // Evaluate the condition using the qualifier type's matching logic
245
- return succeed(condition.qualifier.type.matches(condition.value, qualifierValue, condition.operator));
246
- })
247
- .onFailure((err) => {
248
- this._listener?.onContextError(condition.qualifier.name, err);
249
- return fail(err);
250
- })
251
- .orDefault(NoMatch);
252
-
253
- const priority = condition.priority;
254
- const scoreAsDefault = condition.scoreAsDefault ?? NoMatch;
255
- const matchResult: IConditionMatchResult =
256
- score > NoMatch
257
- ? { score, priority, matchType: 'match' }
258
- : scoreAsDefault > NoMatch
259
- ? { score: scoreAsDefault, priority, matchType: 'matchAsDefault' }
260
- : { score: NoMatch, priority, matchType: 'noMatch' };
261
-
262
- // Cache the resolved value for future O(1) lookup
263
- this._conditionCache[conditionIndex] = matchResult;
264
- this._listener?.onCacheMiss('condition', conditionIndex);
265
-
266
- return succeed(matchResult);
267
- }
268
-
269
- /**
270
- * Resolves a condition set by evaluating all its constituent conditions against the current context.
271
- * Uses O(1) caching based on the condition set's globally unique sequential index.
272
- * @param conditionSet - The {@link Conditions.ConditionSet | condition set} to resolve.
273
- * @returns `Success` with the {@link Runtime.ConditionSetResolutionResult | resolution result} if successful,
274
- * or `Failure` with an error message if the condition set cannot be resolved.
275
- * @public
276
- */
277
- public resolveConditionSet(conditionSet: ConditionSet): Result<ConditionSetResolutionResult> {
278
- // Get the condition set's index for cache lookup
279
- const conditionSetIndex = conditionSet.index;
280
- if (conditionSetIndex === undefined) {
281
- return fail(`ConditionSet "${conditionSet.key}" does not have a valid index`);
282
- }
283
-
284
- // Check cache first for O(1) lookup
285
- const cachedResult = this._conditionSetCache[conditionSetIndex];
286
- if (cachedResult !== undefined) {
287
- this._listener?.onCacheHit('conditionSet', conditionSetIndex);
288
- return succeed(cachedResult);
289
- }
290
-
291
- // Resolve all conditions in the condition set
292
- const conditions: Array<IConditionMatchResult> = [];
293
- let matchType: ConditionMatchType = 'match';
294
-
295
- for (const condition of conditionSet.conditions) {
296
- const { value: conditionResult, message: conditionMessage } = this.resolveCondition(condition);
297
-
298
- /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
299
- if (conditionMessage !== undefined) {
300
- this._listener?.onCacheError('conditionSet', conditionSetIndex);
301
- return fail(`Failed to resolve condition "${condition.key}": ${conditionMessage}`);
302
- }
303
-
304
- conditions.push(conditionResult);
305
-
306
- if (conditionResult.matchType === 'noMatch') {
307
- // Cache the failure result
308
- return ConditionSetResolutionResult.create('noMatch', conditions)
309
- .onSuccess((result) => {
310
- this._conditionSetCache[conditionSetIndex] = result;
311
- this._listener?.onCacheMiss('conditionSet', conditionSetIndex);
312
- return succeed(result);
313
- })
314
- .onFailure((err) => {
315
- /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
316
- this._conditionSetCache[conditionSetIndex] = undefined;
317
- this._listener?.onCacheError('conditionSet', conditionSetIndex);
318
- return fail(`${conditionSetIndex}: error creating condition set resolution result: ${err}`);
319
- });
320
- }
321
-
322
- if (conditionResult.matchType === 'matchAsDefault') {
323
- matchType = 'matchAsDefault';
324
- }
325
- }
326
-
327
- // Cache the successful result
328
- return ConditionSetResolutionResult.create(matchType, conditions)
329
- .onSuccess((result) => {
330
- this._conditionSetCache[conditionSetIndex] = result;
331
- this._listener?.onCacheMiss('conditionSet', conditionSetIndex);
332
- return succeed(result);
333
- })
334
- .onFailure((err) => {
335
- /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
336
- this._conditionSetCache[conditionSetIndex] = undefined;
337
- this._listener?.onCacheError('conditionSet', conditionSetIndex);
338
- return fail(`${conditionSetIndex}: error creating condition set resolution result: ${err}`);
339
- });
340
- }
341
-
342
- /**
343
- * Resolves a decision by evaluating all its constituent condition sets against the current context.
344
- * Uses O(1) caching based on the decision's globally unique sequential index.
345
- * @param decision - The {@link Decisions.AbstractDecision | abstract decision} to resolve.
346
- * @returns `Success` with the {@link Runtime.DecisionResolutionResult | resolution result} if successful,
347
- * or `Failure` with an error message if the decision cannot be resolved.
348
- * @public
349
- */
350
- public resolveDecision(decision: AbstractDecision): Result<DecisionResolutionResult> {
351
- // Get the decision's index for cache lookup
352
- const decisionIndex = decision.index;
353
- if (decisionIndex === undefined) {
354
- return fail(`Decision "${decision.key}" does not have a valid index`);
355
- }
356
-
357
- // Check cache first for O(1) lookup
358
- const cachedResult = this._decisionCache[decisionIndex];
359
- if (cachedResult !== undefined) {
360
- this._listener?.onCacheHit('decision', decisionIndex);
361
- return succeed(cachedResult);
362
- }
363
-
364
- // Resolve all condition sets in the decision
365
- const matchingInstanceResults: Array<{ index: number; result: ConditionSetResolutionResult }> = [];
366
- const matchingDefaultInstanceResults: Array<{ index: number; result: ConditionSetResolutionResult }> = [];
367
-
368
- for (let instanceIndex = 0; instanceIndex < decision.candidates.length; instanceIndex++) {
369
- const candidate = decision.candidates[instanceIndex];
370
- const conditionSetResult = this.resolveConditionSet(candidate.conditionSet);
371
-
372
- /* c8 ignore next 4 - defensive coding: extreme internal error scenario not reachable in normal operation */
373
- if (conditionSetResult.isFailure()) {
374
- this._listener?.onCacheError('decision', decisionIndex);
375
- return fail(`${decision.key}: Failed to resolve condition set": ${conditionSetResult.message}`);
376
- }
377
-
378
- const resolution = conditionSetResult.value;
379
-
380
- // Only include condition sets that match
381
- if (resolution.matchType === 'match') {
382
- matchingInstanceResults.push({
383
- index: instanceIndex,
384
- result: resolution
385
- });
386
- } else if (resolution.matchType === 'matchAsDefault') {
387
- matchingDefaultInstanceResults.push({
388
- index: instanceIndex,
389
- result: resolution
390
- });
391
- }
392
- // If condition set doesn't match, we simply skip it and continue (don't fail the decision)
393
- }
394
-
395
- // Sort by condition set resolution priority using the proper comparison logic
396
- matchingInstanceResults.sort((a, b) => ConditionSetResolutionResult.compare(a.result, b.result));
397
- matchingDefaultInstanceResults.sort((a, b) => ConditionSetResolutionResult.compare(a.result, b.result));
398
-
399
- // Extract just the instance indices in priority order
400
- const instanceIndices = matchingInstanceResults.map((item) => item.index);
401
- const defaultInstanceIndices = matchingDefaultInstanceResults.map((item) => item.index);
402
-
403
- // Cache the successful result
404
- const successResult: DecisionResolutionResult = {
405
- success: true,
406
- instanceIndices,
407
- defaultInstanceIndices
408
- };
409
- this._decisionCache[decisionIndex] = successResult;
410
- this._listener?.onCacheMiss('decision', decisionIndex);
411
-
412
- return succeed(successResult);
413
- }
414
-
415
- /**
416
- * Resolves a resource by finding the best matching candidate.
417
- * Uses the resource's associated decision to determine the best match based on the current context.
418
- * @param resource - The {@link Resources.Resource | resource} to resolve.
419
- * @returns `Success` with the best matching candidate if successful,
420
- * or `Failure` with an error message if no candidates match or resolution fails.
421
- * @public
422
- */
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
-
443
- // Get the abstract decision from the resource's concrete decision
444
- const abstractDecision = resource.decision.baseDecision;
445
-
446
- // Resolve the decision to get candidate indices in priority order
447
- const decisionResult = this.resolveDecision(abstractDecision);
448
- /* c8 ignore next 3 - defense in depth almost impossible to hit */
449
- if (decisionResult.isFailure()) {
450
- return fail(`Failed to resolve decision for resource "${resource.id}": ${decisionResult.message}`);
451
- }
452
-
453
- const resolution = decisionResult.value;
454
-
455
- // Check if any candidates matched (regular or default)
456
- if (
457
- !resolution.success ||
458
- (resolution.instanceIndices.length === 0 && resolution.defaultInstanceIndices.length === 0)
459
- ) {
460
- return fail(`No matching candidates found for resource "${resource.id}"`);
461
- }
462
-
463
- // Prefer regular matches over default matches
464
- const candidateIndex =
465
- resolution.instanceIndices.length > 0
466
- ? resolution.instanceIndices[0] // Best regular match
467
- : resolution.defaultInstanceIndices[0]; // Best default match
468
-
469
- if (candidateIndex >= resource.candidates.length) {
470
- return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
471
- }
472
-
473
- const bestCandidate = resource.candidates[candidateIndex];
474
- return succeed(bestCandidate);
475
- }
476
-
477
- /**
478
- * Resolves all matching resource candidates in priority order.
479
- * Uses the resource's associated decision to determine all matching candidates based on the current context.
480
- * @param resource - The {@link Resources.Resource | resource} to resolve.
481
- * @returns `Success` with an array of all matching candidates in priority order if successful,
482
- * or `Failure` with an error message if no candidates match or resolution fails.
483
- * @public
484
- */
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
-
507
- // Get the abstract decision from the resource's concrete decision
508
- const abstractDecision = resource.decision.baseDecision;
509
-
510
- // Resolve the decision to get candidate indices in priority order
511
- const decisionResult = this.resolveDecision(abstractDecision);
512
- /* c8 ignore next 3 - defense in depth almost impossible to hit */
513
- if (decisionResult.isFailure()) {
514
- return fail(`Failed to resolve decision for resource "${resource.id}": ${decisionResult.message}`);
515
- }
516
-
517
- const resolution = decisionResult.value;
518
-
519
- // Check if any candidates matched (regular or default)
520
- if (
521
- !resolution.success ||
522
- (resolution.instanceIndices.length === 0 && resolution.defaultInstanceIndices.length === 0)
523
- ) {
524
- return fail(`No matching candidates found for resource "${resource.id}"`);
525
- }
526
-
527
- // Get all matching candidates: regular matches first, then default matches
528
- const candidates: IResourceCandidate[] = [];
529
-
530
- // Add all regular matches first (already sorted by priority)
531
- for (const candidateIndex of resolution.instanceIndices) {
532
- if (candidateIndex >= resource.candidates.length) {
533
- return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
534
- }
535
- const candidate = resource.candidates[candidateIndex];
536
- candidates.push(candidate);
537
- }
538
-
539
- // Add all default matches after regular matches (already sorted by priority)
540
- for (const candidateIndex of resolution.defaultInstanceIndices) {
541
- /* c8 ignore next 3 - defensive coding: extreme internal error scenario not reachable in normal operation */
542
- if (candidateIndex >= resource.candidates.length) {
543
- return fail(`Invalid candidate index ${candidateIndex} for resource "${resource.id}"`);
544
- }
545
- const candidate = resource.candidates[candidateIndex];
546
- candidates.push(candidate);
547
- }
548
-
549
- return succeed(candidates);
550
- }
551
-
552
- /**
553
- * Resolves a resource to a composed value by merging matching candidates according to their merge methods.
554
- * Starting from the highest priority candidates, finds the first "full" candidate and merges all higher
555
- * priority "partial" candidates into it in ascending order of priority.
556
- * @param resource - The {@link Resources.Resource | resource} to resolve.
557
- * @returns `Success` with the composed JsonValue if successful,
558
- * or `Failure` with an error message if no candidates match or resolution fails.
559
- * @public
560
- */
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
-
582
- return this.resolveAllResourceCandidates(resource).onSuccess((candidates) => {
583
- /* c8 ignore next 3 - defense in depth should never occur */
584
- if (candidates.length === 0) {
585
- return fail(`${resource.id}: No matching candidates found.`);
586
- }
587
-
588
- // Find the first full candidate and collect all partial candidates above it
589
- let fullCandidateIndex = -1;
590
- const partialCandidates: IResourceCandidate[] = [];
591
-
592
- for (let i = 0; i < candidates.length; i++) {
593
- const candidate = candidates[i];
594
- if (!candidate.isPartial) {
595
- // Found the first full candidate
596
- fullCandidateIndex = i;
597
- break;
598
- } else {
599
- // Collect partial candidates (these are in ascending priority order)
600
- partialCandidates.unshift(candidate);
601
- }
602
- }
603
-
604
- // If no full candidate found, use the last candidate as the base
605
- const baseCandidateIndex = fullCandidateIndex >= 0 ? fullCandidateIndex : candidates.length - 1;
606
- const baseCandidate = candidates[baseCandidateIndex];
607
-
608
- // If there are no partial candidates to merge, but null-as-delete is enabled,
609
- // still process through JsonEditor to handle null properties in the base candidate
610
- if (partialCandidates.length === 0) {
611
- if (this.options.suppressNullAsDelete || !isJsonObject(baseCandidate.json)) {
612
- return succeed(baseCandidate.json);
613
- }
614
-
615
- // Process single candidate through JsonEditor to apply null-as-delete
616
- const editor = JsonEditor.create({
617
- merge: {
618
- arrayMergeBehavior: 'replace',
619
- nullAsDelete: true
620
- }
621
- }).orThrow(); // Should never fail with valid options
622
-
623
- return editor
624
- .mergeObjectsInPlace({}, [baseCandidate.json])
625
- .withErrorFormat((err) => `${resource.id}: Composition failed: ${err}`);
626
- }
627
-
628
- const allCandidates = [
629
- baseCandidate.json,
630
- ...partialCandidates.map((candidate) => candidate.json)
631
- ].filter((v): v is JsonObject => isJsonObject(v));
632
-
633
- /* c8 ignore next 3 - defensive check: non-object values in resource candidates should be prevented at validation time */
634
- if (allCandidates.length !== partialCandidates.length + 1) {
635
- return fail(`${resource.id}: Unable to compose non-object candidate values.`);
636
- }
637
-
638
- // Create JsonEditor with array replacement behavior and null-as-delete for resource composition
639
- const editor = JsonEditor.create({
640
- merge: {
641
- arrayMergeBehavior: 'replace',
642
- nullAsDelete: !this.options.suppressNullAsDelete
643
- }
644
- }).orThrow(); // Should never fail with valid options
645
-
646
- return editor
647
- .mergeObjectsInPlace({}, allCandidates)
648
- .withErrorFormat((err) => `${resource.id}: Composition failed: ${err}`);
649
- });
650
- }
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
-
670
- /**
671
- * Clears all caches (condition, condition set, and decision), forcing all cached items
672
- * to be re-evaluated on next access. This should be called when the context changes and cached
673
- * results are no longer valid.
674
- * @public
675
- */
676
- public clearConditionCache(): void {
677
- this._conditionCache.fill(undefined);
678
- this._conditionSetCache.fill(undefined);
679
- this._decisionCache.fill(undefined);
680
-
681
- this._listener?.onCacheClear('condition');
682
- this._listener?.onCacheClear('conditionSet');
683
- this._listener?.onCacheClear('decision');
684
- }
685
-
686
- /**
687
- * Gets the current size of the condition cache array.
688
- * @returns The size of the condition cache array.
689
- * @public
690
- */
691
- public get conditionCacheSize(): number {
692
- return this._conditionCache.length;
693
- }
694
-
695
- /**
696
- * Gets the current size of the condition set cache array.
697
- * @returns The size of the condition set cache array.
698
- * @public
699
- */
700
- public get conditionSetCacheSize(): number {
701
- return this._conditionSetCache.length;
702
- }
703
-
704
- /**
705
- * Gets the current size of the decision cache array.
706
- * @returns The size of the decision cache array.
707
- * @public
708
- */
709
- public get decisionCacheSize(): number {
710
- return this._decisionCache.length;
711
- }
712
- }