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

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 (78) hide show
  1. package/AUTHORING.md +102 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +100 -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 +47 -0
  8. package/bin/apifuse-perf.ts +33 -32
  9. package/bin/apifuse-record.ts +17 -7
  10. package/bin/apifuse-test.ts +6 -4
  11. package/bin/apifuse.ts +36 -35
  12. package/package.json +28 -9
  13. package/src/ceremonies/index.ts +747 -0
  14. package/src/cli/commands.ts +87 -0
  15. package/src/cli/create.ts +845 -0
  16. package/src/cli/templates/provider/Dockerfile.tpl +7 -0
  17. package/src/cli/templates/provider/README.md.tpl +28 -0
  18. package/src/cli/templates/provider/dev.ts.tpl +5 -0
  19. package/src/cli/templates/provider/index.test.ts.tpl +13 -0
  20. package/src/cli/templates/provider/index.ts.tpl +54 -0
  21. package/src/cli/templates/provider/start.ts.tpl +5 -0
  22. package/src/composite.ts +43 -0
  23. package/src/define.ts +527 -41
  24. package/src/dev.ts +2 -6
  25. package/src/errors.ts +42 -0
  26. package/src/index.ts +50 -38
  27. package/src/lint.ts +574 -0
  28. package/src/provider.ts +14 -0
  29. package/src/runtime/auth-flow.ts +67 -0
  30. package/src/runtime/credential.ts +95 -0
  31. package/src/runtime/env.ts +13 -0
  32. package/src/runtime/executor.ts +13 -14
  33. package/src/runtime/http.ts +10 -2
  34. package/src/runtime/insights.ts +3 -3
  35. package/src/runtime/key-derivation.ts +122 -0
  36. package/src/runtime/keyring.ts +148 -0
  37. package/src/runtime/namespace.ts +33 -0
  38. package/src/runtime/prevalidate.ts +252 -0
  39. package/src/runtime/tls.ts +20 -5
  40. package/src/runtime/waterfall.ts +0 -1
  41. package/src/schema.ts +77 -0
  42. package/src/serve.ts +1 -664
  43. package/src/server/index.ts +22 -0
  44. package/src/server/serve.ts +610 -0
  45. package/src/server/types.ts +78 -0
  46. package/src/stealth/profiles.ts +10 -93
  47. package/src/testing/run.ts +391 -32
  48. package/src/types.ts +364 -41
  49. package/bin/apifuse-init.ts +0 -387
  50. package/src/__tests__/auth.test.ts +0 -396
  51. package/src/__tests__/browser-auth.test.ts +0 -180
  52. package/src/__tests__/browser.test.ts +0 -632
  53. package/src/__tests__/define.test.ts +0 -225
  54. package/src/__tests__/errors.test.ts +0 -69
  55. package/src/__tests__/executor.test.ts +0 -214
  56. package/src/__tests__/http.test.ts +0 -238
  57. package/src/__tests__/insights.test.ts +0 -210
  58. package/src/__tests__/instrumentation.test.ts +0 -290
  59. package/src/__tests__/otlp.test.ts +0 -141
  60. package/src/__tests__/perf.test.ts +0 -60
  61. package/src/__tests__/providers-yaml.test.ts +0 -135
  62. package/src/__tests__/proxy.test.ts +0 -359
  63. package/src/__tests__/recipes.test.ts +0 -36
  64. package/src/__tests__/serve.test.ts +0 -233
  65. package/src/__tests__/session.test.ts +0 -231
  66. package/src/__tests__/state.test.ts +0 -100
  67. package/src/__tests__/stealth.test.ts +0 -57
  68. package/src/__tests__/testing.test.ts +0 -97
  69. package/src/__tests__/tls.test.ts +0 -345
  70. package/src/__tests__/types.test.ts +0 -142
  71. package/src/__tests__/utils.test.ts +0 -62
  72. package/src/__tests__/waterfall.test.ts +0 -270
  73. package/src/config/providers-yaml.ts +0 -370
  74. package/src/index.test.ts +0 -1
  75. package/src/protocol.ts +0 -183
  76. package/src/runtime/auth.ts +0 -245
  77. package/src/runtime/session.ts +0 -573
  78. package/src/runtime/state.ts +0 -124
@@ -0,0 +1,252 @@
1
+ import Ajv from "ajv";
2
+ import { RE2 } from "re2-wasm";
3
+
4
+ export interface PrevalidateResult {
5
+ valid: boolean;
6
+ errors?: Array<{ path: string; message: string }>;
7
+ }
8
+
9
+ type JsonSchema = Record<string, unknown>;
10
+
11
+ const DEFAULT_TIMEOUT_MS = 500;
12
+
13
+ function now(): number {
14
+ return Date.now();
15
+ }
16
+
17
+ function cloneWithoutPatterns(value: unknown): unknown {
18
+ if (Array.isArray(value)) {
19
+ return value.map((entry) => cloneWithoutPatterns(entry));
20
+ }
21
+
22
+ if (!value || typeof value !== "object") {
23
+ return value;
24
+ }
25
+
26
+ const cloned: Record<string, unknown> = {};
27
+ for (const [key, entry] of Object.entries(value)) {
28
+ if (key === "pattern") {
29
+ continue;
30
+ }
31
+ cloned[key] = cloneWithoutPatterns(entry);
32
+ }
33
+ return cloned;
34
+ }
35
+
36
+ function buildAjv(): Ajv {
37
+ return new Ajv({ allErrors: true, strict: true, strictSchema: true });
38
+ }
39
+
40
+ function createTimeoutGuard(timeoutMs: number): () => void {
41
+ const startedAt = now();
42
+
43
+ return () => {
44
+ if (now() - startedAt > timeoutMs) {
45
+ throw new Error("prevalidation_timeout");
46
+ }
47
+ };
48
+ }
49
+
50
+ function formatInstancePath(path: string): string {
51
+ return path.length > 0 ? path : "$";
52
+ }
53
+
54
+ function appendPath(basePath: string, segment: string): string {
55
+ if (segment.startsWith("[")) {
56
+ return `${basePath}${segment}`;
57
+ }
58
+
59
+ return basePath === "$" ? `${basePath}.${segment}` : `${basePath}.${segment}`;
60
+ }
61
+
62
+ function isRecord(value: unknown): value is Record<string, unknown> {
63
+ return !!value && typeof value === "object" && !Array.isArray(value);
64
+ }
65
+
66
+ function collectPatternErrors(
67
+ schema: unknown,
68
+ data: unknown,
69
+ path: string,
70
+ guard: () => void,
71
+ errors: Array<{ path: string; message: string }>,
72
+ ): void {
73
+ guard();
74
+
75
+ if (!isRecord(schema)) {
76
+ return;
77
+ }
78
+
79
+ if (typeof schema.pattern === "string" && typeof data === "string") {
80
+ try {
81
+ const regex = new RE2(schema.pattern, "u");
82
+ if (!regex.test(data)) {
83
+ errors.push({
84
+ path,
85
+ message: `must match pattern ${schema.pattern}`,
86
+ });
87
+ }
88
+ } catch (error) {
89
+ const message =
90
+ error instanceof Error ? error.message : "Invalid RE2 pattern";
91
+ errors.push({ path, message });
92
+ }
93
+ }
94
+
95
+ if (schema.$ref !== undefined) {
96
+ return;
97
+ }
98
+
99
+ if (Array.isArray(schema.allOf)) {
100
+ for (const entry of schema.allOf) {
101
+ collectPatternErrors(entry, data, path, guard, errors);
102
+ }
103
+ }
104
+
105
+ if (Array.isArray(schema.anyOf)) {
106
+ for (const entry of schema.anyOf) {
107
+ collectPatternErrors(entry, data, path, guard, errors);
108
+ }
109
+ }
110
+
111
+ if (Array.isArray(schema.oneOf)) {
112
+ for (const entry of schema.oneOf) {
113
+ collectPatternErrors(entry, data, path, guard, errors);
114
+ }
115
+ }
116
+
117
+ if (isRecord(schema.not)) {
118
+ collectPatternErrors(schema.not, data, path, guard, errors);
119
+ }
120
+
121
+ if (isRecord(schema.if)) {
122
+ collectPatternErrors(schema.if, data, path, guard, errors);
123
+ }
124
+
125
+ if (isRecord(schema.then)) {
126
+ collectPatternErrors(schema.then, data, path, guard, errors);
127
+ }
128
+
129
+ if (isRecord(schema.else)) {
130
+ collectPatternErrors(schema.else, data, path, guard, errors);
131
+ }
132
+
133
+ if (Array.isArray(data) && schema.items !== undefined) {
134
+ for (const [index, item] of data.entries()) {
135
+ collectPatternErrors(
136
+ schema.items,
137
+ item,
138
+ appendPath(path, `[${index}]`),
139
+ guard,
140
+ errors,
141
+ );
142
+ }
143
+ }
144
+
145
+ if (!isRecord(data)) {
146
+ return;
147
+ }
148
+
149
+ if (isRecord(schema.properties)) {
150
+ for (const [key, childSchema] of Object.entries(schema.properties)) {
151
+ if (key in data) {
152
+ collectPatternErrors(
153
+ childSchema,
154
+ data[key],
155
+ appendPath(path, key),
156
+ guard,
157
+ errors,
158
+ );
159
+ }
160
+ }
161
+ }
162
+
163
+ if (isRecord(schema.patternProperties)) {
164
+ for (const [pattern, childSchema] of Object.entries(
165
+ schema.patternProperties,
166
+ )) {
167
+ const keyPattern = new RE2(pattern, "u");
168
+ for (const [key, value] of Object.entries(data)) {
169
+ guard();
170
+ if (keyPattern.test(key)) {
171
+ collectPatternErrors(
172
+ childSchema,
173
+ value,
174
+ appendPath(path, key),
175
+ guard,
176
+ errors,
177
+ );
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ if (schema.additionalProperties && isRecord(schema.additionalProperties)) {
184
+ const declaredKeys = isRecord(schema.properties)
185
+ ? new Set(Object.keys(schema.properties))
186
+ : new Set<string>();
187
+
188
+ for (const [key, value] of Object.entries(data)) {
189
+ if (!declaredKeys.has(key)) {
190
+ collectPatternErrors(
191
+ schema.additionalProperties,
192
+ value,
193
+ appendPath(path, key),
194
+ guard,
195
+ errors,
196
+ );
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ export function prevalidate(
203
+ schema: JsonSchema,
204
+ data: unknown,
205
+ options: { timeoutMs?: number } = {},
206
+ ): PrevalidateResult {
207
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
208
+ const guard = createTimeoutGuard(timeoutMs);
209
+
210
+ try {
211
+ guard();
212
+ const ajv = buildAjv();
213
+ const strippedSchema = cloneWithoutPatterns(schema);
214
+ if (!strippedSchema || typeof strippedSchema !== "object") {
215
+ return {
216
+ valid: false,
217
+ errors: [{ path: "$", message: "Invalid schema" }],
218
+ };
219
+ }
220
+ const validate = ajv.compile(strippedSchema);
221
+ const schemaValid = validate(data);
222
+ const errors =
223
+ validate.errors?.map((error) => ({
224
+ path: formatInstancePath(error.instancePath),
225
+ message: error.message ?? "Invalid value",
226
+ })) ?? [];
227
+
228
+ collectPatternErrors(schema, data, "$", guard, errors);
229
+
230
+ if (!schemaValid || errors.length > 0) {
231
+ return { valid: false, errors };
232
+ }
233
+
234
+ return { valid: true };
235
+ } catch (error) {
236
+ if (error instanceof Error && error.message === "prevalidation_timeout") {
237
+ return {
238
+ valid: false,
239
+ errors: [
240
+ {
241
+ path: "$",
242
+ message: `Prevalidation timed out after ${timeoutMs}ms`,
243
+ },
244
+ ],
245
+ };
246
+ }
247
+
248
+ const message =
249
+ error instanceof Error ? error.message : "Prevalidation failed";
250
+ return { valid: false, errors: [{ path: "$", message }] };
251
+ }
252
+ }
@@ -2,7 +2,7 @@ import { ModuleClient, SessionClient } from "tlsclientwrapper";
2
2
 
3
3
  import type { ProxyResolutionOptions } from "../config/loader";
4
4
  import { resolveProxyConfig } from "../config/loader";
5
- import { TransportError } from "../errors";
5
+ import { SDKError, TransportError } from "../errors";
6
6
  import { getStealthProfile } from "../stealth/profiles";
7
7
  import type {
8
8
  CookieJar,
@@ -21,10 +21,21 @@ export type TlsClientOptions = ProxyResolutionOptions & {
21
21
  warn?: (message: string) => void;
22
22
  };
23
23
 
24
+ const REMOVED_CHROME_PROFILE_NAMES = new Set([
25
+ "chrome-120",
26
+ "chrome-124",
27
+ "chrome-129",
28
+ "chrome-130",
29
+ "chrome-131",
30
+ "chrome-133",
31
+ "chrome-144",
32
+ "chrome-146-psk",
33
+ "chrome-131-psk",
34
+ "chrome-130-psk",
35
+ "edge-131",
36
+ ]);
37
+
24
38
  const TLS_PROFILE_MAP: Record<string, string> = {
25
- "chrome-131": "chrome_131",
26
- "chrome-133": "chrome_133",
27
- "chrome-144": "chrome_144",
28
39
  "chrome-146": "chrome_146",
29
40
  "firefox-132": "firefox_132",
30
41
  "firefox-133": "firefox_133",
@@ -99,6 +110,10 @@ class CookieJarImpl implements CookieJar {
99
110
  }
100
111
 
101
112
  function resolveIdentifier(profileName: string): string {
113
+ if (REMOVED_CHROME_PROFILE_NAMES.has(profileName)) {
114
+ throw new SDKError(`Unknown stealth profile: ${profileName}`);
115
+ }
116
+
102
117
  try {
103
118
  const profile = getStealthProfile(profileName);
104
119
  if (profile.tlsClientIdentifier) {
@@ -348,7 +363,7 @@ function createSessionFetcher(
348
363
 
349
364
  return normalizeResponse(response);
350
365
  } catch (error) {
351
- if (error instanceof TransportError) {
366
+ if (error instanceof SDKError || error instanceof TransportError) {
352
367
  throw error;
353
368
  }
354
369
 
@@ -102,7 +102,6 @@ function renderChildren(
102
102
  }
103
103
  const isLast = i === children.length - 1;
104
104
  const provider = isLast ? "└─" : "├─";
105
- const _childPrefix = isLast ? " " : "│ ";
106
105
 
107
106
  const duration = formatDuration(node.span.duration_ms);
108
107
  const bar = renderBar(
package/src/schema.ts ADDED
@@ -0,0 +1,77 @@
1
+ import { ValidationError } from "./errors";
2
+ import type { InferSchemaOutput, SchemaLike, StandardSchemaV1 } from "./types";
3
+
4
+ export type SchemaValidationResult<TSchema extends SchemaLike> =
5
+ | { success: true; data: InferSchemaOutput<TSchema> }
6
+ | { success: false; error: unknown };
7
+
8
+ type UnknownSchemaValidationResult =
9
+ | { success: true; data: unknown }
10
+ | { success: false; error: unknown };
11
+
12
+ function isFailureResult<Output>(
13
+ result: StandardSchemaV1.Result<Output>,
14
+ ): result is StandardSchemaV1.FailureResult {
15
+ return "issues" in result;
16
+ }
17
+ function isPromiseResult<Output>(
18
+ result:
19
+ | StandardSchemaV1.Result<Output>
20
+ | Promise<StandardSchemaV1.Result<Output>>,
21
+ ): result is Promise<StandardSchemaV1.Result<Output>> {
22
+ return result instanceof Promise;
23
+ }
24
+ function formatStandardSchemaIssues(
25
+ issues: readonly StandardSchemaV1.Issue[],
26
+ ): string {
27
+ return issues.map((issue) => issue.message).join("; ");
28
+ }
29
+ export function parseSchema<TSchema extends SchemaLike>(
30
+ schema: TSchema,
31
+ value: unknown,
32
+ fieldPath: string,
33
+ ): Promise<InferSchemaOutput<TSchema>>;
34
+ export async function parseSchema(
35
+ schema: SchemaLike,
36
+ value: unknown,
37
+ fieldPath: string,
38
+ ): Promise<unknown> {
39
+ if ("parse" in schema && typeof schema.parse === "function")
40
+ return schema.parse(value);
41
+ const result = schema["~standard"].validate(value);
42
+ const resolved = isPromiseResult(result) ? await result : result;
43
+ if (isFailureResult(resolved))
44
+ throw new ValidationError(
45
+ `Schema validation failed for ${fieldPath}: ${formatStandardSchemaIssues(resolved.issues)}`,
46
+ { zodError: resolved.issues },
47
+ );
48
+ return resolved.value;
49
+ }
50
+ export function safeParseSchemaSync<TSchema extends SchemaLike>(
51
+ schema: TSchema,
52
+ value: unknown,
53
+ fieldPath: string,
54
+ ): SchemaValidationResult<TSchema>;
55
+ export function safeParseSchemaSync(
56
+ schema: SchemaLike,
57
+ value: unknown,
58
+ fieldPath: string,
59
+ ): UnknownSchemaValidationResult {
60
+ if ("safeParse" in schema && typeof schema.safeParse === "function")
61
+ return schema.safeParse(value);
62
+ try {
63
+ const result = schema["~standard"].validate(value);
64
+ if (isPromiseResult(result))
65
+ return {
66
+ success: false,
67
+ error: new ValidationError(
68
+ `Schema validation for ${fieldPath} returned a Promise. defineProvider fixture validation requires synchronous Standard Schema validation.`,
69
+ ),
70
+ };
71
+ if (isFailureResult(result))
72
+ return { success: false, error: result.issues };
73
+ return { success: true, data: result.value };
74
+ } catch (error) {
75
+ return { success: false, error };
76
+ }
77
+ }