@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.2

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 (138) hide show
  1. package/AUTHORING.md +134 -0
  2. package/CHANGELOG.md +61 -0
  3. package/README.md +21 -9
  4. package/SUBMISSION.md +10 -11
  5. package/bin/apifuse-pack-check.ts +22 -0
  6. package/bin/apifuse-submit-check.ts +1747 -388
  7. package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
  8. package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  9. package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  10. package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  11. package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  12. package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  13. package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  14. package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  15. package/dist/auth-turn/fixtures/valid/abort.json +8 -0
  16. package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
  17. package/dist/auth-turn/fixtures/valid/complete.json +13 -0
  18. package/dist/auth-turn/fixtures/valid/form.json +14 -0
  19. package/dist/auth-turn/fixtures/valid/message.json +13 -0
  20. package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
  21. package/dist/auth-turn/fixtures/valid/pending.json +5 -0
  22. package/dist/auth-turn/fixtures/valid/poll.json +9 -0
  23. package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
  24. package/dist/auth-turn/fixtures/valid/retry.json +8 -0
  25. package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  26. package/dist/auth-turn/index.d.ts +195 -0
  27. package/dist/auth-turn/index.js +133 -0
  28. package/dist/auth.d.ts +76 -0
  29. package/dist/auth.js +436 -0
  30. package/dist/ceremonies/index.js +7 -31
  31. package/dist/cli/create.js +45 -30
  32. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  33. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  34. package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
  35. package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
  36. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  37. package/dist/cli/templates/provider/README.md.tpl +163 -0
  38. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  39. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  40. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  41. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  42. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  43. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  44. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  45. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  46. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  47. package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  48. package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  49. package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  50. package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  51. package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  52. package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  53. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  54. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  55. package/dist/contract.js +1 -0
  56. package/dist/define.d.ts +6 -1
  57. package/dist/define.js +140 -70
  58. package/dist/index.d.ts +3 -2
  59. package/dist/index.js +2 -1
  60. package/dist/lint.d.ts +1 -0
  61. package/dist/lint.js +27 -0
  62. package/dist/provider.d.ts +4 -2
  63. package/dist/provider.js +2 -1
  64. package/dist/runtime/auth-flow.js +2 -0
  65. package/dist/runtime/browser.js +203 -0
  66. package/dist/runtime/http.js +46 -336
  67. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  68. package/dist/runtime/proxy-retry-policy.js +326 -0
  69. package/dist/runtime/stealth.d.ts +5 -2
  70. package/dist/runtime/stealth.js +206 -206
  71. package/dist/server/index.d.ts +4 -0
  72. package/dist/server/index.js +4 -0
  73. package/dist/server/self-test-input-tokens.d.ts +1 -0
  74. package/dist/server/self-test-input-tokens.js +37 -0
  75. package/dist/server/self-test-redaction.d.ts +20 -0
  76. package/dist/server/self-test-redaction.js +70 -0
  77. package/dist/server/self-test-token.d.ts +30 -0
  78. package/dist/server/self-test-token.js +50 -0
  79. package/dist/server/self-test.d.ts +98 -0
  80. package/dist/server/self-test.js +555 -0
  81. package/dist/server/serve.d.ts +6 -0
  82. package/dist/server/serve.js +38 -10
  83. package/dist/server/types.d.ts +1 -0
  84. package/dist/server/types.js +1 -0
  85. package/dist/testing/run.js +5 -1
  86. package/dist/types.d.ts +150 -0
  87. package/package.json +9 -2
  88. package/src/auth-turn/auth-turn.v1.schema.json +89 -0
  89. package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  90. package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  91. package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  92. package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  93. package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  94. package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  95. package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  96. package/src/auth-turn/fixtures/valid/abort.json +8 -0
  97. package/src/auth-turn/fixtures/valid/challenge.json +17 -0
  98. package/src/auth-turn/fixtures/valid/complete.json +13 -0
  99. package/src/auth-turn/fixtures/valid/form.json +14 -0
  100. package/src/auth-turn/fixtures/valid/message.json +13 -0
  101. package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
  102. package/src/auth-turn/fixtures/valid/pending.json +5 -0
  103. package/src/auth-turn/fixtures/valid/poll.json +9 -0
  104. package/src/auth-turn/fixtures/valid/redirect.json +16 -0
  105. package/src/auth-turn/fixtures/valid/retry.json +8 -0
  106. package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  107. package/src/auth-turn/index.ts +177 -0
  108. package/src/auth.ts +786 -0
  109. package/src/ceremonies/index.ts +9 -43
  110. package/src/cli/create.ts +60 -97
  111. package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
  112. package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
  113. package/src/cli/templates/provider/README.md.tpl +7 -4
  114. package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  115. package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  116. package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  117. package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  118. package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  119. package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  120. package/src/contract.ts +1 -0
  121. package/src/define.ts +198 -71
  122. package/src/index.ts +16 -0
  123. package/src/lint.ts +33 -0
  124. package/src/provider.ts +27 -0
  125. package/src/runtime/auth-flow.ts +2 -0
  126. package/src/runtime/browser.ts +293 -1
  127. package/src/runtime/http.ts +84 -530
  128. package/src/runtime/proxy-retry-policy.ts +469 -0
  129. package/src/runtime/stealth.ts +269 -353
  130. package/src/server/index.ts +36 -0
  131. package/src/server/self-test-input-tokens.ts +46 -0
  132. package/src/server/self-test-redaction.ts +97 -0
  133. package/src/server/self-test-token.ts +70 -0
  134. package/src/server/self-test.ts +725 -0
  135. package/src/server/serve.ts +75 -6
  136. package/src/server/types.ts +1 -0
  137. package/src/testing/run.ts +9 -1
  138. package/src/types.ts +186 -0
@@ -3,6 +3,106 @@ import { ProviderError } from "../errors";
3
3
  const require = createRequire(import.meta.url);
4
4
  const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
5
5
  const SELECTOR_POLL_INTERVAL_MS = 100;
6
+ const RESOURCE_POLICY_ROUTE_PATTERN = "**/*";
7
+ const DEFAULT_RESOURCE_METHODS = ["GET", "HEAD"];
8
+ function toResourceBody(body) {
9
+ if (body === undefined || typeof body === "string" || Buffer.isBuffer(body)) {
10
+ return body;
11
+ }
12
+ if (body instanceof ArrayBuffer) {
13
+ return Buffer.from(new Uint8Array(body));
14
+ }
15
+ return Buffer.from(body);
16
+ }
17
+ function isResourceMethod(method) {
18
+ return method === "GET" || method === "HEAD";
19
+ }
20
+ async function toResourceRequest(request) {
21
+ const method = request.method().toUpperCase();
22
+ if (!isResourceMethod(method)) {
23
+ return null;
24
+ }
25
+ return {
26
+ headers: await request.allHeaders(),
27
+ method,
28
+ resourceType: request.resourceType(),
29
+ url: request.url(),
30
+ };
31
+ }
32
+ function toCdpResourceRequest(params) {
33
+ if (!isRecord(params)) {
34
+ return null;
35
+ }
36
+ const requestId = params.requestId;
37
+ const rawRequest = params.request;
38
+ if (typeof requestId !== "string" || !isRecord(rawRequest)) {
39
+ return null;
40
+ }
41
+ const url = rawRequest.url;
42
+ const method = String(rawRequest.method ?? "").toUpperCase();
43
+ if (typeof url !== "string" || !isResourceMethod(method)) {
44
+ return null;
45
+ }
46
+ return {
47
+ requestId,
48
+ request: {
49
+ headers: toCdpResourceHeaders(rawRequest.headers),
50
+ method,
51
+ resourceType: typeof params.resourceType === "string" ? params.resourceType : undefined,
52
+ url,
53
+ },
54
+ };
55
+ }
56
+ function getCdpPausedRequestId(params) {
57
+ if (!isRecord(params) || typeof params.requestId !== "string") {
58
+ return null;
59
+ }
60
+ return params.requestId;
61
+ }
62
+ function toCdpResourceHeaders(value) {
63
+ if (!isRecord(value)) {
64
+ return {};
65
+ }
66
+ const headers = {};
67
+ for (const [name, headerValue] of Object.entries(value)) {
68
+ if (typeof headerValue === "string") {
69
+ headers[name] = headerValue;
70
+ }
71
+ }
72
+ return headers;
73
+ }
74
+ function matchesResourceRoute(match, request) {
75
+ if (typeof match === "string") {
76
+ return request.url === match;
77
+ }
78
+ if (match instanceof RegExp) {
79
+ return match.test(request.url);
80
+ }
81
+ return match(request);
82
+ }
83
+ function toCdpFulfillParams(requestId, decision) {
84
+ const body = toResourceBody(decision.body);
85
+ return {
86
+ ...(body === undefined
87
+ ? {}
88
+ : { body: Buffer.from(body).toString("base64") }),
89
+ ...(decision.headers === undefined
90
+ ? {}
91
+ : {
92
+ responseHeaders: Object.entries(decision.headers).map(([name, value]) => ({ name, value })),
93
+ }),
94
+ requestId,
95
+ responseCode: decision.status ?? 200,
96
+ };
97
+ }
98
+ async function fulfillResourceRoute(route, decision) {
99
+ const body = toResourceBody(decision.body);
100
+ await route.fulfill({
101
+ ...(body === undefined ? {} : { body }),
102
+ ...(decision.headers === undefined ? {} : { headers: decision.headers }),
103
+ status: decision.status ?? 200,
104
+ });
105
+ }
6
106
  function getDefaultCdpPoolUrl(env = process.env) {
7
107
  return env.APIFUSE__CDP_POOL__URL;
8
108
  }
@@ -256,6 +356,38 @@ class PlaywrightBrowserPage {
256
356
  async close() {
257
357
  await this.page.close();
258
358
  }
359
+ async withResourcePolicy(policy, run) {
360
+ const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
361
+ const handler = async (route) => {
362
+ const request = await toResourceRequest(route.request());
363
+ if (!request || !allowedMethods.has(request.method)) {
364
+ await route.abort("blockedbyclient");
365
+ return;
366
+ }
367
+ for (const resourceRoute of policy.routes) {
368
+ if (!matchesResourceRoute(resourceRoute.match, request)) {
369
+ continue;
370
+ }
371
+ const decision = await resourceRoute.handle(request);
372
+ switch (decision.action) {
373
+ case "fulfill":
374
+ await fulfillResourceRoute(route, decision);
375
+ return;
376
+ case "block":
377
+ await route.abort("blockedbyclient");
378
+ return;
379
+ }
380
+ }
381
+ await route.abort("blockedbyclient");
382
+ };
383
+ await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
384
+ try {
385
+ return await run();
386
+ }
387
+ finally {
388
+ await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
389
+ }
390
+ }
259
391
  }
260
392
  class PlaywrightBrowserClient {
261
393
  options;
@@ -737,6 +869,77 @@ class CdpPoolBrowserPage {
737
869
  await this.pageClient.close();
738
870
  }
739
871
  }
872
+ async withResourcePolicy(policy, run) {
873
+ const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
874
+ const handlePausedRequest = (params) => {
875
+ void this.handleResourcePolicyPausedRequest(params, policy, allowedMethods);
876
+ };
877
+ const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
878
+ try {
879
+ await this.pageClient.send("Fetch.enable", {
880
+ patterns: [{ requestStage: "Request", urlPattern: "*" }],
881
+ });
882
+ }
883
+ catch (error) {
884
+ unsubscribe();
885
+ throw new ProviderError("CDP browser target does not support BrowserPage.withResourcePolicy()", {
886
+ cause: error instanceof Error ? error : undefined,
887
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
888
+ fix: "Use a Chromium CDP target with the Fetch domain enabled, or use the local Playwright browser runtime.",
889
+ });
890
+ }
891
+ try {
892
+ return await run();
893
+ }
894
+ finally {
895
+ unsubscribe();
896
+ await this.pageClient.send("Fetch.disable");
897
+ }
898
+ }
899
+ async handleResourcePolicyPausedRequest(params, policy, allowedMethods) {
900
+ const requestId = getCdpPausedRequestId(params);
901
+ if (requestId === null) {
902
+ return;
903
+ }
904
+ try {
905
+ const parsed = toCdpResourceRequest(params);
906
+ if (!parsed || !allowedMethods.has(parsed.request.method)) {
907
+ await this.failCdpResourceRequest(requestId);
908
+ return;
909
+ }
910
+ for (const resourceRoute of policy.routes) {
911
+ if (!matchesResourceRoute(resourceRoute.match, parsed.request)) {
912
+ continue;
913
+ }
914
+ const decision = await resourceRoute.handle(parsed.request);
915
+ switch (decision.action) {
916
+ case "fulfill":
917
+ await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
918
+ return;
919
+ case "block":
920
+ await this.failCdpResourceRequest(parsed.requestId);
921
+ return;
922
+ }
923
+ }
924
+ await this.failCdpResourceRequest(parsed.requestId);
925
+ }
926
+ catch {
927
+ await this.failCdpResourceRequest(requestId);
928
+ }
929
+ }
930
+ async failCdpResourceRequest(requestId) {
931
+ try {
932
+ await this.pageClient.send("Fetch.failRequest", {
933
+ errorReason: "BlockedByClient",
934
+ requestId,
935
+ });
936
+ }
937
+ catch (error) {
938
+ if (error instanceof Error) {
939
+ return;
940
+ }
941
+ }
942
+ }
740
943
  async initialize() {
741
944
  if (this.initialized) {
742
945
  return;