@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.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.
- package/.claude/skills/urlcode-authoring/SKILL.md +106 -0
- package/.claude/skills/urlcode-operations/SKILL.md +114 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/CONTRIBUTING.md +30 -2
- package/README.md +157 -230
- package/ROADMAP.md +85 -2
- package/SECURITY.md +8 -1
- package/dist/BUILD-MANIFEST.json +62 -32
- package/dist/adapters.js +4 -23
- package/dist/agent-lists.js +1 -1
- package/dist/agents-guide.js +113 -0
- package/dist/authoring-files.js +60 -0
- package/dist/authoring.js +11 -1
- package/dist/aws.js +4 -3
- package/dist/build-cloudflare.js +11 -24
- package/dist/bulk.js +37 -0
- package/dist/capabilities.js +208 -0
- package/dist/capability-query.js +72 -0
- package/dist/catalog.js +105 -0
- package/dist/cli.js +154 -15
- package/dist/client-address.js +1 -1
- package/dist/compliance-rules/baseline.js +8 -8
- package/dist/compliance-rules/privacy.js +3 -3
- package/dist/compliance-rules/strict.js +5 -5
- package/dist/conditions.js +88 -0
- package/dist/config.js +60 -4
- package/dist/context.js +156 -0
- package/dist/ecosystem-cli.js +88 -0
- package/dist/egress.js +98 -0
- package/dist/examples.js +92 -0
- package/dist/explain-cli.js +64 -0
- package/dist/explain.js +132 -0
- package/dist/extensions.js +172 -0
- package/dist/function-sources.js +15 -3
- package/dist/index.js +37 -0
- package/dist/init-with.js +165 -0
- package/dist/interchange-cli.js +42 -0
- package/dist/interchange.js +189 -0
- package/dist/link-cli.js +1 -1
- package/dist/management-policy.js +0 -1
- package/dist/manifest.js +111 -0
- package/dist/match.js +2 -2
- package/dist/mcp-authoring.js +147 -0
- package/dist/mcp.js +97 -0
- package/dist/observability.js +6 -0
- package/dist/operator-host.js +29 -0
- package/dist/plugins.js +12 -0
- package/dist/policies/agents.js +2 -2
- package/dist/policies/compression.js +2 -1
- package/dist/policies/security.js +0 -0
- package/dist/policies.js +1 -1
- package/dist/policy.js +29 -7
- package/dist/prerender.js +100 -41
- package/dist/project-tests.js +3 -3
- package/dist/provider-verification.js +92 -0
- package/dist/proxy.js +44 -0
- package/dist/readiness.js +5 -5
- package/dist/recipes.js +41 -0
- package/dist/route-diff.js +106 -0
- package/dist/router.js +42 -2
- package/dist/runtime.js +95 -14
- package/dist/schema-query.js +62 -0
- package/dist/signals.js +24 -0
- package/dist/site.js +0 -0
- package/dist/tooling.js +96 -0
- package/dist/types/adapters.d.ts +7 -4
- package/dist/types/agent-lists.d.ts +0 -1
- package/dist/types/agents-guide.d.ts +17 -0
- package/dist/types/authoring-files.d.ts +10 -0
- package/dist/types/aws.d.ts +3 -1
- package/dist/types/build-cloudflare.d.ts +1 -0
- package/dist/types/bulk.d.ts +27 -0
- package/dist/types/capabilities.d.ts +55 -0
- package/dist/types/capability-query.d.ts +24 -0
- package/dist/types/catalog.d.ts +65 -0
- package/dist/types/client-address.d.ts +0 -1
- package/dist/types/compliance-rules/baseline.d.ts +1 -9
- package/dist/types/compliance-rules/privacy.d.ts +1 -4
- package/dist/types/compliance-rules/strict.d.ts +0 -5
- package/dist/types/conditions.d.ts +19 -0
- package/dist/types/config.d.ts +20 -2
- package/dist/types/context.d.ts +65 -0
- package/dist/types/ecosystem-cli.d.ts +17 -0
- package/dist/types/egress.d.ts +46 -0
- package/dist/types/examples.d.ts +50 -0
- package/dist/types/explain-cli.d.ts +11 -0
- package/dist/types/explain.d.ts +95 -0
- package/dist/types/extensions.d.ts +122 -0
- package/dist/types/function-sources.d.ts +5 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/init-with.d.ts +30 -0
- package/dist/types/interchange-cli.d.ts +16 -0
- package/dist/types/interchange.d.ts +42 -0
- package/dist/types/link-cli.d.ts +2 -1
- package/dist/types/management-policy.d.ts +0 -1
- package/dist/types/manifest.d.ts +81 -0
- package/dist/types/match.d.ts +1 -0
- package/dist/types/mcp-authoring.d.ts +92 -0
- package/dist/types/mcp.d.ts +12 -0
- package/dist/types/observability.d.ts +2 -0
- package/dist/types/operator-host.d.ts +8 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/policies/agents.d.ts +0 -2
- package/dist/types/policies/compression.d.ts +2 -0
- package/dist/types/policies/security.d.ts +0 -1
- package/dist/types/policy.d.ts +15 -4
- package/dist/types/project-tests.d.ts +3 -1
- package/dist/types/provider-verification.d.ts +53 -0
- package/dist/types/proxy.d.ts +21 -0
- package/dist/types/readiness.d.ts +1 -1
- package/dist/types/recipes.d.ts +30 -0
- package/dist/types/route-diff.d.ts +27 -0
- package/dist/types/runtime.d.ts +11 -0
- package/dist/types/schema-query.d.ts +12 -0
- package/dist/types/signals.d.ts +25 -0
- package/dist/types/site.d.ts +0 -1
- package/dist/types/tooling.d.ts +115 -0
- package/dist/types/types.d.ts +57 -0
- package/dist/types/typescript-authoring.d.ts +12 -0
- package/dist/types/vercel.d.ts +3 -1
- package/dist/types/verify-deployment.d.ts +47 -0
- package/dist/types.js +21 -2
- package/dist/typescript-authoring.js +104 -0
- package/dist/vercel.js +4 -3
- package/dist/verify-deployment.js +270 -0
- package/docs/AI-AUTHORING.md +130 -8
- package/docs/BULK.md +79 -0
- package/docs/CAPABILITIES.md +179 -0
- package/docs/CAPACITY.md +1 -1
- package/docs/CI.md +142 -0
- package/docs/CONDITIONS.md +74 -0
- package/docs/DEPLOYMENT-CHECKS.md +108 -0
- package/docs/DYNAMIC-LINKS.md +18 -518
- package/docs/EGRESS.md +125 -0
- package/docs/EXTENSIONS.md +226 -0
- package/docs/FRAMEWORK.md +182 -0
- package/docs/INSTALL.md +45 -7
- package/docs/INTERCHANGE.md +134 -0
- package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
- package/docs/MIDDLEWARE.md +2 -0
- package/docs/NEXT-PHASE-PLAN.md +90 -0
- package/docs/NEXT-STEPS.md +415 -0
- package/docs/OBSERVABILITY.md +4 -2
- package/docs/OPERATIONAL-PROOF.md +4 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/PLUGINS.md +37 -0
- package/docs/POLICIES.md +12 -309
- package/docs/PRERENDER.md +40 -0
- package/docs/PROJECT-DIRECTION.md +42 -0
- package/docs/PROVIDER-VERIFICATION.md +84 -0
- package/docs/READINESS.md +21 -1
- package/docs/README.md +82 -31
- package/docs/RECIPES.md +99 -0
- package/docs/RELEASE-READINESS.md +11 -9
- package/docs/RELEASE-SECURITY.md +27 -4
- package/docs/SECURITY-AUDIT.md +1 -1
- package/docs/SPECIFICATION.md +95 -8
- package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
- package/docs/SPIKE-EXTENSIONS.md +1 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
- package/docs/STANDARDS.md +150 -142
- package/docs/STARTERS.md +21 -1
- package/docs/TOOLING.md +291 -0
- package/docs/TYPESCRIPT-AUTHORING.md +67 -0
- package/docs/TYPESCRIPT.md +1 -1
- package/docs/USABILITY-REVIEW.md +123 -0
- package/docs/YAML-GUIDE.md +18 -479
- package/docs/YAML-REFERENCE.md +127 -16
- package/docs/links/cli.md +110 -0
- package/docs/links/limits.md +175 -0
- package/docs/links/management-api.md +80 -0
- package/docs/links/pools.md +75 -0
- package/docs/links/setup.md +135 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/contract.md +52 -0
- package/docs/policies/hardened.md +56 -0
- package/docs/policies/interoperability.md +169 -0
- package/docs/policies/operations.md +45 -0
- package/docs/yaml/assets.md +36 -0
- package/docs/yaml/conditions.md +20 -0
- package/docs/yaml/functions.md +160 -0
- package/docs/yaml/links.md +30 -0
- package/docs/yaml/middleware.md +29 -0
- package/docs/yaml/organization.md +74 -0
- package/docs/yaml/policies.md +37 -0
- package/docs/yaml/redirects.md +64 -0
- package/docs/yaml/responses.md +57 -0
- package/docs/yaml/site.md +24 -0
- package/examples/assets/example.yaml +17 -0
- package/examples/aws/example.yaml +20 -0
- package/examples/cloudflare/example.yaml +19 -0
- package/examples/compliance/example.yaml +11 -0
- package/examples/conditions/README.md +12 -0
- package/examples/conditions/example.yaml +19 -0
- package/examples/conditions/tests/requests.json +13 -0
- package/examples/conditions/urlcode.yaml +24 -0
- package/examples/cookbook/README.md +8 -4
- package/examples/cookbook/example.yaml +17 -0
- package/examples/cookbook/functions/catalog.mjs +3 -0
- package/examples/cookbook/functions/fail.mjs +4 -0
- package/examples/cookbook/functions/items.mjs +3 -0
- package/examples/cookbook/functions/profile.mjs +3 -0
- package/examples/cookbook/functions/resource.mjs +3 -0
- package/examples/cookbook/functions/status.mjs +3 -0
- package/examples/cookbook/middleware/auth.mjs +48 -0
- package/examples/cookbook/middleware/body.mjs +15 -0
- package/examples/cookbook/middleware/bucket.mjs +19 -0
- package/examples/cookbook/middleware/cors.mjs +21 -0
- package/examples/cookbook/middleware/debug.mjs +13 -0
- package/examples/cookbook/middleware/envelope.mjs +11 -0
- package/examples/cookbook/middleware/errors.mjs +11 -0
- package/examples/cookbook/middleware/etag.mjs +18 -0
- package/examples/cookbook/middleware/locale.mjs +16 -0
- package/examples/cookbook/middleware/maintenance.mjs +10 -0
- package/examples/cookbook/middleware/methods.mjs +15 -0
- package/examples/cookbook/middleware/negotiate.mjs +20 -0
- package/examples/cookbook/middleware/referer.mjs +12 -0
- package/examples/cookbook/middleware/request-id.mjs +16 -0
- package/examples/cookbook/route-index.json +676 -0
- package/examples/cookbook/routes/middleware.yaml +126 -0
- package/examples/cookbook/tests/requests.json +526 -0
- package/examples/cookbook/urlcode.yaml +1 -0
- package/examples/egress/README.md +22 -0
- package/examples/egress/example.yaml +19 -0
- package/examples/egress/urlcode.yaml +19 -0
- package/examples/extensions/README.md +7 -0
- package/examples/extensions/example.yaml +21 -0
- package/examples/extensions/urlcode.yaml +25 -0
- package/examples/live-links/example.yaml +21 -0
- package/examples/monitoring/example.yaml +8 -0
- package/examples/prerender/example.yaml +16 -0
- package/examples/provider-conformance/README.md +12 -0
- package/examples/provider-conformance/example.yaml +14 -0
- package/examples/provider-conformance/urlcode.yaml +34 -0
- package/examples/tunnel/example.yaml +8 -0
- package/examples/vercel/example.yaml +19 -0
- package/llms-full.txt +2709 -0
- package/llms.txt +48 -19
- package/package.json +29 -7
- package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
- package/recipes/authenticated-json-api/README.md +51 -0
- package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
- package/recipes/authenticated-json-api/recipe.yaml +34 -0
- package/recipes/authenticated-json-api/tests/requests.json +39 -0
- package/recipes/authenticated-json-api/urlcode.yaml +12 -0
- package/recipes/contact-form/README.md +25 -0
- package/recipes/contact-form/functions/contact.mjs +17 -0
- package/recipes/contact-form/recipe.yaml +33 -0
- package/recipes/contact-form/tests/requests.json +47 -0
- package/recipes/contact-form/urlcode.yaml +18 -0
- package/recipes/cors-api/README.md +16 -0
- package/recipes/cors-api/functions/items.mjs +3 -0
- package/recipes/cors-api/middleware/cors.mjs +21 -0
- package/recipes/cors-api/recipe.yaml +26 -0
- package/recipes/cors-api/tests/requests.json +65 -0
- package/recipes/cors-api/urlcode.yaml +12 -0
- package/recipes/health-page/README.md +13 -0
- package/recipes/health-page/recipe.yaml +23 -0
- package/recipes/health-page/tests/requests.json +36 -0
- package/recipes/health-page/urlcode.yaml +19 -0
- package/recipes/json-api/README.md +6 -0
- package/recipes/json-api/functions/echo.mjs +3 -0
- package/recipes/json-api/recipe.yaml +25 -0
- package/recipes/json-api/tests/requests.json +34 -0
- package/recipes/json-api/urlcode.yaml +12 -0
- package/recipes/middleware/README.md +34 -0
- package/recipes/middleware/functions/catalog.mjs +3 -0
- package/recipes/middleware/functions/fail.mjs +4 -0
- package/recipes/middleware/functions/items.mjs +3 -0
- package/recipes/middleware/functions/profile.mjs +3 -0
- package/recipes/middleware/functions/resource.mjs +3 -0
- package/recipes/middleware/functions/status.mjs +3 -0
- package/recipes/middleware/middleware/auth.mjs +48 -0
- package/recipes/middleware/middleware/body.mjs +15 -0
- package/recipes/middleware/middleware/bucket.mjs +19 -0
- package/recipes/middleware/middleware/cors.mjs +21 -0
- package/recipes/middleware/middleware/debug.mjs +13 -0
- package/recipes/middleware/middleware/envelope.mjs +11 -0
- package/recipes/middleware/middleware/errors.mjs +11 -0
- package/recipes/middleware/middleware/etag.mjs +18 -0
- package/recipes/middleware/middleware/locale.mjs +16 -0
- package/recipes/middleware/middleware/maintenance.mjs +10 -0
- package/recipes/middleware/middleware/methods.mjs +15 -0
- package/recipes/middleware/middleware/negotiate.mjs +20 -0
- package/recipes/middleware/middleware/referer.mjs +12 -0
- package/recipes/middleware/middleware/request-id.mjs +16 -0
- package/recipes/middleware/public/guide.txt +1 -0
- package/recipes/middleware/recipe.yaml +50 -0
- package/recipes/middleware/tests/requests.json +528 -0
- package/recipes/middleware/urlcode.yaml +127 -0
- package/recipes/protected-download/README.md +22 -0
- package/recipes/protected-download/files/report.txt +1 -0
- package/recipes/protected-download/recipe.yaml +31 -0
- package/recipes/protected-download/tests/requests.json +32 -0
- package/recipes/protected-download/urlcode.yaml +15 -0
- package/recipes/redirect/README.md +7 -0
- package/recipes/redirect/recipe.yaml +25 -0
- package/recipes/redirect/tests/requests.json +19 -0
- package/recipes/redirect/urlcode.yaml +9 -0
- package/recipes/static-plus-api/README.md +15 -0
- package/recipes/static-plus-api/functions/info.mjs +3 -0
- package/recipes/static-plus-api/public/assets/index.html +3 -0
- package/recipes/static-plus-api/public/assets/site.css +1 -0
- package/recipes/static-plus-api/public/index.html +8 -0
- package/recipes/static-plus-api/recipe.yaml +29 -0
- package/recipes/static-plus-api/tests/requests.json +56 -0
- package/recipes/static-plus-api/urlcode.yaml +23 -0
- package/recipes/typescript/README.md +7 -0
- package/recipes/typescript/functions/hello.ts +5 -0
- package/recipes/typescript/recipe.yaml +23 -0
- package/recipes/typescript/tests/requests.json +18 -0
- package/recipes/typescript/urlcode.yaml +5 -0
- package/recipes/webhook-receiver/README.md +16 -0
- package/recipes/webhook-receiver/functions/receive.mjs +16 -0
- package/recipes/webhook-receiver/recipe.yaml +26 -0
- package/recipes/webhook-receiver/tests/requests.json +59 -0
- package/recipes/webhook-receiver/urlcode.yaml +16 -0
- package/schemas/recipe.schema.json +138 -0
- package/schemas/urlcode.schema.json +656 -80
- package/skills/urlcode/SKILL.md +98 -0
- package/starters/default/.github/workflows/urlcode.yml +23 -0
- package/starters/default/.mcp.json +12 -0
- package/starters/default/AGENTS.md +79 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
version: "1"
|
|
2
|
+
# Reusable middleware patterns, one route per module. Bindings are literal values
|
|
3
|
+
# here; production projects use {secret: name} with an operator grant instead.
|
|
4
|
+
# Rename or delete the routes you do not need; the modules are independent.
|
|
5
|
+
routes:
|
|
6
|
+
/api/private:
|
|
7
|
+
description: Bearer token gate with a constant-time comparison
|
|
8
|
+
env:
|
|
9
|
+
API_TOKEN: {value: cookbook-token}
|
|
10
|
+
middleware:
|
|
11
|
+
- source: middleware/auth.mjs
|
|
12
|
+
export: bearer
|
|
13
|
+
respond: {json: {private: true}}
|
|
14
|
+
/admin/panel:
|
|
15
|
+
description: HTTP Basic authentication decoded inside the guest
|
|
16
|
+
env:
|
|
17
|
+
ADMIN_USER: {value: admin}
|
|
18
|
+
ADMIN_PASSWORD: {value: cookbook-password}
|
|
19
|
+
middleware:
|
|
20
|
+
- source: middleware/auth.mjs
|
|
21
|
+
export: basic
|
|
22
|
+
respond: {text: Admin panel}
|
|
23
|
+
/cors/data:
|
|
24
|
+
description: CORS allowlist with preflight handled before the handler
|
|
25
|
+
methods: [GET, HEAD, OPTIONS]
|
|
26
|
+
env:
|
|
27
|
+
ALLOWED_ORIGINS: {value: https://app.example.com https://admin.example.com}
|
|
28
|
+
middleware:
|
|
29
|
+
- source: middleware/cors.mjs
|
|
30
|
+
respond: {json: {shared: true}}
|
|
31
|
+
/traced:
|
|
32
|
+
description: Caller correlation id and Server-Timing on every response
|
|
33
|
+
middleware:
|
|
34
|
+
- source: middleware/request-id.mjs
|
|
35
|
+
respond: {text: Traced}
|
|
36
|
+
/maintenance:
|
|
37
|
+
description: Kill switch returning 503 unless a bypass header is presented
|
|
38
|
+
env:
|
|
39
|
+
MAINTENANCE: {value: "true"}
|
|
40
|
+
MAINTENANCE_BYPASS: {value: let-me-in}
|
|
41
|
+
middleware:
|
|
42
|
+
- source: middleware/maintenance.mjs
|
|
43
|
+
respond: {text: Still here}
|
|
44
|
+
/fragile:
|
|
45
|
+
description: Error boundary turning a thrown exception into a JSON 500
|
|
46
|
+
parameters:
|
|
47
|
+
- name: fail
|
|
48
|
+
in: query
|
|
49
|
+
schema: {type: boolean, default: false}
|
|
50
|
+
middleware:
|
|
51
|
+
- source: middleware/request-id.mjs
|
|
52
|
+
- source: middleware/errors.mjs
|
|
53
|
+
function:
|
|
54
|
+
source: functions/fail.mjs
|
|
55
|
+
args:
|
|
56
|
+
fail: {from: query, name: fail}
|
|
57
|
+
/api/items:
|
|
58
|
+
description: JSON envelope around a function response
|
|
59
|
+
middleware:
|
|
60
|
+
- source: middleware/envelope.mjs
|
|
61
|
+
function: {source: functions/items.mjs}
|
|
62
|
+
/negotiated:
|
|
63
|
+
description: Accept-driven JSON or plain-text representation, 406 otherwise
|
|
64
|
+
middleware:
|
|
65
|
+
- source: middleware/negotiate.mjs
|
|
66
|
+
function: {source: functions/status.mjs}
|
|
67
|
+
/resource:
|
|
68
|
+
description: Method override tunneling PUT, PATCH or DELETE through POST
|
|
69
|
+
methods: [GET, HEAD, POST, PUT, PATCH, DELETE]
|
|
70
|
+
middleware:
|
|
71
|
+
- source: middleware/methods.mjs
|
|
72
|
+
export: override
|
|
73
|
+
function: {source: functions/resource.mjs}
|
|
74
|
+
/versioned:
|
|
75
|
+
description: Weak ETag and 304 for function output
|
|
76
|
+
middleware:
|
|
77
|
+
- source: middleware/etag.mjs
|
|
78
|
+
function: {source: functions/catalog.mjs}
|
|
79
|
+
/experiment:
|
|
80
|
+
description: Sticky A/B bucket cookie choosing between two destinations
|
|
81
|
+
env:
|
|
82
|
+
VARIANT_URL: {value: https://example.com/landing-b}
|
|
83
|
+
middleware:
|
|
84
|
+
- source: middleware/bucket.mjs
|
|
85
|
+
redirect: {url: https://example.com/landing}
|
|
86
|
+
/welcome:
|
|
87
|
+
description: Accept-Language redirect to a localized destination
|
|
88
|
+
env:
|
|
89
|
+
LOCALES: {value: en de fr}
|
|
90
|
+
SITE: {value: https://example.com}
|
|
91
|
+
middleware:
|
|
92
|
+
- source: middleware/locale.mjs
|
|
93
|
+
redirect: {url: https://example.com/en/welcome}
|
|
94
|
+
/downloads/report:
|
|
95
|
+
description: Referer allowlist in front of a native download
|
|
96
|
+
env:
|
|
97
|
+
ALLOWED_REFERERS: {value: example.com www.example.com}
|
|
98
|
+
middleware:
|
|
99
|
+
- source: middleware/referer.mjs
|
|
100
|
+
download:
|
|
101
|
+
file: public/guide.txt
|
|
102
|
+
filename: report.txt
|
|
103
|
+
contentType: text/plain
|
|
104
|
+
cacheControl: no-store
|
|
105
|
+
/profile:
|
|
106
|
+
description: Body validated once in middleware, parsed object passed through state
|
|
107
|
+
methods: [POST]
|
|
108
|
+
request:
|
|
109
|
+
body:
|
|
110
|
+
required: true
|
|
111
|
+
maxBytes: 4096
|
|
112
|
+
contentTypes: [application/json]
|
|
113
|
+
format: json
|
|
114
|
+
middleware:
|
|
115
|
+
- source: middleware/body.mjs
|
|
116
|
+
function: {source: functions/profile.mjs}
|
|
117
|
+
/inspect:
|
|
118
|
+
description: Authoring aid echoing inputs, args and redacted headers on demand
|
|
119
|
+
parameters:
|
|
120
|
+
- name: topic
|
|
121
|
+
in: query
|
|
122
|
+
schema: {type: string, default: routing}
|
|
123
|
+
env:
|
|
124
|
+
DEBUG: {value: "true"}
|
|
125
|
+
middleware:
|
|
126
|
+
- source: middleware/debug.mjs
|
|
127
|
+
respond: {text: Nothing to see}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Protected download
|
|
2
|
+
|
|
3
|
+
`/downloads/report` serves `files/report.txt` as an attachment, but only after
|
|
4
|
+
the operator-installed `auth` extension authorizes the request (`auth: true`,
|
|
5
|
+
the short form of `policies.extensions.auth`). The file is
|
|
6
|
+
served natively: no guest code runs, and the response is forced to `no-store`.
|
|
7
|
+
|
|
8
|
+
Like the `authenticated-json-api` recipe, this project declares the extension
|
|
9
|
+
and needs an operator host file outside the project plus the canonical origin
|
|
10
|
+
for every command:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
urlcode validate --local --project . --host-file /operator/host.mjs --origin https://files.example.com
|
|
14
|
+
urlcode test --project . --host-file /operator/host.mjs --origin https://files.example.com
|
|
15
|
+
urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://files.example.com
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use the host file from the `authenticated-json-api` README with the config
|
|
19
|
+
schema accepting `realm: downloads`; the bundled fixtures expect the demo token
|
|
20
|
+
`demo-token`. A real deployment registers `urlcode-auth` instead. Replace
|
|
21
|
+
`files/report.txt` with the real attachment and adjust `filename` and
|
|
22
|
+
`contentType`. Cloudflare refuses extensions.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Quarterly report (placeholder). Replace this file with the real attachment.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
id: protected-download
|
|
2
|
+
description: Native attachment download that only the operator-installed auth extension lets through.
|
|
3
|
+
tags: [download, attachment, protected, auth, authenticated, file, private, extension, content-disposition, "401"]
|
|
4
|
+
complexity: advanced
|
|
5
|
+
capabilities: [download, enabled, extension, methods, policies.extensions]
|
|
6
|
+
targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
|
|
7
|
+
routes: 1
|
|
8
|
+
services:
|
|
9
|
+
- name: auth extension
|
|
10
|
+
description: An operator registry providing the `auth` extension; the authenticated-json-api README shows a minimal protocol fixture.
|
|
11
|
+
grants:
|
|
12
|
+
- kind: extension
|
|
13
|
+
description: The registration pins projectSha256 to this exact revision, including the attachment.
|
|
14
|
+
- kind: origin
|
|
15
|
+
description: The canonical origin passed as --origin at every command that activates the project.
|
|
16
|
+
inputs:
|
|
17
|
+
- name: attachment
|
|
18
|
+
file: files/report.txt
|
|
19
|
+
description: Replace the placeholder and adjust filename and contentType in urlcode.yaml.
|
|
20
|
+
files: [urlcode.yaml, files/report.txt, tests/requests.json, README.md]
|
|
21
|
+
tests:
|
|
22
|
+
fixtures: tests/requests.json
|
|
23
|
+
commands:
|
|
24
|
+
- urlcode validate --local --project . --host-file /operator/host.mjs --origin https://files.example.com
|
|
25
|
+
- urlcode test --project . --host-file /operator/host.mjs --origin https://files.example.com
|
|
26
|
+
- urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://files.example.com
|
|
27
|
+
behavior:
|
|
28
|
+
- GET /downloads/report without credentials answers 401
|
|
29
|
+
- GET with the accepted credential answers 200, Content-Disposition attachment and Cache-Control no-store
|
|
30
|
+
- HEAD mirrors both cases with an empty body
|
|
31
|
+
- no guest code runs; the file is served natively and snapshotted at activation
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/downloads/report",
|
|
4
|
+
"status": 401
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "/downloads/report",
|
|
8
|
+
"headers": {
|
|
9
|
+
"authorization": "Bearer demo-token"
|
|
10
|
+
},
|
|
11
|
+
"status": 200,
|
|
12
|
+
"expectBody": "Quarterly report (placeholder). Replace this file with the real attachment.\n",
|
|
13
|
+
"expectHeaders": {
|
|
14
|
+
"content-disposition": "attachment; filename=quarterly-report.txt",
|
|
15
|
+
"cache-control": "no-store"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "/downloads/report",
|
|
20
|
+
"method": "HEAD",
|
|
21
|
+
"headers": {
|
|
22
|
+
"authorization": "Bearer demo-token"
|
|
23
|
+
},
|
|
24
|
+
"status": 200,
|
|
25
|
+
"expectBody": ""
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "/downloads/report",
|
|
29
|
+
"method": "HEAD",
|
|
30
|
+
"status": 401
|
|
31
|
+
}
|
|
32
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: "1"
|
|
2
|
+
extensions:
|
|
3
|
+
auth:
|
|
4
|
+
version: "1"
|
|
5
|
+
config:
|
|
6
|
+
realm: downloads
|
|
7
|
+
routes:
|
|
8
|
+
/downloads/report:
|
|
9
|
+
description: Attachment served natively, only after the auth extension authorizes the caller.
|
|
10
|
+
download:
|
|
11
|
+
file: files/report.txt
|
|
12
|
+
filename: quarterly-report.txt
|
|
13
|
+
contentType: text/plain
|
|
14
|
+
cacheControl: no-store
|
|
15
|
+
auth: true
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Permanent documentation redirect
|
|
2
|
+
|
|
3
|
+
Run `urlcode validate --local --project .` and `urlcode serve --project .`.
|
|
4
|
+
`/docs?campaign=launch&private=discarded` redirects to
|
|
5
|
+
`https://example.com/documentation?campaign=launch` with status 301.
|
|
6
|
+
Replace the example destination before deploying. Incoming query parameters are
|
|
7
|
+
not forwarded unless explicitly allowlisted.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
id: redirect
|
|
2
|
+
description: Permanent redirect that forwards only an allowlisted query key.
|
|
3
|
+
tags: [redirect, permanent, "301", query, passthrough, documentation, native]
|
|
4
|
+
complexity: starter
|
|
5
|
+
capabilities: [enabled, methods, redirect]
|
|
6
|
+
targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
|
|
7
|
+
routes: 1
|
|
8
|
+
inputs:
|
|
9
|
+
- name: destination
|
|
10
|
+
file: urlcode.yaml
|
|
11
|
+
description: Replace https://example.com/documentation with the real target.
|
|
12
|
+
- name: query.pass
|
|
13
|
+
file: urlcode.yaml
|
|
14
|
+
description: The query keys forwarded to the destination; everything else is dropped.
|
|
15
|
+
files: [urlcode.yaml, tests/requests.json, README.md]
|
|
16
|
+
tests:
|
|
17
|
+
fixtures: tests/requests.json
|
|
18
|
+
commands:
|
|
19
|
+
- urlcode validate --local --project .
|
|
20
|
+
- urlcode test --project .
|
|
21
|
+
- urlcode audit --project . --expect-routes 1
|
|
22
|
+
behavior:
|
|
23
|
+
- GET /docs?campaign=launch&private=x answers 301 to https://example.com/documentation?campaign=launch
|
|
24
|
+
- HEAD answers the same status with an empty body
|
|
25
|
+
- unknown paths answer 404
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/docs?campaign=launch&private=discarded",
|
|
4
|
+
"status": 301,
|
|
5
|
+
"expectHeaders": {
|
|
6
|
+
"location": "https://example.com/documentation?campaign=launch"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "/docs",
|
|
11
|
+
"method": "HEAD",
|
|
12
|
+
"status": 301,
|
|
13
|
+
"expectBody": ""
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "/missing",
|
|
17
|
+
"status": 404
|
|
18
|
+
}
|
|
19
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Static site with a JSON API
|
|
2
|
+
|
|
3
|
+
Run `urlcode validate --local --project .`, `urlcode test --project .` and
|
|
4
|
+
`urlcode audit --project . --expect-routes 3`.
|
|
5
|
+
|
|
6
|
+
`/` serves `public/index.html`, `/assets/*` serves everything under
|
|
7
|
+
`public/assets` (with `index.html` for the directory itself), and `/api/info`
|
|
8
|
+
is a sandboxed function returning JSON built from literal `args`. Pages and
|
|
9
|
+
assets are native: no guest code runs for them, and they are snapshotted at
|
|
10
|
+
activation, so new files need a reload. Edit the HTML, add files under
|
|
11
|
+
`public/assets`, and change or extend `functions/info.mjs`.
|
|
12
|
+
|
|
13
|
+
The function needs the self-hosted runtime; drop `/api/info` to deploy the
|
|
14
|
+
static part on a serverless target. Static mounts cannot escape their
|
|
15
|
+
directory, and the recipe sets explicit caching so nothing is guessed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head><meta charset="utf-8"><title>Example site</title><link rel="stylesheet" href="/assets/site.css"></head>
|
|
4
|
+
<body>
|
|
5
|
+
<h1>Example site</h1>
|
|
6
|
+
<p>Static pages are served natively; <a href="/api/info">/api/info</a> is a sandboxed function.</p>
|
|
7
|
+
</body>
|
|
8
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
id: static-plus-api
|
|
2
|
+
description: A native static site (home page and an assets directory) next to one sandboxed JSON endpoint.
|
|
3
|
+
tags: [static, site, page, html, assets, css, api, json, function, cache-control, website]
|
|
4
|
+
complexity: intermediate
|
|
5
|
+
capabilities: [enabled, function, methods, page, response.headers, static]
|
|
6
|
+
targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
|
|
7
|
+
routes: 3
|
|
8
|
+
inputs:
|
|
9
|
+
- name: pages
|
|
10
|
+
file: public/index.html
|
|
11
|
+
description: The home page and everything under public/assets.
|
|
12
|
+
- name: args
|
|
13
|
+
file: urlcode.yaml
|
|
14
|
+
description: The literal name and version /api/info reports.
|
|
15
|
+
- name: handler
|
|
16
|
+
file: functions/info.mjs
|
|
17
|
+
description: Replace the literal JSON with the real endpoint.
|
|
18
|
+
files: [urlcode.yaml, public/index.html, public/assets/index.html, public/assets/site.css, functions/info.mjs, tests/requests.json, README.md]
|
|
19
|
+
tests:
|
|
20
|
+
fixtures: tests/requests.json
|
|
21
|
+
commands:
|
|
22
|
+
- urlcode validate --local --project .
|
|
23
|
+
- urlcode test --project .
|
|
24
|
+
- urlcode audit --project . --expect-routes 3
|
|
25
|
+
behavior:
|
|
26
|
+
- GET / answers the home page as text/html with Cache-Control no-cache
|
|
27
|
+
- GET /assets/site.css answers text/css cached for one hour; /assets/ answers the directory index; missing files answer 404
|
|
28
|
+
- GET /api/info answers 200 {"name":"Example site","version":"1"} with no-store
|
|
29
|
+
- drop /api/info to deploy the static part on a serverless target
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/",
|
|
4
|
+
"status": 200,
|
|
5
|
+
"expectHeaders": {
|
|
6
|
+
"content-type": "text/html; charset=utf-8",
|
|
7
|
+
"cache-control": "no-cache"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "/",
|
|
12
|
+
"method": "HEAD",
|
|
13
|
+
"status": 200,
|
|
14
|
+
"expectBody": ""
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "/assets/site.css",
|
|
18
|
+
"status": 200,
|
|
19
|
+
"expectBody": "body { font-family: system-ui, sans-serif; margin: 2rem; }\n",
|
|
20
|
+
"expectHeaders": {
|
|
21
|
+
"content-type": "text/css; charset=utf-8",
|
|
22
|
+
"cache-control": "public, max-age=3600"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "/assets/",
|
|
27
|
+
"status": 200,
|
|
28
|
+
"expectHeaders": {
|
|
29
|
+
"content-type": "text/html; charset=utf-8"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "/assets/site.css",
|
|
34
|
+
"method": "HEAD",
|
|
35
|
+
"status": 200,
|
|
36
|
+
"expectBody": ""
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "/assets/missing.css",
|
|
40
|
+
"status": 404
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "/api/info",
|
|
44
|
+
"status": 200,
|
|
45
|
+
"expectBody": "{\"name\":\"Example site\",\"version\":\"1\"}",
|
|
46
|
+
"expectHeaders": {
|
|
47
|
+
"cache-control": "no-store"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "/api/info",
|
|
52
|
+
"method": "HEAD",
|
|
53
|
+
"status": 200,
|
|
54
|
+
"expectBody": ""
|
|
55
|
+
}
|
|
56
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: "1"
|
|
2
|
+
routes:
|
|
3
|
+
/:
|
|
4
|
+
description: The site's home page served natively from public/index.html.
|
|
5
|
+
page:
|
|
6
|
+
file: public/index.html
|
|
7
|
+
cacheControl: no-cache
|
|
8
|
+
/assets/*:
|
|
9
|
+
description: Every file under public/assets, with a directory index and one-hour caching.
|
|
10
|
+
static:
|
|
11
|
+
directory: public/assets
|
|
12
|
+
index: index.html
|
|
13
|
+
cacheControl: public, max-age=3600
|
|
14
|
+
/api/info:
|
|
15
|
+
description: A small JSON endpoint next to the static site, computed in the sandbox.
|
|
16
|
+
function:
|
|
17
|
+
source: functions/info.mjs
|
|
18
|
+
args:
|
|
19
|
+
name: Example site
|
|
20
|
+
version: "1"
|
|
21
|
+
response:
|
|
22
|
+
headers:
|
|
23
|
+
Cache-Control: no-store
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Build-time TypeScript guest
|
|
2
|
+
|
|
3
|
+
Run `urlcode build-typescript --project . --out ../hello-built` with a new output
|
|
4
|
+
directory, then `urlcode validate --local --project ../hello-built` and
|
|
5
|
+
`urlcode serve --project ../hello-built`. GET `/hello` returns JSON.
|
|
6
|
+
TypeScript is transpiled ahead of time. The runtime executes only the emitted
|
|
7
|
+
JavaScript in QuickJS/WASM. The build does not type-check or read tsconfig.json.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
id: typescript
|
|
2
|
+
description: Typed guest function transpiled ahead of time; only the emitted JavaScript runs in the sandbox.
|
|
3
|
+
tags: [typescript, function, build, typed, compile, sandbox]
|
|
4
|
+
complexity: intermediate
|
|
5
|
+
capabilities: [enabled, function, methods]
|
|
6
|
+
targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
|
|
7
|
+
routes: 1
|
|
8
|
+
inputs:
|
|
9
|
+
- name: handler
|
|
10
|
+
file: functions/hello.ts
|
|
11
|
+
description: The typed guest; keep to the Request/Response sandbox API.
|
|
12
|
+
files: [urlcode.yaml, functions/hello.ts, tests/requests.json, README.md]
|
|
13
|
+
tests:
|
|
14
|
+
fixtures: tests/requests.json
|
|
15
|
+
commands:
|
|
16
|
+
- urlcode build-typescript --project . --out ../hello-built
|
|
17
|
+
- urlcode validate --local --project ../hello-built
|
|
18
|
+
- urlcode test --project ../hello-built
|
|
19
|
+
- urlcode audit --project ../hello-built --expect-routes 1
|
|
20
|
+
behavior:
|
|
21
|
+
- after the build, GET /hello answers 200 JSON from the compiled guest
|
|
22
|
+
- the unbuilt project does not activate; .ts sources never run
|
|
23
|
+
- POST answers 405
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/hello",
|
|
4
|
+
"status": 200,
|
|
5
|
+
"expectBody": "{\"message\":\"Hello from a compiled TypeScript guest\"}"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"path": "/hello",
|
|
9
|
+
"method": "HEAD",
|
|
10
|
+
"status": 200,
|
|
11
|
+
"expectBody": ""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "/hello",
|
|
15
|
+
"method": "POST",
|
|
16
|
+
"status": 405
|
|
17
|
+
}
|
|
18
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Webhook receiver
|
|
2
|
+
|
|
3
|
+
Run `urlcode validate --local --project .`, `urlcode test --project .` and
|
|
4
|
+
`urlcode audit --project . --expect-routes 1`.
|
|
5
|
+
|
|
6
|
+
`POST /webhook` accepts at most 64 KiB of `application/json`. The runtime
|
|
7
|
+
rejects other methods (405), other content types (415), oversized bodies (413)
|
|
8
|
+
and malformed JSON (400) before the sandbox runs. The function then requires an
|
|
9
|
+
`X-Webhook-Event` header and a JSON object carrying a string `id`, and answers
|
|
10
|
+
`202 {"received":true,...}`.
|
|
11
|
+
|
|
12
|
+
This recipe verifies shape, not origin. Nothing here checks a signature: the
|
|
13
|
+
guest has no crypto API and no network access, so signed webhooks belong behind
|
|
14
|
+
an operator-installed extension or a trusted host in front of this route. Edit
|
|
15
|
+
`functions/receive.mjs` to check the fields your sender guarantees, and keep
|
|
16
|
+
the body limit as small as the sender allows.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The runtime has already enforced method, content type, size and JSON syntax.
|
|
2
|
+
// The guest checks the shape it cares about and acknowledges; it has no network
|
|
3
|
+
// access, so forwarding happens through a declared route signal or a host process
|
|
4
|
+
// that reads the runtime log, never from here.
|
|
5
|
+
const eventPattern = /^[a-z][a-z0-9_.-]{0,63}$/;
|
|
6
|
+
export default async function receive(request) {
|
|
7
|
+
const event = request.headers.get('x-webhook-event');
|
|
8
|
+
if (!event || !eventPattern.test(event)) {
|
|
9
|
+
return Response.json({error: 'missing or invalid X-Webhook-Event header'}, {status: 400});
|
|
10
|
+
}
|
|
11
|
+
const payload = await request.json();
|
|
12
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload) || typeof payload.id !== 'string') {
|
|
13
|
+
return Response.json({error: 'body must be a JSON object with a string id'}, {status: 422});
|
|
14
|
+
}
|
|
15
|
+
return Response.json({received: true, event, id: payload.id}, {status: 202});
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id: webhook-receiver
|
|
2
|
+
description: Accept a bounded JSON event that names its type in a header, check its shape and acknowledge with 202.
|
|
3
|
+
tags: [webhook, receiver, event, post, json, body, "202", callback, ingest, function]
|
|
4
|
+
complexity: starter
|
|
5
|
+
capabilities: [enabled, function, methods, request.body, response.headers]
|
|
6
|
+
targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
|
|
7
|
+
routes: 1
|
|
8
|
+
inputs:
|
|
9
|
+
- name: shape check
|
|
10
|
+
file: functions/receive.mjs
|
|
11
|
+
description: The header name, event pattern and payload fields the sender guarantees.
|
|
12
|
+
- name: maxBytes
|
|
13
|
+
file: urlcode.yaml
|
|
14
|
+
description: The body limit (64 KiB); keep it as small as the sender allows.
|
|
15
|
+
files: [urlcode.yaml, functions/receive.mjs, tests/requests.json, README.md]
|
|
16
|
+
tests:
|
|
17
|
+
fixtures: tests/requests.json
|
|
18
|
+
commands:
|
|
19
|
+
- urlcode validate --local --project .
|
|
20
|
+
- urlcode test --project .
|
|
21
|
+
- urlcode audit --project . --expect-routes 1
|
|
22
|
+
behavior:
|
|
23
|
+
- POST /webhook with application/json, X-Webhook-Event and a string id answers 202 {"received":true,"event":...,"id":...}
|
|
24
|
+
- a missing event header answers 400; a body that is not an object with a string id answers 422
|
|
25
|
+
- other methods 405, other content types 415, oversized bodies 413 and malformed JSON 400 before the sandbox runs
|
|
26
|
+
- the guest has no crypto or network API, so signatures are not verified here; put signed webhooks behind an operator extension or trusted host
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/webhook",
|
|
4
|
+
"method": "POST",
|
|
5
|
+
"headers": {
|
|
6
|
+
"content-type": "application/json",
|
|
7
|
+
"x-webhook-event": "order.created"
|
|
8
|
+
},
|
|
9
|
+
"body": "{\"id\":\"evt_123\",\"total\":42}",
|
|
10
|
+
"status": 202,
|
|
11
|
+
"expectBody": "{\"received\":true,\"event\":\"order.created\",\"id\":\"evt_123\"}",
|
|
12
|
+
"expectHeaders": {
|
|
13
|
+
"cache-control": "no-store"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "/webhook",
|
|
18
|
+
"method": "POST",
|
|
19
|
+
"headers": {
|
|
20
|
+
"content-type": "application/json"
|
|
21
|
+
},
|
|
22
|
+
"body": "{\"id\":\"evt_123\"}",
|
|
23
|
+
"status": 400
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "/webhook",
|
|
27
|
+
"method": "POST",
|
|
28
|
+
"headers": {
|
|
29
|
+
"content-type": "application/json",
|
|
30
|
+
"x-webhook-event": "order.created"
|
|
31
|
+
},
|
|
32
|
+
"body": "[1,2,3]",
|
|
33
|
+
"status": 422
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"path": "/webhook",
|
|
37
|
+
"method": "POST",
|
|
38
|
+
"headers": {
|
|
39
|
+
"content-type": "application/json",
|
|
40
|
+
"x-webhook-event": "order.created"
|
|
41
|
+
},
|
|
42
|
+
"body": "{not json",
|
|
43
|
+
"status": 400
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"path": "/webhook",
|
|
47
|
+
"method": "POST",
|
|
48
|
+
"headers": {
|
|
49
|
+
"content-type": "text/plain",
|
|
50
|
+
"x-webhook-event": "order.created"
|
|
51
|
+
},
|
|
52
|
+
"body": "id=1",
|
|
53
|
+
"status": 415
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"path": "/webhook",
|
|
57
|
+
"status": 405
|
|
58
|
+
}
|
|
59
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
version: "1"
|
|
2
|
+
routes:
|
|
3
|
+
/webhook:
|
|
4
|
+
description: Accept a bounded JSON event body that names its event type in a header.
|
|
5
|
+
methods: [POST]
|
|
6
|
+
request:
|
|
7
|
+
body:
|
|
8
|
+
required: true
|
|
9
|
+
maxBytes: 65536
|
|
10
|
+
contentTypes: [application/json]
|
|
11
|
+
format: json
|
|
12
|
+
function:
|
|
13
|
+
source: functions/receive.mjs
|
|
14
|
+
response:
|
|
15
|
+
headers:
|
|
16
|
+
Cache-Control: no-store
|