@particle-academy/fancy-connector-core 0.13.0 → 0.15.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 +63 -0
- package/README.md +6 -1
- package/dist/index.cjs +150 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -1
- package/dist/index.d.ts +117 -1
- package/dist/index.js +142 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,69 @@ All notable changes to `fancy-connector-core` are documented here, in
|
|
|
6
6
|
**This package is pre-1.0, so breaking changes land in MINOR releases.** The
|
|
7
7
|
version number is not a promise it can keep yet; the entries below are.
|
|
8
8
|
|
|
9
|
+
## [0.15.0] - 2026-11-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`SetupStep` says who does each step and what it yields.** New optional
|
|
14
|
+
fields, in both runtimes:
|
|
15
|
+
- `actor`: `person` (the default when absent) or `agent`; `SETUP_ACTORS` /
|
|
16
|
+
`Setup::ACTORS` ship it as data.
|
|
17
|
+
- `prompt`: what an agent is asked to do. Required for an agent step.
|
|
18
|
+
- `copy`: `[{label, value}]` values the person pastes somewhere, such as a
|
|
19
|
+
callback URL.
|
|
20
|
+
- `fields`: the credential keys the step yields.
|
|
21
|
+
- `done`: how the person knows the step is done.
|
|
22
|
+
|
|
23
|
+
PHP's `SetupStep` gains `fromArray()` / `toArray()`. Absent stays absent:
|
|
24
|
+
a field nobody wrote is omitted, never null.
|
|
25
|
+
- **`renderSetup` / `Setup::render`: a connector's steps rendered for one
|
|
26
|
+
app.** `{{appUrl}}`, `{{appName}}` and `{{connector}}` are filled in every
|
|
27
|
+
text a person or an agent reads.
|
|
28
|
+
- Any other `{{...}}` is refused as `unknown-token`, so a person never
|
|
29
|
+
pastes a literal brace into a provider console.
|
|
30
|
+
- A token with no value, or an empty one, is refused as `missing-context`,
|
|
31
|
+
so a callback never becomes `/hooks/twilio`.
|
|
32
|
+
- `appUrl` loses one trailing slash first, and a value is never expanded
|
|
33
|
+
again.
|
|
34
|
+
- Held to `fixtures/setup-render/cases.json` in both runtimes.
|
|
35
|
+
|
|
36
|
+
## [0.14.0] - 2026-11-07
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`readWithCursor` / `CursorRead::read`: a read that resumes from a
|
|
41
|
+
HOST-HELD cursor, and pages.** It's for Gmail's `history.list` after a push,
|
|
42
|
+
and Drive's `changes.list` after a channel notification, where the delivery
|
|
43
|
+
says only that something moved. The package holds no state: the host stores
|
|
44
|
+
the cursor, passes it in, and persists `cursor.next` when the run succeeds.
|
|
45
|
+
- `next` is read ONLY from the LAST page: Gmail's `historyId` is the
|
|
46
|
+
mailbox's current one on every page.
|
|
47
|
+
- More than `page.maxPages` is refused as `cursor-overflow`, never a partial
|
|
48
|
+
read.
|
|
49
|
+
- The provider's `expiredStatus` (Gmail's 404) is refused as
|
|
50
|
+
`cursor-expired`. Every other failure is rethrown unchanged.
|
|
51
|
+
- No stored cursor is refused as `cursor-missing`, and the provider is not
|
|
52
|
+
called.
|
|
53
|
+
- A collected list that's absent is `[]`; one that isn't a list is
|
|
54
|
+
`bad-page`.
|
|
55
|
+
- Cursors are strings.
|
|
56
|
+
- A refusal carries the caller's `resyncQuery`.
|
|
57
|
+
|
|
58
|
+
The reasons ship as data (`CURSOR_READ_REASONS`, `CursorRead::REASONS`).
|
|
59
|
+
Both runtimes read `fixtures/cursor-read/cases.json`: 14 authored cases
|
|
60
|
+
against a scripted provider, every call compared.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- The README and the npm description now say plainly that PHP apps install
|
|
65
|
+
the package with Composer.
|
|
66
|
+
- `fixtures/jwt` gains rows 0015 to 0024: signature length, values at and
|
|
67
|
+
above the modulus, and valid RSA signatures over malformed PKCS#1 v1.5
|
|
68
|
+
blocks.
|
|
69
|
+
|
|
70
|
+
`CONNECTOR_API_VERSION` is unchanged: everything here is optional.
|
|
71
|
+
|
|
9
72
|
## [0.13.0] - 2026-11-01
|
|
10
73
|
|
|
11
74
|
### Added
|
package/README.md
CHANGED
|
@@ -9,10 +9,15 @@ versioned rendering, chain posting, declared metric shapes, credential-free
|
|
|
9
9
|
probes, and API-drift reporting.
|
|
10
10
|
|
|
11
11
|
Matched **TypeScript + PHP**, one repository, **zero runtime dependencies in
|
|
12
|
-
either ecosystem**.
|
|
12
|
+
either ecosystem**. TypeScript and Node apps install it from npm; **PHP apps
|
|
13
|
+
(Laravel included) install it with Composer**. Both packages are released
|
|
14
|
+
together at the same version.
|
|
13
15
|
|
|
14
16
|
```bash
|
|
17
|
+
# TypeScript / Node
|
|
15
18
|
npm install @particle-academy/fancy-connector-core
|
|
19
|
+
|
|
20
|
+
# PHP (Composer)
|
|
16
21
|
composer require particle-academy/fancy-connector-core
|
|
17
22
|
```
|
|
18
23
|
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(src_exports, {
|
|
|
25
25
|
CONNECTOR_API_VERSION: () => CONNECTOR_API_VERSION,
|
|
26
26
|
CONNECTOR_MODES: () => CONNECTOR_MODES,
|
|
27
27
|
CREDENTIAL_SCOPES: () => CREDENTIAL_SCOPES,
|
|
28
|
+
CURSOR_READ_REASONS: () => CURSOR_READ_REASONS,
|
|
28
29
|
ConnectorAmbiguous: () => ConnectorAmbiguous,
|
|
29
30
|
ConnectorApiMismatch: () => ConnectorApiMismatch,
|
|
30
31
|
ConnectorAuthError: () => ConnectorAuthError,
|
|
@@ -36,6 +37,7 @@ __export(src_exports, {
|
|
|
36
37
|
ConnectorRequestError: () => ConnectorRequestError,
|
|
37
38
|
ConnectorTransient: () => ConnectorTransient,
|
|
38
39
|
ConnectorUnreachable: () => ConnectorUnreachable,
|
|
40
|
+
CursorReadError: () => CursorReadError,
|
|
39
41
|
DEFAULT_IDEMPOTENCY_WINDOW_SECONDS: () => DEFAULT_IDEMPOTENCY_WINDOW_SECONDS,
|
|
40
42
|
DEFAULT_RETRY: () => DEFAULT_RETRY,
|
|
41
43
|
DELIVERY_DECODE_KINDS: () => DELIVERY_DECODE_KINDS,
|
|
@@ -53,8 +55,12 @@ __export(src_exports, {
|
|
|
53
55
|
RENDERER_VERSION: () => RENDERER_VERSION,
|
|
54
56
|
REVIEW_STALE_DAYS: () => REVIEW_STALE_DAYS,
|
|
55
57
|
SANDBOX_KINDS: () => SANDBOX_KINDS,
|
|
58
|
+
SETUP_ACTORS: () => SETUP_ACTORS,
|
|
59
|
+
SETUP_RENDER_REASONS: () => SETUP_RENDER_REASONS,
|
|
60
|
+
SETUP_TOKENS: () => SETUP_TOKENS,
|
|
56
61
|
SIGNED_URI_RULES: () => SIGNED_URI_RULES,
|
|
57
62
|
SUPPORTED_CONNECTOR_API: () => SUPPORTED_CONNECTOR_API,
|
|
63
|
+
SetupRenderError: () => SetupRenderError,
|
|
58
64
|
assertConnectorApi: () => assertConnectorApi,
|
|
59
65
|
byteRangeOf: () => byteRangeOf,
|
|
60
66
|
callConnector: () => callConnector,
|
|
@@ -104,9 +110,11 @@ __export(src_exports, {
|
|
|
104
110
|
probe: () => probe,
|
|
105
111
|
providerProblems: () => providerProblems,
|
|
106
112
|
providerSandboxIsSelectable: () => providerSandboxIsSelectable,
|
|
113
|
+
readWithCursor: () => readWithCursor,
|
|
107
114
|
registerConnectionHost: () => registerConnectionHost,
|
|
108
115
|
registerTransport: () => registerTransport,
|
|
109
116
|
render: () => render,
|
|
117
|
+
renderSetup: () => renderSetup,
|
|
110
118
|
reported: () => reported,
|
|
111
119
|
resetRateState: () => resetRateState,
|
|
112
120
|
resolveConnection: () => resolveConnection,
|
|
@@ -302,10 +310,10 @@ var lastCallAt = /* @__PURE__ */ new Map();
|
|
|
302
310
|
async function respectRate(channel, minIntervalMs, now = () => Date.now(), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
303
311
|
if (minIntervalMs <= 0) return { waitedMs: 0 };
|
|
304
312
|
const previous = lastCallAt.get(channel);
|
|
305
|
-
const
|
|
306
|
-
const waitedMs = previous === void 0 ? 0 : Math.max(0, previous + minIntervalMs -
|
|
313
|
+
const at2 = now();
|
|
314
|
+
const waitedMs = previous === void 0 ? 0 : Math.max(0, previous + minIntervalMs - at2);
|
|
307
315
|
if (waitedMs > 0) await sleep(waitedMs);
|
|
308
|
-
lastCallAt.set(channel,
|
|
316
|
+
lastCallAt.set(channel, at2 + waitedMs);
|
|
309
317
|
return { waitedMs };
|
|
310
318
|
}
|
|
311
319
|
function resetRateState() {
|
|
@@ -803,6 +811,71 @@ function readString(config, key) {
|
|
|
803
811
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
804
812
|
}
|
|
805
813
|
|
|
814
|
+
// src/cursor-read.ts
|
|
815
|
+
var CURSOR_READ_REASONS = ["cursor-missing", "cursor-expired", "cursor-overflow", "bad-page"];
|
|
816
|
+
var CursorReadError = class extends Error {
|
|
817
|
+
constructor(reason, message, resyncQuery) {
|
|
818
|
+
super(message);
|
|
819
|
+
this.reason = reason;
|
|
820
|
+
this.resyncQuery = resyncQuery;
|
|
821
|
+
this.name = "CursorReadError";
|
|
822
|
+
}
|
|
823
|
+
reason;
|
|
824
|
+
resyncQuery;
|
|
825
|
+
};
|
|
826
|
+
async function readWithCursor(spec, cursor, call, options = {}) {
|
|
827
|
+
const refuse = (reason, message) => new CursorReadError(reason, message, options.resyncQuery);
|
|
828
|
+
const used = asCursor(cursor);
|
|
829
|
+
if (used === null) throw refuse("cursor-missing", `no stored ${spec.param}: the host seeds one before the first read`);
|
|
830
|
+
const collected = [];
|
|
831
|
+
let last;
|
|
832
|
+
let token = null;
|
|
833
|
+
let pages = 0;
|
|
834
|
+
for (; ; ) {
|
|
835
|
+
if (spec.page && pages === spec.page.maxPages) {
|
|
836
|
+
throw refuse("cursor-overflow", `more than ${spec.page.maxPages} pages since ${spec.param} ${used}`);
|
|
837
|
+
}
|
|
838
|
+
const params = { [spec.param]: used };
|
|
839
|
+
if (spec.page && token !== null) params[spec.page.param] = token;
|
|
840
|
+
try {
|
|
841
|
+
last = await call(params);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
const status = error?.status;
|
|
844
|
+
if (spec.expiredStatus !== void 0 && status === spec.expiredStatus) {
|
|
845
|
+
throw refuse("cursor-expired", `${spec.param} ${used} is out of the provider's range (${status})`);
|
|
846
|
+
}
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
849
|
+
pages++;
|
|
850
|
+
if (!spec.page) break;
|
|
851
|
+
const items = at(last, spec.page.collect);
|
|
852
|
+
if (items !== void 0 && !Array.isArray(items)) {
|
|
853
|
+
throw refuse("bad-page", `${spec.page.collect} is not a list`);
|
|
854
|
+
}
|
|
855
|
+
if (items) collected.push(...items);
|
|
856
|
+
token = asCursor(at(last, spec.page.from));
|
|
857
|
+
if (token === null) break;
|
|
858
|
+
}
|
|
859
|
+
const data = spec.page ? withList(last, spec.page.collect, collected) : last;
|
|
860
|
+
return { data, cursor: { used, next: asCursor(at(last, spec.advanceFrom)) } };
|
|
861
|
+
}
|
|
862
|
+
function asCursor(value) {
|
|
863
|
+
if (typeof value === "string") return value === "" ? null : value;
|
|
864
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
function at(value, path) {
|
|
868
|
+
return path.split(".").reduce(
|
|
869
|
+
(v, key) => v !== null && typeof v === "object" && !Array.isArray(v) ? v[key] : void 0,
|
|
870
|
+
value
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function withList(data, path, list) {
|
|
874
|
+
const [head, ...rest] = path.split(".");
|
|
875
|
+
const base = data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
876
|
+
return { ...base, [head]: rest.length ? withList(base[head], rest.join("."), list) : list };
|
|
877
|
+
}
|
|
878
|
+
|
|
806
879
|
// src/drift.ts
|
|
807
880
|
function resolveRef(document, node, depth = 0) {
|
|
808
881
|
if (depth > 8 || node === null || typeof node !== "object") return node;
|
|
@@ -1457,32 +1530,32 @@ function base64urlBytes(segment) {
|
|
|
1457
1530
|
var EXPIRES_AT_UNITS = ["rfc3339", "epoch-ms"];
|
|
1458
1531
|
var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
1459
1532
|
var DEFAULT_CONTEXT = { service: "", operation: "subscription" };
|
|
1460
|
-
function instantMs(value, field,
|
|
1533
|
+
function instantMs(value, field, at2) {
|
|
1461
1534
|
if (typeof value !== "string" || !INSTANT.test(value)) {
|
|
1462
1535
|
throw new ConnectorConfigError(
|
|
1463
1536
|
`${field} must be an RFC 3339 instant such as "2026-09-22T00:00:00Z", got ${JSON.stringify(value)}. A provider's own shape \u2014 Google's epoch milliseconds, say \u2014 is converted by the connector, never guessed at here.`,
|
|
1464
|
-
|
|
1537
|
+
at2
|
|
1465
1538
|
);
|
|
1466
1539
|
}
|
|
1467
1540
|
const ms = Date.parse(value.replace(/(\.\d{3})\d+/, "$1"));
|
|
1468
1541
|
if (Number.isNaN(ms)) {
|
|
1469
|
-
throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`,
|
|
1542
|
+
throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`, at2);
|
|
1470
1543
|
}
|
|
1471
1544
|
return ms;
|
|
1472
1545
|
}
|
|
1473
|
-
function leaseFromResponse(declaration, response,
|
|
1546
|
+
function leaseFromResponse(declaration, response, at2 = DEFAULT_CONTEXT) {
|
|
1474
1547
|
const { expiresAtFrom: path, expiresAtUnit: unit } = declaration;
|
|
1475
1548
|
if (!EXPIRES_AT_UNITS.includes(unit)) {
|
|
1476
1549
|
throw new ConnectorConfigError(
|
|
1477
1550
|
`expiresAtUnit must be one of ${EXPIRES_AT_UNITS.join(" | ")}, got ${JSON.stringify(unit)}.`,
|
|
1478
|
-
|
|
1551
|
+
at2
|
|
1479
1552
|
);
|
|
1480
1553
|
}
|
|
1481
1554
|
const value = readPath(response, path);
|
|
1482
1555
|
if (value === void 0 || value === null) {
|
|
1483
1556
|
throw new ConnectorConfigError(
|
|
1484
1557
|
`${path} is not present in the response, so no lease can be built \u2014 a subscription with no known expiry is one nobody renews.`,
|
|
1485
|
-
|
|
1558
|
+
at2
|
|
1486
1559
|
);
|
|
1487
1560
|
}
|
|
1488
1561
|
let expiresAt;
|
|
@@ -1491,7 +1564,7 @@ function leaseFromResponse(declaration, response, at = DEFAULT_CONTEXT) {
|
|
|
1491
1564
|
if (!Number.isSafeInteger(ms)) {
|
|
1492
1565
|
throw new ConnectorConfigError(
|
|
1493
1566
|
`${path} must be epoch milliseconds as digits under "epoch-ms", got ${JSON.stringify(value)}.`,
|
|
1494
|
-
|
|
1567
|
+
at2
|
|
1495
1568
|
);
|
|
1496
1569
|
}
|
|
1497
1570
|
expiresAt = new Date(ms).toISOString();
|
|
@@ -1499,14 +1572,14 @@ function leaseFromResponse(declaration, response, at = DEFAULT_CONTEXT) {
|
|
|
1499
1572
|
if (typeof value !== "string" || !INSTANT.test(value)) {
|
|
1500
1573
|
throw new ConnectorConfigError(
|
|
1501
1574
|
`${path} must be an RFC 3339 instant under "rfc3339", got ${JSON.stringify(value)}.`,
|
|
1502
|
-
|
|
1575
|
+
at2
|
|
1503
1576
|
);
|
|
1504
1577
|
}
|
|
1505
|
-
expiresAt = new Date(instantMs(value, path,
|
|
1578
|
+
expiresAt = new Date(instantMs(value, path, at2)).toISOString();
|
|
1506
1579
|
}
|
|
1507
1580
|
return subscriptionLease(
|
|
1508
1581
|
{ expiresAt, renewBeforeSeconds: declaration.renewBeforeSeconds, renewOperation: declaration.renewOperation },
|
|
1509
|
-
|
|
1582
|
+
at2
|
|
1510
1583
|
);
|
|
1511
1584
|
}
|
|
1512
1585
|
function readPath(value, path) {
|
|
@@ -1517,18 +1590,18 @@ function readPath(value, path) {
|
|
|
1517
1590
|
}
|
|
1518
1591
|
return current;
|
|
1519
1592
|
}
|
|
1520
|
-
function subscriptionLease(input,
|
|
1521
|
-
instantMs(input.expiresAt, "expiresAt",
|
|
1593
|
+
function subscriptionLease(input, at2 = DEFAULT_CONTEXT) {
|
|
1594
|
+
instantMs(input.expiresAt, "expiresAt", at2);
|
|
1522
1595
|
if (!Number.isInteger(input.renewBeforeSeconds) || input.renewBeforeSeconds <= 0) {
|
|
1523
1596
|
throw new ConnectorConfigError(
|
|
1524
1597
|
`renewBeforeSeconds must be a positive whole number of seconds, got ${JSON.stringify(input.renewBeforeSeconds)}. Zero would make the lease due only at the instant it expires, which is a lease nobody renews.`,
|
|
1525
|
-
|
|
1598
|
+
at2
|
|
1526
1599
|
);
|
|
1527
1600
|
}
|
|
1528
1601
|
if (typeof input.renewOperation !== "string" || input.renewOperation.trim() === "") {
|
|
1529
1602
|
throw new ConnectorConfigError(
|
|
1530
1603
|
"renewOperation must name the operation the host calls when the lease is due; a due lease with nothing to call is one nobody renews.",
|
|
1531
|
-
|
|
1604
|
+
at2
|
|
1532
1605
|
);
|
|
1533
1606
|
}
|
|
1534
1607
|
return { expiresAt: input.expiresAt, renewBeforeSeconds: input.renewBeforeSeconds, renewOperation: input.renewOperation };
|
|
@@ -1537,10 +1610,10 @@ function leaseRenewAt(lease) {
|
|
|
1537
1610
|
return new Date(instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT) - lease.renewBeforeSeconds * 1e3).toISOString();
|
|
1538
1611
|
}
|
|
1539
1612
|
function leaseState(lease, now) {
|
|
1540
|
-
const
|
|
1613
|
+
const at2 = now instanceof Date ? now.getTime() : instantMs(now, "now", DEFAULT_CONTEXT);
|
|
1541
1614
|
const expires = instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT);
|
|
1542
|
-
if (
|
|
1543
|
-
if (
|
|
1615
|
+
if (at2 >= expires) return "expired";
|
|
1616
|
+
if (at2 >= expires - lease.renewBeforeSeconds * 1e3) return "due";
|
|
1544
1617
|
return "active";
|
|
1545
1618
|
}
|
|
1546
1619
|
function leaseAction(lease, now) {
|
|
@@ -1860,8 +1933,8 @@ function splitMultipart(body, boundary) {
|
|
|
1860
1933
|
if (partStart >= 0 && partStart <= body.length) children.push(body.subarray(partStart));
|
|
1861
1934
|
return children;
|
|
1862
1935
|
}
|
|
1863
|
-
function startsWith(haystack,
|
|
1864
|
-
for (let k = 0; k < needle.length; k++) if (haystack[
|
|
1936
|
+
function startsWith(haystack, at2, needle) {
|
|
1937
|
+
for (let k = 0; k < needle.length; k++) if (haystack[at2 + k] !== needle[k]) return false;
|
|
1865
1938
|
return true;
|
|
1866
1939
|
}
|
|
1867
1940
|
function parseMime(raw) {
|
|
@@ -2428,6 +2501,53 @@ function withResolvedLimit(rules, limit) {
|
|
|
2428
2501
|
var CREDENTIAL_SCOPES = ["provider", "account"];
|
|
2429
2502
|
var PROBLEM_SEVERITIES = ["block", "warn"];
|
|
2430
2503
|
var CANONICAL_METRICS = ["like", "share", "reply", "quote", "view"];
|
|
2504
|
+
var SETUP_ACTORS = ["person", "agent"];
|
|
2505
|
+
|
|
2506
|
+
// src/setup.ts
|
|
2507
|
+
var SETUP_TOKENS = ["appUrl", "appName", "connector"];
|
|
2508
|
+
var SETUP_RENDER_REASONS = ["unknown-token", "missing-context"];
|
|
2509
|
+
var SetupRenderError = class extends Error {
|
|
2510
|
+
constructor(reason, message) {
|
|
2511
|
+
super(message);
|
|
2512
|
+
this.reason = reason;
|
|
2513
|
+
this.name = "SetupRenderError";
|
|
2514
|
+
}
|
|
2515
|
+
reason;
|
|
2516
|
+
};
|
|
2517
|
+
var TOKEN = /\{\{([^{}]*)\}\}/g;
|
|
2518
|
+
function fill(text, context) {
|
|
2519
|
+
return text.replace(TOKEN, (whole, name) => {
|
|
2520
|
+
if (!SETUP_TOKENS.includes(name)) {
|
|
2521
|
+
throw new SetupRenderError(
|
|
2522
|
+
"unknown-token",
|
|
2523
|
+
`setup step text uses ${whole}, which is not one of ${SETUP_TOKENS.map((t) => `{{${t}}}`).join(", ")}`
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
let value = context[name];
|
|
2527
|
+
if (value === void 0 || value === null || value === "") {
|
|
2528
|
+
throw new SetupRenderError("missing-context", `setup step text uses ${whole} and the host gave no value for ${name}`);
|
|
2529
|
+
}
|
|
2530
|
+
if (name === "appUrl" && value.endsWith("/")) value = value.slice(0, -1);
|
|
2531
|
+
return value;
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
function renderSetup(steps, context) {
|
|
2535
|
+
return steps.map((step) => {
|
|
2536
|
+
const out = {
|
|
2537
|
+
title: fill(step.title, context),
|
|
2538
|
+
detail: fill(step.detail, context),
|
|
2539
|
+
actor: step.actor ?? "person",
|
|
2540
|
+
fields: [...step.fields ?? []]
|
|
2541
|
+
};
|
|
2542
|
+
if (step.url !== void 0) out.url = fill(step.url, context);
|
|
2543
|
+
if (step.prompt !== void 0) out.prompt = fill(step.prompt, context);
|
|
2544
|
+
if (step.done !== void 0) out.done = fill(step.done, context);
|
|
2545
|
+
if (step.copy !== void 0) {
|
|
2546
|
+
out.copy = step.copy.map((c) => ({ label: fill(c.label, context), value: fill(c.value, context) }));
|
|
2547
|
+
}
|
|
2548
|
+
return out;
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2431
2551
|
|
|
2432
2552
|
// src/trigger.ts
|
|
2433
2553
|
function isOidcJwtSpec(spec) {
|
|
@@ -2503,6 +2623,7 @@ function hasDelivery(value) {
|
|
|
2503
2623
|
CONNECTOR_API_VERSION,
|
|
2504
2624
|
CONNECTOR_MODES,
|
|
2505
2625
|
CREDENTIAL_SCOPES,
|
|
2626
|
+
CURSOR_READ_REASONS,
|
|
2506
2627
|
ConnectorAmbiguous,
|
|
2507
2628
|
ConnectorApiMismatch,
|
|
2508
2629
|
ConnectorAuthError,
|
|
@@ -2514,6 +2635,7 @@ function hasDelivery(value) {
|
|
|
2514
2635
|
ConnectorRequestError,
|
|
2515
2636
|
ConnectorTransient,
|
|
2516
2637
|
ConnectorUnreachable,
|
|
2638
|
+
CursorReadError,
|
|
2517
2639
|
DEFAULT_IDEMPOTENCY_WINDOW_SECONDS,
|
|
2518
2640
|
DEFAULT_RETRY,
|
|
2519
2641
|
DELIVERY_DECODE_KINDS,
|
|
@@ -2531,8 +2653,12 @@ function hasDelivery(value) {
|
|
|
2531
2653
|
RENDERER_VERSION,
|
|
2532
2654
|
REVIEW_STALE_DAYS,
|
|
2533
2655
|
SANDBOX_KINDS,
|
|
2656
|
+
SETUP_ACTORS,
|
|
2657
|
+
SETUP_RENDER_REASONS,
|
|
2658
|
+
SETUP_TOKENS,
|
|
2534
2659
|
SIGNED_URI_RULES,
|
|
2535
2660
|
SUPPORTED_CONNECTOR_API,
|
|
2661
|
+
SetupRenderError,
|
|
2536
2662
|
assertConnectorApi,
|
|
2537
2663
|
byteRangeOf,
|
|
2538
2664
|
callConnector,
|
|
@@ -2582,9 +2708,11 @@ function hasDelivery(value) {
|
|
|
2582
2708
|
probe,
|
|
2583
2709
|
providerProblems,
|
|
2584
2710
|
providerSandboxIsSelectable,
|
|
2711
|
+
readWithCursor,
|
|
2585
2712
|
registerConnectionHost,
|
|
2586
2713
|
registerTransport,
|
|
2587
2714
|
render,
|
|
2715
|
+
renderSetup,
|
|
2588
2716
|
reported,
|
|
2589
2717
|
resetRateState,
|
|
2590
2718
|
resolveConnection,
|