@workwae/sdk 0.0.0-stage → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +233 -2
  2. package/client.d.ts +36 -0
  3. package/contract/accounts/models.d.ts +766 -0
  4. package/contract/accounts/routes.d.ts +1480 -0
  5. package/contract/ai/models.d.ts +29 -0
  6. package/contract/ai/routes.d.ts +98 -0
  7. package/contract/audit-log/models.d.ts +121 -0
  8. package/contract/audit-log/routes.d.ts +81 -0
  9. package/contract/calendar/models.d.ts +1012 -0
  10. package/contract/calendar/routes.d.ts +1843 -0
  11. package/contract/connect/models.d.ts +284 -0
  12. package/contract/connect/routes.d.ts +570 -0
  13. package/contract/contacts/models.d.ts +259 -0
  14. package/contract/contacts/routes.d.ts +648 -0
  15. package/contract/email-validation/models.d.ts +119 -0
  16. package/contract/email-validation/routes.d.ts +106 -0
  17. package/contract/errors.d.ts +116 -0
  18. package/contract/events/models.d.ts +2424 -0
  19. package/contract/events/routes.d.ts +117 -0
  20. package/contract/exports/models.d.ts +67 -0
  21. package/contract/exports/routes.d.ts +206 -0
  22. package/contract/identities/models.d.ts +98 -0
  23. package/contract/identities/routes.d.ts +318 -0
  24. package/contract/limits.d.ts +118 -0
  25. package/contract/mail/models.d.ts +571 -0
  26. package/contract/mail/routes.d.ts +1189 -0
  27. package/contract/mcp-keys/models.d.ts +125 -0
  28. package/contract/mcp-keys/routes.d.ts +151 -0
  29. package/contract/meetings/models.d.ts +227 -0
  30. package/contract/meetings/routes.d.ts +405 -0
  31. package/contract/oauth-apps/models.d.ts +86 -0
  32. package/contract/oauth-apps/routes.d.ts +195 -0
  33. package/contract/org-connections/models.d.ts +427 -0
  34. package/contract/org-connections/routes.d.ts +1107 -0
  35. package/contract/plans/credit-packs.d.ts +30 -0
  36. package/contract/plans/models.d.ts +208 -0
  37. package/contract/primitives.d.ts +85 -0
  38. package/contract/public.d.ts +32 -0
  39. package/contract/reasons.d.ts +1222 -0
  40. package/contract/router.d.ts +12986 -0
  41. package/contract/scheduling/models.d.ts +405 -0
  42. package/contract/scheduling/routes.d.ts +756 -0
  43. package/contract/send/models.d.ts +787 -0
  44. package/contract/send/routes.d.ts +1488 -0
  45. package/contract/settings/models.d.ts +163 -0
  46. package/contract/settings/routes.d.ts +162 -0
  47. package/contract/suppressions/models.d.ts +224 -0
  48. package/contract/suppressions/routes.d.ts +365 -0
  49. package/contract/templates/models.d.ts +155 -0
  50. package/contract/templates/routes.d.ts +305 -0
  51. package/contract/threads/models.d.ts +112 -0
  52. package/contract/threads/routes.d.ts +376 -0
  53. package/contract/tracking-domain/models.d.ts +41 -0
  54. package/contract/tracking-domain/routes.d.ts +193 -0
  55. package/contract/usage/models.d.ts +185 -0
  56. package/contract/usage/routes.d.ts +109 -0
  57. package/contract/webhooks/models.d.ts +894 -0
  58. package/contract/webhooks/routes.d.ts +882 -0
  59. package/contract/webhooks/signing.d.ts +19 -0
  60. package/errors.d.ts +15 -0
  61. package/index.d.ts +6 -0
  62. package/index.js +1 -0
  63. package/package.json +44 -4
  64. package/pagination.d.ts +40 -0
  65. package/webhooks.d.ts +28 -0
package/README.md CHANGED
@@ -1,3 +1,234 @@
1
- # Temporary Holding Version
1
+ # @workwae/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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 at https://docs.workwae.com (`/reference` for every
15
+ operation, `/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 console 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](https://docs.workwae.com/changelog).
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;