@ulvio/client 0.1.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/README.md +201 -0
- package/dist/index.cjs +642 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +440 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +600 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @ulvio/client
|
|
2
|
+
|
|
3
|
+
Official TypeScript client for the Ulvio platform, plus the html-to-pdf and utilities services.
|
|
4
|
+
|
|
5
|
+
A single `Ulvio` class exposes domain-scoped sub-clients. Each backend service is configured independently and is **optional** — projects only need to set the URLs (and platform key) for the services they actually use.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @ulvio/client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js **24+**.
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Ulvio } from '@ulvio/client';
|
|
19
|
+
|
|
20
|
+
const client = new Ulvio({
|
|
21
|
+
// Platform — required for mail / mailbox / sms / whatsapp / voice / files / ai
|
|
22
|
+
platformApiUrl: process.env.ULVIO_PLATFORM_API_URL,
|
|
23
|
+
platformApiKey: process.env.ULVIO_PLATFORM_API_KEY,
|
|
24
|
+
|
|
25
|
+
// HTML-to-PDF — required for client.htmlToPdf.*
|
|
26
|
+
htmlToPdfApiUrl: process.env.HTML_TO_PDF_API_URL,
|
|
27
|
+
|
|
28
|
+
// Utilities (MJML / Liquid / Markdown) — required for client.utilities.*
|
|
29
|
+
utilitiesApiUrl: process.env.UTILITIES_API_URL,
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Sub-client | Requires |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `client.mail`, `client.mailbox`, `client.sms`, `client.whatsapp`, `client.voice`, `client.files`, `client.ai` | `platformApiUrl` + `platformApiKey` |
|
|
36
|
+
| `client.htmlToPdf` | `htmlToPdfApiUrl` |
|
|
37
|
+
| `client.utilities` | `utilitiesApiUrl` |
|
|
38
|
+
|
|
39
|
+
If you call a sub-client whose required config is missing, the call throws an `UlvioError` with one of:
|
|
40
|
+
|
|
41
|
+
- `platform_not_configured`
|
|
42
|
+
- `html_to_pdf_not_configured`
|
|
43
|
+
- `utilities_not_configured`
|
|
44
|
+
|
|
45
|
+
This is distinct from runtime/network errors, so consumers can detect misconfiguration explicitly:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { UlvioError } from '@ulvio/client';
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await client.mail.sendTransactional({ ... });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err instanceof UlvioError && err.code === 'platform_not_configured') {
|
|
54
|
+
// surface a deployment-time configuration error
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Partial configuration
|
|
61
|
+
|
|
62
|
+
A project that only needs html-to-pdf can construct the client with a single field:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const client = new Ulvio({ htmlToPdfApiUrl: 'http://html-to-pdf:3002' });
|
|
66
|
+
await client.htmlToPdf.convert({ html: btoa('<h1>hi</h1>'), outputMode: 'base64' });
|
|
67
|
+
// client.mail.* would throw platform_not_configured.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Mail
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
await client.mail.sendTransactional({
|
|
76
|
+
from: 'noreply@example.com',
|
|
77
|
+
to: ['user@example.com'],
|
|
78
|
+
subject: 'Welcome',
|
|
79
|
+
body_html: '<p>Hi!</p>',
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Mailbox
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const { messages } = await client.mailbox.list(20);
|
|
87
|
+
const msg = await client.mailbox.get(messages[0].id);
|
|
88
|
+
await client.mailbox.markProcessed(msg.id);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`getConnectorStatus()` reports whether the upstream connector is healthy. When unhealthy, the listing/reading methods throw an `UlvioError` whose `code` is `CONNECTOR_UNHEALTHY` — use the `isConnectorUnhealthyError` helper to detect it:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { isConnectorUnhealthyError } from '@ulvio/client';
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await client.mailbox.list();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (isConnectorUnhealthyError(err)) {
|
|
100
|
+
// back off polling
|
|
101
|
+
} else {
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### SMS / WhatsApp / Voice
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
await client.sms.send({ from: '+1...', to: '+1...', body: 'hello' });
|
|
111
|
+
await client.whatsapp.send({ to: '+1...', template_name: 'reminder', language_code: 'en' });
|
|
112
|
+
await client.voice.transcribe({ file: base64Audio, file_name: 'call.webm' });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Files
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
await client.files.upload('reports/2026/q1.pdf', buffer, 'application/pdf');
|
|
119
|
+
const res = await client.files.get('reports/2026/q1.pdf');
|
|
120
|
+
const { url } = await client.files.presignedDownloadUrl('reports/2026/q1.pdf', 600);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### AI proxy
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { z } from 'zod';
|
|
127
|
+
|
|
128
|
+
const Person = z.object({ name: z.string(), age: z.number() });
|
|
129
|
+
|
|
130
|
+
const data = await client.ai.parse({
|
|
131
|
+
model: 'claude-opus-4-7',
|
|
132
|
+
input: 'Extract the person from: "Ada Lovelace, 36"',
|
|
133
|
+
schema: Person,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
for await (const event of client.ai.stream({
|
|
137
|
+
model: 'claude-opus-4-7',
|
|
138
|
+
input: '...',
|
|
139
|
+
schema: Person,
|
|
140
|
+
})) {
|
|
141
|
+
if (event.type === 'partial') console.log(event.data);
|
|
142
|
+
if (event.type === 'complete') console.log('done', event.data);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### HTML-to-PDF
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const { pdf } = await client.htmlToPdf.convert(
|
|
150
|
+
{ html: btoa('<h1>Invoice</h1>'), outputMode: 'base64' },
|
|
151
|
+
{
|
|
152
|
+
onQueued: ({ position }) => console.log('queued at', position),
|
|
153
|
+
onProcessing: ({ progress }) => console.log('progress', progress),
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Or have the service PUT the rendered PDF directly to a presigned URL:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
await client.htmlToPdf.convert({
|
|
162
|
+
sourceUrl: 'https://example.com/invoice/1',
|
|
163
|
+
uploadUrl: presignedPutUrl,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Utilities
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const { html } = await client.utilities.compileMjml({ mjml: '...' });
|
|
171
|
+
const { result } = await client.utilities.renderLiquid({ template: 'Hi {{ name }}', data: { name: 'Ada' } });
|
|
172
|
+
const { variables } = await client.utilities.extractLiquidVariables({ template: '{{ a }} {{ b }}' });
|
|
173
|
+
const { html: md } = await client.utilities.renderMarkdown({ markdown: '# title' });
|
|
174
|
+
const { html: email } = await client.utilities.renderEmail({ mjml: '...', data: { ... } });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm install
|
|
181
|
+
npm run typecheck
|
|
182
|
+
npm test
|
|
183
|
+
npm run build
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
End-to-end tests run against live containers via Docker Compose:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm run test:e2e
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Releasing
|
|
193
|
+
|
|
194
|
+
Tag the commit with the version (`vX.Y.Z`); the `Release` workflow verifies the tag matches `package.json` and runs `npm publish --provenance`.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm version patch # bumps package.json + creates a vX.Y.Z tag
|
|
198
|
+
git push --follow-tags
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The workflow needs an `NPM_TOKEN` secret with publish access to the `@ulvio` scope.
|