@onlineapps/content-resolver 1.1.2 → 1.1.4
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 +31 -0
- package/package.json +1 -1
- package/src/index.js +115 -2
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ const content = await resolver.resolve('minio://workflow/path/to/file.txt');
|
|
|
41
41
|
// Store large content - returns Content Descriptor
|
|
42
42
|
const descriptor = await resolver.store(largeText, { workflow_id: 'wf-123' }, 'document.html');
|
|
43
43
|
// → {
|
|
44
|
+
// _descriptor: true,
|
|
44
45
|
// type: 'file',
|
|
45
46
|
// storage_ref: 'minio://workflow/...',
|
|
46
47
|
// filename: 'document.html',
|
|
@@ -109,6 +110,30 @@ Create Content Descriptor from raw content. Automatically decides inline vs file
|
|
|
109
110
|
#### `normalizeToDescriptor(value, options): Promise<Object>`
|
|
110
111
|
Normalize any value (string, reference, Buffer) to Content Descriptor.
|
|
111
112
|
|
|
113
|
+
#### `createDescriptorFromFile(tempPath, options): Promise<Object>`
|
|
114
|
+
Create Content Descriptor from a temp file. **Used by ApiMapper for file outputs.**
|
|
115
|
+
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
|--------|------|---------|-------------|
|
|
118
|
+
| `filename` | string | basename | Output filename |
|
|
119
|
+
| `content_type` | string | auto-detect | MIME type |
|
|
120
|
+
| `context` | Object | {} | `{ workflow_id, step_id }` |
|
|
121
|
+
| `deleteAfterUpload` | boolean | true | Delete temp file after upload |
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Handler returns temp file path
|
|
125
|
+
const handlerOutput = { temp_path: '/tmp/output.pdf', filename: 'report.pdf', content_type: 'application/pdf' };
|
|
126
|
+
|
|
127
|
+
// ApiMapper calls createDescriptorFromFile
|
|
128
|
+
const descriptor = await resolver.createDescriptorFromFile(handlerOutput.temp_path, {
|
|
129
|
+
filename: handlerOutput.filename,
|
|
130
|
+
content_type: handlerOutput.content_type,
|
|
131
|
+
context: { workflow_id: 'wf-123', step_id: 'pdf_gen' },
|
|
132
|
+
deleteAfterUpload: true // Cleanup temp file
|
|
133
|
+
});
|
|
134
|
+
// → Content Descriptor with _descriptor: true, type: 'file', storage_ref, etc.
|
|
135
|
+
```
|
|
136
|
+
|
|
112
137
|
#### `resolveInput(input, fields?): Promise<Object>`
|
|
113
138
|
Resolves all reference fields in input object (legacy method).
|
|
114
139
|
|
|
@@ -122,6 +147,7 @@ Stores large content fields as Descriptors (returns Descriptors, not plain strin
|
|
|
122
147
|
```javascript
|
|
123
148
|
// Inline content (small)
|
|
124
149
|
{
|
|
150
|
+
_descriptor: true,
|
|
125
151
|
type: 'inline',
|
|
126
152
|
content: 'actual text content',
|
|
127
153
|
encoding: 'utf-8',
|
|
@@ -133,6 +159,7 @@ Stores large content fields as Descriptors (returns Descriptors, not plain strin
|
|
|
133
159
|
|
|
134
160
|
// File content (large or binary)
|
|
135
161
|
{
|
|
162
|
+
_descriptor: true,
|
|
136
163
|
type: 'file',
|
|
137
164
|
storage_ref: 'minio://workflow/path/to/file',
|
|
138
165
|
filename: 'invoice.pdf',
|
|
@@ -142,6 +169,10 @@ Stores large content fields as Descriptors (returns Descriptors, not plain strin
|
|
|
142
169
|
}
|
|
143
170
|
```
|
|
144
171
|
|
|
172
|
+
**Descriptor Identification:**
|
|
173
|
+
- `_descriptor: true` - Explicit identifier that value is a Content Descriptor
|
|
174
|
+
- `type: "inline"|"file"` - Distinguishes inline text from file reference
|
|
175
|
+
|
|
145
176
|
### Usage Example
|
|
146
177
|
|
|
147
178
|
```javascript
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -28,7 +28,8 @@ function isDescriptor(value) {
|
|
|
28
28
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
// Check explicit identifier first, then fallback to type check for backward compatibility
|
|
32
|
+
return value._descriptor === true || (value.type === 'inline' || value.type === 'file');
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -196,6 +197,7 @@ class ContentResolver {
|
|
|
196
197
|
async store(content, context = {}, filename = null, content_type = null) {
|
|
197
198
|
if (!content) {
|
|
198
199
|
return {
|
|
200
|
+
_descriptor: true,
|
|
199
201
|
type: 'inline',
|
|
200
202
|
content: '',
|
|
201
203
|
encoding: 'utf-8',
|
|
@@ -417,6 +419,7 @@ class ContentResolver {
|
|
|
417
419
|
const finalContentType = content_type || getContentType(finalFilename, buffer);
|
|
418
420
|
|
|
419
421
|
return {
|
|
422
|
+
_descriptor: true,
|
|
420
423
|
type: 'file',
|
|
421
424
|
storage_ref: `minio://workflow/${result.path}`,
|
|
422
425
|
filename: finalFilename,
|
|
@@ -450,6 +453,7 @@ class ContentResolver {
|
|
|
450
453
|
const finalFilename = filename || `${result.fingerprint.slice(0, 8)}.${ext}`;
|
|
451
454
|
|
|
452
455
|
return {
|
|
456
|
+
_descriptor: true,
|
|
453
457
|
type: 'file',
|
|
454
458
|
storage_ref: `minio://workflow/${result.path}`,
|
|
455
459
|
filename: finalFilename,
|
|
@@ -464,6 +468,7 @@ class ContentResolver {
|
|
|
464
468
|
const fingerprint = crypto.createHash('sha256').update(contentString).digest('hex');
|
|
465
469
|
|
|
466
470
|
return {
|
|
471
|
+
_descriptor: true,
|
|
467
472
|
type: 'inline',
|
|
468
473
|
content: contentString,
|
|
469
474
|
encoding: 'utf-8',
|
|
@@ -481,8 +486,12 @@ class ContentResolver {
|
|
|
481
486
|
* @returns {Promise<Object>} Content Descriptor
|
|
482
487
|
*/
|
|
483
488
|
async normalizeToDescriptor(value, options = {}) {
|
|
484
|
-
// Already a Descriptor
|
|
489
|
+
// Already a Descriptor - ensure it has _descriptor flag
|
|
485
490
|
if (isDescriptor(value)) {
|
|
491
|
+
// Ensure _descriptor flag is present
|
|
492
|
+
if (value._descriptor !== true) {
|
|
493
|
+
return { ...value, _descriptor: true };
|
|
494
|
+
}
|
|
486
495
|
return value;
|
|
487
496
|
}
|
|
488
497
|
|
|
@@ -502,6 +511,110 @@ class ContentResolver {
|
|
|
502
511
|
// Fallback
|
|
503
512
|
return await this.createDescriptor(String(value), options);
|
|
504
513
|
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Create Content Descriptor from a temp file path
|
|
517
|
+
* Reads file, uploads to MinIO, returns Descriptor, DELETES temp file
|
|
518
|
+
*
|
|
519
|
+
* @param {string} tempPath - Path to temp file
|
|
520
|
+
* @param {Object} options - Options
|
|
521
|
+
* @param {string} options.filename - Output filename
|
|
522
|
+
* @param {string} options.content_type - MIME type
|
|
523
|
+
* @param {Object} options.context - Workflow context { workflow_id, step_id }
|
|
524
|
+
* @param {boolean} options.deleteAfterUpload - Delete temp file after upload (default: true)
|
|
525
|
+
* @returns {Promise<Object>} Content Descriptor
|
|
526
|
+
*/
|
|
527
|
+
async createDescriptorFromFile(tempPath, options = {}) {
|
|
528
|
+
const fs = require('fs');
|
|
529
|
+
const path = require('path');
|
|
530
|
+
|
|
531
|
+
const {
|
|
532
|
+
filename,
|
|
533
|
+
content_type,
|
|
534
|
+
context = {},
|
|
535
|
+
deleteAfterUpload = true
|
|
536
|
+
} = options;
|
|
537
|
+
|
|
538
|
+
// Validate temp file exists
|
|
539
|
+
if (!fs.existsSync(tempPath)) {
|
|
540
|
+
throw new Error(`[ContentResolver:FAIL] Temp file not found: ${tempPath}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Read file into buffer
|
|
544
|
+
const buffer = fs.readFileSync(tempPath);
|
|
545
|
+
const size = buffer.length;
|
|
546
|
+
|
|
547
|
+
// Determine filename and content_type
|
|
548
|
+
const finalFilename = filename || path.basename(tempPath);
|
|
549
|
+
const finalContentType = content_type || getContentType(finalFilename, buffer);
|
|
550
|
+
|
|
551
|
+
// Log start
|
|
552
|
+
const logContext = {
|
|
553
|
+
workflow_id: context.workflow_id || 'standalone',
|
|
554
|
+
step_id: context.step_id || 'content-resolver'
|
|
555
|
+
};
|
|
556
|
+
console.log(`[${logContext.workflow_id}:${logContext.step_id}:ContentResolver:STORE_START] ${JSON.stringify({
|
|
557
|
+
temp_path: tempPath,
|
|
558
|
+
filename: finalFilename,
|
|
559
|
+
content_type: finalContentType,
|
|
560
|
+
size: size
|
|
561
|
+
})}`);
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
// Upload to MinIO
|
|
565
|
+
const storage = await this.getStorage();
|
|
566
|
+
const workflowId = context.workflow_id || 'standalone';
|
|
567
|
+
const stepId = context.step_id || 'content';
|
|
568
|
+
const pathPrefix = `outputs/${workflowId}/${stepId}`;
|
|
569
|
+
const ext = finalFilename.split('.').pop() || 'bin';
|
|
570
|
+
|
|
571
|
+
const result = await storage.uploadWithFingerprint(
|
|
572
|
+
'workflow',
|
|
573
|
+
buffer,
|
|
574
|
+
pathPrefix,
|
|
575
|
+
ext
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// Build Descriptor
|
|
579
|
+
const descriptor = {
|
|
580
|
+
_descriptor: true,
|
|
581
|
+
type: 'file',
|
|
582
|
+
storage_ref: `minio://workflow/${result.path}`,
|
|
583
|
+
filename: finalFilename,
|
|
584
|
+
content_type: finalContentType,
|
|
585
|
+
size: size,
|
|
586
|
+
fingerprint: result.fingerprint
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Delete temp file if requested
|
|
590
|
+
if (deleteAfterUpload) {
|
|
591
|
+
try {
|
|
592
|
+
fs.unlinkSync(tempPath);
|
|
593
|
+
} catch (unlinkErr) {
|
|
594
|
+
console.warn(`[${logContext.workflow_id}:${logContext.step_id}:ContentResolver:WARN] Failed to delete temp file: ${unlinkErr.message}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Log success
|
|
599
|
+
console.log(`[${logContext.workflow_id}:${logContext.step_id}:ContentResolver:STORE_SUCCESS] ${JSON.stringify({
|
|
600
|
+
storage_ref: descriptor.storage_ref,
|
|
601
|
+
filename: descriptor.filename,
|
|
602
|
+
size: descriptor.size,
|
|
603
|
+
fingerprint: descriptor.fingerprint.substring(0, 16) + '...'
|
|
604
|
+
})}`);
|
|
605
|
+
|
|
606
|
+
return descriptor;
|
|
607
|
+
|
|
608
|
+
} catch (error) {
|
|
609
|
+
// Log failure
|
|
610
|
+
console.error(`[${logContext.workflow_id}:${logContext.step_id}:ContentResolver:STORE_FAIL] ${JSON.stringify({
|
|
611
|
+
error: error.message,
|
|
612
|
+
temp_path: tempPath,
|
|
613
|
+
filename: finalFilename
|
|
614
|
+
})}`);
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
505
618
|
}
|
|
506
619
|
|
|
507
620
|
// Export class and utilities
|