@ulvio/client 0.1.0 → 0.3.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @ulvio/client
2
2
 
3
- Official TypeScript client for the Ulvio platform, plus the html-to-pdf and utilities services.
3
+ Official TypeScript client for the Ulvio platform.
4
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.
5
+ A single `Ulvio` class exposes domain-scoped sub-clients (mail, mailbox, sms, whatsapp, voice, files, ai, html-to-pdf, utilities), all reachable via the same `baseUrl` + `apiKey`. The platform proxies html-to-pdf and utilities to their internal services transparently, so consumers only need one set of credentials.
6
6
 
7
7
  ## Install
8
8
 
@@ -18,55 +18,26 @@ Requires Node.js **24+**.
18
18
  import { Ulvio } from '@ulvio/client';
19
19
 
20
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,
21
+ baseUrl: process.env.ULVIO_BASE_URL,
22
+ apiKey: process.env.ULVIO_API_KEY,
30
23
  });
31
24
  ```
32
25
 
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:
26
+ Both `baseUrl` and `apiKey` are required for every sub-client. If either is missing when a method is called, the call throws an `UlvioError` with code `not_configured` — distinct from runtime/network errors, so consumers can detect misconfiguration explicitly:
46
27
 
47
28
  ```ts
48
- import { UlvioError } from '@ulvio/client';
29
+ import { UlvioError, NOT_CONFIGURED_CODE } from '@ulvio/client';
49
30
 
50
31
  try {
51
32
  await client.mail.sendTransactional({ ... });
52
33
  } catch (err) {
53
- if (err instanceof UlvioError && err.code === 'platform_not_configured') {
34
+ if (err instanceof UlvioError && err.code === NOT_CONFIGURED_CODE) {
54
35
  // surface a deployment-time configuration error
55
36
  }
56
37
  throw err;
57
38
  }
58
39
  ```
59
40
 
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
41
  ## Usage
71
42
 
72
43
  ### Mail
@@ -174,6 +145,33 @@ const { html: md } = await client.utilities.renderMarkdown({ markdown: '# title'
174
145
  const { html: email } = await client.utilities.renderEmail({ mjml: '...', data: { ... } });
175
146
  ```
176
147
 
148
+ ## Migrating from 0.2.x to 0.3.0
149
+
150
+ The 0.3.0 release collapses the four per-service config fields to a single `{ baseUrl, apiKey }` pair, and routes html-to-pdf and utilities through the platform forwarder.
151
+
152
+ - Replace `platformApiUrl` / `htmlToPdfApiUrl` / `utilitiesApiUrl` with **one** `baseUrl` pointing at `api.ulvio.dev` (or your platform-dev URL).
153
+ - Replace `platformApiKey` with `apiKey` — the same key now authenticates every sub-client.
154
+ - The legacy env vars (`ULVIO_PLATFORM_API_URL`, `HTML_TO_PDF_API_URL`, `UTILITIES_API_URL`) should be replaced with `ULVIO_BASE_URL` + `ULVIO_API_KEY`.
155
+ - The three legacy error codes (`platform_not_configured`, `html_to_pdf_not_configured`, `utilities_not_configured`) are gone; catch `not_configured` (exported as `NOT_CONFIGURED_CODE`) instead.
156
+
157
+ ```ts
158
+ // before (0.2.x)
159
+ new Ulvio({
160
+ platformApiUrl: process.env.ULVIO_PLATFORM_API_URL,
161
+ platformApiKey: process.env.ULVIO_PLATFORM_API_KEY,
162
+ htmlToPdfApiUrl: process.env.HTML_TO_PDF_API_URL,
163
+ utilitiesApiUrl: process.env.UTILITIES_API_URL,
164
+ });
165
+
166
+ // after (0.3.0)
167
+ new Ulvio({
168
+ baseUrl: process.env.ULVIO_BASE_URL,
169
+ apiKey: process.env.ULVIO_API_KEY,
170
+ });
171
+ ```
172
+
173
+ The platform side must be running a version that exposes `/html-to-pdf/*` and `/utilities/*` forwarders (`ulvio-dev/platform` and `ulvio-dev/platform-dev`).
174
+
177
175
  ## Development
178
176
 
179
177
  ```bash
package/dist/index.cjs CHANGED
@@ -23,13 +23,11 @@ __export(index_exports, {
23
23
  AiClient: () => AiClient,
24
24
  CONNECTOR_UNHEALTHY_CODE: () => CONNECTOR_UNHEALTHY_CODE,
25
25
  FilesClient: () => FilesClient,
26
- HTML_TO_PDF_NOT_CONFIGURED_CODE: () => HTML_TO_PDF_NOT_CONFIGURED_CODE,
27
26
  HtmlToPdfClient: () => HtmlToPdfClient,
28
27
  MailClient: () => MailClient,
29
28
  MailboxClient: () => MailboxClient,
30
- PLATFORM_NOT_CONFIGURED_CODE: () => PLATFORM_NOT_CONFIGURED_CODE,
29
+ NOT_CONFIGURED_CODE: () => NOT_CONFIGURED_CODE,
31
30
  SmsClient: () => SmsClient,
32
- UTILITIES_NOT_CONFIGURED_CODE: () => UTILITIES_NOT_CONFIGURED_CODE,
33
31
  Ulvio: () => Ulvio,
34
32
  UlvioError: () => UlvioError,
35
33
  UtilitiesClient: () => UtilitiesClient,
@@ -41,9 +39,7 @@ module.exports = __toCommonJS(index_exports);
41
39
 
42
40
  // src/errors.ts
43
41
  var CONNECTOR_UNHEALTHY_CODE = "CONNECTOR_UNHEALTHY";
44
- var PLATFORM_NOT_CONFIGURED_CODE = "platform_not_configured";
45
- var HTML_TO_PDF_NOT_CONFIGURED_CODE = "html_to_pdf_not_configured";
46
- var UTILITIES_NOT_CONFIGURED_CODE = "utilities_not_configured";
42
+ var NOT_CONFIGURED_CODE = "not_configured";
47
43
  var UlvioError = class extends Error {
48
44
  code;
49
45
  status;
@@ -82,40 +78,20 @@ var HttpClient = class {
82
78
  this.config = config;
83
79
  }
84
80
  config;
85
- requirePlatform(method) {
86
- const url = this.config.platformApiUrl;
87
- const key = this.config.platformApiKey;
81
+ requireConfig(method) {
82
+ const url = this.config.baseUrl;
83
+ const key = this.config.apiKey;
88
84
  if (!url || !key) {
85
+ const missing = !url && !key ? "baseUrl and apiKey" : !url ? "baseUrl" : "apiKey";
89
86
  throw new UlvioError(
90
- PLATFORM_NOT_CONFIGURED_CODE,
91
- `${method} requires platformApiUrl and platformApiKey to be set on the Ulvio client`
87
+ NOT_CONFIGURED_CODE,
88
+ `${method} requires ${missing} to be set on the Ulvio client`
92
89
  );
93
90
  }
94
91
  return { url: trimSlash(url), key };
95
92
  }
96
- requireHtmlToPdf(method) {
97
- const url = this.config.htmlToPdfApiUrl;
98
- if (!url) {
99
- throw new UlvioError(
100
- HTML_TO_PDF_NOT_CONFIGURED_CODE,
101
- `${method} requires htmlToPdfApiUrl to be set on the Ulvio client`
102
- );
103
- }
104
- return trimSlash(url);
105
- }
106
- requireUtilities(method) {
107
- const url = this.config.utilitiesApiUrl;
108
- if (!url) {
109
- throw new UlvioError(
110
- UTILITIES_NOT_CONFIGURED_CODE,
111
- `${method} requires utilitiesApiUrl to be set on the Ulvio client`
112
- );
113
- }
114
- return trimSlash(url);
115
- }
116
- // ── Platform (authenticated) ──────────────────────────────────────────
117
- async platformRequest(callerName, method, path, options = {}) {
118
- const { url, key } = this.requirePlatform(callerName);
93
+ async request(callerName, method, path, options = {}) {
94
+ const { url, key } = this.requireConfig(callerName);
119
95
  const res = await fetch(`${url}${path}`, {
120
96
  method,
121
97
  headers: {
@@ -127,8 +103,8 @@ var HttpClient = class {
127
103
  if (!res.ok) throw await parseError(res);
128
104
  return await res.json();
129
105
  }
130
- async platformRequestRaw(callerName, method, path, options = {}) {
131
- const { url, key } = this.requirePlatform(callerName);
106
+ async requestRaw(callerName, method, path, options = {}) {
107
+ const { url, key } = this.requireConfig(callerName);
132
108
  const headers = { ...options.headers };
133
109
  if (options.contentType) headers["Content-Type"] = options.contentType;
134
110
  headers["Authorization"] = `Bearer ${key}`;
@@ -140,30 +116,6 @@ var HttpClient = class {
140
116
  if (!res.ok) throw await parseError(res);
141
117
  return res;
142
118
  }
143
- // ── HTML-to-PDF (unauthenticated) ─────────────────────────────────────
144
- async htmlToPdfFetch(callerName, path, init) {
145
- const url = this.requireHtmlToPdf(callerName);
146
- return fetch(`${url}${path}`, init);
147
- }
148
- // ── Utilities (unauthenticated) ───────────────────────────────────────
149
- async utilitiesRequest(callerName, method, path, options = {}) {
150
- const url = this.requireUtilities(callerName);
151
- const res = await fetch(`${url}${path}`, {
152
- method,
153
- headers: { "Content-Type": "application/json" },
154
- body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
155
- });
156
- const data = await res.json().catch(() => void 0);
157
- if (!res.ok) {
158
- const errBody = data;
159
- throw new UlvioError(
160
- errBody?.error?.code ?? "request_failed",
161
- errBody?.error?.message ?? res.statusText,
162
- { status: res.status, response: data }
163
- );
164
- }
165
- return data;
166
- }
167
119
  };
168
120
 
169
121
  // src/clients/mail.ts
@@ -173,7 +125,7 @@ var MailClient = class {
173
125
  }
174
126
  http;
175
127
  sendTransactional(params) {
176
- return this.http.platformRequest(
128
+ return this.http.request(
177
129
  "client.mail.sendTransactional()",
178
130
  "POST",
179
131
  "/v1/transactional-mail/send",
@@ -189,7 +141,7 @@ var MailboxClient = class {
189
141
  }
190
142
  http;
191
143
  send(params) {
192
- return this.http.platformRequest(
144
+ return this.http.request(
193
145
  "client.mailbox.send()",
194
146
  "POST",
195
147
  "/v1/mailbox/send",
@@ -198,21 +150,21 @@ var MailboxClient = class {
198
150
  }
199
151
  list(limit) {
200
152
  const qs = limit !== void 0 ? `?limit=${limit}` : "";
201
- return this.http.platformRequest(
153
+ return this.http.request(
202
154
  "client.mailbox.list()",
203
155
  "GET",
204
156
  `/v1/mailbox/messages${qs}`
205
157
  );
206
158
  }
207
159
  get(id) {
208
- return this.http.platformRequest(
160
+ return this.http.request(
209
161
  "client.mailbox.get()",
210
162
  "GET",
211
163
  `/v1/mailbox/messages/${encodeURIComponent(id)}`
212
164
  );
213
165
  }
214
166
  markProcessed(id) {
215
- return this.http.platformRequest(
167
+ return this.http.request(
216
168
  "client.mailbox.markProcessed()",
217
169
  "POST",
218
170
  `/v1/mailbox/messages/${encodeURIComponent(id)}/mark-processed`
@@ -222,7 +174,7 @@ var MailboxClient = class {
222
174
  * Download a mailbox message attachment as a Buffer.
223
175
  */
224
176
  async getAttachment(messageId, attachmentId) {
225
- const res = await this.http.platformRequestRaw(
177
+ const res = await this.http.requestRaw(
226
178
  "client.mailbox.getAttachment()",
227
179
  "GET",
228
180
  `/v1/mailbox/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`
@@ -231,7 +183,7 @@ var MailboxClient = class {
231
183
  return Buffer.from(arrayBuffer);
232
184
  }
233
185
  getConnectorStatus() {
234
- return this.http.platformRequest(
186
+ return this.http.request(
235
187
  "client.mailbox.getConnectorStatus()",
236
188
  "GET",
237
189
  "/v1/mailbox/connector-status"
@@ -241,7 +193,7 @@ var MailboxClient = class {
241
193
  * Dev-only: toggle the mock connector's health state.
242
194
  */
243
195
  setConnectorStatus(healthy) {
244
- return this.http.platformRequest(
196
+ return this.http.request(
245
197
  "client.mailbox.setConnectorStatus()",
246
198
  "PATCH",
247
199
  "/v1/mailbox/connector-status",
@@ -257,7 +209,7 @@ var SmsClient = class {
257
209
  }
258
210
  http;
259
211
  send(params) {
260
- return this.http.platformRequest(
212
+ return this.http.request(
261
213
  "client.sms.send()",
262
214
  "POST",
263
215
  "/v1/sms/send",
@@ -273,7 +225,7 @@ var WhatsAppClient = class {
273
225
  }
274
226
  http;
275
227
  send(params) {
276
- return this.http.platformRequest(
228
+ return this.http.request(
277
229
  "client.whatsapp.send()",
278
230
  "POST",
279
231
  "/v1/whatsapp/send",
@@ -289,7 +241,7 @@ var VoiceClient = class {
289
241
  }
290
242
  http;
291
243
  transcribe(params) {
292
- return this.http.platformRequest(
244
+ return this.http.request(
293
245
  "client.voice.transcribe()",
294
246
  "POST",
295
247
  "/v1/voice/send",
@@ -305,7 +257,7 @@ var FilesClient = class {
305
257
  }
306
258
  http;
307
259
  async upload(key, file, contentType = "application/octet-stream") {
308
- const res = await this.http.platformRequestRaw(
260
+ const res = await this.http.requestRaw(
309
261
  "client.files.upload()",
310
262
  "PUT",
311
263
  `/v1/files/${encodeURI(key)}`,
@@ -317,7 +269,7 @@ var FilesClient = class {
317
269
  * Download a file. Returns the raw Response for streaming.
318
270
  */
319
271
  get(key) {
320
- return this.http.platformRequestRaw(
272
+ return this.http.requestRaw(
321
273
  "client.files.get()",
322
274
  "GET",
323
275
  `/v1/files/${encodeURI(key)}`
@@ -329,21 +281,21 @@ var FilesClient = class {
329
281
  if (limit !== void 0) params.set("limit", String(limit));
330
282
  if (cursor) params.set("cursor", cursor);
331
283
  const qs = params.toString();
332
- return this.http.platformRequest(
284
+ return this.http.request(
333
285
  "client.files.list()",
334
286
  "GET",
335
287
  `/v1/files${qs ? `?${qs}` : ""}`
336
288
  );
337
289
  }
338
290
  delete(key) {
339
- return this.http.platformRequest(
291
+ return this.http.request(
340
292
  "client.files.delete()",
341
293
  "DELETE",
342
294
  `/v1/files/${encodeURI(key)}`
343
295
  );
344
296
  }
345
297
  deleteMany(prefix) {
346
- return this.http.platformRequest(
298
+ return this.http.request(
347
299
  "client.files.deleteMany()",
348
300
  "DELETE",
349
301
  `/v1/files?prefix=${encodeURIComponent(prefix)}`
@@ -354,7 +306,7 @@ var FilesClient = class {
354
306
  * @param expiresIn TTL in seconds (default 3600, min 60, max 86400)
355
307
  */
356
308
  presignedDownloadUrl(key, expiresIn) {
357
- return this.http.platformRequest(
309
+ return this.http.request(
358
310
  "client.files.presignedDownloadUrl()",
359
311
  "POST",
360
312
  "/v1/files/presigned-download",
@@ -367,7 +319,7 @@ var FilesClient = class {
367
319
  * @param maxSize Max file size in bytes (default 50MB)
368
320
  */
369
321
  presignedUploadUrl(key, contentType, expiresIn, maxSize) {
370
- return this.http.platformRequest(
322
+ return this.http.request(
371
323
  "client.files.presignedUploadUrl()",
372
324
  "POST",
373
325
  "/v1/files/presigned-upload",
@@ -411,7 +363,7 @@ var AiClient = class {
411
363
  http;
412
364
  async parse(params) {
413
365
  const wire = buildPayload(params);
414
- const response = await this.http.platformRequest(
366
+ const response = await this.http.request(
415
367
  "client.ai.parse()",
416
368
  "POST",
417
369
  "/v1/ai/parse",
@@ -421,7 +373,7 @@ var AiClient = class {
421
373
  }
422
374
  async *stream(params) {
423
375
  const wire = buildPayload(params);
424
- const res = await this.http.platformRequestRaw(
376
+ const res = await this.http.requestRaw(
425
377
  "client.ai.stream()",
426
378
  "POST",
427
379
  "/v1/ai/stream",
@@ -493,19 +445,16 @@ var HtmlToPdfClient = class {
493
445
  }
494
446
  http;
495
447
  async convert(params, callbacks) {
496
- const res = await this.http.htmlToPdfFetch("client.htmlToPdf.convert()", "/api/html-to-pdf", {
497
- method: "POST",
498
- headers: { "Content-Type": "application/json" },
499
- body: JSON.stringify(params)
500
- });
501
- if (!res.ok) {
502
- const data = await res.json().catch(() => void 0);
503
- throw new UlvioError(
504
- data?.error?.code ?? "html_to_pdf_failed",
505
- data?.error?.message ?? res.statusText,
506
- { status: res.status, response: data }
507
- );
508
- }
448
+ const res = await this.http.requestRaw(
449
+ "client.htmlToPdf.convert()",
450
+ "POST",
451
+ "/html-to-pdf/convert",
452
+ {
453
+ body: JSON.stringify(params),
454
+ contentType: "application/json",
455
+ headers: { Accept: "text/event-stream" }
456
+ }
457
+ );
509
458
  if (!res.body) {
510
459
  throw new UlvioError("html_to_pdf_no_body", "html-to-pdf service returned no body", {
511
460
  status: res.status
@@ -555,42 +504,42 @@ var UtilitiesClient = class {
555
504
  }
556
505
  http;
557
506
  compileMjml(params) {
558
- return this.http.utilitiesRequest(
507
+ return this.http.request(
559
508
  "client.utilities.compileMjml()",
560
509
  "POST",
561
- "/api/mjml/compile",
510
+ "/utilities/mjml/compile",
562
511
  { body: params }
563
512
  );
564
513
  }
565
514
  renderLiquid(params) {
566
- return this.http.utilitiesRequest(
515
+ return this.http.request(
567
516
  "client.utilities.renderLiquid()",
568
517
  "POST",
569
- "/api/liquidjs/render",
518
+ "/utilities/liquidjs/render",
570
519
  { body: params }
571
520
  );
572
521
  }
573
522
  extractLiquidVariables(params) {
574
- return this.http.utilitiesRequest(
523
+ return this.http.request(
575
524
  "client.utilities.extractLiquidVariables()",
576
525
  "POST",
577
- "/api/liquidjs/variables",
526
+ "/utilities/liquidjs/variables",
578
527
  { body: params }
579
528
  );
580
529
  }
581
530
  renderMarkdown(params) {
582
- return this.http.utilitiesRequest(
531
+ return this.http.request(
583
532
  "client.utilities.renderMarkdown()",
584
533
  "POST",
585
- "/api/markdown/render",
534
+ "/utilities/markdown/render",
586
535
  { body: params }
587
536
  );
588
537
  }
589
538
  renderEmail(params) {
590
- return this.http.utilitiesRequest(
539
+ return this.http.request(
591
540
  "client.utilities.renderEmail()",
592
541
  "POST",
593
- "/api/render-email",
542
+ "/utilities/render-email",
594
543
  { body: params }
595
544
  );
596
545
  }
@@ -607,7 +556,7 @@ var Ulvio = class {
607
556
  ai;
608
557
  htmlToPdf;
609
558
  utilities;
610
- constructor(config = {}) {
559
+ constructor(config) {
611
560
  const http = new HttpClient(config);
612
561
  this.mail = new MailClient(http);
613
562
  this.mailbox = new MailboxClient(http);
@@ -625,13 +574,11 @@ var Ulvio = class {
625
574
  AiClient,
626
575
  CONNECTOR_UNHEALTHY_CODE,
627
576
  FilesClient,
628
- HTML_TO_PDF_NOT_CONFIGURED_CODE,
629
577
  HtmlToPdfClient,
630
578
  MailClient,
631
579
  MailboxClient,
632
- PLATFORM_NOT_CONFIGURED_CODE,
580
+ NOT_CONFIGURED_CODE,
633
581
  SmsClient,
634
- UTILITIES_NOT_CONFIGURED_CODE,
635
582
  Ulvio,
636
583
  UlvioError,
637
584
  UtilitiesClient,