babysea 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) BabySea, 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 ADDED
@@ -0,0 +1,473 @@
1
+ # BabySea
2
+
3
+ Official TypeScript SDK for the [BabySea API](https://babysea.ai). BabySea offers one API for image and video generation across multiple AI inference providers, with automatic failover and a unified schema.
4
+
5
+ [![npm version](./badges/version.svg)](https://www.npmjs.com/package/babysea) [![license](./badges/license.svg)](./LICENSE) [![npm type definitions](./badges/types.svg)](https://www.typescriptlang.org/) [![node](./badges/node.svg)](https://nodejs.org/en/about/previous-releases)
6
+
7
+ - **Zero dependencies** - pure `fetch` and `crypto.subtle`, works everywhere
8
+ - **Isomorphic** - Node 18+, Edge runtimes (Vercel, Cloudflare Workers), browsers
9
+ - **ESM + CJS** dual build with full TypeScript types
10
+ - **Auto-retry** with exponential backoff and `Retry-After` support
11
+ - **Typed errors** - structured BSE error codes, retry flags, rate limit info
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install babysea
19
+ # or
20
+ pnpm add babysea
21
+ # or
22
+ yarn add babysea
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Quick Start
28
+
29
+ ```ts
30
+ import { BabySea } from 'babysea';
31
+
32
+ const client = new BabySea({
33
+ apiKey: 'bye_...',
34
+ region: 'us', // 'us' | 'eu'
35
+ });
36
+
37
+ // Preview cost before generating
38
+ const est = await client.estimate('{model_identifier}', 2);
39
+ console.log(est.data.credit_balance_can_afford); // true
40
+ console.log(est.data.cost_total_consumed); // 0.102
41
+
42
+ // Generate an image
43
+ const result = await client.generate('{model_identifier}', {
44
+ generation_prompt: 'A cute baby seal on the beach at golden hour',
45
+ generation_ratio: '16:9',
46
+ generation_output_format: 'png',
47
+ generation_output_number: 1,
48
+ });
49
+
50
+ console.log(result.data.generation_id);
51
+ // → "gen_01jh..."
52
+ ```
53
+
54
+ > Generations are async - you get a `generation_id` immediately. Use **webhooks** to receive the completed output.
55
+
56
+ ---
57
+
58
+ ## Configuration
59
+
60
+ ```ts
61
+ const client = new BabySea({
62
+ /** API key (required). Create one in your BabySea dashboard. */
63
+ apiKey: 'bye_...',
64
+
65
+ /**
66
+ * Region endpoint.
67
+ * - 'us' → https://api.us.babysea.ai (default)
68
+ * - 'eu' → https://api.eu.babysea.ai
69
+ */
70
+ region: 'us',
71
+
72
+ /**
73
+ * Override the base URL entirely - for custom domains or self-hosted.
74
+ * Takes precedence over `region`.
75
+ */
76
+ baseUrl: 'https://acme.api.us.babysea.ai',
77
+
78
+ /** Request timeout in milliseconds. Default: 30 000 (30s). */
79
+ timeout: 30_000,
80
+
81
+ /** Max automatic retries on retryable errors (429, 5xx). Default: 2. */
82
+ maxRetries: 2,
83
+ });
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Methods
89
+
90
+ ### `generate(model, params)` - Create an image generation
91
+
92
+ ```ts
93
+ const result = await client.generate('{model_identifier}', {
94
+ // Required
95
+ generation_prompt: 'A serene Japanese garden in spring',
96
+
97
+ // Optional
98
+ generation_ratio: '16:9', // '1:1' | '16:9' | '9:16' | '4:3' | '3:4'
99
+ generation_output_format: 'png', // 'png' | 'jpeg' | 'webp'
100
+ generation_output_number: 1, // number of output images
101
+ generation_input_file: [ // input image URLs (for img2img models)
102
+ 'https://example.com/reference.jpg',
103
+ ],
104
+ generation_provider_order: 'replicate, fal', // optional, model-dependent default
105
+ });
106
+
107
+ const { generation_id, generation_provider_order } = result.data;
108
+ ```
109
+
110
+ ---
111
+
112
+ ### `generateVideo(model, params)` - Create a video generation
113
+
114
+ ```ts
115
+ // Duration-only video model
116
+ const result = await client.generateVideo('{model_identifier}', {
117
+ // Required
118
+ generation_prompt: 'A baby seal swimming in the ocean',
119
+ generation_duration: 5, // seconds (range varies per model)
120
+
121
+ // Optional
122
+ generation_ratio: '16:9',
123
+ generation_output_format: 'mp4',
124
+ generation_input_file: [
125
+ 'https://example.com/reference.jpg',
126
+ ],
127
+ generation_provider_order: 'replicate, fal',
128
+ });
129
+
130
+ // Duration + resolution video model
131
+ const hd = await client.generateVideo('{model_identifier}', {
132
+ generation_prompt: 'Cinematic drone shot over a coral reef',
133
+ generation_duration: 8,
134
+ generation_resolution: '1080p', // required for resolution-priced models
135
+ generation_ratio: '16:9',
136
+ });
137
+
138
+ const { generation_id, generation_provider_order } = result.data;
139
+ ```
140
+
141
+ ---
142
+
143
+ ### `estimate(model, count?)` - Preview cost before generating
144
+
145
+ ```ts
146
+ const est = await client.estimate('{model_identifier}', 5);
147
+
148
+ est.data.cost_per_generation; // 0.051 credits
149
+ est.data.cost_total_consumed; // 0.255 credits
150
+ est.data.credit_balance; // 10.000
151
+ est.data.credit_balance_can_afford; // true
152
+ est.data.credit_balance_max_affordable; // 196
153
+ ```
154
+
155
+ ---
156
+
157
+ ### `getGeneration(id)` - Fetch a single generation
158
+
159
+ ```ts
160
+ const gen = await client.getGeneration('gen_01jh...');
161
+
162
+ gen.data.generation_status; // 'processing' | 'succeeded' | 'failed' | 'canceled'
163
+ gen.data.generation_output_file; // string[] of output URLs (when succeeded)
164
+ ```
165
+
166
+ ---
167
+
168
+ ### `listGenerations(options?)` - List with pagination
169
+
170
+ ```ts
171
+ const page = await client.listGenerations({ limit: 20, offset: 0 });
172
+
173
+ page.total; // total count across all pages
174
+ page.data.generations; // Generation[]
175
+ ```
176
+
177
+ ---
178
+
179
+ ### `cancelGeneration(id)` - Cancel an in-progress generation
180
+
181
+ ```ts
182
+ const cancel = await client.cancelGeneration('gen_01jh...');
183
+
184
+ cancel.data.generation_status; // 'canceled'
185
+ cancel.data.credits_refunded; // true
186
+ cancel.data.provider_cancel_sent; // true (best-effort signal to provider)
187
+ ```
188
+
189
+ > Only available while status is `pending` or `processing`. Sends a cancel signal to the upstream provider (best-effort) and refunds credits.
190
+
191
+ ---
192
+
193
+ ### `deleteGeneration(id)` - Delete a generation and its files
194
+
195
+ ```ts
196
+ const del = await client.deleteGeneration('gen_01jh...');
197
+
198
+ del.data.files_deleted; // number of storage files removed
199
+ ```
200
+
201
+ ---
202
+
203
+ ### `status()` - Verify API key and connectivity
204
+
205
+ ```ts
206
+ const s = await client.status();
207
+
208
+ s.data.account_id;
209
+ s.data.apikey_prefix; // 'bye_abc...'
210
+ s.data.apikey_last_used_at;
211
+ s.data.apikey_expires_at;
212
+ ```
213
+
214
+ ---
215
+
216
+ ### `account()` - Account details
217
+
218
+ ```ts
219
+ const acct = await client.account();
220
+
221
+ acct.data.account_name;
222
+ acct.data.account_email;
223
+ acct.data.account_is_personal;
224
+ ```
225
+
226
+ ---
227
+
228
+ ### `billing()` - Credit balance and subscription
229
+
230
+ ```ts
231
+ const bill = await client.billing();
232
+
233
+ bill.data.billing_credit_balance; // 42.500
234
+ bill.data.billing_plan; // 'Scale'
235
+ bill.data.billing_period_ends_at;
236
+ ```
237
+
238
+ ---
239
+
240
+ ### `usage(days?)` - Usage analytics
241
+
242
+ ```ts
243
+ const u = await client.usage(30); // last 30 days (1–90)
244
+
245
+ u.data.usage_total_generations;
246
+ u.data.usage_total_estimated_cost;
247
+ u.data.usage_providers; // per-provider submission breakdown
248
+ u.data.usage_endpoints; // per-endpoint request breakdown
249
+ ```
250
+
251
+ ---
252
+
253
+ ### `health.*` - Infrastructure health
254
+
255
+ ```ts
256
+ // Provider circuit breaker state
257
+ const prov = await client.health.providers();
258
+ prov.data.providers;
259
+ // [{ provider: 'replicate', status: 'healthy', failure_rate: 0.01, window: {...} }, ...]
260
+
261
+ // Model availability across inference providers
262
+ const healthModels = await client.health.models();
263
+ healthModels.data.models.forEach((m) => {
264
+ console.log(m.model_identifier, m.model_pricing);
265
+ });
266
+
267
+ // Redis cache latency
268
+ const cache = await client.health.cache();
269
+ cache.data.latency_ms;
270
+
271
+ // Supabase storage latency
272
+ const storage = await client.health.storage();
273
+ storage.data.latency_ms;
274
+ ```
275
+
276
+ ---
277
+
278
+ ### `library.*` - Model and provider catalog
279
+
280
+ ```ts
281
+ // All available models (image + video) with pricing and input schemas
282
+ const models = await client.library.models();
283
+ models.data.models.forEach((m) => {
284
+ // Flat pricing (image + some video models)
285
+ // m.model_pricing → number
286
+
287
+ // Resolution-based pricing (some video models)
288
+ // m.model_pricing → { "480p": 0.030, "720p": 0.062, "1080p": 0.166 }
289
+
290
+ if (typeof m.model_pricing === 'number') {
291
+ console.log(m.model_identifier, m.model_pricing);
292
+ } else {
293
+ console.log(m.model_identifier, m.model_pricing); // per-resolution map
294
+ }
295
+ });
296
+
297
+ // All provider integration details
298
+ const providers = await client.library.providers();
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Error Handling
304
+
305
+ ```ts
306
+ import { BabySea, BabySeaError, BabySeaTimeoutError, BabySeaRetryError } from 'babysea';
307
+
308
+ try {
309
+ await client.generate('{model_identifier}', {
310
+ generation_prompt: 'A rainy city street',
311
+ });
312
+ } catch (err) {
313
+ if (err instanceof BabySeaError) {
314
+ console.error(err.code); // 'BSE1004' - structured error code
315
+ console.error(err.type); // 'insufficient_credits'
316
+ console.error(err.message); // human-readable
317
+ console.error(err.status); // HTTP status (402, 429, 5xx...)
318
+ console.error(err.retryable); // boolean - safe to retry?
319
+ console.error(err.requestId); // unique ID for support
320
+
321
+ // Present on 429 responses
322
+ if (err.rateLimit) {
323
+ console.error(err.rateLimit.remaining); // requests left in window
324
+ console.error(err.rateLimit.retryAfter); // seconds until reset
325
+ }
326
+ }
327
+
328
+ if (err instanceof BabySeaTimeoutError) {
329
+ // Request exceeded the configured `timeout`
330
+ }
331
+
332
+ if (err instanceof BabySeaRetryError) {
333
+ // All retry attempts exhausted
334
+ console.error(err.attempts); // number of attempts made
335
+ console.error(err.lastError); // the final BabySeaError
336
+ }
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Webhooks
343
+
344
+ Receive real-time generation events on your server. BabySea signs every delivery with HMAC-SHA256 (Stripe-style `t=<ts>,v1=<hex>`).
345
+
346
+ ```ts
347
+ import { verifyWebhook } from 'babysea/webhooks';
348
+
349
+ // Next.js App Router
350
+ export async function POST(req: Request) {
351
+ const rawBody = await req.text();
352
+ const signature = req.headers.get('X-BabySea-Signature') ?? '';
353
+
354
+ let payload;
355
+ try {
356
+ payload = await verifyWebhook(
357
+ rawBody,
358
+ signature,
359
+ process.env.BABYSEA_WEBHOOK_SECRET!,
360
+ );
361
+ } catch {
362
+ return new Response('Invalid signature', { status: 400 });
363
+ }
364
+
365
+ switch (payload.webhook_event) {
366
+ case 'generation.completed':
367
+ const urls = payload.webhook_data.generation_output_file;
368
+ // urls → string[] of output image URLs
369
+ await saveToDatabase(payload.webhook_data.generation_id, urls);
370
+ break;
371
+
372
+ case 'generation.failed':
373
+ console.error(payload.webhook_data.generation_error);
374
+ break;
375
+
376
+ case 'generation.canceled':
377
+ // credits were automatically refunded
378
+ break;
379
+ }
380
+
381
+ return new Response('OK');
382
+ }
383
+ ```
384
+
385
+ ### Webhook Events
386
+
387
+ | Event | When |
388
+ |---|---|
389
+ | `generation.started` | Generation accepted, provider called |
390
+ | `generation.completed` | Provider succeeded, output files available |
391
+ | `generation.failed` | All providers failed or infrastructure error |
392
+ | `generation.canceled` | Canceled by user, credits refunded |
393
+ | `webhook.test` | Test ping from the dashboard |
394
+
395
+ ---
396
+
397
+ ## API Key Scopes
398
+
399
+ BabySea supports scoped API keys - issue a read-only key for your analytics dashboard, a generate-only key for your backend, and a full-access key for internal tools.
400
+
401
+ | Scope | Routes |
402
+ |---|---|
403
+ | `generation:write` | POST `/v1/generate/image/:model`, POST `/v1/generate/video/:model` |
404
+ | `generation:read` | GET `/v1/content/:generationId`, GET `/v1/content/list` |
405
+ | `generation:delete` | DELETE `/v1/content/:generationId`, POST `/v1/content/generation/cancel/:generationId` |
406
+ | `account:read` | GET `/v1/user/account`, `/v1/user/billing`, `/v1/usage`, `/v1/status` |
407
+ | `health:read` | GET `/v1/health/*` |
408
+ | `library:read` | GET `/v1/library/*`, `/v1/estimate/*` |
409
+
410
+ **Preset bundles:**
411
+
412
+ | Preset | Included scopes |
413
+ |---|---|
414
+ | `full_access` | All scopes |
415
+ | `generate_only` | `generation:write`, `generation:read`, `library:read` |
416
+ | `read_only` | `generation:read`, `account:read`, `health:read`, `library:read` |
417
+ | `monitor_only` | `health:read`, `library:read` |
418
+
419
+ ---
420
+
421
+ ## Response Envelope
422
+
423
+ All successful responses share this shape:
424
+
425
+ ```ts
426
+ interface ApiResponse<T> {
427
+ status: 'success';
428
+ request_id: string; // unique ID - include this when contacting support
429
+ message: string;
430
+ timestamp: string;
431
+ data: T;
432
+ }
433
+
434
+ // Paginated responses add:
435
+ interface PaginatedResponse<T> extends ApiResponse<T> {
436
+ total: number;
437
+ limit: number;
438
+ offset: number;
439
+ }
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Rate Limits
445
+
446
+ | Plan | General (req/min) | Generation (req/min) |
447
+ |---|---|---|
448
+ | Free | 30 | 10 |
449
+ | Starter ($9/mo) | 60 | 20 |
450
+ | Pro ($29/mo) | 150 | 50 |
451
+ | Scale ($99/mo) | 300 | 100 |
452
+ | Enterprise ($199/mo) | 600 | 200 |
453
+
454
+ All limits are per-account (shared across all API keys under the same account). The SDK automatically retries 429 responses using `Retry-After`, up to `maxRetries` (default: 2).
455
+
456
+ ---
457
+
458
+ ## Regions
459
+
460
+ | Region | Endpoint |
461
+ |---|---|
462
+ | `us` | `https://api.us.babysea.ai` |
463
+ | `eu` | `https://api.eu.babysea.ai` |
464
+
465
+ Custom API domains (Scale and Enterprise plans) are supported via the `baseUrl` option.
466
+
467
+ BabySea routes requests across all configured inference providers (Replicate, Fal, BytePlus) automatically with failover.
468
+
469
+ ---
470
+
471
+ ## License
472
+
473
+ MIT
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="78" height="20" role="img" aria-label="license: MIT"><title>license: MIT</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="78" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="31" height="20" fill="#4c1"/><rect width="78" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">license</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">license</text><text aria-hidden="true" x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">MIT</text><text x="615" y="140" transform="scale(.1)" fill="#fff" textLength="210">MIT</text></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="78" height="20" role="img" aria-label="node: &gt;=18"><title>node: &gt;=18</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="78" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="41" height="20" fill="#339933"/><rect width="78" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">node</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">node</text><text aria-hidden="true" x="565" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="310">&gt;=18</text><text x="565" y="140" transform="scale(.1)" fill="#fff" textLength="310">&gt;=18</text></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="192" height="20" role="img" aria-label="npm type definitions: TypeScript"><title>npm type definitions: TypeScript</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="192" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="123" height="20" fill="#555"/><rect x="123" width="69" height="20" fill="#3178c6"/><rect width="192" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="625" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="1130">npm type definitions</text><text x="625" y="140" transform="scale(.1)" fill="#fff" textLength="1130">npm type definitions</text><text aria-hidden="true" x="1565" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="590">TypeScript</text><text x="1565" y="140" transform="scale(.1)" fill="#fff" textLength="590">TypeScript</text></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="80" height="20" role="img" aria-label="npm: v1.0.2"><title>npm: v1.0.2</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="80" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="45" height="20" fill="#cb3837"/><rect width="80" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">npm</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">npm</text><text aria-hidden="true" x="565" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">v1.0.2</text><text x="565" y="140" transform="scale(.1)" fill="#fff" textLength="350">v1.0.2</text></g></svg>