@nopaque/sdk 0.7.0 → 0.8.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/CHANGELOG.md +35 -1
- package/README.md +25 -0
- package/dist/index.cjs +174 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +271 -1
- package/dist/index.d.ts +271 -1
- package/dist/index.mjs +174 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.8.0] - 2026-09-30
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- An error body carrying `message` instead of `error` now surfaces its text.
|
|
14
|
+
API Gateway's own 401, 403 and 429 responses, and a few older handlers, send
|
|
15
|
+
`message`; the SDK read only `error`, so those errors showed as just
|
|
16
|
+
`HTTP 403` with no reason.
|
|
17
|
+
- `pnpm test:integration` allows 30 s per test. The live-API schedule
|
|
18
|
+
round-trip makes three calls and could pass Vitest's 5 s default on cold
|
|
19
|
+
Lambdas.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `surveys` resource for survey tests, where your platform sends the survey and
|
|
24
|
+
nopaque answers as the respondent.
|
|
25
|
+
- `surveys.start()`, `stop()` and `list()` over `/testing/survey-runs`.
|
|
26
|
+
`start()` takes `sender` — the number your survey platform sends from — and
|
|
27
|
+
sends it as `endUserE164`. `list()` returns the runs plus the workspace's
|
|
28
|
+
`numbers: { total, busy, free }`.
|
|
29
|
+
- `surveys.results.list()` / `listPage()` / `get()` over
|
|
30
|
+
`/testing/survey-results`. A result carries the conversation as `turns`.
|
|
31
|
+
- `surveys.configs.create()` / `list()` / `get()` / `update()` / `delete()`
|
|
32
|
+
over `/testing/survey-test-configs`. `configs.list()` is not paginated and
|
|
33
|
+
returns a plain array.
|
|
34
|
+
- `surveys.waitForResult(runId)` polls until `capture.status` is `final` or
|
|
35
|
+
`failed` and returns the result either way. A 404 counts as "not written
|
|
36
|
+
yet" for `notFoundGrace` (default 60 s). Without a `timeout`, the deadline
|
|
37
|
+
is the test's `expiresAt` plus 10 minutes. Throws `NopaqueTimeoutError` on
|
|
38
|
+
the deadline without stopping the test.
|
|
39
|
+
|
|
7
40
|
## [0.7.0] - 2026-08-19
|
|
8
41
|
|
|
9
42
|
### Fixed
|
|
@@ -205,5 +238,6 @@ client.mapping.create({
|
|
|
205
238
|
- Typed error class hierarchy.
|
|
206
239
|
- Dual ESM + CJS output with TypeScript definitions.
|
|
207
240
|
|
|
208
|
-
[Unreleased]: https://github.com/nopaque/node-sdk/compare/v0.
|
|
241
|
+
[Unreleased]: https://github.com/nopaque/node-sdk/compare/v0.8.0...HEAD
|
|
242
|
+
[0.8.0]: https://github.com/nopaque/node-sdk/releases/tag/v0.8.0
|
|
209
243
|
[0.1.0]: https://github.com/nopaque/node-sdk/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -55,6 +55,31 @@ const finished = await client.digitalTesting.waitForRun(run.id);
|
|
|
55
55
|
console.log(finished.status, finished.outcome, finished.passRate);
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### Survey tests
|
|
59
|
+
|
|
60
|
+
Survey testing reverses the direction: your platform sends the survey, and
|
|
61
|
+
nopaque answers as the respondent. Start a test, point your survey at the
|
|
62
|
+
number it returns, then wait for the conversation.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const test = await client.surveys.start({
|
|
66
|
+
configId: 'cfg_123',
|
|
67
|
+
sender: '+447700900123', // the number your survey platform sends FROM
|
|
68
|
+
windowSecs: 300,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Trigger your survey from `sender` to this number before `expiresAt`.
|
|
72
|
+
console.log('Send the survey to', test.agentE164);
|
|
73
|
+
|
|
74
|
+
const result = await client.surveys.waitForResult(test.runId);
|
|
75
|
+
// A failed capture is a RESULT, not an error: check `capture.error`.
|
|
76
|
+
console.log(result.outcome, result.capture.status);
|
|
77
|
+
for (const turn of result.turns) console.log(`${turn.from}: ${turn.text}`);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`start()` throws `ConflictError` when the workspace has no free survey number
|
|
81
|
+
for that sender. `surveys.list()` shows how many are free.
|
|
82
|
+
|
|
58
83
|
## Features
|
|
59
84
|
|
|
60
85
|
- Full coverage of the Nopaque REST API via API-key auth
|
package/dist/index.cjs
CHANGED
|
@@ -133,7 +133,7 @@ function resolveConfig(opts) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// src/version.ts
|
|
136
|
-
var VERSION = true ? "0.
|
|
136
|
+
var VERSION = true ? "0.8.0" : "0.0.0-dev";
|
|
137
137
|
|
|
138
138
|
// src/userAgent.ts
|
|
139
139
|
function composeUserAgent() {
|
|
@@ -274,7 +274,7 @@ async function classifyResponse(response) {
|
|
|
274
274
|
body = null;
|
|
275
275
|
}
|
|
276
276
|
const bodyObj = body && typeof body === "object" ? body : {};
|
|
277
|
-
const message = typeof bodyObj.error === "string" ? bodyObj.error : `HTTP ${response.status}`;
|
|
277
|
+
const message = typeof bodyObj.error === "string" ? bodyObj.error : typeof bodyObj.message === "string" ? bodyObj.message : `HTTP ${response.status}`;
|
|
278
278
|
const code = typeof bodyObj.code === "string" ? bodyObj.code : null;
|
|
279
279
|
const details = bodyObj.details ?? null;
|
|
280
280
|
const requestId = response.headers.get("x-request-id");
|
|
@@ -1317,6 +1317,176 @@ var SchedulerResource = class extends Resource {
|
|
|
1317
1317
|
}
|
|
1318
1318
|
};
|
|
1319
1319
|
|
|
1320
|
+
// src/resources/surveys.ts
|
|
1321
|
+
var TERMINAL_CAPTURE = /* @__PURE__ */ new Set(["final", "failed"]);
|
|
1322
|
+
var DEFAULT_NOT_FOUND_GRACE = 6e4;
|
|
1323
|
+
var CAPTURE_GRACE = 6e5;
|
|
1324
|
+
var SurveyResultsResource = class extends Resource {
|
|
1325
|
+
/** Paginated list of result summaries, newest first. Transcripts are omitted. */
|
|
1326
|
+
list(params = {}, requestOptions) {
|
|
1327
|
+
return new Paginator({
|
|
1328
|
+
fetchPage: async (p) => {
|
|
1329
|
+
const { nextToken, cursor, ...rest } = p;
|
|
1330
|
+
const raw = await this.transport.request(
|
|
1331
|
+
"GET",
|
|
1332
|
+
"/testing/survey-results",
|
|
1333
|
+
{
|
|
1334
|
+
// `nextToken` first: the Paginator advances by writing it, and a
|
|
1335
|
+
// caller-supplied `cursor` would otherwise pin every page to page one.
|
|
1336
|
+
params: { ...rest, cursor: nextToken ?? cursor },
|
|
1337
|
+
requestOptions
|
|
1338
|
+
}
|
|
1339
|
+
);
|
|
1340
|
+
return {
|
|
1341
|
+
items: raw.results ?? raw.items ?? [],
|
|
1342
|
+
nextToken: raw.nextCursor ?? raw.nextToken ?? null
|
|
1343
|
+
};
|
|
1344
|
+
},
|
|
1345
|
+
params: { ...params }
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
/** One page of result summaries. */
|
|
1349
|
+
async listPage(params = {}, requestOptions) {
|
|
1350
|
+
const { nextToken, cursor, ...rest } = params;
|
|
1351
|
+
const raw = await this.transport.request("GET", "/testing/survey-results", {
|
|
1352
|
+
params: { ...rest, cursor: nextToken ?? cursor },
|
|
1353
|
+
requestOptions
|
|
1354
|
+
});
|
|
1355
|
+
return new Page(raw.results ?? raw.items ?? [], raw.nextCursor ?? raw.nextToken ?? null);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* One result with its conversation. While the test is live the capture may be
|
|
1359
|
+
* `provisional` and still grow.
|
|
1360
|
+
*/
|
|
1361
|
+
async get(runId, requestOptions) {
|
|
1362
|
+
return await this.transport.request("GET", `/testing/survey-results/${runId}`, {
|
|
1363
|
+
requestOptions
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
var SurveyConfigsResource = class extends Resource {
|
|
1368
|
+
async create(body, requestOptions) {
|
|
1369
|
+
return await this.transport.request("POST", "/testing/survey-test-configs", {
|
|
1370
|
+
body,
|
|
1371
|
+
requestOptions
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
/** Every config in the workspace. Not paginated; `mission` and `acceptance` are omitted. */
|
|
1375
|
+
async list(requestOptions) {
|
|
1376
|
+
const raw = await this.transport.request(
|
|
1377
|
+
"GET",
|
|
1378
|
+
"/testing/survey-test-configs",
|
|
1379
|
+
{ requestOptions }
|
|
1380
|
+
);
|
|
1381
|
+
return raw.configs ?? [];
|
|
1382
|
+
}
|
|
1383
|
+
async get(configId, requestOptions) {
|
|
1384
|
+
return await this.transport.request("GET", `/testing/survey-test-configs/${configId}`, {
|
|
1385
|
+
requestOptions
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
/** PATCH - only the fields passed are sent. `null` clears `description`, `tags` or `voiceId`. */
|
|
1389
|
+
async update(configId, body, requestOptions) {
|
|
1390
|
+
return await this.transport.request("PATCH", `/testing/survey-test-configs/${configId}`, {
|
|
1391
|
+
body,
|
|
1392
|
+
requestOptions
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
/** Tests already started from the config are unaffected. */
|
|
1396
|
+
async delete(configId, requestOptions) {
|
|
1397
|
+
await this.transport.request("DELETE", `/testing/survey-test-configs/${configId}`, {
|
|
1398
|
+
requestOptions
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
var SurveysResource = class extends Resource {
|
|
1403
|
+
results;
|
|
1404
|
+
configs;
|
|
1405
|
+
constructor(transport) {
|
|
1406
|
+
super(transport);
|
|
1407
|
+
this.results = new SurveyResultsResource(transport);
|
|
1408
|
+
this.configs = new SurveyConfigsResource(transport);
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Start a survey test. Nothing runs yet: nopaque begins WAITING for a survey
|
|
1412
|
+
* sent from `sender` to the returned `agentE164`, until `expiresAt`.
|
|
1413
|
+
*
|
|
1414
|
+
* Throws `ConflictError` (409) when the workspace has no live survey numbers,
|
|
1415
|
+
* or when every one of them is already running a test against this sender -
|
|
1416
|
+
* the message tells the two apart. `NotFoundError` (404) for an unknown
|
|
1417
|
+
* config. A 503 means the number could not be put into service and nothing
|
|
1418
|
+
* was reserved; it is safe to try again, but this method does not.
|
|
1419
|
+
*/
|
|
1420
|
+
async start(params, requestOptions) {
|
|
1421
|
+
const { configId, sender, windowSecs, expectedTurns, maxMessages } = params;
|
|
1422
|
+
const body = {
|
|
1423
|
+
configId,
|
|
1424
|
+
endUserE164: sender,
|
|
1425
|
+
...windowSecs !== void 0 && { windowSecs },
|
|
1426
|
+
...expectedTurns !== void 0 && { expectedTurns },
|
|
1427
|
+
...maxMessages !== void 0 && { maxMessages }
|
|
1428
|
+
};
|
|
1429
|
+
return await this.transport.request("POST", "/testing/survey-runs", { body, requestOptions });
|
|
1430
|
+
}
|
|
1431
|
+
/** Stop a test before its window closes, freeing its number. */
|
|
1432
|
+
async stop(runId, requestOptions) {
|
|
1433
|
+
await this.transport.request("DELETE", `/testing/survey-runs/${runId}`, { requestOptions });
|
|
1434
|
+
}
|
|
1435
|
+
/** Every survey test holding a number, plus how many numbers are free. Not paginated. */
|
|
1436
|
+
async list(requestOptions) {
|
|
1437
|
+
return await this.transport.request("GET", "/testing/survey-runs", { requestOptions });
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Poll `results.get(runId)` until the capture is `final` or `failed`, and
|
|
1441
|
+
* return the result either way. A `failed` capture is a RESULT, not an error -
|
|
1442
|
+
* inspect `capture.error`.
|
|
1443
|
+
*
|
|
1444
|
+
* The result row is written asynchronously after `start()`, so a 404 is
|
|
1445
|
+
* treated as "not yet" for `notFoundGrace` and rethrown after that. Throws
|
|
1446
|
+
* `NopaqueTimeoutError` on the deadline; the test itself is NOT stopped.
|
|
1447
|
+
*/
|
|
1448
|
+
async waitForResult(runId, opts = {}) {
|
|
1449
|
+
const notFoundGrace = opts.notFoundGrace ?? DEFAULT_NOT_FOUND_GRACE;
|
|
1450
|
+
const started = Date.now();
|
|
1451
|
+
let deadline = started + (opts.timeout ?? notFoundGrace);
|
|
1452
|
+
let deadlineDerived = opts.timeout !== void 0;
|
|
1453
|
+
let step = 0;
|
|
1454
|
+
while (true) {
|
|
1455
|
+
let result;
|
|
1456
|
+
try {
|
|
1457
|
+
result = await this.results.get(runId, opts.requestOptions);
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
if (!(err instanceof NotFoundError) || Date.now() - started >= notFoundGrace) throw err;
|
|
1460
|
+
}
|
|
1461
|
+
if (result) {
|
|
1462
|
+
if (opts.onUpdate) {
|
|
1463
|
+
try {
|
|
1464
|
+
opts.onUpdate(result);
|
|
1465
|
+
} catch {
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (TERMINAL_CAPTURE.has(result.capture?.status)) return result;
|
|
1469
|
+
if (!deadlineDerived) {
|
|
1470
|
+
deadline = result.expiresAt * 1e3 + CAPTURE_GRACE;
|
|
1471
|
+
deadlineDerived = true;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const now = Date.now();
|
|
1475
|
+
if (now >= deadline) {
|
|
1476
|
+
throw new NopaqueTimeoutError(
|
|
1477
|
+
`waitForResult timed out after ${now - started}ms waiting for survey result ${runId}`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
const interval = Math.min(
|
|
1481
|
+
pollIntervalCurve(step, { base: opts.pollInterval, cap: opts.intervalCap }),
|
|
1482
|
+
Math.max(0, deadline - now)
|
|
1483
|
+
);
|
|
1484
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1485
|
+
step++;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1320
1490
|
// src/resources/sweeps.ts
|
|
1321
1491
|
var RUN_TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
1322
1492
|
var SweepsResource = class extends Resource {
|
|
@@ -1549,6 +1719,7 @@ var Nopaque = class {
|
|
|
1549
1719
|
digitalTesting;
|
|
1550
1720
|
digitalTestConfigs;
|
|
1551
1721
|
digitalCompliance;
|
|
1722
|
+
surveys;
|
|
1552
1723
|
transport;
|
|
1553
1724
|
constructor(options = {}) {
|
|
1554
1725
|
const config = resolveConfig(options);
|
|
@@ -1569,6 +1740,7 @@ var Nopaque = class {
|
|
|
1569
1740
|
this.digitalTesting = new DigitalTestingResource(this.transport);
|
|
1570
1741
|
this.digitalTestConfigs = new DigitalTestConfigsResource(this.transport);
|
|
1571
1742
|
this.digitalCompliance = new DigitalComplianceResource(this.transport);
|
|
1743
|
+
this.surveys = new SurveysResource(this.transport);
|
|
1572
1744
|
}
|
|
1573
1745
|
close() {
|
|
1574
1746
|
}
|