@calimero-network/mero-js 7.0.2 → 7.0.4
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 +85 -456
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,120 +1,21 @@
|
|
|
1
|
-
# Mero.js
|
|
1
|
+
# Mero.js — Pure JavaScript SDK for Calimero
|
|
2
2
|
|
|
3
|
-
A lightweight, universal JavaScript SDK for Calimero
|
|
3
|
+
A lightweight, universal JavaScript/TypeScript SDK for [Calimero](https://calimero-network.github.io/core/).
|
|
4
|
+
Authenticate to a node, drive the admin API, execute WASM methods over JSON-RPC,
|
|
5
|
+
and stream real-time events — from the browser, Node, Deno, Bun, or edge
|
|
6
|
+
runtimes. Zero runtime dependencies; built entirely on Web Platform APIs
|
|
7
|
+
(`fetch`, `WebSocket`, `AbortController`).
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
## Documentation
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
Full documentation is published at
|
|
12
|
+
**<https://calimero-network.github.io/mero-js/>** — quickstart, guides, and the
|
|
13
|
+
complete API reference (`MeroJs`, admin API, auth API, RPC, events, cloud,
|
|
14
|
+
capabilities, and the error model). The source lives under [`docs/`](docs/).
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- 🔧 **Dependency Injection**: Flexible and testable architecture
|
|
13
|
-
- ⚡ **Modern**: ES2020+ with TypeScript support
|
|
14
|
-
- 🛡️ **Type Safe**: Full TypeScript definitions
|
|
15
|
-
- 🔄 **Smart Retry**: Distinguishes user aborts vs timeouts for intelligent retry logic
|
|
16
|
-
- 🎯 **Advanced Cancellation**: Signal combination and proper timeout handling
|
|
17
|
-
|
|
18
|
-
## Behavior
|
|
19
|
-
|
|
20
|
-
### Error Model (Throwing)
|
|
21
|
-
|
|
22
|
-
The client **throws** `HTTPError` on any `!response.ok` status. Network/timeout/abort errors are re-thrown as-is:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
try {
|
|
26
|
-
const data = await httpClient.get('/api/data');
|
|
27
|
-
// Success - data is the parsed response
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if (error instanceof HTTPError) {
|
|
30
|
-
console.log(`HTTP ${error.status}: ${error.statusText}`);
|
|
31
|
-
console.log('URL:', error.url);
|
|
32
|
-
console.log('Headers:', error.headers);
|
|
33
|
-
console.log('Body:', error.bodyText); // Up to 64KB
|
|
34
|
-
} else if (error.name === 'TimeoutError') {
|
|
35
|
-
// Request timed out
|
|
36
|
-
} else if (error.name === 'AbortError') {
|
|
37
|
-
// Request was cancelled
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Parsing Defaults & Override
|
|
43
|
-
|
|
44
|
-
Each method accepts optional `{ parse?: 'json'|'text'|'blob'|'arrayBuffer'|'response' }`:
|
|
45
|
-
|
|
46
|
-
**Default parsing rules:**
|
|
47
|
-
|
|
48
|
-
- `Content-Type: application/json` → parse as JSON
|
|
49
|
-
- `Content-Type: text/*` → parse as text
|
|
50
|
-
- Other types → parse as `arrayBuffer`
|
|
51
|
-
|
|
52
|
-
**Override:**
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// Get raw Response object
|
|
56
|
-
const response = await httpClient.get('/api/data', { parse: 'response' });
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Retry Policy (`withRetry`)
|
|
60
|
-
|
|
61
|
-
The `withRetry` helper retries on:
|
|
62
|
-
|
|
63
|
-
- `HTTPError` with status `429` or `>= 500`
|
|
64
|
-
- `TimeoutError` (internal timeouts)
|
|
65
|
-
- `TypeError` (network failures)
|
|
66
|
-
|
|
67
|
-
**Never retries:**
|
|
68
|
-
|
|
69
|
-
- `AbortError` (user/caller aborts)
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { withRetry } from '@calimero-network/mero-js';
|
|
73
|
-
|
|
74
|
-
const data = await withRetry(
|
|
75
|
-
(attempt) => httpClient.get('/api/data'),
|
|
76
|
-
{ attempts: 3 }, // Default: 3 attempts
|
|
77
|
-
);
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Signal Composition
|
|
81
|
-
|
|
82
|
-
The client combines caller signals with internal timeouts:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
const userSignal = new AbortController().signal;
|
|
86
|
-
const data = await httpClient.get('/api/data', {
|
|
87
|
-
signal: userSignal,
|
|
88
|
-
timeoutMs: 5000,
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Header Precedence & Authorization
|
|
93
|
-
|
|
94
|
-
- **Caller headers win**: Request-level headers override client defaults
|
|
95
|
-
- **Authorization rules**: Only set `Authorization: Bearer ${token}` if caller didn't provide any `authorization` header (case-insensitive)
|
|
96
|
-
|
|
97
|
-
### FormData Handling
|
|
98
|
-
|
|
99
|
-
For `FormData` bodies, the client does **not** set `content-type` (lets browser/undici add the boundary):
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
const formData = new FormData();
|
|
103
|
-
formData.append('file', file);
|
|
104
|
-
await httpClient.post('/api/upload', formData);
|
|
105
|
-
// No content-type header is set by the client
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Credentials
|
|
109
|
-
|
|
110
|
-
No implicit `same-origin` default. Credentials are only set if explicitly provided:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
const client = createBrowserHttpClient({
|
|
114
|
-
baseUrl: 'https://api.example.com',
|
|
115
|
-
credentials: 'include', // Only if you want to include credentials
|
|
116
|
-
});
|
|
117
|
-
```
|
|
16
|
+
> **Not React?** This is a framework-agnostic SDK. React bindings live in the
|
|
17
|
+
> separate [`@calimero-network/mero-react`](https://github.com/calimero-network/mero-react)
|
|
18
|
+
> package.
|
|
118
19
|
|
|
119
20
|
## Installation
|
|
120
21
|
|
|
@@ -122,376 +23,103 @@ const client = createBrowserHttpClient({
|
|
|
122
23
|
npm install @calimero-network/mero-js
|
|
123
24
|
```
|
|
124
25
|
|
|
125
|
-
|
|
26
|
+
Requires Node.js 18+ (native `fetch`) or any modern browser. The package ships
|
|
27
|
+
ESM, CommonJS, and a minified browser build, is side-effect-free (tree-shakeable),
|
|
28
|
+
and includes its own TypeScript declarations.
|
|
126
29
|
|
|
127
|
-
|
|
30
|
+
## Quick start
|
|
128
31
|
|
|
129
32
|
```typescript
|
|
130
|
-
import {
|
|
33
|
+
import { createMeroJs, HTTPError, RpcError } from '@calimero-network/mero-js';
|
|
131
34
|
|
|
132
|
-
const
|
|
133
|
-
baseUrl: '
|
|
134
|
-
|
|
135
|
-
onTokenRefresh: async (newToken) =>
|
|
136
|
-
localStorage.setItem('access_token', newToken),
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Make requests
|
|
140
|
-
try {
|
|
141
|
-
const data = await httpClient.get<{ message: string }>('/api/hello');
|
|
142
|
-
console.log(data.message);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error('Request failed:', error);
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Node.js Usage
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
import { createNodeHttpClient } from '@calimero-network/mero-js';
|
|
152
|
-
// For Node.js < 18, install undici: npm install undici
|
|
153
|
-
import { fetch as undiciFetch } from 'undici';
|
|
154
|
-
|
|
155
|
-
const httpClient = createNodeHttpClient({
|
|
156
|
-
baseUrl: 'https://api.calimero.network',
|
|
157
|
-
fetch: undiciFetch, // Required for Node.js < 18
|
|
158
|
-
getAuthToken: async () => process.env.ACCESS_TOKEN,
|
|
35
|
+
const sdk = createMeroJs({
|
|
36
|
+
baseUrl: 'http://localhost:2528',
|
|
37
|
+
// timeoutMs defaults to 10000; pass a tokenStore to persist auth
|
|
159
38
|
});
|
|
160
39
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Universal Usage
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
import { createUniversalHttpClient } from '@calimero-network/mero-js';
|
|
169
|
-
|
|
170
|
-
const httpClient = createUniversalHttpClient({
|
|
171
|
-
baseUrl: 'https://api.calimero.network',
|
|
172
|
-
getAuthToken: async () => {
|
|
173
|
-
return typeof window !== 'undefined'
|
|
174
|
-
? localStorage.getItem('access_token')
|
|
175
|
-
: process.env.ACCESS_TOKEN;
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## API Reference
|
|
181
|
-
|
|
182
|
-
### Factory Functions
|
|
183
|
-
|
|
184
|
-
#### `createBrowserHttpClient(options)`
|
|
185
|
-
|
|
186
|
-
Creates an HTTP client optimized for browser environments.
|
|
40
|
+
// Authenticate (username/password against the node's auth service)
|
|
41
|
+
await sdk.authenticate({ username: 'dev', password: 'dev' });
|
|
187
42
|
|
|
188
|
-
|
|
43
|
+
// Admin API — list this node's contexts
|
|
44
|
+
const { contexts } = await sdk.admin.getContexts(); // ContextWithGroup[]
|
|
45
|
+
console.log(`found ${contexts.length} contexts`);
|
|
189
46
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
#### `createUniversalHttpClient(options)`
|
|
193
|
-
|
|
194
|
-
Creates an HTTP client that works in both browser and Node.js.
|
|
195
|
-
|
|
196
|
-
### Options
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
interface HttpClientOptions {
|
|
200
|
-
baseUrl: string; // Base URL for all requests
|
|
201
|
-
fetch?: typeof fetch; // Custom fetch implementation (Node.js)
|
|
202
|
-
getAuthToken?: () => Promise<string | undefined>; // Token getter
|
|
203
|
-
onTokenRefresh?: (token: string) => Promise<void>; // Token refresh callback
|
|
204
|
-
defaultHeaders?: Record<string, string>; // Default headers
|
|
205
|
-
timeoutMs?: number; // Request timeout (default: 30000)
|
|
206
|
-
credentials?: RequestCredentials; // CORS credentials (default: 'same-origin' for browser)
|
|
207
|
-
defaultAbortSignal?: AbortSignal; // Default abort signal for all requests
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### HTTP Methods
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// GET request
|
|
215
|
-
const response = await httpClient.get<T>('/api/endpoint', init?);
|
|
216
|
-
|
|
217
|
-
// POST request
|
|
218
|
-
const response = await httpClient.post<T>('/api/endpoint', body?, init?);
|
|
219
|
-
|
|
220
|
-
// PUT request
|
|
221
|
-
const response = await httpClient.put<T>('/api/endpoint', body?, init?);
|
|
222
|
-
|
|
223
|
-
// DELETE request
|
|
224
|
-
const response = await httpClient.delete<T>('/api/endpoint', init?);
|
|
225
|
-
|
|
226
|
-
// PATCH request
|
|
227
|
-
const response = await httpClient.patch<T>('/api/endpoint', body?, init?);
|
|
228
|
-
|
|
229
|
-
// HEAD request
|
|
230
|
-
const response = await httpClient.head<T>('/api/endpoint', init?);
|
|
231
|
-
|
|
232
|
-
// Generic request
|
|
233
|
-
const response = await httpClient.request<T>('/api/endpoint', init?);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Request Options
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
interface RequestOptions extends RequestInit {
|
|
240
|
-
parse?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'response';
|
|
241
|
-
timeoutMs?: number;
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### Response Format
|
|
246
|
-
|
|
247
|
-
All methods return the parsed data directly or throw errors:
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
// Success - returns parsed data
|
|
251
|
-
const data: T = await httpClient.get<T>('/api/data');
|
|
252
|
-
|
|
253
|
-
// Error - throws HTTPError or other errors
|
|
47
|
+
// Execute a context method over JSON-RPC
|
|
254
48
|
try {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
49
|
+
const value = await sdk.rpc.execute<string>({
|
|
50
|
+
contextId: contexts[0].id,
|
|
51
|
+
method: 'get_value',
|
|
52
|
+
argsJson: { key: 'greeting' },
|
|
53
|
+
});
|
|
54
|
+
console.log(value);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err instanceof RpcError) console.error(`contract error ${err.code}: ${err.message}`);
|
|
57
|
+
else if (err instanceof HTTPError) console.error(`HTTP ${err.status}: ${err.statusText}`);
|
|
58
|
+
else throw err;
|
|
263
59
|
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Advanced Usage
|
|
267
|
-
|
|
268
|
-
### Custom Transport
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
import { createHttpClient, Transport } from '@calimero-network/mero-js';
|
|
272
|
-
|
|
273
|
-
const transport: Transport = {
|
|
274
|
-
fetch: customFetch,
|
|
275
|
-
baseUrl: 'https://api.example.com',
|
|
276
|
-
getAuthToken: async () => 'your-token',
|
|
277
|
-
timeoutMs: 5000,
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const httpClient = createHttpClient(transport);
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Error Handling
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
try {
|
|
287
|
-
const data = await httpClient.get('/api/data');
|
|
288
|
-
// Success - data is the parsed response
|
|
289
|
-
console.log(data);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
if (error instanceof HTTPError) {
|
|
292
|
-
// HTTP error (4xx, 5xx)
|
|
293
|
-
console.error(`HTTP ${error.status}: ${error.statusText}`);
|
|
294
|
-
console.error('URL:', error.url);
|
|
295
|
-
console.error('Body:', error.bodyText);
|
|
296
|
-
} else if (error.name === 'TimeoutError') {
|
|
297
|
-
// Request timed out
|
|
298
|
-
console.error('Request timed out');
|
|
299
|
-
} else if (error.name === 'AbortError') {
|
|
300
|
-
// Request was cancelled
|
|
301
|
-
console.error('Request was cancelled');
|
|
302
|
-
} else {
|
|
303
|
-
// Network or other errors
|
|
304
|
-
console.error('Request failed:', error);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Custom Headers
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
const data = await httpClient.post('/api/data', body, {
|
|
313
|
-
headers: {
|
|
314
|
-
'Content-Type': 'application/json',
|
|
315
|
-
'X-Custom-Header': 'value',
|
|
316
|
-
},
|
|
317
|
-
});
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Request Cancellation
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
const abortController = new AbortController();
|
|
324
|
-
|
|
325
|
-
// Cancel request after 5 seconds
|
|
326
|
-
setTimeout(() => abortController.abort(), 5000);
|
|
327
|
-
|
|
328
|
-
const data = await httpClient.get('/api/slow-endpoint', {
|
|
329
|
-
signal: abortController.signal,
|
|
330
|
-
});
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### FormData Support
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
const formData = new FormData();
|
|
337
|
-
formData.append('file', fileInput.files[0]);
|
|
338
|
-
formData.append('name', 'John Doe');
|
|
339
|
-
|
|
340
|
-
const data = await httpClient.post('/api/upload', formData);
|
|
341
|
-
// Content-Type is set by the browser/undici with proper boundary
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Response Parsing
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
// Explicit parsing
|
|
348
|
-
const jsonData = await httpClient.get('/api/data', { parse: 'json' });
|
|
349
|
-
const textData = await httpClient.get('/api/text', { parse: 'text' });
|
|
350
|
-
const blobData = await httpClient.get('/api/file', { parse: 'blob' });
|
|
351
60
|
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
### Retry with Exponential Backoff
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
import { withRetry } from '@calimero-network/mero-js';
|
|
360
|
-
|
|
361
|
-
const data = await withRetry(
|
|
362
|
-
(attempt) => httpClient.get('/api/unreliable-endpoint'),
|
|
363
|
-
{ attempts: 3 }, // Default: 3 attempts
|
|
364
|
-
);
|
|
365
|
-
```
|
|
61
|
+
// Stream real-time context events (SSE)
|
|
62
|
+
sdk.events.on('event', (e) => console.log(e.contextId, e.type, e.data));
|
|
63
|
+
await sdk.events.connect();
|
|
64
|
+
await sdk.events.subscribe([contexts[0].id]);
|
|
366
65
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
import { combineSignals, createTimeoutSignal } from '@calimero-network/mero-js';
|
|
371
|
-
|
|
372
|
-
// Combine multiple abort signals
|
|
373
|
-
const userSignal = new AbortController().signal;
|
|
374
|
-
const timeoutSignal = createTimeoutSignal(5000);
|
|
375
|
-
const combinedSignal = combineSignals([userSignal, timeoutSignal]);
|
|
376
|
-
|
|
377
|
-
const data = await httpClient.get('/api/endpoint', {
|
|
378
|
-
signal: combinedSignal,
|
|
379
|
-
});
|
|
66
|
+
// Tear down open connections when done
|
|
67
|
+
sdk.close();
|
|
380
68
|
```
|
|
381
69
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
credentials: 'include', // Include cookies in CORS requests
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// For APIs that require credentials
|
|
391
|
-
const data = await httpClient.get('/api/protected', {
|
|
392
|
-
credentials: 'include',
|
|
393
|
-
});
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
## Migration from Axios
|
|
397
|
-
|
|
398
|
-
If you're migrating from an Axios-based client:
|
|
399
|
-
|
|
400
|
-
1. **Replace imports**: Use the factory functions instead of direct class instantiation
|
|
401
|
-
2. **Update method signatures**: Methods now use `RequestInit` instead of custom options
|
|
402
|
-
3. **Handle responses**: Methods now return parsed data directly or throw errors (no more `ResponseData<T>`)
|
|
403
|
-
4. **Token management**: Use the `getAuthToken` and `onTokenRefresh` callbacks
|
|
404
|
-
|
|
405
|
-
## Browser Support
|
|
70
|
+
`sdk.auth`, `sdk.admin`, `sdk.rpc`, `sdk.events` (SSE) and `sdk.ws` (experimental
|
|
71
|
+
WebSocket) are all reachable from the one `MeroJs` instance. See the
|
|
72
|
+
[quickstart](https://calimero-network.github.io/mero-js/get-started/quickstart/)
|
|
73
|
+
and [authentication guide](https://calimero-network.github.io/mero-js/get-started/authentication/)
|
|
74
|
+
for token persistence and the refresh lifecycle.
|
|
406
75
|
|
|
407
|
-
|
|
408
|
-
- Node.js 18+ (native fetch) or Node.js 16+ with `undici`
|
|
76
|
+
## Error model
|
|
409
77
|
|
|
410
|
-
|
|
78
|
+
Every failure surfaces as a typed error:
|
|
411
79
|
|
|
412
|
-
-
|
|
413
|
-
|
|
80
|
+
- **`HTTPError`** — a non-2xx response. Network failures, timeouts, and
|
|
81
|
+
cancellations are also wrapped as `HTTPError` with **`status: 0`** (the
|
|
82
|
+
underlying `AbortError`/`TimeoutError` is not propagated).
|
|
83
|
+
- **`AuthRevokedError`** (extends `HTTPError`) — the refresh token was reused or
|
|
84
|
+
revoked; the session is over. The SDK has already cleared its tokens.
|
|
85
|
+
- **`RpcError`** — the WASM contract returned a JSON-RPC error (`code`, `message`,
|
|
86
|
+
optional `type`/`data`).
|
|
414
87
|
|
|
415
|
-
|
|
88
|
+
`401`s are handled internally: the SDK refreshes the token and retries once, so
|
|
89
|
+
you only see a `401` if the refresh itself fails. Full details in the
|
|
90
|
+
[error model reference](https://calimero-network.github.io/mero-js/reference/error-model/).
|
|
416
91
|
|
|
417
|
-
|
|
92
|
+
## Lower-level HTTP client
|
|
418
93
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
94
|
+
If you need raw HTTP against a node (without the `MeroJs` facade), the SDK
|
|
95
|
+
exports composable clients — `createBrowserHttpClient`, `createNodeHttpClient`,
|
|
96
|
+
`createUniversalHttpClient`, `createHttpClient` — plus `withRetry`,
|
|
97
|
+
`combineSignals`, and `createTimeoutSignal`:
|
|
422
98
|
|
|
423
|
-
**Note**: Run `npm run build` before running the examples, as they import from the built library.
|
|
424
|
-
|
|
425
|
-
## Admin API
|
|
426
|
-
|
|
427
|
-
The `AdminApiClient` provides methods for managing Calimero node resources:
|
|
428
|
-
|
|
429
|
-
### Namespaces (application instances)
|
|
430
|
-
```typescript
|
|
431
|
-
const namespaces = await admin.listNamespaces();
|
|
432
|
-
const identity = await admin.getNamespaceIdentity(namespaceId);
|
|
433
|
-
const appNamespaces = await admin.listNamespacesForApplication(appId);
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### Groups (governance boundaries)
|
|
437
99
|
```typescript
|
|
438
|
-
|
|
439
|
-
const group = await admin.createGroup({ applicationId: '...' });
|
|
440
|
-
const info = await admin.getGroupInfo(groupId);
|
|
441
|
-
const members = await admin.listGroupMembers(groupId);
|
|
442
|
-
const contexts = await admin.listGroupContexts(groupId);
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### Contexts (WASM execution instances)
|
|
446
|
-
```typescript
|
|
447
|
-
// Single-service app
|
|
448
|
-
const ctx = await admin.createContext({ applicationId: '...' });
|
|
100
|
+
import { createBrowserHttpClient } from '@calimero-network/mero-js';
|
|
449
101
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
serviceName: 'chat',
|
|
102
|
+
const http = createBrowserHttpClient({
|
|
103
|
+
baseUrl: 'https://node.example.com',
|
|
104
|
+
getAuthToken: async () => localStorage.getItem('access_token') ?? undefined,
|
|
454
105
|
});
|
|
455
|
-
```
|
|
456
106
|
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
const { blobs } = await admin.listBlobs(); // [{ blobId, size }, ...] metadata
|
|
460
|
-
const info = await admin.getBlobInfo(blobId); // { blobId, size, hash, mimeType } via HEAD (no download)
|
|
461
|
-
const bytes = await admin.getBlob(blobId); // ArrayBuffer — the raw blob content
|
|
462
|
-
await admin.deleteBlob(blobId);
|
|
463
|
-
```
|
|
464
|
-
> `getBlob` returns the **raw bytes** as an `ArrayBuffer` (the endpoint streams the
|
|
465
|
-
> blob, e.g. `application/gzip`, not JSON). Use `listBlobs()` for `{ blobId, size }`
|
|
466
|
-
> metadata.
|
|
467
|
-
|
|
468
|
-
### Group settings (TEE admission policy)
|
|
469
|
-
```typescript
|
|
470
|
-
await admin.setTeeAdmissionPolicy(groupId, policy);
|
|
471
|
-
const current = await admin.getTeeAdmissionPolicy(groupId); // allowed MRTD/RTMR sets, acceptMock, ...
|
|
107
|
+
const data = await http.get<{ message: string }>('/api/hello');
|
|
472
108
|
```
|
|
473
109
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
cloud.enableHA({ groupId, contextId, redirectUrl });
|
|
479
|
-
```
|
|
110
|
+
Methods (`get`/`post`/`put`/`delete`/`patch`/`head`/`request`) return parsed data
|
|
111
|
+
directly and support per-call `{ signal, timeoutMs, parse }`. See the
|
|
112
|
+
[HTTP transport guide](https://calimero-network.github.io/mero-js/guides/http-transport/)
|
|
113
|
+
for options, parsing rules, retries, and cancellation.
|
|
480
114
|
|
|
481
115
|
## Development
|
|
482
116
|
|
|
483
117
|
```bash
|
|
484
|
-
# Install dependencies
|
|
485
118
|
pnpm install
|
|
486
|
-
|
|
487
|
-
# Build
|
|
488
119
|
pnpm build
|
|
489
|
-
|
|
490
|
-
# Test (unit)
|
|
491
|
-
pnpm test
|
|
492
|
-
|
|
493
|
-
# Lint
|
|
120
|
+
pnpm test # unit tests (mocked, fast)
|
|
494
121
|
pnpm lint
|
|
122
|
+
pnpm typecheck
|
|
495
123
|
```
|
|
496
124
|
|
|
497
125
|
### Testing & the contract gate
|
|
@@ -499,8 +127,8 @@ pnpm lint
|
|
|
499
127
|
The SDK mirrors core's HTTP wire types by hand, so two layers guard against drift:
|
|
500
128
|
|
|
501
129
|
- **Unit tests** (`pnpm test`) — mocked, fast, cover the client logic.
|
|
502
|
-
- **E2E** (`pnpm test:e2e`) — runs the real SDK against a live `merod`. Point it
|
|
503
|
-
node and provide credentials:
|
|
130
|
+
- **E2E** (`pnpm test:e2e`) — runs the real SDK against a live `merod`. Point it
|
|
131
|
+
at a node and provide credentials:
|
|
504
132
|
```bash
|
|
505
133
|
NODE_URL=http://localhost:2528 MERO_E2E_USER=dev MERO_E2E_PASS=dev pnpm test:e2e
|
|
506
134
|
```
|
|
@@ -508,11 +136,12 @@ The SDK mirrors core's HTTP wire types by hand, so two layers guard against drif
|
|
|
508
136
|
PR-built `merod` (core's `sdk-e2e`).
|
|
509
137
|
|
|
510
138
|
The e2e records every request as `METHOD /path` (see `tests/e2e/coverage-recorder.ts`).
|
|
511
|
-
Core's route-coverage gate is **method-aware**: it fails if any admin route *or
|
|
512
|
-
ships without an SDK test — so e.g. `GET /blobs/:id` can't hide behind
|
|
513
|
-
When you add an SDK method for a new endpoint, exercise it in
|
|
514
|
-
`tests/e2e/coverage-sweep.test.ts` is the catch-all) so
|
|
139
|
+
Core's route-coverage gate is **method-aware**: it fails if any admin route *or
|
|
140
|
+
verb* ships without an SDK test — so e.g. `GET /blobs/:id` can't hide behind
|
|
141
|
+
`DELETE /blobs/:id`. When you add an SDK method for a new endpoint, exercise it in
|
|
142
|
+
the e2e (the sweep in `tests/e2e/coverage-sweep.test.ts` is the catch-all) so
|
|
143
|
+
coverage stays complete.
|
|
515
144
|
|
|
516
145
|
## License
|
|
517
146
|
|
|
518
|
-
MIT
|
|
147
|
+
MIT — see [LICENSE](LICENSE).
|