@platforma-sdk/workflow-tengo 1.2.6

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 (51) hide show
  1. package/README.md +6 -0
  2. package/dist/tengo/lib/assets.lib.tengo +64 -0
  3. package/dist/tengo/lib/constants.lib.tengo +75 -0
  4. package/dist/tengo/lib/exec.internal.lib.tengo +211 -0
  5. package/dist/tengo/lib/exec.lib.tengo +516 -0
  6. package/dist/tengo/lib/exec.package.lib.tengo +296 -0
  7. package/dist/tengo/lib/exec.runcmd.lib.tengo +275 -0
  8. package/dist/tengo/lib/exec.runenv.lib.tengo +221 -0
  9. package/dist/tengo/lib/exec.stream.lib.tengo +64 -0
  10. package/dist/tengo/lib/file.lib.tengo +112 -0
  11. package/dist/tengo/lib/ll.lib.tengo +233 -0
  12. package/dist/tengo/lib/maps.lib.tengo +102 -0
  13. package/dist/tengo/lib/objects.lib.tengo +22 -0
  14. package/dist/tengo/lib/oop.lib.tengo +29 -0
  15. package/dist/tengo/lib/path.lib.tengo +165 -0
  16. package/dist/tengo/lib/pframes.constants.lib.tengo +53 -0
  17. package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +47 -0
  18. package/dist/tengo/lib/pframes.lib.tengo +84 -0
  19. package/dist/tengo/lib/pframes.ll.lib.tengo +86 -0
  20. package/dist/tengo/lib/pframes.util.lib.tengo +244 -0
  21. package/dist/tengo/lib/pframes.xsv.lib.tengo +178 -0
  22. package/dist/tengo/lib/render.lib.tengo +100 -0
  23. package/dist/tengo/lib/sets.lib.tengo +93 -0
  24. package/dist/tengo/lib/slices.lib.tengo +202 -0
  25. package/dist/tengo/lib/smart.lib.tengo +1005 -0
  26. package/dist/tengo/lib/tpl.lib.tengo +535 -0
  27. package/dist/tengo/lib/tpl.state-await.lib.tengo +255 -0
  28. package/dist/tengo/lib/tpl.util.lib.tengo +19 -0
  29. package/dist/tengo/lib/validation.lib.tengo +428 -0
  30. package/dist/tengo/lib/workdir.lib.tengo +300 -0
  31. package/dist/tengo/lib/workdir.proc.lib.tengo +158 -0
  32. package/dist/tengo/lib/workdir.save.lib.tengo +158 -0
  33. package/dist/tengo/lib/workflow.bobject.lib.tengo +51 -0
  34. package/dist/tengo/lib/workflow.bquery.lib.tengo +208 -0
  35. package/dist/tengo/lib/workflow.constants.lib.tengo +8 -0
  36. package/dist/tengo/lib/workflow.context.lib.tengo +56 -0
  37. package/dist/tengo/lib/workflow.lib.tengo +298 -0
  38. package/dist/tengo/tpl/exec.create-python-venv.plj.gz +0 -0
  39. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.aggregate.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.import-xsv-map.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.map-pframe.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/index.d.ts +4 -0
  50. package/index.js +13 -0
  51. package/package.json +25 -0
@@ -0,0 +1,535 @@
1
+ /* A library exposing template functional components */
2
+
3
+ tx := import("tx")
4
+ ll := import("@platforma-sdk/workflow-tengo:ll")
5
+ render := import("@platforma-sdk/workflow-tengo:render")
6
+ smart := import("@platforma-sdk/workflow-tengo:smart")
7
+ sets := import("@platforma-sdk/workflow-tengo:sets")
8
+ maps := import("@platforma-sdk/workflow-tengo:maps")
9
+ tplutil := import("@platforma-sdk/workflow-tengo:tpl.util")
10
+ constants := import("@platforma-sdk/workflow-tengo:constants")
11
+ stateAwait := import("@platforma-sdk/workflow-tengo:tpl.state-await")
12
+ validation := import("@platforma-sdk/workflow-tengo:validation")
13
+
14
+ /**
15
+ * Unmarshal resource according by:
16
+ * - converting all json resources (potentially nested) into a tengo objects (maps)
17
+ * - converting all pl.maps (resource maps) into tengo maps
18
+ *
19
+ * @param resource: smart resource to unmarshal
20
+ * @param preprocessors: input preprocessors
21
+ * @return object: unmarshaled resource
22
+ */
23
+ _unmarshal := func(resource, preprocessors) {
24
+ ll.assert(smart.isResource(resource), "expected resource, found ", resource)
25
+
26
+ // instance of this
27
+ self := func(resource) { return _unmarshal(resource, preprocessors) }
28
+
29
+ // try preprocessors first
30
+ for preproc in preprocessors {
31
+ if preproc.canParse(resource) {
32
+ return preproc.parse(resource, self)
33
+ }
34
+ }
35
+
36
+ if smart.isReference(resource) && resource.hasError() {
37
+ ll.panic("input resource has errors: ", resource, resource.getError())
38
+ }
39
+
40
+ if smart.isJsonResource(resource) || resource.checkResourceType(constants.RTYPE_BOBJECT_SPEC) {
41
+
42
+ // parse json data of the resource
43
+ return resource.getDataAsJson()
44
+ } else if smart.isMapResource(resource) || smart.isLockedEphMapResource(resource) {
45
+
46
+ // unmarshal map
47
+ result := {}
48
+ for field, value in resource.inputs() {
49
+ if value.isErrorSet() {
50
+ ll.panic("input %v field has error: %v", field, field.getError().getDataAsJson().message)
51
+ } else if value.isSet() {
52
+ result[field] = _unmarshal(value.getValue(), preprocessors)
53
+ } else {
54
+ result[field] = value
55
+ }
56
+
57
+ }
58
+ return result
59
+ } else {
60
+ // nothing todo
61
+ return resource
62
+ }
63
+ }
64
+
65
+ /**
66
+ * A wrapper object around template functionality
67
+ */
68
+ tpl := func() {
69
+ self := undefined
70
+
71
+ _renderer := smart.resourceBuilder(ll.getCurrentTemplateRenderer())
72
+
73
+ _rendererType := _renderer.info().Type
74
+
75
+ _isEphemeral := _rendererType.Name == constants.RTYPE_RENDER_EPH_TEMPLATE.Name
76
+
77
+ _hasPrepared := _renderer.get(tplutil.INPUTS_MAP_FIELD).hasField(tplutil.PREPARE_MAP_FIELD_NAME)
78
+
79
+ _preparedMap := _hasPrepared ? _renderer.get(tplutil.INPUTS_MAP_FIELD).get(tplutil.PREPARE_MAP_FIELD_NAME) : undefined
80
+
81
+ _templateId := _renderer.get(tplutil.TEMPLATE_FIELD).id
82
+
83
+ _stateRequests := []
84
+ _delegatedOutputs := []
85
+
86
+ self = {
87
+
88
+ /**
89
+ * Current renderer smart resource
90
+ */
91
+ renderer: func() {
92
+ return _renderer
93
+ },
94
+
95
+ /**
96
+ * Whether this template is ephemeral (rendered by ephemeral template router)
97
+ */
98
+ isEphemeral: func() {
99
+ return _isEphemeral
100
+ },
101
+
102
+ /**
103
+ * Current template resource
104
+ */
105
+ template: func() {
106
+ return smart.resource(_templateId)
107
+ },
108
+
109
+ _unmarshallers: [],
110
+
111
+ /**
112
+ * Register a preprocessor of input resource. The preprocessor must have two
113
+ * methods: `canParse(resource)` (bool) and `parse(resource)` (object). Registered
114
+ * preprocessors will be used when unmarshaling resources.
115
+ */
116
+ registerUnmarshaller: func(preproc) {
117
+ ll.assert(is_undefined(self._inputs), "input preprocessors must be registered before getting inputs")
118
+ self._unmarshallers = append(self._unmarshallers, preproc)
119
+ },
120
+
121
+ /**
122
+ * @return map[string]field: - a map of inputs to the fields of the input map.
123
+ */
124
+ rawInputs: func() {
125
+ return self.renderer().get(tplutil.INPUTS_MAP_FIELD).inputs()
126
+ },
127
+
128
+ _inputs: undefined,
129
+
130
+ /**
131
+ * @return map: a map of the template inputs
132
+ */
133
+ inputs: func() {
134
+ if self._inputs == undefined {
135
+ inputs := _unmarshal(self.renderer().get(tplutil.INPUTS_MAP_FIELD), self._unmarshallers)
136
+
137
+ // if we have prepared inputs, flatten them
138
+ pp := inputs[tplutil.PREPARE_MAP_FIELD_NAME]
139
+ if !is_undefined(pp) {
140
+ delete(inputs, tplutil.PREPARE_MAP_FIELD_NAME)
141
+ for k, v in pp {
142
+ ll.assert(is_undefined(inputs[k]), k, " key already exists in inputs map")
143
+ inputs[k] = v
144
+ }
145
+ }
146
+
147
+ self._inputs = inputs
148
+ }
149
+ return self._inputs
150
+ },
151
+
152
+ /**
153
+ * @return bool: whether there is input with a given name
154
+ */
155
+ hasInput: func(name) {
156
+ return self.renderer().get(tplutil.INPUTS_MAP_FIELD).hasField(name)
157
+ },
158
+
159
+ // accumulated output definitions
160
+ definedOutputs : [],
161
+
162
+ /**
163
+ * Defines current template output(s) names.
164
+ *
165
+ * For pure templates, all outputs must be defined before the template body, and only registered outputs can be used
166
+ * inside. Optional for ephemeral templates.
167
+ */
168
+ defineOutputs : func(...name) {
169
+ if len(name) == 1 && is_array(name[0]) {
170
+ for v in name[0] {
171
+ self.definedOutputs = append(self.definedOutputs, v)
172
+ }
173
+ } else {
174
+ self.definedOutputs = append(self.definedOutputs, name...)
175
+ }
176
+ },
177
+
178
+ /**
179
+ * In ephemeral template, body will only be executed when corresponding input staet is
180
+ * reached.
181
+ *
182
+ * Signature:
183
+ * awaitState(path..., requestedState)
184
+ *
185
+ * Usage examples:
186
+ * awaitState("AllInputsSet") - all inputs of the inputs map must be set
187
+ * awaitState("inputName1", "ResourceReady") - resource in the input inputName1 must be ready
188
+ * awaitState("inputName1", "nestedField", "Set") - nested input field in the input
189
+ * inputName1 must exist and point to some resource
190
+ * awaitState("inputName1", { wildcard: "*" }, "InputsLocked") - nested resource by the
191
+ * inputName1 field must exist and have locked inputs and all
192
+ * resources referenced by those fields must have all inputs
193
+ * locked
194
+ * awaitState("inputName1", { match: "^some_prefix\\." }, "InputsLocked") - nested resource
195
+ * by the inputName1 field must exist and have locked inputs and
196
+ * all resources referenced by inputs with "some_prefix." prefix
197
+ * must have all inputs locked
198
+ *
199
+ * Example, the following two expressions are equivalent:
200
+ * awaitState("inputName1", { wildcard: "*" }, "Set")
201
+ * awaitState("inputName1", { match: "^some_prefix\\." }, "Set")
202
+ * awaitState("inputName1", "AllInputsSet")
203
+ *
204
+ * Allowed states:
205
+ * Exists - field exists
206
+ * Set - field points to existing resource
207
+ * InputsLocked - field points to resource with locked inputs
208
+ * AllInputsSet - field points to resource with all inputs set
209
+ * ResourceReady - field points to ready resource
210
+ */
211
+ awaitState: func(...pathAndState) {
212
+ if !self.isEphemeral() {
213
+ ll.panic("awaitState allowed only in ephemeral templates")
214
+ }
215
+ path := pathAndState[:len(pathAndState) - 1]
216
+ state := pathAndState[len(pathAndState) - 1]
217
+ // if len(self.prepareQueue) == 0 {
218
+ _stateRequests = append(_stateRequests, {
219
+ path: path,
220
+ state: state
221
+ })
222
+ // } else if len(self.prepareQueue) == 1 {
223
+ // _stateRequestsAfterPrepare = append(_stateRequestsAfterPrepare, {
224
+ // path: path,
225
+ // state: state
226
+ // })
227
+ // } else {
228
+ // ll.panic("Multiple prepare are not supported")
229
+ // }
230
+ },
231
+
232
+ /**
233
+ * True means we are in the inner render cycle, when all "to be prepared" fields are passed
234
+ * already to inputs and no need to call prepare callbacks again.
235
+ */
236
+ ignorePrepare: _hasPrepared,
237
+
238
+ /**
239
+ * Resource map with the resources passed
240
+ */
241
+ preparedMapResource: func() {
242
+ return _preparedMap
243
+ },
244
+
245
+ /**
246
+ * A queue of 'prepare' callbacks
247
+ */
248
+ prepareQueue : [],
249
+
250
+ /**
251
+ * Prepare additional resources for the template body. The body function of the workflow will be executed only when the references
252
+ * returned from prepare method will be resolved into a ready resources. The resulting resources will be passed as an input to the body function.
253
+ *
254
+ * @param cb: a callback function returning a map of references to be resolved
255
+ */
256
+ prepare : func(codeFn) {
257
+ if self.ignorePrepare {
258
+ return
259
+ }
260
+
261
+ ll.assert(len(self.prepareQueue) == 0, "multiple prepare statements are not supported yet")
262
+
263
+ self.prepareQueue = append(self.prepareQueue, codeFn)
264
+ },
265
+
266
+ /**
267
+ * Creates renderer which is re-rendering the current template additionally passing
268
+ * resolve references as extra inputs.
269
+ */
270
+ renderPrepare: func() {
271
+ ll.assert(!self.ignorePrepare, "expected !ignorePrepare")
272
+
273
+ inputs := self.inputs()
274
+
275
+ // create new inputs map
276
+ newInputs := copy(inputs)
277
+
278
+ // build a prepare map
279
+ builder := undefined
280
+ if self.isEphemeral() {
281
+ builder = smart.ephemeralMapBuilder()
282
+ } else {
283
+ builder = smart.mapBuilder()
284
+ }
285
+
286
+ // validate inputs first if needed
287
+ self._validateInputs()
288
+ for cb in self.prepareQueue {
289
+ // run callback
290
+ r := cb(inputs)
291
+
292
+ ll.assert(is_map(r), "expected map, got ", r)
293
+
294
+ for name, ref in r {
295
+ ll.assert(smart.isReference(ref), "expected reference, got ", ref)
296
+ ll.assert(is_undefined(newInputs[name]), "prepare name is already in use: ", name)
297
+
298
+ builder.add(name, ref)
299
+ }
300
+ }
301
+ newInputs[tplutil.PREPARE_MAP_FIELD_NAME] = builder.build()
302
+
303
+ // re-render self
304
+ renderer := undefined
305
+ if self.isEphemeral() {
306
+ renderer = render.createEphemeral(self.template(), newInputs)
307
+ } else {
308
+ renderer = render.create(self.template(), newInputs)
309
+ }
310
+ // set outputs
311
+ for output in self.definedOutputs {
312
+ self.getOutputField(output).set(renderer.output(output))
313
+ }
314
+ },
315
+
316
+ /**
317
+ * Sets results map to resource output fields. Non-resource objects in the result values
318
+ * will be automatically converted into Json resources.
319
+ */
320
+ setOutputs : func(resultMap) {
321
+ ll.assert(ll.isMap(resultMap), "expected map, got ", resultMap)
322
+
323
+ // set outputs
324
+ for name, value in resultMap {
325
+ f := self.getOutputField(name)
326
+ if !f.isSet() {
327
+ f.setRefOrJson(value)
328
+ }
329
+ }
330
+ },
331
+
332
+ /**
333
+ * Returns renderer's output field with a given name
334
+ */
335
+ getOutputField: func(name) {
336
+ return self.renderer().getField(tplutil.TEMPLATE_OUTPUT_PREFIX + name)
337
+ },
338
+
339
+ /**
340
+ * Creates relevant output fields and locks renderer's outputs
341
+ */
342
+ createOutputsAndLock: func(outputs) {
343
+ for name in outputs {
344
+ self.renderer().createOutputField(tplutil.TEMPLATE_OUTPUT_PREFIX + name)
345
+ }
346
+ self.renderer().lockOutputs()
347
+ },
348
+
349
+ _inputsSchema: undefined,
350
+
351
+ /**
352
+ * Validate inputs schema before passing inputs to body.
353
+ */
354
+ validateInputs: func(schema) {
355
+ ll.assert(is_undefined(self._inputsSchema), "validateInputs was already invoked")
356
+ self._inputsSchema = schema
357
+ },
358
+
359
+ _validateInputs: func() {
360
+ schema := self._inputsSchema
361
+ if is_undefined(schema) {
362
+ return
363
+ }
364
+ validation.assertJsonSchema(self.inputs(), schema)
365
+ },
366
+
367
+ /**
368
+ * Routes template.
369
+ */
370
+ body: func(bodyFn){
371
+ if self.isEphemeral() {
372
+
373
+ self.routeEph(bodyFn)
374
+ } else {
375
+
376
+ self.routePure(bodyFn)
377
+ }
378
+ },
379
+
380
+ /**
381
+ * Routes pure template.
382
+ */
383
+ routePure: func(bodyFn){
384
+ // Checks
385
+ if len(self.definedOutputs) == 0 {
386
+ ll.panic("please define outputs before template body")
387
+ }
388
+
389
+ //
390
+ // Rules of pl guarantees that pure template code will be executed twice:
391
+ //
392
+ // (1) once for initialization, at this point it must initialize all outputs, for the
393
+ // deduplication and recovery mechanisms to pick them up and, if possible, populate them
394
+ // with results from equivalent previous invocations
395
+ //
396
+ // (2) after initialization is done and all deduplication and recovery algorithms did their
397
+ // magic, if some (or all) of the outputs, our consumers demand are still absent, main
398
+ // body of the template is executed, to calculate them
399
+ //
400
+
401
+ if ll.isInitializing() {
402
+
403
+ // Initialization
404
+ self.createOutputsAndLock(self.definedOutputs)
405
+ } else {
406
+
407
+ // processing resolves
408
+ if len(self.prepareQueue) != 0 {
409
+
410
+ // re-render self to
411
+ self.renderPrepare()
412
+ } else {
413
+ // run body func
414
+ self._validateInputs()
415
+ result := bodyFn(self.inputs())
416
+
417
+ ll.assert(ll.isMap(result),
418
+ "expected map from tpl.body() callback. Did you forget 'return' statement?")
419
+
420
+ outputsWithValues := sets.fromMapKeys(result)
421
+ outputsWithValues = sets.add(outputsWithValues, _delegatedOutputs...)
422
+
423
+ ll.assert(sets.fromSlice(self.definedOutputs) == outputsWithValues,
424
+ "tpl.body() callback must return the same list of outputs as were given to 'defineOutputs()'. Want %s, got %s",
425
+ self.definedOutputs, sets.toSlice(outputsWithValues))
426
+
427
+ // set result to self outputs
428
+ self.setOutputs(result)
429
+ }
430
+ }
431
+ },
432
+
433
+ /**
434
+ * Routes ephemeral template.
435
+ */
436
+ routeEph: func(bodyFn) {
437
+
438
+ if len(_stateRequests) == 0 {
439
+ _stateRequests = [{
440
+ path: [],
441
+ state: "AllInputsSet"
442
+ }]
443
+ }
444
+
445
+ if _hasPrepared {
446
+ _stateRequests = append(_stateRequests, {
447
+ path: [tplutil.PREPARE_MAP_FIELD_NAME],
448
+ state: "ResourceReady"
449
+ })
450
+ }
451
+
452
+ stateReached := stateAwait.await(
453
+ _renderer.getField(tplutil.INPUTS_MAP_FIELD),
454
+ _renderer,
455
+ _stateRequests
456
+ )
457
+
458
+ if stateReached {
459
+
460
+ if len(self.prepareQueue) > 0 {
461
+ ll.assert(len(self.definedOutputs) > 0, "outputs should be defined prior to prepare")
462
+
463
+ self.createOutputsAndLock(self.definedOutputs)
464
+
465
+ self.renderPrepare()
466
+ } else {
467
+
468
+ // run body func
469
+ self._validateInputs()
470
+ result := bodyFn(self.inputs())
471
+ ll.assert(ll.isMap(result),
472
+ "expected map from tpl-eph.body() callback. Did you forget 'return' statement?")
473
+
474
+ self.createOutputsAndLock(maps.getKeys(result))
475
+
476
+ // set result to self outputs
477
+ self.setOutputs(result)
478
+ }
479
+ }
480
+ },
481
+
482
+ /**
483
+ * Delegates itself to the specified template, i.e. runs specified template and
484
+ * saves its outputs to this.
485
+ *
486
+ * @param tpl: template - specified template
487
+ * @param extraInputs: map - additional inputs to pass to the template
488
+ * @param outputs: array - array of expected outputs to save
489
+ */
490
+ routeDelegate: func(tpl, extraInputs, outputs) {
491
+ _delegatedOutputs = outputs
492
+
493
+ inputs := self.rawInputs()
494
+ for k, v in extraInputs {
495
+ inputs[k] = v
496
+ }
497
+
498
+ renderer := undefined
499
+ if self.isEphemeral() {
500
+ renderer = render.createEphemeral(tpl, inputs)
501
+ } else {
502
+ renderer = render.create(tpl, inputs)
503
+ }
504
+
505
+ tplOutputs := {}
506
+ for o in outputs {
507
+ tplOutputs[o] = renderer.output(o)
508
+ }
509
+
510
+ self.createOutputsAndLock(outputs)
511
+ self.setOutputs(tplOutputs)
512
+ }
513
+ }
514
+ return ll.toStrict(self)
515
+ }
516
+
517
+ /**
518
+ * Singleton instance of this resource
519
+ */
520
+ instance := tpl()
521
+
522
+ export ll.toStrict({
523
+ renderer : instance.renderer,
524
+ registerUnmarshaller : instance.registerUnmarshaller,
525
+ rawInputs : instance.rawInputs,
526
+ hasInput : instance.hasInput,
527
+ inputs : instance.inputs,
528
+ defineOutputs : instance.defineOutputs,
529
+ prepare : instance.prepare,
530
+ awaitState : instance.awaitState,
531
+ delegate : instance.routeDelegate,
532
+ validateInputs : instance.validateInputs,
533
+ body : instance.body,
534
+ template : instance.template
535
+ })