@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.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +74 -0
- package/dist/dashboard/public/app.js +1004 -0
- package/dist/dashboard/public/index.html +142 -0
- package/dist/dashboard/public/public/app.js +1004 -0
- package/dist/dashboard/public/public/index.html +142 -0
- package/dist/dashboard/public/public/styles.css +1464 -0
- package/dist/dashboard/public/styles.css +1464 -0
- package/dist/dashboard/routes/api.d.ts +7 -0
- package/dist/dashboard/routes/api.js +245 -0
- package/dist/dashboard/server.d.ts +9 -0
- package/dist/dashboard/server.js +45 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +23 -0
- package/dist/knowledge/db.d.ts +22 -0
- package/dist/knowledge/db.js +182 -0
- package/dist/knowledge/schema.d.ts +275 -0
- package/dist/knowledge/schema.js +135 -0
- package/dist/knowledge/scorer.d.ts +63 -0
- package/dist/knowledge/scorer.js +314 -0
- package/dist/knowledge/search.d.ts +37 -0
- package/dist/knowledge/search.js +111 -0
- package/dist/knowledge/synonyms.d.ts +36 -0
- package/dist/knowledge/synonyms.js +523 -0
- package/dist/knowledge/tfidf.d.ts +42 -0
- package/dist/knowledge/tfidf.js +138 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +40 -0
- package/dist/tools/check-freshness.d.ts +9 -0
- package/dist/tools/check-freshness.js +95 -0
- package/dist/tools/compare-apis.d.ts +8 -0
- package/dist/tools/compare-apis.js +149 -0
- package/dist/tools/find-api.d.ts +9 -0
- package/dist/tools/find-api.js +120 -0
- package/dist/tools/get-setup-guide.d.ts +8 -0
- package/dist/tools/get-setup-guide.js +127 -0
- package/dist/updater/linter.d.ts +31 -0
- package/dist/updater/linter.js +219 -0
- package/dist/updater/report.d.ts +29 -0
- package/dist/updater/report.js +96 -0
- package/dist/updater/staleness.d.ts +39 -0
- package/dist/updater/staleness.js +66 -0
- package/dist/updater/version-tracker.d.ts +28 -0
- package/dist/updater/version-tracker.js +50 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.js +13 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.js +32 -0
- package/package.json +56 -0
- package/src/entries/ai/anthropic.json +95 -0
- package/src/entries/ai/eleven-labs.json +90 -0
- package/src/entries/ai/openai.json +95 -0
- package/src/entries/ai/replicate.json +87 -0
- package/src/entries/ai/resemble-ai.json +88 -0
- package/src/entries/ai/stability-ai.json +89 -0
- package/src/entries/analytics/posthog.json +88 -0
- package/src/entries/analytics/sentry.json +84 -0
- package/src/entries/auth/auth0.json +90 -0
- package/src/entries/auth/clerk.json +95 -0
- package/src/entries/cms/contentful.json +92 -0
- package/src/entries/cms/sanity.json +92 -0
- package/src/entries/cms/strapi.json +93 -0
- package/src/entries/commerce/medusa.json +91 -0
- package/src/entries/commerce/shopify-api.json +91 -0
- package/src/entries/communication/sendbird.json +85 -0
- package/src/entries/communication/stream-chat.json +94 -0
- package/src/entries/database/firebase.json +88 -0
- package/src/entries/database/neon.json +94 -0
- package/src/entries/database/planetscale.json +95 -0
- package/src/entries/database/supabase.json +94 -0
- package/src/entries/database/upstash.json +94 -0
- package/src/entries/devops/fly-io.json +90 -0
- package/src/entries/devops/netlify.json +90 -0
- package/src/entries/devops/railway.json +90 -0
- package/src/entries/devops/vercel.json +90 -0
- package/src/entries/email/mailgun.json +91 -0
- package/src/entries/email/postmark.json +91 -0
- package/src/entries/email/resend.json +89 -0
- package/src/entries/email/sendgrid.json +90 -0
- package/src/entries/forms/formspark.json +85 -0
- package/src/entries/forms/typeform.json +98 -0
- package/src/entries/infrastructure/aws-s3.json +104 -0
- package/src/entries/infrastructure/cloudflare-r2.json +92 -0
- package/src/entries/infrastructure/cloudflare-workers.json +92 -0
- package/src/entries/infrastructure/digital-ocean-spaces.json +87 -0
- package/src/entries/integration/nango.json +90 -0
- package/src/entries/integration/zapier.json +92 -0
- package/src/entries/maps/google-maps.json +89 -0
- package/src/entries/maps/mapbox.json +87 -0
- package/src/entries/media/deepgram.json +84 -0
- package/src/entries/media/imgix.json +84 -0
- package/src/entries/media/mux.json +94 -0
- package/src/entries/messaging/ably.json +94 -0
- package/src/entries/messaging/pusher.json +94 -0
- package/src/entries/messaging/twilio.json +94 -0
- package/src/entries/messaging/vonage.json +89 -0
- package/src/entries/notifications/knock.json +84 -0
- package/src/entries/notifications/novu.json +84 -0
- package/src/entries/notifications/onesignal.json +84 -0
- package/src/entries/payments/lemonsqueezy.json +91 -0
- package/src/entries/payments/paddle.json +90 -0
- package/src/entries/payments/paypal.json +91 -0
- package/src/entries/payments/razorpay.json +85 -0
- package/src/entries/payments/square.json +91 -0
- package/src/entries/payments/stripe.json +96 -0
- package/src/entries/scheduling/cal-com.json +90 -0
- package/src/entries/scheduling/calendly.json +90 -0
- package/src/entries/search/algolia.json +96 -0
- package/src/entries/security/arcjet.json +89 -0
- package/src/entries/security/snyk.json +90 -0
- package/src/entries/storage/cloudinary.json +93 -0
- package/src/entries/storage/uploadthing.json +90 -0
- package/src/entries/testing/browserstack.json +86 -0
- package/src/entries/testing/checkly.json +89 -0
- package/src/entries/workflow/inngest.json +88 -0
- package/src/entries/workflow/temporal.json +90 -0
- package/src/entries/workflow/trigger-dev.json +89 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Algolia",
|
|
3
|
+
"slug": "algolia",
|
|
4
|
+
"category": "search",
|
|
5
|
+
"subcategory": "hosted-search",
|
|
6
|
+
"website": "https://www.algolia.com",
|
|
7
|
+
"description": "Algolia is a hosted search API that delivers sub-100ms search results with typo tolerance, faceting, and relevance tuning. It is the standard for adding fast, user-facing search to e-commerce, documentation sites, and SaaS applications — without the complexity of running Elasticsearch.",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Add real-time search with typo tolerance to a web application",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Build e-commerce product search with filtering and facets",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Add instant search to a documentation site",
|
|
19
|
+
"fit": "perfect"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"task": "Build search autocomplete and suggestions",
|
|
23
|
+
"fit": "perfect"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"task": "Full-text search on unstructured text without indexing",
|
|
27
|
+
"fit": "partial"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"auth": {
|
|
31
|
+
"method": "api_key",
|
|
32
|
+
"setupSteps": [
|
|
33
|
+
"Sign up at algolia.com",
|
|
34
|
+
"Create a new Application",
|
|
35
|
+
"Go to Settings > API Keys",
|
|
36
|
+
"Copy the Application ID, Search-Only API Key (for frontend), and Admin API Key (for indexing)",
|
|
37
|
+
"Set ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY, and ALGOLIA_ADMIN_API_KEY environment variables",
|
|
38
|
+
"Never expose the Admin API Key on the frontend — use the Search-Only key in client code"
|
|
39
|
+
],
|
|
40
|
+
"envVarName": "ALGOLIA_ADMIN_API_KEY",
|
|
41
|
+
"codeSnippet": "import algoliasearch from 'algoliasearch';\nconst client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_API_KEY!);\nconst index = client.initIndex('products');"
|
|
42
|
+
},
|
|
43
|
+
"pricing": {
|
|
44
|
+
"model": "freemium",
|
|
45
|
+
"freeTier": "10,000 records, 10,000 search requests/month",
|
|
46
|
+
"startingPrice": "$50/month (Grow plan) for higher limits",
|
|
47
|
+
"costPer": "Varies by plan; roughly $1/1,000 search operations beyond plan limits",
|
|
48
|
+
"pricingUrl": "https://www.algolia.com/pricing"
|
|
49
|
+
},
|
|
50
|
+
"rateLimits": {
|
|
51
|
+
"tier": "free tier",
|
|
52
|
+
"limit": "10,000 search operations/month, 10,000 records, 10 indices",
|
|
53
|
+
"notes": "An 'operation' is counted per index searched. If you search 3 indices per query, that's 3 operations per search. Rate limits on API calls are generous (unlimited on paid plans).",
|
|
54
|
+
"retryStrategy": "Algolia's client SDK handles retries and failover automatically. The SDK retries against multiple hosts for reliability."
|
|
55
|
+
},
|
|
56
|
+
"sdk": {
|
|
57
|
+
"primaryLanguage": "typescript",
|
|
58
|
+
"installCommand": "npm install --save-exact algoliasearch",
|
|
59
|
+
"importStatement": "import algoliasearch from 'algoliasearch';",
|
|
60
|
+
"otherLanguages": ["python", "ruby", "java", "php", "go", "swift", "kotlin", "scala"]
|
|
61
|
+
},
|
|
62
|
+
"codeExamples": [
|
|
63
|
+
{
|
|
64
|
+
"title": "Index records and search",
|
|
65
|
+
"language": "typescript",
|
|
66
|
+
"code": "import algoliasearch from 'algoliasearch';\n\n// Admin client for indexing (server-side only)\nconst client = algoliasearch(\n process.env.ALGOLIA_APP_ID!,\n process.env.ALGOLIA_ADMIN_API_KEY!\n);\nconst index = client.initIndex('products');\n\n// Index some records\nawait index.saveObjects([\n { objectID: '1', name: 'iPhone 15 Pro', brand: 'Apple', price: 999 },\n { objectID: '2', name: 'Galaxy S24', brand: 'Samsung', price: 899 },\n]);\n\n// Search\nconst { hits } = await index.search('iphone', {\n hitsPerPage: 10,\n filters: 'price < 1000',\n});\n\nconsole.log('Results:', hits.map(h => h.name));",
|
|
67
|
+
"notes": "Always provide objectID for each record. Without it, Algolia generates one, making updates and deletes harder. Use saveObject() for upserts."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"title": "React InstantSearch (frontend)",
|
|
71
|
+
"language": "typescript",
|
|
72
|
+
"code": "import { liteClient } from 'algoliasearch/lite';\nimport { InstantSearch, SearchBox, Hits } from 'react-instantsearch';\n\nconst searchClient = liteClient(\n process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,\n process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY! // Search-only key\n);\n\nfunction ProductHit({ hit }: { hit: Record<string, unknown> }) {\n return <div>{String(hit.name)} — ${String(hit.price)}</div>;\n}\n\nexport function ProductSearch() {\n return (\n <InstantSearch searchClient={searchClient} indexName=\"products\">\n <SearchBox placeholder=\"Search products...\" />\n <Hits hitComponent={ProductHit} />\n </InstantSearch>\n );\n}",
|
|
73
|
+
"notes": "Use algoliasearch/lite on the frontend for a smaller bundle. Use your Search-Only API key in NEXT_PUBLIC_ env vars — never the Admin key."
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"gotchas": [
|
|
77
|
+
"The Admin API key has full write access including deleting your entire index. Never expose it in client-side code. Use the Search-Only API key for all frontend/client usage.",
|
|
78
|
+
"Algolia counts 'operations' not requests. A single search that touches multiple indices counts as multiple operations. With 10,000 operations/month on the free tier, this is easy to exhaust with moderate traffic.",
|
|
79
|
+
"Records must be JSON objects with a maximum size of 10KB. Large text content must be chunked before indexing. Images and binary files cannot be stored — only their metadata.",
|
|
80
|
+
"Index settings (searchable attributes, ranking, facets) must be configured explicitly. The defaults are reasonable but production search quality requires tuning ranking formulas and facet configuration.",
|
|
81
|
+
"Algolia is not free for any meaningful production traffic. The 10k operation free tier is exhausted quickly. Budget for at least $50/month if you have real users."
|
|
82
|
+
],
|
|
83
|
+
"reliability": {
|
|
84
|
+
"uptimeGuarantee": "99.99% SLA on paid plans; distributed across 3 data centers per cluster",
|
|
85
|
+
"statusPageUrl": "https://status.algolia.com",
|
|
86
|
+
"notes": "Algolia's search infrastructure is globally distributed with automatic failover. Average response time is under 10ms."
|
|
87
|
+
},
|
|
88
|
+
"qualityScore": 8,
|
|
89
|
+
"qualityJustification": "The gold standard for hosted search — unmatched search quality, excellent TypeScript SDK, and great documentation. The pricing model (per operation, not per query) is confusing and expensive at scale. For simple use cases, the free tier is exhausted quickly. Excellent product, challenging economics.",
|
|
90
|
+
"alternatives": [],
|
|
91
|
+
"complementary": ["stripe", "cloudinary"],
|
|
92
|
+
"bestFor": "Real-time site search with typo tolerance, facets, and sub-100ms responses for e-commerce and documentation sites",
|
|
93
|
+
"lastVerified": "2026-02-25",
|
|
94
|
+
"entryVersion": 1,
|
|
95
|
+
"addedBy": "claude-code-session-1"
|
|
96
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Arcjet",
|
|
3
|
+
"slug": "arcjet",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"subcategory": "app-security",
|
|
6
|
+
"website": "https://arcjet.com",
|
|
7
|
+
"description": "Arcjet is a developer-first security SDK for Node.js, Next.js, Bun, and Deno applications. Provides rate limiting, bot protection, email validation, attack protection (SQL injection, XSS), and shield middleware — all via a single SDK installed in your app. Unlike WAFs that sit in front of your server, Arcjet runs as middleware inside your application code.",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Add rate limiting to an API endpoint without external infrastructure",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Protect a signup form from bot signups with email validation",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Block common attack patterns (SQLi, XSS) at the application layer",
|
|
19
|
+
"fit": "good"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"task": "Implement tiered rate limiting (free users vs paid users)",
|
|
23
|
+
"fit": "good"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"auth": {
|
|
27
|
+
"method": "api_key",
|
|
28
|
+
"setupSteps": [
|
|
29
|
+
"Create an Arcjet account at arcjet.com",
|
|
30
|
+
"Create a new site in the Arcjet dashboard",
|
|
31
|
+
"Copy the ARCJET_KEY from the site settings",
|
|
32
|
+
"Install the SDK: npm install @arcjet/next (or @arcjet/node for plain Node.js)",
|
|
33
|
+
"Add ARCJET_KEY to your .env file and middleware configuration"
|
|
34
|
+
],
|
|
35
|
+
"envVarName": "ARCJET_KEY",
|
|
36
|
+
"codeSnippet": "// ARCJET_KEY=ajkey_your_key_here\n\nimport arcjet, { shield } from '@arcjet/next';\n\nconst aj = arcjet({\n key: process.env.ARCJET_KEY!,\n rules: [\n // Shield detects common attacks: SQLi, XSS, path traversal\n shield({ mode: 'LIVE' }),\n ],\n});"
|
|
37
|
+
},
|
|
38
|
+
"pricing": {
|
|
39
|
+
"model": "freemium",
|
|
40
|
+
"freeTier": "Free: 10,000 requests/month, all features included",
|
|
41
|
+
"startingPrice": "$29/month (Starter) for 500,000 requests/month",
|
|
42
|
+
"costPer": "$5/million requests above included",
|
|
43
|
+
"pricingUrl": "https://arcjet.com/pricing"
|
|
44
|
+
},
|
|
45
|
+
"rateLimits": {
|
|
46
|
+
"tier": "free tier",
|
|
47
|
+
"limit": "10,000 total requests/month (not just rate-limited requests — all traffic through Arcjet middleware counts). Each request that passes through the Arcjet SDK counts.",
|
|
48
|
+
"notes": "All requests routed through Arcjet middleware count toward the monthly limit, not just blocked ones. For high-traffic routes (e.g., public homepage), exempt them from Arcjet to conserve quota.",
|
|
49
|
+
"retryStrategy": "Arcjet returns ArcjetDecision objects with DENY/ALLOW/CHALLENGE decisions. Implement graceful degradation: if Arcjet SDK returns an error, fail open (allow the request) to avoid downtime."
|
|
50
|
+
},
|
|
51
|
+
"sdk": {
|
|
52
|
+
"primaryLanguage": "typescript",
|
|
53
|
+
"installCommand": "npm install --save-exact @arcjet/next",
|
|
54
|
+
"importStatement": "import arcjet, { tokenBucket, shield, detectBot, validateEmail } from '@arcjet/next';",
|
|
55
|
+
"otherLanguages": ["javascript", "bun", "deno"]
|
|
56
|
+
},
|
|
57
|
+
"codeExamples": [
|
|
58
|
+
{
|
|
59
|
+
"title": "Rate limit an API route with Arcjet (Next.js)",
|
|
60
|
+
"language": "typescript",
|
|
61
|
+
"code": "import arcjet, { tokenBucket, shield } from '@arcjet/next';\nimport { NextRequest, NextResponse } from 'next/server';\n\nconst aj = arcjet({\n key: process.env.ARCJET_KEY!,\n characteristics: ['ip.src'],\n rules: [\n // Protect against common web attacks\n shield({ mode: 'LIVE' }),\n // Token bucket: 10 requests capacity, refills 10 per 60 seconds\n tokenBucket({\n mode: 'LIVE',\n refillRate: 10,\n interval: 60,\n capacity: 10,\n }),\n ],\n});\n\nexport async function POST(req: NextRequest): Promise<NextResponse> {\n const decision = await aj.protect(req, { requested: 1 });\n\n if (decision.isDenied()) {\n if (decision.reason.isRateLimit()) {\n return NextResponse.json(\n { error: 'Too many requests. Please try again later.' },\n { status: 429 }\n );\n }\n if (decision.reason.isShield()) {\n return NextResponse.json(\n { error: 'Suspicious request blocked.' },\n { status: 403 }\n );\n }\n return NextResponse.json({ error: 'Forbidden.' }, { status: 403 });\n }\n\n // Request is allowed — proceed with handler logic\n const body: unknown = await req.json();\n return NextResponse.json({ ok: true, received: body });\n}",
|
|
62
|
+
"notes": "The requested: 1 argument deducts 1 token per request. For expensive operations (e.g., AI inference), pass requested: 5 to deduct more tokens and enforce stricter limits without changing the rate limit config."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"title": "Bot protection and email validation on signup",
|
|
66
|
+
"language": "typescript",
|
|
67
|
+
"code": "import arcjet, { detectBot, validateEmail, shield } from '@arcjet/next';\nimport { NextRequest, NextResponse } from 'next/server';\n\nconst aj = arcjet({\n key: process.env.ARCJET_KEY!,\n characteristics: ['ip.src'],\n rules: [\n shield({ mode: 'LIVE' }),\n // Block bots — allow search engine crawlers\n detectBot({\n mode: 'LIVE',\n allow: ['CATEGORY:SEARCH_ENGINE', 'CATEGORY:MONITOR'],\n }),\n // Reject disposable/invalid email addresses\n validateEmail({\n mode: 'LIVE',\n block: ['DISPOSABLE', 'INVALID', 'NO_MX_RECORDS'],\n }),\n ],\n});\n\ninterface SignupBody {\n email: string;\n name: string;\n}\n\nexport async function POST(req: NextRequest): Promise<NextResponse> {\n const body = (await req.json()) as SignupBody;\n\n const decision = await aj.protect(req, { email: body.email });\n\n if (decision.isDenied()) {\n if (decision.reason.isEmail()) {\n return NextResponse.json(\n { error: 'Invalid or disposable email address. Please use a real email.' },\n { status: 422 }\n );\n }\n if (decision.reason.isBot()) {\n return NextResponse.json(\n { error: 'Automated signups are not allowed.' },\n { status: 403 }\n );\n }\n return NextResponse.json({ error: 'Signup blocked.' }, { status: 403 });\n }\n\n // Valid human with a real email — create the account\n return NextResponse.json({ ok: true, message: 'Account created.' });\n}",
|
|
68
|
+
"notes": "Pass email: body.email to aj.protect() so Arcjet's validateEmail rule can check the specific address in the request. The DISPOSABLE block type rejects known temporary email providers (Mailinator, 10minutemail, etc.) without your own blocklist."
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"gotchas": [
|
|
72
|
+
"Arcjet counts ALL requests through its middleware toward your monthly limit — not just blocked or rate-limited ones. On a public site with 100K pageviews/month, that's 100K Arcjet requests. Either exempt public-facing routes from Arcjet middleware or upgrade to a paid plan early.",
|
|
73
|
+
"Arcjet adds latency to every request it processes (typically 1-5ms for cloud analysis). For APIs with strict latency requirements (under 10ms P99), this overhead may be significant. Profile with and without Arcjet before deploying to latency-sensitive endpoints.",
|
|
74
|
+
"Arcjet's bot detection can block legitimate crawlers (Googlebot, Bingbot) if the rule is too aggressive. Use the category-specific allow list (detectBot({ allow: ['CATEGORY:SEARCH_ENGINE'] })) to whitelist known good bots."
|
|
75
|
+
],
|
|
76
|
+
"reliability": {
|
|
77
|
+
"uptimeGuarantee": "99.99% uptime SLA",
|
|
78
|
+
"statusPageUrl": "https://status.arcjet.com",
|
|
79
|
+
"notes": "Globally distributed edge network. Arcjet SDK includes a local fallback mode: if the Arcjet cloud is unreachable, the SDK fails open (allows requests) to prevent your app from going down due to a security service outage."
|
|
80
|
+
},
|
|
81
|
+
"qualityScore": 8,
|
|
82
|
+
"qualityJustification": "Best developer experience for adding rate limiting and bot protection as code — no separate WAF configuration, no infrastructure changes, just npm install and middleware. The SDK approach means security rules are version-controlled alongside the app. Main limitation: request-based billing (not just blocked requests) can be expensive on high-traffic public routes.",
|
|
83
|
+
"alternatives": [],
|
|
84
|
+
"complementary": ["vercel", "cloudflare-workers", "supabase", "clerk"],
|
|
85
|
+
"bestFor": "Adding rate limiting, bot protection, and attack detection directly in your Node.js or Next.js application code — no WAF or reverse proxy required",
|
|
86
|
+
"lastVerified": "2026-02-25",
|
|
87
|
+
"entryVersion": 1,
|
|
88
|
+
"addedBy": "claude-code-session-4"
|
|
89
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Snyk",
|
|
3
|
+
"slug": "snyk",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"subcategory": "vulnerability-scanning",
|
|
6
|
+
"website": "https://snyk.io",
|
|
7
|
+
"description": "Snyk is a developer security platform that scans code, open source dependencies, container images, and infrastructure-as-code for vulnerabilities. Integrates with GitHub, GitLab, CI/CD pipelines, and IDEs. The Snyk API allows automating vulnerability scans and reporting programmatically. Used by security-conscious teams to catch vulnerabilities before they reach production.",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Scan npm dependencies for known vulnerabilities in CI/CD",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Get automated pull request checks for new dependency vulnerabilities",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Audit a project's dependencies for security issues",
|
|
19
|
+
"fit": "good"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"task": "Monitor a production deployment for newly discovered vulnerabilities",
|
|
23
|
+
"fit": "good"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"auth": {
|
|
27
|
+
"method": "api_key",
|
|
28
|
+
"setupSteps": [
|
|
29
|
+
"Create a Snyk account at snyk.io",
|
|
30
|
+
"Navigate to Account Settings (top-right avatar menu)",
|
|
31
|
+
"Scroll to Auth Token and click to reveal your token",
|
|
32
|
+
"Copy the token",
|
|
33
|
+
"Install the CLI globally: npm install -g snyk",
|
|
34
|
+
"Authenticate the CLI: snyk auth (opens browser) or set SNYK_TOKEN env var directly"
|
|
35
|
+
],
|
|
36
|
+
"envVarName": "SNYK_TOKEN",
|
|
37
|
+
"codeSnippet": "// SNYK_TOKEN=your_snyk_token\n\n// Test dependencies for vulnerabilities (CLI)\n// SNYK_TOKEN=$SNYK_TOKEN snyk test --json\n\n// In CI, fail the build on high-severity issues\n// snyk test --severity-threshold=high\n\n// Monitor a project (uploads snapshot for ongoing monitoring)\n// snyk monitor --project-name=my-app"
|
|
38
|
+
},
|
|
39
|
+
"pricing": {
|
|
40
|
+
"model": "freemium",
|
|
41
|
+
"freeTier": "Free: 200 open source tests/month, unlimited projects, IDE integration, 1 contributor",
|
|
42
|
+
"startingPrice": "$25/contributor/month (Team) for unlimited tests, PR checks, license compliance",
|
|
43
|
+
"costPer": null,
|
|
44
|
+
"pricingUrl": "https://snyk.io/plans/"
|
|
45
|
+
},
|
|
46
|
+
"rateLimits": {
|
|
47
|
+
"tier": "free tier",
|
|
48
|
+
"limit": "200 open source test runs/month (free). API: rate limits not publicly documented but throttling occurs for bulk operations.",
|
|
49
|
+
"notes": "CLI test runs against the Snyk API count toward monthly test limits. CI/CD integration should be configured to test only on meaningful events (push to main, PR creation) rather than every commit to conserve test quota.",
|
|
50
|
+
"retryStrategy": "Use --json flag with snyk test for structured output. Implement retry for API timeout errors in CI scripts."
|
|
51
|
+
},
|
|
52
|
+
"sdk": {
|
|
53
|
+
"primaryLanguage": "typescript",
|
|
54
|
+
"installCommand": "npm install --save-exact snyk",
|
|
55
|
+
"importStatement": "// Primary interface is CLI: snyk test, snyk monitor. REST API available for reporting.",
|
|
56
|
+
"otherLanguages": ["python", "java", "go", "ruby"]
|
|
57
|
+
},
|
|
58
|
+
"codeExamples": [
|
|
59
|
+
{
|
|
60
|
+
"title": "Add Snyk to a CI/CD pipeline (GitHub Actions)",
|
|
61
|
+
"language": "typescript",
|
|
62
|
+
"code": "// .github/workflows/security.yml\n// name: Security scan\n// on:\n// push:\n// branches: [main]\n// pull_request:\n// branches: [main]\n// jobs:\n// snyk:\n// runs-on: ubuntu-latest\n// steps:\n// - uses: actions/checkout@v4\n// - uses: actions/setup-node@v4\n// with:\n// node-version: '20'\n// - run: npm ci\n// - uses: snyk/actions/node@master\n// env:\n// SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n// with:\n// args: --severity-threshold=high --json-file-output=snyk-results.json\n// - uses: actions/upload-artifact@v4\n// if: always()\n// with:\n// name: snyk-results\n// path: snyk-results.json\n\n// Parse Snyk JSON output in TypeScript\nimport { execSync } from 'child_process';\n\ninterface SnykVulnerability {\n id: string;\n title: string;\n severity: 'critical' | 'high' | 'medium' | 'low';\n packageName: string;\n version: string;\n fixedIn: string[];\n}\n\ninterface SnykResult {\n ok: boolean;\n vulnerabilities: SnykVulnerability[];\n dependencyCount: number;\n}\n\nfunction runSnykTest(projectPath: string): SnykResult {\n const output = execSync(`snyk test --json`, {\n cwd: projectPath,\n env: { ...process.env, SNYK_TOKEN: process.env.SNYK_TOKEN },\n }).toString();\n return JSON.parse(output) as SnykResult;\n}",
|
|
63
|
+
"notes": "Use snyk/actions/node@master in GitHub Actions for the simplest integration. The --json-file-output flag writes results even when vulnerabilities are found (snyk test exits with code 1 on findings, which would otherwise fail the step before output is written)."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"title": "Run Snyk vulnerability test programmatically",
|
|
67
|
+
"language": "typescript",
|
|
68
|
+
"code": "import { execSync, ExecSyncOptionsWithStringEncoding } from 'child_process';\n\ninterface SnykVuln {\n id: string;\n title: string;\n severity: string;\n packageName: string;\n version: string;\n fixedIn: string[];\n isUpgradable: boolean;\n isPatchable: boolean;\n}\n\ninterface SnykReport {\n ok: boolean;\n vulnerabilities: SnykVuln[];\n dependencyCount: number;\n summary: string;\n}\n\nfunction auditDependencies(cwd: string): SnykReport {\n const opts: ExecSyncOptionsWithStringEncoding = {\n cwd,\n encoding: 'utf8',\n env: { ...process.env },\n // snyk test exits 1 on findings — don't throw, capture output\n stdio: ['pipe', 'pipe', 'pipe'],\n };\n\n let raw: string;\n try {\n raw = execSync('snyk test --json', opts);\n } catch (err: unknown) {\n // Exit code 1 means vulnerabilities found — stdout still has JSON\n const execError = err as { stdout?: string };\n raw = execError.stdout ?? '{}';\n }\n\n const report = JSON.parse(raw) as SnykReport;\n\n const highs = report.vulnerabilities.filter(\n (v) => v.severity === 'high' || v.severity === 'critical'\n );\n\n if (highs.length > 0) {\n console.error(`Found ${highs.length} high/critical vulnerabilities:`);\n for (const v of highs) {\n console.error(` [${v.severity.toUpperCase()}] ${v.title} in ${v.packageName}@${v.version}`);\n }\n }\n\n return report;\n}",
|
|
69
|
+
"notes": "snyk test exits with code 1 when vulnerabilities are found, so execSync throws. Capture stdout from the thrown error — the JSON output is still present. Always check the ok field on the parsed result, not just the exit code."
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"gotchas": [
|
|
73
|
+
"Snyk tests count against your monthly limit EVERY time they run. A CI/CD pipeline that runs on every commit to every branch can exhaust 200 free tests in days on an active team. Set CI to test only on PRs or pushes to protected branches.",
|
|
74
|
+
"Snyk fix (auto-fixing vulnerabilities) can break your app by upgrading transitive dependencies to versions that introduce breaking API changes. Always review Snyk's suggested fixes in a test environment before applying, especially major version upgrades.",
|
|
75
|
+
"Snyk's severity ratings (Critical, High, Medium, Low) are based on CVSS scores but may differ from the NVD rating. A 'High' vulnerability in Snyk might be exploitable only under specific conditions not applicable to your code path. Review the vulnerability details rather than acting on severity alone."
|
|
76
|
+
],
|
|
77
|
+
"reliability": {
|
|
78
|
+
"uptimeGuarantee": "99.9% uptime SLA (Business+)",
|
|
79
|
+
"statusPageUrl": "https://status.snyk.io",
|
|
80
|
+
"notes": "AWS-hosted with global CDN. Vulnerability database is continuously updated as new CVEs are published. CLI operates in offline mode for scanning but requires connectivity to report results."
|
|
81
|
+
},
|
|
82
|
+
"qualityScore": 8,
|
|
83
|
+
"qualityJustification": "Industry standard for dependency vulnerability scanning with the best developer integration (IDE plugins, PR checks, auto-fix PRs). The free tier (200 tests/month) is sufficient for small projects. Main friction: test quota consumption in CI/CD, and automated fix PRs can introduce breaking changes.",
|
|
84
|
+
"alternatives": [],
|
|
85
|
+
"complementary": ["vercel", "fly-io", "railway"],
|
|
86
|
+
"bestFor": "Automated dependency vulnerability scanning integrated into your development workflow — CI checks, IDE warnings, and automated fix PRs for known security issues",
|
|
87
|
+
"lastVerified": "2026-02-25",
|
|
88
|
+
"entryVersion": 1,
|
|
89
|
+
"addedBy": "claude-code-session-4"
|
|
90
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Cloudinary",
|
|
3
|
+
"slug": "cloudinary",
|
|
4
|
+
"category": "storage",
|
|
5
|
+
"subcategory": "media-management",
|
|
6
|
+
"website": "https://cloudinary.com",
|
|
7
|
+
"description": "Cloudinary is a cloud-based media management platform that handles image and video upload, storage, transformation, optimization, and delivery via CDN. It provides powerful on-the-fly image transformations through URL parameters, making it the standard choice for apps that need dynamic image processing.",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Upload, store, and serve images and videos via CDN",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Resize, crop, and transform images on-the-fly via URL parameters",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Optimize images automatically for web performance",
|
|
19
|
+
"fit": "perfect"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"task": "Store and process user-uploaded files",
|
|
23
|
+
"fit": "good"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"task": "Store arbitrary files like PDFs or binaries",
|
|
27
|
+
"fit": "partial"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"auth": {
|
|
31
|
+
"method": "api_key",
|
|
32
|
+
"setupSteps": [
|
|
33
|
+
"Sign up at cloudinary.com",
|
|
34
|
+
"Go to Dashboard and find your Cloud Name, API Key, and API Secret",
|
|
35
|
+
"Set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET environment variables",
|
|
36
|
+
"Alternatively, use the CLOUDINARY_URL env var: cloudinary://API_KEY:API_SECRET@CLOUD_NAME"
|
|
37
|
+
],
|
|
38
|
+
"envVarName": "CLOUDINARY_API_SECRET",
|
|
39
|
+
"codeSnippet": "import { v2 as cloudinary } from 'cloudinary';\ncloudinary.config({\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME,\n api_key: process.env.CLOUDINARY_API_KEY,\n api_secret: process.env.CLOUDINARY_API_SECRET,\n});"
|
|
40
|
+
},
|
|
41
|
+
"pricing": {
|
|
42
|
+
"model": "freemium",
|
|
43
|
+
"freeTier": "25 credits/month (1 credit = 1000 transformations or 1GB storage or 1GB bandwidth)",
|
|
44
|
+
"startingPrice": "$89/month for 225 credits",
|
|
45
|
+
"costPer": "Varies by credits consumed; $0.05 per additional credit overage",
|
|
46
|
+
"pricingUrl": "https://cloudinary.com/pricing"
|
|
47
|
+
},
|
|
48
|
+
"rateLimits": {
|
|
49
|
+
"tier": "free tier",
|
|
50
|
+
"limit": "500 requests/hour for API operations",
|
|
51
|
+
"notes": "Delivery (CDN) has no rate limit — only management API operations are rate-limited. Transformations count against your credit quota.",
|
|
52
|
+
"retryStrategy": "Implement exponential backoff for 429 responses; CDN delivery is separate from management API and has no limits"
|
|
53
|
+
},
|
|
54
|
+
"sdk": {
|
|
55
|
+
"primaryLanguage": "typescript",
|
|
56
|
+
"installCommand": "npm install --save-exact cloudinary",
|
|
57
|
+
"importStatement": "import { v2 as cloudinary } from 'cloudinary';",
|
|
58
|
+
"otherLanguages": ["python", "java", "ruby", "php", "go", "android", "ios"]
|
|
59
|
+
},
|
|
60
|
+
"codeExamples": [
|
|
61
|
+
{
|
|
62
|
+
"title": "Upload an image",
|
|
63
|
+
"language": "typescript",
|
|
64
|
+
"code": "import { v2 as cloudinary } from 'cloudinary';\n\ncloudinary.config({\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME,\n api_key: process.env.CLOUDINARY_API_KEY,\n api_secret: process.env.CLOUDINARY_API_SECRET,\n});\n\n// Upload from file path\nconst result = await cloudinary.uploader.upload('./profile.jpg', {\n public_id: 'users/user_123/profile',\n folder: 'users',\n overwrite: true,\n resource_type: 'image',\n});\n\nconsole.log('Uploaded URL:', result.secure_url);\nconsole.log('Public ID:', result.public_id);",
|
|
65
|
+
"notes": "Use public_id to control the URL path. Set overwrite: true to replace existing images. The returned secure_url is the CDN URL for direct use in <img> tags."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"title": "Transform image via URL",
|
|
69
|
+
"language": "typescript",
|
|
70
|
+
"code": "import { v2 as cloudinary } from 'cloudinary';\n\ncloudinary.config({ /* ... */ });\n\n// Generate a URL with transformations — no API call needed!\nconst url = cloudinary.url('users/user_123/profile', {\n width: 200,\n height: 200,\n crop: 'fill',\n gravity: 'face', // Auto-detect and center on face\n quality: 'auto',\n fetch_format: 'auto', // Serve WebP to browsers that support it\n});\n\nconsole.log('Transformed URL:', url);\n// Outputs: https://res.cloudinary.com/your-cloud/image/upload/w_200,h_200,c_fill,g_face,q_auto,f_auto/users/user_123/profile",
|
|
71
|
+
"notes": "Transformations are applied on-the-fly and cached at the CDN edge. No server-side processing required — just build the URL."
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"gotchas": [
|
|
75
|
+
"Cloudinary's credit system is confusing — storage, bandwidth, AND transformations all consume credits at different rates. Monitor your dashboard carefully to avoid unexpected charges, especially if you have many unique transformations.",
|
|
76
|
+
"Upload signatures are required for secure client-side uploads. Never expose your API Secret in frontend code. Use cloudinary.utils.api_sign_request() server-side to generate upload signatures.",
|
|
77
|
+
"Every unique transformation URL is a separate cached variant. If you programmatically generate many transformation combinations, you can quickly exhaust your transformation quota. Stick to a fixed set of transformation presets.",
|
|
78
|
+
"The free tier's 25 credits/month sounds generous but goes quickly. 1GB of images stored + delivered = 2 credits. Plan your usage carefully."
|
|
79
|
+
],
|
|
80
|
+
"reliability": {
|
|
81
|
+
"uptimeGuarantee": "99.99% uptime SLA on paid plans",
|
|
82
|
+
"statusPageUrl": "https://status.cloudinary.com",
|
|
83
|
+
"notes": "Global CDN with 180+ PoPs. Images are replicated across multiple regions automatically. Management API has occasional latency spikes."
|
|
84
|
+
},
|
|
85
|
+
"qualityScore": 8,
|
|
86
|
+
"qualityJustification": "Best media management platform with unmatched transformation capabilities. URL-based transformations are genuinely magical. TypeScript SDK has some rough edges. Pricing model is complex and can surprise developers. Still the default choice for any app with significant media needs.",
|
|
87
|
+
"alternatives": ["uploadthing", "aws-s3", "cloudflare-r2", "digital-ocean-spaces", "imgix", "mux"],
|
|
88
|
+
"complementary": ["openai", "anthropic"],
|
|
89
|
+
"bestFor": "Image and video management with on-the-fly transformations, auto-optimization, and global CDN delivery",
|
|
90
|
+
"lastVerified": "2026-02-25",
|
|
91
|
+
"entryVersion": 1,
|
|
92
|
+
"addedBy": "claude-code-session-1"
|
|
93
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "UploadThing",
|
|
3
|
+
"slug": "uploadthing",
|
|
4
|
+
"category": "storage",
|
|
5
|
+
"subcategory": "file-uploads",
|
|
6
|
+
"website": "https://uploadthing.com",
|
|
7
|
+
"description": "UploadThing is a developer-friendly file upload service designed primarily for Next.js applications. It provides end-to-end type-safe file uploads with automatic storage, a built-in file router, and seamless integration with React and Next.js — all without needing to configure S3 buckets or cloud storage directly.",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Add file uploads to a Next.js application",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Upload user profile pictures and attachments",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Handle document uploads (PDFs, Word files) in a web app",
|
|
19
|
+
"fit": "good"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"task": "Complex media transformations like Cloudinary",
|
|
23
|
+
"fit": "partial"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"auth": {
|
|
27
|
+
"method": "api_key",
|
|
28
|
+
"setupSteps": [
|
|
29
|
+
"Sign up at uploadthing.com",
|
|
30
|
+
"Create a new app in the dashboard",
|
|
31
|
+
"Copy your UPLOADTHING_TOKEN from the dashboard",
|
|
32
|
+
"Set UPLOADTHING_TOKEN environment variable",
|
|
33
|
+
"Install the SDK and configure your file router (see code example)"
|
|
34
|
+
],
|
|
35
|
+
"envVarName": "UPLOADTHING_TOKEN",
|
|
36
|
+
"codeSnippet": "import { createUploadthing } from 'uploadthing/next';\nconst f = createUploadthing();"
|
|
37
|
+
},
|
|
38
|
+
"pricing": {
|
|
39
|
+
"model": "freemium",
|
|
40
|
+
"freeTier": "2GB storage, 2GB bandwidth/month",
|
|
41
|
+
"startingPrice": "$10/month for 100GB storage, 100GB bandwidth",
|
|
42
|
+
"costPer": "$0.10/GB storage/month, $0.10/GB bandwidth after plan limits",
|
|
43
|
+
"pricingUrl": "https://uploadthing.com/pricing"
|
|
44
|
+
},
|
|
45
|
+
"rateLimits": {
|
|
46
|
+
"tier": "free tier",
|
|
47
|
+
"limit": "2GB storage, 2GB bandwidth/month; file size limits configurable per route",
|
|
48
|
+
"notes": "Individual file size limit is set in your file router (maxFileSize option). Default is 4MB per file.",
|
|
49
|
+
"retryStrategy": "UploadThing handles retries internally. For server-side uploads, implement standard exponential backoff."
|
|
50
|
+
},
|
|
51
|
+
"sdk": {
|
|
52
|
+
"primaryLanguage": "typescript",
|
|
53
|
+
"installCommand": "npm install --save-exact uploadthing @uploadthing/react",
|
|
54
|
+
"importStatement": "import { createUploadthing } from 'uploadthing/next';",
|
|
55
|
+
"otherLanguages": ["solid", "svelte", "vue"]
|
|
56
|
+
},
|
|
57
|
+
"codeExamples": [
|
|
58
|
+
{
|
|
59
|
+
"title": "Configure file router (Next.js App Router)",
|
|
60
|
+
"language": "typescript",
|
|
61
|
+
"code": "// app/api/uploadthing/core.ts\nimport { createUploadthing, type FileRouter } from 'uploadthing/next';\nimport { auth } from '@clerk/nextjs/server'; // or your auth provider\n\nconst f = createUploadthing();\n\nexport const ourFileRouter = {\n profileImageUploader: f({ image: { maxFileSize: '4MB', maxFileCount: 1 } })\n .middleware(async () => {\n const { userId } = await auth();\n if (!userId) throw new Error('Unauthorized');\n return { userId }; // Passed to onUploadComplete\n })\n .onUploadComplete(async ({ metadata, file }) => {\n console.log('Upload complete for user:', metadata.userId);\n console.log('File URL:', file.url);\n return { uploadedBy: metadata.userId };\n }),\n} satisfies FileRouter;\n\nexport type OurFileRouter = typeof ourFileRouter;",
|
|
62
|
+
"notes": "Create the route handler at app/api/uploadthing/route.ts using createRouteHandler({ router: ourFileRouter }). The middleware runs server-side before upload."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"title": "Upload component (React)",
|
|
66
|
+
"language": "typescript",
|
|
67
|
+
"code": "// components/FileUploader.tsx\n'use client';\n\nimport { UploadButton } from '@uploadthing/react';\nimport type { OurFileRouter } from '@/app/api/uploadthing/core';\n\nexport function ProfileImageUploader() {\n return (\n <UploadButton<OurFileRouter, 'profileImageUploader'>\n endpoint=\"profileImageUploader\"\n onClientUploadComplete={(res) => {\n console.log('Files:', res);\n alert('Upload complete!');\n }}\n onUploadError={(error: Error) => {\n alert(`Upload error: ${error.message}`);\n }}\n />\n );\n}",
|
|
68
|
+
"notes": "UploadButton is a pre-built React component. Use UploadDropzone for drag-and-drop. The file router type ensures full end-to-end type safety."
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"gotchas": [
|
|
72
|
+
"UploadThing is tightly integrated with Next.js and the App Router. Using it with other frameworks (Express, Fastify) requires more manual setup and is less well-documented.",
|
|
73
|
+
"Files are stored on UploadThing's infrastructure (Cloudflare R2 backed) — there is no option to bring your own S3 bucket on standard plans. Enterprise plans may differ.",
|
|
74
|
+
"The free tier (2GB storage) is very limited for production applications with user-uploaded content. Budget for storage costs early.",
|
|
75
|
+
"The UPLOADTHING_SECRET/UPLOADTHING_TOKEN format changed in recent SDK versions. Make sure you're using the format matching your SDK version."
|
|
76
|
+
],
|
|
77
|
+
"reliability": {
|
|
78
|
+
"uptimeGuarantee": null,
|
|
79
|
+
"statusPageUrl": "https://status.uploadthing.com",
|
|
80
|
+
"notes": "UploadThing uses Cloudflare R2 as its backing storage. Generally very reliable. Smaller team than Cloudinary so issues take longer to resolve."
|
|
81
|
+
},
|
|
82
|
+
"qualityScore": 7,
|
|
83
|
+
"qualityJustification": "Excellent Next.js integration with type-safe file router pattern — the best developer experience for file uploads in the Next.js ecosystem. Limited to file upload/storage only (no transformations). Free tier is small. Score reflects excellent DX for its specific use case.",
|
|
84
|
+
"alternatives": ["cloudinary"],
|
|
85
|
+
"complementary": ["clerk", "openai"],
|
|
86
|
+
"bestFor": "Simple, type-safe file uploads in Next.js applications without the overhead of configuring cloud storage",
|
|
87
|
+
"lastVerified": "2026-02-25",
|
|
88
|
+
"entryVersion": 1,
|
|
89
|
+
"addedBy": "claude-code-session-1"
|
|
90
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "BrowserStack",
|
|
3
|
+
"slug": "browserstack",
|
|
4
|
+
"category": "testing",
|
|
5
|
+
"subcategory": "cross-browser-testing",
|
|
6
|
+
"website": "https://www.browserstack.com",
|
|
7
|
+
"description": "BrowserStack provides real device and browser cloud for automated and manual testing. Run Selenium, Playwright, Cypress, and Appium tests on 3,000+ real browsers and mobile devices without maintaining your own device farm. Integrates with CI/CD pipelines (GitHub Actions, Jenkins, CircleCI).",
|
|
8
|
+
"useCases": [
|
|
9
|
+
{
|
|
10
|
+
"task": "Run automated Playwright or Cypress tests on real browsers in CI",
|
|
11
|
+
"fit": "perfect"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"task": "Test a web app on mobile devices (iOS, Android) without owning devices",
|
|
15
|
+
"fit": "perfect"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"task": "Debug browser-specific layout issues with live interactive sessions",
|
|
19
|
+
"fit": "good"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"auth": {
|
|
23
|
+
"method": "api_key",
|
|
24
|
+
"setupSteps": [
|
|
25
|
+
"Create a BrowserStack account at browserstack.com",
|
|
26
|
+
"Navigate to the Automate product dashboard",
|
|
27
|
+
"Go to Settings in the top-right menu",
|
|
28
|
+
"Copy your Username and Access Key from the Automate section",
|
|
29
|
+
"Set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY as environment variables"
|
|
30
|
+
],
|
|
31
|
+
"envVarName": "BROWSERSTACK_USERNAME",
|
|
32
|
+
"codeSnippet": "// Set credentials as environment variables\n// BROWSERSTACK_USERNAME=your_username\n// BROWSERSTACK_ACCESS_KEY=your_access_key\n\nimport { chromium } from 'playwright';\n\nconst browser = await chromium.connect(\n `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify({\n browser: 'chrome',\n browser_version: 'latest',\n os: 'Windows',\n os_version: '11',\n 'browserstack.username': process.env.BROWSERSTACK_USERNAME,\n 'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY,\n }))}`\n);"
|
|
33
|
+
},
|
|
34
|
+
"pricing": {
|
|
35
|
+
"model": "paid",
|
|
36
|
+
"freeTier": "Free trial: 100 minutes of Automate and 100 minutes of App Automate",
|
|
37
|
+
"startingPrice": "$38/month (Automate Starter) for 1 parallel test, 100 minutes/month",
|
|
38
|
+
"costPer": null,
|
|
39
|
+
"pricingUrl": "https://www.browserstack.com/pricing"
|
|
40
|
+
},
|
|
41
|
+
"rateLimits": {
|
|
42
|
+
"tier": "Starter plan",
|
|
43
|
+
"limit": "1 parallel session (Starter), up to 1,000 parallel sessions (Enterprise). Session length max: 3 hours (Automate)",
|
|
44
|
+
"notes": "Session queues when all parallels are in use. Idle sessions timeout after 90 seconds of no commands. Tests must send keep-alive commands during long operations.",
|
|
45
|
+
"retryStrategy": "Implement test-level retry for flaky network issues. BrowserStack sessions auto-terminate after 90s idle to avoid consuming minutes."
|
|
46
|
+
},
|
|
47
|
+
"sdk": {
|
|
48
|
+
"primaryLanguage": "typescript",
|
|
49
|
+
"installCommand": "npm install --save-exact @playwright/test browserstacklocal",
|
|
50
|
+
"importStatement": "import { chromium } from 'playwright';",
|
|
51
|
+
"otherLanguages": ["python", "java", "ruby", "php"]
|
|
52
|
+
},
|
|
53
|
+
"codeExamples": [
|
|
54
|
+
{
|
|
55
|
+
"title": "Run Playwright test on BrowserStack",
|
|
56
|
+
"language": "typescript",
|
|
57
|
+
"code": "import { test, expect } from '@playwright/test';\nimport { chromium } from 'playwright';\n\ntest('homepage loads on BrowserStack Chrome', async () => {\n const caps = {\n browser: 'chrome',\n browser_version: 'latest',\n os: 'Windows',\n os_version: '11',\n name: 'Homepage smoke test',\n build: `CI build ${process.env.GITHUB_RUN_NUMBER ?? 'local'}`,\n 'browserstack.username': process.env.BROWSERSTACK_USERNAME!,\n 'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY!,\n };\n\n const browser = await chromium.connect(\n `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify(caps))}`\n );\n\n const page = await browser.newPage();\n await page.goto('https://example.com');\n\n await expect(page).toHaveTitle(/Example Domain/);\n await browser.close();\n});",
|
|
58
|
+
"notes": "Pass build and name caps to group test results in the BrowserStack Automate dashboard. The GITHUB_RUN_NUMBER env var ties results to a specific CI run. Always close the browser to release the parallel session immediately."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"title": "Configure BrowserStack in playwright.config.ts",
|
|
62
|
+
"language": "typescript",
|
|
63
|
+
"code": "import { defineConfig, devices } from '@playwright/test';\n\nexport default defineConfig({\n testDir: './tests',\n timeout: 60_000,\n use: {\n // BrowserStack Playwright CDPendpoint\n connectOptions: {\n wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(\n JSON.stringify({\n browser: 'chrome',\n browser_version: 'latest',\n os: 'OS X',\n os_version: 'Sonoma',\n 'browserstack.username': process.env.BROWSERSTACK_USERNAME,\n 'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY,\n build: process.env.GITHUB_SHA ?? 'local',\n project: 'my-app',\n })\n )}`,\n },\n },\n projects: [\n {\n name: 'chrome-windows',\n use: { ...devices['Desktop Chrome'] },\n },\n {\n name: 'safari-mac',\n use: { ...devices['Desktop Safari'] },\n },\n ],\n});",
|
|
64
|
+
"notes": "Centralise the BrowserStack connection in playwright.config.ts so individual test files stay browser-agnostic. Use process.env.GITHUB_SHA as the build identifier to correlate results with commits in the BrowserStack dashboard."
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"gotchas": [
|
|
68
|
+
"BrowserStack sessions have a hard 90-second idle timeout. If your test has a page.waitForNavigation() or sleep that takes over 90 seconds without sending any WebDriver command, the session is killed and your test reports a 'Session not found' error. Send intermediate commands or reduce idle time.",
|
|
69
|
+
"Local testing (testing localhost or internal staging URLs) requires the BrowserStackLocal binary running as a tunnel daemon. The binary must be started before the test and stopped after. Forgetting to start the tunnel is one of the most common CI failures.",
|
|
70
|
+
"BrowserStack test minutes are consumed even for failed tests. A flaky test suite that retries 3x will consume 3x the minutes. Optimize your test suite to reduce reruns, and use BrowserStack's test reporting to identify flaky tests.",
|
|
71
|
+
"The free trial limits (100 minutes) are consumed quickly — a typical Playwright test suite takes 2-5 minutes per test. 100 minutes covers 20-50 tests total. Plan your budget before converting to a paid plan."
|
|
72
|
+
],
|
|
73
|
+
"reliability": {
|
|
74
|
+
"uptimeGuarantee": "99.9% uptime SLA",
|
|
75
|
+
"statusPageUrl": "https://status.browserstack.com",
|
|
76
|
+
"notes": "Global device cloud infrastructure with redundant device pools. BrowserStack maintains physical devices across multiple data centers to ensure availability."
|
|
77
|
+
},
|
|
78
|
+
"qualityScore": 8,
|
|
79
|
+
"qualityJustification": "Industry standard for cross-browser and cross-device testing. 3,000+ real devices is genuinely useful — no other service comes close for mobile device coverage. Playwright and Cypress integrations are well-maintained. Main friction: expensive at scale, and session timeouts require careful test design.",
|
|
80
|
+
"alternatives": ["checkly"],
|
|
81
|
+
"complementary": ["vercel", "netlify"],
|
|
82
|
+
"bestFor": "Running automated end-to-end tests on real browsers and mobile devices across your CI/CD pipeline without maintaining a device farm",
|
|
83
|
+
"lastVerified": "2026-02-25",
|
|
84
|
+
"entryVersion": 1,
|
|
85
|
+
"addedBy": "claude-code-session-4"
|
|
86
|
+
}
|