@zooid/sdk 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zooid-ai
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/dist/index.cjs ADDED
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ZooidClient: () => ZooidClient,
24
+ ZooidError: () => ZooidError,
25
+ verifyWebhook: () => verifyWebhook
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/error.ts
30
+ var ZooidError = class extends Error {
31
+ /** HTTP status code from the server response. */
32
+ status;
33
+ constructor(status, message) {
34
+ super(message);
35
+ this.name = "ZooidError";
36
+ this.status = status;
37
+ }
38
+ };
39
+
40
+ // src/client.ts
41
+ var ZooidClient = class {
42
+ /** Base URL of the Zooid server (trailing slashes stripped). */
43
+ server;
44
+ token;
45
+ _fetch;
46
+ constructor(options) {
47
+ this.server = options.server.replace(/\/+$/, "");
48
+ this.token = options.token;
49
+ this._fetch = options.fetch ?? globalThis.fetch;
50
+ }
51
+ async request(method, path, body) {
52
+ const headers = {};
53
+ if (this.token) {
54
+ headers["Authorization"] = `Bearer ${this.token}`;
55
+ }
56
+ if (body !== void 0) {
57
+ headers["Content-Type"] = "application/json";
58
+ }
59
+ const res = await this._fetch(this.server + path, {
60
+ method,
61
+ headers,
62
+ body: body !== void 0 ? JSON.stringify(body) : void 0
63
+ });
64
+ if (!res.ok) {
65
+ let message = `HTTP ${res.status}`;
66
+ try {
67
+ const json = await res.json();
68
+ if (json.error) message = String(json.error);
69
+ } catch {
70
+ }
71
+ throw new ZooidError(res.status, message);
72
+ }
73
+ if (res.status === 204) {
74
+ return void 0;
75
+ }
76
+ return res.json();
77
+ }
78
+ /** Fetch server discovery metadata from `GET /.well-known/zooid.json`. */
79
+ async getMetadata() {
80
+ return this.request("GET", "/.well-known/zooid.json");
81
+ }
82
+ /** Fetch editable server identity from `GET /api/v1/server`. */
83
+ async getServerMeta() {
84
+ return this.request("GET", "/api/v1/server");
85
+ }
86
+ /** Update server identity metadata via `PUT /api/v1/server`. Requires admin token. */
87
+ async updateServerMeta(options) {
88
+ return this.request("PUT", "/api/v1/server", options);
89
+ }
90
+ /** List all channels via `GET /api/v1/channels`. */
91
+ async listChannels() {
92
+ const res = await this.request("GET", "/api/v1/channels");
93
+ return res.channels;
94
+ }
95
+ /** Create a new channel via `POST /api/v1/channels`. Requires admin token. */
96
+ async createChannel(options) {
97
+ return this.request("POST", "/api/v1/channels", options);
98
+ }
99
+ /** Generate a signed claim for the Zooid Directory. Requires admin token. */
100
+ async getClaim(channels, action) {
101
+ const body = { channels };
102
+ if (action) body.action = action;
103
+ return this.request("POST", "/api/v1/directory/claim", body);
104
+ }
105
+ /** Add a named publisher to a channel. Requires admin token. */
106
+ async addPublisher(channelId, name) {
107
+ return this.request(
108
+ "POST",
109
+ `/api/v1/channels/${channelId}/publishers`,
110
+ { name }
111
+ );
112
+ }
113
+ /** Publish a single event to a channel. Requires a publish-scoped token. */
114
+ async publish(channelId, options) {
115
+ const body = { data: options.data };
116
+ if (options.type !== void 0) {
117
+ body.type = options.type;
118
+ }
119
+ return this.request("POST", `/api/v1/channels/${channelId}/events`, body);
120
+ }
121
+ /** Publish multiple events in a single request. Requires a publish-scoped token. */
122
+ async publishBatch(channelId, events) {
123
+ const body = {
124
+ events: events.map((e) => {
125
+ const item = { data: e.data };
126
+ if (e.type !== void 0) item.type = e.type;
127
+ return item;
128
+ })
129
+ };
130
+ const res = await this.request(
131
+ "POST",
132
+ `/api/v1/channels/${channelId}/events`,
133
+ body
134
+ );
135
+ return res.events;
136
+ }
137
+ tail(channelId, options) {
138
+ if (options?.follow) {
139
+ return this.createTailStream(channelId, options);
140
+ }
141
+ return this.poll(channelId, options);
142
+ }
143
+ createTailStream(channelId, options) {
144
+ const buffer = [];
145
+ let waiting = null;
146
+ let done = false;
147
+ let unsub = null;
148
+ this.subscribe(channelId, (event) => {
149
+ if (waiting) {
150
+ const resolve = waiting;
151
+ waiting = null;
152
+ resolve({ value: event, done: false });
153
+ } else {
154
+ buffer.push(event);
155
+ }
156
+ }, {
157
+ mode: options.mode,
158
+ interval: options.interval,
159
+ type: options.type
160
+ }).then((fn) => {
161
+ unsub = fn;
162
+ if (done) fn();
163
+ });
164
+ const stream = {
165
+ close() {
166
+ done = true;
167
+ unsub?.();
168
+ if (waiting) {
169
+ waiting({ value: void 0, done: true });
170
+ waiting = null;
171
+ }
172
+ },
173
+ [Symbol.asyncIterator]() {
174
+ return {
175
+ next() {
176
+ if (buffer.length > 0) {
177
+ return Promise.resolve({ value: buffer.shift(), done: false });
178
+ }
179
+ if (done) {
180
+ return Promise.resolve({ value: void 0, done: true });
181
+ }
182
+ return new Promise((resolve) => {
183
+ waiting = resolve;
184
+ });
185
+ },
186
+ return() {
187
+ stream.close();
188
+ return Promise.resolve({ value: void 0, done: true });
189
+ }
190
+ };
191
+ }
192
+ };
193
+ return stream;
194
+ }
195
+ /** Poll events from a channel with cursor-based pagination. */
196
+ async poll(channelId, options) {
197
+ const params = new URLSearchParams();
198
+ if (options?.cursor) params.set("cursor", options.cursor);
199
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
200
+ if (options?.type) params.set("type", options.type);
201
+ if (options?.since) params.set("since", options.since);
202
+ const qs = params.toString();
203
+ const path = `/api/v1/channels/${channelId}/events${qs ? `?${qs}` : ""}`;
204
+ return this.request("GET", path);
205
+ }
206
+ /** Register a webhook to receive events via POST. */
207
+ async registerWebhook(channelId, url, options) {
208
+ return this.request("POST", `/api/v1/channels/${channelId}/webhooks`, {
209
+ url,
210
+ ...options
211
+ });
212
+ }
213
+ /** Remove a webhook registration. Requires admin token. */
214
+ async removeWebhook(channelId, webhookId) {
215
+ await this.request(
216
+ "DELETE",
217
+ `/api/v1/channels/${channelId}/webhooks/${webhookId}`
218
+ );
219
+ }
220
+ /**
221
+ * Subscribe to a channel. Tries WebSocket first (mode `'auto'`), falls back to polling.
222
+ *
223
+ * Returns a promise that resolves with an unsubscribe function.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * const unsub = await client.subscribe('my-channel', (event) => {
228
+ * console.log('New event:', event.id);
229
+ * });
230
+ *
231
+ * // Later: stop
232
+ * unsub();
233
+ * ```
234
+ */
235
+ async subscribe(channelId, callback, options) {
236
+ const mode = options?.mode ?? "auto";
237
+ if (mode === "poll") {
238
+ return this.startPolling(channelId, callback, options);
239
+ }
240
+ const tryWs = (retryOnFail) => {
241
+ return new Promise((resolve, reject) => {
242
+ const url = this.buildWsUrl(channelId, options);
243
+ const ws = new globalThis.WebSocket(url);
244
+ ws.onopen = () => {
245
+ resolve(() => {
246
+ ws.close();
247
+ });
248
+ };
249
+ ws.onmessage = (e) => {
250
+ try {
251
+ const event = JSON.parse(String(e.data));
252
+ callback(event);
253
+ } catch {
254
+ }
255
+ };
256
+ ws.onclose = () => {
257
+ };
258
+ ws.onerror = () => {
259
+ ws.close();
260
+ if (retryOnFail) {
261
+ setTimeout(() => {
262
+ tryWs(false).then(resolve, reject);
263
+ }, 1e3);
264
+ } else if (mode === "auto") {
265
+ this.startPolling(channelId, callback, options).then(resolve, reject);
266
+ } else {
267
+ reject(new Error("WebSocket connection failed"));
268
+ }
269
+ };
270
+ });
271
+ };
272
+ return tryWs(true);
273
+ }
274
+ buildWsUrl(channelId, options) {
275
+ const base = this.server.replace(/^http:\/\//, "ws://").replace(/^https:\/\//, "wss://");
276
+ const params = new URLSearchParams();
277
+ if (this.token) params.set("token", this.token);
278
+ if (options?.type) params.set("types", options.type);
279
+ const qs = params.toString();
280
+ return `${base}/api/v1/channels/${channelId}/ws${qs ? `?${qs}` : ""}`;
281
+ }
282
+ startPolling(channelId, callback, options) {
283
+ const interval = options?.interval ?? 5e3;
284
+ const type = options?.type;
285
+ let cursor = null;
286
+ let timer = null;
287
+ let stopped = false;
288
+ const doPoll = async () => {
289
+ try {
290
+ const pollOpts = {};
291
+ if (cursor) pollOpts.cursor = cursor;
292
+ if (type) pollOpts.type = type;
293
+ const result = await this.poll(channelId, pollOpts);
294
+ if (stopped) return;
295
+ for (const event of result.events) {
296
+ callback(event);
297
+ }
298
+ if (result.cursor) {
299
+ cursor = result.cursor;
300
+ }
301
+ } catch {
302
+ }
303
+ };
304
+ doPoll();
305
+ timer = setInterval(doPoll, interval);
306
+ return Promise.resolve(() => {
307
+ stopped = true;
308
+ if (timer !== null) {
309
+ clearInterval(timer);
310
+ timer = null;
311
+ }
312
+ });
313
+ }
314
+ };
315
+
316
+ // src/verify.ts
317
+ async function verifyWebhook(options) {
318
+ const { body, signature, timestamp, publicKey, maxAge } = options;
319
+ if (maxAge !== void 0) {
320
+ const ts = Date.parse(timestamp);
321
+ if (Number.isNaN(ts)) return false;
322
+ const age = (Date.now() - ts) / 1e3;
323
+ if (age > maxAge || age < -maxAge) return false;
324
+ }
325
+ const key = await crypto.subtle.importKey(
326
+ "spki",
327
+ base64ToArrayBuffer(publicKey),
328
+ "Ed25519",
329
+ false,
330
+ ["verify"]
331
+ );
332
+ const message = new TextEncoder().encode(`${timestamp}.${body}`);
333
+ const sigBytes = base64ToArrayBuffer(signature);
334
+ return crypto.subtle.verify("Ed25519", key, sigBytes, message);
335
+ }
336
+ function base64ToArrayBuffer(input) {
337
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
338
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
339
+ const binary = atob(padded);
340
+ const bytes = new Uint8Array(binary.length);
341
+ for (let i = 0; i < binary.length; i++) {
342
+ bytes[i] = binary.charCodeAt(i);
343
+ }
344
+ return bytes.buffer;
345
+ }
346
+ // Annotate the CommonJS export names for ESM import in node:
347
+ 0 && (module.exports = {
348
+ ZooidClient,
349
+ ZooidError,
350
+ verifyWebhook
351
+ });
@@ -0,0 +1,260 @@
1
+ import { ChannelListItem, ZooidEvent as ZooidEvent$1, Webhook, ServerDiscovery, ServerIdentity, PollResult } from '@zooid/types';
2
+ export { PollResult, ServerDiscovery, ServerIdentity, ServerMeta, ServerMetadata, ZooidEvent } from '@zooid/types';
3
+
4
+ type ZooidEvent = ZooidEvent$1;
5
+ /** Channel info as returned by `GET /api/v1/channels`. Alias for {@link ChannelListItem}. */
6
+ type ChannelInfo = ChannelListItem;
7
+ /** Webhook registration result. Alias for {@link Webhook}. */
8
+ type WebhookResult = Webhook;
9
+ /** Options for constructing a {@link ZooidClient}. */
10
+ interface ZooidClientOptions {
11
+ /** Base URL of the Zooid server (e.g. `"https://zooid.example.workers.dev"`). */
12
+ server: string;
13
+ /** JWT token (admin, publish, or subscribe scoped). */
14
+ token?: string;
15
+ /** Custom fetch implementation (for testing or custom environments). */
16
+ fetch?: typeof globalThis.fetch;
17
+ }
18
+ /** Options for creating a new channel via `POST /api/v1/channels`. */
19
+ interface CreateChannelOptions {
20
+ /** URL-safe slug identifier (lowercase + hyphens, 3-64 chars). */
21
+ id: string;
22
+ /** Human-readable display name. */
23
+ name: string;
24
+ /** Optional channel description. */
25
+ description?: string;
26
+ /** Whether the channel is publicly accessible. Defaults to `true`. */
27
+ is_public?: boolean;
28
+ /** Optional JSON Schema for event payload validation. */
29
+ schema?: Record<string, unknown>;
30
+ /** When `true`, events are rejected if they don't match `schema`. */
31
+ strict?: boolean;
32
+ }
33
+ /** Result of creating a new channel. */
34
+ interface CreateChannelResult {
35
+ /** The channel's slug identifier. */
36
+ id: string;
37
+ /** JWT token scoped for publishing to this channel. */
38
+ publish_token: string;
39
+ /** JWT token scoped for subscribing to this channel. */
40
+ subscribe_token: string;
41
+ }
42
+ /** Result of adding a publisher to a channel. */
43
+ interface PublisherResult {
44
+ /** ULID identifier for the new publisher. */
45
+ id: string;
46
+ /** Display name of the publisher. */
47
+ name: string;
48
+ /** JWT token scoped for publishing as this publisher. */
49
+ publish_token: string;
50
+ }
51
+ /** Options for publishing a single event. */
52
+ interface PublishOptions {
53
+ /** Optional event type string for subscriber filtering. */
54
+ type?: string;
55
+ /** Event payload (will be JSON-serialized, max 64 KB). */
56
+ data: unknown;
57
+ }
58
+ /** Options for polling events from a channel. */
59
+ interface PollOptions {
60
+ /** Opaque cursor from a previous poll response. */
61
+ cursor?: string;
62
+ /** ISO 8601 timestamp — only return events created after this time. */
63
+ since?: string;
64
+ /** Maximum number of events to return (default: 50). */
65
+ limit?: number;
66
+ /** Filter events by type. */
67
+ type?: string;
68
+ }
69
+ /** Options for registering a webhook. */
70
+ interface WebhookOptions {
71
+ /** Only deliver events matching these types. Omit for all events. */
72
+ event_types?: string[];
73
+ /** Webhook lifetime in seconds (default: 3 days, max: 30 days). */
74
+ ttl_seconds?: number;
75
+ }
76
+ /** Subscribe transport mode. */
77
+ type SubscribeMode = 'auto' | 'ws' | 'poll';
78
+ /** Options for the subscribe helper. */
79
+ interface SubscribeOptions {
80
+ /** Polling interval in milliseconds. Default: `5000`. */
81
+ interval?: number;
82
+ /** Transport mode. `'auto'` (default) tries WebSocket first, falls back to polling. */
83
+ mode?: SubscribeMode;
84
+ /** Event type filter — passed as `?types=` on WS, `?type=` on poll. */
85
+ type?: string;
86
+ }
87
+ /** Options for the `tail()` method. Extends poll options with follow mode. */
88
+ interface TailOptions extends PollOptions {
89
+ /** When `true`, subscribe and stream events as they arrive. */
90
+ follow?: boolean;
91
+ /** Transport mode for follow mode. Default: `'auto'`. */
92
+ mode?: SubscribeMode;
93
+ /** Polling interval in ms for follow mode (poll transport). Default: `5000`. */
94
+ interval?: number;
95
+ }
96
+ /**
97
+ * An async iterable stream of events returned by `tail({ follow: true })`.
98
+ * Call `close()` to stop the underlying subscription and end the stream.
99
+ */
100
+ interface TailStream extends AsyncIterable<ZooidEvent> {
101
+ /** Stop the subscription and end the stream. */
102
+ close(): void;
103
+ }
104
+ /** Result of generating a signed directory claim. */
105
+ interface ClaimResult {
106
+ /** Base64url-encoded JSON claim payload. */
107
+ claim: string;
108
+ /** Base64url-encoded Ed25519 signature of the claim. */
109
+ signature: string;
110
+ }
111
+ /** Options for updating server identity metadata. */
112
+ interface UpdateServerMetaOptions {
113
+ /** Server display name. */
114
+ name?: string;
115
+ /** Server description (set to `null` to clear). */
116
+ description?: string | null;
117
+ /** Tags for categorization. */
118
+ tags?: string[];
119
+ /** Operator name (set to `null` to clear). */
120
+ owner?: string | null;
121
+ /** Company name (set to `null` to clear). */
122
+ company?: string | null;
123
+ /** Contact email (set to `null` to clear). */
124
+ email?: string | null;
125
+ }
126
+
127
+ /**
128
+ * Client for the Zooid pub/sub API.
129
+ *
130
+ * Provides methods for channel management, event publishing/polling,
131
+ * webhook registration, and server metadata access.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const client = new ZooidClient({
136
+ * server: 'https://zooid.example.workers.dev',
137
+ * token: 'eyJ...',
138
+ * });
139
+ *
140
+ * const channels = await client.listChannels();
141
+ * ```
142
+ */
143
+ declare class ZooidClient {
144
+ /** Base URL of the Zooid server (trailing slashes stripped). */
145
+ readonly server: string;
146
+ private token?;
147
+ private _fetch;
148
+ constructor(options: ZooidClientOptions);
149
+ private request;
150
+ /** Fetch server discovery metadata from `GET /.well-known/zooid.json`. */
151
+ getMetadata(): Promise<ServerDiscovery>;
152
+ /** Fetch editable server identity from `GET /api/v1/server`. */
153
+ getServerMeta(): Promise<ServerIdentity>;
154
+ /** Update server identity metadata via `PUT /api/v1/server`. Requires admin token. */
155
+ updateServerMeta(options: UpdateServerMetaOptions): Promise<ServerIdentity>;
156
+ /** List all channels via `GET /api/v1/channels`. */
157
+ listChannels(): Promise<ChannelInfo[]>;
158
+ /** Create a new channel via `POST /api/v1/channels`. Requires admin token. */
159
+ createChannel(options: CreateChannelOptions): Promise<CreateChannelResult>;
160
+ /** Generate a signed claim for the Zooid Directory. Requires admin token. */
161
+ getClaim(channels: string[], action?: 'delete'): Promise<ClaimResult>;
162
+ /** Add a named publisher to a channel. Requires admin token. */
163
+ addPublisher(channelId: string, name: string): Promise<PublisherResult>;
164
+ /** Publish a single event to a channel. Requires a publish-scoped token. */
165
+ publish(channelId: string, options: PublishOptions): Promise<ZooidEvent$1>;
166
+ /** Publish multiple events in a single request. Requires a publish-scoped token. */
167
+ publishBatch(channelId: string, events: PublishOptions[]): Promise<ZooidEvent$1[]>;
168
+ /**
169
+ * Fetch events from a channel.
170
+ *
171
+ * Without `follow`, performs a one-shot poll (alias for {@link poll}).
172
+ * With `follow: true`, returns an async iterable stream that wraps {@link subscribe}.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // One-shot
177
+ * const result = await client.tail('my-channel', { limit: 10 });
178
+ *
179
+ * // Follow mode
180
+ * const stream = client.tail('my-channel', { follow: true });
181
+ * for await (const event of stream) {
182
+ * console.log(event);
183
+ * }
184
+ * ```
185
+ */
186
+ tail(channelId: string, options: TailOptions & {
187
+ follow: true;
188
+ }): TailStream;
189
+ tail(channelId: string, options?: TailOptions): Promise<PollResult>;
190
+ private createTailStream;
191
+ /** Poll events from a channel with cursor-based pagination. */
192
+ poll(channelId: string, options?: PollOptions): Promise<PollResult>;
193
+ /** Register a webhook to receive events via POST. */
194
+ registerWebhook(channelId: string, url: string, options?: WebhookOptions): Promise<WebhookResult>;
195
+ /** Remove a webhook registration. Requires admin token. */
196
+ removeWebhook(channelId: string, webhookId: string): Promise<void>;
197
+ /**
198
+ * Subscribe to a channel. Tries WebSocket first (mode `'auto'`), falls back to polling.
199
+ *
200
+ * Returns a promise that resolves with an unsubscribe function.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const unsub = await client.subscribe('my-channel', (event) => {
205
+ * console.log('New event:', event.id);
206
+ * });
207
+ *
208
+ * // Later: stop
209
+ * unsub();
210
+ * ```
211
+ */
212
+ subscribe(channelId: string, callback: (event: ZooidEvent$1) => void, options?: SubscribeOptions): Promise<() => void>;
213
+ private buildWsUrl;
214
+ private startPolling;
215
+ }
216
+
217
+ /**
218
+ * Error thrown by {@link ZooidClient} when the server returns a non-2xx response.
219
+ *
220
+ * The `status` property contains the HTTP status code, and `message` contains
221
+ * the error description from the response body (or a fallback like `"HTTP 500"`).
222
+ */
223
+ declare class ZooidError extends Error {
224
+ /** HTTP status code from the server response. */
225
+ status: number;
226
+ constructor(status: number, message: string);
227
+ }
228
+
229
+ /** Options for verifying a Zooid webhook signature. */
230
+ interface VerifyWebhookOptions {
231
+ /** The raw JSON request body string. */
232
+ body: string;
233
+ /** Base64-encoded Ed25519 signature from the `X-Zooid-Signature` header. */
234
+ signature: string;
235
+ /** ISO 8601 timestamp from the `X-Zooid-Timestamp` header. */
236
+ timestamp: string;
237
+ /** Base64-encoded SPKI public key from `/.well-known/zooid.json`. */
238
+ publicKey: string;
239
+ /** Maximum age in seconds before the timestamp is considered stale. Default: no check. */
240
+ maxAge?: number;
241
+ }
242
+ /**
243
+ * Verify an Ed25519 webhook signature from a Zooid server.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * import { verifyWebhook } from '@zooid/sdk';
248
+ *
249
+ * const isValid = await verifyWebhook({
250
+ * body: rawBody,
251
+ * signature: req.headers['x-zooid-signature'],
252
+ * timestamp: req.headers['x-zooid-timestamp'],
253
+ * publicKey: cachedPublicKey,
254
+ * maxAge: 300,
255
+ * });
256
+ * ```
257
+ */
258
+ declare function verifyWebhook(options: VerifyWebhookOptions): Promise<boolean>;
259
+
260
+ export { type ChannelInfo, type ClaimResult, type CreateChannelOptions, type CreateChannelResult, type PollOptions, type PublishOptions, type PublisherResult, type SubscribeMode, type SubscribeOptions, type TailOptions, type TailStream, type UpdateServerMetaOptions, type VerifyWebhookOptions, type WebhookOptions, type WebhookResult, ZooidClient, type ZooidClientOptions, ZooidError, verifyWebhook };
@@ -0,0 +1,260 @@
1
+ import { ChannelListItem, ZooidEvent as ZooidEvent$1, Webhook, ServerDiscovery, ServerIdentity, PollResult } from '@zooid/types';
2
+ export { PollResult, ServerDiscovery, ServerIdentity, ServerMeta, ServerMetadata, ZooidEvent } from '@zooid/types';
3
+
4
+ type ZooidEvent = ZooidEvent$1;
5
+ /** Channel info as returned by `GET /api/v1/channels`. Alias for {@link ChannelListItem}. */
6
+ type ChannelInfo = ChannelListItem;
7
+ /** Webhook registration result. Alias for {@link Webhook}. */
8
+ type WebhookResult = Webhook;
9
+ /** Options for constructing a {@link ZooidClient}. */
10
+ interface ZooidClientOptions {
11
+ /** Base URL of the Zooid server (e.g. `"https://zooid.example.workers.dev"`). */
12
+ server: string;
13
+ /** JWT token (admin, publish, or subscribe scoped). */
14
+ token?: string;
15
+ /** Custom fetch implementation (for testing or custom environments). */
16
+ fetch?: typeof globalThis.fetch;
17
+ }
18
+ /** Options for creating a new channel via `POST /api/v1/channels`. */
19
+ interface CreateChannelOptions {
20
+ /** URL-safe slug identifier (lowercase + hyphens, 3-64 chars). */
21
+ id: string;
22
+ /** Human-readable display name. */
23
+ name: string;
24
+ /** Optional channel description. */
25
+ description?: string;
26
+ /** Whether the channel is publicly accessible. Defaults to `true`. */
27
+ is_public?: boolean;
28
+ /** Optional JSON Schema for event payload validation. */
29
+ schema?: Record<string, unknown>;
30
+ /** When `true`, events are rejected if they don't match `schema`. */
31
+ strict?: boolean;
32
+ }
33
+ /** Result of creating a new channel. */
34
+ interface CreateChannelResult {
35
+ /** The channel's slug identifier. */
36
+ id: string;
37
+ /** JWT token scoped for publishing to this channel. */
38
+ publish_token: string;
39
+ /** JWT token scoped for subscribing to this channel. */
40
+ subscribe_token: string;
41
+ }
42
+ /** Result of adding a publisher to a channel. */
43
+ interface PublisherResult {
44
+ /** ULID identifier for the new publisher. */
45
+ id: string;
46
+ /** Display name of the publisher. */
47
+ name: string;
48
+ /** JWT token scoped for publishing as this publisher. */
49
+ publish_token: string;
50
+ }
51
+ /** Options for publishing a single event. */
52
+ interface PublishOptions {
53
+ /** Optional event type string for subscriber filtering. */
54
+ type?: string;
55
+ /** Event payload (will be JSON-serialized, max 64 KB). */
56
+ data: unknown;
57
+ }
58
+ /** Options for polling events from a channel. */
59
+ interface PollOptions {
60
+ /** Opaque cursor from a previous poll response. */
61
+ cursor?: string;
62
+ /** ISO 8601 timestamp — only return events created after this time. */
63
+ since?: string;
64
+ /** Maximum number of events to return (default: 50). */
65
+ limit?: number;
66
+ /** Filter events by type. */
67
+ type?: string;
68
+ }
69
+ /** Options for registering a webhook. */
70
+ interface WebhookOptions {
71
+ /** Only deliver events matching these types. Omit for all events. */
72
+ event_types?: string[];
73
+ /** Webhook lifetime in seconds (default: 3 days, max: 30 days). */
74
+ ttl_seconds?: number;
75
+ }
76
+ /** Subscribe transport mode. */
77
+ type SubscribeMode = 'auto' | 'ws' | 'poll';
78
+ /** Options for the subscribe helper. */
79
+ interface SubscribeOptions {
80
+ /** Polling interval in milliseconds. Default: `5000`. */
81
+ interval?: number;
82
+ /** Transport mode. `'auto'` (default) tries WebSocket first, falls back to polling. */
83
+ mode?: SubscribeMode;
84
+ /** Event type filter — passed as `?types=` on WS, `?type=` on poll. */
85
+ type?: string;
86
+ }
87
+ /** Options for the `tail()` method. Extends poll options with follow mode. */
88
+ interface TailOptions extends PollOptions {
89
+ /** When `true`, subscribe and stream events as they arrive. */
90
+ follow?: boolean;
91
+ /** Transport mode for follow mode. Default: `'auto'`. */
92
+ mode?: SubscribeMode;
93
+ /** Polling interval in ms for follow mode (poll transport). Default: `5000`. */
94
+ interval?: number;
95
+ }
96
+ /**
97
+ * An async iterable stream of events returned by `tail({ follow: true })`.
98
+ * Call `close()` to stop the underlying subscription and end the stream.
99
+ */
100
+ interface TailStream extends AsyncIterable<ZooidEvent> {
101
+ /** Stop the subscription and end the stream. */
102
+ close(): void;
103
+ }
104
+ /** Result of generating a signed directory claim. */
105
+ interface ClaimResult {
106
+ /** Base64url-encoded JSON claim payload. */
107
+ claim: string;
108
+ /** Base64url-encoded Ed25519 signature of the claim. */
109
+ signature: string;
110
+ }
111
+ /** Options for updating server identity metadata. */
112
+ interface UpdateServerMetaOptions {
113
+ /** Server display name. */
114
+ name?: string;
115
+ /** Server description (set to `null` to clear). */
116
+ description?: string | null;
117
+ /** Tags for categorization. */
118
+ tags?: string[];
119
+ /** Operator name (set to `null` to clear). */
120
+ owner?: string | null;
121
+ /** Company name (set to `null` to clear). */
122
+ company?: string | null;
123
+ /** Contact email (set to `null` to clear). */
124
+ email?: string | null;
125
+ }
126
+
127
+ /**
128
+ * Client for the Zooid pub/sub API.
129
+ *
130
+ * Provides methods for channel management, event publishing/polling,
131
+ * webhook registration, and server metadata access.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const client = new ZooidClient({
136
+ * server: 'https://zooid.example.workers.dev',
137
+ * token: 'eyJ...',
138
+ * });
139
+ *
140
+ * const channels = await client.listChannels();
141
+ * ```
142
+ */
143
+ declare class ZooidClient {
144
+ /** Base URL of the Zooid server (trailing slashes stripped). */
145
+ readonly server: string;
146
+ private token?;
147
+ private _fetch;
148
+ constructor(options: ZooidClientOptions);
149
+ private request;
150
+ /** Fetch server discovery metadata from `GET /.well-known/zooid.json`. */
151
+ getMetadata(): Promise<ServerDiscovery>;
152
+ /** Fetch editable server identity from `GET /api/v1/server`. */
153
+ getServerMeta(): Promise<ServerIdentity>;
154
+ /** Update server identity metadata via `PUT /api/v1/server`. Requires admin token. */
155
+ updateServerMeta(options: UpdateServerMetaOptions): Promise<ServerIdentity>;
156
+ /** List all channels via `GET /api/v1/channels`. */
157
+ listChannels(): Promise<ChannelInfo[]>;
158
+ /** Create a new channel via `POST /api/v1/channels`. Requires admin token. */
159
+ createChannel(options: CreateChannelOptions): Promise<CreateChannelResult>;
160
+ /** Generate a signed claim for the Zooid Directory. Requires admin token. */
161
+ getClaim(channels: string[], action?: 'delete'): Promise<ClaimResult>;
162
+ /** Add a named publisher to a channel. Requires admin token. */
163
+ addPublisher(channelId: string, name: string): Promise<PublisherResult>;
164
+ /** Publish a single event to a channel. Requires a publish-scoped token. */
165
+ publish(channelId: string, options: PublishOptions): Promise<ZooidEvent$1>;
166
+ /** Publish multiple events in a single request. Requires a publish-scoped token. */
167
+ publishBatch(channelId: string, events: PublishOptions[]): Promise<ZooidEvent$1[]>;
168
+ /**
169
+ * Fetch events from a channel.
170
+ *
171
+ * Without `follow`, performs a one-shot poll (alias for {@link poll}).
172
+ * With `follow: true`, returns an async iterable stream that wraps {@link subscribe}.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // One-shot
177
+ * const result = await client.tail('my-channel', { limit: 10 });
178
+ *
179
+ * // Follow mode
180
+ * const stream = client.tail('my-channel', { follow: true });
181
+ * for await (const event of stream) {
182
+ * console.log(event);
183
+ * }
184
+ * ```
185
+ */
186
+ tail(channelId: string, options: TailOptions & {
187
+ follow: true;
188
+ }): TailStream;
189
+ tail(channelId: string, options?: TailOptions): Promise<PollResult>;
190
+ private createTailStream;
191
+ /** Poll events from a channel with cursor-based pagination. */
192
+ poll(channelId: string, options?: PollOptions): Promise<PollResult>;
193
+ /** Register a webhook to receive events via POST. */
194
+ registerWebhook(channelId: string, url: string, options?: WebhookOptions): Promise<WebhookResult>;
195
+ /** Remove a webhook registration. Requires admin token. */
196
+ removeWebhook(channelId: string, webhookId: string): Promise<void>;
197
+ /**
198
+ * Subscribe to a channel. Tries WebSocket first (mode `'auto'`), falls back to polling.
199
+ *
200
+ * Returns a promise that resolves with an unsubscribe function.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const unsub = await client.subscribe('my-channel', (event) => {
205
+ * console.log('New event:', event.id);
206
+ * });
207
+ *
208
+ * // Later: stop
209
+ * unsub();
210
+ * ```
211
+ */
212
+ subscribe(channelId: string, callback: (event: ZooidEvent$1) => void, options?: SubscribeOptions): Promise<() => void>;
213
+ private buildWsUrl;
214
+ private startPolling;
215
+ }
216
+
217
+ /**
218
+ * Error thrown by {@link ZooidClient} when the server returns a non-2xx response.
219
+ *
220
+ * The `status` property contains the HTTP status code, and `message` contains
221
+ * the error description from the response body (or a fallback like `"HTTP 500"`).
222
+ */
223
+ declare class ZooidError extends Error {
224
+ /** HTTP status code from the server response. */
225
+ status: number;
226
+ constructor(status: number, message: string);
227
+ }
228
+
229
+ /** Options for verifying a Zooid webhook signature. */
230
+ interface VerifyWebhookOptions {
231
+ /** The raw JSON request body string. */
232
+ body: string;
233
+ /** Base64-encoded Ed25519 signature from the `X-Zooid-Signature` header. */
234
+ signature: string;
235
+ /** ISO 8601 timestamp from the `X-Zooid-Timestamp` header. */
236
+ timestamp: string;
237
+ /** Base64-encoded SPKI public key from `/.well-known/zooid.json`. */
238
+ publicKey: string;
239
+ /** Maximum age in seconds before the timestamp is considered stale. Default: no check. */
240
+ maxAge?: number;
241
+ }
242
+ /**
243
+ * Verify an Ed25519 webhook signature from a Zooid server.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * import { verifyWebhook } from '@zooid/sdk';
248
+ *
249
+ * const isValid = await verifyWebhook({
250
+ * body: rawBody,
251
+ * signature: req.headers['x-zooid-signature'],
252
+ * timestamp: req.headers['x-zooid-timestamp'],
253
+ * publicKey: cachedPublicKey,
254
+ * maxAge: 300,
255
+ * });
256
+ * ```
257
+ */
258
+ declare function verifyWebhook(options: VerifyWebhookOptions): Promise<boolean>;
259
+
260
+ export { type ChannelInfo, type ClaimResult, type CreateChannelOptions, type CreateChannelResult, type PollOptions, type PublishOptions, type PublisherResult, type SubscribeMode, type SubscribeOptions, type TailOptions, type TailStream, type UpdateServerMetaOptions, type VerifyWebhookOptions, type WebhookOptions, type WebhookResult, ZooidClient, type ZooidClientOptions, ZooidError, verifyWebhook };
package/dist/index.js ADDED
@@ -0,0 +1,322 @@
1
+ // src/error.ts
2
+ var ZooidError = class extends Error {
3
+ /** HTTP status code from the server response. */
4
+ status;
5
+ constructor(status, message) {
6
+ super(message);
7
+ this.name = "ZooidError";
8
+ this.status = status;
9
+ }
10
+ };
11
+
12
+ // src/client.ts
13
+ var ZooidClient = class {
14
+ /** Base URL of the Zooid server (trailing slashes stripped). */
15
+ server;
16
+ token;
17
+ _fetch;
18
+ constructor(options) {
19
+ this.server = options.server.replace(/\/+$/, "");
20
+ this.token = options.token;
21
+ this._fetch = options.fetch ?? globalThis.fetch;
22
+ }
23
+ async request(method, path, body) {
24
+ const headers = {};
25
+ if (this.token) {
26
+ headers["Authorization"] = `Bearer ${this.token}`;
27
+ }
28
+ if (body !== void 0) {
29
+ headers["Content-Type"] = "application/json";
30
+ }
31
+ const res = await this._fetch(this.server + path, {
32
+ method,
33
+ headers,
34
+ body: body !== void 0 ? JSON.stringify(body) : void 0
35
+ });
36
+ if (!res.ok) {
37
+ let message = `HTTP ${res.status}`;
38
+ try {
39
+ const json = await res.json();
40
+ if (json.error) message = String(json.error);
41
+ } catch {
42
+ }
43
+ throw new ZooidError(res.status, message);
44
+ }
45
+ if (res.status === 204) {
46
+ return void 0;
47
+ }
48
+ return res.json();
49
+ }
50
+ /** Fetch server discovery metadata from `GET /.well-known/zooid.json`. */
51
+ async getMetadata() {
52
+ return this.request("GET", "/.well-known/zooid.json");
53
+ }
54
+ /** Fetch editable server identity from `GET /api/v1/server`. */
55
+ async getServerMeta() {
56
+ return this.request("GET", "/api/v1/server");
57
+ }
58
+ /** Update server identity metadata via `PUT /api/v1/server`. Requires admin token. */
59
+ async updateServerMeta(options) {
60
+ return this.request("PUT", "/api/v1/server", options);
61
+ }
62
+ /** List all channels via `GET /api/v1/channels`. */
63
+ async listChannels() {
64
+ const res = await this.request("GET", "/api/v1/channels");
65
+ return res.channels;
66
+ }
67
+ /** Create a new channel via `POST /api/v1/channels`. Requires admin token. */
68
+ async createChannel(options) {
69
+ return this.request("POST", "/api/v1/channels", options);
70
+ }
71
+ /** Generate a signed claim for the Zooid Directory. Requires admin token. */
72
+ async getClaim(channels, action) {
73
+ const body = { channels };
74
+ if (action) body.action = action;
75
+ return this.request("POST", "/api/v1/directory/claim", body);
76
+ }
77
+ /** Add a named publisher to a channel. Requires admin token. */
78
+ async addPublisher(channelId, name) {
79
+ return this.request(
80
+ "POST",
81
+ `/api/v1/channels/${channelId}/publishers`,
82
+ { name }
83
+ );
84
+ }
85
+ /** Publish a single event to a channel. Requires a publish-scoped token. */
86
+ async publish(channelId, options) {
87
+ const body = { data: options.data };
88
+ if (options.type !== void 0) {
89
+ body.type = options.type;
90
+ }
91
+ return this.request("POST", `/api/v1/channels/${channelId}/events`, body);
92
+ }
93
+ /** Publish multiple events in a single request. Requires a publish-scoped token. */
94
+ async publishBatch(channelId, events) {
95
+ const body = {
96
+ events: events.map((e) => {
97
+ const item = { data: e.data };
98
+ if (e.type !== void 0) item.type = e.type;
99
+ return item;
100
+ })
101
+ };
102
+ const res = await this.request(
103
+ "POST",
104
+ `/api/v1/channels/${channelId}/events`,
105
+ body
106
+ );
107
+ return res.events;
108
+ }
109
+ tail(channelId, options) {
110
+ if (options?.follow) {
111
+ return this.createTailStream(channelId, options);
112
+ }
113
+ return this.poll(channelId, options);
114
+ }
115
+ createTailStream(channelId, options) {
116
+ const buffer = [];
117
+ let waiting = null;
118
+ let done = false;
119
+ let unsub = null;
120
+ this.subscribe(channelId, (event) => {
121
+ if (waiting) {
122
+ const resolve = waiting;
123
+ waiting = null;
124
+ resolve({ value: event, done: false });
125
+ } else {
126
+ buffer.push(event);
127
+ }
128
+ }, {
129
+ mode: options.mode,
130
+ interval: options.interval,
131
+ type: options.type
132
+ }).then((fn) => {
133
+ unsub = fn;
134
+ if (done) fn();
135
+ });
136
+ const stream = {
137
+ close() {
138
+ done = true;
139
+ unsub?.();
140
+ if (waiting) {
141
+ waiting({ value: void 0, done: true });
142
+ waiting = null;
143
+ }
144
+ },
145
+ [Symbol.asyncIterator]() {
146
+ return {
147
+ next() {
148
+ if (buffer.length > 0) {
149
+ return Promise.resolve({ value: buffer.shift(), done: false });
150
+ }
151
+ if (done) {
152
+ return Promise.resolve({ value: void 0, done: true });
153
+ }
154
+ return new Promise((resolve) => {
155
+ waiting = resolve;
156
+ });
157
+ },
158
+ return() {
159
+ stream.close();
160
+ return Promise.resolve({ value: void 0, done: true });
161
+ }
162
+ };
163
+ }
164
+ };
165
+ return stream;
166
+ }
167
+ /** Poll events from a channel with cursor-based pagination. */
168
+ async poll(channelId, options) {
169
+ const params = new URLSearchParams();
170
+ if (options?.cursor) params.set("cursor", options.cursor);
171
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
172
+ if (options?.type) params.set("type", options.type);
173
+ if (options?.since) params.set("since", options.since);
174
+ const qs = params.toString();
175
+ const path = `/api/v1/channels/${channelId}/events${qs ? `?${qs}` : ""}`;
176
+ return this.request("GET", path);
177
+ }
178
+ /** Register a webhook to receive events via POST. */
179
+ async registerWebhook(channelId, url, options) {
180
+ return this.request("POST", `/api/v1/channels/${channelId}/webhooks`, {
181
+ url,
182
+ ...options
183
+ });
184
+ }
185
+ /** Remove a webhook registration. Requires admin token. */
186
+ async removeWebhook(channelId, webhookId) {
187
+ await this.request(
188
+ "DELETE",
189
+ `/api/v1/channels/${channelId}/webhooks/${webhookId}`
190
+ );
191
+ }
192
+ /**
193
+ * Subscribe to a channel. Tries WebSocket first (mode `'auto'`), falls back to polling.
194
+ *
195
+ * Returns a promise that resolves with an unsubscribe function.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const unsub = await client.subscribe('my-channel', (event) => {
200
+ * console.log('New event:', event.id);
201
+ * });
202
+ *
203
+ * // Later: stop
204
+ * unsub();
205
+ * ```
206
+ */
207
+ async subscribe(channelId, callback, options) {
208
+ const mode = options?.mode ?? "auto";
209
+ if (mode === "poll") {
210
+ return this.startPolling(channelId, callback, options);
211
+ }
212
+ const tryWs = (retryOnFail) => {
213
+ return new Promise((resolve, reject) => {
214
+ const url = this.buildWsUrl(channelId, options);
215
+ const ws = new globalThis.WebSocket(url);
216
+ ws.onopen = () => {
217
+ resolve(() => {
218
+ ws.close();
219
+ });
220
+ };
221
+ ws.onmessage = (e) => {
222
+ try {
223
+ const event = JSON.parse(String(e.data));
224
+ callback(event);
225
+ } catch {
226
+ }
227
+ };
228
+ ws.onclose = () => {
229
+ };
230
+ ws.onerror = () => {
231
+ ws.close();
232
+ if (retryOnFail) {
233
+ setTimeout(() => {
234
+ tryWs(false).then(resolve, reject);
235
+ }, 1e3);
236
+ } else if (mode === "auto") {
237
+ this.startPolling(channelId, callback, options).then(resolve, reject);
238
+ } else {
239
+ reject(new Error("WebSocket connection failed"));
240
+ }
241
+ };
242
+ });
243
+ };
244
+ return tryWs(true);
245
+ }
246
+ buildWsUrl(channelId, options) {
247
+ const base = this.server.replace(/^http:\/\//, "ws://").replace(/^https:\/\//, "wss://");
248
+ const params = new URLSearchParams();
249
+ if (this.token) params.set("token", this.token);
250
+ if (options?.type) params.set("types", options.type);
251
+ const qs = params.toString();
252
+ return `${base}/api/v1/channels/${channelId}/ws${qs ? `?${qs}` : ""}`;
253
+ }
254
+ startPolling(channelId, callback, options) {
255
+ const interval = options?.interval ?? 5e3;
256
+ const type = options?.type;
257
+ let cursor = null;
258
+ let timer = null;
259
+ let stopped = false;
260
+ const doPoll = async () => {
261
+ try {
262
+ const pollOpts = {};
263
+ if (cursor) pollOpts.cursor = cursor;
264
+ if (type) pollOpts.type = type;
265
+ const result = await this.poll(channelId, pollOpts);
266
+ if (stopped) return;
267
+ for (const event of result.events) {
268
+ callback(event);
269
+ }
270
+ if (result.cursor) {
271
+ cursor = result.cursor;
272
+ }
273
+ } catch {
274
+ }
275
+ };
276
+ doPoll();
277
+ timer = setInterval(doPoll, interval);
278
+ return Promise.resolve(() => {
279
+ stopped = true;
280
+ if (timer !== null) {
281
+ clearInterval(timer);
282
+ timer = null;
283
+ }
284
+ });
285
+ }
286
+ };
287
+
288
+ // src/verify.ts
289
+ async function verifyWebhook(options) {
290
+ const { body, signature, timestamp, publicKey, maxAge } = options;
291
+ if (maxAge !== void 0) {
292
+ const ts = Date.parse(timestamp);
293
+ if (Number.isNaN(ts)) return false;
294
+ const age = (Date.now() - ts) / 1e3;
295
+ if (age > maxAge || age < -maxAge) return false;
296
+ }
297
+ const key = await crypto.subtle.importKey(
298
+ "spki",
299
+ base64ToArrayBuffer(publicKey),
300
+ "Ed25519",
301
+ false,
302
+ ["verify"]
303
+ );
304
+ const message = new TextEncoder().encode(`${timestamp}.${body}`);
305
+ const sigBytes = base64ToArrayBuffer(signature);
306
+ return crypto.subtle.verify("Ed25519", key, sigBytes, message);
307
+ }
308
+ function base64ToArrayBuffer(input) {
309
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
310
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
311
+ const binary = atob(padded);
312
+ const bytes = new Uint8Array(binary.length);
313
+ for (let i = 0; i < binary.length; i++) {
314
+ bytes[i] = binary.charCodeAt(i);
315
+ }
316
+ return bytes.buffer;
317
+ }
318
+ export {
319
+ ZooidClient,
320
+ ZooidError,
321
+ verifyWebhook
322
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@zooid/sdk",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@zooid/types": "0.0.1"
17
+ },
18
+ "devDependencies": {
19
+ "@cloudflare/vitest-pool-workers": "^0.8.34",
20
+ "tsup": "^8.5.1",
21
+ "vitest": "^3.2.0"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "license": "MIT",
27
+ "author": "Ori Ben",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/zooid-ai/zooid",
31
+ "directory": "packages/sdk"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup src/index.ts --format esm,cjs --dts",
35
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
36
+ "test": "vitest run",
37
+ "test:integration": "vitest run --config vitest.integration.config.ts"
38
+ }
39
+ }