@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apifuse/provider-sdk",
3
- "version": "2.1.0-beta.2",
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",
@@ -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: ["bun run check", "bun run test"],
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,
@@ -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
- if (error.code === "NOT_FOUND" || error.code === "NO_DATA") {
273
- return 404;
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;