@platforma-sdk/workflow-tengo 4.7.0 → 4.7.2

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 (35) hide show
  1. package/dist/tengo/lib/feats.lib.tengo +7 -1
  2. package/dist/tengo/lib/ll.lib.tengo +138 -1
  3. package/dist/tengo/lib/smart.lib.tengo +71 -1
  4. package/dist/tengo/lib/tpl.base.lib.tengo +3 -2
  5. package/dist/tengo/lib/tpl.state-await.lib.tengo +88 -29
  6. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  7. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  8. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  9. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  10. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  11. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  12. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  13. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  14. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  15. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  16. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  17. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  18. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  19. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  20. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  21. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  22. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  23. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  24. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  25. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  26. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  27. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  28. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  29. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  30. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  31. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  32. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  33. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  34. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  35. package/package.json +2 -2
@@ -37,5 +37,11 @@ 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
+
42
+
43
+
44
+
45
+
46
+ fullFeaturedApi: _isEnabled("fullFeaturedAPI")
41
47
  })
@@ -289,10 +289,137 @@ parseUrl := func(url) {
289
289
 
290
290
 
291
291
 
292
+
293
+
292
294
  getCtx := func() {
295
+ if methodExists(plapi, "getPersistentCtx") {
296
+ return plapi.getPersistentCtx()
297
+ }
298
+
299
+
300
+ return plapi.getCtx()
301
+ }
302
+
303
+
304
+
305
+
306
+
307
+ getPersistentCtx := func() {
308
+ if methodExists(plapi, "getPersistentCtx") {
309
+ return plapi.getPersistentCtx()
310
+ }
311
+
312
+
293
313
  return plapi.getCtx()
294
314
  }
295
315
 
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+ getExecutionCtx := func() {
327
+ assert(methodExists(plapi, "getExecutionCtx"), "ll.getExecutionCtx: method does not exists in this backend instance.")
328
+ return plapi.getExecutionCtx()
329
+ }
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+ moduleVars := func(moduleName) {
338
+ assert(is_string(moduleName), "ll.moduleVars: moduleName must be a string")
339
+
340
+ name := "module/" + moduleName
341
+
342
+ ctx := getExecutionCtx()
343
+ if is_undefined(ctx[name]) {
344
+ ctx[name] = {}
345
+ }
346
+
347
+ return {
348
+ get: func(k) {
349
+ return ctx[name][k]
350
+ },
351
+
352
+ set: func(k, v) {
353
+ ctx[name][k] = v
354
+ }
355
+ }
356
+ }
357
+
358
+
359
+
360
+
361
+ sha256Encode := func(b) {
362
+ assert(methodExists(plapi, "sha256Encode"), "ll.sha256Encode: method does not exists in this backend instance.")
363
+ return plapi.sha256Encode(b)
364
+ }
365
+
366
+
367
+
368
+
369
+ gzipEncode := func(b) {
370
+ assert(methodExists(plapi, "gzipEncode"), "ll.gzipEncode: method does not exists in this backend instance.")
371
+ return plapi.gzipEncode(b)
372
+ }
373
+
374
+
375
+
376
+
377
+ gzipDecode := func(b) {
378
+ assert(methodExists(plapi, "gzipDecode"), "ll.gzipDecode: method does not exists in this backend instance.")
379
+ return plapi.gzipDecode(b)
380
+ }
381
+
382
+
383
+
384
+
385
+ base32Encode := func(b) {
386
+ assert(methodExists(plapi, "base32Encode"), "ll.base32Encode: method does not exists in this backend instance.")
387
+ return plapi.base32Encode(b)
388
+ }
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+ base32HexEncode := func(b) {
398
+ assert(methodExists(plapi, "base32HexEncode"), "ll.base32HexEncode: method does not exists in this backend instance.")
399
+ return plapi.base32HexEncode(b)
400
+ }
401
+
402
+
403
+
404
+
405
+ base32Decode := func(s) {
406
+ assert(methodExists(plapi, "base32Decode"), "ll.base32Decode: method does not exists in this backend instance.")
407
+ return plapi.base32Decode(s)
408
+ }
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+ base32HexDecode := func(s) {
418
+ assert(methodExists(plapi, "base32HexDecode"), "ll.base32HexDecode: method does not exists in this backend instance.")
419
+ return plapi.base32HexDecode(s)
420
+ }
421
+
422
+
296
423
  export toStrict({
297
424
  apiVersion: apiVersion,
298
425
  print: print,
@@ -315,5 +442,15 @@ export toStrict({
315
442
  pathSeparator: pathSeparator,
316
443
  filepathCanonize: filepathCanonize,
317
444
  parseUrl: parseUrl,
318
- getCtx: getCtx
445
+ getCtx: getCtx,
446
+ getPersistentCtx: getPersistentCtx,
447
+ getExecutionCtx: getExecutionCtx,
448
+ moduleVars: moduleVars,
449
+ sha256Encode: sha256Encode,
450
+ gzipEncode: gzipEncode,
451
+ gzipDecode: gzipDecode,
452
+ base32Encode: base32Encode,
453
+ base32HexEncode: base32HexEncode,
454
+ base32Decode: base32Decode,
455
+ base32HexDecode: base32HexDecode
319
456
  })
@@ -362,6 +362,13 @@ resource = func(r) {
362
362
 
363
363
 
364
364
 
365
+
366
+
367
+
368
+
369
+
370
+
371
+
365
372
 
366
373
 
367
374
 
@@ -390,7 +397,14 @@ resource = func(r) {
390
397
 
391
398
 
392
399
  isFinal: func() {
393
- return self.info().ResourceReady || self.info().IsDuplicate || self.info().HasErrors
400
+
401
+ if false {
402
+ return self.info().IsFinal
403
+ }
404
+
405
+ info := self.info()
406
+
407
+ return info.ResourceReady || info.IsDuplicate || info.HasErrors
394
408
  },
395
409
 
396
410
  isEphemeral: func() {
@@ -685,6 +699,24 @@ resource = func(r) {
685
699
 
686
700
 
687
701
 
702
+ createDynamicField: func(name) {
703
+ if is_bytes(name) {
704
+ name = string(name)
705
+ }
706
+ ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
707
+ fid := ll.fieldId(self.id, name)
708
+ tx.createField(fid, constants.FTYPE_DYNAMIC)
709
+ return field(fid)
710
+ },
711
+
712
+
713
+
714
+
715
+
716
+
717
+
718
+
719
+
688
720
 
689
721
  kvGet: func(key) {
690
722
  return tx.resourceKeyValueGet(self.id, key)
@@ -696,6 +728,17 @@ resource = func(r) {
696
728
 
697
729
 
698
730
 
731
+ kvGetFlag: func(key) {
732
+ ll.assert(feats.fullFeaturedApi, "smart.resource.kvGetFlag: the feature is not available on older backends. Please, update the backend.")
733
+ return tx.resourceKeyValueGetFlag(self.id, key)
734
+ },
735
+
736
+
737
+
738
+
739
+
740
+
741
+
699
742
 
700
743
  kvSet: func(key, value) {
701
744
  tx.resourceKeyValueSet(self.id, key, value)
@@ -707,6 +750,17 @@ resource = func(r) {
707
750
 
708
751
 
709
752
 
753
+ kvSetFlag: func(key, flag) {
754
+ ll.assert(feats.fullFeaturedApi, "smart.resource.kvSetFlag: the feature is not available on older backends. Please, update the backend.")
755
+ tx.resourceKeyValueSetFlag(self.id, key, flag)
756
+ },
757
+
758
+
759
+
760
+
761
+
762
+
763
+
710
764
 
711
765
  kvGetAsString: func(key) {
712
766
  value := tx.resourceKeyValueGet(self.id, key)
@@ -1092,6 +1146,22 @@ field = func(f) {
1092
1146
 
1093
1147
  buildFutureField: func(nameOrPath) {
1094
1148
  return _buildFutureField(strictSelf, nameOrPath)
1149
+ },
1150
+
1151
+
1152
+
1153
+
1154
+ reset: func() {
1155
+ ll.assert(feats.fullFeaturedApi, "smart.field.reset: the feature is not available on older backends. Please, update the backend.")
1156
+ tx.resetField(self.id)
1157
+ },
1158
+
1159
+
1160
+
1161
+
1162
+ remove: func() {
1163
+ ll.assert(feats.fullFeaturedApi, "smart.field.remove: the feature is not available on older backends. Please, update the backend.")
1164
+ tx.removeField(self.id)
1095
1165
  }
1096
1166
  }
1097
1167
 
@@ -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.fullFeaturedApi {
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.2",
4
4
  "description": "Tengo SDK library",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "@platforma-open/milaboratories.software-ptabler": "1.6.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@platforma-sdk/tengo-builder": "^2.1.6"
24
+ "@platforma-sdk/tengo-builder": "^2.1.8"
25
25
  },
26
26
  "scripts": {
27
27
  "type-check": "pl-tengo check",