kaddidlehopper 0.1.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 (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,372 @@
1
+ ---
2
+ name: netlify-functions
3
+ description: Create and deploy Netlify serverless functions and edge functions. Use when implementing API endpoints, server-side logic, background jobs, scheduled tasks, or edge computing on Netlify.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: netlify
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Netlify Functions
11
+
12
+ Netlify Functions are serverless functions that run on-demand without managing servers. They support JavaScript, TypeScript, and Go.
13
+
14
+ ## When to Use
15
+
16
+ - API endpoints for your frontend
17
+ - Server-side data processing
18
+ - Third-party API integrations (hiding API keys)
19
+ - Background/async processing
20
+ - Scheduled jobs (cron-like)
21
+ - Edge computing (low-latency responses)
22
+
23
+ ## Directory Structure
24
+
25
+ ```
26
+ project/
27
+ ├── netlify/
28
+ │ └── functions/
29
+ │ ├── hello.ts # → /.netlify/functions/hello
30
+ │ ├── api/
31
+ │ │ └── users.ts # → /.netlify/functions/api-users
32
+ │ └── process-background.ts # Background function
33
+ │ └── edge-functions/
34
+ │ └── geo.ts # Edge function
35
+ ├── netlify.toml
36
+ └── package.json
37
+ ```
38
+
39
+ ## Basic Serverless Function (TypeScript)
40
+
41
+ ```typescript
42
+ // netlify/functions/hello.ts
43
+ import type { Context } from "@netlify/functions";
44
+
45
+ export default async (request: Request, context: Context) => {
46
+ const { name = "World" } = await request.json().catch(() => ({}));
47
+
48
+ return new Response(JSON.stringify({ message: `Hello, ${name}!` }), {
49
+ status: 200,
50
+ headers: { "Content-Type": "application/json" },
51
+ });
52
+ };
53
+ ```
54
+
55
+ ## Function with Path Parameters
56
+
57
+ ```typescript
58
+ // netlify/functions/users.ts
59
+ import type { Context } from "@netlify/functions";
60
+
61
+ export default async (request: Request, context: Context) => {
62
+ const { id } = context.params; // From path like /api/users/:id
63
+
64
+ // Fetch user from database
65
+ const user = await getUser(id);
66
+
67
+ if (!user) {
68
+ return new Response(JSON.stringify({ error: "User not found" }), {
69
+ status: 404,
70
+ headers: { "Content-Type": "application/json" },
71
+ });
72
+ }
73
+
74
+ return Response.json(user);
75
+ };
76
+
77
+ // Configure custom path in the function
78
+ export const config = {
79
+ path: "/api/users/:id",
80
+ };
81
+ ```
82
+
83
+ ## Handling Different HTTP Methods
84
+
85
+ ```typescript
86
+ // netlify/functions/items.ts
87
+ import type { Context } from "@netlify/functions";
88
+
89
+ export default async (request: Request, context: Context) => {
90
+ const { method } = request;
91
+
92
+ switch (method) {
93
+ case "GET":
94
+ return handleGet(request, context);
95
+ case "POST":
96
+ return handlePost(request, context);
97
+ case "PUT":
98
+ return handlePut(request, context);
99
+ case "DELETE":
100
+ return handleDelete(request, context);
101
+ default:
102
+ return new Response("Method not allowed", { status: 405 });
103
+ }
104
+ };
105
+
106
+ async function handleGet(request: Request, context: Context) {
107
+ const items = await fetchItems();
108
+ return Response.json(items);
109
+ }
110
+
111
+ async function handlePost(request: Request, context: Context) {
112
+ const body = await request.json();
113
+ const newItem = await createItem(body);
114
+ return Response.json(newItem, { status: 201 });
115
+ }
116
+ ```
117
+
118
+ ## Environment Variables
119
+
120
+ Access environment variables via `process.env`:
121
+
122
+ ```typescript
123
+ // netlify/functions/api.ts
124
+ export default async (request: Request, context: Context) => {
125
+ const apiKey = process.env.API_KEY;
126
+
127
+ if (!apiKey) {
128
+ return new Response("API key not configured", { status: 500 });
129
+ }
130
+
131
+ const response = await fetch("https://api.example.com/data", {
132
+ headers: { Authorization: `Bearer ${apiKey}` },
133
+ });
134
+
135
+ return Response.json(await response.json());
136
+ };
137
+ ```
138
+
139
+ Set environment variables in Netlify UI or `netlify.toml`:
140
+
141
+ ```toml
142
+ # netlify.toml
143
+ [build.environment]
144
+ API_KEY = "your-api-key" # Better to set in Netlify UI for secrets
145
+ ```
146
+
147
+ ## Background Functions
148
+
149
+ For long-running tasks (up to 15 minutes), use background functions:
150
+
151
+ ```typescript
152
+ // netlify/functions/process-background.ts
153
+ // Name MUST end with `-background`
154
+ import type { Context } from "@netlify/functions";
155
+
156
+ export default async (request: Request, context: Context) => {
157
+ const data = await request.json();
158
+
159
+ // Long-running task - client gets 202 immediately
160
+ await processLargeDataset(data);
161
+
162
+ // Response is ignored for background functions
163
+ // Client always receives 202 Accepted immediately
164
+ };
165
+ ```
166
+
167
+ **Naming**: File must be named `*-background.ts` (e.g., `process-background.ts`)
168
+
169
+ ## Scheduled Functions
170
+
171
+ Run functions on a schedule (cron):
172
+
173
+ ```typescript
174
+ // netlify/functions/daily-cleanup.ts
175
+ import type { Config } from "@netlify/functions";
176
+
177
+ export default async () => {
178
+ await cleanupOldRecords();
179
+ console.log("Cleanup completed");
180
+ };
181
+
182
+ export const config: Config = {
183
+ schedule: "0 0 * * *", // Run daily at midnight UTC
184
+ };
185
+ ```
186
+
187
+ Common cron patterns:
188
+ - `"0 * * * *"` - Every hour
189
+ - `"0 0 * * *"` - Daily at midnight
190
+ - `"0 0 * * 0"` - Weekly on Sunday
191
+ - `"*/5 * * * *"` - Every 5 minutes
192
+
193
+ ## Edge Functions
194
+
195
+ Edge functions run closer to users for ultra-low latency:
196
+
197
+ ```typescript
198
+ // netlify/edge-functions/geo.ts
199
+ import type { Context } from "@netlify/edge-functions";
200
+
201
+ export default async (request: Request, context: Context) => {
202
+ const { country, city } = context.geo;
203
+
204
+ return new Response(JSON.stringify({
205
+ message: `Hello from ${city}, ${country}!`,
206
+ timestamp: new Date().toISOString(),
207
+ }), {
208
+ headers: { "Content-Type": "application/json" },
209
+ });
210
+ };
211
+
212
+ export const config = {
213
+ path: "/api/location",
214
+ };
215
+ ```
216
+
217
+ Configure in `netlify.toml`:
218
+
219
+ ```toml
220
+ [[edge_functions]]
221
+ function = "geo"
222
+ path = "/api/location"
223
+ ```
224
+
225
+ ## Edge Function vs Serverless Function
226
+
227
+ | Feature | Serverless | Edge |
228
+ |---------|-----------|------|
229
+ | Location | Single region | Global edge |
230
+ | Timeout | 10s (26s on paid) | 50ms |
231
+ | Cold start | Can be slow | Very fast |
232
+ | Use case | Heavy computation | Low-latency responses |
233
+ | Middleware | No | Yes |
234
+
235
+ ## Context Object Properties
236
+
237
+ ```typescript
238
+ export default async (request: Request, context: Context) => {
239
+ // Request info
240
+ context.ip; // Client IP address
241
+ context.geo.city; // Geo location
242
+ context.geo.country.code; // Country code
243
+ context.geo.subdivision.code; // State/region
244
+
245
+ // Deploy info
246
+ context.site.id; // Site ID
247
+ context.deploy.id; // Deploy ID
248
+ context.deploy.published; // Is this the published deploy?
249
+
250
+ // Function info
251
+ context.requestId; // Unique request ID
252
+ context.params; // URL path parameters
253
+
254
+ // Cookies
255
+ context.cookies.get("session");
256
+ context.cookies.set({ name: "session", value: "abc123" });
257
+
258
+ // Environment
259
+ context.env.get("API_KEY");
260
+ };
261
+ ```
262
+
263
+ ## CORS Configuration
264
+
265
+ ```typescript
266
+ // netlify/functions/api.ts
267
+ const headers = {
268
+ "Access-Control-Allow-Origin": "*",
269
+ "Access-Control-Allow-Headers": "Content-Type",
270
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
271
+ };
272
+
273
+ export default async (request: Request, context: Context) => {
274
+ // Handle preflight
275
+ if (request.method === "OPTIONS") {
276
+ return new Response(null, { status: 204, headers });
277
+ }
278
+
279
+ // Your logic here
280
+ const data = { message: "Hello" };
281
+
282
+ return new Response(JSON.stringify(data), {
283
+ status: 200,
284
+ headers: { ...headers, "Content-Type": "application/json" },
285
+ });
286
+ };
287
+ ```
288
+
289
+ ## netlify.toml Configuration
290
+
291
+ ```toml
292
+ [build]
293
+ functions = "netlify/functions"
294
+
295
+ [functions]
296
+ # Set default Node.js version
297
+ node_bundler = "esbuild"
298
+
299
+ # Include files in function bundle
300
+ included_files = ["data/**"]
301
+
302
+ # Function-specific settings
303
+ [functions."api-*"]
304
+ # Increase memory for API functions
305
+ memory = 1024
306
+
307
+ # Edge function declarations
308
+ [[edge_functions]]
309
+ function = "auth"
310
+ path = "/dashboard/*"
311
+ ```
312
+
313
+ ## Local Development
314
+
315
+ ```bash
316
+ # Install Netlify CLI
317
+ npm install -g netlify-cli
318
+
319
+ # Run locally with functions
320
+ netlify dev
321
+
322
+ # Functions available at http://localhost:8888/.netlify/functions/
323
+ ```
324
+
325
+ ## Common Patterns
326
+
327
+ ### Database Connection
328
+
329
+ ```typescript
330
+ // netlify/functions/db.ts
331
+ import { neon } from "@neondatabase/serverless";
332
+
333
+ const sql = neon(process.env.DATABASE_URL!);
334
+
335
+ export default async (request: Request) => {
336
+ const users = await sql`SELECT * FROM users LIMIT 10`;
337
+ return Response.json(users);
338
+ };
339
+ ```
340
+
341
+ ### Rate Limiting with Blobs
342
+
343
+ ```typescript
344
+ import { getStore } from "@netlify/blobs";
345
+
346
+ export default async (request: Request, context: Context) => {
347
+ const store = getStore("rate-limits");
348
+ const ip = context.ip;
349
+
350
+ const current = await store.get(ip, { type: "json" }) as { count: number; reset: number } | null;
351
+ const now = Date.now();
352
+
353
+ if (current && current.reset > now && current.count >= 100) {
354
+ return new Response("Rate limit exceeded", { status: 429 });
355
+ }
356
+
357
+ await store.setJSON(ip, {
358
+ count: (current?.count || 0) + 1,
359
+ reset: current?.reset > now ? current.reset : now + 60000,
360
+ });
361
+
362
+ // Continue with request...
363
+ };
364
+ ```
365
+
366
+ ## Limits
367
+
368
+ - **Serverless functions**: 10 second timeout (26s on paid plans)
369
+ - **Background functions**: 15 minute timeout
370
+ - **Edge functions**: 50ms CPU time
371
+ - **Payload size**: 6MB request/response
372
+ - **Memory**: 1024MB default (configurable)