@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 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.