meridianjs 0.2.4 → 0.2.7

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +155 -530
  3. package/dist/analytics/collector.d.ts +16 -0
  4. package/dist/analytics/collector.d.ts.map +1 -1
  5. package/dist/analytics/collector.js +20 -0
  6. package/dist/analytics/collector.js.map +1 -1
  7. package/dist/capabilities/registry.d.ts.map +1 -1
  8. package/dist/capabilities/registry.js +7 -0
  9. package/dist/capabilities/registry.js.map +1 -1
  10. package/dist/core/pipeline.d.ts.map +1 -1
  11. package/dist/core/pipeline.js +18 -1
  12. package/dist/core/pipeline.js.map +1 -1
  13. package/dist/core/types.d.ts +9 -4
  14. package/dist/core/types.d.ts.map +1 -1
  15. package/dist/core/types.js +1 -1
  16. package/dist/core/types.js.map +1 -1
  17. package/dist/generator/index.d.ts +2 -0
  18. package/dist/generator/index.d.ts.map +1 -1
  19. package/dist/generator/index.js +2 -1
  20. package/dist/generator/index.js.map +1 -1
  21. package/dist/generator/openapi-export.d.ts +51 -0
  22. package/dist/generator/openapi-export.d.ts.map +1 -0
  23. package/dist/generator/openapi-export.js +95 -0
  24. package/dist/generator/openapi-export.js.map +1 -0
  25. package/dist/generator/templates.d.ts.map +1 -1
  26. package/dist/generator/templates.js +89 -18
  27. package/dist/generator/templates.js.map +1 -1
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +15 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/policies/builtin.d.ts +16 -0
  33. package/dist/policies/builtin.d.ts.map +1 -1
  34. package/dist/policies/builtin.js +92 -0
  35. package/dist/policies/builtin.js.map +1 -1
  36. package/dist/policies/index.d.ts +1 -1
  37. package/dist/policies/index.d.ts.map +1 -1
  38. package/dist/policies/index.js +1 -1
  39. package/dist/policies/index.js.map +1 -1
  40. package/dist/providers/billdesk/adapter.d.ts +36 -0
  41. package/dist/providers/billdesk/adapter.d.ts.map +1 -0
  42. package/dist/providers/billdesk/adapter.js +212 -0
  43. package/dist/providers/billdesk/adapter.js.map +1 -0
  44. package/dist/providers/billdesk/index.d.ts +3 -0
  45. package/dist/providers/billdesk/index.d.ts.map +1 -0
  46. package/dist/providers/billdesk/index.js +3 -0
  47. package/dist/providers/billdesk/index.js.map +1 -0
  48. package/dist/providers/billdesk/pagination.d.ts +15 -0
  49. package/dist/providers/billdesk/pagination.d.ts.map +1 -0
  50. package/dist/providers/billdesk/pagination.js +51 -0
  51. package/dist/providers/billdesk/pagination.js.map +1 -0
  52. package/dist/providers/ccavenue/adapter.d.ts +38 -0
  53. package/dist/providers/ccavenue/adapter.d.ts.map +1 -0
  54. package/dist/providers/ccavenue/adapter.js +181 -0
  55. package/dist/providers/ccavenue/adapter.js.map +1 -0
  56. package/dist/providers/ccavenue/index.d.ts +3 -0
  57. package/dist/providers/ccavenue/index.d.ts.map +1 -0
  58. package/dist/providers/ccavenue/index.js +3 -0
  59. package/dist/providers/ccavenue/index.js.map +1 -0
  60. package/dist/providers/ccavenue/pagination.d.ts +16 -0
  61. package/dist/providers/ccavenue/pagination.d.ts.map +1 -0
  62. package/dist/providers/ccavenue/pagination.js +20 -0
  63. package/dist/providers/ccavenue/pagination.js.map +1 -0
  64. package/dist/providers/datadog/adapter.d.ts +30 -0
  65. package/dist/providers/datadog/adapter.d.ts.map +1 -0
  66. package/dist/providers/datadog/adapter.js +163 -0
  67. package/dist/providers/datadog/adapter.js.map +1 -0
  68. package/dist/providers/datadog/index.d.ts +3 -0
  69. package/dist/providers/datadog/index.d.ts.map +1 -0
  70. package/dist/providers/datadog/index.js +3 -0
  71. package/dist/providers/datadog/index.js.map +1 -0
  72. package/dist/providers/datadog/pagination.d.ts +16 -0
  73. package/dist/providers/datadog/pagination.d.ts.map +1 -0
  74. package/dist/providers/datadog/pagination.js +41 -0
  75. package/dist/providers/datadog/pagination.js.map +1 -0
  76. package/dist/providers/s3/adapter.d.ts +37 -0
  77. package/dist/providers/s3/adapter.d.ts.map +1 -0
  78. package/dist/providers/s3/adapter.js +206 -0
  79. package/dist/providers/s3/adapter.js.map +1 -0
  80. package/dist/providers/s3/index.d.ts +4 -0
  81. package/dist/providers/s3/index.d.ts.map +1 -0
  82. package/dist/providers/s3/index.js +4 -0
  83. package/dist/providers/s3/index.js.map +1 -0
  84. package/dist/providers/s3/pagination.d.ts +18 -0
  85. package/dist/providers/s3/pagination.d.ts.map +1 -0
  86. package/dist/providers/s3/pagination.js +58 -0
  87. package/dist/providers/s3/pagination.js.map +1 -0
  88. package/dist/providers/s3/sigv4.d.ts +29 -0
  89. package/dist/providers/s3/sigv4.d.ts.map +1 -0
  90. package/dist/providers/s3/sigv4.js +101 -0
  91. package/dist/providers/s3/sigv4.js.map +1 -0
  92. package/dist/providers/sentry/adapter.d.ts +29 -0
  93. package/dist/providers/sentry/adapter.d.ts.map +1 -0
  94. package/dist/providers/sentry/adapter.js +154 -0
  95. package/dist/providers/sentry/adapter.js.map +1 -0
  96. package/dist/providers/sentry/index.d.ts +3 -0
  97. package/dist/providers/sentry/index.d.ts.map +1 -0
  98. package/dist/providers/sentry/index.js +3 -0
  99. package/dist/providers/sentry/index.js.map +1 -0
  100. package/dist/providers/sentry/pagination.d.ts +17 -0
  101. package/dist/providers/sentry/pagination.d.ts.map +1 -0
  102. package/dist/providers/sentry/pagination.js +54 -0
  103. package/dist/providers/sentry/pagination.js.map +1 -0
  104. package/dist/public.d.ts +14 -2
  105. package/dist/public.d.ts.map +1 -1
  106. package/dist/public.js +10 -2
  107. package/dist/public.js.map +1 -1
  108. package/dist/schema/monitor.d.ts +14 -1
  109. package/dist/schema/monitor.d.ts.map +1 -1
  110. package/dist/schema/monitor.js +24 -0
  111. package/dist/schema/monitor.js.map +1 -1
  112. package/dist/services/service-client.d.ts +8 -1
  113. package/dist/services/service-client.d.ts.map +1 -1
  114. package/dist/services/service-client.js +87 -0
  115. package/dist/services/service-client.js.map +1 -1
  116. package/dist/upi/index.d.ts +43 -0
  117. package/dist/upi/index.d.ts.map +1 -0
  118. package/dist/upi/index.js +57 -0
  119. package/dist/upi/index.js.map +1 -0
  120. package/package.json +9 -2
package/README.md CHANGED
@@ -4,47 +4,58 @@
4
4
 
5
5
  **Integration Reliability SDK**
6
6
 
7
- *One interface. Every provider. Built for production.*
7
+ [![npm](https://img.shields.io/npm/v/meridianjs?color=0070f3)](https://www.npmjs.com/package/meridianjs)
8
+ [![version](https://img.shields.io/badge/version-0.2.5-blue)](CHANGELOG.md)
9
+ [![tests](https://img.shields.io/badge/tests-1602%20passing-brightgreen)](https://vitest.dev)
10
+ [![adapters](https://img.shields.io/badge/adapters-39-blueviolet)](#providers)
11
+ [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
8
12
 
9
- [![npm version](https://img.shields.io/npm/v/meridianjs?color=0070f3&label=npm)](https://www.npmjs.com/package/meridianjs)
10
- [![npm downloads](https://img.shields.io/npm/dm/meridianjs?color=0070f3)](https://www.npmjs.com/package/meridianjs)
11
- [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
12
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
13
- [![Version](https://img.shields.io/badge/version-0.2.4-blue)](CHANGELOG.md)
14
- [![Tests](https://img.shields.io/badge/tests-1568%20passing-brightgreen)](https://vitest.dev)
15
- [![Adapters](https://img.shields.io/badge/adapters-39-blueviolet)](#provider-status-matrix)
13
+ One interface for 39 APIs. Automatic failover. Built-in observability. Zero runtime dependencies.
16
14
 
17
15
  </div>
18
16
 
17
+ ```bash
18
+ npm install meridianjs
19
+ ```
20
+
19
21
  ---
20
22
 
21
- Your application integrates with Stripe, OpenAI, Razorpay, Twilio — and a dozen more.
23
+ ## Why Meridian Exists
22
24
 
23
- Each one fails differently. Each one changes silently. Each one has a different error shape, pagination strategy, and rate limit header.
25
+ Every integration team rewrites the same things.
24
26
 
25
- **Meridian sits between your application and every provider.**
27
+ Retries. Pagination. Rate limit parsing. Error normalization. Failover logic.
26
28
 
27
- It normalizes the differences, absorbs the failures, and makes providers swappable without touching your application code.
29
+ They write it once for Stripe. Then again for Razorpay. Then again for OpenAI. The code is never shared because every provider has a different shape.
28
30
 
29
- ```
30
- Your Application
31
-
32
-
33
- Meridian
34
- ┌────┼─────┐
35
- ▼ ▼ ▼
36
- Stripe OpenAI Razorpay ··· 39 providers
37
- ```
31
+ Meridian provides a single reliability layer between your application and third-party APIs. Write your integration once. Every provider gets the same retry behavior, the same error format, the same pagination interface, the same circuit breaker, the same trace data.
32
+
33
+ The two features that make it more than a wrapper:
38
34
 
39
- When OpenAI goes down, Meridian routes to Anthropic. When Stripe rate-limits you, Meridian backs off and retries. When a provider silently renames a field, Meridian tells you before it breaks production.
35
+ **Service abstraction** your application calls `service("payments")`, not `provider("stripe")`. Meridian decides which provider handles the request. Your application is never coupled to a specific vendor.
40
36
 
41
- Zero runtime dependencies. Node.js 18+.
37
+ **Schema drift detection** — providers silently change their API responses. Meridian detects the change before it reaches production.
42
38
 
43
39
  ---
44
40
 
45
- ## The Killer Feature: Providers Become Replaceable
41
+ ## Architecture
42
+
43
+ ```mermaid
44
+ graph TD
45
+ App[Your Application] --> M[Meridian]
46
+ M --> P[Policy Engine]
47
+ P --> CB[Circuit Breaker]
48
+ CB --> RL[Rate Limiter]
49
+ RL --> RT[Retry]
50
+ RT --> S[Stripe]
51
+ RT --> O[OpenAI]
52
+ RT --> R[Razorpay]
53
+ RT --> More[···36 more]
54
+ ```
55
+
56
+ ---
46
57
 
47
- Your application stops naming vendors. Meridian routes, fails over, and recovers — automatically.
58
+ ## Quick Start
48
59
 
49
60
  ```typescript
50
61
  import { Meridian } from "meridianjs";
@@ -52,604 +63,218 @@ import { Meridian } from "meridianjs";
52
63
  const meridian = await Meridian.create({
53
64
  localUnsafe: true,
54
65
  providers: {
55
- openai: { auth: { apiKey: process.env.OPENAI_API_KEY } },
56
- anthropic: { auth: { apiKey: process.env.ANTHROPIC_API_KEY } },
57
- gemini: { auth: { apiKey: process.env.GEMINI_API_KEY } },
58
- },
59
- services: {
60
- // Your app calls "llm". It never touches "openai" or "anthropic".
61
- llm: {
62
- providers: ["openai", "anthropic", "gemini"],
63
- strategy: "failover",
64
- },
66
+ stripe: { auth: { apiKey: process.env.STRIPE_KEY } },
67
+ openai: { auth: { apiKey: process.env.OPENAI_KEY } },
65
68
  },
66
69
  });
67
70
 
68
- // OpenAI outage? Anthropic takes over. No code change. No redeployment.
69
- const result = await meridian.service("llm")!.post("/v1/chat/completions", {
70
- body: { messages: [{ role: "user", content: "Hello" }] },
71
- });
71
+ const { data, meta } = await meridian.provider("stripe")!.get("/v1/customers");
72
72
 
73
- console.log(result.meta.provider); // "openai" or "anthropic" — whoever answered
74
- console.log(result.meta.trace.retries); // 0, 1, 2 — what it took to get a response
75
- console.log(result.meta.trace.latency); // ms end-to-end
73
+ meta.rateLimit.remaining // always normalized
74
+ meta.pagination?.hasNext // always normalized
75
+ meta.trace.latency // ms, always present
76
+ meta.trace.retries // how many retries
77
+ meta.trace.circuitBreaker // CLOSED | OPEN | HALF_OPEN
76
78
  ```
77
79
 
78
- This works across payments, messaging, KYC, logistics — any category where multiple providers exist.
79
-
80
80
  ---
81
81
 
82
- ## Install
83
-
84
- ```bash
85
- npm install meridianjs
86
- ```
87
-
88
- ---
82
+ ## What Meridian Does
89
83
 
90
- ## What Meridian Owns
91
-
92
- | Problem | Without Meridian | With Meridian |
84
+ | | Without | With |
93
85
  |---|---|---|
94
- | Error handling | Different shape per provider | `MeridianError` — always `category`, `retryable`, `retryAfter` |
95
- | Rate limits | Parse headers manually per provider | `meta.rateLimit` — always normalized |
96
- | Pagination | Different cursor/offset/link per provider | `meta.pagination` — always normalized |
97
- | Retries | Roll your own | Exponential backoff with jitter, idempotency-safe |
98
- | Circuit breaking | Roll your own | Automatic, per-provider |
99
- | Provider outage | Your app breaks | Automatic failover to next provider |
100
- | Request tracing | Nothing | `meta.trace` — latency, retries, circuit state |
101
- | API drift | Silent production breaks | `meridian.schema.check()` — detect before it breaks |
86
+ | Errors | Different shape per provider | `MeridianError` — always `category`, `retryable`, `retryAfter` |
87
+ | Rate limits | Parse per provider | `meta.rateLimit` — normalized |
88
+ | Pagination | cursor / offset / link per provider | `meta.pagination` — normalized |
89
+ | Retries | Manual | Exponential backoff, idempotency-safe |
90
+ | Circuit breaking | Manual | Automatic, per-provider |
91
+ | Provider outage | App breaks | Automatic failover |
92
+ | API drift | Silent breakage | `meridian.schema.check()` |
102
93
 
103
94
  ---
104
95
 
105
- ## Service Abstraction & Failover
96
+ ## Provider Failover
106
97
 
107
- Your application stops knowing which vendor it uses. If OpenAI goes down, Anthropic takes over. No code changes, no deployment.
98
+ Your app calls `"llm"`. It never touches `"openai"` or `"anthropic"` directly.
108
99
 
109
100
  ```typescript
110
101
  const meridian = await Meridian.create({
111
102
  localUnsafe: true,
112
103
  providers: {
113
- openai: { auth: { apiKey: process.env.OPENAI_API_KEY } },
114
- anthropic: { auth: { apiKey: process.env.ANTHROPIC_API_KEY } },
115
- gemini: { auth: { apiKey: process.env.GEMINI_API_KEY } },
104
+ openai: { auth: { apiKey: "..." } },
105
+ anthropic: { auth: { apiKey: "..." } },
106
+ gemini: { auth: { apiKey: "..." } },
116
107
  },
117
108
  services: {
118
- llm: {
119
- providers: ["openai", "anthropic", "gemini"],
120
- strategy: "failover", // try in order, advance on network/rate/provider errors
121
- },
122
- payments: {
123
- providers: ["stripe", "razorpay"],
124
- strategy: "highest-success-rate", // always routes to healthiest provider
125
- },
126
- cheapLlm: {
127
- providers: ["openai", "anthropic", "gemini"],
128
- strategy: "cheapest",
129
- costs: { openai: 0.03, anthropic: 0.01, gemini: 0.02 }, // per 1K tokens
130
- },
109
+ llm: { providers: ["openai", "anthropic", "gemini"], strategy: "failover" },
131
110
  },
132
111
  });
133
112
 
134
- // Your application never touches provider names again
135
- const result = await meridian.service("llm")!.post("/v1/chat/completions", {
136
- body: { model: "gpt-4o", messages: [{ role: "user", content: "Hello" }] },
137
- });
113
+ await meridian.service("llm")!.post("/v1/chat/completions", { body: { ... } });
138
114
  ```
139
115
 
140
- **Routing strategies:**
141
-
142
- | Strategy | Behaviour |
143
- |---|---|
144
- | `"failover"` | Try providers in order. Advance on `rate_limit`, `network`, or `provider` errors. |
145
- | `"round-robin"` | Distribute requests evenly across all providers. |
146
- | `"lowest-latency"` | Route to the fastest provider. Self-calibrates using EWMA over `meta.trace.latency`. |
147
- | `"cheapest"` | Route to the cheapest provider. Fails over in ascending cost order. |
148
- | `"highest-success-rate"` | Route to the provider with the best live success rate from `meridian.analytics()`. |
149
-
150
- ---
151
-
152
- ## Request Trace
153
-
154
- Every response carries operational telemetry — no configuration needed:
155
-
156
- ```typescript
157
- const result = await meridian.provider("stripe").get("/v1/customers");
158
-
159
- console.log(result.meta.trace);
160
- // {
161
- // retries: 2,
162
- // latency: 341, // ms end-to-end including retries
163
- // circuitBreaker: "CLOSED",
164
- // rateLimitRemaining: 91
165
- // }
116
+ ```mermaid
117
+ sequenceDiagram
118
+ App->>Meridian: service("llm").post(...)
119
+ Meridian->>OpenAI: attempt
120
+ OpenAI-->>Meridian: 503 outage
121
+ Meridian->>Anthropic: failover
122
+ Anthropic-->>Meridian: 200 OK
123
+ Meridian-->>App: result (meta.provider = "anthropic")
166
124
  ```
167
125
 
168
- ---
169
-
170
- ## Analytics & Health
126
+ **Routing strategies:** `failover` · `round-robin` · `lowest-latency` · `cheapest` · `highest-success-rate` · `weighted` · `geo`
171
127
 
172
128
  ```typescript
173
- // After some traffic has flowed through:
174
- console.log(meridian.analytics());
175
- // {
176
- // stripe: { requests: 12431, errors: 37, errorRate: "0.3%", avgLatency: 240, p95Latency: 480 },
177
- // razorpay: { requests: 8111, errors: 146, errorRate: "1.8%", avgLatency: 410, p95Latency: 820 },
178
- // openai: { requests: 4200, errors: 50, errorRate: "1.2%", avgLatency: 780, p95Latency: 1500 }
179
- // }
180
-
181
- console.log(meridian.health());
182
- // {
183
- // stripe: { status: "healthy", successRate: "99.7%", avgLatency: 240, circuitBreaker: "CLOSED" },
184
- // razorpay: { status: "degraded", successRate: "98.2%", avgLatency: 410, circuitBreaker: "CLOSED" },
185
- // openai: { status: "down", successRate: "88.1%", avgLatency: 780, circuitBreaker: "OPEN" }
186
- // }
187
- ```
129
+ // weighted: 70% Stripe, 30% Razorpay
130
+ payments: { providers: ["stripe", "razorpay"], strategy: "weighted", weights: { stripe: 70, razorpay: 30 } }
188
131
 
189
- Health thresholds: `>= 99%` healthy, `>= 95%` → degraded, `< 95%` → down. Circuit breaker state overrides: `OPEN` → down, `HALF_OPEN` → at least degraded.
132
+ // geo: route by region (MERIDIAN_REGION env var)
133
+ payments: { providers: ["razorpay", "stripe"], strategy: "geo", regions: { "ap-south-1": ["razorpay"], "us-east-1": ["stripe"] } }
134
+ ```
190
135
 
191
136
  ---
192
137
 
193
- ## Debug Recording & Replay
138
+ ## Observability
194
139
 
195
- Record production requests. Replay failures locally.
140
+ Every response includes a trace. No configuration needed.
196
141
 
197
142
  ```typescript
198
- meridian.debug.enable();
143
+ result.meta.trace
144
+ // { retries: 2, latency: 341, circuitBreaker: "CLOSED", rateLimitRemaining: 91 }
199
145
 
200
- // Run your application...
201
-
202
- const recordings = meridian.debug.recordings();
203
- // [
204
- // {
205
- // requestId: "abc-123",
206
- // provider: "stripe",
207
- // endpoint: "/v1/charges",
208
- // method: "POST",
209
- // statusCode: 200,
210
- // duration: 241,
211
- // trace: { retries: 0, latency: 241, circuitBreaker: "CLOSED", rateLimitRemaining: 99 },
212
- // options: { method: "POST", body: { amount: 1000, currency: "usd" } }
213
- // }
214
- // ]
215
-
216
- // Replay a specific request with identical options:
217
- const fresh = await meridian.replay("abc-123");
218
-
219
- meridian.debug.disable();
220
- meridian.debug.clear();
146
+ meridian.analytics()
147
+ // { stripe: { requests: 12431, errorRate: "0.3%", avgLatency: 240, p95Latency: 480 } }
148
+
149
+ meridian.health()
150
+ // { stripe: { status: "healthy", successRate: "99.7%", circuitBreaker: "CLOSED" } }
151
+
152
+ meridian.cost()
153
+ // { providers: { openai: { requests: 1243, costPerRequest: 0.03, estimatedSpend: 37.29 } },
154
+ // total: { requests: 1243, estimatedSpend: 37.29 }, since: "2026-06-05T...", currency: "USD" }
221
155
  ```
222
156
 
223
157
  ---
224
158
 
225
159
  ## Policy Engine
226
160
 
227
- Block requests before they leave your application. Enforce compliance rules at the SDK layer.
161
+ Runs before every request. No network round-trip on block.
228
162
 
229
163
  ```typescript
230
- import { Meridian, blockPII, allowedProviders, readOnly, customPolicy } from "meridianjs";
231
-
232
- const meridian = await Meridian.create({
233
- localUnsafe: true,
234
- providers: { openai: { auth: { apiKey: "..." } } },
235
- policies: [
236
- // Block PII (credit cards, SSNs, emails, Aadhaar, PAN) from reaching OpenAI
237
- blockPII(["openai"]),
238
-
239
- // Only allow these providers to be used
240
- allowedProviders(["openai", "stripe"]),
241
-
242
- // No write operations to GitHub in this service
243
- readOnly(["github"]),
244
-
245
- // Custom policy with your own logic
246
- customPolicy("require-tenant-id", (ctx) =>
247
- ctx.body && typeof ctx.body === "object" && "tenantId" in ctx.body
248
- ? { allow: true }
249
- : { allow: false, reason: "tenantId is required in all requests" }
250
- ),
251
- ],
252
- });
164
+ import { blockPII, redact, requireFields, denyCountries, allowedProviders, readOnly, customPolicy } from "meridianjs";
165
+
166
+ policies: [
167
+ blockPII(["openai"]), // blocks credit cards, SSNs, emails, Aadhaar, PAN
168
+ redact(["user.ssn", "card.number"]), // redacts fields in-place request still goes through
169
+ requireFields(["tenantId"]), // blocks if required fields missing
170
+ denyCountries(["KP", "IR"]), // blocks by ISO 3166-1 country code
171
+ allowedProviders(["openai", "stripe"]), // provider whitelist
172
+ readOnly(["github"]), // no writes
173
+ customPolicy("require-tenant", (ctx) =>
174
+ "tenantId" in (ctx.body as object)
175
+ ? { allow: true }
176
+ : { allow: false, reason: "tenantId required" }
177
+ ),
178
+ ]
253
179
  ```
254
180
 
255
- A blocked request throws `MeridianError` with `category: "validation"` containing the policy name and reason. Policies run before the request leaves the process — no network round-trip wasted.
256
-
257
- **Built-in policies:**
258
-
259
- | Function | Description |
260
- |---|---|
261
- | `blockPII(providers?)` | Detects and blocks credit cards, SSNs, emails, phone numbers, Aadhaar, PAN |
262
- | `allowedProviders(list)` | Only allow requests to the specified providers |
263
- | `blockedProviders(list)` | Block requests to the specified providers entirely |
264
- | `readOnly(providers?)` | Block POST, PUT, PATCH, DELETE |
265
- | `customPolicy(name, fn)` | Define any rule as a function |
266
-
267
181
  ---
268
182
 
269
- ## Multi-Provider Transactions
183
+ ## Transactions
270
184
 
271
- Coordinate operations across multiple providers. If any step fails, compensating rollbacks run automatically in reverse order.
185
+ Saga pattern. Failed steps trigger compensating rollbacks in reverse order.
272
186
 
273
187
  ```typescript
274
- import { Meridian } from "meridianjs";
275
-
276
- const stripe = meridian.provider("stripe")!;
277
- const sendgrid = meridian.provider("sendgrid")!;
278
- const hubspot = meridian.provider("hubspot")!;
279
-
280
- const result = await meridian.transaction([
188
+ await meridian.transaction([
281
189
  {
282
190
  name: "charge",
283
- execute: async () =>
284
- stripe.post("/v1/charges", { body: { amount: 2000, currency: "usd", source: "tok_visa" } }),
285
- rollback: async (r) =>
286
- stripe.post(`/v1/charges/${(r.data as any).id}/refund`),
191
+ execute: () => stripe.post("/v1/charges", { body: { amount: 2000 } }),
192
+ rollback: (r) => stripe.post(`/v1/charges/${r.data.id}/refund`),
287
193
  },
288
194
  {
289
195
  name: "email",
290
- execute: async () =>
291
- sendgrid.post("/v3/mail/send", { body: { to: "user@example.com", subject: "Receipt" } }),
292
- // no rollback — can't unsend an email
293
- },
294
- {
295
- name: "crm",
296
- execute: async () =>
297
- hubspot.post("/crm/v3/objects/contacts", { body: { properties: { email: "user@example.com" } } }),
298
- rollback: async (r) =>
299
- hubspot.delete(`/crm/v3/objects/contacts/${(r.data as any).id}`),
196
+ execute: () => sendgrid.post("/v3/mail/send", { body: { ... } }),
300
197
  },
301
198
  ]);
302
-
303
- // { succeeded: ["charge", "email", "crm"], rolledBack: [], results: {...} }
304
- ```
305
-
306
- If `crm` fails: charge is refunded, `email` has no rollback (skipped), and `TransactionError` is thrown with `failed`, `succeeded`, `rolledBack`, `rollbackErrors`, and partial `results`.
307
-
308
- ---
309
-
310
- ## Schema Drift Detection
311
-
312
- Snapshot API response schemas and get alerted when providers silently rename or remove fields.
313
-
314
- ```typescript
315
- // Baseline today's response:
316
- const customers = await meridian.provider("stripe")!.get("/v1/customers");
317
- await meridian.schema.snapshot("stripe", "/v1/customers", customers.data);
318
-
319
- // Run again tomorrow (or in CI):
320
- const latest = await meridian.provider("stripe")!.get("/v1/customers");
321
- const drifts = await meridian.schema.check("stripe", "/v1/customers", latest.data);
322
-
323
- if (drifts.length > 0) {
324
- console.error("Stripe API drift detected:");
325
- for (const d of drifts) {
326
- console.error(` ${d.severity} ${d.type}: field "${d.field}" changed from ${d.oldValue} → ${d.newValue}`);
327
- }
328
- }
329
- // ERROR FIELD_REMOVED: field "customer_name" changed from "string" → undefined
330
- // WARNING REQUIRED_REMOVED: field "name" changed from true → false
331
199
  ```
332
200
 
333
- Schemas are stored by default in `.meridian/schemas/` (file system). Configure a custom `SchemaStorage` for cloud storage or databases.
334
-
335
- ---
336
-
337
- ## Provider Capability Registry
338
-
339
- Discover what each provider supports. Build capability-driven routing.
340
-
341
- ```typescript
342
- meridian.providers();
343
- // [
344
- // { name: "openai", capabilities: ["chat", "completions", "embeddings", "streaming", "vision", ...] },
345
- // { name: "stripe", capabilities: ["payments", "subscriptions", "refunds", "invoices", ...] },
346
- // { name: "razorpay", capabilities: ["payments", "upi", "subscriptions", "refunds", ...] },
347
- // ]
348
-
349
- meridian.findProviders({ capability: "streaming" });
350
- // [{ name: "openai", ... }, { name: "anthropic", ... }, { name: "gemini", ... }, { name: "mistral", ... }]
351
-
352
- meridian.findProviders({ capability: "kyc" });
353
- // [{ name: "hyperverge", ... }, { name: "digio", ... }, { name: "karza", ... }, { name: "idfy", ... }]
354
-
355
- meridian.findProviders({ capability: "upi" });
356
- // [{ name: "razorpay", ... }, { name: "phonepe", ... }, { name: "cashfree", ... }, { name: "setu", ... }]
201
+ ```mermaid
202
+ flowchart LR
203
+ A[charge ✓] --> B[email ✗]
204
+ B -- failure --> C[rollback: charge]
205
+ C --> D[TransactionError]
357
206
  ```
358
207
 
359
- Custom adapters can declare additional capabilities via the optional `capabilities(): string[]` method.
360
-
361
208
  ---
362
209
 
363
- ## Adapter Generator
364
-
365
- Scaffold a new provider adapter in under a minute.
366
-
367
- ```bash
368
- # From a description:
369
- npx meridian generate --provider acme --base-url https://api.acme.com --auth bearer
370
-
371
- # From an OpenAPI 3.x spec:
372
- npx meridian generate --provider acme --openapi ./acme-openapi.json
373
- ```
374
-
375
- Generates four files in `src/providers/acme/`:
376
-
377
- | File | Contents |
378
- |---|---|
379
- | `adapter.ts` | Full working adapter with auth, error mapping, rate-limit header parsing |
380
- | `adapter.test.ts` | 8 tests that pass immediately |
381
- | `pagination.ts` | Cursor pagination stub with TODO comments |
382
- | `index.ts` | Barrel export |
383
-
384
- **Options:**
385
-
386
- | Flag | Description | Default |
387
- |---|---|---|
388
- | `--provider` | Provider name (required) | — |
389
- | `--openapi` | Path to OpenAPI 3.x JSON file | — |
390
- | `--base-url` | API base URL | `https://api.<name>.com` |
391
- | `--auth` | Auth type: `apiKey`, `bearer`, `basic`, `oauth2` | `apiKey` |
392
- | `--output` | Output directory | `src/providers/<name>/` |
393
-
394
- ---
395
-
396
- ## Error Handling
397
-
398
- Every provider error has the same shape — no more per-provider archaeology:
399
-
400
- ```typescript
401
- try {
402
- const result = await meridian.provider("stripe")!.post("/v1/charges", { body });
403
- } catch (err) {
404
- if (err instanceof MeridianError) {
405
- err.category; // "auth" | "rate_limit" | "validation" | "network" | "provider"
406
- err.code; // "AUTH_FAILED" | "RATE_LIMITED" | "BAD_REQUEST" | "UPSTREAM_5XX" | ...
407
- err.retryable; // boolean — safe to retry?
408
- err.retryAfter; // Date | undefined — when to retry
409
- err.provider; // "stripe"
410
- err.requestId; // for support tickets
411
- }
412
- }
413
- ```
414
-
415
- ---
416
-
417
- ## Circuit Breaker
418
-
419
- Each provider has its own circuit breaker. Opens after repeated failures, probes with a single request after a timeout, closes on success.
420
-
421
- ```typescript
422
- const status = meridian.getCircuitStatus("stripe");
423
- // {
424
- // state: "CLOSED", // "CLOSED" | "OPEN" | "HALF_OPEN"
425
- // failures: 0,
426
- // successes: 12,
427
- // lastFailure: null,
428
- // nextAttempt: null
429
- // }
430
- ```
431
-
432
- ---
433
-
434
- ## Pagination
435
-
436
- ```typescript
437
- for await (const page of meridian.provider("stripe")!.paginate("/v1/customers")) {
438
- console.log(page.meta.pagination);
439
- // { hasNext: true, cursor: "cu_next123", total: 4200 }
440
- for (const customer of page.data as Customer[]) {
441
- process(customer);
442
- }
443
- }
444
- ```
445
-
446
- Works identically for cursor, offset, and link-header pagination — the adapter handles the translation.
447
-
448
- ---
449
-
450
- ## Streaming
451
-
452
- ```typescript
453
- for await (const chunk of meridian.provider("openai")!.stream("/v1/chat/completions", {
454
- body: { model: "gpt-4o", messages: [{ role: "user", content: "Hello" }], stream: true },
455
- })) {
456
- process.stdout.write(chunk.data as string);
457
- }
458
- ```
459
-
460
- Supported: OpenAI, Anthropic, Gemini, Mistral, Cohere.
461
-
462
- ---
210
+ ## Schema Drift Detection
463
211
 
464
- ## Batch Requests
212
+ Snapshot a response. Check later. Get alerted when the provider changes their API silently.
465
213
 
466
214
  ```typescript
467
- const results = await meridian.provider("stripe")!.batch([
468
- { method: "GET", endpoint: "/v1/customers/cu_1" },
469
- { method: "GET", endpoint: "/v1/customers/cu_2" },
470
- { method: "GET", endpoint: "/v1/customers/cu_3" },
471
- ], 5); // max 5 concurrent
472
-
473
- // Results always in input order. Errors are MeridianError, never thrown.
474
- ```
215
+ await meridian.schema.snapshot("stripe", "/v1/customers", response.data);
475
216
 
476
- ---
217
+ const drifts = await meridian.schema.check("stripe", "/v1/customers", laterResponse.data);
218
+ // [{ type: "FIELD_REMOVED", field: "customer_name", severity: "ERROR" }]
477
219
 
478
- ## Webhook Verification
220
+ await meridian.schema.alert("stripe", "/v1/customers", newData, (drifts, provider, endpoint) => {
221
+ pagerDuty.trigger(`Schema drift on ${provider}${endpoint}`);
222
+ });
479
223
 
480
- ```typescript
481
- import { StripeAdapter } from "meridianjs";
482
-
483
- const adapter = new StripeAdapter();
484
- const valid = adapter.verifyWebhook(
485
- req.rawBody,
486
- req.headers["stripe-signature"],
487
- process.env.STRIPE_WEBHOOK_SECRET,
488
- );
224
+ const report = await meridian.schema.report("stripe");
225
+ // { provider: "stripe", endpoints: [{ endpoint: "/v1/customers", fieldCount: 12, version: "..." }] }
489
226
  ```
490
227
 
491
- Timing-safe HMAC verification available on Stripe, Razorpay, Cashfree, Braintree, Twilio, Adyen, and more.
492
-
493
228
  ---
494
229
 
495
- ## Configuration Reference
230
+ ## More Features
496
231
 
497
232
  ```typescript
498
- const meridian = await Meridian.create({
499
- // Required: either localUnsafe:true or stateStorage
500
- localUnsafe: true, // dev only warns on startup
501
-
502
- // Per-provider configuration
503
- providers: {
504
- stripe: {
505
- auth: { apiKey: "sk_live_..." }, // or token, username/password, clientId/Secret
506
- baseUrl: "https://api.stripe.com", // override base URL
507
- retry: { maxRetries: 3, baseDelay: 1000, maxDelay: 30000, jitter: true },
508
- circuitBreaker: { failureThreshold: 5, timeout: 60000, errorThresholdPercentage: 50 },
509
- rateLimit: { tokensPerSecond: 10, maxTokens: 100, adaptiveBackoff: true },
510
- },
511
- },
512
-
513
- // Service abstraction — group providers behind a logical name
514
- services: {
515
- llm: { providers: ["openai", "anthropic"], strategy: "failover" },
516
- },
517
-
518
- // Default settings applied to all providers
519
- defaults: {
520
- timeout: 30000,
521
- retry: { maxRetries: 2 },
522
- circuitBreaker: { failureThreshold: 5 },
523
- },
524
-
525
- // Policy engine
526
- policies: [blockPII(["openai"]), readOnly(["github"])],
527
-
528
- // Observability (any number of adapters)
529
- observability: [new ConsoleObservability(), new PrometheusObservability()],
530
-
531
- // Distributed state (required for multi-instance deployments)
532
- mode: "distributed",
533
- stateStorage: new RedisStateStorage(redisClient),
534
-
535
- // Schema validation & drift detection
536
- schemaValidation: {
537
- enabled: true,
538
- storage: new FileSystemSchemaStorage(".meridian/schemas"),
539
- onDrift: (drifts) => alerting.notify(drifts),
540
- },
233
+ // Debug & replay
234
+ meridian.debug.enable();
235
+ await meridian.replay(requestId); // re-runs with exact original options
541
236
 
542
- // Compliance
543
- compliance: {
544
- piiRedaction: true, // redacts Aadhaar, PAN, bank accounts from logs
545
- indiaMode: true, // DPDPA-compliant redaction
546
- auditLog: true,
547
- },
237
+ // Capability registry
238
+ meridian.findProviders({ capability: "streaming" });
239
+ // [{ name: "openai" }, { name: "anthropic" }, { name: "gemini" }, ...]
548
240
 
549
- // Idempotency
550
- idempotency: {
551
- defaultLevel: IdempotencyLevel.SAFE,
552
- autoGenerateKeys: true,
553
- },
554
- });
555
- ```
241
+ // Adapter generator
242
+ // npx meridian generate --provider acme --openapi ./acme.json
243
+ // → adapter.ts adapter.test.ts pagination.ts index.ts (8 tests pass immediately)
556
244
 
557
- ---
245
+ // Pagination
246
+ for await (const page of meridian.provider("stripe")!.paginate("/v1/customers")) { ... }
558
247
 
559
- ## Provider Status Matrix
248
+ // Streaming (OpenAI, Anthropic, Gemini, Mistral, Cohere)
249
+ for await (const chunk of meridian.provider("openai")!.stream("/v1/chat/completions", { body })) { ... }
560
250
 
561
- Every adapter passes a 19-invariant contract test suite before being listed. Run the suite yourself:
251
+ // Batch
252
+ await meridian.provider("stripe")!.batch([{ method: "GET", endpoint: "/v1/customers/1" }, ...], 5);
562
253
 
563
- ```bash
564
- npm run test:contracts # all 39 adapters
565
- npm run test:contracts stripe # single provider
254
+ // Webhook verification
255
+ new StripeAdapter().verifyWebhook(req.rawBody, req.headers["stripe-signature"], secret);
566
256
  ```
567
257
 
568
- | Provider | Category | Contract | Status |
569
- |:---|:---|:---:|:---:|
570
- | **Adyen** | Payments | 19/19 | ✅ |
571
- | **Anthropic** | AI / LLM | 19/19 | ✅ |
572
- | **Apollo.io** | CRM / Sales | 19/19 | ✅ |
573
- | **Auth0** | Auth / Identity | 19/19 | ✅ |
574
- | **Braintree** | Payments | 19/19 | ✅ |
575
- | **Cashfree** | Payments | 19/19 | ✅ |
576
- | **Checkout.com** | Payments | 19/19 | ✅ |
577
- | **Cleartax** | Tax / Compliance | 19/19 | ✅ |
578
- | **Cohere** | AI / LLM | 19/19 | ✅ |
579
- | **Decentro** | Banking / Fintech | 19/19 | ✅ |
580
- | **Delhivery** | Logistics | 19/19 | ✅ |
581
- | **Digio** | eSign / KYC | 19/19 | ✅ |
582
- | **Exotel** | Communications | 19/19 | ✅ |
583
- | **Google Gemini** | AI / LLM | 19/19 | ✅ |
584
- | **GitHub** | Developer Tools | 19/19 | ✅ |
585
- | **Gupshup** | Communications | 19/19 | ✅ |
586
- | **HubSpot** | CRM | 19/19 | ✅ |
587
- | **HyperVerge** | KYC / Identity | 19/19 | ✅ |
588
- | **IDfy** | KYC / Identity | 19/19 | ✅ |
589
- | **Juspay** | Payments | 19/19 | ✅ |
590
- | **Karza** | KYC / Verification | 19/19 | ✅ |
591
- | **Klarna** | Payments | 19/19 | ✅ |
592
- | **Mailgun** | Communications | 19/19 | ✅ |
593
- | **MapMyIndia** | Maps / Geo | 19/19 | ✅ |
594
- | **Mistral** | AI / LLM | 19/19 | ✅ |
595
- | **Mollie** | Payments | 19/19 | ✅ |
596
- | **MSG91** | Communications | 19/19 | ✅ |
597
- | **OpenAI** | AI / LLM | 19/19 | ✅ |
598
- | **PayU** | Payments | 19/19 | ✅ |
599
- | **Perfios** | Financial Data | 19/19 | ✅ |
600
- | **PhonePe** | Payments | 19/19 | ✅ |
601
- | **Razorpay** | Payments | 19/19 | ✅ |
602
- | **SendGrid** | Communications | 19/19 | ✅ |
603
- | **Setu** | Banking / UPI | 19/19 | ✅ |
604
- | **Shiprocket** | Logistics | 19/19 | ✅ |
605
- | **Stripe** | Payments | 19/19 | ✅ |
606
- | **Supabase** | Database / Auth | 19/19 | ✅ |
607
- | **Twilio** | Communications | 19/19 | ✅ |
608
- | **Vonage** | Communications | 19/19 | ✅ |
609
-
610
258
  ---
611
259
 
612
- ## Adding a Provider
260
+ ## Providers
613
261
 
614
- Fastest path: use the generator.
262
+ 39 adapters, each passing 19 contract invariants. `npm run test:contracts stripe`
615
263
 
616
- ```bash
617
- npx meridian generate --provider myprovider --openapi ./myprovider-openapi.json
618
- ```
619
-
620
- Manual path: implement `ProviderAdapter` and register it:
621
-
622
- ```typescript
623
- import type { ProviderAdapter } from "meridianjs";
624
-
625
- class MyAdapter implements ProviderAdapter {
626
- buildRequest(input) { ... }
627
- parseResponse(raw) { ... }
628
- parseError(raw) { ... }
629
- authStrategy(cfg) { ... }
630
- rateLimitPolicy(h) { ... }
631
- paginationStrategy(){ ... }
632
- getIdempotencyConfig() { ... }
633
- }
634
-
635
- await meridian.registerProvider("myprovider", new MyAdapter(), {
636
- auth: { apiKey: process.env.MY_API_KEY },
637
- });
638
- ```
264
+ | Category | Providers |
265
+ |---|---|
266
+ | **Payments** | Stripe · Razorpay · Cashfree · PayU · Juspay · Braintree · Adyen · Klarna · Mollie · PhonePe · Checkout.com |
267
+ | **AI / LLM** | OpenAI · Anthropic · Gemini · Cohere · Mistral |
268
+ | **Communications** | Twilio · SendGrid · Mailgun · Vonage · MSG91 · Exotel · Gupshup |
269
+ | **KYC / Identity** | HyperVerge · Digio · Karza · IDfy · Setu · Decentro · Perfios |
270
+ | **Tools & Infra** | GitHub · HubSpot · Supabase · Auth0 · Apollo |
271
+ | **Logistics** | Shiprocket · Delhivery |
272
+ | **Other** | MapMyIndia · Cleartax |
639
273
 
640
274
  ---
641
275
 
642
276
  ## Contributing
643
277
 
644
- 1. Fork feature branchPR
645
- 2. Every new adapter requires: `adapter.ts`, `adapter.test.ts`, `pagination.ts`, `index.ts`
646
- 3. All existing contract tests must continue to pass: `npm test`
647
- 4. The adapter generator produces a correct starting point: `npx meridian generate --provider yourprovider`
648
-
649
- ---
650
-
651
- ## Links
278
+ New adapter: `npx meridian generate --provider name --openapi ./spec.json`implement TODOs`npm test`.
652
279
 
653
- - [Changelog](CHANGELOG.md)
654
- - [License: MIT](LICENSE.md)
655
- - [npm](https://www.npmjs.com/package/meridianjs)
280
+ [Changelog](CHANGELOG.md) · [License: MIT](LICENSE.md) · [npm](https://www.npmjs.com/package/meridianjs)