@adobe-commerce/aio-toolkit 1.0.1 → 1.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/CHANGELOG.md +44 -0
- package/README.md +66 -10
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +56 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.2] - 2025-09-30
|
|
9
|
+
|
|
10
|
+
### 🛠️ Framework Component Enhancements & Critical Bug Fixes
|
|
11
|
+
|
|
12
|
+
This minor release introduces significant enhancements to core framework components, resolving critical issues and adding comprehensive new functionality. Both changes maintain full backward compatibility while substantially improving developer experience and API integration capabilities.
|
|
13
|
+
|
|
14
|
+
#### 🔧 Framework Components
|
|
15
|
+
|
|
16
|
+
- **FileRepository** `[Enhanced]` - Optional ID parameter support and standardized file management
|
|
17
|
+
- Optional ID parameter in save method: `save(payload, id?)` signature
|
|
18
|
+
- ID parameter takes precedence over payload.id property
|
|
19
|
+
- Automatic ID sanitization (alphanumeric + underscore characters only)
|
|
20
|
+
- Fallback to timestamp-based ID generation for invalid inputs
|
|
21
|
+
- Enhanced return value: method now returns sanitized filename string instead of boolean
|
|
22
|
+
- Standardized date formatting with ISO 8601 format for all timestamps
|
|
23
|
+
- Property naming consistency: `created_at` → `createdAt`, `updated_at` → `updatedAt`
|
|
24
|
+
- String-only ID handling across all methods with comprehensive error handling
|
|
25
|
+
- Enhanced type safety with null return values on failures
|
|
26
|
+
|
|
27
|
+
#### 🔗 Integration Components
|
|
28
|
+
|
|
29
|
+
- **RestClient** `[Enhanced]` - Comprehensive payload type support resolving critical compatibility issues
|
|
30
|
+
- URLSearchParams support for form-encoded requests (resolves Issue #40)
|
|
31
|
+
- FormData support for file uploads and multipart request handling
|
|
32
|
+
- String payload support for text and XML request transmission
|
|
33
|
+
- Binary data support for Buffer, ArrayBuffer, and Uint8Array objects
|
|
34
|
+
- Smart Content-Type management with user header preservation
|
|
35
|
+
- Intelligent Content-Type defaults per payload type when not specified
|
|
36
|
+
- Full backward compatibility maintained for existing JSON usage
|
|
37
|
+
- Enhanced OAuth2 authentication flow support and form-based API integrations
|
|
38
|
+
|
|
39
|
+
#### 🐛 Critical Bug Fixes
|
|
40
|
+
|
|
41
|
+
- **Issue #41 - FileRepository Save Method Enhancement** `[Resolved]`
|
|
42
|
+
- **Problem**: Missing ID parameter support, incorrect return type, inconsistent date formatting
|
|
43
|
+
- **Solution**: Complete method signature overhaul with flexible ID handling and proper return types
|
|
44
|
+
- **Impact**: Enables advanced file management workflows with explicit ID control
|
|
45
|
+
|
|
46
|
+
- **Issue #40 - RestClient URLSearchParams Failure** `[Resolved]`
|
|
47
|
+
- **Problem**: `makeRequest()` failed with 405 Method Not Allowed for form-encoded POST requests
|
|
48
|
+
- **Root Cause**: Always JSON.stringify() payloads and forced 'application/json' Content-Type
|
|
49
|
+
- **Solution**: Intelligent payload type detection with appropriate serialization and headers
|
|
50
|
+
- **Impact**: Enables OAuth2 authentication flows and form-based API integrations
|
|
51
|
+
|
|
8
52
|
## [1.0.1] - 2025-09-22
|
|
9
53
|
|
|
10
54
|
### 🚀 Enhanced Developer Experience & API Improvements
|
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ exports.main = helloWorldAction;
|
|
|
180
180
|
File-based storage with CRUD operations for Adobe I/O Runtime applications.
|
|
181
181
|
|
|
182
182
|
**Key Methods:**
|
|
183
|
-
- `save(payload)`: Saves data
|
|
183
|
+
- `save(payload, id?)`: Saves data with optional ID parameter. The `id` parameter takes precedence over `payload.id`. IDs are automatically sanitized to alphanumeric + underscore characters.
|
|
184
184
|
- `load(id)`: Loads data by ID
|
|
185
185
|
- `list()`: Lists all stored records
|
|
186
186
|
- `delete(ids)`: Deletes records by ID array
|
|
@@ -244,7 +244,7 @@ exports.main = RuntimeAction.execute(
|
|
|
244
244
|
```
|
|
245
245
|
|
|
246
246
|
##### **4. Save Action**
|
|
247
|
-
Save entity data with
|
|
247
|
+
Save entity data with flexible ID handling using the new optional ID parameter:
|
|
248
248
|
|
|
249
249
|
```javascript
|
|
250
250
|
const { HttpMethod, RuntimeAction, RuntimeActionResponse } = require("@adobe-commerce/aio-toolkit");
|
|
@@ -263,15 +263,22 @@ exports.main = RuntimeAction.execute(
|
|
|
263
263
|
async (params) => {
|
|
264
264
|
const entityRepository = new EntityRepository();
|
|
265
265
|
|
|
266
|
+
// Build payload with required fields
|
|
266
267
|
let payload = {};
|
|
267
268
|
for (const fieldName in requiredParams) {
|
|
268
269
|
payload[requiredParams[fieldName]] = params[requiredParams[fieldName]];
|
|
269
270
|
}
|
|
270
|
-
if (Object.prototype.hasOwnProperty.call(params, 'id')) {
|
|
271
|
-
payload['id'] = params['id'];
|
|
272
|
-
}
|
|
273
271
|
|
|
274
|
-
|
|
272
|
+
// Extract ID parameter for prioritized handling
|
|
273
|
+
const explicitId = params.id || params.customId || null;
|
|
274
|
+
|
|
275
|
+
// Save with optional ID parameter - it takes precedence over payload.id
|
|
276
|
+
const savedId = await entityRepository.save(payload, explicitId);
|
|
277
|
+
|
|
278
|
+
return RuntimeActionResponse.success({
|
|
279
|
+
id: savedId,
|
|
280
|
+
message: 'Entity saved successfully'
|
|
281
|
+
});
|
|
275
282
|
}
|
|
276
283
|
);
|
|
277
284
|
```
|
|
@@ -297,9 +304,11 @@ exports.main = RuntimeAction.execute(
|
|
|
297
304
|
|
|
298
305
|
This approach provides:
|
|
299
306
|
- **Separation of concerns**: Each CRUD operation has its own action file
|
|
300
|
-
- **Reusable repository**: Custom repository can be shared across actions
|
|
307
|
+
- **Reusable repository**: Custom repository can be shared across actions
|
|
301
308
|
- **Proper validation**: Required parameters and headers are enforced
|
|
302
309
|
- **Consistent responses**: All actions use RuntimeActionResponse for standardized output
|
|
310
|
+
- **Flexible ID management**: Support for explicit IDs, payload IDs, and auto-generation
|
|
311
|
+
- **Automatic sanitization**: IDs are cleaned to ensure file system compatibility
|
|
303
312
|
|
|
304
313
|
### 🏪 Commerce Components
|
|
305
314
|
|
|
@@ -352,18 +361,65 @@ const newProduct = await client.post('rest/V1/products', {}, productData);
|
|
|
352
361
|
**External API integration and utility functions**
|
|
353
362
|
|
|
354
363
|
#### `RestClient`
|
|
355
|
-
HTTP client for external API integration.
|
|
364
|
+
HTTP client for external API integration with support for various payload types.
|
|
356
365
|
|
|
366
|
+
**Basic Usage**
|
|
357
367
|
```typescript
|
|
358
368
|
const { RestClient } = require('@adobe-commerce/aio-toolkit');
|
|
359
369
|
|
|
360
370
|
const client = new RestClient();
|
|
371
|
+
|
|
372
|
+
// GET request
|
|
361
373
|
const response = await client.get('https://api.example.com/data', {
|
|
362
|
-
'Authorization': 'Bearer token'
|
|
363
|
-
'Content-Type': 'application/json'
|
|
374
|
+
'Authorization': 'Bearer token'
|
|
364
375
|
});
|
|
365
376
|
```
|
|
366
377
|
|
|
378
|
+
**JSON Payloads (default)**
|
|
379
|
+
```typescript
|
|
380
|
+
// POST with JSON (automatic Content-Type: application/json)
|
|
381
|
+
const jsonData = { name: 'Product', price: 99.99 };
|
|
382
|
+
const response = await client.post('https://api.example.com/products', {
|
|
383
|
+
'Authorization': 'Bearer token'
|
|
384
|
+
}, jsonData);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Form-Encoded Requests**
|
|
388
|
+
```typescript
|
|
389
|
+
// URLSearchParams for form-encoded data (automatic Content-Type: application/x-www-form-urlencoded)
|
|
390
|
+
const formData = new URLSearchParams({
|
|
391
|
+
grant_type: 'client_credentials',
|
|
392
|
+
client_id: 'your-client-id',
|
|
393
|
+
client_secret: 'your-client-secret'
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const tokenResponse = await client.post('https://auth.example.com/token', {
|
|
397
|
+
Accept: 'application/json'
|
|
398
|
+
}, formData);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**File Upload**
|
|
402
|
+
```typescript
|
|
403
|
+
// FormData for file uploads (Content-Type boundary handled automatically)
|
|
404
|
+
const uploadData = new FormData();
|
|
405
|
+
uploadData.append('file', fileBuffer, 'document.pdf');
|
|
406
|
+
uploadData.append('description', 'Important document');
|
|
407
|
+
|
|
408
|
+
const uploadResponse = await client.post('https://api.example.com/upload', {
|
|
409
|
+
'Authorization': 'Bearer token'
|
|
410
|
+
}, uploadData);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Text/XML Payloads**
|
|
414
|
+
```typescript
|
|
415
|
+
// String payloads with custom content type
|
|
416
|
+
const xmlData = '<?xml version="1.0"?><order><id>123</id></order>';
|
|
417
|
+
const xmlResponse = await client.post('https://api.example.com/orders', {
|
|
418
|
+
'Authorization': 'Bearer token',
|
|
419
|
+
'Content-Type': 'application/xml'
|
|
420
|
+
}, xmlData);
|
|
421
|
+
```
|
|
422
|
+
|
|
367
423
|
#### `BearerToken`
|
|
368
424
|
Bearer token extraction and JWT analysis utility. Supports both standard HTTP headers and OpenWhisk format for maximum portability.
|
|
369
425
|
|
package/dist/index.d.mts
CHANGED
|
@@ -121,9 +121,9 @@ declare class OpenwhiskAction {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
interface FileRecord {
|
|
124
|
-
id: string
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
id: string;
|
|
125
|
+
createdAt: string;
|
|
126
|
+
updatedAt: string;
|
|
127
127
|
[key: string]: any;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -133,8 +133,9 @@ declare class FileRepository {
|
|
|
133
133
|
constructor(filepath: string);
|
|
134
134
|
list(): Promise<FileRecord[]>;
|
|
135
135
|
load(id?: string): Promise<FileRecord>;
|
|
136
|
-
save(payload?: Partial<FileRecord
|
|
137
|
-
delete(ids?:
|
|
136
|
+
save(payload?: Partial<FileRecord>, id?: string | null): Promise<string | null>;
|
|
137
|
+
delete(ids?: string[]): Promise<FileRecord[]>;
|
|
138
|
+
private sanitizeFileId;
|
|
138
139
|
private getFiles;
|
|
139
140
|
}
|
|
140
141
|
|
package/dist/index.d.ts
CHANGED
|
@@ -121,9 +121,9 @@ declare class OpenwhiskAction {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
interface FileRecord {
|
|
124
|
-
id: string
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
id: string;
|
|
125
|
+
createdAt: string;
|
|
126
|
+
updatedAt: string;
|
|
127
127
|
[key: string]: any;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -133,8 +133,9 @@ declare class FileRepository {
|
|
|
133
133
|
constructor(filepath: string);
|
|
134
134
|
list(): Promise<FileRecord[]>;
|
|
135
135
|
load(id?: string): Promise<FileRecord>;
|
|
136
|
-
save(payload?: Partial<FileRecord
|
|
137
|
-
delete(ids?:
|
|
136
|
+
save(payload?: Partial<FileRecord>, id?: string | null): Promise<string | null>;
|
|
137
|
+
delete(ids?: string[]): Promise<FileRecord[]>;
|
|
138
|
+
private sanitizeFileId;
|
|
138
139
|
private getFiles;
|
|
139
140
|
}
|
|
140
141
|
|
package/dist/index.js
CHANGED
|
@@ -484,39 +484,44 @@ var _FileRepository = class _FileRepository {
|
|
|
484
484
|
/**
|
|
485
485
|
* Saves a file record to the repository
|
|
486
486
|
* @param payload - The data to save
|
|
487
|
-
* @
|
|
487
|
+
* @param id - Optional ID for the file (sanitized to alphanumeric + underscore, takes precedence over payload.id)
|
|
488
|
+
* @returns Promise<string | null> The filename on success, null on failure
|
|
488
489
|
*/
|
|
489
|
-
async save(payload = {}) {
|
|
490
|
+
async save(payload = {}, id) {
|
|
490
491
|
try {
|
|
491
492
|
const filesLib = await this.getFiles();
|
|
492
|
-
let
|
|
493
|
-
if (
|
|
494
|
-
|
|
493
|
+
let fileId;
|
|
494
|
+
if (id) {
|
|
495
|
+
fileId = this.sanitizeFileId(id);
|
|
496
|
+
} else if ("id" in payload && payload.id !== void 0) {
|
|
497
|
+
fileId = String(payload.id);
|
|
498
|
+
} else {
|
|
499
|
+
fileId = String((/* @__PURE__ */ new Date()).getTime());
|
|
495
500
|
}
|
|
496
|
-
const filepath = `${this.filepath}/${
|
|
501
|
+
const filepath = `${this.filepath}/${fileId}.json`;
|
|
497
502
|
const existingFile = await filesLib.list(filepath);
|
|
498
503
|
if (existingFile.length) {
|
|
499
504
|
const buffer = await filesLib.read(filepath);
|
|
500
505
|
const existingData = JSON.parse(buffer.toString());
|
|
501
506
|
payload = {
|
|
502
507
|
...payload,
|
|
503
|
-
|
|
508
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
504
509
|
};
|
|
505
510
|
payload = { ...existingData, ...payload };
|
|
506
511
|
await filesLib.delete(filepath);
|
|
507
512
|
} else {
|
|
508
513
|
payload = {
|
|
509
514
|
...payload,
|
|
510
|
-
id:
|
|
511
|
-
|
|
512
|
-
|
|
515
|
+
id: fileId,
|
|
516
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
513
518
|
};
|
|
514
519
|
}
|
|
515
520
|
await filesLib.write(filepath, JSON.stringify(payload));
|
|
516
|
-
return
|
|
521
|
+
return fileId;
|
|
517
522
|
} catch (error) {
|
|
518
523
|
console.error("Error saving file:", error);
|
|
519
|
-
return
|
|
524
|
+
return null;
|
|
520
525
|
}
|
|
521
526
|
}
|
|
522
527
|
/**
|
|
@@ -531,6 +536,21 @@ var _FileRepository = class _FileRepository {
|
|
|
531
536
|
}
|
|
532
537
|
return await this.list();
|
|
533
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Sanitizes the file ID to contain only alphanumeric characters and underscores
|
|
541
|
+
* @param id - The ID to sanitize
|
|
542
|
+
* @returns Sanitized ID with invalid characters replaced by underscores
|
|
543
|
+
*/
|
|
544
|
+
sanitizeFileId(id) {
|
|
545
|
+
if (!id || typeof id !== "string") {
|
|
546
|
+
return String((/* @__PURE__ */ new Date()).getTime());
|
|
547
|
+
}
|
|
548
|
+
const sanitized = id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
549
|
+
if (!sanitized || /^_+$/.test(sanitized)) {
|
|
550
|
+
return String((/* @__PURE__ */ new Date()).getTime());
|
|
551
|
+
}
|
|
552
|
+
return sanitized;
|
|
553
|
+
}
|
|
534
554
|
/**
|
|
535
555
|
* Initializes and returns the Files library instance
|
|
536
556
|
* @returns Promise<any> Initialized Files library instance
|
|
@@ -708,13 +728,32 @@ var _RestClient = class _RestClient {
|
|
|
708
728
|
headers
|
|
709
729
|
};
|
|
710
730
|
if (payload !== null) {
|
|
731
|
+
let body;
|
|
732
|
+
let contentType;
|
|
733
|
+
if (payload instanceof URLSearchParams) {
|
|
734
|
+
body = payload.toString();
|
|
735
|
+
contentType = headers["Content-Type"] || "application/x-www-form-urlencoded";
|
|
736
|
+
} else if (typeof FormData !== "undefined" && payload instanceof FormData) {
|
|
737
|
+
body = payload;
|
|
738
|
+
contentType = headers["Content-Type"];
|
|
739
|
+
} else if (typeof payload === "string") {
|
|
740
|
+
body = payload;
|
|
741
|
+
contentType = headers["Content-Type"] || "text/plain";
|
|
742
|
+
} else if (payload instanceof Buffer || payload instanceof ArrayBuffer || typeof Uint8Array !== "undefined" && payload instanceof Uint8Array) {
|
|
743
|
+
body = payload;
|
|
744
|
+
contentType = headers["Content-Type"] || "application/octet-stream";
|
|
745
|
+
} else {
|
|
746
|
+
body = JSON.stringify(payload);
|
|
747
|
+
contentType = headers["Content-Type"] || "application/json";
|
|
748
|
+
}
|
|
749
|
+
const requestHeaders = { ...headers };
|
|
750
|
+
if (contentType) {
|
|
751
|
+
requestHeaders["Content-Type"] = contentType;
|
|
752
|
+
}
|
|
711
753
|
options = {
|
|
712
754
|
...options,
|
|
713
|
-
body
|
|
714
|
-
headers:
|
|
715
|
-
...headers,
|
|
716
|
-
"Content-Type": "application/json"
|
|
717
|
-
}
|
|
755
|
+
body,
|
|
756
|
+
headers: requestHeaders
|
|
718
757
|
};
|
|
719
758
|
}
|
|
720
759
|
return await (0, import_node_fetch.default)(endpoint, options);
|