@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.5
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 +53 -0
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-check.ts +18 -58
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +23 -61
- package/bin/apifuse-submit-check.ts +266 -34
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -18
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +8 -18
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +19 -42
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +4 -6
- package/dist/define.d.ts +8 -1
- package/dist/define.js +57 -115
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -3
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +43 -42
- package/dist/index.js +38 -37
- package/dist/lint.js +8 -15
- package/dist/provider.d.ts +11 -11
- package/dist/provider.js +9 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +3 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +15 -29
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +12 -34
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +2 -2
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +5 -5
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +5 -15
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +3 -5
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +1 -1
- package/dist/runtime/proxy-telemetry.js +16 -48
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +3 -3
- package/dist/runtime/stealth.d.ts +2 -2
- package/dist/runtime/stealth.js +6 -6
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +104 -3
- package/dist/server/self-test.js +673 -115
- package/dist/server/serve.d.ts +3 -3
- package/dist/server/serve.js +37 -49
- package/dist/server/types.js +2 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +7 -14
- package/dist/types.d.ts +57 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +6 -4
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +28 -86
- package/src/ceremonies/index.ts +24 -78
- package/src/config/loader.ts +58 -176
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +12 -28
- package/src/define.ts +174 -495
- package/src/dev.ts +4 -9
- package/src/errors.ts +4 -11
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +44 -42
- package/src/lint.ts +89 -159
- package/src/provider.ts +12 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +5 -7
- package/src/runtime/browser.ts +64 -187
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +55 -129
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +9 -19
- package/src/runtime/http.ts +7 -7
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +19 -58
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +17 -38
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +23 -72
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/state.ts +30 -109
- package/src/runtime/stealth.ts +8 -8
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +12 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +857 -132
- package/src/server/serve.ts +89 -238
- package/src/server/types.ts +6 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +19 -67
- package/src/types.ts +58 -0
- package/src/user-input.ts +118 -0
package/AUTHORING.md
CHANGED
|
@@ -18,6 +18,59 @@
|
|
|
18
18
|
|
|
19
19
|
Provider code is the declaration input to the internal platform registry. The public SDK owns provider authoring/runtime ergonomics; internal docs, deploy, and discovery projections are built downstream from those declarations. `bun run lint:providers` enforces provider authoring standards.
|
|
20
20
|
|
|
21
|
+
### User-input round-trips: never dead-end (`needs_input`)
|
|
22
|
+
|
|
23
|
+
A mutation MUST NOT fail for a problem the end user can resolve by choosing among live options (a required menu/course selection, a form question, a stale-but-recoverable state token). Throwing an error there strands the consuming agent: consumer error-shaping layers routinely strip error metadata, and a model that only sees "error" narrates failure to the user instead of relaying the choice.
|
|
24
|
+
|
|
25
|
+
Return the official success-shaped contract from `src/user-input.ts` instead (`ProviderNeedsInputPayload`, guard `isProviderNeedsInputPayload`):
|
|
26
|
+
|
|
27
|
+
- `status: "needs_input"` plus `required_selections` — only the still-pending questions, with human-readable `label`s and `valid_options` the agent relays verbatim. The agent never chooses for the user; anything with no real choice (agreement checkboxes, single-option required groups) is the provider's job to auto-answer.
|
|
28
|
+
- `selected_options` — selections already settled, echoed so the retry keeps them (copy them back and add the user's new answers).
|
|
29
|
+
- a freshly minted provider state token in the same response, so the retry never races an expired token.
|
|
30
|
+
|
|
31
|
+
No retry templates, next-action routing, or other agent choreography: provider payloads carry upstream-backed data only, and the consumer owns how the ask is phrased and how the retry call is shaped.
|
|
32
|
+
|
|
33
|
+
Declare the union in the operation `output` schema (`z.union([CreatedSchema, NeedsInputSchema])`). Reserve hard errors for genuinely unrecoverable flows (payment-gated, unsupported input kinds) and state the concrete reason in the error `message` itself, not only in `details`. Reference implementation: `providers/catchtable` `reserve` in the platform monorepo.
|
|
34
|
+
|
|
35
|
+
### Attempt tokens: the server carries the decisions
|
|
36
|
+
|
|
37
|
+
When a mutation needs more than one user decision (or one decision plus a
|
|
38
|
+
final go/no-go), do not make the agent re-send accumulated state across
|
|
39
|
+
rounds — weak models drop or corrupt it. Split the operation into a
|
|
40
|
+
**prepare/confirm pair** driven by a server-held attempt record:
|
|
41
|
+
|
|
42
|
+
- The prepare operation is non-destructive. A start call takes only the
|
|
43
|
+
scalar intent fields; every response returns a fresh `attempt_token`
|
|
44
|
+
referencing a server-side record (`ctx.choice.issue` with
|
|
45
|
+
`storage.mode: "server"`) that stores every settled decision. Continue
|
|
46
|
+
calls take `attempt_token` plus only the NEW answers.
|
|
47
|
+
- `needs_input` rounds list only the still-pending selections; settled
|
|
48
|
+
decisions may ride along in a display-only field but are never re-sent.
|
|
49
|
+
- When nothing is pending, the prepare operation returns `status: "ready"`
|
|
50
|
+
with a human-readable summary — the consumer's user-facing confirmation.
|
|
51
|
+
- The confirm operation is the only mutation and takes exactly
|
|
52
|
+
`{attempt_token}`. It re-validates everything live before executing and
|
|
53
|
+
returns `needs_input` (fresh token) instead of proceeding when upstream
|
|
54
|
+
drift invalidates a stored decision — never substitute a different option
|
|
55
|
+
for what the user picked.
|
|
56
|
+
- Expired or foreign tokens fail factually (nothing happened; start a new
|
|
57
|
+
attempt with the scalar fields) — no answer salvage from a dead token.
|
|
58
|
+
- **The provider must enforce consumption itself.** `ctx.choice` server
|
|
59
|
+
storage keeps tokens parseable until TTL — `parse` does not invalidate
|
|
60
|
+
them, so a confirm handler that only parses can be replayed into a second
|
|
61
|
+
booking or payment. After a successful execution, record the result under
|
|
62
|
+
the token's digest in `ctx.state` and make replays idempotent: a repeated
|
|
63
|
+
confirm returns the original created payload without touching upstream,
|
|
64
|
+
and later prepare rounds on the consumed token fail factually with the
|
|
65
|
+
existing reference. Record the result only after upstream success, so an
|
|
66
|
+
interrupted confirm stays retryable.
|
|
67
|
+
|
|
68
|
+
The invariant behind all of it: complex flow state is the system's job, not
|
|
69
|
+
the model's. The model carries exactly one opaque key between calls.
|
|
70
|
+
Reference implementations: `providers/catchtable` `reserve`/`reserve-confirm`
|
|
71
|
+
(including the consume-on-success guard) and `providers/modu-parking`
|
|
72
|
+
payment state tokens in the platform monorepo.
|
|
73
|
+
|
|
21
74
|
### Description template
|
|
22
75
|
|
|
23
76
|
Every operation `description` MUST be at least 150 characters and follow this structure:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.5
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit 82fa14e99a9af7edd44e3196aa3f4e87b4699edf.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.4
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 73f2c6ec429c2fbce8ac458a67111e4844b99178.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.3
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit 74e8e18b502dd9b02dbf0d3e702f917570312fc0.
|
package/bin/apifuse-check.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
-
import type { ProviderDefinition } from "../src";
|
|
10
|
-
import { lintProvider, type ProviderLintMode } from "../src/lint";
|
|
11
|
-
import { safeParseSchemaSync } from "../src/schema";
|
|
9
|
+
import type { ProviderDefinition } from "../src/index.js";
|
|
10
|
+
import { lintProvider, type ProviderLintMode } from "../src/lint.js";
|
|
11
|
+
import { safeParseSchemaSync } from "../src/schema.js";
|
|
12
12
|
|
|
13
13
|
const HELP_TEXT = `Usage: apifuse check [path]
|
|
14
14
|
Example: apifuse check providers/korea-air-quality
|
|
@@ -24,9 +24,7 @@ export type RunChecksOptions = {
|
|
|
24
24
|
lintMode?: ProviderLintMode;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
type SafeParseResult =
|
|
28
|
-
| { success: true; data: unknown }
|
|
29
|
-
| { success: false; error: unknown };
|
|
27
|
+
type SafeParseResult = { success: true; data: unknown } | { success: false; error: unknown };
|
|
30
28
|
|
|
31
29
|
export async function main() {
|
|
32
30
|
const args = normalizeArgs(process.argv.slice(2));
|
|
@@ -142,17 +140,9 @@ function isScannableProviderSourceFile(relativePath: string): boolean {
|
|
|
142
140
|
);
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
function collectProviderSourceFiles(
|
|
146
|
-
providerRoot: string,
|
|
147
|
-
): Record<string, string> {
|
|
143
|
+
function collectProviderSourceFiles(providerRoot: string): Record<string, string> {
|
|
148
144
|
const sources: Record<string, string> = {};
|
|
149
|
-
const skipDirectories = new Set([
|
|
150
|
-
".git",
|
|
151
|
-
"node_modules",
|
|
152
|
-
"dist",
|
|
153
|
-
"build",
|
|
154
|
-
".next",
|
|
155
|
-
]);
|
|
145
|
+
const skipDirectories = new Set([".git", "node_modules", "dist", "build", ".next"]);
|
|
156
146
|
const visit = (directory: string) => {
|
|
157
147
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
158
148
|
const path = resolve(directory, entry.name);
|
|
@@ -162,10 +152,7 @@ function collectProviderSourceFiles(
|
|
|
162
152
|
}
|
|
163
153
|
continue;
|
|
164
154
|
}
|
|
165
|
-
if (
|
|
166
|
-
!entry.isFile() ||
|
|
167
|
-
!isScannableProviderSourceFile(path.slice(providerRoot.length + 1))
|
|
168
|
-
) {
|
|
155
|
+
if (!entry.isFile() || !isScannableProviderSourceFile(path.slice(providerRoot.length + 1))) {
|
|
169
156
|
continue;
|
|
170
157
|
}
|
|
171
158
|
sources[path.slice(providerRoot.length + 1)] = readFileSync(path, "utf8");
|
|
@@ -175,10 +162,7 @@ function collectProviderSourceFiles(
|
|
|
175
162
|
return sources;
|
|
176
163
|
}
|
|
177
164
|
|
|
178
|
-
function checkIndex(
|
|
179
|
-
indexPath: string,
|
|
180
|
-
provider: ProviderDefinition | undefined,
|
|
181
|
-
): CheckResult {
|
|
165
|
+
function checkIndex(indexPath: string, provider: ProviderDefinition | undefined): CheckResult {
|
|
182
166
|
if (!existsSync(indexPath)) {
|
|
183
167
|
return {
|
|
184
168
|
message: "index.ts exists and exports default defineProvider",
|
|
@@ -200,9 +184,7 @@ function checkIndex(
|
|
|
200
184
|
};
|
|
201
185
|
}
|
|
202
186
|
|
|
203
|
-
function checkOperations(
|
|
204
|
-
provider: ProviderDefinition | undefined,
|
|
205
|
-
): CheckResult {
|
|
187
|
+
function checkOperations(provider: ProviderDefinition | undefined): CheckResult {
|
|
206
188
|
if (!provider) {
|
|
207
189
|
return {
|
|
208
190
|
message: "All operations have handler, input, output",
|
|
@@ -277,20 +259,14 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
|
|
|
277
259
|
continue;
|
|
278
260
|
}
|
|
279
261
|
|
|
280
|
-
const requestResult = parseFixture(
|
|
281
|
-
operation.input,
|
|
282
|
-
operation.fixtures.request,
|
|
283
|
-
);
|
|
262
|
+
const requestResult = parseFixture(operation.input, operation.fixtures.request);
|
|
284
263
|
if (!requestResult.success) {
|
|
285
264
|
failures.push(
|
|
286
265
|
`${operationId}: request fixture invalid (${formatSchemaError(requestResult.error)})`,
|
|
287
266
|
);
|
|
288
267
|
}
|
|
289
268
|
|
|
290
|
-
const responseResult = parseFixture(
|
|
291
|
-
operation.output,
|
|
292
|
-
operation.fixtures.response,
|
|
293
|
-
);
|
|
269
|
+
const responseResult = parseFixture(operation.output, operation.fixtures.response);
|
|
294
270
|
if (!responseResult.success) {
|
|
295
271
|
failures.push(
|
|
296
272
|
`${operationId}: response fixture invalid (${formatSchemaError(responseResult.error)})`,
|
|
@@ -317,13 +293,8 @@ function checkAuthoringLint(
|
|
|
317
293
|
};
|
|
318
294
|
}
|
|
319
295
|
|
|
320
|
-
const diagnostics = lintProvider(
|
|
321
|
-
|
|
322
|
-
{ mode: lintMode },
|
|
323
|
-
);
|
|
324
|
-
const errors = diagnostics.filter(
|
|
325
|
-
(diagnostic) => diagnostic.level === "error",
|
|
326
|
-
);
|
|
296
|
+
const diagnostics = lintProvider({ ...provider, providerSourceFiles }, { mode: lintMode });
|
|
297
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.level === "error");
|
|
327
298
|
const details = diagnostics.map((diagnostic) => {
|
|
328
299
|
const field = diagnostic.field ? `${diagnostic.field}: ` : "";
|
|
329
300
|
return `${diagnostic.level.toUpperCase()} ${diagnostic.rule} ${field}${diagnostic.message}`;
|
|
@@ -336,9 +307,7 @@ function checkAuthoringLint(
|
|
|
336
307
|
};
|
|
337
308
|
}
|
|
338
309
|
|
|
339
|
-
function checkProviderMetadata(
|
|
340
|
-
provider: ProviderDefinition | undefined,
|
|
341
|
-
): CheckResult {
|
|
310
|
+
function checkProviderMetadata(provider: ProviderDefinition | undefined): CheckResult {
|
|
342
311
|
if (!provider) {
|
|
343
312
|
return {
|
|
344
313
|
message: "Provider metadata is declared in defineProvider",
|
|
@@ -425,18 +394,12 @@ function checkPackageJson(packageJsonPath: string): CheckResult {
|
|
|
425
394
|
}
|
|
426
395
|
}
|
|
427
396
|
|
|
428
|
-
function assertProviderDefinition(
|
|
429
|
-
value: unknown,
|
|
430
|
-
): ProviderDefinition | undefined {
|
|
397
|
+
function assertProviderDefinition(value: unknown): ProviderDefinition | undefined {
|
|
431
398
|
return isProviderDefinition(value) ? value : undefined;
|
|
432
399
|
}
|
|
433
400
|
|
|
434
401
|
function isProviderDefinition(value: unknown): value is ProviderDefinition {
|
|
435
|
-
if (
|
|
436
|
-
!isRecord(value) ||
|
|
437
|
-
!isRecord(value.meta) ||
|
|
438
|
-
!isRecord(value.operations)
|
|
439
|
-
) {
|
|
402
|
+
if (!isRecord(value) || !isRecord(value.meta) || !isRecord(value.operations)) {
|
|
440
403
|
return false;
|
|
441
404
|
}
|
|
442
405
|
|
|
@@ -457,8 +420,7 @@ function hasSchemaParser(value: unknown): boolean {
|
|
|
457
420
|
return (
|
|
458
421
|
isRecord(value) &&
|
|
459
422
|
(typeof value.safeParse === "function" ||
|
|
460
|
-
(isRecord(value["~standard"]) &&
|
|
461
|
-
typeof value["~standard"].validate === "function"))
|
|
423
|
+
(isRecord(value["~standard"]) && typeof value["~standard"].validate === "function"))
|
|
462
424
|
);
|
|
463
425
|
}
|
|
464
426
|
|
|
@@ -470,9 +432,7 @@ function formatSchemaError(error: unknown): string {
|
|
|
470
432
|
if (Array.isArray(error)) {
|
|
471
433
|
return error
|
|
472
434
|
.map((issue) =>
|
|
473
|
-
isRecord(issue) && typeof issue.message === "string"
|
|
474
|
-
? issue.message
|
|
475
|
-
: String(issue),
|
|
435
|
+
isRecord(issue) && typeof issue.message === "string" ? issue.message : String(issue),
|
|
476
436
|
)
|
|
477
437
|
.join(", ");
|
|
478
438
|
}
|
package/bin/apifuse-create.ts
CHANGED
package/bin/apifuse-dev.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, relative, resolve } from "node:path";
|
|
5
|
-
import type { ProviderDefinition } from "../src";
|
|
5
|
+
import type { ProviderDefinition } from "../src/index.js";
|
|
6
6
|
import {
|
|
7
7
|
createBrowserClient,
|
|
8
8
|
createCredentialContext,
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
createSttClientFromEnv,
|
|
15
15
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
16
16
|
ProviderError,
|
|
17
|
-
} from "../src";
|
|
18
|
-
import { createMemoryProviderRuntimeState } from "../src/runtime/state";
|
|
19
|
-
import { createTraceContext } from "../src/runtime/trace";
|
|
20
|
-
import type { BrowserClient, ProviderContext } from "../src/types";
|
|
17
|
+
} from "../src/index.js";
|
|
18
|
+
import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
|
|
19
|
+
import { createTraceContext } from "../src/runtime/trace.js";
|
|
20
|
+
import type { BrowserClient, ProviderContext } from "../src/types.js";
|
|
21
21
|
|
|
22
22
|
const HELP_TEXT = `Usage: apifuse dev [path]
|
|
23
23
|
Example: apifuse dev providers/korea-air-quality
|
|
@@ -33,12 +33,9 @@ export async function main() {
|
|
|
33
33
|
|
|
34
34
|
const providerPath = resolveProviderPath(args[0] ?? ".");
|
|
35
35
|
const providerModule = await import(resolve(providerPath, "index.ts"));
|
|
36
|
-
const provider = assertProviderDefinition(
|
|
37
|
-
providerModule.default,
|
|
38
|
-
providerPath,
|
|
39
|
-
);
|
|
36
|
+
const provider = assertProviderDefinition(providerModule.default, providerPath);
|
|
40
37
|
|
|
41
|
-
const { startDevServer } = await import("../src/dev");
|
|
38
|
+
const { startDevServer } = await import("../src/dev.js");
|
|
42
39
|
const port = Number(process.env.APIFUSE__RUNTIME__PORT) || 3900;
|
|
43
40
|
|
|
44
41
|
startDevServer(provider, { port });
|
|
@@ -57,8 +54,7 @@ export async function main() {
|
|
|
57
54
|
|
|
58
55
|
const firstOperation = Object.keys(provider.operations)[0];
|
|
59
56
|
if (firstOperation) {
|
|
60
|
-
const sampleInput =
|
|
61
|
-
provider.operations[firstOperation]?.fixtures?.request ?? {};
|
|
57
|
+
const sampleInput = provider.operations[firstOperation]?.fixtures?.request ?? {};
|
|
62
58
|
const sampleBody = JSON.stringify({
|
|
63
59
|
requestId: `req_local_${firstOperation}`,
|
|
64
60
|
input: sampleInput,
|
|
@@ -149,9 +145,7 @@ function renderHotReloadCommand(providerPath: string, port: number): string {
|
|
|
149
145
|
const devEntry = resolve(providerPath, "dev.ts");
|
|
150
146
|
if (existsSync(devEntry)) {
|
|
151
147
|
const relativeDevEntry = relative(process.cwd(), devEntry) || "dev.ts";
|
|
152
|
-
const portPrefix = process.env.APIFUSE__RUNTIME__PORT
|
|
153
|
-
? `APIFUSE__RUNTIME__PORT=${port} `
|
|
154
|
-
: "";
|
|
148
|
+
const portPrefix = process.env.APIFUSE__RUNTIME__PORT ? `APIFUSE__RUNTIME__PORT=${port} ` : "";
|
|
155
149
|
return `${portPrefix}bun --hot ${relativeDevEntry}`;
|
|
156
150
|
}
|
|
157
151
|
return "rerun `apifuse dev` after edits (no dev.ts entrypoint found)";
|
|
@@ -166,40 +160,28 @@ function createUnsupportedBrowserStub(): BrowserClient {
|
|
|
166
160
|
engine: "playwright-stealth",
|
|
167
161
|
async close() {},
|
|
168
162
|
async newPage() {
|
|
169
|
-
throw new ProviderError(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
fix: 'Set provider runtime to "browser" to use ctx.browser',
|
|
174
|
-
},
|
|
175
|
-
);
|
|
163
|
+
throw new ProviderError("Browser runtime is not enabled for this provider", {
|
|
164
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
165
|
+
fix: 'Set provider runtime to "browser" to use ctx.browser',
|
|
166
|
+
});
|
|
176
167
|
},
|
|
177
168
|
async rawPage() {
|
|
178
|
-
throw new ProviderError(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
fix: 'Set provider runtime to "browser" and APIFUSE__CDP_POOL__URL to use ctx.browser.rawPage',
|
|
183
|
-
},
|
|
184
|
-
);
|
|
169
|
+
throw new ProviderError("Browser runtime is not enabled for this provider", {
|
|
170
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
171
|
+
fix: 'Set provider runtime to "browser" and APIFUSE__CDP_POOL__URL to use ctx.browser.rawPage',
|
|
172
|
+
});
|
|
185
173
|
},
|
|
186
174
|
async withIsolatedContext() {
|
|
187
|
-
throw new ProviderError(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
fix: 'Set provider runtime to "browser" to use ctx.browser.withIsolatedContext',
|
|
192
|
-
},
|
|
193
|
-
);
|
|
175
|
+
throw new ProviderError("Browser runtime is not enabled for this provider", {
|
|
176
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
177
|
+
fix: 'Set provider runtime to "browser" to use ctx.browser.withIsolatedContext',
|
|
178
|
+
});
|
|
194
179
|
},
|
|
195
180
|
async solveChallenge() {
|
|
196
|
-
throw new ProviderError(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
fix: 'Set provider runtime to "browser" to use ctx.browser.solveChallenge',
|
|
201
|
-
},
|
|
202
|
-
);
|
|
181
|
+
throw new ProviderError("Browser runtime is not enabled for this provider", {
|
|
182
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
183
|
+
fix: 'Set provider runtime to "browser" to use ctx.browser.solveChallenge',
|
|
184
|
+
});
|
|
203
185
|
},
|
|
204
186
|
};
|
|
205
187
|
}
|
|
@@ -216,10 +198,7 @@ function createUnsupportedAuthStub() {
|
|
|
216
198
|
};
|
|
217
199
|
}
|
|
218
200
|
|
|
219
|
-
function assertProviderDefinition(
|
|
220
|
-
value: unknown,
|
|
221
|
-
providerPath: string,
|
|
222
|
-
): ProviderDefinition {
|
|
201
|
+
function assertProviderDefinition(value: unknown, providerPath: string): ProviderDefinition {
|
|
223
202
|
if (!isProviderDefinition(value)) {
|
|
224
203
|
throw new Error(
|
|
225
204
|
`Expected ${resolve(providerPath, "index.ts")} to export default defineProvider(...)`,
|
|
@@ -230,11 +209,7 @@ function assertProviderDefinition(
|
|
|
230
209
|
}
|
|
231
210
|
|
|
232
211
|
function isProviderDefinition(value: unknown): value is ProviderDefinition {
|
|
233
|
-
if (
|
|
234
|
-
!isRecord(value) ||
|
|
235
|
-
!isRecord(value.meta) ||
|
|
236
|
-
!isRecord(value.operations)
|
|
237
|
-
) {
|
|
212
|
+
if (!isRecord(value) || !isRecord(value.meta) || !isRecord(value.operations)) {
|
|
238
213
|
return false;
|
|
239
214
|
}
|
|
240
215
|
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
execFileSync,
|
|
6
|
-
spawn,
|
|
7
|
-
spawnSync,
|
|
8
|
-
} from "node:child_process";
|
|
9
|
-
import {
|
|
10
|
-
existsSync,
|
|
11
|
-
mkdirSync,
|
|
12
|
-
mkdtempSync,
|
|
13
|
-
readFileSync,
|
|
14
|
-
rmSync,
|
|
15
|
-
writeFileSync,
|
|
16
|
-
} from "node:fs";
|
|
3
|
+
import { type ChildProcess, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
5
|
import { createServer } from "node:net";
|
|
18
6
|
import { tmpdir } from "node:os";
|
|
19
7
|
import { join, resolve } from "node:path";
|
|
@@ -41,9 +29,7 @@ const PING_RESPONSE_SCHEMA = z.object({
|
|
|
41
29
|
|
|
42
30
|
const KEEP_TEMP = process.env.APIFUSE__PACK_SMOKE__KEEP_TEMP === "1";
|
|
43
31
|
|
|
44
|
-
const tempRoot = mkdtempSync(
|
|
45
|
-
join(tmpdir(), "apifuse-provider-sdk-pack-smoke-"),
|
|
46
|
-
);
|
|
32
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "apifuse-provider-sdk-pack-smoke-"));
|
|
47
33
|
const packDir = join(tempRoot, "pack");
|
|
48
34
|
const consumerDir = join(tempRoot, "consumer");
|
|
49
35
|
const externalWorkspaceDir = join(tempRoot, "external-workspace");
|
|
@@ -81,15 +67,7 @@ try {
|
|
|
81
67
|
|
|
82
68
|
run(
|
|
83
69
|
"bun",
|
|
84
|
-
[
|
|
85
|
-
cliBin,
|
|
86
|
-
"create",
|
|
87
|
-
"dx-smoke",
|
|
88
|
-
"--yes",
|
|
89
|
-
"--json",
|
|
90
|
-
"--sdk-specifier",
|
|
91
|
-
tarballSpecifier,
|
|
92
|
-
],
|
|
70
|
+
[cliBin, "create", "dx-smoke", "--yes", "--json", "--sdk-specifier", tarballSpecifier],
|
|
93
71
|
consumerDir,
|
|
94
72
|
);
|
|
95
73
|
|
|
@@ -99,11 +77,7 @@ try {
|
|
|
99
77
|
run("bun", ["run", "test"], generatedProviderDir);
|
|
100
78
|
assertGeneratedReadme(generatedProviderDir);
|
|
101
79
|
await smokeGeneratedDevServer(generatedProviderDir);
|
|
102
|
-
assertExternalWorkspaceTopology(
|
|
103
|
-
cliBin,
|
|
104
|
-
externalWorkspaceDir,
|
|
105
|
-
tarballSpecifier,
|
|
106
|
-
);
|
|
80
|
+
assertExternalWorkspaceTopology(cliBin, externalWorkspaceDir, tarballSpecifier);
|
|
107
81
|
|
|
108
82
|
console.log(
|
|
109
83
|
`Provider SDK packed-artifact smoke passed: ${tarballPath} -> ${generatedProviderDir}`,
|
|
@@ -148,15 +122,8 @@ function assertExternalWorkspaceTopology(
|
|
|
148
122
|
externalWorkspaceDir,
|
|
149
123
|
);
|
|
150
124
|
|
|
151
|
-
const generatedProviderDir = join(
|
|
152
|
-
|
|
153
|
-
"external-workspace-smoke",
|
|
154
|
-
);
|
|
155
|
-
const forbiddenProviderDir = join(
|
|
156
|
-
externalWorkspaceDir,
|
|
157
|
-
"providers",
|
|
158
|
-
"external-workspace-smoke",
|
|
159
|
-
);
|
|
125
|
+
const generatedProviderDir = join(externalWorkspaceDir, "external-workspace-smoke");
|
|
126
|
+
const forbiddenProviderDir = join(externalWorkspaceDir, "providers", "external-workspace-smoke");
|
|
160
127
|
if (!existsSync(generatedProviderDir)) {
|
|
161
128
|
throw new Error(
|
|
162
129
|
"Public create must generate a one-provider repository at <name>/ even when providers/ exists.",
|
|
@@ -168,9 +135,7 @@ function assertExternalWorkspaceTopology(
|
|
|
168
135
|
);
|
|
169
136
|
}
|
|
170
137
|
|
|
171
|
-
const packageJson = JSON.parse(
|
|
172
|
-
readFileSync(join(generatedProviderDir, "package.json"), "utf8"),
|
|
173
|
-
);
|
|
138
|
+
const packageJson = JSON.parse(readFileSync(join(generatedProviderDir, "package.json"), "utf8"));
|
|
174
139
|
const sdkDependency = packageJson?.dependencies?.["@apifuse/provider-sdk"];
|
|
175
140
|
if (sdkDependency !== tarballSpecifier) {
|
|
176
141
|
throw new Error(
|
|
@@ -178,9 +143,7 @@ function assertExternalWorkspaceTopology(
|
|
|
178
143
|
);
|
|
179
144
|
}
|
|
180
145
|
if (JSON.stringify(packageJson).includes("workspace:")) {
|
|
181
|
-
throw new Error(
|
|
182
|
-
"External bounty workspace scaffold must not contain workspace: dependencies.",
|
|
183
|
-
);
|
|
146
|
+
throw new Error("External bounty workspace scaffold must not contain workspace: dependencies.");
|
|
184
147
|
}
|
|
185
148
|
|
|
186
149
|
run("bun", ["install"], generatedProviderDir);
|
|
@@ -199,30 +162,20 @@ function assertExternalWorkspaceTopology(
|
|
|
199
162
|
},
|
|
200
163
|
);
|
|
201
164
|
if (monorepoAttempt.status === 0) {
|
|
202
|
-
throw new Error(
|
|
203
|
-
"--preset monorepo must reject outside the private APIFuse monorepo.",
|
|
204
|
-
);
|
|
165
|
+
throw new Error("--preset monorepo must reject outside the private APIFuse monorepo.");
|
|
205
166
|
}
|
|
206
167
|
const rejectionOutput = `${monorepoAttempt.stdout}\n${monorepoAttempt.stderr}`;
|
|
207
|
-
if (
|
|
208
|
-
!rejectionOutput.includes(
|
|
209
|
-
"Monorepo preset is internal to the APIFuse repository",
|
|
210
|
-
)
|
|
211
|
-
) {
|
|
168
|
+
if (!rejectionOutput.includes("Monorepo preset is internal to the APIFuse repository")) {
|
|
212
169
|
throw new Error(`Unexpected monorepo rejection output: ${rejectionOutput}`);
|
|
213
170
|
}
|
|
214
171
|
}
|
|
215
172
|
|
|
216
173
|
function packSdk(destination: string): { filename: string } {
|
|
217
|
-
const raw = execFileSync(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
encoding: "utf8",
|
|
223
|
-
stdio: ["ignore", "pipe", "inherit"],
|
|
224
|
-
},
|
|
225
|
-
);
|
|
174
|
+
const raw = execFileSync("npm", ["pack", "--json", "--pack-destination", destination], {
|
|
175
|
+
cwd: process.cwd(),
|
|
176
|
+
encoding: "utf8",
|
|
177
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
178
|
+
});
|
|
226
179
|
const parsed = PACK_RESULT_SCHEMA.parse(JSON.parse(raw));
|
|
227
180
|
const first = parsed[0];
|
|
228
181
|
if (!first) {
|
|
@@ -252,29 +205,19 @@ function run(command: string, args: string[], cwd: string): void {
|
|
|
252
205
|
function assertGeneratedReadme(providerDir: string): void {
|
|
253
206
|
const readme = readFileSync(join(providerDir, "README.md"), "utf8");
|
|
254
207
|
if (!readme.includes('"requestId":"req_local_ping"')) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
"Generated README is missing requestId in local smoke docs.",
|
|
257
|
-
);
|
|
208
|
+
throw new Error("Generated README is missing requestId in local smoke docs.");
|
|
258
209
|
}
|
|
259
210
|
if (readme.includes('"connection":null')) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
"Generated README must not document connection:null for no-auth local smoke.",
|
|
262
|
-
);
|
|
211
|
+
throw new Error("Generated README must not document connection:null for no-auth local smoke.");
|
|
263
212
|
}
|
|
264
213
|
if (!readme.includes("bunx playwright install chromium")) {
|
|
265
|
-
throw new Error(
|
|
266
|
-
"Generated README is missing browser runtime troubleshooting guidance.",
|
|
267
|
-
);
|
|
214
|
+
throw new Error("Generated README is missing browser runtime troubleshooting guidance.");
|
|
268
215
|
}
|
|
269
216
|
if (!readme.includes("impit")) {
|
|
270
|
-
throw new Error(
|
|
271
|
-
"Generated README is missing impit stealth runtime guidance.",
|
|
272
|
-
);
|
|
217
|
+
throw new Error("Generated README is missing impit stealth runtime guidance.");
|
|
273
218
|
}
|
|
274
219
|
if (!readme.includes("bun run submit-check")) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
"Generated README must document the submit-check pre-submission workflow.",
|
|
277
|
-
);
|
|
220
|
+
throw new Error("Generated README must document the submit-check pre-submission workflow.");
|
|
278
221
|
}
|
|
279
222
|
if (!readme.includes("bun run record -- --operation <operation>")) {
|
|
280
223
|
throw new Error(
|
|
@@ -368,9 +311,7 @@ async function waitForHttp(
|
|
|
368
311
|
|
|
369
312
|
while (Date.now() < deadline) {
|
|
370
313
|
if (server.exitCode !== null) {
|
|
371
|
-
throw new Error(
|
|
372
|
-
`Dev server exited early with code ${server.exitCode}\n${getOutput()}`,
|
|
373
|
-
);
|
|
314
|
+
throw new Error(`Dev server exited early with code ${server.exitCode}\n${getOutput()}`);
|
|
374
315
|
}
|
|
375
316
|
|
|
376
317
|
try {
|