@apifuse/provider-sdk 2.2.0-beta.62 → 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 +367 -9
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/bin/apifuse-check.ts +84 -2
- package/bin/apifuse-dev.ts +11 -31
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-pack-check.ts +11 -4
- package/bin/apifuse-pack-types.ts +187 -4
- package/bin/apifuse-record.ts +44 -32
- package/bin/apifuse-submit-check.ts +9 -5
- package/bin/apifuse-test.ts +3 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-provider-shape.d.ts +5 -1
- package/dist/cli/migrate-provider-shape.js +4 -2
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +5 -0
- package/dist/dev.d.ts +3 -1
- package/dist/dev.js +6 -1
- package/dist/engine.d.ts +35 -1
- package/dist/engine.js +21 -2
- package/dist/error-resolution.d.ts +1 -0
- package/dist/error-resolution.js +54 -0
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +50 -0
- 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/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/index.d.ts +11 -3
- package/dist/index.js +9 -1
- package/dist/lint.d.ts +15 -0
- package/dist/lint.js +469 -82
- package/dist/provider.d.ts +6 -6
- package/dist/provider.js +3 -3
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +3 -0
- package/dist/runtime/browser.js +208 -47
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.js +67 -7
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/diagnostic-env.d.ts +3 -0
- package/dist/runtime/diagnostic-env.js +10 -1
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/handle.js +9 -1
- package/dist/runtime/http-telemetry-guard.d.ts +10 -0
- package/dist/runtime/http-telemetry-guard.js +62 -10
- package/dist/runtime/http.js +141 -35
- package/dist/runtime/instrumentation.js +92 -13
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.js +35 -8
- package/dist/runtime/request-telemetry.d.ts +2 -0
- package/dist/runtime/request-telemetry.js +2 -1
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +3 -2
- package/dist/runtime/resolver-shared.js +4 -4
- package/dist/runtime/resolver-telemetry.d.ts +1 -0
- package/dist/runtime/resolver-telemetry.js +1 -1
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +46 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
- package/dist/runtime/resolver-vendors/hosts.js +35 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
- package/dist/runtime/resolver-vendors/types.d.ts +5 -4
- package/dist/runtime/resolver.d.ts +4 -1
- package/dist/runtime/resolver.js +46 -13
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +26 -7
- package/dist/runtime/stealth-cookies.d.ts +5 -1
- package/dist/runtime/stealth-cookies.js +21 -1
- package/dist/runtime/stealth-owned-headers.d.ts +26 -0
- package/dist/runtime/stealth-owned-headers.js +37 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +4 -1
- package/dist/runtime/stealth.js +228 -85
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +15 -6
- package/dist/runtime-boundary-lint.d.ts +132 -0
- package/dist/runtime-boundary-lint.js +586 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +4 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test.js +1 -18
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +9 -0
- package/dist/server/serve-implementation.d.ts +51 -2
- package/dist/server/serve-implementation.js +337 -90
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +7 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
- package/dist/stateful/stateful-provider-runtime-context.js +1 -0
- package/dist/types.d.ts +123 -1
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +6 -1
- package/src/cli/commands.ts +14 -0
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-provider-shape.ts +10 -4
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/define.ts +13 -0
- package/src/dev.ts +9 -2
- package/src/engine.ts +38 -2
- package/src/error-resolution.ts +58 -0
- package/src/errors.ts +77 -1
- package/src/handle-meta.ts +51 -1
- package/src/handle.ts +92 -10
- package/src/health-scenario.ts +38 -0
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/index.ts +86 -0
- package/src/lint.ts +604 -91
- package/src/provider.ts +12 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +249 -65
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +68 -17
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/diagnostic-env.ts +11 -1
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/handle.ts +9 -1
- package/src/runtime/http-telemetry-guard.ts +66 -13
- package/src/runtime/http.ts +168 -37
- package/src/runtime/instrumentation.ts +105 -16
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +114 -74
- package/src/runtime/request-telemetry.ts +2 -1
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +9 -4
- package/src/runtime/resolver-telemetry.ts +1 -1
- package/src/runtime/resolver-vendors/browser.ts +65 -7
- package/src/runtime/resolver-vendors/hosts.ts +43 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
- package/src/runtime/resolver-vendors/types.ts +5 -4
- package/src/runtime/resolver.ts +64 -21
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +46 -45
- package/src/runtime/stealth-cookies.ts +24 -1
- package/src/runtime/stealth-owned-headers.ts +41 -0
- package/src/runtime/stealth-telemetry.ts +304 -0
- package/src/runtime/stealth.ts +355 -150
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +93 -71
- package/src/runtime-boundary-lint.ts +703 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +53 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test.ts +1 -21
- package/src/server/sensitive-values.ts +14 -0
- package/src/server/serve-implementation.ts +569 -108
- package/src/server/types.ts +7 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
- package/src/stateful/stateful-provider-runtime-context.ts +4 -0
- package/src/types.ts +128 -1
- package/src/typescript-module.ts +100 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
package/AUTHORING.md
CHANGED
|
@@ -136,6 +136,75 @@ Hosted runtime settings are engine-owned as well: every `APIFUSE__CDP_POOL__*`
|
|
|
136
136
|
variable, the Cloudflare OCR/STT tokens and account identifier,
|
|
137
137
|
`APIFUSE__OCR__API_KEY`, and `APIFUSE__CACHE__KEY_PEPPER`. `defineProvider`
|
|
138
138
|
rejects them in `secrets`; the engine reads them from its own environment.
|
|
139
|
+
Provider code must not probe those names either — to learn whether OCR is
|
|
140
|
+
configured, read `ctx.ocr.available` (see the OCR section below).
|
|
141
|
+
|
|
142
|
+
### Engine mode
|
|
143
|
+
|
|
144
|
+
Provider processes attach their capability bindings through an engine. Today the
|
|
145
|
+
default is the in-process engine; ADR-0011 replaces it with the authenticated
|
|
146
|
+
remote engine in a phased migration, and `serve` logs every boot as
|
|
147
|
+
`provider_engine_mode` (`deprecated: true` for in-process). You do not need to
|
|
148
|
+
change anything now.
|
|
149
|
+
|
|
150
|
+
A deployment names the mode with the engine-owned `APIFUSE__ENGINE__MODE`
|
|
151
|
+
variable (`in-process` or `remote`; trimmed and case-folded, blank means unset);
|
|
152
|
+
hosts may pass `engineMode` to `serve`/`createServerApp`/`startDevServer`
|
|
153
|
+
instead. The manifest wins when the two disagree, so provider code cannot
|
|
154
|
+
silently downgrade what the deployment asked for; the disagreement is reported in
|
|
155
|
+
the boot event's `warnings`. An unrecognized value is warned about and ignored —
|
|
156
|
+
it never crashes the process. `APIFUSE__ENGINE__MODE` cannot be declared in
|
|
157
|
+
`secrets` (`APIFUSE__ENGINE__*` is engine-owned).
|
|
158
|
+
|
|
159
|
+
`remote` has no client in this release: the server attaches an engine that fails
|
|
160
|
+
every request with `PROVIDER_ENGINE_MODE_UNSUPPORTED` and reports `503` on
|
|
161
|
+
`/readyz`. It never falls back to in-process. `apifuse dev` and `apifuse record`
|
|
162
|
+
refuse to start instead.
|
|
163
|
+
|
|
164
|
+
`GET /health` is liveness only and stays engine-blind; `GET /readyz` reports the
|
|
165
|
+
resolved engine attachment and is what readiness/startup probes should use — but
|
|
166
|
+
only for a provider already pinned to an SDK release that serves the route.
|
|
167
|
+
Earlier pins answer `404`, and a failing readiness/startup probe is not
|
|
168
|
+
self-healing, so manifests move to `/readyz` after the pin wave, never before.
|
|
169
|
+
|
|
170
|
+
### Deployment intent
|
|
171
|
+
|
|
172
|
+
Deployment intent (runtime profile, resources, HPA, Redis, extra TCP ports) has
|
|
173
|
+
exactly one authored home: the optional `deployment` key inside
|
|
174
|
+
`defineProvider({...})`. The SDK passes it through verbatim; the APIFuse
|
|
175
|
+
registry resolves omitted fields from the runtime profile and rejects anything
|
|
176
|
+
it cannot resolve.
|
|
177
|
+
|
|
178
|
+
- Declare only what differs from the profile. Profile defaults:
|
|
179
|
+
`runtime: "shared"`, `language: "typescript"`, `replicas: 1`,
|
|
180
|
+
`hpa: { enabled: true, minReplicas: 1, maxReplicas: 1, targetCPUUtilizationPercentage: 70 }`,
|
|
181
|
+
resources `25m/128Mi` (shared) or `200m/256Mi` (browser), `buildContext: "."`.
|
|
182
|
+
Most providers therefore declare no `deployment` key at all.
|
|
183
|
+
- The deployment `runtime` axis (`shared | dedicated | browser`) is distinct
|
|
184
|
+
from the execution `runtime` (`standard | shared | browser`) declared next to
|
|
185
|
+
it.
|
|
186
|
+
- Do not author a standalone `deploy.ts`, a `deployment` extra on a spread
|
|
187
|
+
default export (`export default { ...provider, deployment }`), or a
|
|
188
|
+
re-exported config module. A repository that still carries `deploy.ts`
|
|
189
|
+
migrates with `apifuse migrate-deployment .`: it hoists the file's non-default
|
|
190
|
+
values into the declaration, proves the result resolves identically, and
|
|
191
|
+
deletes the file. The `/deploy.ts` CODEOWNERS entry stays until the platform
|
|
192
|
+
stops honoring a re-added legacy file (`--drop-codeowners-lock` then). A
|
|
193
|
+
refusal names the manual step.
|
|
194
|
+
- `apifuse check` warns during the migration window
|
|
195
|
+
(`deployment/legacy-deploy-file`, `deployment/spread-export`,
|
|
196
|
+
`deployment/redundant-default`); the rules become errors once no repository
|
|
197
|
+
carries the legacy file.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const buildProvider = defineProvider({
|
|
201
|
+
id: "example",
|
|
202
|
+
version: "1.0.0",
|
|
203
|
+
runtime: "standard",
|
|
204
|
+
deployment: { cache: { redis: { enabled: true } } },
|
|
205
|
+
// ...
|
|
206
|
+
});
|
|
207
|
+
```
|
|
139
208
|
|
|
140
209
|
### Factored operations
|
|
141
210
|
|
|
@@ -388,6 +457,40 @@ Best-practice rules:
|
|
|
388
457
|
- Submission checks and health checks must not invoke live STT by default.
|
|
389
458
|
Provide explicit smoke evidence when a provider depends on audio OTP behavior.
|
|
390
459
|
|
|
460
|
+
### OCR runtime capability: availability is the SDK's verdict
|
|
461
|
+
|
|
462
|
+
Declare `ocr: { mode: "required" | "optional" }` and call `ctx.ocr` from
|
|
463
|
+
operation handlers or auth-flow handlers. Whether a working backend is wired
|
|
464
|
+
in is decided by the SDK, never by provider code: `ctx.ocr.available` is
|
|
465
|
+
`true` when `createOcrClientFromEnv` found a configured backend and `false`
|
|
466
|
+
for the unsupported/error client that undeclared or misconfigured providers
|
|
467
|
+
receive. Branch on that flag for behavior that depends on OCR being present
|
|
468
|
+
(for example, whether a CAPTCHA field is required from the user), and keep the
|
|
469
|
+
`OCR_UNAVAILABLE` / `UNSUPPORTED_OCR_BACKEND` error fallback for the call
|
|
470
|
+
itself: `available: true` promises a configured backend, not a successful
|
|
471
|
+
upstream call.
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
async start(ctx) {
|
|
475
|
+
const image = await fetchCaptcha(ctx);
|
|
476
|
+
return ctx.auth.nextForm({
|
|
477
|
+
turnId: "credentials",
|
|
478
|
+
fields: {
|
|
479
|
+
captchaText: { type: "string", required: !ctx.ocr.available, /* ... */ },
|
|
480
|
+
},
|
|
481
|
+
data: { captchaImage: image, ocrConfigured: ctx.ocr.available },
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Do not re-implement the predicate by reading `APIFUSE__OCR__*` or
|
|
487
|
+
`APIFUSE__CLOUDFLARE__ACCOUNT_ID`: those names are engine-owned, the
|
|
488
|
+
runtime-boundary lint reports the read, and a drift between the copy and the
|
|
489
|
+
SDK's own selection logic downgrades every login to the manual path silently.
|
|
490
|
+
|
|
491
|
+
A custom `OcrContext` override (`serve({ ocr })`, `createFlowContext({ ocr })`,
|
|
492
|
+
or a hand-written test double) must set `available` explicitly as a plain data
|
|
493
|
+
property; the SDK's own clients do, and `bindOcrTelemetry` preserves it.
|
|
391
494
|
|
|
392
495
|
### Health journey DX for SMS/payment flows
|
|
393
496
|
|
|
@@ -536,18 +639,20 @@ operation:
|
|
|
536
639
|
|
|
537
640
|
```ts
|
|
538
641
|
errorCodes: [{
|
|
539
|
-
code: "
|
|
540
|
-
status:
|
|
541
|
-
retryable:
|
|
542
|
-
description: "The upstream
|
|
642
|
+
code: "ITEM_SOLD_OUT",
|
|
643
|
+
status: 409,
|
|
644
|
+
retryable: false,
|
|
645
|
+
description: "The upstream sold the item before checkout completed.",
|
|
543
646
|
}],
|
|
544
647
|
handler: async () => {
|
|
545
|
-
throw new ProviderError("
|
|
546
|
-
code: "UPSTREAM_SCHEMA_ERROR",
|
|
547
|
-
});
|
|
648
|
+
throw new ProviderError("Item sold out", { code: "ITEM_SOLD_OUT" });
|
|
548
649
|
},
|
|
549
650
|
```
|
|
550
651
|
|
|
652
|
+
Declare only codes the SDK does not already register. `UPSTREAM_SCHEMA_ERROR`,
|
|
653
|
+
`UPSTREAM_AUTH_ERROR`, and `INVALID_REQUEST` are registered below, so throwing
|
|
654
|
+
them needs no declaration at all.
|
|
655
|
+
|
|
551
656
|
`defineProvider` accepts only statuses the server can emit: 400, 401, 404, 429,
|
|
552
657
|
500, 502, 503, and 504. Invalid declared statuses fail provider definition,
|
|
553
658
|
not a live request. Status selection uses this order:
|
|
@@ -567,10 +672,10 @@ The registered mappings are:
|
|
|
567
672
|
| Error code or fallback | HTTP status |
|
|
568
673
|
| --- | ---: |
|
|
569
674
|
| `AUTH_REQUIRED`, `reauth_required` | 401 |
|
|
570
|
-
| `MISSING_SECRET` | 400 |
|
|
675
|
+
| `MISSING_SECRET`, `UPSTREAM_AUTH_ERROR`, `INVALID_REQUEST` | 400 |
|
|
571
676
|
| `NOT_FOUND`, `not_found`, `NO_DATA` | 404 |
|
|
572
677
|
| `RATE_LIMITED`, `UPSTREAM_RATE_LIMIT`, `LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR` | 429 |
|
|
573
|
-
| `UPSTREAM_ERROR`, `BLOCKED` | 502 |
|
|
678
|
+
| `UPSTREAM_ERROR`, `BLOCKED`, `UPSTREAM_SCHEMA_ERROR` | 502 |
|
|
574
679
|
| `STT_UNAVAILABLE`, `UNSUPPORTED_STT_BACKEND`, `STATEFUL_FORWARDING_REPLAY_CACHE_FULL` | 503 |
|
|
575
680
|
| Unregistered input `ValidationError` code | 400 |
|
|
576
681
|
| Other unregistered `ProviderError` code | 500 |
|
|
@@ -587,6 +692,154 @@ Throw the domain `ProviderError` directly. Subclassing or wrapping it as a
|
|
|
587
692
|
domain code's `status` instead. Genuine `TransportError` values remain
|
|
588
693
|
SDK-owned and keep their 502/504 mapping.
|
|
589
694
|
|
|
695
|
+
#### Three fleet-consensus codes are registered
|
|
696
|
+
|
|
697
|
+
`UPSTREAM_AUTH_ERROR` (400), `UPSTREAM_SCHEMA_ERROR` (502), and
|
|
698
|
+
`INVALID_REQUEST` (400) are registered mappings. All three are non-retryable.
|
|
699
|
+
|
|
700
|
+
- `UPSTREAM_AUTH_ERROR` is 400, not 401 or 502, because it means the upstream
|
|
701
|
+
refused a *platform-managed* service key. The caller holds no credential, so
|
|
702
|
+
401 ("re-authenticate") asks for something the caller cannot do, and 502
|
|
703
|
+
("the upstream is sick") promises a recovery that will never arrive. It is a
|
|
704
|
+
deployment/config defect, exactly like `MISSING_SECRET`.
|
|
705
|
+
- `UPSTREAM_SCHEMA_ERROR` is non-retryable: a retry returns the same payload
|
|
706
|
+
the provider already could not normalize.
|
|
707
|
+
- `INVALID_REQUEST` is the spelling for caller-side bad input. `INVALID_INPUT`,
|
|
708
|
+
`VALIDATION_ERROR`, and `BAD_REQUEST` are not registered.
|
|
709
|
+
|
|
710
|
+
Like `UPSTREAM_ERROR` and `BLOCKED`, these are thrown by provider code rather
|
|
711
|
+
than by the SDK, so they are registered mappings and not SDK-owned codes: an
|
|
712
|
+
existing operation declaration still wins at runtime (step 2 above). Registering
|
|
713
|
+
them therefore never rewrites a status you already declared. What it does do is
|
|
714
|
+
let you delete the declaration — and when your declaration disagrees with the
|
|
715
|
+
registered mapping, `apifuse check` reports it rather than leaving the fleet
|
|
716
|
+
answering one code two ways:
|
|
717
|
+
|
|
718
|
+
- `error-code-status-conflicts-sdk` — declared `status` differs from the
|
|
719
|
+
registered status.
|
|
720
|
+
- `error-code-retryable-conflicts-sdk` — declared `retryable` differs from the
|
|
721
|
+
registered retryability.
|
|
722
|
+
|
|
723
|
+
Both are warnings. Drop the conflicting field so the SDK supplies the value, or,
|
|
724
|
+
if your operation genuinely means something else, throw a distinct code that
|
|
725
|
+
says so.
|
|
726
|
+
|
|
727
|
+
### Localized error messages (`messageKey` / `fixKey`)
|
|
728
|
+
|
|
729
|
+
The positional `ProviderError` message stays **English**. It is what
|
|
730
|
+
`error.message`, cause frames, the `provider_request_failed` log, OTLP
|
|
731
|
+
attributes and tests see. Only two fields of the response body are localized:
|
|
732
|
+
`error.message` and `error.fix`. Nothing else — not `code`, not `details`, not
|
|
733
|
+
`errorCodes[].description`, not the `X-ApiFuse-Error-Observability` header.
|
|
734
|
+
|
|
735
|
+
Attach locale keys instead of parsing `Accept-Language` yourself:
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
// locales/en.json
|
|
739
|
+
{ "errors": { "upstreamSchema": {
|
|
740
|
+
"message": "{provider} returned an unexpected response.",
|
|
741
|
+
"fix": "Retry in a few minutes."
|
|
742
|
+
} } }
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
throw new ProviderError("The culture data service returned an unexpected response.", {
|
|
747
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
748
|
+
messageKey: "errors.upstreamSchema.message",
|
|
749
|
+
fixKey: "errors.upstreamSchema.fix",
|
|
750
|
+
params: { provider: "Culture Data" },
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Keys can also live on the declaration, which localizes **every** site that
|
|
755
|
+
throws that code without touching the throw sites:
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
errorCodes: [{
|
|
759
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
760
|
+
description: "The upstream response no longer matches its schema.",
|
|
761
|
+
messageKey: "errors.upstreamSchema.message",
|
|
762
|
+
fixKey: "errors.upstreamSchema.fix",
|
|
763
|
+
}],
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
`status` and `retryable` are omitted because `UPSTREAM_SCHEMA_ERROR` is a
|
|
767
|
+
registered code: the SDK already supplies 502 and non-retryable, and declaring
|
|
768
|
+
a different value trips `error-code-status-conflicts-sdk` /
|
|
769
|
+
`error-code-retryable-conflicts-sdk`. A declaration that carries only keys is
|
|
770
|
+
still worth writing — it localizes every site that throws the code.
|
|
771
|
+
|
|
772
|
+
Resolution order per field, evaluated at serve time:
|
|
773
|
+
|
|
774
|
+
1. the throw site's `messageKey` / `fixKey`;
|
|
775
|
+
2. the matching `errorCodes[]` entry's `messageKey` / `fixKey`;
|
|
776
|
+
3. the derived `errors.<code>.message` / `errors.<code>.fix` (the code is used
|
|
777
|
+
verbatim as a catalog segment, so `errors.UPSTREAM_SCHEMA_ERROR.message`
|
|
778
|
+
works with no declaration at all);
|
|
779
|
+
4. the English literal passed to the constructor.
|
|
780
|
+
|
|
781
|
+
Each candidate is resolved in the caller's locale first and then in `en`, so a
|
|
782
|
+
more specific key always beats a more specific locale. A missing key, a
|
|
783
|
+
malformed key, a non-string catalog value or an absent catalog is a miss, never
|
|
784
|
+
a throw: the caller always gets text. Steps 2 and 3 do not apply to SDK-owned
|
|
785
|
+
failures (transport, Zod, stateful deadline, SDK runtime codes) — a provider
|
|
786
|
+
catalog must not be able to relabel `Request timed out`.
|
|
787
|
+
|
|
788
|
+
**Key grammar.** A locale key is dot-separated segments. Every segment is
|
|
789
|
+
camelCase (`upstreamSchema`, `whenToUse`) or an array index (`0`), and the
|
|
790
|
+
first segment is always a camelCase namespace. The one exception is the segment
|
|
791
|
+
directly under `errors`, which may also be the error code spelled the way it is
|
|
792
|
+
thrown — `errors.UPSTREAM_SCHEMA_ERROR.message`, `errors.BLOCKED.fix`,
|
|
793
|
+
`errors.reauth_required.message`. Both spellings work, so
|
|
794
|
+
`errors.upstreamSchema.message` and `errors.UPSTREAM_SCHEMA_ERROR.message` are
|
|
795
|
+
equally legal keys; write the code form when you want the key to match the
|
|
796
|
+
derived path in step 3. Nothing else loosens: a space, a dot inside a segment,
|
|
797
|
+
an empty segment, a mixed-case or hyphenated code, and a leading digit are all
|
|
798
|
+
still malformed.
|
|
799
|
+
|
|
800
|
+
Locale negotiation reads `Accept-Language` from the request envelope's
|
|
801
|
+
`headers` map first (the gateway stamps the flow's locale there) and then from
|
|
802
|
+
the HTTP header, honours quality values, matches on the primary subtag, and
|
|
803
|
+
supports `en`, `ko`, `ja` with `en` as the default. A request with no
|
|
804
|
+
`Accept-Language` therefore keeps serving the `en` catalog value, so existing
|
|
805
|
+
contract fixtures do not churn.
|
|
806
|
+
|
|
807
|
+
**Interpolation.** `{name}` placeholders, `{{` and `}}` for literal braces.
|
|
808
|
+
`params` accepts strings and finite numbers only. A placeholder with no usable
|
|
809
|
+
param is left verbatim so the authoring gap is visible rather than silently
|
|
810
|
+
deleting text.
|
|
811
|
+
|
|
812
|
+
**Params are untrusted.** Upstream error text routinely ends up in `params`, so
|
|
813
|
+
every string value is scrubbed before substitution: secrets and e-mail
|
|
814
|
+
addresses redacted, control/bidi/newline characters encoded or collapsed,
|
|
815
|
+
`<`, `>` and backticks removed, HTML-entity ampersands (`<`) neutralized
|
|
816
|
+
while ordinary ampersands survive, and the value capped at 200 characters.
|
|
817
|
+
Substituted text is never rescanned, so a value containing `{other}` cannot
|
|
818
|
+
trigger a second interpolation round. Prefer `details` when you need to relay
|
|
819
|
+
an upstream body verbatim; `params` is for short, bounded values.
|
|
820
|
+
|
|
821
|
+
**Catalog loading.** Catalogs are read once when the server is built (from the
|
|
822
|
+
provider directory's `locales/{en,ko,ja}.json`, loading only the locales that
|
|
823
|
+
exist). Pass `serve(provider, { localeCatalogs })` to bundle them instead. A
|
|
824
|
+
catalog that exists but cannot be parsed logs one
|
|
825
|
+
`provider_locale_catalogs_unavailable` warn at boot and every field falls back
|
|
826
|
+
to its English literal.
|
|
827
|
+
|
|
828
|
+
**Lint.** `apifuse check` reports:
|
|
829
|
+
|
|
830
|
+
- `error-locale-key-missing` / `error-locale-key-malformed` (**error**) — a
|
|
831
|
+
literal `messageKey`/`fixKey`, on a throw site or on an `errorCodes[]` entry,
|
|
832
|
+
that `locales/en.json` does not resolve to non-empty text, or that is not a
|
|
833
|
+
legal locale dot path (see **Key grammar** above).
|
|
834
|
+
- `thrown-error-message-not-localized` (**warn**) — a throw site whose declared
|
|
835
|
+
code has no key anywhere and no `errors.<code>.message` in `en`, so it is
|
|
836
|
+
served untranslated. Warning during the migration; promoted to error per
|
|
837
|
+
provider as each wave lands.
|
|
838
|
+
|
|
839
|
+
`ko`/`ja` coverage needs no new rule: `en` is the baseline, so the existing
|
|
840
|
+
catalog parity and placeholder checks already fail an `errors.*` key that is
|
|
841
|
+
missing or untranslated in `ko`/`ja`.
|
|
842
|
+
|
|
590
843
|
### Declared secrets are SDK-enforced
|
|
591
844
|
|
|
592
845
|
Environment/secret presence validation is single-sourced in the SDK. Declare
|
|
@@ -640,6 +893,69 @@ Note the asymmetry: the gate treats whitespace-only values as missing, but
|
|
|
640
893
|
`ctx.env.get()` still returns the raw value to handlers — trim at the point of
|
|
641
894
|
use if the upstream is whitespace-sensitive.
|
|
642
895
|
|
|
896
|
+
### Runtime boundary: no `process.env`, `node:fs`, `child_process`, or raw `fetch()`
|
|
897
|
+
|
|
898
|
+
Provider runtime code reaches the outside world through the context only:
|
|
899
|
+
`ctx.http` / `ctx.stealth` for HTTP, `ctx.browser` for browser work,
|
|
900
|
+
`ctx.env.get()` / `ctx.credential` for configuration and secrets, `ctx.files` /
|
|
901
|
+
`ctx.cache` / `ctx.state` for data that must outlive a request. Ambient Node
|
|
902
|
+
and Bun globals bypass `allowedHosts`, proxy policy, retries, redaction, the
|
|
903
|
+
secret presence gate, and telemetry. `apifuse check` reports them (**warn** in
|
|
904
|
+
this release; the level is raised once the fleet is clean):
|
|
905
|
+
|
|
906
|
+
- `process-env-direct-read` — `process.env.X`, `process.env["X"]`, a bare
|
|
907
|
+
`process.env` object use, `Bun.env`. The `APIFUSE__RUNTIME__*` bootstrap
|
|
908
|
+
family (`APIFUSE__RUNTIME__PORT`, `…POD_ID`, `…POD_ENDPOINT`) is exempt
|
|
909
|
+
everywhere; every other value is declared in `defineProvider` under
|
|
910
|
+
`secrets: [{ name, required }]` and read with `ctx.env.get(name)`. The names
|
|
911
|
+
visible to `ctx.env.get` are exactly the declared `secrets[].name` (minus
|
|
912
|
+
engine-owned names; an `oauth2_proxied` auth flow additionally sees
|
|
913
|
+
`APIFUSE__AUTH_PROXY__URL`). `env: true` (or `env: {}`) is the capability
|
|
914
|
+
declaration that turns the `ctx.env` binding on — a type *and* runtime gate,
|
|
915
|
+
since touching `ctx.env` without it throws `PROVIDER_CAPABILITY_UNDECLARED`
|
|
916
|
+
— and does not widen the readable names. Test/E2E toggles are not secrets
|
|
917
|
+
and cannot move to `ctx.env`: replace them with test doubles (inject
|
|
918
|
+
`ctx.env` / `ctx.http`) or recorded fixtures.
|
|
919
|
+
- `node-runtime-module-import` — value imports of `fs`, `fs/promises`, `net`,
|
|
920
|
+
`tls`, `dgram`, `http`, `https`, `http2`, `child_process` (with or without
|
|
921
|
+
`node:`), and `Bun.spawn` / `Bun.spawnSync` / ``Bun.$` ` `` / `Bun.file` /
|
|
922
|
+
`Bun.write`. Type-only imports (`import type`, or every binding inline `type`) are ignored; `node:path`, `node:crypto`,
|
|
923
|
+
`node:url` are fine.
|
|
924
|
+
- `direct-fetch-call` — the global `fetch()` (also `globalThis.fetch`).
|
|
925
|
+
`ctx.stealth.fetch()` never matches, nor does a call to a `fetch` binding
|
|
926
|
+
declared in an enclosing scope (parameter, variable, import, catch clause).
|
|
927
|
+
The check is lexical: a wrapper that takes `fetch` as a parameter does not
|
|
928
|
+
hide a global `fetch()` in another function of the same file.
|
|
929
|
+
|
|
930
|
+
Scope is runtime source only: tests, recorded fixtures, `.d.ts`, the root
|
|
931
|
+
`dev.ts` / `start.ts` / `deploy.ts` entrypoints, and the `scripts/`, `tools/`,
|
|
932
|
+
`bin/` directories at the provider root are not scanned. Keep fixture
|
|
933
|
+
recorders, smoke scripts, and other operator tooling under `scripts/`, and do
|
|
934
|
+
not import from there in request-path modules.
|
|
935
|
+
|
|
936
|
+
Acknowledge a deliberate exception on the finding line or the comment-only
|
|
937
|
+
line above it with `// @apifuse-allow <rule>: <reason>`; the finding then
|
|
938
|
+
appears as an `INFO` audit line instead of a warning.
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
// Before: request-path module reads the ambient environment and the disk
|
|
942
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
943
|
+
const serviceKey = process.env.EXAMPLE_SERVICE_KEY;
|
|
944
|
+
if (process.env.EXAMPLE_DIAG_TAP_DIR) {
|
|
945
|
+
await writeFile(`${process.env.EXAMPLE_DIAG_TAP_DIR}/booking.json`, JSON.stringify(response));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// After: the credential is a declared secret read through the context —
|
|
949
|
+
// defineProvider({
|
|
950
|
+
// env: true, // turns the ctx.env capability on (type + runtime gate)
|
|
951
|
+
// secrets: [{ name: "APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY", required: true }], // the readable names
|
|
952
|
+
// })
|
|
953
|
+
// — and the ad-hoc diagnostic tap is deleted: a toggle that is not a secret has
|
|
954
|
+
// no place in ctx.env; observability goes through ctx.trace instead.
|
|
955
|
+
const serviceKey = ctx.env.get("APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY");
|
|
956
|
+
const response = await ctx.trace.span("booking.confirm", () => confirmBooking(ctx, serviceKey));
|
|
957
|
+
```
|
|
958
|
+
|
|
643
959
|
### Credentials forced into query parameters
|
|
644
960
|
|
|
645
961
|
Prefer an authorization header or request body whenever the upstream supports
|
|
@@ -678,6 +994,48 @@ redacted. If a login flow must consume a rotated credential from `Location`,
|
|
|
678
994
|
inspect it inside `stopWhen`; that callback receives the real hop while callback
|
|
679
995
|
failures are sanitized before propagation.
|
|
680
996
|
|
|
997
|
+
### Request-bound proof headers
|
|
998
|
+
|
|
999
|
+
When an upstream requires a single-use, request-bound header (DPoP proofs,
|
|
1000
|
+
HTTP message signatures, HMAC nonces), pass `headers` as a factory instead of a
|
|
1001
|
+
record. `ctx.http` calls it once per issued attempt with the resolved URL and
|
|
1002
|
+
method, so a managed retry sends a fresh proof rather than replaying the first
|
|
1003
|
+
one, and the request keeps proxy failover and the transient retry taxonomy:
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
const response = await ctx.http.get(requestUrl, {
|
|
1007
|
+
headers: async ({ url, method }) => {
|
|
1008
|
+
// RFC 9449 §4.2: `htu` is the target URI without query and fragment.
|
|
1009
|
+
const htu = new URL(url);
|
|
1010
|
+
htu.search = "";
|
|
1011
|
+
htu.hash = "";
|
|
1012
|
+
return {
|
|
1013
|
+
...commonHeaders,
|
|
1014
|
+
dpop: await createDpopProof(htu.toString(), method),
|
|
1015
|
+
};
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
`url` is the exact URL the attempt is issued against, including `params` and
|
|
1021
|
+
`sensitiveParams`. Bind to it instead of a URL you rebuild, but strip the query
|
|
1022
|
+
and fragment for fields that are defined without them (DPoP `htu`), and never
|
|
1023
|
+
log it or copy it verbatim into a signed field a verifier logs.
|
|
1024
|
+
|
|
1025
|
+
Keep the factory fast and local. It runs after proxy resolution, so an already
|
|
1026
|
+
allocated egress endpoint and its lease are held while it is awaited; a remote
|
|
1027
|
+
signing round trip there can outlive the lease TTL. Mint locally, and set
|
|
1028
|
+
`timeout` on the call so a hung signer cannot hold the endpoint indefinitely.
|
|
1029
|
+
|
|
1030
|
+
The factory is not invoked for redirect hops (they reuse the attempt's
|
|
1031
|
+
headers), for skipped duplicate proxy offsets, or for an attempt whose proxy
|
|
1032
|
+
allocation failed — those build no request, so `attempt` counts only the
|
|
1033
|
+
requests that are actually issued. A factory that throws fails the request with
|
|
1034
|
+
the non-retryable `TransportError` code `http_header_factory_failed`, which is
|
|
1035
|
+
classified as a provider-side fault (`provider_error`), not an upstream
|
|
1036
|
+
failure. `ctx.stealth` has its own `headers` option and does not accept a
|
|
1037
|
+
factory.
|
|
1038
|
+
|
|
681
1039
|
### Public local debugging checklist
|
|
682
1040
|
|
|
683
1041
|
- Operation smoke requests use the provider server envelope:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.64
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit 0505a938340d08db3ea3e2302cab5f6b5dd2b4e5.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.63
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit a9b85acc5af4c40ec59774b0d1e864e7ed21828e.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.62
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit 98c623f799268b6d13ce59844b2b33ed78dcd0c3.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vooy Korea, Inc. (주식회사 부이코리아)
|
|
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
CHANGED
|
@@ -78,6 +78,7 @@ The operation request body is the same envelope used by the APIFuse gateway:
|
|
|
78
78
|
| `headers` | no | Extra caller headers to expose through `ctx.request.headers`. |
|
|
79
79
|
| `connectionId` | no | Connection identity only; it does not include credentials. The gateway sends it for `optional` connection mode, and only when the caller supplied a connection that passed authorization. Exposed as `ctx.request.connectionId`. |
|
|
80
80
|
| `connection` | no | Credential-bearing connection data. The gateway sends it for `required` connection mode; omit it for no-auth/public (`none` mode) operations. For local debugging, pass an object with `id`, `mode`, `secrets`, `metadata`, and `externalRef`. Do not pass `null`. |
|
|
81
|
+
| `tenantId` | no | Gateway-asserted principal scope: the organization id for customer subjects, the service-account id for platform service accounts. Sent for every connection mode, including `none`; absent on direct calls and self-test. Exposed as `ctx.request.tenantId` (`undefined` when absent or empty). Caller headers never populate it. |
|
|
81
82
|
|
|
82
83
|
The gateway sends only `connection` for `required` mode, only `connectionId`
|
|
83
84
|
for `optional` mode (and nothing when the caller passed no connection), and
|
package/bin/apifuse-check.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
DECLARATION_INVALID_CODE,
|
|
16
16
|
validateFailClosedDeclaration,
|
|
17
17
|
} from "../src/declaration-validation.js";
|
|
18
|
+
import { redundantDeploymentDefaults } from "../src/cli/deployment-intent.js";
|
|
18
19
|
import { isProviderError } from "../src/errors.js";
|
|
19
20
|
import type { ProviderDefinition } from "../src/index.js";
|
|
20
21
|
import { lintProviderWithInformation, type ProviderLintMode } from "../src/lint.js";
|
|
@@ -140,6 +141,7 @@ export async function runChecks(
|
|
|
140
141
|
}
|
|
141
142
|
const provider = assertProviderDefinition(providerModule?.default);
|
|
142
143
|
const providerSourceFiles = collectProviderSourceFiles(providerRoot);
|
|
144
|
+
const localeCatalogEn = readEnglishLocaleCatalog(providerRoot);
|
|
143
145
|
|
|
144
146
|
return [
|
|
145
147
|
checkIndex(indexPath, provider),
|
|
@@ -147,8 +149,9 @@ export async function runChecks(
|
|
|
147
149
|
checkOperations(provider),
|
|
148
150
|
checkFixtures(provider),
|
|
149
151
|
checkSchemas(provider),
|
|
150
|
-
checkAuthoringLint(provider, providerSourceFiles, options.lintMode),
|
|
152
|
+
checkAuthoringLint(provider, providerSourceFiles, localeCatalogEn, options.lintMode),
|
|
151
153
|
checkProviderMetadata(provider),
|
|
154
|
+
await checkDeploymentIntent(providerRoot, provider),
|
|
152
155
|
checkDockerfile(dockerfilePath),
|
|
153
156
|
checkPackageJson(packageJsonPath),
|
|
154
157
|
checkProviderJson(providerJsonPath, packageJsonPath),
|
|
@@ -367,9 +370,30 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
|
|
|
367
370
|
};
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Reads `locales/en.json` so the error-localization lint rules can resolve
|
|
375
|
+
* declared `messageKey`/`fixKey` values. An absent or unreadable catalog is
|
|
376
|
+
* reported as an empty catalog rather than as "no catalog": a provider that
|
|
377
|
+
* references locale keys without shipping `en.json` must still fail the
|
|
378
|
+
* missing-key rule instead of silently skipping it.
|
|
379
|
+
*/
|
|
380
|
+
function readEnglishLocaleCatalog(providerRoot: string): Record<string, unknown> {
|
|
381
|
+
const catalogPath = resolve(providerRoot, "locales", "en.json");
|
|
382
|
+
if (!existsSync(catalogPath)) return {};
|
|
383
|
+
try {
|
|
384
|
+
const parsed: unknown = JSON.parse(readFileSync(catalogPath, "utf8"));
|
|
385
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
386
|
+
? (parsed as Record<string, unknown>)
|
|
387
|
+
: {};
|
|
388
|
+
} catch {
|
|
389
|
+
return {};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
370
393
|
function checkAuthoringLint(
|
|
371
394
|
provider: ProviderDefinition | undefined,
|
|
372
395
|
providerSourceFiles: Record<string, string>,
|
|
396
|
+
localeCatalogEn: Record<string, unknown>,
|
|
373
397
|
lintMode: ProviderLintMode = "official",
|
|
374
398
|
): CheckResult {
|
|
375
399
|
if (!provider) {
|
|
@@ -380,7 +404,7 @@ function checkAuthoringLint(
|
|
|
380
404
|
}
|
|
381
405
|
|
|
382
406
|
const { diagnostics, information } = lintProviderWithInformation(
|
|
383
|
-
{ ...provider, providerSourceFiles },
|
|
407
|
+
{ ...provider, providerSourceFiles, localeCatalogEn },
|
|
384
408
|
{ mode: lintMode },
|
|
385
409
|
);
|
|
386
410
|
const errors = diagnostics.filter((diagnostic) => diagnostic.level === "error");
|
|
@@ -448,6 +472,64 @@ function checkProviderMetadata(provider: ProviderDefinition | undefined): CheckR
|
|
|
448
472
|
};
|
|
449
473
|
}
|
|
450
474
|
|
|
475
|
+
export const DEPLOYMENT_INTENT_CHECK_MESSAGE =
|
|
476
|
+
"Deployment intent lives only in defineProvider({ deployment }) without restating profile defaults";
|
|
477
|
+
export const LEGACY_DEPLOY_FILE_RULE = "deployment/legacy-deploy-file";
|
|
478
|
+
export const REDUNDANT_DEPLOYMENT_DEFAULT_RULE = "deployment/redundant-default";
|
|
479
|
+
export const SPREAD_EXPORT_DEPLOYMENT_RULE = "deployment/spread-export";
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Deployment intent has one authored home: the `deployment` key on
|
|
483
|
+
* `defineProvider()`, carrying only the fields that differ from the runtime
|
|
484
|
+
* profile. A standalone `deploy.ts` is a legacy surface and a field that
|
|
485
|
+
* restates a profile default is noise the platform derives anyway. Both are
|
|
486
|
+
* reported as warnings while the fleet migrates (`apifuse
|
|
487
|
+
* migrate-deployment`); they become errors once no repository carries the
|
|
488
|
+
* legacy file.
|
|
489
|
+
*/
|
|
490
|
+
async function checkDeploymentIntent(
|
|
491
|
+
providerRoot: string,
|
|
492
|
+
provider: ProviderDefinition | undefined,
|
|
493
|
+
): Promise<CheckResult> {
|
|
494
|
+
const details: string[] = [];
|
|
495
|
+
if (existsSync(resolve(providerRoot, "deploy.ts"))) {
|
|
496
|
+
details.push(
|
|
497
|
+
`WARN ${LEGACY_DEPLOY_FILE_RULE}: deploy.ts is a legacy deployment surface; run \`apifuse migrate-deployment .\` to move its non-default values into defineProvider({ deployment }) and delete the file.`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
// A `deployment` extra on `export default { ...provider, deployment }` is
|
|
501
|
+
// indistinguishable from a declaration key once the module is loaded, so
|
|
502
|
+
// inspect the source. The inspector needs the TypeScript compiler (a
|
|
503
|
+
// devDependency of provider repositories); without it this rule is skipped.
|
|
504
|
+
const indexPath = resolve(providerRoot, "index.ts");
|
|
505
|
+
if (existsSync(indexPath)) {
|
|
506
|
+
try {
|
|
507
|
+
const { findSpreadExportDeploymentProperty } = await import(
|
|
508
|
+
"../src/cli/migrate-deployment-intent.js"
|
|
509
|
+
);
|
|
510
|
+
const spread = findSpreadExportDeploymentProperty(readFileSync(indexPath, "utf8"), indexPath);
|
|
511
|
+
if (spread !== undefined) {
|
|
512
|
+
details.push(
|
|
513
|
+
`WARN ${SPREAD_EXPORT_DEPLOYMENT_RULE}: index.ts:${spread.line} attaches \`deployment\` to a spread default export; the only authored surface is the \`deployment\` key inside defineProvider({...}). Run \`apifuse migrate-deployment .\` to hoist it.`,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
} catch {
|
|
517
|
+
// TypeScript unavailable: the source-shape rule cannot run here.
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (provider?.deployment !== undefined) {
|
|
521
|
+
for (const redundant of redundantDeploymentDefaults(provider.deployment)) {
|
|
522
|
+
details.push(`WARN ${REDUNDANT_DEPLOYMENT_DEFAULT_RULE}: ${redundant.message}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
message: DEPLOYMENT_INTENT_CHECK_MESSAGE,
|
|
527
|
+
// Warning-only during the fleet migration window.
|
|
528
|
+
passed: true,
|
|
529
|
+
details,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
451
533
|
function checkDockerfile(dockerfilePath: string): CheckResult {
|
|
452
534
|
return {
|
|
453
535
|
message: "Dockerfile exists",
|