@canopy-io/node 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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/index.cjs +625 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9688 -0
- package/dist/index.d.ts +9688 -0
- package/dist/index.js +611 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Canopy Identity Inc.
|
|
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,113 @@
|
|
|
1
|
+
# @canopy-io/node
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for [Canopy](https://canopy-io.com) — hierarchical identity and access management for B2B SaaS.
|
|
4
|
+
|
|
5
|
+
> **Status: 0.1.0.** The client, pagination and the four main resources are built and tested. 27 of 81 operations have a typed wrapper; the rest are reachable through `canopy.client.request` with the same envelope handling, error typing and retry policy.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @canopy-io/node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node 18 or later. Ships ESM and CommonJS, and has **zero runtime dependencies**.
|
|
14
|
+
|
|
15
|
+
Despite the name, it is not Node-only: the client is `fetch` and nothing else, so the same build runs in browsers, on Cloudflare Workers and on Deno.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Canopy, isCanopyError } from "@canopy-io/node";
|
|
21
|
+
|
|
22
|
+
const canopy = new Canopy({ apiKey: process.env.CANOPY_API_KEY });
|
|
23
|
+
|
|
24
|
+
// The call every integrator makes on every request.
|
|
25
|
+
const { allowed } = await canopy.permissions.evaluate({
|
|
26
|
+
identity_id: identityId,
|
|
27
|
+
permission: "documents.read",
|
|
28
|
+
scope: "node",
|
|
29
|
+
node_id: nodeId,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Pagination is handled for you, whichever style the endpoint uses.
|
|
33
|
+
for await (const identity of canopy.identities.list({ take: 50 })) {
|
|
34
|
+
console.log(identity.email);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Errors carry a stable code, not just a message.
|
|
38
|
+
try {
|
|
39
|
+
await canopy.assignments.create({
|
|
40
|
+
identity_id: identityId,
|
|
41
|
+
node_id: nodeId,
|
|
42
|
+
role_id: roleId,
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (isCanopyError(error) && error.code === "rbac.assignment_conflict") {
|
|
46
|
+
// Already assigned — not a failure worth surfacing.
|
|
47
|
+
} else {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Anything without a typed wrapper is still reachable, with the same envelope
|
|
54
|
+
handling and retry policy:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const page = await canopy.client.request("GET", "/api/v1/audit-events", {
|
|
58
|
+
query: { limit: 50 },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Design
|
|
63
|
+
|
|
64
|
+
Three decisions shape this package.
|
|
65
|
+
|
|
66
|
+
**Types are generated from the published spec, never hand-written.**
|
|
67
|
+
`src/generated/types.ts` comes from <https://canopy-io.com/openapi/api.json>, the same document that renders Canopy's API reference. `npm run generate:check` fails if the committed types no longer match the live spec, so the SDK cannot silently describe an API that has moved on.
|
|
68
|
+
|
|
69
|
+
**Zero runtime dependencies.**
|
|
70
|
+
The client is `fetch` and nothing else, so it runs unchanged on Node, in browsers, on Cloudflare Workers, and on Deno — and it adds no supply-chain surface to anything that installs it. CI fails if a runtime dependency appears.
|
|
71
|
+
|
|
72
|
+
**The hand-written layer exists for correctness, not convenience.**
|
|
73
|
+
Writing a `fetch` call against a documented REST API is easy, and an LLM will do it for you. What neither gets reliably right is the part this package owns:
|
|
74
|
+
|
|
75
|
+
- **Which operations are safe to retry.** GET, HEAD, PUT and DELETE are idempotent by HTTP definition and are retried on a 5xx; POST is not, and a blind retry there can create a second role assignment. A 429 is retried regardless, because the request was refused before anything happened.
|
|
76
|
+
- **Two pagination styles behind one shape.** The audit log is cursor-paginated; everything else is offset. The top-level response is identical either way, so a hand-rolled loop silently reads only the first page of one of them — or never terminates.
|
|
77
|
+
- **The five-shape response envelope.** `{ data }`, `{ items }`, `{ items, pagination }`, `{ summary, results }` for partial success, `{ error }`, and bare 204.
|
|
78
|
+
- **Typed error codes.** `catch (e) { if (e.code === "rbac.assignment_conflict") }` branches on a contract rather than on a message that may be reworded.
|
|
79
|
+
- **Webhook signature verification.** Security-critical, and easy to get subtly wrong — timing-unsafe comparison, missing timestamp check. _(Not built yet — see the roadmap.)_
|
|
80
|
+
|
|
81
|
+
## Roadmap
|
|
82
|
+
|
|
83
|
+
- [x] Package scaffold, dual ESM/CJS build, generated types
|
|
84
|
+
- [x] Client core — auth, envelope unwrapping, typed errors, retry
|
|
85
|
+
- [x] Pagination — one async iterator covering offset and cursor
|
|
86
|
+
- [x] Resource wrappers — `permissions`, `identities`, `roles`, `assignments`
|
|
87
|
+
- [x] Spec-drift guard — fails when the API's surface changes
|
|
88
|
+
- [ ] Webhook signature verification
|
|
89
|
+
|
|
90
|
+
Other languages are deliberately **not** planned here. Point your own generator at the published spec — that serves Python, Go and the rest better than a partly-maintained SDK would.
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm ci
|
|
96
|
+
npm run verify # lint, typecheck, spec drift, test, build
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To work against an unreleased API, point the generator somewhere else:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
CANOPY_SPEC_URL=../canopy/apps/canopy-api/spec/openapi.api.json npm run generate
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Releases use [changesets](https://github.com/changesets/changesets). Include one in your PR:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm run changeset
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT © Canopy Identity Inc.
|