@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 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
- ### string2file
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
- async function string2file(input, context)
25
+ function normalizeString(value)
32
26
  ```
33
27
 
34
- **Parameters:**
35
- - `input` (string|Object) - String content or inline descriptor
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
- **Returns:**
39
- - `Promise<Object>` - File descriptor
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
- ```json
52
- {
53
- "input": {
54
- "file": "{{string2file('Hello world')}}"
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
- **Parameters:**
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
- "markdown": "{{file2string(steps.pdf.output.file_descriptor)}}"
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
- - **Async**: All helpers must be async functions (return Promise)
150
- - **Context**: Helpers receive workflow context as last parameter
151
- - **Idempotent**: Same input should produce same output
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/conn-base-storage` - For MinIO storage operations
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.1",
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/conn-base-storage": "^1.0.0"
22
+ "@onlineapps/content-resolver": "1.1.6"
23
23
  },
24
24
  "devDependencies": {
25
- "jest": "^29.7.0"
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
- * File helper functions for cookbook template resolution
5
- * Converts between string content and file descriptors
4
+ * Helper functions for cookbook template resolution.
5
+ * Includes safe string normalization and descriptor/string conversions.
6
6
  */
7
7
 
8
- const StorageConnector = require('@onlineapps/conn-base-storage');
8
+ const ContentResolver = require('@onlineapps/content-resolver');
9
9
 
10
10
  /**
11
- * Get storage instance from context or create new one
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
- async function getStorageFromContext(context) {
17
- // If storage is already in context, use it
18
- if (context._storage) {
19
- return context._storage;
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
- // Otherwise create new storage instance
23
- const storage = new StorageConnector({
24
- endpoint: process.env.MINIO_HOST || 'api_shared_storage',
25
- port: parseInt(process.env.MINIO_PORT || '9000', 10),
26
- accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
27
- secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin',
28
- useSSL: process.env.MINIO_USE_SSL === 'true'
29
- });
30
-
31
- // Cache in context for reuse
32
- context._storage = storage;
33
- return storage;
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
- * Uploads content to MinIO and returns file descriptor
39
- *
40
- * @param {string|Object} input - String content or inline descriptor
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 (!input) {
77
+ if (input === null || input === undefined) {
59
78
  throw new Error('[string2file] Input is required');
60
79
  }
61
80
 
62
- // Pokud je file descriptor, vrať ho
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
- // Pokud je inline descriptor, převeď na file
68
- if (input._descriptor && input.type === 'inline') {
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
- const storage = await getStorageFromContext(context);
75
- const encoding = input.encoding || 'utf-8';
76
- const buffer = Buffer.from(content, encoding);
77
-
78
- // Upload do MinIO
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
- // Pokud je string, vytvoř file descriptor
102
+ // Raw string -> file descriptor
101
103
  if (typeof input === 'string') {
102
- const storage = await getStorageFromContext(context);
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
- size: buffer.length,
122
- fingerprint: result.fingerprint
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
- * Downloads content from MinIO if needed
132
- *
133
- * @param {Object|string} descriptor - File descriptor or string
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
- // Pokud není descriptor, vrať jak je (pokud je string)
156
- if (!descriptor || !descriptor._descriptor) {
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
  };