meridianjs 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -160
- package/README.md +560 -93
- package/dist/analytics/collector.d.ts +28 -0
- package/dist/analytics/collector.d.ts.map +1 -0
- package/dist/analytics/collector.js +62 -0
- package/dist/analytics/collector.js.map +1 -0
- package/dist/analytics/index.d.ts +3 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +2 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/capabilities/index.d.ts +3 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +2 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/registry.d.ts +6 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +61 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/core/pipeline.d.ts +3 -1
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +25 -0
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/types.d.ts +37 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -2
- package/dist/core/types.js.map +1 -1
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/index.js +2 -0
- package/dist/debug/index.js.map +1 -0
- package/dist/debug/recorder.d.ts +32 -0
- package/dist/debug/recorder.d.ts.map +1 -0
- package/dist/debug/recorder.js +76 -0
- package/dist/debug/recorder.js.map +1 -0
- package/dist/generator/cli.d.ts +20 -0
- package/dist/generator/cli.d.ts.map +1 -0
- package/dist/generator/cli.js +56 -0
- package/dist/generator/cli.js.map +1 -0
- package/dist/generator/index.d.ts +9 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +49 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/openapi.d.ts +13 -0
- package/dist/generator/openapi.d.ts.map +1 -0
- package/dist/generator/openapi.js +49 -0
- package/dist/generator/openapi.js.map +1 -0
- package/dist/generator/templates.d.ts +16 -0
- package/dist/generator/templates.d.ts.map +1 -0
- package/dist/generator/templates.js +290 -0
- package/dist/generator/templates.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -1
- package/dist/policies/builtin.d.ts +24 -0
- package/dist/policies/builtin.d.ts.map +1 -0
- package/dist/policies/builtin.js +94 -0
- package/dist/policies/builtin.js.map +1 -0
- package/dist/policies/index.d.ts +2 -0
- package/dist/policies/index.d.ts.map +1 -0
- package/dist/policies/index.js +2 -0
- package/dist/policies/index.js.map +1 -0
- package/dist/public.d.ts +15 -0
- package/dist/public.d.ts.map +1 -1
- package/dist/public.js +15 -0
- package/dist/public.js.map +1 -1
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/monitor.d.ts +10 -0
- package/dist/schema/monitor.d.ts.map +1 -0
- package/dist/schema/monitor.js +46 -0
- package/dist/schema/monitor.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/service-client.d.ts +34 -0
- package/dist/services/service-client.d.ts.map +1 -0
- package/dist/services/service-client.js +156 -0
- package/dist/services/service-client.js.map +1 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.d.ts.map +1 -0
- package/dist/transactions/index.js +2 -0
- package/dist/transactions/index.js.map +1 -0
- package/dist/transactions/saga.d.ts +22 -0
- package/dist/transactions/saga.d.ts.map +1 -0
- package/dist/transactions/saga.js +58 -0
- package/dist/transactions/saga.js.map +1 -0
- package/dist/validation/drift-detector.d.ts.map +1 -1
- package/dist/validation/drift-detector.js +17 -8
- package/dist/validation/drift-detector.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,147 +2,477 @@
|
|
|
2
2
|
|
|
3
3
|
# Meridian
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Integration Reliability SDK**
|
|
6
|
+
|
|
7
|
+
*One interface. Every provider. Built for production.*
|
|
6
8
|
|
|
7
9
|
[](https://www.npmjs.com/package/meridianjs)
|
|
8
10
|
[](https://www.npmjs.com/package/meridianjs)
|
|
9
11
|
[](LICENSE.md)
|
|
10
12
|
[](https://www.typescriptlang.org/)
|
|
11
|
-
[](CHANGELOG.md)
|
|
14
|
+
[](https://vitest.dev)
|
|
12
15
|
[](#provider-status-matrix)
|
|
13
16
|
|
|
14
|
-
A TypeScript-first SDK that gives every third-party API the same interface — normalized errors, rate limits, pagination, and response shapes, regardless of provider.
|
|
15
|
-
|
|
16
17
|
</div>
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
21
|
+
Your application integrates with Stripe, OpenAI, Razorpay, Twilio — and a dozen more.
|
|
22
|
+
|
|
23
|
+
Each one fails differently. Each one changes silently. Each one has a different error shape, pagination strategy, and rate limit header.
|
|
24
|
+
|
|
25
|
+
**Meridian sits between your application and every provider.**
|
|
26
|
+
|
|
27
|
+
It normalizes the differences, absorbs the failures, and makes providers swappable without touching your application code.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Your Application
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
Meridian
|
|
34
|
+
┌────┼─────┐
|
|
35
|
+
▼ ▼ ▼
|
|
36
|
+
Stripe OpenAI Razorpay ··· 39 providers
|
|
37
|
+
```
|
|
38
|
+
|
|
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.
|
|
40
|
+
|
|
41
|
+
Zero runtime dependencies. Node.js 18+.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## The Killer Feature: Providers Become Replaceable
|
|
46
|
+
|
|
47
|
+
Your application stops naming vendors. Meridian routes, fails over, and recovers — automatically.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Meridian } from "meridianjs";
|
|
51
|
+
|
|
52
|
+
const meridian = await Meridian.create({
|
|
53
|
+
localUnsafe: true,
|
|
54
|
+
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
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
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
|
+
});
|
|
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
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This works across payments, messaging, KYC, logistics — any category where multiple providers exist.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
20
82
|
## Install
|
|
21
83
|
|
|
22
84
|
```bash
|
|
23
85
|
npm install meridianjs
|
|
24
86
|
```
|
|
25
87
|
|
|
26
|
-
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What Meridian Owns
|
|
91
|
+
|
|
92
|
+
| Problem | Without Meridian | With Meridian |
|
|
93
|
+
|---|---|---|
|
|
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 |
|
|
27
102
|
|
|
28
103
|
---
|
|
29
104
|
|
|
30
|
-
##
|
|
105
|
+
## Service Abstraction & Failover
|
|
31
106
|
|
|
32
|
-
|
|
33
|
-
import { Meridian } from "meridianjs";
|
|
107
|
+
Your application stops knowing which vendor it uses. If OpenAI goes down, Anthropic takes over. No code changes, no deployment.
|
|
34
108
|
|
|
109
|
+
```typescript
|
|
35
110
|
const meridian = await Meridian.create({
|
|
111
|
+
localUnsafe: true,
|
|
36
112
|
providers: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 } },
|
|
116
|
+
},
|
|
117
|
+
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
|
|
43
130
|
},
|
|
44
131
|
},
|
|
45
132
|
});
|
|
46
133
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
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
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
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:
|
|
49
155
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
// }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Analytics & Health
|
|
171
|
+
|
|
172
|
+
```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
|
+
// }
|
|
53
187
|
```
|
|
54
188
|
|
|
55
|
-
|
|
189
|
+
Health thresholds: `>= 99%` → healthy, `>= 95%` → degraded, `< 95%` → down. Circuit breaker state overrides: `OPEN` → down, `HALF_OPEN` → at least degraded.
|
|
56
190
|
|
|
57
191
|
---
|
|
58
192
|
|
|
59
|
-
##
|
|
193
|
+
## Debug Recording & Replay
|
|
194
|
+
|
|
195
|
+
Record production requests. Replay failures locally.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
meridian.debug.enable();
|
|
199
|
+
|
|
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();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
60
224
|
|
|
61
|
-
|
|
225
|
+
## Policy Engine
|
|
226
|
+
|
|
227
|
+
Block requests before they leave your application. Enforce compliance rules at the SDK layer.
|
|
228
|
+
|
|
229
|
+
```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
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
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 |
|
|
62
260
|
|---|---|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
| **Pagination** | Unified `meta.pagination` across cursor, offset, and link-based strategies |
|
|
69
|
-
| **Webhook verification** | Timing-safe HMAC verification on every payment/comms adapter |
|
|
70
|
-
| **Streaming** | SSE streaming for OpenAI, Anthropic, Mistral, Cohere |
|
|
71
|
-
| **Batch requests** | Fan-out with concurrency control via `.batch()` |
|
|
72
|
-
| **India Compliance** | DPDPA-compliant PII redaction (Aadhaar, PAN, UPI VPA, bank accounts) |
|
|
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 |
|
|
73
266
|
|
|
74
267
|
---
|
|
75
268
|
|
|
76
|
-
## Provider
|
|
269
|
+
## Multi-Provider Transactions
|
|
270
|
+
|
|
271
|
+
Coordinate operations across multiple providers. If any step fails, compensating rollbacks run automatically in reverse order.
|
|
272
|
+
|
|
273
|
+
```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([
|
|
281
|
+
{
|
|
282
|
+
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`),
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
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}`),
|
|
300
|
+
},
|
|
301
|
+
]);
|
|
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
|
+
```
|
|
332
|
+
|
|
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", ... }]
|
|
77
351
|
|
|
78
|
-
|
|
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", ... }]
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Custom adapters can declare additional capabilities via the optional `capabilities(): string[]` method.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Adapter Generator
|
|
364
|
+
|
|
365
|
+
Scaffold a new provider adapter in under a minute.
|
|
79
366
|
|
|
80
367
|
```bash
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
373
|
```
|
|
84
374
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
| **HubSpot** | CRM | 19/19 Passed | ✅ Production-Ready |
|
|
104
|
-
| **HyperVerge** | KYC / Identity | 19/19 Passed | ✅ Production-Ready |
|
|
105
|
-
| **IDfy** | KYC / Identity | 19/19 Passed | ✅ Production-Ready |
|
|
106
|
-
| **Juspay** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
107
|
-
| **Karza** | KYC / Verification | 19/19 Passed | ✅ Production-Ready |
|
|
108
|
-
| **Klarna** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
109
|
-
| **Mailgun** | Communications | 19/19 Passed | ✅ Production-Ready |
|
|
110
|
-
| **MapMyIndia** | Maps / Geo | 19/19 Passed | ✅ Production-Ready |
|
|
111
|
-
| **Mistral** | AI / LLM | 19/19 Passed | ✅ Production-Ready |
|
|
112
|
-
| **Mollie** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
113
|
-
| **MSG91** | Communications | 19/19 Passed | ✅ Production-Ready |
|
|
114
|
-
| **OpenAI** | AI / LLM | 19/19 Passed | ✅ Production-Ready |
|
|
115
|
-
| **PayU** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
116
|
-
| **Perfios** | Financial Data | 19/19 Passed | ✅ Production-Ready |
|
|
117
|
-
| **PhonePe** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
118
|
-
| **Razorpay** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
119
|
-
| **SendGrid** | Communications | 19/19 Passed | ✅ Production-Ready |
|
|
120
|
-
| **Setu** | Banking / UPI | 19/19 Passed | ✅ Production-Ready |
|
|
121
|
-
| **Shiprocket** | Logistics | 19/19 Passed | ✅ Production-Ready |
|
|
122
|
-
| **Stripe** | Payments | 19/19 Passed | ✅ Production-Ready |
|
|
123
|
-
| **Supabase** | Database / Auth | 19/19 Passed | ✅ Production-Ready |
|
|
124
|
-
| **Twilio** | Communications | 19/19 Passed | ✅ Production-Ready |
|
|
125
|
-
| **Vonage** | Communications | 19/19 Passed | ✅ Production-Ready |
|
|
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>/` |
|
|
126
393
|
|
|
127
394
|
---
|
|
128
395
|
|
|
129
396
|
## Error Handling
|
|
130
397
|
|
|
131
|
-
Every error has the same shape — no more
|
|
398
|
+
Every provider error has the same shape — no more per-provider archaeology:
|
|
132
399
|
|
|
133
400
|
```typescript
|
|
134
401
|
try {
|
|
135
|
-
const result = await meridian.provider("stripe")
|
|
402
|
+
const result = await meridian.provider("stripe")!.post("/v1/charges", { body });
|
|
136
403
|
} catch (err) {
|
|
137
404
|
if (err instanceof MeridianError) {
|
|
138
405
|
err.category; // "auth" | "rate_limit" | "validation" | "network" | "provider"
|
|
139
|
-
err.
|
|
140
|
-
err.
|
|
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
|
|
141
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);
|
|
142
442
|
}
|
|
143
443
|
}
|
|
144
444
|
```
|
|
145
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
|
+
---
|
|
463
|
+
|
|
464
|
+
## Batch Requests
|
|
465
|
+
|
|
466
|
+
```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
|
+
```
|
|
475
|
+
|
|
146
476
|
---
|
|
147
477
|
|
|
148
478
|
## Webhook Verification
|
|
@@ -158,31 +488,168 @@ const valid = adapter.verifyWebhook(
|
|
|
158
488
|
);
|
|
159
489
|
```
|
|
160
490
|
|
|
161
|
-
|
|
491
|
+
Timing-safe HMAC verification available on Stripe, Razorpay, Cashfree, Braintree, Twilio, Adyen, and more.
|
|
162
492
|
|
|
163
493
|
---
|
|
164
494
|
|
|
165
|
-
##
|
|
495
|
+
## Configuration Reference
|
|
166
496
|
|
|
167
497
|
```typescript
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
},
|
|
541
|
+
|
|
542
|
+
// Compliance
|
|
543
|
+
compliance: {
|
|
544
|
+
piiRedaction: true, // redacts Aadhaar, PAN, bank accounts from logs
|
|
545
|
+
indiaMode: true, // DPDPA-compliant redaction
|
|
546
|
+
auditLog: true,
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
// Idempotency
|
|
550
|
+
idempotency: {
|
|
551
|
+
defaultLevel: IdempotencyLevel.SAFE,
|
|
552
|
+
autoGenerateKeys: true,
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
173
558
|
|
|
174
|
-
|
|
559
|
+
## Provider Status Matrix
|
|
560
|
+
|
|
561
|
+
Every adapter passes a 19-invariant contract test suite before being listed. Run the suite yourself:
|
|
562
|
+
|
|
563
|
+
```bash
|
|
564
|
+
npm run test:contracts # all 39 adapters
|
|
565
|
+
npm run test:contracts stripe # single provider
|
|
175
566
|
```
|
|
176
567
|
|
|
177
|
-
|
|
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
|
+
---
|
|
611
|
+
|
|
612
|
+
## Adding a Provider
|
|
613
|
+
|
|
614
|
+
Fastest path: use the generator.
|
|
615
|
+
|
|
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
|
+
```
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Contributing
|
|
178
643
|
|
|
179
|
-
|
|
644
|
+
1. Fork → feature branch → PR
|
|
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`
|
|
180
648
|
|
|
181
649
|
---
|
|
182
650
|
|
|
183
651
|
## Links
|
|
184
652
|
|
|
185
|
-
- [GitHub](https://github.com/Raghaverma/Meridianjs)
|
|
186
653
|
- [Changelog](CHANGELOG.md)
|
|
187
|
-
- [Roadmap](ROADMAP.md)
|
|
188
654
|
- [License: MIT](LICENSE.md)
|
|
655
|
+
- [npm](https://www.npmjs.com/package/meridianjs)
|