@onlineapps/cookbook-template-helpers 2.0.0 → 2.0.2

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
@@ -1,3 +1,14 @@
1
+ > Status: current
2
+ > Owns: the helper functions a cookbook template variable may call
3
+
4
+ <!-- BEGIN GENERATED: library-uniform — regenerate: npx oa-sync-template readme-uniform --all -->
5
+ Uniform: [library/orchestration](../../connector/conn-orch-validator/manifests/library.manifest.json)
6
+
7
+ Duty sections that apply:
8
+
9
+ - `all`: L-MAIN, L-ENGINES, L-TESTS, L-TEST-SCRIPT, L-PACK-TESTS, L-PINS, L-NO-FILE-RANGE, L-CHANGELOG, L-README, L-README-REGION, L-CONSUMER
10
+ <!-- END GENERATED: library-uniform -->
11
+
1
12
  # @onlineapps/cookbook-template-helpers
2
13
 
3
14
  Template helper functions for cookbook variable resolution. Helper functions enable data transformations directly in cookbook template variables.
@@ -100,7 +111,7 @@ if (typeof helperFn !== 'function') {
100
111
  balíček exportuje. Neznámý helper je tvrdá chyba, ne prázdná hodnota.
101
112
 
102
113
  Živý cookbook, který touhle cestou prochází:
103
- [`infra/api_doorman/cookbooks/tester-helpers.json`](../../../infra/api_doorman/cookbooks/tester-helpers.json)
114
+ [`infra/api_doorman/static/cookbooks/tester-helpers.json`](../../../infra/api_doorman/static/cookbooks/tester-helpers.json)
104
115
  — volá `normalizeString`, `webalizeString`, `string2file` i `file2string`.
105
116
 
106
117
  ## Adding a Helper
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@onlineapps/cookbook-template-helpers",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Template helper functions for cookbook variable resolution - string2file, file2string, and more",
5
+ "oa": {
6
+ "category": "orchestration"
7
+ },
5
8
  "main": "src/index.js",
6
9
  "scripts": {
7
- "test": "jest",
10
+ "test": "npm run test:unit",
11
+ "test:unit": "jest tests/unit",
8
12
  "test:watch": "jest --watch",
9
13
  "test:coverage": "jest --coverage",
10
- "docs": "jsdoc2md --files src/**/*.js > API.md"
14
+ "docs": "jsdoc2md --files 'src/**/*.js' > API.md.tmp && mv API.md.tmp API.md || (rm -f API.md.tmp; exit 1)"
11
15
  },
12
16
  "keywords": [
13
17
  "cookbook",
@@ -19,13 +23,15 @@
19
23
  "author": "OnlineApps",
20
24
  "license": "PROPRIETARY",
21
25
  "dependencies": {
22
- "@onlineapps/content-resolver": "3.0.0"
26
+ "@onlineapps/content-resolver": "4.0.1",
27
+ "@onlineapps/logger-contract": "2.0.0"
23
28
  },
24
29
  "devDependencies": {
25
- "jest": "29.7.0"
30
+ "jest": "29.7.0",
31
+ "jsdoc-to-markdown": "^8.0.0"
26
32
  },
27
33
  "engines": {
28
- "node": ">=18.0.0"
34
+ "node": ">=24.0.0 <25"
29
35
  },
30
36
  "files": [
31
37
  "src"
@@ -6,10 +6,7 @@
6
6
  */
7
7
 
8
8
  const ContentResolver = require('@onlineapps/content-resolver');
9
-
10
- // The logger methods `ContentResolver` requires of the object this module hands
11
- // it — the order is the order the error message lists them in.
12
- const LOGGER_METHODS = ['info', 'warn', 'error', 'debug'];
9
+ const { assertLogger } = require('@onlineapps/logger-contract');
13
10
 
14
11
  /**
15
12
  * Normalize string: remove control/unsafe chars, keep letters (incl. diacritics), numbers,
@@ -76,22 +73,11 @@ function getContentResolver(helperContext) {
76
73
  // passed this gate and failed one frame down — delayed validation, forbidden
77
74
  // by architecture-principles.md §4. Owner confirmation:
78
75
  // api/docs/governance/confirmations/connector-logger-contract.md 001.
79
- if (!helperContext.logger) {
80
- throw new Error(
81
- '[cookbook-template-helpers] logger is required - Expected: a logger with info/warn/error/debug, '
82
- + 'so the resolver this helper builds writes where the service writes. '
83
- + 'Fix: pass context.logger (e.g. the logger your service already built).'
84
- );
85
- }
86
-
87
- const missing = LOGGER_METHODS.filter((method) => typeof helperContext.logger[method] !== 'function');
88
- if (missing.length > 0) {
89
- throw new Error(
90
- '[cookbook-template-helpers] logger is incomplete - Expected: info, warn, error, debug as functions; '
91
- + `missing: ${missing.join(', ')}. `
92
- + 'Fix: pass a logger implementing all four.'
93
- );
94
- }
76
+ assertLogger(
77
+ 'cookbook-template-helpers',
78
+ helperContext.logger,
79
+ 'the resolver this helper builds writes where the service writes'
80
+ );
95
81
 
96
82
  return new ContentResolver({
97
83
  context: { workflow_id, step_id },
@@ -99,8 +85,45 @@ function getContentResolver(helperContext) {
99
85
  });
100
86
  }
101
87
 
88
+ /**
89
+ * The declared type of an inline descriptor's bytes, refused here if it is not
90
+ * one.
91
+ *
92
+ * `string2file()` turns those bytes into an object in storage, and the
93
+ * descriptor it returns declares that object's type. A type nobody stated
94
+ * therefore cannot travel on: the frame below fills the hole from the filename
95
+ * extension, so the object ends up carrying a guess presented as a fact
96
+ * (measured 2026-09-08 on the live path — `{ filename: 'payload.json' }` with no
97
+ * `content_type` came back `application/json`, and `content_type: null` came
98
+ * back `text/plain`).
99
+ *
100
+ * Required input, then: not derived and not defaulted
101
+ * (`architecture-principles.md` §3), and stated by whoever knows what the bytes
102
+ * are — the caller.
103
+ *
104
+ * @private
105
+ * @param {*} contentType - The `content_type` the inline descriptor carries.
106
+ * @throws {Error} If it is not a non-empty string.
107
+ */
108
+ function assertDeclaredContentType(contentType) {
109
+ if (typeof contentType === 'string' && contentType.trim() !== '') {
110
+ return;
111
+ }
112
+
113
+ throw new Error(
114
+ '[string2file] Missing required input - "content_type" is required on an inline descriptor: '
115
+ + 'it is the type written to storage and it is never guessed. '
116
+ + 'Fix: pass content_type (e.g. text/plain, application/json).'
117
+ );
118
+ }
119
+
102
120
  /**
103
121
  * Convert string or inline descriptor to file descriptor (uploads via ContentResolver).
122
+ *
123
+ * An inline descriptor MUST declare `content_type` (see
124
+ * `assertDeclaredContentType`). A raw string declares nothing, so the helper
125
+ * names the file itself and states the type of the file it named.
126
+ *
104
127
  * @param {string|Object} input
105
128
  * @param {Object} context
106
129
  * @returns {Promise<Object>}
@@ -123,6 +146,7 @@ async function string2file(input, context) {
123
146
  if (typeof content !== 'string') {
124
147
  throw new Error('[string2file] Inline descriptor content must be a string');
125
148
  }
149
+ assertDeclaredContentType(input.content_type);
126
150
  return await resolver.createDescriptor(content, {
127
151
  filename: input.filename,
128
152
  content_type: input.content_type,