@onlineapps/conn-orch-api-mapper 1.0.10 → 1.0.12
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 +107 -6
package/package.json
CHANGED
package/src/ApiMapper.js
CHANGED
|
@@ -260,11 +260,50 @@ class ApiMapper {
|
|
|
260
260
|
/**
|
|
261
261
|
* Convert handler output to Descriptor
|
|
262
262
|
* @private
|
|
263
|
+
*
|
|
264
|
+
* Supported formats from handler/HTTP response:
|
|
265
|
+
* 1. { data: base64_string, encoding: 'base64', filename, content_type } - HTTP transport format
|
|
266
|
+
* 2. { temp_path, filename, content_type } - Local file (only for same-process calls)
|
|
267
|
+
* 3. Buffer - Raw binary data
|
|
268
|
+
* 4. string - Text content
|
|
263
269
|
*/
|
|
264
270
|
async _convertToDescriptor(output, schema, getContentResolver, logPrefix, context, fieldName = null) {
|
|
265
271
|
const fieldLabel = fieldName ? `field '${fieldName}'` : 'output';
|
|
266
272
|
|
|
267
|
-
//
|
|
273
|
+
// Format 1: Base64-encoded data from HTTP response (primary format for distributed services)
|
|
274
|
+
if (output && typeof output === 'object' && output.data && output.encoding === 'base64') {
|
|
275
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
276
|
+
action: 'convert_base64',
|
|
277
|
+
field: fieldName,
|
|
278
|
+
filename: output.filename,
|
|
279
|
+
size: output.size
|
|
280
|
+
})}`);
|
|
281
|
+
|
|
282
|
+
// Decode base64 to Buffer
|
|
283
|
+
const buffer = Buffer.from(output.data, 'base64');
|
|
284
|
+
|
|
285
|
+
const resolver = getContentResolver();
|
|
286
|
+
const descriptor = await resolver.createDescriptor(buffer, {
|
|
287
|
+
filename: output.filename || schema.filename || 'output.bin',
|
|
288
|
+
content_type: output.content_type || schema.content_type,
|
|
289
|
+
context: {
|
|
290
|
+
workflow_id: context.workflow_id || 'standalone',
|
|
291
|
+
step_id: context.step_id || 'api-mapper'
|
|
292
|
+
},
|
|
293
|
+
forceFile: true // Binary data → always store as file
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
297
|
+
action: 'descriptor_created',
|
|
298
|
+
field: fieldName,
|
|
299
|
+
storage_ref: descriptor.storage_ref,
|
|
300
|
+
size: descriptor.size
|
|
301
|
+
})}`);
|
|
302
|
+
|
|
303
|
+
return descriptor;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Format 2: Temp file path (for same-process calls, e.g. tests)
|
|
268
307
|
if (output && typeof output === 'object' && output.temp_path) {
|
|
269
308
|
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
270
309
|
action: 'convert_temp_file',
|
|
@@ -294,7 +333,7 @@ class ApiMapper {
|
|
|
294
333
|
return descriptor;
|
|
295
334
|
}
|
|
296
335
|
|
|
297
|
-
//
|
|
336
|
+
// Format 3: Raw Buffer
|
|
298
337
|
if (Buffer.isBuffer(output)) {
|
|
299
338
|
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
300
339
|
action: 'convert_buffer',
|
|
@@ -314,7 +353,7 @@ class ApiMapper {
|
|
|
314
353
|
});
|
|
315
354
|
}
|
|
316
355
|
|
|
317
|
-
//
|
|
356
|
+
// Format 4: Plain string (text content)
|
|
318
357
|
if (typeof output === 'string') {
|
|
319
358
|
console.log(`${logPrefix}:PROCESS] ${JSON.stringify({
|
|
320
359
|
action: 'convert_string',
|
|
@@ -338,11 +377,11 @@ class ApiMapper {
|
|
|
338
377
|
console.error(`${logPrefix}:FAIL] ${JSON.stringify({
|
|
339
378
|
error: 'UNEXPECTED_OUTPUT_FORMAT',
|
|
340
379
|
field: fieldName,
|
|
341
|
-
expected: '
|
|
380
|
+
expected: '{ data, encoding: "base64" } or { temp_path } or Buffer or string',
|
|
342
381
|
received: typeof output,
|
|
343
382
|
receivedKeys: output && typeof output === 'object' ? Object.keys(output) : null
|
|
344
383
|
})}`);
|
|
345
|
-
throw new Error(`[ApiMapper] FAIL-FAST: ${errorMsg}. Expected temp_path/Buffer/string, got ${typeof output}`);
|
|
384
|
+
throw new Error(`[ApiMapper] FAIL-FAST: ${errorMsg}. Expected base64/temp_path/Buffer/string, got ${typeof output}`);
|
|
346
385
|
}
|
|
347
386
|
|
|
348
387
|
/**
|
|
@@ -368,8 +407,12 @@ class ApiMapper {
|
|
|
368
407
|
// Resolve variables in input using context
|
|
369
408
|
const resolvedInput = this._resolveVariables(input, context);
|
|
370
409
|
|
|
410
|
+
// CRITICAL: Resolve Content Descriptors to raw data BEFORE calling handler
|
|
411
|
+
// Handler receives RAW DATA, not Descriptors! ApiMapper is the boundary.
|
|
412
|
+
const handlerInput = await this._resolveContentDescriptors(resolvedInput, operation);
|
|
413
|
+
|
|
371
414
|
// Build request
|
|
372
|
-
const request = this._buildRequest(operation,
|
|
415
|
+
const request = this._buildRequest(operation, handlerInput);
|
|
373
416
|
|
|
374
417
|
// Make the call
|
|
375
418
|
let response;
|
|
@@ -392,6 +435,64 @@ class ApiMapper {
|
|
|
392
435
|
}
|
|
393
436
|
}
|
|
394
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Resolve Content Descriptors in input to raw data
|
|
440
|
+
* CRITICAL: Handler receives RAW DATA, not Descriptors!
|
|
441
|
+
* This is the boundary between orchestration (Descriptors) and business logic (raw data).
|
|
442
|
+
*
|
|
443
|
+
* @private
|
|
444
|
+
* @param {Object} input - Input with potential Content Descriptors
|
|
445
|
+
* @param {Object} operation - Operation definition (for type hints)
|
|
446
|
+
* @returns {Promise<Object>} Input with Descriptors resolved to raw data
|
|
447
|
+
*/
|
|
448
|
+
async _resolveContentDescriptors(input, operation) {
|
|
449
|
+
if (!input || typeof input !== 'object') {
|
|
450
|
+
return input;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const resolver = new ContentAccessor();
|
|
454
|
+
const result = { ...input };
|
|
455
|
+
|
|
456
|
+
// Get input schema from operation (if available)
|
|
457
|
+
const inputSchema = operation?.input || {};
|
|
458
|
+
|
|
459
|
+
for (const [key, value] of Object.entries(result)) {
|
|
460
|
+
// Check if this is a Content Descriptor
|
|
461
|
+
const isDescriptor = value && typeof value === 'object' &&
|
|
462
|
+
(value._descriptor === true || value.ref || value.storage_ref);
|
|
463
|
+
|
|
464
|
+
if (isDescriptor) {
|
|
465
|
+
const fieldSchema = inputSchema[key] || {};
|
|
466
|
+
const fieldType = fieldSchema.type;
|
|
467
|
+
|
|
468
|
+
// Get storage reference
|
|
469
|
+
const storageRef = value.ref || value.storage_ref;
|
|
470
|
+
|
|
471
|
+
if (storageRef) {
|
|
472
|
+
// Resolve based on field type from operations.json
|
|
473
|
+
if (fieldType === 'file') {
|
|
474
|
+
// For file type, handler expects the Descriptor (pass through)
|
|
475
|
+
// Handler will use ContentResolver itself
|
|
476
|
+
console.log(`[ApiMapper:INPUT_RESOLVE] Field '${key}' type=file, passing Descriptor through`);
|
|
477
|
+
// Keep as-is
|
|
478
|
+
} else {
|
|
479
|
+
// For content/string types, resolve to raw string
|
|
480
|
+
console.log(`[ApiMapper:INPUT_RESOLVE] Field '${key}' type=${fieldType || 'content'}, resolving Descriptor to string`);
|
|
481
|
+
try {
|
|
482
|
+
result[key] = await resolver.getAsString(storageRef);
|
|
483
|
+
console.log(`[ApiMapper:INPUT_RESOLVE] ✓ Resolved '${key}' from ${storageRef} (${result[key].length} chars)`);
|
|
484
|
+
} catch (err) {
|
|
485
|
+
console.error(`[ApiMapper:INPUT_RESOLVE] ✗ Failed to resolve '${key}': ${err.message}`);
|
|
486
|
+
throw new Error(`Failed to resolve Content Descriptor for '${key}': ${err.message}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
|
|
395
496
|
/**
|
|
396
497
|
* Parse OpenAPI specification or operations.json to extract operations
|
|
397
498
|
* @private
|