@workwae/sdk 0.0.0-stage → 0.1.0-alpha.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 +233 -2
- package/client.d.ts +36 -0
- package/contract/accounts/models.d.ts +726 -0
- package/contract/accounts/routes.d.ts +1318 -0
- package/contract/ai/models.d.ts +30 -0
- package/contract/ai/routes.d.ts +89 -0
- package/contract/audit-log/models.d.ts +107 -0
- package/contract/audit-log/routes.d.ts +72 -0
- package/contract/calendar/models.d.ts +837 -0
- package/contract/calendar/routes.d.ts +1299 -0
- package/contract/connect/models.d.ts +272 -0
- package/contract/connect/routes.d.ts +531 -0
- package/contract/contacts/models.d.ts +269 -0
- package/contract/contacts/routes.d.ts +634 -0
- package/contract/errors.d.ts +108 -0
- package/contract/events/models.d.ts +2364 -0
- package/contract/events/routes.d.ts +107 -0
- package/contract/exports/models.d.ts +67 -0
- package/contract/exports/routes.d.ts +196 -0
- package/contract/identities/models.d.ts +100 -0
- package/contract/identities/routes.d.ts +249 -0
- package/contract/limits.d.ts +170 -0
- package/contract/mail/models.d.ts +528 -0
- package/contract/mail/routes.d.ts +749 -0
- package/contract/meetings/models.d.ts +232 -0
- package/contract/meetings/routes.d.ts +386 -0
- package/contract/oauth-apps/models.d.ts +92 -0
- package/contract/oauth-apps/routes.d.ts +187 -0
- package/contract/org-connections/models.d.ts +383 -0
- package/contract/org-connections/routes.d.ts +830 -0
- package/contract/primitives.d.ts +140 -0
- package/contract/public.d.ts +26 -0
- package/contract/reasons.d.ts +711 -0
- package/contract/router.d.ts +9773 -0
- package/contract/send/models.d.ts +750 -0
- package/contract/send/routes.d.ts +1239 -0
- package/contract/settings/models.d.ts +103 -0
- package/contract/settings/routes.d.ts +110 -0
- package/contract/suppressions/models.d.ts +231 -0
- package/contract/suppressions/routes.d.ts +317 -0
- package/contract/templates/models.d.ts +169 -0
- package/contract/templates/routes.d.ts +281 -0
- package/contract/threads/models.d.ts +117 -0
- package/contract/threads/routes.d.ts +336 -0
- package/contract/usage/models.d.ts +213 -0
- package/contract/usage/routes.d.ts +81 -0
- package/contract/webhooks/models.d.ts +879 -0
- package/contract/webhooks/routes.d.ts +839 -0
- package/contract/webhooks/signing.d.ts +35 -0
- package/errors.d.ts +15 -0
- package/index.d.ts +6 -0
- package/index.js +4334 -0
- package/index.js.map +59 -0
- package/package.json +45 -4
- package/pagination.d.ts +40 -0
- package/webhooks.d.ts +28 -0
package/README.md
CHANGED
|
@@ -1,3 +1,234 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @workwae/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The TypeScript client for the [workwae](https://workwae.com) API: one REST API for Gmail,
|
|
4
|
+
Microsoft 365 and IMAP mailboxes, Google, Microsoft and CalDAV calendars, and contacts. Every
|
|
5
|
+
method, input, answer and error is typed from the same contract the API is built on.
|
|
6
|
+
|
|
7
|
+
- Typed methods for every `/v1` operation, with dates as `Date`
|
|
8
|
+
- Cursor helpers for every listing and the change feed
|
|
9
|
+
- Typed errors keyed by stable reasons, with the request id
|
|
10
|
+
- Automatic retries of 429 and 503 answers, honoring `Retry-After`
|
|
11
|
+
- Idempotency keys on every POST
|
|
12
|
+
- Webhook signature verification (Standard Webhooks) and typed event parsing
|
|
13
|
+
|
|
14
|
+
The full guides and API reference are in the board's docs, at `/docs` (`/docs/api` for every
|
|
15
|
+
operation, `/docs/changelog` for what changed).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun add @workwae/sdk
|
|
21
|
+
# or
|
|
22
|
+
npm install @workwae/sdk
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @workwae/sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The package is ESM only and runs on Node.js 20 or later, Bun, Deno, Cloudflare Workers and
|
|
28
|
+
browsers (it needs `fetch` and Web Crypto, nothing else). Its runtime dependencies are `zod` and
|
|
29
|
+
`@orpc/*`.
|
|
30
|
+
|
|
31
|
+
## Create a client
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createClient } from "@workwae/sdk";
|
|
35
|
+
|
|
36
|
+
const workwae = createClient({ apiKey: process.env.WORKWAE_API_KEY });
|
|
37
|
+
|
|
38
|
+
const { items: accounts, nextCursor } = await workwae.accounts.list({ limit: 50 });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Option | Meaning |
|
|
42
|
+
| --------- | --------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `baseUrl` | Defaults to `https://api.workwae.com`. Set it only to route through your own proxy or gateway. |
|
|
44
|
+
| `apiKey` | An API key (`wk_...`), an account token (`wat_...`), or a function returning one per request. |
|
|
45
|
+
| `headers` | Extra headers sent with every request. |
|
|
46
|
+
| `fetch` | Replaces the global `fetch` (a proxy agent, instrumentation, tests). |
|
|
47
|
+
| `retry` | `{ maxRetries, maxDelayMs, wait }`, see [Retries](#retries). |
|
|
48
|
+
|
|
49
|
+
Methods are named after the operation, grouped by resource: `workwae.accounts.get`,
|
|
50
|
+
`workwae.outbox.create`, `workwae.calendarEvents.list`, `workwae.webhooks.replay`. Each takes one
|
|
51
|
+
object holding the path, query and body fields together:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const message = await workwae.messages.get({ accountId, messageId });
|
|
55
|
+
|
|
56
|
+
const queued = await workwae.outbox.create({
|
|
57
|
+
accountId,
|
|
58
|
+
to: [{ name: "Ada Lovelace", address: "ada@example.com" }],
|
|
59
|
+
subject: "Hello",
|
|
60
|
+
text: "Sent with workwae.",
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`DELETE` operations that take options keep the path and the query apart:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
await workwae.accounts.delete({ params: { accountId }, query: { purge: true } });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A second argument carries per-call options: `signal` aborts the call (and any retry wait), and
|
|
71
|
+
`context.idempotencyKey` sets the idempotency key.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
setTimeout(() => controller.abort(), 10_000);
|
|
76
|
+
await workwae.accounts.list({}, { signal: controller.signal });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
API keys are created on the board under **API keys**, with the scopes the integration needs
|
|
82
|
+
(`read`, `write`, `send`, `admin`). They reach every account of the tenant, so keep them on your
|
|
83
|
+
server.
|
|
84
|
+
|
|
85
|
+
To let a browser or mobile app read one account directly, mint a short-lived account token on
|
|
86
|
+
your server and hand it over; it is limited to that one account:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// Server
|
|
90
|
+
const { token, expiresAt } = await workwae.accounts.mintToken({ accountId, ttlSeconds: 900 });
|
|
91
|
+
|
|
92
|
+
// Browser: fetch a fresh token from your backend whenever the client needs one.
|
|
93
|
+
const client = createClient({
|
|
94
|
+
apiKey: () => fetch("/api/workwae-token").then((response) => response.text()),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Pagination
|
|
99
|
+
|
|
100
|
+
Listings answer `{ items, nextCursor }`; pass `nextCursor` back as `cursor` until it is `null`.
|
|
101
|
+
The helpers take the method itself, so filters and item types stay typed:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { changes, pages, paginate } from "@workwae/sdk";
|
|
105
|
+
|
|
106
|
+
// Every item, to the last page.
|
|
107
|
+
for await (const message of paginate(workwae.messages.list, { accountId, unread: true })) {
|
|
108
|
+
console.log(message.subject);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Page by page.
|
|
112
|
+
for await (const page of pages(workwae.accounts.list, { limit: 200 })) {
|
|
113
|
+
console.log(page.items.length, page.nextCursor);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The change feed, until caught up. Persist feed.cursor after processing and resume from it:
|
|
117
|
+
// a page is only passed once its last event was consumed (at-least-once delivery).
|
|
118
|
+
const feed = changes(workwae.changes.list, { cursor: await loadCursor() });
|
|
119
|
+
for await (const event of feed) await handle(event);
|
|
120
|
+
await saveCursor(feed.cursor);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Errors
|
|
124
|
+
|
|
125
|
+
An API refusal is thrown as an `ORPCError` whose `code` is the error's stable reason
|
|
126
|
+
(`account_not_found`, `invalid_input`, `plan_limit_sends`, ...), `status` its HTTP status,
|
|
127
|
+
`message` a human-readable text, `data` the reason's details when it has any, and `requestId`
|
|
128
|
+
the answer's `X-Request-Id` (quote it to support).
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { isApiError, isDefinedError, safe } from "@workwae/sdk";
|
|
132
|
+
|
|
133
|
+
// safe() resolves to [error, data]; isDefinedError() narrows to the reasons the operation declares.
|
|
134
|
+
const [error, account] = await safe(workwae.accounts.get({ accountId }));
|
|
135
|
+
if (isDefinedError(error)) {
|
|
136
|
+
if (error.code === "account_not_found") {
|
|
137
|
+
// ...
|
|
138
|
+
}
|
|
139
|
+
} else if (error) {
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// isApiError(error, reason) checks for one reason anywhere and narrows `data` to it.
|
|
144
|
+
try {
|
|
145
|
+
await workwae.outbox.create({ accountId, to: [], text: "hi" });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isApiError(error, "invalid_input")) console.log(error.data.issues);
|
|
148
|
+
else if (isApiError(error)) console.error(error.code, error.status, error.requestId);
|
|
149
|
+
else throw error;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`ErrorReason` is the union of every reason. Errors that are not API answers (network failures,
|
|
154
|
+
an aborted signal) are thrown as they are.
|
|
155
|
+
|
|
156
|
+
## Retries
|
|
157
|
+
|
|
158
|
+
429 (`rate_limited`, `too_many_requests`) and 503 (`overloaded`) answers are refused before the
|
|
159
|
+
API acts, so the client retries them: twice by default, waiting for `Retry-After`, or backing off
|
|
160
|
+
exponentially with jitter when there is none. A `Retry-After` longer than `maxDelayMs` is not
|
|
161
|
+
waited for: the error is thrown.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const workwae = createClient({
|
|
165
|
+
apiKey,
|
|
166
|
+
retry: { maxRetries: 4, maxDelayMs: 30_000 },
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Rate-limited answers also carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`
|
|
171
|
+
headers; read them through a custom `fetch` if you want to pace yourself.
|
|
172
|
+
|
|
173
|
+
## Idempotency keys
|
|
174
|
+
|
|
175
|
+
Every POST is sent with an `Idempotency-Key`, generated per call and reused across the client's
|
|
176
|
+
own retries, so a retried send never goes out twice. To make a call safe to repeat across
|
|
177
|
+
process restarts (a job queue retrying a send, say), pass a key of your own derived from your
|
|
178
|
+
data:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
await workwae.outbox.create(
|
|
182
|
+
{ accountId, to: [{ address: "ada@example.com" }], subject: "Invoice 1042", text: "..." },
|
|
183
|
+
{ context: { idempotencyKey: "invoice-1042" } },
|
|
184
|
+
);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The API keeps the first answer for 24 hours per tenant and key and replays it for a repeat with
|
|
188
|
+
the same body. The same key with a different body is refused with `idempotency_key_reused`; a
|
|
189
|
+
repeat while the first call still runs with `idempotency_key_in_progress`.
|
|
190
|
+
|
|
191
|
+
## Webhooks
|
|
192
|
+
|
|
193
|
+
workwae signs deliveries with [Standard Webhooks](https://www.standardwebhooks.com/).
|
|
194
|
+
`parseWebhook` verifies a delivery and returns its event, typed by `type`, with dates as `Date`.
|
|
195
|
+
Pass the raw body exactly as received:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { parseWebhook, WebhookVerificationError } from "@workwae/sdk";
|
|
199
|
+
|
|
200
|
+
export async function POST(request: Request) {
|
|
201
|
+
const body = await request.text();
|
|
202
|
+
try {
|
|
203
|
+
const event = await parseWebhook(process.env.WORKWAE_WEBHOOK_SECRET!, request.headers, body);
|
|
204
|
+
if (event.known && event.type === "send.failed") {
|
|
205
|
+
console.log(event.data.outboxId, event.data.error.code);
|
|
206
|
+
}
|
|
207
|
+
// event.known is false for an event type added after this SDK version.
|
|
208
|
+
return new Response(null, { status: 204 });
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof WebhookVerificationError) return new Response(null, { status: 400 });
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`verifyWebhook(secret, headers, body)` only checks the signature and answers a boolean;
|
|
217
|
+
`checkWebhookSignature` answers why it failed (`missing_headers`, `timestamp`, `signature`).
|
|
218
|
+
Both accept a `toleranceSeconds` option (default `WEBHOOK_TOLERANCE_SECONDS`, 300).
|
|
219
|
+
|
|
220
|
+
## Types
|
|
221
|
+
|
|
222
|
+
Every model of the API is exported as a type, under the name the OpenAPI document gives it:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import type { Account, CalendarEvent, Event, EventType, Message, OutboxEntry } from "@workwae/sdk";
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`EVENT_TYPES` lists every event type at runtime (for webhook endpoint filters), and the
|
|
229
|
+
`MAX_TEMPLATE_*` constants are the template field limits.
|
|
230
|
+
|
|
231
|
+
## Versioning
|
|
232
|
+
|
|
233
|
+
The SDK follows semver from 0.1.0; while it is 0.x, a minor version may carry breaking changes,
|
|
234
|
+
always listed in the changelog. Releases are tagged `sdk-v<version>` in the repository.
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ContractRouterClient } from "@orpc/contract";
|
|
2
|
+
import { type V1Contract } from "./contract/public.js";
|
|
3
|
+
/** Per call, as `{ context }`: `workwae.send.create(input, { context: { idempotencyKey } })`. */
|
|
4
|
+
export interface CallContext {
|
|
5
|
+
/** The POST's Idempotency-Key. Without one, each POST call sends a key of its own, the same on
|
|
6
|
+
* every automatic retry. Pass one to make retries of your own (a later process, a timeout)
|
|
7
|
+
* answer what the first attempt did. */
|
|
8
|
+
idempotencyKey?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Every /v1 procedure, typed from the contract: `workwae.accounts.get({ accountId })`. */
|
|
11
|
+
export type WorkwaeClient = ContractRouterClient<V1Contract, CallContext>;
|
|
12
|
+
export interface RetryOptions {
|
|
13
|
+
/** Further attempts after a 429 or 503. Default 2. */
|
|
14
|
+
maxRetries?: number;
|
|
15
|
+
/** Longest single wait. A Retry-After beyond it is not waited for: the error is thrown. Default
|
|
16
|
+
* 60 000 ms. */
|
|
17
|
+
maxDelayMs?: number;
|
|
18
|
+
/** How to wait between attempts; it should reject when `signal` aborts. Default: a timer. */
|
|
19
|
+
wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface ClientOptions {
|
|
22
|
+
/** The API origin; the /v1 paths are appended to it, so a path prefix is kept. Defaults to the
|
|
23
|
+
* hosted API; set it only to go through a proxy or gateway of your own. */
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
/** An API key (`wk_...`) or an account token (`wat_...`), or a function returning one per
|
|
26
|
+
* request, for tokens that are refreshed. */
|
|
27
|
+
apiKey?: string | (() => string | Promise<string>);
|
|
28
|
+
/** Extra headers sent with every request. */
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
/** Replaces the global fetch (a proxy agent, instrumentation, tests). Only this call shape is
|
|
31
|
+
* used, so a plain function works where a runtime's `typeof fetch` carries extra members. */
|
|
32
|
+
fetch?: (request: Request, init?: RequestInit) => Promise<Response>;
|
|
33
|
+
retry?: RetryOptions;
|
|
34
|
+
}
|
|
35
|
+
/** A typed client for the workwae /v1 API. */
|
|
36
|
+
export declare function createClient(options: ClientOptions): WorkwaeClient;
|