@ffschrattenecker/tm1-mcp-server 6.0.0
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.md +1211 -0
- package/LICENSE +21 -0
- package/NOTICE +34 -0
- package/README.md +276 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +238 -0
- package/dist/http-transport.d.ts +8 -0
- package/dist/http-transport.js +132 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +164 -0
- package/dist/lib/callgraph/callGraph.d.ts +124 -0
- package/dist/lib/callgraph/callGraph.js +334 -0
- package/dist/lib/callgraph/choreGraph.d.ts +23 -0
- package/dist/lib/callgraph/choreGraph.js +102 -0
- package/dist/lib/callgraph/dataFlow.d.ts +74 -0
- package/dist/lib/callgraph/dataFlow.js +271 -0
- package/dist/lib/callgraph/datasourceMembership.d.ts +31 -0
- package/dist/lib/callgraph/datasourceMembership.js +142 -0
- package/dist/lib/callgraph/lintTypes.d.ts +28 -0
- package/dist/lib/callgraph/lintTypes.js +2 -0
- package/dist/lib/callgraph/mdxMembers.d.ts +29 -0
- package/dist/lib/callgraph/mdxMembers.js +97 -0
- package/dist/lib/callgraph/referenceIndex.d.ts +179 -0
- package/dist/lib/callgraph/referenceIndex.js +584 -0
- package/dist/lib/callgraph/rulesLinter.d.ts +55 -0
- package/dist/lib/callgraph/rulesLinter.js +776 -0
- package/dist/lib/callgraph/rulesParser.d.ts +26 -0
- package/dist/lib/callgraph/rulesParser.js +71 -0
- package/dist/lib/callgraph/subsetUsage.d.ts +16 -0
- package/dist/lib/callgraph/subsetUsage.js +134 -0
- package/dist/lib/callgraph/tiParser.d.ts +21 -0
- package/dist/lib/callgraph/tiParser.js +672 -0
- package/dist/lib/callgraph/tiSignatures.d.ts +32 -0
- package/dist/lib/callgraph/tiSignatures.js +6185 -0
- package/dist/lib/callgraph/tm1-adapter.d.ts +32 -0
- package/dist/lib/callgraph/tm1-adapter.js +159 -0
- package/dist/lib/callgraph/types.d.ts +53 -0
- package/dist/lib/callgraph/types.js +2 -0
- package/dist/lib/callgraph/variableEnv.d.ts +86 -0
- package/dist/lib/callgraph/variableEnv.js +214 -0
- package/dist/lib/cell-address.d.ts +12 -0
- package/dist/lib/cell-address.js +71 -0
- package/dist/lib/complexity/antipatterns.d.ts +22 -0
- package/dist/lib/complexity/antipatterns.js +302 -0
- package/dist/lib/complexity/comment-classifier.d.ts +24 -0
- package/dist/lib/complexity/comment-classifier.js +43 -0
- package/dist/lib/complexity/cross-process.d.ts +78 -0
- package/dist/lib/complexity/cross-process.js +114 -0
- package/dist/lib/complexity/process-metrics.d.ts +78 -0
- package/dist/lib/complexity/process-metrics.js +278 -0
- package/dist/lib/complexity/rules-metrics.d.ts +31 -0
- package/dist/lib/complexity/rules-metrics.js +99 -0
- package/dist/lib/concurrency.d.ts +11 -0
- package/dist/lib/concurrency.js +27 -0
- package/dist/lib/control-name.d.ts +10 -0
- package/dist/lib/control-name.js +10 -0
- package/dist/lib/coordinate-error.d.ts +3 -0
- package/dist/lib/coordinate-error.js +29 -0
- package/dist/lib/credential-format.d.ts +23 -0
- package/dist/lib/credential-format.js +25 -0
- package/dist/lib/cube-stats/fetcher.d.ts +106 -0
- package/dist/lib/cube-stats/fetcher.js +194 -0
- package/dist/lib/feeders/brackets.d.ts +47 -0
- package/dist/lib/feeders/brackets.js +195 -0
- package/dist/lib/feeders/element-type-cache.d.ts +42 -0
- package/dist/lib/feeders/element-type-cache.js +86 -0
- package/dist/lib/feeders/static-heuristics.d.ts +101 -0
- package/dist/lib/feeders/static-heuristics.js +202 -0
- package/dist/lib/git-process.d.ts +67 -0
- package/dist/lib/git-process.js +243 -0
- package/dist/lib/mask-secrets.d.ts +10 -0
- package/dist/lib/mask-secrets.js +99 -0
- package/dist/lib/mdx.d.ts +3 -0
- package/dist/lib/mdx.js +13 -0
- package/dist/lib/naming/odata-filter.d.ts +15 -0
- package/dist/lib/naming/odata-filter.js +81 -0
- package/dist/lib/naming/rules.d.ts +34 -0
- package/dist/lib/naming/rules.js +215 -0
- package/dist/lib/pro-parser.d.ts +19 -0
- package/dist/lib/pro-parser.js +260 -0
- package/dist/lib/pro-serializer.d.ts +19 -0
- package/dist/lib/pro-serializer.js +170 -0
- package/dist/lib/process-parts-schema.d.ts +45 -0
- package/dist/lib/process-parts-schema.js +46 -0
- package/dist/lib/process-refs.d.ts +88 -0
- package/dist/lib/process-refs.js +441 -0
- package/dist/lib/rules-outline.d.ts +29 -0
- package/dist/lib/rules-outline.js +68 -0
- package/dist/lib/rules-patch.d.ts +6 -0
- package/dist/lib/rules-patch.js +59 -0
- package/dist/lib/safe-regex.d.ts +15 -0
- package/dist/lib/safe-regex.js +37 -0
- package/dist/lib/sample-cells.d.ts +48 -0
- package/dist/lib/sample-cells.js +146 -0
- package/dist/lib/slim-json-schema.d.ts +20 -0
- package/dist/lib/slim-json-schema.js +68 -0
- package/dist/lib/strip-comments.d.ts +23 -0
- package/dist/lib/strip-comments.js +61 -0
- package/dist/lib/tm1-events.d.ts +16 -0
- package/dist/lib/tm1-events.js +29 -0
- package/dist/lib/v12-compat/deprecated-ti.d.ts +17 -0
- package/dist/lib/v12-compat/deprecated-ti.js +257 -0
- package/dist/lib/v12-compat/scanner.d.ts +10 -0
- package/dist/lib/v12-compat/scanner.js +43 -0
- package/dist/lib/variables-ui-data.d.ts +26 -0
- package/dist/lib/variables-ui-data.js +77 -0
- package/dist/load-env.d.ts +2 -0
- package/dist/load-env.js +29 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.js +60 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.js +141 -0
- package/dist/resources/index.d.ts +5 -0
- package/dist/resources/index.js +237 -0
- package/dist/resources/list-handler.d.ts +40 -0
- package/dist/resources/list-handler.js +86 -0
- package/dist/resources/subscriptions.d.ts +19 -0
- package/dist/resources/subscriptions.js +80 -0
- package/dist/schemas/cells.d.ts +17 -0
- package/dist/schemas/cells.js +13 -0
- package/dist/schemas/common.d.ts +16 -0
- package/dist/schemas/common.js +14 -0
- package/dist/schemas/index.d.ts +10 -0
- package/dist/schemas/index.js +23 -0
- package/dist/schemas/metadata.d.ts +70 -0
- package/dist/schemas/metadata.js +47 -0
- package/dist/schemas/monitoring.d.ts +95 -0
- package/dist/schemas/monitoring.js +69 -0
- package/dist/schemas/processes.d.ts +74 -0
- package/dist/schemas/processes.js +66 -0
- package/dist/schemas/scheduling.d.ts +13 -0
- package/dist/schemas/scheduling.js +13 -0
- package/dist/schemas/security.d.ts +19 -0
- package/dist/schemas/security.js +16 -0
- package/dist/schemas/subsets.d.ts +12 -0
- package/dist/schemas/subsets.js +12 -0
- package/dist/schemas/views.d.ts +9 -0
- package/dist/schemas/views.js +9 -0
- package/dist/session-manager.d.ts +79 -0
- package/dist/session-manager.js +281 -0
- package/dist/tm1-client/connection/profile.d.ts +13 -0
- package/dist/tm1-client/connection/profile.js +99 -0
- package/dist/tm1-client/dispatcher.d.ts +5 -0
- package/dist/tm1-client/dispatcher.js +31 -0
- package/dist/tm1-client/http.d.ts +81 -0
- package/dist/tm1-client/http.js +547 -0
- package/dist/tm1-client/services/batch-service.d.ts +123 -0
- package/dist/tm1-client/services/batch-service.js +455 -0
- package/dist/tm1-client/services/cell-service.d.ts +71 -0
- package/dist/tm1-client/services/cell-service.js +333 -0
- package/dist/tm1-client/services/cellset-transform.d.ts +26 -0
- package/dist/tm1-client/services/cellset-transform.js +80 -0
- package/dist/tm1-client/services/chore-service.d.ts +68 -0
- package/dist/tm1-client/services/chore-service.js +179 -0
- package/dist/tm1-client/services/chore-status.d.ts +11 -0
- package/dist/tm1-client/services/chore-status.js +127 -0
- package/dist/tm1-client/services/cube-service.d.ts +134 -0
- package/dist/tm1-client/services/cube-service.js +294 -0
- package/dist/tm1-client/services/dimension-service.d.ts +118 -0
- package/dist/tm1-client/services/dimension-service.js +249 -0
- package/dist/tm1-client/services/element-service.d.ts +245 -0
- package/dist/tm1-client/services/element-service.js +809 -0
- package/dist/tm1-client/services/fallback.d.ts +44 -0
- package/dist/tm1-client/services/fallback.js +72 -0
- package/dist/tm1-client/services/file-service.d.ts +53 -0
- package/dist/tm1-client/services/file-service.js +218 -0
- package/dist/tm1-client/services/hierarchy-service.d.ts +129 -0
- package/dist/tm1-client/services/hierarchy-service.js +332 -0
- package/dist/tm1-client/services/monitoring-service.d.ts +34 -0
- package/dist/tm1-client/services/monitoring-service.js +123 -0
- package/dist/tm1-client/services/odata-page.d.ts +71 -0
- package/dist/tm1-client/services/odata-page.js +87 -0
- package/dist/tm1-client/services/process-service.d.ts +278 -0
- package/dist/tm1-client/services/process-service.js +874 -0
- package/dist/tm1-client/services/process-status.d.ts +21 -0
- package/dist/tm1-client/services/process-status.js +89 -0
- package/dist/tm1-client/services/security-service.d.ts +50 -0
- package/dist/tm1-client/services/security-service.js +93 -0
- package/dist/tm1-client/services/server-service.d.ts +116 -0
- package/dist/tm1-client/services/server-service.js +504 -0
- package/dist/tm1-client/services/subset-service.d.ts +37 -0
- package/dist/tm1-client/services/subset-service.js +122 -0
- package/dist/tm1-client/services/view-service.d.ts +46 -0
- package/dist/tm1-client/services/view-service.js +287 -0
- package/dist/tm1-client.d.ts +56 -0
- package/dist/tm1-client.js +126 -0
- package/dist/tools/analysis/analyze-callgraph.d.ts +28 -0
- package/dist/tools/analysis/analyze-callgraph.js +350 -0
- package/dist/tools/analysis/analyze-chore-graph.d.ts +2 -0
- package/dist/tools/analysis/analyze-chore-graph.js +148 -0
- package/dist/tools/analysis/analyze-object-usage.d.ts +2 -0
- package/dist/tools/analysis/analyze-object-usage.js +143 -0
- package/dist/tools/analysis/audit-complexity.d.ts +2 -0
- package/dist/tools/analysis/audit-complexity.js +255 -0
- package/dist/tools/analysis/audit-feeders.d.ts +2 -0
- package/dist/tools/analysis/audit-feeders.js +417 -0
- package/dist/tools/analysis/audit-naming.d.ts +2 -0
- package/dist/tools/analysis/audit-naming.js +379 -0
- package/dist/tools/analysis/check-v12-readiness.d.ts +2 -0
- package/dist/tools/analysis/check-v12-readiness.js +136 -0
- package/dist/tools/analysis/find-orphan-dimensions.d.ts +2 -0
- package/dist/tools/analysis/find-orphan-dimensions.js +55 -0
- package/dist/tools/analysis/invalidate-callgraph-cache.d.ts +2 -0
- package/dist/tools/analysis/invalidate-callgraph-cache.js +40 -0
- package/dist/tools/analysis/trace-data-flow.d.ts +2 -0
- package/dist/tools/analysis/trace-data-flow.js +138 -0
- package/dist/tools/annotations.d.ts +12 -0
- package/dist/tools/annotations.js +61 -0
- package/dist/tools/celldata/check-feeders.d.ts +2 -0
- package/dist/tools/celldata/check-feeders.js +48 -0
- package/dist/tools/celldata/check-writable-coords.d.ts +2 -0
- package/dist/tools/celldata/check-writable-coords.js +126 -0
- package/dist/tools/celldata/execute-mdx.d.ts +17 -0
- package/dist/tools/celldata/execute-mdx.js +126 -0
- package/dist/tools/celldata/get-cell-value.d.ts +2 -0
- package/dist/tools/celldata/get-cell-value.js +30 -0
- package/dist/tools/celldata/get-view-definition.d.ts +2 -0
- package/dist/tools/celldata/get-view-definition.js +29 -0
- package/dist/tools/celldata/get-view.d.ts +2 -0
- package/dist/tools/celldata/get-view.js +55 -0
- package/dist/tools/celldata/sample-cells.d.ts +2 -0
- package/dist/tools/celldata/sample-cells.js +101 -0
- package/dist/tools/celldata/trace-cell-calculation.d.ts +2 -0
- package/dist/tools/celldata/trace-cell-calculation.js +53 -0
- package/dist/tools/celldata/trace-feeders.d.ts +2 -0
- package/dist/tools/celldata/trace-feeders.js +44 -0
- package/dist/tools/celldata/write-cells.d.ts +2 -0
- package/dist/tools/celldata/write-cells.js +65 -0
- package/dist/tools/confirm.d.ts +10 -0
- package/dist/tools/confirm.js +64 -0
- package/dist/tools/define-tool.d.ts +51 -0
- package/dist/tools/define-tool.js +49 -0
- package/dist/tools/dimension-management/bulk-upsert-elements.d.ts +2 -0
- package/dist/tools/dimension-management/bulk-upsert-elements.js +103 -0
- package/dist/tools/dimension-management/create-dimension.d.ts +2 -0
- package/dist/tools/dimension-management/create-dimension.js +22 -0
- package/dist/tools/dimension-management/create-element-attribute.d.ts +2 -0
- package/dist/tools/dimension-management/create-element-attribute.js +26 -0
- package/dist/tools/dimension-management/create-element.d.ts +2 -0
- package/dist/tools/dimension-management/create-element.js +31 -0
- package/dist/tools/dimension-management/create-hierarchy.d.ts +2 -0
- package/dist/tools/dimension-management/create-hierarchy.js +22 -0
- package/dist/tools/dimension-management/delete-dimension.d.ts +2 -0
- package/dist/tools/dimension-management/delete-dimension.js +25 -0
- package/dist/tools/dimension-management/delete-element.d.ts +2 -0
- package/dist/tools/dimension-management/delete-element.js +26 -0
- package/dist/tools/dimension-management/delete-elements.d.ts +2 -0
- package/dist/tools/dimension-management/delete-elements.js +44 -0
- package/dist/tools/dimension-management/delete-hierarchy.d.ts +2 -0
- package/dist/tools/dimension-management/delete-hierarchy.js +35 -0
- package/dist/tools/dimension-management/get-element-attribute-values.d.ts +2 -0
- package/dist/tools/dimension-management/get-element-attribute-values.js +77 -0
- package/dist/tools/dimension-management/list-element-attributes.d.ts +2 -0
- package/dist/tools/dimension-management/list-element-attributes.js +31 -0
- package/dist/tools/dimension-management/move-element.d.ts +2 -0
- package/dist/tools/dimension-management/move-element.js +28 -0
- package/dist/tools/dimension-management/update-element-attribute-value.d.ts +2 -0
- package/dist/tools/dimension-management/update-element-attribute-value.js +80 -0
- package/dist/tools/dimension-management/update-element.d.ts +2 -0
- package/dist/tools/dimension-management/update-element.js +35 -0
- package/dist/tools/error-format.d.ts +22 -0
- package/dist/tools/error-format.js +130 -0
- package/dist/tools/fileops/delete-file.d.ts +2 -0
- package/dist/tools/fileops/delete-file.js +32 -0
- package/dist/tools/fileops/get-file-content.d.ts +2 -0
- package/dist/tools/fileops/get-file-content.js +76 -0
- package/dist/tools/fileops/list-files.d.ts +2 -0
- package/dist/tools/fileops/list-files.js +43 -0
- package/dist/tools/fileops/search-files.d.ts +2 -0
- package/dist/tools/fileops/search-files.js +76 -0
- package/dist/tools/fileops/upload-file.d.ts +2 -0
- package/dist/tools/fileops/upload-file.js +69 -0
- package/dist/tools/format.d.ts +57 -0
- package/dist/tools/format.js +157 -0
- package/dist/tools/hierarchy.d.ts +6 -0
- package/dist/tools/hierarchy.js +27 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +263 -0
- package/dist/tools/local-file.d.ts +16 -0
- package/dist/tools/local-file.js +88 -0
- package/dist/tools/metadata/get-ancestors.d.ts +2 -0
- package/dist/tools/metadata/get-ancestors.js +36 -0
- package/dist/tools/metadata/get-descendants.d.ts +2 -0
- package/dist/tools/metadata/get-descendants.js +64 -0
- package/dist/tools/metadata/get-hierarchy.d.ts +2 -0
- package/dist/tools/metadata/get-hierarchy.js +130 -0
- package/dist/tools/metadata/list-chores.d.ts +2 -0
- package/dist/tools/metadata/list-chores.js +62 -0
- package/dist/tools/metadata/list-cubes.d.ts +2 -0
- package/dist/tools/metadata/list-cubes.js +128 -0
- package/dist/tools/metadata/list-dimensions.d.ts +2 -0
- package/dist/tools/metadata/list-dimensions.js +131 -0
- package/dist/tools/metadata/list-processes-grouped.d.ts +2 -0
- package/dist/tools/metadata/list-processes-grouped.js +96 -0
- package/dist/tools/metadata/list-processes.d.ts +2 -0
- package/dist/tools/metadata/list-processes.js +108 -0
- package/dist/tools/metadata/resolve-default-members.d.ts +2 -0
- package/dist/tools/metadata/resolve-default-members.js +46 -0
- package/dist/tools/model-building/check-cube-rule.d.ts +2 -0
- package/dist/tools/model-building/check-cube-rule.js +51 -0
- package/dist/tools/model-building/clear-cube.d.ts +2 -0
- package/dist/tools/model-building/clear-cube.js +46 -0
- package/dist/tools/model-building/create-cube.d.ts +2 -0
- package/dist/tools/model-building/create-cube.js +34 -0
- package/dist/tools/model-building/delete-cube.d.ts +2 -0
- package/dist/tools/model-building/delete-cube.js +27 -0
- package/dist/tools/model-building/get-all-cube-rules.d.ts +2 -0
- package/dist/tools/model-building/get-all-cube-rules.js +149 -0
- package/dist/tools/model-building/get-cube-rules.d.ts +2 -0
- package/dist/tools/model-building/get-cube-rules.js +69 -0
- package/dist/tools/model-building/rules-source.d.ts +21 -0
- package/dist/tools/model-building/rules-source.js +49 -0
- package/dist/tools/model-building/search-rules.d.ts +2 -0
- package/dist/tools/model-building/search-rules.js +132 -0
- package/dist/tools/model-building/set-cube-rules.d.ts +2 -0
- package/dist/tools/model-building/set-cube-rules.js +81 -0
- package/dist/tools/model-building/unload-cube.d.ts +2 -0
- package/dist/tools/model-building/unload-cube.js +23 -0
- package/dist/tools/operations/diagnose-process-error.d.ts +2 -0
- package/dist/tools/operations/diagnose-process-error.js +178 -0
- package/dist/tools/operations/error-log-helpers.d.ts +25 -0
- package/dist/tools/operations/error-log-helpers.js +57 -0
- package/dist/tools/operations/get-audit-log.d.ts +2 -0
- package/dist/tools/operations/get-audit-log.js +70 -0
- package/dist/tools/operations/get-cube-stats.d.ts +2 -0
- package/dist/tools/operations/get-cube-stats.js +107 -0
- package/dist/tools/operations/get-error-log-content.d.ts +2 -0
- package/dist/tools/operations/get-error-log-content.js +164 -0
- package/dist/tools/operations/get-jobs.d.ts +3 -0
- package/dist/tools/operations/get-jobs.js +51 -0
- package/dist/tools/operations/get-message-log.d.ts +2 -0
- package/dist/tools/operations/get-message-log.js +63 -0
- package/dist/tools/operations/get-server-info.d.ts +2 -0
- package/dist/tools/operations/get-server-info.js +180 -0
- package/dist/tools/operations/get-server-state.d.ts +2 -0
- package/dist/tools/operations/get-server-state.js +129 -0
- package/dist/tools/operations/get-sessions.d.ts +2 -0
- package/dist/tools/operations/get-sessions.js +76 -0
- package/dist/tools/operations/get-threads.d.ts +3 -0
- package/dist/tools/operations/get-threads.js +50 -0
- package/dist/tools/operations/get-transaction-log.d.ts +2 -0
- package/dist/tools/operations/get-transaction-log.js +63 -0
- package/dist/tools/operations/list-error-logs.d.ts +8 -0
- package/dist/tools/operations/list-error-logs.js +157 -0
- package/dist/tools/operations/save-data.d.ts +2 -0
- package/dist/tools/operations/save-data.js +50 -0
- package/dist/tools/pagination.d.ts +32 -0
- package/dist/tools/pagination.js +116 -0
- package/dist/tools/scheduling/create-chore.d.ts +2 -0
- package/dist/tools/scheduling/create-chore.js +93 -0
- package/dist/tools/scheduling/delete-chore.d.ts +2 -0
- package/dist/tools/scheduling/delete-chore.js +22 -0
- package/dist/tools/scheduling/execute-chore.d.ts +2 -0
- package/dist/tools/scheduling/execute-chore.js +52 -0
- package/dist/tools/scheduling/toggle-chore.d.ts +2 -0
- package/dist/tools/scheduling/toggle-chore.js +22 -0
- package/dist/tools/scheduling/update-chore.d.ts +2 -0
- package/dist/tools/scheduling/update-chore.js +71 -0
- package/dist/tools/schemas/common.d.ts +10 -0
- package/dist/tools/schemas/common.js +23 -0
- package/dist/tools/schemas/items-analysis.d.ts +394 -0
- package/dist/tools/schemas/items-analysis.js +419 -0
- package/dist/tools/schemas/items-cells.d.ts +70 -0
- package/dist/tools/schemas/items-cells.js +65 -0
- package/dist/tools/schemas/items-common.d.ts +18 -0
- package/dist/tools/schemas/items-common.js +39 -0
- package/dist/tools/schemas/items-fileops.d.ts +11 -0
- package/dist/tools/schemas/items-fileops.js +13 -0
- package/dist/tools/schemas/items-metadata.d.ts +262 -0
- package/dist/tools/schemas/items-metadata.js +184 -0
- package/dist/tools/schemas/items-monitoring.d.ts +79 -0
- package/dist/tools/schemas/items-monitoring.js +78 -0
- package/dist/tools/schemas/items-processes.d.ts +279 -0
- package/dist/tools/schemas/items-processes.js +235 -0
- package/dist/tools/schemas/items-scheduling.d.ts +25 -0
- package/dist/tools/schemas/items-scheduling.js +26 -0
- package/dist/tools/schemas/items-security.d.ts +19 -0
- package/dist/tools/schemas/items-security.js +12 -0
- package/dist/tools/schemas/items-subsets.d.ts +2 -0
- package/dist/tools/schemas/items-subsets.js +4 -0
- package/dist/tools/schemas/items-views.d.ts +38 -0
- package/dist/tools/schemas/items-views.js +26 -0
- package/dist/tools/schemas/items.d.ts +12 -0
- package/dist/tools/schemas/items.js +23 -0
- package/dist/tools/schemas/markdown-capable.d.ts +21 -0
- package/dist/tools/schemas/markdown-capable.js +58 -0
- package/dist/tools/schemas/output-schema.d.ts +24 -0
- package/dist/tools/schemas/output-schema.js +25 -0
- package/dist/tools/security/assign-client-group.d.ts +2 -0
- package/dist/tools/security/assign-client-group.js +20 -0
- package/dist/tools/security/create-client.d.ts +2 -0
- package/dist/tools/security/create-client.js +28 -0
- package/dist/tools/security/delete-client.d.ts +2 -0
- package/dist/tools/security/delete-client.js +22 -0
- package/dist/tools/security/get-client.d.ts +2 -0
- package/dist/tools/security/get-client.js +20 -0
- package/dist/tools/security/list-clients.d.ts +2 -0
- package/dist/tools/security/list-clients.js +86 -0
- package/dist/tools/security/list-groups.d.ts +2 -0
- package/dist/tools/security/list-groups.js +43 -0
- package/dist/tools/security/remove-client-group.d.ts +2 -0
- package/dist/tools/security/remove-client-group.js +27 -0
- package/dist/tools/security/update-client.d.ts +2 -0
- package/dist/tools/security/update-client.js +22 -0
- package/dist/tools/subsets/create-subset.d.ts +2 -0
- package/dist/tools/subsets/create-subset.js +44 -0
- package/dist/tools/subsets/delete-subset.d.ts +2 -0
- package/dist/tools/subsets/delete-subset.js +26 -0
- package/dist/tools/subsets/get-subset.d.ts +2 -0
- package/dist/tools/subsets/get-subset.js +29 -0
- package/dist/tools/subsets/list-subsets.d.ts +2 -0
- package/dist/tools/subsets/list-subsets.js +36 -0
- package/dist/tools/subsets/update-subset.d.ts +2 -0
- package/dist/tools/subsets/update-subset.js +33 -0
- package/dist/tools/ti-development/check-process-code.d.ts +2 -0
- package/dist/tools/ti-development/check-process-code.js +96 -0
- package/dist/tools/ti-development/compile-process.d.ts +2 -0
- package/dist/tools/ti-development/compile-process.js +32 -0
- package/dist/tools/ti-development/copy-process.d.ts +2 -0
- package/dist/tools/ti-development/copy-process.js +32 -0
- package/dist/tools/ti-development/delete-process.d.ts +2 -0
- package/dist/tools/ti-development/delete-process.js +26 -0
- package/dist/tools/ti-development/diff-process-with-file.d.ts +2 -0
- package/dist/tools/ti-development/diff-process-with-file.js +208 -0
- package/dist/tools/ti-development/diff-processes.d.ts +2 -0
- package/dist/tools/ti-development/diff-processes.js +312 -0
- package/dist/tools/ti-development/execute-process.d.ts +8 -0
- package/dist/tools/ti-development/execute-process.js +109 -0
- package/dist/tools/ti-development/export-process-to-git.d.ts +2 -0
- package/dist/tools/ti-development/export-process-to-git.js +113 -0
- package/dist/tools/ti-development/export-process-to-pro.d.ts +2 -0
- package/dist/tools/ti-development/export-process-to-pro.js +117 -0
- package/dist/tools/ti-development/get-all-processes-code.d.ts +2 -0
- package/dist/tools/ti-development/get-all-processes-code.js +131 -0
- package/dist/tools/ti-development/get-process-code.d.ts +2 -0
- package/dist/tools/ti-development/get-process-code.js +100 -0
- package/dist/tools/ti-development/get-process-datasource.d.ts +2 -0
- package/dist/tools/ti-development/get-process-datasource.js +20 -0
- package/dist/tools/ti-development/get-process-parameters.d.ts +2 -0
- package/dist/tools/ti-development/get-process-parameters.js +25 -0
- package/dist/tools/ti-development/get-process-variables.d.ts +2 -0
- package/dist/tools/ti-development/get-process-variables.js +38 -0
- package/dist/tools/ti-development/get-process.d.ts +2 -0
- package/dist/tools/ti-development/get-process.js +163 -0
- package/dist/tools/ti-development/import-pro-file.d.ts +2 -0
- package/dist/tools/ti-development/import-pro-file.js +153 -0
- package/dist/tools/ti-development/import-process-from-git.d.ts +2 -0
- package/dist/tools/ti-development/import-process-from-git.js +198 -0
- package/dist/tools/ti-development/install-pro-bundle.d.ts +2 -0
- package/dist/tools/ti-development/install-pro-bundle.js +288 -0
- package/dist/tools/ti-development/preflight.d.ts +39 -0
- package/dist/tools/ti-development/preflight.js +56 -0
- package/dist/tools/ti-development/process-backup.d.ts +29 -0
- package/dist/tools/ti-development/process-backup.js +118 -0
- package/dist/tools/ti-development/search-code.d.ts +2 -0
- package/dist/tools/ti-development/search-code.js +208 -0
- package/dist/tools/ti-development/upsert-process.d.ts +2 -0
- package/dist/tools/ti-development/upsert-process.js +167 -0
- package/dist/tools/ti-development/validate-process-refs.d.ts +2 -0
- package/dist/tools/ti-development/validate-process-refs.js +73 -0
- package/dist/tools/views/create-mdx-view.d.ts +2 -0
- package/dist/tools/views/create-mdx-view.js +21 -0
- package/dist/tools/views/create-native-view.d.ts +2 -0
- package/dist/tools/views/create-native-view.js +81 -0
- package/dist/tools/views/delete-view.d.ts +2 -0
- package/dist/tools/views/delete-view.js +23 -0
- package/dist/tools/views/list-views.d.ts +2 -0
- package/dist/tools/views/list-views.js +32 -0
- package/dist/tools/with-annotations.d.ts +6 -0
- package/dist/tools/with-annotations.js +339 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.js +91 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +18 -0
- package/package.json +107 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
// Element domain service. Owns the OData calls under
|
|
2
|
+
// /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements(...) — create, update,
|
|
3
|
+
// delete, move, bulk upsert, ElementAttribute definition CRUD, plus the
|
|
4
|
+
// element-attribute VALUE read/write that goes through the
|
|
5
|
+
// `}ElementAttributes_{Dim}` control cube. Those last two methods route
|
|
6
|
+
// through CellService for the underlying MDX read and cellset PATCH.
|
|
7
|
+
//
|
|
8
|
+
// See docs/ARCHITECTURE.md for the layering.
|
|
9
|
+
import { mapSettledWithConcurrency } from "../../lib/concurrency.js";
|
|
10
|
+
import { TM1Error, TM1ErrorCode } from "../../types.js";
|
|
11
|
+
import { BatchUnsupportedError } from "./batch-service.js";
|
|
12
|
+
import { rethrowIfSystemic } from "./fallback.js";
|
|
13
|
+
// Max in-flight per-element REST calls within a single bulkUpsert pass. Bounds
|
|
14
|
+
// pressure on TM1's worker pool (mirrors the cap the feeder-audit fan-out uses)
|
|
15
|
+
// while removing the serialized 2-3N round-trips on an explicitly-bulk op.
|
|
16
|
+
const BULK_UPSERT_CONCURRENCY = 8;
|
|
17
|
+
// OData key encoder: double ' per OData literal rules, then percent-encode.
|
|
18
|
+
const enc = (s) => encodeURIComponent(String(s).replace(/'/g, "''"));
|
|
19
|
+
// TM1 signals "element already exists" with different HTTP statuses across
|
|
20
|
+
// versions: some return 409 Conflict, but v11.x (REST 11.8) returns 400 with
|
|
21
|
+
// the message "An element with name ... already exists". Detect both so bulk
|
|
22
|
+
// upsert stays idempotent (update existing) rather than throwing on re-upsert.
|
|
23
|
+
export function isAlreadyExists(err) {
|
|
24
|
+
if (err.httpStatus === 409)
|
|
25
|
+
return true;
|
|
26
|
+
if (err.httpStatus === 400) {
|
|
27
|
+
const text = `${err.message} ${err.details ?? ""}`.toLowerCase();
|
|
28
|
+
return text.includes("already exists");
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// TM1 may report an element's Type as the enum name ("Numeric"|"String"|
|
|
33
|
+
// "Consolidated") or its ordinal (1|2|3). Normalize to the name so callers can
|
|
34
|
+
// compare against ElementCreate.type regardless of representation.
|
|
35
|
+
function normalizeElementType(t) {
|
|
36
|
+
switch (t) {
|
|
37
|
+
case 1:
|
|
38
|
+
case "1":
|
|
39
|
+
return "Numeric";
|
|
40
|
+
case 2:
|
|
41
|
+
case "2":
|
|
42
|
+
return "String";
|
|
43
|
+
case 3:
|
|
44
|
+
case "3":
|
|
45
|
+
return "Consolidated";
|
|
46
|
+
default:
|
|
47
|
+
return String(t);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reject with the first failure in ELEMENT order (not response order), so the
|
|
52
|
+
* error a caller sees is the same one the per-request path would surface.
|
|
53
|
+
* Successful sub-requests in the same batch stay committed — TM1's $batch is
|
|
54
|
+
* non-atomic — exactly as the concurrent per-request path already leaves
|
|
55
|
+
* earlier writes committed when a later one fails.
|
|
56
|
+
*/
|
|
57
|
+
function throwFirstFailure(results, indexOf) {
|
|
58
|
+
let firstIndex = Number.POSITIVE_INFINITY;
|
|
59
|
+
let firstError;
|
|
60
|
+
for (const r of results) {
|
|
61
|
+
if (r.ok)
|
|
62
|
+
continue;
|
|
63
|
+
const i = indexOf(r.id);
|
|
64
|
+
if (i < firstIndex) {
|
|
65
|
+
firstIndex = i;
|
|
66
|
+
firstError = r.error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (firstError)
|
|
70
|
+
throw firstError;
|
|
71
|
+
}
|
|
72
|
+
export class ElementService {
|
|
73
|
+
http;
|
|
74
|
+
cells;
|
|
75
|
+
batch;
|
|
76
|
+
constructor(http, cells,
|
|
77
|
+
// Optional so a caller (and the existing unit tests) can build an
|
|
78
|
+
// ElementService that only ever uses the per-request path. Production wires
|
|
79
|
+
// it, which turns bulkUpsert's N round-trips into a handful of $batch calls.
|
|
80
|
+
batch) {
|
|
81
|
+
this.http = http;
|
|
82
|
+
this.cells = cells;
|
|
83
|
+
this.batch = batch;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create an element in a hierarchy. Consolidated elements may include
|
|
87
|
+
* Components inline; their target elements must already exist.
|
|
88
|
+
* POST /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* Set edge weights that differ from TM1's default.
|
|
92
|
+
*
|
|
93
|
+
* TM1 accepts `Weight` inside a Components link payload and ignores it: the
|
|
94
|
+
* edge is created with weight 1 whatever the body said. Verified live — a
|
|
95
|
+
* consolidation created with weight -1 read back as 1 from the Edges
|
|
96
|
+
* collection itself, so no reader could have recovered the intent. The
|
|
97
|
+
* weight lives on the Edge entity and has to be PATCHed once the link
|
|
98
|
+
* exists.
|
|
99
|
+
*
|
|
100
|
+
* Only deviating weights cost a request: consolidations are overwhelmingly
|
|
101
|
+
* all-1, and a PATCH that sets 1 to 1 is a round trip for nothing.
|
|
102
|
+
*/
|
|
103
|
+
async applyEdgeWeights(dimensionName, hierarchyName, parentName, components) {
|
|
104
|
+
const base = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')`;
|
|
105
|
+
for (const c of components) {
|
|
106
|
+
if (c.weight === undefined || c.weight === 1)
|
|
107
|
+
continue;
|
|
108
|
+
await this.http.request("PATCH", `${base}/Edges(ParentName='${enc(parentName)}',ComponentName='${enc(c.name)}')`, { Weight: c.weight });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Does an element resolve in a hierarchy the way TI would resolve it?
|
|
113
|
+
* The key lookup is TM1's own name resolution: case- and space-insensitive,
|
|
114
|
+
* and it accepts an alias (verified on 11.8.03500: 'northamerica' and the
|
|
115
|
+
* alias 'NA' both resolve 'North America'). 404 → false; anything else,
|
|
116
|
+
* including a denial, is rethrown — "you may not look" is not "missing".
|
|
117
|
+
* GET /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements('{e}')?$select=Name
|
|
118
|
+
*/
|
|
119
|
+
async exists(dimensionName, hierarchyName, elementName) {
|
|
120
|
+
try {
|
|
121
|
+
await this.http.request("GET", `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(elementName)}')?$select=Name`);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
if (e instanceof TM1Error && e.code === TM1ErrorCode.NOT_FOUND)
|
|
126
|
+
return false;
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async create(dimensionName, hierarchyName, element) {
|
|
131
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements`;
|
|
132
|
+
const body = {
|
|
133
|
+
Name: element.name,
|
|
134
|
+
Type: element.type,
|
|
135
|
+
};
|
|
136
|
+
if (element.type === "Consolidated" &&
|
|
137
|
+
element.components &&
|
|
138
|
+
element.components.length > 0) {
|
|
139
|
+
body.Components = element.components.map((c) => ({
|
|
140
|
+
"@odata.id": `Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(c.name)}')`,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
await this.http.request("POST", path, body);
|
|
144
|
+
if (element.components && element.components.length > 0) {
|
|
145
|
+
await this.applyEdgeWeights(dimensionName, hierarchyName, element.name, element.components);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Update an existing element (rename, type change, replace components).
|
|
150
|
+
* PATCH /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements('{name}')
|
|
151
|
+
*/
|
|
152
|
+
async update(dimensionName, hierarchyName, elementName, update) {
|
|
153
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(elementName)}')`;
|
|
154
|
+
const body = {};
|
|
155
|
+
if (update.newName !== undefined) {
|
|
156
|
+
body.Name = update.newName;
|
|
157
|
+
}
|
|
158
|
+
if (update.type !== undefined) {
|
|
159
|
+
body.Type = update.type;
|
|
160
|
+
}
|
|
161
|
+
if (update.components !== undefined) {
|
|
162
|
+
body.Components = update.components.map((c) => ({
|
|
163
|
+
"@odata.id": `Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(c.name)}')`,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
await this.http.request("PATCH", path, body);
|
|
167
|
+
if (update.components !== undefined) {
|
|
168
|
+
await this.applyEdgeWeights(dimensionName, hierarchyName,
|
|
169
|
+
// A rename in the same PATCH takes effect immediately, so the edge
|
|
170
|
+
// key must use the NEW parent name.
|
|
171
|
+
update.newName ?? elementName, update.components);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Delete an element. May fail if the element is referenced in rules.
|
|
176
|
+
* DELETE /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements('{name}')
|
|
177
|
+
*/
|
|
178
|
+
async delete(dimensionName, hierarchyName, elementName) {
|
|
179
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(elementName)}')`;
|
|
180
|
+
await this.http.request("DELETE", path);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* What a bulkUpsert of `elements` would do, read-only: which elements it
|
|
184
|
+
* creates, which it updates, which change type in place (a Numeric ->
|
|
185
|
+
* Consolidated/String conversion discards leaf data), and which existing
|
|
186
|
+
* children a `components` list would drop — components REPLACE the child
|
|
187
|
+
* set, they do not append.
|
|
188
|
+
*
|
|
189
|
+
* Computed BEFORE any write on purpose: bulkUpsert's $batch pass commits its
|
|
190
|
+
* creates as it goes (TM1's $batch is non-atomic), so a plan worked out
|
|
191
|
+
* afterwards could only report damage, not prevent it.
|
|
192
|
+
*
|
|
193
|
+
* One filtered read per 40 names — `Elements?$filter=Name eq … or …` with
|
|
194
|
+
* the edges expanded — instead of loading the hierarchy.
|
|
195
|
+
*/
|
|
196
|
+
async planBulkUpsert(dimensionName, hierarchyName, elements) {
|
|
197
|
+
const base = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements`;
|
|
198
|
+
const lit = (n) => `'${n.replace(/'/g, "''")}'`;
|
|
199
|
+
const current = new Map();
|
|
200
|
+
const names = [...new Set(elements.map((e) => e.name))];
|
|
201
|
+
for (let i = 0; i < names.length; i += 40) {
|
|
202
|
+
const chunk = names.slice(i, i + 40);
|
|
203
|
+
const filter = chunk.map((n) => `Name eq ${lit(n)}`).join(" or ");
|
|
204
|
+
const page = await this.http.request("GET", `${base}?$select=Name,Type&$expand=Edges($select=ComponentName)&$filter=${encodeURIComponent(filter)}`);
|
|
205
|
+
for (const e of page.value)
|
|
206
|
+
current.set(e.Name.toLowerCase(), {
|
|
207
|
+
type: e.Type,
|
|
208
|
+
children: (e.Edges ?? []).map((x) => x.ComponentName),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const plan = {
|
|
212
|
+
creates: [],
|
|
213
|
+
updates: [],
|
|
214
|
+
typeChanges: [],
|
|
215
|
+
removals: [],
|
|
216
|
+
};
|
|
217
|
+
for (const el of elements) {
|
|
218
|
+
const now = current.get(el.name.toLowerCase());
|
|
219
|
+
if (!now) {
|
|
220
|
+
plan.creates.push(el.name);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
plan.updates.push(el.name);
|
|
224
|
+
if (now.type !== el.type)
|
|
225
|
+
plan.typeChanges.push({ name: el.name, from: now.type, to: el.type });
|
|
226
|
+
if (el.type === "Consolidated" &&
|
|
227
|
+
el.components &&
|
|
228
|
+
el.components.length > 0) {
|
|
229
|
+
const keep = new Set(el.components.map((c) => c.name.toLowerCase()));
|
|
230
|
+
const dropped = now.children.filter((c) => !keep.has(c.toLowerCase()));
|
|
231
|
+
if (dropped.length > 0)
|
|
232
|
+
plan.removals.push({ parent: el.name, children: dropped });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return plan;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Delete many elements of one hierarchy. Through `$batch` when the server
|
|
239
|
+
* has it (one DELETE sub-request per element, chunked by BatchService), else
|
|
240
|
+
* per request with bounded concurrency.
|
|
241
|
+
*
|
|
242
|
+
* Never all-or-nothing: TM1's `$batch` is non-atomic, so each element gets
|
|
243
|
+
* its own result and one missing name does not stop the rest. A systemic
|
|
244
|
+
* failure (auth, transport) still aborts the call.
|
|
245
|
+
*
|
|
246
|
+
* Falling back after BatchUnsupportedError is safe: that error is only raised
|
|
247
|
+
* before the first successful batch on the connection. Should a refused
|
|
248
|
+
* envelope still have removed some elements, their per-request replay
|
|
249
|
+
* reports NOT_FOUND instead of claiming a second delete.
|
|
250
|
+
*/
|
|
251
|
+
async deleteMany(dimensionName, hierarchyName, elementNames) {
|
|
252
|
+
const pathOf = (name) => `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(name)}')`;
|
|
253
|
+
const failed = (elementName, e) => ({
|
|
254
|
+
elementName,
|
|
255
|
+
deleted: false,
|
|
256
|
+
error: { code: e.code, message: e.message },
|
|
257
|
+
});
|
|
258
|
+
if (this.batch && !this.batch.isKnownUnsupported) {
|
|
259
|
+
try {
|
|
260
|
+
const results = await this.batch.execute(elementNames.map((name, i) => ({
|
|
261
|
+
id: `d${i}`,
|
|
262
|
+
method: "DELETE",
|
|
263
|
+
path: pathOf(name),
|
|
264
|
+
})));
|
|
265
|
+
const byIndex = new Map(results.map((r) => [Number(r.id.slice(1)), r]));
|
|
266
|
+
return elementNames.map((elementName, i) => {
|
|
267
|
+
const r = byIndex.get(i);
|
|
268
|
+
if (r === undefined)
|
|
269
|
+
return failed(elementName, new TM1Error({
|
|
270
|
+
code: TM1ErrorCode.TM1_ERROR,
|
|
271
|
+
message: "No sub-response for this element in the $batch reply.",
|
|
272
|
+
}));
|
|
273
|
+
return r.ok
|
|
274
|
+
? { elementName, deleted: true }
|
|
275
|
+
: failed(elementName, r.error);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (!(err instanceof BatchUnsupportedError))
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const settled = await mapSettledWithConcurrency(elementNames, BULK_UPSERT_CONCURRENCY, async (name) => {
|
|
284
|
+
await this.http.request("DELETE", pathOf(name));
|
|
285
|
+
});
|
|
286
|
+
return settled.map((r, i) => {
|
|
287
|
+
const elementName = elementNames[i];
|
|
288
|
+
if (r.status === "fulfilled")
|
|
289
|
+
return { elementName, deleted: true };
|
|
290
|
+
rethrowIfSystemic(r.reason);
|
|
291
|
+
return failed(elementName, r.reason);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Scan element names in a hierarchy with a hard cap, paginating server-side.
|
|
296
|
+
*
|
|
297
|
+
* A single bulk Elements fetch can exceed V8's max string length on large
|
|
298
|
+
* dimensions (response.text() buffers the whole body into one string), so
|
|
299
|
+
* this probes the total via $count on the first page, then pages by
|
|
300
|
+
* `pageSize` until either `maxScan` names are collected or the hierarchy is
|
|
301
|
+
* exhausted. Callers learn the true `total` and whether the scan was
|
|
302
|
+
* `truncated` — never a silent skip. Owns the V8-string-limit workaround so
|
|
303
|
+
* tools don't reimplement raw OData paging.
|
|
304
|
+
*
|
|
305
|
+
* `opts.filter` narrows the scan to names matching an OData predicate, and
|
|
306
|
+
* changes what the numbers mean: the server then evaluates every element and
|
|
307
|
+
* returns only the matches, so `scanned` becomes the hierarchy total (the
|
|
308
|
+
* server checked all of it) and `truncated` reports a capped MATCH set
|
|
309
|
+
* rather than a partial walk. That is the difference between an audit that
|
|
310
|
+
* examines the first 100k names and one that examines the dimension —
|
|
311
|
+
* see src/lib/naming/odata-filter.ts.
|
|
312
|
+
*
|
|
313
|
+
* GET /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements?$select=Name
|
|
314
|
+
*/
|
|
315
|
+
async scanElementNames(dimensionName, hierarchyName, opts) {
|
|
316
|
+
const { pageSize, maxScan, filter, scopeTotal } = opts;
|
|
317
|
+
const hierarchyPath = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')`;
|
|
318
|
+
const basePath = `${hierarchyPath}/Elements?$select=Name&$top=${pageSize}` +
|
|
319
|
+
(filter ? `&$filter=${encodeURIComponent(filter)}` : "");
|
|
320
|
+
// First page carries $count=true so we learn the total in one round-trip
|
|
321
|
+
// without the /$count endpoint (TM1 v11 returns text/plain there and
|
|
322
|
+
// rejects the Accept: application/json the shared HTTP client sends).
|
|
323
|
+
const firstPage = await this.http.request("GET", `${basePath}&$skip=0&$count=true`);
|
|
324
|
+
const total = firstPage["@odata.count"] ?? firstPage.value.length;
|
|
325
|
+
const scanLimit = Math.min(total, maxScan);
|
|
326
|
+
const names = [];
|
|
327
|
+
const firstSliceEnd = Math.min(firstPage.value.length, scanLimit);
|
|
328
|
+
for (let i = 0; i < firstSliceEnd; i++) {
|
|
329
|
+
names.push(firstPage.value[i].Name);
|
|
330
|
+
}
|
|
331
|
+
let skip = firstPage.value.length;
|
|
332
|
+
let lastPageSize = firstPage.value.length;
|
|
333
|
+
while (lastPageSize === pageSize && names.length < scanLimit) {
|
|
334
|
+
const page = await this.http.request("GET", `${basePath}&$skip=${skip}`);
|
|
335
|
+
const remaining = scanLimit - names.length;
|
|
336
|
+
const sliceEnd = Math.min(page.value.length, remaining);
|
|
337
|
+
for (let i = 0; i < sliceEnd; i++) {
|
|
338
|
+
names.push(page.value[i].Name);
|
|
339
|
+
}
|
|
340
|
+
lastPageSize = page.value.length;
|
|
341
|
+
skip += page.value.length;
|
|
342
|
+
}
|
|
343
|
+
if (filter === undefined) {
|
|
344
|
+
return {
|
|
345
|
+
names,
|
|
346
|
+
total,
|
|
347
|
+
scanned: names.length,
|
|
348
|
+
truncated: total > maxScan,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
// With a filter, `total` above counted the MATCHES. The caller still wants
|
|
352
|
+
// to report how large the hierarchy was; when it already knows, that costs
|
|
353
|
+
// nothing, otherwise one 145-byte probe.
|
|
354
|
+
let inScope = scopeTotal;
|
|
355
|
+
if (inScope === undefined) {
|
|
356
|
+
const scopeProbe = await this.http.request("GET", `${hierarchyPath}/Elements?$select=Name&$top=0&$count=true`);
|
|
357
|
+
inScope = scopeProbe["@odata.count"] ?? total;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
names,
|
|
361
|
+
total: inScope,
|
|
362
|
+
scanned: inScope,
|
|
363
|
+
truncated: total > maxScan,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Add an element as a component of a new parent.
|
|
368
|
+
* POST /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements('{newParent}')/Components
|
|
369
|
+
*/
|
|
370
|
+
async move(dimensionName, hierarchyName, elementName, newParent, weight) {
|
|
371
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(newParent)}')/Components`;
|
|
372
|
+
const body = {
|
|
373
|
+
"@odata.id": `Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(elementName)}')`,
|
|
374
|
+
};
|
|
375
|
+
await this.http.request("POST", path, body);
|
|
376
|
+
await this.applyEdgeWeights(dimensionName, hierarchyName, newParent, [
|
|
377
|
+
{ name: elementName, ...(weight !== undefined ? { weight } : {}) },
|
|
378
|
+
]);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Bulk upsert elements into a hierarchy. Two-pass to ensure leaves exist
|
|
382
|
+
* before consolidations reference them: pass 1 creates/upserts every element
|
|
383
|
+
* (PATCH on 409), pass 2 sets Components for Consolidated elements.
|
|
384
|
+
* POST/PATCH /api/v1/Dimensions('{d}')/Hierarchies('{h}')/Elements(...)
|
|
385
|
+
*
|
|
386
|
+
* Prefers OData `$batch` (a handful of round-trips) and falls back to the
|
|
387
|
+
* per-request fan-out when the server has no `$batch` endpoint. Both paths
|
|
388
|
+
* produce the identical result and the identical throw-on-any-failure
|
|
389
|
+
* contract; only the number of HTTP calls differs.
|
|
390
|
+
*/
|
|
391
|
+
async bulkUpsert(dimensionName, hierarchyName, elements) {
|
|
392
|
+
if (this.batch && !this.batch.isKnownUnsupported) {
|
|
393
|
+
try {
|
|
394
|
+
return await this.bulkUpsertViaBatch(dimensionName, hierarchyName, elements);
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
// ONLY "this server has no $batch" falls through. A sub-request failure
|
|
398
|
+
// is not an exception here (it is reported per item and rethrown as the
|
|
399
|
+
// element error), and a systemic transport/auth error propagates from
|
|
400
|
+
// BatchService — neither may silently re-drive the writes.
|
|
401
|
+
//
|
|
402
|
+
// Restarting on the per-request path cannot duplicate or lose work —
|
|
403
|
+
// but NOT because nothing was committed. TM1's $batch is non-atomic
|
|
404
|
+
// (verified live), so an envelope that fails mid-way leaves the
|
|
405
|
+
// sub-requests it already processed in place. What makes the restart
|
|
406
|
+
// safe is WHICH sub-requests those can be: BatchService raises
|
|
407
|
+
// BatchUnsupportedError only before the first successful batch on the
|
|
408
|
+
// connection, and the only pass that can run before that is pass 1a —
|
|
409
|
+
// create-only. Re-creating an element the failed batch already created
|
|
410
|
+
// just takes the "already exists" upsert branch, sees the type it was
|
|
411
|
+
// created with, and patches nothing.
|
|
412
|
+
//
|
|
413
|
+
// The destructive passes are unreachable here by construction: pass 1c
|
|
414
|
+
// (Type PATCH) and pass 2 (Components) only run after pass 1a returned
|
|
415
|
+
// a valid envelope, which sets supported=true and disables this
|
|
416
|
+
// fallback. A batch that fails AFTER one has succeeded (flaky gateway
|
|
417
|
+
// mid-chunk) propagates as a real error instead — which matters,
|
|
418
|
+
// because replaying an already-applied Type PATCH would re-probe, see
|
|
419
|
+
// the new type, and report no typeChange for a conversion that did
|
|
420
|
+
// discard leaf values.
|
|
421
|
+
//
|
|
422
|
+
// tests/unit/batch-fallback-safety.test.ts pins both halves of this.
|
|
423
|
+
if (!(err instanceof BatchUnsupportedError))
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return this.bulkUpsertPerRequest(dimensionName, hierarchyName, elements);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* `$batch` implementation of bulkUpsert: four PASSES regardless of element
|
|
431
|
+
* count — create-all, read-type-of-the-ones-that-existed,
|
|
432
|
+
* patch-the-types-that-differ, then the consolidation Components patches.
|
|
433
|
+
* Each pass is chunked inside BatchService by request count AND serialized
|
|
434
|
+
* size, so N elements cost roughly 4 * ceil(N/200) calls instead of N —
|
|
435
|
+
* more when the sub-requests are large enough to hit the byte budget first.
|
|
436
|
+
*/
|
|
437
|
+
async bulkUpsertViaBatch(dimensionName, hierarchyName, elements) {
|
|
438
|
+
const batch = this.batch;
|
|
439
|
+
const baseUrl = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements`;
|
|
440
|
+
// Correlate by element INDEX, not name: names are caller-supplied and could
|
|
441
|
+
// repeat, which would collapse two sub-responses onto one id.
|
|
442
|
+
const idOf = (i) => `e${i}`;
|
|
443
|
+
const indexOf = (id) => Number(id.slice(1));
|
|
444
|
+
// Pass 1a — create every element. TM1 runs the batch in payload order and
|
|
445
|
+
// continues past failures, so one response comes back per element.
|
|
446
|
+
const created = await batch.execute(elements.map((el, i) => ({
|
|
447
|
+
id: idOf(i),
|
|
448
|
+
method: "POST",
|
|
449
|
+
path: baseUrl,
|
|
450
|
+
body: { Name: el.name, Type: el.type },
|
|
451
|
+
})));
|
|
452
|
+
// Split the failures: "already exists" is the upsert path, anything else is
|
|
453
|
+
// a genuine error that must abort the whole op (same contract as the
|
|
454
|
+
// per-request path). Keep element order so the thrown error is the first
|
|
455
|
+
// failure in element order, not the first to be noticed.
|
|
456
|
+
const existing = [];
|
|
457
|
+
for (const r of created) {
|
|
458
|
+
if (r.ok)
|
|
459
|
+
continue;
|
|
460
|
+
const i = indexOf(r.id);
|
|
461
|
+
if (isAlreadyExists(r.error))
|
|
462
|
+
existing.push(i);
|
|
463
|
+
else
|
|
464
|
+
throw r.error;
|
|
465
|
+
}
|
|
466
|
+
// Pass 1b — read the current Type of the elements that already existed, so
|
|
467
|
+
// a type change can be reported (and skipped when it is a no-op). A failed
|
|
468
|
+
// probe means "type unreadable" and falls through to the unconditional
|
|
469
|
+
// PATCH below, matching the per-request path's behaviour on a NOT_FOUND.
|
|
470
|
+
const probed = await batch.execute(existing.map((i) => ({
|
|
471
|
+
id: idOf(i),
|
|
472
|
+
method: "GET",
|
|
473
|
+
path: `${baseUrl}('${enc(elements[i].name)}')?$select=Type`,
|
|
474
|
+
})));
|
|
475
|
+
const typeById = new Map();
|
|
476
|
+
for (const r of probed) {
|
|
477
|
+
// A transport/auth outage must NOT collapse into "type unreadable": that
|
|
478
|
+
// would silently PATCH the type below and discard the element's leaf cell
|
|
479
|
+
// values on a network blip or an expired session — and report nothing in
|
|
480
|
+
// typeChanges, because an unreadable prior type yields no entry. Same
|
|
481
|
+
// guard the per-request path applies to its probe; only a genuine,
|
|
482
|
+
// non-systemic failure (e.g. NOT_FOUND) may degrade to null.
|
|
483
|
+
if (!r.ok)
|
|
484
|
+
rethrowIfSystemic(r.error);
|
|
485
|
+
const raw = r.ok
|
|
486
|
+
? r.body?.Type
|
|
487
|
+
: undefined;
|
|
488
|
+
typeById.set(indexOf(r.id), raw === undefined || raw === null ? null : normalizeElementType(raw));
|
|
489
|
+
}
|
|
490
|
+
// Pass 1c — patch the types that actually differ, plus the unreadable ones.
|
|
491
|
+
const typeChanges = [];
|
|
492
|
+
const patches = [];
|
|
493
|
+
for (const i of existing) {
|
|
494
|
+
const el = elements[i];
|
|
495
|
+
const from = typeById.get(i) ?? null;
|
|
496
|
+
if (from !== null && from === el.type)
|
|
497
|
+
continue;
|
|
498
|
+
patches.push({
|
|
499
|
+
id: idOf(i),
|
|
500
|
+
method: "PATCH",
|
|
501
|
+
path: `${baseUrl}('${enc(el.name)}')`,
|
|
502
|
+
body: { Type: el.type },
|
|
503
|
+
});
|
|
504
|
+
// A Numeric->Consolidated / Numeric->String conversion discards the
|
|
505
|
+
// element's leaf cell values, so report it rather than let it happen
|
|
506
|
+
// silently. An unreadable prior type yields no entry, as before.
|
|
507
|
+
if (from !== null)
|
|
508
|
+
typeChanges.push({ name: el.name, from, to: el.type });
|
|
509
|
+
}
|
|
510
|
+
throwFirstFailure(await batch.execute(patches), indexOf);
|
|
511
|
+
// Pass barrier: every leaf write above has settled before any consolidation
|
|
512
|
+
// references it via Components. Pass 1 already threw on any hard failure.
|
|
513
|
+
//
|
|
514
|
+
// Pass 2 — PATCH {Components:[...]} is FULL-REPLACE, not append (verified
|
|
515
|
+
// live vs TM1 v11). Consolidations with no/empty components are skipped, so
|
|
516
|
+
// an upsert that omits components leaves existing children intact.
|
|
517
|
+
const components = [];
|
|
518
|
+
for (const [i, el] of elements.entries()) {
|
|
519
|
+
if (el.type !== "Consolidated" ||
|
|
520
|
+
!el.components ||
|
|
521
|
+
el.components.length === 0)
|
|
522
|
+
continue;
|
|
523
|
+
components.push({
|
|
524
|
+
id: idOf(i),
|
|
525
|
+
method: "PATCH",
|
|
526
|
+
path: `${baseUrl}('${enc(el.name)}')`,
|
|
527
|
+
body: {
|
|
528
|
+
Components: el.components.map((c) => ({
|
|
529
|
+
"@odata.id": `Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(c.name)}')`,
|
|
530
|
+
Weight: c.weight,
|
|
531
|
+
})),
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
throwFirstFailure(await batch.execute(components), indexOf);
|
|
536
|
+
// Pass 3 — the weights. TM1 accepts `Weight` inside the Components link
|
|
537
|
+
// above and ignores it: every edge it creates has weight 1 (measured, see
|
|
538
|
+
// applyEdgeWeights). The weight lives on the Edge entity and has to be
|
|
539
|
+
// PATCHed once the link exists, which is only true after pass 2. Only
|
|
540
|
+
// deviating weights cost a request.
|
|
541
|
+
//
|
|
542
|
+
// Own id space: one element can own several edges, and a repeated id makes
|
|
543
|
+
// v12 reject the whole envelope. `edgeOwner` maps each request back to the
|
|
544
|
+
// element that asked for it, so a failure still names the right one.
|
|
545
|
+
const edges = [];
|
|
546
|
+
const edgeOwner = [];
|
|
547
|
+
for (const [i, el] of elements.entries()) {
|
|
548
|
+
if (el.type !== "Consolidated" || !el.components)
|
|
549
|
+
continue;
|
|
550
|
+
for (const c of el.components) {
|
|
551
|
+
if (c.weight === undefined || c.weight === 1)
|
|
552
|
+
continue;
|
|
553
|
+
edges.push({
|
|
554
|
+
id: `e${edgeOwner.length}`,
|
|
555
|
+
method: "PATCH",
|
|
556
|
+
path: `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Edges(ParentName='${enc(el.name)}',ComponentName='${enc(c.name)}')`,
|
|
557
|
+
body: { Weight: c.weight },
|
|
558
|
+
});
|
|
559
|
+
edgeOwner.push(i);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (edges.length > 0) {
|
|
563
|
+
throwFirstFailure(await batch.execute(edges), (id) => edgeOwner[Number(id.slice(1))] ?? Number.POSITIVE_INFINITY);
|
|
564
|
+
}
|
|
565
|
+
return { typeChanges };
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Per-request fallback for bulkUpsert, used when the server has no `$batch`.
|
|
569
|
+
* Fans the calls out with bounded concurrency.
|
|
570
|
+
*/
|
|
571
|
+
async bulkUpsertPerRequest(dimensionName, hierarchyName, elements) {
|
|
572
|
+
const baseUrl = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements`;
|
|
573
|
+
// Pass 1: Create/upsert all elements without components. Same-type element
|
|
574
|
+
// writes are independent, so fan them out with bounded concurrency instead
|
|
575
|
+
// of one serialized round-trip each (was 2-3N sequential calls). The cap
|
|
576
|
+
// keeps constant pressure on TM1's worker pool. Each unit returns its
|
|
577
|
+
// type-change (or null); we collect them index-aligned AFTER settle so
|
|
578
|
+
// typeChanges stays in stable element order regardless of completion order.
|
|
579
|
+
const pass1 = await mapSettledWithConcurrency(elements, BULK_UPSERT_CONCURRENCY, async (el) => {
|
|
580
|
+
const body = { Name: el.name, Type: el.type };
|
|
581
|
+
try {
|
|
582
|
+
await this.http.request("POST", baseUrl, body);
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
if (err instanceof TM1Error && isAlreadyExists(err)) {
|
|
587
|
+
// Element already exists. Patch the type only when it actually
|
|
588
|
+
// differs (avoids a pointless write), and surface the change: a
|
|
589
|
+
// Numeric->Consolidated / Numeric->String conversion discards the
|
|
590
|
+
// element's leaf cell values, so the caller must be told it happened
|
|
591
|
+
// rather than have it occur silently.
|
|
592
|
+
const existing = await this.http
|
|
593
|
+
.request("GET", `${baseUrl}('${enc(el.name)}')?$select=Type`)
|
|
594
|
+
.catch((e) => {
|
|
595
|
+
// A transport/auth outage here must NOT collapse into the
|
|
596
|
+
// unconditional-PATCH branch below: that would silently change the
|
|
597
|
+
// element type (discarding leaf values) on a network blip. Only a
|
|
598
|
+
// genuine "type unreadable" (e.g. NOT_FOUND) may fall through to null.
|
|
599
|
+
rethrowIfSystemic(e);
|
|
600
|
+
return null;
|
|
601
|
+
});
|
|
602
|
+
const from = existing ? normalizeElementType(existing.Type) : null;
|
|
603
|
+
if (from && from !== el.type) {
|
|
604
|
+
await this.http.request("PATCH", `${baseUrl}('${enc(el.name)}')`, { Type: el.type });
|
|
605
|
+
return { name: el.name, from, to: el.type };
|
|
606
|
+
}
|
|
607
|
+
if (!from) {
|
|
608
|
+
// Type unreadable — preserve prior behaviour and patch unconditionally.
|
|
609
|
+
await this.http.request("PATCH", `${baseUrl}('${enc(el.name)}')`, { Type: el.type });
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
throw err;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
// Pass barrier: surface the first pass-1 failure (in element order) BEFORE
|
|
617
|
+
// any consolidation write. Pass 2 references leaves via Components, so it
|
|
618
|
+
// must not start until every leaf has settled — concurrency is safe within
|
|
619
|
+
// a pass but NOT across this barrier. Rejecting here preserves the prior
|
|
620
|
+
// "bulkUpsert throws if any element failed" contract.
|
|
621
|
+
const typeChanges = [];
|
|
622
|
+
for (const r of pass1) {
|
|
623
|
+
if (r.status === "rejected")
|
|
624
|
+
throw r.reason;
|
|
625
|
+
if (r.value)
|
|
626
|
+
typeChanges.push(r.value);
|
|
627
|
+
}
|
|
628
|
+
// Pass 2: Set components for consolidated elements.
|
|
629
|
+
// PATCH {Components:[...]} is FULL-REPLACE, not append (verified live vs
|
|
630
|
+
// TM1 v11: upserting [L3,L4] over existing [L1,L2] leaves the element with
|
|
631
|
+
// exactly {L3,L4}). Consolidations with no/empty components are skipped
|
|
632
|
+
// here, so an upsert that omits components leaves existing children intact
|
|
633
|
+
// — only a non-empty list rewrites the child set. Documented on the tool's
|
|
634
|
+
// `components` input so callers don't silently drop children. These writes
|
|
635
|
+
// are mutually independent (each targets a distinct element), so they fan
|
|
636
|
+
// out with the same bounded concurrency.
|
|
637
|
+
const consolidated = elements.filter((el) => el.type === "Consolidated" && el.components && el.components.length > 0);
|
|
638
|
+
const pass2 = await mapSettledWithConcurrency(consolidated, BULK_UPSERT_CONCURRENCY, async (el) => {
|
|
639
|
+
const path = `${baseUrl}('${enc(el.name)}')`;
|
|
640
|
+
const body = {
|
|
641
|
+
Components: el.components.map((c) => ({
|
|
642
|
+
"@odata.id": `Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/Elements('${enc(c.name)}')`,
|
|
643
|
+
Weight: c.weight,
|
|
644
|
+
})),
|
|
645
|
+
};
|
|
646
|
+
await this.http.request("PATCH", path, body);
|
|
647
|
+
});
|
|
648
|
+
for (const r of pass2) {
|
|
649
|
+
if (r.status === "rejected")
|
|
650
|
+
throw r.reason;
|
|
651
|
+
}
|
|
652
|
+
// Pass 3: the weights, for the same reason as the $batch path — TM1 takes
|
|
653
|
+
// `Weight` in the Components link and ignores it, so every edge lands at 1
|
|
654
|
+
// until it is PATCHed on the Edge entity. Runs after pass 2 because the
|
|
655
|
+
// edge has to exist first.
|
|
656
|
+
const pass3 = await mapSettledWithConcurrency(consolidated, BULK_UPSERT_CONCURRENCY, (el) => this.applyEdgeWeights(dimensionName, hierarchyName, el.name, el.components));
|
|
657
|
+
for (const r of pass3) {
|
|
658
|
+
if (r.status === "rejected")
|
|
659
|
+
throw r.reason;
|
|
660
|
+
}
|
|
661
|
+
return { typeChanges };
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* List element-attribute definitions for a hierarchy.
|
|
665
|
+
* GET /api/v1/Dimensions('{d}')/Hierarchies('{h}')/ElementAttributes
|
|
666
|
+
*/
|
|
667
|
+
async listAttributes(dimensionName, hierarchyName) {
|
|
668
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/ElementAttributes`;
|
|
669
|
+
const response = await this.http.request("GET", path);
|
|
670
|
+
return response.value.map((a) => ({
|
|
671
|
+
name: a.Name,
|
|
672
|
+
type: a.Type,
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Define a new element attribute on a hierarchy. Prefer TI prolog
|
|
677
|
+
* (DimensionElementInsert on `}ElementAttributes_{dim}`) for reproducible
|
|
678
|
+
* deployments — this REST path is for ad-hoc / debugging use.
|
|
679
|
+
* POST /api/v1/Dimensions('{d}')/Hierarchies('{h}')/ElementAttributes
|
|
680
|
+
*/
|
|
681
|
+
async createAttribute(dimensionName, hierarchyName, attributeName, attributeType) {
|
|
682
|
+
const path = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(hierarchyName)}')/ElementAttributes`;
|
|
683
|
+
await this.http.request("POST", path, {
|
|
684
|
+
Name: attributeName,
|
|
685
|
+
Type: attributeType,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Read all attribute values for one element via MDX on
|
|
690
|
+
* `}ElementAttributes_{Dim}`. Routes through CellService.executeMdx for the
|
|
691
|
+
* cellset round-trip.
|
|
692
|
+
*/
|
|
693
|
+
async getAttributeValues(dimensionName, elementName) {
|
|
694
|
+
// Escape `]` → `]]` in every bracketed identifier: an element or dimension
|
|
695
|
+
// named e.g. `Foo]` would otherwise break out of its MDX identifier and
|
|
696
|
+
// shift the read onto arbitrary members (MDX injection).
|
|
697
|
+
const esc = (s) => s.replace(/]/g, "]]");
|
|
698
|
+
const dim = esc(dimensionName);
|
|
699
|
+
const elem = esc(elementName);
|
|
700
|
+
const ctrlCube = `}ElementAttributes_${dim}`;
|
|
701
|
+
const mdx = `SELECT {[}ElementAttributes_${dim}].MEMBERS} ON COLUMNS ` +
|
|
702
|
+
`FROM [${ctrlCube}] ` +
|
|
703
|
+
`WHERE ([${dim}].[${elem}])`;
|
|
704
|
+
const result = await this.cells.executeMdx(mdx);
|
|
705
|
+
const out = [];
|
|
706
|
+
const tuples = result.axes[0]?.tuples ?? [];
|
|
707
|
+
for (let i = 0; i < tuples.length; i++) {
|
|
708
|
+
const attrName = tuples[i].members[0]?.name ?? "";
|
|
709
|
+
const cell = result.cells[i];
|
|
710
|
+
out.push({
|
|
711
|
+
elementName,
|
|
712
|
+
attributeName: attrName,
|
|
713
|
+
value: cell?.value ?? null,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return out;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Attribute values for a window of elements — every element of the
|
|
720
|
+
* dimension's default hierarchy, name-sorted, `limit` at a time. One MDX
|
|
721
|
+
* against `}ElementAttributes_{Dim}` with the window pushed into the row set
|
|
722
|
+
* (`SUBSET(TM1SORT(TM1SUBSETALL(...)))`), so the query text stays short no
|
|
723
|
+
* matter how many elements a page holds — TM1's request entity limit can be
|
|
724
|
+
* as low as 32 KB. `total` comes from `$count` on the Elements collection.
|
|
725
|
+
*
|
|
726
|
+
* `attributeNames` narrows the columns; omitted, every attribute is read.
|
|
727
|
+
*/
|
|
728
|
+
async getAttributeValuesPage(dimensionName, opts) {
|
|
729
|
+
const countPath = `/api/v1/Dimensions('${enc(dimensionName)}')/Hierarchies('${enc(dimensionName)}')/Elements?$select=Name&$top=1&$count=true`;
|
|
730
|
+
const counted = await this.http.request("GET", countPath);
|
|
731
|
+
const total = counted["@odata.count"] ?? 0;
|
|
732
|
+
if (opts.limit === 0 || opts.offset >= total)
|
|
733
|
+
return { total, items: [] };
|
|
734
|
+
const esc = (s) => s.replace(/]/g, "]]");
|
|
735
|
+
const dim = esc(dimensionName);
|
|
736
|
+
const attrDim = `[}ElementAttributes_${dim}].[}ElementAttributes_${dim}]`;
|
|
737
|
+
const columns = opts.attributeNames && opts.attributeNames.length > 0
|
|
738
|
+
? `{${opts.attributeNames.map((a) => `${attrDim}.[${esc(a)}]`).join(", ")}}`
|
|
739
|
+
: `{TM1SUBSETALL(${attrDim})}`;
|
|
740
|
+
const mdx = `SELECT ${columns} ON COLUMNS, ` +
|
|
741
|
+
`SUBSET(TM1SORT({TM1SUBSETALL([${dim}].[${dim}])}, ASC), ${opts.offset}, ${opts.limit}) ON ROWS ` +
|
|
742
|
+
`FROM [}ElementAttributes_${dim}]`;
|
|
743
|
+
const result = await this.cells.executeMdx(mdx);
|
|
744
|
+
const attrs = (result.axes[0]?.tuples ?? []).map((t) => t.members[0]?.name ?? "");
|
|
745
|
+
const rows = result.axes[1]?.tuples ?? [];
|
|
746
|
+
// Cells are row-major: cell (r, c) sits at r * columns + c.
|
|
747
|
+
const items = rows.map((t, r) => {
|
|
748
|
+
const values = {};
|
|
749
|
+
attrs.forEach((a, c) => {
|
|
750
|
+
values[a] = result.cells[r * attrs.length + c]?.value ?? null;
|
|
751
|
+
});
|
|
752
|
+
return { elementName: t.members[0]?.name ?? "", values };
|
|
753
|
+
});
|
|
754
|
+
return { total, items };
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Set a single attribute value. See {@link updateAttributeValues}.
|
|
758
|
+
*/
|
|
759
|
+
async updateAttributeValue(dimensionName, elementName, attributeName, value) {
|
|
760
|
+
await this.updateAttributeValues(dimensionName, [
|
|
761
|
+
{ elementName, attributeName, value },
|
|
762
|
+
]);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Set attribute values by writing to the `}ElementAttributes_{Dim}` control
|
|
766
|
+
* cube — every update in ONE cellset write (CellService.writeCells).
|
|
767
|
+
*
|
|
768
|
+
* Values are coerced to the attribute's type, looked up once: a Numeric
|
|
769
|
+
* attribute takes a number or a numeric string (`"12.5"`), String and Alias
|
|
770
|
+
* attributes take the value as text. An unknown attribute or a non-numeric
|
|
771
|
+
* value for a Numeric attribute is a VALIDATION_ERROR that names it, raised
|
|
772
|
+
* before anything is written.
|
|
773
|
+
*
|
|
774
|
+
* Prefer TI processes (CellPutS / AttrPutS) for reproducible deployments;
|
|
775
|
+
* this REST-direct path is for ad-hoc / debugging use.
|
|
776
|
+
*/
|
|
777
|
+
async updateAttributeValues(dimensionName, updates) {
|
|
778
|
+
if (updates.length === 0)
|
|
779
|
+
return;
|
|
780
|
+
const types = new Map((await this.listAttributes(dimensionName, dimensionName)).map((a) => [
|
|
781
|
+
a.name,
|
|
782
|
+
a.type,
|
|
783
|
+
]));
|
|
784
|
+
const cells = updates.map((u) => {
|
|
785
|
+
const type = types.get(u.attributeName);
|
|
786
|
+
if (type === undefined) {
|
|
787
|
+
throw new TM1Error({
|
|
788
|
+
code: TM1ErrorCode.VALIDATION_ERROR,
|
|
789
|
+
message: `Attribute '${u.attributeName}' does not exist on dimension '${dimensionName}'.`,
|
|
790
|
+
hint: `Known attributes: ${[...types.keys()].join(", ") || "(none)"}. Create it first with tm1_create_element_attribute.`,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
let value = String(u.value);
|
|
794
|
+
if (type === "Numeric") {
|
|
795
|
+
const n = typeof u.value === "number" ? u.value : Number(u.value.trim());
|
|
796
|
+
if (u.value === "" || !Number.isFinite(n)) {
|
|
797
|
+
throw new TM1Error({
|
|
798
|
+
code: TM1ErrorCode.VALIDATION_ERROR,
|
|
799
|
+
message: `Attribute '${u.attributeName}' is Numeric; '${String(u.value)}' (element '${u.elementName}') is not a number.`,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
value = n;
|
|
803
|
+
}
|
|
804
|
+
return { elements: [u.elementName, u.attributeName], value };
|
|
805
|
+
});
|
|
806
|
+
await this.cells.writeCells(`}ElementAttributes_${dimensionName}`, [dimensionName, `}ElementAttributes_${dimensionName}`], cells);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
//# sourceMappingURL=element-service.js.map
|