@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.
- package/CHANGELOG.json +21 -0
- package/CHANGELOG.md +14 -1
- package/README.md +794 -137
- package/dist/ts-res.d.ts +2257 -268
- package/dist/tsdoc-metadata.json +1 -1
- package/eslint.config.js +16 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -1
- package/lib/packlets/bundle/bundleBuilder.js +7 -4
- package/lib/packlets/bundle/bundleLoader.js +1 -0
- package/lib/packlets/bundle/bundleUtils.js +14 -16
- package/lib/packlets/bundle/index.d.ts +3 -1
- package/lib/packlets/bundle/index.js +14 -3
- package/lib/packlets/common/convert.d.ts +15 -0
- package/lib/packlets/common/convert.js +16 -1
- package/lib/packlets/common/helpers/context.js +1 -0
- package/lib/packlets/common/helpers/resources.d.ts +7 -0
- package/lib/packlets/common/helpers/resources.js +13 -0
- package/lib/packlets/common/resources.d.ts +44 -1
- package/lib/packlets/common/validate/conditions.d.ts +17 -1
- package/lib/packlets/common/validate/conditions.js +32 -6
- package/lib/packlets/common/validate/regularExpressions.d.ts +4 -0
- package/lib/packlets/common/validate/regularExpressions.js +5 -1
- package/lib/packlets/common/validate/resources.d.ts +35 -1
- package/lib/packlets/common/validate/resources.js +55 -0
- package/lib/packlets/conditions/condition.js +1 -0
- package/lib/packlets/conditions/conditionSet.d.ts +9 -0
- package/lib/packlets/conditions/conditionSet.js +75 -1
- package/lib/packlets/config/configInitFactory.d.ts +217 -0
- package/lib/packlets/config/configInitFactory.js +302 -0
- package/lib/packlets/config/convert.d.ts +9 -0
- package/lib/packlets/config/convert.js +12 -1
- package/lib/packlets/config/index.d.ts +1 -0
- package/lib/packlets/config/index.js +1 -0
- package/lib/packlets/config/json.d.ts +1 -1
- package/lib/packlets/config/systemConfiguration.d.ts +16 -3
- package/lib/packlets/config/systemConfiguration.js +19 -40
- package/lib/packlets/import/fsItem.d.ts +2 -1
- package/lib/packlets/import/fsItem.js +9 -13
- package/lib/packlets/import/importManager.d.ts +2 -1
- package/lib/packlets/import/importers/jsonImporter.js +1 -0
- package/lib/packlets/import/importers/pathImporter.d.ts +2 -1
- package/lib/packlets/import/importers/pathImporter.js +2 -1
- package/lib/packlets/qualifier-types/config/convert.d.ts +22 -1
- package/lib/packlets/qualifier-types/config/convert.js +67 -2
- package/lib/packlets/qualifier-types/config/json.d.ts +24 -2
- package/lib/packlets/qualifier-types/config/json.js +11 -0
- package/lib/packlets/qualifier-types/convert.d.ts +1 -1
- package/lib/packlets/qualifier-types/helpers.d.ts +14 -5
- package/lib/packlets/qualifier-types/helpers.js +2 -2
- package/lib/packlets/qualifier-types/languageQualifierType.d.ts +28 -2
- package/lib/packlets/qualifier-types/languageQualifierType.js +73 -0
- package/lib/packlets/qualifier-types/literalQualifierType.d.ts +31 -5
- package/lib/packlets/qualifier-types/literalQualifierType.js +75 -0
- package/lib/packlets/qualifier-types/literalValueHierarchy.d.ts +7 -11
- package/lib/packlets/qualifier-types/literalValueHierarchy.js +12 -1
- package/lib/packlets/qualifier-types/qualifierType.d.ts +31 -2
- package/lib/packlets/qualifier-types/territoryQualifierType.d.ts +31 -5
- package/lib/packlets/qualifier-types/territoryQualifierType.js +80 -0
- package/lib/packlets/qualifiers/qualifierCollector.js +1 -2
- package/lib/packlets/resource-json/compiled/convert.js +2 -1
- package/lib/packlets/resource-json/compiled/json.d.ts +6 -2
- package/lib/packlets/resource-types/config/convert.d.ts +1 -1
- package/lib/packlets/resource-types/config/convert.js +3 -1
- package/lib/packlets/resource-types/config/json.d.ts +3 -1
- package/lib/packlets/resource-types/jsonResourceType.d.ts +10 -1
- package/lib/packlets/resource-types/jsonResourceType.js +7 -3
- package/lib/packlets/resource-types/resourceType.d.ts +100 -8
- package/lib/packlets/resource-types/resourceType.js +39 -3
- package/lib/packlets/resources/candidateReducer.js +3 -0
- package/lib/packlets/resources/candidateValue.d.ts +92 -0
- package/lib/packlets/resources/candidateValue.js +128 -0
- package/lib/packlets/resources/candidateValueCollector.d.ts +55 -0
- package/lib/packlets/resources/candidateValueCollector.js +96 -0
- package/lib/packlets/resources/common.d.ts +14 -0
- package/lib/packlets/resources/deltaGenerator.d.ts +189 -0
- package/lib/packlets/resources/deltaGenerator.js +344 -0
- package/lib/packlets/resources/index.d.ts +3 -0
- package/lib/packlets/resources/index.js +3 -0
- package/lib/packlets/resources/resource.d.ts +6 -6
- package/lib/packlets/resources/resource.js +5 -10
- package/lib/packlets/resources/resourceBuilder.d.ts +6 -0
- package/lib/packlets/resources/resourceBuilder.js +4 -1
- package/lib/packlets/resources/resourceCandidate.d.ts +15 -2
- package/lib/packlets/resources/resourceCandidate.js +17 -5
- package/lib/packlets/resources/resourceManagerBuilder.d.ts +131 -10
- package/lib/packlets/resources/resourceManagerBuilder.js +289 -45
- package/lib/packlets/runtime/compiledResourceCollection.d.ts +27 -11
- package/lib/packlets/runtime/compiledResourceCollection.js +71 -28
- package/lib/packlets/runtime/conditionSetResolutionResult.js +3 -0
- package/lib/packlets/runtime/context/contextQualifierProvider.d.ts +53 -3
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.d.ts +82 -37
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.js +49 -77
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.d.ts +23 -2
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.js +53 -31
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.d.ts +4 -4
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.js +2 -2
- package/lib/packlets/runtime/iResourceManager.d.ts +17 -5
- package/lib/packlets/runtime/index.d.ts +1 -0
- package/lib/packlets/runtime/index.js +1 -0
- package/lib/packlets/runtime/resource-tree/common.d.ts +1 -1
- package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.d.ts +7 -7
- package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.js +5 -5
- package/lib/packlets/runtime/resourceResolver.d.ts +43 -1
- package/lib/packlets/runtime/resourceResolver.js +56 -28
- package/lib/packlets/runtime/resourceTreeResolver.d.ts +152 -0
- package/lib/packlets/runtime/resourceTreeResolver.js +222 -0
- package/lib/packlets/runtime/validate.js +1 -0
- package/lib/packlets/zip-archive/convert.d.ts +52 -0
- package/lib/packlets/zip-archive/convert.js +103 -0
- package/lib/packlets/zip-archive/index.d.ts +23 -0
- package/lib/packlets/zip-archive/index.js +95 -0
- package/lib/packlets/zip-archive/json.d.ts +64 -0
- package/lib/packlets/zip-archive/json.js +24 -0
- package/lib/packlets/zip-archive/types.d.ts +98 -0
- package/lib/packlets/zip-archive/types.js +39 -0
- package/lib/packlets/zip-archive/zipArchiveCreator.d.ts +35 -0
- package/lib/packlets/zip-archive/zipArchiveCreator.js +195 -0
- package/lib/packlets/zip-archive/zipArchiveFormat.d.ts +70 -0
- package/lib/packlets/zip-archive/zipArchiveFormat.js +184 -0
- package/lib/packlets/zip-archive/zipArchiveLoader.d.ts +70 -0
- package/lib/packlets/zip-archive/zipArchiveLoader.js +286 -0
- package/package.json +26 -32
- package/CLAUDE.md +0 -186
- package/EXAMPLE_INIT_PARAMS.md +0 -88
- package/PROGRESS_SUMMARY.md +0 -48
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/packlets/bundle/bundleBuilder.d.ts.map +0 -1
- package/lib/packlets/bundle/bundleBuilder.js.map +0 -1
- package/lib/packlets/bundle/bundleLoader.d.ts.map +0 -1
- package/lib/packlets/bundle/bundleLoader.js.map +0 -1
- package/lib/packlets/bundle/bundleNormalizer.d.ts.map +0 -1
- package/lib/packlets/bundle/bundleNormalizer.js.map +0 -1
- package/lib/packlets/bundle/bundleUtils.d.ts.map +0 -1
- package/lib/packlets/bundle/bundleUtils.js.map +0 -1
- package/lib/packlets/bundle/convert.d.ts.map +0 -1
- package/lib/packlets/bundle/convert.js.map +0 -1
- package/lib/packlets/bundle/index.d.ts.map +0 -1
- package/lib/packlets/bundle/index.js.map +0 -1
- package/lib/packlets/bundle/model.d.ts.map +0 -1
- package/lib/packlets/bundle/model.js.map +0 -1
- package/lib/packlets/common/conditions.d.ts.map +0 -1
- package/lib/packlets/common/conditions.js.map +0 -1
- package/lib/packlets/common/convert.d.ts.map +0 -1
- package/lib/packlets/common/convert.js.map +0 -1
- package/lib/packlets/common/helpers/conditions.d.ts.map +0 -1
- package/lib/packlets/common/helpers/conditions.js.map +0 -1
- package/lib/packlets/common/helpers/context.d.ts.map +0 -1
- package/lib/packlets/common/helpers/context.js.map +0 -1
- package/lib/packlets/common/helpers/index.d.ts.map +0 -1
- package/lib/packlets/common/helpers/index.js.map +0 -1
- package/lib/packlets/common/helpers/qualifierDefaultValues.d.ts.map +0 -1
- package/lib/packlets/common/helpers/qualifierDefaultValues.js.map +0 -1
- package/lib/packlets/common/helpers/resources.d.ts.map +0 -1
- package/lib/packlets/common/helpers/resources.js.map +0 -1
- package/lib/packlets/common/index.d.ts.map +0 -1
- package/lib/packlets/common/index.js.map +0 -1
- package/lib/packlets/common/resources.d.ts.map +0 -1
- package/lib/packlets/common/resources.js.map +0 -1
- package/lib/packlets/common/validate/conditions.d.ts.map +0 -1
- package/lib/packlets/common/validate/conditions.js.map +0 -1
- package/lib/packlets/common/validate/index.d.ts.map +0 -1
- package/lib/packlets/common/validate/index.js.map +0 -1
- package/lib/packlets/common/validate/regularExpressions.d.ts.map +0 -1
- package/lib/packlets/common/validate/regularExpressions.js.map +0 -1
- package/lib/packlets/common/validate/resources.d.ts.map +0 -1
- package/lib/packlets/common/validate/resources.js.map +0 -1
- package/lib/packlets/conditions/condition.d.ts.map +0 -1
- package/lib/packlets/conditions/condition.js.map +0 -1
- package/lib/packlets/conditions/conditionCollector.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionCollector.js.map +0 -1
- package/lib/packlets/conditions/conditionDecls.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionDecls.js.map +0 -1
- package/lib/packlets/conditions/conditionSet.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionSet.js.map +0 -1
- package/lib/packlets/conditions/conditionSetCollector.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionSetCollector.js.map +0 -1
- package/lib/packlets/conditions/conditionSetDecls.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionSetDecls.js.map +0 -1
- package/lib/packlets/conditions/conditionToken.d.ts.map +0 -1
- package/lib/packlets/conditions/conditionToken.js.map +0 -1
- package/lib/packlets/conditions/convert/conditionSetDecls.d.ts.map +0 -1
- package/lib/packlets/conditions/convert/conditionSetDecls.js.map +0 -1
- package/lib/packlets/conditions/convert/decls.d.ts.map +0 -1
- package/lib/packlets/conditions/convert/decls.js.map +0 -1
- package/lib/packlets/conditions/convert/index.d.ts.map +0 -1
- package/lib/packlets/conditions/convert/index.js.map +0 -1
- package/lib/packlets/conditions/index.d.ts.map +0 -1
- package/lib/packlets/conditions/index.js.map +0 -1
- package/lib/packlets/config/common.d.ts.map +0 -1
- package/lib/packlets/config/common.js.map +0 -1
- package/lib/packlets/config/convert.d.ts.map +0 -1
- package/lib/packlets/config/convert.js.map +0 -1
- package/lib/packlets/config/index.d.ts.map +0 -1
- package/lib/packlets/config/index.js.map +0 -1
- package/lib/packlets/config/json.d.ts.map +0 -1
- package/lib/packlets/config/json.js.map +0 -1
- package/lib/packlets/config/predefined/default.d.ts.map +0 -1
- package/lib/packlets/config/predefined/default.js.map +0 -1
- package/lib/packlets/config/predefined/extended.d.ts.map +0 -1
- package/lib/packlets/config/predefined/extended.js.map +0 -1
- package/lib/packlets/config/predefined/index.d.ts.map +0 -1
- package/lib/packlets/config/predefined/index.js.map +0 -1
- package/lib/packlets/config/systemConfiguration.d.ts.map +0 -1
- package/lib/packlets/config/systemConfiguration.js.map +0 -1
- package/lib/packlets/context/contextDecls.d.ts.map +0 -1
- package/lib/packlets/context/contextDecls.js.map +0 -1
- package/lib/packlets/context/contextToken.d.ts.map +0 -1
- package/lib/packlets/context/contextToken.js.map +0 -1
- package/lib/packlets/context/convert/decls.d.ts.map +0 -1
- package/lib/packlets/context/convert/decls.js.map +0 -1
- package/lib/packlets/context/convert/index.d.ts.map +0 -1
- package/lib/packlets/context/convert/index.js.map +0 -1
- package/lib/packlets/context/index.d.ts.map +0 -1
- package/lib/packlets/context/index.js.map +0 -1
- package/lib/packlets/decisions/abstractDecision.d.ts.map +0 -1
- package/lib/packlets/decisions/abstractDecision.js.map +0 -1
- package/lib/packlets/decisions/abstractDecisionCollector.d.ts.map +0 -1
- package/lib/packlets/decisions/abstractDecisionCollector.js.map +0 -1
- package/lib/packlets/decisions/candidate.d.ts.map +0 -1
- package/lib/packlets/decisions/candidate.js.map +0 -1
- package/lib/packlets/decisions/common.d.ts.map +0 -1
- package/lib/packlets/decisions/common.js.map +0 -1
- package/lib/packlets/decisions/concreteDecision.d.ts.map +0 -1
- package/lib/packlets/decisions/concreteDecision.js.map +0 -1
- package/lib/packlets/decisions/decision.d.ts.map +0 -1
- package/lib/packlets/decisions/decision.js.map +0 -1
- package/lib/packlets/decisions/index.d.ts.map +0 -1
- package/lib/packlets/decisions/index.js.map +0 -1
- package/lib/packlets/import/fsItem.d.ts.map +0 -1
- package/lib/packlets/import/fsItem.js.map +0 -1
- package/lib/packlets/import/importContext.d.ts.map +0 -1
- package/lib/packlets/import/importContext.js.map +0 -1
- package/lib/packlets/import/importManager.d.ts.map +0 -1
- package/lib/packlets/import/importManager.js.map +0 -1
- package/lib/packlets/import/importable.d.ts.map +0 -1
- package/lib/packlets/import/importable.js.map +0 -1
- package/lib/packlets/import/importers/collectionImporter.d.ts.map +0 -1
- package/lib/packlets/import/importers/collectionImporter.js.map +0 -1
- package/lib/packlets/import/importers/fsItemImporter.d.ts.map +0 -1
- package/lib/packlets/import/importers/fsItemImporter.js.map +0 -1
- package/lib/packlets/import/importers/importer.d.ts.map +0 -1
- package/lib/packlets/import/importers/importer.js.map +0 -1
- package/lib/packlets/import/importers/index.d.ts.map +0 -1
- package/lib/packlets/import/importers/index.js.map +0 -1
- package/lib/packlets/import/importers/jsonImporter.d.ts.map +0 -1
- package/lib/packlets/import/importers/jsonImporter.js.map +0 -1
- package/lib/packlets/import/importers/pathImporter.d.ts.map +0 -1
- package/lib/packlets/import/importers/pathImporter.js.map +0 -1
- package/lib/packlets/import/index.d.ts.map +0 -1
- package/lib/packlets/import/index.js.map +0 -1
- package/lib/packlets/qualifier-types/config/convert.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/config/convert.js.map +0 -1
- package/lib/packlets/qualifier-types/config/index.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/config/index.js.map +0 -1
- package/lib/packlets/qualifier-types/config/json.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/config/json.js.map +0 -1
- package/lib/packlets/qualifier-types/convert.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/convert.js.map +0 -1
- package/lib/packlets/qualifier-types/helpers.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/helpers.js.map +0 -1
- package/lib/packlets/qualifier-types/index.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/index.js.map +0 -1
- package/lib/packlets/qualifier-types/languageQualifierType.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/languageQualifierType.js.map +0 -1
- package/lib/packlets/qualifier-types/literalQualifierType.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/literalQualifierType.js.map +0 -1
- package/lib/packlets/qualifier-types/literalValueHierarchy.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/literalValueHierarchy.js.map +0 -1
- package/lib/packlets/qualifier-types/qualifierType.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/qualifierType.js.map +0 -1
- package/lib/packlets/qualifier-types/qualifierTypeCollector.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/qualifierTypeCollector.js.map +0 -1
- package/lib/packlets/qualifier-types/territoryQualifierType.d.ts.map +0 -1
- package/lib/packlets/qualifier-types/territoryQualifierType.js.map +0 -1
- package/lib/packlets/qualifiers/convert/decls.d.ts.map +0 -1
- package/lib/packlets/qualifiers/convert/decls.js.map +0 -1
- package/lib/packlets/qualifiers/convert/index.d.ts.map +0 -1
- package/lib/packlets/qualifiers/convert/index.js.map +0 -1
- package/lib/packlets/qualifiers/convert/qualifier.d.ts.map +0 -1
- package/lib/packlets/qualifiers/convert/qualifier.js.map +0 -1
- package/lib/packlets/qualifiers/index.d.ts.map +0 -1
- package/lib/packlets/qualifiers/index.js.map +0 -1
- package/lib/packlets/qualifiers/qualifier.d.ts.map +0 -1
- package/lib/packlets/qualifiers/qualifier.js.map +0 -1
- package/lib/packlets/qualifiers/qualifierCollector.d.ts.map +0 -1
- package/lib/packlets/qualifiers/qualifierCollector.js.map +0 -1
- package/lib/packlets/qualifiers/qualifierDecl.d.ts.map +0 -1
- package/lib/packlets/qualifiers/qualifierDecl.js.map +0 -1
- package/lib/packlets/qualifiers/qualifierDefaultValueDecls.d.ts.map +0 -1
- package/lib/packlets/qualifiers/qualifierDefaultValueDecls.js.map +0 -1
- package/lib/packlets/qualifiers/qualifierDefaultValueToken.d.ts.map +0 -1
- package/lib/packlets/qualifiers/qualifierDefaultValueToken.js.map +0 -1
- package/lib/packlets/resource-json/compiled/common.d.ts.map +0 -1
- package/lib/packlets/resource-json/compiled/common.js.map +0 -1
- package/lib/packlets/resource-json/compiled/convert.d.ts.map +0 -1
- package/lib/packlets/resource-json/compiled/convert.js.map +0 -1
- package/lib/packlets/resource-json/compiled/index.d.ts.map +0 -1
- package/lib/packlets/resource-json/compiled/index.js.map +0 -1
- package/lib/packlets/resource-json/compiled/json.d.ts.map +0 -1
- package/lib/packlets/resource-json/compiled/json.js.map +0 -1
- package/lib/packlets/resource-json/convert.d.ts.map +0 -1
- package/lib/packlets/resource-json/convert.js.map +0 -1
- package/lib/packlets/resource-json/helpers.d.ts.map +0 -1
- package/lib/packlets/resource-json/helpers.js.map +0 -1
- package/lib/packlets/resource-json/index.d.ts.map +0 -1
- package/lib/packlets/resource-json/index.js.map +0 -1
- package/lib/packlets/resource-json/json.d.ts.map +0 -1
- package/lib/packlets/resource-json/json.js.map +0 -1
- package/lib/packlets/resource-json/normalized.d.ts.map +0 -1
- package/lib/packlets/resource-json/normalized.js.map +0 -1
- package/lib/packlets/resource-json/resourceDeclCollection.d.ts.map +0 -1
- package/lib/packlets/resource-json/resourceDeclCollection.js.map +0 -1
- package/lib/packlets/resource-json/resourceDeclContainer.d.ts.map +0 -1
- package/lib/packlets/resource-json/resourceDeclContainer.js.map +0 -1
- package/lib/packlets/resource-json/resourceDeclTree.d.ts.map +0 -1
- package/lib/packlets/resource-json/resourceDeclTree.js.map +0 -1
- package/lib/packlets/resource-types/config/convert.d.ts.map +0 -1
- package/lib/packlets/resource-types/config/convert.js.map +0 -1
- package/lib/packlets/resource-types/config/index.d.ts.map +0 -1
- package/lib/packlets/resource-types/config/index.js.map +0 -1
- package/lib/packlets/resource-types/config/json.d.ts.map +0 -1
- package/lib/packlets/resource-types/config/json.js.map +0 -1
- package/lib/packlets/resource-types/helpers.d.ts.map +0 -1
- package/lib/packlets/resource-types/helpers.js.map +0 -1
- package/lib/packlets/resource-types/index.d.ts.map +0 -1
- package/lib/packlets/resource-types/index.js.map +0 -1
- package/lib/packlets/resource-types/jsonResourceType.d.ts.map +0 -1
- package/lib/packlets/resource-types/jsonResourceType.js.map +0 -1
- package/lib/packlets/resource-types/resourceType.d.ts.map +0 -1
- package/lib/packlets/resource-types/resourceType.js.map +0 -1
- package/lib/packlets/resource-types/resourceTypeCollector.d.ts.map +0 -1
- package/lib/packlets/resource-types/resourceTypeCollector.js.map +0 -1
- package/lib/packlets/resources/candidateReducer.d.ts.map +0 -1
- package/lib/packlets/resources/candidateReducer.js.map +0 -1
- package/lib/packlets/resources/common.d.ts.map +0 -1
- package/lib/packlets/resources/common.js.map +0 -1
- package/lib/packlets/resources/index.d.ts.map +0 -1
- package/lib/packlets/resources/index.js.map +0 -1
- package/lib/packlets/resources/resource.d.ts.map +0 -1
- package/lib/packlets/resources/resource.js.map +0 -1
- package/lib/packlets/resources/resourceBuilder.d.ts.map +0 -1
- package/lib/packlets/resources/resourceBuilder.js.map +0 -1
- package/lib/packlets/resources/resourceCandidate.d.ts.map +0 -1
- package/lib/packlets/resources/resourceCandidate.js.map +0 -1
- package/lib/packlets/resources/resourceManagerBuilder.d.ts.map +0 -1
- package/lib/packlets/resources/resourceManagerBuilder.js.map +0 -1
- package/lib/packlets/runtime/cacheListener.d.ts.map +0 -1
- package/lib/packlets/runtime/cacheListener.js.map +0 -1
- package/lib/packlets/runtime/cacheMetrics.d.ts.map +0 -1
- package/lib/packlets/runtime/cacheMetrics.js.map +0 -1
- package/lib/packlets/runtime/compiledResourceCollection.d.ts.map +0 -1
- package/lib/packlets/runtime/compiledResourceCollection.js.map +0 -1
- package/lib/packlets/runtime/conditionSetResolutionResult.d.ts.map +0 -1
- package/lib/packlets/runtime/conditionSetResolutionResult.js.map +0 -1
- package/lib/packlets/runtime/context/contextQualifierProvider.d.ts.map +0 -1
- package/lib/packlets/runtime/context/contextQualifierProvider.js.map +0 -1
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.d.ts.map +0 -1
- package/lib/packlets/runtime/context/contextQualifierProviderValidator.js.map +0 -1
- package/lib/packlets/runtime/context/index.d.ts.map +0 -1
- package/lib/packlets/runtime/context/index.js.map +0 -1
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.d.ts.map +0 -1
- package/lib/packlets/runtime/context/simpleContextQualifierProvider.js.map +0 -1
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.d.ts.map +0 -1
- package/lib/packlets/runtime/context/validatingSimpleContextQualifierProvider.js.map +0 -1
- package/lib/packlets/runtime/iResourceManager.d.ts.map +0 -1
- package/lib/packlets/runtime/iResourceManager.js.map +0 -1
- package/lib/packlets/runtime/index.d.ts.map +0 -1
- package/lib/packlets/runtime/index.js.map +0 -1
- package/lib/packlets/runtime/resource-tree/common.d.ts.map +0 -1
- package/lib/packlets/runtime/resource-tree/common.js.map +0 -1
- package/lib/packlets/runtime/resource-tree/index.d.ts.map +0 -1
- package/lib/packlets/runtime/resource-tree/index.js.map +0 -1
- package/lib/packlets/runtime/resource-tree/readOnlyResourceTree.d.ts.map +0 -1
- package/lib/packlets/runtime/resource-tree/readOnlyResourceTree.js.map +0 -1
- package/lib/packlets/runtime/resource-tree/resourceTreeChildren.d.ts.map +0 -1
- package/lib/packlets/runtime/resource-tree/resourceTreeChildren.js.map +0 -1
- package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.d.ts.map +0 -1
- package/lib/packlets/runtime/resource-tree/resourceTreeChildrenValidator.js.map +0 -1
- package/lib/packlets/runtime/resourceResolver.d.ts.map +0 -1
- package/lib/packlets/runtime/resourceResolver.js.map +0 -1
- package/lib/packlets/runtime/validate.d.ts.map +0 -1
- 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
|
-
##
|
|
8
|
+
## Understanding the Resource Model
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
//
|
|
50
|
-
const
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
85
|
-
manager.addLooseCandidate(resource).orThrow();
|
|
86
|
-
});
|
|
407
|
+
### Resource File Formats
|
|
87
408
|
|
|
88
|
-
|
|
89
|
-
manager.build().orThrow();
|
|
409
|
+
Resources can be stored in two formats:
|
|
90
410
|
|
|
91
|
-
|
|
92
|
-
const context = { language: 'en-CA', territory: 'CA' };
|
|
93
|
-
const candidates = greetingResource.getCandidatesForContext(context);
|
|
411
|
+
**1. Resource Collection Format** (`_resources.json`):
|
|
94
412
|
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
490
|
+
The importer recognizes qualifier values in folder and file names using specific patterns:
|
|
109
491
|
|
|
110
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
781
|
+
### Custom System Configuration
|
|
186
782
|
|
|
187
783
|
```typescript
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
206
|
-
- **
|
|
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
|
-
//
|
|
479
|
-
const
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
//
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
1170
|
+
# Install dependencies (from repository root)
|
|
1171
|
+
rush install
|
|
521
1172
|
|
|
522
|
-
# Build the project
|
|
523
|
-
|
|
1173
|
+
# Build the project
|
|
1174
|
+
rushx build
|
|
524
1175
|
|
|
525
1176
|
# Run tests
|
|
526
|
-
|
|
1177
|
+
rushx test
|
|
527
1178
|
|
|
528
1179
|
# Run tests with coverage
|
|
529
|
-
|
|
1180
|
+
rushx coverage
|
|
530
1181
|
|
|
531
1182
|
# Lint code
|
|
532
|
-
|
|
1183
|
+
rushx lint
|
|
1184
|
+
|
|
1185
|
+
# Fix linting issues
|
|
1186
|
+
rushx fixlint
|
|
1187
|
+
|
|
1188
|
+
# Generate API documentation
|
|
1189
|
+
rushx build-docs
|
|
533
1190
|
|
|
534
|
-
#
|
|
535
|
-
|
|
1191
|
+
# Build project and docs together
|
|
1192
|
+
rushx build-all
|
|
536
1193
|
```
|
|
537
1194
|
|
|
538
1195
|
## License
|