@platforma-sdk/workflow-tengo 2.14.0 → 2.15.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 (80) hide show
  1. package/README.md +17 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.mjs +3 -1
  5. package/dist/tengo/lib/assets.lib.tengo +28 -28
  6. package/dist/tengo/lib/assets.swinfo.lib.tengo +24 -24
  7. package/dist/tengo/lib/constants.lib.tengo +6 -3
  8. package/dist/tengo/lib/exec.R.internal.lib.tengo +2 -2
  9. package/dist/tengo/lib/exec.R.lib.tengo +41 -41
  10. package/dist/tengo/lib/exec.descriptor.lib.tengo +3 -3
  11. package/dist/tengo/lib/exec.internal.lib.tengo +41 -41
  12. package/dist/tengo/lib/exec.lib.tengo +289 -289
  13. package/dist/tengo/lib/exec.limits.lib.tengo +49 -49
  14. package/dist/tengo/lib/exec.monetization.lib.tengo +1 -1
  15. package/dist/tengo/lib/exec.monetization_internal.lib.tengo +38 -38
  16. package/dist/tengo/lib/exec.package.lib.tengo +53 -53
  17. package/dist/tengo/lib/exec.python.internal.lib.tengo +1 -1
  18. package/dist/tengo/lib/exec.python.lib.tengo +37 -37
  19. package/dist/tengo/lib/exec.runcmd.lib.tengo +138 -138
  20. package/dist/tengo/lib/exec.runenv.lib.tengo +18 -18
  21. package/dist/tengo/lib/exec.stream.lib.tengo +25 -25
  22. package/dist/tengo/lib/feats.lib.tengo +3 -3
  23. package/dist/tengo/lib/file.internal.lib.tengo +31 -31
  24. package/dist/tengo/lib/file.lib.tengo +29 -29
  25. package/dist/tengo/lib/ll.get-future-field-default.lib.tengo +137 -0
  26. package/dist/tengo/lib/ll.lib.tengo +129 -129
  27. package/dist/tengo/lib/maps.lib.tengo +38 -38
  28. package/dist/tengo/lib/objects.lib.tengo +6 -6
  29. package/dist/tengo/lib/oop.lib.tengo +11 -11
  30. package/dist/tengo/lib/path.lib.tengo +60 -60
  31. package/dist/tengo/lib/pframes.builder.lib.tengo +3 -3
  32. package/dist/tengo/lib/pframes.constants.lib.tengo +20 -20
  33. package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +5 -5
  34. package/dist/tengo/lib/pframes.lib.tengo +206 -198
  35. package/dist/tengo/lib/pframes.ll.lib.tengo +59 -59
  36. package/dist/tengo/lib/pframes.pcolumn.lib.tengo +18 -18
  37. package/dist/tengo/lib/pframes.remove-nulls.lib.tengo +36 -0
  38. package/dist/tengo/lib/pframes.spec.lib.tengo +83 -83
  39. package/dist/tengo/lib/pframes.util.lib.tengo +176 -176
  40. package/dist/tengo/lib/pframes.xsv-import-file.lib.tengo +160 -0
  41. package/dist/tengo/lib/pframes.xsv.lib.tengo +84 -167
  42. package/dist/tengo/lib/render.lib.tengo +100 -45
  43. package/dist/tengo/lib/sets.lib.tengo +44 -44
  44. package/dist/tengo/lib/slices.lib.tengo +126 -126
  45. package/dist/tengo/lib/smart.lib.tengo +654 -507
  46. package/dist/tengo/lib/test.lib.tengo +120 -120
  47. package/dist/tengo/lib/tpl.lib.tengo +206 -170
  48. package/dist/tengo/lib/tpl.state-await.lib.tengo +17 -17
  49. package/dist/tengo/lib/tpl.util.lib.tengo +5 -5
  50. package/dist/tengo/lib/units.lib.tengo +2 -2
  51. package/dist/tengo/lib/validation.lib.tengo +130 -130
  52. package/dist/tengo/lib/waiter.lib.tengo +13 -13
  53. package/dist/tengo/lib/workdir.lib.tengo +126 -126
  54. package/dist/tengo/lib/workdir.proc.lib.tengo +47 -47
  55. package/dist/tengo/lib/workdir.save.lib.tengo +53 -53
  56. package/dist/tengo/lib/workflow.bobject.lib.tengo +11 -11
  57. package/dist/tengo/lib/workflow.bquery.lib.tengo +40 -40
  58. package/dist/tengo/lib/workflow.constants.lib.tengo +1 -1
  59. package/dist/tengo/lib/workflow.context.lib.tengo +16 -16
  60. package/dist/tengo/lib/workflow.lib.tengo +76 -76
  61. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  62. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  63. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  64. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  65. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  66. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  67. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  68. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  69. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  70. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  71. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  72. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  73. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  74. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  75. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  76. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  77. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  78. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  79. package/package.json +2 -2
  80. package/dist/tengo/tpl/pframes.map-pframe.plj.gz +0 -0
@@ -1,6 +1,6 @@
1
- /*
2
- * Library provides smart.Resource and smart.Field primitives.
3
- */
1
+
2
+
3
+
4
4
 
5
5
  tx := import("tx")
6
6
  ll := import("@platforma-sdk/workflow-tengo:ll")
@@ -11,120 +11,128 @@ maps := import("@platforma-sdk/workflow-tengo:maps")
11
11
  json := import("json")
12
12
  times := import("times")
13
13
  constants := import("@platforma-sdk/workflow-tengo:constants")
14
+ ffDefault := import("@platforma-sdk/workflow-tengo:ll.get-future-field-default")
15
+
16
+
17
+
18
+
14
19
 
15
- //////////////// definitions ////////////////
16
20
 
17
- /**
18
- * A read-only wrapper object around raw platforma resource
19
- */
20
21
  resource := undefined
21
22
 
22
- /**
23
- * A wrapper object around raw platforma resource field
24
- */
23
+
24
+
25
+
25
26
  field := undefined
26
27
 
27
- /**
28
- * A convenient builder for resource objects
29
- */
28
+
29
+
30
+
30
31
  resourceBuilder := undefined
31
32
 
32
- /**
33
- * Builder for structural resource
34
- *
35
- * @param resourceType { Name: "", Version: "" }
36
- */
33
+
34
+
35
+
36
+
37
+
37
38
  structBuilder := undefined
38
39
 
39
- /**
40
- * Builder for map resource
41
- */
40
+
41
+
42
+
42
43
  mapBuilder := undefined
43
44
 
44
- /**
45
- * Builder for ephemeral map resource
46
- */
45
+
46
+
47
+
47
48
  ephemeralMapBuilder := undefined
48
49
 
49
- /**
50
- * Builder for emphemeral resources
51
- *
52
- * @param resourceType { Name: "", Version: "" }
53
- */
50
+
51
+
52
+
53
+
54
+
54
55
  ephemeralBuilder := undefined
55
56
 
56
- /**
57
- * Create value resource
58
- *
59
- * @param resourceType { Name: "", Version: "" }
60
- * @param data optional data
61
- */
57
+
58
+
59
+
60
+
61
+
62
+ createNullResource := undefined
63
+
64
+
65
+
66
+
67
+
68
+
69
+
62
70
  createValueResource := undefined
63
71
 
64
- /**
65
- * Create Json resource
66
- *
67
- * @param value value to save in the resource
68
- */
72
+
73
+
74
+
75
+
76
+
69
77
  createJsonResource := undefined
70
78
 
71
- /**
72
- * Create map resource
73
- *
74
- * @param keyToValue: map[string]anyResource
75
- * @return resource
76
- */
79
+
80
+
81
+
82
+
83
+
84
+
77
85
  createMapResource := undefined
78
86
 
79
- /**
80
- * Create binary map resource
81
- *
82
- * @param keyToValue: map[string]valueResource
83
- * @return resource
84
- */
87
+
88
+
89
+
90
+
91
+
92
+
85
93
  createBinaryMapResource := undefined
86
94
 
87
- /**
88
- * Create blob map resource
89
- *
90
- * @param keyToValue: map[string]blobResource
91
- * @return resource
92
- */
95
+
96
+
97
+
98
+
99
+
100
+
93
101
  createBlobMapResource := undefined
94
102
 
95
- ////////////////// helpers ///////////////////////////
96
103
 
97
- /**
98
- * Returns true if given object is a smart.Resource
99
- */
104
+
105
+
106
+
107
+
100
108
  isResource := func(f) {
101
109
  return ll.isStrict(f) && maps.containsKey(f, "_type") && f["_type"] == "resource"
102
110
  }
103
111
 
104
- /**
105
- * Returns true if given object is a smart.Field
106
- */
112
+
113
+
114
+
107
115
  isField := func(f) {
108
116
  return ll.isStrict(f) && maps.containsKey(f, "_type") && f["_type"] == "field"
109
117
  }
110
118
 
111
- /**
112
- * Returns true if given object is a smart.Field or smart.Resource
113
- */
119
+
120
+
121
+
114
122
  isReference := func(f) {
115
123
  return isField(f) || isResource(f)
116
124
  }
117
125
 
118
- /**
119
- * Gets future field by given field name and field type.
120
- *
121
- * @param from: reference - whether a resource or a field we get a field from.
122
- * @param name: string - field name
123
- * @param fieldType: field type: input, output
124
- * @param isEph: bool - resolve in ephemeral or pure mode
125
- *
126
- * @return field: smart field
127
- */
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
128
136
  _getFutureField := func(from, name, fieldType, isEph) {
129
137
  ll.assert(isReference(from), "'from' must be either a resource or a field, got: %#v", from)
130
138
 
@@ -134,9 +142,80 @@ _getFutureField := func(from, name, fieldType, isEph) {
134
142
 
135
143
  return field(futureField.ResultFID)
136
144
  }
137
- /**
138
- * Returns true if given object is a StdMap resource
139
- */
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+ _buildFutureField := func(resourceOrField, nameOrPath, ...eph) {
160
+ globalIsEph := undefined
161
+ if len(eph) > 0 && is_bool(eph[0]) {
162
+ globalIsEph = eph[0]
163
+ }
164
+
165
+
166
+ optional := false
167
+
168
+
169
+ processSingleElement := func(resource, element) {
170
+
171
+ fieldType := "input"
172
+ fieldName := undefined
173
+ isEph := globalIsEph
174
+
175
+ if is_map(element) {
176
+ if !is_undefined(globalIsEph) {
177
+ ll.panic("global eph is not supported for structural path elements")
178
+ }
179
+
180
+ if !is_undefined(element.type) {
181
+ fieldType = element.type
182
+ }
183
+
184
+ fieldName = element.name
185
+ optional = optional || element.optional
186
+ isEph = element.eph
187
+ } else {
188
+ fieldName = element
189
+ }
190
+
191
+ if optional {
192
+ return resource.getFutureFieldOptional(fieldName, fieldType, isEph)
193
+ } else {
194
+ return resource.getFutureField(fieldName, fieldType, isEph)
195
+ }
196
+ }
197
+
198
+ if is_array(nameOrPath) {
199
+ current := resourceOrField
200
+ for i, pathElement in nameOrPath {
201
+ current = processSingleElement(current, pathElement)
202
+ }
203
+ return current
204
+ } else {
205
+ return processSingleElement(resourceOrField, nameOrPath)
206
+ }
207
+ }
208
+
209
+
210
+
211
+
212
+ _getFutureInputField := func(resourceOrField, nameOrPath, ...eph) {
213
+ return _buildFutureField(resourceOrField, nameOrPath, eph...)
214
+ }
215
+
216
+
217
+
218
+
140
219
  isMapResource := func(r) {
141
220
  return isResource(r) && (
142
221
  r.info().Type.Name == constants.RTYPE_MAP.Name ||
@@ -144,38 +223,45 @@ isMapResource := func(r) {
144
223
 
145
224
  }
146
225
 
147
- /**
148
- * Returns true if given object is an ephemeral std map resource
149
- */
226
+
227
+
228
+
150
229
  isEphMapResource := func(r) {
151
230
  return isResource(r) && r.info().Type.Name == constants.RTYPE_MAP_EPH.Name
152
231
  }
153
232
 
154
- /**
155
- * Returns true if given object is an ephemeral std map resource with locked inputs (so the list of inputs is final)
156
- */
233
+
234
+
235
+
157
236
  isLockedEphMapResource := func(r) {
158
237
  return isEphMapResource(r) && r.info().InputsLocked
159
238
  }
160
239
 
161
- /**
162
- * Returns true if given object is a map or ephemeral map resource
163
- */
240
+
241
+
242
+
164
243
  isAnyMapResource := func(r) {
165
244
  return isMapResource(r) || isEphMapResource(r)
166
245
  }
167
246
 
168
- /**
169
- * Returns true if given object is a json resource
170
- */
247
+
248
+
249
+
171
250
  isJsonResource := func(r) {
172
251
  return isResource(r) && r.info().Type.Name == constants.RTYPE_JSON.Name
173
252
  }
174
253
 
175
- // deep check whether map has resources somewhere inside
254
+
255
+
256
+
257
+ isNullResource := func(r) {
258
+ return isResource(r) && r.info().Type.Name == constants.RTYPE_NULL.Name
259
+ }
260
+
261
+
176
262
  _hasReferenceValues := func(object) {
177
263
  if isReference(object) {
178
- // first check is reference since resource/field will return true on ll.isMap
264
+
179
265
  return true
180
266
  } else if ll.isMap(object) {
181
267
  for _, v in object {
@@ -193,17 +279,17 @@ _hasReferenceValues := func(object) {
193
279
  return false
194
280
  }
195
281
 
196
- /**
197
- * Convert given object to resource. If object is a true json, then
198
- * a simple Json resource will be created, otherwise a resource map.
199
- */
282
+
283
+
284
+
285
+
200
286
  _toRefOrJson := func(object) {
201
287
  if isReference(object) {
202
- // first check is reference since resource/field will return true on ll.isMap
288
+
203
289
  return object
204
290
  } else if ll.isMap(object) {
205
291
 
206
- // just json if there are no nested resources
292
+
207
293
  if !_hasReferenceValues(object) {
208
294
  return createJsonResource(object)
209
295
  }
@@ -216,7 +302,7 @@ _toRefOrJson := func(object) {
216
302
  return mb.build()
217
303
  } else if is_array(object) {
218
304
 
219
- // assert we are not having refs inside
305
+
220
306
  ll.assert(!_hasReferenceValues(object), "array can't contain references")
221
307
 
222
308
  return createJsonResource(object)
@@ -229,11 +315,11 @@ resourceTypesEqual := func(rType1, rType2) {
229
315
  return rType1.Name == rType2.Name && rType1.Version == rType2.Version
230
316
  }
231
317
 
232
- ////////////////// implementation ///////////////////////////
233
318
 
234
- /**
235
- * @param r either resource id or resource info
236
- */
319
+
320
+
321
+
322
+
237
323
  resource = func(r) {
238
324
  self := undefined
239
325
  strictSelf := undefined
@@ -255,47 +341,47 @@ resource = func(r) {
255
341
  self = {
256
342
  _type: "resource",
257
343
 
258
- /**
259
- * Resource id (int64)
260
- */
344
+
345
+
346
+
261
347
  id: id,
262
348
 
263
- /**
264
- * Resource info:
265
- *
266
- * ID: int64
267
- * Data: byte[],
268
- * Type: { Name: string, Version: string }
269
- * Features: { ephemeral: bool }
270
- * Inputs: strict map
271
- * Outputs: strict map
272
- * Dynamic: strict map
273
- * OTW: strict map
274
- * ResourceReady: bool
275
- * AllInputsSet: bool
276
- * AllOutputsSet: bool
277
- * IsDuplicate: bool
278
- * InputsLocked: bool
279
- * OutputsLocked: bool
280
- * HasErrors: bool
281
- */
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
282
368
  info: func() {
283
369
  return tx.getResource(self.id)
284
370
  },
285
371
 
286
- /**
287
- * Returns true if resource has type equal to the one provided as an agrument.
288
- *
289
- * @param expectedType type to compare resource type with
290
- * @return true if type matches, false otherwise
291
- */
372
+
373
+
374
+
375
+
376
+
377
+
292
378
  checkResourceType: func(expectedType) {
293
379
  return resourceTypesEqual(self.info().Type, expectedType)
294
380
  },
295
381
 
296
- /**
297
- * Resource is ready when all inputs are set to the terminal values.
298
- */
382
+
383
+
384
+
299
385
  isReady: func() {
300
386
  return self.info().ResourceReady
301
387
  },
@@ -304,20 +390,20 @@ resource = func(r) {
304
390
  resourceInfo := self.info()
305
391
 
306
392
  if !maps.containsKey(resourceInfo, "Features") {
307
- // by default we consider all resources to be ephemeral for backward compatiblity to keep stable logic
308
- // in platforma backend before v1.16.0.
309
- // TODO: once we consider this version old enough, we can panic here instead of 'return true'
393
+
394
+
395
+
310
396
  return true
311
397
  }
312
398
 
313
399
  return resourceInfo.Features.ephemeral
314
400
  },
315
401
 
316
- /**
317
- * Get raw resource data
318
- *
319
- * @return data: []byte - raw resource data
320
- */
402
+
403
+
404
+
405
+
406
+
321
407
  getData: func() {
322
408
  if self.hasError() {
323
409
  ll.panic("resource has errors: ", self.info())
@@ -325,31 +411,31 @@ resource = func(r) {
325
411
  return self.info().Data;
326
412
  },
327
413
 
328
- /**
329
- * Get resource data as Json
330
- */
414
+
415
+
416
+
331
417
  getDataAsJson: func() {
332
418
  return json.decode(self.getData());
333
419
  },
334
420
 
335
- /**
336
- * Whether this resource has error
337
- */
421
+
422
+
423
+
338
424
  hasError: func() {
339
425
  return self._hasField("OneTimeWritables", "resourceError")
340
426
  },
341
427
 
342
- /**
343
- * Whether any of the input fields have errors. The resource itself may still not have
344
- * error status because error propagation was not yet happened.
345
- */
428
+
429
+
430
+
431
+
346
432
  hasInputErrors: func() {
347
433
  return self.info().HasErrors
348
434
  },
349
435
 
350
- /**
351
- * Return error set in the resource
352
- */
436
+
437
+
438
+
353
439
  getError: func() {
354
440
  if !self.info().HasErrors {
355
441
  ll.panic("no error set in resource")
@@ -361,29 +447,29 @@ resource = func(r) {
361
447
  return ll.fromStrict(self.info()[fieldMapName])[fieldName] != undefined
362
448
  },
363
449
 
364
- /**
365
- * Whether resource has input field with specified name
366
- *
367
- * @param name field name
368
- */
450
+
451
+
452
+
453
+
454
+
369
455
  hasInputField: func(name) {
370
456
  return self._hasField("Inputs", name)
371
457
  },
372
458
 
373
- /**
374
- * Whether resource has output field with specified name
375
- *
376
- * @param name field name
377
- */
459
+
460
+
461
+
462
+
463
+
378
464
  hasOutputField: func(name) {
379
465
  return self._hasField("Outputs", name)
380
466
  },
381
467
 
382
- /**
383
- * Whether resource has any field with specified name
384
- *
385
- * @param name field name
386
- */
468
+
469
+
470
+
471
+
472
+
387
473
  hasField: func(name) {
388
474
  return self._hasField("Inputs", name) ||
389
475
  self._hasField("Outputs", name) ||
@@ -391,11 +477,11 @@ resource = func(r) {
391
477
  self._hasField("OneTimeWritables", name)
392
478
  },
393
479
 
394
- // cached _inputs
480
+
395
481
  _inputs: undefined,
396
- /**
397
- * Returns resource inputs map name -> smart.Field
398
- */
482
+
483
+
484
+
399
485
  inputs: func() {
400
486
  if self._inputs == undefined {
401
487
  info := self.info()
@@ -408,11 +494,11 @@ resource = func(r) {
408
494
  return self._inputs
409
495
  },
410
496
 
411
- // cached _outputs
497
+
412
498
  _outputs: undefined,
413
- /**
414
- * Returns resource outputs map name -> smart.Field
415
- */
499
+
500
+
501
+
416
502
  outputs: func() {
417
503
  if(self._outputs == undefined) {
418
504
  info := self.info()
@@ -425,11 +511,11 @@ resource = func(r) {
425
511
  return self._outputs
426
512
  },
427
513
 
428
- /**
429
- * Get smart.Field by name
430
- *
431
- * @param name field name
432
- */
514
+
515
+
516
+
517
+
518
+
433
519
  getField: func(name) {
434
520
  if(!self.hasField(name)) {
435
521
  ll.panic("field %v is not found", name)
@@ -438,59 +524,70 @@ resource = func(r) {
438
524
  return field(ll.fieldId(self.id, name))
439
525
  },
440
526
 
441
- /**
442
- * Get future field given field name and field type.
443
- *
444
- * @param name: string - field name
445
- * @param fieldType: field type - input, output
446
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
447
- * to pass deduplication before resolution.
448
- *
449
- * @return field: smart.field
450
- */
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
451
537
  getFutureField: func(name, fieldType, ...eph) {
452
538
  isEph := strictSelf.isEphemeral()
453
- if len(eph) > 0 {
539
+ if len(eph) > 0 && is_bool(eph[0]) {
454
540
  isEph = eph[0]
455
541
  }
456
542
  return _getFutureField(strictSelf, name, fieldType, isEph)
457
543
  },
458
544
 
459
- /**
460
- * Get future input field given field name or a path
461
- *
462
- * @param nameOrPath: string|string[] - field name or an array representing a path to be traversed
463
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
464
- * to pass deduplication before resolution.
465
- *
466
- * @return field: smart.field
467
- */
468
- getFutureInputField: func(nameOrPath, ...eph) {
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+
555
+
556
+ getFutureFieldOptional: func(name, fieldType, ...eph) {
469
557
  isEph := strictSelf.isEphemeral()
470
- if len(eph) > 0 {
558
+ if len(eph) > 0 && is_bool(eph[0]) {
471
559
  isEph = eph[0]
472
560
  }
473
561
 
474
- if is_array(nameOrPath) {
475
- current := strictSelf
476
- for name in nameOrPath {
477
- current = current.getFutureField(name, "input", isEph)
478
- }
479
- return current
480
- } else {
481
- return self.getFutureField(nameOrPath, "input", isEph)
482
- }
562
+ fieldId := ffDefault.getFutureFieldWithDefault(self.id, name, fieldType, isEph)
563
+ return field(fieldId)
483
564
  },
484
565
 
485
- /**
486
- * Get future output field given field name
487
- *
488
- * @param name: string - field name
489
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
490
- * to pass deduplication before resolution.
491
- *
492
- * @return field: smart.field
493
- */
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+ getFutureInputField: func(nameOrPath, ...eph) {
579
+ return _getFutureInputField(strictSelf, nameOrPath, eph...)
580
+ },
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
494
591
  getFutureOutputField: func(name, ...eph) {
495
592
  isEph := strictSelf.isEphemeral()
496
593
  if len(eph) > 0 {
@@ -498,52 +595,66 @@ resource = func(r) {
498
595
  }
499
596
 
500
597
  if !feats.pureFutureFields && !isEph {
501
- // Dirty hack that saves from issues with buggy pure getField before v1.16.1
502
- // We can safely drop it when we consider v1.16.0 old enough.
598
+
599
+
503
600
  return self.getFutureField(name, "output", true).expectNonEphemeral()
504
601
  }
505
602
 
506
603
  return self.getFutureField(name, "output", isEph)
507
604
  },
508
605
 
509
- /**
510
- * Get a resource set in field of a given field name
511
- *
512
- * @param name field name
513
- */
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+ buildFutureField: func(nameOrPath) {
617
+ return _buildFutureField(self, nameOrPath)
618
+ },
619
+
620
+
621
+
622
+
623
+
624
+
514
625
  get: func(name) {
515
626
  return self.getField(name).getValue()
516
627
  },
517
628
 
518
- /**
519
- * Gets value from key value storage associated with resource if corresponding
520
- * record exists.
521
- *
522
- * @param key string key
523
- * @return bytes or undefined
524
- */
629
+
630
+
631
+
632
+
633
+
634
+
635
+
525
636
  kvGet: func(key) {
526
637
  return tx.resourceKeyValueGet(self.id, key)
527
638
  },
528
639
 
529
- /**
530
- * Sets bytes or string value to a key value storage associated
531
- * with resource.
532
- *
533
- * @param key string key
534
- * @param value to be set
535
- */
640
+
641
+
642
+
643
+
644
+
645
+
646
+
536
647
  kvSet: func(key, value) {
537
648
  tx.resourceKeyValueSet(self.id, key, value)
538
649
  },
539
650
 
540
- /**
541
- * Gets value as string from key value storage associated with resource if
542
- * corresponding record exists.
543
- *
544
- * @param key string key
545
- * @return string or undefined
546
- */
651
+
652
+
653
+
654
+
655
+
656
+
657
+
547
658
  kvGetAsString: func(key) {
548
659
  value := tx.resourceKeyValueGet(self.id, key)
549
660
  if is_undefined(value) {
@@ -553,13 +664,13 @@ resource = func(r) {
553
664
  }
554
665
  },
555
666
 
556
- /**
557
- * Gets value from key value storage associated with resource and
558
- * decodes as json if corresponding record exists.
559
- *
560
- * @param key string key
561
- * @return value or undefined
562
- */
667
+
668
+
669
+
670
+
671
+
672
+
673
+
563
674
  kvGetAsJson: func(key) {
564
675
  bytes := self.kvGet(key)
565
676
  if is_undefined(bytes){
@@ -569,12 +680,12 @@ resource = func(r) {
569
680
  }
570
681
  },
571
682
 
572
- /**
573
- * Sets json value to a key value storage associated with resource.
574
- *
575
- * @param key string key
576
- * @param value to be set
577
- */
683
+
684
+
685
+
686
+
687
+
688
+
578
689
  kvSetJson: func(key, value) {
579
690
  self.kvSet(key, json.encode(value))
580
691
  }
@@ -598,77 +709,77 @@ field = func(f) {
598
709
  self = {
599
710
  _type: "field",
600
711
 
601
- /**
602
- * Field id struct:
603
- *
604
- * ResourceID: int64
605
- * Name: string
606
- *
607
- */
712
+
713
+
714
+
715
+
716
+
717
+
718
+
608
719
  id: id,
609
720
 
610
- /**
611
- * Field name
612
- */
721
+
722
+
723
+
613
724
  name: func() {
614
725
  return self.id.Name
615
726
  },
616
727
 
617
- /**
618
- * Associated smart.Resource
619
- */
728
+
729
+
730
+
620
731
  resource: func() {
621
732
  return resource(self.id.ResourceID)
622
733
  },
623
734
 
624
- /**
625
- * Field info:
626
- *
627
- * ID: { ResourceId: int64, Name: string }
628
- * Type: string - see constants.FTYPE_* for possible values
629
- * Features: { ephemeral: bool }
630
- * Value: int64 - value resource id
631
- * Error: int64 - error resource id
632
- * IsSet: bool - whether field is set
633
- */
735
+
736
+
737
+
738
+
739
+
740
+
741
+
742
+
743
+
744
+
634
745
  info: func() {
635
746
  return tx.getField(self.id)
636
747
  },
637
748
 
638
- /**
639
- * Whether the field points to resource or error
640
- */
749
+
750
+
751
+
641
752
  isSet: func() {
642
753
  return self.info().IsSet
643
754
  },
644
755
 
645
- /**
646
- * Whether field is resolved to a value
647
- */
756
+
757
+
758
+
648
759
  isValueSet: func() {
649
760
  return self.info().Value != 0
650
761
  },
651
762
 
652
- /**
653
- * Whether field is resolved to a error
654
- */
763
+
764
+
765
+
655
766
  isErrorSet: func() {
656
767
  return self.info().Error != 0
657
768
  },
658
769
 
659
- /**
660
- * Some fields cannot point to ephemeral resources.
661
- * This method checks, if current field can.
662
- *
663
- * @return bool - "true' when field can point to ephemeral resource, 'false' when not
664
- */
770
+
771
+
772
+
773
+
774
+
775
+
665
776
  isEphemeral: func() {
666
777
  fieldInfo := self.info()
667
778
 
668
779
  if !maps.containsKey(fieldInfo, "Features") {
669
- // by default we consider all fields can point to ephemeral resources for backward compatiblity to keep stable logic
670
- // in platforma backend before v1.16.0.
671
- // TODO: once we consider this version old enough, we can panic here instead of 'return true'
780
+
781
+
782
+
672
783
  return true
673
784
  }
674
785
 
@@ -687,12 +798,12 @@ field = func(f) {
687
798
  return rr
688
799
  },
689
800
 
690
- /**
691
- * Use cacheMillis, cacheSeconds, cacheMinutes, cacheHours and cacheDays to not make hidden mistakes in cache time ranges.
692
- * Sets the cache time.
693
- *
694
- * @param time: duration - the cache time from 'times' library.
695
- */
801
+
802
+
803
+
804
+
805
+
806
+
696
807
  setCache: func(time) {
697
808
  ll.assert(is_int(time) && time >= times.millisecond,
698
809
  "smart.field.setCache: cache time must be an integer >= millisecond. Did you forget to import a standard tengo library 'times'?")
@@ -701,62 +812,62 @@ field = func(f) {
701
812
  return self
702
813
  },
703
814
 
704
- /**
705
- * Sets the cache time in milliseconds.
706
- *
707
- * @param millis: number
708
- */
815
+
816
+
817
+
818
+
819
+
709
820
  setCacheMillis: func(millis) {
710
821
  ll.assert(is_int(millis) && millis > 0,
711
822
  "smart.field.setCacheMillis: cache time must be a number of milliseconds (> 0)")
712
823
  return self.setCache(millis * times.millisecond)
713
824
  },
714
825
 
715
- /**
716
- * Sets the cache time in seconds.
717
- *
718
- * @param seconds: number
719
- */
826
+
827
+
828
+
829
+
830
+
720
831
  setCacheSeconds: func(seconds) {
721
832
  ll.assert(is_int(seconds) && seconds > 0,
722
833
  "smart.field.setCacheSeconds: cache time must be a number of seconds (> 0)")
723
834
  return self.setCache(seconds * times.second)
724
835
  },
725
836
 
726
- /**
727
- * Sets the cache time in minutes.
728
- *
729
- * @param minutes: number
730
- */
837
+
838
+
839
+
840
+
841
+
731
842
  setCacheMinutes: func(minutes) {
732
843
  ll.assert(is_int(minutes) && minutes > 0,
733
844
  "smart.field.setCacheMinutes: cache time must be a number of minutes (> 0)")
734
845
  return self.setCache(minutes * times.minute)
735
846
  },
736
847
 
737
- /**
738
- * Sets the cache time in hours.
739
- *
740
- * @param hours: number
741
- */
848
+
849
+
850
+
851
+
852
+
742
853
  setCacheHours: func(hours) {
743
854
  ll.assert(is_int(hours) && hours > 0, "smart.field.setCacheHours: cache time must be a number of hours (> 0)")
744
855
  return self.setCache(hours * times.hour)
745
856
  },
746
857
 
747
- /**
748
- * Sets the cache time in days
749
- *
750
- * @param days: number
751
- */
858
+
859
+
860
+
861
+
862
+
752
863
  setCacheDays: func(days) {
753
864
  ll.assert(is_int(days) && days > 0, "smart.field.setCacheDays: cache time must be a number of days (> 0)")
754
865
  return self.setCache(days * 24 * times.hour)
755
866
  },
756
867
 
757
- /**
758
- * Returns a smart.Resource object from the resolved field value
759
- */
868
+
869
+
870
+
760
871
  getValue: func() {
761
872
  if self.isErrorSet() {
762
873
  err := self.getError().getDataAsJson()
@@ -771,36 +882,36 @@ field = func(f) {
771
882
  return resource(self.info().Value)
772
883
  },
773
884
 
774
- /**
775
- * Returns a smart.Resource object from the resolved field error
776
- */
885
+
886
+
887
+
777
888
  getError: func() {
778
889
  return resource(self.info().Error)
779
890
  },
780
891
 
781
- /**
782
- * Set (resolve) this field to a Json resource
783
- *
784
- * @param value any
785
- */
892
+
893
+
894
+
895
+
896
+
786
897
  setJson: func(value) {
787
898
  self.set(createJsonResource(value))
788
899
  },
789
900
 
790
- /**
791
- * Set (resolve) this field to a Map resource
792
- *
793
- * @param keyToValue any
794
- */
901
+
902
+
903
+
904
+
905
+
795
906
  setMap: func(keyToValue) {
796
907
  self.set(createMapResource(keyToValue))
797
908
  },
798
909
 
799
- /**
800
- * Set (resolve) this field to either another resource or field
801
- *
802
- * @param reference smart.resource or smart.field
803
- */
910
+
911
+
912
+
913
+
914
+
804
915
  set: func(reference) {
805
916
  ll.assert(!self.isSet(), "field ", self.name(), " is already set")
806
917
 
@@ -813,12 +924,12 @@ field = func(f) {
813
924
  }
814
925
  },
815
926
 
816
- /**
817
- * Sets this field to either another resource, field or a JSON value.
818
- *
819
- * @param refOrJson: resource|field|JSON
820
- * @return nothing
821
- */
927
+
928
+
929
+
930
+
931
+
932
+
822
933
  setRefOrJson: func(refOrJson) {
823
934
  if is_undefined(refOrJson) {
824
935
  ll.panic("attempt to assign undefined value to field %v", self.name())
@@ -826,66 +937,77 @@ field = func(f) {
826
937
  self.set(_toRefOrJson(refOrJson))
827
938
  },
828
939
 
829
- /**
830
- * Returns a json encoded data of the resolved value resource
831
- */
940
+
941
+
942
+
832
943
  getValueAsJson: func() {
833
944
  return self.getValue().getDataAsJson()
834
945
  },
835
946
 
836
- /**
837
- * Get future field given field name and field type.
838
- *
839
- * @param name: string - field name
840
- * @param fieldType: field type: input, output
841
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
842
- * to pass deduplication before resolution.
843
- *
844
- * @return field: smart.field
845
- */
947
+
948
+
949
+
950
+
951
+
952
+
953
+
954
+
955
+
956
+
846
957
  getFutureField: func(name, fieldType, ...eph) {
847
958
  isEph := strictSelf.isEphemeral()
848
- if len(eph) > 0 {
959
+ if len(eph) > 0 && is_bool(eph[0]) {
849
960
  isEph = eph[0]
850
961
  }
851
962
  return _getFutureField(strictSelf, name, fieldType, isEph)
852
963
  },
853
964
 
854
- /**
855
- * Get future input field given field name or a path
856
- *
857
- * @param nameOrPath: string|string[] - field name or an array representing a path to be traversed
858
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
859
- * to pass deduplication before resolution.
860
- *
861
- * @return field: smart field
862
- */
863
- getFutureInputField: func(nameOrPath, ...eph) {
965
+
966
+
967
+
968
+
969
+
970
+
971
+
972
+
973
+
974
+
975
+
976
+ getFutureFieldOptional: func(name, fieldType, ...eph) {
864
977
  isEph := strictSelf.isEphemeral()
865
- if len(eph) > 0 {
978
+ if len(eph) > 0 && is_bool(eph[0]) {
866
979
  isEph = eph[0]
867
980
  }
868
981
 
869
- if is_array(nameOrPath) {
870
- current := strictSelf
871
- for name in nameOrPath {
872
- current = current.getFutureField(name, "input", eph...)
873
- }
874
- return current
875
- } else {
876
- return self.getFutureField(nameOrPath, "input", eph...)
877
- }
982
+ fieldId := ffDefault.getFutureFieldWithDefault(self.id, name, fieldType, isEph)
983
+ return field(fieldId)
984
+ },
985
+
986
+
987
+
988
+
989
+
990
+
991
+
992
+
993
+
994
+
995
+
996
+
997
+
998
+ getFutureInputField: func(nameOrPath, ...eph) {
999
+ return _getFutureInputField(strictSelf, nameOrPath, eph...)
878
1000
  },
879
1001
 
880
- /**
881
- * Get future output field given field name
882
- *
883
- * @param name: string - field name
884
- * @param eph: bool (optional) - when false, the operation becomes pure function, waiting for resource
885
- * to pass deduplication before resolution.
886
- *
887
- * @return field: smart field
888
- */
1002
+
1003
+
1004
+
1005
+
1006
+
1007
+
1008
+
1009
+
1010
+
889
1011
  getFutureOutputField: func(name, ...eph) {
890
1012
  isEph := strictSelf.isEphemeral()
891
1013
  if len(eph) > 0 {
@@ -893,12 +1015,26 @@ field = func(f) {
893
1015
  }
894
1016
 
895
1017
  if !feats.pureFutureFields && !isEph {
896
- // Dirty hack that saves from issues with buggy pure getField before v1.16.1
897
- // We can safely drop it when we consider v1.16.0 old enough.
1018
+
1019
+
898
1020
  return self.getFutureField(name, "output", true).expectNonEphemeral()
899
1021
  }
900
1022
 
901
1023
  return self.getFutureField(name, "output", isEph)
1024
+ },
1025
+
1026
+
1027
+
1028
+
1029
+
1030
+
1031
+
1032
+
1033
+
1034
+
1035
+
1036
+ buildFutureField: func(nameOrPath) {
1037
+ return _buildFutureField(strictSelf, nameOrPath)
902
1038
  }
903
1039
  }
904
1040
 
@@ -908,29 +1044,29 @@ field = func(f) {
908
1044
  }
909
1045
 
910
1046
 
911
- /**
912
- * Universal resource builder.
913
- *
914
- * @param r either resource id or resource info
915
- */
1047
+
1048
+
1049
+
1050
+
1051
+
916
1052
  resourceBuilder = func(r) {
917
1053
  super := resource(r)
918
1054
 
919
1055
  self := undefined
920
1056
 
921
- // Mutator methods of the builder cannot be used after it was built.
1057
+
922
1058
  built := false
923
1059
  assertNotBuilt := func() {
924
1060
  ll.assert(!built, "builder methods must not be used after a build() was called")
925
1061
  }
926
1062
 
927
1063
  self = ll.toStrict(oop.inherit(super, {
928
- /**
929
- * Create new field with given name and field type.
930
- *
931
- * @param name field name
932
- * @param fieldType: field type - input, output, dynamic, service, OTW. Use FTYPE_* constants.
933
- */
1064
+
1065
+
1066
+
1067
+
1068
+
1069
+
934
1070
  createField: func(name, fieldType) {
935
1071
  assertNotBuilt()
936
1072
  fid := ll.fieldId(super.id, name)
@@ -938,92 +1074,92 @@ resourceBuilder = func(r) {
938
1074
  return field(fid)
939
1075
  },
940
1076
 
941
- /**
942
- * Create service field with given name.
943
- * Service fields behave very like inputs, but with deduplication disabled.
944
- * This means, that if you create 2 resources, each having 1 input field with value "A" and the service
945
- * field with different values, they would be considered the same by backend.
946
- */
1077
+
1078
+
1079
+
1080
+
1081
+
1082
+
947
1083
  createServiceField: func(name) {
948
1084
  assertNotBuilt()
949
1085
  return self.createField(name, constants.FTYPE_SERVICE)
950
1086
  },
951
1087
 
952
- /**
953
- * Create new input field with given name.
954
- *
955
- * @param name field name
956
- */
1088
+
1089
+
1090
+
1091
+
1092
+
957
1093
  createInputField: func(name) {
958
1094
  assertNotBuilt()
959
1095
  return self.createField(name, constants.FTYPE_INPUT)
960
1096
  },
961
1097
 
962
- /**
963
- * Create new output field with given name.
964
- *
965
- * @param name field name
966
- */
1098
+
1099
+
1100
+
1101
+
1102
+
967
1103
  createOutputField: func(name) {
968
1104
  assertNotBuilt()
969
1105
  return self.createField(name, constants.FTYPE_OUTPUT)
970
1106
  },
971
1107
 
972
- /**
973
- * Lock resource outputs
974
- */
1108
+
1109
+
1110
+
975
1111
  lockOutputs: func() {
976
1112
  assertNotBuilt()
977
1113
  tx.lockOutputs(super.id)
978
1114
  },
979
1115
 
980
- /**
981
- * Lock resource inputs
982
- */
1116
+
1117
+
1118
+
983
1119
  lockInputs: func() {
984
1120
  assertNotBuilt()
985
1121
  tx.lockInputs(super.id)
986
1122
  },
987
1123
 
988
- /**
989
- * Lock both resource inputs and outputs
990
- */
1124
+
1125
+
1126
+
991
1127
  lock: func() {
992
1128
  assertNotBuilt()
993
1129
  self.lockInputs()
994
1130
  self.lockOutputs()
995
1131
  },
996
1132
 
997
- /**
998
- * Lock inputs and outputs and return resulting smart.Resource
999
- */
1133
+
1134
+
1135
+
1000
1136
  lockAndBuild: func() {
1001
1137
  self.lock()
1002
1138
  built = true
1003
1139
  return super
1004
1140
  },
1005
1141
 
1006
- /**
1007
- * Lock outputs and return resulting smart.Resource
1008
- */
1142
+
1143
+
1144
+
1009
1145
  lockOutputsAndBuild: func() {
1010
1146
  self.lockOutputs()
1011
1147
  built = true
1012
1148
  return super
1013
1149
  },
1014
1150
 
1015
- /**
1016
- * Lock inputs and return resulting smart.Resource
1017
- */
1151
+
1152
+
1153
+
1018
1154
  lockInputsAndBuild: func() {
1019
1155
  self.lockInputs()
1020
1156
  built = true
1021
1157
  return super
1022
1158
  },
1023
1159
 
1024
- /**
1025
- * Return smart.Resource without locking inputs and outputs
1026
- */
1160
+
1161
+
1162
+
1027
1163
  buildUnlocked: func() {
1028
1164
  built = true
1029
1165
  return super
@@ -1033,12 +1169,12 @@ resourceBuilder = func(r) {
1033
1169
  return self
1034
1170
  }
1035
1171
 
1036
- /**
1037
- * Creates a structural resource and returns a builder from it.
1038
- *
1039
- * @params resourceType a resource type
1040
- * @params data, optional, bytes that will go in Data of the resource.
1041
- */
1172
+
1173
+
1174
+
1175
+
1176
+
1177
+
1042
1178
  structBuilder = func(resourceType, ...data) {
1043
1179
  id := undefined
1044
1180
  if len(data) > 0 {
@@ -1050,71 +1186,71 @@ structBuilder = func(resourceType, ...data) {
1050
1186
  return resourceBuilder(id)
1051
1187
  }
1052
1188
 
1053
- /**
1054
- * @param isEph: is ephemeral map
1055
- */
1189
+
1190
+
1191
+
1056
1192
  _mapBuilder := func(isEph) {
1057
1193
  builder := isEph ? ephemeralBuilder(constants.RTYPE_MAP_EPH) : structBuilder(constants.RTYPE_MAP)
1058
- // ensure no outputs will be created
1194
+
1059
1195
  builder.lockOutputs()
1060
1196
 
1061
1197
  self := undefined
1062
1198
 
1063
1199
  self = ll.toStrict(oop.inherit(builder, {
1064
- /**
1065
- * Adds a reference to the map by a given key.
1066
- *
1067
- * @param key: string - how the field will be named in the map
1068
- * @param value: reference - a field or resource that will be set in the field
1069
- * @return self: mapBuilder
1070
- */
1200
+
1201
+
1202
+
1203
+
1204
+
1205
+
1206
+
1071
1207
  addRef: func(key, value) {
1072
1208
  builder.createInputField(key).set(value)
1073
1209
  return self
1074
1210
  },
1075
1211
 
1076
- /**
1077
- * Adds a primitive value that can be converted to JSON to the map.
1078
- *
1079
- * @param key: string - how the field will be named in the map
1080
- * @param value: any - a primitive value, e.g. Tengo's map.
1081
- * @return self: mapBuilder
1082
- */
1212
+
1213
+
1214
+
1215
+
1216
+
1217
+
1218
+
1083
1219
  addJson: func(key, value) {
1084
1220
  builder.createInputField(key).set(createJsonResource(value))
1085
1221
  return self
1086
1222
  },
1087
1223
 
1088
- /**
1089
- * Adds object automatically converting non-reference objects into Json values.
1090
- *
1091
- * @param key: string - how the field will be named in the map
1092
- * @param value: any - a reference or a primitive value
1093
- * @return self: mapBuilder
1094
- */
1224
+
1225
+
1226
+
1227
+
1228
+
1229
+
1230
+
1095
1231
  add: func(key, value) {
1096
1232
  builder.createInputField(key).setRefOrJson(value)
1097
1233
  return self
1098
1234
  },
1099
1235
 
1100
- /**
1101
- * Adds object automatically converting non-reference objects into Json values.
1102
- * The object is added into service field, meaning the value would not affect deduplication of the map.
1103
- *
1104
- * @param key: string - how the field will be named in the map
1105
- * @param value: any - a reference or a primitive value
1106
- * @return self: mapBuilder
1107
- */
1236
+
1237
+
1238
+
1239
+
1240
+
1241
+
1242
+
1243
+
1108
1244
  addService: func(key, value) {
1109
1245
  builder.createServiceField(key).setRefOrJson(value)
1110
1246
  return self
1111
1247
  },
1112
1248
 
1113
- /**
1114
- * Creates a map in the platforma
1115
- *
1116
- * @return nothing
1117
- */
1249
+
1250
+
1251
+
1252
+
1253
+
1118
1254
  build: func() {
1119
1255
  return builder.lockAndBuild()
1120
1256
  }
@@ -1142,6 +1278,15 @@ ephemeralBuilder = func(resourceType, ...data) {
1142
1278
  return resourceBuilder(id)
1143
1279
  }
1144
1280
 
1281
+ createNullResource = func(...data) {
1282
+ ll.assert(len(data) <= 1, "can't create null resource with more than one argument")
1283
+ if len(data) > 0 && !is_undefined(data[0]) {
1284
+ return resource(tx.createValue(constants.RTYPE_NULL.Name, constants.RTYPE_NULL.Version, data[0]))
1285
+ } else {
1286
+ return resource(tx.createValue(constants.RTYPE_NULL.Name, constants.RTYPE_NULL.Version, ""))
1287
+ }
1288
+ }
1289
+
1145
1290
  createValueResource = func(resourceType, data) {
1146
1291
  ll.assert(data != undefined, "can't create resource with undefined data")
1147
1292
  return resource(tx.createValue(resourceType.Name, resourceType.Version, data))
@@ -1150,10 +1295,10 @@ createValueResource = func(resourceType, data) {
1150
1295
  createJsonResource = func(value) {
1151
1296
  ll.assert(value != undefined, "can't create Json value from undefined")
1152
1297
 
1153
- // important: strict map can't be encoded to JSON
1298
+
1154
1299
 
1155
1300
  ll.assert(!ll.isStrict(value), "can't encode strict map: ", value)
1156
- // value = ll.ensureNonStrict(value)
1301
+
1157
1302
 
1158
1303
  encoded := json.encode(value)
1159
1304
 
@@ -1191,6 +1336,7 @@ export ll.toStrict({
1191
1336
  isAnyMapResource: isAnyMapResource,
1192
1337
  isLockedEphMapResource: isLockedEphMapResource,
1193
1338
  isJsonResource: isJsonResource,
1339
+ isNullResource: isNullResource,
1194
1340
  resource: resource,
1195
1341
  field: field,
1196
1342
  resourceBuilder: resourceBuilder,
@@ -1203,5 +1349,6 @@ export ll.toStrict({
1203
1349
  createMapResourceWithType: createMapResourceWithType,
1204
1350
  createMapResource: createMapResource,
1205
1351
  createBinaryMapResource: createBinaryMapResource,
1206
- createBlobMapResource: createBlobMapResource
1352
+ createBlobMapResource: createBlobMapResource,
1353
+ createNullResource: createNullResource
1207
1354
  })