@platforma-sdk/workflow-tengo 4.9.3 → 4.11.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 (54) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +2 -0
  3. package/dist/index.mjs +2 -0
  4. package/dist/tengo/lib/anonymize.impl.lib.tengo +177 -0
  5. package/dist/tengo/lib/anonymize.lib.tengo +223 -0
  6. package/dist/tengo/lib/anonymize.util.lib.tengo +56 -0
  7. package/dist/tengo/lib/exec.lib.tengo +80 -1
  8. package/dist/tengo/lib/pframes.lib.tengo +48 -3
  9. package/dist/tengo/lib/pframes.spec.lib.tengo +1 -1
  10. package/dist/tengo/lib/pframes.xsv-builder.lib.tengo +77 -0
  11. package/dist/tengo/lib/pframes.xsv-import-file.lib.tengo +8 -1
  12. package/dist/tengo/lib/pframes.xsv.lib.tengo +8 -1
  13. package/dist/tengo/lib/pt.expression.lib.tengo +48 -19
  14. package/dist/tengo/lib/pt.lib.tengo +27 -1
  15. package/dist/tengo/lib/render.lib.tengo +34 -8
  16. package/dist/tengo/lib/smart.lib.tengo +30 -8
  17. package/dist/tengo/lib/tpl.base.lib.tengo +86 -12
  18. package/dist/tengo/lib/workflow.lib.tengo +1 -0
  19. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  20. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  21. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  22. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  23. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  24. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  25. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  26. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  27. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  28. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  29. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  30. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  31. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  32. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  33. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  34. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  35. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  36. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  37. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  38. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  47. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  48. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  49. package/dist/tengo/tpl/pframes.xsv-import-map.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/package.json +2 -2
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" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-anchored" | "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" | "exec.load-software.impl" | "exec.exec" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
2
+ declare type TplName = "ll.get-field" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "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" | "exec.load-software.impl" | "exec.exec" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "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
@@ -7,6 +7,8 @@ module.exports = { Templates: {
7
7
  'workdir.save': { type: 'from-file', path: require.resolve('./tengo/tpl/workdir.save.plj.gz') },
8
8
  'workflow.build-ctx': { type: 'from-file', path: require.resolve('./tengo/tpl/workflow.build-ctx.plj.gz') },
9
9
  'workflow.query-anchored': { type: 'from-file', path: require.resolve('./tengo/tpl/workflow.query-anchored.plj.gz') },
10
+ 'anonymize.deanonymize': { type: 'from-file', path: require.resolve('./tengo/tpl/anonymize.deanonymize.plj.gz') },
11
+ 'anonymize.impl': { type: 'from-file', path: require.resolve('./tengo/tpl/anonymize.impl.plj.gz') },
10
12
  'check_network.workdir_type_name_extractor': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.workdir_type_name_extractor.plj.gz') },
11
13
  'exec.delayed-compute-allocation.impl': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz') },
12
14
  'exec.monetization_to_json': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.monetization_to_json.plj.gz') },
package/dist/index.mjs CHANGED
@@ -8,6 +8,8 @@ export const Templates = {
8
8
  'workdir.save': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/workdir.save.plj.gz') },
9
9
  'workflow.build-ctx': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/workflow.build-ctx.plj.gz') },
10
10
  'workflow.query-anchored': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/workflow.query-anchored.plj.gz') },
11
+ 'anonymize.deanonymize': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/anonymize.deanonymize.plj.gz') },
12
+ 'anonymize.impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/anonymize.impl.plj.gz') },
11
13
  'check_network.workdir_type_name_extractor': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.workdir_type_name_extractor.plj.gz') },
12
14
  'exec.delayed-compute-allocation.impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz') },
13
15
  'exec.monetization_to_json': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.monetization_to_json.plj.gz') },
@@ -0,0 +1,177 @@
1
+ smart := import("@platforma-sdk/workflow-tengo:smart")
2
+ ll := import("@platforma-sdk/workflow-tengo:ll")
3
+ fmt := import("fmt")
4
+ maps := import("@platforma-sdk/workflow-tengo:maps")
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
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+ anonymizeFields := func(targets, ...opts) {
42
+
43
+ options := {}
44
+ if len(opts) > 0 {
45
+ if len(opts) == 1 && is_map(opts[0]) {
46
+ options = opts[0]
47
+ } else {
48
+ ll.panic("anonymizeFields: optional parameters must be a single map argument, got: %v", opts)
49
+ }
50
+ }
51
+
52
+
53
+ fieldMatcher := options.fieldMatcher
54
+ if is_undefined(fieldMatcher) {
55
+ fieldMatcher = func(fieldName) { return true }
56
+ }
57
+
58
+ fieldNameDeriver := options.fieldNameDeriver
59
+ if is_undefined(fieldNameDeriver) {
60
+ fieldNameDeriver = func(originalFieldName, anonymizedFieldName) { return anonymizedFieldName }
61
+ }
62
+
63
+ cidBytes := options.cidBytes
64
+ if is_undefined(cidBytes) {
65
+ cidBytes = 20
66
+ }
67
+
68
+ groupBy := options.groupBy
69
+ if is_undefined(groupBy) {
70
+ groupBy = func(fieldName) { return fieldName }
71
+ }
72
+
73
+
74
+ if !is_callable(fieldMatcher) {
75
+ ll.panic("anonymizeFields: fieldMatcher must be a function, got: %v", fieldMatcher)
76
+ }
77
+ if !is_callable(fieldNameDeriver) {
78
+ ll.panic("anonymizeFields: fieldNameDeriver must be a function, got: %v", fieldNameDeriver)
79
+ }
80
+ if !is_callable(groupBy) {
81
+ ll.panic("anonymizeFields: groupBy must be a function, got: %v", groupBy)
82
+ }
83
+ if !is_int(cidBytes) || cidBytes <= 0 {
84
+ ll.panic("anonymizeFields: cidBytes must be a positive integer, got: %v", cidBytes)
85
+ }
86
+ if cidBytes % 5 != 0 {
87
+ ll.panic("anonymizeFields: cidBytes must be a multiple of 5 to maintain base32 encoding alignment, got: %v", cidBytes)
88
+ }
89
+
90
+ isSingleTarget := false
91
+ if smart.isReference(targets) {
92
+ isSingleTarget = true
93
+ targets = { "default": targets }
94
+ }
95
+
96
+ groupCIDs := {} // groupId -> concatenated CIDs bytes
97
+
98
+ maps.forEach(targets, func(targetKey, target) {
99
+ targetInfo := target.info()
100
+
101
+ ll.assert(target.isFinal(), "target resource must be final")
102
+
103
+ ll.assert(len(ll.fromStrict(target.outputs())) == 0, "target resource must not have outputs")
104
+ ll.assert(len(ll.fromStrict(target.metaInputs())) == 0, "target resource must not have service inputs")
105
+
106
+ maps.forEach(target.inputs(), func(fieldName, field) {
107
+ if !fieldMatcher(fieldName) {
108
+ return
109
+ }
110
+
111
+ groupId := groupBy(fieldName)
112
+ value := field.getValue()
113
+ cid := value.info().CanonicalID
114
+
115
+ if is_undefined(groupCIDs[groupId]) {
116
+ groupCIDs[groupId] = bytes(0)
117
+ }
118
+
119
+ groupCIDs[groupId] = groupCIDs[groupId] + cid
120
+ })
121
+ })
122
+
123
+ groupAnonymizedNames := {} // groupId -> anonymizedFieldName
124
+ groupRepetitionCounts := {} // anonymizedFieldName -> repetition count
125
+ anonymizedToGroupId := {} // anonymizedFieldName -> groupId
126
+
127
+ maps.forEach(groupCIDs, func(groupId, concatenatedCIDs) {
128
+ hashedCIDs := ll.sha256Encode(concatenatedCIDs)
129
+ encodedCid := ll.base32Encode(hashedCIDs[0:cidBytes])
130
+
131
+ repetitionIndex := 0
132
+ if !is_undefined(groupRepetitionCounts[encodedCid]) {
133
+ repetitionIndex = groupRepetitionCounts[encodedCid]
134
+ }
135
+ groupRepetitionCounts[encodedCid] = repetitionIndex + 1
136
+ anonymizedName := encodedCid + "-" + repetitionIndex
137
+ groupAnonymizedNames[groupId] = anonymizedName
138
+ anonymizedToGroupId[anonymizedName] = groupId
139
+ })
140
+
141
+ results := {}
142
+ maps.forEach(targets, func(targetKey, target) {
143
+ targetInfo := target.info()
144
+ resultBuilder := smart.structBuilder(targetInfo.Type, target.getData())
145
+ maps.forEach(target.inputs(), func(fieldName, field) {
146
+ value := field.getValue()
147
+
148
+ if !fieldMatcher(fieldName) {
149
+ resultBuilder.createInputField(fieldName).set(value)
150
+ return
151
+ }
152
+
153
+ groupId := groupBy(fieldName)
154
+ anonymizedFieldName := groupAnonymizedNames[groupId]
155
+ finalFieldName := fieldNameDeriver(fieldName, anonymizedFieldName)
156
+
157
+ resultBuilder.createInputField(finalFieldName).set(value)
158
+ })
159
+ results[targetKey] = resultBuilder.lockAndBuild()
160
+ })
161
+
162
+ if isSingleTarget {
163
+ return {
164
+ result: results["default"],
165
+ mapping: anonymizedToGroupId
166
+ }
167
+ } else {
168
+ return {
169
+ result: results,
170
+ mapping: anonymizedToGroupId
171
+ }
172
+ }
173
+ }
174
+
175
+ export ll.toStrict({
176
+ anonymizeFields: anonymizeFields
177
+ })
@@ -0,0 +1,223 @@
1
+
2
+
3
+
4
+ render := import("@platforma-sdk/workflow-tengo:render")
5
+ assets := import("@platforma-sdk/workflow-tengo:assets")
6
+ ll := import("@platforma-sdk/workflow-tengo:ll")
7
+ smart := import("@platforma-sdk/workflow-tengo:smart")
8
+ times := import("times")
9
+ maps := import("@platforma-sdk/workflow-tengo:maps")
10
+
11
+ anonymizeImplTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:anonymize.impl")
12
+ deanonymizeTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:anonymize.deanonymize")
13
+
14
+ _renderAnonymization := func(targets, params) {
15
+ inputs := {
16
+ params: smart.createJsonResource(params)
17
+ }
18
+
19
+ isSingleTarget := smart.isReference(targets)
20
+
21
+ if isSingleTarget {
22
+ inputs.target = targets
23
+ } else {
24
+ for key, value in targets {
25
+ inputs["target." + key] = value
26
+ }
27
+ }
28
+
29
+ anonymizeResult := render.create(anonymizeImplTpl, inputs)
30
+
31
+ if isSingleTarget {
32
+ return ll.toStrict({
33
+ result: anonymizeResult.output("result"),
34
+ mapping: anonymizeResult.output("mapping")
35
+ })
36
+ } else {
37
+ result := {}
38
+ for key in maps.getKeys(targets) {
39
+ result[key] = anonymizeResult.resolveOutput(["result", key])
40
+ }
41
+ return ll.toStrict({
42
+ result: ll.toStrict(result),
43
+ mapping: anonymizeResult.output("mapping")
44
+ })
45
+ }
46
+ }
47
+
48
+ _renderDeAnonymization := func(targets, mapping, params) {
49
+ inputs := {
50
+ params: smart.createJsonResource(params),
51
+ mapping: mapping
52
+ }
53
+
54
+ isSingleTarget := smart.isReference(targets)
55
+
56
+ if isSingleTarget {
57
+ inputs.target = targets
58
+ } else {
59
+ for key, value in targets {
60
+ inputs["target." + key] = value
61
+ }
62
+ }
63
+
64
+ deanonymizeResult := render.create(deanonymizeTpl, inputs)
65
+
66
+ if isSingleTarget {
67
+ return deanonymizeResult.output("result")
68
+ } else {
69
+ result := {}
70
+ for key in maps.getKeys(targets) {
71
+ result[key] = deanonymizeResult.resolveOutput(["result", key])
72
+ }
73
+ return ll.toStrict(result)
74
+ }
75
+ }
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+ anonymizeFields := func(target, ...opts) {
94
+
95
+ options := {}
96
+ if len(opts) > 0 {
97
+ if len(opts) == 1 && is_map(opts[0]) {
98
+ options = opts[0]
99
+ } else {
100
+ ll.panic("anonymizeFields: optional parameters must be a single map argument, got: %v", opts)
101
+ }
102
+ }
103
+
104
+ params := {}
105
+ if !is_undefined(options.fieldRegex) {
106
+ params.fieldRegex = options.fieldRegex
107
+ }
108
+ if !is_undefined(options.fieldPrefix) {
109
+ params.fieldPrefix = options.fieldPrefix
110
+ }
111
+
112
+ return _renderAnonymization(target, params)
113
+ }
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+ anonymizePKeys := func(target, pKeyIndices) {
155
+ if !is_array(pKeyIndices) {
156
+ ll.panic("anonymizePKeys: pKeyIndices must be an array of integers, got: %v", pKeyIndices)
157
+ }
158
+
159
+ for i, idx in pKeyIndices {
160
+ if !is_int(idx) {
161
+ ll.panic("anonymizePKeys: pKeyIndices[%d] must be an integer, got: %v", i, idx)
162
+ }
163
+ }
164
+
165
+ params := {
166
+ pKeyIndices: pKeyIndices
167
+ }
168
+
169
+ return _renderAnonymization(target, params)
170
+ }
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+ deanonymizePKeys := func(target, targetPKeyIndex, pKeyIndices, mapping) {
198
+ if !is_int(targetPKeyIndex) {
199
+ ll.panic("deanonymizePKeys: targetPKeyIndex must be an integer, got: %v", targetPKeyIndex)
200
+ }
201
+ if !is_array(pKeyIndices) {
202
+ ll.panic("deanonymizePKeys: pKeyIndices must be an array of integers, got: %v", pKeyIndices)
203
+ }
204
+
205
+ for i, idx in pKeyIndices {
206
+ if !is_int(idx) {
207
+ ll.panic("deanonymizePKeys: pKeyIndices[%d] must be an integer, got: %v", i, idx)
208
+ }
209
+ }
210
+
211
+ params := {
212
+ targetPKeyIndex: targetPKeyIndex,
213
+ pKeyIndices: pKeyIndices
214
+ }
215
+
216
+ return _renderDeAnonymization(target, mapping, params)
217
+ }
218
+
219
+ export ll.toStrict({
220
+ anonymizeFields: anonymizeFields,
221
+ anonymizePKeys: anonymizePKeys,
222
+ deanonymizePKeys: deanonymizePKeys
223
+ })
@@ -0,0 +1,56 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ text := import("text")
3
+ json := import("json")
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ removeSuffix := func(fieldName) {
14
+ if text.has_suffix(fieldName, "]") {
15
+ return fieldName
16
+ }
17
+
18
+ lastBracketIndex := text.last_index(fieldName, "]")
19
+ if lastBracketIndex == -1 {
20
+ ll.panic("No closing bracket found in field name: %s", fieldName)
21
+ }
22
+
23
+ return fieldName[:lastBracketIndex+1]
24
+ }
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+ decodePDataField := func(fieldName) {
35
+ keyPart := removeSuffix(fieldName)
36
+
37
+ suffix := ""
38
+ if len(fieldName) > len(keyPart) {
39
+ suffix = fieldName[len(keyPart):]
40
+ }
41
+
42
+ keyArray := json.decode(keyPart)
43
+ if !is_array(keyArray) {
44
+ ll.panic("Expected JSON array for pcolumn key, got: %v", keyArray)
45
+ }
46
+
47
+ return {
48
+ key: keyArray,
49
+ suffix: suffix
50
+ }
51
+ }
52
+
53
+ export ll.toStrict({
54
+ removeSuffix: removeSuffix,
55
+ decodePDataField: decodePDataField
56
+ })
@@ -53,6 +53,7 @@ builder := func() {
53
53
  ramRequest := undefined
54
54
 
55
55
  cache := 0
56
+ inputCache := 0
56
57
  nErrorLines := 200
57
58
 
58
59
 
@@ -422,6 +423,79 @@ builder := func() {
422
423
 
423
424
 
424
425
 
426
+
427
+
428
+
429
+
430
+ cacheInputs: func(time) {
431
+ ll.assert(
432
+ is_int(time),
433
+ "input cache time must be an integer. " +
434
+ "Did you forget to import a standard tengo library 'times'?")
435
+ inputCache = time
436
+ return self
437
+ },
438
+
439
+
440
+
441
+
442
+
443
+
444
+ cacheInputsMillis: func(millis) {
445
+ ll.assert(is_int(millis) && millis > 0, "input cache time must be a number of milliseconds")
446
+ inputCache = millis * times.millisecond
447
+ return self
448
+ },
449
+
450
+
451
+
452
+
453
+
454
+
455
+ cacheInputsSeconds: func(seconds) {
456
+ ll.assert(is_int(seconds) && seconds > 0, "input cache time must be a number of seconds")
457
+ inputCache = seconds * times.second
458
+ return self
459
+ },
460
+
461
+
462
+
463
+
464
+
465
+
466
+ cacheInputsMinutes: func(minutes) {
467
+ ll.assert(is_int(minutes) && minutes > 0, "input cache time must be a number of minutes")
468
+ inputCache = minutes * times.minute
469
+ return self
470
+ },
471
+
472
+
473
+
474
+
475
+
476
+
477
+ cacheInputsHours: func(hours) {
478
+ ll.assert(is_int(hours) && hours > 0, "input cache time must be a number of hours")
479
+ inputCache = hours * times.hour
480
+ return self
481
+ },
482
+
483
+
484
+
485
+
486
+
487
+
488
+ cacheInputsDays: func(days) {
489
+ ll.assert(is_int(days) && days > 0, "input cache time must be a number of days")
490
+ inputCache = days * times.hour * 24
491
+ return self
492
+ },
493
+
494
+
495
+
496
+
497
+
498
+
425
499
  nErrorLines: func(lines) {
426
500
  ll.assert(
427
501
  is_int(lines) && lines >= 0,
@@ -859,7 +933,12 @@ builder := func() {
859
933
  }
860
934
  }
861
935
 
862
- tpl := render.create(assets.importTemplate("@platforma-sdk/workflow-tengo:exec.exec"), pureExecInputs, metaExecInputs)
936
+ renderOpts := { metaInputs: metaExecInputs }
937
+ if inputCache > 0 {
938
+ renderOpts.inputCache = inputCache
939
+ }
940
+
941
+ tpl := render.create(assets.importTemplate("@platforma-sdk/workflow-tengo:exec.exec"), pureExecInputs, renderOpts)
863
942
 
864
943
  self := undefined
865
944
  self = ll.toStrict(oop.inherit(tpl, {
@@ -6,6 +6,7 @@ ll := import("@platforma-sdk/workflow-tengo:ll")
6
6
  maps := import("@platforma-sdk/workflow-tengo:maps")
7
7
  smart := import("@platforma-sdk/workflow-tengo:smart")
8
8
  slices := import("@platforma-sdk/workflow-tengo:slices")
9
+ sets := import("@platforma-sdk/workflow-tengo:sets")
9
10
  assets := import("@platforma-sdk/workflow-tengo:assets")
10
11
  render := import("@platforma-sdk/workflow-tengo:render")
11
12
  builder := import("@platforma-sdk/workflow-tengo:pframes.builder")
@@ -178,6 +179,19 @@ __primitiveValueTypes := { "Int": true, "Long": true, "Float": true, "Double": t
178
179
 
179
180
 
180
181
 
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
181
195
 
182
196
 
183
197
 
@@ -247,6 +261,10 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
247
261
  passKey: opts.passKey
248
262
  }
249
263
 
264
+ if !is_undefined(opts.stepCache) && opts.stepCache > 0 {
265
+ processTemplateParams.stepCache = opts.stepCache
266
+ }
267
+
250
268
  if !is_undefined(opts.cpu) {
251
269
  processTemplateParams.cpu = opts.cpu
252
270
  }
@@ -283,6 +301,33 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
283
301
  processTemplateParams.aggregationIndices = aggregationIndices
284
302
 
285
303
 
304
+ anonymizationIndices := []
305
+ anonymizedMatchers := slices.filter(opts.aggregate, func(m) { return is_map(m) && m.anonymize })
306
+
307
+ nonAnonymizedAggregationIndices := undefined
308
+
309
+ if len(anonymizedMatchers) > 0 {
310
+ anonymizedAxesIndices := pSpec.matchAxes(input.spec.axesSpec, anonymizedMatchers)
311
+ anonymizedIndicesSet := sets.fromSlice(anonymizedAxesIndices)
312
+
313
+ nonAnonymizedAggregationIndices = []
314
+ for i, aggIdx in aggregationIndices {
315
+ if anonymizedIndicesSet[aggIdx] {
316
+ anonymizationIndices = append(anonymizationIndices, i)
317
+ } else {
318
+ nonAnonymizedAggregationIndices = append(nonAnonymizedAggregationIndices, aggIdx)
319
+ }
320
+ }
321
+
322
+ if len(anonymizationIndices) > 0 {
323
+ processTemplateParams.anonymizationIndices = anonymizationIndices
324
+ }
325
+ } else {
326
+ nonAnonymizedAggregationIndices = aggregationIndices
327
+ }
328
+
329
+
330
+
286
331
  groupAxesIndices := pUtil.calculateGroupAxesIndices(len(input.spec.axesSpec), aggregationIndices)
287
332
  iterationAxesSpec = []
288
333
  for i in groupAxesIndices {
@@ -291,7 +336,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
291
336
 
292
337
  if opts.passAggregationAxesSpec {
293
338
  aggregationAxesSpec := []
294
- for i in aggregationIndices {
339
+ for i in nonAnonymizedAggregationIndices {
295
340
  aggregationAxesSpec = append(aggregationAxesSpec, input.spec.axesSpec[i])
296
341
  }
297
342
 
@@ -300,7 +345,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
300
345
 
301
346
  if opts.passAggregationAxesNames {
302
347
  aggregationAxesNames := []
303
- for i in aggregationIndices {
348
+ for i in nonAnonymizedAggregationIndices {
304
349
  aggregationAxesNames = append(aggregationAxesNames, input.spec.axesSpec[i].name)
305
350
  }
306
351
 
@@ -314,7 +359,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
314
359
  passSpec := maps.deepTransform(distilledSpec, {
315
360
  axesSpec: func(axesSpec) {
316
361
  aggregationAxesSpec := []
317
- for i in aggregationIndices {
362
+ for i in nonAnonymizedAggregationIndices {
318
363
  aggregationAxesSpec = append(aggregationAxesSpec, axesSpec[i])
319
364
  }
320
365
  return aggregationAxesSpec
@@ -93,7 +93,7 @@ normalizeColumnSpec := func(spec) {
93
93
 
94
94
 
95
95
  _P_MATCHER_SCHEMA := {
96
- `__options__,closed`: ``,
96
+
97
97
  `type,?`: `string,regex=Int|Long|Float|Double|String`,
98
98
  `name`: `string`,
99
99
  `domain,?`: { any: `string` },