@adityanair98/api-oracle 0.5.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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/dist/cli.d.ts +11 -0
  4. package/dist/cli.js +74 -0
  5. package/dist/dashboard/public/app.js +1004 -0
  6. package/dist/dashboard/public/index.html +142 -0
  7. package/dist/dashboard/public/public/app.js +1004 -0
  8. package/dist/dashboard/public/public/index.html +142 -0
  9. package/dist/dashboard/public/public/styles.css +1464 -0
  10. package/dist/dashboard/public/styles.css +1464 -0
  11. package/dist/dashboard/routes/api.d.ts +7 -0
  12. package/dist/dashboard/routes/api.js +245 -0
  13. package/dist/dashboard/server.d.ts +9 -0
  14. package/dist/dashboard/server.js +45 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.js +23 -0
  17. package/dist/knowledge/db.d.ts +22 -0
  18. package/dist/knowledge/db.js +182 -0
  19. package/dist/knowledge/schema.d.ts +275 -0
  20. package/dist/knowledge/schema.js +135 -0
  21. package/dist/knowledge/scorer.d.ts +63 -0
  22. package/dist/knowledge/scorer.js +314 -0
  23. package/dist/knowledge/search.d.ts +37 -0
  24. package/dist/knowledge/search.js +111 -0
  25. package/dist/knowledge/synonyms.d.ts +36 -0
  26. package/dist/knowledge/synonyms.js +523 -0
  27. package/dist/knowledge/tfidf.d.ts +42 -0
  28. package/dist/knowledge/tfidf.js +138 -0
  29. package/dist/server.d.ts +9 -0
  30. package/dist/server.js +40 -0
  31. package/dist/tools/check-freshness.d.ts +9 -0
  32. package/dist/tools/check-freshness.js +95 -0
  33. package/dist/tools/compare-apis.d.ts +8 -0
  34. package/dist/tools/compare-apis.js +149 -0
  35. package/dist/tools/find-api.d.ts +9 -0
  36. package/dist/tools/find-api.js +120 -0
  37. package/dist/tools/get-setup-guide.d.ts +8 -0
  38. package/dist/tools/get-setup-guide.js +127 -0
  39. package/dist/updater/linter.d.ts +31 -0
  40. package/dist/updater/linter.js +219 -0
  41. package/dist/updater/report.d.ts +29 -0
  42. package/dist/updater/report.js +96 -0
  43. package/dist/updater/staleness.d.ts +39 -0
  44. package/dist/updater/staleness.js +66 -0
  45. package/dist/updater/version-tracker.d.ts +28 -0
  46. package/dist/updater/version-tracker.js +50 -0
  47. package/dist/utils/config.d.ts +11 -0
  48. package/dist/utils/config.js +13 -0
  49. package/dist/utils/logger.d.ts +20 -0
  50. package/dist/utils/logger.js +32 -0
  51. package/package.json +56 -0
  52. package/src/entries/ai/anthropic.json +95 -0
  53. package/src/entries/ai/eleven-labs.json +90 -0
  54. package/src/entries/ai/openai.json +95 -0
  55. package/src/entries/ai/replicate.json +87 -0
  56. package/src/entries/ai/resemble-ai.json +88 -0
  57. package/src/entries/ai/stability-ai.json +89 -0
  58. package/src/entries/analytics/posthog.json +88 -0
  59. package/src/entries/analytics/sentry.json +84 -0
  60. package/src/entries/auth/auth0.json +90 -0
  61. package/src/entries/auth/clerk.json +95 -0
  62. package/src/entries/cms/contentful.json +92 -0
  63. package/src/entries/cms/sanity.json +92 -0
  64. package/src/entries/cms/strapi.json +93 -0
  65. package/src/entries/commerce/medusa.json +91 -0
  66. package/src/entries/commerce/shopify-api.json +91 -0
  67. package/src/entries/communication/sendbird.json +85 -0
  68. package/src/entries/communication/stream-chat.json +94 -0
  69. package/src/entries/database/firebase.json +88 -0
  70. package/src/entries/database/neon.json +94 -0
  71. package/src/entries/database/planetscale.json +95 -0
  72. package/src/entries/database/supabase.json +94 -0
  73. package/src/entries/database/upstash.json +94 -0
  74. package/src/entries/devops/fly-io.json +90 -0
  75. package/src/entries/devops/netlify.json +90 -0
  76. package/src/entries/devops/railway.json +90 -0
  77. package/src/entries/devops/vercel.json +90 -0
  78. package/src/entries/email/mailgun.json +91 -0
  79. package/src/entries/email/postmark.json +91 -0
  80. package/src/entries/email/resend.json +89 -0
  81. package/src/entries/email/sendgrid.json +90 -0
  82. package/src/entries/forms/formspark.json +85 -0
  83. package/src/entries/forms/typeform.json +98 -0
  84. package/src/entries/infrastructure/aws-s3.json +104 -0
  85. package/src/entries/infrastructure/cloudflare-r2.json +92 -0
  86. package/src/entries/infrastructure/cloudflare-workers.json +92 -0
  87. package/src/entries/infrastructure/digital-ocean-spaces.json +87 -0
  88. package/src/entries/integration/nango.json +90 -0
  89. package/src/entries/integration/zapier.json +92 -0
  90. package/src/entries/maps/google-maps.json +89 -0
  91. package/src/entries/maps/mapbox.json +87 -0
  92. package/src/entries/media/deepgram.json +84 -0
  93. package/src/entries/media/imgix.json +84 -0
  94. package/src/entries/media/mux.json +94 -0
  95. package/src/entries/messaging/ably.json +94 -0
  96. package/src/entries/messaging/pusher.json +94 -0
  97. package/src/entries/messaging/twilio.json +94 -0
  98. package/src/entries/messaging/vonage.json +89 -0
  99. package/src/entries/notifications/knock.json +84 -0
  100. package/src/entries/notifications/novu.json +84 -0
  101. package/src/entries/notifications/onesignal.json +84 -0
  102. package/src/entries/payments/lemonsqueezy.json +91 -0
  103. package/src/entries/payments/paddle.json +90 -0
  104. package/src/entries/payments/paypal.json +91 -0
  105. package/src/entries/payments/razorpay.json +85 -0
  106. package/src/entries/payments/square.json +91 -0
  107. package/src/entries/payments/stripe.json +96 -0
  108. package/src/entries/scheduling/cal-com.json +90 -0
  109. package/src/entries/scheduling/calendly.json +90 -0
  110. package/src/entries/search/algolia.json +96 -0
  111. package/src/entries/security/arcjet.json +89 -0
  112. package/src/entries/security/snyk.json +90 -0
  113. package/src/entries/storage/cloudinary.json +93 -0
  114. package/src/entries/storage/uploadthing.json +90 -0
  115. package/src/entries/testing/browserstack.json +86 -0
  116. package/src/entries/testing/checkly.json +89 -0
  117. package/src/entries/workflow/inngest.json +88 -0
  118. package/src/entries/workflow/temporal.json +90 -0
  119. package/src/entries/workflow/trigger-dev.json +89 -0
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "Resend",
3
+ "slug": "resend",
4
+ "category": "email",
5
+ "subcategory": "transactional-email",
6
+ "website": "https://resend.com",
7
+ "description": "Resend is a developer-first email API built for modern applications. It offers a clean REST API, first-class TypeScript SDK, and native React Email integration — making it the fastest way to ship beautiful transactional emails.",
8
+ "useCases": [
9
+ {
10
+ "task": "Send transactional emails with dynamic templates",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Send password reset and account verification emails",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Send welcome and onboarding emails",
19
+ "fit": "perfect"
20
+ },
21
+ {
22
+ "task": "Send bulk marketing email campaigns",
23
+ "fit": "partial"
24
+ }
25
+ ],
26
+ "auth": {
27
+ "method": "api_key",
28
+ "setupSteps": [
29
+ "Sign up at resend.com",
30
+ "Navigate to API Keys in the dashboard",
31
+ "Click 'Create API Key' and give it a name",
32
+ "Add and verify your sending domain under Domains (or use onboarding@resend.dev for testing)",
33
+ "Copy the API key and set it as the RESEND_API_KEY environment variable"
34
+ ],
35
+ "envVarName": "RESEND_API_KEY",
36
+ "codeSnippet": "const resend = new Resend(process.env.RESEND_API_KEY);"
37
+ },
38
+ "pricing": {
39
+ "model": "freemium",
40
+ "freeTier": "3,000 emails/month, 1 custom domain",
41
+ "startingPrice": "$20/month for 50,000 emails",
42
+ "costPer": "$0.0004 per email above plan limit",
43
+ "pricingUrl": "https://resend.com/pricing"
44
+ },
45
+ "rateLimits": {
46
+ "tier": "free tier",
47
+ "limit": "10 emails/second, 3,000 emails/month",
48
+ "notes": "Paid plans have higher throughput. Rate limit headers are returned on every response.",
49
+ "retryStrategy": "Respect Retry-After header on 429 responses; use exponential backoff starting at 1s"
50
+ },
51
+ "sdk": {
52
+ "primaryLanguage": "typescript",
53
+ "installCommand": "npm install --save-exact resend",
54
+ "importStatement": "import { Resend } from 'resend';",
55
+ "otherLanguages": ["python", "ruby", "go", "php", "elixir", "rust"]
56
+ },
57
+ "codeExamples": [
58
+ {
59
+ "title": "Send a basic transactional email",
60
+ "language": "typescript",
61
+ "code": "import { Resend } from 'resend';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nconst { data, error } = await resend.emails.send({\n from: 'Acme <noreply@acme.com>',\n to: ['user@example.com'],\n subject: 'Your order has shipped!',\n html: '<h1>Your order is on the way</h1><p>Track it at <a href=\"https://acme.com/track\">acme.com/track</a></p>',\n});\n\nif (error) {\n throw new Error(`Failed to send email: ${error.message}`);\n}\n\nconsole.log('Email sent, id:', data?.id);",
62
+ "notes": "The 'from' address must use a verified domain. For testing without domain verification, use 'onboarding@resend.dev' as the from address."
63
+ },
64
+ {
65
+ "title": "Send with React Email component",
66
+ "language": "typescript",
67
+ "code": "import { Resend } from 'resend';\nimport { WelcomeEmail } from './emails/WelcomeEmail';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nconst { data, error } = await resend.emails.send({\n from: 'Acme <noreply@acme.com>',\n to: ['user@example.com'],\n subject: 'Welcome to Acme!',\n react: <WelcomeEmail username=\"Jane\" verifyUrl=\"https://acme.com/verify/abc123\" />,\n});\n\nif (error) throw new Error(error.message);\nconsole.log('Sent:', data?.id);",
68
+ "notes": "Requires @react-email/components and react as peer dependencies. Run: npm install --save-exact @react-email/components react"
69
+ }
70
+ ],
71
+ "gotchas": [
72
+ "The 'from' address must use a verified domain — you cannot send from gmail.com or other shared domains. Use 'onboarding@resend.dev' only for initial testing, not in production.",
73
+ "Free tier is limited to 1 custom domain. If you need to send from multiple domains (e.g., different brands or staging vs. production), you must upgrade to a paid plan.",
74
+ "Resend is optimized for transactional email — bulk marketing campaigns to cold lists are against their terms of service and may result in account suspension."
75
+ ],
76
+ "reliability": {
77
+ "uptimeGuarantee": "99.99% SLA on paid plans",
78
+ "statusPageUrl": "https://status.resend.com",
79
+ "notes": "Resend routes through multiple delivery providers. Average delivery time is under 5 seconds. Bounce and complaint handling is automatic."
80
+ },
81
+ "qualityScore": 9,
82
+ "qualityJustification": "Best-in-class developer experience with the cleanest API in the email space. First-class TypeScript, excellent React Email integration, honest pricing, and fast onboarding. Slight deduction for limited free tier domain count and no bulk email support.",
83
+ "alternatives": ["sendgrid", "postmark", "mailgun"],
84
+ "complementary": ["clerk", "stripe"],
85
+ "bestFor": "Developer-first transactional email with React Email templates and minimal setup",
86
+ "lastVerified": "2026-02-25",
87
+ "entryVersion": 1,
88
+ "addedBy": "claude-code-session-1"
89
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "SendGrid",
3
+ "slug": "sendgrid",
4
+ "category": "email",
5
+ "subcategory": "transactional-email",
6
+ "website": "https://sendgrid.com",
7
+ "description": "SendGrid (by Twilio) is an enterprise-grade cloud email platform supporting both transactional and marketing email at massive scale. It is the industry standard for high-volume senders, with deep analytics, deliverability tools, and a comprehensive REST API.",
8
+ "useCases": [
9
+ {
10
+ "task": "Send high-volume transactional emails at enterprise scale",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Send marketing campaigns to large email lists",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Send password reset and verification emails",
19
+ "fit": "good"
20
+ },
21
+ {
22
+ "task": "Manage email templates with dynamic content",
23
+ "fit": "good"
24
+ }
25
+ ],
26
+ "auth": {
27
+ "method": "api_key",
28
+ "setupSteps": [
29
+ "Sign up at sendgrid.com (or use your Twilio account)",
30
+ "Go to Settings > API Keys",
31
+ "Create a new API key with 'Full Access' or scoped permissions",
32
+ "Verify your sender identity under Settings > Sender Authentication",
33
+ "Set SENDGRID_API_KEY environment variable"
34
+ ],
35
+ "envVarName": "SENDGRID_API_KEY",
36
+ "codeSnippet": "const sgMail = require('@sendgrid/mail');\nsgMail.setApiKey(process.env.SENDGRID_API_KEY);"
37
+ },
38
+ "pricing": {
39
+ "model": "freemium",
40
+ "freeTier": "100 emails/day forever (shared IP)",
41
+ "startingPrice": "$19.95/month for 50,000 emails",
42
+ "costPer": "Varies by plan; Essentials plan ~$0.0004/email",
43
+ "pricingUrl": "https://sendgrid.com/pricing"
44
+ },
45
+ "rateLimits": {
46
+ "tier": "free tier",
47
+ "limit": "100 emails/day, 600 requests/minute on API",
48
+ "notes": "Free tier uses shared IP pool which can affect deliverability. Dedicated IPs available on Pro plans at extra cost.",
49
+ "retryStrategy": "Implement exponential backoff for 429 and 503 responses; SendGrid recommends retry with jitter"
50
+ },
51
+ "sdk": {
52
+ "primaryLanguage": "typescript",
53
+ "installCommand": "npm install --save-exact @sendgrid/mail",
54
+ "importStatement": "import sgMail from '@sendgrid/mail';",
55
+ "otherLanguages": ["python", "ruby", "java", "go", "php", "csharp"]
56
+ },
57
+ "codeExamples": [
58
+ {
59
+ "title": "Send a basic email",
60
+ "language": "typescript",
61
+ "code": "import sgMail from '@sendgrid/mail';\n\nsgMail.setApiKey(process.env.SENDGRID_API_KEY!);\n\ntry {\n await sgMail.send({\n to: 'user@example.com',\n from: 'noreply@yourdomain.com', // Must be verified\n subject: 'Your order confirmation',\n text: 'Thank you for your order!',\n html: '<p>Thank you for your order!</p>',\n });\n console.log('Email sent successfully');\n} catch (error) {\n console.error('SendGrid error:', error);\n throw error;\n}",
62
+ "notes": "The 'from' address must be a verified sender. Set up Sender Authentication in the dashboard before sending."
63
+ },
64
+ {
65
+ "title": "Send with a dynamic template",
66
+ "language": "typescript",
67
+ "code": "import sgMail from '@sendgrid/mail';\n\nsgMail.setApiKey(process.env.SENDGRID_API_KEY!);\n\nawait sgMail.send({\n to: 'user@example.com',\n from: 'noreply@yourdomain.com',\n templateId: 'd-your-template-id-here',\n dynamicTemplateData: {\n username: 'Jane',\n resetUrl: 'https://app.com/reset/abc123',\n expiresIn: '1 hour',\n },\n});",
68
+ "notes": "Create templates in the SendGrid dashboard under Email API > Dynamic Templates. Template IDs start with 'd-'."
69
+ }
70
+ ],
71
+ "gotchas": [
72
+ "Free tier (100 emails/day) uses a shared IP pool — deliverability is lower than paid tiers with dedicated IPs. Expect higher spam rates on shared IPs.",
73
+ "Sender authentication (domain authentication via DNS records) is required for good deliverability. Setting up SPF, DKIM, and DMARC records is mandatory for production.",
74
+ "The @sendgrid/mail SDK uses CommonJS require() internally, which can cause issues in pure ESM projects. Use dynamic import or configure your bundler accordingly.",
75
+ "SendGrid's free tier is being deprecated and replaced by Twilio's free trial — check current free tier limits as they may have changed."
76
+ ],
77
+ "reliability": {
78
+ "uptimeGuarantee": "99.95% SLA on Pro and Premier plans",
79
+ "statusPageUrl": "https://status.sendgrid.com",
80
+ "notes": "SendGrid is battle-tested at multi-billion email/month scale. Global infrastructure with multiple data centers."
81
+ },
82
+ "qualityScore": 7,
83
+ "qualityJustification": "Industry standard with unmatched scale and ecosystem, but the API and SDK feel dated compared to newer alternatives. The free tier limit (100/day) is very low, setup complexity is higher, and the Twilio acquisition has introduced some organizational complexity. Still the best choice for enterprise scale.",
84
+ "alternatives": ["resend", "postmark", "mailgun"],
85
+ "complementary": ["stripe", "twilio"],
86
+ "bestFor": "High-volume transactional and marketing email at enterprise scale with deep analytics",
87
+ "lastVerified": "2026-02-25",
88
+ "entryVersion": 1,
89
+ "addedBy": "claude-code-session-1"
90
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "Formspark",
3
+ "slug": "formspark",
4
+ "category": "forms",
5
+ "subcategory": "form-backend",
6
+ "website": "https://formspark.io",
7
+ "description": "Formspark is a form backend service — point your HTML form action to a Formspark endpoint and submissions are collected, spam-filtered, and forwarded to your email or webhook. No server-side code required. Ideal for static sites and JAMstack apps that need form submission handling without building a backend.",
8
+ "useCases": [
9
+ {
10
+ "task": "Add a contact form to a static site without a backend",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Collect form submissions and receive email notifications",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Set up form-to-webhook integration for automation",
19
+ "fit": "good"
20
+ }
21
+ ],
22
+ "auth": {
23
+ "method": "api_key",
24
+ "setupSteps": [
25
+ "Create a Formspark account at formspark.io",
26
+ "Click 'Create a form' in the dashboard and give it a name",
27
+ "Copy the Form ID shown in the dashboard (e.g., abc12345)",
28
+ "Use the Form ID in your form action URL: https://submit.formspark.io/f/{FORM_ID}",
29
+ "Set FORMSPARK_FORM_ID environment variable with the copied Form ID"
30
+ ],
31
+ "envVarName": "FORMSPARK_FORM_ID",
32
+ "codeSnippet": "const response = await fetch(`https://submit.formspark.io/f/${process.env.FORMSPARK_FORM_ID}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n email: 'user@example.com',\n message: 'Hello from my app!',\n }),\n});\n\nif (!response.ok) {\n throw new Error(`Formspark submission failed: ${response.status}`);\n}\n\nconsole.log('Form submitted successfully');"
33
+ },
34
+ "pricing": {
35
+ "model": "freemium",
36
+ "freeTier": "Free: 250 submissions/month for lifetime",
37
+ "startingPrice": "$9/month (Solo) for 3,000 submissions/month, 5 forms, custom domain",
38
+ "costPer": null,
39
+ "pricingUrl": "https://formspark.io/#pricing"
40
+ },
41
+ "rateLimits": {
42
+ "tier": "free tier",
43
+ "limit": "250 submissions/month, no per-second rate limit documented",
44
+ "notes": "Formspark applies spam filtering using hCaptcha and honeypot fields. Custom spam rules available on paid plans. Email notifications sent per-submission on all plans.",
45
+ "retryStrategy": "Submit forms once and handle user feedback on success/error. Use the JSON API for custom error handling in JavaScript-submitted forms."
46
+ },
47
+ "sdk": {
48
+ "primaryLanguage": "typescript",
49
+ "installCommand": "npm install --save-exact @formspark/use-formspark",
50
+ "importStatement": "import { useFormspark } from '@formspark/use-formspark';",
51
+ "otherLanguages": ["javascript"]
52
+ },
53
+ "codeExamples": [
54
+ {
55
+ "title": "Simple HTML form submission",
56
+ "language": "typescript",
57
+ "code": "import React, { FormEvent, useState } from 'react';\n\nconst FORMSPARK_FORM_ID = process.env.NEXT_PUBLIC_FORMSPARK_FORM_ID!;\n\ninterface ContactFormData {\n name: string;\n email: string;\n message: string;\n}\n\nexport function ContactForm(): JSX.Element {\n const [formData, setFormData] = useState<ContactFormData>({\n name: '',\n email: '',\n message: '',\n });\n const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');\n\n const handleSubmit = async (e: FormEvent<HTMLFormElement>): Promise<void> => {\n e.preventDefault();\n setStatus('submitting');\n\n try {\n const response = await fetch(\n `https://submit.formspark.io/f/${FORMSPARK_FORM_ID}`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(formData),\n }\n );\n\n if (!response.ok) {\n throw new Error(`Submission failed with status: ${response.status}`);\n }\n\n setStatus('success');\n setFormData({ name: '', email: '', message: '' });\n } catch (err) {\n console.error('Form submission error:', err);\n setStatus('error');\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n <input\n type=\"text\"\n placeholder=\"Your name\"\n value={formData.name}\n onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}\n required\n />\n <input\n type=\"email\"\n placeholder=\"Your email\"\n value={formData.email}\n onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}\n required\n />\n <textarea\n placeholder=\"Your message\"\n value={formData.message}\n onChange={(e) => setFormData((prev) => ({ ...prev, message: e.target.value }))}\n required\n />\n <button type=\"submit\" disabled={status === 'submitting'}>\n {status === 'submitting' ? 'Sending...' : 'Send Message'}\n </button>\n {status === 'success' && <p>Message sent successfully!</p>}\n {status === 'error' && <p>Something went wrong. Please try again.</p>}\n </form>\n );\n}",
58
+ "notes": "Use NEXT_PUBLIC_ prefix for the form ID in Next.js so it's available client-side. The Formspark JSON API accepts any key-value pairs — all submitted fields appear in the notification email and dashboard."
59
+ },
60
+ {
61
+ "title": "React hook for Formspark submission",
62
+ "language": "typescript",
63
+ "code": "import React, { FormEvent } from 'react';\nimport { useFormspark } from '@formspark/use-formspark';\n\nconst FORMSPARK_FORM_ID = process.env.NEXT_PUBLIC_FORMSPARK_FORM_ID!;\n\ninterface NewsletterFormData {\n email: string;\n firstName?: string;\n}\n\nexport function NewsletterSignup(): JSX.Element {\n const [submit, submitting] = useFormspark({\n formId: FORMSPARK_FORM_ID,\n });\n\n const [email, setEmail] = React.useState('');\n const [firstName, setFirstName] = React.useState('');\n const [done, setDone] = React.useState(false);\n\n const handleSubmit = async (e: FormEvent<HTMLFormElement>): Promise<void> => {\n e.preventDefault();\n\n const data: NewsletterFormData = { email };\n if (firstName) data.firstName = firstName;\n\n await submit(data);\n setDone(true);\n setEmail('');\n setFirstName('');\n };\n\n if (done) {\n return <p>Thanks for subscribing!</p>;\n }\n\n return (\n <form onSubmit={handleSubmit}>\n <input\n type=\"text\"\n placeholder=\"First name (optional)\"\n value={firstName}\n onChange={(e) => setFirstName(e.target.value)}\n />\n <input\n type=\"email\"\n placeholder=\"Email address\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n />\n {/* Honeypot field to catch bots — must remain hidden */}\n <input type=\"text\" name=\"_honeypot\" style={{ display: 'none' }} />\n <button type=\"submit\" disabled={submitting}>\n {submitting ? 'Subscribing...' : 'Subscribe'}\n </button>\n </form>\n );\n}",
64
+ "notes": "The useFormspark hook from @formspark/use-formspark handles loading state automatically. The `submitting` boolean is true while the request is in-flight. Adding a hidden `_honeypot` input field helps filter bot submissions."
65
+ }
66
+ ],
67
+ "gotchas": [
68
+ "Formspark's free tier is 250 submissions PER MONTH total across all forms. A popular contact form can exhaust this in days. Plan your pricing tier before going to production.",
69
+ "File upload fields are NOT supported by Formspark. If your form includes file attachments, you need to handle file uploads separately (to Cloudinary or S3) and submit only the URL via Formspark.",
70
+ "Formspark sends email notifications per submission — with a busy form receiving 100 submissions/day, that's 100 emails. Set up a webhook to a service like Zapier or n8n to filter and batch notifications instead."
71
+ ],
72
+ "reliability": {
73
+ "uptimeGuarantee": null,
74
+ "statusPageUrl": "https://status.formspark.io",
75
+ "notes": "AWS-backed infrastructure. No published SLA. Suitable for contact forms and lead capture but not for mission-critical data collection pipelines."
76
+ },
77
+ "qualityScore": 7,
78
+ "qualityJustification": "Best-in-class for simple form backends — minimal setup, reliable delivery, spam filtering included. The lifetime free tier (250 submissions/month) is rare and genuinely useful. File uploads and lack of SLA are the main limitations.",
79
+ "alternatives": ["typeform"],
80
+ "complementary": ["vercel", "netlify", "resend"],
81
+ "bestFor": "Adding form submission handling to static sites and JAMstack apps without any server-side code — contact forms, lead capture, feedback collection",
82
+ "lastVerified": "2026-02-25",
83
+ "entryVersion": 1,
84
+ "addedBy": "claude-code-session-4"
85
+ }
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "Typeform",
3
+ "slug": "typeform",
4
+ "category": "forms",
5
+ "subcategory": "conversational-forms",
6
+ "website": "https://developer.typeform.com",
7
+ "description": "Typeform provides conversational, one-question-at-a-time forms with high completion rates. The Typeform API allows developers to create forms programmatically, retrieve responses, and set up webhooks for real-time form submission handling. Used for surveys, lead capture, onboarding questionnaires, and feedback collection.",
8
+ "useCases": [
9
+ {
10
+ "task": "Embed a conversational survey or questionnaire into an app",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Retrieve form responses programmatically for analysis",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Trigger actions on form submission via webhook",
19
+ "fit": "good"
20
+ },
21
+ {
22
+ "task": "Create forms programmatically with the Typeform API",
23
+ "fit": "good"
24
+ }
25
+ ],
26
+ "auth": {
27
+ "method": "api_key",
28
+ "setupSteps": [
29
+ "Create a Typeform account at typeform.com",
30
+ "Click your profile avatar in the top-right corner",
31
+ "Go to Profile \u2192 Personal tokens",
32
+ "Click 'Generate a new token', give it a name, and copy the token",
33
+ "Set TYPEFORM_API_TOKEN environment variable with the copied token"
34
+ ],
35
+ "envVarName": "TYPEFORM_API_TOKEN",
36
+ "codeSnippet": "const response = await fetch('https://api.typeform.com/me', {\n headers: {\n Authorization: `Bearer ${process.env.TYPEFORM_API_TOKEN}`,\n },\n});\nconst data = await response.json();\nconsole.log('Typeform user:', data);"
37
+ },
38
+ "pricing": {
39
+ "model": "freemium",
40
+ "freeTier": "Free: 10 questions/form, 10 responses/month, basic logic",
41
+ "startingPrice": "$25/month (Basic) for unlimited responses and questions, 1 user",
42
+ "costPer": null,
43
+ "pricingUrl": "https://www.typeform.com/pricing/"
44
+ },
45
+ "rateLimits": {
46
+ "tier": "free tier",
47
+ "limit": "REST API: 5 requests/second; Responses API: 1,000 items per page max",
48
+ "notes": "Webhooks deliver responses in near-real-time (usually <10 seconds). The Responses API uses cursor-based pagination for large result sets. Typeform rate limits are per-user, not per-form.",
49
+ "retryStrategy": "Implement retry on 429 responses. Typeform webhooks include a checksum header for verification \u2014 always validate before processing."
50
+ },
51
+ "sdk": {
52
+ "primaryLanguage": "typescript",
53
+ "installCommand": "npm install --save-exact @typeform/api-client",
54
+ "importStatement": "import { createClient } from '@typeform/api-client';",
55
+ "otherLanguages": [
56
+ "javascript",
57
+ "python"
58
+ ]
59
+ },
60
+ "codeExamples": [
61
+ {
62
+ "title": "Retrieve form responses",
63
+ "language": "typescript",
64
+ "code": "import { createClient } from '@typeform/api-client';\n\nconst typeformClient = createClient({\n token: process.env.TYPEFORM_API_TOKEN!,\n});\n\nconst FORM_ID = 'your-form-id';\n\ninterface TypeformResponse {\n response_id: string;\n submitted_at: string;\n answers: Array<{\n field: { id: string; type: string };\n type: string;\n text?: string;\n choice?: { label: string };\n number?: number;\n }>;\n}\n\nasync function getFormResponses(): Promise<void> {\n const result = await typeformClient.responses.list({\n uid: FORM_ID,\n pageSize: 100,\n sort: 'submitted_at,desc',\n });\n\n console.log(`Total responses: ${result.total_items}`);\n\n for (const response of result.items as TypeformResponse[]) {\n console.log(`Response ID: ${response.response_id}`);\n console.log(`Submitted at: ${response.submitted_at}`);\n\n for (const answer of response.answers) {\n const value = answer.text ?? answer.choice?.label ?? answer.number ?? 'N/A';\n console.log(` Field ${answer.field.id} (${answer.type}): ${value}`);\n }\n }\n}\n\ngetFormResponses().catch(console.error);",
65
+ "notes": "The @typeform/api-client wraps the REST API. Use the `pageSize` and `before` cursor parameters for paginating through large response sets. The `sort` parameter supports field,direction syntax."
66
+ },
67
+ {
68
+ "title": "Handle Typeform webhook submission",
69
+ "language": "typescript",
70
+ "code": "import express, { Request, Response } from 'express';\nimport crypto from 'crypto';\n\nconst app = express();\n\n// Use raw body parser to allow signature verification\napp.use(\n '/webhooks/typeform',\n express.raw({ type: 'application/json' })\n);\n\ninterface TypeformWebhookPayload {\n form_response: {\n form_id: string;\n token: string;\n submitted_at: string;\n answers: Array<{\n field: { id: string; ref: string; type: string };\n type: string;\n text?: string;\n email?: string;\n choice?: { label: string };\n }>;\n };\n}\n\nfunction verifyTypeformSignature(\n rawBody: Buffer,\n signatureHeader: string,\n secret: string\n): boolean {\n const hmac = crypto.createHmac('sha256', secret);\n hmac.update(rawBody);\n const digest = `sha256=${hmac.digest('base64')}`;\n return crypto.timingSafeEqual(\n Buffer.from(digest),\n Buffer.from(signatureHeader)\n );\n}\n\napp.post('/webhooks/typeform', (req: Request, res: Response): void => {\n const signature = req.headers['typeform-signature'] as string;\n const webhookSecret = process.env.TYPEFORM_WEBHOOK_SECRET!;\n\n if (!signature || !verifyTypeformSignature(req.body as Buffer, signature, webhookSecret)) {\n res.status(401).json({ error: 'Invalid signature' });\n return;\n }\n\n // Return 200 immediately before processing\n res.status(200).json({ received: true });\n\n const payload = JSON.parse((req.body as Buffer).toString()) as TypeformWebhookPayload;\n const { form_response } = payload;\n\n console.log(`Form submission received for form: ${form_response.form_id}`);\n console.log(`Submission token: ${form_response.token}`);\n console.log(`Submitted at: ${form_response.submitted_at}`);\n\n for (const answer of form_response.answers) {\n const value = answer.text ?? answer.email ?? answer.choice?.label ?? 'N/A';\n console.log(` [${answer.field.ref}]: ${value}`);\n }\n});\n\napp.listen(3000, () => console.log('Webhook server running on port 3000'));",
71
+ "notes": "Always return 200 immediately after signature verification to prevent Typeform from retrying. Process the payload asynchronously. The TYPEFORM_WEBHOOK_SECRET is set in the Typeform dashboard when creating the webhook. Use express.raw() (not express.json()) to preserve the raw body for HMAC verification."
72
+ }
73
+ ],
74
+ "gotchas": [
75
+ "Typeform's free plan limits responses to 10/month per form \u2014 this is a hard limit. Once exceeded, respondents see an error. For production forms with any real traffic, the free tier is insufficient.",
76
+ "The embedded Typeform widget loads a full JavaScript bundle (~500KB) from typeform.com servers. This significantly impacts page load performance. Use the @typeform/embed-react package with lazy loading, or consider a native form library for performance-critical pages.",
77
+ "Typeform webhook payloads are NOT retried indefinitely \u2014 if your endpoint returns an error, Typeform retries a limited number of times before giving up. Use a queue (like Inngest or Trigger.dev) to process webhook events asynchronously and return 200 immediately.",
78
+ "Logic jumps and Hidden Fields in Typeform are configured in the form builder UI, NOT in the API. You cannot programmatically add conditional logic when creating a form via API \u2014 the logic must be added in the Typeform interface after creation."
79
+ ],
80
+ "reliability": {
81
+ "uptimeGuarantee": "99.9% uptime SLA (Business+)",
82
+ "statusPageUrl": "https://status.typeform.com",
83
+ "notes": "Globally distributed infrastructure. Webhook delivery is near-real-time but not guaranteed \u2014 implement idempotency keys for critical workflows."
84
+ },
85
+ "qualityScore": 7,
86
+ "qualityJustification": "Highest form completion rates in the industry due to the conversational one-question format. Strong API for retrieving responses. Main friction: the 10-response free tier is impractical for testing, the embed bundle is heavy, and the API for creating forms with logic is limited.",
87
+ "alternatives": [
88
+ "formspark"
89
+ ],
90
+ "complementary": [
91
+ "resend",
92
+ "supabase"
93
+ ],
94
+ "bestFor": "Conversational surveys and forms where completion rate is the priority \u2014 lead capture, NPS surveys, onboarding questionnaires",
95
+ "lastVerified": "2026-02-25",
96
+ "entryVersion": 1,
97
+ "addedBy": "claude-code-session-4"
98
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "AWS S3",
3
+ "slug": "aws-s3",
4
+ "category": "infrastructure",
5
+ "subcategory": "object-storage",
6
+ "website": "https://aws.amazon.com/s3/",
7
+ "description": "AWS S3 is the industry-standard object storage service \u2014 massively durable (11 nines), infinitely scalable, and the API that every competing storage service has adopted as the universal interface.",
8
+ "useCases": [
9
+ {
10
+ "task": "Store and serve files, images, and documents at scale",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Host static website assets with CDN",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Store application backups and logs",
19
+ "fit": "perfect"
20
+ },
21
+ {
22
+ "task": "Store large media files for video streaming pipeline",
23
+ "fit": "good"
24
+ }
25
+ ],
26
+ "auth": {
27
+ "method": "api_key",
28
+ "setupSteps": [
29
+ "Create an AWS account at aws.amazon.com",
30
+ "Go to IAM \u2192 Users \u2192 Create user",
31
+ "Attach a policy with S3 permissions (e.g. AmazonS3FullAccess or a custom policy)",
32
+ "Go to the user's Security credentials tab \u2192 Create access key",
33
+ "Copy the Access Key ID and Secret Access Key",
34
+ "Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION environment variables"
35
+ ],
36
+ "envVarName": "AWS_ACCESS_KEY_ID",
37
+ "codeSnippet": "import { S3Client } from '@aws-sdk/client-s3';\n\nconst s3 = new S3Client({\n region: process.env.AWS_REGION!,\n credentials: {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n },\n});"
38
+ },
39
+ "pricing": {
40
+ "model": "usage_based",
41
+ "freeTier": "12 months free: 5GB standard storage, 20K GET requests, 2K PUT requests (new AWS accounts only)",
42
+ "startingPrice": "$0.023/GB-month (S3 Standard, us-east-1)",
43
+ "costPer": "$0.023/GB-month storage, $0.0004/1K PUT requests, $0.0004/1K GET requests, $0.09/GB data transfer out",
44
+ "pricingUrl": "https://aws.amazon.com/s3/pricing/"
45
+ },
46
+ "rateLimits": {
47
+ "tier": "no hard limits",
48
+ "limit": "3,500 PUT/COPY/POST/DELETE requests/second per prefix, 5,500 GET/HEAD requests/second per prefix (auto-scaling with request patterns)",
49
+ "notes": "S3 automatically scales request rate based on traffic patterns. Rate limits apply per key prefix, so distributing objects across multiple prefixes multiplies throughput linearly.",
50
+ "retryStrategy": "AWS SDK has built-in exponential backoff retry. For high throughput, use multiple key prefixes to distribute load across partitions."
51
+ },
52
+ "sdk": {
53
+ "primaryLanguage": "typescript",
54
+ "installCommand": "npm install --save-exact @aws-sdk/client-s3 @aws-sdk/s3-request-presigner",
55
+ "importStatement": "import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';",
56
+ "otherLanguages": [
57
+ "python",
58
+ "java",
59
+ "go",
60
+ "ruby",
61
+ "php"
62
+ ]
63
+ },
64
+ "codeExamples": [
65
+ {
66
+ "title": "Upload a file to S3",
67
+ "language": "typescript",
68
+ "code": "import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\n\nconst s3 = new S3Client({\n region: process.env.AWS_REGION!,\n credentials: {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n },\n});\n\nasync function uploadFile(\n bucketName: string,\n key: string,\n body: Buffer | string,\n contentType: string\n): Promise<string> {\n const command = new PutObjectCommand({\n Bucket: bucketName,\n Key: key,\n Body: body,\n ContentType: contentType,\n });\n\n await s3.send(command);\n\n return `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;\n}\n\n// Usage\nconst url = await uploadFile(\n 'my-app-bucket',\n 'uploads/profile-picture.jpg',\n imageBuffer,\n 'image/jpeg'\n);\nconsole.log('Uploaded to:', url);",
69
+ "notes": "The Key is the full object path within the bucket. ContentType should always be set \u2014 browsers use it to decide how to handle the file. Objects are private by default; use PutObjectAcl or a bucket policy to make them public."
70
+ },
71
+ {
72
+ "title": "Generate a presigned URL for temporary access",
73
+ "language": "typescript",
74
+ "code": "import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nconst s3 = new S3Client({\n region: process.env.AWS_REGION!,\n credentials: {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n },\n});\n\nasync function getPresignedDownloadUrl(\n bucketName: string,\n key: string,\n expiresInSeconds: number = 3600\n): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: bucketName,\n Key: key,\n });\n\n const signedUrl = await getSignedUrl(s3, command, {\n expiresIn: expiresInSeconds,\n });\n\n return signedUrl;\n}\n\n// Usage: generate a URL valid for 1 hour\nconst downloadUrl = await getPresignedDownloadUrl(\n 'my-app-bucket',\n 'uploads/invoice-2024.pdf',\n 3600\n);\nconsole.log('Temporary URL (expires in 1 hour):', downloadUrl);",
75
+ "notes": "Presigned URLs allow temporary, authenticated access to private objects without exposing credentials. expiresIn is in seconds; max is 7 days (604800). Use presigned PUT URLs to allow direct browser-to-S3 uploads without routing through your server."
76
+ }
77
+ ],
78
+ "gotchas": [
79
+ "S3 objects are private by default. To make them publicly accessible you must either set a bucket policy or generate presigned URLs. 'Making a bucket public' is an account-level setting you must explicitly enable \u2014 Block Public Access is on by default and must be turned off at both the account and bucket level before a bucket policy can grant public access.",
80
+ "Egress (data transfer OUT from S3 to the internet) costs $0.09/GB. For apps serving many large files, this adds up quickly. Use CloudFront CDN in front of S3 to reduce egress costs, or migrate to Cloudflare R2 (zero egress fees) for high-traffic files.",
81
+ "S3 eventual consistency was a historical gotcha, but since December 2020, S3 offers strong read-after-write consistency for all operations. However, S3 is still NOT a database \u2014 don't use it for frequently-updated objects that require atomic operations.",
82
+ "IAM permissions are complex. A common mistake is giving s3:* to an IAM user \u2014 instead, scope permissions tightly: s3:PutObject and s3:GetObject on specific bucket ARNs. Never use root account credentials in application code."
83
+ ],
84
+ "reliability": {
85
+ "uptimeGuarantee": "99.999999999% (11 nines) durability, 99.99% availability SLA for S3 Standard",
86
+ "statusPageUrl": "https://status.aws.amazon.com",
87
+ "notes": "Data is replicated across a minimum of 3 Availability Zones within the selected AWS region. S3 Standard-IA, Glacier, and other storage classes have different durability and availability SLAs."
88
+ },
89
+ "qualityScore": 9,
90
+ "qualityJustification": "The industry standard for object storage. 11-nines durability, massive ecosystem support, and the S3 API is the universal interface adopted by all competitors. Egress costs and IAM complexity are the main friction points, but nothing comes close for reliability and ecosystem breadth.",
91
+ "alternatives": [
92
+ "cloudflare-r2",
93
+ "digital-ocean-spaces",
94
+ "cloudinary"
95
+ ],
96
+ "complementary": [
97
+ "cloudflare-workers",
98
+ "vercel"
99
+ ],
100
+ "bestFor": "Storing any type of file at scale \u2014 from user uploads to application backups \u2014 with the most widely supported API in cloud infrastructure",
101
+ "lastVerified": "2026-02-25",
102
+ "entryVersion": 1,
103
+ "addedBy": "claude-code-session-4"
104
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "Cloudflare R2",
3
+ "slug": "cloudflare-r2",
4
+ "category": "infrastructure",
5
+ "subcategory": "object-storage",
6
+ "website": "https://cloudflare.com/products/r2/",
7
+ "description": "Cloudflare R2 is S3-compatible object storage with zero egress fees, making it the ideal replacement for AWS S3 when serving files to end users where data transfer costs are a concern.",
8
+ "useCases": [
9
+ {
10
+ "task": "Store and serve user-uploaded files without egress fees",
11
+ "fit": "perfect"
12
+ },
13
+ {
14
+ "task": "Replace AWS S3 for a high-traffic media storage use case",
15
+ "fit": "perfect"
16
+ },
17
+ {
18
+ "task": "Store static assets served via Cloudflare CDN",
19
+ "fit": "perfect"
20
+ },
21
+ {
22
+ "task": "Archive backups with low storage costs",
23
+ "fit": "good"
24
+ }
25
+ ],
26
+ "auth": {
27
+ "method": "api_key",
28
+ "setupSteps": [
29
+ "Log in to the Cloudflare dashboard at dash.cloudflare.com",
30
+ "Navigate to R2 Object Storage and create a bucket",
31
+ "Note your Account ID from the right sidebar",
32
+ "Go to R2 → Manage R2 API Tokens → Create API Token",
33
+ "Select Object Read & Write permissions for the desired bucket(s)",
34
+ "Copy the Access Key ID and Secret Access Key — the secret is shown only once",
35
+ "Set CLOUDFLARE_R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ACCOUNT_ID, and R2_BUCKET_NAME environment variables"
36
+ ],
37
+ "envVarName": "CLOUDFLARE_R2_ACCESS_KEY_ID",
38
+ "codeSnippet": "import { S3Client } from '@aws-sdk/client-s3';\n\nconst r2 = new S3Client({\n region: 'auto',\n endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n credentials: {\n accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n});"
39
+ },
40
+ "pricing": {
41
+ "model": "freemium",
42
+ "freeTier": "10GB storage/month, 1M Class A operations (writes), 10M Class B operations (reads) — all free forever",
43
+ "startingPrice": "$0.015/GB-month after free tier",
44
+ "costPer": "$0.015/GB-month storage, $4.50/million Class A ops, $0.36/million Class B ops. Egress: $0 always.",
45
+ "pricingUrl": "https://developers.cloudflare.com/r2/pricing/"
46
+ },
47
+ "rateLimits": {
48
+ "tier": "no published hard limits",
49
+ "limit": "Consistent with S3 performance. No egress rate limiting. Bound by account bandwidth limits.",
50
+ "notes": "R2 does not publish specific per-bucket request rate limits. Performance is generally consistent with S3. Contact Cloudflare enterprise support for guaranteed throughput requirements.",
51
+ "retryStrategy": "Use exponential backoff on 429/503 responses. R2 uses the same error codes as S3."
52
+ },
53
+ "sdk": {
54
+ "primaryLanguage": "typescript",
55
+ "installCommand": "npm install --save-exact @aws-sdk/client-s3",
56
+ "importStatement": "import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';",
57
+ "otherLanguages": ["python", "go", "ruby", "java"]
58
+ },
59
+ "codeExamples": [
60
+ {
61
+ "title": "Upload to R2 using S3-compatible SDK",
62
+ "language": "typescript",
63
+ "code": "import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\n\nconst r2 = new S3Client({\n region: 'auto',\n endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n credentials: {\n accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n});\n\nasync function uploadToR2(\n key: string,\n body: Buffer | string,\n contentType: string\n): Promise<void> {\n const command = new PutObjectCommand({\n Bucket: process.env.R2_BUCKET_NAME!,\n Key: key,\n Body: body,\n ContentType: contentType,\n });\n\n await r2.send(command);\n console.log(`Uploaded ${key} to R2 bucket ${process.env.R2_BUCKET_NAME}`);\n}\n\n// Usage\nawait uploadToR2(\n 'avatars/user-456.png',\n imageBuffer,\n 'image/png'\n);",
64
+ "notes": "The only difference from AWS S3 is the endpoint URL and region: 'auto'. The rest of the SDK API is identical. You do NOT need the @aws-sdk/s3-request-presigner separately — it is already compatible with R2."
65
+ },
66
+ {
67
+ "title": "Generate presigned URL for R2 object",
68
+ "language": "typescript",
69
+ "code": "import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nconst r2 = new S3Client({\n region: 'auto',\n endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n credentials: {\n accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n});\n\nasync function getR2PresignedUrl(\n key: string,\n expiresInSeconds: number = 3600\n): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: process.env.R2_BUCKET_NAME!,\n Key: key,\n });\n\n const signedUrl = await getSignedUrl(r2, command, {\n expiresIn: expiresInSeconds,\n });\n\n return signedUrl;\n}\n\n// Usage: temporary download link valid for 15 minutes\nconst url = await getR2PresignedUrl('documents/report-q4.pdf', 900);\nconsole.log('Presigned R2 URL:', url);",
70
+ "notes": "Presigned URLs for R2 work identically to S3. The signed URL points to the R2 endpoint, not amazonaws.com. If the bucket is configured with a public custom domain, use that domain for public objects instead of presigned URLs."
71
+ }
72
+ ],
73
+ "gotchas": [
74
+ "R2 does NOT support all S3 features. Notably missing: S3 lifecycle policies with transition between storage classes, S3 Select, S3 Batch Operations, and Object Lock. Check the R2/S3 API compatibility matrix at developers.cloudflare.com before migrating from S3.",
75
+ "Public bucket access in R2 requires connecting the bucket to a Cloudflare domain (a custom domain or the r2.dev subdomain). Unlike S3, you cannot make individual objects public via ACLs — the entire bucket must be configured for public access via the dashboard.",
76
+ "R2 stores data in Cloudflare's infrastructure but does NOT support cross-region replication or S3-style Multi-Region Access Points. All data within a bucket is stored in a single Cloudflare datacenter location (though served globally via Cloudflare's CDN when using public access).",
77
+ "The R2 free tier (10GB) is per-account, not per-bucket. If you use R2 across multiple projects under the same Cloudflare account, they all share the same 10GB monthly free storage allocation."
78
+ ],
79
+ "reliability": {
80
+ "uptimeGuarantee": "Cloudflare does not publish a specific durability SLA for R2, but uses erasure coding across multiple datacenters",
81
+ "statusPageUrl": "https://www.cloudflarestatus.com",
82
+ "notes": "R2 is backed by Cloudflare's global network infrastructure. Erasure coding provides data durability. No explicit 11-nines durability guarantee unlike S3, but Cloudflare's infrastructure is generally considered highly reliable."
83
+ },
84
+ "qualityScore": 8,
85
+ "qualityJustification": "The zero egress fee model is genuinely transformative for apps that serve many files to users — can cut storage costs by 80%+ compared to S3 for read-heavy workloads. S3-compatible API means existing code works with minimal changes. Main limitation is missing advanced S3 features and the single-region storage model.",
86
+ "alternatives": ["aws-s3", "digital-ocean-spaces", "cloudinary"],
87
+ "complementary": ["cloudflare-workers", "vercel", "netlify"],
88
+ "bestFor": "Object storage for user-uploaded files and media when egress costs are a concern — zero egress fees make it dramatically cheaper than S3 for high-traffic files",
89
+ "lastVerified": "2026-02-25",
90
+ "entryVersion": 1,
91
+ "addedBy": "claude-code-session-4"
92
+ }