@ownichat/sdk 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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@ownichat/sdk` are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/): breaking changes to the owni.chat API surface
5
+ (a removed field, a changed response shape) ship as a major release.
6
+
7
+ ## 0.1.0
8
+
9
+ First public release.
10
+
11
+ - `createOwniClient()` — typed client for the owni.chat external API
12
+ - `me()` — resolve the project, workspace and widget key a server key is bound to
13
+ - `products` — `list`, `get`, `upsert`, `delete`, and `bulkUpsert` with automatic
14
+ splitting into the API's 100-item batches
15
+ - `knowledge` — create URL, text and FAQ sources, upload files, trigger reindexing
16
+ - `domains` — register, list and remove the origins the widget may load on
17
+ - `@ownichat/sdk/webhooks` — `verifyWebhookSignature` and `parseWebhookRequest` for
18
+ HMAC-SHA256 signed deliveries, with replay protection
19
+ - `@ownichat/sdk/widget` — browser client for the public widget API
20
+ - Automatic retry with exponential backoff on 429 and 5xx, honouring `Retry-After`
21
+ - Typed errors: `OwniApiError`, `OwniConnectionError`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 owni.chat
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,201 @@
1
+ <div align="center">
2
+
3
+ # @ownichat/sdk
4
+
5
+ **Official API client for [owni.chat](https://owni.chat)** — the AI chat widget that answers
6
+ from your real catalog.
7
+
8
+ [![npm](https://img.shields.io/npm/v/@ownichat/sdk?color=306cf1)](https://www.npmjs.com/package/@ownichat/sdk)
9
+ [![bundle](https://img.shields.io/bundlephobia/minzip/@ownichat/sdk?color=306cf1)](https://bundlephobia.com/package/@ownichat/sdk)
10
+ [![types](https://img.shields.io/npm/types/@ownichat/sdk?color=306cf1)](https://www.npmjs.com/package/@ownichat/sdk)
11
+ [![license](https://img.shields.io/npm/l/@ownichat/sdk?color=306cf1)](./LICENSE)
12
+
13
+ </div>
14
+
15
+ ```sh
16
+ npm install @ownichat/sdk
17
+ ```
18
+
19
+ | | |
20
+ | --- | --- |
21
+ | 🛍️ **Catalog sync** | Push products, prices and stock — batching handled for you |
22
+ | 📚 **Knowledge** | Create URL, text and FAQ sources, upload files, trigger reindexing |
23
+ | 🌐 **Domains** | Register the origins the widget may load on |
24
+ | 🔐 **Webhooks** | Verify HMAC-signed deliveries, with replay protection |
25
+ | 💬 **Widget API** | Talk to the public chat API when building your own UI |
26
+
27
+ **Zero runtime dependencies.** Node 18+ (uses the global `fetch`), and works in Deno, Bun,
28
+ Cloudflare Workers and other edge runtimes. Ships ESM and CJS with types for both.
29
+
30
+ ## Quick start
31
+
32
+ Create an API key in the owni.chat dashboard under **Settings → API keys**. It looks like
33
+ `ak_live_…` and must stay server-side.
34
+
35
+ ```ts
36
+ import { createOwniClient } from '@ownichat/sdk';
37
+
38
+ const owni = createOwniClient({ apiKey: process.env.OWNI_API_KEY! });
39
+
40
+ // Everything the key is bound to — including the public key for the widget script tag
41
+ const { project } = await owni.me();
42
+ console.log(project.project_key); // pk_…
43
+ ```
44
+
45
+ That single call is what makes one-field installs possible: the merchant pastes the API key,
46
+ and your integration discovers the project id and the widget key on its own.
47
+
48
+ ## Sync a product catalog
49
+
50
+ ```ts
51
+ await owni.products.bulkUpsert([
52
+ {
53
+ external_id: '1042', // your shop's product id
54
+ title: 'Merino wool scarf',
55
+ description: 'Lightweight, 100% merino.',
56
+ url: 'https://shop.example.com/p/1042', // required
57
+ price: '49.00',
58
+ old_price: '69.00',
59
+ currency: 'EUR',
60
+ image_url: 'https://shop.example.com/i/1042.jpg',
61
+ availability: 'in_stock',
62
+ category: 'Accessories',
63
+ brand: 'Example',
64
+ attributes: [{ name: 'Colour', value: 'Charcoal' }],
65
+ },
66
+ ]);
67
+ ```
68
+
69
+ `bulkUpsert` accepts any number of products and splits them into the API's 100-item batches
70
+ for you, sequentially, so a full catalog sync stays within the rate limit.
71
+
72
+ Other catalog calls:
73
+
74
+ ```ts
75
+ await owni.products.list({ page: 1, pageSize: 50 });
76
+ await owni.products.get('1042');
77
+ await owni.products.upsert({ external_id: '1042', title: '…', url: '…' });
78
+ await owni.products.delete('1042');
79
+ ```
80
+
81
+ Keep `external_id` equal to your shop's own product id — it is what the widget's
82
+ "add to cart" snippet interpolates when a visitor buys from a product card.
83
+
84
+ ## Allowed domains
85
+
86
+ The widget only loads on origins registered for the project. Register the storefront you're
87
+ installed on so staging sites and secondary domains work without dashboard edits:
88
+
89
+ ```ts
90
+ await owni.domains.add('staging.shop.example.com'); // idempotent
91
+ await owni.domains.list();
92
+ ```
93
+
94
+ ## Knowledge sources
95
+
96
+ ```ts
97
+ await owni.knowledge.createSource({ type: 'url', name: 'Shipping policy', source_url: 'https://shop.example.com/shipping' });
98
+ await owni.knowledge.createSource({ type: 'faq', name: 'FAQ', raw_text: 'Q: …\nA: …' });
99
+
100
+ const file = new Blob([await readFile('./returns.pdf')]);
101
+ await owni.knowledge.uploadFile({ file, filename: 'returns.pdf' });
102
+ ```
103
+
104
+ ## Webhooks
105
+
106
+ owni.chat signs outgoing webhooks with `X-Owni-Signature` (HMAC-SHA256 over
107
+ `<timestamp>.<raw body>`) and `X-Owni-Timestamp`. Verify both — the timestamp is what stops
108
+ a captured delivery from being replayed later.
109
+
110
+ ```ts
111
+ import { parseWebhookRequest } from '@ownichat/sdk/webhooks';
112
+
113
+ export async function POST(request: Request) {
114
+ const body = await request.text(); // raw body, not request.json()
115
+
116
+ const result = parseWebhookRequest({
117
+ body,
118
+ headers: request.headers,
119
+ secret: process.env.OWNI_WEBHOOK_SECRET!,
120
+ });
121
+
122
+ if (!result.valid) {
123
+ return new Response(result.reason, { status: 401 });
124
+ }
125
+
126
+ if (result.event.event === 'lead_captured') {
127
+ // …
128
+ }
129
+ return new Response('ok');
130
+ }
131
+ ```
132
+
133
+ `verifyWebhookSignature` is available separately if you already parse the body yourself. Both
134
+ work with a `Headers` object and with a plain Node header record.
135
+
136
+ The signing secret is shown once when you connect the webhook integration, and can be
137
+ regenerated from **Integrations → Webhooks → Rotate secret**. Integrations created before
138
+ signing existed keep receiving unsigned deliveries until you rotate.
139
+
140
+ ## Widget API (browser)
141
+
142
+ Only needed when building a custom chat UI — the standard embed script already does this.
143
+
144
+ ```ts
145
+ import { createWidgetClient } from '@ownichat/sdk/widget';
146
+
147
+ const widget = createWidgetClient({ projectKey: 'pk_…' });
148
+ const config = await widget.getConfig();
149
+ const session = await widget.createSession({ source_page_url: location.href });
150
+ ```
151
+
152
+ Requests are origin-checked server-side, so the page must run on the project domain or one of
153
+ its registered domains.
154
+
155
+ ## Errors and retries
156
+
157
+ ```ts
158
+ import { OwniApiError, OwniConnectionError } from '@ownichat/sdk';
159
+
160
+ try {
161
+ await owni.products.bulkUpsert(products);
162
+ } catch (error) {
163
+ if (error instanceof OwniApiError && error.isAuthError) {
164
+ // wrong, revoked, expired or under-scoped key — retrying will not help
165
+ }
166
+ }
167
+ ```
168
+
169
+ 429 and 5xx responses are retried automatically with exponential backoff, honouring
170
+ `Retry-After`. Configure with `maxRetries` (default 3) and `timeoutMs` (default 30 000).
171
+
172
+ ## Options
173
+
174
+ | Option | Default | Notes |
175
+ | --- | --- | --- |
176
+ | `apiKey` | — | Required. `ak_live_…`, server-side only. |
177
+ | `baseUrl` | `https://app.owni.chat` | Point at your own instance if self-hosting. |
178
+ | `projectId` | resolved via `me()` | Supply it to skip one round trip. |
179
+ | `fetch` | global `fetch` | Inject your own for tests or proxies. |
180
+ | `maxRetries` | `3` | Applies to 429 and 5xx only. |
181
+ | `timeoutMs` | `30000` | Per request, including retries individually. |
182
+
183
+ ## Related packages
184
+
185
+ | Package | For |
186
+ | --- | --- |
187
+ | [`@ownichat/react`](https://www.npmjs.com/package/@ownichat/react) | Widget provider, hooks and inline chat panel |
188
+ | [`@ownichat/next`](https://www.npmjs.com/package/@ownichat/next) | Next.js server component, client hooks and webhook route |
189
+
190
+ Running a ready-made platform? owni.chat ships official plugins for WordPress/WooCommerce,
191
+ Shopify and OpenCart — see [owni.chat/integrations](https://owni.chat/integrations).
192
+
193
+ ## Notes
194
+
195
+ The widget bundle shipped by owni.chat has its own copy of the browser API types; the two are
196
+ kept in sync by hand for now. If you spot a drift, the backend's Joi schemas are the source of
197
+ truth.
198
+
199
+ ## License
200
+
201
+ MIT © [owni.chat](https://owni.chat)
@@ -0,0 +1,29 @@
1
+ // src/errors.ts
2
+ var OwniApiError = class extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, message, body) {
6
+ super(message);
7
+ this.name = "OwniApiError";
8
+ this.status = status;
9
+ this.body = body;
10
+ }
11
+ /** 401/403 — a wrong, revoked, expired or under-scoped API key. Retrying never helps. */
12
+ get isAuthError() {
13
+ return this.status === 401 || this.status === 403;
14
+ }
15
+ /** 429 — the external API allows 60 requests per minute per key. */
16
+ get isRateLimited() {
17
+ return this.status === 429;
18
+ }
19
+ };
20
+ var OwniConnectionError = class extends Error {
21
+ cause;
22
+ constructor(message, cause) {
23
+ super(message);
24
+ this.name = "OwniConnectionError";
25
+ this.cause = cause;
26
+ }
27
+ };
28
+
29
+ export { OwniApiError, OwniConnectionError };
package/dist/index.cjs ADDED
@@ -0,0 +1,318 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var OwniApiError = class extends Error {
5
+ status;
6
+ body;
7
+ constructor(status, message, body) {
8
+ super(message);
9
+ this.name = "OwniApiError";
10
+ this.status = status;
11
+ this.body = body;
12
+ }
13
+ /** 401/403 — a wrong, revoked, expired or under-scoped API key. Retrying never helps. */
14
+ get isAuthError() {
15
+ return this.status === 401 || this.status === 403;
16
+ }
17
+ /** 429 — the external API allows 60 requests per minute per key. */
18
+ get isRateLimited() {
19
+ return this.status === 429;
20
+ }
21
+ };
22
+ var OwniConnectionError = class extends Error {
23
+ cause;
24
+ constructor(message, cause) {
25
+ super(message);
26
+ this.name = "OwniConnectionError";
27
+ this.cause = cause;
28
+ }
29
+ };
30
+
31
+ // src/http.ts
32
+ var DEFAULT_MAX_RETRIES = 3;
33
+ var DEFAULT_TIMEOUT_MS = 3e4;
34
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
35
+ var retryDelayMs = (attempt, retryAfterHeader) => {
36
+ if (retryAfterHeader) {
37
+ const seconds = Number(retryAfterHeader);
38
+ if (Number.isFinite(seconds) && seconds >= 0) {
39
+ return Math.min(seconds * 1e3, 6e4);
40
+ }
41
+ }
42
+ const base = Math.min(2 ** attempt * 500, 16e3);
43
+ return base + Math.random() * 250;
44
+ };
45
+ var isRetryableStatus = (status) => status === 429 || status >= 500;
46
+ var HttpClient = class {
47
+ baseUrl;
48
+ apiKey;
49
+ fetchImpl;
50
+ maxRetries;
51
+ timeoutMs;
52
+ constructor(options) {
53
+ if (!options.apiKey) {
54
+ throw new Error("An owni.chat API key is required (ak_live_\u2026).");
55
+ }
56
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
57
+ this.apiKey = options.apiKey;
58
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
59
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
60
+ const resolvedFetch = options.fetch ?? globalThis.fetch;
61
+ if (!resolvedFetch) {
62
+ throw new Error(
63
+ "No fetch implementation found. Use Node 18+ or pass `fetch` in the client options."
64
+ );
65
+ }
66
+ this.fetchImpl = resolvedFetch;
67
+ }
68
+ async request(options) {
69
+ const url = this.buildUrl(options.path, options.query);
70
+ const headers = {
71
+ Authorization: `Bearer ${this.apiKey}`,
72
+ Accept: "application/json",
73
+ ...options.headers
74
+ };
75
+ let body = options.body;
76
+ if (options.json !== void 0) {
77
+ headers["Content-Type"] = "application/json";
78
+ body = JSON.stringify(options.json);
79
+ }
80
+ let lastError;
81
+ for (let attempt = 0; attempt <= this.maxRetries; attempt += 1) {
82
+ let response;
83
+ try {
84
+ response = await this.fetchWithTimeout(url, {
85
+ method: options.method ?? "GET",
86
+ headers,
87
+ body
88
+ });
89
+ } catch (error) {
90
+ lastError = new OwniConnectionError(`Request to ${url} failed`, error);
91
+ if (attempt === this.maxRetries) throw lastError;
92
+ await sleep(retryDelayMs(attempt, null));
93
+ continue;
94
+ }
95
+ if (response.ok) {
96
+ return await this.parseJson(response);
97
+ }
98
+ if (isRetryableStatus(response.status) && attempt < this.maxRetries) {
99
+ await sleep(retryDelayMs(attempt, response.headers.get("retry-after")));
100
+ continue;
101
+ }
102
+ throw await this.toApiError(response);
103
+ }
104
+ throw lastError ?? new OwniConnectionError(`Request to ${url} failed`);
105
+ }
106
+ buildUrl(path, query) {
107
+ const url = new URL(`${this.baseUrl}${path}`);
108
+ for (const [key, value] of Object.entries(query ?? {})) {
109
+ if (value !== void 0) {
110
+ url.searchParams.set(key, String(value));
111
+ }
112
+ }
113
+ return url.toString();
114
+ }
115
+ async fetchWithTimeout(url, init) {
116
+ const controller = new AbortController();
117
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
118
+ try {
119
+ return await this.fetchImpl(url, { ...init, signal: controller.signal });
120
+ } finally {
121
+ clearTimeout(timer);
122
+ }
123
+ }
124
+ async parseJson(response) {
125
+ const text = await response.text();
126
+ if (!text) return null;
127
+ try {
128
+ return JSON.parse(text);
129
+ } catch {
130
+ return text;
131
+ }
132
+ }
133
+ async toApiError(response) {
134
+ const body = await this.parseJson(response);
135
+ const message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : `owni.chat API responded with status ${response.status}`;
136
+ return new OwniApiError(response.status, message, body);
137
+ }
138
+ };
139
+
140
+ // src/client.ts
141
+ var DEFAULT_BASE_URL = "https://app.owni.chat";
142
+ var BULK_CHUNK_SIZE = 100;
143
+ var OwniClient = class {
144
+ http;
145
+ projectIdPromise = null;
146
+ products;
147
+ knowledge;
148
+ domains;
149
+ constructor(options) {
150
+ this.http = new HttpClient({
151
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
152
+ apiKey: options.apiKey,
153
+ fetch: options.fetch,
154
+ maxRetries: options.maxRetries,
155
+ timeoutMs: options.timeoutMs
156
+ });
157
+ if (options.projectId) {
158
+ this.projectIdPromise = Promise.resolve(options.projectId);
159
+ }
160
+ this.products = new ProductsResource(this);
161
+ this.knowledge = new KnowledgeResource(this);
162
+ this.domains = new DomainsResource(this);
163
+ }
164
+ /**
165
+ * Everything the key is bound to, including the `project_key` a plugin needs for the
166
+ * widget script tag. This is what makes "paste one API key" installs possible.
167
+ */
168
+ me() {
169
+ return this.http.request({ path: "/api/external/v1/me" });
170
+ }
171
+ /** Resolved once per client and cached; concurrent callers share the same request. */
172
+ projectId() {
173
+ if (!this.projectIdPromise) {
174
+ this.projectIdPromise = this.me().then((identity) => identity.project.id).catch((error) => {
175
+ this.projectIdPromise = null;
176
+ throw error;
177
+ });
178
+ }
179
+ return this.projectIdPromise;
180
+ }
181
+ /** @internal */
182
+ async projectRequest(options) {
183
+ const projectId = await this.projectId();
184
+ return this.http.request({
185
+ method: options.method,
186
+ path: options.path(projectId),
187
+ query: options.query,
188
+ json: options.json,
189
+ body: options.body,
190
+ headers: options.headers
191
+ });
192
+ }
193
+ };
194
+ var ProductsResource = class {
195
+ constructor(client) {
196
+ this.client = client;
197
+ }
198
+ client;
199
+ list(input = {}) {
200
+ return this.client.projectRequest({
201
+ path: (projectId) => `/api/external/v1/projects/${projectId}/products`,
202
+ query: { page: input.page, page_size: input.pageSize }
203
+ });
204
+ }
205
+ async get(externalId) {
206
+ const data = await this.client.projectRequest({
207
+ path: (projectId) => `/api/external/v1/projects/${projectId}/products/${encodeURIComponent(externalId)}`
208
+ });
209
+ return data.product;
210
+ }
211
+ async upsert(product) {
212
+ return this.client.projectRequest({
213
+ method: "PUT",
214
+ path: (projectId) => `/api/external/v1/projects/${projectId}/products/${encodeURIComponent(product.external_id)}`,
215
+ json: product
216
+ });
217
+ }
218
+ async delete(externalId) {
219
+ const data = await this.client.projectRequest({
220
+ method: "DELETE",
221
+ path: (projectId) => `/api/external/v1/projects/${projectId}/products/${encodeURIComponent(externalId)}`
222
+ });
223
+ return data.product;
224
+ }
225
+ /**
226
+ * Sends products in chunks of {@link BULK_CHUNK_SIZE} and merges the results, so a
227
+ * full catalog can be passed in one call. Chunks are sequential on purpose: the API
228
+ * re-indexes embeddings after each batch and parallel writes just trip the rate limit.
229
+ */
230
+ async bulkUpsert(products) {
231
+ const merged = { upserted: 0, created: 0, updated: 0, items: [] };
232
+ if (products.length === 0) return merged;
233
+ for (let index = 0; index < products.length; index += BULK_CHUNK_SIZE) {
234
+ const chunk = products.slice(index, index + BULK_CHUNK_SIZE);
235
+ const result = await this.client.projectRequest({
236
+ method: "POST",
237
+ path: (projectId) => `/api/external/v1/projects/${projectId}/products/bulk`,
238
+ json: { products: chunk }
239
+ });
240
+ merged.upserted += result.upserted;
241
+ merged.created += result.created;
242
+ merged.updated += result.updated;
243
+ merged.items.push(...result.items);
244
+ }
245
+ return merged;
246
+ }
247
+ };
248
+ var KnowledgeResource = class {
249
+ constructor(client) {
250
+ this.client = client;
251
+ }
252
+ client;
253
+ async createSource(input) {
254
+ const data = await this.client.projectRequest({
255
+ method: "POST",
256
+ path: (projectId) => `/api/external/v1/projects/${projectId}/knowledge-sources`,
257
+ json: input
258
+ });
259
+ return data.knowledge_source;
260
+ }
261
+ /** `file` is a Blob/File; in Node 18+ use `new Blob([buffer])` or `fs.openAsBlob`. */
262
+ async uploadFile(input) {
263
+ const form = new FormData();
264
+ form.append("file", input.file, input.filename);
265
+ if (input.name) form.append("name", input.name);
266
+ const data = await this.client.projectRequest({
267
+ method: "POST",
268
+ path: (projectId) => `/api/external/v1/projects/${projectId}/knowledge-sources/upload`,
269
+ body: form
270
+ });
271
+ return data.knowledge_source;
272
+ }
273
+ async reindex(sourceId) {
274
+ const data = await this.client.projectRequest({
275
+ method: "POST",
276
+ path: (projectId) => `/api/external/v1/projects/${projectId}/knowledge-sources/${encodeURIComponent(sourceId)}/reindex`
277
+ });
278
+ return data.embedding_job;
279
+ }
280
+ };
281
+ var DomainsResource = class {
282
+ constructor(client) {
283
+ this.client = client;
284
+ }
285
+ client;
286
+ async list() {
287
+ const data = await this.client.projectRequest({
288
+ path: (projectId) => `/api/external/v1/projects/${projectId}/domains`
289
+ });
290
+ return data.domains;
291
+ }
292
+ /**
293
+ * Registers an origin the widget is allowed to load on. Idempotent — re-sending an
294
+ * existing domain returns `created: false` instead of failing, so plugins can call
295
+ * this on every settings save.
296
+ */
297
+ add(domain) {
298
+ return this.client.projectRequest({
299
+ method: "POST",
300
+ path: (projectId) => `/api/external/v1/projects/${projectId}/domains`,
301
+ json: { domain }
302
+ });
303
+ }
304
+ remove(domainId) {
305
+ return this.client.projectRequest({
306
+ method: "DELETE",
307
+ path: (projectId) => `/api/external/v1/projects/${projectId}/domains/${encodeURIComponent(domainId)}`
308
+ });
309
+ }
310
+ };
311
+ var createOwniClient = (options) => new OwniClient(options);
312
+
313
+ exports.BULK_CHUNK_SIZE = BULK_CHUNK_SIZE;
314
+ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
315
+ exports.OwniApiError = OwniApiError;
316
+ exports.OwniClient = OwniClient;
317
+ exports.OwniConnectionError = OwniConnectionError;
318
+ exports.createOwniClient = createOwniClient;