@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.3
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 +9 -0
- package/CHANGELOG.md +7 -0
- package/README.md +13 -0
- package/SUBMISSION.md +86 -0
- package/bin/apifuse-pack-check.ts +8 -0
- package/bin/apifuse-pack-smoke.ts +6 -0
- package/bin/apifuse-submit-check.ts +880 -0
- package/package.json +3 -2
- package/src/cli/commands.ts +23 -0
- package/src/cli/create.ts +7 -1
- package/src/cli/templates/provider/README.md.tpl +42 -0
- package/src/define.ts +91 -0
- package/src/index.ts +3 -0
- package/src/server/serve.ts +12 -3
- package/src/types.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apifuse/provider-sdk",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "ApiFuse Provider SDK — Build providers with zero architectural constraints",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"bin",
|
|
20
20
|
"README.md",
|
|
21
21
|
"AUTHORING.md",
|
|
22
|
-
"CHANGELOG.md"
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"SUBMISSION.md"
|
|
23
24
|
],
|
|
24
25
|
"keywords": [
|
|
25
26
|
"apifuse",
|
package/src/cli/commands.ts
CHANGED
|
@@ -2,6 +2,8 @@ export type ApifuseCommandName =
|
|
|
2
2
|
| "create"
|
|
3
3
|
| "dev"
|
|
4
4
|
| "check"
|
|
5
|
+
| "submit-check"
|
|
6
|
+
| "bounty-check"
|
|
5
7
|
| "record"
|
|
6
8
|
| "test"
|
|
7
9
|
| "perf";
|
|
@@ -46,6 +48,26 @@ export const COMMAND_MANIFEST: Record<
|
|
|
46
48
|
examples: ["apifuse check .", "apifuse check providers/airkorea"],
|
|
47
49
|
modulePath: "./apifuse-check",
|
|
48
50
|
},
|
|
51
|
+
"submit-check": {
|
|
52
|
+
name: "submit-check",
|
|
53
|
+
summary:
|
|
54
|
+
"Score provider bounty submission readiness and emit checklist evidence.",
|
|
55
|
+
usage:
|
|
56
|
+
"apifuse submit-check [path] [--tier bronze|silver|gold|diamond] [--json] [--markdown <path>] [--smoke-note <text>]",
|
|
57
|
+
examples: [
|
|
58
|
+
"apifuse submit-check .",
|
|
59
|
+
"apifuse submit-check . --tier silver --markdown submission-report.md",
|
|
60
|
+
],
|
|
61
|
+
modulePath: "./apifuse-submit-check",
|
|
62
|
+
},
|
|
63
|
+
"bounty-check": {
|
|
64
|
+
name: "bounty-check",
|
|
65
|
+
summary: "Alias for submit-check.",
|
|
66
|
+
usage:
|
|
67
|
+
"apifuse bounty-check [path] [--tier bronze|silver|gold|diamond] [--json] [--markdown <path>] [--smoke-note <text>]",
|
|
68
|
+
examples: ["apifuse bounty-check . --markdown submission-report.md"],
|
|
69
|
+
modulePath: "./apifuse-submit-check",
|
|
70
|
+
},
|
|
49
71
|
record: {
|
|
50
72
|
name: "record",
|
|
51
73
|
summary: "Call a provider operation and capture upstream raw fixture data.",
|
|
@@ -81,6 +103,7 @@ export const COMMAND_ORDER: ApifuseCommandName[] = [
|
|
|
81
103
|
"create",
|
|
82
104
|
"dev",
|
|
83
105
|
"check",
|
|
106
|
+
"submit-check",
|
|
84
107
|
"record",
|
|
85
108
|
"test",
|
|
86
109
|
"perf",
|
package/src/cli/create.ts
CHANGED
|
@@ -538,7 +538,11 @@ export async function buildProviderCreatePlan(
|
|
|
538
538
|
packageName,
|
|
539
539
|
preset: options.preset,
|
|
540
540
|
providerRoot,
|
|
541
|
-
validationCommands: [
|
|
541
|
+
validationCommands: [
|
|
542
|
+
"bun run check",
|
|
543
|
+
"bun run submit-check",
|
|
544
|
+
"bun run test",
|
|
545
|
+
],
|
|
542
546
|
workspaceRoot: resolvedWorkspaceRoot,
|
|
543
547
|
};
|
|
544
548
|
}
|
|
@@ -568,6 +572,8 @@ function renderPackageJson(input: {
|
|
|
568
572
|
scripts: {
|
|
569
573
|
dev: "apifuse dev .",
|
|
570
574
|
check: "apifuse check .",
|
|
575
|
+
"submit-check":
|
|
576
|
+
"apifuse submit-check . --markdown submission-report.md",
|
|
571
577
|
test: "apifuse test .",
|
|
572
578
|
record: "apifuse record .",
|
|
573
579
|
"perf:sample": "apifuse perf . --operation ping --runs 3",
|
|
@@ -8,6 +8,48 @@ Generated with `apifuse create`.
|
|
|
8
8
|
bun run dev
|
|
9
9
|
bun run check
|
|
10
10
|
bun run test
|
|
11
|
+
bun run submit-check
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Pre-submission report
|
|
16
|
+
|
|
17
|
+
Before posting bounty evidence, run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun run submit-check
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This writes `submission-report.md` with a review-readiness score, blockers,
|
|
24
|
+
warnings, health coverage notes, fixture/schema evidence, and remediation. A
|
|
25
|
+
score is not a payout guarantee; blockers must be fixed before maintainer
|
|
26
|
+
review. The generated `ping` starter intentionally warns until you replace it
|
|
27
|
+
with real upstream-backed Operations. The full public-only checklist is shipped
|
|
28
|
+
in `node_modules/@apifuse/provider-sdk/SUBMISSION.md`.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Operation guide
|
|
32
|
+
|
|
33
|
+
### Parameters
|
|
34
|
+
|
|
35
|
+
Starter `ping` accepts `{ "value": string }`. Replace this section with each
|
|
36
|
+
real operation's input schema, required fields, formats, limits, and examples
|
|
37
|
+
before submitting bounty evidence.
|
|
38
|
+
|
|
39
|
+
### Response
|
|
40
|
+
|
|
41
|
+
Starter `ping` returns `{ "ok": boolean, "message": string }`. Replace this
|
|
42
|
+
section with the normalized response fields, units, enum values, pagination,
|
|
43
|
+
and upstream caveats for each real operation.
|
|
44
|
+
|
|
45
|
+
### Example
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"requestId": "req_local_ping",
|
|
50
|
+
"input": { "value": "hello" },
|
|
51
|
+
"headers": {}
|
|
52
|
+
}
|
|
11
53
|
```
|
|
12
54
|
|
|
13
55
|
## Provider server contract
|
package/src/define.ts
CHANGED
|
@@ -185,6 +185,96 @@ function validateOperationIds(
|
|
|
185
185
|
);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
const OPERATION_CONTRACT_VERSION_REGEX =
|
|
189
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
190
|
+
const VALID_OPERATION_LIFECYCLES = [
|
|
191
|
+
"stable",
|
|
192
|
+
"beta",
|
|
193
|
+
"deprecated",
|
|
194
|
+
"removed",
|
|
195
|
+
] as const;
|
|
196
|
+
|
|
197
|
+
function assertNonEmptyString(
|
|
198
|
+
value: unknown,
|
|
199
|
+
field: string,
|
|
200
|
+
providerId: string,
|
|
201
|
+
operationName: string,
|
|
202
|
+
): asserts value is string {
|
|
203
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
204
|
+
throw new ValidationError(
|
|
205
|
+
`Provider "${providerId}" operation "${operationName}" has invalid ${field}: must be a non-empty string.`,
|
|
206
|
+
{ fix: `Set ${field} to a non-empty customer-facing value.` },
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function validateOperationContracts(
|
|
212
|
+
providerId: string,
|
|
213
|
+
operations: Record<string, ProviderOperation>,
|
|
214
|
+
): void {
|
|
215
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
216
|
+
const contract = operation.contract;
|
|
217
|
+
if (contract === undefined) continue;
|
|
218
|
+
if (!contract || typeof contract !== "object") {
|
|
219
|
+
throw new ValidationError(
|
|
220
|
+
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.contract: must be an object.`,
|
|
221
|
+
{
|
|
222
|
+
fix: `Remove operations.${operationName}.contract or provide { version, lifecycle, deprecation }.`,
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (
|
|
227
|
+
contract.version !== undefined &&
|
|
228
|
+
(typeof contract.version !== "string" ||
|
|
229
|
+
!OPERATION_CONTRACT_VERSION_REGEX.test(contract.version))
|
|
230
|
+
) {
|
|
231
|
+
throw new ValidationError(
|
|
232
|
+
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.contract.version: expected semver major.minor.patch.`,
|
|
233
|
+
{ fix: `Use an operation contract version such as "1.0.0".` },
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (contract.lifecycle !== undefined) {
|
|
237
|
+
assertLiteralField(
|
|
238
|
+
contract.lifecycle,
|
|
239
|
+
`operations.${operationName}.contract.lifecycle`,
|
|
240
|
+
VALID_OPERATION_LIFECYCLES,
|
|
241
|
+
providerId,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
contract.lifecycle === "deprecated" ||
|
|
246
|
+
contract.lifecycle === "removed"
|
|
247
|
+
) {
|
|
248
|
+
if (!contract.deprecation || typeof contract.deprecation !== "object") {
|
|
249
|
+
throw new ValidationError(
|
|
250
|
+
`Provider "${providerId}" operation "${operationName}" is ${contract.lifecycle} but lacks operations.${operationName}.contract.deprecation metadata.`,
|
|
251
|
+
{
|
|
252
|
+
fix: `Add announcedAt, removalAfter, and migrationGuide to operations.${operationName}.contract.deprecation.`,
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
assertNonEmptyString(
|
|
257
|
+
contract.deprecation.announcedAt,
|
|
258
|
+
`operations.${operationName}.contract.deprecation.announcedAt`,
|
|
259
|
+
providerId,
|
|
260
|
+
operationName,
|
|
261
|
+
);
|
|
262
|
+
assertNonEmptyString(
|
|
263
|
+
contract.deprecation.removalAfter,
|
|
264
|
+
`operations.${operationName}.contract.deprecation.removalAfter`,
|
|
265
|
+
providerId,
|
|
266
|
+
operationName,
|
|
267
|
+
);
|
|
268
|
+
assertNonEmptyString(
|
|
269
|
+
contract.deprecation.migrationGuide,
|
|
270
|
+
`operations.${operationName}.contract.deprecation.migrationGuide`,
|
|
271
|
+
providerId,
|
|
272
|
+
operationName,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
188
278
|
function validateOperationAnnotations(
|
|
189
279
|
providerId: string,
|
|
190
280
|
operations: Record<string, ProviderOperation>,
|
|
@@ -622,6 +712,7 @@ export function defineProvider<
|
|
|
622
712
|
);
|
|
623
713
|
validateOperationIds(config.id, config.operations);
|
|
624
714
|
validateOperationAnnotations(config.id, config.operations);
|
|
715
|
+
validateOperationContracts(config.id, config.operations);
|
|
625
716
|
validateOperationHealthChecks(config.id, config.operations);
|
|
626
717
|
validateProviderHealthMonitor(config.id, config.healthMonitor);
|
|
627
718
|
validateOperationFixtures(config.id, config.operations);
|
package/src/index.ts
CHANGED
|
@@ -75,10 +75,13 @@ export type {
|
|
|
75
75
|
HttpResponse,
|
|
76
76
|
InferSchemaOutput,
|
|
77
77
|
OperationAnnotations,
|
|
78
|
+
OperationContractMetadata,
|
|
78
79
|
OperationDefinition,
|
|
80
|
+
OperationDeprecationMetadata,
|
|
79
81
|
OperationDocMeta,
|
|
80
82
|
OperationErrorCode,
|
|
81
83
|
OperationInputExample,
|
|
84
|
+
OperationLifecycle,
|
|
82
85
|
OperationRelationships,
|
|
83
86
|
ProbeInterval,
|
|
84
87
|
ProviderContext,
|
package/src/server/serve.ts
CHANGED
|
@@ -259,7 +259,7 @@ function publicProviderErrorMessage(error: ProviderError): string {
|
|
|
259
259
|
return error.message;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
function toStatusCode(error: unknown): 400 | 404 | 500 | 502 | 504 {
|
|
262
|
+
function toStatusCode(error: unknown): 400 | 404 | 429 | 500 | 502 | 504 {
|
|
263
263
|
if (error instanceof z.ZodError) {
|
|
264
264
|
return 400;
|
|
265
265
|
}
|
|
@@ -269,8 +269,17 @@ function toStatusCode(error: unknown): 400 | 404 | 500 | 502 | 504 {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
if (error instanceof ProviderError) {
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
switch (error.code) {
|
|
273
|
+
case "NOT_FOUND":
|
|
274
|
+
case "not_found":
|
|
275
|
+
case "NO_DATA":
|
|
276
|
+
return 404;
|
|
277
|
+
case "RATE_LIMITED":
|
|
278
|
+
case "UPSTREAM_RATE_LIMIT":
|
|
279
|
+
case "LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR":
|
|
280
|
+
return 429;
|
|
281
|
+
case "UPSTREAM_ERROR":
|
|
282
|
+
return 502;
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
return 400;
|
package/src/types.ts
CHANGED
|
@@ -537,6 +537,25 @@ export interface ContextDeclaration {
|
|
|
537
537
|
keys: string[];
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
export type OperationLifecycle = "stable" | "beta" | "deprecated" | "removed";
|
|
541
|
+
|
|
542
|
+
export interface OperationDeprecationMetadata {
|
|
543
|
+
announcedAt: string;
|
|
544
|
+
removalAfter: string;
|
|
545
|
+
replacement?: string;
|
|
546
|
+
migrationGuide: string;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export interface OperationContractMetadata {
|
|
550
|
+
/**
|
|
551
|
+
* Callable operation contract version. Defaults to 1.0.0 for the clean
|
|
552
|
+
* pre-GA baseline; it intentionally does not fall back to provider.version.
|
|
553
|
+
*/
|
|
554
|
+
version?: string;
|
|
555
|
+
lifecycle?: OperationLifecycle;
|
|
556
|
+
deprecation?: OperationDeprecationMetadata;
|
|
557
|
+
}
|
|
558
|
+
|
|
540
559
|
export interface OperationDefinition<
|
|
541
560
|
TInput extends SchemaLike = SchemaLike,
|
|
542
561
|
TOutput extends SchemaLike = SchemaLike,
|
|
@@ -548,6 +567,7 @@ export interface OperationDefinition<
|
|
|
548
567
|
derivations?: Record<string, string>;
|
|
549
568
|
inputExamples?: OperationInputExample[];
|
|
550
569
|
annotations?: OperationAnnotations;
|
|
570
|
+
contract?: OperationContractMetadata;
|
|
551
571
|
tags?: string[];
|
|
552
572
|
relatedOperations?: OperationRelationships;
|
|
553
573
|
input: TInput;
|