@platforma-sdk/workflow-tengo 2.9.6 → 2.9.8

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 (64) hide show
  1. package/dist/tengo/lib/assets.lib.tengo +52 -52
  2. package/dist/tengo/lib/assets.swinfo.lib.tengo +45 -45
  3. package/dist/tengo/lib/constants.lib.tengo +32 -32
  4. package/dist/tengo/lib/exec.R.internal.lib.tengo +29 -29
  5. package/dist/tengo/lib/exec.R.lib.tengo +166 -166
  6. package/dist/tengo/lib/exec.constants.lib.tengo +4 -4
  7. package/dist/tengo/lib/exec.descriptor.lib.tengo +31 -31
  8. package/dist/tengo/lib/exec.internal.lib.tengo +297 -297
  9. package/dist/tengo/lib/exec.lib.tengo +815 -815
  10. package/dist/tengo/lib/exec.monetization.lib.tengo +130 -130
  11. package/dist/tengo/lib/exec.package.lib.tengo +212 -212
  12. package/dist/tengo/lib/exec.python.internal.lib.tengo +6 -6
  13. package/dist/tengo/lib/exec.python.lib.tengo +146 -146
  14. package/dist/tengo/lib/exec.runcmd.lib.tengo +371 -371
  15. package/dist/tengo/lib/exec.runenv.lib.tengo +92 -92
  16. package/dist/tengo/lib/exec.stream.lib.tengo +40 -40
  17. package/dist/tengo/lib/file.internal.lib.tengo +77 -77
  18. package/dist/tengo/lib/file.lib.tengo +57 -57
  19. package/dist/tengo/lib/ll.lib.tengo +244 -244
  20. package/dist/tengo/lib/maps.lib.tengo +152 -152
  21. package/dist/tengo/lib/objects.lib.tengo +13 -13
  22. package/dist/tengo/lib/oop.lib.tengo +19 -19
  23. package/dist/tengo/lib/path.lib.tengo +125 -125
  24. package/dist/tengo/lib/pframes.constants.lib.tengo +28 -28
  25. package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +33 -33
  26. package/dist/tengo/lib/pframes.lib.tengo +43 -43
  27. package/dist/tengo/lib/pframes.ll.lib.tengo +72 -72
  28. package/dist/tengo/lib/pframes.pcolumn.lib.tengo +49 -49
  29. package/dist/tengo/lib/pframes.spec.lib.tengo +96 -96
  30. package/dist/tengo/lib/pframes.util.lib.tengo +288 -288
  31. package/dist/tengo/lib/pframes.xsv.lib.tengo +272 -272
  32. package/dist/tengo/lib/render.lib.tengo +75 -75
  33. package/dist/tengo/lib/sets.lib.tengo +68 -68
  34. package/dist/tengo/lib/slices.lib.tengo +258 -155
  35. package/dist/tengo/lib/smart.lib.tengo +1069 -1069
  36. package/dist/tengo/lib/test.lib.tengo +161 -161
  37. package/dist/tengo/lib/tpl.lib.tengo +515 -515
  38. package/dist/tengo/lib/tpl.state-await.lib.tengo +240 -240
  39. package/dist/tengo/lib/tpl.util.lib.tengo +9 -9
  40. package/dist/tengo/lib/validation.lib.tengo +378 -378
  41. package/dist/tengo/lib/workdir.lib.tengo +331 -331
  42. package/dist/tengo/lib/workdir.proc.lib.tengo +144 -144
  43. package/dist/tengo/lib/workdir.save.lib.tengo +130 -130
  44. package/dist/tengo/lib/workflow.bobject.lib.tengo +26 -26
  45. package/dist/tengo/lib/workflow.bquery.lib.tengo +165 -165
  46. package/dist/tengo/lib/workflow.constants.lib.tengo +2 -2
  47. package/dist/tengo/lib/workflow.context.lib.tengo +32 -32
  48. package/dist/tengo/lib/workflow.lib.tengo +206 -206
  49. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  50. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  51. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  52. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  53. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  54. package/dist/tengo/tpl/pframes.aggregate.plj.gz +0 -0
  55. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  56. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  57. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  58. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  59. package/dist/tengo/tpl/pframes.map-pframe.plj.gz +0 -0
  60. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  61. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  62. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  63. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  64. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
-
1
+ /* A library exposing template functional components */
2
2
 
3
3
  tx := import("tx")
4
4
  ll := import("@platforma-sdk/workflow-tengo:ll")
@@ -13,523 +13,523 @@ validation := import("@platforma-sdk/workflow-tengo:validation")
13
13
 
14
14
  bquery := import("@platforma-sdk/workflow-tengo:workflow.bquery")
15
15
 
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
16
+ /**
17
+ * Unmarshal resource according by:
18
+ * - converting all json resources (potentially nested) into a tengo objects (maps)
19
+ * - converting all pl.maps (resource maps) into tengo maps
20
+ *
21
+ * @param resource: smart resource to unmarshal
22
+ * @param preprocessors: input preprocessors
23
+ * @return object: unmarshaled resource
24
+ */
25
25
  _unmarshal := func(resource, preprocessors) {
26
- ll.assert(smart.isResource(resource), "expected resource, found ", resource)
27
-
28
-
29
- self := func(resource) { return _unmarshal(resource, preprocessors) }
30
-
31
- for preproc in preprocessors {
32
- if preproc.canParse(resource) {
33
- return preproc.parse(resource, self)
34
- }
35
- }
36
-
37
- if smart.isReference(resource) && resource.hasError() {
38
- ll.panic("input resource has errors: ", resource, resource.getError())
39
- }
40
-
41
- if smart.isJsonResource(resource) || resource.checkResourceType(constants.RTYPE_BOBJECT_SPEC) {
42
-
43
-
44
- return resource.getDataAsJson()
45
- } else if smart.isMapResource(resource) || smart.isLockedEphMapResource(resource) {
46
-
47
-
48
- result := {}
49
- for field, value in resource.inputs() {
50
- if value.isErrorSet() {
51
- ll.panic("input %v field has error: %v", field, field.getError().getDataAsJson().message)
52
- } else if value.isSet() {
53
- result[field] = _unmarshal(value.getValue(), preprocessors)
54
- } else {
55
- result[field] = value
56
- }
57
-
58
- }
59
- return result
60
- } else {
61
-
62
- return resource
63
- }
64
- }
65
-
66
-
67
-
68
-
26
+ ll.assert(smart.isResource(resource), "expected resource, found ", resource)
27
+
28
+ // instance of this
29
+ self := func(resource) { return _unmarshal(resource, preprocessors) }
30
+ // try preprocessors first
31
+ for preproc in preprocessors {
32
+ if preproc.canParse(resource) {
33
+ return preproc.parse(resource, self)
34
+ }
35
+ }
36
+
37
+ if smart.isReference(resource) && resource.hasError() {
38
+ ll.panic("input resource has errors: ", resource, resource.getError())
39
+ }
40
+
41
+ if smart.isJsonResource(resource) || resource.checkResourceType(constants.RTYPE_BOBJECT_SPEC) {
42
+
43
+ // parse json data of the resource
44
+ return resource.getDataAsJson()
45
+ } else if smart.isMapResource(resource) || smart.isLockedEphMapResource(resource) {
46
+
47
+ // unmarshal map
48
+ result := {}
49
+ for field, value in resource.inputs() {
50
+ if value.isErrorSet() {
51
+ ll.panic("input %v field has error: %v", field, field.getError().getDataAsJson().message)
52
+ } else if value.isSet() {
53
+ result[field] = _unmarshal(value.getValue(), preprocessors)
54
+ } else {
55
+ result[field] = value
56
+ }
57
+
58
+ }
59
+ return result
60
+ } else {
61
+ // nothing todo
62
+ return resource
63
+ }
64
+ }
65
+
66
+ /**
67
+ * A wrapper object around template functionality
68
+ */
69
69
  tpl := func() {
70
- self := undefined
71
-
72
- _renderer := smart.resourceBuilder(ll.getCurrentTemplateRenderer())
73
-
74
- _rendererType := _renderer.info().Type
75
-
76
- _isEphemeral := _rendererType.Name == constants.RTYPE_RENDER_EPH_TEMPLATE.Name
77
-
78
- _hasPrepared := _renderer.get(tplutil.INPUTS_MAP_FIELD).hasField(tplutil.PREPARE_MAP_FIELD_NAME)
79
-
80
- _preparedMap := _hasPrepared ? _renderer.get(tplutil.INPUTS_MAP_FIELD).get(tplutil.PREPARE_MAP_FIELD_NAME) : undefined
81
-
82
- _templateId := _renderer.get(tplutil.TEMPLATE_FIELD).id
83
-
84
- _stateRequests := []
85
- _delegatedOutputs := []
86
-
87
- self = {
88
-
89
-
90
-
91
-
92
- renderer: func() {
93
- return _renderer
94
- },
95
-
96
-
97
-
98
-
99
- isEphemeral: func() {
100
- return _isEphemeral
101
- },
102
-
103
-
104
-
105
-
106
- template: func() {
107
- return smart.resource(_templateId)
108
- },
109
-
110
- _unmarshallers: [bquery.resultUnmarshaller()],
111
-
112
-
113
-
114
-
115
-
116
-
117
- registerUnmarshaller: func(preproc) {
118
- ll.assert(is_undefined(self._inputs), "input preprocessors must be registered before getting inputs")
119
- self._unmarshallers = append(self._unmarshallers, preproc)
120
- },
121
-
122
-
123
-
124
-
125
- rawInputs: func() {
126
- return self.renderer().get(tplutil.INPUTS_MAP_FIELD).inputs()
127
- },
128
-
129
- _inputs: undefined,
130
-
131
-
132
-
133
-
134
- inputs: func() {
135
- if self._inputs == undefined {
136
- inputs := _unmarshal(self.renderer().get(tplutil.INPUTS_MAP_FIELD), self._unmarshallers)
137
-
138
-
139
- pp := inputs[tplutil.PREPARE_MAP_FIELD_NAME]
140
- if !is_undefined(pp) {
141
- delete(inputs, tplutil.PREPARE_MAP_FIELD_NAME)
142
- for k, v in pp {
143
- ll.assert(is_undefined(inputs[k]), k, " key already exists in inputs map")
144
- inputs[k] = v
145
- }
146
- }
147
-
148
- self._inputs = inputs
149
- }
150
- return self._inputs
151
- },
152
-
153
-
154
-
155
-
156
- hasInput: func(name) {
157
- return self.renderer().get(tplutil.INPUTS_MAP_FIELD).hasField(name)
158
- },
159
-
160
-
161
- definedOutputs : [],
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
- defineOutputs : func(...name) {
170
- if len(name) == 1 && is_array(name[0]) {
171
- for v in name[0] {
172
- self.definedOutputs = append(self.definedOutputs, v)
173
- }
174
- } else {
175
- self.definedOutputs = append(self.definedOutputs, name...)
176
- }
177
- },
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
- awaitState: func(...pathAndState) {
213
- if !self.isEphemeral() {
214
- ll.panic("awaitState allowed only in ephemeral templates")
215
- }
216
- path := pathAndState[:len(pathAndState) - 1]
217
- state := pathAndState[len(pathAndState) - 1]
218
-
219
- _stateRequests = append(_stateRequests, {
220
- path: path,
221
- state: state
222
- })
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
- },
232
-
233
-
234
-
235
-
236
-
237
- ignorePrepare: _hasPrepared,
238
-
239
-
240
-
241
-
242
- preparedMapResource: func() {
243
- return _preparedMap
244
- },
245
-
246
-
247
-
248
-
249
- prepareQueue : [],
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
- prepare : func(codeFn) {
258
- if self.ignorePrepare {
259
- return
260
- }
261
-
262
- ll.assert(len(self.prepareQueue) == 0, "multiple prepare statements are not supported yet")
263
-
264
- self.prepareQueue = append(self.prepareQueue, codeFn)
265
- },
266
-
267
-
268
-
269
-
270
-
271
- renderPrepare: func() {
272
- ll.assert(!self.ignorePrepare, "expected !ignorePrepare")
273
-
274
- inputs := self.inputs()
275
-
276
-
277
- newInputs := copy(inputs)
278
-
279
-
280
- builder := undefined
281
- if self.isEphemeral() {
282
- builder = smart.ephemeralMapBuilder()
283
- } else {
284
- builder = smart.mapBuilder()
285
- }
286
-
287
-
288
- self._validateInputs()
289
- for cb in self.prepareQueue {
290
-
291
- r := cb(inputs)
292
-
293
- ll.assert(is_map(r), "expected map, got ", r)
294
-
295
- for name, ref in r {
296
- ll.assert(is_undefined(newInputs[name]), "prepare name is already in use: %s", name)
297
-
298
- builder.add(name, ref)
299
- }
300
- }
301
- newInputs[tplutil.PREPARE_MAP_FIELD_NAME] = builder.build()
302
-
303
-
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
-
311
- for output in self.definedOutputs {
312
- self.getOutputField(output).set(renderer.output(output))
313
- }
314
- },
315
-
316
-
317
-
318
-
319
-
320
- setOutputs : func(resultMap) {
321
- ll.assert(ll.isMap(resultMap), "expected map, got ", resultMap)
322
-
323
-
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
-
334
-
335
- getOutputField: func(name) {
336
- return self.renderer().getField(tplutil.TEMPLATE_OUTPUT_PREFIX + name)
337
- },
338
-
339
-
340
-
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
-
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
-
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
-
382
-
383
- routePure: func(bodyFn){
384
-
385
- if len(self.definedOutputs) == 0 {
386
- ll.panic("please define outputs before template body")
387
- }
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
- if ll.isInitializing() {
402
-
403
-
404
- self.createOutputsAndLock(self.definedOutputs)
405
- } else {
406
-
407
-
408
- if len(self.prepareQueue) != 0 {
409
-
410
-
411
- self.renderPrepare()
412
- } else {
413
-
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
-
428
- self.setOutputs(result)
429
- }
430
- }
431
- },
432
-
433
-
434
-
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
-
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
-
477
- self.setOutputs(result)
478
- }
479
- }
480
- },
481
-
482
-
483
-
484
-
485
-
486
-
487
-
488
-
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
-
519
-
70
+ self := undefined
71
+
72
+ _renderer := smart.resourceBuilder(ll.getCurrentTemplateRenderer())
73
+
74
+ _rendererType := _renderer.info().Type
75
+
76
+ _isEphemeral := _rendererType.Name == constants.RTYPE_RENDER_EPH_TEMPLATE.Name
77
+
78
+ _hasPrepared := _renderer.get(tplutil.INPUTS_MAP_FIELD).hasField(tplutil.PREPARE_MAP_FIELD_NAME)
79
+
80
+ _preparedMap := _hasPrepared ? _renderer.get(tplutil.INPUTS_MAP_FIELD).get(tplutil.PREPARE_MAP_FIELD_NAME) : undefined
81
+
82
+ _templateId := _renderer.get(tplutil.TEMPLATE_FIELD).id
83
+
84
+ _stateRequests := []
85
+ _delegatedOutputs := []
86
+
87
+ self = {
88
+
89
+ /**
90
+ * Current renderer smart resource
91
+ */
92
+ renderer: func() {
93
+ return _renderer
94
+ },
95
+
96
+ /**
97
+ * Whether this template is ephemeral (rendered by ephemeral template router)
98
+ */
99
+ isEphemeral: func() {
100
+ return _isEphemeral
101
+ },
102
+
103
+ /**
104
+ * Current template resource
105
+ */
106
+ template: func() {
107
+ return smart.resource(_templateId)
108
+ },
109
+
110
+ _unmarshallers: [bquery.resultUnmarshaller()],
111
+
112
+ /**
113
+ * Register a preprocessor of input resource. The preprocessor must have two
114
+ * methods: `canParse(resource)` (bool) and `parse(resource)` (object). Registered
115
+ * preprocessors will be used when unmarshaling resources.
116
+ */
117
+ registerUnmarshaller: func(preproc) {
118
+ ll.assert(is_undefined(self._inputs), "input preprocessors must be registered before getting inputs")
119
+ self._unmarshallers = append(self._unmarshallers, preproc)
120
+ },
121
+
122
+ /**
123
+ * @return map[string]field: - a map of inputs to the fields of the input map.
124
+ */
125
+ rawInputs: func() {
126
+ return self.renderer().get(tplutil.INPUTS_MAP_FIELD).inputs()
127
+ },
128
+
129
+ _inputs: undefined,
130
+
131
+ /**
132
+ * @return map: a map of the template inputs
133
+ */
134
+ inputs: func() {
135
+ if self._inputs == undefined {
136
+ inputs := _unmarshal(self.renderer().get(tplutil.INPUTS_MAP_FIELD), self._unmarshallers)
137
+
138
+ // if we have prepared inputs, flatten them
139
+ pp := inputs[tplutil.PREPARE_MAP_FIELD_NAME]
140
+ if !is_undefined(pp) {
141
+ delete(inputs, tplutil.PREPARE_MAP_FIELD_NAME)
142
+ for k, v in pp {
143
+ ll.assert(is_undefined(inputs[k]), k, " key already exists in inputs map")
144
+ inputs[k] = v
145
+ }
146
+ }
147
+
148
+ self._inputs = inputs
149
+ }
150
+ return self._inputs
151
+ },
152
+
153
+ /**
154
+ * @return bool: whether there is input with a given name
155
+ */
156
+ hasInput: func(name) {
157
+ return self.renderer().get(tplutil.INPUTS_MAP_FIELD).hasField(name)
158
+ },
159
+
160
+ // accumulated output definitions
161
+ definedOutputs : [],
162
+
163
+ /**
164
+ * Defines current template output(s) names.
165
+ *
166
+ * For pure templates, all outputs must be defined before the template body, and only registered outputs can be used
167
+ * inside. Optional for ephemeral templates.
168
+ */
169
+ defineOutputs : func(...name) {
170
+ if len(name) == 1 && is_array(name[0]) {
171
+ for v in name[0] {
172
+ self.definedOutputs = append(self.definedOutputs, v)
173
+ }
174
+ } else {
175
+ self.definedOutputs = append(self.definedOutputs, name...)
176
+ }
177
+ },
178
+
179
+ /**
180
+ * In ephemeral template, body will only be executed when corresponding input staet is
181
+ * reached.
182
+ *
183
+ * Signature:
184
+ * awaitState(path..., requestedState)
185
+ *
186
+ * Usage examples:
187
+ * awaitState("AllInputsSet") - all inputs of the inputs map must be set
188
+ * awaitState("inputName1", "ResourceReady") - resource in the input inputName1 must be ready
189
+ * awaitState("inputName1", "nestedField", "Set") - nested input field in the input
190
+ * inputName1 must exist and point to some resource
191
+ * awaitState("inputName1", { wildcard: "*" }, "InputsLocked") - nested resource by the
192
+ * inputName1 field must exist and have locked inputs and all
193
+ * resources referenced by those fields must have all inputs
194
+ * locked
195
+ * awaitState("inputName1", { match: "^some_prefix\\." }, "InputsLocked") - nested resource
196
+ * by the inputName1 field must exist and have locked inputs and
197
+ * all resources referenced by inputs with "some_prefix." prefix
198
+ * must have all inputs locked
199
+ *
200
+ * Example, the following two expressions are equivalent:
201
+ * awaitState("inputName1", { wildcard: "*" }, "Set")
202
+ * awaitState("inputName1", { match: "^some_prefix\\." }, "Set")
203
+ * awaitState("inputName1", "AllInputsSet")
204
+ *
205
+ * Allowed states:
206
+ * Exists - field exists
207
+ * Set - field points to existing resource
208
+ * InputsLocked - field points to resource with locked inputs
209
+ * AllInputsSet - field points to resource with all inputs set
210
+ * ResourceReady - field points to ready resource
211
+ */
212
+ awaitState: func(...pathAndState) {
213
+ if !self.isEphemeral() {
214
+ ll.panic("awaitState allowed only in ephemeral templates")
215
+ }
216
+ path := pathAndState[:len(pathAndState) - 1]
217
+ state := pathAndState[len(pathAndState) - 1]
218
+ // if len(self.prepareQueue) == 0 {
219
+ _stateRequests = append(_stateRequests, {
220
+ path: path,
221
+ state: state
222
+ })
223
+ // } else if len(self.prepareQueue) == 1 {
224
+ // _stateRequestsAfterPrepare = append(_stateRequestsAfterPrepare, {
225
+ // path: path,
226
+ // state: state
227
+ // })
228
+ // } else {
229
+ // ll.panic("Multiple prepare are not supported")
230
+ // }
231
+ },
232
+
233
+ /**
234
+ * True means we are in the inner render cycle, when all "to be prepared" fields are passed
235
+ * already to inputs and no need to call prepare callbacks again.
236
+ */
237
+ ignorePrepare: _hasPrepared,
238
+
239
+ /**
240
+ * Resource map with the resources passed
241
+ */
242
+ preparedMapResource: func() {
243
+ return _preparedMap
244
+ },
245
+
246
+ /**
247
+ * A queue of 'prepare' callbacks
248
+ */
249
+ prepareQueue : [],
250
+
251
+ /**
252
+ * Prepare additional resources for the template body. The body function of the workflow will be executed only when the references
253
+ * returned from prepare method will be resolved into a ready resources. The resulting resources will be passed as an input to the body function.
254
+ *
255
+ * @param cb: a callback function returning a map of references to be resolved
256
+ */
257
+ prepare : func(codeFn) {
258
+ if self.ignorePrepare {
259
+ return
260
+ }
261
+
262
+ ll.assert(len(self.prepareQueue) == 0, "multiple prepare statements are not supported yet")
263
+
264
+ self.prepareQueue = append(self.prepareQueue, codeFn)
265
+ },
266
+
267
+ /**
268
+ * Creates renderer which is re-rendering the current template additionally passing
269
+ * resolve references as extra inputs.
270
+ */
271
+ renderPrepare: func() {
272
+ ll.assert(!self.ignorePrepare, "expected !ignorePrepare")
273
+
274
+ inputs := self.inputs()
275
+
276
+ // create new inputs map
277
+ newInputs := copy(inputs)
278
+
279
+ // build a prepare map
280
+ builder := undefined
281
+ if self.isEphemeral() {
282
+ builder = smart.ephemeralMapBuilder()
283
+ } else {
284
+ builder = smart.mapBuilder()
285
+ }
286
+
287
+ // validate inputs first if needed
288
+ self._validateInputs()
289
+ for cb in self.prepareQueue {
290
+ // run callback
291
+ r := cb(inputs)
292
+
293
+ ll.assert(is_map(r), "expected map, got ", r)
294
+
295
+ for name, ref in r {
296
+ ll.assert(is_undefined(newInputs[name]), "prepare name is already in use: %s", 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
520
  instance := tpl()
521
521
 
522
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
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
535
  })