@platforma-sdk/workflow-tengo 4.11.0 → 4.13.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 (45) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -0
  3. package/dist/index.mjs +1 -0
  4. package/dist/tengo/lib/maps.lib.tengo +8 -2
  5. package/dist/tengo/lib/pframes.lib.tengo +148 -30
  6. package/dist/tengo/lib/pframes.spec.lib.tengo +37 -3
  7. package/dist/tengo/lib/pt.expression.lib.tengo +206 -45
  8. package/dist/tengo/lib/slices.lib.tengo +1 -1
  9. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  10. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  11. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  12. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  13. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  14. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  15. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  16. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  17. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  18. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  19. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  20. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  21. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  22. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  23. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  24. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  25. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  26. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  27. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  28. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  29. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  30. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  31. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  32. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  33. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  35. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  37. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  38. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  41. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  42. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  43. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  44. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  45. 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" | "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";
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" | "pframes.parse-to-json" | "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
@@ -24,6 +24,7 @@ module.exports = { Templates: {
24
24
  'exec.exec-impl': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.exec-impl.plj.gz') },
25
25
  'exec.monetization_info': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.monetization_info.plj.gz') },
26
26
  'exec.python.create-venv': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.python.create-venv.plj.gz') },
27
+ 'pframes.parse-to-json': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.parse-to-json.plj.gz') },
27
28
  'exec.load-software.impl': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.load-software.impl.plj.gz') },
28
29
  'exec.exec': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.exec.plj.gz') },
29
30
  'pframes.xsv-export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
package/dist/index.mjs CHANGED
@@ -25,6 +25,7 @@ export const Templates = {
25
25
  'exec.exec-impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.exec-impl.plj.gz') },
26
26
  'exec.monetization_info': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.monetization_info.plj.gz') },
27
27
  'exec.python.create-venv': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.python.create-venv.plj.gz') },
28
+ 'pframes.parse-to-json': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.parse-to-json.plj.gz') },
28
29
  'exec.load-software.impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.load-software.impl.plj.gz') },
29
30
  'exec.exec': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.exec.plj.gz') },
30
31
  'pframes.xsv-export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
@@ -213,8 +213,14 @@ deepTransform = func(...maps) {
213
213
  } else {
214
214
  res[k] = result
215
215
  }
216
- } else if is_map(res[k]) && is_map(v) {
217
- res[k] = deepTransform(res[k], v)
216
+ } else if is_map(v) {
217
+ if is_map(res[k]) {
218
+ res[k] = deepTransform(res[k], v)
219
+ } else {
220
+
221
+
222
+ res[k] = deepTransform({}, v)
223
+ }
218
224
  } else {
219
225
  res[k] = v
220
226
  }
@@ -20,6 +20,29 @@ pColumnData := import("@platforma-sdk/workflow-tengo:pframes.data")
20
20
  llProcessTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.process-pcolumn-data")
21
21
 
22
22
 
23
+ TSV_CONTENT_SETTINGS_SCHEMA := {
24
+ "axes": [{
25
+ "column": "string",
26
+ "spec": {
27
+ "type": "string,regex=Int|Long|Float|Double|String",
28
+ "name,?": "string",
29
+ "domain,?": { "any": "string" },
30
+ "annotations,?": { "any": "string" }
31
+ }
32
+ }],
33
+ "columns": [{
34
+ "column": "string",
35
+ "id,?": "string",
36
+ "spec": {
37
+ "valueType": "string,regex=Int|Long|Float|Double|String",
38
+ "name,?": "string",
39
+ "domain,?": { "any": "string" },
40
+ "annotations,?": { "any": "string" }
41
+ }
42
+ }]
43
+ }
44
+
45
+
23
46
 
24
47
 
25
48
 
@@ -185,6 +208,36 @@ __primitiveValueTypes := { "Int": true, "Long": true, "Float": true, "Double": t
185
208
 
186
209
 
187
210
 
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
188
241
 
189
242
 
190
243
 
@@ -249,12 +302,25 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
249
302
  ll.panic("can't use passAggregationAxesSpec, passAggregationAxesNames or passDistilledAggregationSpec without aggregate")
250
303
  }
251
304
 
305
+ createOutputTrace := func(output) {
306
+ hasOutputTrace := !is_undefined(output.traceSteps)
307
+ hasGlobalTrace := !is_undefined(opts.traceSteps)
308
+ hasInputTrace := !is_undefined(input.spec.annotations[pSpec.A_TRACE])
252
309
 
253
- trace := undefined
254
- if !is_undefined(opts.traceSteps) || !is_undefined(input.spec.annotations[pSpec.A_TRACE]) {
255
- trace = pSpec.makeTrace(input.spec, (opts.traceSteps ? opts.traceSteps : [])...)
256
- }
310
+ outputTraceSteps := slices.normalize(opts.traceSteps) + slices.normalize(output.traceSteps)
311
+
312
+ trace := undefined
313
+ if len(outputTraceSteps) > 0 || hasInputTrace {
314
+ trace = pSpec.makeTrace(input.spec, outputTraceSteps...)
315
+ }
257
316
 
317
+ outputOverrideTrace := output.overrideTrace
318
+ if is_undefined(outputOverrideTrace) {
319
+ outputOverrideTrace = hasOutputTrace || hasGlobalTrace
320
+ }
321
+
322
+ return { trace: trace, overrideTrace: outputOverrideTrace }
323
+ }
258
324
 
259
325
  processTemplateParams := {
260
326
  eph: opts.isEphemeral,
@@ -289,7 +355,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
289
355
  metaExtra = {}
290
356
  }
291
357
 
292
- iterationAxesSpec := input.spec.axesSpec
358
+ iterationAxesSpec := copy(input.spec.axesSpec)
293
359
 
294
360
 
295
361
  if !isPotentiallyPartitioned {
@@ -376,19 +442,20 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
376
442
  for output in outputs {
377
443
 
378
444
  outputsMap[output.name] = output
445
+ traceInfo := createOutputTrace(output)
379
446
  if output.type == "Resource" || output.type == "BinaryPartitioned" || output.type == "JsonPartitioned" {
380
- spec := maps.merge(output.spec, { axesSpec: iterationAxesSpec + slices.normalize(output.spec.axesSpec) })
381
- if !is_undefined(trace) {
382
- spec = trace.inject(spec)
447
+ spec := maps.merge(output.spec, { axesSpec: copy(iterationAxesSpec) + slices.normalize(output.spec.axesSpec) })
448
+ if !is_undefined(traceInfo.trace) {
449
+ spec = traceInfo.trace.inject(spec, {override: traceInfo.overrideTrace})
383
450
  }
384
451
  outputSpecs[output.name] = spec
385
- processedOutputs = append(processedOutputs, maps.merge(output, {spec: undefined })) // removing spec field
452
+ processedOutputs = append(processedOutputs, maps.merge(output, {spec: undefined, traceSteps: undefined, overrideTrace: undefined }))
386
453
  } else if output.type == "ResourceMap" {
387
454
  processedOutputs = append(processedOutputs, maps.merge(output,
388
- { keyLength: len(output.spec.axesSpec), spec: undefined }))
389
- spec := maps.merge(output.spec, { axesSpec: iterationAxesSpec + slices.normalize(output.spec.axesSpec) })
390
- if !is_undefined(trace) {
391
- spec = trace.inject(spec)
455
+ { keyLength: len(output.spec.axesSpec), spec: undefined, traceSteps: undefined, overrideTrace: undefined }))
456
+ spec := maps.merge(output.spec, { axesSpec: copy(iterationAxesSpec) + slices.normalize(output.spec.axesSpec) })
457
+ if !is_undefined(traceInfo.trace) {
458
+ spec = traceInfo.trace.inject(spec, {override: traceInfo.overrideTrace})
392
459
  }
393
460
  outputSpecs[output.name] = spec
394
461
  } else if output.type == "Xsv" {
@@ -400,17 +467,68 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
400
467
  })
401
468
  purifiedPfconvCfg := decomposition.purifiedCfg
402
469
  columnsSpec := decomposition.columnsSpec
403
- if !is_undefined(trace) {
470
+ if !is_undefined(traceInfo.trace) {
404
471
  for columnName, spec in columnsSpec {
405
- columnsSpec[columnName] = trace.inject(spec)
472
+ columnsSpec[columnName] = traceInfo.trace.inject(spec, {override: traceInfo.overrideTrace})
406
473
  }
407
474
  }
408
475
  outputSpecs[output.name] = columnsSpec
409
476
  processedOutputs = append(processedOutputs, maps.merge(output,
410
477
  {
411
478
  settings: purifiedPfconvCfg,
412
- flattenWithDelimiter: "/"
479
+ flattenWithDelimiter: "/",
480
+ traceSteps: undefined,
481
+ overrideTrace: undefined
413
482
  }))
483
+ } else if output.type == "TsvContent" {
484
+ validation.assertType(output.settings, TSV_CONTENT_SETTINGS_SCHEMA, "Invalid TsvContent settings")
485
+
486
+
487
+ transformedSettings := {
488
+ axes: slices.map(output.settings.axes, func(axis) {
489
+ return {
490
+ name: axis.column,
491
+ type: axis.spec.type
492
+ }
493
+ }),
494
+ columns: slices.map(output.settings.columns, func(column) {
495
+ transformedColumn := {
496
+ name: column.column,
497
+ type: column.spec.valueType
498
+ }
499
+ if !is_undefined(column.id) {
500
+ transformedColumn.id = column.id
501
+ }
502
+ return transformedColumn
503
+ })
504
+ }
505
+
506
+
507
+ columnsSpec := {}
508
+ axesSpec := copy(iterationAxesSpec)
509
+ for axis in output.settings.axes {
510
+ axesSpec = append(axesSpec, axis.spec)
511
+ }
512
+
513
+ for column in output.settings.columns {
514
+ columnId := column.id
515
+ if is_undefined(columnId) {
516
+ columnId = column.column
517
+ }
518
+ spec := maps.merge(column.spec, { axesSpec: axesSpec, kind: "PColumn" })
519
+ if !is_undefined(traceInfo.trace) {
520
+ spec = traceInfo.trace.inject(spec, {override: traceInfo.overrideTrace})
521
+ }
522
+ columnsSpec[columnId] = spec
523
+ }
524
+
525
+ outputSpecs[output.name] = columnsSpec
526
+ processedOutputs = append(processedOutputs, maps.merge(output, {
527
+ settings: transformedSettings,
528
+ flattenWithDelimiter: "/",
529
+ traceSteps: undefined,
530
+ overrideTrace: undefined
531
+ }))
414
532
  } else {
415
533
  ll.panic("unknown output type: " + output.type)
416
534
  }
@@ -455,9 +573,9 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
455
573
  }
456
574
 
457
575
  spec := outputSpecs[name]
458
- if output.type == "Xsv" {
576
+ if output.type == "Xsv" || output.type == "TsvContent" {
459
577
  if len(column) != 1 {
460
- ll.panic("column name must be provided for Xsv output")
578
+ ll.panic("column name must be provided for " + output.type + " output")
461
579
  }
462
580
  columnName := column[0]
463
581
  spec = spec[columnName]
@@ -467,7 +585,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
467
585
  return spec
468
586
  } else {
469
587
  if len(column) > 0 {
470
- ll.panic("column name should not be provided for non-Xsv output")
588
+ ll.panic("column name should not be provided for non-Xsv/TsvContent output")
471
589
  }
472
590
  return spec
473
591
  }
@@ -490,14 +608,14 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
490
608
  ll.panic("unknown output: " + name)
491
609
  }
492
610
 
493
- if output.type == "Xsv" {
611
+ if output.type == "Xsv" || output.type == "TsvContent" {
494
612
  if len(column) != 1 {
495
- ll.panic("column name must be provided for Xsv output")
613
+ ll.panic("column name must be provided for " + output.type + " output")
496
614
  }
497
615
  return renderResult.output(name + "/" + column[0])
498
616
  } else {
499
617
  if len(column) > 0 {
500
- ll.panic("column name should not be provided for non-Xsv output")
618
+ ll.panic("column name should not be provided for non-Xsv/TsvContent output")
501
619
  }
502
620
  return renderResult.output(name)
503
621
  }
@@ -520,9 +638,9 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
520
638
  ll.panic("unknown output: " + name)
521
639
  }
522
640
 
523
- if output.type == "Xsv" {
641
+ if output.type == "Xsv" || output.type == "TsvContent" {
524
642
  if len(column) != 1 {
525
- ll.panic("column name must be provided for Xsv output")
643
+ ll.panic("column name must be provided for " + output.type + " output")
526
644
  }
527
645
  columnName := column[0]
528
646
  spec := outputSpecs[name][columnName]
@@ -535,7 +653,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
535
653
  }
536
654
  } else {
537
655
  if len(column) > 0 {
538
- ll.panic("column name should not be provided for non-Xsv output")
656
+ ll.panic("column name should not be provided for non-Xsv/TsvContent output")
539
657
  }
540
658
  return {
541
659
  data: renderResult.output(name),
@@ -556,8 +674,8 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
556
674
  ll.panic("unknown output: " + name)
557
675
  }
558
676
 
559
- if output.type != "Xsv" {
560
- ll.panic("outputFrame can only be used with Xsv outputs")
677
+ if output.type != "Xsv" && output.type != "TsvContent" {
678
+ ll.panic("outputFrame can only be used with Xsv or TsvContent outputs")
561
679
  }
562
680
 
563
681
  builder := builder.pFrameBuilder()
@@ -581,7 +699,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
581
699
  }
582
700
 
583
701
  maps.forEach(outputsMap, func(name, output) {
584
- if output.type == "Xsv" {
702
+ if output.type == "Xsv" || output.type == "TsvContent" {
585
703
 
586
704
  for columnName, spec in outputSpecs[name] {
587
705
  builder.add(
@@ -649,8 +767,8 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
649
767
  if is_undefined(output) {
650
768
  ll.panic("unknown output: " + name)
651
769
  }
652
- if output.type != "Xsv" {
653
- ll.panic("output is not Xsv: " + name)
770
+ if output.type != "Xsv" && output.type != "TsvContent" {
771
+ ll.panic("output is not Xsv or TsvContent: " + name)
654
772
  }
655
773
 
656
774
  return maps.getKeys(outputSpecs[name])
@@ -258,10 +258,44 @@ makeTrace := func(input, ...steps) {
258
258
  return ll.toStrict({
259
259
  value: currentTrace,
260
260
  valueStr: currentTraceStr,
261
-
262
- inject: func(spec) {
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+ inject: func(spec, ...opts) {
263
274
  validation.assertType(spec, P_COLUMN_SPEC_SCHEMA)
264
- return maps.deepMerge(spec, {annotations: {"pl7.app/trace": currentTraceStr}})
275
+
276
+
277
+ options := {}
278
+ if len(opts) > 0 {
279
+ if len(opts) == 1 && is_map(opts[0]) {
280
+ options = opts[0]
281
+ } else {
282
+ ll.panic("inject: optional parameters must be a single map argument, got: %v", opts)
283
+ }
284
+ }
285
+
286
+
287
+ override := options.override
288
+ if is_undefined(override) {
289
+ override = true
290
+ }
291
+
292
+ return maps.deepTransform(spec, {annotations: {"pl7.app/trace": func(oldValue) {
293
+
294
+ if override || is_undefined(oldValue) {
295
+ return currentTraceStr
296
+ }
297
+ return oldValue
298
+ }}})
265
299
  }
266
300
  })
267
301
  }
@@ -1,6 +1,8 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
2
  maps := import("@platforma-sdk/workflow-tengo:maps")
3
3
  slices := import("@platforma-sdk/workflow-tengo:slices")
4
+ validation := import("@platforma-sdk/workflow-tengo:validation")
5
+ fmt := import("fmt")
4
6
 
5
7
 
6
8
 
@@ -16,6 +18,24 @@ when := undefined
16
18
  rank := undefined
17
19
 
18
20
 
21
+
22
+
23
+ _validateOptions := func(options, schema, functionName) {
24
+ if len(options) > 1 {
25
+ ll.panic("%s accepts at most one options map argument.", functionName)
26
+ }
27
+ if len(options) == 0 {
28
+ return {}
29
+ }
30
+
31
+ opsMap := options[0]
32
+ ll.assert(is_map(opsMap), "Options argument to %s(), if provided, must be an options map.", functionName)
33
+
34
+ validation.assertType(opsMap, schema, fmt.sprintf("%s options validation error", functionName))
35
+ return opsMap
36
+ }
37
+
38
+
19
39
  _isExpression := func(item) {
20
40
  return ll.isMap(item) && ll.methodExists(item, "getExpression")
21
41
  }
@@ -266,15 +286,13 @@ _newExpression = func(expression, currentAlias) {
266
286
 
267
287
 
268
288
  cast: func(dtype, ...options) {
289
+ ops := _validateOptions(options, {
290
+ "__options__,closed": "",
291
+ "strict,?": "bool"
292
+ }, "cast")
269
293
  strictVal := false
270
- if len(options) > 0 {
271
- ll.assert(len(options) == 1, "cast accepts at most one options map argument.")
272
- opsMap := options[0]
273
- ll.assert(is_map(opsMap), "Second argument to cast(), if provided, must be an options map.")
274
- if !is_undefined(opsMap.strict) {
275
- ll.assert(is_bool(opsMap.strict), "strict in options map must be a boolean")
276
- strictVal = opsMap.strict
277
- }
294
+ if !is_undefined(ops.strict) {
295
+ strictVal = ops.strict
278
296
  }
279
297
  return _newExpression(
280
298
  {type: "cast", value: expression, dtype: dtype, strict: strictVal},
@@ -334,14 +352,19 @@ _newExpression = func(expression, currentAlias) {
334
352
  if len(args) > 0 {
335
353
  arg := args[0]
336
354
  if is_map(arg) {
337
- if arg.length != undefined && arg.end != undefined {
355
+ ops := _validateOptions([arg], {
356
+ "__options__,closed": "",
357
+ "length,?": "any",
358
+ "end,?": "any"
359
+ }, "strSlice")
360
+ if ops.length != undefined && ops.end != undefined {
338
361
  ll.panic("strSlice cannot have both 'length' and 'end' options.")
339
362
  }
340
- if !is_undefined(arg.length) {
341
- expr.length = _mapToSingleExpressionStruct(arg.length, "col")
363
+ if !is_undefined(ops.length) {
364
+ expr.length = _mapToSingleExpressionStruct(ops.length, "col")
342
365
  }
343
- if !is_undefined(arg.end) {
344
- expr.end = _mapToSingleExpressionStruct(arg.end, "col")
366
+ if !is_undefined(ops.end) {
367
+ expr.end = _mapToSingleExpressionStruct(ops.end, "col")
345
368
  }
346
369
  } else {
347
370
  expr.length = _mapToSingleExpressionStruct(arg, "col")
@@ -361,21 +384,19 @@ _newExpression = func(expression, currentAlias) {
361
384
 
362
385
 
363
386
  strReplace: func(pattern, replacement, ...options) {
387
+ ops := _validateOptions(options, {
388
+ "__options__,closed": "",
389
+ "replaceAll,?": "bool",
390
+ "literal,?": "bool"
391
+ }, "strReplace")
364
392
  replaceAllVal := false
365
393
  literalVal := false
366
394
 
367
- if len(options) > 0 {
368
- ll.assert(len(options) == 1, "strReplace accepts at most one options map argument.")
369
- opsMap := options[0]
370
- ll.assert(is_map(opsMap), "Third argument to strReplace(), if provided, must be an options map.")
371
- if !is_undefined(opsMap.replaceAll) {
372
- ll.assert(is_bool(opsMap.replaceAll), "replaceAll in options map must be a boolean")
373
- replaceAllVal = opsMap.replaceAll
374
- }
375
- if !is_undefined(opsMap.literal) {
376
- ll.assert(is_bool(opsMap.literal), "literal in options map must be a boolean")
377
- literalVal = opsMap.literal
378
- }
395
+ if !is_undefined(ops.replaceAll) {
396
+ replaceAllVal = ops.replaceAll
397
+ }
398
+ if !is_undefined(ops.literal) {
399
+ literalVal = ops.literal
379
400
  }
380
401
 
381
402
  return _newExpression({
@@ -413,6 +434,147 @@ _newExpression = func(expression, currentAlias) {
413
434
 
414
435
 
415
436
 
437
+ strContains: func(pattern, ...options) {
438
+ ops := _validateOptions(options, {
439
+ "__options__,closed": "",
440
+ "literal,?": "bool",
441
+ "strict,?": "bool"
442
+ }, "strContains")
443
+ literalVal := false
444
+ strictVal := true
445
+
446
+ if !is_undefined(ops.literal) {
447
+ literalVal = ops.literal
448
+ }
449
+ if !is_undefined(ops.strict) {
450
+ strictVal = ops.strict
451
+ }
452
+
453
+ return _newExpression({
454
+ type: "str_contains",
455
+ value: expression,
456
+ pattern: _mapToSingleExpressionStruct(pattern, "lit"),
457
+ literal: literalVal,
458
+ strict: strictVal
459
+ }, undefined)
460
+ },
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+ strContainsAny: func(patterns, ...options) {
470
+ ll.assert(is_array(patterns), "patterns must be an array of strings")
471
+ for pattern in patterns {
472
+ ll.assert(is_string(pattern), "all patterns must be strings")
473
+ }
474
+
475
+ ops := _validateOptions(options, {
476
+ "__options__,closed": "",
477
+ "asciiCaseInsensitive,?": "bool"
478
+ }, "strContainsAny")
479
+ asciiCaseInsensitiveVal := false
480
+ if !is_undefined(ops.asciiCaseInsensitive) {
481
+ asciiCaseInsensitiveVal = ops.asciiCaseInsensitive
482
+ }
483
+
484
+ return _newExpression({
485
+ type: "str_contains_any",
486
+ value: expression,
487
+ patterns: patterns,
488
+ asciiCaseInsensitive: asciiCaseInsensitiveVal
489
+ }, undefined)
490
+ },
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+ strCountMatches: func(pattern, ...options) {
500
+ ops := _validateOptions(options, {
501
+ "__options__,closed": "",
502
+ "literal,?": "bool"
503
+ }, "strCountMatches")
504
+ literalVal := false
505
+
506
+ if !is_undefined(ops.literal) {
507
+ literalVal = ops.literal
508
+ }
509
+
510
+ return _newExpression({
511
+ type: "str_count_matches",
512
+ value: expression,
513
+ pattern: _mapToSingleExpressionStruct(pattern, "lit"),
514
+ literal: literalVal
515
+ }, undefined)
516
+ },
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+ strExtract: func(pattern, ...options) {
526
+ ops := _validateOptions(options, {
527
+ "__options__,closed": "",
528
+ "groupIndex,?": "number"
529
+ }, "strExtract")
530
+ groupIndexVal := 0
531
+
532
+ if !is_undefined(ops.groupIndex) {
533
+ groupIndexVal = ops.groupIndex
534
+ }
535
+
536
+ return _newExpression({
537
+ type: "str_extract",
538
+ value: expression,
539
+ pattern: _mapToSingleExpressionStruct(pattern, "lit"),
540
+ groupIndex: groupIndexVal
541
+ }, undefined)
542
+ },
543
+
544
+
545
+
546
+
547
+
548
+
549
+ strStartsWith: func(prefix) {
550
+ return _newExpression({
551
+ type: "str_starts_with",
552
+ value: expression,
553
+ prefix: _mapToSingleExpressionStruct(prefix, "lit")
554
+ }, undefined)
555
+ },
556
+
557
+
558
+
559
+
560
+
561
+
562
+ strEndsWith: func(suffix) {
563
+ return _newExpression({
564
+ type: "str_ends_with",
565
+ value: expression,
566
+ suffix: _mapToSingleExpressionStruct(suffix, "lit")
567
+ }, undefined)
568
+ },
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
416
578
  strDistance: func(string2, metric, returnSimilarity) {
417
579
  ll.assert(_isExpression(string2), "string2 must be an expression object")
418
580
  ll.assert(is_string(metric), "metric must be a string")
@@ -561,13 +723,16 @@ _newExpression = func(expression, currentAlias) {
561
723
  actualArgs := args
562
724
 
563
725
  if len(args) > 0 && is_map(args[len(args)-1]) && !(_isExpression(args[len(args)-1])) {
564
- ops := args[len(args)-1]
726
+ ops := _validateOptions([args[len(args)-1]], {
727
+ "__options__,closed": "",
728
+ "orderBy,?": "any",
729
+ "descending,?": "bool"
730
+ }, "cumsum")
565
731
  actualArgs = args[0:len(args)-1]
566
- if maps.containsKey(ops, "orderBy") {
732
+ if !is_undefined(ops.orderBy) {
567
733
  additionalOrderByVal = _mapToExpressionStructList(ops.orderBy, "col")
568
734
  }
569
- if maps.containsKey(ops, "descending") {
570
- ll.assert(is_bool(ops.descending), "descending in options map must be a boolean")
735
+ if !is_undefined(ops.descending) {
571
736
  descendingVal = ops.descending
572
737
  }
573
738
  }
@@ -645,15 +810,13 @@ concatStr := func(expressions, ...options) {
645
810
  ll.assert(is_array(expressions), "First argument to concatStr must be an array of expressions")
646
811
  ll.assert(len(expressions) > 0, "Expression array for concatStr cannot be empty")
647
812
 
813
+ ops := _validateOptions(options, {
814
+ "__options__,closed": "",
815
+ "delimiter,?": "string"
816
+ }, "concatStr")
648
817
  delimiterVal := "" // Default to empty string
649
- if len(options) > 0 {
650
- ll.assert(len(options) == 1, "concatStr accepts at most one options map argument.")
651
- opsMap := options[0]
652
- ll.assert(is_map(opsMap), "Second argument to concatStr(), if provided, must be an options map.")
653
- if !is_undefined(opsMap.delimiter) {
654
- ll.assert(is_string(opsMap.delimiter), "delimiter in options map must be a string")
655
- delimiterVal = opsMap.delimiter
656
- }
818
+ if !is_undefined(ops.delimiter) {
819
+ delimiterVal = ops.delimiter
657
820
  }
658
821
 
659
822
  return _newExpression({
@@ -800,14 +963,12 @@ rank = func(orderByExpressions, ...options) {
800
963
  orderByVal = _mapToExpressionStructList(orderByExpressions, "col")
801
964
  ll.assert(len(orderByVal) > 0, "rank() 'orderBy' list cannot be empty after processing arguments.")
802
965
 
803
- if len(options) > 0 {
804
- ll.assert(len(options) == 1, "rank() accepts at most one options map argument.")
805
- opsMap := options[0]
806
- ll.assert(is_map(opsMap), "Second argument to rank(), if provided, must be an options map.")
807
- if maps.containsKey(opsMap, "descending") {
808
- ll.assert(is_bool(opsMap.descending), "descending in options map must be a boolean")
809
- descendingVal = opsMap.descending
810
- }
966
+ ops := _validateOptions(options, {
967
+ "__options__,closed": "",
968
+ "descending,?": "bool"
969
+ }, "rank")
970
+ if !is_undefined(ops.descending) {
971
+ descendingVal = ops.descending
811
972
  }
812
973
 
813
974
  return _newWindowExpression({
@@ -310,7 +310,7 @@ normalize := func(s) {
310
310
  if is_undefined(s) {
311
311
  return []
312
312
  }
313
- return s
313
+ return copy(s)
314
314
  }
315
315
 
316
316
  export ll.toStrict({
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": "4.11.0",
3
+ "version": "4.13.0",
4
4
  "description": "Tengo SDK library",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@milaboratories/software-pframes-conv": "2.2.2",
20
20
  "@platforma-open/milaboratories.software-small-binaries": "^1.15.21",
21
- "@platforma-open/milaboratories.software-ptabler": "1.7.0"
21
+ "@platforma-open/milaboratories.software-ptabler": "1.8.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@platforma-sdk/tengo-builder": "^2.1.12"