@platforma-sdk/workflow-tengo 4.7.0 → 4.7.1

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 (34) hide show
  1. package/dist/tengo/lib/feats.lib.tengo +2 -1
  2. package/dist/tengo/lib/smart.lib.tengo +16 -0
  3. package/dist/tengo/lib/tpl.base.lib.tengo +3 -2
  4. package/dist/tengo/lib/tpl.state-await.lib.tengo +88 -29
  5. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  6. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  7. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  8. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  9. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  10. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  11. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  12. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  13. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  14. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  15. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  16. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  17. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  18. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  19. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  20. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  21. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  22. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  23. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  24. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  25. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  26. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  27. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  28. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  29. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  30. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  31. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  32. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  33. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  34. package/package.json +1 -1
@@ -37,5 +37,6 @@ export ll.toStrict({
37
37
  pureFutureFields: _isEnabled("pureFutureFields") || _api >= 1, // 'pure' future fields magic that provides deduplication
38
38
  commandExpressions: _isEnabled("commandExpressions") || _api >= 2, // rich expressions in command arguments ( {{ system.ram_bytes / 20 }} )
39
39
  workdirLimits: _isEnabled("workdirLimits") || _api >= 4, // workdir storage controller can limit concurrency
40
- computeLimits: _isEnabled("computeLimits") // runner can plan CPU and RAM
40
+ computeLimits: _isEnabled("computeLimits"), // runner can plan CPU and RAM
41
+ resourceDuplicateEvent: false // template can subscribe to ResourceDuplicate event
41
42
  })
@@ -685,6 +685,22 @@ resource = func(r) {
685
685
 
686
686
 
687
687
 
688
+ createDynamicField: func(name) {
689
+ if is_bytes(name) {
690
+ name = string(name)
691
+ }
692
+ ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
693
+ fid := ll.fieldId(self.id, name)
694
+ tx.createField(fid, constants.FTYPE_DYNAMIC)
695
+ return field(fid)
696
+ },
697
+
698
+
699
+
700
+
701
+
702
+
703
+
688
704
 
689
705
  kvGet: func(key) {
690
706
  return tx.resourceKeyValueGet(self.id, key)
@@ -102,7 +102,8 @@ AWAIT_TEMPLATES := {
102
102
  "PColumnBundleWithPartitions": [
103
103
  [{ wildcard: "*" }, { match: "^ref$" }, "ResourceReady"], // optional ref field
104
104
  [{ wildcard: "*" }, "spec", "ResourceReady"],
105
- [{ wildcard: "*" }, "data", "InputsLocked"]
105
+ [{ wildcard: "*" }, "data", "InputsLocked"],
106
+ [{ wildcard: "*" }, "data", { match: "^\\{.*\\}$" }, "InputsLocked"] // for multi-hit BQuery results; key is always ref
106
107
  ]
107
108
  }
108
109
 
@@ -568,7 +569,7 @@ tpl := func(ops) {
568
569
  if _hasPrepared {
569
570
  _stateRequests = append(_stateRequests, {
570
571
  path: [tplutil.PREPARE_MAP_FIELD_NAME],
571
- state: "ResourceReady"
572
+ state: "Final"
572
573
  })
573
574
  }
574
575
 
@@ -7,6 +7,9 @@ tx := import("tx")
7
7
  ll := import("@platforma-sdk/workflow-tengo:ll")
8
8
  text := import("text")
9
9
  regexp := import("@platforma-sdk/workflow-tengo:regexp")
10
+ smart := import("@platforma-sdk/workflow-tengo:smart")
11
+ maps := import("@platforma-sdk/workflow-tengo:maps")
12
+ feats := import("@platforma-sdk/workflow-tengo:feats")
10
13
 
11
14
  createContext := func() {
12
15
  return {
@@ -15,6 +18,9 @@ createContext := func() {
15
18
  }
16
19
  }
17
20
 
21
+
22
+ RTYPE_AWAITER := { Name: "awaiter", Version: "1" }
23
+
18
24
  awaitOneState := func(field, path, state) {
19
25
  if field.isErrorSet() && !field.isValueSet() {
20
26
 
@@ -51,9 +57,6 @@ awaitOneState := func(field, path, state) {
51
57
  subscriptions: [{
52
58
  resource: resource.id,
53
59
  event: "FieldCreated"
54
- },{
55
- resource: resource.id,
56
- event: "InputsLocked"
57
60
  }]
58
61
  }
59
62
  }
@@ -93,19 +96,19 @@ awaitOneState := func(field, path, state) {
93
96
  }
94
97
 
95
98
  subscriptions := []
96
- for nextFieldName, nextField in resource.inputs() {
99
+ maps.forEach(resource.inputs(), func(nextFieldName, nextField) {
97
100
  if !matcher(nextFieldName) {
98
- continue
101
+ return
99
102
  }
100
103
  res := awaitOneState(nextField, rPath, state)
101
104
  if res.type == "Ready" {
102
- continue
105
+ return
103
106
  } else if res.type == "Subscribe" {
104
107
  subscriptions = append(subscriptions, res.subscriptions...)
105
108
  } else {
106
109
  ll.panic("Unknown result type: %v", res.type)
107
110
  }
108
- }
111
+ })
109
112
  if len(subscriptions) == 0 {
110
113
  return { type: "Ready" }
111
114
  } else {
@@ -157,7 +160,7 @@ awaitOneState := func(field, path, state) {
157
160
  }]
158
161
  }
159
162
  }
160
- } else if state == "ResourceReady" {
163
+ } else if state == "ResourceReady" || state == "Final" {
161
164
 
162
165
 
163
166
  if resource.isFinal() {
@@ -167,7 +170,7 @@ awaitOneState := func(field, path, state) {
167
170
  type: "Subscribe",
168
171
  subscriptions: [{
169
172
  resource: resource.id,
170
- event: "ResourceReady"
173
+ event: "Final" // will also effectively subscribe to ResourceDuplicate and ResourceError
171
174
  }]
172
175
  }
173
176
  }
@@ -178,19 +181,21 @@ awaitOneState := func(field, path, state) {
178
181
  }
179
182
  }
180
183
 
184
+ _renderer := smart.resource(ll.getCurrentTemplateRenderer())
185
+
181
186
 
182
187
  await := func(rootField, ctxStorageResource, requests) {
183
188
  pssKey := "await:" + string(rootField.id.ResourceID) + ":" + rootField.id.Name
184
189
  prevSubscriptions := ctxStorageResource.kvGetAsJson(pssKey)
185
190
 
186
- if !is_undefined(prevSubscriptions) && len(prevSubscriptions) == 0 {
191
+ lastSubscriptionKey := undefined
192
+ if !is_undefined(prevSubscriptions) {
193
+ if is_undefined(prevSubscriptions.lastSubscriptionKey) {
187
194
 
188
- ll.panic("Unexpected state, desired state was reached in the previous call")
189
- }
190
-
191
- if is_undefined(prevSubscriptions) {
192
-
193
- prevSubscriptions = {}
195
+ ll.panic("Unexpected state, desired state was reached in the previous call")
196
+ } else {
197
+ lastSubscriptionKey = prevSubscriptions.lastSubscriptionKey
198
+ }
194
199
  }
195
200
 
196
201
  subscriptions := {}
@@ -222,34 +227,88 @@ await := func(rootField, ctxStorageResource, requests) {
222
227
  }
223
228
 
224
229
 
225
- for key, sub in subscriptions {
226
- if !is_undefined(prevSubscriptions[key]) {
227
230
 
228
- continue
231
+
232
+
233
+
234
+
235
+
236
+
237
+ firstSubscriptionKey := undefined
238
+ firstSubscription := undefined
239
+ lastSubscriptionKeyPresent := false
240
+ maps.forEach(subscriptions, func(key, sub) {
241
+ if is_undefined(firstSubscriptionKey) {
242
+ firstSubscriptionKey = key
243
+ firstSubscription = sub
229
244
  }
230
- e := {}
231
- e[sub.event] = true
232
- tx.subscribeTo(sub.resource, pssKey + ":" + key, e)
245
+ if !is_undefined(lastSubscriptionKey) && lastSubscriptionKey == key {
246
+ lastSubscriptionKeyPresent = true
247
+ }
248
+ })
249
+
250
+
251
+
252
+ if lastSubscriptionKeyPresent {
253
+ return false
233
254
  }
234
255
 
235
256
 
236
- for key, _ in prevSubscriptions {
237
- if !is_undefined(subscriptions[key]) {
257
+ if !is_undefined(firstSubscriptionKey) {
238
258
 
239
- continue
259
+ fullEventId := pssKey + ":" + firstSubscriptionKey
260
+ targetResource := firstSubscription.resource
261
+ targetEvents := [firstSubscription.event]
262
+
263
+ if firstSubscription.event == "Final" { // special case
264
+ if !feats.resourceDuplicateEvent {
265
+
266
+
267
+
268
+
269
+
270
+
271
+ target := smart.resource(targetResource)
272
+ isEphemeral := target.info().Features.ephemeral
273
+ if !isEphemeral { // doing proxy-object workaround for pure resources only
274
+ awaiterBuilder := smart.ephemeralBuilder(RTYPE_AWAITER)
275
+ awaiterBuilder.createInputField("target").set(target)
276
+ awaiter := awaiterBuilder.lockAndBuild()
277
+ _renderer.createDynamicField("tmp:" + fullEventId).set(awaiter)
278
+ targetResource = awaiter.info().ID // converting to global resource id; this is a hack to overcome the fact that tx.subscribeTo can't work with local ids
279
+ }
280
+ targetEvents = ["ResourceReady", "ResourceError"]
281
+
282
+ } else {
283
+ targetEvents = ["ResourceReady", "ResourceError", "ResourceDuplicate"]
284
+ }
285
+ } else if firstSubscription.event == "FieldCreated" { // another special case
286
+
287
+ targetEvents = ["FieldCreated", "InputsLocked"]
240
288
  }
241
- tx.unsubscribeFrom(pssKey + ":" + key)
289
+
290
+ e := {}
291
+ for event in targetEvents {
292
+ e[event] = true
293
+ }
294
+ tx.subscribeTo(targetResource, fullEventId, e)
295
+
296
+ }
297
+
298
+
299
+ if !is_undefined(lastSubscriptionKey) {
300
+ tx.unsubscribeFrom(pssKey + ":" + lastSubscriptionKey)
242
301
  }
243
302
 
244
303
 
245
304
  newPrevSubscriptions := {}
246
- for key, _ in subscriptions {
247
- newPrevSubscriptions[key] = true
305
+ if !is_undefined(firstSubscriptionKey) {
306
+ newPrevSubscriptions.lastSubscriptionKey = firstSubscriptionKey
248
307
  }
249
308
 
250
309
  ctxStorageResource.kvSetJson(pssKey, newPrevSubscriptions)
251
310
 
252
- return len(subscriptions) == 0
311
+ return is_undefined(firstSubscriptionKey)
253
312
  }
254
313
 
255
314
  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.7.0",
3
+ "version": "4.7.1",
4
4
  "description": "Tengo SDK library",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",