@planescrm/sdk 0.2.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/README.md +155 -0
- package/dist/index.d.mts +596 -0
- package/dist/index.d.ts +596 -0
- package/dist/index.js +646 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +581 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# @planes/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the [Planes CRM](https://planescrm.com) platform — a typed API
|
|
4
|
+
client, an ergonomic webhook router, OAuth helpers, and an action-handler server
|
|
5
|
+
for building agents.
|
|
6
|
+
|
|
7
|
+
> **Not yet published.** `@planes/sdk` is currently a private workspace package —
|
|
8
|
+
> it is **not** on npm. Consume it from within this monorepo as a workspace
|
|
9
|
+
> dependency (`"@planes/sdk": "workspace:*"`), or request access from the Planes
|
|
10
|
+
> team. The `npm install @planes/sdk` examples below describe the intended
|
|
11
|
+
> published surface once publishing is enabled.
|
|
12
|
+
|
|
13
|
+
## Client
|
|
14
|
+
|
|
15
|
+
`PlanesClient` is a typed client over the `/api/v1` REST surface. It accepts
|
|
16
|
+
exactly **one** authentication mode.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { PlanesClient } from '@planes/sdk';
|
|
20
|
+
|
|
21
|
+
// 1. A pre-obtained OAuth access token (not auto-refreshed).
|
|
22
|
+
const client = new PlanesClient({ accessToken: 'tok_...' });
|
|
23
|
+
|
|
24
|
+
// 2. A pk_live_ API key.
|
|
25
|
+
const client = new PlanesClient({ apiKey: 'pk_live_...' });
|
|
26
|
+
|
|
27
|
+
// 3. OAuth-managed — the client obtains and refreshes access tokens itself,
|
|
28
|
+
// persisting them through a TokenStorage (in-memory by default).
|
|
29
|
+
const client = new PlanesClient({
|
|
30
|
+
clientId: process.env.PLANES_CLIENT_ID!,
|
|
31
|
+
clientSecret: process.env.PLANES_CLIENT_SECRET!,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const { data, total } = await client.opportunities.list({ page: 1, limit: 20 });
|
|
35
|
+
const opp = await client.opportunities.get('opp_123');
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Resources: `opportunities`, `customers`, `properties`, `calendarEvents`,
|
|
39
|
+
`contracts`, `jobs`, `estimates`, `invoices`, `inventory`.
|
|
40
|
+
|
|
41
|
+
### Errors
|
|
42
|
+
|
|
43
|
+
Every failure throws a subclass of `PlanesError`, each carrying `status` and
|
|
44
|
+
`code`:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { PlanesError, RateLimitError, ValidationError } from '@planes/sdk';
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await client.opportunities.get('missing');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof RateLimitError) {
|
|
53
|
+
await sleep(err.retryAfter * 1000); // seconds
|
|
54
|
+
} else if (err instanceof ValidationError) {
|
|
55
|
+
console.error(err.fieldErrors); // [{ field, message }]
|
|
56
|
+
} else if (err instanceof PlanesError) {
|
|
57
|
+
console.error(err.status, err.code, err.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Webhooks
|
|
63
|
+
|
|
64
|
+
`client.webhooks` is an ergonomic router over the signed webhook envelope. Handlers
|
|
65
|
+
subscribe to a `"{entity_type}.{event_type}"` key, an entity wildcard
|
|
66
|
+
`"{entity_type}.*"`, or the global wildcard `"*"`. It holds no auth — the signing
|
|
67
|
+
secret is supplied per `process()` call — so wire it into your own HTTP handler.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
client.webhooks
|
|
71
|
+
.on('opportunity.created', (d) => console.log(d.event.snapshot))
|
|
72
|
+
.on('opportunity.*', (d) => audit(d))
|
|
73
|
+
.on('*', (d) => metrics.count(d.event.entity_type));
|
|
74
|
+
|
|
75
|
+
// inside your POST route (Express-style):
|
|
76
|
+
app.post('/webhooks/planes', async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
await client.webhooks.process({
|
|
79
|
+
body: req.rawBody, // the verbatim request body string
|
|
80
|
+
signature: req.header('X-Planes-Signature')!,
|
|
81
|
+
secret: process.env.PLANES_WEBHOOK_SECRET!,
|
|
82
|
+
});
|
|
83
|
+
res.sendStatus(204);
|
|
84
|
+
} catch {
|
|
85
|
+
res.sendStatus(400); // signature verification failed
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## OAuth (authorization-code flow)
|
|
91
|
+
|
|
92
|
+
Use the OAuth helpers to drive the login redirect and exchange the returned code
|
|
93
|
+
for tokens.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { buildAuthorizeUrl, exchangeCode } from '@planes/sdk';
|
|
97
|
+
|
|
98
|
+
const baseUrl = 'https://api.planescrm.com/api/v1';
|
|
99
|
+
|
|
100
|
+
// 1. Redirect the user to the authorization URL.
|
|
101
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
102
|
+
baseUrl,
|
|
103
|
+
clientId: process.env.PLANES_CLIENT_ID!,
|
|
104
|
+
redirectUri: 'https://your.app/oauth/callback',
|
|
105
|
+
scopes: ['opportunities:read', 'opportunities:write'],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 2. In your callback handler, exchange the code for tokens.
|
|
109
|
+
const tokens = await exchangeCode({
|
|
110
|
+
baseUrl,
|
|
111
|
+
clientId: process.env.PLANES_CLIENT_ID!,
|
|
112
|
+
clientSecret: process.env.PLANES_CLIENT_SECRET!,
|
|
113
|
+
code: req.query.code as string,
|
|
114
|
+
redirectUri: 'https://your.app/oauth/callback',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 3. Seed an OAuth-managed client so refreshes happen automatically.
|
|
118
|
+
import { PlanesClient, MemoryTokenStorage } from '@planes/sdk';
|
|
119
|
+
|
|
120
|
+
const client = new PlanesClient({
|
|
121
|
+
clientId: process.env.PLANES_CLIENT_ID!,
|
|
122
|
+
clientSecret: process.env.PLANES_CLIENT_SECRET!,
|
|
123
|
+
tokenStorage: new MemoryTokenStorage({
|
|
124
|
+
accessToken: tokens.access_token,
|
|
125
|
+
refreshToken: tokens.refresh_token,
|
|
126
|
+
expiresAt: Date.now() + tokens.expires_in * 1000,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Agents (action handlers)
|
|
132
|
+
|
|
133
|
+
Build an agent HTTP server that receives platform action requests. `createAgentApp()`
|
|
134
|
+
gives a chainable builder; call `registerAction(type, handler)` per action and
|
|
135
|
+
`listen()` to start.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { createAgentApp } from '@planes/sdk';
|
|
139
|
+
|
|
140
|
+
createAgentApp()
|
|
141
|
+
.registerAction('enrich-opportunity', async (ctx) => {
|
|
142
|
+
// ctx is the flattened ActionContext: action_type, entity_type,
|
|
143
|
+
// entity_id, tenant_id, data, stage, metadata, callback_token, ...
|
|
144
|
+
const enriched = await enrich(ctx.entity_id);
|
|
145
|
+
return { status: 'completed', output: { enriched } };
|
|
146
|
+
})
|
|
147
|
+
.registerAction('send-welcome-email', async (ctx) => {
|
|
148
|
+
await sendEmail(ctx.data);
|
|
149
|
+
return { status: 'completed', output: { sent: true } };
|
|
150
|
+
})
|
|
151
|
+
.listen({ port: 3000, path: '/actions' });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The handler's returned `ActionResult` is serialized straight back as the HTTP
|
|
155
|
+
response — which is exactly how the platform's external-agent handler consumes it.
|