@apifuse/provider-sdk 2.2.0-beta.63 → 2.2.0-beta.64
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/AUTHORING.md +99 -0
- package/CHANGELOG.md +4 -0
- package/LICENSE +21 -0
- package/bin/apifuse-submit-check.ts +9 -5
- package/dist/handle-meta.d.ts +36 -1
- package/dist/handle-meta.js +16 -0
- package/dist/handle.d.ts +23 -5
- package/dist/handle.js +54 -8
- package/dist/health-scenario.d.ts +38 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/lint.d.ts +5 -0
- package/dist/lint.js +57 -33
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/handle.js +9 -1
- package/dist/runtime/ocr.js +5 -0
- package/dist/runtime-boundary-lint.d.ts +132 -0
- package/dist/runtime-boundary-lint.js +586 -0
- package/dist/types.d.ts +29 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +1 -1
- package/src/handle-meta.ts +51 -1
- package/src/handle.ts +92 -10
- package/src/health-scenario.ts +38 -0
- package/src/index.ts +14 -0
- package/src/lint.ts +82 -41
- package/src/provider.ts +2 -0
- package/src/runtime/handle.ts +9 -1
- package/src/runtime/ocr.ts +5 -0
- package/src/runtime-boundary-lint.ts +703 -0
- package/src/types.ts +29 -0
- package/src/typescript-module.ts +100 -0
package/dist/lint.js
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
1
|
import { SDK_CANONICAL_ERROR_CODE_RETRYABILITY, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "./error-resolution.js";
|
|
3
|
-
import { APIFUSE_HANDLE_META_KEY, handleFieldDescription,
|
|
2
|
+
import { APIFUSE_HANDLE_META_KEY, handleFieldDescription, handleFieldNameFor, handleHasDirectionalFieldNames, handleIssuerList, isHandleFieldMeta, isHandleIssuedBy, } from "./handle-meta.js";
|
|
4
3
|
import { PROVIDER_ERROR_CATALOG_NAMESPACE } from "./i18n/error-messages.js";
|
|
5
4
|
import { getProviderLocaleSegments, isProviderLocaleKey, } from "./i18n/keys.js";
|
|
6
5
|
import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
|
|
7
6
|
import { isStealthOwnedHeaderName, SDK_OWNED_CHROME_HEADER_PREFIX, } from "./runtime/stealth-owned-headers.js";
|
|
7
|
+
import { lintRuntimeBoundary, scriptKindForSourceFile } from "./runtime-boundary-lint.js";
|
|
8
8
|
import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
|
|
9
|
-
|
|
10
|
-
// `typeof import(...)` keeps the type without emitting a static import: the
|
|
11
|
-
// typescript package is a CLI-only dependency and src/lint.ts is production
|
|
12
|
-
// runtime, which the typescript-import-boundary test enforces.
|
|
13
|
-
let typeScriptModule;
|
|
14
|
-
function getTypeScript() {
|
|
15
|
-
typeScriptModule ??= requireModule("typescript");
|
|
16
|
-
return typeScriptModule;
|
|
17
|
-
}
|
|
9
|
+
import { getTypeScript } from "./typescript-module.js";
|
|
18
10
|
const CREDENTIAL_BEARING_AUTH_MODES = [
|
|
19
11
|
"credentials",
|
|
20
12
|
"oauth2",
|
|
@@ -860,9 +852,9 @@ function isReportedOwnedHeaderName(value) {
|
|
|
860
852
|
}
|
|
861
853
|
return name.startsWith(OWNED_CLIENT_HINT_HEADER_PREFIX);
|
|
862
854
|
}
|
|
863
|
-
function collectBrowserVersionLiteralFindings(source) {
|
|
855
|
+
function collectBrowserVersionLiteralFindings(fileName, source) {
|
|
864
856
|
const ts = getTypeScript();
|
|
865
|
-
const sourceFile = ts.createSourceFile(
|
|
857
|
+
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKindForSourceFile(ts, fileName));
|
|
866
858
|
let findings = [];
|
|
867
859
|
const seen = new Set();
|
|
868
860
|
// Owned-header entries only count once the file is known to reach
|
|
@@ -980,19 +972,27 @@ function lintBrowserVersionLiterals(provider) {
|
|
|
980
972
|
!RECORDED_FIXTURE_SOURCE_FILE_PATTERN.test(filePath));
|
|
981
973
|
if (sourceFiles.length > 0) {
|
|
982
974
|
for (const [filePath, source] of sourceFiles) {
|
|
983
|
-
sources.push({ field: `sourceFiles.${filePath}`, source });
|
|
975
|
+
sources.push({ field: `sourceFiles.${filePath}`, fileName: filePath, source });
|
|
984
976
|
}
|
|
985
977
|
}
|
|
986
978
|
else {
|
|
987
979
|
if (provider.authFlowSource)
|
|
988
|
-
sources.push({
|
|
980
|
+
sources.push({
|
|
981
|
+
field: "auth.flow",
|
|
982
|
+
fileName: "auth-flow.ts",
|
|
983
|
+
source: provider.authFlowSource,
|
|
984
|
+
});
|
|
989
985
|
for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
|
|
990
986
|
const source = getOperationSource(operation);
|
|
991
987
|
if (source)
|
|
992
|
-
sources.push({
|
|
988
|
+
sources.push({
|
|
989
|
+
field: `operations.${operationKey}.handler`,
|
|
990
|
+
fileName: `${operationKey}.ts`,
|
|
991
|
+
source,
|
|
992
|
+
});
|
|
993
993
|
}
|
|
994
994
|
}
|
|
995
|
-
return sources.flatMap(({ field, source }) => collectBrowserVersionLiteralFindings(source).map((finding) => ({
|
|
995
|
+
return sources.flatMap(({ field, fileName, source }) => collectBrowserVersionLiteralFindings(fileName, source).map((finding) => ({
|
|
996
996
|
rule: "browser-version-literal",
|
|
997
997
|
level: "error",
|
|
998
998
|
field,
|
|
@@ -1565,6 +1565,7 @@ function readHandleKindDeclarations(value) {
|
|
|
1565
1565
|
return (typeof record.name === "string" &&
|
|
1566
1566
|
(record.type === "cursor" || record.type === "draft") &&
|
|
1567
1567
|
typeof record.fieldName === "string" &&
|
|
1568
|
+
(record.outputFieldName === undefined || typeof record.outputFieldName === "string") &&
|
|
1568
1569
|
(record.issuedBy === undefined || isHandleIssuedBy(record.issuedBy)));
|
|
1569
1570
|
});
|
|
1570
1571
|
}
|
|
@@ -1600,7 +1601,13 @@ function lintHandleDeclarations(provider) {
|
|
|
1600
1601
|
for (const { side, fields } of positions) {
|
|
1601
1602
|
for (const occurrence of fields) {
|
|
1602
1603
|
const field = `operations.${operationKey}.${occurrence.path}`;
|
|
1603
|
-
const { kind
|
|
1604
|
+
const { kind } = occurrence.meta;
|
|
1605
|
+
// A kind may name one handle differently in each direction (the canon's
|
|
1606
|
+
// `cursor` in, `next_cursor` out). The rule is still exact: each side
|
|
1607
|
+
// must use the key declared for THAT side, so the handle's identity
|
|
1608
|
+
// stays readable from the property key and the round trip has exactly
|
|
1609
|
+
// one edge — read `output`, send back under `input`.
|
|
1610
|
+
const expected = handleFieldNameFor(occurrence.meta, side);
|
|
1604
1611
|
if (!kindByName.has(kind)) {
|
|
1605
1612
|
diagnostics.push({
|
|
1606
1613
|
rule: "handle-kind-undeclared",
|
|
@@ -1609,12 +1616,16 @@ function lintHandleDeclarations(provider) {
|
|
|
1609
1616
|
message: `${providerLabel} operation "${operationKey}" ${side} field "${occurrence.path}" is a handle of kind "${kind}", which is not declared in provider.handle. Add the kind to handle: [...] or remove the field.`,
|
|
1610
1617
|
});
|
|
1611
1618
|
}
|
|
1612
|
-
if (occurrence.key !==
|
|
1619
|
+
if (occurrence.key !== expected) {
|
|
1620
|
+
const directional = handleHasDirectionalFieldNames(occurrence.meta);
|
|
1621
|
+
const advice = directional
|
|
1622
|
+
? `Use the kind's ${side} fieldName on every ${side} schema; its ${side === "input" ? "output" : "input"} fieldName is "${handleFieldNameFor(occurrence.meta, side === "input" ? "output" : "input")}".`
|
|
1623
|
+
: "Use the same key in every schema so the LLM sees one name.";
|
|
1613
1624
|
diagnostics.push({
|
|
1614
1625
|
rule: "handle-field-name",
|
|
1615
1626
|
level: "error",
|
|
1616
1627
|
field,
|
|
1617
|
-
message: `${providerLabel} operation "${operationKey}" ${side} declares handle kind "${kind}" under property "${occurrence.key}" but the kind's fieldName is "${
|
|
1628
|
+
message: `${providerLabel} operation "${operationKey}" ${side} declares handle kind "${kind}" under property "${occurrence.key}" but the kind's ${directional ? `${side} ` : ""}fieldName is "${expected}". ${advice}`,
|
|
1618
1629
|
});
|
|
1619
1630
|
}
|
|
1620
1631
|
if (side === "input") {
|
|
@@ -1641,7 +1652,7 @@ function lintHandleDeclarations(provider) {
|
|
|
1641
1652
|
rule: "handle-field-parity",
|
|
1642
1653
|
level: "error",
|
|
1643
1654
|
field,
|
|
1644
|
-
message: `${providerLabel} handle kind "${kind.name}" is never issued: no operation output contains its field "${kind
|
|
1655
|
+
message: `${providerLabel} handle kind "${kind.name}" is never issued: no operation output contains its field "${handleFieldNameFor(kind, "output")}". Add ${kind.name}.field() to the issuing operation's output.`,
|
|
1645
1656
|
});
|
|
1646
1657
|
}
|
|
1647
1658
|
if (!accepted) {
|
|
@@ -1649,7 +1660,7 @@ function lintHandleDeclarations(provider) {
|
|
|
1649
1660
|
rule: "handle-field-parity",
|
|
1650
1661
|
level: "error",
|
|
1651
1662
|
field,
|
|
1652
|
-
message: `${providerLabel} handle kind "${kind.name}" is never accepted: no operation input contains its field "${kind
|
|
1663
|
+
message: `${providerLabel} handle kind "${kind.name}" is never accepted: no operation input contains its field "${handleFieldNameFor(kind, "input")}". Add ${kind.name}.field() to the consuming operation's input.`,
|
|
1653
1664
|
});
|
|
1654
1665
|
}
|
|
1655
1666
|
if (kind.issuedBy === undefined)
|
|
@@ -1676,7 +1687,7 @@ function lintHandleDeclarations(provider) {
|
|
|
1676
1687
|
rule: "handle-issued-by",
|
|
1677
1688
|
level: "error",
|
|
1678
1689
|
field,
|
|
1679
|
-
message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${known[0]}", but that operation's output has no "${kind
|
|
1690
|
+
message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${known[0]}", but that operation's output has no "${handleFieldNameFor(kind, "output")}" handle field.`,
|
|
1680
1691
|
});
|
|
1681
1692
|
}
|
|
1682
1693
|
else {
|
|
@@ -1686,7 +1697,7 @@ function lintHandleDeclarations(provider) {
|
|
|
1686
1697
|
field,
|
|
1687
1698
|
message: `${providerLabel} handle kind "${kind.name}" declares issuedBy ${known
|
|
1688
1699
|
.map((operationKey) => `"${operationKey}"`)
|
|
1689
|
-
.join(", ")}, but none of those operations' outputs has a "${kind
|
|
1700
|
+
.join(", ")}, but none of those operations' outputs has a "${handleFieldNameFor(kind, "output")}" handle field. At least one listed issuer must output it.`,
|
|
1690
1701
|
});
|
|
1691
1702
|
}
|
|
1692
1703
|
}
|
|
@@ -1711,7 +1722,7 @@ const LEGACY_CHOICE_IDENTIFIERS = new Set([
|
|
|
1711
1722
|
* the provider) while code inside `${}` interpolations still does. Falls back
|
|
1712
1723
|
* to a raw-source regex when TypeScript is not installed.
|
|
1713
1724
|
*/
|
|
1714
|
-
function findLegacyChoiceUsage(source) {
|
|
1725
|
+
function findLegacyChoiceUsage(fileName, source) {
|
|
1715
1726
|
let ts;
|
|
1716
1727
|
try {
|
|
1717
1728
|
ts = getTypeScript();
|
|
@@ -1719,7 +1730,7 @@ function findLegacyChoiceUsage(source) {
|
|
|
1719
1730
|
catch {
|
|
1720
1731
|
return LEGACY_CHOICE_USAGE_PATTERN.exec(source)?.[0];
|
|
1721
1732
|
}
|
|
1722
|
-
const file = ts.createSourceFile(
|
|
1733
|
+
const file = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, false, scriptKindForSourceFile(ts, fileName));
|
|
1723
1734
|
let found;
|
|
1724
1735
|
const visit = (node) => {
|
|
1725
1736
|
if (found)
|
|
@@ -1743,19 +1754,23 @@ function findLegacyChoiceUsage(source) {
|
|
|
1743
1754
|
function lintLegacyChoiceUsage(provider) {
|
|
1744
1755
|
const sources = [];
|
|
1745
1756
|
if (provider.authFlowSource) {
|
|
1746
|
-
sources.push({ field: "auth.flow", source: provider.authFlowSource });
|
|
1757
|
+
sources.push({ field: "auth.flow", fileName: "auth-flow.ts", source: provider.authFlowSource });
|
|
1747
1758
|
}
|
|
1748
1759
|
for (const [filePath, source] of Object.entries(provider.providerSourceFiles ?? {})) {
|
|
1749
|
-
sources.push({ field: `sourceFiles.${filePath}`, source });
|
|
1760
|
+
sources.push({ field: `sourceFiles.${filePath}`, fileName: filePath, source });
|
|
1750
1761
|
}
|
|
1751
1762
|
for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
|
|
1752
1763
|
const source = getOperationSource(operation);
|
|
1753
1764
|
if (source) {
|
|
1754
|
-
sources.push({
|
|
1765
|
+
sources.push({
|
|
1766
|
+
field: `operations.${operationKey}.handler`,
|
|
1767
|
+
fileName: `${operationKey}.ts`,
|
|
1768
|
+
source,
|
|
1769
|
+
});
|
|
1755
1770
|
}
|
|
1756
1771
|
}
|
|
1757
|
-
return sources.flatMap(({ field, source }) => {
|
|
1758
|
-
const match = findLegacyChoiceUsage(source);
|
|
1772
|
+
return sources.flatMap(({ field, fileName, source }) => {
|
|
1773
|
+
const match = findLegacyChoiceUsage(fileName, source);
|
|
1759
1774
|
if (!match)
|
|
1760
1775
|
return [];
|
|
1761
1776
|
return [
|
|
@@ -1872,6 +1887,10 @@ export function lintProviderWithInformation(provider, options = {}) {
|
|
|
1872
1887
|
...lintLegacyChoiceUsage(provider),
|
|
1873
1888
|
...lintHandleDeclarations(provider),
|
|
1874
1889
|
];
|
|
1890
|
+
// Runtime boundary rules (process.env, node:fs/net/child_process, raw
|
|
1891
|
+
// fetch) also yield information entries for `@apifuse-allow` acknowledgements.
|
|
1892
|
+
const runtimeBoundary = lintRuntimeBoundary(getTypeScript(), provider, getOperationSource);
|
|
1893
|
+
diagnostics.push(...runtimeBoundary.diagnostics);
|
|
1875
1894
|
if (provider.operations) {
|
|
1876
1895
|
const authMode = provider.auth?.mode;
|
|
1877
1896
|
for (const [operationKey, operation] of Object.entries(provider.operations)) {
|
|
@@ -1911,7 +1930,7 @@ export function lintProviderWithInformation(provider, options = {}) {
|
|
|
1911
1930
|
}
|
|
1912
1931
|
}
|
|
1913
1932
|
if (!provider.operations) {
|
|
1914
|
-
return applyPinnedWireFieldPaths(provider, diagnostics);
|
|
1933
|
+
return withInformation(applyPinnedWireFieldPaths(provider, diagnostics), runtimeBoundary.information);
|
|
1915
1934
|
}
|
|
1916
1935
|
diagnostics.push(...Object.entries(provider.operations).flatMap(([operationKey, operation]) => [
|
|
1917
1936
|
...lintOperation({
|
|
@@ -1930,5 +1949,10 @@ export function lintProviderWithInformation(provider, options = {}) {
|
|
|
1930
1949
|
: `operations.${operationKey}`,
|
|
1931
1950
|
message: `[${operationKey}] ${diagnostic.message}`,
|
|
1932
1951
|
}))));
|
|
1933
|
-
return applyPinnedWireFieldPaths(provider, diagnostics);
|
|
1952
|
+
return withInformation(applyPinnedWireFieldPaths(provider, diagnostics), runtimeBoundary.information);
|
|
1953
|
+
}
|
|
1954
|
+
function withInformation(result, information) {
|
|
1955
|
+
if (information.length === 0)
|
|
1956
|
+
return result;
|
|
1957
|
+
return { diagnostics: result.diagnostics, information: [...information, ...result.information] };
|
|
1934
1958
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_
|
|
|
11
11
|
export type { ProviderCapabilityKey, ProviderEngine, ProviderEngineAttachmentInput, ProviderEngineBindingCandidates, ProviderEngineCapabilitySurface, ProviderEngineMode, ProviderEngineRequest, ProviderEngineResidentSurface, ProviderEngineSession, ProviderEngineTransport, } from "./engine.js";
|
|
12
12
|
export { getProviderLocalePath, type ProviderErrorMessageParams, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
|
|
13
13
|
export * from "./handle.js";
|
|
14
|
-
export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
|
|
14
|
+
export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, handleFieldNameFor, handleHasDirectionalFieldNames, isHandleFieldMeta, } from "./handle-meta.js";
|
|
15
15
|
export { type CreateHandleContextOptions, type CreateTestHandleContextOptions, createHandleContext, createTestHandleContext, type HandleNormalizationClass, type NormalizedHandle, normalizeHandle, } from "./runtime/handle.js";
|
|
16
16
|
export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
|
|
17
17
|
export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
|
package/dist/provider.js
CHANGED
|
@@ -6,7 +6,7 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
|
|
|
6
6
|
export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RUNTIME_ENV_NAMES, ENGINE_OWNED_TELEMETRY_ENV_NAMES, isEngineOwnedEnvName, isEngineOwnedProxyCredentialName, isEngineOwnedResolverCredentialName, isEngineOwnedRuntimeEnvName, isEngineOwnedTelemetryEnvName, PROVIDER_CAPABILITY_KEYS, PROVIDER_ENGINE_MODE_ENV, PROVIDER_ENGINE_PROTOCOL_VERSION, readEngineProxyCredentials, } from "./engine.js";
|
|
7
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
|
|
8
8
|
export * from "./handle.js";
|
|
9
|
-
export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
|
|
9
|
+
export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, handleFieldNameFor, handleHasDirectionalFieldNames, isHandleFieldMeta, } from "./handle-meta.js";
|
|
10
10
|
export { createHandleContext, createTestHandleContext, normalizeHandle, } from "./runtime/handle.js";
|
|
11
11
|
export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
|
|
12
12
|
export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
|
package/dist/runtime/handle.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { randomInt, randomUUID } from "node:crypto";
|
|
12
12
|
import { isProviderError } from "../errors.js";
|
|
13
13
|
import { HandleError, handleRecoverySentence, isDraftKind, } from "../handle.js";
|
|
14
|
-
import { formatHandleIssuers } from "../handle-meta.js";
|
|
14
|
+
import { formatHandleIssuers, handleHasDirectionalFieldNames } from "../handle-meta.js";
|
|
15
15
|
import { BOUND_HANDLE_WORD_COUNT, findHandleWordWithinDistance, HIGH_PUBLIC_HANDLE_WORD_COUNT, ISSUABLE_HANDLE_WORDS, PUBLIC_HANDLE_WORD_COUNT, } from "./handle-wordlist.js";
|
|
16
16
|
import { createMemoryProviderRuntimeState } from "./state.js";
|
|
17
17
|
const RECORD_VERSION = 1;
|
|
@@ -153,6 +153,14 @@ export function formatNormalizationClasses(classes) {
|
|
|
153
153
|
// Errors (spec §5)
|
|
154
154
|
// ---------------------------------------------------------------------------
|
|
155
155
|
function publicCollapsedMessage(kind) {
|
|
156
|
+
// "a new one" is unambiguous only while the kind has one name. When it is
|
|
157
|
+
// issued under `next_cursor` and accepted under `cursor`, the caller has to
|
|
158
|
+
// be told which field to read, so use the full recovery sentence — it names
|
|
159
|
+
// both keys and is identical for invalid / expired / not-found, which is
|
|
160
|
+
// what keeps the public collapse indistinguishable.
|
|
161
|
+
if (handleHasDirectionalFieldNames(kind)) {
|
|
162
|
+
return `\`${kind.fieldName}\` is not valid or has expired. ${handleRecoverySentence(kind)}`;
|
|
163
|
+
}
|
|
156
164
|
const issuers = formatHandleIssuers(kind.issuedBy);
|
|
157
165
|
const again = issuers ? `Call ${issuers} again to get a new one.` : "Request a new one.";
|
|
158
166
|
return `\`${kind.fieldName}\` is not valid or has expired. ${again}`;
|
package/dist/runtime/ocr.js
CHANGED
|
@@ -42,6 +42,10 @@ function createErrorOcrClient(options) {
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
return tagCapability({
|
|
45
|
+
// Own data property: `bindOcrTelemetry` re-spreads the context, so a
|
|
46
|
+
// getter or prototype member would silently vanish before reaching
|
|
47
|
+
// provider code.
|
|
48
|
+
available: false,
|
|
45
49
|
async recognize() {
|
|
46
50
|
return unavailable();
|
|
47
51
|
},
|
|
@@ -274,6 +278,7 @@ function toOcrTransportError(error) {
|
|
|
274
278
|
}
|
|
275
279
|
function createOcrClient(model, recognize) {
|
|
276
280
|
return {
|
|
281
|
+
available: true,
|
|
277
282
|
recognize,
|
|
278
283
|
async extractCaptchaText(image, options = {}) {
|
|
279
284
|
const result = await recognize({ image, hint: "captcha" });
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime boundary lint: provider runtime source must reach the outside world
|
|
3
|
+
* through the SDK context (`ctx.http`, `ctx.stealth`, `ctx.env`,
|
|
4
|
+
* `ctx.credential`, `ctx.browser`), not through ambient Node/Bun globals.
|
|
5
|
+
*
|
|
6
|
+
* Three rules, all reported at `warn` level in this first release so the
|
|
7
|
+
* fleet can migrate before the level is raised:
|
|
8
|
+
*
|
|
9
|
+
* - `process-env-direct-read`: `process.env.X`, `process.env["X"]`, a bare
|
|
10
|
+
* `process.env` object use, and the `Bun.env` equivalents. Runtime bootstrap
|
|
11
|
+
* keys (`APIFUSE__RUNTIME__*`) are exempt everywhere because `serve()`
|
|
12
|
+
* callers read the port and pod identity before a context exists.
|
|
13
|
+
* - `node-runtime-module-import`: value imports of `fs`, `fs/promises`, `net`,
|
|
14
|
+
* `tls`, `dgram`, `http`, `https`, `http2`, `child_process` (with or without
|
|
15
|
+
* the `node:` prefix) via import/export declarations, `require()`, or dynamic
|
|
16
|
+
* `import()`, plus the Bun equivalents `Bun.spawn`, `Bun.spawnSync`, `Bun.$`,
|
|
17
|
+
* `Bun.file`, `Bun.write`. Type-only imports are not runtime reach and are
|
|
18
|
+
* ignored.
|
|
19
|
+
* - `direct-fetch-call`: a call to the global `fetch` (bare identifier, or
|
|
20
|
+
* `globalThis`/`window`/`self`/`global` member). A bare `fetch` call whose
|
|
21
|
+
* enclosing scope declares a `fetch` binding (parameter, variable, import,
|
|
22
|
+
* function, catch clause) is calling that binding, not the global, and is
|
|
23
|
+
* left alone; the check is lexical, so a wrapper that takes `fetch` as a
|
|
24
|
+
* parameter does not hide a global `fetch()` elsewhere in the same file.
|
|
25
|
+
* `ctx.stealth.fetch()` is a member call and never matches.
|
|
26
|
+
*
|
|
27
|
+
* Files are parsed with the script kind their extension implies (`.ts` as
|
|
28
|
+
* TypeScript, `.tsx` as TSX, …). Parsing everything as TSX would make a
|
|
29
|
+
* generic arrow (`<T>(value: T) => value`) or an angle-bracket assertion in an
|
|
30
|
+
* ordinary `.ts` file read as JSX, and everything after it would silently drop
|
|
31
|
+
* out of the AST.
|
|
32
|
+
*
|
|
33
|
+
* Scope is the provider *runtime* source: JavaScript/TypeScript files under
|
|
34
|
+
* `providerSourceFiles` minus tests, recorded fixtures, the root bootstrap
|
|
35
|
+
* entrypoints (`dev.ts`, `start.ts`, `deploy.ts`) and the operator tooling
|
|
36
|
+
* directories (`scripts/`, `tools/`, `bin/`). Tooling legitimately reads the
|
|
37
|
+
* ambient environment and the filesystem when a human runs it; the rule is
|
|
38
|
+
* about code the pod executes on behalf of a request.
|
|
39
|
+
*
|
|
40
|
+
* A deliberate exception is acknowledged with the same escape hatch the submit
|
|
41
|
+
* check uses: `// @apifuse-allow <rule>: <reason>` on the finding line or the
|
|
42
|
+
* line directly above it. Acknowledged findings are reported as information so
|
|
43
|
+
* `apifuse check` still lists them.
|
|
44
|
+
*/
|
|
45
|
+
import type { LintDiagnostic, ProviderLintInformation, ProviderLintResult } from "./lint.js";
|
|
46
|
+
import { type TypeScriptCompilerModuleLike, type TypeScriptModule } from "./typescript-module.js";
|
|
47
|
+
export declare const PROCESS_ENV_DIRECT_READ_RULE = "process-env-direct-read";
|
|
48
|
+
export declare const NODE_RUNTIME_MODULE_IMPORT_RULE = "node-runtime-module-import";
|
|
49
|
+
export declare const DIRECT_FETCH_CALL_RULE = "direct-fetch-call";
|
|
50
|
+
export declare const RUNTIME_BOUNDARY_RULES: readonly ["process-env-direct-read", "node-runtime-module-import", "direct-fetch-call"];
|
|
51
|
+
export type RuntimeBoundaryRule = (typeof RUNTIME_BOUNDARY_RULES)[number];
|
|
52
|
+
/**
|
|
53
|
+
* Environment names a provider may read from `process.env` directly. These
|
|
54
|
+
* are consumed before `serve()` builds a context (port, pod identity), so
|
|
55
|
+
* `ctx.env` cannot carry them. The whole `APIFUSE__RUNTIME__` family is the
|
|
56
|
+
* bootstrap contract; nothing else is exempt.
|
|
57
|
+
*/
|
|
58
|
+
export declare const BOOTSTRAP_ENV_NAME_PREFIX = "APIFUSE__RUNTIME__";
|
|
59
|
+
export declare function isBootstrapEnvName(name: string): boolean;
|
|
60
|
+
type RuntimeBoundaryFinding = {
|
|
61
|
+
rule: RuntimeBoundaryRule;
|
|
62
|
+
/** 1-based line. */
|
|
63
|
+
line: number;
|
|
64
|
+
/** What was reached: the env name, module specifier, or call text. */
|
|
65
|
+
subject: string;
|
|
66
|
+
};
|
|
67
|
+
type RuntimeBoundaryFileReport = {
|
|
68
|
+
findings: RuntimeBoundaryFinding[];
|
|
69
|
+
acknowledged: Array<RuntimeBoundaryFinding & {
|
|
70
|
+
reason: string;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
/** True for the files the runtime boundary rules look at. */
|
|
74
|
+
export declare function isRuntimeBoundarySourceFile(relativePath: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Script kind for a provider source file, from its extension. `.tsx`/`.jsx`
|
|
77
|
+
* enable JSX; everything else (`.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`,
|
|
78
|
+
* and the synthetic names used for in-memory sources) does not, so a generic
|
|
79
|
+
* arrow or `<T>value` assertion in a `.ts` file parses as TypeScript instead
|
|
80
|
+
* of swallowing the rest of the file as unterminated JSX.
|
|
81
|
+
*/
|
|
82
|
+
export declare function scriptKindForSourceFile(ts: TypeScriptModule, fileName: string): import("typescript").ScriptKind;
|
|
83
|
+
export declare function analyzeRuntimeBoundary(ts: TypeScriptModule, fileName: string, source: string): RuntimeBoundaryFileReport;
|
|
84
|
+
export type RuntimeBoundaryLintInput = {
|
|
85
|
+
authFlowSource?: string;
|
|
86
|
+
providerSourceFiles?: Record<string, string>;
|
|
87
|
+
operations?: Record<string, {
|
|
88
|
+
handler?: unknown;
|
|
89
|
+
source?: string;
|
|
90
|
+
}>;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Lint the provider runtime boundary. Returns warn-level diagnostics (one per
|
|
94
|
+
* file and rule, listing every line) and information entries for findings
|
|
95
|
+
* acknowledged with `@apifuse-allow`.
|
|
96
|
+
*/
|
|
97
|
+
export declare function lintRuntimeBoundary(ts: TypeScriptModule, provider: RuntimeBoundaryLintInput, readOperationSource: (operation: {
|
|
98
|
+
handler?: unknown;
|
|
99
|
+
source?: string;
|
|
100
|
+
}) => string): {
|
|
101
|
+
diagnostics: LintDiagnostic[];
|
|
102
|
+
information: ProviderLintInformation[];
|
|
103
|
+
};
|
|
104
|
+
export type LintRuntimeBoundarySourcesOptions = {
|
|
105
|
+
/**
|
|
106
|
+
* A loaded TypeScript compiler API module to parse with (`typescript` 6.x
|
|
107
|
+
* or `@typescript/typescript6`). When omitted the SDK resolves one from its
|
|
108
|
+
* own install location, trying `typescript` first and falling back to
|
|
109
|
+
* `@typescript/typescript6` when `typescript` is the 7.x native shell
|
|
110
|
+
* without `createSourceFile`. A caller whose dependency tree pins its own
|
|
111
|
+
* compiler module (the APIFuse monorepo contract check) passes it here so
|
|
112
|
+
* the parse does not depend on what hoists next to the SDK.
|
|
113
|
+
*/
|
|
114
|
+
typescript?: TypeScriptCompilerModuleLike;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Lint provider runtime source files for the runtime boundary rules without a
|
|
118
|
+
* provider definition: `files` maps provider-root-relative paths to source
|
|
119
|
+
* text (the same shape `apifuse check` collects as `providerSourceFiles`).
|
|
120
|
+
* Out-of-scope paths (tests, recorded fixtures, `.d.ts`, the root
|
|
121
|
+
* `dev.ts`/`start.ts`/`deploy.ts`, `scripts/`, `tools/`, `bin/`) are skipped
|
|
122
|
+
* with `isRuntimeBoundarySourceFile`. Diagnostics carry
|
|
123
|
+
* `field: "sourceFiles.<path>"`; `@apifuse-allow` acknowledgements come back
|
|
124
|
+
* as `information`.
|
|
125
|
+
*
|
|
126
|
+
* Parses with `options.typescript` when given, otherwise with the compiler
|
|
127
|
+
* module resolved from the installed SDK (see `getTypeScript`). This is the
|
|
128
|
+
* entry point the platform contract check uses to apply the rules with the
|
|
129
|
+
* monorepo's SDK pin instead of each provider's own pin.
|
|
130
|
+
*/
|
|
131
|
+
export declare function lintRuntimeBoundarySources(files: Record<string, string>, options?: LintRuntimeBoundarySourcesOptions): ProviderLintResult;
|
|
132
|
+
export {};
|