samva 0.0.1 → 0.2.0
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/AGENTS.md +13 -0
- package/LICENSE +21 -0
- package/README.md +392 -5
- package/dist/categories-CsUa8ojo.js +143 -0
- package/dist/client/client/client.gen.d.ts +2 -0
- package/dist/client/client/index.d.ts +10 -0
- package/dist/client/client/types.gen.d.ts +120 -0
- package/dist/client/client/utils.gen.d.ts +37 -0
- package/dist/client/client.gen.d.ts +12 -0
- package/dist/client/core/auth.gen.d.ts +25 -0
- package/dist/client/core/bodySerializer.gen.d.ts +25 -0
- package/dist/client/core/params.gen.d.ts +43 -0
- package/dist/client/core/pathSerializer.gen.d.ts +33 -0
- package/dist/client/core/queryKeySerializer.gen.d.ts +18 -0
- package/dist/client/core/serverSentEvents.gen.d.ts +71 -0
- package/dist/client/core/types.gen.d.ts +83 -0
- package/dist/client/core/utils.gen.d.ts +19 -0
- package/dist/client/sdk.gen.d.ts +1558 -0
- package/dist/client/types.gen.d.ts +7917 -0
- package/dist/client-BqkU-sFv.js +6049 -0
- package/dist/effect/api-keys.d.ts +190 -0
- package/dist/effect/api-keys.js +27 -0
- package/dist/effect/campaigns.d.ts +2055 -0
- package/dist/effect/campaigns.js +33 -0
- package/dist/effect/categories.d.ts +65 -0
- package/dist/effect/categories.js +2 -0
- package/dist/effect/client.d.ts +9 -0
- package/dist/effect/client.js +13 -0
- package/dist/effect/contacts.d.ts +1278 -0
- package/dist/effect/contacts.js +39 -0
- package/dist/effect/conversations.d.ts +1604 -0
- package/dist/effect/conversations.js +23 -0
- package/dist/effect/design.d.ts +209 -0
- package/dist/effect/design.js +13 -0
- package/dist/effect/email.d.ts +1022 -0
- package/dist/effect/email.js +111 -0
- package/dist/effect/generated-metadata.d.ts +54 -0
- package/dist/effect/generated.d.ts +32156 -0
- package/dist/effect/index.d.ts +36 -0
- package/dist/effect/index.js +17 -0
- package/dist/effect/internal/client.d.ts +35 -0
- package/dist/effect/internal/operation.d.ts +466 -0
- package/dist/effect/messages.d.ts +1047 -0
- package/dist/effect/messages.js +23 -0
- package/dist/effect/operations.d.ts +97 -0
- package/dist/effect/operations.js +15 -0
- package/dist/effect/organizations.d.ts +559 -0
- package/dist/effect/organizations.js +21 -0
- package/dist/effect/pagination.d.ts +37 -0
- package/dist/effect/retry.d.ts +60 -0
- package/dist/effect/retry.js +112 -0
- package/dist/effect/runtime.d.ts +7 -0
- package/dist/effect/scheduled-messages.d.ts +1009 -0
- package/dist/effect/scheduled-messages.js +17 -0
- package/dist/effect/templates.d.ts +2591 -0
- package/dist/effect/templates.js +66 -0
- package/dist/effect/webhooks.d.ts +228 -0
- package/dist/effect/webhooks.js +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/normalize-LkWl4HUF.js +82 -0
- package/dist/normalize.d.ts +87 -0
- package/dist/operation-Ccqh_yxb.js +596 -0
- package/dist/promises/errors.gen.d.ts +179 -0
- package/dist/promises/index.d.ts +153 -0
- package/dist/promises/index.js +2 -0
- package/dist/promises/types.gen.d.ts +1 -0
- package/dist/promises-Dgv8UmCC.js +5911 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/service-map.d.ts +732 -0
- package/dist/webhooks/effect.d.ts +1 -0
- package/dist/webhooks/effect.js +1 -0
- package/dist/webhooks/promise.d.ts +1 -0
- package/dist/webhooks/promise.js +1 -0
- package/package.json +151 -4
- package/CLAUDE.md +0 -111
- package/bun.lock +0 -29
- package/index.ts +0 -1
- package/tsconfig.json +0 -29
package/AGENTS.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Samva SDK consumer guide
|
|
2
|
+
|
|
3
|
+
Use `createClient` from `samva` for Promise applications. Ergonomic methods return decoded values
|
|
4
|
+
and throw `SamvaApiError` subclasses; use `client.raw` only when request and response envelopes are
|
|
5
|
+
required.
|
|
6
|
+
|
|
7
|
+
Effect applications should import Pascal-cased domain modules from focused subpaths such as
|
|
8
|
+
`samva/effect/email` and provide `Client.layerFetch` from `samva/effect/client`. Install the optional
|
|
9
|
+
peer `effect@4.0.0-beta.102` when using those entrypoints. Keep that exact beta pinned. Tagged API
|
|
10
|
+
failures remain in the Effect error channel and can be handled with `Effect.catchTag`.
|
|
11
|
+
|
|
12
|
+
Treat exported request and response types as the API contract. Do not import package-internal files
|
|
13
|
+
or generated implementation paths; use only the subpaths declared in `package.json`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arya Labs, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,15 +1,402 @@
|
|
|
1
1
|
# samva
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for Samva's public customer API.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
See [AGENTS.md](./AGENTS.md) for concise guidance when using this package with coding agents.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The root entrypoint is Promise-based:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createClient } from "samva";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The same Promise SDK is also available at `samva/promises`. Native Effect users import domain modules such as `samva/effect/email` and provide `samva/effect/client`. Both clients are generated from Samva's public OpenAPI document, while the Effect runtime does not wrap or execute the Promise client. Effect is an optional peer dependency, so Promise-only applications do not install or load it.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
4
20
|
|
|
5
21
|
```bash
|
|
6
|
-
|
|
22
|
+
npm install samva
|
|
23
|
+
# or
|
|
24
|
+
bun add samva
|
|
25
|
+
# or
|
|
26
|
+
yarn add samva
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createClient } from "samva";
|
|
33
|
+
|
|
34
|
+
const client = createClient({
|
|
35
|
+
baseUrl: "https://api.samva.dev",
|
|
36
|
+
apiKey: "<samva-api-key>",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const contact = await client.contacts.findOrCreate({
|
|
40
|
+
name: "Ada Lovelace",
|
|
41
|
+
email: "ada@example.com",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const message = await client.messages.send({
|
|
45
|
+
to: [contact],
|
|
46
|
+
channel: "email",
|
|
47
|
+
email: {
|
|
48
|
+
subject: "Hello",
|
|
49
|
+
html: "<p>Hello World</p>",
|
|
50
|
+
text: "Hello World",
|
|
51
|
+
},
|
|
52
|
+
metadata: {
|
|
53
|
+
externalUserId: "user_123",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const messages = await client.messages.list({
|
|
58
|
+
page: "1",
|
|
59
|
+
limit: "20",
|
|
60
|
+
});
|
|
7
61
|
```
|
|
8
62
|
|
|
9
|
-
|
|
63
|
+
## Effect Quick Start
|
|
64
|
+
|
|
65
|
+
Install `effect` next to `samva` when using the Effect entrypoint:
|
|
10
66
|
|
|
11
67
|
```bash
|
|
12
|
-
bun
|
|
68
|
+
bun add samva effect@4.0.0-beta.102
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Effect } from "effect";
|
|
73
|
+
import * as Client from "samva/effect/client";
|
|
74
|
+
import * as Email from "samva/effect/email";
|
|
75
|
+
|
|
76
|
+
const program = Email.send({
|
|
77
|
+
to: "ada@example.com",
|
|
78
|
+
subject: "Welcome",
|
|
79
|
+
html: "<h1>Welcome to Samva</h1>",
|
|
80
|
+
}).pipe(Effect.provide(Client.layerFetch({ apiKey: "<samva-api-key>" })));
|
|
81
|
+
|
|
82
|
+
const message = await Effect.runPromise(program);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Authentication
|
|
86
|
+
|
|
87
|
+
Samva supports two client auth modes. Use one per `createClient` instance.
|
|
88
|
+
|
|
89
|
+
### API key (automation, CI, single org)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const client = createClient({
|
|
93
|
+
apiKey: "samva_sk_live_...",
|
|
94
|
+
baseUrl: "https://api.samva.dev",
|
|
95
|
+
});
|
|
13
96
|
```
|
|
14
97
|
|
|
15
|
-
|
|
98
|
+
- `X-API-Key` is configured automatically from `apiKey`.
|
|
99
|
+
- Organization context is derived from the API key — do not send `x-org-slug`.
|
|
100
|
+
|
|
101
|
+
### OAuth bearer (interactive, multi-org)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const client = createClient({
|
|
105
|
+
authToken: sessionAccessToken,
|
|
106
|
+
baseUrl: "https://api.samva.dev",
|
|
107
|
+
headers: { "x-org-slug": "your-org-slug" },
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- Requests use `Authorization: Bearer <authToken>`.
|
|
112
|
+
- The token is user-scoped; send the target organization slug on every request via the `x-org-slug` header.
|
|
113
|
+
- There is no SDK `orgSlug` helper — pass the header directly.
|
|
114
|
+
|
|
115
|
+
For terminal workflows (`samva login`, `samva org use`, `samva email send --dry-run`), see the [`@samva/cli` README](https://github.com/AryaLabsHQ/samva/tree/main/packages/cli).
|
|
116
|
+
|
|
117
|
+
## Service Surface
|
|
118
|
+
|
|
119
|
+
The Promise client exposes:
|
|
120
|
+
|
|
121
|
+
- `apiKeys`
|
|
122
|
+
- `campaigns`
|
|
123
|
+
- `contacts`
|
|
124
|
+
- `conversations`
|
|
125
|
+
- `design`
|
|
126
|
+
- `email`
|
|
127
|
+
- `messages`
|
|
128
|
+
- `operations`
|
|
129
|
+
- `organizations`
|
|
130
|
+
- `scheduledMessages`
|
|
131
|
+
- `templates`
|
|
132
|
+
- `webhooks`
|
|
133
|
+
|
|
134
|
+
The Effect SDK exposes Pascal-cased domain modules with lower-camel operations:
|
|
135
|
+
|
|
136
|
+
- `ApiKeys`
|
|
137
|
+
- `Campaigns`
|
|
138
|
+
- `Contacts`
|
|
139
|
+
- `Conversations`
|
|
140
|
+
- `Design`
|
|
141
|
+
- `Email`
|
|
142
|
+
- `Messages`
|
|
143
|
+
- `Operations`
|
|
144
|
+
- `Organizations`
|
|
145
|
+
- `ScheduledMessages`
|
|
146
|
+
- `Templates`
|
|
147
|
+
- `Webhooks`
|
|
148
|
+
|
|
149
|
+
Effect operations take direct parameter objects: body, path, and query fields are not wrapped in `payload` or `params` envelopes. Import the modules you use from `samva/effect/<lower-kebab>` and provide `Client.layerFetch` once at the program boundary. `Client.raw` exposes the underlying generated client for advanced access, but ordinary workflows do not require it.
|
|
150
|
+
|
|
151
|
+
Effect failures carry Samva's real tagged errors (`RateLimitedError`, `ValidationError`, `PaymentRequiredError`, …) directly in the error channel, so `Effect.catchTag("RateLimitedError", …)` works against any operation without enumerating status codes, and each error exposes its own fields (no wrapper `cause`). The SDK also exports category helpers such as `isRetryable`, `isTransient`, `catchAuthError`, and `catchTransient`. Success values decode to domain types: annotated absolute instants become `Date`, sensitive fields become `Redacted`, and unannotated wire timestamps remain strings.
|
|
152
|
+
|
|
153
|
+
### Default-on retry
|
|
154
|
+
|
|
155
|
+
The Effect client retries safe operations out of the box, so you do not wrap calls in `Effect.retry`:
|
|
156
|
+
|
|
157
|
+
- **What retries** — read operations (`GET`/`HEAD`) and sends. Sends are safe because the client attaches an `Idempotency-Key` (below), so the server deduplicates a replayed attempt. Every other mutating operation (`create`, `update`, `remove`, …) is keyless and is never auto-retried.
|
|
158
|
+
- **When** — only for `isRetryable` failures: throttling (`429`), transient server errors (including a transient `BillingProviderError`), and request-transport failures. `ValidationError`, `ConflictError`, `NotFound`, auth, and billing-limit `PaymentRequiredError` failures never retry.
|
|
159
|
+
- **How** — jittered exponential backoff, bounded to four attempts total. A `429` honors the server's `retryAfterSeconds` hint (capped at 60s) in place of the computed delay.
|
|
160
|
+
|
|
161
|
+
Retry policy is read from context via the `Retry.Service` service. When it is absent, the default applies, so no operation gains a requirement. Override it with a Layer:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { Effect } from "effect";
|
|
165
|
+
import { isRetryable } from "samva/effect/categories";
|
|
166
|
+
import * as Retry from "samva/effect/retry";
|
|
167
|
+
|
|
168
|
+
// Turn auto-retry off for a program:
|
|
169
|
+
program.pipe(Effect.provide(Retry.layerDisabled));
|
|
170
|
+
|
|
171
|
+
// Or install a custom policy (any Effect.retry options):
|
|
172
|
+
program.pipe(Effect.provide(Retry.layer({ times: 6, while: isRetryable })));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Idempotency keys
|
|
176
|
+
|
|
177
|
+
Each `messages.send` / `email.send` call generates an `Idempotency-Key` per logical call — stable across the built-in retries, distinct across calls — so a retried send never delivers twice. Pass an explicit key for cross-process deduplication (safe to replay the same request from a different machine):
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const send = Email.send(input, { idempotencyKey: "order-4417-receipt" });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Reusing a key with identical content replays the original response; reusing it with different content fails with `ConflictError`.
|
|
184
|
+
|
|
185
|
+
### Stream pagination
|
|
186
|
+
|
|
187
|
+
Every list operation that returns the uniform `{ items, pagination: { page, limit, total, totalPages } }` envelope carries two `Stream` companions, so you traverse pages without hand-rolling a `page` loop:
|
|
188
|
+
|
|
189
|
+
- **`list.items(input?)`** — a `Stream` of individual items across all pages.
|
|
190
|
+
- **`list.pages(input?)`** — a `Stream` of raw page envelopes.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { Stream } from "effect";
|
|
194
|
+
import * as Contacts from "samva/effect/contacts";
|
|
195
|
+
|
|
196
|
+
// Every contact, one page fetched at a time:
|
|
197
|
+
const allContacts = Stream.runCollect(Contacts.list.items());
|
|
198
|
+
|
|
199
|
+
// Bounded consumption is lazy — this fetches only as many pages as it needs:
|
|
200
|
+
const firstFifty = Stream.runCollect(Contacts.list.items().pipe(Stream.take(50)));
|
|
201
|
+
|
|
202
|
+
// Caller params (start page, limit, filters) are respected:
|
|
203
|
+
const activeFrom2 = Contacts.list.items({ page: "2", limit: "100", status: "active" });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The companions are attached uniformly to every paginated list, including `Messages.list`, `Contacts.list`, `Campaigns.list`, `Email.listDomains`, and `Webhooks.list`. Each page fetch reuses the ambient `Retry` policy. Reads retry per page; pagination does not nest retries. Page and limit inputs retain their OpenAPI wire-string representation. The Promise client is unchanged; stream pagination is Effect-only.
|
|
207
|
+
|
|
208
|
+
### Redacted secrets
|
|
209
|
+
|
|
210
|
+
Secret-bearing response fields — the plaintext API key returned by `apiKeys.create` / `apiKeys.rotate`, and the webhook signing secret from `webhooks.regenerateSecret` — decode to `Redacted<string>` on the Effect surface. The secret never leaks through logs, `String(...)`, or `JSON.stringify`; unwrap it explicitly with `Redacted.value` when you need the plaintext:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { Effect, Redacted } from "effect";
|
|
214
|
+
import * as ApiKeys from "samva/effect/api-keys";
|
|
215
|
+
|
|
216
|
+
const inspectKey = Effect.gen(function* () {
|
|
217
|
+
const created = yield* ApiKeys.create({ name: "CI" });
|
|
218
|
+
`${created.key}`; // "<redacted>" — safe to log
|
|
219
|
+
return Redacted.value(created.key); // the plaintext key, unwrapped on purpose
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The wire body and the Promise client keep plain strings; only the Effect surface is `Redacted`.
|
|
224
|
+
|
|
225
|
+
## OpenAPI vendor extensions
|
|
226
|
+
|
|
227
|
+
The published `openapi.json` carries machine-readable semantics on top of the standard schema, derived from the same contract annotations the Effect SDK uses (no second taxonomy):
|
|
228
|
+
|
|
229
|
+
| Extension | Where | Meaning |
|
|
230
|
+
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
|
|
231
|
+
| `x-category` | error schemas | The error's semantic categories (e.g. `["throttling", "transient"]`). Omitted when uncategorized. |
|
|
232
|
+
| `x-retryable` | error schemas | `true` when the error is safe to retry. |
|
|
233
|
+
| `x-idempotent` | operations | `true` when the operation accepts an `Idempotency-Key` header. |
|
|
234
|
+
| `x-sensitive` | schema properties | `true` for secret-bearing fields (plain `string` on the wire; `Redacted` in the Effect SDK). |
|
|
235
|
+
|
|
236
|
+
These let MCP and third-party consumers read retry/idempotency/secret semantics straight from the spec.
|
|
237
|
+
|
|
238
|
+
## Usage Examples
|
|
239
|
+
|
|
240
|
+
### Send Email Through Unified Messages
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const contact = await client.contacts.findOrCreate({
|
|
244
|
+
name: "Ada Lovelace",
|
|
245
|
+
email: "ada@example.com",
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const response = await client.messages.send({
|
|
249
|
+
to: [contact],
|
|
250
|
+
channel: "email",
|
|
251
|
+
email: {
|
|
252
|
+
subject: "Welcome",
|
|
253
|
+
html: "<h1>Welcome to Samva</h1>",
|
|
254
|
+
text: "Welcome to Samva",
|
|
255
|
+
},
|
|
256
|
+
metadata: {
|
|
257
|
+
campaign: "welcome",
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Native Effect Send Email
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import * as Email from "samva/effect/email";
|
|
266
|
+
|
|
267
|
+
const message =
|
|
268
|
+
yield *
|
|
269
|
+
Email.send({
|
|
270
|
+
to: "ada@example.com",
|
|
271
|
+
subject: "Welcome",
|
|
272
|
+
html: "<h1>Welcome to Samva</h1>",
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### List Conversations
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const response = await client.conversations.list({
|
|
280
|
+
page: "1",
|
|
281
|
+
limit: "20",
|
|
282
|
+
status: "active",
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Schedule a Message
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const scheduled = await client.scheduledMessages.create({
|
|
290
|
+
send: {
|
|
291
|
+
to: [{ email: "ada@example.com" }],
|
|
292
|
+
channel: "email",
|
|
293
|
+
email: { subject: "Reminder", html: "<p>See you tomorrow.</p>" },
|
|
294
|
+
},
|
|
295
|
+
scheduledFor: "2026-08-01T09:00:00Z",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Later, cancel it before it dispatches
|
|
299
|
+
await client.scheduledMessages.cancel({ id: scheduled.id });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Create and Run a Campaign
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const campaign = await client.campaigns.create({
|
|
306
|
+
name: "August newsletter",
|
|
307
|
+
channel: "email",
|
|
308
|
+
content: {
|
|
309
|
+
channel: "email",
|
|
310
|
+
email: { subject: "What's new", html: "<h1>Updates</h1>" },
|
|
311
|
+
},
|
|
312
|
+
audience: { includeTags: ["subscribers"], excludeTags: ["bounced"] },
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const run = await client.campaigns.scheduleRun({
|
|
316
|
+
id: campaign.id,
|
|
317
|
+
scheduledFor: "2026-08-01T09:00:00Z",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const recipients = await client.campaigns.listRecipients({
|
|
321
|
+
id: campaign.id,
|
|
322
|
+
runId: run.id,
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Custom Headers
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const client = createClient({
|
|
330
|
+
apiKey: "<samva-api-key>",
|
|
331
|
+
headers: {
|
|
332
|
+
"X-Custom-Header": "value",
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Error Handling
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { RateLimitedError, SamvaApiError, SamvaTransportError } from "samva";
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const message = await client.email.send({
|
|
344
|
+
to: "ada@example.com",
|
|
345
|
+
subject: "Test",
|
|
346
|
+
html: "<p>Test</p>",
|
|
347
|
+
});
|
|
348
|
+
console.log(message.id);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error instanceof RateLimitedError) {
|
|
351
|
+
console.error(`Retry after ${error.retryAfterSeconds} seconds`);
|
|
352
|
+
} else if (error instanceof SamvaApiError) {
|
|
353
|
+
console.error(error._tag, error.status, error.message);
|
|
354
|
+
} else if (error instanceof SamvaTransportError) {
|
|
355
|
+
console.error("Transport failure", error.cause);
|
|
356
|
+
} else {
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Every ergonomic method returns its decoded success value and throws a generated `SamvaApiError`
|
|
363
|
+
subclass for failures declared by OpenAPI. Network and malformed-response failures throw
|
|
364
|
+
`SamvaTransportError`.
|
|
365
|
+
|
|
366
|
+
### Raw response access
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
const result = await client.raw.messages.send({
|
|
370
|
+
body: {
|
|
371
|
+
to: [{ contactId: "contact-id" }],
|
|
372
|
+
channel: "sms",
|
|
373
|
+
sms: { text: "Hello" },
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (result.error) console.error(result.response?.status, result.error);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Use `client.raw` only when you need the generated `{ data, error, request, response }` envelope or
|
|
381
|
+
transport-level request options.
|
|
382
|
+
|
|
383
|
+
## Type Safety
|
|
384
|
+
|
|
385
|
+
All request and response types are generated from OpenAPI schemas:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import type { MessagesSendData } from "samva";
|
|
389
|
+
|
|
390
|
+
const requestData: MessagesSendData = {
|
|
391
|
+
body: {
|
|
392
|
+
to: [{ contactId: "contact-id" }],
|
|
393
|
+
channel: "email",
|
|
394
|
+
email: {
|
|
395
|
+
subject: "Hello",
|
|
396
|
+
html: "<p>Hello World</p>",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const response = await client.messages.send(requestData.body);
|
|
402
|
+
```
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as Predicate$1 from "effect/Predicate";
|
|
3
|
+
//#region src/effect/generated-metadata.ts
|
|
4
|
+
const SamvaErrorMetadata = {
|
|
5
|
+
BillingProviderError: {
|
|
6
|
+
categories: ["transient"],
|
|
7
|
+
retryable: true
|
|
8
|
+
},
|
|
9
|
+
ConflictError: {
|
|
10
|
+
categories: ["conflict"],
|
|
11
|
+
retryable: false
|
|
12
|
+
},
|
|
13
|
+
EmailChannelError: {
|
|
14
|
+
categories: [],
|
|
15
|
+
retryable: false
|
|
16
|
+
},
|
|
17
|
+
ExternalServiceError: {
|
|
18
|
+
categories: ["transient"],
|
|
19
|
+
retryable: true
|
|
20
|
+
},
|
|
21
|
+
FlagDisabledError: {
|
|
22
|
+
categories: [],
|
|
23
|
+
retryable: false
|
|
24
|
+
},
|
|
25
|
+
ForbiddenError: {
|
|
26
|
+
categories: ["auth"],
|
|
27
|
+
retryable: false
|
|
28
|
+
},
|
|
29
|
+
InternalError: {
|
|
30
|
+
categories: ["transient"],
|
|
31
|
+
retryable: true
|
|
32
|
+
},
|
|
33
|
+
PaymentRequiredError: {
|
|
34
|
+
categories: ["billing"],
|
|
35
|
+
retryable: false
|
|
36
|
+
},
|
|
37
|
+
RateLimitedError: {
|
|
38
|
+
categories: ["throttling", "transient"],
|
|
39
|
+
retryable: true
|
|
40
|
+
},
|
|
41
|
+
ResourceNotFoundError: {
|
|
42
|
+
categories: ["notFound"],
|
|
43
|
+
retryable: false
|
|
44
|
+
},
|
|
45
|
+
UnauthorizedError: {
|
|
46
|
+
categories: ["auth"],
|
|
47
|
+
retryable: false
|
|
48
|
+
},
|
|
49
|
+
ValidationError: {
|
|
50
|
+
categories: ["validation"],
|
|
51
|
+
retryable: false
|
|
52
|
+
},
|
|
53
|
+
WebhookEnqueueError: {
|
|
54
|
+
categories: ["transient"],
|
|
55
|
+
retryable: true
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/effect/categories.ts
|
|
60
|
+
/**
|
|
61
|
+
* Semantic error-category helpers for the Effect SDK.
|
|
62
|
+
*
|
|
63
|
+
* Every operation's error channel carries the server's real tagged errors
|
|
64
|
+
* ({@link import("./index").ConflictError} …). These helpers let callers catch
|
|
65
|
+
* or test by *category* — auth, validation, throttling, transient … — without
|
|
66
|
+
* enumerating tags, and cover the raw transport/decode errors too. The category
|
|
67
|
+
* assignments are generated from the OpenAPI category extensions.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { catchAuthError, isRetryable } from "samva/effect";
|
|
72
|
+
*
|
|
73
|
+
* samva.messages.send(input).pipe(
|
|
74
|
+
* catchAuthError((error) => Effect.logError(`auth failed: ${error.message}`)),
|
|
75
|
+
* Effect.retry({ while: isRetryable }),
|
|
76
|
+
* );
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
const emailChannelCategories = (reason) => {
|
|
80
|
+
switch (reason) {
|
|
81
|
+
case "auth": return ["auth"];
|
|
82
|
+
case "rejected": return ["validation"];
|
|
83
|
+
case "rate_limited": return ["throttling", "transient"];
|
|
84
|
+
case "transport": return ["transient"];
|
|
85
|
+
default: return [];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const categoriesOf = (error) => {
|
|
89
|
+
if (!Predicate$1.isObject(error) || typeof error._tag !== "string") return [];
|
|
90
|
+
if (error._tag === "EmailChannelError") return emailChannelCategories(error.reason);
|
|
91
|
+
return SamvaErrorMetadata[error._tag]?.categories ?? [];
|
|
92
|
+
};
|
|
93
|
+
const hasCategory = (error, category) => categoriesOf(error).includes(category);
|
|
94
|
+
const isRetryableError = (error) => {
|
|
95
|
+
if (!Predicate$1.isObject(error) || typeof error._tag !== "string") return false;
|
|
96
|
+
if (error._tag === "EmailChannelError") return categoriesOf(error).includes("transient");
|
|
97
|
+
return SamvaErrorMetadata[error._tag]?.retryable ?? false;
|
|
98
|
+
};
|
|
99
|
+
const categoryGuard = (category) => (error) => hasCategory(error, category);
|
|
100
|
+
const isAuthError = categoryGuard("auth");
|
|
101
|
+
const isValidationError = categoryGuard("validation");
|
|
102
|
+
const isNotFoundError = categoryGuard("notFound");
|
|
103
|
+
const isConflictError = categoryGuard("conflict");
|
|
104
|
+
const isThrottlingError = categoryGuard("throttling");
|
|
105
|
+
const isBillingError = categoryGuard("billing");
|
|
106
|
+
/**
|
|
107
|
+
* Network-level request failure (no HTTP response). Only `TransportError` is
|
|
108
|
+
* transient/retryable: `EncodeError` and `InvalidUrlError` are deterministic
|
|
109
|
+
* programmer errors (bad input, bad URL) that would fail identically on every
|
|
110
|
+
* attempt — retrying them would loop forever.
|
|
111
|
+
*/
|
|
112
|
+
const isRequestTransportError = (error) => {
|
|
113
|
+
if (!Predicate$1.isTagged(error, "HttpClientError")) return false;
|
|
114
|
+
const reason = error.reason;
|
|
115
|
+
return Predicate$1.isObject(reason) && Predicate$1.isTagged(reason, "TransportError");
|
|
116
|
+
};
|
|
117
|
+
/** True for transient failures, including raw request-transport errors (D4). */
|
|
118
|
+
const isTransient = (error) => hasCategory(error, "transient") || isRequestTransportError(error);
|
|
119
|
+
/** True when an error is declared safe to retry (throttling / transient / transport). */
|
|
120
|
+
const isRetryable = (error) => isRetryableError(error) || isRequestTransportError(error);
|
|
121
|
+
/**
|
|
122
|
+
* Catch every error in `category`. The handler receives the category's error
|
|
123
|
+
* union — including `EmailChannelError` where its `reason` can place it in the
|
|
124
|
+
* category — while only total-membership classes are narrowed out of the error
|
|
125
|
+
* channel (`EmailChannelError` stays: instances with other reasons pass through).
|
|
126
|
+
*/
|
|
127
|
+
const catchCategory = (category, handler) => (effect) => Effect.catchIf(effect, (error) => hasCategory(error, category), handler);
|
|
128
|
+
const catchAuthError = (handler) => catchCategory("auth", handler);
|
|
129
|
+
const catchValidationError = (handler) => catchCategory("validation", handler);
|
|
130
|
+
const catchNotFoundError = (handler) => catchCategory("notFound", handler);
|
|
131
|
+
const catchConflictError = (handler) => catchCategory("conflict", handler);
|
|
132
|
+
const catchThrottlingError = (handler) => catchCategory("throttling", handler);
|
|
133
|
+
const catchBillingError = (handler) => catchCategory("billing", handler);
|
|
134
|
+
/**
|
|
135
|
+
* Catch transient failures — including raw request-transport errors — for
|
|
136
|
+
* fallback/degrade paths. The declared error channel is left intact because
|
|
137
|
+
* transient covers untyped transport errors as well.
|
|
138
|
+
*/
|
|
139
|
+
const catchTransient = (handler) => (effect) => Effect.catchIf(effect, (error) => isTransient(error), handler);
|
|
140
|
+
//#endregion
|
|
141
|
+
export { isTransient as _, catchNotFoundError as a, catchValidationError as c, isAuthError as d, isBillingError as f, isThrottlingError as g, isRetryable as h, catchConflictError as i, categoriesOf as l, isNotFoundError as m, catchBillingError as n, catchThrottlingError as o, isConflictError as p, catchCategory as r, catchTransient as s, catchAuthError as t, hasCategory as u, isValidationError as v };
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=categories-CsUa8ojo.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { Auth } from "../core/auth.gen";
|
|
2
|
+
export type { QuerySerializerOptions } from "../core/bodySerializer.gen";
|
|
3
|
+
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer, } from "../core/bodySerializer.gen";
|
|
4
|
+
export { buildClientParams } from "../core/params.gen";
|
|
5
|
+
export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen";
|
|
6
|
+
export type { ServerSentEventsResult } from "../core/serverSentEvents.gen";
|
|
7
|
+
export type { ClientMeta } from "../core/types.gen";
|
|
8
|
+
export { createClient } from "./client.gen";
|
|
9
|
+
export type { Client, ClientOptions, Config, CreateClientConfig, Options, RequestOptions, RequestResult, ResolvedRequestOptions, ResponseStyle, TDataShape, } from "./types.gen";
|
|
10
|
+
export { createConfig, mergeHeaders } from "./utils.gen";
|