@onlineapps/conn-orch-api-mapper 1.0.8 → 1.0.9
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/package.json +1 -1
- package/src/ApiMapper.js +167 -162
package/package.json
CHANGED
package/src/ApiMapper.js
CHANGED
|
@@ -125,220 +125,225 @@ class ApiMapper {
|
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* Transform HTTP response to cookbook format
|
|
128
|
-
* Normalizes output fields to Content Descriptor based on operations.json output schema
|
|
129
128
|
*
|
|
130
|
-
*
|
|
131
|
-
* 1.
|
|
132
|
-
* 2.
|
|
129
|
+
* CRITICAL RULES:
|
|
130
|
+
* 1. Handler returns RAW data (temp_path, filename, content_type) - NEVER Descriptors!
|
|
131
|
+
* 2. ApiMapper is the ONLY place that creates Descriptors
|
|
132
|
+
* 3. FAIL-FAST on unexpected formats - no fallbacks!
|
|
133
133
|
*
|
|
134
134
|
* @method transformResponse
|
|
135
|
-
* @param {Object} httpResponse - HTTP response
|
|
136
|
-
* @param {Object} [operation] - Operation schema from operations.json
|
|
137
|
-
* @param {Object} [context] - Workflow context
|
|
138
|
-
* @returns {Promise<Object>} Transformed response for
|
|
135
|
+
* @param {Object} httpResponse - HTTP response from handler
|
|
136
|
+
* @param {Object} [operation] - Operation schema from operations.json
|
|
137
|
+
* @param {Object} [context] - Workflow context { workflow_id, step_id }
|
|
138
|
+
* @returns {Promise<Object>} Transformed response (Descriptor for file types)
|
|
139
139
|
*/
|
|
140
140
|
async transformResponse(httpResponse, operation = null, context = {}) {
|
|
141
141
|
// Extract relevant data from HTTP response
|
|
142
142
|
let output = httpResponse.data || httpResponse;
|
|
143
143
|
|
|
144
|
+
const workflowId = context.workflow_id || 'standalone';
|
|
145
|
+
const stepId = context.step_id || 'unknown';
|
|
146
|
+
const logPrefix = `[${workflowId}:${stepId}:ApiMapper`;
|
|
147
|
+
|
|
144
148
|
// === LOGGING: Input ===
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
150
|
+
action: 'transform_start',
|
|
147
151
|
hasOperation: !!operation,
|
|
148
|
-
operationOutput: operation?.output,
|
|
149
152
|
outputType: typeof output,
|
|
150
|
-
outputKeys: output && typeof output === 'object' ? Object.keys(output) : null
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
outputKeys: output && typeof output === 'object' ? Object.keys(output) : null
|
|
154
|
+
})}`);
|
|
155
|
+
|
|
156
|
+
// === FAIL-FAST: Handler should NEVER return Descriptors ===
|
|
157
|
+
if (output && typeof output === 'object') {
|
|
158
|
+
if (output._descriptor === true) {
|
|
159
|
+
const errorMsg = 'Handler returned _descriptor:true - handlers must return RAW data, not Descriptors!';
|
|
160
|
+
console.error(`${logPrefix}:FAIL] ${JSON.stringify({
|
|
161
|
+
error: 'HANDLER_RETURNED_DESCRIPTOR',
|
|
162
|
+
message: errorMsg,
|
|
163
|
+
handler_output_keys: Object.keys(output),
|
|
164
|
+
expected: 'object with temp_path/filename or raw data'
|
|
165
|
+
})}`);
|
|
166
|
+
throw new Error(`[ApiMapper] FAIL-FAST: ${errorMsg}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Also fail if handler returned storage_ref directly (handler shouldn't know about MinIO)
|
|
170
|
+
if (output.storage_ref && output.storage_ref.startsWith('minio://')) {
|
|
171
|
+
const errorMsg = 'Handler returned storage_ref - handlers must not interact with MinIO directly!';
|
|
172
|
+
console.error(`${logPrefix}:FAIL] ${JSON.stringify({
|
|
173
|
+
error: 'HANDLER_RETURNED_STORAGE_REF',
|
|
174
|
+
message: errorMsg,
|
|
175
|
+
storage_ref: output.storage_ref,
|
|
176
|
+
expected: 'object with temp_path/filename'
|
|
177
|
+
})}`);
|
|
178
|
+
throw new Error(`[ApiMapper] FAIL-FAST: ${errorMsg}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
154
181
|
|
|
155
|
-
// If no operation schema, return as-is
|
|
182
|
+
// If no operation schema, return as-is (for non-file outputs)
|
|
156
183
|
if (!operation || !operation.output) {
|
|
157
|
-
console.log(`${logPrefix}
|
|
184
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
185
|
+
action: 'no_schema',
|
|
186
|
+
result: 'returning_as_is'
|
|
187
|
+
})}`);
|
|
158
188
|
return output;
|
|
159
189
|
}
|
|
160
190
|
|
|
161
191
|
const outputSchema = operation.output;
|
|
162
192
|
|
|
163
|
-
// Initialize
|
|
164
|
-
let
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
167
|
-
|
|
168
|
-
context: {
|
|
169
|
-
workflow_id: context.workflow_id || 'standalone',
|
|
170
|
-
step_id: context.step_id || 'api-mapper'
|
|
171
|
-
}
|
|
193
|
+
// Initialize ContentResolver (lazy)
|
|
194
|
+
let contentResolver = null;
|
|
195
|
+
const getContentResolver = () => {
|
|
196
|
+
if (!contentResolver) {
|
|
197
|
+
contentResolver = new ContentAccessor({
|
|
198
|
+
context: { workflow_id: workflowId, step_id: stepId }
|
|
172
199
|
});
|
|
173
200
|
}
|
|
174
|
-
return
|
|
201
|
+
return contentResolver;
|
|
175
202
|
};
|
|
176
203
|
|
|
177
204
|
// === DETECT OUTPUT SCHEMA FORMAT ===
|
|
178
|
-
// Format A: Direct type - { type: "file",
|
|
205
|
+
// Format A: Direct type - { type: "file", fields: {...} }
|
|
179
206
|
// Format B: Named fields - { "fieldName": { type: "string" }, ... }
|
|
180
207
|
const isDirectType = outputSchema.type &&
|
|
181
|
-
|
|
182
|
-
outputSchema.type === 'content' ||
|
|
183
|
-
outputSchema.type === 'string' ||
|
|
184
|
-
outputSchema.type === 'object' ||
|
|
185
|
-
outputSchema.type === 'array');
|
|
208
|
+
['file', 'content', 'string', 'object', 'array'].includes(outputSchema.type);
|
|
186
209
|
|
|
187
|
-
console.log(`${logPrefix}
|
|
210
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
211
|
+
action: 'schema_detected',
|
|
212
|
+
format: isDirectType ? 'DIRECT_TYPE' : 'NAMED_FIELDS',
|
|
213
|
+
schemaType: outputSchema.type
|
|
214
|
+
})}`);
|
|
188
215
|
|
|
189
216
|
// === FORMAT A: Direct type (entire output is one value) ===
|
|
190
217
|
if (isDirectType) {
|
|
191
218
|
const schemaType = outputSchema.type;
|
|
192
219
|
|
|
193
|
-
//
|
|
220
|
+
// For file/content types, convert to Descriptor
|
|
194
221
|
if (schemaType === 'file' || schemaType === 'content') {
|
|
195
|
-
|
|
196
|
-
if (output && typeof output === 'object' && output._descriptor === true) {
|
|
197
|
-
console.log(`${logPrefix} OUTPUT: Already Descriptor with _descriptor=true`);
|
|
198
|
-
return output;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check if output looks like Descriptor but missing _descriptor flag
|
|
202
|
-
if (output && typeof output === 'object' && (output.type === 'file' || output.type === 'inline')) {
|
|
203
|
-
console.log(`${logPrefix} Normalizing Descriptor (missing _descriptor flag)`);
|
|
204
|
-
try {
|
|
205
|
-
const accessor = getContentAccessor();
|
|
206
|
-
const normalized = await accessor.normalizeToDescriptor(output, {
|
|
207
|
-
filename: output.filename,
|
|
208
|
-
content_type: output.content_type
|
|
209
|
-
});
|
|
210
|
-
console.log(`${logPrefix} OUTPUT: Normalized Descriptor:`, JSON.stringify({
|
|
211
|
-
_descriptor: normalized._descriptor,
|
|
212
|
-
type: normalized.type,
|
|
213
|
-
storage_ref: normalized.storage_ref,
|
|
214
|
-
filename: normalized.filename
|
|
215
|
-
}));
|
|
216
|
-
return normalized;
|
|
217
|
-
} catch (error) {
|
|
218
|
-
console.error(`${logPrefix} FAIL: Failed to normalize Descriptor: ${error.message}`);
|
|
219
|
-
// FAIL-FAST: Don't silently continue with broken data
|
|
220
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but failed to normalize: ${error.message}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Output is string (storage reference) - convert to Descriptor
|
|
225
|
-
if (typeof output === 'string') {
|
|
226
|
-
console.log(`${logPrefix} Converting string to Descriptor: ${output.substring(0, 50)}...`);
|
|
227
|
-
try {
|
|
228
|
-
const accessor = getContentAccessor();
|
|
229
|
-
const normalized = await accessor.normalizeToDescriptor(output, {
|
|
230
|
-
filename: outputSchema.filename,
|
|
231
|
-
content_type: outputSchema.content_type
|
|
232
|
-
});
|
|
233
|
-
console.log(`${logPrefix} OUTPUT: String converted to Descriptor`);
|
|
234
|
-
return normalized;
|
|
235
|
-
} catch (error) {
|
|
236
|
-
console.error(`${logPrefix} FAIL: Failed to convert string to Descriptor: ${error.message}`);
|
|
237
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but string conversion failed: ${error.message}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Output is object with storage_ref - extract and convert
|
|
242
|
-
if (output && typeof output === 'object' && output.storage_ref) {
|
|
243
|
-
console.log(`${logPrefix} Converting object with storage_ref to Descriptor`);
|
|
244
|
-
try {
|
|
245
|
-
const accessor = getContentAccessor();
|
|
246
|
-
const normalized = await accessor.normalizeToDescriptor(output.storage_ref, {
|
|
247
|
-
filename: output.filename || output.output_name,
|
|
248
|
-
content_type: output.content_type
|
|
249
|
-
});
|
|
250
|
-
console.log(`${logPrefix} OUTPUT: Object converted to Descriptor`);
|
|
251
|
-
return normalized;
|
|
252
|
-
} catch (error) {
|
|
253
|
-
console.error(`${logPrefix} FAIL: Failed to convert object to Descriptor: ${error.message}`);
|
|
254
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but object conversion failed: ${error.message}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// FAIL-FAST: Schema expects file/content but got unexpected format
|
|
259
|
-
console.error(`${logPrefix} FAIL-FAST: Schema expects ${schemaType} but got unexpected output format:`, {
|
|
260
|
-
outputType: typeof output,
|
|
261
|
-
outputKeys: output && typeof output === 'object' ? Object.keys(output) : null
|
|
262
|
-
});
|
|
263
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Schema expects '${schemaType}' but received unexpected format. Output type: ${typeof output}`);
|
|
222
|
+
return await this._convertToDescriptor(output, outputSchema, getContentResolver, logPrefix, context);
|
|
264
223
|
}
|
|
265
224
|
|
|
266
|
-
// For other
|
|
267
|
-
console.log(`${logPrefix}
|
|
225
|
+
// For other types, return as-is
|
|
226
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
227
|
+
action: 'pass_through',
|
|
228
|
+
schemaType: schemaType
|
|
229
|
+
})}`);
|
|
268
230
|
return output;
|
|
269
231
|
}
|
|
270
232
|
|
|
271
233
|
// === FORMAT B: Named fields ===
|
|
272
234
|
const normalizedOutput = { ...output };
|
|
273
235
|
|
|
274
|
-
// Process each output field
|
|
275
236
|
for (const [fieldName, fieldSchema] of Object.entries(outputSchema)) {
|
|
276
|
-
|
|
277
|
-
if (!fieldSchema || typeof fieldSchema !== 'object') {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
237
|
+
if (!fieldSchema || typeof fieldSchema !== 'object') continue;
|
|
280
238
|
|
|
281
239
|
const fieldType = fieldSchema.type;
|
|
282
240
|
const fieldValue = output[fieldName];
|
|
283
241
|
|
|
284
|
-
|
|
242
|
+
if (fieldValue === undefined || fieldValue === null) continue;
|
|
285
243
|
|
|
286
|
-
//
|
|
287
|
-
if (fieldValue === undefined || fieldValue === null) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Normalize fields with type: "file" or type: "content" to Descriptor
|
|
244
|
+
// For file/content fields, convert to Descriptor
|
|
292
245
|
if (fieldType === 'file' || fieldType === 'content') {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// If field is already a Descriptor (has _descriptor flag), pass through
|
|
297
|
-
if (typeof fieldValue === 'object' && fieldValue !== null && fieldValue._descriptor === true) {
|
|
298
|
-
console.log(`${logPrefix} Field '${fieldName}' already Descriptor with _descriptor=true`);
|
|
299
|
-
normalizedOutput[fieldName] = fieldValue;
|
|
300
|
-
} else if (typeof fieldValue === 'object' && fieldValue !== null && (fieldValue.type === 'file' || fieldValue.type === 'inline')) {
|
|
301
|
-
// Looks like Descriptor but missing _descriptor flag - normalize
|
|
302
|
-
console.log(`${logPrefix} Field '${fieldName}' normalizing Descriptor (missing _descriptor flag)`);
|
|
303
|
-
normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue, {
|
|
304
|
-
filename: fieldValue.filename,
|
|
305
|
-
content_type: fieldValue.content_type
|
|
306
|
-
});
|
|
307
|
-
} else if (typeof fieldValue === 'string') {
|
|
308
|
-
// String value - normalize to Descriptor
|
|
309
|
-
console.log(`${logPrefix} Field '${fieldName}' converting string to Descriptor`);
|
|
310
|
-
normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue, {
|
|
311
|
-
filename: fieldSchema.filename,
|
|
312
|
-
content_type: fieldSchema.content_type
|
|
313
|
-
});
|
|
314
|
-
} else if (typeof fieldValue === 'object' && fieldValue !== null && fieldValue.storage_ref) {
|
|
315
|
-
// Object with storage_ref - extract and normalize
|
|
316
|
-
console.log(`${logPrefix} Field '${fieldName}' converting object with storage_ref to Descriptor`);
|
|
317
|
-
normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue.storage_ref, {
|
|
318
|
-
filename: fieldValue.filename || fieldValue.output_name,
|
|
319
|
-
content_type: fieldValue.content_type
|
|
320
|
-
});
|
|
321
|
-
} else {
|
|
322
|
-
// FAIL-FAST: Unexpected format for file/content field
|
|
323
|
-
console.error(`${logPrefix} FAIL-FAST: Field '${fieldName}' expects ${fieldType} but got unexpected format:`, {
|
|
324
|
-
valueType: typeof fieldValue,
|
|
325
|
-
valueKeys: fieldValue && typeof fieldValue === 'object' ? Object.keys(fieldValue) : null
|
|
326
|
-
});
|
|
327
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Field '${fieldName}' expects '${fieldType}' but received unexpected format`);
|
|
328
|
-
}
|
|
329
|
-
} catch (error) {
|
|
330
|
-
if (error.message.includes('FAIL-FAST')) {
|
|
331
|
-
throw error; // Re-throw fail-fast errors
|
|
332
|
-
}
|
|
333
|
-
console.error(`${logPrefix} FAIL: Failed to normalize field '${fieldName}': ${error.message}`);
|
|
334
|
-
throw new Error(`[ApiMapper] FAIL-FAST: Failed to normalize field '${fieldName}' to Descriptor: ${error.message}`);
|
|
335
|
-
}
|
|
246
|
+
normalizedOutput[fieldName] = await this._convertToDescriptor(
|
|
247
|
+
fieldValue, fieldSchema, getContentResolver, logPrefix, context, fieldName
|
|
248
|
+
);
|
|
336
249
|
}
|
|
337
250
|
}
|
|
338
251
|
|
|
339
|
-
console.log(`${logPrefix}
|
|
252
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
253
|
+
action: 'transform_complete',
|
|
254
|
+
outputKeys: Object.keys(normalizedOutput)
|
|
255
|
+
})}`);
|
|
256
|
+
|
|
340
257
|
return normalizedOutput;
|
|
341
258
|
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Convert handler output to Descriptor
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
async _convertToDescriptor(output, schema, getContentResolver, logPrefix, context, fieldName = null) {
|
|
265
|
+
const fieldLabel = fieldName ? `field '${fieldName}'` : 'output';
|
|
266
|
+
|
|
267
|
+
// Handler should return object with temp_path for file operations
|
|
268
|
+
if (output && typeof output === 'object' && output.temp_path) {
|
|
269
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
270
|
+
action: 'convert_temp_file',
|
|
271
|
+
field: fieldName,
|
|
272
|
+
temp_path: output.temp_path,
|
|
273
|
+
filename: output.filename
|
|
274
|
+
})}`);
|
|
275
|
+
|
|
276
|
+
const resolver = getContentResolver();
|
|
277
|
+
const descriptor = await resolver.createDescriptorFromFile(output.temp_path, {
|
|
278
|
+
filename: output.filename,
|
|
279
|
+
content_type: output.content_type,
|
|
280
|
+
context: {
|
|
281
|
+
workflow_id: context.workflow_id || 'standalone',
|
|
282
|
+
step_id: context.step_id || 'api-mapper'
|
|
283
|
+
},
|
|
284
|
+
deleteAfterUpload: true
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
288
|
+
action: 'descriptor_created',
|
|
289
|
+
field: fieldName,
|
|
290
|
+
storage_ref: descriptor.storage_ref,
|
|
291
|
+
size: descriptor.size
|
|
292
|
+
})}`);
|
|
293
|
+
|
|
294
|
+
return descriptor;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Handler returned Buffer - store it
|
|
298
|
+
if (Buffer.isBuffer(output)) {
|
|
299
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
300
|
+
action: 'convert_buffer',
|
|
301
|
+
field: fieldName,
|
|
302
|
+
size: output.length
|
|
303
|
+
})}`);
|
|
304
|
+
|
|
305
|
+
const resolver = getContentResolver();
|
|
306
|
+
return await resolver.createDescriptor(output, {
|
|
307
|
+
filename: schema.filename || 'output.bin',
|
|
308
|
+
content_type: schema.content_type,
|
|
309
|
+
context: {
|
|
310
|
+
workflow_id: context.workflow_id || 'standalone',
|
|
311
|
+
step_id: context.step_id || 'api-mapper'
|
|
312
|
+
},
|
|
313
|
+
forceFile: true
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handler returned string (for content type) - may be inline or stored
|
|
318
|
+
if (typeof output === 'string') {
|
|
319
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
320
|
+
action: 'convert_string',
|
|
321
|
+
field: fieldName,
|
|
322
|
+
length: output.length
|
|
323
|
+
})}`);
|
|
324
|
+
|
|
325
|
+
const resolver = getContentResolver();
|
|
326
|
+
return await resolver.createDescriptor(output, {
|
|
327
|
+
filename: schema.filename,
|
|
328
|
+
content_type: schema.content_type,
|
|
329
|
+
context: {
|
|
330
|
+
workflow_id: context.workflow_id || 'standalone',
|
|
331
|
+
step_id: context.step_id || 'api-mapper'
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// FAIL-FAST: Unexpected format
|
|
337
|
+
const errorMsg = `${fieldLabel} expects file/content but got unexpected format`;
|
|
338
|
+
console.error(`${logPrefix}:FAIL] ${JSON.stringify({
|
|
339
|
+
error: 'UNEXPECTED_OUTPUT_FORMAT',
|
|
340
|
+
field: fieldName,
|
|
341
|
+
expected: 'object with temp_path, Buffer, or string',
|
|
342
|
+
received: typeof output,
|
|
343
|
+
receivedKeys: output && typeof output === 'object' ? Object.keys(output) : null
|
|
344
|
+
})}`);
|
|
345
|
+
throw new Error(`[ApiMapper] FAIL-FAST: ${errorMsg}. Expected temp_path/Buffer/string, got ${typeof output}`);
|
|
346
|
+
}
|
|
342
347
|
|
|
343
348
|
/**
|
|
344
349
|
* Call operation with parameters
|