@emmvish/stable-request 1.2.0 โ 1.2.1
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 +467 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ It is designed for real-world distributed systems where HTTP success (200) does
|
|
|
9
9
|
Most HTTP client libraries only retry on network failures or specific HTTP status codes. **stable-request** goes further by providing:
|
|
10
10
|
|
|
11
11
|
- โ
**Content-aware Retries** - Validate response content and retry even on successful HTTP responses
|
|
12
|
+
- ๐ **Multi-Phase Workflows** - Orchestrate complex workflows with sequential phases and mixed phase execution modes (concurrent & sequential)
|
|
12
13
|
- ๐ **Batch Processing** - Execute multiple requests with hierarchical configuration (global โ group โ request)
|
|
13
14
|
- ๐ฏ **Request Groups** - Organize related requests with shared settings and logical boundaries
|
|
14
15
|
- ๐งช **Trial Mode** - Simulate failures to test your retry logic without depending on real network instability
|
|
@@ -717,6 +718,275 @@ const results = await stableApiGateway(
|
|
|
717
718
|
);
|
|
718
719
|
```
|
|
719
720
|
|
|
721
|
+
## Multi-Phase Workflows
|
|
722
|
+
|
|
723
|
+
For complex operations that require multiple stages of execution, use `stableWorkflow` to orchestrate phase-based workflows with full control over execution order and error handling.
|
|
724
|
+
|
|
725
|
+
### Basic Workflow
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
import { stableWorkflow } from '@emmvish/stable-request';
|
|
729
|
+
|
|
730
|
+
const workflow = await stableWorkflow(
|
|
731
|
+
[
|
|
732
|
+
{
|
|
733
|
+
id: 'validation',
|
|
734
|
+
concurrentExecution: true,
|
|
735
|
+
requests: [
|
|
736
|
+
{
|
|
737
|
+
id: 'check-inventory',
|
|
738
|
+
requestOptions: {
|
|
739
|
+
reqData: { path: '/inventory/check' },
|
|
740
|
+
resReq: true
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
id: 'validate-payment',
|
|
745
|
+
requestOptions: {
|
|
746
|
+
reqData: { path: '/payment/validate' },
|
|
747
|
+
resReq: true
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
id: 'processing',
|
|
754
|
+
concurrentExecution: false,
|
|
755
|
+
stopOnFirstError: true,
|
|
756
|
+
requests: [
|
|
757
|
+
{
|
|
758
|
+
id: 'charge-payment',
|
|
759
|
+
requestOptions: {
|
|
760
|
+
reqData: { path: '/payment/charge', method: REQUEST_METHODS.POST },
|
|
761
|
+
resReq: true
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: 'reserve-inventory',
|
|
766
|
+
requestOptions: {
|
|
767
|
+
reqData: { path: '/inventory/reserve', method: REQUEST_METHODS.POST },
|
|
768
|
+
resReq: true
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
]
|
|
772
|
+
}
|
|
773
|
+
],
|
|
774
|
+
{
|
|
775
|
+
workflowId: 'order-processing-123',
|
|
776
|
+
stopOnFirstPhaseError: true,
|
|
777
|
+
logPhaseResults: true,
|
|
778
|
+
commonRequestData: {
|
|
779
|
+
hostname: 'api.example.com',
|
|
780
|
+
headers: { 'X-Transaction-Id': 'txn-123' }
|
|
781
|
+
},
|
|
782
|
+
commonAttempts: 3,
|
|
783
|
+
commonWait: 1000
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
console.log('Workflow completed:', workflow.success);
|
|
788
|
+
console.log(`${workflow.successfulRequests}/${workflow.totalRequests} requests succeeded`);
|
|
789
|
+
console.log(`Completed in ${workflow.executionTime}ms`);
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**Workflow Result:**
|
|
793
|
+
```typescript
|
|
794
|
+
interface STABLE_WORKFLOW_RESULT {
|
|
795
|
+
workflowId: string; // Workflow identifier
|
|
796
|
+
success: boolean; // Did all phases succeed?
|
|
797
|
+
executionTime: number; // Total workflow duration (ms)
|
|
798
|
+
timestamp: string; // ISO timestamp
|
|
799
|
+
totalPhases: number; // Number of phases defined
|
|
800
|
+
completedPhases: number; // Number of phases executed
|
|
801
|
+
totalRequests: number; // Total requests across all phases
|
|
802
|
+
successfulRequests: number; // Successful requests
|
|
803
|
+
failedRequests: number; // Failed requests
|
|
804
|
+
phases: PHASE_RESULT[]; // Detailed results per phase
|
|
805
|
+
error?: string; // Workflow-level error (if any)
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Phase Configuration
|
|
810
|
+
|
|
811
|
+
Each phase can have its own execution mode and error handling:
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
{
|
|
815
|
+
id: 'phase-name', // Optional: phase identifier
|
|
816
|
+
concurrentExecution?: boolean, // true = parallel, false = sequential
|
|
817
|
+
stopOnFirstError?: boolean, // Stop phase on first request failure
|
|
818
|
+
commonConfig?: { /* phase-level common config */ },
|
|
819
|
+
requests: [/* array of requests */]
|
|
820
|
+
}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**Configuration Priority:**
|
|
824
|
+
Individual Request > Phase Common Config > Workflow Common Config
|
|
825
|
+
|
|
826
|
+
### Workflow with Request Groups
|
|
827
|
+
|
|
828
|
+
Combine workflows with request groups for fine-grained control:
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
const workflow = await stableWorkflow(
|
|
832
|
+
[
|
|
833
|
+
{
|
|
834
|
+
id: 'critical-validation',
|
|
835
|
+
concurrentExecution: true,
|
|
836
|
+
requests: [
|
|
837
|
+
{
|
|
838
|
+
id: 'auth-check',
|
|
839
|
+
groupId: 'critical',
|
|
840
|
+
requestOptions: {
|
|
841
|
+
reqData: { path: '/auth/verify' },
|
|
842
|
+
resReq: true
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
id: 'rate-limit-check',
|
|
847
|
+
groupId: 'critical',
|
|
848
|
+
requestOptions: {
|
|
849
|
+
reqData: { path: '/ratelimit/check' },
|
|
850
|
+
resReq: true
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
]
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id: 'data-processing',
|
|
857
|
+
concurrentExecution: false,
|
|
858
|
+
commonConfig: {
|
|
859
|
+
// Phase-specific overrides
|
|
860
|
+
commonAttempts: 5,
|
|
861
|
+
commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
|
|
862
|
+
},
|
|
863
|
+
requests: [
|
|
864
|
+
{
|
|
865
|
+
id: 'process-data',
|
|
866
|
+
groupId: 'standard',
|
|
867
|
+
requestOptions: {
|
|
868
|
+
reqData: { path: '/data/process', method: REQUEST_METHODS.POST },
|
|
869
|
+
resReq: true
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: 'store-result',
|
|
874
|
+
groupId: 'standard',
|
|
875
|
+
requestOptions: {
|
|
876
|
+
reqData: { path: '/data/store', method: REQUEST_METHODS.POST },
|
|
877
|
+
resReq: true
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
id: 'notifications',
|
|
884
|
+
concurrentExecution: true,
|
|
885
|
+
requests: [
|
|
886
|
+
{
|
|
887
|
+
id: 'email-notification',
|
|
888
|
+
groupId: 'optional',
|
|
889
|
+
requestOptions: {
|
|
890
|
+
reqData: { path: '/notify/email' },
|
|
891
|
+
resReq: true
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
id: 'webhook-notification',
|
|
896
|
+
groupId: 'optional',
|
|
897
|
+
requestOptions: {
|
|
898
|
+
reqData: { path: '/notify/webhook' },
|
|
899
|
+
resReq: true
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
]
|
|
903
|
+
}
|
|
904
|
+
],
|
|
905
|
+
{
|
|
906
|
+
workflowId: 'data-pipeline-workflow',
|
|
907
|
+
stopOnFirstPhaseError: true,
|
|
908
|
+
logPhaseResults: true,
|
|
909
|
+
|
|
910
|
+
commonRequestData: {
|
|
911
|
+
hostname: 'api.example.com'
|
|
912
|
+
},
|
|
913
|
+
commonAttempts: 3,
|
|
914
|
+
commonWait: 1000,
|
|
915
|
+
|
|
916
|
+
// Request groups with different reliability requirements
|
|
917
|
+
requestGroups: [
|
|
918
|
+
{
|
|
919
|
+
id: 'critical',
|
|
920
|
+
commonConfig: {
|
|
921
|
+
commonAttempts: 10,
|
|
922
|
+
commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
|
|
923
|
+
commonWait: 2000,
|
|
924
|
+
commonHandleErrors: async ({ errorLog }) => {
|
|
925
|
+
await alerting.critical('Critical service failure', errorLog);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
id: 'standard',
|
|
931
|
+
commonConfig: {
|
|
932
|
+
commonAttempts: 5,
|
|
933
|
+
commonRetryStrategy: RETRY_STRATEGIES.LINEAR,
|
|
934
|
+
commonWait: 1000
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: 'optional',
|
|
939
|
+
commonConfig: {
|
|
940
|
+
commonAttempts: 2,
|
|
941
|
+
commonFinalErrorAnalyzer: async () => true // Suppress errors
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
);
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Phase Observability Hooks
|
|
950
|
+
|
|
951
|
+
Monitor workflow execution with phase-level hooks:
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
const workflow = await stableWorkflow(
|
|
955
|
+
[
|
|
956
|
+
// ...phases...
|
|
957
|
+
],
|
|
958
|
+
{
|
|
959
|
+
workflowId: 'monitored-workflow',
|
|
960
|
+
|
|
961
|
+
// Called after each phase completes successfully
|
|
962
|
+
handlePhaseCompletion: async ({ workflowId, phaseResult }) => {
|
|
963
|
+
console.log(`Phase ${phaseResult.phaseId} completed`);
|
|
964
|
+
|
|
965
|
+
await analytics.track('workflow_phase_complete', {
|
|
966
|
+
workflowId,
|
|
967
|
+
phaseId: phaseResult.phaseId,
|
|
968
|
+
duration: phaseResult.executionTime,
|
|
969
|
+
successRate: phaseResult.successfulRequests / phaseResult.totalRequests
|
|
970
|
+
});
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
// Called when a phase fails
|
|
974
|
+
handlePhaseError: async ({ workflowId, phaseResult, error }) => {
|
|
975
|
+
console.error(`Phase ${phaseResult.phaseId} failed`);
|
|
976
|
+
|
|
977
|
+
await alerting.notify('workflow_phase_failed', {
|
|
978
|
+
workflowId,
|
|
979
|
+
phaseId: phaseResult.phaseId,
|
|
980
|
+
error: error.message,
|
|
981
|
+
failedRequests: phaseResult.failedRequests
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
|
|
985
|
+
logPhaseResults: true // Enable console logging
|
|
986
|
+
}
|
|
987
|
+
);
|
|
988
|
+
```
|
|
989
|
+
|
|
720
990
|
## Real-World Examples
|
|
721
991
|
|
|
722
992
|
### 1. Polling for Job Completion
|
|
@@ -965,6 +1235,119 @@ const report = {
|
|
|
965
1235
|
console.log('System Health:', report);
|
|
966
1236
|
```
|
|
967
1237
|
|
|
1238
|
+
### 6. Data Pipeline (ETL Workflow)
|
|
1239
|
+
|
|
1240
|
+
```typescript
|
|
1241
|
+
const etlWorkflow = await stableWorkflow(
|
|
1242
|
+
[
|
|
1243
|
+
{
|
|
1244
|
+
id: 'extract',
|
|
1245
|
+
concurrentExecution: true,
|
|
1246
|
+
commonConfig: {
|
|
1247
|
+
commonAttempts: 5,
|
|
1248
|
+
commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
|
|
1249
|
+
},
|
|
1250
|
+
requests: [
|
|
1251
|
+
{ id: 'extract-users', requestOptions: { reqData: { path: '/extract/users' }, resReq: true } },
|
|
1252
|
+
{ id: 'extract-orders', requestOptions: { reqData: { path: '/extract/orders' }, resReq: true } },
|
|
1253
|
+
{ id: 'extract-products', requestOptions: { reqData: { path: '/extract/products' }, resReq: true } }
|
|
1254
|
+
]
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
id: 'transform',
|
|
1258
|
+
concurrentExecution: false,
|
|
1259
|
+
stopOnFirstError: true,
|
|
1260
|
+
requests: [
|
|
1261
|
+
{
|
|
1262
|
+
id: 'clean-data',
|
|
1263
|
+
requestOptions: {
|
|
1264
|
+
reqData: { path: '/transform/clean', method: REQUEST_METHODS.POST },
|
|
1265
|
+
resReq: true
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
id: 'enrich-data',
|
|
1270
|
+
requestOptions: {
|
|
1271
|
+
reqData: { path: '/transform/enrich', method: REQUEST_METHODS.POST },
|
|
1272
|
+
resReq: true
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
id: 'validate-data',
|
|
1277
|
+
requestOptions: {
|
|
1278
|
+
reqData: { path: '/transform/validate', method: REQUEST_METHODS.POST },
|
|
1279
|
+
resReq: true,
|
|
1280
|
+
responseAnalyzer: async ({ data }) => {
|
|
1281
|
+
return data?.validationErrors?.length === 0;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
]
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
id: 'load',
|
|
1289
|
+
concurrentExecution: true,
|
|
1290
|
+
requests: [
|
|
1291
|
+
{
|
|
1292
|
+
id: 'load-warehouse',
|
|
1293
|
+
requestOptions: {
|
|
1294
|
+
reqData: { path: '/load/warehouse', method: REQUEST_METHODS.POST },
|
|
1295
|
+
resReq: true
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
id: 'update-indexes',
|
|
1300
|
+
requestOptions: {
|
|
1301
|
+
reqData: { path: '/load/indexes', method: REQUEST_METHODS.POST },
|
|
1302
|
+
resReq: true
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
id: 'refresh-cache',
|
|
1307
|
+
requestOptions: {
|
|
1308
|
+
reqData: { path: '/cache/refresh', method: REQUEST_METHODS.POST },
|
|
1309
|
+
resReq: true
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
]
|
|
1313
|
+
}
|
|
1314
|
+
],
|
|
1315
|
+
{
|
|
1316
|
+
workflowId: `etl-${new Date().toISOString()}`,
|
|
1317
|
+
stopOnFirstPhaseError: true,
|
|
1318
|
+
logPhaseResults: true,
|
|
1319
|
+
|
|
1320
|
+
commonRequestData: {
|
|
1321
|
+
hostname: 'pipeline.example.com'
|
|
1322
|
+
},
|
|
1323
|
+
commonAttempts: 3,
|
|
1324
|
+
commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
|
|
1325
|
+
|
|
1326
|
+
handlePhaseCompletion: async ({ phaseResult }) => {
|
|
1327
|
+
const recordsProcessed = phaseResult.responses
|
|
1328
|
+
.filter(r => r.success)
|
|
1329
|
+
.reduce((sum, r) => sum + (r.data?.recordCount || 0), 0);
|
|
1330
|
+
|
|
1331
|
+
await metrics.gauge('etl.phase.records', recordsProcessed, {
|
|
1332
|
+
phase: phaseResult.phaseId
|
|
1333
|
+
});
|
|
1334
|
+
},
|
|
1335
|
+
|
|
1336
|
+
handlePhaseError: async ({ phaseResult, error }) => {
|
|
1337
|
+
await pagerDuty.alert('ETL Pipeline Phase Failed', {
|
|
1338
|
+
phase: phaseResult.phaseId,
|
|
1339
|
+
error: error.message,
|
|
1340
|
+
failedRequests: phaseResult.failedRequests
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
console.log(`ETL Pipeline: ${etlWorkflow.success ? 'SUCCESS' : 'FAILED'}`);
|
|
1347
|
+
console.log(`Total time: ${etlWorkflow.executionTime}ms`);
|
|
1348
|
+
console.log(`Records processed: ${etlWorkflow.successfulRequests}/${etlWorkflow.totalRequests}`);
|
|
1349
|
+
```
|
|
1350
|
+
|
|
968
1351
|
## Complete API Reference
|
|
969
1352
|
|
|
970
1353
|
### `stableRequest(options)`
|
|
@@ -1029,6 +1412,51 @@ interface REQUEST_DATA<RequestDataType = any> {
|
|
|
1029
1412
|
| `commonRequestData` | `Partial<REQUEST_DATA>` | `{ hostname: '' }` | Common set of request options for each request |
|
|
1030
1413
|
| `commonHookParams` | `HookParams` | `{ }` | Common options for each request hook |
|
|
1031
1414
|
|
|
1415
|
+
### `stableWorkflow(phases, options)`
|
|
1416
|
+
|
|
1417
|
+
Execute a multi-phase workflow with full control over execution order and error handling.
|
|
1418
|
+
|
|
1419
|
+
**Phases Array:**
|
|
1420
|
+
```typescript
|
|
1421
|
+
interface STABLE_WORKFLOW_PHASE {
|
|
1422
|
+
id?: string; // Phase identifier (auto-generated if omitted)
|
|
1423
|
+
concurrentExecution?: boolean; // true = parallel, false = sequential (default: true)
|
|
1424
|
+
stopOnFirstError?: boolean; // Stop phase on first request failure (default: false)
|
|
1425
|
+
commonConfig?: Omit<API_GATEWAY_OPTIONS, 'concurrentExecution' | 'stopOnFirstError' | 'requestGroups'>;
|
|
1426
|
+
requests: API_GATEWAY_REQUEST[]; // Array of requests for this phase
|
|
1427
|
+
}
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
**Workflow Options:**
|
|
1431
|
+
|
|
1432
|
+
| Option | Type | Default | Description |
|
|
1433
|
+
|--------|------|---------|-------------|
|
|
1434
|
+
| `workflowId` | `string` | `workflow-{timestamp}` | Workflow identifier |
|
|
1435
|
+
| `stopOnFirstPhaseError` | `boolean` | `false` | Stop workflow if any phase fails |
|
|
1436
|
+
| `logPhaseResults` | `boolean` | `false` | Log phase execution to console |
|
|
1437
|
+
| `handlePhaseCompletion` | `function` | `undefined` | Hook called after each successful phase |
|
|
1438
|
+
| `handlePhaseError` | `function` | `undefined` | Hook called when a phase fails |
|
|
1439
|
+
| `maxSerializableChars` | `number` | `1000` | Max chars for serialization in hooks |
|
|
1440
|
+
| `workflowHookParams` | `WorkflowHookParams` | {} | Custom set of params passed to hooks |
|
|
1441
|
+
| All `stableApiGateway` options | - | - | Applied as workflow-level defaults |
|
|
1442
|
+
|
|
1443
|
+
**STABLE_WORKFLOW_RESULT response:**
|
|
1444
|
+
```typescript
|
|
1445
|
+
interface STABLE_WORKFLOW_RESULT {
|
|
1446
|
+
workflowId: string;
|
|
1447
|
+
success: boolean; // All phases successful?
|
|
1448
|
+
executionTime: number; // Total workflow duration (ms)
|
|
1449
|
+
timestamp: string; // ISO timestamp
|
|
1450
|
+
totalPhases: number;
|
|
1451
|
+
completedPhases: number;
|
|
1452
|
+
totalRequests: number;
|
|
1453
|
+
successfulRequests: number;
|
|
1454
|
+
failedRequests: number;
|
|
1455
|
+
phases: PHASE_RESULT[]; // Detailed results per phase
|
|
1456
|
+
error?: string; // Workflow-level error
|
|
1457
|
+
}
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1032
1460
|
### Hooks Reference
|
|
1033
1461
|
|
|
1034
1462
|
#### responseAnalyzer
|
|
@@ -1084,6 +1512,40 @@ finalErrorAnalyzer: async ({ reqData, error, trialMode, params }) => {
|
|
|
1084
1512
|
}
|
|
1085
1513
|
```
|
|
1086
1514
|
|
|
1515
|
+
#### handlePhaseCompletion
|
|
1516
|
+
|
|
1517
|
+
**Purpose:** Execute phase-bridging code upon successful completion of a phase
|
|
1518
|
+
|
|
1519
|
+
```typescript
|
|
1520
|
+
handlePhaseCompletion: async ({ workflowId, phaseResult, maxSerializableChars, params }) => {
|
|
1521
|
+
await logger.log(phaseResult.phaseId, phaseResult.success);
|
|
1522
|
+
// phaseResult includes:
|
|
1523
|
+
// - phaseId, phaseIndex
|
|
1524
|
+
// - success, executionTime, timestamp
|
|
1525
|
+
// - totalRequests, successfulRequests, failedRequests
|
|
1526
|
+
// - responses array
|
|
1527
|
+
}
|
|
1528
|
+
```
|
|
1529
|
+
|
|
1530
|
+
#### handlePhaseError
|
|
1531
|
+
|
|
1532
|
+
**Purpose:** Execute error handling code if a phase runs into an error
|
|
1533
|
+
|
|
1534
|
+
```typescript
|
|
1535
|
+
handlePhaseError: async ({ workflowId, phaseResult, error, maxSerializableChars, params }) => {
|
|
1536
|
+
await logger.error(error);
|
|
1537
|
+
// Similar to handlePhaseCompletion, plus:
|
|
1538
|
+
// - error: the error object
|
|
1539
|
+
}
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
## Configuration Hierarchy
|
|
1543
|
+
|
|
1544
|
+
1. **Workflow-level** (lowest priority): Applied to all phases
|
|
1545
|
+
2. **Phase-level**: Overrides workflow-level
|
|
1546
|
+
3. **Request Group**: Overrides phase-level
|
|
1547
|
+
4. **Individual Request** (highest priority): Overrides everything
|
|
1548
|
+
|
|
1087
1549
|
## TypeScript Support
|
|
1088
1550
|
|
|
1089
1551
|
Fully typed with generics:
|
|
@@ -1130,6 +1592,11 @@ console.log(user.id); // TypeScript knows this exists
|
|
|
1130
1592
|
8. **Set appropriate timeouts** to prevent hanging requests
|
|
1131
1593
|
9. **Use idempotency keys** for payment/financial operations
|
|
1132
1594
|
10. **Log contextual information** in your hooks for debugging
|
|
1595
|
+
11. **Design workflows with clear phases** - Each phase should represent a logical stage
|
|
1596
|
+
12. **Use phase hooks** (`handlePhaseCompletion`, `handlePhaseError`) for workflow observability
|
|
1597
|
+
13. **Set `stopOnFirstPhaseError: true`** for critical workflows that shouldn't continue after failure
|
|
1598
|
+
14. **Use mixed execution modes** - Concurrent for independent operations, sequential for dependencies
|
|
1599
|
+
15. **Leverage request groups in workflows** for different reliability tiers within phases
|
|
1133
1600
|
|
|
1134
1601
|
## License
|
|
1135
1602
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { stableRequest, stableApiGateway, stableWorkflow } from './core/index.js';
|
|
2
2
|
export { INVALID_AXIOS_RESPONSES, REQUEST_METHODS, RESPONSE_ERRORS, RETRY_STRATEGIES, VALID_REQUEST_PROTOCOLS } from './enums/index.js';
|
|
3
|
-
export type { API_GATEWAY_OPTIONS, API_GATEWAY_REQUEST, API_GATEWAY_REQUEST_OPTIONS_TYPE, API_GATEWAY_RESPONSE, CONCURRENT_REQUEST_EXECUTION_OPTIONS, ERROR_LOG, FinalErrorAnalysisHookOptions, HandleErrorHookOptions, HandlePhaseCompletionHookOptions, HandlePhaseErrorHookOptions, HandleSuccessfulAttemptDataHookOptions, HookParams, ReqFnResponse, REQUEST_DATA, RequestGroup, REQUEST_METHOD_TYPES, ResponseAnalysisHookOptions, RETRY_STRATEGY_TYPES, SEQUENTIAL_REQUEST_EXECUTION_OPTIONS, STABLE_REQUEST, STABLE_WORKFLOW_PHASE, STABLE_WORKFLOW_OPTIONS, STABLE_WORKFLOW_PHASE_RESULT, STABLE_WORKFLOW_RESULT, SUCCESSFUL_ATTEMPT_DATA, VALID_REQUEST_PROTOCOL_TYPES, TRIAL_MODE_OPTIONS } from './types/index.js';
|
|
3
|
+
export type { API_GATEWAY_OPTIONS, API_GATEWAY_REQUEST, API_GATEWAY_REQUEST_OPTIONS_TYPE, API_GATEWAY_RESPONSE, CONCURRENT_REQUEST_EXECUTION_OPTIONS, ERROR_LOG, FinalErrorAnalysisHookOptions, HandleErrorHookOptions, HandlePhaseCompletionHookOptions, HandlePhaseErrorHookOptions, HandleSuccessfulAttemptDataHookOptions, HookParams, ReqFnResponse, REQUEST_DATA, RequestGroup, REQUEST_METHOD_TYPES, ResponseAnalysisHookOptions, RETRY_STRATEGY_TYPES, SEQUENTIAL_REQUEST_EXECUTION_OPTIONS, STABLE_REQUEST, STABLE_WORKFLOW_PHASE, STABLE_WORKFLOW_OPTIONS, STABLE_WORKFLOW_PHASE_RESULT, STABLE_WORKFLOW_RESULT, SUCCESSFUL_ATTEMPT_DATA, VALID_REQUEST_PROTOCOL_TYPES, WorkflowHookParams, TRIAL_MODE_OPTIONS } from './types/index.js';
|
|
4
4
|
export { delay, executeConcurrently, executeSequentially, extractCommonRequestConfigOptions, generateAxiosRequestConfig, getNewDelayTime, isRetryableError, prepareApiRequestData, prepareApiRequestOptions, reqFn, safelyExecuteUnknownFunction, safelyStringify, validateTrialModeProbabilities } from './utilities/index.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,aAAa,EACb,gBAAgB,EAChB,cAAc,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACH,uBAAuB,EACvB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACR,mBAAmB,EACnB,mBAAmB,EACnB,gCAAgC,EAChC,oBAAoB,EACpB,oCAAoC,EACpC,SAAS,EACT,6BAA6B,EAC7B,sBAAsB,EACtB,gCAAgC,EAChC,2BAA2B,EAC3B,sCAAsC,EACtC,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,EACpB,oCAAoC,EACpC,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,4BAA4B,EAC5B,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,kBAAkB,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACH,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iCAAiC,EACjC,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,EACL,4BAA4B,EAC5B,eAAe,EACf,8BAA8B,EACjC,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,aAAa,EACb,gBAAgB,EAChB,cAAc,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACH,uBAAuB,EACvB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACR,mBAAmB,EACnB,mBAAmB,EACnB,gCAAgC,EAChC,oBAAoB,EACpB,oCAAoC,EACpC,SAAS,EACT,6BAA6B,EAC7B,sBAAsB,EACtB,gCAAgC,EAChC,2BAA2B,EAC3B,sCAAsC,EACtC,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,EACpB,oCAAoC,EACpC,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,4BAA4B,EAC5B,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,kBAAkB,EAClB,kBAAkB,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACH,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iCAAiC,EACjC,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,EACL,4BAA4B,EAC5B,eAAe,EACf,8BAA8B,EACjC,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,aAAa,EACb,gBAAgB,EAChB,cAAc,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACH,uBAAuB,EACvB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,aAAa,EACb,gBAAgB,EAChB,cAAc,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACH,uBAAuB,EACvB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;AAiC1B,OAAO,EACH,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iCAAiC,EACjC,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,EACL,4BAA4B,EAC5B,eAAe,EACf,8BAA8B,EACjC,MAAM,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emmvish/stable-request",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "stable-request is a TypeScript-first HTTP reliability toolkit for workflow-driven API integrations, that goes beyond status-code retries by validating response content, handling eventual consistency, coordinating batch workflows with intelligent grouping, and providing deep observability into every request attempt. It is designed for real-world distributed systems where HTTP success does not guarantee business success. It also provides extensive support for managing multiple requests so as to achieve workflow automation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|