@platforma-sdk/model 1.54.10 → 1.54.13

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 (34) hide show
  1. package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
  2. package/dist/components/PlDataTable/state-migration.js.map +1 -1
  3. package/dist/components/PlDataTable/table.cjs +11 -2
  4. package/dist/components/PlDataTable/table.cjs.map +1 -1
  5. package/dist/components/PlDataTable/table.d.ts.map +1 -1
  6. package/dist/components/PlDataTable/table.js +12 -3
  7. package/dist/components/PlDataTable/table.js.map +1 -1
  8. package/dist/components/PlDataTable/v5.d.ts +7 -4
  9. package/dist/components/PlDataTable/v5.d.ts.map +1 -1
  10. package/dist/filters/converters/filterToQuery.cjs +3 -4
  11. package/dist/filters/converters/filterToQuery.cjs.map +1 -1
  12. package/dist/filters/converters/filterToQuery.d.ts +1 -1
  13. package/dist/filters/converters/filterToQuery.d.ts.map +1 -1
  14. package/dist/filters/converters/filterToQuery.js +3 -4
  15. package/dist/filters/converters/filterToQuery.js.map +1 -1
  16. package/dist/filters/distill.cjs.map +1 -1
  17. package/dist/filters/distill.d.ts +3 -2
  18. package/dist/filters/distill.d.ts.map +1 -1
  19. package/dist/filters/distill.js.map +1 -1
  20. package/dist/filters/traverse.cjs +7 -3
  21. package/dist/filters/traverse.cjs.map +1 -1
  22. package/dist/filters/traverse.d.ts +14 -12
  23. package/dist/filters/traverse.d.ts.map +1 -1
  24. package/dist/filters/traverse.js +7 -3
  25. package/dist/filters/traverse.js.map +1 -1
  26. package/dist/package.json.cjs +1 -1
  27. package/dist/package.json.js +1 -1
  28. package/package.json +6 -6
  29. package/src/components/PlDataTable/state-migration.ts +4 -4
  30. package/src/components/PlDataTable/table.ts +16 -3
  31. package/src/components/PlDataTable/v5.ts +9 -5
  32. package/src/filters/converters/filterToQuery.ts +8 -7
  33. package/src/filters/distill.ts +19 -11
  34. package/src/filters/traverse.ts +44 -24
@@ -1 +1 @@
1
- {"version":3,"file":"filterToQuery.js","sources":["../../../src/filters/converters/filterToQuery.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/pl-model-common\";\nimport type {\n FilterSpec,\n FilterSpecLeaf,\n PTableColumnId,\n SingleAxisSelector,\n SpecQueryExpression,\n} from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"../traverse\";\n\n/** Parses a CanonicalizedJson<PTableColumnId> string into a SpecQueryExpression reference. */\nfunction resolveColumnRef(columnStr: string): SpecQueryExpression {\n const parsed = JSON.parse(columnStr) as PTableColumnId;\n if (parsed.type === \"axis\") {\n return { type: \"axisRef\", value: parsed.id as SingleAxisSelector };\n }\n return { type: \"columnRef\", value: parsed.id };\n}\n\n/** Converts a FilterSpec tree into a SpecQueryExpression. */\nexport function filterSpecToSpecQueryExpr(\n filter: FilterSpec<FilterSpecLeaf<string>>,\n): SpecQueryExpression {\n return traverseFilterSpec(filter, {\n leaf: leafToSpecQueryExpr,\n and: (inputs) => {\n if (inputs.length === 0) {\n throw new Error(\"AND filter requires at least one operand\");\n }\n return { type: \"and\", input: inputs };\n },\n or: (inputs) => {\n if (inputs.length === 0) {\n throw new Error(\"OR filter requires at least one operand\");\n }\n return { type: \"or\", input: inputs };\n },\n not: (input) => ({ type: \"not\", input }),\n });\n}\n\nfunction leafToSpecQueryExpr(filter: FilterSpecLeaf<string>): SpecQueryExpression {\n switch (filter.type) {\n case \"patternEquals\":\n return {\n type: \"stringEquals\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n };\n case \"patternNotEquals\":\n return {\n type: \"not\",\n input: {\n type: \"stringEquals\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n },\n };\n case \"patternContainSubsequence\":\n return {\n type: \"stringContains\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n };\n case \"patternNotContainSubsequence\":\n return {\n type: \"not\",\n input: {\n type: \"stringContains\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n },\n };\n case \"patternMatchesRegularExpression\":\n return {\n type: \"stringRegex\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n };\n case \"patternFuzzyContainSubsequence\":\n return {\n type: \"stringContainsFuzzy\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n maxEdits: filter.maxEdits ?? 1,\n caseInsensitive: false,\n substitutionsOnly: filter.substitutionsOnly ?? false,\n wildcard: filter.wildcard ?? null,\n };\n\n case \"equal\":\n return {\n type: \"numericComparison\",\n operand: \"eq\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"notEqual\":\n return {\n type: \"numericComparison\",\n operand: \"ne\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"lessThan\":\n return {\n type: \"numericComparison\",\n operand: \"lt\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"greaterThan\":\n return {\n type: \"numericComparison\",\n operand: \"gt\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"lessThanOrEqual\":\n return {\n type: \"numericComparison\",\n operand: \"le\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"greaterThanOrEqual\":\n return {\n type: \"numericComparison\",\n operand: \"ge\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n\n case \"equalToColumn\":\n return {\n type: \"numericComparison\",\n operand: \"eq\",\n left: resolveColumnRef(filter.column),\n right: resolveColumnRef(filter.rhs),\n };\n case \"lessThanColumn\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"lt\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"lt\", left, right };\n }\n case \"greaterThanColumn\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"gt\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"gt\", left, right };\n }\n case \"lessThanColumnOrEqual\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"le\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"le\", left, right };\n }\n case \"greaterThanColumnOrEqual\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"ge\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"ge\", left, right };\n }\n\n case \"inSet\":\n return {\n type: \"isIn\",\n input: resolveColumnRef(filter.column),\n set: filter.value,\n };\n case \"notInSet\":\n return {\n type: \"not\",\n input: {\n type: \"isIn\",\n input: resolveColumnRef(filter.column),\n set: filter.value,\n },\n };\n\n case \"isNA\":\n return {\n type: \"isNull\",\n input: resolveColumnRef(filter.column),\n };\n case \"isNotNA\":\n return {\n type: \"not\",\n input: {\n type: \"isNull\",\n input: resolveColumnRef(filter.column),\n },\n };\n\n case \"ifNa\":\n return {\n type: \"ifNull\",\n input: resolveColumnRef(filter.column),\n replacement: { type: \"constant\", value: filter.replacement },\n };\n\n case \"topN\":\n case \"bottomN\":\n throw new Error(`Filter type \"${filter.type}\" is not supported in query expressions`);\n\n case undefined:\n throw new Error(\"Filter type is undefined\");\n\n default:\n assertNever(filter);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filterToQuery.js","sources":["../../../src/filters/converters/filterToQuery.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/pl-model-common\";\nimport type {\n FilterSpec,\n FilterSpecLeaf,\n PTableColumnId,\n SingleAxisSelector,\n SpecQueryExpression,\n} from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"../traverse\";\n\n/** Parses a CanonicalizedJson<PTableColumnId> string into a SpecQueryExpression reference. */\nfunction resolveColumnRef(columnStr: string): SpecQueryExpression {\n const parsed = JSON.parse(columnStr) as PTableColumnId;\n return parsed.type === \"axis\"\n ? { type: \"axisRef\", value: parsed.id as SingleAxisSelector }\n : { type: \"columnRef\", value: parsed.id };\n}\n\n/** Converts a FilterSpec tree into a SpecQueryExpression. */\nexport function filterSpecToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(\n filter: FilterSpec<Leaf>,\n): SpecQueryExpression {\n return traverseFilterSpec(filter, {\n leaf: leafToSpecQueryExpr,\n and: (inputs) => {\n if (inputs.length === 0) {\n throw new Error(\"AND filter requires at least one operand\");\n }\n return { type: \"and\", input: inputs };\n },\n or: (inputs) => {\n if (inputs.length === 0) {\n throw new Error(\"OR filter requires at least one operand\");\n }\n return { type: \"or\", input: inputs };\n },\n not: (input) => ({ type: \"not\", input }),\n });\n}\n\nfunction leafToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(\n filter: Leaf,\n): SpecQueryExpression {\n switch (filter.type) {\n case \"patternEquals\":\n return {\n type: \"stringEquals\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n };\n case \"patternNotEquals\":\n return {\n type: \"not\",\n input: {\n type: \"stringEquals\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n },\n };\n case \"patternContainSubsequence\":\n return {\n type: \"stringContains\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n };\n case \"patternNotContainSubsequence\":\n return {\n type: \"not\",\n input: {\n type: \"stringContains\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n caseInsensitive: false,\n },\n };\n case \"patternMatchesRegularExpression\":\n return {\n type: \"stringRegex\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n };\n case \"patternFuzzyContainSubsequence\":\n return {\n type: \"stringContainsFuzzy\",\n input: resolveColumnRef(filter.column),\n value: filter.value,\n maxEdits: filter.maxEdits ?? 1,\n caseInsensitive: false,\n substitutionsOnly: filter.substitutionsOnly ?? false,\n wildcard: filter.wildcard ?? null,\n };\n\n case \"equal\":\n return {\n type: \"numericComparison\",\n operand: \"eq\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"notEqual\":\n return {\n type: \"numericComparison\",\n operand: \"ne\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"lessThan\":\n return {\n type: \"numericComparison\",\n operand: \"lt\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"greaterThan\":\n return {\n type: \"numericComparison\",\n operand: \"gt\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"lessThanOrEqual\":\n return {\n type: \"numericComparison\",\n operand: \"le\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n case \"greaterThanOrEqual\":\n return {\n type: \"numericComparison\",\n operand: \"ge\",\n left: resolveColumnRef(filter.column),\n right: { type: \"constant\", value: filter.x },\n };\n\n case \"equalToColumn\":\n return {\n type: \"numericComparison\",\n operand: \"eq\",\n left: resolveColumnRef(filter.column),\n right: resolveColumnRef(filter.rhs),\n };\n case \"lessThanColumn\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"lt\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"lt\", left, right };\n }\n case \"greaterThanColumn\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"gt\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"gt\", left, right };\n }\n case \"lessThanColumnOrEqual\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"le\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"le\", left, right };\n }\n case \"greaterThanColumnOrEqual\": {\n const left = resolveColumnRef(filter.column);\n const right = resolveColumnRef(filter.rhs);\n if (filter.minDiff !== undefined && filter.minDiff !== 0) {\n return {\n type: \"numericComparison\",\n operand: \"ge\",\n left: {\n type: \"numericBinary\",\n operand: \"add\",\n left,\n right: { type: \"constant\", value: filter.minDiff },\n },\n right,\n };\n }\n return { type: \"numericComparison\", operand: \"ge\", left, right };\n }\n\n case \"inSet\":\n return {\n type: \"isIn\",\n input: resolveColumnRef(filter.column),\n set: filter.value,\n };\n case \"notInSet\":\n return {\n type: \"not\",\n input: {\n type: \"isIn\",\n input: resolveColumnRef(filter.column),\n set: filter.value,\n },\n };\n\n case \"isNA\":\n return {\n type: \"isNull\",\n input: resolveColumnRef(filter.column),\n };\n case \"isNotNA\":\n return {\n type: \"not\",\n input: {\n type: \"isNull\",\n input: resolveColumnRef(filter.column),\n },\n };\n\n case \"ifNa\":\n return {\n type: \"ifNull\",\n input: resolveColumnRef(filter.column),\n replacement: { type: \"constant\", value: filter.replacement },\n };\n\n case \"topN\":\n case \"bottomN\":\n throw new Error(`Filter type \"${filter.type}\" is not supported in query expressions`);\n\n case undefined:\n throw new Error(\"Filter type is undefined\");\n\n default:\n assertNever(filter);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"distill.cjs","sources":["../../src/filters/distill.ts"],"sourcesContent":["import { DistributiveKeys, UnionToTuples } from \"@milaboratories/helpers\";\nimport { type FilterSpec, type FilterSpecLeaf } from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"./traverse\";\n\n/** All possible field names that can appear in any FilterSpecLeaf variant. */\ntype FilterSpecLeafKey = DistributiveKeys<FilterSpecLeaf<string>>;\n\n/** Compile-time check: every key in the tuple is a valid leaf key (via satisfies). */\nconst KNOWN_LEAF_KEYS_TUPLE: UnionToTuples<FilterSpecLeafKey> = [\n \"n\",\n \"x\",\n \"rhs\",\n \"type\",\n \"value\",\n \"column\",\n \"minDiff\",\n \"maxEdits\",\n \"wildcard\",\n \"replacement\",\n \"substitutionsOnly\",\n];\nconst KNOWN_LEAF_KEYS: Set<FilterSpecLeafKey> = new Set(KNOWN_LEAF_KEYS_TUPLE);\n\n/** Returns true if the leaf is filled — type is defined and no required fields are undefined. */\nfunction isFilledLeaf(node: Record<string, unknown>): boolean {\n if (node.type == null) return false;\n return !Object.values(node).some((v) => v === undefined);\n}\n\nfunction distillLeaf(node: Record<string, unknown>): FilterSpecLeaf<string> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (KNOWN_LEAF_KEYS.has(key as FilterSpecLeafKey)) {\n result[key] = value;\n }\n }\n return result as FilterSpecLeaf<string>;\n}\n\n/**\n * Strips non-FilterSpec metadata (whitelist approach) and removes\n * unfilled leaves (type is undefined or any required field is undefined).\n */\nexport function distillFilterSpec<T extends FilterSpecLeaf<unknown>>(\n filter: null | undefined | FilterSpec<T, unknown, unknown>,\n): null | FilterSpec<T> {\n if (filter == null) return null;\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n if (!isFilledLeaf(leaf as Record<string, unknown>)) return null;\n return distillLeaf(leaf as Record<string, unknown>) as FilterSpec<T>;\n },\n and: (results) => {\n const filtered = results.filter((f): f is FilterSpec<T> => f !== null);\n return filtered.length === 0 ? null : { type: \"and\", filters: filtered };\n },\n or: (results) => {\n const filtered = results.filter((f): f is FilterSpec<T> => f !== null);\n return filtered.length === 0 ? null : { type: \"or\", filters: filtered };\n },\n not: (result) => (result === null ? null : { type: \"not\", filter: result }),\n });\n}\n"],"names":["traverseFilterSpec"],"mappings":";;;;AAOA;AACA,MAAM,qBAAqB,GAAqC;IAC9D,GAAG;IACH,GAAG;IACH,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,aAAa;IACb,mBAAmB;CACpB;AACD,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAC,qBAAqB,CAAC;AAE9E;AACA,SAAS,YAAY,CAAC,IAA6B,EAAA;AACjD,IAAA,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;AAAE,QAAA,OAAO,KAAK;AACnC,IAAA,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AAC1D;AAEA,SAAS,WAAW,CAAC,IAA6B,EAAA;IAChD,MAAM,MAAM,GAA4B,EAAE;AAC1C,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AAC/C,QAAA,IAAI,eAAe,CAAC,GAAG,CAAC,GAAwB,CAAC,EAAE;AACjD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;QACrB;IACF;AACA,IAAA,OAAO,MAAgC;AACzC;AAEA;;;AAGG;AACG,SAAU,iBAAiB,CAC/B,MAA0D,EAAA;IAE1D,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,OAAOA,2BAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;AACb,YAAA,IAAI,CAAC,YAAY,CAAC,IAA+B,CAAC;AAAE,gBAAA,OAAO,IAAI;AAC/D,YAAA,OAAO,WAAW,CAAC,IAA+B,CAAkB;QACtE,CAAC;AACD,QAAA,GAAG,EAAE,CAAC,OAAO,KAAI;AACf,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,KAAK,IAAI,CAAC;YACtE,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC1E,CAAC;AACD,QAAA,EAAE,EAAE,CAAC,OAAO,KAAI;AACd,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,KAAK,IAAI,CAAC;YACtE,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE;QACzE,CAAC;QACD,GAAG,EAAE,CAAC,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5E,KAAA,CAAC;AACJ;;;;"}
1
+ {"version":3,"file":"distill.cjs","sources":["../../src/filters/distill.ts"],"sourcesContent":["import { DistributiveKeys, UnionToTuples } from \"@milaboratories/helpers\";\nimport {\n RootFilterSpec,\n type FilterSpec,\n type FilterSpecLeaf,\n} from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"./traverse\";\nimport { InferFilterSpecLeaf } from \"@milaboratories/pl-model-common\";\n\n/** All possible field names that can appear in any FilterSpecLeaf variant. */\ntype FilterSpecLeafKey = DistributiveKeys<FilterSpecLeaf<string>>;\n\n/** Compile-time check: every key in the tuple is a valid leaf key (via satisfies). */\nconst KNOWN_LEAF_KEYS_TUPLE: UnionToTuples<FilterSpecLeafKey> = [\n \"n\",\n \"x\",\n \"rhs\",\n \"type\",\n \"value\",\n \"column\",\n \"minDiff\",\n \"maxEdits\",\n \"wildcard\",\n \"replacement\",\n \"substitutionsOnly\",\n];\nconst KNOWN_LEAF_KEYS: Set<FilterSpecLeafKey> = new Set(KNOWN_LEAF_KEYS_TUPLE);\n\n/** Returns true if the leaf is filled — type is defined and no required fields are undefined. */\nfunction isFilledLeaf(node: Record<string, unknown>): boolean {\n if (node.type == null) return false;\n return !Object.values(node).some((v) => v === undefined);\n}\n\nfunction distillLeaf(node: Record<string, unknown>): FilterSpecLeaf<string> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (KNOWN_LEAF_KEYS.has(key as FilterSpecLeafKey)) {\n result[key] = value;\n }\n }\n return result as FilterSpecLeaf<string>;\n}\n\n/**\n * Strips non-FilterSpec metadata (whitelist approach) and removes\n * unfilled leaves (type is undefined or any required field is undefined).\n */\nexport function distillFilterSpec<\n FS extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R extends FS extends RootFilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>\n ? RootFilterSpec<InferFilterSpecLeaf<FS>>\n : FilterSpec<InferFilterSpecLeaf<FS>>,\n>(filter: null | undefined | FS): null | R {\n if (filter == null) return null;\n return traverseFilterSpec<FS, null | R>(filter, {\n leaf: (leaf) => {\n if (!isFilledLeaf(leaf as Record<string, unknown>)) return null;\n return distillLeaf(leaf as Record<string, unknown>) as R;\n },\n and: (results) => {\n const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);\n return filtered.length === 0 ? null : ({ type: \"and\", filters: filtered } as R);\n },\n or: (results) => {\n const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);\n return filtered.length === 0 ? null : ({ type: \"or\", filters: filtered } as R);\n },\n not: (result) => (result === null ? null : ({ type: \"not\", filter: result } as R)),\n });\n}\n"],"names":["traverseFilterSpec"],"mappings":";;;;AAYA;AACA,MAAM,qBAAqB,GAAqC;IAC9D,GAAG;IACH,GAAG;IACH,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,aAAa;IACb,mBAAmB;CACpB;AACD,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAC,qBAAqB,CAAC;AAE9E;AACA,SAAS,YAAY,CAAC,IAA6B,EAAA;AACjD,IAAA,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;AAAE,QAAA,OAAO,KAAK;AACnC,IAAA,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AAC1D;AAEA,SAAS,WAAW,CAAC,IAA6B,EAAA;IAChD,MAAM,MAAM,GAA4B,EAAE;AAC1C,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AAC/C,QAAA,IAAI,eAAe,CAAC,GAAG,CAAC,GAAwB,CAAC,EAAE;AACjD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;QACrB;IACF;AACA,IAAA,OAAO,MAAgC;AACzC;AAEA;;;AAGG;AACG,SAAU,iBAAiB,CAK/B,MAA6B,EAAA;IAC7B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,OAAOA,2BAAkB,CAAe,MAAM,EAAE;AAC9C,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;AACb,YAAA,IAAI,CAAC,YAAY,CAAC,IAA+B,CAAC;AAAE,gBAAA,OAAO,IAAI;AAC/D,YAAA,OAAO,WAAW,CAAC,IAA+B,CAAM;QAC1D,CAAC;AACD,QAAA,GAAG,EAAE,CAAC,OAAO,KAAI;AACf,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAiC,CAAC,KAAK,IAAI,CAAC;YAC9E,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAQ;QACjF,CAAC;AACD,QAAA,EAAE,EAAE,CAAC,OAAO,KAAI;AACd,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAiC,CAAC,KAAK,IAAI,CAAC;YAC9E,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAQ;QAChF,CAAC;QACD,GAAG,EAAE,CAAC,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAQ,CAAC;AACnF,KAAA,CAAC;AACJ;;;;"}
@@ -1,7 +1,8 @@
1
- import { type FilterSpec, type FilterSpecLeaf } from "@milaboratories/pl-model-common";
1
+ import { RootFilterSpec, type FilterSpec, type FilterSpecLeaf } from "@milaboratories/pl-model-common";
2
+ import { InferFilterSpecLeaf } from "@milaboratories/pl-model-common";
2
3
  /**
3
4
  * Strips non-FilterSpec metadata (whitelist approach) and removes
4
5
  * unfilled leaves (type is undefined or any required field is undefined).
5
6
  */
6
- export declare function distillFilterSpec<T extends FilterSpecLeaf<unknown>>(filter: null | undefined | FilterSpec<T, unknown, unknown>): null | FilterSpec<T>;
7
+ export declare function distillFilterSpec<FS extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>, R extends FS extends RootFilterSpec<FilterSpecLeaf<unknown>, unknown, unknown> ? RootFilterSpec<InferFilterSpecLeaf<FS>> : FilterSpec<InferFilterSpecLeaf<FS>>>(filter: null | undefined | FS): null | R;
7
8
  //# sourceMappingURL=distill.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"distill.d.ts","sourceRoot":"","sources":["../../src/filters/distill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAsCvF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,cAAc,CAAC,OAAO,CAAC,EACjE,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GACzD,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAiBtB"}
1
+ {"version":3,"file":"distill.d.ts","sourceRoot":"","sources":["../../src/filters/distill.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAqCtE;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAChE,CAAC,SAAS,EAAE,SAAS,cAAc,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAC1E,cAAc,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,GACvC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,EACvC,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,CAiBzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"distill.js","sources":["../../src/filters/distill.ts"],"sourcesContent":["import { DistributiveKeys, UnionToTuples } from \"@milaboratories/helpers\";\nimport { type FilterSpec, type FilterSpecLeaf } from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"./traverse\";\n\n/** All possible field names that can appear in any FilterSpecLeaf variant. */\ntype FilterSpecLeafKey = DistributiveKeys<FilterSpecLeaf<string>>;\n\n/** Compile-time check: every key in the tuple is a valid leaf key (via satisfies). */\nconst KNOWN_LEAF_KEYS_TUPLE: UnionToTuples<FilterSpecLeafKey> = [\n \"n\",\n \"x\",\n \"rhs\",\n \"type\",\n \"value\",\n \"column\",\n \"minDiff\",\n \"maxEdits\",\n \"wildcard\",\n \"replacement\",\n \"substitutionsOnly\",\n];\nconst KNOWN_LEAF_KEYS: Set<FilterSpecLeafKey> = new Set(KNOWN_LEAF_KEYS_TUPLE);\n\n/** Returns true if the leaf is filled — type is defined and no required fields are undefined. */\nfunction isFilledLeaf(node: Record<string, unknown>): boolean {\n if (node.type == null) return false;\n return !Object.values(node).some((v) => v === undefined);\n}\n\nfunction distillLeaf(node: Record<string, unknown>): FilterSpecLeaf<string> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (KNOWN_LEAF_KEYS.has(key as FilterSpecLeafKey)) {\n result[key] = value;\n }\n }\n return result as FilterSpecLeaf<string>;\n}\n\n/**\n * Strips non-FilterSpec metadata (whitelist approach) and removes\n * unfilled leaves (type is undefined or any required field is undefined).\n */\nexport function distillFilterSpec<T extends FilterSpecLeaf<unknown>>(\n filter: null | undefined | FilterSpec<T, unknown, unknown>,\n): null | FilterSpec<T> {\n if (filter == null) return null;\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n if (!isFilledLeaf(leaf as Record<string, unknown>)) return null;\n return distillLeaf(leaf as Record<string, unknown>) as FilterSpec<T>;\n },\n and: (results) => {\n const filtered = results.filter((f): f is FilterSpec<T> => f !== null);\n return filtered.length === 0 ? null : { type: \"and\", filters: filtered };\n },\n or: (results) => {\n const filtered = results.filter((f): f is FilterSpec<T> => f !== null);\n return filtered.length === 0 ? null : { type: \"or\", filters: filtered };\n },\n not: (result) => (result === null ? null : { type: \"not\", filter: result }),\n });\n}\n"],"names":[],"mappings":";;AAOA;AACA,MAAM,qBAAqB,GAAqC;IAC9D,GAAG;IACH,GAAG;IACH,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,aAAa;IACb,mBAAmB;CACpB;AACD,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAC,qBAAqB,CAAC;AAE9E;AACA,SAAS,YAAY,CAAC,IAA6B,EAAA;AACjD,IAAA,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;AAAE,QAAA,OAAO,KAAK;AACnC,IAAA,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AAC1D;AAEA,SAAS,WAAW,CAAC,IAA6B,EAAA;IAChD,MAAM,MAAM,GAA4B,EAAE;AAC1C,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AAC/C,QAAA,IAAI,eAAe,CAAC,GAAG,CAAC,GAAwB,CAAC,EAAE;AACjD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;QACrB;IACF;AACA,IAAA,OAAO,MAAgC;AACzC;AAEA;;;AAGG;AACG,SAAU,iBAAiB,CAC/B,MAA0D,EAAA;IAE1D,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,OAAO,kBAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;AACb,YAAA,IAAI,CAAC,YAAY,CAAC,IAA+B,CAAC;AAAE,gBAAA,OAAO,IAAI;AAC/D,YAAA,OAAO,WAAW,CAAC,IAA+B,CAAkB;QACtE,CAAC;AACD,QAAA,GAAG,EAAE,CAAC,OAAO,KAAI;AACf,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,KAAK,IAAI,CAAC;YACtE,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC1E,CAAC;AACD,QAAA,EAAE,EAAE,CAAC,OAAO,KAAI;AACd,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,KAAK,IAAI,CAAC;YACtE,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE;QACzE,CAAC;QACD,GAAG,EAAE,CAAC,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5E,KAAA,CAAC;AACJ;;;;"}
1
+ {"version":3,"file":"distill.js","sources":["../../src/filters/distill.ts"],"sourcesContent":["import { DistributiveKeys, UnionToTuples } from \"@milaboratories/helpers\";\nimport {\n RootFilterSpec,\n type FilterSpec,\n type FilterSpecLeaf,\n} from \"@milaboratories/pl-model-common\";\nimport { traverseFilterSpec } from \"./traverse\";\nimport { InferFilterSpecLeaf } from \"@milaboratories/pl-model-common\";\n\n/** All possible field names that can appear in any FilterSpecLeaf variant. */\ntype FilterSpecLeafKey = DistributiveKeys<FilterSpecLeaf<string>>;\n\n/** Compile-time check: every key in the tuple is a valid leaf key (via satisfies). */\nconst KNOWN_LEAF_KEYS_TUPLE: UnionToTuples<FilterSpecLeafKey> = [\n \"n\",\n \"x\",\n \"rhs\",\n \"type\",\n \"value\",\n \"column\",\n \"minDiff\",\n \"maxEdits\",\n \"wildcard\",\n \"replacement\",\n \"substitutionsOnly\",\n];\nconst KNOWN_LEAF_KEYS: Set<FilterSpecLeafKey> = new Set(KNOWN_LEAF_KEYS_TUPLE);\n\n/** Returns true if the leaf is filled — type is defined and no required fields are undefined. */\nfunction isFilledLeaf(node: Record<string, unknown>): boolean {\n if (node.type == null) return false;\n return !Object.values(node).some((v) => v === undefined);\n}\n\nfunction distillLeaf(node: Record<string, unknown>): FilterSpecLeaf<string> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (KNOWN_LEAF_KEYS.has(key as FilterSpecLeafKey)) {\n result[key] = value;\n }\n }\n return result as FilterSpecLeaf<string>;\n}\n\n/**\n * Strips non-FilterSpec metadata (whitelist approach) and removes\n * unfilled leaves (type is undefined or any required field is undefined).\n */\nexport function distillFilterSpec<\n FS extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R extends FS extends RootFilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>\n ? RootFilterSpec<InferFilterSpecLeaf<FS>>\n : FilterSpec<InferFilterSpecLeaf<FS>>,\n>(filter: null | undefined | FS): null | R {\n if (filter == null) return null;\n return traverseFilterSpec<FS, null | R>(filter, {\n leaf: (leaf) => {\n if (!isFilledLeaf(leaf as Record<string, unknown>)) return null;\n return distillLeaf(leaf as Record<string, unknown>) as R;\n },\n and: (results) => {\n const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);\n return filtered.length === 0 ? null : ({ type: \"and\", filters: filtered } as R);\n },\n or: (results) => {\n const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);\n return filtered.length === 0 ? null : ({ type: \"or\", filters: filtered } as R);\n },\n not: (result) => (result === null ? null : ({ type: \"not\", filter: result } as R)),\n });\n}\n"],"names":[],"mappings":";;AAYA;AACA,MAAM,qBAAqB,GAAqC;IAC9D,GAAG;IACH,GAAG;IACH,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,aAAa;IACb,mBAAmB;CACpB;AACD,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAC,qBAAqB,CAAC;AAE9E;AACA,SAAS,YAAY,CAAC,IAA6B,EAAA;AACjD,IAAA,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;AAAE,QAAA,OAAO,KAAK;AACnC,IAAA,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AAC1D;AAEA,SAAS,WAAW,CAAC,IAA6B,EAAA;IAChD,MAAM,MAAM,GAA4B,EAAE;AAC1C,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AAC/C,QAAA,IAAI,eAAe,CAAC,GAAG,CAAC,GAAwB,CAAC,EAAE;AACjD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;QACrB;IACF;AACA,IAAA,OAAO,MAAgC;AACzC;AAEA;;;AAGG;AACG,SAAU,iBAAiB,CAK/B,MAA6B,EAAA;IAC7B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,OAAO,kBAAkB,CAAe,MAAM,EAAE;AAC9C,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;AACb,YAAA,IAAI,CAAC,YAAY,CAAC,IAA+B,CAAC;AAAE,gBAAA,OAAO,IAAI;AAC/D,YAAA,OAAO,WAAW,CAAC,IAA+B,CAAM;QAC1D,CAAC;AACD,QAAA,GAAG,EAAE,CAAC,OAAO,KAAI;AACf,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAiC,CAAC,KAAK,IAAI,CAAC;YAC9E,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAQ;QACjF,CAAC;AACD,QAAA,EAAE,EAAE,CAAC,OAAO,KAAI;AACd,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAiC,CAAC,KAAK,IAAI,CAAC;YAC9E,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAQ;QAChF,CAAC;QACD,GAAG,EAAE,CAAC,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,GAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAQ,CAAC;AACnF,KAAA,CAAC;AACJ;;;;"}
@@ -12,17 +12,21 @@
12
12
  * 3. For leaf nodes, call `leaf` directly
13
13
  */
14
14
  function traverseFilterSpec(filter, visitor) {
15
+ return traverseFilterSpecImpl(filter, visitor);
16
+ }
17
+ /** Internal implementation with simple generics for clean recursion. */
18
+ function traverseFilterSpecImpl(filter, visitor) {
15
19
  switch (filter.type) {
16
20
  case "and":
17
21
  return visitor.and(filter.filters
18
22
  .filter((f) => f.type !== undefined)
19
- .map((f) => traverseFilterSpec(f, visitor)));
23
+ .map((f) => traverseFilterSpecImpl(f, visitor)));
20
24
  case "or":
21
25
  return visitor.or(filter.filters
22
26
  .filter((f) => f.type !== undefined)
23
- .map((f) => traverseFilterSpec(f, visitor)));
27
+ .map((f) => traverseFilterSpecImpl(f, visitor)));
24
28
  case "not":
25
- return visitor.not(traverseFilterSpec(filter.filter, visitor));
29
+ return visitor.not(traverseFilterSpecImpl(filter.filter, visitor));
26
30
  default:
27
31
  return visitor.leaf(filter);
28
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"traverse.cjs","sources":["../../src/filters/traverse.ts"],"sourcesContent":["import type { FilterSpecLeaf, FilterSpecNode } from \"@milaboratories/pl-model-common\";\n\n/**\n * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.\n *\n * Entries with `{ type: undefined }` inside `and`/`or` arrays are skipped\n * (these represent unfilled filter slots in the UI).\n *\n * Traversal order:\n * 1. Recurse into child filters (`and`/`or`/`not`)\n * 2. Apply the corresponding visitor callback with already-traversed children\n * 3. For leaf nodes, call `leaf` directly\n */\nexport function traverseFilterSpec<Leaf extends FilterSpecLeaf<unknown>, CommonNode, CommonLeaf, R>(\n filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>,\n visitor: {\n /** Handle a leaf filter node. */\n leaf: (leaf: CommonLeaf & Leaf) => R;\n /** Handle an AND node after children have been traversed. */\n and: (results: R[]) => R;\n /** Handle an OR node after children have been traversed. */\n or: (results: R[]) => R;\n /** Handle a NOT node after the inner filter has been traversed. */\n not: (result: R) => R;\n },\n): R {\n switch (filter.type) {\n case \"and\":\n return visitor.and(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpec(f, visitor)),\n );\n case \"or\":\n return visitor.or(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpec(f, visitor)),\n );\n case \"not\":\n return visitor.not(traverseFilterSpec(filter.filter, visitor));\n default:\n return visitor.leaf(filter as CommonLeaf & Leaf);\n }\n}\n\n/** Collects all column references (`column` and `rhs` fields) from filter leaves. */\nexport function collectFilterSpecColumns<\n Leaf extends FilterSpecLeaf<unknown>,\n CommonNode,\n CommonLeaf,\n>(filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>): string[] {\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n const cols: string[] = [];\n if (\"column\" in leaf && leaf.column !== undefined) cols.push(leaf.column as string);\n if (\"rhs\" in leaf && leaf.rhs !== undefined) cols.push(leaf.rhs as string);\n return cols;\n },\n and: (results) => results.flat(),\n or: (results) => results.flat(),\n not: (result) => result,\n });\n}\n"],"names":[],"mappings":";;AAEA;;;;;;;;;;AAUG;AACG,SAAU,kBAAkB,CAChC,MAAoD,EACpD,OASC,EAAA;AAED,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAC9C;AACH,QAAA,KAAK,IAAI;AACP,YAAA,OAAO,OAAO,CAAC,EAAE,CACf,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAC9C;AACH,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChE,QAAA;AACE,YAAA,OAAO,OAAO,CAAC,IAAI,CAAC,MAA2B,CAAC;;AAEtD;AAEA;AACM,SAAU,wBAAwB,CAItC,MAAoD,EAAA;IACpD,OAAO,kBAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;YACb,MAAM,IAAI,GAAa,EAAE;YACzB,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAgB,CAAC;YACnF,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAa,CAAC;AAC1E,YAAA,OAAO,IAAI;QACb,CAAC;QACD,GAAG,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;QAChC,EAAE,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;AAC/B,QAAA,GAAG,EAAE,CAAC,MAAM,KAAK,MAAM;AACxB,KAAA,CAAC;AACJ;;;;;"}
1
+ {"version":3,"file":"traverse.cjs","sources":["../../src/filters/traverse.ts"],"sourcesContent":["import type {\n FilterSpec,\n FilterSpecLeaf,\n FilterSpecNode,\n InferFilterSpecLeafColumn,\n} from \"@milaboratories/pl-model-common\";\nimport type {\n InferFilterSpecCommonLeaf,\n InferFilterSpecLeaf,\n} from \"@milaboratories/pl-model-common\";\n\nexport type FilterSpecVisitor<LeafArg, R> = {\n /** Handle a leaf filter node. */\n leaf: (leaf: LeafArg) => R;\n /** Handle an AND node after children have been traversed. */\n and: (results: R[]) => R;\n /** Handle an OR node after children have been traversed. */\n or: (results: R[]) => R;\n /** Handle a NOT node after the inner filter has been traversed. */\n not: (result: R) => R;\n};\n\n/**\n * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.\n *\n * Entries with `{ type: undefined }` inside `and`/`or` arrays are skipped\n * (these represent unfilled filter slots in the UI).\n *\n * Traversal order:\n * 1. Recurse into child filters (`and`/`or`/`not`)\n * 2. Apply the corresponding visitor callback with already-traversed children\n * 3. For leaf nodes, call `leaf` directly\n */\nexport function traverseFilterSpec<\n F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R,\n>(\n filter: F,\n visitor: FilterSpecVisitor<InferFilterSpecCommonLeaf<F> & InferFilterSpecLeaf<F>, R>,\n): R {\n return traverseFilterSpecImpl(filter, visitor as FilterSpecVisitor<unknown, R>);\n}\n/** Internal implementation with simple generics for clean recursion. */\nfunction traverseFilterSpecImpl<R>(\n filter: FilterSpecNode<FilterSpecLeaf<unknown>, unknown, unknown>,\n visitor: FilterSpecVisitor<unknown, R>,\n): R {\n switch (filter.type) {\n case \"and\":\n return visitor.and(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpecImpl(f, visitor)),\n );\n case \"or\":\n return visitor.or(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpecImpl(f, visitor)),\n );\n case \"not\":\n return visitor.not(traverseFilterSpecImpl(filter.filter, visitor));\n default:\n return visitor.leaf(filter);\n }\n}\n\n/** Collects all column references (`column` and `rhs` fields) from filter leaves. */\nexport function collectFilterSpecColumns<\n F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R extends InferFilterSpecLeafColumn<F> = InferFilterSpecLeafColumn<F>,\n>(filter: F): R[] {\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n const cols: R[] = [];\n if (\"column\" in leaf && leaf.column !== undefined) cols.push(leaf.column as R);\n if (\"rhs\" in leaf && leaf.rhs !== undefined) cols.push(leaf.rhs as R);\n return cols;\n },\n and: (results) => results.flat(),\n or: (results) => results.flat(),\n not: (result) => result,\n });\n}\n"],"names":[],"mappings":";;AAsBA;;;;;;;;;;AAUG;AACG,SAAU,kBAAkB,CAIhC,MAAS,EACT,OAAoF,EAAA;AAEpF,IAAA,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAwC,CAAC;AACjF;AACA;AACA,SAAS,sBAAsB,CAC7B,MAAiE,EACjE,OAAsC,EAAA;AAEtC,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAClD;AACH,QAAA,KAAK,IAAI;AACP,YAAA,OAAO,OAAO,CAAC,EAAE,CACf,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAClD;AACH,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpE,QAAA;AACE,YAAA,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;;AAEjC;AAEA;AACM,SAAU,wBAAwB,CAGtC,MAAS,EAAA;IACT,OAAO,kBAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;YACb,MAAM,IAAI,GAAQ,EAAE;YACpB,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAW,CAAC;YAC9E,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAQ,CAAC;AACrE,YAAA,OAAO,IAAI;QACb,CAAC;QACD,GAAG,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;QAChC,EAAE,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;AAC/B,QAAA,GAAG,EAAE,CAAC,MAAM,KAAK,MAAM;AACxB,KAAA,CAAC;AACJ;;;;;"}
@@ -1,4 +1,15 @@
1
- import type { FilterSpecLeaf, FilterSpecNode } from "@milaboratories/pl-model-common";
1
+ import type { FilterSpec, FilterSpecLeaf, InferFilterSpecLeafColumn } from "@milaboratories/pl-model-common";
2
+ import type { InferFilterSpecCommonLeaf, InferFilterSpecLeaf } from "@milaboratories/pl-model-common";
3
+ export type FilterSpecVisitor<LeafArg, R> = {
4
+ /** Handle a leaf filter node. */
5
+ leaf: (leaf: LeafArg) => R;
6
+ /** Handle an AND node after children have been traversed. */
7
+ and: (results: R[]) => R;
8
+ /** Handle an OR node after children have been traversed. */
9
+ or: (results: R[]) => R;
10
+ /** Handle a NOT node after the inner filter has been traversed. */
11
+ not: (result: R) => R;
12
+ };
2
13
  /**
3
14
  * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.
4
15
  *
@@ -10,16 +21,7 @@ import type { FilterSpecLeaf, FilterSpecNode } from "@milaboratories/pl-model-co
10
21
  * 2. Apply the corresponding visitor callback with already-traversed children
11
22
  * 3. For leaf nodes, call `leaf` directly
12
23
  */
13
- export declare function traverseFilterSpec<Leaf extends FilterSpecLeaf<unknown>, CommonNode, CommonLeaf, R>(filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>, visitor: {
14
- /** Handle a leaf filter node. */
15
- leaf: (leaf: CommonLeaf & Leaf) => R;
16
- /** Handle an AND node after children have been traversed. */
17
- and: (results: R[]) => R;
18
- /** Handle an OR node after children have been traversed. */
19
- or: (results: R[]) => R;
20
- /** Handle a NOT node after the inner filter has been traversed. */
21
- not: (result: R) => R;
22
- }): R;
24
+ export declare function traverseFilterSpec<F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>, R>(filter: F, visitor: FilterSpecVisitor<InferFilterSpecCommonLeaf<F> & InferFilterSpecLeaf<F>, R>): R;
23
25
  /** Collects all column references (`column` and `rhs` fields) from filter leaves. */
24
- export declare function collectFilterSpecColumns<Leaf extends FilterSpecLeaf<unknown>, CommonNode, CommonLeaf>(filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>): string[];
26
+ export declare function collectFilterSpecColumns<F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>, R extends InferFilterSpecLeafColumn<F> = InferFilterSpecLeafColumn<F>>(filter: F): R[];
25
27
  //# sourceMappingURL=traverse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../../src/filters/traverse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,cAAc,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,EAChG,MAAM,EAAE,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,EACpD,OAAO,EAAE;IACP,iCAAiC;IACjC,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,KAAK,CAAC,CAAC;IACrC,6DAA6D;IAC7D,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACzB,4DAA4D;IAC5D,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACxB,mEAAmE;IACnE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;CACvB,GACA,CAAC,CAmBH;AAED,qFAAqF;AACrF,wBAAgB,wBAAwB,CACtC,IAAI,SAAS,cAAc,CAAC,OAAO,CAAC,EACpC,UAAU,EACV,UAAU,EACV,MAAM,EAAE,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,MAAM,EAAE,CAYhE"}
1
+ {"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../../src/filters/traverse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EAEd,yBAAyB,EAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;AAEzC,MAAM,MAAM,iBAAiB,CAAC,OAAO,EAAE,CAAC,IAAI;IAC1C,iCAAiC;IACjC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;IAC3B,6DAA6D;IAC7D,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACzB,4DAA4D;IAC5D,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACxB,mEAAmE;IACnE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAC/D,CAAC,EAED,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,iBAAiB,CAAC,yBAAyB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GACnF,CAAC,CAEH;AA0BD,qFAAqF;AACrF,wBAAgB,wBAAwB,CACtC,CAAC,SAAS,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAC/D,CAAC,SAAS,yBAAyB,CAAC,CAAC,CAAC,GAAG,yBAAyB,CAAC,CAAC,CAAC,EACrE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAYhB"}
@@ -10,17 +10,21 @@
10
10
  * 3. For leaf nodes, call `leaf` directly
11
11
  */
12
12
  function traverseFilterSpec(filter, visitor) {
13
+ return traverseFilterSpecImpl(filter, visitor);
14
+ }
15
+ /** Internal implementation with simple generics for clean recursion. */
16
+ function traverseFilterSpecImpl(filter, visitor) {
13
17
  switch (filter.type) {
14
18
  case "and":
15
19
  return visitor.and(filter.filters
16
20
  .filter((f) => f.type !== undefined)
17
- .map((f) => traverseFilterSpec(f, visitor)));
21
+ .map((f) => traverseFilterSpecImpl(f, visitor)));
18
22
  case "or":
19
23
  return visitor.or(filter.filters
20
24
  .filter((f) => f.type !== undefined)
21
- .map((f) => traverseFilterSpec(f, visitor)));
25
+ .map((f) => traverseFilterSpecImpl(f, visitor)));
22
26
  case "not":
23
- return visitor.not(traverseFilterSpec(filter.filter, visitor));
27
+ return visitor.not(traverseFilterSpecImpl(filter.filter, visitor));
24
28
  default:
25
29
  return visitor.leaf(filter);
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"traverse.js","sources":["../../src/filters/traverse.ts"],"sourcesContent":["import type { FilterSpecLeaf, FilterSpecNode } from \"@milaboratories/pl-model-common\";\n\n/**\n * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.\n *\n * Entries with `{ type: undefined }` inside `and`/`or` arrays are skipped\n * (these represent unfilled filter slots in the UI).\n *\n * Traversal order:\n * 1. Recurse into child filters (`and`/`or`/`not`)\n * 2. Apply the corresponding visitor callback with already-traversed children\n * 3. For leaf nodes, call `leaf` directly\n */\nexport function traverseFilterSpec<Leaf extends FilterSpecLeaf<unknown>, CommonNode, CommonLeaf, R>(\n filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>,\n visitor: {\n /** Handle a leaf filter node. */\n leaf: (leaf: CommonLeaf & Leaf) => R;\n /** Handle an AND node after children have been traversed. */\n and: (results: R[]) => R;\n /** Handle an OR node after children have been traversed. */\n or: (results: R[]) => R;\n /** Handle a NOT node after the inner filter has been traversed. */\n not: (result: R) => R;\n },\n): R {\n switch (filter.type) {\n case \"and\":\n return visitor.and(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpec(f, visitor)),\n );\n case \"or\":\n return visitor.or(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpec(f, visitor)),\n );\n case \"not\":\n return visitor.not(traverseFilterSpec(filter.filter, visitor));\n default:\n return visitor.leaf(filter as CommonLeaf & Leaf);\n }\n}\n\n/** Collects all column references (`column` and `rhs` fields) from filter leaves. */\nexport function collectFilterSpecColumns<\n Leaf extends FilterSpecLeaf<unknown>,\n CommonNode,\n CommonLeaf,\n>(filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>): string[] {\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n const cols: string[] = [];\n if (\"column\" in leaf && leaf.column !== undefined) cols.push(leaf.column as string);\n if (\"rhs\" in leaf && leaf.rhs !== undefined) cols.push(leaf.rhs as string);\n return cols;\n },\n and: (results) => results.flat(),\n or: (results) => results.flat(),\n not: (result) => result,\n });\n}\n"],"names":[],"mappings":"AAEA;;;;;;;;;;AAUG;AACG,SAAU,kBAAkB,CAChC,MAAoD,EACpD,OASC,EAAA;AAED,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAC9C;AACH,QAAA,KAAK,IAAI;AACP,YAAA,OAAO,OAAO,CAAC,EAAE,CACf,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAC9C;AACH,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChE,QAAA;AACE,YAAA,OAAO,OAAO,CAAC,IAAI,CAAC,MAA2B,CAAC;;AAEtD;AAEA;AACM,SAAU,wBAAwB,CAItC,MAAoD,EAAA;IACpD,OAAO,kBAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;YACb,MAAM,IAAI,GAAa,EAAE;YACzB,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAgB,CAAC;YACnF,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAa,CAAC;AAC1E,YAAA,OAAO,IAAI;QACb,CAAC;QACD,GAAG,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;QAChC,EAAE,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;AAC/B,QAAA,GAAG,EAAE,CAAC,MAAM,KAAK,MAAM;AACxB,KAAA,CAAC;AACJ;;;;"}
1
+ {"version":3,"file":"traverse.js","sources":["../../src/filters/traverse.ts"],"sourcesContent":["import type {\n FilterSpec,\n FilterSpecLeaf,\n FilterSpecNode,\n InferFilterSpecLeafColumn,\n} from \"@milaboratories/pl-model-common\";\nimport type {\n InferFilterSpecCommonLeaf,\n InferFilterSpecLeaf,\n} from \"@milaboratories/pl-model-common\";\n\nexport type FilterSpecVisitor<LeafArg, R> = {\n /** Handle a leaf filter node. */\n leaf: (leaf: LeafArg) => R;\n /** Handle an AND node after children have been traversed. */\n and: (results: R[]) => R;\n /** Handle an OR node after children have been traversed. */\n or: (results: R[]) => R;\n /** Handle a NOT node after the inner filter has been traversed. */\n not: (result: R) => R;\n};\n\n/**\n * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.\n *\n * Entries with `{ type: undefined }` inside `and`/`or` arrays are skipped\n * (these represent unfilled filter slots in the UI).\n *\n * Traversal order:\n * 1. Recurse into child filters (`and`/`or`/`not`)\n * 2. Apply the corresponding visitor callback with already-traversed children\n * 3. For leaf nodes, call `leaf` directly\n */\nexport function traverseFilterSpec<\n F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R,\n>(\n filter: F,\n visitor: FilterSpecVisitor<InferFilterSpecCommonLeaf<F> & InferFilterSpecLeaf<F>, R>,\n): R {\n return traverseFilterSpecImpl(filter, visitor as FilterSpecVisitor<unknown, R>);\n}\n/** Internal implementation with simple generics for clean recursion. */\nfunction traverseFilterSpecImpl<R>(\n filter: FilterSpecNode<FilterSpecLeaf<unknown>, unknown, unknown>,\n visitor: FilterSpecVisitor<unknown, R>,\n): R {\n switch (filter.type) {\n case \"and\":\n return visitor.and(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpecImpl(f, visitor)),\n );\n case \"or\":\n return visitor.or(\n filter.filters\n .filter((f) => f.type !== undefined)\n .map((f) => traverseFilterSpecImpl(f, visitor)),\n );\n case \"not\":\n return visitor.not(traverseFilterSpecImpl(filter.filter, visitor));\n default:\n return visitor.leaf(filter);\n }\n}\n\n/** Collects all column references (`column` and `rhs` fields) from filter leaves. */\nexport function collectFilterSpecColumns<\n F extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,\n R extends InferFilterSpecLeafColumn<F> = InferFilterSpecLeafColumn<F>,\n>(filter: F): R[] {\n return traverseFilterSpec(filter, {\n leaf: (leaf) => {\n const cols: R[] = [];\n if (\"column\" in leaf && leaf.column !== undefined) cols.push(leaf.column as R);\n if (\"rhs\" in leaf && leaf.rhs !== undefined) cols.push(leaf.rhs as R);\n return cols;\n },\n and: (results) => results.flat(),\n or: (results) => results.flat(),\n not: (result) => result,\n });\n}\n"],"names":[],"mappings":"AAsBA;;;;;;;;;;AAUG;AACG,SAAU,kBAAkB,CAIhC,MAAS,EACT,OAAoF,EAAA;AAEpF,IAAA,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAwC,CAAC;AACjF;AACA;AACA,SAAS,sBAAsB,CAC7B,MAAiE,EACjE,OAAsC,EAAA;AAEtC,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAClD;AACH,QAAA,KAAK,IAAI;AACP,YAAA,OAAO,OAAO,CAAC,EAAE,CACf,MAAM,CAAC;iBACJ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,SAAS;AAClC,iBAAA,GAAG,CAAC,CAAC,CAAC,KAAK,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAClD;AACH,QAAA,KAAK,KAAK;AACR,YAAA,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpE,QAAA;AACE,YAAA,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;;AAEjC;AAEA;AACM,SAAU,wBAAwB,CAGtC,MAAS,EAAA;IACT,OAAO,kBAAkB,CAAC,MAAM,EAAE;AAChC,QAAA,IAAI,EAAE,CAAC,IAAI,KAAI;YACb,MAAM,IAAI,GAAQ,EAAE;YACpB,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAW,CAAC;YAC9E,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAQ,CAAC;AACrE,YAAA,OAAO,IAAI;QACb,CAAC;QACD,GAAG,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;QAChC,EAAE,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE;AAC/B,QAAA,GAAG,EAAE,CAAC,MAAM,KAAK,MAAM;AACxB,KAAA,CAAC;AACJ;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.54.10";
3
+ var version = "1.54.13";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.map
@@ -1,4 +1,4 @@
1
- var version = "1.54.10";
1
+ var version = "1.54.13";
2
2
 
3
3
  export { version };
4
4
  //# sourceMappingURL=package.json.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.54.10",
3
+ "version": "1.54.13",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -24,19 +24,19 @@
24
24
  "fast-json-patch": "^3.1.1",
25
25
  "utility-types": "^3.11.0",
26
26
  "zod": "~3.23.8",
27
- "@milaboratories/helpers": "1.13.4",
28
27
  "@milaboratories/pl-error-like": "1.12.8",
29
- "@milaboratories/ptabler-expression-js": "1.1.20",
30
- "@milaboratories/pl-model-common": "1.24.10"
28
+ "@milaboratories/pl-model-common": "1.24.11",
29
+ "@milaboratories/helpers": "1.13.5",
30
+ "@milaboratories/ptabler-expression-js": "1.1.21"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@vitest/coverage-istanbul": "^4.0.16",
34
34
  "fast-json-patch": "^3.1.1",
35
35
  "typescript": "~5.6.3",
36
36
  "vitest": "^4.0.16",
37
+ "@milaboratories/ts-configs": "1.2.1",
37
38
  "@milaboratories/ts-builder": "1.2.10",
38
- "@milaboratories/build-configs": "1.4.4",
39
- "@milaboratories/ts-configs": "1.2.1"
39
+ "@milaboratories/build-configs": "1.4.4"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "ts-builder build --target node",
@@ -158,10 +158,10 @@ function migrateV4toV5(
158
158
  const nextId = () => ++idCounter;
159
159
 
160
160
  const migratedCache: PlDataTableStateV2CacheEntry[] = state.stateCache.map((entry) => {
161
- const leaves: PlDataTableFiltersWithMeta[] = [];
161
+ const leaves: PlDataTableFiltersWithMeta["filters"] = [];
162
162
  for (const f of entry.filtersState) {
163
163
  if (f.filter !== null && !f.filter.disabled) {
164
- const column = canonicalizeJson<PTableColumnId>(f.id);
164
+ const column = canonicalizeJson(f.id);
165
165
  leaves.push(migrateTableFilter(column, f.filter.value, nextId));
166
166
  }
167
167
  }
@@ -198,10 +198,10 @@ function migrateV4toV5(
198
198
 
199
199
  /** Migrate a single per-column PlTableFilter to a tree-based FilterSpec node */
200
200
  function migrateTableFilter(
201
- column: string,
201
+ column: CanonicalizedJson<PTableColumnId>,
202
202
  filter: PlTableFilter,
203
203
  nextId: () => number,
204
- ): PlDataTableFiltersWithMeta {
204
+ ): PlDataTableFiltersWithMeta["filters"][number] {
205
205
  const id = nextId();
206
206
  switch (filter.type) {
207
207
  case "isNA":
@@ -26,6 +26,7 @@ import {
26
26
  readAnnotation,
27
27
  uniqueBy,
28
28
  isBooleanExpression,
29
+ parseJson,
29
30
  } from "@milaboratories/pl-model-common";
30
31
  import { filterSpecToSpecQueryExpr } from "../../filters";
31
32
  import type { RenderCtxBase, TreeNodeAccessor, PColumnDataUniversal } from "../../render";
@@ -186,7 +187,7 @@ export function createPlDataTableV2<A, U>(
186
187
  const stateFilters = tableStateNormalized.pTableParams.filters;
187
188
  const opsFilters = ops?.filters ?? null;
188
189
  const filters: null | PlDataTableFilters =
189
- stateFilters !== null && opsFilters !== null
190
+ stateFilters != null && opsFilters != null
190
191
  ? { type: "and", filters: [stateFilters, opsFilters] }
191
192
  : (stateFilters ?? opsFilters);
192
193
  const filterColumns = filters ? collectFilterSpecColumns(filters) : [];
@@ -218,6 +219,7 @@ export function createPlDataTableV2<A, U>(
218
219
  sorting,
219
220
  coreColumnPredicate: ops?.coreColumnPredicate,
220
221
  });
222
+
221
223
  const fullHandle = ctx.createPTableV2(fullDef);
222
224
  if (!fullHandle) return undefined;
223
225
 
@@ -245,12 +247,22 @@ export function createPlDataTableV2<A, U>(
245
247
  coreColumns.forEach((c) => hiddenColumns.delete(c));
246
248
  }
247
249
 
248
- // Sorting changes the order of result rows — preserve sorted columns from being hidden
250
+ // Preserve sorted columns from being hidden
249
251
  sorting
250
252
  .map((s) => s.column)
251
253
  .filter((c): c is PTableColumnIdColumn => c.type === "column")
252
254
  .forEach((c) => hiddenColumns.delete(c.id));
253
255
 
256
+ // Preserve filter columns from being hidden
257
+ if (filters) {
258
+ collectFilterSpecColumns(filters)
259
+ .flatMap((c) => {
260
+ const obj = parseJson(c);
261
+ return obj.type === "column" ? [obj.id] : [];
262
+ })
263
+ .forEach((c) => hiddenColumns.delete(c));
264
+ }
265
+
254
266
  const visibleColumns = columns.filter((c) => !hiddenColumns.has(c.id));
255
267
  const visibleLabelColumns = getMatchingLabelColumns(
256
268
  visibleColumns.map(getColumnIdAndSpec),
@@ -269,13 +281,14 @@ export function createPlDataTableV2<A, U>(
269
281
  coreColumnPredicate,
270
282
  });
271
283
  const visibleHandle = ctx.createPTableV2(visibleDef);
284
+
272
285
  if (!visibleHandle) return undefined;
273
286
 
274
287
  return {
275
288
  sourceId: tableStateNormalized.pTableParams.sourceId,
276
289
  fullTableHandle: fullHandle,
277
290
  visibleTableHandle: visibleHandle,
278
- } satisfies PlDataTableModel;
291
+ } as PlDataTableModel;
279
292
  }
280
293
 
281
294
  /** Create sheet entries for PlDataTable */
@@ -8,8 +8,10 @@ import type {
8
8
  PTableSorting,
9
9
  PColumnIdAndSpec,
10
10
  PTableHandle,
11
+ RootFilterSpec,
12
+ PTableColumnId,
11
13
  } from "@milaboratories/pl-model-common";
12
- import type { FilterSpec, FilterSpecLeaf } from "../../filters";
14
+ import type { FilterSpecLeaf } from "../../filters";
13
15
 
14
16
  export type PlTableColumnId = {
15
17
  /** Original column spec */
@@ -60,10 +62,10 @@ export type PlDataTableSheetState = {
60
62
  };
61
63
 
62
64
  /** Tree-based filter state compatible with PlAdvancedFilter's RootFilter */
63
- export type PlDataTableFilters = FilterSpec<FilterSpecLeaf<string>>;
64
- export type PlDataTableFiltersWithMeta = FilterSpec<
65
- FilterSpecLeaf<string>,
66
- { id: number; isExpanded?: boolean }
65
+ export type PlDataTableFilters = RootFilterSpec<FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>>;
66
+ export type PlDataTableFiltersWithMeta = RootFilterSpec<
67
+ FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>,
68
+ { id: number; isExpanded?: boolean; source?: "table-filter" | "table-search" }
67
69
  >;
68
70
 
69
71
  export type PlDataTableStateV2CacheEntry = {
@@ -75,6 +77,8 @@ export type PlDataTableStateV2CacheEntry = {
75
77
  sheetsState: PlDataTableSheetState[];
76
78
  /** Filters state (tree-based, compatible with PlAdvancedFilter) */
77
79
  filtersState: null | PlDataTableFiltersWithMeta;
80
+ /** Fast search string */
81
+ searchString?: string;
78
82
  };
79
83
 
80
84
  export type PTableParamsV2 =
@@ -11,15 +11,14 @@ import { traverseFilterSpec } from "../traverse";
11
11
  /** Parses a CanonicalizedJson<PTableColumnId> string into a SpecQueryExpression reference. */
12
12
  function resolveColumnRef(columnStr: string): SpecQueryExpression {
13
13
  const parsed = JSON.parse(columnStr) as PTableColumnId;
14
- if (parsed.type === "axis") {
15
- return { type: "axisRef", value: parsed.id as SingleAxisSelector };
16
- }
17
- return { type: "columnRef", value: parsed.id };
14
+ return parsed.type === "axis"
15
+ ? { type: "axisRef", value: parsed.id as SingleAxisSelector }
16
+ : { type: "columnRef", value: parsed.id };
18
17
  }
19
18
 
20
19
  /** Converts a FilterSpec tree into a SpecQueryExpression. */
21
- export function filterSpecToSpecQueryExpr(
22
- filter: FilterSpec<FilterSpecLeaf<string>>,
20
+ export function filterSpecToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(
21
+ filter: FilterSpec<Leaf>,
23
22
  ): SpecQueryExpression {
24
23
  return traverseFilterSpec(filter, {
25
24
  leaf: leafToSpecQueryExpr,
@@ -39,7 +38,9 @@ export function filterSpecToSpecQueryExpr(
39
38
  });
40
39
  }
41
40
 
42
- function leafToSpecQueryExpr(filter: FilterSpecLeaf<string>): SpecQueryExpression {
41
+ function leafToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(
42
+ filter: Leaf,
43
+ ): SpecQueryExpression {
43
44
  switch (filter.type) {
44
45
  case "patternEquals":
45
46
  return {
@@ -1,6 +1,11 @@
1
1
  import { DistributiveKeys, UnionToTuples } from "@milaboratories/helpers";
2
- import { type FilterSpec, type FilterSpecLeaf } from "@milaboratories/pl-model-common";
2
+ import {
3
+ RootFilterSpec,
4
+ type FilterSpec,
5
+ type FilterSpecLeaf,
6
+ } from "@milaboratories/pl-model-common";
3
7
  import { traverseFilterSpec } from "./traverse";
8
+ import { InferFilterSpecLeaf } from "@milaboratories/pl-model-common";
4
9
 
5
10
  /** All possible field names that can appear in any FilterSpecLeaf variant. */
6
11
  type FilterSpecLeafKey = DistributiveKeys<FilterSpecLeaf<string>>;
@@ -41,23 +46,26 @@ function distillLeaf(node: Record<string, unknown>): FilterSpecLeaf<string> {
41
46
  * Strips non-FilterSpec metadata (whitelist approach) and removes
42
47
  * unfilled leaves (type is undefined or any required field is undefined).
43
48
  */
44
- export function distillFilterSpec<T extends FilterSpecLeaf<unknown>>(
45
- filter: null | undefined | FilterSpec<T, unknown, unknown>,
46
- ): null | FilterSpec<T> {
49
+ export function distillFilterSpec<
50
+ FS extends FilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>,
51
+ R extends FS extends RootFilterSpec<FilterSpecLeaf<unknown>, unknown, unknown>
52
+ ? RootFilterSpec<InferFilterSpecLeaf<FS>>
53
+ : FilterSpec<InferFilterSpecLeaf<FS>>,
54
+ >(filter: null | undefined | FS): null | R {
47
55
  if (filter == null) return null;
48
- return traverseFilterSpec(filter, {
56
+ return traverseFilterSpec<FS, null | R>(filter, {
49
57
  leaf: (leaf) => {
50
58
  if (!isFilledLeaf(leaf as Record<string, unknown>)) return null;
51
- return distillLeaf(leaf as Record<string, unknown>) as FilterSpec<T>;
59
+ return distillLeaf(leaf as Record<string, unknown>) as R;
52
60
  },
53
61
  and: (results) => {
54
- const filtered = results.filter((f): f is FilterSpec<T> => f !== null);
55
- return filtered.length === 0 ? null : { type: "and", filters: filtered };
62
+ const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);
63
+ return filtered.length === 0 ? null : ({ type: "and", filters: filtered } as R);
56
64
  },
57
65
  or: (results) => {
58
- const filtered = results.filter((f): f is FilterSpec<T> => f !== null);
59
- return filtered.length === 0 ? null : { type: "or", filters: filtered };
66
+ const filtered = results.filter((f): f is NonNullable<typeof f> => f !== null);
67
+ return filtered.length === 0 ? null : ({ type: "or", filters: filtered } as R);
60
68
  },
61
- not: (result) => (result === null ? null : { type: "not", filter: result }),
69
+ not: (result) => (result === null ? null : ({ type: "not", filter: result } as R)),
62
70
  });
63
71
  }