@platforma-sdk/workflow-tengo 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +6 -0
  2. package/dist/tengo/lib/assets.lib.tengo +64 -0
  3. package/dist/tengo/lib/constants.lib.tengo +75 -0
  4. package/dist/tengo/lib/exec.internal.lib.tengo +211 -0
  5. package/dist/tengo/lib/exec.lib.tengo +516 -0
  6. package/dist/tengo/lib/exec.package.lib.tengo +296 -0
  7. package/dist/tengo/lib/exec.runcmd.lib.tengo +275 -0
  8. package/dist/tengo/lib/exec.runenv.lib.tengo +221 -0
  9. package/dist/tengo/lib/exec.stream.lib.tengo +64 -0
  10. package/dist/tengo/lib/file.lib.tengo +112 -0
  11. package/dist/tengo/lib/ll.lib.tengo +233 -0
  12. package/dist/tengo/lib/maps.lib.tengo +102 -0
  13. package/dist/tengo/lib/objects.lib.tengo +22 -0
  14. package/dist/tengo/lib/oop.lib.tengo +29 -0
  15. package/dist/tengo/lib/path.lib.tengo +165 -0
  16. package/dist/tengo/lib/pframes.constants.lib.tengo +53 -0
  17. package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +47 -0
  18. package/dist/tengo/lib/pframes.lib.tengo +84 -0
  19. package/dist/tengo/lib/pframes.ll.lib.tengo +86 -0
  20. package/dist/tengo/lib/pframes.util.lib.tengo +244 -0
  21. package/dist/tengo/lib/pframes.xsv.lib.tengo +178 -0
  22. package/dist/tengo/lib/render.lib.tengo +100 -0
  23. package/dist/tengo/lib/sets.lib.tengo +93 -0
  24. package/dist/tengo/lib/slices.lib.tengo +202 -0
  25. package/dist/tengo/lib/smart.lib.tengo +1005 -0
  26. package/dist/tengo/lib/tpl.lib.tengo +535 -0
  27. package/dist/tengo/lib/tpl.state-await.lib.tengo +255 -0
  28. package/dist/tengo/lib/tpl.util.lib.tengo +19 -0
  29. package/dist/tengo/lib/validation.lib.tengo +428 -0
  30. package/dist/tengo/lib/workdir.lib.tengo +300 -0
  31. package/dist/tengo/lib/workdir.proc.lib.tengo +158 -0
  32. package/dist/tengo/lib/workdir.save.lib.tengo +158 -0
  33. package/dist/tengo/lib/workflow.bobject.lib.tengo +51 -0
  34. package/dist/tengo/lib/workflow.bquery.lib.tengo +208 -0
  35. package/dist/tengo/lib/workflow.constants.lib.tengo +8 -0
  36. package/dist/tengo/lib/workflow.context.lib.tengo +56 -0
  37. package/dist/tengo/lib/workflow.lib.tengo +298 -0
  38. package/dist/tengo/tpl/exec.create-python-venv.plj.gz +0 -0
  39. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.aggregate.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.import-xsv-map.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.map-pframe.plj.gz +0 -0
  47. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  48. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  49. package/index.d.ts +4 -0
  50. package/index.js +13 -0
  51. package/package.json +25 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Library implementing state machine to await certain resource states in
3
+ * ephemeral templates.
4
+ */
5
+
6
+ tx := import("tx")
7
+ ll := import("@platforma-sdk/workflow-tengo:ll")
8
+ sets := import("@platforma-sdk/workflow-tengo:sets")
9
+ text := import("text")
10
+
11
+ createContext := func() {
12
+ return {
13
+ activeSubscriptions: {},
14
+ resolvedStates: {}
15
+ }
16
+ }
17
+
18
+ awaitOneState := func(field, path, state) {
19
+ if field.isErrorSet() && !field.isValueSet() {
20
+ // Field is in pure error state (just error and no referenced resource).
21
+ // This is a terminal state, so we stop any traversal here, and return value
22
+ // the same as we would return if we achieved the desired state. It is
23
+ // template's body responsibility to correctly handle the error state.
24
+ return { type: "Ready" }
25
+ }
26
+
27
+ if len(path) > 0 {
28
+ step := path[0]
29
+ rPath := path[1:]
30
+
31
+ if !field.isValueSet() {
32
+ return {
33
+ type: "Subscribe",
34
+ subscriptions: [{
35
+ resource: field.id.ResourceID,
36
+ event: "InputSet"
37
+ }]
38
+ }
39
+ }
40
+
41
+ resource := field.getValue()
42
+
43
+ if is_string(step) {
44
+ nextField := resource.inputs()[step]
45
+ if nextField == undefined {
46
+ if resource.info().InputsLocked {
47
+ ll.panic("field %v not found and inputs locked", step)
48
+ }
49
+ return {
50
+ type: "Subscribe",
51
+ subscriptions: [{
52
+ resource: resource.id,
53
+ event: "FieldCreated"
54
+ },{
55
+ resource: resource.id,
56
+ event: "InputsLocked"
57
+ }]
58
+ }
59
+ }
60
+ return awaitOneState(nextField, rPath, state)
61
+ } else if is_map(step) {
62
+ if is_undefined(step.wildcard) == is_undefined(step.match) {
63
+ ll.panic("Unknown step structure: %v", step)
64
+ }
65
+
66
+ matcher := undefined
67
+
68
+ if !is_undefined(step.wildcard) {
69
+ if step.wildcard != "*" {
70
+ ll.panic("Complex wildcards are not supported, use { match: \"{regexp}\" }: %v", step.wildcard)
71
+ }
72
+ matcher = func(field) {
73
+ return true
74
+ }
75
+ } else {
76
+ regex := text.re_compile(step.match)
77
+ if is_error(regex) {
78
+ ll.panic("Error parsing regex %v: %v", step.match, regex)
79
+ }
80
+ matcher = func(field) {
81
+ return regex.match(field)
82
+ }
83
+ }
84
+
85
+ if !resource.info().InputsLocked {
86
+ return {
87
+ type: "Subscribe",
88
+ subscriptions: [{
89
+ resource: resource.id,
90
+ event: "InputsLocked"
91
+ }]
92
+ }
93
+ }
94
+
95
+ subscriptions := []
96
+ for nextFieldName, nextField in resource.inputs() {
97
+ if !matcher(nextFieldName) {
98
+ continue
99
+ }
100
+ res := awaitOneState(nextField, rPath, state)
101
+ if res.type == "Ready" {
102
+ continue
103
+ } else if res.type == "Subscribe" {
104
+ subscriptions = append(subscriptions, res.subscriptions...)
105
+ } else {
106
+ ll.panic("Unknown result type: %v", res.type)
107
+ }
108
+ }
109
+ if len(subscriptions) == 0 {
110
+ return { type: "Ready" }
111
+ } else {
112
+ return {
113
+ type: "Subscribe",
114
+ subscriptions: subscriptions
115
+ }
116
+ }
117
+ } else {
118
+ ll.panic("Malformed state await request path: %v", step)
119
+ }
120
+ } else {
121
+ if state == "Exists" {
122
+ return { type: "Ready" }
123
+ } else {
124
+ if !field.isSet() {
125
+ return {
126
+ type: "Subscribe",
127
+ subscriptions: [{
128
+ resource: field.id.ResourceID,
129
+ event: "InputSet"
130
+ }]
131
+ }
132
+ }
133
+ resource := field.getValue()
134
+ if state == "Set" {
135
+ return { type: "Ready" }
136
+ } else if state == "InputsLocked" {
137
+ if resource.info().InputsLocked {
138
+ return { type: "Ready" }
139
+ } else {
140
+ return {
141
+ type: "Subscribe",
142
+ subscriptions: [{
143
+ resource: resource.id,
144
+ event: "InputsLocked"
145
+ }]
146
+ }
147
+ }
148
+ } else if state == "AllInputsSet" {
149
+ if resource.info().AllInputsSet {
150
+ return { type: "Ready" }
151
+ } else {
152
+ return {
153
+ type: "Subscribe",
154
+ subscriptions: [{
155
+ resource: resource.id,
156
+ event: "AllInputsSet"
157
+ }]
158
+ }
159
+ }
160
+ } else if state == "ResourceReady" {
161
+ if resource.info().ResourceReady {
162
+ return { type: "Ready" }
163
+ } else {
164
+ return {
165
+ type: "Subscribe",
166
+ subscriptions: [{
167
+ resource: resource.id,
168
+ event: "ResourceReady"
169
+ }]
170
+ }
171
+ }
172
+ } else {
173
+ ll.panic("Unknown await state: %v", state)
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ /** Returns true if all requested states are reached */
180
+ await := func(rootField, ctxStorageResource, requests) {
181
+ pssKey := "await:" + string(rootField.id.ResourceID) + ":" + rootField.id.Name
182
+ prevSubscriptions := ctxStorageResource.kvGetAsJson(pssKey)
183
+
184
+ if !is_undefined(prevSubscriptions) && len(prevSubscriptions) == 0 {
185
+ // initialization was finished before, in a previous template execution
186
+ ll.panic("Unexpected state, desired state was reached in the previous call")
187
+ }
188
+
189
+ if is_undefined(prevSubscriptions) {
190
+ // initializing previous subscriptions set
191
+ prevSubscriptions = {}
192
+ }
193
+
194
+ subscriptions := {}
195
+
196
+ addSubscription := func(subscription) {
197
+ key := string(subscription.resource) + ":" + subscription.event
198
+ if !is_undefined(subscriptions[key]) {
199
+ return false
200
+ } else {
201
+ subscriptions[key] = subscription
202
+ return true
203
+ }
204
+ }
205
+
206
+ for _, request in requests {
207
+ if !is_map(request) || !is_array(request.path) || !is_string(request.state) {
208
+ ll.panic("Malformed state await request: %v", request)
209
+ }
210
+ res := awaitOneState(rootField, request.path, request.state)
211
+ if res.type == "Ready" {
212
+ continue
213
+ } else if res.type == "Subscribe" {
214
+ for subscription in res.subscriptions {
215
+ addSubscription(subscription)
216
+ }
217
+ } else {
218
+ ll.panic("Unknown result type %v", res.type)
219
+ }
220
+ }
221
+
222
+ // creating new subscriptions
223
+ for key, sub in subscriptions {
224
+ if !is_undefined(prevSubscriptions[key]) {
225
+ // don't create subscription, if it was created in the past
226
+ continue
227
+ }
228
+ e := {}
229
+ e[sub.event] = true
230
+ tx.subscribeTo(sub.resource, pssKey + ":" + key, e)
231
+ }
232
+
233
+ // unsubscribing from subscriptions we don't need anymore
234
+ for key, _ in prevSubscriptions {
235
+ if !is_undefined(subscriptions[key]) {
236
+ // don't unsubscribe from subscriptions, we still need
237
+ continue
238
+ }
239
+ tx.unsubscribeFrom(pssKey + ":" + key)
240
+ }
241
+
242
+ // creating and saving new context; savong only keys
243
+ newPrevSubscriptions := {}
244
+ for key, _ in subscriptions {
245
+ newPrevSubscriptions[key] = true
246
+ }
247
+ // saving the context
248
+ ctxStorageResource.kvSetJson(pssKey, newPrevSubscriptions)
249
+
250
+ return len(subscriptions) == 0
251
+ }
252
+
253
+ export ll.toStrict({
254
+ await: await
255
+ })
@@ -0,0 +1,19 @@
1
+ /* Templates/renderers utils */
2
+
3
+ ll := import("@platforma-sdk/workflow-tengo:ll")
4
+
5
+ // template field name in renderer resource
6
+ TEMPLATE_FIELD := "template"
7
+ // field name of a resource map with the inputs in renderer resource
8
+ INPUTS_MAP_FIELD := "inputs"
9
+ // prefix of output fields template results in renderer resource
10
+ TEMPLATE_OUTPUT_PREFIX := "outputs/"
11
+ // input field containing a map with resources to prepare for the body
12
+ PREPARE_MAP_FIELD_NAME := "__prepare__"
13
+
14
+ export ll.toStrict({
15
+ TEMPLATE_FIELD : TEMPLATE_FIELD,
16
+ INPUTS_MAP_FIELD : INPUTS_MAP_FIELD,
17
+ TEMPLATE_OUTPUT_PREFIX : TEMPLATE_OUTPUT_PREFIX,
18
+ PREPARE_MAP_FIELD_NAME : PREPARE_MAP_FIELD_NAME
19
+ })
@@ -0,0 +1,428 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ sets := import("@platforma-sdk/workflow-tengo:sets")
3
+ fmt := import("fmt")
4
+ text := import("text")
5
+ maps := import("@platforma-sdk/workflow-tengo:maps")
6
+ enum := import("enum")
7
+ smart := import("@platforma-sdk/workflow-tengo:smart")
8
+
9
+ json := import("json")
10
+
11
+ //
12
+ // Tools for implementing your own checks:
13
+ //
14
+
15
+ _formatKeyPath := func(current, format, key) {
16
+ if current == "" {
17
+ if !is_string(key) {
18
+ return fmt.sprintf("[%d]", key)
19
+ }
20
+
21
+ return key
22
+ }
23
+
24
+ return fmt.sprintf(format, current, key)
25
+ }
26
+
27
+ /**
28
+ * Validation success result.
29
+ */
30
+ success := { result: true }
31
+
32
+ /**
33
+ * Validation failure result with a message.
34
+ *
35
+ * @param keyPath: string - the current stack state.
36
+ * @param msg: string - the failure message.
37
+ * @param args: ...any - additional arguments for the message.
38
+ */
39
+ fail := func(keyPath, msg, ...args) {
40
+ return {
41
+ result: false,
42
+ message: keyPath + ": " + fmt.sprintf(msg, args...)
43
+ }
44
+ }
45
+
46
+ isSuccess := func(r) {
47
+ return r.result == true
48
+ }
49
+
50
+ /**
51
+ * Returns true if the validation failed.
52
+ *
53
+ * @param r: either fail or success.
54
+ */
55
+ isFail := func(r) {
56
+ return r.result == false
57
+ }
58
+
59
+ /**
60
+ * Checks a condition and returns success or failure.
61
+ *
62
+ * @param condition: bool - the condition to check.
63
+ * @param stack: any - the current stack state.
64
+ * @param msg: string - the failure message if the condition is false.
65
+ * @param args: variadic any - additional arguments for the message.
66
+ */
67
+ check := func(condition, keyPath, msg, ...args) {
68
+ if condition {
69
+ return success
70
+ }
71
+
72
+ return fail(keyPath, msg, args...)
73
+ }
74
+
75
+ //
76
+ // Validation:
77
+ //
78
+
79
+ /**
80
+ * Validates JSON types based on tags when the schema is a string.
81
+ *
82
+ * @param js: any - the JSON to validate.
83
+ * @param tags: { key: string, tags: []tag } - the tags to validate against.
84
+ * @param stack: ...any - the current stack state.
85
+ */
86
+ _validateTypes := func(js, tags, keyPath, ...stack) {
87
+ k := tags.key
88
+
89
+ if k == "any" {
90
+ return success
91
+ }
92
+
93
+ if k == "alphanumeric" && ((is_string(js) && text.re_match("^[[:alnum:]]*$", js)) || is_int(js)) {
94
+ return success
95
+ }
96
+
97
+ if k == "string" && is_string(js) {
98
+ regex := tags.tags["regex"]
99
+ if !is_undefined(regex) && !text.re_match(regex, js) {
100
+ return fail(keyPath, "value %q does not conform regex %q", js, regex)
101
+ }
102
+
103
+ return success
104
+ }
105
+
106
+ if k == "char" && is_char(js) {
107
+ return success
108
+ }
109
+
110
+ if k == "bytes" && is_bytes(js) {
111
+ return success
112
+ }
113
+
114
+ if k == "null" && is_undefined(js) {
115
+ return success
116
+ }
117
+
118
+ if k == "number" && (is_int(js) || is_float(js)) {
119
+ return success
120
+ }
121
+
122
+ if k == "bool" && is_bool(js) {
123
+ return success
124
+ }
125
+
126
+ return fail(keyPath, "js %q is not one of a type: %s", js, tags.key)
127
+ }
128
+
129
+ /**
130
+ * Parses JSON tags from a string rule.
131
+ *
132
+ * @param rule: string - a key with tags, e.g. "any,type=string,regex=abc.*"
133
+ * @return parsedRule: { key: string, tags: map } - the key and tags parsed from the rule.
134
+ */
135
+ _parseTags := func(rule) {
136
+ tagsKV := text.split(rule, ",")
137
+ key := tagsKV[0]
138
+ tagsKV = tagsKV[1:]
139
+
140
+ tags := {}
141
+ for _, kv in tagsKV {
142
+ kAndV := text.split(kv, "=")
143
+ k := kAndV[0]
144
+ if len(kAndV) == 1 {
145
+ tags[k] = true
146
+ } else {
147
+ tags[k] = kAndV[1]
148
+ }
149
+ }
150
+
151
+ return ll.toStrict({
152
+ key: key,
153
+ tags: tags
154
+ })
155
+ }
156
+
157
+ /**
158
+ * Validates JSON against a schema.
159
+ *
160
+ * @param js: any - the JSON to validate.
161
+ * @param schema: any - the schema to validate against.
162
+ * @param stack: ...any - the current stack state.
163
+ * @return either fail or success
164
+ */
165
+ _validateJson := func(js, schema, keyPath, ...stack) {
166
+ if is_undefined(schema) {
167
+ if len(stack) == 0 {
168
+ return fail(keyPath, "key is not defined by schema, but exists in the JSON")
169
+ }
170
+
171
+ return fail(keyPath, "key is not defined by schema, but exists in the JSON. Validated item: %#v", stack[0])
172
+ }
173
+
174
+ // if the schema is a string, then
175
+ // we check the predefined type.
176
+ if is_string(schema) {
177
+ if schema == "any" {
178
+ return success
179
+ }
180
+ tags := _parseTags(schema)
181
+
182
+ return _validateTypes(js, tags, keyPath, stack...)
183
+ }
184
+
185
+ // if the schema is a function, call it
186
+ // with the element and the path of how we
187
+ // got this element, i.e. recursion history.
188
+ if is_callable(schema) {
189
+ return schema(js, keyPath, stack)
190
+ }
191
+
192
+ // if the schema is an array with "or" at the beginning,
193
+ // we should check if the element is succeed with
194
+ // any of the following elements.
195
+ if is_array(schema) && len(schema) > 0 && schema[0] == "or" {
196
+ for i, elem in schema[1:] {
197
+ r := _validateJson(js, elem, keyPath, stack...)
198
+ if isSuccess(r) {
199
+ return r
200
+ }
201
+ }
202
+
203
+ return fail(keyPath, "js %q does not fit any of the following rules: %q", js, schema)
204
+ }
205
+
206
+ // if the schema is an array, then check that every
207
+ // element of the json has the schema that is the first element
208
+ // of the schema.
209
+ if is_array(schema) {
210
+ if !is_array(js) {
211
+ return fail(keyPath, "js %q must be array", js)
212
+ }
213
+
214
+ schema = schema[0]
215
+ for i, elem in js {
216
+ r := _validateJson(elem, schema, _formatKeyPath(keyPath, "%s[%d]", i), js, stack...)
217
+ if isFail(r) {
218
+ return r
219
+ }
220
+ }
221
+
222
+ return success
223
+ }
224
+
225
+ // only map left, since schema can't be undefined
226
+ // if the schema is a map, check that every key
227
+ // in json-object is in the schema and vice-versa,
228
+ // unless there are tags "any" or "omitempty".
229
+ if !ll.isMap(js) {
230
+ return fail(keyPath, "js %#v must be map", js)
231
+ }
232
+
233
+ // schema.key -> tags
234
+ schemaTags := {}
235
+ // schema.key -> rule
236
+ schemaRules := {}
237
+
238
+ for k, v in schema {
239
+ p := _parseTags(k)
240
+ schemaTags[p.key] = p
241
+ schemaRules[p.key] = v
242
+ }
243
+
244
+ // tags that should be applied to all elements
245
+ any := schemaTags["any"]
246
+
247
+ options := schemaTags["__options__"]
248
+ if is_undefined(options) {
249
+ options = {}
250
+ }
251
+
252
+ for _key, elem in js {
253
+ if any {
254
+ r := _validateJson(elem, schemaRules["any"], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
255
+ if isFail(r) {
256
+ return r
257
+ }
258
+
259
+ if !is_undefined(any.tags["type"]) {
260
+ r := _validateJson(_key, any.tags["type"], keyPath, stack...)
261
+ if isFail(r) {
262
+ return r
263
+ }
264
+ }
265
+
266
+ continue
267
+ }
268
+
269
+ // tags for the key
270
+ key := schemaTags[_key]
271
+
272
+ if is_undefined(key) {
273
+ if options.tags["closed"] == true {
274
+ return fail(keyPath, "only keys %v from the schema must be set, found %q", maps.getKeys(schema), _key)
275
+ }
276
+
277
+ continue
278
+ }
279
+
280
+ if key.tags["type"] {
281
+ r := _validateJson(_key, key.tags["type"], keyPath, stack...)
282
+ if isFail(r) {
283
+ return r
284
+ }
285
+ }
286
+
287
+ if key.tags["omitempty"] && is_undefined(elem) {
288
+ continue
289
+ }
290
+
291
+ r := _validateJson(elem, schemaRules[_key], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
292
+ if isFail(r) {
293
+ return r
294
+ }
295
+ }
296
+
297
+ return success
298
+ }
299
+
300
+ /**
301
+ * Checks if the given JSON conforms to the given schema.
302
+ *
303
+ * @param js: any - the JSON to check.
304
+ * @param schema: any - the schema to check against.
305
+ * @return either fail or success
306
+ */
307
+ checkJson := func(js, schema) {
308
+ return _validateJson(js, schema, "")
309
+ }
310
+
311
+ /**
312
+ * Checks if the given JSON is valid according to the given schema.
313
+ *
314
+ * @param js: any - the JSON to check.
315
+ * @param schema: any - the schema to check against.
316
+ * @return result: bool - the result of the validation (true/false).
317
+ */
318
+ isValid := func(js, schema) {
319
+ return _validateJson(js, schema, "").result
320
+ }
321
+
322
+ /**
323
+ * Asserts that a given JSON conforms to a given schema.
324
+ *
325
+ * @param js: any - the JSON to assert.
326
+ * @param schema: any - the schema to assert against.
327
+ * @param failMsg: string - optional parameter with custom message to be printed on validation error
328
+ */
329
+ assertJsonSchema := func(js, schema, ...failMsg) {
330
+ r := _validateJson(js, schema, "")
331
+
332
+ msg := "JSON validation error"
333
+ if len(failMsg) > 0 {
334
+ msg = failMsg[0]
335
+ }
336
+
337
+ if isFail(r) {
338
+ ll.panic("%s: %s", msg, r.message)
339
+ }
340
+ }
341
+
342
+ //
343
+ // Domain-specific schemas and checks:
344
+ //
345
+
346
+ /**
347
+ * Checks if the given element is a resource.
348
+ *
349
+ * @param options: object - resource validation options
350
+ */
351
+ resource := func(...args) {
352
+
353
+ ops := {}
354
+ if len(args) > 0 {
355
+ ops = args[0]
356
+ }
357
+
358
+ assertJsonSchema(ops, {
359
+ `type,omitempty`: {
360
+ Name: `string`,
361
+ Version: `string`
362
+ }
363
+ })
364
+
365
+ checker := func(elem, keyPath, stack) {
366
+ c := undefined
367
+
368
+ c = check(smart.isResource(elem), keyPath, "the element " + elem + " must be a resource")
369
+ if isFail(c) {
370
+ return c
371
+ }
372
+
373
+ if !is_undefined(ops.type) {
374
+ c = check(elem.info().Type.Name == ops.type.Name && elem.info().Type.Version == ops.type.Version, keyPath,
375
+ "the element " + elem + " must have the following type: ", ops.type, " found: ", elem.info().Type)
376
+ if isFail(c) {
377
+ return c
378
+ }
379
+ }
380
+
381
+ return c
382
+ }
383
+
384
+ return checker
385
+ }
386
+
387
+ /**
388
+ * Checks if the given element is a resource with specified type.
389
+ *
390
+ * @param rt: ResourceType - desired resource type
391
+ */
392
+ resourceType := func(rt) {
393
+ return func(elem, keyPath, stack) {
394
+ return check(smart.isResource(elem) && elem.info().Type.Name == rt.Name, keyPath, "the element " + elem + " must be a resource")
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Checks if the given element is a reference.
400
+ *
401
+ * @param elem: any - the element to check.
402
+ * @param stack: any - the current stack state.
403
+ */
404
+ reference := func(elem, keyPath, stack) {
405
+ return check(smart.isReference(elem), keyPath, "the element " + elem + " must be a resource or a field")
406
+ }
407
+
408
+ /**
409
+ * Schema for reference
410
+ */
411
+ refSchema := {
412
+ blockId: `string`,
413
+ name: `string`
414
+ }
415
+
416
+ export ll.toStrict({
417
+ success: success,
418
+ fail: fail,
419
+ isSuccess: isSuccess,
420
+ isFail: isFail,
421
+ checkJson: checkJson,
422
+ isValid: isValid,
423
+ assertJsonSchema: assertJsonSchema,
424
+ resource: resource,
425
+ reference: reference,
426
+ resourceType: resourceType,
427
+ ref: refSchema
428
+ })