@platforma-sdk/workflow-tengo 5.3.5 → 5.3.7

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 (50) hide show
  1. package/dist/index.cjs +3 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +7 -23
  5. package/dist/tengo/lib/pframes.util.lib.tengo +2 -2
  6. package/dist/tengo/lib/pframes.xsv-import-file.lib.tengo +10 -0
  7. package/dist/tengo/lib/pframes.xsv-import-pt.lib.tengo +234 -0
  8. package/dist/tengo/lib/pframes.xsv.lib.tengo +8 -81
  9. package/dist/tengo/lib/pt.expression.lib.tengo +85 -2
  10. package/dist/tengo/lib/pt.lib.tengo +98 -73
  11. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  12. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  13. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  14. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  15. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  16. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  17. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  18. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  19. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  20. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  21. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  22. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  23. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  24. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  25. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  26. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  27. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  28. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  29. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  30. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  31. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  32. package/dist/tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz +0 -0
  33. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  35. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  37. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  38. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  44. package/dist/tengo/tpl/pt.import-dir.plj.gz +0 -0
  45. package/dist/tengo/tpl/pt.workflow-run.plj.gz +0 -0
  46. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  47. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  48. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  49. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  50. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  module.exports = { Templates: {
2
2
  'll.get-field': { type: 'from-file', path: require.resolve('./tengo/tpl/ll.get-field.plj.gz') },
3
+ 'pframes.export-parquet-pcolumn-chunk': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz') },
3
4
  'pframes.export-single-pcolumn': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-single-pcolumn.plj.gz') },
4
5
  'pframes.import-dir': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.import-dir.plj.gz') },
5
6
  'pframes.remove-nulls': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.remove-nulls.plj.gz') },
@@ -28,10 +29,11 @@ module.exports = { Templates: {
28
29
  'pframes.parse-to-json': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.parse-to-json.plj.gz') },
29
30
  'exec.load-software.impl': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.load-software.impl.plj.gz') },
30
31
  'exec.exec': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.exec.plj.gz') },
31
- 'pframes.xsv-import-if-not-null': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
32
32
  'pt.workflow-run': { type: 'from-file', path: require.resolve('./tengo/tpl/pt.workflow-run.plj.gz') },
33
33
  'check_network.run_hello_world': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world.plj.gz') },
34
34
  'check_network.run_hello_world_py': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world_py.plj.gz') },
35
+ 'pframes.xsv-export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
36
+ 'pframes.xsv-import-if-not-null': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
35
37
  'pframes.xsv-import-map': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-map.plj.gz') },
36
38
  'pframes.process-pcolumn-data': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
37
39
  'pframes.export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-pframe.plj.gz') },
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  declare type TemplateFromFile = { readonly type: "from-file"; readonly path: string; };
2
- declare type TplName = "ll.get-field" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-anchored" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "pframes.slice-data" | "check_network.create_workdir_from_storage" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.R.create-renv" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.parse-to-json" | "exec.load-software.impl" | "exec.exec" | "pframes.xsv-import-if-not-null" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.xsv-import-map" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
2
+ declare type TplName = "ll.get-field" | "pframes.export-parquet-pcolumn-chunk" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-anchored" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "pframes.slice-data" | "check_network.create_workdir_from_storage" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.R.create-renv" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.parse-to-json" | "exec.load-software.impl" | "exec.exec" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
3
3
  declare const Templates: Record<TplName, TemplateFromFile>;
4
4
  export { Templates };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  export const Templates = {
3
3
  'll.get-field': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/ll.get-field.plj.gz') },
4
+ 'pframes.export-parquet-pcolumn-chunk': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz') },
4
5
  'pframes.export-single-pcolumn': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-single-pcolumn.plj.gz') },
5
6
  'pframes.import-dir': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.import-dir.plj.gz') },
6
7
  'pframes.remove-nulls': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.remove-nulls.plj.gz') },
@@ -29,10 +30,11 @@ export const Templates = {
29
30
  'pframes.parse-to-json': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.parse-to-json.plj.gz') },
30
31
  'exec.load-software.impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.load-software.impl.plj.gz') },
31
32
  'exec.exec': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.exec.plj.gz') },
32
- 'pframes.xsv-import-if-not-null': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
33
33
  'pt.workflow-run': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pt.workflow-run.plj.gz') },
34
34
  'check_network.run_hello_world': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world.plj.gz') },
35
35
  'check_network.run_hello_world_py': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world_py.plj.gz') },
36
+ 'pframes.xsv-export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
37
+ 'pframes.xsv-import-if-not-null': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
36
38
  'pframes.xsv-import-map': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-map.plj.gz') },
37
39
  'pframes.process-pcolumn-data': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
38
40
  'pframes.export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-pframe.plj.gz') },
@@ -9,6 +9,8 @@ json := import("json")
9
9
 
10
10
  nestedTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.export-single-pcolumn")
11
11
 
12
+ parquetChunkTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.export-parquet-pcolumn-chunk")
13
+
12
14
 
13
15
  exportPColumnData := func(data) {
14
16
  ll.assert(data.info().InputsLocked, "column data resource not locked for export: %v", data.info().Type)
@@ -41,29 +43,11 @@ exportPColumnData := func(data) {
41
43
 
42
44
  newData := smart.structBuilder(data.info().Type, data.info().Data)
43
45
  for key, partData in data.inputs() {
44
- partDataResource := partData.getValue()
45
- ll.assert(
46
- partDataResource.checkResourceType(pConstants.RTYPE_PARQUET_CHUNK),
47
- "expected resource %v for axis key %v, got %v",
48
- pConstants.RTYPE_PARQUET_CHUNK,
49
- key,
50
- partDataResource.info().Type
51
- )
52
- ll.assert(partDataResource.info().InputsLocked, "resource %v inputs not locked", partDataResource.info().Type)
53
-
54
-
55
-
56
-
57
- chunkMetadata := partDataResource.getDataAsJson()
58
- chunkMetadata.isDownloadable = true
59
-
60
- parquetChunkResource := smart.structBuilder(partDataResource.info().Type, json.encode(chunkMetadata))
61
-
62
- partDataFields := partDataResource.metaInputs()
63
- parquetChunkResource.createMetaField("blob").set(file.exportFile(partDataFields["blob"]))
64
- parquetChunkResource.createMetaField("mapping").set(partDataFields["mapping"])
65
-
66
- newData.createInputField(key).set(parquetChunkResource.lockAndBuild())
46
+ exportedDataPart := render.createEphemeral(parquetChunkTpl, {
47
+ key: key,
48
+ data: partData
49
+ }).output("result")
50
+ newData.createInputField(key).set(exportedDataPart)
67
51
  }
68
52
  return newData.lockAndBuild()
69
53
 
@@ -177,7 +177,7 @@ PFCONV_IMPORT_CFG_SCHEMA := {
177
177
  `columnNamePrefix,?`: `string`,
178
178
 
179
179
 
180
- `storageFormat,?` : `string,regex=Binary|Json`,
180
+ `storageFormat,?` : `string,regex=Binary|Json|Parquet`,
181
181
 
182
182
 
183
183
  `partitionKeyLength,?` : `number`
@@ -468,7 +468,7 @@ addColumnToWd := func(name, spec, data, execBuilder, ops) {
468
468
  vt := spec.valueType
469
469
  ll.assert(vt == "Int" || vt == "Long" || vt == "Float" || vt == "Double" || vt == "String",
470
470
  "Unsupported value type: ", vt, spec)
471
-
471
+
472
472
  ll.assert(!is_undefined(data) && smart.isResource(data), "data must be a resource, got: %v", data)
473
473
 
474
474
  ll.assert(!is_undefined(execBuilder), "execBuilder must be defined")
@@ -7,6 +7,7 @@ util := import("@platforma-sdk/workflow-tengo:pframes.util")
7
7
  json := import("json")
8
8
  constants := import("@platforma-sdk/workflow-tengo:pframes.constants")
9
9
  execConstants := import("@platforma-sdk/workflow-tengo:exec.constants")
10
+ xsvImportPt := import("@platforma-sdk/workflow-tengo:pframes.xsv-import-pt")
10
11
 
11
12
  transposeXsvSw := assets.importSoftware("@platforma-open/milaboratories.software-small-binaries:table-converter")
12
13
  pfconvSw := assets.importSoftware("@milaboratories/software-pframes-conv:main")
@@ -95,6 +96,11 @@ importFile := func(xsvFile, xsvType, spec, ...ops) {
95
96
  len(spec.axes)
96
97
  )
97
98
 
99
+ storageFormat := spec.storageFormat
100
+ if is_undefined(storageFormat) {
101
+ storageFormat = "Binary"
102
+ }
103
+
98
104
  xsvFileName := "file." + xsvType
99
105
 
100
106
  ops := renderOps({ dataOnly: false, splitDataAndSpec: false, queue: execConstants.LIGHT_QUEUE, inputCache: undefined }, ops...)
@@ -161,6 +167,10 @@ importFile := func(xsvFile, xsvType, spec, ...ops) {
161
167
  xsvFile = transposeXsv.getFile(transposedFileName)
162
168
  }
163
169
 
170
+ if storageFormat == "Parquet" {
171
+ return xsvImportPt.importFileParquet(xsvFile, xsvType, spec, ops)
172
+ }
173
+
164
174
 
165
175
  pureSpec := util.purifySpec(spec)
166
176
 
@@ -0,0 +1,234 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ validation := import("@platforma-sdk/workflow-tengo:validation")
3
+ objects := import("@platforma-sdk/workflow-tengo:objects")
4
+ util := import("@platforma-sdk/workflow-tengo:pframes.util")
5
+ pt := import("@platforma-sdk/workflow-tengo:pt")
6
+ execConstants := import("@platforma-sdk/workflow-tengo:exec.constants")
7
+ maps := import("@platforma-sdk/workflow-tengo:maps")
8
+ slices := import("@platforma-sdk/workflow-tengo:slices")
9
+ smart := import("@platforma-sdk/workflow-tengo:smart")
10
+
11
+ getColumnSpec := func(axesSpec, col) {
12
+ spec := maps.deepMerge(col.spec, {
13
+ kind: "PColumn",
14
+ axesSpec: axesSpec
15
+ })
16
+ return objects.deleteUndefined(spec)
17
+ }
18
+
19
+ applyPreProcessSteps := func(colExpr, preProcessSteps) {
20
+ result := colExpr
21
+ if !is_undefined(preProcessSteps) {
22
+ for step in preProcessSteps {
23
+ if step.type == "regexpExtract" {
24
+ result = result.extractEcmaRegex(step.pattern)
25
+ } else if step.type == "regexpReplace" {
26
+ result = result.replaceEcmaRegex(step.pattern, step.replacement)
27
+ } else {
28
+ ll.panic("unknown preProcess step type: %v", step.type)
29
+ }
30
+ }
31
+ }
32
+ return result
33
+ }
34
+
35
+ applyNaRegex := func(colExpr, naRegex, colType) {
36
+ if is_undefined(naRegex) {
37
+ return colExpr.cast(colType)
38
+ }
39
+ return pt.when(colExpr.matchesEcmaRegex(naRegex)).
40
+ then(pt.lit(undefined).cast(colType)).
41
+ otherwise(colExpr.cast(colType))
42
+ }
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+ importFileParquet := func(xsvFile, xsvType, spec, ops) {
62
+ ll.assert(xsvType == "csv" || xsvType == "tsv", "unknown xsv type: %v", xsvType)
63
+
64
+ validation.assertType(spec, util.PFCONV_IMPORT_CFG_SCHEMA)
65
+
66
+
67
+ ll.assert(is_undefined(spec.index), "index axis is not supported with Parquet storage format")
68
+
69
+ ll.assert(is_undefined(spec.allowArtificialColumns), "allowArtificialColumns are not supported with Parquet storage format")
70
+
71
+ if !is_undefined(spec.separator) {
72
+ expectedSeparator := ","
73
+ if xsvType == "tsv" {
74
+ expectedSeparator = "\t"
75
+ }
76
+ ll.assert(spec.separator == expectedSeparator,
77
+ "separator does not match xsvType %v, expected '%v', got '%v'",
78
+ xsvType, expectedSeparator, spec.separator)
79
+ }
80
+
81
+ wf := pt.workflow()
82
+ if !is_undefined(ops.queue) {
83
+ if ops.queue == execConstants.HEAVY_QUEUE {
84
+ wf.inHeavyQueue()
85
+ } else if ops.queue == execConstants.MEDIUM_QUEUE {
86
+ wf.inMediumQueue()
87
+ } else if ops.queue == execConstants.LIGHT_QUEUE {
88
+ wf.inLightQueue()
89
+ } else if ops.queue == execConstants.UI_TASKS_QUEUE {
90
+ wf.inUiQueue()
91
+ } else {
92
+ ll.panic("Unsupported queue: %v", ops.queue)
93
+ }
94
+ }
95
+ if !is_undefined(ops.cpu) {
96
+ wf.cpu(ops.cpu)
97
+ }
98
+ if !is_undefined(ops.mem) {
99
+ wf.mem(ops.mem)
100
+ }
101
+ if !is_undefined(ops.inputCache) {
102
+ wf.cacheInputs(ops.inputCache)
103
+ }
104
+
105
+ schema := []
106
+ for ax in spec.axes {
107
+ schema = append(schema, {
108
+ column: ax.column,
109
+ type: "String"
110
+ })
111
+ }
112
+ for col in spec.columns {
113
+ schema = append(schema, {
114
+ column: col.column,
115
+ type: "String"
116
+ })
117
+ }
118
+ df := wf.frame(xsvFile, {
119
+ xsvType: xsvType,
120
+ ignoreErrors: spec.skipEmptyLines == true,
121
+ commentPrefix: spec.commentLinePrefix,
122
+ schema: schema
123
+ })
124
+
125
+ allColumns := []
126
+ for ax in spec.axes {
127
+ allColumns = append(allColumns, ax.column)
128
+ }
129
+ for col in spec.columns {
130
+ allColumns = append(allColumns, col.column)
131
+ }
132
+ df = df.select(allColumns...)
133
+
134
+ filterConditions := []
135
+ for ax in spec.axes {
136
+ if !is_undefined(ax.filterOutRegex) {
137
+ filterConditions = append(filterConditions, pt.col(ax.column).matchesEcmaRegex(ax.filterOutRegex).not())
138
+ }
139
+ }
140
+ for col in spec.columns {
141
+ if !is_undefined(col.filterOutRegex) {
142
+ filterConditions = append(filterConditions, pt.col(col.column).matchesEcmaRegex(col.filterOutRegex).not())
143
+ }
144
+ }
145
+ if len(filterConditions) > 0 {
146
+ df = df.filter(pt.and(filterConditions...))
147
+ }
148
+
149
+ projection := []
150
+ for ax in spec.axes {
151
+ colExpr := pt.col(ax.column)
152
+ colExpr = applyPreProcessSteps(colExpr, ax.preProcess)
153
+ colExpr = applyNaRegex(colExpr, ax.naRegex, ax.spec.type)
154
+ projection = append(projection, colExpr.alias(ax.column))
155
+ }
156
+ for col in spec.columns {
157
+ colExpr := pt.col(col.column)
158
+ colExpr = applyPreProcessSteps(colExpr, col.preProcess)
159
+ colExpr = applyNaRegex(colExpr, col.naRegex, col.spec.valueType)
160
+ projection = append(projection, colExpr.alias(col.column))
161
+ }
162
+ df = df.withColumns(projection...)
163
+
164
+ notNullConditions := []
165
+
166
+
167
+ for col in spec.columns {
168
+ if col.allowNA == false {
169
+
170
+ notNullConditions = append(notNullConditions, pt.col(col.column).isNotNull())
171
+ }
172
+ }
173
+ if len(notNullConditions) > 0 {
174
+ df = df.filter(pt.and(notNullConditions...))
175
+ }
176
+
177
+ saveParams := {
178
+ axes: [],
179
+ columns: [],
180
+ partitionKeyLength: 0
181
+ }
182
+ if !is_undefined(spec.partitionKeyLength) {
183
+ saveParams.partitionKeyLength = spec.partitionKeyLength
184
+ }
185
+ for ax in spec.axes {
186
+ saveParams.axes = append(saveParams.axes, {
187
+ column: ax.column,
188
+ spec: maps.deepMerge({ name: ax.column }, ax.spec)
189
+ })
190
+ }
191
+ for col in spec.columns {
192
+ saveParams.columns = append(saveParams.columns, {
193
+ column: col.column,
194
+ spec: maps.deepMerge({ name: col.column }, col.spec)
195
+ })
196
+ }
197
+
198
+ frameName := "xsv_import_output"
199
+ df.saveFrameDirect(frameName, saveParams)
200
+ pf := wf.run().getFrameDirect(frameName)
201
+
202
+ if ops.dataOnly {
203
+ pfd := smart.mapBuilder()
204
+ for col in spec.columns {
205
+ id := util.xsvColumnId(col)
206
+ field := pf.getFutureInputField(col.column + ".data")
207
+ pfd.createInputField(id).set(field)
208
+ }
209
+ return pfd.lockAndBuild()
210
+ }
211
+
212
+ result := {}
213
+ axesSpec := slices.map(spec.axes, func(ax) { return ax.spec })
214
+ for col in spec.columns {
215
+ colSpec := getColumnSpec(axesSpec, col)
216
+ data := pf.getFutureInputField(col.column + ".data")
217
+
218
+ id := util.xsvColumnId(col)
219
+ if ops.splitDataAndSpec {
220
+ result[id] = {
221
+ spec: colSpec,
222
+ data: data
223
+ }
224
+ } else {
225
+ result[id + ".spec"] = colSpec
226
+ result[id + ".data"] = data
227
+ }
228
+ }
229
+ return result
230
+ }
231
+
232
+ export ll.toStrict({
233
+ importFileParquet: importFileParquet
234
+ })
@@ -10,15 +10,10 @@ render := import("@platforma-sdk/workflow-tengo:render")
10
10
  maps := import("@platforma-sdk/workflow-tengo:maps")
11
11
  util := import("@platforma-sdk/workflow-tengo:pframes.util")
12
12
  pBuilder := import("@platforma-sdk/workflow-tengo:pframes.builder")
13
- pframesSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
14
13
  ll := import("@platforma-sdk/workflow-tengo:ll")
15
14
  assets := import("@platforma-sdk/workflow-tengo:assets")
16
15
  xsvIF := import("@platforma-sdk/workflow-tengo:pframes.xsv-import-file")
17
16
  text := import("text")
18
- execConstants := import("@platforma-sdk/workflow-tengo:exec.constants")
19
- pt := import("@platforma-sdk/workflow-tengo:pt")
20
- slices := import("@platforma-sdk/workflow-tengo:slices")
21
- canonical := import("@platforma-sdk/workflow-tengo:canonical")
22
17
 
23
18
  importXsvIfNullTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.xsv-import-if-not-null")
24
19
 
@@ -304,84 +299,16 @@ exportFrame := func(pf, xsvType, params, ...ops) {
304
299
  ll.panic("Unexpected p-frame format: %s", pf)
305
300
  }
306
301
 
307
- resultFile := "result." + xsvType
308
-
309
- columnEntries := []
310
- rawColumns := []
311
- maps.forEach(util.pFrameToColumnsMap(pf), func(frameUniqueColumnId, columnInfo) {
312
- columnEntries = append(columnEntries, pt.p.column(frameUniqueColumnId, columnInfo))
313
- rawColumns = append(rawColumns, { id: frameUniqueColumnId, spec: columnInfo.spec })
302
+ exportPfTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.xsv-export-pframe")
303
+ r := render.create(exportPfTpl, {
304
+ pf: pf,
305
+ xsvType: xsvType,
306
+ params: partialParams
307
+ }, {
308
+ metaInputs: objects.deleteUndefined(runMetaInputs)
314
309
  })
315
310
 
316
- frameInput := undefined
317
- if params.joinType == "Inner" {
318
- frameInput = pt.p.inner(columnEntries...)
319
- } else {
320
- frameInput = pt.p.full(columnEntries...)
321
- }
322
-
323
- numericSelector := pt.sc.numeric()
324
- if is_string(params.naStr) {
325
- numericSelector = numericSelector.fillNull(params.naStr)
326
- }
327
- stringSelector := pt.sc.string()
328
- if is_string(params.nullStr) {
329
- stringSelector = stringSelector.fillNull(params.nullStr)
330
- }
331
-
332
- specDistiller := pframesSpec.createSpecDistiller(slices.map(rawColumns, func(c) { return c.spec }))
333
- canonicalMap := {}
334
- uniqueAxes := []
335
- for column in rawColumns {
336
- for axis in column.spec.axesSpec {
337
- distilledAxis := specDistiller.distill(axis)
338
- canonicalizedAxisSpec := canonical.encode(distilledAxis)
339
- if is_undefined(canonicalMap[canonicalizedAxisSpec]) {
340
- canonicalMap[canonicalizedAxisSpec] = true
341
- distilledAxis.label = axis.annotations["pl7.app/label"]
342
- uniqueAxes = append(uniqueAxes, distilledAxis)
343
- }
344
- }
345
- }
346
-
347
- columnSelectors := []
348
- for axis in uniqueAxes {
349
- label := axis.label
350
- delete(axis, "label")
351
- columnSelectors = append(columnSelectors, pt.sc.axis(axis).alias(label))
352
- }
353
- for column in rawColumns {
354
- columnSelectors = append(columnSelectors, pt.col(column.id).alias(column.spec.annotations["pl7.app/label"]))
355
- }
356
-
357
- wf := pt.workflow()
358
- if runMetaInputs.queue == execConstants.LIGHT_QUEUE {
359
- wf = wf.inLightQueue()
360
- } else if runMetaInputs.queue == execConstants.MEDIUM_QUEUE {
361
- wf = wf.inMediumQueue()
362
- } else if runMetaInputs.queue == execConstants.HEAVY_QUEUE {
363
- wf = wf.inHeavyQueue()
364
- } else if runMetaInputs.queue == execConstants.UI_TASKS_QUEUE {
365
- wf = wf.inUiQueue()
366
- } else if !is_undefined(runMetaInputs.queue) {
367
- ll.panic("Unsupported queue: %v", runMetaInputs.queue)
368
- }
369
- if !is_undefined(runMetaInputs.cpu) {
370
- wf = wf.cpu(runMetaInputs.cpu)
371
- }
372
- if !is_undefined(runMetaInputs.mem) {
373
- wf = wf.mem(runMetaInputs.mem)
374
- }
375
- if !is_undefined(runMetaInputs.inputCache) {
376
- wf = wf.cacheInputs(runMetaInputs.inputCache)
377
- }
378
- wf.frame(frameInput).
379
- select(numericSelector, stringSelector).
380
- select(columnSelectors...).
381
- save(resultFile)
382
-
383
- result := wf.run()
384
- return result.getFile(resultFile)
311
+ return r.output("result")
385
312
  }
386
313
 
387
314
  export ll.toStrict({
@@ -110,9 +110,11 @@ _newWindowExpression = func(expression, aggregation, currentAlias) {
110
110
  _getExpression: func(ctx) {
111
111
  ll.assert(is_map(ctx), "ctx must be a map")
112
112
  ll.assert(!is_undefined(expression), "Using this operation as a window expression is not allowed. Use it as a group by expression instead. %v", aggregation)
113
- ll.assert(!is_undefined(currentAlias), "Alias is not defined for this window expression.")
114
113
 
115
114
  valueExpr := expression(ctx)
115
+ if is_undefined(currentAlias) {
116
+ return valueExpr
117
+ }
116
118
  return {type: "alias", value: valueExpr, name: currentAlias}
117
119
  },
118
120
 
@@ -979,7 +981,11 @@ _newExpression = func(expression) {
979
981
 
980
982
 
981
983
  matchesEcmaRegex: func(ecmaRegex) {
982
- ll.assert(is_string(ecmaRegex) && len(ecmaRegex) > 0, "ecma_regex must be a non-empty string.")
984
+ ll.assert(is_string(ecmaRegex), "ecma_regex must be a string.")
985
+
986
+ if len(ecmaRegex) == 0 {
987
+ return self.eq("")
988
+ }
983
989
 
984
990
  return _newExpression(func(ctx) {
985
991
  valueExpr := self._getExpression(ctx)
@@ -1030,6 +1036,83 @@ _newExpression = func(expression) {
1030
1036
  substitutionsOnly: substitutionsOnly
1031
1037
  })
1032
1038
  })
1039
+ },
1040
+
1041
+
1042
+
1043
+
1044
+
1045
+
1046
+
1047
+
1048
+
1049
+
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1057
+
1058
+
1059
+ replaceEcmaRegex: func(ecmaRegex, replacement) {
1060
+ ll.assert(is_string(ecmaRegex) && len(ecmaRegex) > 0, "ecmaRegex must be a non-empty string.")
1061
+ ll.assert(is_string(replacement), "replacement must be a string.")
1062
+
1063
+ return _newExpression(func(ctx) {
1064
+ valueExpr := self._getExpression(ctx)
1065
+ return {
1066
+ type: "replace_ecma_regex",
1067
+ value: valueExpr,
1068
+ ecmaRegex: ecmaRegex,
1069
+ replacement: replacement
1070
+ }
1071
+ })
1072
+ },
1073
+
1074
+
1075
+
1076
+
1077
+
1078
+
1079
+
1080
+
1081
+
1082
+
1083
+
1084
+
1085
+
1086
+
1087
+
1088
+
1089
+
1090
+
1091
+
1092
+
1093
+
1094
+
1095
+
1096
+
1097
+
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
1104
+
1105
+ extractEcmaRegex: func(ecmaRegex) {
1106
+ ll.assert(is_string(ecmaRegex) && len(ecmaRegex) > 0, "ecmaRegex must be a non-empty string.")
1107
+
1108
+ return _newExpression(func(ctx) {
1109
+ valueExpr := self._getExpression(ctx)
1110
+ return {
1111
+ type: "extract_ecma_regex",
1112
+ value: valueExpr,
1113
+ ecmaRegex: ecmaRegex
1114
+ }
1115
+ })
1033
1116
  }
1034
1117
  })
1035
1118
  return self
@@ -83,6 +83,8 @@ _isPEntry := func(entry) {
83
83
  return ll.isMap(entry) && ll.methodExists(entry, "_getColumnsMap") && ll.methodExists(entry, "_getJoinTree");
84
84
  }
85
85
 
86
+ _frameToPEntries := undefined
87
+
86
88
  slicedColumnCounter := 0
87
89
 
88
90
  p := ll.toStrict({
@@ -111,7 +113,7 @@ p := ll.toStrict({
111
113
  slicedColumn: func(name, columnMap, axisFilters) {
112
114
  _validateColumn(name, columnMap)
113
115
  util.validateAxisFilters(axisFilters)
114
-
116
+
115
117
  slicedColumnCounter += 1
116
118
  oldName := "p_sliced_" + slicedColumnCounter
117
119
 
@@ -131,31 +133,35 @@ p := ll.toStrict({
131
133
 
132
134
  inner: func(...entries) {
133
135
  ll.assert(len(entries) > 0, "inner join requires at least one entry")
134
-
136
+
137
+ if len(entries) == 1 && !_isPEntry(entries[0]) {
138
+ entries = _frameToPEntries(entries[0])
139
+ }
140
+
135
141
  mergedColMap := {}
136
142
  joinTreeEntries := []
137
-
143
+
138
144
  for entry in entries {
139
145
  ll.assert(_isPEntry(entry), "all entries must be PEntry objects")
140
-
146
+
141
147
  entryColMap := entry._getColumnsMap()
142
148
  entryJoinTree := entry._getJoinTree()
143
-
149
+
144
150
  for colName, colInfo in entryColMap {
145
151
  if !is_undefined(mergedColMap[colName]) {
146
152
  ll.panic("column name %s is already used, please pick a different name", colName)
147
153
  }
148
154
  mergedColMap[colName] = colInfo
149
155
  }
150
-
156
+
151
157
  joinTreeEntries = append(joinTreeEntries, entryJoinTree)
152
158
  }
153
-
159
+
154
160
  joinTree := {
155
161
  type: "inner",
156
162
  entries: joinTreeEntries
157
163
  }
158
-
164
+
159
165
  return _newPEntry(mergedColMap, joinTree)
160
166
  },
161
167
 
@@ -165,31 +171,35 @@ p := ll.toStrict({
165
171
 
166
172
  full: func(...entries) {
167
173
  ll.assert(len(entries) > 0, "full join requires at least one entry")
168
-
174
+
175
+ if len(entries) == 1 && !_isPEntry(entries[0]) {
176
+ entries = _frameToPEntries(entries[0])
177
+ }
178
+
169
179
  mergedColMap := {}
170
180
  joinTreeEntries := []
171
-
181
+
172
182
  for entry in entries {
173
183
  ll.assert(_isPEntry(entry), "all entries must be PEntry objects")
174
-
184
+
175
185
  entryColMap := entry._getColumnsMap()
176
186
  entryJoinTree := entry._getJoinTree()
177
-
187
+
178
188
  for colName, colInfo in entryColMap {
179
189
  if !is_undefined(mergedColMap[colName]) {
180
190
  ll.panic("column name %s is already used, please pick a different name", colName)
181
191
  }
182
192
  mergedColMap[colName] = colInfo
183
193
  }
184
-
194
+
185
195
  joinTreeEntries = append(joinTreeEntries, entryJoinTree)
186
196
  }
187
-
197
+
188
198
  joinTree := {
189
199
  type: "full",
190
200
  entries: joinTreeEntries
191
201
  }
192
-
202
+
193
203
  return _newPEntry(mergedColMap, joinTree)
194
204
  },
195
205
 
@@ -201,44 +211,75 @@ p := ll.toStrict({
201
211
  outer: func(primary, ...secondary) {
202
212
  ll.assert(_isPEntry(primary), "primary entry must be a PEntry object")
203
213
  ll.assert(len(secondary) > 0, "outer join requires at least one secondary entry")
204
-
214
+
205
215
  mergedColMap := {}
206
-
216
+
207
217
  primaryColMap := primary._getColumnsMap()
208
218
  primaryJoinTree := primary._getJoinTree()
209
-
219
+
210
220
  for colName, colInfo in primaryColMap {
211
221
  mergedColMap[colName] = colInfo
212
222
  }
213
-
223
+
214
224
  secondaryJoinTrees := []
215
-
225
+
216
226
  for entry in secondary {
217
227
  ll.assert(_isPEntry(entry), "all secondary entries must be PEntry objects")
218
-
228
+
219
229
  entryColMap := entry._getColumnsMap()
220
230
  entryJoinTree := entry._getJoinTree()
221
-
231
+
222
232
  for colName, colInfo in entryColMap {
223
233
  if !is_undefined(mergedColMap[colName]) {
224
234
  ll.panic("column name %s is already used, please pick a different name", colName)
225
235
  }
226
236
  mergedColMap[colName] = colInfo
227
237
  }
228
-
238
+
229
239
  secondaryJoinTrees = append(secondaryJoinTrees, entryJoinTree)
230
240
  }
231
-
241
+
232
242
  joinTree := {
233
243
  type: "outer",
234
244
  primary: primaryJoinTree,
235
245
  secondary: secondaryJoinTrees
236
246
  }
237
-
247
+
238
248
  return _newPEntry(mergedColMap, joinTree)
239
249
  }
240
250
  })
241
251
 
252
+ _frameToPEntries = func(frameInput) {
253
+ frameResource := undefined
254
+ if pframesUtil.isFlatPfMap(frameInput) {
255
+ frameResource = frameInput
256
+ } else if pframesUtil.isStructuredPfMap(frameInput) {
257
+ pfBuilder := pframesBuilder.pFrameBuilder()
258
+ for key, value in frameInput {
259
+ pfBuilder.add(key, value.spec, value.data)
260
+ }
261
+ frameResource = pfBuilder.build()
262
+ } else if smart.isReference(frameInput) {
263
+ if smart.isField(frameInput) {
264
+ ll.assert(frameInput.isSet(), "frameInput must be set")
265
+ frameResource = frameInput.getValue()
266
+ } else {
267
+ frameResource = frameInput
268
+ }
269
+ ll.assert(frameResource.checkResourceType(pframesConstants.RTYPE_P_FRAME), "frameResource must be a PFrame resource")
270
+ } else {
271
+ ll.panic("unsupported frameInput type: %v", frameInput)
272
+ }
273
+
274
+ columnsMap := pframesUtil.pFrameToColumnsMap(frameResource)
275
+ columnEntries := []
276
+ maps.forEach(columnsMap, func(frameUniqueColumnId, columnInfo) {
277
+ columnEntry := p.column(frameUniqueColumnId, columnInfo)
278
+ columnEntries = append(columnEntries, columnEntry)
279
+ })
280
+ return columnEntries
281
+ }
282
+
242
283
 
243
284
  _detectFormatFromFileName := func(fileName) {
244
285
  if text.has_suffix(fileName, ".csv") {
@@ -360,11 +401,12 @@ workflow := func() {
360
401
 
361
402
 
362
403
 
404
+
363
405
 
364
406
 
365
407
  frame: func(frameInput, ...optionsRaw) {
366
408
  ll.assert(!is_undefined(frameInput), "frameInput must be provided")
367
-
409
+
368
410
  opts := {}
369
411
  if len(optionsRaw) > 0 {
370
412
  if len(optionsRaw) == 1 && is_map(optionsRaw[0]) {
@@ -374,7 +416,6 @@ workflow := func() {
374
416
  }
375
417
  }
376
418
 
377
- frameResource := undefined
378
419
  fileRef := undefined
379
420
  fileContent := undefined
380
421
  inputSchema := undefined
@@ -386,6 +427,7 @@ workflow := func() {
386
427
  optionsSchema := undefined
387
428
  nRowsOpt := undefined
388
429
  ignoreErrorsOpt := undefined
430
+ commentPrefixOpt := undefined
389
431
 
390
432
  if is_map(frameInput) && !is_undefined(frameInput.file) {
391
433
  fileRef = frameInput.file
@@ -400,28 +442,9 @@ workflow := func() {
400
442
 
401
443
  finalFormat = inputXsvType
402
444
  } else if smart.isReference(frameInput) {
403
- frameInputResource := frameInput
404
- if smart.isField(frameInputResource) {
405
- frameInputResource = frameInputResource.getValue()
406
- }
407
- if frameInputResource.checkResourceType(pframesConstants.RTYPE_P_FRAME) {
408
- frameResource = frameInputResource
409
- finalFormat = "pframe"
410
- } else {
411
- fileRef = frameInput
412
- }
445
+ fileRef = frameInput
413
446
  } else if is_string(frameInput) || is_bytes(frameInput) {
414
447
  fileContent = frameInput
415
- } else if pframesUtil.isFlatPfMap(frameInput) {
416
- frameResource = frameInput
417
- finalFormat = "pframe"
418
- } else if pframesUtil.isStructuredPfMap(frameInput) {
419
- pfBuilder := pframesBuilder.pFrameBuilder()
420
- for key, value in frameInput {
421
- pfBuilder.add(key, value.spec, value.data)
422
- }
423
- frameResource = pfBuilder.build()
424
- finalFormat = "pframe"
425
448
  } else if _isPEntry(frameInput) {
426
449
  finalFormat = "pentry"
427
450
  } else {
@@ -498,6 +521,13 @@ workflow := func() {
498
521
  ignoreErrorsOpt = opts.ignoreErrors
499
522
  }
500
523
 
524
+ if !is_undefined(opts.commentPrefix) {
525
+ if !is_string(opts.commentPrefix) {
526
+ ll.panic("'commentPrefix' option must be a string. Got: %T", opts.commentPrefix)
527
+ }
528
+ commentPrefixOpt = opts.commentPrefix
529
+ }
530
+
501
531
  if !is_undefined(fileRef) {
502
532
  ll.assert(
503
533
  smart.isReference(fileRef),
@@ -511,17 +541,7 @@ workflow := func() {
511
541
 
512
542
 
513
543
  step := undefined
514
- if finalFormat == "pentry" || finalFormat == "pframe" {
515
- if finalFormat == "pframe" {
516
- columnsMap := pframesUtil.pFrameToColumnsMap(frameResource)
517
- columnEntries := []
518
- for frameUniqueColumnId, columnInfo in columnsMap {
519
- columnEntry := p.column(frameUniqueColumnId, columnInfo)
520
- columnEntries = append(columnEntries, columnEntry)
521
- }
522
- frameInput = p.full(columnEntries...)
523
- }
524
-
544
+ if finalFormat == "pentry" {
525
545
  columnsMap := frameInput._getColumnsMap()
526
546
  src := frameInput._getJoinTree()
527
547
 
@@ -577,13 +597,17 @@ workflow := func() {
577
597
  name: finalDataFrameId
578
598
  }
579
599
 
580
- if finalFormat == "csv" {
581
- step.delimiter = ","
582
- } else if finalFormat == "tsv" {
583
- step.delimiter = "\t"
584
- }
600
+ if finalFormat == "csv" {
601
+ step.delimiter = ","
602
+ } else if finalFormat == "tsv" {
603
+ step.delimiter = "\t"
585
604
  }
586
605
 
606
+ if !is_undefined(commentPrefixOpt) {
607
+ step.commentPrefix = commentPrefixOpt
608
+ }
609
+ }
610
+
587
611
 
588
612
  finalSchemaToUse := undefined
589
613
  if !is_undefined(optionsSchema) {
@@ -705,16 +729,17 @@ workflow := func() {
705
729
  for _, frame in inFrames {
706
730
  for _, columnInfo in frame.columns {
707
731
  for axis in columnInfo.spec.axesSpec {
708
- if is_undefined(canonicalMap[axis.name]) {
709
- canonicalMap[axis.name] = {}
732
+ distilledAxis := specDistiller.distill(axis)
733
+ if is_undefined(canonicalMap[distilledAxis.name]) {
734
+ canonicalMap[distilledAxis.name] = {}
710
735
  }
711
- canonicalizedAxisSpec := canonical.encode(axis)
712
- if is_undefined(canonicalMap[axis.name][canonicalizedAxisSpec]) {
713
- canonicalMap[axis.name][canonicalizedAxisSpec] = true
714
- if is_undefined(distilledAxesSpecs[axis.name]) {
715
- distilledAxesSpecs[axis.name] = []
736
+ canonicalizedAxisSpec := canonical.encode(distilledAxis)
737
+ if is_undefined(canonicalMap[distilledAxis.name][canonicalizedAxisSpec]) {
738
+ canonicalMap[distilledAxis.name][canonicalizedAxisSpec] = true
739
+ if is_undefined(distilledAxesSpecs[distilledAxis.name]) {
740
+ distilledAxesSpecs[distilledAxis.name] = []
716
741
  }
717
- distilledAxesSpecs[axis.name] = append(distilledAxesSpecs[axis.name], axis)
742
+ distilledAxesSpecs[distilledAxis.name] = append(distilledAxesSpecs[distilledAxis.name], distilledAxis)
718
743
  }
719
744
  }
720
745
  }
@@ -830,7 +855,7 @@ workflow := func() {
830
855
  ll.panic("inFile must have either file or content, got: %v", inFile)
831
856
  }
832
857
  }
833
-
858
+
834
859
  outFrames := {}
835
860
  for frameName, specsById in outFramesSpecs {
836
861
  columnIds := []
@@ -947,7 +972,7 @@ _newDataFrameGroupBy = func(parentWorkflow, dfName, groupByExpressions) {
947
972
  }
948
973
  return aggExpr._getAggregation(ctx)
949
974
  })
950
-
975
+
951
976
  return {
952
977
  type: "aggregate",
953
978
  inputTable: dfName,
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/workflow-tengo",
3
- "version": "5.3.5",
3
+ "version": "5.3.7",
4
4
  "description": "Tengo SDK library",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@milaboratories/software-pframes-conv": "2.2.9",
20
20
  "@platforma-open/milaboratories.software-small-binaries": "^1.15.23",
21
- "@platforma-open/milaboratories.software-ptabler": "1.12.6",
21
+ "@platforma-open/milaboratories.software-ptabler": "1.12.8",
22
22
  "@platforma-open/milaboratories.software-ptexter": "1.1.1"
23
23
  },
24
24
  "devDependencies": {