@platforma-sdk/workflow-tengo 5.13.3 → 5.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/index.cjs +2 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/tengo/lib/exec.delayed-compute-allocation.lib.tengo +3 -3
  5. package/dist/tengo/lib/exec.lib.tengo +12 -7
  6. package/dist/tengo/lib/exec.limits.lib.tengo +11 -6
  7. package/dist/tengo/lib/pframes.export-util.lib.tengo +113 -0
  8. package/dist/tengo/lib/pframes.lib.tengo +2 -0
  9. package/dist/tengo/lib/pframes.table-builder.lib.tengo +494 -0
  10. package/dist/tengo/lib/tpl.base.lib.tengo +9 -0
  11. package/dist/tengo/lib/workflow.lib.tengo +55 -7
  12. package/dist/tengo/lib/workflow.resolve-util.lib.tengo +65 -0
  13. package/dist/tengo/tpl/annotations.compute.plj.gz +0 -0
  14. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  15. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  16. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  17. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  18. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  19. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  20. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  21. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  22. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  23. package/dist/tengo/tpl/exec.conda.create-env.plj.gz +0 -0
  24. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  25. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  26. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  27. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  28. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  29. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  30. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  31. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  32. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  33. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.build-table.plj.gz +0 -0
  35. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz +0 -0
  37. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  38. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  47. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  48. package/dist/tengo/tpl/pt.import-dir.plj.gz +0 -0
  49. package/dist/tengo/tpl/pt.workflow-run.plj.gz +0 -0
  50. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  51. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  52. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  53. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  54. package/dist/tengo/tpl/workflow.query-context-filter.plj.gz +0 -0
  55. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -31,12 +31,13 @@ module.exports = { Templates: {
31
31
  'pframes.parse-to-json': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.parse-to-json.plj.gz') },
32
32
  'exec.exec': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.exec.plj.gz') },
33
33
  'pt.workflow-run': { type: 'from-file', path: require.resolve('./tengo/tpl/pt.workflow-run.plj.gz') },
34
- 'annotations.compute': { type: 'from-file', path: require.resolve('./tengo/tpl/annotations.compute.plj.gz') },
35
34
  'check_network.run_hello_world': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world.plj.gz') },
36
35
  'check_network.run_hello_world_py': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world_py.plj.gz') },
36
+ 'pframes.build-table': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.build-table.plj.gz') },
37
37
  'pframes.xsv-export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
38
38
  'pframes.xsv-import-if-not-null': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
39
39
  'pframes.xsv-import-map': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-map.plj.gz') },
40
+ 'annotations.compute': { type: 'from-file', path: require.resolve('./tengo/tpl/annotations.compute.plj.gz') },
40
41
  'pframes.process-pcolumn-data': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
41
42
  'pframes.export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-pframe.plj.gz') },
42
43
  'pframes.export-pframe-for-ui': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-pframe-for-ui.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-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-context-filter" | "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" | "workflow.query-anchored" | "check_network.create_workdir_from_storage" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.slice-data" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.load-software.impl" | "pframes.parse-to-json" | "exec.exec" | "pt.workflow-run" | "annotations.compute" | "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";
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-context-filter" | "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" | "workflow.query-anchored" | "check_network.create_workdir_from_storage" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.slice-data" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.load-software.impl" | "pframes.parse-to-json" | "exec.exec" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.build-table" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "annotations.compute" | "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
@@ -32,12 +32,13 @@ export const Templates = {
32
32
  'pframes.parse-to-json': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.parse-to-json.plj.gz') },
33
33
  'exec.exec': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.exec.plj.gz') },
34
34
  'pt.workflow-run': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pt.workflow-run.plj.gz') },
35
- 'annotations.compute': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/annotations.compute.plj.gz') },
36
35
  'check_network.run_hello_world': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world.plj.gz') },
37
36
  'check_network.run_hello_world_py': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world_py.plj.gz') },
37
+ 'pframes.build-table': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.build-table.plj.gz') },
38
38
  'pframes.xsv-export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
39
39
  'pframes.xsv-import-if-not-null': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
40
40
  'pframes.xsv-import-map': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-map.plj.gz') },
41
+ 'annotations.compute': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/annotations.compute.plj.gz') },
41
42
  'pframes.process-pcolumn-data': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
42
43
  'pframes.export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-pframe.plj.gz') },
43
44
  'pframes.export-pframe-for-ui': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-pframe-for-ui.plj.gz') }
@@ -22,7 +22,7 @@ create := func(workdir, req) {
22
22
  "queue": "string",
23
23
  "cpu,?": "number",
24
24
  "ram,?": ["or", "number", "string"],
25
- "gpu,?": "number"
25
+ "gpuMemory,?": "string"
26
26
  }, "request must be a valid request")
27
27
 
28
28
  reqMap := { queue: req.queue }
@@ -32,8 +32,8 @@ create := func(workdir, req) {
32
32
  if !is_undefined(req.ram) {
33
33
  reqMap.ram = req.ram
34
34
  }
35
- if !is_undefined(req.gpu) && req.gpu > 0 {
36
- reqMap.gpu = req.gpu
35
+ if !is_undefined(req.gpuMemory) {
36
+ reqMap.gpuMemory = req.gpuMemory
37
37
  }
38
38
 
39
39
  delayedComputeAllocation := render.createEphemeral(tpl, {
@@ -51,7 +51,7 @@ builder := func() {
51
51
  queue := execConstants.HEAVY_QUEUE
52
52
  cpuRequest := undefined
53
53
  ramRequest := undefined
54
- gpuCount := 0
54
+ gpuMemory := undefined
55
55
 
56
56
  commandName := ""
57
57
  cache := 0
@@ -443,10 +443,15 @@ builder := func() {
443
443
 
444
444
 
445
445
 
446
- gpu: func(count) {
447
- validation.assertType(count, "number", "exec.builder.gpu: count must be a number")
448
- ll.assert(count > 0, "exec.builder.gpu: count must be greater than 0")
449
- gpuCount = count
446
+
447
+ gpuMemory: func(...args) {
448
+ ll.assert(len(args) <= 1, "exec.builder.gpuMemory: accepts 0 or 1 argument")
449
+ if len(args) == 0 {
450
+ gpuMemory = "1GiB"
451
+ } else {
452
+ validation.assertType(args[0], "string", "exec.builder.gpuMemory: argument must be a size string like '16GiB'")
453
+ gpuMemory = args[0]
454
+ }
450
455
  return self
451
456
  },
452
457
 
@@ -1039,8 +1044,8 @@ builder := func() {
1039
1044
  cpu: cpuRequest,
1040
1045
  ram: ramRequest
1041
1046
  }
1042
- if gpuCount > 0 {
1043
- quotaData.gpu = gpuCount
1047
+ if !is_undefined(gpuMemory) {
1048
+ quotaData.gpuMemory = gpuMemory
1044
1049
  }
1045
1050
  metaExecInputs := {
1046
1051
  quota: smart.createJsonResource(quotaData)
@@ -105,11 +105,16 @@ quotaBuilder := func() {
105
105
 
106
106
 
107
107
 
108
- gpu: func(amount) {
109
- validation.assertType(amount, ["or", "number", "null"], "exec.quota.builder.gpu: GPU amount should be a number")
110
- ll.assert(is_undefined(amount) || amount > 0, "exec.quota.builder.gpu: amount should be greater than 0")
111
- gpu_request = amount
112
108
 
109
+ gpuMemory: func(...args) {
110
+ if len(args) == 0 {
111
+ gpu_request = "1GiB"
112
+ } else if len(args) == 1 {
113
+ validation.assertType(args[0], "string", "exec.quota.builder.gpuMemory: argument must be a size string")
114
+ gpu_request = args[0]
115
+ } else {
116
+ ll.panic("exec.quota.builder.gpuMemory: accepts 0 or 1 argument")
117
+ }
113
118
  return self
114
119
  },
115
120
 
@@ -130,8 +135,8 @@ quotaBuilder := func() {
130
135
  if !is_undefined(ram_request) {
131
136
  data.memory = ram_request
132
137
  }
133
- if !is_undefined(gpu_request) && gpu_request > 0 {
134
- data.gpu = gpu_request
138
+ if !is_undefined(gpu_request) {
139
+ data.gpuMemory = gpu_request
135
140
  }
136
141
 
137
142
  return smart.ephemeralBuilder(_RTYPE_QUOTA, json.encode(data)).lockAndBuild()
@@ -0,0 +1,113 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+ ll := import("@platforma-sdk/workflow-tengo:ll")
9
+ slices := import("@platforma-sdk/workflow-tengo:slices")
10
+ canonical := import("@platforma-sdk/workflow-tengo:canonical")
11
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
12
+ pt := import("@platforma-sdk/workflow-tengo:pt")
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+ buildExportSelectors := func(rawColumns, opts) {
48
+ axisLabel := opts.axisLabel
49
+ columnLabel := opts.columnLabel
50
+
51
+ ll.assert(is_callable(axisLabel), "opts.axisLabel must be callable")
52
+ ll.assert(is_callable(columnLabel), "opts.columnLabel must be callable")
53
+
54
+
55
+ labelOwners := {}
56
+ registerLabel := func(label, owner) {
57
+ prev := labelOwners[label]
58
+ ll.assert(is_undefined(prev),
59
+ "label '%s' is not unique (already used by %v, again by %v)",
60
+ label, prev, owner)
61
+ labelOwners[label] = owner
62
+ }
63
+
64
+
65
+
66
+ distiller := pSpec.createSpecDistiller(slices.map(rawColumns, func(c) {
67
+ return c.spec
68
+ }))
69
+
70
+
71
+
72
+
73
+ seenAxis := {}
74
+ uniqueAxes := [] // entries: { spec, label } — spec is distilled
75
+ for column in rawColumns {
76
+ for axis in column.spec.axesSpec {
77
+ distilled := distiller.distill(axis)
78
+ key := canonical.encode(distilled)
79
+ if is_undefined(seenAxis[key]) {
80
+ seenAxis[key] = true
81
+ label := axisLabel(axis)
82
+ registerLabel(label, axis.name)
83
+ uniqueAxes = append(uniqueAxes, {
84
+ spec: distilled,
85
+ label: label
86
+ })
87
+ }
88
+ }
89
+ }
90
+
91
+
92
+
93
+ sortSelectors := []
94
+ columnSelectors := []
95
+ for ua in uniqueAxes {
96
+ sortSelectors = append(sortSelectors, pt.axis(ua.spec))
97
+ columnSelectors = append(columnSelectors, pt.axis(ua.spec).alias(ua.label))
98
+ }
99
+ for column in rawColumns {
100
+ label := columnLabel(column)
101
+ registerLabel(label, column.spec.name)
102
+ columnSelectors = append(columnSelectors, pt.col(column.frameKey).alias(label))
103
+ }
104
+
105
+ return {
106
+ sortSelectors: sortSelectors,
107
+ columnSelectors: columnSelectors
108
+ }
109
+ }
110
+
111
+ export ll.toStrict({
112
+ buildExportSelectors: buildExportSelectors
113
+ })
@@ -15,6 +15,7 @@ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
15
15
  validation := import("@platforma-sdk/workflow-tengo:validation")
16
16
  constants := import("@platforma-sdk/workflow-tengo:pframes.constants")
17
17
  xsvBuilder := import("@platforma-sdk/workflow-tengo:pframes.xsv-builder")
18
+ tableBuilderMod := import("@platforma-sdk/workflow-tengo:pframes.table-builder")
18
19
  pColumnData := import("@platforma-sdk/workflow-tengo:pframes.data")
19
20
 
20
21
  llProcessTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.process-pcolumn-data")
@@ -1110,5 +1111,6 @@ export ll.toStrict({
1110
1111
  tsvFileBuilder: func() { return xsvBuilder.xsvFileBuilder("tsv") },
1111
1112
  csvFileBuilder: func() { return xsvBuilder.xsvFileBuilder("csv") },
1112
1113
  parquetFileBuilder: func() { return xsvBuilder.xsvFileBuilder("parquet") },
1114
+ tableBuilder: tableBuilderMod.tableBuilder,
1113
1115
  parseToJson: parseToJson
1114
1116
  })
@@ -0,0 +1,494 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+ ll := import("@platforma-sdk/workflow-tengo:ll")
31
+ render := import("@platforma-sdk/workflow-tengo:render")
32
+ assets := import("@platforma-sdk/workflow-tengo:assets")
33
+ maps := import("@platforma-sdk/workflow-tengo:maps")
34
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
35
+ bquery := import("@platforma-sdk/workflow-tengo:workflow.bquery")
36
+
37
+ buildTableTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.build-table")
38
+
39
+ VALID_FORMATS := {
40
+ csv: true,
41
+ tsv: true,
42
+ ndjson: true,
43
+ parquet: true
44
+ }
45
+
46
+
47
+
48
+ isUnresolved := func(value) {
49
+ stack := [value]
50
+ si := 0
51
+ for si < len(stack) {
52
+ v := stack[si]
53
+ si += 1
54
+ if ll.isMap(v) {
55
+ for k, child in v {
56
+ if k == "__isRef" && child == true { return true }
57
+ stack = append(stack, child)
58
+ }
59
+ } else if is_array(v) {
60
+ for _, child in v {
61
+ stack = append(stack, child)
62
+ }
63
+ }
64
+ }
65
+ return false
66
+ }
67
+
68
+
69
+ isResolvedColumn := func(value) {
70
+ if !ll.isMap(value) { return false }
71
+ hasSpec := false
72
+ hasData := false
73
+ for k, _ in value {
74
+ if k == "spec" { hasSpec = true }
75
+ if k == "data" { hasData = true }
76
+ }
77
+ return hasSpec && hasData
78
+ }
79
+
80
+
81
+ isPrimaryRef := func(value) {
82
+ return ll.isMap(value) && value.__isPrimaryRef == "v1"
83
+ }
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+ tableBuilder := func(format, ...ctxArg) {
97
+ ll.assert(!is_undefined(VALID_FORMATS[format]),
98
+ "format must be one of [tsv, csv, parquet, ndjson], got '%s'", format)
99
+
100
+ _ctx := undefined
101
+ if len(ctxArg) > 0 {
102
+ _ctx = ctxArg[0]
103
+ }
104
+
105
+ _primaries := []
106
+ _columns := []
107
+ _joinMode := "inner"
108
+ _axisHeaders := []
109
+ _cpu := undefined
110
+ _mem := undefined
111
+ _cacheInputs := undefined
112
+ _hasUnresolved := false
113
+
114
+ self := undefined
115
+ self = {
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+ addPrimary: func(name, value, ...opts) {
126
+ ll.assert(is_string(name), "name must be a string")
127
+ header := undefined
128
+ if len(opts) > 0 && ll.isMap(opts[0]) {
129
+ header = opts[0].header
130
+ }
131
+
132
+ unresolved := isUnresolved(value)
133
+ if !unresolved {
134
+ ll.assert(
135
+ isResolvedColumn(value) ||
136
+ (isPrimaryRef(value) && isResolvedColumn(value.column)),
137
+ "value must be a resolved column {spec, data}, " +
138
+ "a resolved PrimaryRef, or a structure containing PlRefs; got: %v",
139
+ value)
140
+ }
141
+ if unresolved {
142
+ _hasUnresolved = true
143
+ }
144
+
145
+ _primaries = append(_primaries, {
146
+ name: name,
147
+ value: value,
148
+ header: header
149
+ })
150
+ return self
151
+ },
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+ addColumn: func(query, ...opts) {
162
+ header := undefined
163
+ if len(opts) > 0 && ll.isMap(opts[0]) {
164
+ header = opts[0].header
165
+ }
166
+ if isUnresolved(query) {
167
+ _hasUnresolved = true
168
+ }
169
+ _columns = append(_columns, {
170
+ query: query,
171
+ header: header,
172
+ mode: "single"
173
+ })
174
+ return self
175
+ },
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+ addColumns: func(query, ...opts) {
186
+ headerPrefix := undefined
187
+ headerSuffix := undefined
188
+ if len(opts) > 0 && ll.isMap(opts[0]) {
189
+ headerPrefix = opts[0].headerPrefix
190
+ headerSuffix = opts[0].headerSuffix
191
+ }
192
+
193
+
194
+ if is_array(query) {
195
+ for col in query {
196
+ ll.assert(isResolvedColumn(col),
197
+ "array elements must be resolved columns {spec, data}")
198
+ _columns = append(_columns, {
199
+ query: col,
200
+ headerPrefix: headerPrefix,
201
+ headerSuffix: headerSuffix,
202
+ mode: "multi"
203
+ })
204
+ }
205
+ return self
206
+ }
207
+
208
+ if isUnresolved(query) {
209
+ _hasUnresolved = true
210
+ }
211
+ _columns = append(_columns, {
212
+ query: query,
213
+ headerPrefix: headerPrefix,
214
+ headerSuffix: headerSuffix,
215
+ mode: "multi"
216
+ })
217
+ return self
218
+ },
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+ setJoinMode: func(mode) {
227
+ ll.assert(mode == "inner" || mode == "full",
228
+ "mode must be 'inner' or 'full', got '%s'", mode)
229
+ _joinMode = mode
230
+ return self
231
+ },
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+ setAxisHeader: func(matcher, header) {
240
+ ll.assert(is_string(header), "header must be a string")
241
+ processedMatcher := undefined
242
+ if is_string(matcher) {
243
+ processedMatcher = { name: matcher }
244
+ } else if ll.isMap(matcher) {
245
+ processedMatcher = pSpec.axisSpecToMatcher(matcher)
246
+ } else {
247
+ ll.panic("matcher must be a string or AxisSpec map")
248
+ }
249
+ _axisHeaders = append(_axisHeaders, [processedMatcher, header])
250
+ return self
251
+ },
252
+
253
+
254
+
255
+
256
+ cpu: func(cores) {
257
+ _cpu = cores
258
+ return self
259
+ },
260
+
261
+
262
+
263
+
264
+
265
+ mem: func(size) {
266
+ _mem = size
267
+ return self
268
+ },
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+ cacheInputs: func(time) {
280
+ ll.assert(is_int(time),
281
+ "time must be an integer duration " +
282
+ "(did you forget to import the 'times' library?)")
283
+ _cacheInputs = time
284
+ return self
285
+ },
286
+
287
+
288
+
289
+
290
+
291
+
292
+ build: func() {
293
+ ll.assert(len(_primaries) > 0,
294
+ "at least one primary required")
295
+
296
+
297
+ hasQuerySpec := false
298
+ for _, c in _columns {
299
+ if !isUnresolved(c.query) && !isResolvedColumn(c.query) {
300
+ hasQuerySpec = true
301
+ break
302
+ }
303
+ }
304
+ if _hasUnresolved || hasQuerySpec {
305
+ ll.assert(!is_undefined(_ctx),
306
+ "unresolved inputs or column query specs require workflow context. " +
307
+ "Use wf.tableBuilder() or pass pre-resolved inputs.")
308
+ }
309
+
310
+
311
+ resolveColumn := func(value) {
312
+ if isUnresolved(value) {
313
+ r := bquery.resolve(value, _ctx)
314
+ return {
315
+ spec: r.getFutureInputField("spec"),
316
+ data: r.getFutureInputField("data")
317
+ }
318
+ }
319
+ return { spec: value.spec, data: value.data }
320
+ }
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+ primaryEntries := []
329
+ primaryPlRefs := {}
330
+ for _, p in _primaries {
331
+ column := p.value
332
+ rawFilters := []
333
+ if isPrimaryRef(p.value) {
334
+ column = p.value.column
335
+ if !is_undefined(p.value.filter) {
336
+ rawFilters = append(rawFilters, p.value.filter)
337
+ }
338
+ }
339
+
340
+ resolvedCol := resolveColumn(column)
341
+ entry := {
342
+ name: p.name,
343
+ header: p.header,
344
+ spec: resolvedCol.spec,
345
+ data: resolvedCol.data,
346
+ filters: []
347
+ }
348
+ if isUnresolved(column) {
349
+ primaryPlRefs[p.name] = column
350
+ }
351
+
352
+ for _, rawFlt in rawFilters {
353
+ resolvedFlt := resolveColumn(rawFlt)
354
+ entry.filters = append(entry.filters, {
355
+ spec: resolvedFlt.spec,
356
+ data: resolvedFlt.data
357
+ })
358
+ }
359
+
360
+ primaryEntries = append(primaryEntries, entry)
361
+ }
362
+
363
+
364
+
365
+
366
+
367
+
368
+ singleQueryByIdx := {}
369
+ multiQueryResultByIdx := {}
370
+ qMap := {}
371
+ for idx, c in _columns {
372
+ if !isUnresolved(c.query) && !isResolvedColumn(c.query) {
373
+ strategy := "expectSingle"
374
+ if c.mode == "multi" { strategy = "expectMultiple" }
375
+ qMap["q" + string(idx)] = maps.deepMerge(c.query, {
376
+ matchStrategy: strategy
377
+ })
378
+ }
379
+ }
380
+ if len(qMap) > 0 {
381
+ ll.assert(len(primaryPlRefs) > 0,
382
+ "column query specs require at least one primary added as a PlRef " +
383
+ "or PrimaryRef (pre-resolved primaries cannot anchor column queries)")
384
+
385
+ qResult := bquery.anchoredQuery(_ctx, primaryPlRefs, qMap)
386
+ for idx, c in _columns {
387
+ if !isUnresolved(c.query) && !isResolvedColumn(c.query) {
388
+ r := qResult.getResult("q" + string(idx))
389
+ if c.mode == "multi" {
390
+
391
+
392
+ multiQueryResultByIdx[string(idx)] = r
393
+ } else {
394
+ singleQueryByIdx[string(idx)] = {
395
+ spec: r.getFutureInputField("spec"),
396
+ data: r.getFutureInputField("data")
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
402
+
403
+
404
+
405
+ columnEntries := []
406
+ for idx, c in _columns {
407
+ entry := {
408
+ mode: c.mode,
409
+ header: c.header,
410
+ headerPrefix: c.headerPrefix,
411
+ headerSuffix: c.headerSuffix
412
+ }
413
+ if isResolvedColumn(c.query) {
414
+ entry.spec = c.query.spec
415
+ entry.data = c.query.data
416
+ } else if isUnresolved(c.query) {
417
+ resolved := resolveColumn(c.query)
418
+ entry.spec = resolved.spec
419
+ entry.data = resolved.data
420
+ } else if c.mode == "multi" {
421
+ entry.multiResult = multiQueryResultByIdx[string(idx)]
422
+ } else {
423
+ qr := singleQueryByIdx[string(idx)]
424
+ entry.spec = qr.spec
425
+ entry.data = qr.data
426
+ }
427
+ columnEntries = append(columnEntries, entry)
428
+ }
429
+
430
+
431
+
432
+
433
+
434
+
435
+ primariesMap := {}
436
+ for idx, p in primaryEntries {
437
+ entry := {
438
+ __isPrimaryRef: "v1",
439
+ name: p.name,
440
+ column: { spec: p.spec, data: p.data }
441
+ }
442
+ if !is_undefined(p.header) { entry.header = p.header }
443
+
444
+ if len(p.filters) > 0 {
445
+ ll.assert(len(p.filters) == 1,
446
+ "v1 PrimaryRef supports at most one filter, got %d",
447
+ len(p.filters))
448
+ flt := p.filters[0]
449
+ entry.filter = { spec: flt.spec, data: flt.data }
450
+ }
451
+
452
+ primariesMap[string(idx)] = entry
453
+ }
454
+
455
+ columnsMap := {}
456
+ for idx, c in columnEntries {
457
+ entry := { mode: c.mode }
458
+ if !is_undefined(c.multiResult) {
459
+ entry.multiResult = c.multiResult
460
+ } else {
461
+ entry.spec = c.spec
462
+ entry.data = c.data
463
+ }
464
+ if !is_undefined(c.header) { entry.header = c.header }
465
+ if !is_undefined(c.headerPrefix) { entry.headerPrefix = c.headerPrefix }
466
+ if !is_undefined(c.headerSuffix) { entry.headerSuffix = c.headerSuffix }
467
+ columnsMap[string(idx)] = entry
468
+ }
469
+
470
+ tplInputs := {
471
+ format: format,
472
+ joinMode: _joinMode,
473
+ axisHeaders: _axisHeaders,
474
+ primaryCount: len(primaryEntries),
475
+ columnCount: len(columnEntries),
476
+ primaries: primariesMap,
477
+ columns: columnsMap
478
+ }
479
+
480
+ metaInputs := {}
481
+ if !is_undefined(_cpu) { metaInputs.cpu = _cpu }
482
+ if !is_undefined(_mem) { metaInputs.mem = _mem }
483
+ if !is_undefined(_cacheInputs) { metaInputs.cacheInputs = _cacheInputs }
484
+
485
+ run := render.createEphemeral(buildTableTpl, tplInputs, { metaInputs: metaInputs })
486
+ return run.output("result")
487
+ }
488
+ }
489
+ return ll.toStrict(self)
490
+ }
491
+
492
+ export ll.toStrict({
493
+ tableBuilder: tableBuilder
494
+ })
@@ -97,6 +97,15 @@ AWAIT_TEMPLATES := {
97
97
  [{ wildcard: "*" }, "spec", "ResourceReady"],
98
98
  [{ wildcard: "*" }, "data", "InputsLocked"],
99
99
  [{ wildcard: "*" }, "data", { match: "^\\{.*\\}$" }, "InputsLocked"] // for multi-hit BQuery results; key is always ref
100
+ ],
101
+
102
+
103
+
104
+
105
+
106
+ "PrimarySpecsReady": [
107
+ ["column", "spec", "ResourceReady"],
108
+ [{ match: "^filter$" }, "spec", "ResourceReady"]
100
109
  ]
101
110
  }
102
111
 
@@ -15,7 +15,9 @@ sets := import("@platforma-sdk/workflow-tengo:sets")
15
15
  oop := import("@platforma-sdk/workflow-tengo:oop")
16
16
  bobject := import("@platforma-sdk/workflow-tengo:workflow.bobject")
17
17
  bquery := import("@platforma-sdk/workflow-tengo:workflow.bquery")
18
+ resolveUtil := import("@platforma-sdk/workflow-tengo:workflow.resolve-util")
18
19
  pBundle := import("@platforma-sdk/workflow-tengo:pframes.bundle")
20
+ pTableBuilder := import("@platforma-sdk/workflow-tengo:pframes.table-builder")
19
21
 
20
22
 
21
23
  self := import("@platforma-sdk/workflow-tengo:tpl")
@@ -217,6 +219,13 @@ query := func(spec, ...options) {
217
219
  }
218
220
 
219
221
 
222
+ _resolveOne := func(ref, ...options) {
223
+ r := bquery.resolve(ref, getParentBCtx(), options...)
224
+ return ll.toStrict(oop.inherit(r, {
225
+ spec: r.getFutureInputField("spec"),
226
+ data: r.getFutureInputField("data")
227
+ }))
228
+ }
220
229
 
221
230
 
222
231
 
@@ -232,13 +241,38 @@ query := func(spec, ...options) {
232
241
 
233
242
 
234
243
 
235
- resolve := func(ref, ...options) {
236
- r := bquery.resolve(ref, getParentBCtx(), options...)
237
244
 
238
- return ll.toStrict(oop.inherit(r, {
239
- spec: r.getFutureInputField("spec"),
240
- data: r.getFutureInputField("data")
241
- }))
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+ resolve := func(input, ...options) {
265
+
266
+
267
+
268
+ if ll.isMap(input) && (input.__isRef == true || (is_string(input.blockId) && is_string(input.name))) {
269
+ return _resolveOne(input, options...)
270
+ }
271
+
272
+
273
+ return resolveUtil.deepResolve(input, func(ref) {
274
+ return _resolveOne(ref, options...)
275
+ })
242
276
  }
243
277
 
244
278
 
@@ -367,6 +401,19 @@ createPBundleBuilder := func() {
367
401
 
368
402
 
369
403
 
404
+
405
+
406
+
407
+
408
+
409
+
410
+ tableBuilder := func(format) {
411
+ return pTableBuilder.tableBuilder(format, getParentBCtx())
412
+ }
413
+
414
+
415
+
416
+
370
417
  body := func(bodyFn) {
371
418
 
372
419
  if _isProduction() {
@@ -411,5 +458,6 @@ export ll.toStrict({
411
458
  blockId: blockId,
412
459
  getBlockId: getBlockId,
413
460
  validateArgs: validateArgs,
414
- createPBundleBuilder: createPBundleBuilder
461
+ createPBundleBuilder: createPBundleBuilder,
462
+ tableBuilder: tableBuilder
415
463
  })
@@ -0,0 +1,65 @@
1
+
2
+
3
+
4
+
5
+
6
+ ll := import("@platforma-sdk/workflow-tengo:ll")
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ deepResolve := func(value, resolveFn) {
21
+ root := { value: undefined }
22
+ queue := [{val: value, target: root, key: "value"}]
23
+ qi := 0
24
+ refCount := 0
25
+
26
+ for qi < len(queue) {
27
+ item := queue[qi]
28
+ qi = qi + 1
29
+ v := item.val
30
+
31
+ if ll.isMap(v) {
32
+ if v.__isRef == true {
33
+ refCount = refCount + 1
34
+ item.target[item.key] = resolveFn(v)
35
+ } else {
36
+ result := {}
37
+ item.target[item.key] = result
38
+ for k, child in v {
39
+ queue = append(queue, {val: child, target: result, key: k})
40
+ }
41
+ }
42
+ } else if is_array(v) {
43
+
44
+ result := []
45
+ for i := 0; i < len(v); i++ {
46
+ result = append(result, undefined)
47
+ }
48
+ item.target[item.key] = result
49
+ for idx, child in v {
50
+ queue = append(queue, {val: child, target: result, key: idx})
51
+ }
52
+ } else {
53
+ item.target[item.key] = v
54
+ }
55
+ }
56
+
57
+ ll.assert(refCount > 0,
58
+ "wf.resolve(): input contains no PlRef (__isRef: true)")
59
+
60
+ return root.value
61
+ }
62
+
63
+ export ll.toStrict({
64
+ deepResolve: deepResolve
65
+ })
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.13.3",
3
+ "version": "5.15.0",
4
4
  "description": "Tengo SDK library",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -18,11 +18,11 @@
18
18
  "dependencies": {
19
19
  "@milaboratories/software-pframes-conv": "2.2.9",
20
20
  "@platforma-open/milaboratories.software-small-binaries": "^2.0.3",
21
- "@platforma-open/milaboratories.software-ptabler": "1.15.2",
22
- "@platforma-open/milaboratories.software-ptexter": "1.2.2"
21
+ "@platforma-open/milaboratories.software-ptexter": "1.2.2",
22
+ "@platforma-open/milaboratories.software-ptabler": "1.15.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@platforma-sdk/tengo-builder": "2.5.8"
25
+ "@platforma-sdk/tengo-builder": "2.5.14"
26
26
  },
27
27
  "scripts": {
28
28
  "type-check": "pl-tengo check",