@revoengine/sdk 1.0.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 +639 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/dist/errors-ieF9jeMP.d.cts +2760 -0
- package/dist/errors-ieF9jeMP.d.ts +2760 -0
- package/dist/hosted.cjs +19424 -0
- package/dist/hosted.cjs.map +1 -0
- package/dist/hosted.d.cts +7 -0
- package/dist/hosted.d.ts +7 -0
- package/dist/hosted.js +19430 -0
- package/dist/hosted.js.map +1 -0
- package/dist/index.cjs +19596 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11163 -0
- package/dist/index.d.ts +11163 -0
- package/dist/index.js +19590 -0
- package/dist/index.js.map +1 -0
- package/licenses/THIRD_PARTY_LICENSES.txt +745 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RevoEngine
|
|
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,639 @@
|
|
|
1
|
+
# @revoengine/sdk
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for RevoEngine. It provides the modern `api`, `utils`,
|
|
4
|
+
`storage`, and `agents` namespaces plus the `batch` controller in both
|
|
5
|
+
Revo-hosted `CUSTOM_NODEJS` components and standalone Node.js applications.
|
|
6
|
+
|
|
7
|
+
Requirements: Node.js 22 or newer.
|
|
8
|
+
|
|
9
|
+
## Choose your runtime
|
|
10
|
+
|
|
11
|
+
| Environment | How to start | Authentication |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Revo-hosted `CUSTOM_NODEJS` | Export `async function init(runtime)` | Revo injects an execution-bound runtime; user code receives no API key. |
|
|
14
|
+
| Standalone Node.js | Create `new RevoClient(options)` | Supply a RevoEngine API key explicitly or through an environment variable. |
|
|
15
|
+
|
|
16
|
+
Both modes expose the same `api`, `utils`, `storage`, `agents`, and `batch`
|
|
17
|
+
properties, but return types reflect where the work happens:
|
|
18
|
+
|
|
19
|
+
| Call kind | Revo-hosted | Standalone |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Execution snapshot, cache, debug, and logging | Synchronous | Throws synchronously because no execution context exists |
|
|
22
|
+
| Current user and instance | Synchronous injected snapshot | Asynchronous lazy `/api/v1/me` discovery |
|
|
23
|
+
| Remote `api`, `storage`, and `agents` calls | Asynchronous | Asynchronous |
|
|
24
|
+
| Local `utils` and `batch.configure()` | Synchronous unless the utility is inherently asynchronous | Same |
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Standalone applications install the SDK normally:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install @revoengine/sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
New Revo-hosted components receive `@revoengine/sdk: "1.0.0"` and a JSDoc
|
|
35
|
+
`RevoRuntime` type import in their generated starter files.
|
|
36
|
+
RevoEngine pins that version again in the deployment package and injects the
|
|
37
|
+
runtime into the component entrypoint. Component code does not construct a
|
|
38
|
+
`RevoClient`.
|
|
39
|
+
|
|
40
|
+
The relevant generated `package.json` fields are:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"type": "module",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@revoengine/sdk": "1.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Hosted user code receives neither a tenant API key nor the private runtime
|
|
52
|
+
credential and does not perform `/api/v1/me` discovery. The generated parent
|
|
53
|
+
worker owns the short-lived, execution-bound bridge. The
|
|
54
|
+
`@revoengine/sdk/hosted` entrypoint is platform-internal and must not be
|
|
55
|
+
imported by a component.
|
|
56
|
+
|
|
57
|
+
## Revo-hosted CUSTOM_NODEJS
|
|
58
|
+
|
|
59
|
+
### Basic entrypoint
|
|
60
|
+
|
|
61
|
+
Export a named `init` function. Do not construct `RevoClient` and do not call a
|
|
62
|
+
separate initialization method.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
/** @param {import('@revoengine/sdk').RevoRuntime} runtime */
|
|
66
|
+
export async function init(runtime) {
|
|
67
|
+
const { api, utils, storage, agents, batch } = runtime;
|
|
68
|
+
const { data: rows, next } = await api.getDatabaseData('Sales', {
|
|
69
|
+
fields: ['saleId', 'customerId', 'total', 'createdAt'],
|
|
70
|
+
sort: ['-createdAt'],
|
|
71
|
+
take: 25,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const file = await storage.putObject({
|
|
75
|
+
name: `sales-${utils.randomUUID()}.json`,
|
|
76
|
+
data: JSON.stringify({ rows, next }),
|
|
77
|
+
mimeType: 'application/json',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
storageEntryId: file.storageEntryId,
|
|
82
|
+
exportedRows: rows.length,
|
|
83
|
+
hasNextPage: next,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`putObject()` is appropriate here because this is a deliberately small JSON
|
|
89
|
+
payload. Use an upload session for larger exports, as shown below.
|
|
90
|
+
|
|
91
|
+
Existing components that export `async function init()` remain compatible;
|
|
92
|
+
JavaScript ignores the additional runtime argument.
|
|
93
|
+
|
|
94
|
+
### Input, context, and secrets
|
|
95
|
+
|
|
96
|
+
Hosted context comes from the current execution snapshot and is synchronous, just
|
|
97
|
+
like the low-code surface. Only platform-backed work returns a promise.
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
export async function init(runtime) {
|
|
101
|
+
const { api } = runtime;
|
|
102
|
+
const input = api.input();
|
|
103
|
+
const currentUser = api.currentUser();
|
|
104
|
+
const instance = api.getInstanceDetails();
|
|
105
|
+
const signingSecret = await api.getSecret('WEBHOOK_SIGNING_SECRET');
|
|
106
|
+
|
|
107
|
+
api.log({
|
|
108
|
+
message: 'Processing custom component',
|
|
109
|
+
args: {
|
|
110
|
+
userId: currentUser.userId,
|
|
111
|
+
instanceId: instance.id,
|
|
112
|
+
hasInput: input != null,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Use the secret without returning or logging its value.
|
|
117
|
+
return { accepted: Boolean(signingSecret) };
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Execution-only methods such as `api.input()`, `api.getContext()`,
|
|
122
|
+
`api.getExecutionId()`, and `api.getOperationId()` are available only in hosted
|
|
123
|
+
executions.
|
|
124
|
+
|
|
125
|
+
### Configure hosted batching
|
|
126
|
+
|
|
127
|
+
Configure batching before the first remote call:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
export async function init(runtime) {
|
|
131
|
+
runtime.batch.configure({
|
|
132
|
+
failureMode: 'stop',
|
|
133
|
+
maxCalls: 16,
|
|
134
|
+
maxInFlight: 2,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const [customers, agents] = await Promise.all([
|
|
138
|
+
runtime.api.getDatabaseData('Customers', { take: 100 }),
|
|
139
|
+
runtime.agents.list(),
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
customers: customers.data,
|
|
144
|
+
agents,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`batch.configure()` is synchronous and rejects changes after the first remote
|
|
150
|
+
batch has been dispatched.
|
|
151
|
+
|
|
152
|
+
## Standalone Node.js
|
|
153
|
+
|
|
154
|
+
### Basic client
|
|
155
|
+
|
|
156
|
+
Constructing `RevoClient` is synchronous and performs no network I/O. The first
|
|
157
|
+
platform-backed call validates the API key through `/api/v1/me`, reads the
|
|
158
|
+
instance bound to that key, discovers its Sandbox endpoint, and dispatches the
|
|
159
|
+
queued runtime batch.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { RevoClient } from '@revoengine/sdk';
|
|
163
|
+
|
|
164
|
+
const apiKey = process.env.REVO_API_KEY;
|
|
165
|
+
if (!apiKey) throw new Error('REVO_API_KEY is required.');
|
|
166
|
+
|
|
167
|
+
const revo = new RevoClient({
|
|
168
|
+
apiKey,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
type Sale = {
|
|
172
|
+
saleId: string;
|
|
173
|
+
customerId: string;
|
|
174
|
+
total: number;
|
|
175
|
+
createdAt: string;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = await revo.api.getDatabaseData<Sale>('Sales', {
|
|
179
|
+
filter: { field: 'total', op: 'gte', value: 100 },
|
|
180
|
+
sort: ['-createdAt'],
|
|
181
|
+
take: 100,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log(result.data);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
There is no `init()` or connection method.
|
|
188
|
+
|
|
189
|
+
### Environment configuration
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const revo = new RevoClient();
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Supported environment aliases, in precedence order after explicit constructor
|
|
196
|
+
options:
|
|
197
|
+
|
|
198
|
+
- URL: `REVO_URL`, `REVO_BASE_URL`, `REVOENGINE_URL`, `REVOENGINE_BASE_URL`
|
|
199
|
+
- API key: `REVO_TOKEN`, `REVO_API_KEY`, `REVOENGINE_TOKEN`, `REVOENGINE_API_KEY`
|
|
200
|
+
|
|
201
|
+
The default API URL is `https://api.revoengine.com`. The API key already
|
|
202
|
+
identifies its instance, so `RevoClient` has no instance option and sends no
|
|
203
|
+
separate instance header. `/api/v1/me` is the sole source of the bound instance
|
|
204
|
+
identity and Sandbox endpoint.
|
|
205
|
+
|
|
206
|
+
Keep the API key in a server-side secret or environment variable. This SDK is
|
|
207
|
+
for Node.js processes and must not be bundled into browser code with a key.
|
|
208
|
+
|
|
209
|
+
Explicit options always win:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const apiKey = process.env.REVO_API_KEY;
|
|
213
|
+
if (!apiKey) throw new Error('REVO_API_KEY is required.');
|
|
214
|
+
|
|
215
|
+
const revo = new RevoClient({
|
|
216
|
+
baseUrl: 'https://api.revoengine.com',
|
|
217
|
+
apiKey,
|
|
218
|
+
requestTimeoutMs: 30_000,
|
|
219
|
+
batch: {
|
|
220
|
+
failureMode: 'independent',
|
|
221
|
+
maxCalls: 16,
|
|
222
|
+
maxRequestBytes: 128 * 1024,
|
|
223
|
+
maxInFlight: 2,
|
|
224
|
+
maxQueuedCalls: 256,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The SDK never reads or writes Revo CLI credential files.
|
|
230
|
+
|
|
231
|
+
## Automatic batching
|
|
232
|
+
|
|
233
|
+
Calls queued in the same microtask are grouped. `Promise.all` is the normal way
|
|
234
|
+
to issue a batch:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const [customers, crmToken, activeAgents] = await Promise.all([
|
|
238
|
+
revo.api.getDatabaseData('Customers', { take: 100 }),
|
|
239
|
+
revo.api.getSecret('CRM_API_TOKEN'),
|
|
240
|
+
revo.agents.list(),
|
|
241
|
+
]);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Sequential `await` statements naturally produce separate batches:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const customers = await revo.api.getDatabaseData('Customers', { take: 100 });
|
|
248
|
+
const agents = await revo.agents.list();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The default `independent` mode executes every valid call and settles each
|
|
252
|
+
promise independently. In `stop` mode calls execute in order; after one call
|
|
253
|
+
fails, later calls in that batch reject with `BATCH_STOPPED`.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const revo = new RevoClient({
|
|
257
|
+
batch: { failureMode: 'stop' },
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Defaults are 32 calls per request, 256 KiB per request, four in-flight batches,
|
|
262
|
+
and 1,024 queued calls. Client configuration can only lower server ceilings.
|
|
263
|
+
The complete batch is validated before any call executes.
|
|
264
|
+
|
|
265
|
+
Runtime batches are never retried automatically because they may contain
|
|
266
|
+
mutations. If transport is interrupted after dispatch, unresolved calls reject
|
|
267
|
+
with `RevoTransportError` and `indeterminate: true`.
|
|
268
|
+
|
|
269
|
+
Batching is not a transaction. `api.transactionDatabaseData` is intentionally
|
|
270
|
+
not part of SDK 1.0.0.
|
|
271
|
+
|
|
272
|
+
## Local utils
|
|
273
|
+
|
|
274
|
+
`utils` runs locally and is available before authentication or discovery:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const payload = JSON.stringify({ event: 'customer.updated' });
|
|
278
|
+
const secret = process.env.SIGNING_SECRET;
|
|
279
|
+
if (!secret) throw new Error('SIGNING_SECRET is required.');
|
|
280
|
+
|
|
281
|
+
const requestId = revo.utils.randomUUID();
|
|
282
|
+
const signature = revo.utils.hmac(payload, secret);
|
|
283
|
+
const encoded = revo.utils.encodeBase64('RevoEngine');
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Only the modern first-class utility surface is exposed; deprecated low-code
|
|
287
|
+
aliases are not included. `utils.sleep()`, `utils.generateHash()`, and
|
|
288
|
+
`utils.compareHash()` remain asynchronous by design; the other local utilities
|
|
289
|
+
return their values directly.
|
|
290
|
+
|
|
291
|
+
## Runtime context
|
|
292
|
+
|
|
293
|
+
Hosted context reads are synchronous because the generated worker injects an
|
|
294
|
+
immutable execution snapshot before `init(runtime)`:
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
export async function init(runtime) {
|
|
298
|
+
const input = runtime.api.input();
|
|
299
|
+
const currentUser = runtime.api.currentUser();
|
|
300
|
+
const instanceId = runtime.api.getCurrentInstance();
|
|
301
|
+
|
|
302
|
+
runtime.api.log({
|
|
303
|
+
message: 'Started',
|
|
304
|
+
args: { userId: currentUser.userId, instanceId, hasInput: input != null },
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return runtime.api.getSecret('SIGNING_SECRET');
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The three standalone identity methods are asynchronous because their first call
|
|
312
|
+
performs lazy `/api/v1/me` discovery and later calls use the cached profile:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const [currentUser, instanceId, instance] = await Promise.all([
|
|
316
|
+
revo.api.currentUser(),
|
|
317
|
+
revo.api.getCurrentInstance(),
|
|
318
|
+
revo.api.getInstanceDetails(),
|
|
319
|
+
]);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Other context methods throw `RevoRuntimeContextUnavailableError` synchronously
|
|
323
|
+
outside a hosted execution. The distinct `RevoApi` and `RevoStandaloneApi` types
|
|
324
|
+
make the hosted snapshot and standalone discovery behavior explicit.
|
|
325
|
+
|
|
326
|
+
## Storage
|
|
327
|
+
|
|
328
|
+
### Small text or binary objects
|
|
329
|
+
|
|
330
|
+
`storage.putObject()` is convenient for small payloads that fit the runtime
|
|
331
|
+
batch limit:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const settingsFile = await revo.storage.putObject({
|
|
335
|
+
name: 'settings.json',
|
|
336
|
+
data: JSON.stringify({ locale: 'pl-PL', enabled: true }),
|
|
337
|
+
dataEncoding: 'utf8',
|
|
338
|
+
mimeType: 'application/json',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
async function uploadSmallPdf(pdfBuffer: Buffer) {
|
|
342
|
+
return revo.storage.putObject({
|
|
343
|
+
name: 'invoice.pdf',
|
|
344
|
+
data: pdfBuffer.toString('base64'),
|
|
345
|
+
dataEncoding: 'base64',
|
|
346
|
+
mimeType: 'application/pdf',
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Large binary uploads
|
|
352
|
+
|
|
353
|
+
Large files must use an upload session or signed URL so their bytes do not enter
|
|
354
|
+
the JSON runtime batch:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import type { RevoRuntime } from '@revoengine/sdk';
|
|
358
|
+
|
|
359
|
+
export async function uploadBinary(
|
|
360
|
+
runtime: Pick<RevoRuntime, 'storage'>,
|
|
361
|
+
name: string,
|
|
362
|
+
contentType: string,
|
|
363
|
+
bytes: Uint8Array,
|
|
364
|
+
) {
|
|
365
|
+
const { session, upload } = await runtime.storage.createUploadSession({
|
|
366
|
+
name,
|
|
367
|
+
contentTypeHint: contentType,
|
|
368
|
+
sizeHint: bytes.byteLength,
|
|
369
|
+
uploadMode: 'direct',
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const sessionId = session.storageUploadSessionId;
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
if (!upload.uploadUrl) {
|
|
376
|
+
throw new Error('RevoEngine did not return a direct upload URL.');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const headers = new Headers(upload.headers);
|
|
380
|
+
if (!headers.has('content-type')) {
|
|
381
|
+
headers.set('content-type', contentType);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const response = await fetch(upload.uploadUrl, {
|
|
385
|
+
method: upload.method,
|
|
386
|
+
headers,
|
|
387
|
+
body: Buffer.from(bytes),
|
|
388
|
+
});
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
throw new Error(`Upload failed with HTTP ${response.status}.`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const { entry } = await runtime.storage.finalizeUploadSession(sessionId);
|
|
394
|
+
return entry;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
await runtime.storage.abortUploadSession(sessionId).catch(() => undefined);
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The helper accepts either a `RevoClient` or the runtime injected into hosted
|
|
403
|
+
`init(runtime)`. Session creation, finalization, and best-effort abort remain
|
|
404
|
+
runtime calls; only the binary bytes bypass the JSON batch.
|
|
405
|
+
|
|
406
|
+
## Excel export from database data
|
|
407
|
+
|
|
408
|
+
Install `exceljs` in your standalone application or add it to the hosted
|
|
409
|
+
component's `package.json` dependencies.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import ExcelJS from 'exceljs';
|
|
413
|
+
import type { RevoRuntime } from '@revoengine/sdk';
|
|
414
|
+
import { uploadBinary } from './upload-binary.js';
|
|
415
|
+
|
|
416
|
+
type SaleRow = {
|
|
417
|
+
saleId: string;
|
|
418
|
+
customerId: string;
|
|
419
|
+
total: number;
|
|
420
|
+
createdAt: string;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
type ExportRuntime = Pick<RevoRuntime, 'storage'> & {
|
|
424
|
+
readonly api: Pick<RevoRuntime['api'], 'getDatabaseData'>;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
export async function exportSales(
|
|
428
|
+
runtime: ExportRuntime,
|
|
429
|
+
) {
|
|
430
|
+
const workbook = new ExcelJS.Workbook();
|
|
431
|
+
const sheet = workbook.addWorksheet('Sales');
|
|
432
|
+
sheet.columns = [
|
|
433
|
+
{ header: 'Sale ID', key: 'saleId' },
|
|
434
|
+
{ header: 'Customer', key: 'customerId' },
|
|
435
|
+
{ header: 'Total', key: 'total' },
|
|
436
|
+
{ header: 'Created at', key: 'createdAt' },
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
let skip = 0;
|
|
440
|
+
for (;;) {
|
|
441
|
+
const page = await runtime.api.getDatabaseData<SaleRow>('Sales', {
|
|
442
|
+
fields: ['saleId', 'customerId', 'total', 'createdAt'],
|
|
443
|
+
sort: { saleId: 'ASC' },
|
|
444
|
+
take: 1_000,
|
|
445
|
+
skip,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
sheet.addRows(page.data);
|
|
449
|
+
if (!page.next) break;
|
|
450
|
+
if (page.results === 0) {
|
|
451
|
+
throw new Error('Sales pagination did not advance.');
|
|
452
|
+
}
|
|
453
|
+
skip += page.results;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const bytes = Buffer.from(await workbook.xlsx.writeBuffer());
|
|
457
|
+
return uploadBinary(
|
|
458
|
+
runtime,
|
|
459
|
+
'sales-export.xlsx',
|
|
460
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
461
|
+
bytes,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Standalone call site:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { RevoClient } from '@revoengine/sdk';
|
|
470
|
+
import { exportSales } from './export-sales.js';
|
|
471
|
+
|
|
472
|
+
const revo = new RevoClient();
|
|
473
|
+
const file = await exportSales(revo);
|
|
474
|
+
console.log(file.storageEntryId);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
The same exporter can be used in Revo-hosted code:
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
import { exportSales } from './export-sales.js';
|
|
481
|
+
|
|
482
|
+
export async function init(runtime) {
|
|
483
|
+
const file = await exportSales(runtime);
|
|
484
|
+
return { storageEntryId: file.storageEntryId };
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The stable unique sort makes offset pagination deterministic. If the table can
|
|
489
|
+
change during export, filter on an immutable snapshot/version marker so later
|
|
490
|
+
pages describe the same logical dataset.
|
|
491
|
+
|
|
492
|
+
## PDF generation
|
|
493
|
+
|
|
494
|
+
Install `pdfkit` in your application or hosted component dependencies:
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
import PDFDocument from 'pdfkit';
|
|
498
|
+
import type { RevoRuntime } from '@revoengine/sdk';
|
|
499
|
+
import { uploadBinary } from './upload-binary.js';
|
|
500
|
+
|
|
501
|
+
function renderCustomerPdf(customer: unknown): Promise<Buffer> {
|
|
502
|
+
return new Promise((resolve, reject) => {
|
|
503
|
+
const document = new PDFDocument({ margin: 48 });
|
|
504
|
+
const chunks: Buffer[] = [];
|
|
505
|
+
|
|
506
|
+
document.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
507
|
+
document.on('error', reject);
|
|
508
|
+
document.on('end', () => resolve(Buffer.concat(chunks)));
|
|
509
|
+
|
|
510
|
+
document.fontSize(18).text('Customer summary');
|
|
511
|
+
document.moveDown();
|
|
512
|
+
document.fontSize(10).text(JSON.stringify(customer, null, 2));
|
|
513
|
+
document.end();
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
type ExportRuntime = Pick<RevoRuntime, 'storage'> & {
|
|
518
|
+
readonly api: Pick<RevoRuntime['api'], 'getDatabaseData'>;
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
export async function exportCustomer(
|
|
522
|
+
runtime: ExportRuntime,
|
|
523
|
+
customerId: string,
|
|
524
|
+
) {
|
|
525
|
+
const { data } = await runtime.api.getDatabaseData('Customers', {
|
|
526
|
+
filter: { field: 'customerId', op: 'eq', value: customerId },
|
|
527
|
+
take: 1,
|
|
528
|
+
});
|
|
529
|
+
if (!data[0]) throw new Error(`Customer ${customerId} was not found.`);
|
|
530
|
+
|
|
531
|
+
const pdf = await renderCustomerPdf(data[0]);
|
|
532
|
+
return uploadBinary(runtime, `customer-${customerId}.pdf`, 'application/pdf', pdf);
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Standalone call site:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
import { RevoClient } from '@revoengine/sdk';
|
|
540
|
+
import { exportCustomer } from './export-customer.js';
|
|
541
|
+
|
|
542
|
+
const revo = new RevoClient();
|
|
543
|
+
const file = await exportCustomer(revo, 'customer-1');
|
|
544
|
+
console.log(file.storageEntryId);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Revo-hosted call site:
|
|
548
|
+
|
|
549
|
+
```js
|
|
550
|
+
import { exportCustomer } from './export-customer.js';
|
|
551
|
+
|
|
552
|
+
export async function init(runtime) {
|
|
553
|
+
const input = runtime.api.input();
|
|
554
|
+
if (!input || typeof input.customerId !== 'string') {
|
|
555
|
+
throw new Error('customerId input is required.');
|
|
556
|
+
}
|
|
557
|
+
const file = await exportCustomer(runtime, input.customerId);
|
|
558
|
+
return { storageEntryId: file.storageEntryId };
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Jobs and agents
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
const jobId = await revo.api.triggerJob(
|
|
566
|
+
'8f1fe47f-90e6-4c16-a9ea-ccba5ca2a8da',
|
|
567
|
+
{ customerId: 'customer-1' },
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const agents = await revo.agents.list();
|
|
571
|
+
const agentId = agents[0]?.agentId;
|
|
572
|
+
if (agentId) {
|
|
573
|
+
const run = await revo.agents.startRun(agentId, {
|
|
574
|
+
instruction: `Process the result of job ${jobId}.`,
|
|
575
|
+
triggerType: 'REQUEST',
|
|
576
|
+
triggerRefType: 'JOB',
|
|
577
|
+
triggerRefId: jobId,
|
|
578
|
+
state: { jobId },
|
|
579
|
+
});
|
|
580
|
+
console.log(run.agentRunId);
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Available methods and request types are exported through the package's
|
|
585
|
+
TypeScript declarations. The generated declarations are the canonical SDK
|
|
586
|
+
contract for the installed version.
|
|
587
|
+
|
|
588
|
+
## Error handling
|
|
589
|
+
|
|
590
|
+
All SDK errors derive from `RevoError`:
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
import {
|
|
594
|
+
RevoError,
|
|
595
|
+
RevoRemoteError,
|
|
596
|
+
RevoTransportError,
|
|
597
|
+
} from '@revoengine/sdk';
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
await revo.api.insertDatabaseData('Sales', [{ saleId: 'sale-1' }]);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if (error instanceof RevoTransportError && error.indeterminate) {
|
|
603
|
+
// The request may have reached RevoEngine. Check by idempotency key or
|
|
604
|
+
// business identifier before attempting the mutation again.
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
if (error instanceof RevoRemoteError) {
|
|
608
|
+
console.error(error.code, error.statusCode, error.message);
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
if (error instanceof RevoError) {
|
|
612
|
+
console.error(error.code, error.message);
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Common subclasses include:
|
|
620
|
+
|
|
621
|
+
- `RevoConfigurationError`
|
|
622
|
+
- `RevoAuthenticationError`
|
|
623
|
+
- `RevoPermissionDeniedError`
|
|
624
|
+
- `RevoRuntimeContextUnavailableError`
|
|
625
|
+
- `RevoBatchConfigurationError`
|
|
626
|
+
- `RevoBatchLimitError`
|
|
627
|
+
- `RevoRemoteError`
|
|
628
|
+
- `RevoProtocolError`
|
|
629
|
+
- `RevoTransportError`
|
|
630
|
+
|
|
631
|
+
## SDK 1.0 compatibility notes
|
|
632
|
+
|
|
633
|
+
- Node.js 22 or newer is required.
|
|
634
|
+
- The package provides ESM, CommonJS, and TypeScript declarations.
|
|
635
|
+
- The package has no runtime dependencies.
|
|
636
|
+
- No package-level `api`, `utils`, `storage`, or `agents` singleton is exported.
|
|
637
|
+
- Deprecated low-code methods and aliases are excluded.
|
|
638
|
+
- `api.transactionDatabaseData` is excluded from 1.0.0.
|
|
639
|
+
- Runtime batches are non-transactional and are not retried automatically.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The published `@revoengine/sdk` JavaScript bundles incorporate the following
|
|
4
|
+
third-party packages. This inventory is generated from the shipped bundle source
|
|
5
|
+
maps and is checked against the repository license policy on every release.
|
|
6
|
+
|
|
7
|
+
| Package | Version | License |
|
|
8
|
+
| --- | ---: | --- |
|
|
9
|
+
| `buffer-equal-constant-time` | `1.0.1` | `BSD-3-Clause` |
|
|
10
|
+
| `copy-anything` | `4.1.0` | `MIT` |
|
|
11
|
+
| `ecdsa-sig-formatter` | `1.0.11` | `Apache-2.0` |
|
|
12
|
+
| `jsonwebtoken` | `9.0.3` | `MIT` |
|
|
13
|
+
| `jwa` | `2.0.1` | `MIT` |
|
|
14
|
+
| `jws` | `4.0.1` | `MIT` |
|
|
15
|
+
| `lodash.includes` | `4.3.0` | `MIT` |
|
|
16
|
+
| `lodash.isboolean` | `3.0.3` | `MIT` |
|
|
17
|
+
| `lodash.isinteger` | `4.0.4` | `MIT` |
|
|
18
|
+
| `lodash.isnumber` | `3.0.3` | `MIT` |
|
|
19
|
+
| `lodash.isplainobject` | `4.0.6` | `MIT` |
|
|
20
|
+
| `lodash.isstring` | `4.0.1` | `MIT` |
|
|
21
|
+
| `lodash.once` | `4.1.1` | `MIT` |
|
|
22
|
+
| `ms` | `2.1.3` | `MIT` |
|
|
23
|
+
| `object-hash` | `3.0.0` | `MIT` |
|
|
24
|
+
| `safe-buffer` | `5.2.1` | `MIT` |
|
|
25
|
+
| `semver` | `7.8.5` | `ISC` |
|
|
26
|
+
| `superjson` | `2.2.6` | `MIT` |
|
|
27
|
+
| `uuid` | `13.0.2` | `MIT` |
|
|
28
|
+
|
|
29
|
+
The complete license texts are distributed in
|
|
30
|
+
[`licenses/THIRD_PARTY_LICENSES.txt`](licenses/THIRD_PARTY_LICENSES.txt).
|