@apifuse/provider-sdk 2.0.0-beta.1 → 2.1.0-beta.1

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.
Files changed (79) hide show
  1. package/AUTHORING.md +93 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +133 -28
  4. package/bin/apifuse-check.ts +78 -71
  5. package/bin/apifuse-create.ts +12 -0
  6. package/bin/apifuse-dev.ts +24 -61
  7. package/bin/apifuse-pack-check.ts +87 -0
  8. package/bin/apifuse-pack-smoke.ts +122 -0
  9. package/bin/apifuse-perf.ts +33 -32
  10. package/bin/apifuse-record.ts +17 -7
  11. package/bin/apifuse-test.ts +6 -4
  12. package/bin/apifuse.ts +36 -35
  13. package/package.json +29 -9
  14. package/src/ceremonies/index.ts +768 -0
  15. package/src/cli/commands.ts +87 -0
  16. package/src/cli/create.ts +845 -0
  17. package/src/cli/templates/provider/Dockerfile.tpl +7 -0
  18. package/src/cli/templates/provider/README.md.tpl +41 -0
  19. package/src/cli/templates/provider/dev.ts.tpl +5 -0
  20. package/src/cli/templates/provider/index.test.ts.tpl +13 -0
  21. package/src/cli/templates/provider/index.ts.tpl +58 -0
  22. package/src/cli/templates/provider/start.ts.tpl +5 -0
  23. package/src/config/loader.ts +61 -1
  24. package/src/define.ts +565 -41
  25. package/src/dev.ts +2 -6
  26. package/src/errors.ts +42 -0
  27. package/src/index.ts +44 -38
  28. package/src/lint.ts +574 -0
  29. package/src/provider.ts +13 -0
  30. package/src/runtime/auth-flow.ts +67 -0
  31. package/src/runtime/credential.ts +95 -0
  32. package/src/runtime/env.ts +13 -0
  33. package/src/runtime/executor.ts +13 -14
  34. package/src/runtime/http.ts +36 -12
  35. package/src/runtime/insights.ts +3 -3
  36. package/src/runtime/key-derivation.ts +122 -0
  37. package/src/runtime/keyring.ts +148 -0
  38. package/src/runtime/namespace.ts +33 -0
  39. package/src/runtime/prevalidate.ts +252 -0
  40. package/src/runtime/tls.ts +41 -17
  41. package/src/runtime/waterfall.ts +0 -1
  42. package/src/schema.ts +77 -0
  43. package/src/serve.ts +1 -664
  44. package/src/server/index.ts +22 -0
  45. package/src/server/serve.ts +624 -0
  46. package/src/server/types.ts +78 -0
  47. package/src/stealth/profiles.ts +10 -93
  48. package/src/testing/run.ts +391 -32
  49. package/src/types.ts +390 -41
  50. package/bin/apifuse-init.ts +0 -387
  51. package/src/__tests__/auth.test.ts +0 -396
  52. package/src/__tests__/browser-auth.test.ts +0 -180
  53. package/src/__tests__/browser.test.ts +0 -632
  54. package/src/__tests__/define.test.ts +0 -225
  55. package/src/__tests__/errors.test.ts +0 -69
  56. package/src/__tests__/executor.test.ts +0 -214
  57. package/src/__tests__/http.test.ts +0 -238
  58. package/src/__tests__/insights.test.ts +0 -210
  59. package/src/__tests__/instrumentation.test.ts +0 -290
  60. package/src/__tests__/otlp.test.ts +0 -141
  61. package/src/__tests__/perf.test.ts +0 -60
  62. package/src/__tests__/providers-yaml.test.ts +0 -135
  63. package/src/__tests__/proxy.test.ts +0 -359
  64. package/src/__tests__/recipes.test.ts +0 -36
  65. package/src/__tests__/serve.test.ts +0 -233
  66. package/src/__tests__/session.test.ts +0 -231
  67. package/src/__tests__/state.test.ts +0 -100
  68. package/src/__tests__/stealth.test.ts +0 -57
  69. package/src/__tests__/testing.test.ts +0 -97
  70. package/src/__tests__/tls.test.ts +0 -345
  71. package/src/__tests__/types.test.ts +0 -142
  72. package/src/__tests__/utils.test.ts +0 -62
  73. package/src/__tests__/waterfall.test.ts +0 -270
  74. package/src/config/providers-yaml.ts +0 -370
  75. package/src/index.test.ts +0 -1
  76. package/src/protocol.ts +0 -183
  77. package/src/runtime/auth.ts +0 -245
  78. package/src/runtime/session.ts +0 -573
  79. package/src/runtime/state.ts +0 -124
@@ -1,225 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { z } from "zod";
3
-
4
- import { defineProvider } from "../define";
5
- import { ProviderError, ValidationError } from "../errors";
6
- import type { ProviderContext } from "../types";
7
-
8
- const InputSchema = z.object({ id: z.string() });
9
- const OutputSchema = z.object({ name: z.string(), price: z.number() });
10
-
11
- const validConfig = {
12
- id: "coingecko-prices",
13
- version: "1.0.0",
14
- runtime: "standard" as const,
15
- meta: {
16
- displayName: "CoinGecko Prices",
17
- description: "Simple finance provider",
18
- category: "finance",
19
- tags: ["prices"],
20
- },
21
- operations: {
22
- prices: {
23
- input: InputSchema,
24
- output: OutputSchema,
25
- handler: async (_ctx: ProviderContext, input: unknown) => {
26
- const parsed = InputSchema.parse(input);
27
-
28
- return {
29
- name: parsed.id,
30
- price: 50_000,
31
- };
32
- },
33
- fixtures: {
34
- request: { id: "bitcoin" },
35
- response: { name: "Bitcoin", price: 50_000 },
36
- },
37
- },
38
- },
39
- };
40
-
41
- describe("defineProvider", () => {
42
- it("returns provider with top-level identity fields accessible", () => {
43
- const provider = defineProvider(validConfig);
44
-
45
- expect(provider.id).toBe("coingecko-prices");
46
- expect(provider.version).toBe("1.0.0");
47
- expect(provider.runtime).toBe("standard");
48
- expect(provider.meta.displayName).toBe("CoinGecko Prices");
49
- });
50
-
51
- it("preserves operation definitions", async () => {
52
- const provider = defineProvider(validConfig);
53
- await expect(
54
- provider.operations.prices.handler?.({} as never, { id: "bitcoin" }),
55
- ).resolves.toEqual({ name: "bitcoin", price: 50_000 });
56
- });
57
-
58
- it("throws ProviderError for invalid id format - uppercase", () => {
59
- expect(() =>
60
- defineProvider({
61
- ...validConfig,
62
- id: "CoinGecko",
63
- }),
64
- ).toThrow(ProviderError);
65
- });
66
-
67
- it("throws ProviderError for invalid id format - single word", () => {
68
- expect(() =>
69
- defineProvider({ ...validConfig, id: "coingecko" }),
70
- ).not.toThrow();
71
- });
72
-
73
- it("throws ProviderError for invalid id format - spaces", () => {
74
- expect(() =>
75
- defineProvider({
76
- ...validConfig,
77
- id: "coin gecko prices",
78
- }),
79
- ).toThrow(ProviderError);
80
- });
81
-
82
- it("throws ProviderError for invalid id format - underscore", () => {
83
- expect(() =>
84
- defineProvider({
85
- ...validConfig,
86
- id: "coingecko_prices",
87
- }),
88
- ).toThrow(ProviderError);
89
- });
90
-
91
- it("throws ValidationError when operation fixture.request does not match input schema", () => {
92
- const badConfig = {
93
- ...validConfig,
94
- operations: {
95
- ...validConfig.operations,
96
- prices: {
97
- ...validConfig.operations.prices,
98
- fixtures: {
99
- request: { wrong_field: "x" } as unknown as z.infer<
100
- typeof InputSchema
101
- >,
102
- response: validConfig.operations.prices.fixtures.response,
103
- },
104
- },
105
- },
106
- };
107
-
108
- expect(() => defineProvider(badConfig)).toThrow(ValidationError);
109
- });
110
-
111
- it("throws ValidationError when operation fixture.response does not match output schema", () => {
112
- const badConfig = {
113
- ...validConfig,
114
- operations: {
115
- ...validConfig.operations,
116
- prices: {
117
- ...validConfig.operations.prices,
118
- fixtures: {
119
- request: validConfig.operations.prices.fixtures.request,
120
- response: { wrong: true } as unknown as z.infer<
121
- typeof OutputSchema
122
- >,
123
- },
124
- },
125
- },
126
- };
127
-
128
- expect(() => defineProvider(badConfig)).toThrow(ValidationError);
129
- });
130
-
131
- it("ValidationError includes zodError for actionable debugging", () => {
132
- const badConfig = {
133
- ...validConfig,
134
- operations: {
135
- ...validConfig.operations,
136
- prices: {
137
- ...validConfig.operations.prices,
138
- fixtures: {
139
- request: { wrong_field: "x" } as unknown as z.infer<
140
- typeof InputSchema
141
- >,
142
- response: validConfig.operations.prices.fixtures.response,
143
- },
144
- },
145
- },
146
- };
147
-
148
- try {
149
- defineProvider(badConfig);
150
- } catch (error) {
151
- expect(error instanceof ValidationError).toBe(true);
152
- expect((error as ValidationError).zodError).toBeDefined();
153
- }
154
- });
155
-
156
- it("ProviderError has fix hint for invalid id", () => {
157
- try {
158
- defineProvider({ ...validConfig, id: "BAD_ID" });
159
- } catch (error) {
160
- expect(error instanceof ProviderError).toBe(true);
161
- expect((error as ProviderError).fix).toBeDefined();
162
- }
163
- });
164
-
165
- it("throws ProviderError when no operations defined", () => {
166
- expect(() => defineProvider({ ...validConfig, operations: {} })).toThrow(
167
- ProviderError,
168
- );
169
- });
170
-
171
- it("works without operation fixtures", () => {
172
- const noFixturesConfig = {
173
- ...validConfig,
174
- operations: {
175
- ...validConfig.operations,
176
- prices: {
177
- ...validConfig.operations.prices,
178
- fixtures: undefined,
179
- },
180
- },
181
- };
182
-
183
- const provider = defineProvider(noFixturesConfig);
184
-
185
- expect(provider.id).toBe("coingecko-prices");
186
- expect(provider.operations.prices.fixtures).toBeUndefined();
187
- });
188
-
189
- it("requires browser config when runtime is browser", () => {
190
- expect(() =>
191
- defineProvider({
192
- ...validConfig,
193
- runtime: "browser",
194
- }),
195
- ).toThrow(ProviderError);
196
- });
197
-
198
- it("rejects browser config when runtime is not browser", () => {
199
- expect(() =>
200
- defineProvider({
201
- ...validConfig,
202
- browser: { engine: "nodriver" },
203
- }),
204
- ).toThrow(ProviderError);
205
- });
206
-
207
- it("keeps operation schema inference usable for input parsing", () => {
208
- const provider = defineProvider(validConfig);
209
- const parsed = provider.operations.prices.input?.safeParse({
210
- id: "bitcoin",
211
- });
212
-
213
- expect(parsed?.success).toBe(true);
214
- });
215
-
216
- it("keeps operation schema inference usable for output parsing", () => {
217
- const provider = defineProvider(validConfig);
218
- const parsed = provider.operations.prices.output?.safeParse({
219
- name: "Bitcoin",
220
- price: 50_000,
221
- });
222
-
223
- expect(parsed?.success).toBe(true);
224
- });
225
- });
@@ -1,69 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- AuthError,
4
- ProviderError,
5
- SDKError,
6
- TransportError,
7
- ValidationError,
8
- } from "../errors";
9
-
10
- describe("ProviderError", () => {
11
- it("should include fix hint", () => {
12
- const err = new ProviderError("Something broke", { fix: "Try again" });
13
- expect(err.fix).toBe("Try again");
14
- expect(err.message).toBe("Something broke");
15
- expect(err.name).toBe("ProviderError");
16
- });
17
-
18
- it("should include code", () => {
19
- const err = new ProviderError("Error", { code: "E001" });
20
- expect(err.code).toBe("E001");
21
- });
22
-
23
- it("should be instanceof Error", () => {
24
- const err = new ProviderError("test");
25
- expect(err instanceof Error).toBe(true);
26
- expect(err instanceof ProviderError).toBe(true);
27
- });
28
- });
29
-
30
- describe("AuthError", () => {
31
- it("should be instanceof ProviderError", () => {
32
- const err = new AuthError("Unauthorized", {
33
- code: "refresh_failed",
34
- fix: "Re-authenticate",
35
- });
36
- expect(err instanceof ProviderError).toBe(true);
37
- expect(err instanceof AuthError).toBe(true);
38
- expect(err.name).toBe("AuthError");
39
- expect(err.code).toBe("refresh_failed");
40
- });
41
- });
42
-
43
- describe("ValidationError", () => {
44
- it("should store zodError", () => {
45
- const zodErr = { issues: [{ path: ["id"], message: "Required" }] };
46
- const err = new ValidationError("Invalid input", {
47
- zodError: zodErr,
48
- fix: "Check id field",
49
- });
50
- expect(err.zodError).toEqual(zodErr);
51
- expect(err instanceof ProviderError).toBe(true);
52
- });
53
- });
54
-
55
- describe("TransportError", () => {
56
- it("should store HTTP status", () => {
57
- const err = new TransportError("Connection failed", { status: 502 });
58
- expect(err.status).toBe(502);
59
- expect(err instanceof ProviderError).toBe(true);
60
- });
61
- });
62
-
63
- describe("SDKError", () => {
64
- it("should be instanceof ProviderError", () => {
65
- const err = new SDKError("Internal SDK error");
66
- expect(err instanceof ProviderError).toBe(true);
67
- expect(err.name).toBe("SDKError");
68
- });
69
- });
@@ -1,214 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
-
3
- import { z } from "../../node_modules/zod";
4
-
5
- import { TransportError } from "../errors";
6
- import { executeOperation } from "../runtime/executor";
7
- import type { ProviderContext, ProviderDefinition } from "../types";
8
-
9
- function createMockCtx(fetchResponse: unknown, status = 200): ProviderContext {
10
- return {
11
- tls: {
12
- fetch: mock(async (_url: string, _opts?: unknown) => ({
13
- status,
14
- ok: status >= 200 && status < 300,
15
- headers: {},
16
- rawHeaders: [],
17
- body: "",
18
- cookies: {
19
- get: () => undefined,
20
- getAll: () => ({}),
21
- toString: () => "",
22
- },
23
- json: async <T>() => fetchResponse as T,
24
- })),
25
- createSession: mock(() => ({
26
- fetch: async () => ({
27
- status,
28
- ok: status >= 200 && status < 300,
29
- headers: {},
30
- rawHeaders: [] as [string, string][],
31
- body: "",
32
- cookies: {
33
- get: () => undefined,
34
- getAll: () => ({}),
35
- toString: () => "",
36
- },
37
- json: async <T>() => ({}) as T,
38
- }),
39
- close: () => {},
40
- })),
41
- },
42
- http: {} as ProviderContext["http"],
43
- browser: {} as ProviderContext["browser"],
44
- session: {
45
- get: mock(async () => null),
46
- set: mock(async () => {}),
47
- delete: mock(async () => {}),
48
- },
49
- state: {} as ProviderContext["state"],
50
- trace: {
51
- span: async <T>(_name: string, fn: () => Promise<T>) => fn(),
52
- },
53
- auth: {
54
- requestField: mock(async () => ""),
55
- },
56
- };
57
- }
58
-
59
- function createMockProvider(options?: {
60
- handler?: ProviderDefinition["operations"][string]["handler"];
61
- auth?: ProviderDefinition["auth"];
62
- }): ProviderDefinition {
63
- return {
64
- id: "test-provider",
65
- version: "1.0.0",
66
- runtime: "standard",
67
- auth: options?.auth,
68
- meta: {
69
- displayName: "Test Provider",
70
- category: "test",
71
- },
72
- operations: {
73
- search: {
74
- description: "Search",
75
- input: z.object({ query: z.string() }),
76
- output: z.object({ results: z.array(z.string()) }),
77
- handler:
78
- options?.handler ??
79
- (async (_ctx: ProviderContext, input: unknown) => {
80
- const parsed = z.object({ query: z.string() }).parse(input);
81
-
82
- return {
83
- results: [parsed.query],
84
- };
85
- }),
86
- },
87
- },
88
- };
89
- }
90
-
91
- describe("executeOperation", () => {
92
- it("runs the handler and validates output", async () => {
93
- const ctx = createMockCtx({});
94
- const provider = createMockProvider();
95
-
96
- const result = await executeOperation(provider, "search", ctx, {
97
- query: "test",
98
- });
99
-
100
- expect(result).toEqual({ results: ["test"] });
101
- });
102
-
103
- it("passes through handler output parsing", async () => {
104
- const ctx = createMockCtx({});
105
- const provider = createMockProvider({
106
- handler: async () => ({ results: ["result1", "result2"] }),
107
- });
108
-
109
- const result = await executeOperation(provider, "search", ctx, {
110
- query: "test",
111
- });
112
-
113
- expect(result).toEqual({ results: ["result1", "result2"] });
114
- });
115
-
116
- it("throws ProviderError when operation not found", async () => {
117
- const ctx = createMockCtx({});
118
- const provider = createMockProvider();
119
-
120
- await expect(
121
- executeOperation(provider, "nonexistent", ctx, {}),
122
- ).rejects.toThrow("nonexistent");
123
- });
124
-
125
- it("throws ProviderError when operation has no handler", async () => {
126
- const ctx = createMockCtx({});
127
- const provider: ProviderDefinition = {
128
- ...createMockProvider(),
129
- operations: {
130
- search: {
131
- description: "No upstream",
132
- input: z.object({ query: z.string() }),
133
- output: z.object({ results: z.array(z.string()) }),
134
- handler: undefined as never,
135
- },
136
- },
137
- };
138
-
139
- await expect(
140
- executeOperation(provider, "search", ctx, { query: "test" }),
141
- ).rejects.toThrow(TypeError);
142
- });
143
-
144
- it("auto-refreshes on 401 response if auth is configured", async () => {
145
- let callCount = 0;
146
- const ctx: ProviderContext = {
147
- ...createMockCtx({}),
148
- tls: {
149
- fetch: mock(async () => {
150
- callCount++;
151
- const responseStatus = callCount === 1 ? 401 : 200;
152
- return {
153
- status: responseStatus,
154
- ok: responseStatus >= 200 && responseStatus < 300,
155
- headers: {},
156
- rawHeaders: [],
157
- body: "",
158
- cookies: {
159
- get: () => undefined,
160
- getAll: () => ({}),
161
- toString: () => "",
162
- },
163
- json: async <T>() => ({ items: [] }) as T,
164
- };
165
- }),
166
- createSession: mock(() => ({
167
- fetch: async () => ({
168
- status: 200,
169
- ok: true,
170
- headers: {},
171
- rawHeaders: [] as [string, string][],
172
- body: "",
173
- cookies: {
174
- get: () => undefined,
175
- getAll: () => ({}),
176
- toString: () => "",
177
- },
178
- json: async <T>() => ({}) as T,
179
- }),
180
- close: () => {},
181
- })),
182
- },
183
- session: {
184
- get: mock(async (key: string) => (key === "__auth__" ? "token" : null)),
185
- set: mock(async () => {}),
186
- delete: mock(async () => {}),
187
- },
188
- };
189
-
190
- const authRefresh = mock(async () => {});
191
- const provider = createMockProvider({
192
- auth: {
193
- mode: "credentials",
194
- refresh: authRefresh,
195
- },
196
- handler: async () => {
197
- callCount++;
198
- if (callCount === 1) {
199
- throw new TransportError("Unauthorized", { status: 401 });
200
- }
201
-
202
- return { results: [] };
203
- },
204
- });
205
-
206
- const result = await executeOperation(provider, "search", ctx, {
207
- query: "test",
208
- });
209
-
210
- expect(authRefresh).toHaveBeenCalledTimes(1);
211
- expect(callCount).toBe(2);
212
- expect(result).toEqual({ results: [] });
213
- });
214
- });