@zindua/sdk 1.3.0 → 1.4.1
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 +61 -4
- package/dist/client.d.ts +58 -3
- package/dist/client.js +82 -3
- package/dist/index.d.ts +1 -1
- package/dist/validate.d.ts +1 -1
- package/dist/validate.js +14 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Official **server-side** SDK for [Zindua](https://zindua.run): send transactiona
|
|
|
5
5
|
| | |
|
|
6
6
|
|---|---|
|
|
7
7
|
| Full API reference | [zindua.run/developers](https://zindua.run/developers) |
|
|
8
|
+
| React Email → Zindua | [zindua.run/react-email](https://zindua.run/react-email) |
|
|
8
9
|
| Dashboard (keys, templates, services) | [zindua.run/login](https://zindua.run/login) |
|
|
9
10
|
| Pricing | [zindua.run/pricing](https://zindua.run/pricing) |
|
|
10
11
|
|
|
@@ -88,7 +89,7 @@ Guide: [WhatsApp anti-ban Guardian](https://zindua.run/whatsapp/anti-ban)
|
|
|
88
89
|
### Upgrade (already installed?)
|
|
89
90
|
|
|
90
91
|
```bash
|
|
91
|
-
npm install @zindua/sdk@1.
|
|
92
|
+
npm install @zindua/sdk@1.4.1
|
|
92
93
|
```
|
|
93
94
|
|
|
94
95
|
```typescript
|
|
@@ -184,6 +185,60 @@ Allowed codes: short ISO 639-1 style (`fr`, `en`, `sw`, `en-us`). Invalid format
|
|
|
184
185
|
|
|
185
186
|
---
|
|
186
187
|
|
|
188
|
+
## Templates API (v1.4.0+)
|
|
189
|
+
|
|
190
|
+
Hosted templates stay the source of truth. Use these methods to **import** HTML (e.g. from [React Email](https://zindua.run/react-email) `render()`) and optionally **render** without sending.
|
|
191
|
+
|
|
192
|
+
| Method | HTTP | Purpose |
|
|
193
|
+
|--------|------|---------|
|
|
194
|
+
| `getTemplates()` | `GET /templates` | List slugs, langs, `{{variables}}` |
|
|
195
|
+
| `upsertTemplate({ slug, lang, subject, bodyHtml, bodyText?, isDefault? })` | `PUT /templates/{slug}/versions/{lang}` | Create/update a locale (creates the slug if missing) |
|
|
196
|
+
| `renderTemplate({ template, lang?, variables? })` | `POST /templates/render` | Interpolate `{{vars}}` → `{ subject, html, text }` — **no delivery** |
|
|
197
|
+
| `send({ to, template, … })` | `POST /send` | **Recommended** — interpolate + deliver via your connected Service |
|
|
198
|
+
|
|
199
|
+
### Push HTML (React Email / CLI-friendly)
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { readFileSync } from "node:fs";
|
|
203
|
+
import { Zindua } from "@zindua/sdk";
|
|
204
|
+
|
|
205
|
+
const zindua = new Zindua({ apiKey: process.env.ZINDUA_API_KEY! });
|
|
206
|
+
|
|
207
|
+
await zindua.upsertTemplate({
|
|
208
|
+
slug: "welcome",
|
|
209
|
+
lang: "en",
|
|
210
|
+
subject: "Welcome {{name}}",
|
|
211
|
+
bodyHtml: readFileSync("./welcome.html", "utf8"),
|
|
212
|
+
isDefault: true,
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Companion helper (local `render` + upsert): [`@zindua/react-email`](https://www.npmjs.com/package/@zindua/react-email) → `pushReactEmail({ client, slug, lang, subject, element })`.
|
|
217
|
+
|
|
218
|
+
CLI:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npx @zindua/cli templates push --slug welcome --lang en \
|
|
222
|
+
--subject "Welcome {{name}}" --html ./welcome.html --default
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Render without sending (escape hatch)
|
|
226
|
+
|
|
227
|
+
Prefer `send()` for production. Use `renderTemplate` only for progressive migration (keep Nodemailer/SendGrid in-process temporarily) or CI previews:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const rendered = await zindua.renderTemplate({
|
|
231
|
+
template: "welcome",
|
|
232
|
+
lang: "en",
|
|
233
|
+
variables: { name: "Ada", appName: "Acme" },
|
|
234
|
+
});
|
|
235
|
+
// rendered.subject, rendered.html, rendered.text
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Keep SendGrid / SES / SMTP credentials in **Dashboard → Service**. Do not re-wire transporters in the SDK.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
187
242
|
## Plans and channels
|
|
188
243
|
|
|
189
244
|
| Plan | Email API | WhatsApp | Email / month | WhatsApp OTP / month |
|
|
@@ -580,7 +635,7 @@ Many errors include a **`context`** field (same shape as success) with `plan` an
|
|
|
580
635
|
1. Store the API key in environment variables or a secrets manager.
|
|
581
636
|
2. Call Zindua from your **API routes** only, not from React/Vue/mobile bundles.
|
|
582
637
|
3. Use **`znd_test_…`** in staging; **`znd_live_…`** in production.
|
|
583
|
-
4. Pin the SDK version in `package.json`, e.g. `"@zindua/sdk": "1.
|
|
638
|
+
4. Pin the SDK version in `package.json`, e.g. `"@zindua/sdk": "1.3.0"`.
|
|
584
639
|
5. One Zindua project per client when quotas, senders, or templates must stay isolated.
|
|
585
640
|
|
|
586
641
|
---
|
|
@@ -602,9 +657,11 @@ See [HTTP / cURL](https://zindua.run/developers#http).
|
|
|
602
657
|
|
|
603
658
|
| Export | Description |
|
|
604
659
|
|--------|-------------|
|
|
605
|
-
| `Zindua` | Client class |
|
|
660
|
+
| `Zindua` | Client class — `send`, `getTemplates`, `upsertTemplate`, `renderTemplate`, `verifyEmail`, `getLog`, `getProject`, `connect`, `pushMirror` |
|
|
606
661
|
| `ZinduaError` | Error type (`status`, `code`, `message`, `details`) |
|
|
607
|
-
| `ZinduaSendResult` | Success payload type |
|
|
662
|
+
| `ZinduaSendResult` | Success payload type for `send()` |
|
|
663
|
+
| `ZinduaUpsertTemplateResult` | Result of `upsertTemplate()` |
|
|
664
|
+
| `ZinduaRenderTemplateResult` | Result of `renderTemplate()` |
|
|
608
665
|
| `ZinduaSendContext` | Type of `result.context` |
|
|
609
666
|
| `DEFAULT_API_BASE` | `https://zindua.run/api/v1` |
|
|
610
667
|
| `LIMITS` | Client-side validation limits |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type SendChannel = "email" | "whatsapp";
|
|
2
2
|
export type ZinduaSendOptions = {
|
|
3
|
-
to: string;
|
|
3
|
+
to: string | string[];
|
|
4
4
|
template: string;
|
|
5
5
|
channel?: SendChannel;
|
|
6
6
|
lang?: string;
|
|
@@ -26,6 +26,9 @@ export type ZinduaEmailVerifyResult = {
|
|
|
26
26
|
suppressed: boolean;
|
|
27
27
|
suppressionReason: string | null;
|
|
28
28
|
deliverable: boolean;
|
|
29
|
+
catchAll?: boolean;
|
|
30
|
+
headline?: string;
|
|
31
|
+
youShould?: string;
|
|
29
32
|
};
|
|
30
33
|
export type ZinduaClientOptions = {
|
|
31
34
|
apiKey: string;
|
|
@@ -62,6 +65,42 @@ export type ZinduaTemplateInfo = {
|
|
|
62
65
|
defaultLang: string;
|
|
63
66
|
variables: string[];
|
|
64
67
|
};
|
|
68
|
+
export type ZinduaUpsertTemplateOptions = {
|
|
69
|
+
slug: string;
|
|
70
|
+
lang: string;
|
|
71
|
+
subject: string;
|
|
72
|
+
bodyHtml: string;
|
|
73
|
+
bodyText?: string;
|
|
74
|
+
isDefault?: boolean;
|
|
75
|
+
};
|
|
76
|
+
export type ZinduaUpsertTemplateResult = {
|
|
77
|
+
ok: true;
|
|
78
|
+
slug: string;
|
|
79
|
+
created: boolean;
|
|
80
|
+
templateCreated: boolean;
|
|
81
|
+
version: {
|
|
82
|
+
lang: string;
|
|
83
|
+
subject: string;
|
|
84
|
+
isDefault: boolean;
|
|
85
|
+
updatedAt: string;
|
|
86
|
+
};
|
|
87
|
+
variables: string[];
|
|
88
|
+
};
|
|
89
|
+
export type ZinduaRenderTemplateOptions = {
|
|
90
|
+
template: string;
|
|
91
|
+
lang?: string;
|
|
92
|
+
variables?: Record<string, string>;
|
|
93
|
+
};
|
|
94
|
+
export type ZinduaRenderTemplateResult = {
|
|
95
|
+
ok: true;
|
|
96
|
+
template: string;
|
|
97
|
+
langUsed: string;
|
|
98
|
+
langFallback: boolean;
|
|
99
|
+
subject: string;
|
|
100
|
+
html: string;
|
|
101
|
+
text: string | null;
|
|
102
|
+
variables: string[];
|
|
103
|
+
};
|
|
65
104
|
export type ZinduaConnectResult = {
|
|
66
105
|
ok: true;
|
|
67
106
|
connected: true;
|
|
@@ -113,6 +152,8 @@ export type ZinduaSendResult = {
|
|
|
113
152
|
channel: SendChannel;
|
|
114
153
|
status: string;
|
|
115
154
|
logId: string;
|
|
155
|
+
logIds?: string[];
|
|
156
|
+
billed?: number;
|
|
116
157
|
langUsed?: string;
|
|
117
158
|
langFallback?: boolean;
|
|
118
159
|
testMode?: boolean;
|
|
@@ -172,7 +213,9 @@ export declare class Zindua {
|
|
|
172
213
|
expiresAt: string;
|
|
173
214
|
};
|
|
174
215
|
}>;
|
|
175
|
-
respond: (challengeId: string, value: string
|
|
216
|
+
respond: (challengeId: string, value: string, options?: {
|
|
217
|
+
userExternalId?: string;
|
|
218
|
+
}) => Promise<{
|
|
176
219
|
ok: boolean;
|
|
177
220
|
status: string;
|
|
178
221
|
error?: string;
|
|
@@ -238,7 +281,9 @@ export declare class Zindua {
|
|
|
238
281
|
expiresAt: string;
|
|
239
282
|
};
|
|
240
283
|
}>;
|
|
241
|
-
respond: (challengeId: string, value: string
|
|
284
|
+
respond: (challengeId: string, value: string, options?: {
|
|
285
|
+
userExternalId?: string;
|
|
286
|
+
}) => Promise<{
|
|
242
287
|
ok: boolean;
|
|
243
288
|
status: string;
|
|
244
289
|
error?: string;
|
|
@@ -289,6 +334,16 @@ export declare class Zindua {
|
|
|
289
334
|
templates: ZinduaTemplateInfo[];
|
|
290
335
|
limits?: Record<string, unknown>;
|
|
291
336
|
}>;
|
|
337
|
+
/**
|
|
338
|
+
* Create or update a template locale (PUT …/templates/{slug}/versions/{lang}).
|
|
339
|
+
* Use after React Email `render()` to push HTML — creates the slug if missing.
|
|
340
|
+
*/
|
|
341
|
+
upsertTemplate(options: ZinduaUpsertTemplateOptions): Promise<ZinduaUpsertTemplateResult>;
|
|
342
|
+
/**
|
|
343
|
+
* Interpolate a hosted template without sending (POST /templates/render).
|
|
344
|
+
* Prefer `send()` for delivery — use this for progressive migration only.
|
|
345
|
+
*/
|
|
346
|
+
renderTemplate(options: ZinduaRenderTemplateOptions): Promise<ZinduaRenderTemplateResult>;
|
|
292
347
|
/**
|
|
293
348
|
* Verify an email address (format, MX, disposable, typo suggestion).
|
|
294
349
|
* POST /api/v1/email/verify — protects your BYO provider reputation.
|
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.Zindua = void 0;
|
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
5
|
const validate_1 = require("./validate");
|
|
6
6
|
const whatsapp_anti_ban_1 = require("./whatsapp-anti-ban");
|
|
7
|
-
const SDK_VERSION = "1.
|
|
7
|
+
const SDK_VERSION = "1.4.1";
|
|
8
8
|
const USER_AGENT = `Zindua-JS/${SDK_VERSION}`;
|
|
9
9
|
function buildPayload(options, channel) {
|
|
10
10
|
const to = (0, validate_1.validateRecipient)(options.to, channel);
|
|
@@ -120,8 +120,8 @@ class Zindua {
|
|
|
120
120
|
getStatus: async (challengeId) => {
|
|
121
121
|
return this.request("GET", `challenges/${encodeURIComponent(challengeId)}`);
|
|
122
122
|
},
|
|
123
|
-
respond: async (challengeId, value) => {
|
|
124
|
-
return this.request("POST", `challenges/${encodeURIComponent(challengeId)}/respond`, { value });
|
|
123
|
+
respond: async (challengeId, value, options) => {
|
|
124
|
+
return this.request("POST", `challenges/${encodeURIComponent(challengeId)}/respond`, { value, userExternalId: options?.userExternalId });
|
|
125
125
|
},
|
|
126
126
|
/**
|
|
127
127
|
* SSE listener for challenge status (requires API key — server-side or trusted runtime).
|
|
@@ -258,6 +258,78 @@ class Zindua {
|
|
|
258
258
|
limits: data.limits,
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Create or update a template locale (PUT …/templates/{slug}/versions/{lang}).
|
|
263
|
+
* Use after React Email `render()` to push HTML — creates the slug if missing.
|
|
264
|
+
*/
|
|
265
|
+
async upsertTemplate(options) {
|
|
266
|
+
const slug = (0, validate_1.validateTemplateSlug)(options.slug);
|
|
267
|
+
const lang = (0, validate_1.validateLang)(options.lang);
|
|
268
|
+
if (!lang) {
|
|
269
|
+
throw new errors_1.ZinduaError("lang is required.", { status: 0, code: "MISSING_FIELDS" });
|
|
270
|
+
}
|
|
271
|
+
const subject = typeof options.subject === "string" ? options.subject.trim() : "";
|
|
272
|
+
const bodyHtml = typeof options.bodyHtml === "string" ? options.bodyHtml : "";
|
|
273
|
+
if (!subject) {
|
|
274
|
+
throw new errors_1.ZinduaError("subject is required.", { status: 0, code: "MISSING_FIELDS" });
|
|
275
|
+
}
|
|
276
|
+
if (!bodyHtml.trim()) {
|
|
277
|
+
throw new errors_1.ZinduaError("bodyHtml is required.", { status: 0, code: "MISSING_FIELDS" });
|
|
278
|
+
}
|
|
279
|
+
const payload = { subject, bodyHtml };
|
|
280
|
+
if (typeof options.bodyText === "string")
|
|
281
|
+
payload.bodyText = options.bodyText;
|
|
282
|
+
if (options.isDefault === true)
|
|
283
|
+
payload.isDefault = true;
|
|
284
|
+
const data = await this.request("PUT", `templates/${encodeURIComponent(slug)}/versions/${encodeURIComponent(lang)}`, payload);
|
|
285
|
+
if (data.ok !== true || !data.version || typeof data.version !== "object") {
|
|
286
|
+
throw new errors_1.ZinduaError("API response missing upsert payload.", {
|
|
287
|
+
status: 200,
|
|
288
|
+
code: "INVALID_RESPONSE",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
slug: typeof data.slug === "string" ? data.slug : slug,
|
|
294
|
+
created: Boolean(data.created),
|
|
295
|
+
templateCreated: Boolean(data.templateCreated),
|
|
296
|
+
version: data.version,
|
|
297
|
+
variables: Array.isArray(data.variables) ? data.variables : [],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Interpolate a hosted template without sending (POST /templates/render).
|
|
302
|
+
* Prefer `send()` for delivery — use this for progressive migration only.
|
|
303
|
+
*/
|
|
304
|
+
async renderTemplate(options) {
|
|
305
|
+
const template = (0, validate_1.validateTemplateSlug)(options.template);
|
|
306
|
+
const lang = (0, validate_1.validateLang)(options.lang);
|
|
307
|
+
const variables = (0, validate_1.sanitizeVariables)(options.variables);
|
|
308
|
+
const payload = { template };
|
|
309
|
+
if (lang)
|
|
310
|
+
payload.lang = lang;
|
|
311
|
+
if (variables)
|
|
312
|
+
payload.variables = variables;
|
|
313
|
+
const data = await this.request("POST", "templates/render", payload);
|
|
314
|
+
if (data.ok !== true ||
|
|
315
|
+
typeof data.subject !== "string" ||
|
|
316
|
+
typeof data.html !== "string") {
|
|
317
|
+
throw new errors_1.ZinduaError("API response missing render payload.", {
|
|
318
|
+
status: 200,
|
|
319
|
+
code: "INVALID_RESPONSE",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
ok: true,
|
|
324
|
+
template: typeof data.template === "string" ? data.template : template,
|
|
325
|
+
langUsed: typeof data.langUsed === "string" ? data.langUsed : lang ?? "en",
|
|
326
|
+
langFallback: Boolean(data.langFallback),
|
|
327
|
+
subject: data.subject,
|
|
328
|
+
html: data.html,
|
|
329
|
+
text: typeof data.text === "string" ? data.text : null,
|
|
330
|
+
variables: Array.isArray(data.variables) ? data.variables : [],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
261
333
|
/**
|
|
262
334
|
* Verify an email address (format, MX, disposable, typo suggestion).
|
|
263
335
|
* POST /api/v1/email/verify — protects your BYO provider reputation.
|
|
@@ -288,6 +360,9 @@ class Zindua {
|
|
|
288
360
|
suppressed: Boolean(data.suppressed),
|
|
289
361
|
suppressionReason: typeof data.suppressionReason === "string" ? data.suppressionReason : null,
|
|
290
362
|
deliverable: Boolean(data.deliverable),
|
|
363
|
+
catchAll: Boolean(data.catchAll),
|
|
364
|
+
headline: typeof data.headline === "string" ? data.headline : undefined,
|
|
365
|
+
youShould: typeof data.youShould === "string" ? data.youShould : undefined,
|
|
291
366
|
};
|
|
292
367
|
}
|
|
293
368
|
/** Delivery status for a logId returned by send() (GET /logs/{logId}). */
|
|
@@ -326,6 +401,10 @@ class Zindua {
|
|
|
326
401
|
channel: (data.channel === "whatsapp" ? "whatsapp" : "email"),
|
|
327
402
|
status: typeof data.status === "string" ? data.status : "queued",
|
|
328
403
|
logId: data.logId,
|
|
404
|
+
logIds: Array.isArray(data.logIds)
|
|
405
|
+
? data.logIds.filter((id) => typeof id === "string")
|
|
406
|
+
: undefined,
|
|
407
|
+
billed: typeof data.billed === "number" ? data.billed : undefined,
|
|
329
408
|
langUsed: typeof data.langUsed === "string" ? data.langUsed : undefined,
|
|
330
409
|
langFallback: typeof data.langFallback === "boolean" ? data.langFallback : undefined,
|
|
331
410
|
testMode: typeof data.testMode === "boolean" ? data.testMode : undefined,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Zindua } from "./client";
|
|
2
|
-
export type { ZinduaClientOptions, ZinduaConnectResult, ZinduaEmailVerifyResult, ZinduaProjectInfo, ZinduaSendContext, ZinduaSendOptions, ZinduaSendResult, ZinduaTemplateInfo, SendChannel, } from "./client";
|
|
2
|
+
export type { ZinduaClientOptions, ZinduaConnectResult, ZinduaEmailVerifyResult, ZinduaProjectInfo, ZinduaRenderTemplateOptions, ZinduaRenderTemplateResult, ZinduaSendContext, ZinduaSendOptions, ZinduaSendResult, ZinduaTemplateInfo, ZinduaUpsertTemplateOptions, ZinduaUpsertTemplateResult, SendChannel, } from "./client";
|
|
3
3
|
export { ZinduaError } from "./errors";
|
|
4
4
|
export type { ZinduaErrorCode } from "./errors";
|
|
5
5
|
export { DEFAULT_API_BASE, LIMITS } from "./validate";
|
package/dist/validate.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function validateApiKey(apiKey: string): string;
|
|
|
16
16
|
export declare function validateBaseUrl(raw: string): string;
|
|
17
17
|
export declare function resolveBaseUrl(explicit?: string): string;
|
|
18
18
|
export declare function validateTemplateSlug(template: string): string;
|
|
19
|
-
export declare function validateRecipient(to: string, channel: "email" | "whatsapp"): string;
|
|
19
|
+
export declare function validateRecipient(to: string | string[], channel: "email" | "whatsapp"): string | string[];
|
|
20
20
|
export declare function validateChannel(channel?: string): "email" | "whatsapp";
|
|
21
21
|
export declare function validateLang(lang?: string): string | undefined;
|
|
22
22
|
export declare function sanitizeVariables(variables?: Record<string, string>): Record<string, string> | undefined;
|
package/dist/validate.js
CHANGED
|
@@ -104,6 +104,20 @@ function validateTemplateSlug(template) {
|
|
|
104
104
|
return slug;
|
|
105
105
|
}
|
|
106
106
|
function validateRecipient(to, channel) {
|
|
107
|
+
const parts = (Array.isArray(to) ? to : [to])
|
|
108
|
+
.flatMap((value) => String(value).split(/[,;]/))
|
|
109
|
+
.map((value) => value.trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
if (parts.length === 0) {
|
|
112
|
+
throw new errors_1.ZinduaError("to is required and must be under 320 characters.", {
|
|
113
|
+
status: 0,
|
|
114
|
+
code: "MISSING_FIELDS",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const validated = parts.map((part) => validateOneRecipient(part, channel));
|
|
118
|
+
return validated.length === 1 ? validated[0] : validated;
|
|
119
|
+
}
|
|
120
|
+
function validateOneRecipient(to, channel) {
|
|
107
121
|
const recipient = to.trim();
|
|
108
122
|
if (!recipient || recipient.length > exports.LIMITS.maxToLength) {
|
|
109
123
|
throw new errors_1.ZinduaError("to is required and must be under 320 characters.", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zindua/sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Official Zindua SDK for Node.js — transactional email
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "Official Zindua SDK for Node.js — transactional email, WhatsApp, template upsert/render via /api/v1.",
|
|
5
5
|
"author": "Zindua",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://zindua.run/developers#sdks",
|