meridianjs 0.2.4 → 0.2.5
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 +42 -0
- package/README.md +155 -530
- package/dist/analytics/collector.d.ts +16 -0
- package/dist/analytics/collector.d.ts.map +1 -1
- package/dist/analytics/collector.js +20 -0
- package/dist/analytics/collector.js.map +1 -1
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +18 -1
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/types.d.ts +9 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/generator/templates.d.ts.map +1 -1
- package/dist/generator/templates.js +3 -1
- package/dist/generator/templates.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/policies/builtin.d.ts +16 -0
- package/dist/policies/builtin.d.ts.map +1 -1
- package/dist/policies/builtin.js +92 -0
- package/dist/policies/builtin.js.map +1 -1
- package/dist/policies/index.d.ts +1 -1
- package/dist/policies/index.d.ts.map +1 -1
- package/dist/policies/index.js +1 -1
- package/dist/policies/index.js.map +1 -1
- package/dist/public.d.ts +3 -1
- package/dist/public.d.ts.map +1 -1
- package/dist/public.js +1 -1
- package/dist/public.js.map +1 -1
- package/dist/schema/monitor.d.ts +14 -1
- package/dist/schema/monitor.d.ts.map +1 -1
- package/dist/schema/monitor.js +24 -0
- package/dist/schema/monitor.js.map +1 -1
- package/dist/services/service-client.d.ts +8 -1
- package/dist/services/service-client.d.ts.map +1 -1
- package/dist/services/service-client.js +87 -0
- package/dist/services/service-client.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -4,47 +4,58 @@
|
|
|
4
4
|
|
|
5
5
|
**Integration Reliability SDK**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/meridianjs)
|
|
8
|
+
[](CHANGELOG.md)
|
|
9
|
+
[](https://vitest.dev)
|
|
10
|
+
[](#providers)
|
|
11
|
+
[](LICENSE.md)
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
[](https://www.npmjs.com/package/meridianjs)
|
|
11
|
-
[](LICENSE.md)
|
|
12
|
-
[](https://www.typescriptlang.org/)
|
|
13
|
-
[](CHANGELOG.md)
|
|
14
|
-
[](https://vitest.dev)
|
|
15
|
-
[](#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
|
-
|
|
23
|
+
## Why Meridian Exists
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
Every integration team rewrites the same things.
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Retries. Pagination. Rate limit parsing. Error normalization. Failover logic.
|
|
26
28
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
##
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
npm install meridianjs
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
82
|
+
## What Meridian Does
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
| Problem | Without Meridian | With Meridian |
|
|
84
|
+
| | Without | With |
|
|
93
85
|
|---|---|---|
|
|
94
|
-
|
|
|
95
|
-
| Rate limits | Parse
|
|
96
|
-
| Pagination |
|
|
97
|
-
| Retries |
|
|
98
|
-
| Circuit breaking |
|
|
99
|
-
| Provider outage |
|
|
100
|
-
|
|
|
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
|
-
##
|
|
96
|
+
## Provider Failover
|
|
106
97
|
|
|
107
|
-
Your
|
|
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:
|
|
114
|
-
anthropic: { auth: { apiKey:
|
|
115
|
-
gemini: { auth: { apiKey:
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
//
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
138
|
+
## Observability
|
|
194
139
|
|
|
195
|
-
|
|
140
|
+
Every response includes a trace. No configuration needed.
|
|
196
141
|
|
|
197
142
|
```typescript
|
|
198
|
-
|
|
143
|
+
result.meta.trace
|
|
144
|
+
// { retries: 2, latency: 341, circuitBreaker: "CLOSED", rateLimitRemaining: 91 }
|
|
199
145
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
//
|
|
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
|
-
|
|
161
|
+
Runs before every request. No network round-trip on block.
|
|
228
162
|
|
|
229
163
|
```typescript
|
|
230
|
-
import {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
##
|
|
183
|
+
## Transactions
|
|
270
184
|
|
|
271
|
-
|
|
185
|
+
Saga pattern. Failed steps trigger compensating rollbacks in reverse order.
|
|
272
186
|
|
|
273
187
|
```typescript
|
|
274
|
-
|
|
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:
|
|
284
|
-
|
|
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:
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
212
|
+
Snapshot a response. Check later. Get alerted when the provider changes their API silently.
|
|
465
213
|
|
|
466
214
|
```typescript
|
|
467
|
-
|
|
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
|
-
|
|
220
|
+
await meridian.schema.alert("stripe", "/v1/customers", newData, (drifts, provider, endpoint) => {
|
|
221
|
+
pagerDuty.trigger(`Schema drift on ${provider}${endpoint}`);
|
|
222
|
+
});
|
|
479
223
|
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
##
|
|
230
|
+
## More Features
|
|
496
231
|
|
|
497
232
|
```typescript
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
248
|
+
// Streaming (OpenAI, Anthropic, Gemini, Mistral, Cohere)
|
|
249
|
+
for await (const chunk of meridian.provider("openai")!.stream("/v1/chat/completions", { body })) { ... }
|
|
560
250
|
|
|
561
|
-
|
|
251
|
+
// Batch
|
|
252
|
+
await meridian.provider("stripe")!.batch([{ method: "GET", endpoint: "/v1/customers/1" }, ...], 5);
|
|
562
253
|
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
##
|
|
260
|
+
## Providers
|
|
613
261
|
|
|
614
|
-
|
|
262
|
+
39 adapters, each passing 19 contract invariants. `npm run test:contracts stripe`
|
|
615
263
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|