@platforma-sdk/model 1.63.12 → 1.64.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.
Files changed (162) hide show
  1. package/dist/columns/column_collection_builder.cjs +105 -92
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +13 -12
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +107 -94
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/columns/column_selector.cjs +8 -80
  8. package/dist/columns/column_selector.cjs.map +1 -1
  9. package/dist/columns/column_selector.d.ts +6 -14
  10. package/dist/columns/column_selector.d.ts.map +1 -1
  11. package/dist/columns/column_selector.js +6 -77
  12. package/dist/columns/column_selector.js.map +1 -1
  13. package/dist/columns/column_snapshot.cjs +3 -3
  14. package/dist/columns/column_snapshot.cjs.map +1 -1
  15. package/dist/columns/column_snapshot.d.ts +3 -3
  16. package/dist/columns/column_snapshot.d.ts.map +1 -1
  17. package/dist/columns/column_snapshot.js +3 -3
  18. package/dist/columns/column_snapshot.js.map +1 -1
  19. package/dist/columns/column_snapshot_provider.cjs +1 -1
  20. package/dist/columns/column_snapshot_provider.cjs.map +1 -1
  21. package/dist/columns/column_snapshot_provider.d.ts +8 -8
  22. package/dist/columns/column_snapshot_provider.d.ts.map +1 -1
  23. package/dist/columns/column_snapshot_provider.js +1 -1
  24. package/dist/columns/column_snapshot_provider.js.map +1 -1
  25. package/dist/columns/ctx_column_sources.cjs.map +1 -1
  26. package/dist/columns/ctx_column_sources.d.ts +2 -1
  27. package/dist/columns/ctx_column_sources.d.ts.map +1 -1
  28. package/dist/columns/ctx_column_sources.js.map +1 -1
  29. package/dist/columns/expand_by_partition.cjs +106 -0
  30. package/dist/columns/expand_by_partition.cjs.map +1 -0
  31. package/dist/columns/expand_by_partition.d.ts +33 -0
  32. package/dist/columns/expand_by_partition.d.ts.map +1 -0
  33. package/dist/columns/expand_by_partition.js +105 -0
  34. package/dist/columns/expand_by_partition.js.map +1 -0
  35. package/dist/columns/index.cjs +1 -0
  36. package/dist/columns/index.d.ts +4 -3
  37. package/dist/columns/index.js +1 -0
  38. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +26 -0
  39. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -0
  40. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +25 -0
  41. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -0
  42. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs +68 -0
  43. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -0
  44. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js +67 -0
  45. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -0
  46. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs +20 -9
  47. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
  48. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +4 -0
  49. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts.map +1 -1
  50. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js +21 -10
  51. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
  52. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +257 -175
  53. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  54. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +37 -21
  55. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
  56. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +260 -175
  57. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  58. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +64 -0
  59. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -0
  60. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +17 -0
  61. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -0
  62. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +63 -0
  63. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -0
  64. package/dist/components/PlDataTable/createPlDataTable/index.cjs +2 -1
  65. package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
  66. package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -1
  67. package/dist/components/PlDataTable/createPlDataTable/index.d.ts.map +1 -1
  68. package/dist/components/PlDataTable/createPlDataTable/index.js +2 -1
  69. package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
  70. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +109 -0
  71. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -0
  72. package/dist/components/PlDataTable/createPlDataTable/utils.d.ts +19 -0
  73. package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -0
  74. package/dist/components/PlDataTable/createPlDataTable/utils.js +102 -0
  75. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -0
  76. package/dist/components/PlDataTable/index.cjs +3 -1
  77. package/dist/components/PlDataTable/index.d.ts +4 -2
  78. package/dist/components/PlDataTable/index.js +3 -1
  79. package/dist/components/PlDataTable/labels.cjs +25 -11
  80. package/dist/components/PlDataTable/labels.cjs.map +1 -1
  81. package/dist/components/PlDataTable/labels.js +25 -11
  82. package/dist/components/PlDataTable/labels.js.map +1 -1
  83. package/dist/components/PlDataTable/state-migration.cjs +4 -1
  84. package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
  85. package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
  86. package/dist/components/PlDataTable/state-migration.js +4 -1
  87. package/dist/components/PlDataTable/state-migration.js.map +1 -1
  88. package/dist/components/PlDataTable/typesV5.d.ts +5 -4
  89. package/dist/components/PlDataTable/typesV5.d.ts.map +1 -1
  90. package/dist/components/index.cjs +3 -1
  91. package/dist/components/index.d.ts +3 -1
  92. package/dist/components/index.js +3 -1
  93. package/dist/index.cjs +13 -9
  94. package/dist/index.d.ts +8 -6
  95. package/dist/index.js +6 -4
  96. package/dist/labels/derive_distinct_labels.cjs +39 -27
  97. package/dist/labels/derive_distinct_labels.cjs.map +1 -1
  98. package/dist/labels/derive_distinct_labels.d.ts +15 -15
  99. package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
  100. package/dist/labels/derive_distinct_labels.js +39 -27
  101. package/dist/labels/derive_distinct_labels.js.map +1 -1
  102. package/dist/labels/index.cjs +0 -1
  103. package/dist/labels/index.d.ts +1 -2
  104. package/dist/labels/index.js +0 -1
  105. package/dist/package.cjs +1 -1
  106. package/dist/package.js +1 -1
  107. package/dist/render/api.cjs +10 -3
  108. package/dist/render/api.cjs.map +1 -1
  109. package/dist/render/api.d.ts +2 -2
  110. package/dist/render/api.d.ts.map +1 -1
  111. package/dist/render/api.js +10 -3
  112. package/dist/render/api.js.map +1 -1
  113. package/dist/render/util/column_collection.cjs +3 -3
  114. package/dist/render/util/column_collection.cjs.map +1 -1
  115. package/dist/render/util/column_collection.d.ts.map +1 -1
  116. package/dist/render/util/column_collection.js +3 -3
  117. package/dist/render/util/column_collection.js.map +1 -1
  118. package/dist/render/util/label.cjs +2 -2
  119. package/dist/render/util/label.cjs.map +1 -1
  120. package/dist/render/util/label.js +2 -2
  121. package/dist/render/util/label.js.map +1 -1
  122. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  123. package/dist/render/util/pcolumn_data.d.ts +2 -2
  124. package/dist/render/util/pcolumn_data.d.ts.map +1 -1
  125. package/dist/render/util/pcolumn_data.js.map +1 -1
  126. package/package.json +6 -6
  127. package/src/columns/column_collection_builder.test.ts +40 -27
  128. package/src/columns/column_collection_builder.ts +176 -131
  129. package/src/columns/column_selector.test.ts +17 -399
  130. package/src/columns/column_selector.ts +14 -127
  131. package/src/columns/column_snapshot.ts +5 -5
  132. package/src/columns/column_snapshot_provider.ts +11 -10
  133. package/src/columns/ctx_column_sources.ts +2 -2
  134. package/src/columns/expand_by_partition.test.ts +4 -4
  135. package/src/columns/expand_by_partition.ts +4 -3
  136. package/src/columns/index.ts +1 -0
  137. package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +42 -0
  138. package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +89 -0
  139. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +39 -11
  140. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +502 -313
  141. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +122 -0
  142. package/src/components/PlDataTable/createPlDataTable/index.ts +4 -2
  143. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +257 -0
  144. package/src/components/PlDataTable/createPlDataTable/utils.ts +160 -0
  145. package/src/components/PlDataTable/index.ts +13 -2
  146. package/src/components/PlDataTable/labels.ts +29 -18
  147. package/src/components/PlDataTable/state-migration.ts +3 -1
  148. package/src/components/PlDataTable/typesV5.ts +4 -4
  149. package/src/labels/derive_distinct_labels.test.ts +143 -45
  150. package/src/labels/derive_distinct_labels.ts +102 -49
  151. package/src/labels/index.ts +0 -1
  152. package/src/render/api.ts +15 -5
  153. package/src/render/util/column_collection.ts +4 -3
  154. package/src/render/util/label.ts +2 -2
  155. package/src/render/util/pcolumn_data.ts +5 -3
  156. package/dist/labels/write_labels_to_specs.cjs +0 -14
  157. package/dist/labels/write_labels_to_specs.cjs.map +0 -1
  158. package/dist/labels/write_labels_to_specs.d.ts +0 -7
  159. package/dist/labels/write_labels_to_specs.d.ts.map +0 -1
  160. package/dist/labels/write_labels_to_specs.js +0 -13
  161. package/dist/labels/write_labels_to_specs.js.map +0 -1
  162. package/src/labels/write_labels_to_specs.ts +0 -12
@@ -1,6 +1,7 @@
1
1
  require("../_virtual/_rolldown/runtime.cjs");
2
2
  let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
3
3
  let _milaboratories_helpers = require("@milaboratories/helpers");
4
+ let es_toolkit = require("es-toolkit");
4
5
  //#region src/labels/derive_distinct_labels.ts
5
6
  const DISTANCE_PENALTY = .001;
6
7
  const LABEL_TYPE = "__LABEL__";
@@ -8,13 +9,19 @@ const LABEL_TYPE_FULL = "__LABEL__@1";
8
9
  function deriveDistinctLabels(values, options = {}) {
9
10
  const forceTraceElements = options.forceTraceElements !== void 0 && options.forceTraceElements.length > 0 ? new Set(options.forceTraceElements) : void 0;
10
11
  const separator = options.separator ?? " / ";
12
+ const linkerSuffixes = values.map((v, i) => {
13
+ const spec = "spec" in v && typeof v.spec === "object" ? v.spec : v;
14
+ const linkerLabels = extractLinkerLabels(v);
15
+ if (linkerLabels.length === 0) return void 0;
16
+ return (0, es_toolkit.isFunction)(options.linkerLabelFormatter) ? options.linkerLabelFormatter(linkerLabels, spec, i) : `via ${linkerLabels.join(" > ")}`;
17
+ });
11
18
  const records = values.map((v) => enrichRecord(v, options));
12
19
  const stats = collectTypeStats(records);
13
20
  const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);
14
21
  const build = (typeSet, force) => buildLabels(records, typeSet, forceTraceElements, separator, force);
15
22
  if (mainTypes.length === 0) {
16
23
  if (secondaryTypes.length !== 0) throw new Error("Non-empty secondary types list while main types list is empty.");
17
- return build(new Set(LABEL_TYPE_FULL), true);
24
+ return applyLinkerSuffixes(build(new Set(LABEL_TYPE_FULL), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive labels using native column labels"), linkerSuffixes);
18
25
  }
19
26
  let includedCount = 0;
20
27
  let additionalType = -1;
@@ -24,25 +31,37 @@ function deriveDistinctLabels(values, options = {}) {
24
31
  for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);
25
32
  if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);
26
33
  const candidateResult = build(currentSet, false);
27
- if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels");
34
+ if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return applyLinkerSuffixes(build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), linkerSuffixes);
28
35
  additionalType++;
29
36
  if (additionalType >= mainTypes.length) {
30
37
  includedCount++;
31
38
  additionalType = includedCount;
32
39
  }
33
40
  }
34
- return build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels");
41
+ return applyLinkerSuffixes(build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), linkerSuffixes);
42
+ }
43
+ /** Apply pre-formatted linker suffixes to labels that have them. */
44
+ function applyLinkerSuffixes(labels, suffixes) {
45
+ return labels.map((label, i) => (0, es_toolkit.isNil)(suffixes[i]) ? label : `${label} ${suffixes[i]}`);
46
+ }
47
+ /** Extract linker labels from every step of the linkers path. */
48
+ function extractLinkerLabels(entry) {
49
+ if (!("spec" in entry) || typeof entry.spec !== "object") return [];
50
+ const path = entry.linkerPath;
51
+ if (path === void 0 || path.length === 0) return [];
52
+ const labels = [];
53
+ for (const step of path) {
54
+ const label = ((0, _milaboratories_pl_model_common.readAnnotation)(step.spec, _milaboratories_pl_model_common.Annotation.LinkLabel) ?? (0, _milaboratories_pl_model_common.readAnnotation)(step.spec, _milaboratories_pl_model_common.Annotation.Label))?.trim();
55
+ if (label !== void 0 && label.length > 0) labels.push(label);
56
+ }
57
+ return labels;
35
58
  }
36
59
  function extractSpecAndTrace(entry) {
37
- if ("spec" in entry && typeof entry.spec === "object") return {
38
- spec: entry.spec,
39
- prefixTrace: entry.prefixTrace,
40
- suffixTrace: entry.suffixTrace
41
- };
60
+ const isEnriched = "spec" in entry && typeof entry.spec === "object";
42
61
  return {
43
- spec: entry,
44
- prefixTrace: void 0,
45
- suffixTrace: void 0
62
+ spec: isEnriched ? entry.spec : entry,
63
+ extraTrace: isEnriched ? entry.extraTrace : void 0,
64
+ linkerPath: isEnriched ? entry.linkerPath : void 0
46
65
  };
47
66
  }
48
67
  function buildFullTrace(trace) {
@@ -62,14 +81,16 @@ function buildFullTrace(trace) {
62
81
  return result;
63
82
  }
64
83
  function enrichRecord(value, options) {
65
- const { spec, prefixTrace, suffixTrace } = extractSpecAndTrace(value);
84
+ const { spec, extraTrace } = extractSpecAndTrace(value);
66
85
  const label = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Label);
67
86
  const traceStr = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Trace);
68
87
  const baseTrace = traceStr ? (0, _milaboratories_pl_model_common.parseJson)(traceStr) ?? [] : [];
88
+ const prefixExtra = extraTrace?.filter((e) => e.position === "prefix") ?? [];
89
+ const suffixExtra = extraTrace?.filter((e) => e.position !== "prefix") ?? [];
69
90
  const trace = [
70
- ...prefixTrace ?? [],
91
+ ...prefixExtra,
71
92
  ...baseTrace,
72
- ...suffixTrace ?? []
93
+ ...suffixExtra
73
94
  ];
74
95
  if (label !== void 0) {
75
96
  const labelEntry = {
@@ -80,10 +101,7 @@ function enrichRecord(value, options) {
80
101
  if (options.addLabelAsSuffix) trace.push(labelEntry);
81
102
  else trace.splice(0, 0, labelEntry);
82
103
  }
83
- return {
84
- value,
85
- fullTrace: buildFullTrace(trace)
86
- };
104
+ return { fullTrace: buildFullTrace(trace) };
87
105
  }
88
106
  function collectTypeStats(records) {
89
107
  const importances = /* @__PURE__ */ new Map();
@@ -118,22 +136,16 @@ function buildLabels(records, includedTypes, forceTraceElements, separator, forc
118
136
  for (const ft of r.fullTrace) if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) parts.push(ft.label);
119
137
  if (parts.length === 0) {
120
138
  if (!force) return void 0;
121
- result.push({
122
- label: "Unlabeled",
123
- value: r.value
124
- });
139
+ result.push("Unlabeled");
125
140
  continue;
126
141
  }
127
- result.push({
128
- label: parts.join(separator),
129
- value: r.value
130
- });
142
+ result.push(parts.join(separator));
131
143
  }
132
144
  return result;
133
145
  }
134
146
  function countUniqueLabels(result) {
135
147
  if (result === void 0) return 0;
136
- return new Set(result.map((c) => c.label)).size;
148
+ return new Set(result).size;
137
149
  }
138
150
  function minimizeTypeSet(typeSet, records, stats, forceTraceElements, options, separator) {
139
151
  const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_labels.cjs","names":["Annotation"],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type CanonicalizedJson,\n type PObjectSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\n\nexport type WithLabel<T> = {\n value: T;\n label: string;\n};\n\ntype TraceEntry = {\n id?: string;\n type: string;\n label: string;\n importance?: number;\n};\n\nexport type Trace = TraceEntry[];\n\nexport type Entry =\n | PObjectSpec\n | { spec: PObjectSpec; prefixTrace?: TraceEntry[]; suffixTrace?: TraceEntry[] };\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport function deriveDistinctLabels<T extends Entry>(\n values: T[],\n options: DeriveLabelsOptions = {},\n): WithLabel<T>[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n // Phase 1: enrich each value with parsed trace\n const records = values.map((v) => enrichRecord(v, options));\n\n // Phase 2: collect global type statistics\n const stats = collectTypeStats(records);\n\n // Phase 3: classify types into main (present everywhere) and secondary\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n return build(new Set(LABEL_TYPE_FULL), true)!;\n }\n\n // Phase 4: search for minimal type set that produces unique labels\n //\n // includedCount = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>();\n if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return build(minimized, false) ?? throwError(\"Failed to derive unique labels\");\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n // Fallback: include all types, then minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return build(minimized, true) ?? throwError(\"Failed to derive unique labels\");\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord<T> = {\n value: T;\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractSpecAndTrace(entry: Entry): {\n spec: PObjectSpec;\n prefixTrace: TraceEntry[] | undefined;\n suffixTrace: TraceEntry[] | undefined;\n} {\n if (\"spec\" in entry && typeof entry.spec === \"object\") {\n return { spec: entry.spec, prefixTrace: entry.prefixTrace, suffixTrace: entry.suffixTrace };\n }\n return { spec: entry as PObjectSpec, prefixTrace: undefined, suffixTrace: undefined };\n}\n\nfunction buildFullTrace(trace: TraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord<T extends Entry>(value: T, options: DeriveLabelsOptions): EnrichedRecord<T> {\n const { spec, prefixTrace, suffixTrace } = extractSpecAndTrace(value);\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace) as\n | CanonicalizedJson<TraceEntry[]>\n | undefined;\n const baseTrace: Trace = traceStr ? (parseJson<Trace>(traceStr) ?? []) : [];\n const trace = [...(prefixTrace ?? []), ...baseTrace, ...(suffixTrace ?? [])];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n return { value, fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats<T>(records: EnrichedRecord<T>[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction buildLabels<T>(\n records: EnrichedRecord<T>[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): WithLabel<T>[] | undefined {\n const result: WithLabel<T>[] = [];\n\n for (const r of records) {\n const parts: string[] = [];\n for (const ft of r.fullTrace) {\n if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) {\n parts.push(ft.label);\n }\n }\n\n if (parts.length === 0) {\n if (!force) return undefined;\n result.push({ label: \"Unlabeled\", value: r.value });\n continue;\n }\n\n result.push({ label: parts.join(separator), value: r.value });\n }\n\n return result;\n}\n\nfunction countUniqueLabels<T>(result: WithLabel<T>[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result.map((c) => c.label)).size;\n}\n\nfunction minimizeTypeSet<T>(\n typeSet: Set<string>,\n records: EnrichedRecord<T>[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n options: DeriveLabelsOptions,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter(\n (t) =>\n !forceTraceElements?.has(t.split(\"@\")[0]) &&\n !(options.includeNativeLabel && t === LABEL_TYPE_FULL),\n )\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derive_distinct_labels.cjs","names":["Annotation"],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type PObjectSpec,\n type StringifiedJson,\n type Trace,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nexport type { Trace, TraceEntry } from \"@milaboratories/pl-model-common\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\n\n/** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */\ntype ExtendedTraceEntry = Trace[number] & {\n importance?: number;\n position?: \"prefix\" | \"suffix\";\n};\n\nexport type Entry =\n | PObjectSpec\n | {\n spec: PObjectSpec;\n /** Extra trace entries merged with the base trace from annotations. */\n extraTrace?: ExtendedTraceEntry[];\n /** Linker steps traversed to discover this column; used to append \"via $linkLabel\" to derived labels. */\n linkerPath?: { spec: PObjectSpec }[];\n };\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n /** Custom formatter for linker path suffix. Receives the array of linker labels from the full traversal chain,\n * the column spec, and the column index.\n * If returns undefined, no linker suffix is appended. By default labels are joined with \" > \" and prefixed with \"via \". */\n linkerLabelFormatter?: (\n linkerLabels: string[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n};\n\nexport function deriveDistinctLabels(values: Entry[], options: DeriveLabelsOptions = {}): string[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n // Collect per-entry linker suffixes before disambiguation\n const linkerSuffixes = values.map((v, i) => {\n const spec = \"spec\" in v && typeof v.spec === \"object\" ? v.spec : (v as PObjectSpec);\n const linkerLabels = extractLinkerLabels(v);\n if (linkerLabels.length === 0) return undefined;\n return isFunction(options.linkerLabelFormatter)\n ? options.linkerLabelFormatter(linkerLabels, spec, i)\n : `via ${linkerLabels.join(\" > \")}`;\n });\n\n // Phase 1: enrich each value with parsed trace\n const records = values.map((v) => enrichRecord(v, options));\n\n // Phase 2: collect global type statistics\n const stats = collectTypeStats(records);\n\n // Phase 3: classify types into main (present everywhere) and secondary\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n\n return applyLinkerSuffixes(\n build(new Set(LABEL_TYPE_FULL), true) ??\n throwError(\"Failed to derive labels using native column labels\"),\n linkerSuffixes,\n );\n }\n\n // Phase 4: search for minimal type set that produces unique labels\n //\n // includedCount = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>();\n if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, false) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n // Fallback: include all types, then minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, true) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n}\n\n/** Apply pre-formatted linker suffixes to labels that have them. */\nfunction applyLinkerSuffixes(labels: string[], suffixes: (string | undefined)[]): string[] {\n return labels.map((label, i) => (isNil(suffixes[i]) ? label : `${label} ${suffixes[i]}`));\n}\n\n/** Extract linker labels from every step of the linkers path. */\nfunction extractLinkerLabels(entry: Entry): string[] {\n if (!(\"spec\" in entry) || typeof entry.spec !== \"object\") return [];\n const path = entry.linkerPath;\n if (path === undefined || path.length === 0) return [];\n const labels: string[] = [];\n for (const step of path) {\n const label = (\n readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label)\n )?.trim();\n if (label !== undefined && label.length > 0) {\n labels.push(label);\n }\n }\n return labels;\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = ExtendedTraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord = {\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractSpecAndTrace(entry: Entry): {\n spec: PObjectSpec;\n extraTrace: ExtendedTraceEntry[] | undefined;\n linkerPath: { spec: PObjectSpec }[] | undefined;\n} {\n const isEnriched = \"spec\" in entry && typeof entry.spec === \"object\";\n return {\n spec: isEnriched ? entry.spec : (entry as PObjectSpec),\n extraTrace: isEnriched ? entry.extraTrace : undefined,\n linkerPath: isEnriched ? entry.linkerPath : undefined,\n };\n}\n\nfunction buildFullTrace(trace: ExtendedTraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord(value: Entry, options: DeriveLabelsOptions): EnrichedRecord {\n const { spec, extraTrace } = extractSpecAndTrace(value);\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = traceStr\n ? (parseJson(traceStr as StringifiedJson<ExtendedTraceEntry[]>) ?? [])\n : [];\n const prefixExtra = extraTrace?.filter((e) => e.position === \"prefix\") ?? [];\n const suffixExtra = extraTrace?.filter((e) => e.position !== \"prefix\") ?? [];\n const trace = [...prefixExtra, ...baseTrace, ...suffixExtra];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n return { fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats(records: EnrichedRecord[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction buildLabels(\n records: EnrichedRecord[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): string[] | undefined {\n const result: string[] = [];\n\n for (const r of records) {\n const parts: string[] = [];\n for (const ft of r.fullTrace) {\n if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) {\n parts.push(ft.label);\n }\n }\n\n if (parts.length === 0) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\n\n result.push(parts.join(separator));\n }\n\n return result;\n}\n\nfunction countUniqueLabels(result: string[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result).size;\n}\n\nfunction minimizeTypeSet(\n typeSet: Set<string>,\n records: EnrichedRecord[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n options: DeriveLabelsOptions,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter(\n (t) =>\n !forceTraceElements?.has(t.split(\"@\")[0]) &&\n !(options.includeNativeLabel && t === LABEL_TYPE_FULL),\n )\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
@@ -1,29 +1,29 @@
1
- import { PObjectSpec } from "@milaboratories/pl-model-common";
1
+ import { PObjectSpec, Trace, Trace as Trace$1, TraceEntry } from "@milaboratories/pl-model-common";
2
2
 
3
3
  //#region src/labels/derive_distinct_labels.d.ts
4
- type WithLabel<T> = {
5
- value: T;
6
- label: string;
7
- };
8
- type TraceEntry = {
9
- id?: string;
10
- type: string;
11
- label: string;
4
+ /** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */
5
+ type ExtendedTraceEntry = Trace[number] & {
12
6
  importance?: number;
7
+ position?: "prefix" | "suffix";
13
8
  };
14
- type Trace = TraceEntry[];
15
9
  type Entry = PObjectSpec | {
16
- spec: PObjectSpec;
17
- prefixTrace?: TraceEntry[];
18
- suffixTrace?: TraceEntry[];
10
+ spec: PObjectSpec; /** Extra trace entries merged with the base trace from annotations. */
11
+ extraTrace?: ExtendedTraceEntry[]; /** Linker steps traversed to discover this column; used to append "via $linkLabel" to derived labels. */
12
+ linkerPath?: {
13
+ spec: PObjectSpec;
14
+ }[];
19
15
  };
20
16
  type DeriveLabelsOptions = {
21
17
  /** Separator to use between label parts (" / " by default) */separator?: string; /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */
22
18
  addLabelAsSuffix?: boolean; /** Force inclusion of native column label */
23
19
  includeNativeLabel?: boolean; /** Trace elements list that will be forced to be included in the label. */
24
20
  forceTraceElements?: string[];
21
+ /** Custom formatter for linker path suffix. Receives the array of linker labels from the full traversal chain,
22
+ * the column spec, and the column index.
23
+ * If returns undefined, no linker suffix is appended. By default labels are joined with " > " and prefixed with "via ". */
24
+ linkerLabelFormatter?: (linkerLabels: string[], spec: PObjectSpec, index: number) => string | undefined;
25
25
  };
26
- declare function deriveDistinctLabels<T extends Entry>(values: T[], options?: DeriveLabelsOptions): WithLabel<T>[];
26
+ declare function deriveDistinctLabels(values: Entry[], options?: DeriveLabelsOptions): string[];
27
27
  //#endregion
28
- export { DeriveLabelsOptions, Entry, Trace, WithLabel, deriveDistinctLabels };
28
+ export { DeriveLabelsOptions, Entry, type Trace$1 as Trace, type TraceEntry, deriveDistinctLabels };
29
29
  //# sourceMappingURL=derive_distinct_labels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":";;;KAaY,SAAA;EACV,KAAA,EAAO,CAAA;EACP,KAAA;AAAA;AAAA,KAGG,UAAA;EACH,EAAA;EACA,IAAA;EACA,KAAA;EACA,UAAA;AAAA;AAAA,KAGU,KAAA,GAAQ,UAAA;AAAA,KAER,KAAA,GACR,WAAA;EACE,IAAA,EAAM,WAAA;EAAa,WAAA,GAAc,UAAA;EAAc,WAAA,GAAc,UAAA;AAAA;AAAA,KAEvD,mBAAA;EAXV,8DAaA,SAAA,WAXA;EAaA,gBAAA,YAbU;EAeV,kBAAA,YAZe;EAcf,kBAAA;AAAA;AAAA,iBAGc,oBAAA,WAA+B,KAAA,CAAA,CAC7C,MAAA,EAAQ,CAAA,IACR,OAAA,GAAS,mBAAA,GACR,SAAA,CAAU,CAAA"}
1
+ {"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":";;;;KAkBK,kBAAA,GAAqB,KAAA;EACxB,UAAA;EACA,QAAA;AAAA;AAAA,KAGU,KAAA,GACR,WAAA;EAEE,IAAA,EAAM,WAAA,EANV;EAQI,UAAA,GAAa,kBAAA,IART;EAUJ,UAAA;IAAe,IAAA,EAAM,WAAA;EAAA;AAAA;AAAA,KAGf,mBAAA;EALO,8DAOjB,SAAA,WALoC;EAOpC,gBAAA,YAbE;EAeF,kBAAA,YAbU;EAeV,kBAAA;EAbiB;;;EAiBjB,oBAAA,IACE,YAAA,YACA,IAAA,EAAM,WAAA,EACN,KAAA;AAAA;AAAA,iBAIY,oBAAA,CAAqB,MAAA,EAAQ,KAAA,IAAS,OAAA,GAAS,mBAAA"}
@@ -1,5 +1,6 @@
1
1
  import { Annotation, parseJson, readAnnotation } from "@milaboratories/pl-model-common";
2
2
  import { throwError } from "@milaboratories/helpers";
3
+ import { isFunction, isNil as isNil$1 } from "es-toolkit";
3
4
  //#region src/labels/derive_distinct_labels.ts
4
5
  const DISTANCE_PENALTY = .001;
5
6
  const LABEL_TYPE = "__LABEL__";
@@ -7,13 +8,19 @@ const LABEL_TYPE_FULL = "__LABEL__@1";
7
8
  function deriveDistinctLabels(values, options = {}) {
8
9
  const forceTraceElements = options.forceTraceElements !== void 0 && options.forceTraceElements.length > 0 ? new Set(options.forceTraceElements) : void 0;
9
10
  const separator = options.separator ?? " / ";
11
+ const linkerSuffixes = values.map((v, i) => {
12
+ const spec = "spec" in v && typeof v.spec === "object" ? v.spec : v;
13
+ const linkerLabels = extractLinkerLabels(v);
14
+ if (linkerLabels.length === 0) return void 0;
15
+ return isFunction(options.linkerLabelFormatter) ? options.linkerLabelFormatter(linkerLabels, spec, i) : `via ${linkerLabels.join(" > ")}`;
16
+ });
10
17
  const records = values.map((v) => enrichRecord(v, options));
11
18
  const stats = collectTypeStats(records);
12
19
  const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);
13
20
  const build = (typeSet, force) => buildLabels(records, typeSet, forceTraceElements, separator, force);
14
21
  if (mainTypes.length === 0) {
15
22
  if (secondaryTypes.length !== 0) throw new Error("Non-empty secondary types list while main types list is empty.");
16
- return build(new Set(LABEL_TYPE_FULL), true);
23
+ return applyLinkerSuffixes(build(new Set(LABEL_TYPE_FULL), true) ?? throwError("Failed to derive labels using native column labels"), linkerSuffixes);
17
24
  }
18
25
  let includedCount = 0;
19
26
  let additionalType = -1;
@@ -23,25 +30,37 @@ function deriveDistinctLabels(values, options = {}) {
23
30
  for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);
24
31
  if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);
25
32
  const candidateResult = build(currentSet, false);
26
- if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? throwError("Failed to derive unique labels");
33
+ if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return applyLinkerSuffixes(build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? throwError("Failed to derive unique labels"), linkerSuffixes);
27
34
  additionalType++;
28
35
  if (additionalType >= mainTypes.length) {
29
36
  includedCount++;
30
37
  additionalType = includedCount;
31
38
  }
32
39
  }
33
- return build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? throwError("Failed to derive unique labels");
40
+ return applyLinkerSuffixes(build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? throwError("Failed to derive unique labels"), linkerSuffixes);
41
+ }
42
+ /** Apply pre-formatted linker suffixes to labels that have them. */
43
+ function applyLinkerSuffixes(labels, suffixes) {
44
+ return labels.map((label, i) => isNil$1(suffixes[i]) ? label : `${label} ${suffixes[i]}`);
45
+ }
46
+ /** Extract linker labels from every step of the linkers path. */
47
+ function extractLinkerLabels(entry) {
48
+ if (!("spec" in entry) || typeof entry.spec !== "object") return [];
49
+ const path = entry.linkerPath;
50
+ if (path === void 0 || path.length === 0) return [];
51
+ const labels = [];
52
+ for (const step of path) {
53
+ const label = (readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label))?.trim();
54
+ if (label !== void 0 && label.length > 0) labels.push(label);
55
+ }
56
+ return labels;
34
57
  }
35
58
  function extractSpecAndTrace(entry) {
36
- if ("spec" in entry && typeof entry.spec === "object") return {
37
- spec: entry.spec,
38
- prefixTrace: entry.prefixTrace,
39
- suffixTrace: entry.suffixTrace
40
- };
59
+ const isEnriched = "spec" in entry && typeof entry.spec === "object";
41
60
  return {
42
- spec: entry,
43
- prefixTrace: void 0,
44
- suffixTrace: void 0
61
+ spec: isEnriched ? entry.spec : entry,
62
+ extraTrace: isEnriched ? entry.extraTrace : void 0,
63
+ linkerPath: isEnriched ? entry.linkerPath : void 0
45
64
  };
46
65
  }
47
66
  function buildFullTrace(trace) {
@@ -61,14 +80,16 @@ function buildFullTrace(trace) {
61
80
  return result;
62
81
  }
63
82
  function enrichRecord(value, options) {
64
- const { spec, prefixTrace, suffixTrace } = extractSpecAndTrace(value);
83
+ const { spec, extraTrace } = extractSpecAndTrace(value);
65
84
  const label = readAnnotation(spec, Annotation.Label);
66
85
  const traceStr = readAnnotation(spec, Annotation.Trace);
67
86
  const baseTrace = traceStr ? parseJson(traceStr) ?? [] : [];
87
+ const prefixExtra = extraTrace?.filter((e) => e.position === "prefix") ?? [];
88
+ const suffixExtra = extraTrace?.filter((e) => e.position !== "prefix") ?? [];
68
89
  const trace = [
69
- ...prefixTrace ?? [],
90
+ ...prefixExtra,
70
91
  ...baseTrace,
71
- ...suffixTrace ?? []
92
+ ...suffixExtra
72
93
  ];
73
94
  if (label !== void 0) {
74
95
  const labelEntry = {
@@ -79,10 +100,7 @@ function enrichRecord(value, options) {
79
100
  if (options.addLabelAsSuffix) trace.push(labelEntry);
80
101
  else trace.splice(0, 0, labelEntry);
81
102
  }
82
- return {
83
- value,
84
- fullTrace: buildFullTrace(trace)
85
- };
103
+ return { fullTrace: buildFullTrace(trace) };
86
104
  }
87
105
  function collectTypeStats(records) {
88
106
  const importances = /* @__PURE__ */ new Map();
@@ -117,22 +135,16 @@ function buildLabels(records, includedTypes, forceTraceElements, separator, forc
117
135
  for (const ft of r.fullTrace) if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) parts.push(ft.label);
118
136
  if (parts.length === 0) {
119
137
  if (!force) return void 0;
120
- result.push({
121
- label: "Unlabeled",
122
- value: r.value
123
- });
138
+ result.push("Unlabeled");
124
139
  continue;
125
140
  }
126
- result.push({
127
- label: parts.join(separator),
128
- value: r.value
129
- });
141
+ result.push(parts.join(separator));
130
142
  }
131
143
  return result;
132
144
  }
133
145
  function countUniqueLabels(result) {
134
146
  if (result === void 0) return 0;
135
- return new Set(result.map((c) => c.label)).size;
147
+ return new Set(result).size;
136
148
  }
137
149
  function minimizeTypeSet(typeSet, records, stats, forceTraceElements, options, separator) {
138
150
  const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_labels.js","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type CanonicalizedJson,\n type PObjectSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\n\nexport type WithLabel<T> = {\n value: T;\n label: string;\n};\n\ntype TraceEntry = {\n id?: string;\n type: string;\n label: string;\n importance?: number;\n};\n\nexport type Trace = TraceEntry[];\n\nexport type Entry =\n | PObjectSpec\n | { spec: PObjectSpec; prefixTrace?: TraceEntry[]; suffixTrace?: TraceEntry[] };\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport function deriveDistinctLabels<T extends Entry>(\n values: T[],\n options: DeriveLabelsOptions = {},\n): WithLabel<T>[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n // Phase 1: enrich each value with parsed trace\n const records = values.map((v) => enrichRecord(v, options));\n\n // Phase 2: collect global type statistics\n const stats = collectTypeStats(records);\n\n // Phase 3: classify types into main (present everywhere) and secondary\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n return build(new Set(LABEL_TYPE_FULL), true)!;\n }\n\n // Phase 4: search for minimal type set that produces unique labels\n //\n // includedCount = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>();\n if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return build(minimized, false) ?? throwError(\"Failed to derive unique labels\");\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n // Fallback: include all types, then minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return build(minimized, true) ?? throwError(\"Failed to derive unique labels\");\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord<T> = {\n value: T;\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractSpecAndTrace(entry: Entry): {\n spec: PObjectSpec;\n prefixTrace: TraceEntry[] | undefined;\n suffixTrace: TraceEntry[] | undefined;\n} {\n if (\"spec\" in entry && typeof entry.spec === \"object\") {\n return { spec: entry.spec, prefixTrace: entry.prefixTrace, suffixTrace: entry.suffixTrace };\n }\n return { spec: entry as PObjectSpec, prefixTrace: undefined, suffixTrace: undefined };\n}\n\nfunction buildFullTrace(trace: TraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord<T extends Entry>(value: T, options: DeriveLabelsOptions): EnrichedRecord<T> {\n const { spec, prefixTrace, suffixTrace } = extractSpecAndTrace(value);\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace) as\n | CanonicalizedJson<TraceEntry[]>\n | undefined;\n const baseTrace: Trace = traceStr ? (parseJson<Trace>(traceStr) ?? []) : [];\n const trace = [...(prefixTrace ?? []), ...baseTrace, ...(suffixTrace ?? [])];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n return { value, fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats<T>(records: EnrichedRecord<T>[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction buildLabels<T>(\n records: EnrichedRecord<T>[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): WithLabel<T>[] | undefined {\n const result: WithLabel<T>[] = [];\n\n for (const r of records) {\n const parts: string[] = [];\n for (const ft of r.fullTrace) {\n if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) {\n parts.push(ft.label);\n }\n }\n\n if (parts.length === 0) {\n if (!force) return undefined;\n result.push({ label: \"Unlabeled\", value: r.value });\n continue;\n }\n\n result.push({ label: parts.join(separator), value: r.value });\n }\n\n return result;\n}\n\nfunction countUniqueLabels<T>(result: WithLabel<T>[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result.map((c) => c.label)).size;\n}\n\nfunction minimizeTypeSet<T>(\n typeSet: Set<string>,\n records: EnrichedRecord<T>[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n options: DeriveLabelsOptions,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter(\n (t) =>\n !forceTraceElements?.has(t.split(\"@\")[0]) &&\n !(options.includeNativeLabel && t === LABEL_TYPE_FULL),\n )\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derive_distinct_labels.js","names":["isNil"],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type PObjectSpec,\n type StringifiedJson,\n type Trace,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nexport type { Trace, TraceEntry } from \"@milaboratories/pl-model-common\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\n\n/** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */\ntype ExtendedTraceEntry = Trace[number] & {\n importance?: number;\n position?: \"prefix\" | \"suffix\";\n};\n\nexport type Entry =\n | PObjectSpec\n | {\n spec: PObjectSpec;\n /** Extra trace entries merged with the base trace from annotations. */\n extraTrace?: ExtendedTraceEntry[];\n /** Linker steps traversed to discover this column; used to append \"via $linkLabel\" to derived labels. */\n linkerPath?: { spec: PObjectSpec }[];\n };\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n /** Custom formatter for linker path suffix. Receives the array of linker labels from the full traversal chain,\n * the column spec, and the column index.\n * If returns undefined, no linker suffix is appended. By default labels are joined with \" > \" and prefixed with \"via \". */\n linkerLabelFormatter?: (\n linkerLabels: string[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n};\n\nexport function deriveDistinctLabels(values: Entry[], options: DeriveLabelsOptions = {}): string[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n // Collect per-entry linker suffixes before disambiguation\n const linkerSuffixes = values.map((v, i) => {\n const spec = \"spec\" in v && typeof v.spec === \"object\" ? v.spec : (v as PObjectSpec);\n const linkerLabels = extractLinkerLabels(v);\n if (linkerLabels.length === 0) return undefined;\n return isFunction(options.linkerLabelFormatter)\n ? options.linkerLabelFormatter(linkerLabels, spec, i)\n : `via ${linkerLabels.join(\" > \")}`;\n });\n\n // Phase 1: enrich each value with parsed trace\n const records = values.map((v) => enrichRecord(v, options));\n\n // Phase 2: collect global type statistics\n const stats = collectTypeStats(records);\n\n // Phase 3: classify types into main (present everywhere) and secondary\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n\n return applyLinkerSuffixes(\n build(new Set(LABEL_TYPE_FULL), true) ??\n throwError(\"Failed to derive labels using native column labels\"),\n linkerSuffixes,\n );\n }\n\n // Phase 4: search for minimal type set that produces unique labels\n //\n // includedCount = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>();\n if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, false) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n // Fallback: include all types, then minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, true) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n}\n\n/** Apply pre-formatted linker suffixes to labels that have them. */\nfunction applyLinkerSuffixes(labels: string[], suffixes: (string | undefined)[]): string[] {\n return labels.map((label, i) => (isNil(suffixes[i]) ? label : `${label} ${suffixes[i]}`));\n}\n\n/** Extract linker labels from every step of the linkers path. */\nfunction extractLinkerLabels(entry: Entry): string[] {\n if (!(\"spec\" in entry) || typeof entry.spec !== \"object\") return [];\n const path = entry.linkerPath;\n if (path === undefined || path.length === 0) return [];\n const labels: string[] = [];\n for (const step of path) {\n const label = (\n readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label)\n )?.trim();\n if (label !== undefined && label.length > 0) {\n labels.push(label);\n }\n }\n return labels;\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = ExtendedTraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord = {\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractSpecAndTrace(entry: Entry): {\n spec: PObjectSpec;\n extraTrace: ExtendedTraceEntry[] | undefined;\n linkerPath: { spec: PObjectSpec }[] | undefined;\n} {\n const isEnriched = \"spec\" in entry && typeof entry.spec === \"object\";\n return {\n spec: isEnriched ? entry.spec : (entry as PObjectSpec),\n extraTrace: isEnriched ? entry.extraTrace : undefined,\n linkerPath: isEnriched ? entry.linkerPath : undefined,\n };\n}\n\nfunction buildFullTrace(trace: ExtendedTraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord(value: Entry, options: DeriveLabelsOptions): EnrichedRecord {\n const { spec, extraTrace } = extractSpecAndTrace(value);\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = traceStr\n ? (parseJson(traceStr as StringifiedJson<ExtendedTraceEntry[]>) ?? [])\n : [];\n const prefixExtra = extraTrace?.filter((e) => e.position === \"prefix\") ?? [];\n const suffixExtra = extraTrace?.filter((e) => e.position !== \"prefix\") ?? [];\n const trace = [...prefixExtra, ...baseTrace, ...suffixExtra];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n return { fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats(records: EnrichedRecord[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction buildLabels(\n records: EnrichedRecord[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): string[] | undefined {\n const result: string[] = [];\n\n for (const r of records) {\n const parts: string[] = [];\n for (const ft of r.fullTrace) {\n if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) {\n parts.push(ft.label);\n }\n }\n\n if (parts.length === 0) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\n\n result.push(parts.join(separator));\n }\n\n return result;\n}\n\nfunction countUniqueLabels(result: string[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result).size;\n}\n\nfunction minimizeTypeSet(\n typeSet: Set<string>,\n records: EnrichedRecord[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n options: DeriveLabelsOptions,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter(\n (t) =>\n !forceTraceElements?.has(t.split(\"@\")[0]) &&\n !(options.includeNativeLabel && t === LABEL_TYPE_FULL),\n )\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
@@ -1,2 +1 @@
1
1
  require("./derive_distinct_labels.cjs");
2
- require("./write_labels_to_specs.cjs");
@@ -1,2 +1 @@
1
- import { DeriveLabelsOptions, Entry, Trace, WithLabel, deriveDistinctLabels } from "./derive_distinct_labels.js";
2
- import { writeLabelsToSpecs } from "./write_labels_to_specs.js";
1
+ import { DeriveLabelsOptions, Entry, Trace, TraceEntry, deriveDistinctLabels } from "./derive_distinct_labels.js";
@@ -1,2 +1 @@
1
1
  import "./derive_distinct_labels.js";
2
- import "./write_labels_to_specs.js";
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.63.12";
2
+ var version = "1.64.0";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.63.12";
2
+ var version = "1.64.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -379,8 +379,8 @@ var RenderCtxBase = class {
379
379
  return def;
380
380
  }
381
381
  createPFrame(def) {
382
- this.verifyInlineAndExplicitColumnsSupport(def);
383
382
  if (!require_pcolumn_data.allPColumnsReady(def)) return void 0;
383
+ this.verifyInlineAndExplicitColumnsSupport(def);
384
384
  return this.ctx.createPFrame(def.map((c) => transformPColumnData(c)));
385
385
  }
386
386
  createPTable(def) {
@@ -405,9 +405,16 @@ var RenderCtxBase = class {
405
405
  }
406
406
  createPTableV2(def) {
407
407
  const columns = (0, _milaboratories_pl_model_common.collectSpecQueryColumns)(def.query);
408
- this.verifyInlineAndExplicitColumnsSupport(columns);
409
408
  if (!require_pcolumn_data.allPColumnsReady(columns)) return void 0;
410
- return this.ctx.createPTableV2((0, _milaboratories_pl_model_common.mapPTableDefV2)(def, (po) => transformPColumnData(po)));
409
+ this.verifyInlineAndExplicitColumnsSupport(columns);
410
+ return this.ctx.createPTableV2((0, _milaboratories_pl_model_common.mapPTableDefV2)(def, (po) => {
411
+ if (po.data === void 0) throw new Error("unreachable: column data undefined after readiness check");
412
+ return transformPColumnData({
413
+ id: po.id,
414
+ spec: po.spec,
415
+ data: po.data
416
+ });
417
+ }));
411
418
  }
412
419
  /** @deprecated scheduled for removal from SDK */
413
420
  getBlockLabel(blockId) {