@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.4

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 (60) hide show
  1. package/AUTHORING.md +172 -8
  2. package/CHANGELOG.md +15 -1
  3. package/README.md +29 -15
  4. package/SUBMISSION.md +86 -0
  5. package/bin/apifuse-dev.ts +12 -5
  6. package/bin/apifuse-pack-check.ts +17 -2
  7. package/bin/apifuse-pack-smoke.ts +133 -6
  8. package/bin/apifuse-perf.ts +19 -15
  9. package/bin/apifuse-record.ts +41 -53
  10. package/bin/apifuse-submit-check.ts +1052 -0
  11. package/bin/apifuse.ts +1 -1
  12. package/package.json +19 -9
  13. package/src/choice-token.ts +164 -0
  14. package/src/cli/commands.ts +24 -3
  15. package/src/cli/create.ts +166 -51
  16. package/src/cli/templates/provider/README.md.tpl +66 -7
  17. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  18. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  19. package/src/cli/templates/provider/index.ts.tpl +5 -47
  20. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  21. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  22. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  23. package/src/cli/templates/provider/operations/ping.ts.tpl +23 -0
  24. package/src/cli/templates/provider/schemas/ping.ts.tpl +16 -0
  25. package/src/cli/templates/provider/start.ts.tpl +1 -1
  26. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  27. package/src/config/loader.ts +1206 -9
  28. package/src/define.ts +1648 -43
  29. package/src/errors.ts +12 -0
  30. package/src/i18n/catalog.ts +121 -0
  31. package/src/i18n/index.ts +2 -0
  32. package/src/i18n/keys.ts +64 -0
  33. package/src/index.ts +152 -8
  34. package/src/lint.ts +297 -42
  35. package/src/observability.ts +41 -0
  36. package/src/provider.ts +60 -3
  37. package/src/public-schema-field-lint.ts +237 -0
  38. package/src/runtime/auth-flow.ts +7 -0
  39. package/src/runtime/browser.ts +77 -21
  40. package/src/runtime/cache.ts +582 -0
  41. package/src/runtime/executor.ts +13 -1
  42. package/src/runtime/http.ts +939 -195
  43. package/src/runtime/insights.ts +11 -11
  44. package/src/runtime/instrumentation.ts +12 -4
  45. package/src/runtime/key-derivation.ts +1 -1
  46. package/src/runtime/keyring.ts +4 -3
  47. package/src/runtime/proxy-errors.ts +132 -0
  48. package/src/runtime/proxy-telemetry.ts +253 -0
  49. package/src/runtime/request-options.ts +66 -0
  50. package/src/runtime/state.ts +76 -0
  51. package/src/runtime/stealth.ts +1145 -0
  52. package/src/runtime/stt.ts +629 -0
  53. package/src/schema.ts +363 -1
  54. package/src/server/serve.ts +827 -60
  55. package/src/server/types.ts +35 -0
  56. package/src/stream.ts +210 -0
  57. package/src/testing/run.ts +17 -4
  58. package/src/types.ts +889 -50
  59. package/src/runtime/tls.ts +0 -434
  60. package/src/types/playwright-stealth.d.ts +0 -9
package/src/schema.ts CHANGED
@@ -1,5 +1,15 @@
1
+ import { type ZodString, type ZodType, z } from "zod";
2
+
1
3
  import { ValidationError } from "./errors";
2
- import type { InferSchemaOutput, SchemaLike, StandardSchemaV1 } from "./types";
4
+ import { providerLocaleKey } from "./i18n/keys";
5
+ import type {
6
+ InferSchemaOutput,
7
+ ProviderLocaleKey,
8
+ SchemaLike,
9
+ StandardSchemaV1,
10
+ } from "./types";
11
+
12
+ export { z };
3
13
 
4
14
  export type SchemaValidationResult<TSchema extends SchemaLike> =
5
15
  | { success: true; data: InferSchemaOutput<TSchema> }
@@ -75,3 +85,355 @@ export function safeParseSchemaSync(
75
85
  return { success: false, error };
76
86
  }
77
87
  }
88
+
89
+ export const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
90
+ export const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
91
+ export const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
92
+ export const APIFUSE_REDACTION_MARKER = "<redacted>";
93
+
94
+ export type SensitivePathSegment = string | "*";
95
+ export type SensitivePath = readonly SensitivePathSegment[];
96
+ export type SensitiveFieldKind =
97
+ | "api_key"
98
+ | "authorization"
99
+ | "cookie"
100
+ | "credential"
101
+ | "otp"
102
+ | "password"
103
+ | "payment_url"
104
+ | "personal_data"
105
+ | "phone"
106
+ | "secret"
107
+ | "token";
108
+
109
+ export interface SensitiveFieldOptions {
110
+ /**
111
+ * Mark this schema as sensitive. Defaults to true for the helper presets.
112
+ */
113
+ sensitive?: boolean;
114
+ /**
115
+ * Machine-readable sensitivity category propagated to JSON Schema.
116
+ */
117
+ kind?: SensitiveFieldKind;
118
+ /**
119
+ * Optional public description applied with Zod's `.describe()`.
120
+ */
121
+ description?: string;
122
+ }
123
+
124
+ export function describeKey<TSchema extends ZodType>(
125
+ schema: TSchema,
126
+ key: ProviderLocaleKey | string,
127
+ ): TSchema {
128
+ const descriptionKey = providerLocaleKey(key);
129
+ const metadata = schema.meta() ?? {};
130
+ return schema.meta({
131
+ ...metadata,
132
+ [APIFUSE_DESCRIPTION_KEY_META_KEY]: descriptionKey,
133
+ });
134
+ }
135
+
136
+ declare module "zod" {
137
+ interface ZodType {
138
+ describeKey(key: ProviderLocaleKey | string): this;
139
+ }
140
+ }
141
+
142
+ const describeKeyMethod = function <TSchema extends ZodType>(
143
+ this: TSchema,
144
+ key: ProviderLocaleKey | string,
145
+ ): TSchema {
146
+ return describeKey(this, key);
147
+ };
148
+
149
+ function installDescribeKeyOnPrototype(prototype: unknown): void {
150
+ const target = prototype as
151
+ | (Record<string, unknown> & { describeKey?: unknown })
152
+ | null;
153
+ if (!target || typeof target.describeKey === "function") {
154
+ return;
155
+ }
156
+ Object.defineProperty(target, "describeKey", {
157
+ configurable: true,
158
+ value: describeKeyMethod,
159
+ writable: true,
160
+ });
161
+ }
162
+
163
+ for (const [name, value] of Object.entries(z)) {
164
+ if (!name.startsWith("Zod") || name.endsWith("Error")) {
165
+ continue;
166
+ }
167
+ if (typeof value !== "function") {
168
+ continue;
169
+ }
170
+ installDescribeKeyOnPrototype(value.prototype);
171
+ }
172
+
173
+ const RESERVED_SENSITIVE_KEYS = new Set([
174
+ "authorization",
175
+ "cookie",
176
+ "secret",
177
+ "secrets",
178
+ "token",
179
+ "accesstoken",
180
+ "refreshtoken",
181
+ "apikey",
182
+ "api_key",
183
+ "password",
184
+ "passwd",
185
+ "otp",
186
+ "otpcode",
187
+ "phone",
188
+ "phonenumber",
189
+ "paymenturl",
190
+ "payment_url",
191
+ ]);
192
+
193
+ export function field<TSchema extends ZodType>(
194
+ schema: TSchema,
195
+ options: SensitiveFieldOptions = {},
196
+ ): TSchema {
197
+ const described =
198
+ options.description && typeof schema.describe === "function"
199
+ ? schema.describe(options.description)
200
+ : schema;
201
+ const metadata = described.meta() ?? {};
202
+ return described.meta({
203
+ ...metadata,
204
+ ...((options.sensitive ?? true)
205
+ ? { [APIFUSE_SENSITIVE_META_KEY]: true }
206
+ : {}),
207
+ ...(options.kind
208
+ ? { [APIFUSE_SENSITIVE_KIND_META_KEY]: options.kind }
209
+ : {}),
210
+ });
211
+ }
212
+
213
+ export function sensitive<TSchema extends ZodType>(
214
+ schema: TSchema,
215
+ kind?: SensitiveFieldKind,
216
+ ): TSchema {
217
+ return field(schema, { sensitive: true, kind });
218
+ }
219
+
220
+ function sensitiveString(
221
+ kind: SensitiveFieldKind,
222
+ description: string,
223
+ options: { description?: string; minLength?: number } = {},
224
+ ): ZodString {
225
+ const schema =
226
+ options.minLength === undefined
227
+ ? z.string()
228
+ : z.string().min(options.minLength);
229
+ return field(schema, {
230
+ kind,
231
+ description: options.description ?? description,
232
+ });
233
+ }
234
+
235
+ export const fields = {
236
+ apiKey: (options?: { description?: string }) =>
237
+ sensitiveString(
238
+ "api_key",
239
+ "Provider API key or credential secret.",
240
+ options,
241
+ ),
242
+ authorization: (options?: { description?: string }) =>
243
+ sensitiveString(
244
+ "authorization",
245
+ "Authorization header value or bearer credential.",
246
+ options,
247
+ ),
248
+ cookie: (options?: { description?: string }) =>
249
+ sensitiveString(
250
+ "cookie",
251
+ "Cookie header or browser session secret.",
252
+ options,
253
+ ),
254
+ otp: (options?: { description?: string }) =>
255
+ sensitiveString("otp", "One-time verification code.", options),
256
+ password: (options?: { description?: string; minLength?: number }) =>
257
+ sensitiveString("password", "Password credential.", options),
258
+ paymentUrl: (options?: { description?: string }) =>
259
+ sensitiveString(
260
+ "payment_url",
261
+ "Sensitive payment or checkout URL.",
262
+ options,
263
+ ),
264
+ phone: (options?: { description?: string }) =>
265
+ sensitiveString("phone", "Phone number or phone-based identity.", options),
266
+ secret: (options?: { description?: string }) =>
267
+ sensitiveString("secret", "Provider secret material.", options),
268
+ token: (options?: { description?: string }) =>
269
+ sensitiveString("token", "Provider access or refresh token.", options),
270
+ } as const;
271
+
272
+ export function isSensitiveSchema(schema: unknown): boolean {
273
+ const metadata = readZodMetadata(schema);
274
+ return (
275
+ metadata !== undefined &&
276
+ Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true
277
+ );
278
+ }
279
+
280
+ export function collectSensitivePaths(schema: unknown): SensitivePath[] {
281
+ const out: SensitivePath[] = [];
282
+ collectSensitivePathsInto(schema, [], out, new Set(), new Set());
283
+ return out;
284
+ }
285
+
286
+ export function redactPayload(
287
+ value: unknown,
288
+ paths: readonly SensitivePath[] = [],
289
+ ): unknown {
290
+ return redactValue(value, [], paths);
291
+ }
292
+
293
+ function collectSensitivePathsInto(
294
+ schema: unknown,
295
+ path: SensitivePathSegment[],
296
+ out: SensitivePath[],
297
+ activeSchemas: Set<unknown>,
298
+ emittedPaths: Set<string>,
299
+ ): void {
300
+ if (!schema || typeof schema !== "object" || activeSchemas.has(schema))
301
+ return;
302
+ activeSchemas.add(schema);
303
+ try {
304
+ if (isSensitiveSchema(schema)) pushSensitivePath(out, emittedPaths, path);
305
+ const def = readZodDef(schema);
306
+ if (!def) return;
307
+ switch (Reflect.get(def, "type")) {
308
+ case "object": {
309
+ const shape = readObjectShape(def);
310
+ for (const [key, child] of Object.entries(shape)) {
311
+ collectSensitivePathsInto(
312
+ child,
313
+ [...path, key],
314
+ out,
315
+ activeSchemas,
316
+ emittedPaths,
317
+ );
318
+ }
319
+ break;
320
+ }
321
+ case "array":
322
+ collectSensitivePathsInto(
323
+ Reflect.get(def, "element"),
324
+ [...path, "*"],
325
+ out,
326
+ activeSchemas,
327
+ emittedPaths,
328
+ );
329
+ break;
330
+ case "optional":
331
+ case "nullable":
332
+ case "default":
333
+ case "catch":
334
+ case "readonly":
335
+ collectSensitivePathsInto(
336
+ Reflect.get(def, "innerType"),
337
+ path,
338
+ out,
339
+ activeSchemas,
340
+ emittedPaths,
341
+ );
342
+ break;
343
+ case "pipe":
344
+ collectSensitivePathsInto(
345
+ Reflect.get(def, "in"),
346
+ path,
347
+ out,
348
+ activeSchemas,
349
+ emittedPaths,
350
+ );
351
+ collectSensitivePathsInto(
352
+ Reflect.get(def, "out"),
353
+ path,
354
+ out,
355
+ activeSchemas,
356
+ emittedPaths,
357
+ );
358
+ break;
359
+ }
360
+ } finally {
361
+ activeSchemas.delete(schema);
362
+ }
363
+ }
364
+
365
+ function pushSensitivePath(
366
+ out: SensitivePath[],
367
+ emittedPaths: Set<string>,
368
+ path: SensitivePathSegment[],
369
+ ): void {
370
+ const key = JSON.stringify(path);
371
+ if (emittedPaths.has(key)) return;
372
+ emittedPaths.add(key);
373
+ out.push([...path]);
374
+ }
375
+
376
+ function redactValue(
377
+ value: unknown,
378
+ path: SensitivePathSegment[],
379
+ paths: readonly SensitivePath[],
380
+ ): unknown {
381
+ if (pathMatches(path, paths)) return APIFUSE_REDACTION_MARKER;
382
+ if (Array.isArray(value)) {
383
+ return value.map((item) => redactValue(item, [...path, "*"], paths));
384
+ }
385
+ if (value && typeof value === "object") {
386
+ const out: Record<string, unknown> = {};
387
+ for (const [key, child] of Object.entries(value)) {
388
+ if (isReservedSensitiveKey(key)) {
389
+ out[key] = APIFUSE_REDACTION_MARKER;
390
+ } else {
391
+ out[key] = redactValue(child, [...path, key], paths);
392
+ }
393
+ }
394
+ return out;
395
+ }
396
+ return value;
397
+ }
398
+
399
+ function pathMatches(
400
+ path: readonly SensitivePathSegment[],
401
+ patterns: readonly SensitivePath[],
402
+ ): boolean {
403
+ return patterns.some((pattern) => {
404
+ if (pattern.length !== path.length) return false;
405
+ return pattern.every(
406
+ (segment, index) => segment === "*" || segment === path[index],
407
+ );
408
+ });
409
+ }
410
+
411
+ function isReservedSensitiveKey(key: string): boolean {
412
+ const normalized = key.toLowerCase().replace(/[-_\s]/g, "");
413
+ return (
414
+ RESERVED_SENSITIVE_KEYS.has(normalized) ||
415
+ RESERVED_SENSITIVE_KEYS.has(key.toLowerCase())
416
+ );
417
+ }
418
+
419
+ function readZodMetadata(schema: unknown): object | undefined {
420
+ if (!schema || typeof schema !== "object" || !("meta" in schema)) {
421
+ return undefined;
422
+ }
423
+ const maybeMeta = schema.meta;
424
+ if (typeof maybeMeta !== "function") return undefined;
425
+ const metadata = maybeMeta.call(schema);
426
+ return metadata && typeof metadata === "object" ? metadata : undefined;
427
+ }
428
+
429
+ function readZodDef(schema: unknown): object | undefined {
430
+ if (!schema || typeof schema !== "object") return undefined;
431
+ const def = Reflect.get(schema, "def") ?? Reflect.get(schema, "_def");
432
+ return def && typeof def === "object" ? def : undefined;
433
+ }
434
+
435
+ function readObjectShape(def: object): object {
436
+ const shape = Reflect.get(def, "shape");
437
+ const value = typeof shape === "function" ? shape() : shape;
438
+ return value && typeof value === "object" ? value : {};
439
+ }