@onlineapps/cookbook-core 1.0.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.
@@ -0,0 +1,441 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://onlineapps.cz/schemas/cookbook-strict/v2.0.0",
4
+ "title": "Cookbook Schema - Strict Mode",
5
+ "description": "Strict validation schema for production environments with all safety checks enabled",
6
+ "type": "object",
7
+ "required": ["steps", "version"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "name": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "maxLength": 100,
14
+ "pattern": "^[a-zA-Z0-9-_]+$"
15
+ },
16
+ "version": {
17
+ "type": "string",
18
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
19
+ "description": "Semantic version of the cookbook schema"
20
+ },
21
+ "description": {
22
+ "type": "string",
23
+ "maxLength": 500
24
+ },
25
+ "api_input": {
26
+ "type": "object",
27
+ "description": "Initial input data for the workflow"
28
+ },
29
+ "metadata": {
30
+ "type": "object",
31
+ "description": "Additional metadata for debugging and tracking",
32
+ "properties": {
33
+ "created_at": {
34
+ "type": "string",
35
+ "format": "date-time"
36
+ },
37
+ "author": {
38
+ "type": "string"
39
+ },
40
+ "tags": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "string"
44
+ }
45
+ }
46
+ }
47
+ },
48
+ "steps": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "maxItems": 1000,
52
+ "items": {
53
+ "$ref": "#/definitions/Step"
54
+ }
55
+ }
56
+ },
57
+ "definitions": {
58
+ "Step": {
59
+ "type": "object",
60
+ "required": ["id", "type"],
61
+ "properties": {
62
+ "id": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "maxLength": 100,
66
+ "pattern": "^[a-zA-Z0-9-_]+$"
67
+ },
68
+ "type": {
69
+ "type": "string",
70
+ "enum": ["task", "foreach", "fork_join", "switch", "sub_workflow", "wait", "dispatch"]
71
+ }
72
+ },
73
+ "oneOf": [
74
+ { "$ref": "#/definitions/TaskStep" },
75
+ { "$ref": "#/definitions/ForeachStep" },
76
+ { "$ref": "#/definitions/ForkJoinStep" },
77
+ { "$ref": "#/definitions/SwitchStep" },
78
+ { "$ref": "#/definitions/SubWorkflowStep" },
79
+ { "$ref": "#/definitions/WaitStep" },
80
+ { "$ref": "#/definitions/DispatchStep" }
81
+ ]
82
+ },
83
+ "TaskStep": {
84
+ "type": "object",
85
+ "required": ["id", "type", "service"],
86
+ "additionalProperties": false,
87
+ "properties": {
88
+ "id": {
89
+ "type": "string",
90
+ "minLength": 1,
91
+ "maxLength": 100,
92
+ "pattern": "^[a-zA-Z0-9-_]+$"
93
+ },
94
+ "type": {
95
+ "type": "string",
96
+ "const": "task"
97
+ },
98
+ "name": {
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 200
102
+ },
103
+ "service": {
104
+ "type": "string",
105
+ "minLength": 1,
106
+ "maxLength": 100,
107
+ "pattern": "^[a-zA-Z0-9-_]+$"
108
+ },
109
+ "operation": {
110
+ "type": "string",
111
+ "minLength": 1,
112
+ "maxLength": 100,
113
+ "description": "Operation identifier for the service to execute"
114
+ },
115
+ "action": {
116
+ "type": "string",
117
+ "enum": ["GET", "POST", "PUT", "DELETE", "PATCH"]
118
+ },
119
+ "endpoint": {
120
+ "type": "string",
121
+ "minLength": 1,
122
+ "maxLength": 500
123
+ },
124
+ "input": {
125
+ "type": "object",
126
+ "additionalProperties": {
127
+ "type": "string",
128
+ "minLength": 1
129
+ },
130
+ "description": "Optional input mapping"
131
+ },
132
+ "output": {
133
+ "type": "object",
134
+ "additionalProperties": {
135
+ "type": "string",
136
+ "minLength": 1
137
+ },
138
+ "description": "Optional output mapping"
139
+ },
140
+ "depends_on": {
141
+ "type": "array",
142
+ "items": {
143
+ "type": "string",
144
+ "minLength": 1,
145
+ "maxLength": 100
146
+ },
147
+ "maxItems": 100
148
+ },
149
+ "retry": {
150
+ "$ref": "#/definitions/Retry"
151
+ },
152
+ "timeoutMs": {
153
+ "type": "integer",
154
+ "minimum": 1,
155
+ "maximum": 3600000
156
+ }
157
+ }
158
+ },
159
+ "ForeachStep": {
160
+ "type": "object",
161
+ "required": ["id", "type", "iterator", "body"],
162
+ "additionalProperties": false,
163
+ "properties": {
164
+ "id": {
165
+ "type": "string",
166
+ "minLength": 1,
167
+ "maxLength": 100,
168
+ "pattern": "^[a-zA-Z0-9-_]+$"
169
+ },
170
+ "type": {
171
+ "type": "string",
172
+ "const": "foreach"
173
+ },
174
+ "name": {
175
+ "type": "string",
176
+ "minLength": 1,
177
+ "maxLength": 200
178
+ },
179
+ "iterator": {
180
+ "type": "string",
181
+ "minLength": 1,
182
+ "pattern": "^\\$"
183
+ },
184
+ "body": {
185
+ "type": "array",
186
+ "minItems": 1,
187
+ "maxItems": 100,
188
+ "items": {
189
+ "$ref": "#/definitions/Step"
190
+ }
191
+ },
192
+ "output": {
193
+ "type": "object",
194
+ "additionalProperties": {
195
+ "type": "string",
196
+ "minLength": 1
197
+ }
198
+ }
199
+ }
200
+ },
201
+ "ForkJoinStep": {
202
+ "type": "object",
203
+ "required": ["id", "type", "branches", "join"],
204
+ "additionalProperties": false,
205
+ "properties": {
206
+ "id": {
207
+ "type": "string",
208
+ "minLength": 1,
209
+ "maxLength": 100,
210
+ "pattern": "^[a-zA-Z0-9-_]+$"
211
+ },
212
+ "type": {
213
+ "type": "string",
214
+ "const": "fork_join"
215
+ },
216
+ "name": {
217
+ "type": "string",
218
+ "minLength": 1,
219
+ "maxLength": 200
220
+ },
221
+ "branches": {
222
+ "type": "array",
223
+ "minItems": 1,
224
+ "maxItems": 50,
225
+ "items": {
226
+ "$ref": "#/definitions/Step"
227
+ }
228
+ },
229
+ "join": {
230
+ "type": "object",
231
+ "required": ["strategy"],
232
+ "additionalProperties": false,
233
+ "properties": {
234
+ "strategy": {
235
+ "type": "string",
236
+ "enum": ["merge", "first", "last", "all", "custom"],
237
+ "description": "How to combine branch results"
238
+ },
239
+ "output": {
240
+ "type": "object",
241
+ "additionalProperties": {
242
+ "type": "string",
243
+ "minLength": 1
244
+ }
245
+ },
246
+ "customHandler": {
247
+ "type": "string",
248
+ "description": "Custom handler name when strategy is 'custom'"
249
+ }
250
+ }
251
+ }
252
+ }
253
+ },
254
+ "SwitchStep": {
255
+ "type": "object",
256
+ "required": ["id", "type", "expression", "cases", "default"],
257
+ "additionalProperties": false,
258
+ "properties": {
259
+ "id": {
260
+ "type": "string",
261
+ "minLength": 1,
262
+ "maxLength": 100,
263
+ "pattern": "^[a-zA-Z0-9-_]+$"
264
+ },
265
+ "type": {
266
+ "type": "string",
267
+ "const": "switch"
268
+ },
269
+ "name": {
270
+ "type": "string",
271
+ "minLength": 1,
272
+ "maxLength": 200
273
+ },
274
+ "expression": {
275
+ "type": "string",
276
+ "minLength": 1,
277
+ "pattern": "^\\$"
278
+ },
279
+ "cases": {
280
+ "type": "object",
281
+ "minProperties": 1,
282
+ "maxProperties": 100,
283
+ "additionalProperties": {
284
+ "$ref": "#/definitions/Step"
285
+ }
286
+ },
287
+ "default": {
288
+ "$ref": "#/definitions/Step"
289
+ },
290
+ "depends_on": {
291
+ "type": "array",
292
+ "items": {
293
+ "type": "string",
294
+ "minLength": 1,
295
+ "maxLength": 100
296
+ },
297
+ "maxItems": 100
298
+ }
299
+ }
300
+ },
301
+ "SubWorkflowStep": {
302
+ "type": "object",
303
+ "required": ["id", "type", "steps"],
304
+ "additionalProperties": false,
305
+ "properties": {
306
+ "id": {
307
+ "type": "string",
308
+ "minLength": 1,
309
+ "maxLength": 100,
310
+ "pattern": "^[a-zA-Z0-9-_]+$"
311
+ },
312
+ "type": {
313
+ "type": "string",
314
+ "const": "sub_workflow"
315
+ },
316
+ "name": {
317
+ "type": "string",
318
+ "minLength": 1,
319
+ "maxLength": 200
320
+ },
321
+ "input": {
322
+ "type": "object",
323
+ "additionalProperties": {
324
+ "type": "string",
325
+ "minLength": 1
326
+ },
327
+ "description": "Optional input for sub-workflow"
328
+ },
329
+ "steps": {
330
+ "type": "array",
331
+ "minItems": 1,
332
+ "maxItems": 100,
333
+ "items": {
334
+ "$ref": "#/definitions/Step"
335
+ }
336
+ }
337
+ }
338
+ },
339
+ "WaitStep": {
340
+ "type": "object",
341
+ "required": ["id", "type", "durationMs"],
342
+ "additionalProperties": false,
343
+ "properties": {
344
+ "id": {
345
+ "type": "string",
346
+ "minLength": 1,
347
+ "maxLength": 100,
348
+ "pattern": "^[a-zA-Z0-9-_]+$"
349
+ },
350
+ "type": {
351
+ "type": "string",
352
+ "const": "wait"
353
+ },
354
+ "name": {
355
+ "type": "string",
356
+ "minLength": 1,
357
+ "maxLength": 200
358
+ },
359
+ "durationMs": {
360
+ "type": "integer",
361
+ "minimum": 0,
362
+ "maximum": 86400000,
363
+ "description": "Wait duration in milliseconds (max 24 hours)"
364
+ }
365
+ }
366
+ },
367
+ "DispatchStep": {
368
+ "type": "object",
369
+ "required": ["id", "type", "method", "target"],
370
+ "additionalProperties": false,
371
+ "properties": {
372
+ "id": {
373
+ "type": "string",
374
+ "minLength": 1,
375
+ "maxLength": 100,
376
+ "pattern": "^[a-zA-Z0-9-_]+$"
377
+ },
378
+ "type": {
379
+ "type": "string",
380
+ "const": "dispatch"
381
+ },
382
+ "name": {
383
+ "type": "string",
384
+ "minLength": 1,
385
+ "maxLength": 200
386
+ },
387
+ "method": {
388
+ "type": "string",
389
+ "enum": ["webhook", "http", "grpc", "kafka", "sqs", "custom"],
390
+ "description": "Dispatch method to use"
391
+ },
392
+ "target": {
393
+ "type": "string",
394
+ "format": "uri",
395
+ "maxLength": 2000
396
+ },
397
+ "input": {
398
+ "type": "object",
399
+ "additionalProperties": {
400
+ "type": "string",
401
+ "minLength": 1
402
+ },
403
+ "description": "Optional dispatch payload"
404
+ },
405
+ "headers": {
406
+ "type": "object",
407
+ "additionalProperties": {
408
+ "type": "string"
409
+ },
410
+ "description": "Optional HTTP headers for webhook/http methods"
411
+ },
412
+ "retry": {
413
+ "$ref": "#/definitions/Retry"
414
+ }
415
+ }
416
+ },
417
+ "Retry": {
418
+ "type": "object",
419
+ "required": ["maxAttempts", "delayMs"],
420
+ "additionalProperties": false,
421
+ "properties": {
422
+ "maxAttempts": {
423
+ "type": "integer",
424
+ "minimum": 1,
425
+ "maximum": 10
426
+ },
427
+ "delayMs": {
428
+ "type": "integer",
429
+ "minimum": 0,
430
+ "maximum": 300000
431
+ },
432
+ "backoffMultiplier": {
433
+ "type": "number",
434
+ "minimum": 1.0,
435
+ "maximum": 5.0,
436
+ "description": "Exponential backoff multiplier"
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
package/src/index.js ADDED
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module @onlineapps/cookbook-core
5
+ *
6
+ * Core cookbook parsing and validation library.
7
+ * Lightweight foundation for workflow definitions - no heavy dependencies.
8
+ */
9
+
10
+ const {
11
+ parseCookbookFromFile,
12
+ parseCookbookFromFileSync,
13
+ parseCookbookFromObject
14
+ } = require('./parser');
15
+
16
+ const {
17
+ validateCookbook,
18
+ validateStep,
19
+ CookbookValidationError
20
+ } = require('./validator');
21
+
22
+ const {
23
+ CookbookSchema,
24
+ loadSchema
25
+ } = require('./schema');
26
+
27
+ const {
28
+ validateAllReferences,
29
+ validateDependsOnReferences,
30
+ validateVariableReferences,
31
+ validateErrorHandlerReferences
32
+ } = require('./referenceValidator');
33
+
34
+ module.exports = {
35
+ // Parser functions
36
+ parseCookbookFromFile,
37
+ parseCookbookFromFileSync,
38
+ parseCookbookFromObject,
39
+
40
+ // Validator functions
41
+ validateCookbook,
42
+ validateStep,
43
+
44
+ // Reference validation
45
+ validateAllReferences,
46
+ validateDependsOnReferences,
47
+ validateVariableReferences,
48
+ validateErrorHandlerReferences,
49
+
50
+ // Schema access
51
+ CookbookSchema,
52
+ loadSchema,
53
+
54
+ // Error classes
55
+ CookbookValidationError,
56
+
57
+ // Utility exports
58
+ VERSION: '1.0.0',
59
+ SCHEMA_VERSION: '1.0.0'
60
+ };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview
5
+ * Cookbook Parser: loads and parses a JSON cookbook, then validates its structure.
6
+ * Exposes functions to parse from file or from raw JavaScript object.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { validateCookbook, CookbookValidationError } = require('./cookbookValidator');
12
+
13
+ /**
14
+ * Read file contents as UTF-8 string (sync version).
15
+ * @param {string} filePath - Path to the cookbook JSON file.
16
+ * @returns {string} Raw file contents.
17
+ * @throws {Error} If file cannot be read.
18
+ */
19
+ function _readFileSync(filePath) {
20
+ const absolutePath = path.resolve(filePath);
21
+ return fs.readFileSync(absolutePath, 'utf-8');
22
+ }
23
+
24
+ /**
25
+ * Read file contents as UTF-8 string (async version).
26
+ * @param {string} filePath - Path to the cookbook JSON file.
27
+ * @returns {Promise<string>} Raw file contents.
28
+ * @throws {Error} If file cannot be read.
29
+ */
30
+ async function _readFileAsync(filePath) {
31
+ const absolutePath = path.resolve(filePath);
32
+ const fsPromises = fs.promises || require('fs').promises;
33
+ return await fsPromises.readFile(absolutePath, 'utf-8');
34
+ }
35
+
36
+ /**
37
+ * Parse a JSON string into an object.
38
+ * @param {string} jsonString - Raw JSON string.
39
+ * @param {string} context - Context label for error messages.
40
+ * @returns {object} Parsed JavaScript object.
41
+ * @throws {SyntaxError} If JSON is invalid.
42
+ */
43
+ function _parseJson(jsonString, context) {
44
+ try {
45
+ return JSON.parse(jsonString);
46
+ } catch (err) {
47
+ const message = `Failed to parse JSON for ${context}: ${err.message}`;
48
+ const syntaxError = new SyntaxError(message);
49
+ syntaxError.originalError = err;
50
+ throw syntaxError;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Parse and validate a cookbook from a JavaScript object.
56
+ * @param {object} rawObject - JavaScript object representing the cookbook.
57
+ * @returns {object} Validated cookbook object (deep-copied).
58
+ * @throws {CookbookValidationError} If validation fails.
59
+ */
60
+ function parseCookbookFromObject(rawObject) {
61
+ // Defensive copy to prevent mutation of original
62
+ const cookbook = JSON.parse(JSON.stringify(rawObject));
63
+
64
+ // Validate structure and nested steps
65
+ validateCookbook(cookbook);
66
+
67
+ return cookbook;
68
+ }
69
+
70
+ /**
71
+ * Parse and validate a cookbook from a JSON file (sync version).
72
+ * @param {string} filePath - Path to the JSON file containing the cookbook.
73
+ * @returns {object} Validated cookbook object.
74
+ * @throws {Error|SyntaxError|CookbookValidationError} If file read, parse, or validation fails.
75
+ */
76
+ function parseCookbookFromFileSync(filePath) {
77
+ // Read raw contents
78
+ const raw = _readFileSync(filePath);
79
+
80
+ // Parse JSON into object
81
+ const parsed = _parseJson(raw, `file '${filePath}'`);
82
+
83
+ // Validate and return
84
+ return parseCookbookFromObject(parsed);
85
+ }
86
+
87
+ /**
88
+ * Parse and validate a cookbook from a JSON file (async version).
89
+ * @param {string} filePath - Path to the JSON file containing the cookbook.
90
+ * @returns {Promise<object>} Validated cookbook object.
91
+ * @throws {Error|SyntaxError|CookbookValidationError} If file read, parse, or validation fails.
92
+ */
93
+ async function parseCookbookFromFile(filePath) {
94
+ // Read raw contents
95
+ const raw = await _readFileAsync(filePath);
96
+
97
+ // Parse JSON into object
98
+ const parsed = _parseJson(raw, `file '${filePath}'`);
99
+
100
+ // Validate and return
101
+ return parseCookbookFromObject(parsed);
102
+ }
103
+
104
+ module.exports = {
105
+ parseCookbookFromFile,
106
+ parseCookbookFromFileSync,
107
+ parseCookbookFromObject,
108
+ };