@particle-academy/fancy-connector-core 0.1.0 → 0.3.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 +114 -1
- package/README.md +25 -0
- package/dist/index.cjs +114 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -9
- package/dist/index.d.ts +143 -9
- package/dist/index.js +104 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,8 +6,120 @@ 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.3.0] - 2026-08-19
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`ECONNRESET` and `EPIPE` are `ambiguous`, not `unreachable`. This was a live
|
|
14
|
+
duplicate-send hazard.** Both were listed as codes proving the request never
|
|
15
|
+
left. They do not. A peer sends RST whenever it tears the connection down,
|
|
16
|
+
*including after it has received and acted on the request but before the
|
|
17
|
+
response comes back*; `EPIPE` is the same story from our side. Node surfaces
|
|
18
|
+
the safe case and the unsafe case as the same code, so the code cannot carry
|
|
19
|
+
the claim.
|
|
20
|
+
|
|
21
|
+
Because `shouldRetry` returns true for `unreachable` **without consulting
|
|
22
|
+
`idempotent`**, a reset produced up to three sends of a connector that had
|
|
23
|
+
explicitly declared `idempotent: false`. For a consumer publishing to social
|
|
24
|
+
networks that is a second public post that cannot be withdrawn.
|
|
25
|
+
|
|
26
|
+
This is the same defect as the 5xx / thrown-transport split fixed earlier, and
|
|
27
|
+
it contradicted this module's own stated rule -- *"an unknown failure treated
|
|
28
|
+
as unreachable would be retried, and the one thing worse than a lost send is
|
|
29
|
+
two sends nobody approved."* The two codes were standing exceptions to a rule
|
|
30
|
+
that should have covered them.
|
|
31
|
+
|
|
32
|
+
**What a consumer must DO:** nothing, if your connectors declare `idempotent`
|
|
33
|
+
honestly -- the fix only removes retries that were never safe. If you have a
|
|
34
|
+
connector marked `idempotent: true` that is not actually safe to repeat, this
|
|
35
|
+
release does not save you; that field is still load-bearing. If you were
|
|
36
|
+
relying on resets being retried for a genuinely idempotent connector, they
|
|
37
|
+
still are: `ambiguous` retries when `idempotent: true`.
|
|
38
|
+
|
|
39
|
+
`ECONNREFUSED`, `ENOTFOUND`, `EAI_AGAIN`, `EHOSTUNREACH` and `ENETUNREACH`
|
|
40
|
+
stay `unreachable` -- they do prove nothing was delivered -- and a test now
|
|
41
|
+
pins them there so the fix cannot overcorrect.
|
|
42
|
+
|
|
43
|
+
Found by a consumer's conformance fixture disagreeing with the core on its
|
|
44
|
+
first run, with a reproduction.
|
|
45
|
+
|
|
9
46
|
## [Unreleased]
|
|
10
47
|
|
|
48
|
+
## [0.2.0] — unreleased
|
|
49
|
+
|
|
50
|
+
The reference consumer migrated five adapters onto `0.1.0`, ran its acceptance
|
|
51
|
+
suite, and filed four findings. All four were cases where the type or the
|
|
52
|
+
classifier was *safe* and still *wrong* — the hardest kind to notice from inside,
|
|
53
|
+
because nothing fails.
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- **`classifyError` ignored the classification `httpFailure` attached.** It
|
|
58
|
+
re-derived from scratch, found no error code, and answered `ambiguous`
|
|
59
|
+
("nobody can tell") for a `400` the provider had been explicit about.
|
|
60
|
+
**This was never a retry bug** — `deliver()` reads `error.classified` first and
|
|
61
|
+
only falls back — but a host that calls the exported classifier directly, which
|
|
62
|
+
is a reasonable thing to do, was told nobody could tell. The **PHP twin already
|
|
63
|
+
did this**, so the two runtimes disagreed. **What a consumer must do: nothing,
|
|
64
|
+
unless you asserted the old answer.** If you pinned `classifyError` on an
|
|
65
|
+
`httpFailure` to `ambiguous`, that assertion now reads `rejected` or
|
|
66
|
+
`refused-explicitly` — which is the answer you wanted.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **BREAKING (types only): `ChainRef` is `object`, was
|
|
71
|
+
`Record<string, string | number>`.** An ordinary interface does not satisfy an
|
|
72
|
+
index signature unless it declares one, so `interface PostRef { uri; cid }` did
|
|
73
|
+
**not** satisfy the old constraint — and the only other fix was to add an index
|
|
74
|
+
signature to a type used across a codebase in order to please a generic used in
|
|
75
|
+
one place. The reference consumer worked around it with `postChain<T & ChainRef>`
|
|
76
|
+
and two `as unknown as` casts, and could not use `ChainOutcome<T>` in its own
|
|
77
|
+
signatures at all. **What a consumer must do: delete the casts.** A constraint
|
|
78
|
+
everyone casts past is not enforcing anything.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **`SandboxKind` gained `restricted-reach`** — the fifth shape, and the
|
|
83
|
+
dangerous one. A Meta app in Development Mode, an unaudited TikTok app that can
|
|
84
|
+
only post `SELF_ONLY`: same credentials, same endpoints, same estate, and only
|
|
85
|
+
the **audience** restricted. That is none of the previous four, and `none` —
|
|
86
|
+
which is what it was being forced into — is actively wrong. It is the shape
|
|
87
|
+
most likely to be mistaken for a sandbox, because it looks exactly like a
|
|
88
|
+
successful post that nobody can see: `ok: true`, a real id, no audience.
|
|
89
|
+
- **`SandboxKind` gained `unverified`** — *nobody has checked yet*. A real state,
|
|
90
|
+
and the type has to carry it: this is the field where being wrong sends someone
|
|
91
|
+
to a live estate believing it is a test one, so "researched but not verified"
|
|
92
|
+
must not be forced to pick one of the other five. A comment saying so is not a
|
|
93
|
+
type.
|
|
94
|
+
- **`sandboxIsSelectable()` / `sandboxRefusal()`** (`SandboxKind::isSelectable()`
|
|
95
|
+
/ `->refusal()` in PHP). Auto-resolution no longer picks "sandbox" for anything
|
|
96
|
+
that cannot be selected, and each refusal explains **its own** reason —
|
|
97
|
+
collapsing the three into "no sandbox available" would hide the one that
|
|
98
|
+
matters.
|
|
99
|
+
- **`providerProblems()`** — the sibling of `capabilityProblems()`, one level up.
|
|
100
|
+
Reports `implemented: true` with `sandbox: "unverified"`, a `verify` that does
|
|
101
|
+
not say what it **proves**, a `restricted-reach` provider whose summary never
|
|
102
|
+
mentions reach, and empty fields or setup on something claimed to be
|
|
103
|
+
implemented.
|
|
104
|
+
- **`ProviderAdapter.proves`** — what a verify does NOT cover, declared on the
|
|
105
|
+
adapter as well as on each result, so a surface can say it *before* anyone runs
|
|
106
|
+
the check.
|
|
107
|
+
- **`withResolvedLimit(rules, limit)`** — the supported way to resolve a
|
|
108
|
+
per-connection limit. A Mastodon instance publishes its own `max_toot_chars`;
|
|
109
|
+
baking a limit into a connector renders to 500 on an instance allowing 5000, or
|
|
110
|
+
to 5000 on one allowing 500, and both fail silently. `undefined` keeps the
|
|
111
|
+
declared limit, because *"I did not look"* is not *"there is no limit"* — and
|
|
112
|
+
`null` is a real answer meaning **uncounted**.
|
|
113
|
+
- **`CREDENTIAL_SCOPES`, `SANDBOX_KINDS`, `PROBLEM_SEVERITIES`,
|
|
114
|
+
`CANONICAL_METRICS`** — every string union that crosses a JSON boundary now
|
|
115
|
+
ships as **data** as well as a type. The compiler cannot follow a type across
|
|
116
|
+
JSON: when `scope` was renamed from `app`/`brand`, a consumer re-declaring the
|
|
117
|
+
field shape on its client kept compiling and its `scope === "brand"` silently
|
|
118
|
+
became never-true, so every credential field would have rendered as shared.
|
|
119
|
+
(PHP had this for free — an enum is runtime data, and `tryFrom` fails loudly on
|
|
120
|
+
a value nobody serves any more.)
|
|
121
|
+
|
|
122
|
+
|
|
11
123
|
## [0.1.0] — unreleased
|
|
12
124
|
|
|
13
125
|
First cut. Extracted and generalised from the `_connector` shared runtime in the
|
|
@@ -99,5 +211,6 @@ clock; this package is the runtime it is written on.
|
|
|
99
211
|
satisfies it with no dependency in either direction and a host that has never
|
|
100
212
|
heard of a workflow engine can implement it.
|
|
101
213
|
|
|
102
|
-
[Unreleased]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.
|
|
214
|
+
[Unreleased]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.2.0...HEAD
|
|
215
|
+
[0.2.0]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.1.0...v0.2.0
|
|
103
216
|
[0.1.0]: https://github.com/Particle-Academy/fancy-connector-core/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -165,6 +165,31 @@ wrong.
|
|
|
165
165
|
|
|
166
166
|
---
|
|
167
167
|
|
|
168
|
+
## Depend on this with an open range, not a caret
|
|
169
|
+
|
|
170
|
+
```jsonc
|
|
171
|
+
// package.json
|
|
172
|
+
"@particle-academy/fancy-connector-core": ">=0.2.0 <2.0.0"
|
|
173
|
+
```
|
|
174
|
+
```jsonc
|
|
175
|
+
// composer.json
|
|
176
|
+
"particle-academy/fancy-connector-core": ">=0.2.0 <2.0.0"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Not `^0.2.0`.** A caret on a `0.x` locks the MINOR — npm and Composer both read
|
|
180
|
+
`^0.2.0` as `>=0.2.0 <0.3.0` — so the next release is out of range and
|
|
181
|
+
**`npm update` reports success having installed nothing.** A resolver quietly
|
|
182
|
+
keeping the old version is indistinguishable from a resolver that had nothing to
|
|
183
|
+
do, which is why this is worth stating rather than leaving to convention.
|
|
184
|
+
|
|
185
|
+
This package intends the open range: **pre-1.0 minors may change what a connector
|
|
186
|
+
sees, and `CONNECTOR_API_VERSION` is what tells you when** — loudly, at
|
|
187
|
+
registration, naming which side is behind. That is a stronger guarantee than a
|
|
188
|
+
caret gives you, and it is the reason the caret is not needed here.
|
|
189
|
+
|
|
190
|
+
The first consumer of this package pinned `^0.1.0` and would have missed 0.2.0
|
|
191
|
+
entirely. That is the whole argument.
|
|
192
|
+
|
|
168
193
|
## Documentation
|
|
169
194
|
|
|
170
195
|
- [`AGENTS.md`](./AGENTS.md) — the invariants, the traps, and what a change here
|
package/dist/index.cjs
CHANGED
|
@@ -21,8 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
AMBIGUOUS_REFUSAL: () => AMBIGUOUS_REFUSAL,
|
|
24
|
+
CANONICAL_METRICS: () => CANONICAL_METRICS,
|
|
24
25
|
CONNECTOR_API_VERSION: () => CONNECTOR_API_VERSION,
|
|
25
26
|
CONNECTOR_MODES: () => CONNECTOR_MODES,
|
|
27
|
+
CREDENTIAL_SCOPES: () => CREDENTIAL_SCOPES,
|
|
26
28
|
ConnectorAmbiguous: () => ConnectorAmbiguous,
|
|
27
29
|
ConnectorApiMismatch: () => ConnectorApiMismatch,
|
|
28
30
|
ConnectorAuthError: () => ConnectorAuthError,
|
|
@@ -39,8 +41,10 @@ __export(src_exports, {
|
|
|
39
41
|
FAKE_EPOCH: () => FAKE_EPOCH,
|
|
40
42
|
MAX_IDEMPOTENCY_KEY_LENGTH: () => MAX_IDEMPOTENCY_KEY_LENGTH,
|
|
41
43
|
NO_IDEMPOTENCY_KEY_WARNING: () => NO_IDEMPOTENCY_KEY_WARNING,
|
|
44
|
+
PROBLEM_SEVERITIES: () => PROBLEM_SEVERITIES,
|
|
42
45
|
RENDERER_VERSION: () => RENDERER_VERSION,
|
|
43
46
|
REVIEW_STALE_DAYS: () => REVIEW_STALE_DAYS,
|
|
47
|
+
SANDBOX_KINDS: () => SANDBOX_KINDS,
|
|
44
48
|
SUPPORTED_CONNECTOR_API: () => SUPPORTED_CONNECTOR_API,
|
|
45
49
|
assertConnectorApi: () => assertConnectorApi,
|
|
46
50
|
byteRangeOf: () => byteRangeOf,
|
|
@@ -72,6 +76,8 @@ __export(src_exports, {
|
|
|
72
76
|
payloadHash: () => payloadHash,
|
|
73
77
|
postChain: () => postChain,
|
|
74
78
|
probe: () => probe,
|
|
79
|
+
providerProblems: () => providerProblems,
|
|
80
|
+
providerSandboxIsSelectable: () => providerSandboxIsSelectable,
|
|
75
81
|
registerConnectionHost: () => registerConnectionHost,
|
|
76
82
|
registerTransport: () => registerTransport,
|
|
77
83
|
render: () => render,
|
|
@@ -83,6 +89,8 @@ __export(src_exports, {
|
|
|
83
89
|
runIdentityFrom: () => runIdentityFrom,
|
|
84
90
|
runKeyFrom: () => runKeyFrom,
|
|
85
91
|
runProbes: () => runProbes,
|
|
92
|
+
sandboxIsSelectable: () => sandboxIsSelectable,
|
|
93
|
+
sandboxRefusal: () => sandboxRefusal,
|
|
86
94
|
satisfiesMinimum: () => satisfiesMinimum,
|
|
87
95
|
seedFrom: () => seedFrom,
|
|
88
96
|
shapeOf: () => shapeOf,
|
|
@@ -93,7 +101,8 @@ __export(src_exports, {
|
|
|
93
101
|
unchecked: () => unchecked,
|
|
94
102
|
utf8Length: () => utf8Length,
|
|
95
103
|
verifyDelivery: () => verifyDelivery,
|
|
96
|
-
verifyHmac: () => verifyHmac
|
|
104
|
+
verifyHmac: () => verifyHmac,
|
|
105
|
+
withResolvedLimit: () => withResolvedLimit
|
|
97
106
|
});
|
|
98
107
|
module.exports = __toCommonJS(src_exports);
|
|
99
108
|
|
|
@@ -149,16 +158,33 @@ function satisfiesMinimum(coreVersion, minimum) {
|
|
|
149
158
|
|
|
150
159
|
// src/delivery.ts
|
|
151
160
|
var UNREACHABLE_CODES = /* @__PURE__ */ new Set([
|
|
161
|
+
// Every code here must PROVE the request never reached an application:
|
|
162
|
+
// nothing listening, no route, or a name that never resolved. If a code can
|
|
163
|
+
// also occur after the peer received and acted on the request, it belongs in
|
|
164
|
+
// AMBIGUOUS_CODES -- `shouldRetry` retries `unreachable` without consulting
|
|
165
|
+
// `idempotent`, so a wrong entry here is a duplicate send, not a slow one.
|
|
152
166
|
"ECONNREFUSED",
|
|
153
167
|
"ENOTFOUND",
|
|
154
168
|
"EAI_AGAIN",
|
|
155
169
|
"EHOSTUNREACH",
|
|
156
170
|
"ENETUNREACH",
|
|
157
|
-
"ECONNRESET",
|
|
158
|
-
"EPIPE",
|
|
159
171
|
"ERR_SOCKET_CONNECTION_TIMEOUT"
|
|
160
172
|
]);
|
|
161
173
|
var AMBIGUOUS_CODES = /* @__PURE__ */ new Set([
|
|
174
|
+
// A peer sends RST whenever it tears the connection down -- INCLUDING after
|
|
175
|
+
// it has received and acted on the request but before the response comes
|
|
176
|
+
// back. EPIPE is the same story from our side: we wrote to a socket the peer
|
|
177
|
+
// had already closed, and whether an earlier complete request was processed
|
|
178
|
+
// first is not knowable from here. Node surfaces the safe case and the unsafe
|
|
179
|
+
// case as the same code, so the code cannot carry the claim.
|
|
180
|
+
//
|
|
181
|
+
// These two sat in UNREACHABLE_CODES until 0.3.0, which made them
|
|
182
|
+
// unconditionally retryable and produced three sends of connectors that had
|
|
183
|
+
// declared `idempotent: false`. Same defect as the 5xx / thrown-transport
|
|
184
|
+
// split this module already fixed once, and a stated exception to a rule that
|
|
185
|
+
// should have covered them.
|
|
186
|
+
"ECONNRESET",
|
|
187
|
+
"EPIPE",
|
|
162
188
|
"ETIMEDOUT",
|
|
163
189
|
"UND_ERR_HEADERS_TIMEOUT",
|
|
164
190
|
"UND_ERR_BODY_TIMEOUT",
|
|
@@ -166,6 +192,8 @@ var AMBIGUOUS_CODES = /* @__PURE__ */ new Set([
|
|
|
166
192
|
]);
|
|
167
193
|
var AMBIGUOUS_NAMES = /* @__PURE__ */ new Set(["AbortError", "TimeoutError"]);
|
|
168
194
|
function classifyError(error) {
|
|
195
|
+
const attached = error?.classified;
|
|
196
|
+
if (attached && typeof attached === "object" && typeof attached.kind === "string") return attached;
|
|
169
197
|
const detail = error instanceof Error ? error.message : String(error);
|
|
170
198
|
const code = error?.code ?? error?.cause?.code ?? "";
|
|
171
199
|
const name = error instanceof Error ? error.name : "";
|
|
@@ -403,19 +431,42 @@ function fakeRequest(service, operation, config, input) {
|
|
|
403
431
|
|
|
404
432
|
// src/mode.ts
|
|
405
433
|
var CONNECTOR_MODES = ["fake", "sandbox", "live"];
|
|
434
|
+
var SANDBOX_KINDS = [
|
|
435
|
+
"credential",
|
|
436
|
+
"base-url",
|
|
437
|
+
"separate-account",
|
|
438
|
+
"restricted-reach",
|
|
439
|
+
"none",
|
|
440
|
+
"unverified"
|
|
441
|
+
];
|
|
442
|
+
var SELECTABLE_SANDBOX = /* @__PURE__ */ new Set([
|
|
443
|
+
"credential",
|
|
444
|
+
"base-url",
|
|
445
|
+
"separate-account"
|
|
446
|
+
]);
|
|
447
|
+
function sandboxIsSelectable(kind) {
|
|
448
|
+
return SELECTABLE_SANDBOX.has(kind);
|
|
449
|
+
}
|
|
450
|
+
function sandboxRefusal(service, kind) {
|
|
451
|
+
if (kind === "restricted-reach") {
|
|
452
|
+
return service + ` has no separate test estate. What it has instead is an ACCOUNT-LEVEL reach restriction - an unreviewed app that posts only to its own developers, or only privately. Same credentials, same endpoints, same estate: only the audience changes, and nothing in a request selects it. That makes it the sandbox shape most likely to be mistaken for a real one, because a restricted call looks exactly like a successful one - ok, a real id, and no audience. Use "fake" to develop without touching the provider, or "live" and confirm reach on the provider's own surface, which is the only place it is visible.`;
|
|
453
|
+
}
|
|
454
|
+
if (kind === "unverified") {
|
|
455
|
+
return "Nobody has verified what test estate " + service + ' offers, so "sandbox" cannot be honoured - the resolver would be guessing, and the cost of guessing wrong here is a workflow pointed at a live estate while somebody believes it is a test one. Find out and declare it, or use "fake".';
|
|
456
|
+
}
|
|
457
|
+
return service + ' has no sandbox estate, so "sandbox" cannot be honoured. Use "fake" to develop without credentials, or "live" to talk to the provider.';
|
|
458
|
+
}
|
|
406
459
|
function resolveConnectorMode(inputs) {
|
|
407
460
|
const { requested, connectionMode, sandbox, hasSandboxCredentials, environment } = inputs;
|
|
408
461
|
if (requested && requested !== "auto") {
|
|
409
|
-
if (requested === "sandbox" && sandbox
|
|
410
|
-
throw new ConnectorModeError(
|
|
411
|
-
`This connector's provider has no sandbox estate, so "sandbox" cannot be honoured. Use "fake" to develop without credentials, or "live" to talk to the provider.`
|
|
412
|
-
);
|
|
462
|
+
if (requested === "sandbox" && !sandboxIsSelectable(sandbox)) {
|
|
463
|
+
throw new ConnectorModeError(sandboxRefusal("This connector's provider", sandbox));
|
|
413
464
|
}
|
|
414
465
|
return requested;
|
|
415
466
|
}
|
|
416
467
|
if (connectionMode) return connectionMode;
|
|
417
468
|
if (environment.production) return "live";
|
|
418
|
-
return sandbox
|
|
469
|
+
return sandboxIsSelectable(sandbox) && hasSandboxCredentials ? "sandbox" : "fake";
|
|
419
470
|
}
|
|
420
471
|
var ConnectorModeError = class extends Error {
|
|
421
472
|
constructor(message) {
|
|
@@ -446,10 +497,8 @@ function connectionsFor(service) {
|
|
|
446
497
|
function resolveConnection(options) {
|
|
447
498
|
const { service, operation, sandbox } = options;
|
|
448
499
|
const requestedMode = options.requested ?? "auto";
|
|
449
|
-
if (requestedMode === "sandbox" && sandbox
|
|
450
|
-
throw new ConnectorModeError(
|
|
451
|
-
`${service} has no sandbox estate, so "sandbox" cannot be honoured. Use "fake" to develop without credentials, or "live" to talk to the provider.`
|
|
452
|
-
);
|
|
500
|
+
if (requestedMode === "sandbox" && !sandboxIsSelectable(sandbox)) {
|
|
501
|
+
throw new ConnectorModeError(sandboxRefusal(service, sandbox));
|
|
453
502
|
}
|
|
454
503
|
const id = options.connectionId?.trim() || service;
|
|
455
504
|
if (options.credentials) {
|
|
@@ -976,6 +1025,41 @@ function reported(values) {
|
|
|
976
1025
|
}
|
|
977
1026
|
return out;
|
|
978
1027
|
}
|
|
1028
|
+
function providerProblems(provider) {
|
|
1029
|
+
const problems = [];
|
|
1030
|
+
if (provider.implemented && provider.sandbox === "unverified") {
|
|
1031
|
+
problems.push(
|
|
1032
|
+
`${provider.id} says it is implemented while its sandbox shape is still "unverified". Something a person can run today must not be ambiguous about which estate it reaches \u2014 verify it, or set implemented: false.`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
if (provider.implemented && provider.fields.length === 0) {
|
|
1036
|
+
problems.push(`${provider.id} says it is implemented but declares no credential fields.`);
|
|
1037
|
+
}
|
|
1038
|
+
if (provider.implemented && provider.setup.length === 0) {
|
|
1039
|
+
problems.push(
|
|
1040
|
+
`${provider.id} says it is implemented and names no setup steps. The code is never the expensive part; the setup is, and a provider whose setup is undocumented is one nobody can stand up.`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
for (const field of provider.fields) {
|
|
1044
|
+
if (field.help.trim().length < 10) {
|
|
1045
|
+
problems.push(`${provider.id}.${field.key} has no help text worth reading.`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (provider.verify && !provider.proves) {
|
|
1049
|
+
problems.push(
|
|
1050
|
+
`${provider.id} ships a verify but does not say what it PROVES. A green tick that means more than it should is worse than no tick \u2014 name the step it does not cover.`
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
if (provider.sandbox === "restricted-reach" && !provider.summary.toLowerCase().includes("reach")) {
|
|
1054
|
+
problems.push(
|
|
1055
|
+
`${provider.id} is declared restricted-reach and its summary never mentions reach. That shape looks exactly like a successful post nobody can see, so the surface has to say so before anyone runs it.`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
return problems;
|
|
1059
|
+
}
|
|
1060
|
+
function providerSandboxIsSelectable(provider) {
|
|
1061
|
+
return sandboxIsSelectable(provider.sandbox);
|
|
1062
|
+
}
|
|
979
1063
|
|
|
980
1064
|
// src/probe.ts
|
|
981
1065
|
async function probe(spec) {
|
|
@@ -1177,6 +1261,14 @@ async function payloadHash(payload) {
|
|
|
1177
1261
|
function isEmptyPayload(payload) {
|
|
1178
1262
|
return payload.segments.every((segment) => segment.text.trim() === "");
|
|
1179
1263
|
}
|
|
1264
|
+
function withResolvedLimit(rules, limit) {
|
|
1265
|
+
return limit === void 0 ? rules : { ...rules, limit };
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/seam.ts
|
|
1269
|
+
var CREDENTIAL_SCOPES = ["provider", "account"];
|
|
1270
|
+
var PROBLEM_SEVERITIES = ["block", "warn"];
|
|
1271
|
+
var CANONICAL_METRICS = ["like", "share", "reply", "quote", "view"];
|
|
1180
1272
|
|
|
1181
1273
|
// src/webhook.ts
|
|
1182
1274
|
async function verifyHmac(options) {
|
|
@@ -1279,8 +1371,10 @@ function hasDelivery(value) {
|
|
|
1279
1371
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1280
1372
|
0 && (module.exports = {
|
|
1281
1373
|
AMBIGUOUS_REFUSAL,
|
|
1374
|
+
CANONICAL_METRICS,
|
|
1282
1375
|
CONNECTOR_API_VERSION,
|
|
1283
1376
|
CONNECTOR_MODES,
|
|
1377
|
+
CREDENTIAL_SCOPES,
|
|
1284
1378
|
ConnectorAmbiguous,
|
|
1285
1379
|
ConnectorApiMismatch,
|
|
1286
1380
|
ConnectorAuthError,
|
|
@@ -1297,8 +1391,10 @@ function hasDelivery(value) {
|
|
|
1297
1391
|
FAKE_EPOCH,
|
|
1298
1392
|
MAX_IDEMPOTENCY_KEY_LENGTH,
|
|
1299
1393
|
NO_IDEMPOTENCY_KEY_WARNING,
|
|
1394
|
+
PROBLEM_SEVERITIES,
|
|
1300
1395
|
RENDERER_VERSION,
|
|
1301
1396
|
REVIEW_STALE_DAYS,
|
|
1397
|
+
SANDBOX_KINDS,
|
|
1302
1398
|
SUPPORTED_CONNECTOR_API,
|
|
1303
1399
|
assertConnectorApi,
|
|
1304
1400
|
byteRangeOf,
|
|
@@ -1330,6 +1426,8 @@ function hasDelivery(value) {
|
|
|
1330
1426
|
payloadHash,
|
|
1331
1427
|
postChain,
|
|
1332
1428
|
probe,
|
|
1429
|
+
providerProblems,
|
|
1430
|
+
providerSandboxIsSelectable,
|
|
1333
1431
|
registerConnectionHost,
|
|
1334
1432
|
registerTransport,
|
|
1335
1433
|
render,
|
|
@@ -1341,6 +1439,8 @@ function hasDelivery(value) {
|
|
|
1341
1439
|
runIdentityFrom,
|
|
1342
1440
|
runKeyFrom,
|
|
1343
1441
|
runProbes,
|
|
1442
|
+
sandboxIsSelectable,
|
|
1443
|
+
sandboxRefusal,
|
|
1344
1444
|
satisfiesMinimum,
|
|
1345
1445
|
seedFrom,
|
|
1346
1446
|
shapeOf,
|
|
@@ -1351,6 +1451,7 @@ function hasDelivery(value) {
|
|
|
1351
1451
|
unchecked,
|
|
1352
1452
|
utf8Length,
|
|
1353
1453
|
verifyDelivery,
|
|
1354
|
-
verifyHmac
|
|
1454
|
+
verifyHmac,
|
|
1455
|
+
withResolvedLimit
|
|
1355
1456
|
});
|
|
1356
1457
|
//# sourceMappingURL=index.cjs.map
|