@fgv/ts-res 5.0.0-9 → 5.0.1-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 (384) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/CHANGELOG.md +14 -1
  3. package/README.md +794 -137
  4. package/dist/ts-res.d.ts +2257 -268
  5. package/dist/tsdoc-metadata.json +1 -1
  6. package/eslint.config.js +16 -0
  7. package/lib/index.d.ts +2 -1
  8. package/lib/index.js +3 -1
  9. package/lib/packlets/bundle/bundleBuilder.js +7 -4
  10. package/lib/packlets/bundle/bundleLoader.js +1 -0
  11. package/lib/packlets/bundle/bundleUtils.js +14 -16
  12. package/lib/packlets/bundle/index.d.ts +3 -1
  13. package/lib/packlets/bundle/index.js +14 -3
  14. package/lib/packlets/common/convert.d.ts +15 -0
  15. package/lib/packlets/common/convert.js +16 -1
  16. package/lib/packlets/common/helpers/context.js +1 -0
  17. package/lib/packlets/common/helpers/resources.d.ts +7 -0
  18. package/lib/packlets/common/helpers/resources.js +13 -0
  19. package/lib/packlets/common/resources.d.ts +44 -1
  20. package/lib/packlets/common/validate/conditions.d.ts +17 -1
  21. package/lib/packlets/common/validate/conditions.js +32 -6
  22. package/lib/packlets/common/validate/regularExpressions.d.ts +4 -0
  23. package/lib/packlets/common/validate/regularExpressions.js +5 -1
  24. package/lib/packlets/common/validate/resources.d.ts +35 -1
  25. package/lib/packlets/common/validate/resources.js +55 -0
  26. package/lib/packlets/conditions/condition.js +1 -0
  27. package/lib/packlets/conditions/conditionSet.d.ts +9 -0
  28. package/lib/packlets/conditions/conditionSet.js +75 -1
  29. package/lib/packlets/config/configInitFactory.d.ts +217 -0
  30. package/lib/packlets/config/configInitFactory.js +302 -0
  31. package/lib/packlets/config/convert.d.ts +9 -0
  32. package/lib/packlets/config/convert.js +12 -1
  33. package/lib/packlets/config/index.d.ts +1 -0
  34. package/lib/packlets/config/index.js +1 -0
  35. package/lib/packlets/config/json.d.ts +1 -1
  36. package/lib/packlets/config/systemConfiguration.d.ts +16 -3
  37. package/lib/packlets/config/systemConfiguration.js +19 -40
  38. package/lib/packlets/import/fsItem.d.ts +2 -1
  39. package/lib/packlets/import/fsItem.js +9 -13
  40. package/lib/packlets/import/importManager.d.ts +2 -1
  41. package/lib/packlets/import/importers/jsonImporter.js +1 -0
  42. package/lib/packlets/import/importers/pathImporter.d.ts +2 -1
  43. package/lib/packlets/import/importers/pathImporter.js +2 -1
  44. package/lib/packlets/qualifier-types/config/convert.d.ts +22 -1
  45. package/lib/packlets/qualifier-types/config/convert.js +67 -2
  46. package/lib/packlets/qualifier-types/config/json.d.ts +24 -2
  47. package/lib/packlets/qualifier-types/config/json.js +11 -0
  48. package/lib/packlets/qualifier-types/convert.d.ts +1 -1
  49. package/lib/packlets/qualifier-types/helpers.d.ts +14 -5
  50. package/lib/packlets/qualifier-types/helpers.js +2 -2
  51. package/lib/packlets/qualifier-types/languageQualifierType.d.ts +28 -2
  52. package/lib/packlets/qualifier-types/languageQualifierType.js +73 -0
  53. package/lib/packlets/qualifier-types/literalQualifierType.d.ts +31 -5
  54. package/lib/packlets/qualifier-types/literalQualifierType.js +75 -0
  55. package/lib/packlets/qualifier-types/literalValueHierarchy.d.ts +7 -11
  56. package/lib/packlets/qualifier-types/literalValueHierarchy.js +12 -1
  57. package/lib/packlets/qualifier-types/qualifierType.d.ts +31 -2
  58. package/lib/packlets/qualifier-types/territoryQualifierType.d.ts +31 -5
  59. package/lib/packlets/qualifier-types/territoryQualifierType.js +80 -0
  60. package/lib/packlets/qualifiers/qualifierCollector.js +1 -2
  61. package/lib/packlets/resource-json/compiled/convert.js +2 -1
  62. package/lib/packlets/resource-json/compiled/json.d.ts +6 -2
  63. package/lib/packlets/resource-types/config/convert.d.ts +1 -1
  64. package/lib/packlets/resource-types/config/convert.js +3 -1
  65. package/lib/packlets/resource-types/config/json.d.ts +3 -1
  66. package/lib/packlets/resource-types/jsonResourceType.d.ts +10 -1
  67. package/lib/packlets/resource-types/jsonResourceType.js +7 -3
  68. package/lib/packlets/resource-types/resourceType.d.ts +100 -8
  69. package/lib/packlets/resource-types/resourceType.js +39 -3
  70. package/lib/packlets/resources/candidateReducer.js +3 -0
  71. package/lib/packlets/resources/candidateValue.d.ts +92 -0
  72. package/lib/packlets/resources/candidateValue.js +128 -0
  73. package/lib/packlets/resources/candidateValueCollector.d.ts +55 -0
  74. package/lib/packlets/resources/candidateValueCollector.js +96 -0
  75. package/lib/packlets/resources/common.d.ts +14 -0
  76. package/lib/packlets/resources/deltaGenerator.d.ts +189 -0
  77. package/lib/packlets/resources/deltaGenerator.js +344 -0
  78. package/lib/packlets/resources/index.d.ts +3 -0
  79. package/lib/packlets/resources/index.js +3 -0
  80. package/lib/packlets/resources/resource.d.ts +6 -6
  81. package/lib/packlets/resources/resource.js +5 -10
  82. package/lib/packlets/resources/resourceBuilder.d.ts +6 -0
  83. package/lib/packlets/resources/resourceBuilder.js +4 -1
  84. package/lib/packlets/resources/resourceCandidate.d.ts +15 -2
  85. package/lib/packlets/resources/resourceCandidate.js +17 -5
  86. package/lib/packlets/resources/resourceManagerBuilder.d.ts +131 -10
  87. package/lib/packlets/resources/resourceManagerBuilder.js +289 -45
  88. package/lib/packlets/runtime/compiledResourceCollection.d.ts +27 -11
  89. package/lib/packlets/runtime/compiledResourceCollection.js +71 -28
  90. package/lib/packlets/runtime/conditionSetResolutionResult.js +3 -0
  91. package/lib/packlets/runtime/context/contextQualifierProvider.d.ts +53 -3
  92. package/lib/packlets/runtime/context/contextQualifierProviderValidator.d.ts +82 -37
  93. package/lib/packlets/runtime/context/contextQualifierProviderValidator.js +49 -77
  94. package/lib/packlets/runtime/context/simpleContextQualifierProvider.d.ts +23 -2
  95. package/lib/packlets/runtime/context/simpleContextQualifierProvider.js +53 -31
  96. package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.d.ts +4 -4
  97. package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.js +2 -2
  98. package/lib/packlets/runtime/iResourceManager.d.ts +17 -5
  99. package/lib/packlets/runtime/index.d.ts +1 -0
  100. package/lib/packlets/runtime/index.js +1 -0
  101. package/lib/packlets/runtime/resource-tree/common.d.ts +1 -1
  102. package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.d.ts +7 -7
  103. package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.js +5 -5
  104. package/lib/packlets/runtime/resourceResolver.d.ts +43 -1
  105. package/lib/packlets/runtime/resourceResolver.js +56 -28
  106. package/lib/packlets/runtime/resourceTreeResolver.d.ts +152 -0
  107. package/lib/packlets/runtime/resourceTreeResolver.js +222 -0
  108. package/lib/packlets/runtime/validate.js +1 -0
  109. package/lib/packlets/zip-archive/convert.d.ts +52 -0
  110. package/lib/packlets/zip-archive/convert.js +103 -0
  111. package/lib/packlets/zip-archive/index.d.ts +23 -0
  112. package/lib/packlets/zip-archive/index.js +95 -0
  113. package/lib/packlets/zip-archive/json.d.ts +64 -0
  114. package/lib/packlets/zip-archive/json.js +24 -0
  115. package/lib/packlets/zip-archive/types.d.ts +98 -0
  116. package/lib/packlets/zip-archive/types.js +39 -0
  117. package/lib/packlets/zip-archive/zipArchiveCreator.d.ts +35 -0
  118. package/lib/packlets/zip-archive/zipArchiveCreator.js +195 -0
  119. package/lib/packlets/zip-archive/zipArchiveFormat.d.ts +70 -0
  120. package/lib/packlets/zip-archive/zipArchiveFormat.js +184 -0
  121. package/lib/packlets/zip-archive/zipArchiveLoader.d.ts +70 -0
  122. package/lib/packlets/zip-archive/zipArchiveLoader.js +286 -0
  123. package/package.json +26 -32
  124. package/CLAUDE.md +0 -186
  125. package/EXAMPLE_INIT_PARAMS.md +0 -88
  126. package/PROGRESS_SUMMARY.md +0 -48
  127. package/lib/index.d.ts.map +0 -1
  128. package/lib/index.js.map +0 -1
  129. package/lib/packlets/bundle/bundleBuilder.d.ts.map +0 -1
  130. package/lib/packlets/bundle/bundleBuilder.js.map +0 -1
  131. package/lib/packlets/bundle/bundleLoader.d.ts.map +0 -1
  132. package/lib/packlets/bundle/bundleLoader.js.map +0 -1
  133. package/lib/packlets/bundle/bundleNormalizer.d.ts.map +0 -1
  134. package/lib/packlets/bundle/bundleNormalizer.js.map +0 -1
  135. package/lib/packlets/bundle/bundleUtils.d.ts.map +0 -1
  136. package/lib/packlets/bundle/bundleUtils.js.map +0 -1
  137. package/lib/packlets/bundle/convert.d.ts.map +0 -1
  138. package/lib/packlets/bundle/convert.js.map +0 -1
  139. package/lib/packlets/bundle/index.d.ts.map +0 -1
  140. package/lib/packlets/bundle/index.js.map +0 -1
  141. package/lib/packlets/bundle/model.d.ts.map +0 -1
  142. package/lib/packlets/bundle/model.js.map +0 -1
  143. package/lib/packlets/common/conditions.d.ts.map +0 -1
  144. package/lib/packlets/common/conditions.js.map +0 -1
  145. package/lib/packlets/common/convert.d.ts.map +0 -1
  146. package/lib/packlets/common/convert.js.map +0 -1
  147. package/lib/packlets/common/helpers/conditions.d.ts.map +0 -1
  148. package/lib/packlets/common/helpers/conditions.js.map +0 -1
  149. package/lib/packlets/common/helpers/context.d.ts.map +0 -1
  150. package/lib/packlets/common/helpers/context.js.map +0 -1
  151. package/lib/packlets/common/helpers/index.d.ts.map +0 -1
  152. package/lib/packlets/common/helpers/index.js.map +0 -1
  153. package/lib/packlets/common/helpers/qualifierDefaultValues.d.ts.map +0 -1
  154. package/lib/packlets/common/helpers/qualifierDefaultValues.js.map +0 -1
  155. package/lib/packlets/common/helpers/resources.d.ts.map +0 -1
  156. package/lib/packlets/common/helpers/resources.js.map +0 -1
  157. package/lib/packlets/common/index.d.ts.map +0 -1
  158. package/lib/packlets/common/index.js.map +0 -1
  159. package/lib/packlets/common/resources.d.ts.map +0 -1
  160. package/lib/packlets/common/resources.js.map +0 -1
  161. package/lib/packlets/common/validate/conditions.d.ts.map +0 -1
  162. package/lib/packlets/common/validate/conditions.js.map +0 -1
  163. package/lib/packlets/common/validate/index.d.ts.map +0 -1
  164. package/lib/packlets/common/validate/index.js.map +0 -1
  165. package/lib/packlets/common/validate/regularExpressions.d.ts.map +0 -1
  166. package/lib/packlets/common/validate/regularExpressions.js.map +0 -1
  167. package/lib/packlets/common/validate/resources.d.ts.map +0 -1
  168. package/lib/packlets/common/validate/resources.js.map +0 -1
  169. package/lib/packlets/conditions/condition.d.ts.map +0 -1
  170. package/lib/packlets/conditions/condition.js.map +0 -1
  171. package/lib/packlets/conditions/conditionCollector.d.ts.map +0 -1
  172. package/lib/packlets/conditions/conditionCollector.js.map +0 -1
  173. package/lib/packlets/conditions/conditionDecls.d.ts.map +0 -1
  174. package/lib/packlets/conditions/conditionDecls.js.map +0 -1
  175. package/lib/packlets/conditions/conditionSet.d.ts.map +0 -1
  176. package/lib/packlets/conditions/conditionSet.js.map +0 -1
  177. package/lib/packlets/conditions/conditionSetCollector.d.ts.map +0 -1
  178. package/lib/packlets/conditions/conditionSetCollector.js.map +0 -1
  179. package/lib/packlets/conditions/conditionSetDecls.d.ts.map +0 -1
  180. package/lib/packlets/conditions/conditionSetDecls.js.map +0 -1
  181. package/lib/packlets/conditions/conditionToken.d.ts.map +0 -1
  182. package/lib/packlets/conditions/conditionToken.js.map +0 -1
  183. package/lib/packlets/conditions/convert/conditionSetDecls.d.ts.map +0 -1
  184. package/lib/packlets/conditions/convert/conditionSetDecls.js.map +0 -1
  185. package/lib/packlets/conditions/convert/decls.d.ts.map +0 -1
  186. package/lib/packlets/conditions/convert/decls.js.map +0 -1
  187. package/lib/packlets/conditions/convert/index.d.ts.map +0 -1
  188. package/lib/packlets/conditions/convert/index.js.map +0 -1
  189. package/lib/packlets/conditions/index.d.ts.map +0 -1
  190. package/lib/packlets/conditions/index.js.map +0 -1
  191. package/lib/packlets/config/common.d.ts.map +0 -1
  192. package/lib/packlets/config/common.js.map +0 -1
  193. package/lib/packlets/config/convert.d.ts.map +0 -1
  194. package/lib/packlets/config/convert.js.map +0 -1
  195. package/lib/packlets/config/index.d.ts.map +0 -1
  196. package/lib/packlets/config/index.js.map +0 -1
  197. package/lib/packlets/config/json.d.ts.map +0 -1
  198. package/lib/packlets/config/json.js.map +0 -1
  199. package/lib/packlets/config/predefined/default.d.ts.map +0 -1
  200. package/lib/packlets/config/predefined/default.js.map +0 -1
  201. package/lib/packlets/config/predefined/extended.d.ts.map +0 -1
  202. package/lib/packlets/config/predefined/extended.js.map +0 -1
  203. package/lib/packlets/config/predefined/index.d.ts.map +0 -1
  204. package/lib/packlets/config/predefined/index.js.map +0 -1
  205. package/lib/packlets/config/systemConfiguration.d.ts.map +0 -1
  206. package/lib/packlets/config/systemConfiguration.js.map +0 -1
  207. package/lib/packlets/context/contextDecls.d.ts.map +0 -1
  208. package/lib/packlets/context/contextDecls.js.map +0 -1
  209. package/lib/packlets/context/contextToken.d.ts.map +0 -1
  210. package/lib/packlets/context/contextToken.js.map +0 -1
  211. package/lib/packlets/context/convert/decls.d.ts.map +0 -1
  212. package/lib/packlets/context/convert/decls.js.map +0 -1
  213. package/lib/packlets/context/convert/index.d.ts.map +0 -1
  214. package/lib/packlets/context/convert/index.js.map +0 -1
  215. package/lib/packlets/context/index.d.ts.map +0 -1
  216. package/lib/packlets/context/index.js.map +0 -1
  217. package/lib/packlets/decisions/abstractDecision.d.ts.map +0 -1
  218. package/lib/packlets/decisions/abstractDecision.js.map +0 -1
  219. package/lib/packlets/decisions/abstractDecisionCollector.d.ts.map +0 -1
  220. package/lib/packlets/decisions/abstractDecisionCollector.js.map +0 -1
  221. package/lib/packlets/decisions/candidate.d.ts.map +0 -1
  222. package/lib/packlets/decisions/candidate.js.map +0 -1
  223. package/lib/packlets/decisions/common.d.ts.map +0 -1
  224. package/lib/packlets/decisions/common.js.map +0 -1
  225. package/lib/packlets/decisions/concreteDecision.d.ts.map +0 -1
  226. package/lib/packlets/decisions/concreteDecision.js.map +0 -1
  227. package/lib/packlets/decisions/decision.d.ts.map +0 -1
  228. package/lib/packlets/decisions/decision.js.map +0 -1
  229. package/lib/packlets/decisions/index.d.ts.map +0 -1
  230. package/lib/packlets/decisions/index.js.map +0 -1
  231. package/lib/packlets/import/fsItem.d.ts.map +0 -1
  232. package/lib/packlets/import/fsItem.js.map +0 -1
  233. package/lib/packlets/import/importContext.d.ts.map +0 -1
  234. package/lib/packlets/import/importContext.js.map +0 -1
  235. package/lib/packlets/import/importManager.d.ts.map +0 -1
  236. package/lib/packlets/import/importManager.js.map +0 -1
  237. package/lib/packlets/import/importable.d.ts.map +0 -1
  238. package/lib/packlets/import/importable.js.map +0 -1
  239. package/lib/packlets/import/importers/collectionImporter.d.ts.map +0 -1
  240. package/lib/packlets/import/importers/collectionImporter.js.map +0 -1
  241. package/lib/packlets/import/importers/fsItemImporter.d.ts.map +0 -1
  242. package/lib/packlets/import/importers/fsItemImporter.js.map +0 -1
  243. package/lib/packlets/import/importers/importer.d.ts.map +0 -1
  244. package/lib/packlets/import/importers/importer.js.map +0 -1
  245. package/lib/packlets/import/importers/index.d.ts.map +0 -1
  246. package/lib/packlets/import/importers/index.js.map +0 -1
  247. package/lib/packlets/import/importers/jsonImporter.d.ts.map +0 -1
  248. package/lib/packlets/import/importers/jsonImporter.js.map +0 -1
  249. package/lib/packlets/import/importers/pathImporter.d.ts.map +0 -1
  250. package/lib/packlets/import/importers/pathImporter.js.map +0 -1
  251. package/lib/packlets/import/index.d.ts.map +0 -1
  252. package/lib/packlets/import/index.js.map +0 -1
  253. package/lib/packlets/qualifier-types/config/convert.d.ts.map +0 -1
  254. package/lib/packlets/qualifier-types/config/convert.js.map +0 -1
  255. package/lib/packlets/qualifier-types/config/index.d.ts.map +0 -1
  256. package/lib/packlets/qualifier-types/config/index.js.map +0 -1
  257. package/lib/packlets/qualifier-types/config/json.d.ts.map +0 -1
  258. package/lib/packlets/qualifier-types/config/json.js.map +0 -1
  259. package/lib/packlets/qualifier-types/convert.d.ts.map +0 -1
  260. package/lib/packlets/qualifier-types/convert.js.map +0 -1
  261. package/lib/packlets/qualifier-types/helpers.d.ts.map +0 -1
  262. package/lib/packlets/qualifier-types/helpers.js.map +0 -1
  263. package/lib/packlets/qualifier-types/index.d.ts.map +0 -1
  264. package/lib/packlets/qualifier-types/index.js.map +0 -1
  265. package/lib/packlets/qualifier-types/languageQualifierType.d.ts.map +0 -1
  266. package/lib/packlets/qualifier-types/languageQualifierType.js.map +0 -1
  267. package/lib/packlets/qualifier-types/literalQualifierType.d.ts.map +0 -1
  268. package/lib/packlets/qualifier-types/literalQualifierType.js.map +0 -1
  269. package/lib/packlets/qualifier-types/literalValueHierarchy.d.ts.map +0 -1
  270. package/lib/packlets/qualifier-types/literalValueHierarchy.js.map +0 -1
  271. package/lib/packlets/qualifier-types/qualifierType.d.ts.map +0 -1
  272. package/lib/packlets/qualifier-types/qualifierType.js.map +0 -1
  273. package/lib/packlets/qualifier-types/qualifierTypeCollector.d.ts.map +0 -1
  274. package/lib/packlets/qualifier-types/qualifierTypeCollector.js.map +0 -1
  275. package/lib/packlets/qualifier-types/territoryQualifierType.d.ts.map +0 -1
  276. package/lib/packlets/qualifier-types/territoryQualifierType.js.map +0 -1
  277. package/lib/packlets/qualifiers/convert/decls.d.ts.map +0 -1
  278. package/lib/packlets/qualifiers/convert/decls.js.map +0 -1
  279. package/lib/packlets/qualifiers/convert/index.d.ts.map +0 -1
  280. package/lib/packlets/qualifiers/convert/index.js.map +0 -1
  281. package/lib/packlets/qualifiers/convert/qualifier.d.ts.map +0 -1
  282. package/lib/packlets/qualifiers/convert/qualifier.js.map +0 -1
  283. package/lib/packlets/qualifiers/index.d.ts.map +0 -1
  284. package/lib/packlets/qualifiers/index.js.map +0 -1
  285. package/lib/packlets/qualifiers/qualifier.d.ts.map +0 -1
  286. package/lib/packlets/qualifiers/qualifier.js.map +0 -1
  287. package/lib/packlets/qualifiers/qualifierCollector.d.ts.map +0 -1
  288. package/lib/packlets/qualifiers/qualifierCollector.js.map +0 -1
  289. package/lib/packlets/qualifiers/qualifierDecl.d.ts.map +0 -1
  290. package/lib/packlets/qualifiers/qualifierDecl.js.map +0 -1
  291. package/lib/packlets/qualifiers/qualifierDefaultValueDecls.d.ts.map +0 -1
  292. package/lib/packlets/qualifiers/qualifierDefaultValueDecls.js.map +0 -1
  293. package/lib/packlets/qualifiers/qualifierDefaultValueToken.d.ts.map +0 -1
  294. package/lib/packlets/qualifiers/qualifierDefaultValueToken.js.map +0 -1
  295. package/lib/packlets/resource-json/compiled/common.d.ts.map +0 -1
  296. package/lib/packlets/resource-json/compiled/common.js.map +0 -1
  297. package/lib/packlets/resource-json/compiled/convert.d.ts.map +0 -1
  298. package/lib/packlets/resource-json/compiled/convert.js.map +0 -1
  299. package/lib/packlets/resource-json/compiled/index.d.ts.map +0 -1
  300. package/lib/packlets/resource-json/compiled/index.js.map +0 -1
  301. package/lib/packlets/resource-json/compiled/json.d.ts.map +0 -1
  302. package/lib/packlets/resource-json/compiled/json.js.map +0 -1
  303. package/lib/packlets/resource-json/convert.d.ts.map +0 -1
  304. package/lib/packlets/resource-json/convert.js.map +0 -1
  305. package/lib/packlets/resource-json/helpers.d.ts.map +0 -1
  306. package/lib/packlets/resource-json/helpers.js.map +0 -1
  307. package/lib/packlets/resource-json/index.d.ts.map +0 -1
  308. package/lib/packlets/resource-json/index.js.map +0 -1
  309. package/lib/packlets/resource-json/json.d.ts.map +0 -1
  310. package/lib/packlets/resource-json/json.js.map +0 -1
  311. package/lib/packlets/resource-json/normalized.d.ts.map +0 -1
  312. package/lib/packlets/resource-json/normalized.js.map +0 -1
  313. package/lib/packlets/resource-json/resourceDeclCollection.d.ts.map +0 -1
  314. package/lib/packlets/resource-json/resourceDeclCollection.js.map +0 -1
  315. package/lib/packlets/resource-json/resourceDeclContainer.d.ts.map +0 -1
  316. package/lib/packlets/resource-json/resourceDeclContainer.js.map +0 -1
  317. package/lib/packlets/resource-json/resourceDeclTree.d.ts.map +0 -1
  318. package/lib/packlets/resource-json/resourceDeclTree.js.map +0 -1
  319. package/lib/packlets/resource-types/config/convert.d.ts.map +0 -1
  320. package/lib/packlets/resource-types/config/convert.js.map +0 -1
  321. package/lib/packlets/resource-types/config/index.d.ts.map +0 -1
  322. package/lib/packlets/resource-types/config/index.js.map +0 -1
  323. package/lib/packlets/resource-types/config/json.d.ts.map +0 -1
  324. package/lib/packlets/resource-types/config/json.js.map +0 -1
  325. package/lib/packlets/resource-types/helpers.d.ts.map +0 -1
  326. package/lib/packlets/resource-types/helpers.js.map +0 -1
  327. package/lib/packlets/resource-types/index.d.ts.map +0 -1
  328. package/lib/packlets/resource-types/index.js.map +0 -1
  329. package/lib/packlets/resource-types/jsonResourceType.d.ts.map +0 -1
  330. package/lib/packlets/resource-types/jsonResourceType.js.map +0 -1
  331. package/lib/packlets/resource-types/resourceType.d.ts.map +0 -1
  332. package/lib/packlets/resource-types/resourceType.js.map +0 -1
  333. package/lib/packlets/resource-types/resourceTypeCollector.d.ts.map +0 -1
  334. package/lib/packlets/resource-types/resourceTypeCollector.js.map +0 -1
  335. package/lib/packlets/resources/candidateReducer.d.ts.map +0 -1
  336. package/lib/packlets/resources/candidateReducer.js.map +0 -1
  337. package/lib/packlets/resources/common.d.ts.map +0 -1
  338. package/lib/packlets/resources/common.js.map +0 -1
  339. package/lib/packlets/resources/index.d.ts.map +0 -1
  340. package/lib/packlets/resources/index.js.map +0 -1
  341. package/lib/packlets/resources/resource.d.ts.map +0 -1
  342. package/lib/packlets/resources/resource.js.map +0 -1
  343. package/lib/packlets/resources/resourceBuilder.d.ts.map +0 -1
  344. package/lib/packlets/resources/resourceBuilder.js.map +0 -1
  345. package/lib/packlets/resources/resourceCandidate.d.ts.map +0 -1
  346. package/lib/packlets/resources/resourceCandidate.js.map +0 -1
  347. package/lib/packlets/resources/resourceManagerBuilder.d.ts.map +0 -1
  348. package/lib/packlets/resources/resourceManagerBuilder.js.map +0 -1
  349. package/lib/packlets/runtime/cacheListener.d.ts.map +0 -1
  350. package/lib/packlets/runtime/cacheListener.js.map +0 -1
  351. package/lib/packlets/runtime/cacheMetrics.d.ts.map +0 -1
  352. package/lib/packlets/runtime/cacheMetrics.js.map +0 -1
  353. package/lib/packlets/runtime/compiledResourceCollection.d.ts.map +0 -1
  354. package/lib/packlets/runtime/compiledResourceCollection.js.map +0 -1
  355. package/lib/packlets/runtime/conditionSetResolutionResult.d.ts.map +0 -1
  356. package/lib/packlets/runtime/conditionSetResolutionResult.js.map +0 -1
  357. package/lib/packlets/runtime/context/contextQualifierProvider.d.ts.map +0 -1
  358. package/lib/packlets/runtime/context/contextQualifierProvider.js.map +0 -1
  359. package/lib/packlets/runtime/context/contextQualifierProviderValidator.d.ts.map +0 -1
  360. package/lib/packlets/runtime/context/contextQualifierProviderValidator.js.map +0 -1
  361. package/lib/packlets/runtime/context/index.d.ts.map +0 -1
  362. package/lib/packlets/runtime/context/index.js.map +0 -1
  363. package/lib/packlets/runtime/context/simpleContextQualifierProvider.d.ts.map +0 -1
  364. package/lib/packlets/runtime/context/simpleContextQualifierProvider.js.map +0 -1
  365. package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.d.ts.map +0 -1
  366. package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.js.map +0 -1
  367. package/lib/packlets/runtime/iResourceManager.d.ts.map +0 -1
  368. package/lib/packlets/runtime/iResourceManager.js.map +0 -1
  369. package/lib/packlets/runtime/index.d.ts.map +0 -1
  370. package/lib/packlets/runtime/index.js.map +0 -1
  371. package/lib/packlets/runtime/resource-tree/common.d.ts.map +0 -1
  372. package/lib/packlets/runtime/resource-tree/common.js.map +0 -1
  373. package/lib/packlets/runtime/resource-tree/index.d.ts.map +0 -1
  374. package/lib/packlets/runtime/resource-tree/index.js.map +0 -1
  375. package/lib/packlets/runtime/resource-tree/readOnlyResourceTree.d.ts.map +0 -1
  376. package/lib/packlets/runtime/resource-tree/readOnlyResourceTree.js.map +0 -1
  377. package/lib/packlets/runtime/resource-tree/resourceTreeChildren.d.ts.map +0 -1
  378. package/lib/packlets/runtime/resource-tree/resourceTreeChildren.js.map +0 -1
  379. package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.d.ts.map +0 -1
  380. package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.js.map +0 -1
  381. package/lib/packlets/runtime/resourceResolver.d.ts.map +0 -1
  382. package/lib/packlets/runtime/resourceResolver.js.map +0 -1
  383. package/lib/packlets/runtime/validate.d.ts.map +0 -1
  384. package/lib/packlets/runtime/validate.js.map +0 -1
package/README.md CHANGED
@@ -5,18 +5,123 @@
5
5
 
6
6
  A TypeScript library for multidimensional resource management with internationalization and localization support. `@fgv/ts-res` provides a sophisticated system for managing resources with complex conditional logic based on qualifiers like language, territory, and custom dimensions.
7
7
 
8
- ## Features
8
+ ## Understanding the Resource Model
9
9
 
10
- - **Multidimensional Resource Management**: Organize resources by language, territory, and custom qualifiers
11
- - **Conditional Resource Resolution**: Automatic selection of the most appropriate resource based on context
12
- - **Type-Safe**: Full TypeScript support with comprehensive type definitions
13
- - **Flexible Resource Formats**: Support for JSON resources with extensible resource types
14
- - **Hierarchical Organization**: Tree-based and collection-based resource structures
15
- - **File System Integration**: Import resources from file systems with automatic condition inference
16
- - **Priority-Based Resolution**: Configurable priority levels for different qualifiers
17
- - **Resource Merging**: Support for augmenting and replacing resources
18
- - **BCP47 Language Support**: Built-in support for BCP47 language tags
19
- - **Extensible Architecture**: Plugin-based system for custom qualifier types
10
+ ### What is a Resource?
11
+
12
+ In `@fgv/ts-res`, a **resource** represents a logical entity (like a user profile form, API configuration, or UI component) that can vary based on context. Instead of simple key-value pairs, resources are composed of **candidates** - each providing a full or partial representation of that entity for various different contexts.
13
+
14
+ The context is represented as a configurable set of typed **qualifiers** with some value. Each candidate has an associated **condition set** consisting of zero or more **conditions**, each of which specifies some qualifier and a value to be matched. Qualifiers not present in a condition set are irrelevant, so the empty condition set is *unconditional* and matches any context.
15
+
16
+ ```typescript
17
+ // Traditional i18n approach - separate keys, full duplication
18
+ {
19
+ "profile.title.en": "User Profile",
20
+ "profile.title.fr": "Profil Utilisateur",
21
+ "profile.button.en": "Save Changes",
22
+ "profile.button.fr": "Enregistrer les Modifications",
23
+ "profile.title.en-CA": "User Profile", // Duplicated!
24
+ "profile.button.en-CA": "Save Changes" // Duplicated!
25
+ }
26
+
27
+ // ts-res approach - one resource, multiple candidates
28
+ {
29
+ "id": "user.profile",
30
+ "candidates": [
31
+ {
32
+ "json": { "title": "User Profile", "button": "Save Changes" },
33
+ "conditions": { "language": "en" }
34
+ },
35
+ {
36
+ "json": { "title": "Profil Utilisateur", "button": "Enregistrer" },
37
+ "conditions": { "language": "fr" }
38
+ },
39
+ {
40
+ "json": { "button": "Save Eh!" }, // Only override button text
41
+ "conditions": { "language": "en", "territory": "CA" }
42
+ }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ ### Partial Candidates: The Power of Composition
48
+
49
+ It is possible that several candidates for some resource will match any given context, which enables **composition** of **partial candidates** - you don't need to duplicate entire resources, just specify what changes:
50
+
51
+ ```typescript
52
+ // Base configuration for all environments
53
+ {
54
+ "id": "api.config",
55
+ "candidates": [
56
+ {
57
+ "json": {
58
+ "timeout": 5000,
59
+ "retries": 3,
60
+ "features": { "analytics": true, "debugging": false }
61
+ },
62
+ "conditions": {} // Base case - applies everywhere
63
+ },
64
+ {
65
+ "json": {
66
+ "url": "https://api.production.com",
67
+ "features": { "debugging": false }
68
+ },
69
+ "conditions": { "environment": "production" }
70
+ },
71
+ {
72
+ "json": {
73
+ "url": "https://api.dev.com",
74
+ "features": { "debugging": true } // Only override debugging
75
+ },
76
+ "conditions": { "environment": "development" }
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ ### Three Resolution Strategies
83
+
84
+ **1. Single Best Match** - Pick the most specific candidate only:
85
+ ```typescript
86
+ // Context: { environment: "development" }
87
+ const bestMatch = resolver.resolveBestResourceCandidate('api.config').orThrow();
88
+ console.log(bestMatch.candidate.json);
89
+ // Returns: { "url": "api.dev.com", "features": { "debugging": true } }
90
+ // Note: Only the development candidate, not merged with base
91
+ ```
92
+
93
+ **2. All Matching Candidates** - Get everything that matches for analysis:
94
+ ```typescript
95
+ const allMatches = resolver.resolveAllCandidates('api.config').orThrow();
96
+ // Returns array: [base candidate, development candidate]
97
+ // Each candidate as separate objects, not merged
98
+ ```
99
+
100
+ **3. Composed Resolution** - Merge all matching candidates in priority order:
101
+ ```typescript
102
+ // Context: { language: "en", territory: "CA", userType: "admin" }
103
+ const composed = resolver.resolveComposedResourceValue('user.dashboard').orThrow();
104
+ console.log(composed);
105
+ // Returns merged result: {
106
+ // title: 'Dashboard', // from base English
107
+ // nav: { home: 'Home', settings: 'Preferences' }, // base + Canadian override
108
+ // actions: {
109
+ // save: 'Save, eh!', // from Canadian variant
110
+ // cancel: 'Cancel', // from base English
111
+ // admin: 'Admin Panel' // from admin variant
112
+ // }
113
+ // }
114
+ ```
115
+
116
+ ### Why This Matters
117
+
118
+ **Eliminates Duplication**: Change base configuration once, variants inherit automatically
119
+
120
+ **Handles Complexity**: Easily manage overlapping conditions (Canadian English admin users vs regular Canadian English users)
121
+
122
+ **Maintainable**: Add new dimensions (user types, themes, devices) without restructuring existing resources
123
+
124
+ **Flexible**: Same resource system handles i18n, configuration management, feature flags, and more
20
125
 
21
126
  ## Installation
22
127
 
@@ -26,139 +131,630 @@ npm install @fgv/ts-res
26
131
 
27
132
  ## Quick Start
28
133
 
134
+ This example demonstrates all three resolution strategies using a user dashboard that varies by language, region, and user type:
135
+
29
136
  ```typescript
30
137
  import * as TsRes from '@fgv/ts-res';
31
138
 
32
- // 1. Set up qualifier types
33
- const qualifierTypes = TsRes.QualifierTypes.QualifierTypeCollector.create({
34
- qualifierTypes: [
35
- TsRes.QualifierTypes.LanguageQualifierType.create().orThrow(),
36
- TsRes.QualifierTypes.TerritoryQualifierType.create().orThrow()
139
+ // 1. Create a resource manager using predefined configuration
140
+ const manager = TsRes.Resources.ResourceManagerBuilder.createPredefined('default').orThrow();
141
+
142
+ // 2. Add a resource with base + partial candidates
143
+ manager.addResource({
144
+ id: 'user.dashboard',
145
+ resourceTypeName: 'json',
146
+ candidates: [
147
+ // Base English dashboard
148
+ {
149
+ json: {
150
+ title: 'Dashboard',
151
+ nav: { home: 'Home', settings: 'Settings' },
152
+ actions: { save: 'Save', cancel: 'Cancel' }
153
+ },
154
+ conditions: { language: 'en' }
155
+ },
156
+ // Canadian English - only changes some terminology
157
+ {
158
+ json: {
159
+ nav: { settings: 'Preferences' }, // Only override this
160
+ actions: { save: 'Save, eh!' } // And this
161
+ },
162
+ conditions: { language: 'en', territory: 'CA' }
163
+ },
164
+ // Admin users get additional actions
165
+ {
166
+ json: {
167
+ actions: {
168
+ save: 'Save',
169
+ cancel: 'Cancel',
170
+ admin: 'Admin Panel' // Additional action
171
+ }
172
+ },
173
+ conditions: { userType: 'admin' }
174
+ }
37
175
  ]
38
176
  }).orThrow();
39
177
 
40
- // 2. Define qualifiers with priorities
41
- const qualifiers = TsRes.Qualifiers.QualifierCollector.create({
42
- qualifierTypes,
43
- qualifiers: [
44
- { name: 'language', typeName: 'language', defaultPriority: 600 },
45
- { name: 'territory', typeName: 'territory', defaultPriority: 700 }
46
- ]
178
+ // 3. Build the resource manager to prepare for resolution
179
+ manager.build().orThrow();
180
+
181
+ // 4. Create resolver and demonstrate different resolution strategies
182
+ const resolver = TsRes.ResourceResolver.create(manager).orThrow();
183
+
184
+ // Canadian admin user context
185
+ const contextualResolver = resolver.withContext({
186
+ language: 'en',
187
+ territory: 'CA',
188
+ userType: 'admin'
47
189
  }).orThrow();
48
190
 
49
- // 3. Set up resource types
50
- const resourceTypes = TsRes.ResourceTypes.ResourceTypeCollector.create({
191
+ // COMPOSED RESOLUTION - Merges base + Canadian + admin candidates
192
+ const composed = contextualResolver.resolveComposedResourceValue('user.dashboard').orThrow();
193
+ console.log(composed);
194
+ // Result: {
195
+ // title: 'Dashboard', // from base English
196
+ // nav: { home: 'Home', settings: 'Preferences' }, // base + Canadian override
197
+ // actions: {
198
+ // save: 'Save, eh!', // from Canadian variant
199
+ // cancel: 'Cancel', // from base English
200
+ // admin: 'Admin Panel' // from admin variant
201
+ // }
202
+ // }
203
+
204
+ // SINGLE BEST MATCH - Get most specific candidate only
205
+ const bestMatch = contextualResolver.resolveBestResourceCandidate('user.dashboard').orThrow();
206
+ console.log(bestMatch.candidate.json);
207
+ // Returns just the admin candidate (most specific match)
208
+
209
+ // ALL MATCHES - Get all matching candidates for analysis
210
+ const allMatches = contextualResolver.resolveAllCandidates('user.dashboard').orThrow();
211
+ console.log(`Found ${allMatches.length} matching candidates`);
212
+ // Returns array: [base English, Canadian English, admin]
213
+ ```
214
+
215
+ ## Features
216
+
217
+ - **Composed Resource Resolution**: Merge multiple partial candidates into complete resources
218
+ - **Multidimensional Qualifiers**: Organize by language, territory, user types, environments, and custom dimensions
219
+ - **Flexible Resolution Strategies**: Single best match, all matches, or composed resolution
220
+ - **Type-Safe**: Full TypeScript support with comprehensive type definitions
221
+ - **Partial Candidate Support**: Override only what changes, inherit the rest
222
+ - **Priority-Based Matching**: Configurable priority levels for different qualifiers
223
+ - **Resource Merging**: Support for augmenting and replacing merge strategies
224
+ - **File System Integration**: Import resources from file systems with automatic condition inference
225
+ - **Context Filtering**: Generate clean environment-specific bundles
226
+ - **BCP47 Language Support**: Built-in support for BCP47 language tags
227
+ - **Extensible Architecture**: Plugin-based system for custom qualifier types
228
+
229
+ ## Technical Concepts
230
+
231
+ ### Qualifiers and Conditions
232
+
233
+ **Qualifiers** define the dimensions along which resources can vary:
234
+
235
+ ```typescript
236
+ // Built-in qualifiers
237
+ - language: 'en', 'fr', 'en-CA' // BCP47 language tags
238
+ - territory: 'US', 'CA', 'FR' // Geographic regions
239
+ - currentTerritory: 'US' // User's current location
240
+
241
+ // Custom qualifiers
242
+ - userType: 'admin', 'user', 'guest'
243
+ - environment: 'dev', 'staging', 'prod'
244
+ - theme: 'light', 'dark'
245
+ - device: 'mobile', 'tablet', 'desktop'
246
+ ```
247
+
248
+ **Conditions** specify when a candidate applies using qualifier-value pairs:
249
+
250
+ ```typescript
251
+ {
252
+ language: 'en-CA', // Canadian English
253
+ territory: 'CA', // Canada
254
+ userType: 'admin' // Admin users only
255
+ }
256
+ ```
257
+
258
+ ### Resolution Algorithm
259
+
260
+ **Step 1: Individual Condition Scoring**
261
+ Each condition is evaluated against the context to produce a score between 0.0 (NoMatch) and 1.0 (PerfectMatch):
262
+
263
+ ```typescript
264
+ // Language hierarchy matching
265
+ context: { language: "en-US" }
266
+ condition: { language: "en-US" } → 1.0 (PerfectMatch)
267
+ condition: { language: "en" } → 0.8 (partial match - more general)
268
+ condition: { language: "fr-FR" } → 0.0 (NoMatch)
269
+
270
+ // Context list priority (earlier positions beat later)
271
+ context: { languages: ["en-US", "fr-FR"] }
272
+ condition: { language: "en" } → matches first preference (higher score)
273
+ condition: { language: "fr-FR" } → matches second preference (lower score)
274
+
275
+ // Territory/literal hierarchies can also produce partial matches
276
+ ```
277
+
278
+ **Step 2: Condition Set Evaluation**
279
+ A candidate's condition set matches only if ALL conditions score > 0.0. If any condition yields NoMatch (0.0), the entire candidate is eliminated.
280
+
281
+ **Step 3: Priority-First Ranking**
282
+ Each qualifier has a default priority that its conditions inherit. Candidates are ranked using a priority-first system:
283
+
284
+ 1. **Primary rule**: Candidate with the highest priority condition ALWAYS wins
285
+ 2. **Secondary rule**: Score is only used as tie-breaker when priorities are equal
286
+ 3. **Comparison**: Sort conditions within each candidate by (priority DESC, score DESC), then compare candidates pairwise
287
+
288
+ ```typescript
289
+ // Context: { language: "en-CA", territory: "CA" }
290
+
291
+ // Example: Language (priority 600) beats Territory (priority 400)
292
+ Candidate A: { language: "en" } → priority 600, score 0.8 (partial match with en-CA)
293
+ Candidate B: { territory: "CA" } → priority 400, score 1.0 (perfect match)
294
+ // Winner: Candidate A (higher priority wins despite lower score)
295
+
296
+ // When priorities match, score breaks ties
297
+ Candidate A: { language: "en-CA" } → priority 600, score 1.0 (perfect match)
298
+ Candidate B: { language: "en" } → priority 600, score 0.8 (partial match)
299
+ // Winner: Candidate A (same priority, higher score)
300
+
301
+ // Multiple conditions: ALL must match, highest priority condition determines rank
302
+ Candidate C: { language: "en", territory: "CA" } → both conditions match
303
+ // Conditions: language (priority 600, score 0.8), territory (priority 400, score 1.0)
304
+ // Candidate rank determined by highest: priority 600, score 0.8
305
+ ```
306
+
307
+ **Step 4: Fallback Resolution (when no candidates match)**
308
+ If no candidates pass Step 2 (all are eliminated by `NoMatch` conditions), the system falls back to resolution considering defaults:
309
+
310
+ ```typescript
311
+ // Qualifier configured with default language = "en-US"
312
+ // Context: { language: "de-DE" } (German - no candidates match)
313
+
314
+ // During fallback, NoMatch conditions get promoted to their scoreAsDefault:
315
+ condition: { language: "en-US" } → scoreAsDefault = 1.0 (perfect match with default)
316
+ condition: { language: "fr-FR" } → scoreAsDefault = 0.0 (no match with default)
317
+ condition: { language: "en-GB" } → scoreAsDefault = 0.7 (partial match with default)
318
+
319
+ // Now English candidates become available for German users as intelligent fallbacks
320
+ ```
321
+
322
+ The `scoreAsDefault` is automatically calculated when creating conditions by comparing the condition's value against the qualifier's configured default value using the same 0.0-1.0 scoring logic.
323
+
324
+ **Step 5: Resolution Strategy Application**
325
+
326
+ - **Single Best Match**: Return the top-ranked candidate only
327
+ - **All Candidates**: Return all candidates that passed matching (Step 2 or fallback Step 4), with their rankings
328
+ - **Composed Resolution**: Merge all matching candidates in rank order (lowest rank merged first, highest rank applied last)
329
+
330
+ ## Creating Resources
331
+
332
+ The most flexible and maintainable way to manage resources is through filesystem import, which automatically infers conditions and resource IDs from your directory structure.
333
+
334
+ ### Filesystem Import (Recommended)
335
+
336
+ Organize your resources in a directory structure that reflects their conditions. Since values like "CA" or "FR" could be either language or territory, use explicit qualifiers or tokens:
337
+
338
+ ```
339
+ resources/
340
+ ├── language=en/ # language: "en"
341
+ │ ├── dashboard.json # id: "dashboard"
342
+ │ ├── profile.json # id: "profile"
343
+ │ └── territory=CA/ # language: "en", territory: "CA"
344
+ │ └── dashboard.json # id: "dashboard" (Canadian override)
345
+ ├── language=fr/ # language: "fr"
346
+ │ ├── dashboard.json # id: "dashboard"
347
+ │ └── territory=CA/ # language: "fr", territory: "CA"
348
+ │ └── dashboard.json # id: "dashboard" (Quebec variant)
349
+ └── _resources.json # Resource collection or configuration
350
+ ```
351
+
352
+ Or with tokens configured for brevity:
353
+
354
+ ```
355
+ resources/
356
+ ├── lang=en/ # language: "en" (using token)
357
+ │ ├── dashboard.json
358
+ │ └── terr=CA/ # territory: "CA" (using token)
359
+ │ └── dashboard.json
360
+ ├── lang=fr/ # language: "fr" (using token)
361
+ │ └── terr=CA/ # territory: "CA" (using token)
362
+ │ └── dashboard.json
363
+ └── resources-config.json # Configuration with token definitions
364
+ ```
365
+
366
+ Import resources with automatic context inference:
367
+
368
+ ```typescript
369
+ import * as TsRes from '@fgv/ts-res';
370
+
371
+ // Create configuration with tokens for cleaner folder names
372
+ const config = TsRes.Config.SystemConfiguration.create({
373
+ name: 'my-app',
374
+ qualifierTypes: [
375
+ TsRes.QualifierTypes.LanguageQualifierType.create().orThrow(),
376
+ TsRes.QualifierTypes.TerritoryQualifierType.create().orThrow()
377
+ ],
378
+ qualifiers: [
379
+ { name: 'language', typeName: 'language', defaultPriority: 600, token: 'lang' },
380
+ { name: 'territory', typeName: 'territory', defaultPriority: 400, token: 'terr' }
381
+ ],
51
382
  resourceTypes: [
52
383
  TsRes.ResourceTypes.JsonResourceType.create().orThrow()
53
384
  ]
54
385
  }).orThrow();
55
386
 
56
- // 4. Create resource manager
57
- const manager = TsRes.Resources.ResourceManager.create({
58
- qualifiers,
59
- resourceTypes
387
+ const manager = TsRes.Resources.ResourceManagerBuilder.create(config).orThrow();
388
+
389
+ // Import from filesystem - conditions are inferred from folder names
390
+ const importManager = TsRes.Import.ImportManager.create({
391
+ filetree: fileTree,
392
+ resources: manager,
393
+ importers: TsRes.Import.ImportManager.getDefaultImporters(),
394
+ initialContext: TsRes.Import.ImportContext.create().orThrow()
395
+ }).orThrow();
396
+
397
+ const result = importManager.importFromFileSystem({
398
+ type: 'path',
399
+ path: './resources'
60
400
  }).orThrow();
61
401
 
62
- // 5. Add resources
63
- const resources = [
64
- {
65
- id: 'greeting.message',
66
- json: { message: 'Hello' },
67
- conditions: { language: 'en' }
68
- },
69
- {
70
- id: 'greeting.message',
71
- json: { message: 'Bonjour' },
72
- conditions: { language: 'fr' }
73
- },
74
- {
75
- id: 'greeting.message',
76
- json: { message: 'Hello from Canada' },
77
- conditions: {
78
- language: 'en-CA',
79
- territory: 'CA'
80
- }
81
- }
82
- ];
402
+ // Result: All resources loaded with appropriate conditions
403
+ // - resources/lang=en/dashboard.json → { language: "en" }
404
+ // - resources/lang=en/terr=CA/dashboard.json → { language: "en", territory: "CA" }
405
+ ```
83
406
 
84
- resources.forEach(resource => {
85
- manager.addLooseCandidate(resource).orThrow();
86
- });
407
+ ### Resource File Formats
87
408
 
88
- // 6. Build and resolve resources
89
- manager.build().orThrow();
409
+ Resources can be stored in two formats:
90
410
 
91
- const greetingResource = manager.getBuiltResource('greeting.message').orThrow();
92
- const context = { language: 'en-CA', territory: 'CA' };
93
- const candidates = greetingResource.getCandidatesForContext(context);
411
+ **1. Resource Collection Format** (`_resources.json`):
94
412
 
95
- // Returns the most specific match: "Hello from Canada"
413
+ Resource collections are flexible containers that can include:
414
+ - **Loose resources**: Complete resources with their candidates
415
+ - **Loose candidates**: Additional candidates to merge with existing resources
416
+ - **Nested collections**: Collections within collections for organization
417
+
418
+ ```json
419
+ {
420
+ "resources": [
421
+ {
422
+ "id": "dashboard",
423
+ "candidates": [
424
+ {
425
+ "json": { "title": "Dashboard", "welcome": "Welcome!" },
426
+ "conditions": { "language": "en" }
427
+ }
428
+ ]
429
+ }
430
+ ],
431
+ "candidates": [
432
+ {
433
+ "resourceId": "dashboard",
434
+ "json": { "welcome": "Welcome, eh!" },
435
+ "conditions": { "language": "en", "territory": "CA" }
436
+ },
437
+ {
438
+ "resourceId": "profile",
439
+ "json": { "title": "User Profile" },
440
+ "conditions": { "language": "en" }
441
+ }
442
+ ],
443
+ "collections": [
444
+ {
445
+ "name": "legal",
446
+ "resources": [
447
+ {
448
+ "id": "legal.privacy",
449
+ "candidates": [
450
+ {
451
+ "json": { "url": "/privacy.html" },
452
+ "conditions": {}
453
+ }
454
+ ]
455
+ }
456
+ ]
457
+ }
458
+ ]
459
+ }
96
460
  ```
97
461
 
98
- ## Core Concepts
462
+ The importer merges all these elements:
463
+ - Loose candidates are added to their target resources
464
+ - Nested collections are flattened with appropriate ID prefixing
465
+ - Multiple candidates for the same resource are merged
99
466
 
100
- ### Qualifiers
467
+ **2. Resource Tree Format** (individual JSON files):
468
+ ```json
469
+ // resources/en/dashboard.json
470
+ {
471
+ "title": "Dashboard",
472
+ "welcome": "Welcome!",
473
+ "nav": {
474
+ "home": "Home",
475
+ "settings": "Settings"
476
+ }
477
+ }
101
478
 
102
- Qualifiers are dimensions along which resources can vary. Common qualifiers include:
479
+ // resources/en/CA/dashboard.json (partial override)
480
+ {
481
+ "welcome": "Welcome, eh!",
482
+ "nav": {
483
+ "settings": "Preferences" // Canadian terminology
484
+ }
485
+ }
486
+ ```
103
487
 
104
- - **Language**: BCP47 language tags (e.g., 'en', 'fr', 'en-CA')
105
- - **Territory**: Geographic regions (e.g., 'US', 'CA', 'FR')
106
- - **Custom**: Application-specific dimensions (e.g., 'theme', 'userType')
488
+ ### Context Collection During Import
107
489
 
108
- ### Conditions
490
+ The importer recognizes qualifier values in folder and file names using specific patterns:
109
491
 
110
- Conditions determine when a resource candidate applies. They consist of qualifier-value pairs:
492
+ **1. Default Pattern: `qualifier=value`**
493
+ ```
494
+ resources/
495
+ ├── language=en/ # { language: "en" }
496
+ ├── language=fr-CA/ # { language: "fr-CA" }
497
+ ├── territory=US/ # { territory: "US" }
498
+ └── environment=production/ # { environment: "production" }
499
+ ```
111
500
 
501
+ **2. Token Shorthand (when configured)**
502
+ Qualifiers can define a "token" for brevity:
112
503
  ```typescript
504
+ // Configuration
113
505
  {
114
- language: 'en-CA',
115
- territory: 'CA',
116
- theme: 'dark'
506
+ qualifiers: [
507
+ { name: "language", token: "lang" }, // Allow "lang=en"
508
+ { name: "homeTerritory", token: "home" }, // Allow "home=US"
509
+ { name: "environment", token: "env" } // Allow "env=prod"
510
+ ]
117
511
  }
118
512
  ```
513
+ ```
514
+ resources/
515
+ ├── lang=en-US/ # { language: "en-US" } via token
516
+ ├── home=CA/ # { homeTerritory: "CA" } via token
517
+ └── env=production/ # { environment: "production" } via token
518
+ ```
119
519
 
120
- ### Resource Resolution
520
+ **3. Optional Token Pattern (use with caution!)**
521
+ When `tokenIsOptional: true`, the qualifier name/token can be omitted:
522
+ ```typescript
523
+ {
524
+ qualifiers: [
525
+ {
526
+ name: "homeTerritory",
527
+ token: "home",
528
+ tokenIsOptional: true // DANGER: "CA" alone will match!
529
+ }
530
+ ]
531
+ }
532
+ ```
533
+ ```
534
+ resources/
535
+ ├── CA/ # { homeTerritory: "CA" } - matches without prefix!
536
+ ├── home=US/ # { homeTerritory: "US" } - explicit still works
537
+ └── strings/ # Regular folder (if "strings" isn't a valid territory)
538
+ ```
121
539
 
122
- The library automatically selects the most appropriate resource based on:
540
+ **⚠️ Warning**: Be very cautious with `tokenIsOptional`. If your qualifier accepts permissive values (like territories that could be any 2-letter code), ordinary folder names might be misinterpreted as qualifier values.
123
541
 
124
- 1. **Specificity**: More specific conditions take precedence
125
- 2. **Priority**: Higher priority qualifiers are weighted more heavily
126
- 3. **Fallback**: Graceful degradation when exact matches aren't found
542
+ **4. Context Accumulation**
543
+ Nested folders inherit and combine parent conditions:
544
+ ```
545
+ resources/
546
+ └── language=en/
547
+ └── territory=CA/
548
+ └── role=admin/ # { language: "en", territory: "CA", role: "admin" }
549
+ ```
550
+
551
+ **5. Resource ID Prefixing**
552
+ Non-qualifier folders become part of the resource ID:
553
+ ```
554
+ resources/
555
+ ├── legal/ # Not a qualifier, becomes ID prefix
556
+ │ ├── privacy.json # id: "legal.privacy"
557
+ │ └── territory=EU/
558
+ │ └── privacy.json # id: "legal.privacy", conditions: { territory: "EU" }
559
+ └── language=fr/
560
+ └── legal/
561
+ └── privacy.json # id: "legal.privacy", conditions: { language: "fr" }
562
+ ```
563
+
564
+ ### Manual Resource Creation (Alternative)
565
+
566
+ For simple cases or dynamic resources, you can create them programmatically:
567
+
568
+ ```typescript
569
+ manager.addResource({
570
+ id: 'api.config',
571
+ resourceTypeName: 'json',
572
+ candidates: [
573
+ {
574
+ json: { url: 'localhost:3000' },
575
+ conditions: { environment: 'development' }
576
+ },
577
+ {
578
+ json: { url: 'api.production.com' },
579
+ conditions: { environment: 'production' }
580
+ }
581
+ ]
582
+ }).orThrow();
583
+ ```
584
+
585
+ This is useful for configuration that changes based on deployment environment or resources generated at runtime.
127
586
 
128
587
  ## Advanced Usage
129
588
 
130
- ### Resource Building with ResourceBuilder
589
+ ### Custom Priority and Default Scoring
590
+
591
+ While priority and `scoreAsDefault` are typically determined automatically by qualifier configuration, they can be overridden for advanced use cases:
592
+
593
+ **Use Case 1: Custom Priority for Territory-Based Content**
594
+
595
+ For legal documents and branding, territory often matters more than language:
131
596
 
132
597
  ```typescript
133
- const builder = TsRes.Resources.ResourceBuilder.create({
134
- qualifiers,
135
- resourceTypes
598
+ // Configuration: Elevate territory priority for legal/brand content
599
+ const territoryPriorityConfig = TsRes.Config.SystemConfiguration.create({
600
+ name: 'territory-priority',
601
+ qualifierTypes: [
602
+ TsRes.QualifierTypes.LanguageQualifierType.create().orThrow(),
603
+ TsRes.QualifierTypes.TerritoryQualifierType.create().orThrow()
604
+ ],
605
+ qualifiers: [
606
+ { name: 'language', typeName: 'language', defaultPriority: 600 },
607
+ { name: 'currentTerritory', typeName: 'territory', defaultPriority: 700 } // Higher than language
608
+ ],
609
+ resourceTypes: [TsRes.ResourceTypes.JsonResourceType.create().orThrow()]
136
610
  }).orThrow();
137
611
 
138
- const resource = builder
139
- .withId('user.profile')
140
- .withResourceTypeName('json')
141
- .withCandidate({
142
- json: { title: 'Profile', button: 'Edit' },
143
- conditions: { language: 'en' }
144
- })
145
- .withCandidate({
146
- json: { title: 'Profil', button: 'Modifier' },
147
- conditions: { language: 'fr' }
148
- })
149
- .build()
150
- .orThrow();
612
+ // Legal document with territory-specific versions
613
+ manager.addResource({
614
+ id: 'legal.privacy-policy',
615
+ candidates: [
616
+ {
617
+ json: { url: '/privacy-us.html' },
618
+ conditions: { currentTerritory: 'US' } // Priority 700
619
+ },
620
+ {
621
+ json: { url: '/privacy-ca.html' },
622
+ conditions: { currentTerritory: 'CA' } // Priority 700
623
+ },
624
+ {
625
+ json: { url: '/privacy-generic-fr.html' },
626
+ conditions: { language: 'fr' } // Priority 600 (lower)
627
+ }
628
+ ]
629
+ }).orThrow();
151
630
 
152
- manager.addResource(resource).orThrow();
631
+ // Context: { language: 'fr-CA', currentTerritory: 'CA' }
632
+ // Winner: Canadian privacy policy (territory priority 700 > language priority 600)
633
+ // Result: { url: '/privacy-ca.html' } - legally correct for Canada
634
+ ```
635
+
636
+ **Use Case 2: Custom scoreAsDefault for "Original Language" Scenarios**
637
+
638
+ For content like quotes or historical documents, you might want to preserve the original language:
639
+
640
+ ```typescript
641
+ // Configuration: Custom scoreAsDefault for originalLanguage qualifier
642
+ const originalLanguageConfig = TsRes.Config.SystemConfiguration.create({
643
+ name: 'preserve-original',
644
+ qualifierTypes: [
645
+ TsRes.QualifierTypes.LanguageQualifierType.create().orThrow()
646
+ ],
647
+ qualifiers: [
648
+ { name: 'language', typeName: 'language', defaultPriority: 600, defaultValue: 'en' },
649
+ { name: 'originalLanguage', typeName: 'language', defaultPriority: 650, defaultValue: 'original' }
650
+ ],
651
+ resourceTypes: [TsRes.ResourceTypes.JsonResourceType.create().orThrow()]
652
+ }).orThrow();
653
+
654
+ // Historical quote that should prefer original language
655
+ manager.addResource({
656
+ id: 'quotes.einstein',
657
+ candidates: [
658
+ {
659
+ json: { text: 'Gott würfelt nicht', attribution: 'Einstein, 1926' },
660
+ conditions: { originalLanguage: 'de' } // German original, scoreAsDefault = 1.0
661
+ },
662
+ {
663
+ json: { text: 'God does not play dice', attribution: 'Einstein, 1926' },
664
+ conditions: { language: 'en' } // English translation, scoreAsDefault = calculated from default
665
+ }
666
+ ]
667
+ }).orThrow();
668
+
669
+ // Context: { language: 'fr' } (French user, no direct matches)
670
+ // Fallback resolution: originalLanguage='de' gets scoreAsDefault=1.0, language='en' gets lower score
671
+ // Winner: German original (higher scoreAsDefault in fallback resolution)
672
+ // Result: { text: 'Gott würfelt nicht', attribution: 'Einstein, 1926' }
673
+ ```
674
+
675
+ These patterns ensure that content with special requirements (legal compliance, historical accuracy) gets appropriate priority treatment.
676
+
677
+ **Individual Condition Overrides for Outlier Cases**
678
+
679
+ For rare exceptions, you can override priority or scoreAsDefault directly in individual condition declarations without creating new configurations:
680
+
681
+ ```typescript
682
+ // Most content uses standard language/territory priorities
683
+ // But this specific legal resource needs territory to take precedence
684
+
685
+ manager.addResource({
686
+ id: 'legal.gdpr-notice',
687
+ candidates: [
688
+ {
689
+ json: { text: 'GDPR applies', url: '/gdpr-eu.html' },
690
+ conditions: {
691
+ territory: {
692
+ qualifier: 'territory',
693
+ operator: 'eq',
694
+ value: 'EU',
695
+ priority: 700 // Override: higher than normal territory priority
696
+ }
697
+ }
698
+ },
699
+ {
700
+ json: { text: 'Generic privacy notice', url: '/privacy.html' },
701
+ conditions: { language: 'en' } // Uses default language priority (600)
702
+ }
703
+ ]
704
+ }).orThrow();
705
+
706
+ // Individual scoreAsDefault override for a specific quote
707
+ manager.addResource({
708
+ id: 'quotes.shakespeare',
709
+ candidates: [
710
+ {
711
+ json: { text: 'To be or not to be', source: 'Hamlet' },
712
+ conditions: {
713
+ originalLanguage: {
714
+ qualifier: 'originalLanguage',
715
+ operator: 'eq',
716
+ value: 'en',
717
+ scoreAsDefault: 1.0 // Override: perfect fallback score for English original
718
+ }
719
+ }
720
+ }
721
+ ]
722
+ }).orThrow();
723
+ ```
724
+
725
+ This approach is more efficient than creating separate configurations when you only have occasional exceptions to the standard priority rules.
726
+
727
+ ### Resource Building with ResourceBuilder
728
+
729
+ ```typescript
730
+ // Using the resource manager's builder for individual resources
731
+ const manager = TsRes.Resources.ResourceManagerBuilder.createPredefined('default').orThrow();
732
+
733
+ manager.addResource({
734
+ id: 'user.profile',
735
+ resourceTypeName: 'json',
736
+ candidates: [
737
+ {
738
+ json: { title: 'Profile', button: 'Edit' },
739
+ conditions: { language: 'en' }
740
+ },
741
+ {
742
+ json: { title: 'Profil', button: 'Modifier' },
743
+ conditions: { language: 'fr' }
744
+ }
745
+ ]
746
+ }).orThrow();
153
747
  ```
154
748
 
155
749
  ### File System Integration
156
750
 
157
751
  ```typescript
158
- // Import resources from file system
752
+ // Create a resource manager and import from file system
753
+ const manager = TsRes.Resources.ResourceManagerBuilder.createPredefined('default').orThrow();
754
+
159
755
  const importManager = TsRes.Import.ImportManager.create({
160
756
  filetree: fileTree,
161
- resources: resourceManager,
757
+ resources: manager,
162
758
  importers: TsRes.Import.ImportManager.getDefaultImporters(),
163
759
  initialContext: TsRes.Import.ImportContext.create().orThrow()
164
760
  }).orThrow();
@@ -182,16 +778,36 @@ Resources support different merge strategies:
182
778
  }
183
779
  ```
184
780
 
185
- ### Custom Qualifier Types
781
+ ### Custom System Configuration
186
782
 
187
783
  ```typescript
188
- const customType = TsRes.QualifierTypes.LiteralQualifierType.create({
189
- key: 'userType',
190
- values: ['admin', 'user', 'guest'],
191
- hierarchy: {
192
- 'admin': ['user', 'guest'],
193
- 'user': ['guest']
194
- }
784
+ // Create a system configuration with custom qualifiers and types
785
+ const customConfig = TsRes.Config.SystemConfiguration.create({
786
+ name: 'custom-config',
787
+ description: 'Custom configuration with user types',
788
+ qualifierTypes: [
789
+ TsRes.QualifierTypes.LanguageQualifierType.create().orThrow(),
790
+ TsRes.QualifierTypes.LiteralQualifierType.create({
791
+ key: 'userType',
792
+ values: ['admin', 'user', 'guest'],
793
+ hierarchy: {
794
+ 'admin': ['user', 'guest'],
795
+ 'user': ['guest']
796
+ }
797
+ }).orThrow()
798
+ ],
799
+ qualifiers: [
800
+ { name: 'language', typeName: 'language', defaultPriority: 600 },
801
+ { name: 'userType', typeName: 'userType', defaultPriority: 500 }
802
+ ],
803
+ resourceTypes: [
804
+ TsRes.ResourceTypes.JsonResourceType.create().orThrow()
805
+ ]
806
+ }).orThrow();
807
+
808
+ const manager = TsRes.Resources.ResourceManagerBuilder.create({
809
+ qualifiers: customConfig.qualifiers,
810
+ resourceTypes: customConfig.resourceTypes
195
811
  }).orThrow();
196
812
  ```
197
813
 
@@ -199,22 +815,26 @@ const customType = TsRes.QualifierTypes.LiteralQualifierType.create({
199
815
 
200
816
  ### Core Classes
201
817
 
202
- - **ResourceManager**: Central orchestrator for resource operations
818
+ - **ResourceManagerBuilder**: Central builder for resource management systems
819
+ - **ResourceResolver**: Runtime resolver for getting resources in specific contexts
203
820
  - **Resource**: Individual resource with candidates for different contexts
204
821
  - **ResourceCandidate**: Specific resource variant with conditions
205
- - **ResourceBuilder**: Builder pattern for constructing resources
206
- - **QualifierCollector**: Manages qualifier definitions
822
+ - **SystemConfiguration**: Configuration defining qualifiers, types, and defaults
823
+ - **Condition**: Individual condition with qualifier, operator, and value
207
824
  - **ConditionSet**: Collection of conditions for resource matching
208
825
 
209
826
  ### Namespaces
210
827
 
211
- - **Resources**: Core resource management classes
828
+ - **Resources**: Core resource management classes (`ResourceManagerBuilder`, etc.)
829
+ - **Config**: System configuration classes (`SystemConfiguration`, predefined configs)
212
830
  - **Qualifiers**: Qualifier definition and management
213
831
  - **Conditions**: Condition and condition set management
214
832
  - **QualifierTypes**: Built-in and custom qualifier type definitions
215
833
  - **Context**: Context definition for resource resolution
216
834
  - **Import**: File system and external resource import utilities
217
835
  - **ResourceJson**: JSON serialization and deserialization
836
+ - **Bundle**: Resource bundling and deployment utilities
837
+ - **Runtime**: Runtime resolution classes (`ResourceResolver`, etc.)
218
838
 
219
839
  ## Context Filtering and Qualifier Reduction
220
840
 
@@ -299,6 +919,10 @@ const completeBundle = resourceManager.getResourceCollectionDecl().orThrow();
299
919
  Filter for a specific environment (e.g., production):
300
920
 
301
921
  ```typescript
922
+ const resourceManager = TsRes.Resources.ResourceManagerBuilder.createPredefined('default').orThrow();
923
+ // ... add feature flag resources ...
924
+ resourceManager.build().orThrow();
925
+
302
926
  const productionContext = resourceManager.validateContext({
303
927
  environment: 'production'
304
928
  }).orThrow();
@@ -475,33 +1099,58 @@ const result = resource.toLooseResourceDecl({
475
1099
  ### Web Application Localization
476
1100
 
477
1101
  ```typescript
478
- // Set up for multi-language, multi-region application
479
- const qualifiers = [
480
- { name: 'language', typeName: 'language', defaultPriority: 600 },
481
- { name: 'homeTerritory', typeName: 'territory', defaultPriority: 700 },
482
- { name: 'currentTerritory', typeName: 'territory', defaultPriority: 800 }
483
- ];
1102
+ // Use predefined configuration or create custom one with territories
1103
+ const manager = TsRes.Resources.ResourceManagerBuilder.createPredefined('territoryPriority').orThrow();
484
1104
 
485
- // Resources with fallback chains
486
- const resources = [
487
- // Default English
488
- { id: 'app.title', json: { title: 'My App' }, conditions: { language: 'en' } },
489
- // Canadian English variant
490
- { id: 'app.title', json: { title: 'My App, eh!' }, conditions: { language: 'en-CA' } },
491
- // French
492
- { id: 'app.title', json: { title: 'Mon App' }, conditions: { language: 'fr' } }
493
- ];
1105
+ manager.addResource({
1106
+ id: 'app.title',
1107
+ resourceTypeName: 'json',
1108
+ candidates: [
1109
+ // Default English
1110
+ { json: { title: 'My App' }, conditions: { language: 'en' } },
1111
+ // Canadian English variant
1112
+ { json: { title: 'My App, eh!' }, conditions: { language: 'en-CA' } },
1113
+ // French
1114
+ { json: { title: 'Mon App' }, conditions: { language: 'fr' } }
1115
+ ]
1116
+ }).orThrow();
1117
+
1118
+ manager.build().orThrow();
1119
+
1120
+ // Create resolver for Canadian user
1121
+ const resolver = TsRes.ResourceResolver.create(manager).orThrow();
1122
+ const canadianResolver = resolver.withContext({
1123
+ language: 'en-CA',
1124
+ currentTerritory: 'CA'
1125
+ }).orThrow();
1126
+
1127
+ const title = canadianResolver.resolveComposedResourceValue('app.title').orThrow();
1128
+ // Returns: { title: 'My App, eh!' }
494
1129
  ```
495
1130
 
496
1131
  ### Configuration Management
497
1132
 
498
1133
  ```typescript
499
- // Environment-specific configurations
500
- const configs = [
501
- { id: 'api.config', json: { url: 'localhost:3000' }, conditions: { env: 'development' } },
502
- { id: 'api.config', json: { url: 'api.staging.com' }, conditions: { env: 'staging' } },
503
- { id: 'api.config', json: { url: 'api.production.com' }, conditions: { env: 'production' } }
504
- ];
1134
+ // Create manager with custom environment qualifier
1135
+ const manager = TsRes.Resources.ResourceManagerBuilder.createPredefined('default').orThrow();
1136
+
1137
+ manager.addResource({
1138
+ id: 'api.config',
1139
+ resourceTypeName: 'json',
1140
+ candidates: [
1141
+ { json: { url: 'localhost:3000' }, conditions: { environment: 'development' } },
1142
+ { json: { url: 'api.staging.com' }, conditions: { environment: 'staging' } },
1143
+ { json: { url: 'api.production.com' }, conditions: { environment: 'production' } }
1144
+ ]
1145
+ }).orThrow();
1146
+
1147
+ manager.build().orThrow();
1148
+
1149
+ // Resolve configuration for production
1150
+ const resolver = TsRes.ResourceResolver.create(manager).orThrow();
1151
+ const prodResolver = resolver.withContext({ environment: 'production' }).orThrow();
1152
+ const config = prodResolver.resolveComposedResourceValue('api.config').orThrow();
1153
+ // Returns: { url: 'api.production.com' }
505
1154
  ```
506
1155
 
507
1156
  ## Dependencies
@@ -515,24 +1164,32 @@ const configs = [
515
1164
 
516
1165
  ## Development
517
1166
 
1167
+ This library is part of a Rush monorepo. Development commands:
1168
+
518
1169
  ```bash
519
- # Install dependencies
520
- npm install
1170
+ # Install dependencies (from repository root)
1171
+ rush install
521
1172
 
522
- # Build the project
523
- npm run build
1173
+ # Build the project
1174
+ rushx build
524
1175
 
525
1176
  # Run tests
526
- npm run test
1177
+ rushx test
527
1178
 
528
1179
  # Run tests with coverage
529
- npm run coverage
1180
+ rushx coverage
530
1181
 
531
1182
  # Lint code
532
- npm run lint
1183
+ rushx lint
1184
+
1185
+ # Fix linting issues
1186
+ rushx fixlint
1187
+
1188
+ # Generate API documentation
1189
+ rushx build-docs
533
1190
 
534
- # Generate documentation
535
- npm run build-docs
1191
+ # Build project and docs together
1192
+ rushx build-all
536
1193
  ```
537
1194
 
538
1195
  ## License