@onlineapps/cookbook-template-helpers 1.0.1 → 1.0.3
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.
- package/README.md +25 -103
- package/package.json +3 -3
- package/src/helpers/fileHelpers.js +86 -155
- package/src/index.js +2 -0
package/README.md
CHANGED
|
@@ -15,104 +15,54 @@ Helper functions can be used in cookbook template variables using the syntax:
|
|
|
15
15
|
{{helper_name(argument)}}
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Arguments can be template variables themselves:
|
|
19
|
-
```
|
|
20
|
-
{{helper_name(steps.step_id.output.field)}}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
18
|
## Available Helpers
|
|
24
19
|
|
|
25
|
-
###
|
|
26
|
-
|
|
27
|
-
Converts string content or inline descriptor to file descriptor. Uploads content to MinIO storage.
|
|
20
|
+
### normalizeString
|
|
21
|
+
Removes nebezpečné / ne-print znaky, ponechá písmena (včetně diakritiky), čísla, mezery a základní interpunkci. Zachová diakritiku a case.
|
|
28
22
|
|
|
29
23
|
**Signature:**
|
|
30
24
|
```javascript
|
|
31
|
-
|
|
25
|
+
function normalizeString(value)
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- `context` (Object) - Workflow context (for storage access)
|
|
28
|
+
### webalizeString
|
|
29
|
+
Normalizuje řetězec pro URL/slug: odstraní diakritiku, interpunkci, nahradí mezery pomlčkami, převede na lowercase, zkolabuje vícenásobné pomlčky.
|
|
37
30
|
|
|
38
|
-
**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
**Examples:**
|
|
42
|
-
|
|
43
|
-
```json
|
|
44
|
-
{
|
|
45
|
-
"input": {
|
|
46
|
-
"file": "{{string2file(steps.markdown.output.content)}}"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
31
|
+
**Signature:**
|
|
32
|
+
```javascript
|
|
33
|
+
function webalizeString(value)
|
|
49
34
|
```
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
### string2file
|
|
37
|
+
Převede string nebo inline descriptor na file descriptor (přes ContentResolver). Ukládá do storage podle nastavení ContentResolveru.
|
|
38
|
+
|
|
39
|
+
**Signature:**
|
|
40
|
+
```javascript
|
|
41
|
+
async function string2file(input, context)
|
|
57
42
|
```
|
|
58
43
|
|
|
59
44
|
### file2string
|
|
60
|
-
|
|
61
|
-
Converts file descriptor to string. Downloads content from MinIO if needed.
|
|
45
|
+
Převede file/inline descriptor na string (přes ContentResolver). File stáhne z MinIO, inline vrátí přímo.
|
|
62
46
|
|
|
63
47
|
**Signature:**
|
|
64
48
|
```javascript
|
|
65
49
|
async function file2string(descriptor, context)
|
|
66
50
|
```
|
|
67
51
|
|
|
68
|
-
|
|
69
|
-
- `descriptor` (Object|string) - File descriptor or string
|
|
70
|
-
- `context` (Object) - Workflow context (for storage access)
|
|
71
|
-
|
|
72
|
-
**Returns:**
|
|
73
|
-
- `Promise<string>` - String content
|
|
74
|
-
|
|
75
|
-
**Examples:**
|
|
52
|
+
## Usage in Cookbook
|
|
76
53
|
|
|
54
|
+
Helpers jsou k dispozici při resolvování template proměnných:
|
|
77
55
|
```json
|
|
78
56
|
{
|
|
79
57
|
"input": {
|
|
80
|
-
"
|
|
58
|
+
"title": "{{normalizeString(api_input.title)}}",
|
|
59
|
+
"slug": "{{webalizeString(api_input.title)}}",
|
|
60
|
+
"file": "{{string2file(api_input.content)}}",
|
|
61
|
+
"content": "{{file2string(steps.prepare.output.file_descriptor)}}"
|
|
81
62
|
}
|
|
82
63
|
}
|
|
83
64
|
```
|
|
84
65
|
|
|
85
|
-
## Usage in Cookbook
|
|
86
|
-
|
|
87
|
-
Helper functions are integrated into the cookbook executor. They are automatically available when resolving template variables.
|
|
88
|
-
|
|
89
|
-
**Example cookbook:**
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{
|
|
93
|
-
"version": "2.0.0",
|
|
94
|
-
"steps": [
|
|
95
|
-
{
|
|
96
|
-
"step_id": "get_markdown",
|
|
97
|
-
"type": "task",
|
|
98
|
-
"service": "hello-service",
|
|
99
|
-
"operation": "generate-markdown",
|
|
100
|
-
"input": { "name": "Karel" }
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"step_id": "create_pdf",
|
|
104
|
-
"type": "task",
|
|
105
|
-
"service": "pdfgen-service",
|
|
106
|
-
"operation": "md2pdf",
|
|
107
|
-
"depends_on": ["get_markdown"],
|
|
108
|
-
"input": {
|
|
109
|
-
"markdown": "{{file2string(steps.get_markdown.output.file_descriptor)}}"
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
66
|
## Integration
|
|
117
67
|
|
|
118
68
|
Helper functions are passed to the cookbook executor:
|
|
@@ -146,41 +96,13 @@ const executor = new CookbookExecutor(cookbook, {
|
|
|
146
96
|
|
|
147
97
|
## Helper Function Requirements
|
|
148
98
|
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
151
|
-
- **
|
|
152
|
-
- **Error handling**: Should throw descriptive errors
|
|
99
|
+
- **Deterministic**: Stejný vstup = stejný výstup
|
|
100
|
+
- **Bez vedlejších efektů**: String helpery jsou čisté; file helpery používají ContentResolver
|
|
101
|
+
- **Chyby**: Měly by být jasné a srozumitelné
|
|
153
102
|
|
|
154
103
|
## Dependencies
|
|
155
104
|
|
|
156
|
-
- `@onlineapps/
|
|
157
|
-
|
|
158
|
-
## Environment Variables
|
|
159
|
-
|
|
160
|
-
Helper functions use the following environment variables for storage access:
|
|
161
|
-
|
|
162
|
-
- `MINIO_HOST` - MinIO endpoint (default: `api_shared_storage`)
|
|
163
|
-
- `MINIO_PORT` - MinIO port (default: `9000`)
|
|
164
|
-
- `MINIO_ACCESS_KEY` - MinIO access key (default: `minioadmin`)
|
|
165
|
-
- `MINIO_SECRET_KEY` - MinIO secret key (default: `minioadmin`)
|
|
166
|
-
- `MINIO_USE_SSL` - Use SSL (default: `false`)
|
|
167
|
-
|
|
168
|
-
## File Descriptor Format
|
|
169
|
-
|
|
170
|
-
File descriptors have the following structure:
|
|
171
|
-
|
|
172
|
-
```javascript
|
|
173
|
-
{
|
|
174
|
-
_descriptor: true,
|
|
175
|
-
type: 'file', // or 'inline'
|
|
176
|
-
storage_ref: 'minio://workflow/path/to/file', // for type='file'
|
|
177
|
-
content: '...', // for type='inline'
|
|
178
|
-
filename: 'document.txt',
|
|
179
|
-
content_type: 'text/plain',
|
|
180
|
-
size: 1234,
|
|
181
|
-
fingerprint: 'sha256...'
|
|
182
|
-
}
|
|
183
|
-
```
|
|
105
|
+
- `@onlineapps/content-resolver` (pro string2file/file2string)
|
|
184
106
|
|
|
185
107
|
## License
|
|
186
108
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/cookbook-template-helpers",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Template helper functions for cookbook variable resolution - string2file, file2string, and more",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"author": "OnlineApps",
|
|
20
20
|
"license": "PROPRIETARY",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@onlineapps/
|
|
22
|
+
"@onlineapps/content-resolver": "1.1.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"jest": "
|
|
25
|
+
"jest": "29.7.0"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18.0.0"
|
|
@@ -1,200 +1,131 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Helper functions for cookbook template resolution.
|
|
5
|
+
* Includes safe string normalization and descriptor/string conversions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const ContentResolver = require('@onlineapps/content-resolver');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Normalize string: remove control/unsafe chars, keep letters (incl. diacritics), numbers,
|
|
12
|
+
* whitespace and basic punctuation .,!?;:-_
|
|
13
|
+
* @param {string} value
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function normalizeString(value) {
|
|
17
|
+
if (value === null || value === undefined) return '';
|
|
18
|
+
const str = String(value);
|
|
19
|
+
// Allow common Latin ranges (including diacritics), numbers, whitespace and basic punctuation
|
|
20
|
+
const cleaned = str.replace(/[^\x20-\x7E\u00A0-\u024F\u1E00-\u1EFF\s\.\,\!\?\;\:\-\_]/g, '');
|
|
21
|
+
// Explicitly drop angle brackets to avoid HTML/script injection noise
|
|
22
|
+
return cleaned.replace(/[<>]/g, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Webalize string: normalize, strip diacritics, remove punctuation, lowercase, replace spaces with hyphens.
|
|
27
|
+
* @param {string} value
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function webalizeString(value) {
|
|
31
|
+
const normalized = normalizeString(value);
|
|
32
|
+
const noPunct = normalized.replace(/[\.\,\!\?\;\:]/g, '');
|
|
33
|
+
const withoutDiacritics = noPunct.normalize('NFD').replace(/\p{Diacritic}/gu, '');
|
|
34
|
+
const withHyphens = withoutDiacritics.trim().replace(/\s+/g, '-');
|
|
35
|
+
return withHyphens.toLowerCase().replace(/-+/g, '-');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get ContentResolver instance (DI first, else new).
|
|
12
40
|
* @private
|
|
13
|
-
* @param {Object} context - Workflow context
|
|
14
|
-
* @returns {Promise<Object>} Storage connector instance
|
|
15
41
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
function getContentResolver(helperContext) {
|
|
43
|
+
if (!helperContext || typeof helperContext !== 'object') {
|
|
44
|
+
throw new Error('[cookbook-template-helpers] Helper context is required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
helperContext.contentResolver &&
|
|
49
|
+
typeof helperContext.contentResolver.getAsString === 'function' &&
|
|
50
|
+
typeof helperContext.contentResolver.createDescriptor === 'function'
|
|
51
|
+
) {
|
|
52
|
+
return helperContext.contentResolver;
|
|
20
53
|
}
|
|
21
54
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
const workflow_id =
|
|
56
|
+
helperContext.workflow_id ||
|
|
57
|
+
helperContext.data?.variables?.workflow_id ||
|
|
58
|
+
helperContext.data?.workflow_id ||
|
|
59
|
+
null;
|
|
60
|
+
|
|
61
|
+
const step_id =
|
|
62
|
+
helperContext.step_id ||
|
|
63
|
+
helperContext.data?.variables?.step_id ||
|
|
64
|
+
helperContext.data?.step_id ||
|
|
65
|
+
null;
|
|
66
|
+
|
|
67
|
+
return new ContentResolver({ context: { workflow_id, step_id } });
|
|
34
68
|
}
|
|
35
69
|
|
|
36
70
|
/**
|
|
37
|
-
* Convert string or inline descriptor to file descriptor
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* @
|
|
41
|
-
* @param {Object} context - Workflow context (for storage access)
|
|
42
|
-
* @returns {Promise<Object>} File descriptor
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* // String input
|
|
46
|
-
* const descriptor = await string2file('Hello world', context);
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* // Inline descriptor input
|
|
50
|
-
* const descriptor = await string2file({
|
|
51
|
-
* _descriptor: true,
|
|
52
|
-
* type: 'inline',
|
|
53
|
-
* content: 'Hello world',
|
|
54
|
-
* filename: 'greeting.txt'
|
|
55
|
-
* }, context);
|
|
71
|
+
* Convert string or inline descriptor to file descriptor (uploads via ContentResolver).
|
|
72
|
+
* @param {string|Object} input
|
|
73
|
+
* @param {Object} context
|
|
74
|
+
* @returns {Promise<Object>}
|
|
56
75
|
*/
|
|
57
76
|
async function string2file(input, context) {
|
|
58
|
-
if (
|
|
77
|
+
if (input === null || input === undefined) {
|
|
59
78
|
throw new Error('[string2file] Input is required');
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
//
|
|
63
|
-
if (input._descriptor && input.type === 'file') {
|
|
81
|
+
// Already a file descriptor
|
|
82
|
+
if (input && input._descriptor === true && input.type === 'file') {
|
|
64
83
|
return input;
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
const resolver = getContentResolver(context);
|
|
87
|
+
|
|
88
|
+
// Inline descriptor -> file
|
|
89
|
+
if (input && input._descriptor === true && input.type === 'inline') {
|
|
69
90
|
const content = input.content;
|
|
70
91
|
if (typeof content !== 'string') {
|
|
71
92
|
throw new Error('[string2file] Inline descriptor content must be a string');
|
|
72
93
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const workflowId = context.workflow_id || context.workflowId || 'temp';
|
|
80
|
-
const stepId = context.step_id || context.stepId || 'helpers';
|
|
81
|
-
const pathPrefix = `helpers/${workflowId}/${stepId}`;
|
|
82
|
-
|
|
83
|
-
const result = await storage.uploadWithFingerprint(
|
|
84
|
-
'workflow',
|
|
85
|
-
buffer,
|
|
86
|
-
pathPrefix
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
_descriptor: true,
|
|
91
|
-
type: 'file',
|
|
92
|
-
storage_ref: `minio://workflow/${result.path}`,
|
|
93
|
-
filename: input.filename || 'content.txt',
|
|
94
|
-
content_type: input.content_type || 'text/plain',
|
|
95
|
-
size: buffer.length,
|
|
96
|
-
fingerprint: result.fingerprint
|
|
97
|
-
};
|
|
94
|
+
return await resolver.createDescriptor(content, {
|
|
95
|
+
filename: input.filename,
|
|
96
|
+
content_type: input.content_type,
|
|
97
|
+
context: { workflow_id: context.workflow_id, step_id: context.step_id },
|
|
98
|
+
forceFile: true
|
|
99
|
+
});
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
//
|
|
102
|
+
// Raw string -> file descriptor
|
|
101
103
|
if (typeof input === 'string') {
|
|
102
|
-
|
|
103
|
-
const buffer = Buffer.from(input, 'utf-8');
|
|
104
|
-
|
|
105
|
-
const workflowId = context.workflow_id || context.workflowId || 'temp';
|
|
106
|
-
const stepId = context.step_id || context.stepId || 'helpers';
|
|
107
|
-
const pathPrefix = `helpers/${workflowId}/${stepId}`;
|
|
108
|
-
|
|
109
|
-
const result = await storage.uploadWithFingerprint(
|
|
110
|
-
'workflow',
|
|
111
|
-
buffer,
|
|
112
|
-
pathPrefix
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
_descriptor: true,
|
|
117
|
-
type: 'file',
|
|
118
|
-
storage_ref: `minio://workflow/${result.path}`,
|
|
104
|
+
return await resolver.createDescriptor(input, {
|
|
119
105
|
filename: 'content.txt',
|
|
120
106
|
content_type: 'text/plain',
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
};
|
|
107
|
+
context: { workflow_id: context.workflow_id, step_id: context.step_id },
|
|
108
|
+
forceFile: true
|
|
109
|
+
});
|
|
124
110
|
}
|
|
125
111
|
|
|
126
112
|
throw new Error('[string2file] Invalid input - expected string or descriptor');
|
|
127
113
|
}
|
|
128
114
|
|
|
129
115
|
/**
|
|
130
|
-
* Convert file descriptor to string
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* @
|
|
134
|
-
* @param {Object} context - Workflow context (for storage access)
|
|
135
|
-
* @returns {Promise<string>} String content
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* // File descriptor input
|
|
139
|
-
* const content = await file2string({
|
|
140
|
-
* _descriptor: true,
|
|
141
|
-
* type: 'file',
|
|
142
|
-
* storage_ref: 'minio://workflow/path/to/file',
|
|
143
|
-
* fingerprint: 'abc123...'
|
|
144
|
-
* }, context);
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* // Inline descriptor input
|
|
148
|
-
* const content = await file2string({
|
|
149
|
-
* _descriptor: true,
|
|
150
|
-
* type: 'inline',
|
|
151
|
-
* content: 'Hello world'
|
|
152
|
-
* }, context);
|
|
116
|
+
* Convert file descriptor to string (downloads via ContentResolver).
|
|
117
|
+
* @param {Object|string} descriptor
|
|
118
|
+
* @param {Object} context
|
|
119
|
+
* @returns {Promise<string>}
|
|
153
120
|
*/
|
|
154
121
|
async function file2string(descriptor, context) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (typeof descriptor === 'string') {
|
|
158
|
-
return descriptor;
|
|
159
|
-
}
|
|
160
|
-
return String(descriptor || '');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Pokud je inline, vrať content
|
|
164
|
-
if (descriptor.type === 'inline') {
|
|
165
|
-
return descriptor.content || '';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Pokud je file, stáhni z MinIO
|
|
169
|
-
if (descriptor.type === 'file') {
|
|
170
|
-
if (!descriptor.storage_ref) {
|
|
171
|
-
throw new Error('[file2string] File descriptor missing storage_ref');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const storage = await getStorageFromContext(context);
|
|
175
|
-
|
|
176
|
-
// Parse storage_ref: minio://workflow/path/to/file
|
|
177
|
-
const match = descriptor.storage_ref.match(/^minio:\/\/([^\/]+)\/(.+)$/);
|
|
178
|
-
if (!match) {
|
|
179
|
-
throw new Error(`[file2string] Invalid storage_ref format: ${descriptor.storage_ref}`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const [, bucket, path] = match;
|
|
183
|
-
|
|
184
|
-
// Download with fingerprint verification
|
|
185
|
-
const content = await storage.downloadWithVerification(
|
|
186
|
-
bucket,
|
|
187
|
-
path,
|
|
188
|
-
descriptor.fingerprint
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
return content.toString('utf-8');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
throw new Error(`[file2string] Invalid descriptor type: ${descriptor.type}`);
|
|
122
|
+
const resolver = getContentResolver(context);
|
|
123
|
+
return await resolver.getAsString(descriptor);
|
|
195
124
|
}
|
|
196
125
|
|
|
197
126
|
module.exports = {
|
|
127
|
+
normalizeString,
|
|
128
|
+
webalizeString,
|
|
198
129
|
string2file,
|
|
199
130
|
file2string
|
|
200
131
|
};
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
const fileHelpers = require('./helpers/fileHelpers');
|
|
16
16
|
|
|
17
17
|
module.exports = {
|
|
18
|
+
normalizeString: fileHelpers.normalizeString,
|
|
19
|
+
webalizeString: fileHelpers.webalizeString,
|
|
18
20
|
string2file: fileHelpers.string2file,
|
|
19
21
|
file2string: fileHelpers.file2string
|
|
20
22
|
};
|