@orvello/sdk 0.1.0 → 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 +151 -0
- package/dist/index.cjs +1070 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +416 -1
- package/dist/index.d.ts +416 -1
- package/dist/index.js +1063 -9
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -15,6 +15,16 @@ accounting, invoicing, bookkeeping, customer-company and payments platform.
|
|
|
15
15
|
- Native PDF and binary blob streaming (`Blob`).
|
|
16
16
|
- `Idempotency-Key` support for safe webhooks and financial mutations.
|
|
17
17
|
- **Zero Runtime Dependencies**: Uses native Fetch and WebCrypto.
|
|
18
|
+
- **The route picks the credential**: the contract carries `auth.type` per route, so business
|
|
19
|
+
routes go out as `Authorization: Bearer` and management routes as `x-api-key` — from the same
|
|
20
|
+
client, without the caller choosing.
|
|
21
|
+
- **Preflight instead of guessing**: `preflight(operationId)` walks the entire path a real call
|
|
22
|
+
would take and stops before writing, so an edge can be re-verified at any time without touching
|
|
23
|
+
it.
|
|
24
|
+
- **Capabilities as data**: `capabilities()` reports what this service can do, derived from the
|
|
25
|
+
contract and backed by the operations behind each claim.
|
|
26
|
+
- **Circuit breaker**: opens on transport failures, deliberately not on 401/403 — a rejected
|
|
27
|
+
credential is not an outage.
|
|
18
28
|
- **Flexible Auth**: Supports static API Keys, Bearer tokens, async token getters, and OAuth2
|
|
19
29
|
Client Credentials with automatic token caching.
|
|
20
30
|
- **Ergonomic Accounting Helpers**: Bank account caching (`getDefaultBankAccount`), base64 receipt
|
|
@@ -72,6 +82,10 @@ ORVELLO_OAUTH_CLIENT_SECRET=...
|
|
|
72
82
|
ORVELLO_OAUTH_SCOPE=...
|
|
73
83
|
# or instead of OAuth:
|
|
74
84
|
ORVELLO_API_KEY=...
|
|
85
|
+
|
|
86
|
+
# The management credential for /oauth/clients/external/* — a different value,
|
|
87
|
+
# not another spelling of the one above.
|
|
88
|
+
ORVELLO_MANAGEMENT_API_KEY=...
|
|
75
89
|
```
|
|
76
90
|
|
|
77
91
|
```typescript
|
|
@@ -84,6 +98,143 @@ silently pointing at a fixed host.
|
|
|
84
98
|
|
|
85
99
|
---
|
|
86
100
|
|
|
101
|
+
## Two credentials, and the route decides which one goes out
|
|
102
|
+
|
|
103
|
+
orvello guards its endpoints in two different ways, and they are not
|
|
104
|
+
interchangeable:
|
|
105
|
+
|
|
106
|
+
| | Business routes | Management routes |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| Examples | `/invoices/external`, `/customer-companies/external`, `/bookkeeping/*` | `/oauth/clients/external/*` |
|
|
109
|
+
| Header | `Authorization: Bearer …` | `x-api-key: …` |
|
|
110
|
+
| Credential | an OAuth2 client (client_credentials) or a token | the service's management key |
|
|
111
|
+
| Belongs to | the **calling app** | the **edge** between manager and service |
|
|
112
|
+
| Configure with | `auth` / `ORVELLO_OAUTH_CLIENT_ID` + `ORVELLO_OAUTH_CLIENT_SECRET` | `managementApiKey` / `ORVELLO_MANAGEMENT_API_KEY` |
|
|
113
|
+
|
|
114
|
+
You never pick. The generated contract carries `auth.type` on every single route,
|
|
115
|
+
and the client reads it — so `client.invoices.getPending()` goes out as a bearer
|
|
116
|
+
call and `client.oauthClients.create(…)` goes out with `x-api-key`, from the same
|
|
117
|
+
client instance. A hand-built path through `client.request()` gets the same
|
|
118
|
+
treatment, because it looks the route up too.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const orvello = createOrvelloClient({
|
|
122
|
+
baseUrl: () => resolveLiveBaseUrl("node-bill"),
|
|
123
|
+
auth: { clientId, clientSecret, scope: "invoices:read invoices:write" },
|
|
124
|
+
managementApiKey: process.env.ORVELLO_MANAGEMENT_API_KEY,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await orvello.invoices.getPending(); // Bearer
|
|
128
|
+
await orvello.oauthClients.create({ name: "shop", role: "editor" }); // x-api-key
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Leaving `managementApiKey` out is a valid choice: a client that only writes
|
|
132
|
+
invoices cannot then hand out access. Calling a management route without it
|
|
133
|
+
fails **before** anything goes on the wire, with a message that names the missing
|
|
134
|
+
setting — rather than earning a `403 Forbidden: Invalid API key`, which reads
|
|
135
|
+
like a wrong key instead of an absent one.
|
|
136
|
+
|
|
137
|
+
## Calling anything in the contract
|
|
138
|
+
|
|
139
|
+
The typed namespaces cover the endpoints most integrations need. Everything else
|
|
140
|
+
is reachable by its `operationId`, with method, path and credential taken from
|
|
141
|
+
the contract:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const clients = await orvello.call("oauth2_external_clients_list", {
|
|
145
|
+
query: { pageSize: 50, isActive: true },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const rotated = await orvello.call("oauth2_external_clients_rotate", {
|
|
149
|
+
params: { idOrClientId: "nbill_oauth2_3Rxp…" },
|
|
150
|
+
body: { gracePeriodHours: 2 },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
An unknown `operationId` throws immediately, and so does a missing path
|
|
155
|
+
parameter — instead of sending `/oauth/clients/external/:idOrClientId` to the
|
|
156
|
+
service and getting a puzzling 404 back.
|
|
157
|
+
|
|
158
|
+
## Preflight: would this call work, without making it
|
|
159
|
+
|
|
160
|
+
Creating an OAuth2 client writes real data on both sides. That is a deliberate,
|
|
161
|
+
approved step — but you still want to know, at any time and without an occasion,
|
|
162
|
+
whether the path still holds.
|
|
163
|
+
|
|
164
|
+
`preflight()` walks the whole way an actual call would take — same base URL, same
|
|
165
|
+
credential channel, same contract — and stops before writing:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const report = await orvello.preflight("oauth2_external_clients_create", {
|
|
169
|
+
body: { name: "node-shop invoicing", role: "editor", scopes: ["invoices:write"] },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
report.ok; // true → the path holds today
|
|
173
|
+
report.sideEffects; // "none" — always; a preflight never writes
|
|
174
|
+
report.authChannel; // "api_key"
|
|
175
|
+
report.steps; // operation → path-parameters → request-shape →
|
|
176
|
+
// reachable → contract → credential-configured → credential-accepted
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Every step carries its reason in plain words, and a skipped step is reported as
|
|
180
|
+
skipped — never as passed. What a green report proves is **entitlement**, not
|
|
181
|
+
**outcome**: the service accepts your credential for this route. It does not
|
|
182
|
+
predict what the write would return. `capabilities().capabilities.serverSideDryRun`
|
|
183
|
+
says so explicitly, because orvello offers no server-side rehearsal for client
|
|
184
|
+
management.
|
|
185
|
+
|
|
186
|
+
Use `skipCredentialProbe: true` to check many edges cheaply — it then only asks
|
|
187
|
+
whether a credential is configured, and says so.
|
|
188
|
+
|
|
189
|
+
## Capabilities: what this service can do, as data
|
|
190
|
+
|
|
191
|
+
A connection manager can only establish, verify and repair an edge if it knows
|
|
192
|
+
what the other side actually offers. `capabilities()` answers that from the
|
|
193
|
+
bundled contract — no network, no credential, so it also works while the
|
|
194
|
+
connection is down:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const report = orvello.capabilities();
|
|
198
|
+
|
|
199
|
+
report.contractSha256; // which contract this build speaks
|
|
200
|
+
report.operationCount; // how many operations it knows
|
|
201
|
+
report.authChannels; // { bearer, api_key, none }
|
|
202
|
+
report.capabilities.clientManagement.supported; // true
|
|
203
|
+
report.capabilities.clientManagement.operations; // the operations that back the claim
|
|
204
|
+
report.capabilities.rotationGracePeriod.supported; // retire + rollback exist
|
|
205
|
+
report.capabilities.serverSideDryRun.reason; // why it is false
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
A capability counts as present exactly when the operations behind it are in the
|
|
209
|
+
contract, and every entry lists them — so a claim can be checked instead of
|
|
210
|
+
believed. Drop a route in orvello and it disappears here on the next
|
|
211
|
+
`sync:contract`, with nobody having to remember.
|
|
212
|
+
|
|
213
|
+
## Health and the circuit breaker
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const health = await orvello.checkHealth();
|
|
217
|
+
health.available; // does the service answer at all
|
|
218
|
+
health.authenticated; // was a credential actually accepted — null means "not asked"
|
|
219
|
+
|
|
220
|
+
orvello.isAvailable(); // circuit-breaker state, no network
|
|
221
|
+
orvello.circuitState();
|
|
222
|
+
orvello.resetCircuit();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`available` and `authenticated` are two questions and stay apart. `authenticated:
|
|
226
|
+
null` means no credential was configured, never "fine".
|
|
227
|
+
|
|
228
|
+
The breaker opens after repeated **transport** failures — timeouts, refused
|
|
229
|
+
connections, 5xx — and short-circuits further calls for a cooldown instead of
|
|
230
|
+
letting each one burn its full timeout. It deliberately does **not** open on 401
|
|
231
|
+
or 403: the service answered, it just said no. A breaker that trips on permission
|
|
232
|
+
errors turns a wrong credential into an apparent outage and sends the search in
|
|
233
|
+
the wrong direction. Configure with `circuitBreaker: { threshold, cooldownMs }`,
|
|
234
|
+
or switch it off with `circuitBreaker: false`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
87
238
|
## Usage Examples
|
|
88
239
|
|
|
89
240
|
### 1. Invoices
|