@platforma-sdk/model 1.79.27 → 1.80.2
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/dist/block_model.d.ts +2 -0
- package/dist/block_model.d.ts.map +1 -1
- package/dist/columns/column.cjs +28 -0
- package/dist/columns/column.cjs.map +1 -0
- package/dist/columns/column.d.ts +30 -0
- package/dist/columns/column.d.ts.map +1 -0
- package/dist/columns/column.js +27 -0
- package/dist/columns/column.js.map +1 -0
- package/dist/columns/column_lazy.cjs +248 -0
- package/dist/columns/column_lazy.cjs.map +1 -0
- package/dist/columns/column_lazy.d.ts +137 -0
- package/dist/columns/column_lazy.d.ts.map +1 -0
- package/dist/columns/column_lazy.js +245 -0
- package/dist/columns/column_lazy.js.map +1 -0
- package/dist/columns/column_providers/index.cjs +62 -0
- package/dist/columns/column_providers/index.cjs.map +1 -0
- package/dist/columns/column_providers/index.d.ts +30 -0
- package/dist/columns/column_providers/index.d.ts.map +1 -0
- package/dist/columns/column_providers/index.js +53 -0
- package/dist/columns/column_providers/index.js.map +1 -0
- package/dist/columns/column_providers/providers.cjs +123 -0
- package/dist/columns/column_providers/providers.cjs.map +1 -0
- package/dist/columns/column_providers/providers.d.ts +75 -0
- package/dist/columns/column_providers/providers.d.ts.map +1 -0
- package/dist/columns/column_providers/providers.js +118 -0
- package/dist/columns/column_providers/providers.js.map +1 -0
- package/dist/columns/column_providers/types.d.ts +23 -0
- package/dist/columns/column_providers/types.d.ts.map +1 -0
- package/dist/columns/column_recipes/column_discovered_recipe.cjs +180 -0
- package/dist/columns/column_recipes/column_discovered_recipe.cjs.map +1 -0
- package/dist/columns/column_recipes/column_discovered_recipe.d.ts +120 -0
- package/dist/columns/column_recipes/column_discovered_recipe.d.ts.map +1 -0
- package/dist/columns/column_recipes/column_discovered_recipe.js +180 -0
- package/dist/columns/column_recipes/column_discovered_recipe.js.map +1 -0
- package/dist/columns/column_recipes/column_filtered_recipe.cjs +133 -0
- package/dist/columns/column_recipes/column_filtered_recipe.cjs.map +1 -0
- package/dist/columns/column_recipes/column_filtered_recipe.d.ts +75 -0
- package/dist/columns/column_recipes/column_filtered_recipe.d.ts.map +1 -0
- package/dist/columns/column_recipes/column_filtered_recipe.js +133 -0
- package/dist/columns/column_recipes/column_filtered_recipe.js.map +1 -0
- package/dist/columns/column_recipes/column_overrided_recipe.cjs +101 -0
- package/dist/columns/column_recipes/column_overrided_recipe.cjs.map +1 -0
- package/dist/columns/column_recipes/column_overrided_recipe.d.ts +73 -0
- package/dist/columns/column_recipes/column_overrided_recipe.d.ts.map +1 -0
- package/dist/columns/column_recipes/column_overrided_recipe.js +101 -0
- package/dist/columns/column_recipes/column_overrided_recipe.js.map +1 -0
- package/dist/columns/column_recipes/index.cjs +72 -0
- package/dist/columns/column_recipes/index.cjs.map +1 -0
- package/dist/columns/column_recipes/index.d.ts +52 -0
- package/dist/columns/column_recipes/index.d.ts.map +1 -0
- package/dist/columns/column_recipes/index.js +68 -0
- package/dist/columns/column_recipes/index.js.map +1 -0
- package/dist/columns/column_recipes/leaf_rebrand.cjs +22 -0
- package/dist/columns/column_recipes/leaf_rebrand.cjs.map +1 -0
- package/dist/columns/column_recipes/leaf_rebrand.js +22 -0
- package/dist/columns/column_recipes/leaf_rebrand.js.map +1 -0
- package/dist/columns/column_recipes/types.d.ts +101 -0
- package/dist/columns/column_recipes/types.d.ts.map +1 -0
- package/dist/columns/columns_collection.cjs +124 -0
- package/dist/columns/columns_collection.cjs.map +1 -0
- package/dist/columns/columns_collection.d.ts +64 -0
- package/dist/columns/columns_collection.d.ts.map +1 -0
- package/dist/columns/columns_collection.js +122 -0
- package/dist/columns/columns_collection.js.map +1 -0
- package/dist/columns/derive_axis_values_labels.cjs +76 -0
- package/dist/columns/derive_axis_values_labels.cjs.map +1 -0
- package/dist/columns/derive_axis_values_labels.d.ts +30 -0
- package/dist/columns/derive_axis_values_labels.d.ts.map +1 -0
- package/dist/columns/derive_axis_values_labels.js +76 -0
- package/dist/columns/derive_axis_values_labels.js.map +1 -0
- package/dist/columns/expand_by_partition.cjs +53 -56
- package/dist/columns/expand_by_partition.cjs.map +1 -1
- package/dist/columns/expand_by_partition.d.ts +22 -18
- package/dist/columns/expand_by_partition.d.ts.map +1 -1
- package/dist/columns/expand_by_partition.js +55 -58
- package/dist/columns/expand_by_partition.js.map +1 -1
- package/dist/columns/index.cjs +44 -18
- package/dist/columns/index.d.ts +15 -7
- package/dist/columns/index.js +12 -6
- package/dist/columns/utils.cjs +186 -0
- package/dist/columns/utils.cjs.map +1 -0
- package/dist/columns/utils.d.ts +96 -0
- package/dist/columns/utils.d.ts.map +1 -0
- package/dist/columns/utils.js +179 -0
- package/dist/columns/utils.js.map +1 -0
- package/dist/components/PFrameForGraphs.cjs +1 -0
- package/dist/components/PFrameForGraphs.cjs.map +1 -1
- package/dist/components/PFrameForGraphs.d.ts +1 -1
- package/dist/components/PFrameForGraphs.d.ts.map +1 -1
- package/dist/components/PFrameForGraphs.js +1 -0
- package/dist/components/PFrameForGraphs.js.map +1 -1
- package/dist/components/PlDataTable/columnResolver.cjs +28 -0
- package/dist/components/PlDataTable/columnResolver.cjs.map +1 -0
- package/dist/components/PlDataTable/columnResolver.js +28 -0
- package/dist/components/PlDataTable/columnResolver.js.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +4 -2
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +4 -2
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs +20 -25
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js +20 -25
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs +58 -26
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js +60 -28
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +224 -194
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +13 -16
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +228 -198
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +40 -98
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +17 -8
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +40 -98
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.cjs +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -2
- package/dist/components/PlDataTable/createPlDataTable/index.js +2 -2
- package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs +51 -169
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.js +52 -166
- package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.js.map +1 -1
- package/dist/components/PlDataTable/index.cjs +1 -1
- package/dist/components/PlDataTable/index.d.ts +4 -4
- package/dist/components/PlDataTable/index.js +2 -2
- package/dist/components/PlDataTable/labels.cjs +0 -1
- package/dist/components/PlDataTable/labels.cjs.map +1 -1
- package/dist/components/PlDataTable/labels.js +0 -1
- package/dist/components/PlDataTable/labels.js.map +1 -1
- package/dist/components/PlDataTable/state-migration.cjs +107 -10
- package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
- package/dist/components/PlDataTable/state-migration.d.ts +5 -4
- package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
- package/dist/components/PlDataTable/state-migration.js +107 -10
- package/dist/components/PlDataTable/state-migration.js.map +1 -1
- package/dist/components/PlDataTable/typesV7.d.ts +1 -36
- package/dist/components/PlDataTable/typesV7.d.ts.map +1 -1
- package/dist/components/PlDataTable/typesV8.d.ts +123 -0
- package/dist/components/PlDataTable/typesV8.d.ts.map +1 -0
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs +27 -34
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.js +27 -34
- package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +28 -33
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/enrichment_discovery.js +29 -34
- package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.cjs +27 -17
- package/dist/components/PlDatasetSelector/filter_discovery.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts +24 -10
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.js +28 -18
- package/dist/components/PlDatasetSelector/filter_discovery.js.map +1 -1
- package/dist/components/PlDatasetSelector/index.d.ts +2 -2
- package/dist/components/index.cjs +1 -1
- package/dist/components/index.d.ts +5 -5
- package/dist/components/index.js +2 -2
- package/dist/filters/converters/filterToQuery.cjs +5 -6
- package/dist/filters/converters/filterToQuery.cjs.map +1 -1
- package/dist/filters/converters/filterToQuery.d.ts +12 -3
- package/dist/filters/converters/filterToQuery.d.ts.map +1 -1
- package/dist/filters/converters/filterToQuery.js +5 -6
- package/dist/filters/converters/filterToQuery.js.map +1 -1
- package/dist/filters/converters/index.d.ts +2 -2
- package/dist/filters/index.d.ts +2 -2
- package/dist/index.cjs +64 -35
- package/dist/index.d.ts +23 -15
- package/dist/index.js +15 -9
- package/dist/labels/derive_distinct_labels.cjs.map +1 -1
- package/dist/labels/derive_distinct_labels.d.ts +1 -2
- package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
- package/dist/labels/derive_distinct_labels.js.map +1 -1
- package/dist/labels/derive_distinct_tooltips.cjs +1 -1
- package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
- package/dist/labels/derive_distinct_tooltips.d.ts +6 -3
- package/dist/labels/derive_distinct_tooltips.d.ts.map +1 -1
- package/dist/labels/derive_distinct_tooltips.js +1 -1
- package/dist/labels/derive_distinct_tooltips.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/pframe_utils/axes.cjs.map +1 -1
- package/dist/pframe_utils/axes.d.ts +2 -2
- package/dist/pframe_utils/axes.d.ts.map +1 -1
- package/dist/pframe_utils/axes.js.map +1 -1
- package/dist/pframe_utils/columns.cjs +6 -10
- package/dist/pframe_utils/columns.cjs.map +1 -1
- package/dist/pframe_utils/columns.d.ts +4 -4
- package/dist/pframe_utils/columns.d.ts.map +1 -1
- package/dist/pframe_utils/columns.js +7 -11
- package/dist/pframe_utils/columns.js.map +1 -1
- package/dist/pframe_utils/index.cjs.map +1 -1
- package/dist/pframe_utils/index.d.ts +1 -1
- package/dist/pframe_utils/index.d.ts.map +1 -1
- package/dist/pframe_utils/index.js.map +1 -1
- package/dist/render/accessor.cjs +15 -16
- package/dist/render/accessor.cjs.map +1 -1
- package/dist/render/accessor.d.ts +7 -10
- package/dist/render/accessor.d.ts.map +1 -1
- package/dist/render/accessor.js +15 -16
- package/dist/render/accessor.js.map +1 -1
- package/dist/render/api.cjs +34 -36
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +30 -20
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +35 -38
- package/dist/render/api.js.map +1 -1
- package/dist/render/index.cjs +4 -0
- package/dist/render/index.d.ts +4 -4
- package/dist/render/index.js +3 -2
- package/dist/render/internal.cjs +3 -2
- package/dist/render/internal.cjs.map +1 -1
- package/dist/render/internal.d.ts +17 -9
- package/dist/render/internal.d.ts.map +1 -1
- package/dist/render/internal.js +3 -2
- package/dist/render/internal.js.map +1 -1
- package/dist/render/util/axis_filtering.cjs.map +1 -1
- package/dist/render/util/axis_filtering.js.map +1 -1
- package/dist/render/util/column_collection.cjs +6 -4
- package/dist/render/util/column_collection.cjs.map +1 -1
- package/dist/render/util/column_collection.d.ts +9 -9
- package/dist/render/util/column_collection.d.ts.map +1 -1
- package/dist/render/util/column_collection.js +6 -4
- package/dist/render/util/column_collection.js.map +1 -1
- package/dist/render/util/index.d.ts +2 -2
- package/dist/render/util/pcolumn_data.cjs +1 -1
- package/dist/render/util/pcolumn_data.cjs.map +1 -1
- package/dist/render/util/pcolumn_data.d.ts +3 -3
- package/dist/render/util/pcolumn_data.d.ts.map +1 -1
- package/dist/render/util/pcolumn_data.js +1 -1
- package/dist/render/util/pcolumn_data.js.map +1 -1
- package/dist/services/block_services.cjs +2 -1
- package/dist/services/block_services.cjs.map +1 -1
- package/dist/services/block_services.d.ts +1 -0
- package/dist/services/block_services.d.ts.map +1 -1
- package/dist/services/block_services.js +2 -1
- package/dist/services/block_services.js.map +1 -1
- package/dist/services/get_services.cjs +2 -2
- package/dist/services/get_services.cjs.map +1 -1
- package/dist/services/get_services.d.ts +4 -1
- package/dist/services/get_services.d.ts.map +1 -1
- package/dist/services/get_services.js +2 -2
- package/dist/services/get_services.js.map +1 -1
- package/package.json +10 -8
- package/src/columns/__test_helpers__/collection_driver.ts +197 -0
- package/src/columns/__test_helpers__/stub_registry.ts +134 -0
- package/src/columns/column.ts +46 -0
- package/src/columns/column_lazy.test.ts +201 -0
- package/src/columns/column_lazy.ts +311 -0
- package/src/columns/column_providers/index.ts +78 -0
- package/src/columns/column_providers/providers.ts +193 -0
- package/src/columns/column_providers/types.ts +24 -0
- package/src/columns/column_recipes/column_discovered_recipe.ts +231 -0
- package/src/columns/column_recipes/column_filtered_recipe.ts +184 -0
- package/src/columns/column_recipes/column_overrided_recipe.ts +146 -0
- package/src/columns/column_recipes/column_recipes.test.ts +681 -0
- package/src/columns/column_recipes/index.ts +127 -0
- package/src/columns/column_recipes/leaf_rebrand.ts +28 -0
- package/src/columns/column_recipes/types.ts +113 -0
- package/src/columns/columns_collection.test.ts +164 -0
- package/src/columns/columns_collection.ts +170 -0
- package/src/columns/derive_axis_values_labels.ts +98 -0
- package/src/columns/expand_by_partition.test.ts +146 -110
- package/src/columns/expand_by_partition.ts +72 -84
- package/src/columns/index.ts +7 -5
- package/src/columns/utils.test.ts +249 -0
- package/src/columns/utils.ts +226 -0
- package/src/components/PFrameForGraphs.ts +3 -1
- package/src/components/PlAnnotations/filters_ui.test.ts +46 -47
- package/src/components/PlDataTable/columnResolver.test.ts +122 -0
- package/src/components/PlDataTable/columnResolver.ts +57 -0
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +9 -9
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +31 -63
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +85 -58
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +316 -354
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +48 -144
- package/src/components/PlDataTable/createPlDataTable/index.ts +2 -2
- package/src/components/PlDataTable/createPlDataTable/utils.test.ts +60 -69
- package/src/components/PlDataTable/createPlDataTable/utils.ts +65 -173
- package/src/components/PlDataTable/createPlDataTableSheet.ts +1 -1
- package/src/components/PlDataTable/index.ts +4 -2
- package/src/components/PlDataTable/labels.ts +4 -33
- package/src/components/PlDataTable/state-migration.ts +151 -22
- package/src/components/PlDataTable/typesV8.ts +188 -0
- package/src/components/PlDatasetSelector/build_dataset_options.ts +35 -60
- package/src/components/PlDatasetSelector/enrichment_discovery.ts +65 -61
- package/src/components/PlDatasetSelector/filter_discovery.test.ts +110 -72
- package/src/components/PlDatasetSelector/filter_discovery.ts +51 -31
- package/src/filters/converters/filterToQuery.test.ts +15 -8
- package/src/filters/converters/filterToQuery.ts +22 -9
- package/src/filters/converters/filterUiToExpressionImpl.test.ts +35 -36
- package/src/labels/derive_distinct_labels.ts +1 -1
- package/src/labels/derive_distinct_tooltips.test.ts +7 -4
- package/src/labels/derive_distinct_tooltips.ts +3 -3
- package/src/pframe_utils/axes.ts +2 -2
- package/src/pframe_utils/columns.ts +14 -23
- package/src/pframe_utils/index.ts +1 -1
- package/src/render/accessor.ts +35 -38
- package/src/render/api.ts +45 -61
- package/src/render/index.ts +2 -1
- package/src/render/internal.ts +43 -10
- package/src/render/util/axis_filtering.ts +0 -1
- package/src/render/util/column_collection.ts +17 -17
- package/src/render/util/pcolumn_data.ts +5 -9
- package/src/services/block_services.ts +1 -0
- package/src/services/get_services.ts +5 -2
- package/dist/columns/column_collection_builder.cjs +0 -241
- package/dist/columns/column_collection_builder.cjs.map +0 -1
- package/dist/columns/column_collection_builder.d.ts +0 -119
- package/dist/columns/column_collection_builder.d.ts.map +0 -1
- package/dist/columns/column_collection_builder.js +0 -241
- package/dist/columns/column_collection_builder.js.map +0 -1
- package/dist/columns/column_selector.cjs +0 -49
- package/dist/columns/column_selector.cjs.map +0 -1
- package/dist/columns/column_selector.d.ts +0 -33
- package/dist/columns/column_selector.d.ts.map +0 -1
- package/dist/columns/column_selector.js +0 -47
- package/dist/columns/column_selector.js.map +0 -1
- package/dist/columns/column_snapshot.cjs +0 -19
- package/dist/columns/column_snapshot.cjs.map +0 -1
- package/dist/columns/column_snapshot.d.ts +0 -39
- package/dist/columns/column_snapshot.d.ts.map +0 -1
- package/dist/columns/column_snapshot.js +0 -18
- package/dist/columns/column_snapshot.js.map +0 -1
- package/dist/columns/column_snapshot_provider.cjs +0 -137
- package/dist/columns/column_snapshot_provider.cjs.map +0 -1
- package/dist/columns/column_snapshot_provider.d.ts +0 -74
- package/dist/columns/column_snapshot_provider.d.ts.map +0 -1
- package/dist/columns/column_snapshot_provider.js +0 -133
- package/dist/columns/column_snapshot_provider.js.map +0 -1
- package/dist/columns/ctx_column_sources.cjs +0 -80
- package/dist/columns/ctx_column_sources.cjs.map +0 -1
- package/dist/columns/ctx_column_sources.d.ts +0 -30
- package/dist/columns/ctx_column_sources.d.ts.map +0 -1
- package/dist/columns/ctx_column_sources.js +0 -79
- package/dist/columns/ctx_column_sources.js.map +0 -1
- package/src/columns/column_collection_builder.test.ts +0 -484
- package/src/columns/column_collection_builder.ts +0 -485
- package/src/columns/column_selector.test.ts +0 -90
- package/src/columns/column_selector.ts +0 -100
- package/src/columns/column_snapshot.ts +0 -58
- package/src/columns/column_snapshot_provider.ts +0 -194
- package/src/columns/ctx_column_sources.ts +0 -99
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-only: plant a stub provider into `_ctxProvidersCache` so that
|
|
3
|
+
* `ColumnRegistry.resolve(id)` returns a `LeafEntry` whose spec accessor
|
|
4
|
+
* reports `hasData() === true` and `getDataAsJson()` returns the supplied
|
|
5
|
+
* spec. Sufficient for code that goes through
|
|
6
|
+
* `getCtxProviders` / `readLeafSpecAccessor` (e.g. `ColumnLazy.fromId`,
|
|
7
|
+
* `ColumnDiscoveredRecipe.fromKey`).
|
|
8
|
+
*
|
|
9
|
+
* Two leaf shapes:
|
|
10
|
+
* - `PColumnSpec` (shorthand) — spec present, accessor locked, `hasData`
|
|
11
|
+
* true. Discriminated at runtime by the presence of a `kind` field.
|
|
12
|
+
* - {@link StubLeafConfig} — fine-grained control: omit `spec` to simulate
|
|
13
|
+
* "no spec field reachable from this leaf"; set `accessorLocked: false`
|
|
14
|
+
* to simulate a still-resolving leaf accessor; set `specHasData: false`
|
|
15
|
+
* to simulate "spec resource exists but bytes not yet written".
|
|
16
|
+
*
|
|
17
|
+
* Registry-wide `isFinal` is also configurable — pass `{ isFinal: false }`
|
|
18
|
+
* to simulate a still-enumerating registry where missing ids should report
|
|
19
|
+
* `resolving` rather than `absent`.
|
|
20
|
+
*/
|
|
21
|
+
import type {
|
|
22
|
+
ColumnEntriesProvider,
|
|
23
|
+
LeafEntry,
|
|
24
|
+
PColumnSpec,
|
|
25
|
+
PObjectId,
|
|
26
|
+
} from "@milaboratories/pl-model-common";
|
|
27
|
+
import type { GlobalCfgRenderCtx } from "../../render/internal";
|
|
28
|
+
import type { TreeNodeAccessor } from "../../render/accessor";
|
|
29
|
+
import { _ctxProvidersCache } from "../column_providers";
|
|
30
|
+
import type { ColumnsProvider } from "../column_providers";
|
|
31
|
+
import { ColumnLazy, ColumnLazyImpl } from "../column_lazy";
|
|
32
|
+
|
|
33
|
+
/** Fine-grained per-leaf stub config. Defaults match the shorthand form. */
|
|
34
|
+
export type StubLeafConfig = {
|
|
35
|
+
/** Spec returned by `${id}.spec` traverse. Omit to simulate a missing field. */
|
|
36
|
+
spec?: PColumnSpec;
|
|
37
|
+
/** Drives `leaf.accessor.getInputsLocked()`. Default `true`. */
|
|
38
|
+
accessorLocked?: boolean;
|
|
39
|
+
/** Drives `spec.hasData()` (ignored when `spec` is omitted). Default `true`. */
|
|
40
|
+
specHasData?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Either the shorthand `PColumnSpec` or a {@link StubLeafConfig}. */
|
|
44
|
+
export type StubLeafInput = PColumnSpec | StubLeafConfig;
|
|
45
|
+
|
|
46
|
+
export function installStubRegistry(
|
|
47
|
+
ctx: GlobalCfgRenderCtx,
|
|
48
|
+
leaves: ReadonlyMap<PObjectId, StubLeafInput> | Record<PObjectId, StubLeafInput>,
|
|
49
|
+
{ isFinal = true }: { isFinal?: boolean } = {},
|
|
50
|
+
): void {
|
|
51
|
+
const map =
|
|
52
|
+
leaves instanceof Map
|
|
53
|
+
? leaves
|
|
54
|
+
: new Map(Object.entries(leaves) as [PObjectId, StubLeafInput][]);
|
|
55
|
+
_ctxProvidersCache.set(ctx, [buildStubProvider(map, isFinal)]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildStubProvider(
|
|
59
|
+
leaves: ReadonlyMap<PObjectId, StubLeafInput>,
|
|
60
|
+
isFinal: boolean,
|
|
61
|
+
): ColumnEntriesProvider<TreeNodeAccessor> & ColumnsProvider {
|
|
62
|
+
const { entries, columns } = [...leaves].reduce(
|
|
63
|
+
(acc, [id, raw]) => {
|
|
64
|
+
const cfg = normalizeLeafInput(raw);
|
|
65
|
+
acc.entries.set(id, { accessor: stubAccessorFor(id, cfg), name: id, id });
|
|
66
|
+
if (cfg.spec !== undefined && cfg.specHasData) {
|
|
67
|
+
acc.columns.push(
|
|
68
|
+
ColumnLazyImpl.fromColumn({ id, spec: cfg.spec, data: undefined as never }),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return acc;
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
entries: new Map<PObjectId, LeafEntry<TreeNodeAccessor>>(),
|
|
75
|
+
columns: [] as ColumnLazy[],
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
return {
|
|
79
|
+
getPObjectEntries: () => entries,
|
|
80
|
+
isFinal: () => isFinal,
|
|
81
|
+
getColumns: () => columns,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type NormalizedLeafConfig = {
|
|
86
|
+
spec?: PColumnSpec;
|
|
87
|
+
accessorLocked: boolean;
|
|
88
|
+
specHasData: boolean;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function normalizeLeafInput(raw: StubLeafInput): NormalizedLeafConfig {
|
|
92
|
+
if (isPColumnSpec(raw)) {
|
|
93
|
+
return { spec: raw, accessorLocked: true, specHasData: true };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
spec: raw.spec,
|
|
97
|
+
accessorLocked: raw.accessorLocked ?? true,
|
|
98
|
+
specHasData: raw.specHasData ?? true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isPColumnSpec(v: StubLeafInput): v is PColumnSpec {
|
|
103
|
+
return typeof v === "object" && v !== null && "kind" in v;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let stubAccessorSeq = 0;
|
|
107
|
+
|
|
108
|
+
function stubAccessorFor(id: PObjectId, cfg: NormalizedLeafConfig): TreeNodeAccessor {
|
|
109
|
+
const specField = `${id}.spec`;
|
|
110
|
+
const specHolder =
|
|
111
|
+
cfg.spec === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: {
|
|
114
|
+
getDataAsJson: <T>() => cfg.spec as unknown as T,
|
|
115
|
+
hasData: () => cfg.specHasData,
|
|
116
|
+
};
|
|
117
|
+
// Per-installation unique handle. The module-level `readSpecAccessor` LRU
|
|
118
|
+
// cache keys by `${accessor.handle}:${name}`, and reinstalling the stub
|
|
119
|
+
// for the same id should produce a fresh cache lookup (otherwise tests
|
|
120
|
+
// that vary leaf state for the same id see stale spec accessors).
|
|
121
|
+
const handle = `${id}#${++stubAccessorSeq}`;
|
|
122
|
+
const stub = {
|
|
123
|
+
handle,
|
|
124
|
+
resolvePath: [],
|
|
125
|
+
traverse: (step: { field?: string } | string) => {
|
|
126
|
+
const field = typeof step === "string" ? step : step?.field;
|
|
127
|
+
return field === specField ? specHolder : undefined;
|
|
128
|
+
},
|
|
129
|
+
getDataAsJson: <T>() => cfg.spec as unknown as T,
|
|
130
|
+
listInputFields: () => (cfg.spec !== undefined ? [specField] : []),
|
|
131
|
+
getInputsLocked: () => cfg.accessorLocked,
|
|
132
|
+
};
|
|
133
|
+
return stub as unknown as TreeNodeAccessor;
|
|
134
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnUniversalId,
|
|
3
|
+
LeafEntry,
|
|
4
|
+
PColumn,
|
|
5
|
+
PlRef,
|
|
6
|
+
PObjectId,
|
|
7
|
+
} from "@milaboratories/pl-model-common";
|
|
8
|
+
import type { GlobalCfgRenderCtx } from "../render/internal";
|
|
9
|
+
import type { TreeNodeAccessor } from "../render";
|
|
10
|
+
import { ColumnLazy, ColumnLazyImpl, type ColumnLazyData } from "./column_lazy";
|
|
11
|
+
import { ColumnRecipe, ColumnRecipeId, isColumnRecipe } from "./column_recipes";
|
|
12
|
+
|
|
13
|
+
export type ColumnSource =
|
|
14
|
+
| PObjectId
|
|
15
|
+
| ColumnUniversalId
|
|
16
|
+
| PlRef
|
|
17
|
+
| LeafEntry<TreeNodeAccessor>
|
|
18
|
+
| PColumn<ColumnLazyData>
|
|
19
|
+
| ColumnLazyImpl;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unified entry point — routes between the two top-level dispatchers:
|
|
23
|
+
* - string id (`PObjectId` / `ColumnUniversalId`) → {@link ColumnRecipe}
|
|
24
|
+
* - object source (`PlRef` / `LeafEntry` / `PColumn` / `ColumnLazy`) → {@link ColumnLazy}
|
|
25
|
+
*
|
|
26
|
+
* `ColumnLazy` is itself a `ColumnRecipe<PObjectId>`, so the return type is
|
|
27
|
+
* the common `ColumnRecipe`.
|
|
28
|
+
*/
|
|
29
|
+
export function Column(
|
|
30
|
+
source: ColumnSource,
|
|
31
|
+
opts?: { ctx?: GlobalCfgRenderCtx },
|
|
32
|
+
): undefined | ColumnRecipe {
|
|
33
|
+
if (typeof source === "string") return ColumnRecipe(source, opts);
|
|
34
|
+
return ColumnLazy(source, opts);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Column<ID extends ColumnRecipeId = ColumnRecipeId> = ColumnRecipe<ID>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Type-guard for the `Column` type — currently aliased to `ColumnRecipe`, so
|
|
41
|
+
* this is equivalent to {@link isColumnRecipe}. Kept as a separate export to
|
|
42
|
+
* mirror the `Column` / `ColumnRecipe` / `ColumnLazy` factory triple.
|
|
43
|
+
*/
|
|
44
|
+
export function isColumn(value: unknown): value is Column {
|
|
45
|
+
return isColumnRecipe(value);
|
|
46
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createLocalPObjectId,
|
|
4
|
+
parseColumnOverriddenId,
|
|
5
|
+
SpecOverrides,
|
|
6
|
+
type PColumnSpec,
|
|
7
|
+
type PObjectId,
|
|
8
|
+
} from "@milaboratories/pl-model-common";
|
|
9
|
+
import { ColumnAbsentError, ColumnLazy, ColumnLazyImpl } from "./column_lazy";
|
|
10
|
+
import { _ctxProvidersCache } from "./column_providers";
|
|
11
|
+
import { installStubRegistry } from "./__test_helpers__/stub_registry";
|
|
12
|
+
|
|
13
|
+
// --- Ambient ctx mock ------------------------------------------------------
|
|
14
|
+
// ColumnLazy constructor pulls `getCfgRenderCtx()` from globalThis. A minimal
|
|
15
|
+
// mock with empty index is enough — these tests don't exercise resolve paths.
|
|
16
|
+
|
|
17
|
+
const mockCtx = {
|
|
18
|
+
getAccessorHandleByName: () => undefined,
|
|
19
|
+
getUpstreamBlockCtx: () => [],
|
|
20
|
+
} as unknown as never;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
(globalThis as { cfgRenderCtx?: unknown }).cfgRenderCtx = mockCtx;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
delete (globalThis as { cfgRenderCtx?: unknown }).cfgRenderCtx;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// --- Helpers ----------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const baseLeaf = createLocalPObjectId(["main", "out"], "col-1");
|
|
33
|
+
|
|
34
|
+
const stubSpec = { kind: "PColumn", name: "stub" } as unknown as PColumnSpec;
|
|
35
|
+
|
|
36
|
+
function lazyOf(id: PObjectId, spec: PColumnSpec = stubSpec) {
|
|
37
|
+
return ColumnLazyImpl.fromColumn({ id, spec, data: undefined });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- clone flattening -------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
describe("ColumnLazy.clone", () => {
|
|
43
|
+
test("two successive clones produce flat wrap (no nesting)", () => {
|
|
44
|
+
const c0 = lazyOf(baseLeaf);
|
|
45
|
+
const c1 = c0.withSpecs({
|
|
46
|
+
annotations: { a: "1" },
|
|
47
|
+
domain: {},
|
|
48
|
+
contextDomain: {},
|
|
49
|
+
axesSpec: {},
|
|
50
|
+
});
|
|
51
|
+
const c2 = c1.withSpecs({
|
|
52
|
+
annotations: { b: "2" },
|
|
53
|
+
domain: {},
|
|
54
|
+
contextDomain: {},
|
|
55
|
+
axesSpec: {},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const parsed = parseColumnOverriddenId(c2.id);
|
|
59
|
+
// source must be the canonical leaf — not a nested wrap
|
|
60
|
+
expect(parsed.source).toBe(baseLeaf);
|
|
61
|
+
expect(parsed.specOverrides.annotations).toEqual({ a: "1", b: "2" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("idempotent: clone with same overrides twice → same id", () => {
|
|
65
|
+
const c0 = lazyOf(baseLeaf);
|
|
66
|
+
const patch: SpecOverrides = {
|
|
67
|
+
annotations: { x: "1" },
|
|
68
|
+
domain: {},
|
|
69
|
+
contextDomain: {},
|
|
70
|
+
axesSpec: {},
|
|
71
|
+
};
|
|
72
|
+
const a = c0.withSpecs(patch);
|
|
73
|
+
const b = c0.withSpecs(patch);
|
|
74
|
+
expect(a.id).toBe(b.id);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// --- spec() override application -------------------------------------------
|
|
79
|
+
|
|
80
|
+
describe("ColumnLazy.spec — applySpecOverrides", () => {
|
|
81
|
+
test("base spec with no overrides returns readers.spec verbatim", () => {
|
|
82
|
+
const c = lazyOf(baseLeaf);
|
|
83
|
+
expect(c.getSpec()).toBe(stubSpec);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("fromId throws ColumnAbsentError when ctx has no handles", () => {
|
|
87
|
+
// Empty registry — `isFinal()` returns true vacuously, so the leaf is
|
|
88
|
+
// provably absent (no provider will ever supply it).
|
|
89
|
+
expect(() => ColumnLazyImpl.fromId(baseLeaf)).toThrow(ColumnAbsentError);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("appends axes at indices past base.axesSpec.length", () => {
|
|
93
|
+
const base = {
|
|
94
|
+
kind: "PColumn",
|
|
95
|
+
name: "col",
|
|
96
|
+
valueType: "String",
|
|
97
|
+
axesSpec: [{ name: "group", type: "String" }],
|
|
98
|
+
annotations: {},
|
|
99
|
+
} as unknown as PColumnSpec;
|
|
100
|
+
const c = lazyOf(baseLeaf, base).withSpecs({
|
|
101
|
+
annotations: {},
|
|
102
|
+
domain: {},
|
|
103
|
+
contextDomain: {},
|
|
104
|
+
axesSpec: { 1: { name: "name", type: "String" } },
|
|
105
|
+
});
|
|
106
|
+
expect(c.getSpec().axesSpec).toEqual([
|
|
107
|
+
{ name: "group", type: "String" },
|
|
108
|
+
{ name: "name", type: "String" },
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("patch on existing axis merges annotations/domain (does not replace whole object)", () => {
|
|
113
|
+
const base = {
|
|
114
|
+
kind: "PColumn",
|
|
115
|
+
name: "col",
|
|
116
|
+
valueType: "String",
|
|
117
|
+
axesSpec: [{ name: "group", type: "String", annotations: { k1: "v1" } }],
|
|
118
|
+
annotations: {},
|
|
119
|
+
} as unknown as PColumnSpec;
|
|
120
|
+
const c = lazyOf(baseLeaf, base).withSpecs({
|
|
121
|
+
annotations: {},
|
|
122
|
+
domain: {},
|
|
123
|
+
contextDomain: {},
|
|
124
|
+
axesSpec: { 0: { annotations: { k2: "v2" } } },
|
|
125
|
+
});
|
|
126
|
+
expect(c.getSpec().axesSpec[0].annotations).toEqual({ k1: "v1", k2: "v2" });
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// --- resolution status / absent throw --------------------------------------
|
|
131
|
+
|
|
132
|
+
describe("ColumnLazy resolution: fromId / fromAccessor / getStatus", () => {
|
|
133
|
+
test("fromId returns undefined when registry is unfinalized and id not found", () => {
|
|
134
|
+
// `isFinal: false` → resolver can't tell yet → resolving → undefined.
|
|
135
|
+
installStubRegistry(mockCtx, {}, { isFinal: false });
|
|
136
|
+
expect(ColumnLazyImpl.fromId(baseLeaf)).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("fromId throws ColumnAbsentError when leaf has no spec field on locked accessor", () => {
|
|
140
|
+
// Leaf entry present, no spec field, accessor locked → absent forever.
|
|
141
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: true } });
|
|
142
|
+
expect(() => ColumnLazyImpl.fromId(baseLeaf)).toThrow(ColumnAbsentError);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("fromId returns undefined when leaf has no spec field on unlocked accessor", () => {
|
|
146
|
+
// Leaf entry present, no spec field, accessor unlocked → spec may still arrive.
|
|
147
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: false } });
|
|
148
|
+
expect(ColumnLazyImpl.fromId(baseLeaf)).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("fromId returns undefined when spec accessor exists but hasData is false", () => {
|
|
152
|
+
// Spec resource present, bytes not yet written — transient resolving.
|
|
153
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { spec: stubSpec, specHasData: false } });
|
|
154
|
+
expect(ColumnLazyImpl.fromId(baseLeaf)).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("fromAccessor throws ColumnAbsentError when accessor is locked without spec", () => {
|
|
158
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: true } });
|
|
159
|
+
const entry = pluckLeafEntry(baseLeaf);
|
|
160
|
+
expect(() => ColumnLazyImpl.fromAccessor(entry)).toThrow(ColumnAbsentError);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("fromAccessor returns undefined when accessor is unlocked without spec", () => {
|
|
164
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: false } });
|
|
165
|
+
const entry = pluckLeafEntry(baseLeaf);
|
|
166
|
+
expect(ColumnLazyImpl.fromAccessor(entry)).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("getStatusById returns 'absent' / 'resolving' / 'present' across configurations", () => {
|
|
170
|
+
// present — spec with data on a locked, populated leaf.
|
|
171
|
+
installStubRegistry(mockCtx, { [baseLeaf]: stubSpec });
|
|
172
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("present");
|
|
173
|
+
|
|
174
|
+
// resolving — same leaf id, spec bytes not yet written.
|
|
175
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { spec: stubSpec, specHasData: false } });
|
|
176
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("resolving");
|
|
177
|
+
|
|
178
|
+
// resolving — leaf entry exists but spec field missing on an unlocked accessor.
|
|
179
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: false } });
|
|
180
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("resolving");
|
|
181
|
+
|
|
182
|
+
// absent — leaf entry exists with no spec field on a locked accessor.
|
|
183
|
+
installStubRegistry(mockCtx, { [baseLeaf]: { accessorLocked: true } });
|
|
184
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("absent");
|
|
185
|
+
|
|
186
|
+
// absent — id not in any provider and registry is final.
|
|
187
|
+
installStubRegistry(mockCtx, {}, { isFinal: true });
|
|
188
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("absent");
|
|
189
|
+
|
|
190
|
+
// resolving — id not in any provider but registry still enumerating.
|
|
191
|
+
installStubRegistry(mockCtx, {}, { isFinal: false });
|
|
192
|
+
expect(ColumnLazy.getStatusById(baseLeaf)).toBe("resolving");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/** Pull the `LeafEntry` planted by the stub registry for a given id. */
|
|
197
|
+
function pluckLeafEntry(id: PObjectId) {
|
|
198
|
+
const entry = _ctxProvidersCache.get(mockCtx)?.[0]?.getPObjectEntries().get(id);
|
|
199
|
+
if (entry === undefined) throw new Error(`pluckLeafEntry: no entry for ${id}`);
|
|
200
|
+
return entry;
|
|
201
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColumnRegistry,
|
|
3
|
+
createGlobalPObjectId,
|
|
4
|
+
isPColumn,
|
|
5
|
+
isPlRef,
|
|
6
|
+
PlRef,
|
|
7
|
+
PObjectId,
|
|
8
|
+
type ColumnUniversalId,
|
|
9
|
+
type LeafEntry,
|
|
10
|
+
type PColumn,
|
|
11
|
+
type PColumnSpec,
|
|
12
|
+
type SpecOverrides,
|
|
13
|
+
type SpecQuery,
|
|
14
|
+
} from "@milaboratories/pl-model-common";
|
|
15
|
+
import type { GlobalCfgRenderCtx, PColumnDataUniversal } from "../render/internal";
|
|
16
|
+
import { getCtxProviders } from "./column_providers";
|
|
17
|
+
import { isNil } from "es-toolkit";
|
|
18
|
+
import { LRUCache } from "lru-cache";
|
|
19
|
+
import { TreeNodeAccessor } from "../render";
|
|
20
|
+
import type {
|
|
21
|
+
ColumnFieldStatus,
|
|
22
|
+
ColumnRecipe,
|
|
23
|
+
ColumnResolutionStatus,
|
|
24
|
+
} from "./column_recipes/types";
|
|
25
|
+
import { ColumnOverriddenRecipe } from "./column_recipes/column_overrided_recipe";
|
|
26
|
+
|
|
27
|
+
export type ColumnLazyId = PObjectId;
|
|
28
|
+
export type ColumnLazyData = undefined | PColumnDataUniversal;
|
|
29
|
+
export type { ColumnFieldStatus, ColumnResolutionStatus } from "./column_recipes/types";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Thrown by leaf-recipe factories when the requested column is provably
|
|
33
|
+
* absent in the active render ctx — i.e. every relevant accessor reports
|
|
34
|
+
* `inputsLocked` and the column did not appear. Distinct from the
|
|
35
|
+
* `undefined` return that the factories still use for the "resolving" case
|
|
36
|
+
* (the column may still appear later).
|
|
37
|
+
*
|
|
38
|
+
* Catch at the boundary (resolver / filter+sort wiring / data-table assembly)
|
|
39
|
+
* when partial absence should be surfaced to the user rather than silently
|
|
40
|
+
* producing an empty result.
|
|
41
|
+
*/
|
|
42
|
+
export class ColumnAbsentError extends Error {
|
|
43
|
+
constructor(public readonly id: ColumnUniversalId) {
|
|
44
|
+
super(`Column is absent in the active render ctx and will not appear: ${id}`);
|
|
45
|
+
this.name = "ColumnAbsentError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ColumnLazy is the leaf-recipe building block: a {@link ColumnRecipe} whose
|
|
51
|
+
* `id` is a bare {@link PObjectId} and whose readers are bound to a single
|
|
52
|
+
* tree-accessor leaf. Layered encodings (Overridden / Discovered / Filtered)
|
|
53
|
+
* are reified through their dedicated recipe classes and reference leaf
|
|
54
|
+
* columns by id.
|
|
55
|
+
*/
|
|
56
|
+
export class ColumnLazyImpl implements ColumnRecipe<PObjectId> {
|
|
57
|
+
private specCache?: { readonly value: PColumnSpec };
|
|
58
|
+
private dataCache?: { readonly value: ColumnLazyData };
|
|
59
|
+
private dataStatusCache?: { readonly value: ColumnFieldStatus };
|
|
60
|
+
|
|
61
|
+
private constructor(
|
|
62
|
+
public readonly id: PObjectId,
|
|
63
|
+
private readonly options: {
|
|
64
|
+
getSpec: () => PColumnSpec;
|
|
65
|
+
getData: () => ColumnLazyData;
|
|
66
|
+
getDataStatus: () => ColumnFieldStatus;
|
|
67
|
+
},
|
|
68
|
+
) {}
|
|
69
|
+
|
|
70
|
+
/** A leaf-recipe references exactly one column — its own id. */
|
|
71
|
+
getReferencedIds(): PObjectId[] {
|
|
72
|
+
return [this.id];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getSpec(): PColumnSpec {
|
|
76
|
+
if (this.specCache === undefined) {
|
|
77
|
+
this.specCache = { value: this.options.getSpec() };
|
|
78
|
+
}
|
|
79
|
+
return this.specCache.value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Leaf-shaped — points straight at `this.id`. */
|
|
83
|
+
getQuery(): SpecQuery {
|
|
84
|
+
return { type: "column", column: this.id } as SpecQuery;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Leaf-only: not on the recipe interface — only the leaf can read data directly. */
|
|
88
|
+
getData(): ColumnLazyData {
|
|
89
|
+
if (this.dataCache === undefined) this.dataCache = { value: this.options.getData() };
|
|
90
|
+
return this.dataCache.value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getDataStatus(): ColumnFieldStatus {
|
|
94
|
+
if (this.dataStatusCache === undefined) {
|
|
95
|
+
this.dataStatusCache = { value: this.options.getDataStatus() };
|
|
96
|
+
}
|
|
97
|
+
return this.dataStatusCache.value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Overlay overrides → produces a {@link ColumnOverriddenRecipe} wrapping
|
|
102
|
+
* this leaf. ColumnLazy itself stays bare (id remains a plain PObjectId);
|
|
103
|
+
* layering lives entirely in the recipe wrappers.
|
|
104
|
+
*/
|
|
105
|
+
withSpecs(overrides: SpecOverrides): ColumnRecipe {
|
|
106
|
+
return ColumnOverriddenRecipe.wrap(this, overrides);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve via the ambient {@link ColumnRegistry}. Spec is resolved eagerly:
|
|
111
|
+
* returns `undefined` if the leaf isn't reachable yet (resolving). Throws
|
|
112
|
+
* {@link ColumnAbsentError} when every relevant accessor is `inputsLocked`
|
|
113
|
+
* and the column did not appear — the column will not exist in this ctx.
|
|
114
|
+
* Data and dataStatus stay lazy.
|
|
115
|
+
*/
|
|
116
|
+
static fromId(id: PObjectId, { ctx }: { ctx?: GlobalCfgRenderCtx } = {}): undefined | ColumnLazy {
|
|
117
|
+
const registry = new ColumnRegistry(getCtxProviders({ ctx }));
|
|
118
|
+
const leaf = registry.resolve(id);
|
|
119
|
+
if (isNil(leaf)) {
|
|
120
|
+
if (registry.isFinal()) throw new ColumnAbsentError(id);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const spec = readSpecAccessor(leaf);
|
|
124
|
+
if (isNil(spec)) {
|
|
125
|
+
if (leaf.accessor.getInputsLocked()) throw new ColumnAbsentError(id);
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
if (!spec.hasData()) return undefined;
|
|
129
|
+
return new ColumnLazyImpl(id, {
|
|
130
|
+
getSpec: () => spec.getDataAsJson<PColumnSpec>(),
|
|
131
|
+
getData: () => readDataAccessor(leaf),
|
|
132
|
+
getDataStatus: () => readDataStatus(leaf),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** {@link PlRef} wrapper over {@link fromId}. */
|
|
137
|
+
static fromPlRef(ref: PlRef): undefined | ColumnLazy {
|
|
138
|
+
return ColumnLazyImpl.fromId(createGlobalPObjectId(ref.blockId, ref.name));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Bind directly to an accessor-backed {@link LeafEntry} — no registry.
|
|
143
|
+
* Throws {@link ColumnAbsentError} if the leaf has no spec field and its
|
|
144
|
+
* accessor is `inputsLocked`. Returns `undefined` while still resolving.
|
|
145
|
+
*/
|
|
146
|
+
static fromAccessor(entry: LeafEntry<TreeNodeAccessor>): undefined | ColumnLazy {
|
|
147
|
+
const spec = readSpecAccessor(entry);
|
|
148
|
+
if (isNil(spec)) {
|
|
149
|
+
if (entry.accessor.getInputsLocked()) throw new ColumnAbsentError(entry.id);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (!spec.hasData()) return undefined;
|
|
153
|
+
return new ColumnLazyImpl(entry.id, {
|
|
154
|
+
getSpec: () => spec.getDataAsJson<PColumnSpec>(),
|
|
155
|
+
getData: () => readDataAccessor(entry),
|
|
156
|
+
getDataStatus: () => readDataStatus(entry),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Wrap a materialised {@link PColumn}. If the input is already a
|
|
162
|
+
* {@link ColumnLazy} it is returned as-is.
|
|
163
|
+
*/
|
|
164
|
+
static fromColumn(column: PColumn<ColumnLazyData> | ColumnLazy): ColumnLazy {
|
|
165
|
+
if (column instanceof ColumnLazyImpl) return column;
|
|
166
|
+
return new ColumnLazyImpl(column.id, {
|
|
167
|
+
getSpec: () => column.spec,
|
|
168
|
+
getData: () => column.data,
|
|
169
|
+
getDataStatus: () => "present",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Distinguishes `present` / `resolving` / `absent` for a {@link PObjectId}
|
|
175
|
+
* in the active render ctx. Falls back to the registry's `isFinal()`
|
|
176
|
+
* when the id has no entry — only then we can say `absent` instead of
|
|
177
|
+
* `resolving`.
|
|
178
|
+
*/
|
|
179
|
+
static getStatusById(
|
|
180
|
+
id: PObjectId,
|
|
181
|
+
{ ctx }: { ctx?: GlobalCfgRenderCtx } = {},
|
|
182
|
+
): ColumnResolutionStatus {
|
|
183
|
+
const registry = new ColumnRegistry(getCtxProviders({ ctx }));
|
|
184
|
+
const leaf = registry.resolve(id);
|
|
185
|
+
if (isNil(leaf)) return registry.isFinal() ? "absent" : "resolving";
|
|
186
|
+
return getLeafEntryStatus(leaf);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** {@link PlRef} wrapper over {@link getStatusById}. */
|
|
190
|
+
static getStatusByPlRef(
|
|
191
|
+
ref: PlRef,
|
|
192
|
+
opts: { ctx?: GlobalCfgRenderCtx } = {},
|
|
193
|
+
): ColumnResolutionStatus {
|
|
194
|
+
return ColumnLazyImpl.getStatusById(createGlobalPObjectId(ref.blockId, ref.name), opts);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** No registry — reads straight off the entry's accessor. */
|
|
198
|
+
static getStatusByAccessor(entry: LeafEntry<TreeNodeAccessor>): ColumnResolutionStatus {
|
|
199
|
+
return getLeafEntryStatus(entry);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Public type alias — `ColumnLazy` (the type) refers to the underlying
|
|
205
|
+
* {@link ColumnLazyImpl} class instance. `ColumnLazy` (the value) is the
|
|
206
|
+
* callable below with the static factories attached.
|
|
207
|
+
*/
|
|
208
|
+
export type ColumnLazy = ColumnLazyImpl;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Unified dispatcher — picks the right `ColumnLazyImpl.fromX` by source
|
|
212
|
+
* shape. For ambiguous inputs callers can still use the explicit factories
|
|
213
|
+
* (also attached as properties: `ColumnLazy.fromId`, `.fromPlRef`,
|
|
214
|
+
* `.fromAccessor`, `.fromColumn`).
|
|
215
|
+
*/
|
|
216
|
+
function ColumnLazyDispatch(
|
|
217
|
+
source: PObjectId | PlRef | LeafEntry<TreeNodeAccessor> | PColumn<ColumnLazyData> | ColumnLazy,
|
|
218
|
+
opts: { ctx?: GlobalCfgRenderCtx } = {},
|
|
219
|
+
): undefined | ColumnLazy {
|
|
220
|
+
if (typeof source === "string") return ColumnLazyImpl.fromId(source, opts);
|
|
221
|
+
if (source instanceof ColumnLazyImpl) return source;
|
|
222
|
+
if ("accessor" in source) return ColumnLazyImpl.fromAccessor(source);
|
|
223
|
+
if (isPlRef(source)) return ColumnLazyImpl.fromPlRef(source);
|
|
224
|
+
if (isPColumn(source)) return ColumnLazyImpl.fromColumn(source);
|
|
225
|
+
throw new Error("ColumnLazy: unknown source shape");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Polymorphic counterpart to {@link ColumnLazyDispatch}: returns the
|
|
230
|
+
* {@link ColumnResolutionStatus} for any factory input without constructing
|
|
231
|
+
* the recipe. For already-materialised sources ({@link PColumn} value,
|
|
232
|
+
* existing {@link ColumnLazy}) status is `present` by construction.
|
|
233
|
+
*/
|
|
234
|
+
function ColumnLazyGetStatus(
|
|
235
|
+
source: PObjectId | PlRef | LeafEntry<TreeNodeAccessor> | PColumn<ColumnLazyData> | ColumnLazy,
|
|
236
|
+
opts: { ctx?: GlobalCfgRenderCtx } = {},
|
|
237
|
+
): ColumnResolutionStatus {
|
|
238
|
+
if (typeof source === "string") return ColumnLazyImpl.getStatusById(source, opts);
|
|
239
|
+
if (source instanceof ColumnLazyImpl) return "present";
|
|
240
|
+
if ("accessor" in source) return ColumnLazyImpl.getStatusByAccessor(source);
|
|
241
|
+
if (isPlRef(source)) return ColumnLazyImpl.getStatusByPlRef(source, opts);
|
|
242
|
+
if (isPColumn(source)) return "present";
|
|
243
|
+
throw new Error("ColumnLazy.getStatus: unknown source shape");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const ColumnLazy = Object.assign(ColumnLazyDispatch, {
|
|
247
|
+
fromId: ColumnLazyImpl.fromId,
|
|
248
|
+
fromPlRef: ColumnLazyImpl.fromPlRef,
|
|
249
|
+
fromAccessor: ColumnLazyImpl.fromAccessor,
|
|
250
|
+
fromColumn: ColumnLazyImpl.fromColumn,
|
|
251
|
+
getStatus: ColumnLazyGetStatus,
|
|
252
|
+
getStatusById: ColumnLazyImpl.getStatusById,
|
|
253
|
+
getStatusByPlRef: ColumnLazyImpl.getStatusByPlRef,
|
|
254
|
+
getStatusByAccessor: ColumnLazyImpl.getStatusByAccessor,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Type-guard narrowing to the leaf recipe. Prefer this over
|
|
259
|
+
* `instanceof ColumnLazyImpl` — `ColumnLazy` (the value) is the dispatcher
|
|
260
|
+
* with statics, not the class, so `instanceof` requires the impl symbol.
|
|
261
|
+
*/
|
|
262
|
+
export function isColumnLazy(value: unknown): value is ColumnLazy {
|
|
263
|
+
return value instanceof ColumnLazyImpl;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const readSpecAccessor = memoizeByEntry(
|
|
267
|
+
({ accessor, name }: LeafEntry<TreeNodeAccessor>): undefined | TreeNodeAccessor =>
|
|
268
|
+
accessor.traverse({ field: `${name}.spec`, assertFieldType: "Input", ignoreError: true }),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Per-entry counterpart to {@link readDataStatus}: tells whether the leaf's
|
|
273
|
+
* **spec** can be read in this ctx, and — for the negative cases —
|
|
274
|
+
* distinguishes `resolving` from `absent` via `getInputsLocked()`.
|
|
275
|
+
*
|
|
276
|
+
* - spec field not yet on the entry's accessor + inputs locked → `absent`
|
|
277
|
+
* - spec field not yet on the entry's accessor + still resolving → `resolving`
|
|
278
|
+
* - spec resource present but bytes not yet written → `resolving`
|
|
279
|
+
* (transient — the spec resource is connected, just unfilled)
|
|
280
|
+
* - spec resource present and `hasData()` → `present`
|
|
281
|
+
*/
|
|
282
|
+
function getLeafEntryStatus(entry: LeafEntry<TreeNodeAccessor>): ColumnResolutionStatus {
|
|
283
|
+
const spec = readSpecAccessor(entry);
|
|
284
|
+
if (isNil(spec)) return entry.accessor.getInputsLocked() ? "absent" : "resolving";
|
|
285
|
+
if (!spec.hasData()) return "resolving";
|
|
286
|
+
return "present";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const readDataAccessor = memoizeByEntry(
|
|
290
|
+
({ accessor, name }: LeafEntry<TreeNodeAccessor>): undefined | TreeNodeAccessor =>
|
|
291
|
+
accessor.traverse({ field: `${name}.data`, assertFieldType: "Input", ignoreError: true }),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const readDataStatus = memoizeByEntry(
|
|
295
|
+
({ accessor, name }: LeafEntry<TreeNodeAccessor>): ColumnFieldStatus => {
|
|
296
|
+
if (accessor.listInputFields().includes(`${name}.data`)) return "present";
|
|
297
|
+
return accessor.getInputsLocked() ? "absent" : "resolving";
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
function memoizeByEntry<R>(
|
|
302
|
+
fn: (entry: LeafEntry<TreeNodeAccessor>) => R,
|
|
303
|
+
): (entry: LeafEntry<TreeNodeAccessor>) => R {
|
|
304
|
+
const cache = new LRUCache<string, { value: R }>({ max: 1000 });
|
|
305
|
+
return (entry) => {
|
|
306
|
+
const key = `${entry.accessor.handle}:${entry.name}`;
|
|
307
|
+
let hit = cache.get(key);
|
|
308
|
+
if (!hit) cache.set(key, (hit = { value: fn(entry) }));
|
|
309
|
+
return hit.value;
|
|
310
|
+
};
|
|
311
|
+
}
|