alchemy 0.37.2 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/bin/alchemy.mjs +44306 -48055
  2. package/bin/alchemy.ts +80 -85
  3. package/bin/commands/create.ts +263 -0
  4. package/bin/constants.ts +13 -0
  5. package/bin/errors.ts +11 -0
  6. package/bin/services/dependencies.ts +47 -0
  7. package/bin/services/get-package-version.ts +11 -0
  8. package/bin/services/package-manager.ts +113 -0
  9. package/bin/services/template-manager.ts +137 -0
  10. package/bin/types.ts +83 -0
  11. package/lib/alchemy.d.ts.map +1 -1
  12. package/lib/alchemy.js +12 -3
  13. package/lib/alchemy.js.map +1 -1
  14. package/lib/apply.d.ts +2 -0
  15. package/lib/apply.d.ts.map +1 -1
  16. package/lib/apply.js +47 -12
  17. package/lib/apply.js.map +1 -1
  18. package/lib/build-date.d.ts +6 -0
  19. package/lib/build-date.d.ts.map +1 -0
  20. package/lib/build-date.js +8 -0
  21. package/lib/build-date.js.map +1 -0
  22. package/lib/cloudflare/api.d.ts.map +1 -1
  23. package/lib/cloudflare/api.js +2 -1
  24. package/lib/cloudflare/api.js.map +1 -1
  25. package/lib/cloudflare/do-state-store/internal.js +5 -3
  26. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  27. package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -1
  28. package/lib/cloudflare/do-state-store/worker.js +6 -1
  29. package/lib/cloudflare/do-state-store/worker.js.map +1 -1
  30. package/lib/cloudflare/pipeline.d.ts +1 -1
  31. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  32. package/lib/cloudflare/pipeline.js +6 -1
  33. package/lib/cloudflare/pipeline.js.map +1 -1
  34. package/lib/cloudflare/worker-stub.d.ts +23 -2
  35. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  36. package/lib/cloudflare/worker-stub.js +16 -2
  37. package/lib/cloudflare/worker-stub.js.map +1 -1
  38. package/lib/cloudflare/worker.d.ts +5 -4
  39. package/lib/cloudflare/worker.d.ts.map +1 -1
  40. package/lib/cloudflare/worker.js +4 -7
  41. package/lib/cloudflare/worker.js.map +1 -1
  42. package/lib/context.d.ts +2 -2
  43. package/lib/context.d.ts.map +1 -1
  44. package/lib/destroy.d.ts +8 -2
  45. package/lib/destroy.d.ts.map +1 -1
  46. package/lib/destroy.js +19 -5
  47. package/lib/destroy.js.map +1 -1
  48. package/lib/scope.d.ts +26 -4
  49. package/lib/scope.d.ts.map +1 -1
  50. package/lib/scope.js +104 -9
  51. package/lib/scope.js.map +1 -1
  52. package/lib/sentry/project.js +1 -1
  53. package/lib/sentry/project.js.map +1 -1
  54. package/lib/util/logger.js +1 -1
  55. package/lib/util/logger.js.map +1 -1
  56. package/lib/util/mutex.d.ts +9 -0
  57. package/lib/util/mutex.d.ts.map +1 -0
  58. package/lib/util/mutex.js +35 -0
  59. package/lib/util/mutex.js.map +1 -0
  60. package/package.json +16 -6
  61. package/src/alchemy.ts +17 -5
  62. package/src/apply.ts +67 -17
  63. package/src/build-date.ts +8 -0
  64. package/src/cloudflare/api.ts +2 -1
  65. package/src/cloudflare/do-state-store/internal.ts +5 -3
  66. package/src/cloudflare/do-state-store/worker.ts +13 -3
  67. package/src/cloudflare/pipeline.ts +6 -0
  68. package/src/cloudflare/worker-stub.ts +31 -2
  69. package/src/cloudflare/worker.ts +6 -15
  70. package/src/context.ts +2 -2
  71. package/src/destroy.ts +30 -7
  72. package/src/scope.ts +145 -10
  73. package/src/sentry/project.ts +1 -1
  74. package/src/util/logger.ts +1 -1
  75. package/src/util/mutex.ts +33 -0
  76. package/templates/astro/.vscode/extensions.json +4 -0
  77. package/templates/astro/.vscode/launch.json +11 -0
  78. package/templates/astro/README.md +48 -0
  79. package/templates/astro/_env.example +1 -0
  80. package/templates/astro/_gitignore +27 -0
  81. package/templates/astro/alchemy.run.ts +16 -0
  82. package/templates/astro/astro.config.mjs +8 -0
  83. package/templates/astro/package.json +23 -0
  84. package/templates/astro/public/favicon.svg +9 -0
  85. package/templates/astro/src/assets/astro.svg +1 -0
  86. package/templates/astro/src/assets/background.svg +1 -0
  87. package/templates/astro/src/components/Welcome.astro +210 -0
  88. package/templates/astro/src/layouts/Layout.astro +22 -0
  89. package/templates/astro/src/pages/api/hello.ts +19 -0
  90. package/templates/astro/src/pages/index.astro +11 -0
  91. package/templates/astro/tsconfig.json +18 -0
  92. package/templates/astro/types/env.d.ts +16 -0
  93. package/templates/astro/wrangler.jsonc +6 -0
  94. package/templates/nuxt/README.md +75 -0
  95. package/templates/nuxt/_env +1 -0
  96. package/templates/nuxt/_env.example +1 -0
  97. package/templates/nuxt/_gitignore +27 -0
  98. package/templates/nuxt/alchemy.run.ts +16 -0
  99. package/templates/nuxt/app.vue +6 -0
  100. package/templates/nuxt/nuxt.config.ts +13 -0
  101. package/templates/nuxt/package.json +26 -0
  102. package/templates/nuxt/public/favicon.ico +0 -0
  103. package/templates/nuxt/public/robots.txt +2 -0
  104. package/templates/nuxt/server/api/hello.ts +7 -0
  105. package/templates/nuxt/server/middleware/auth.ts +5 -0
  106. package/templates/nuxt/server/middleware/hello.ts +5 -0
  107. package/templates/nuxt/server/tsconfig.json +3 -0
  108. package/templates/nuxt/tsconfig.json +15 -0
  109. package/templates/nuxt/types/env.d.ts +16 -0
  110. package/templates/react-router/.vscode/settings.json +5 -0
  111. package/templates/react-router/README.md +79 -0
  112. package/templates/react-router/_env +1 -0
  113. package/templates/react-router/_env.example +1 -0
  114. package/templates/react-router/_gitignore +15 -0
  115. package/templates/react-router/alchemy.run.ts +17 -0
  116. package/templates/react-router/app/app.css +15 -0
  117. package/templates/react-router/app/entry.server.tsx +43 -0
  118. package/templates/react-router/app/root.tsx +75 -0
  119. package/templates/react-router/app/routes/home.tsx +17 -0
  120. package/templates/react-router/app/routes.ts +3 -0
  121. package/templates/react-router/app/welcome/logo-dark.svg +23 -0
  122. package/templates/react-router/app/welcome/logo-light.svg +23 -0
  123. package/templates/react-router/app/welcome/welcome.tsx +90 -0
  124. package/templates/react-router/package.json +34 -0
  125. package/templates/react-router/public/favicon.ico +0 -0
  126. package/templates/react-router/react-router.config.ts +8 -0
  127. package/templates/react-router/tsconfig.cloudflare.json +27 -0
  128. package/templates/react-router/tsconfig.json +17 -0
  129. package/templates/react-router/tsconfig.node.json +20 -0
  130. package/templates/react-router/types/env.d.ts +16 -0
  131. package/templates/react-router/vite.config.ts +14 -0
  132. package/templates/react-router/workers/app.ts +23 -0
  133. package/templates/react-router/wrangler.jsonc +6 -0
  134. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-interruptors.mdc +247 -0
  135. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-middleware.mdc +75 -0
  136. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-react.mdc +110 -0
  137. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-request-response.mdc +213 -0
  138. package/templates/rwsdk/.devcontainer/Dockerfile +8 -0
  139. package/templates/rwsdk/.devcontainer/devcontainer.json +21 -0
  140. package/templates/rwsdk/README.md +62 -0
  141. package/templates/rwsdk/_env +5 -0
  142. package/templates/rwsdk/_env.example +5 -0
  143. package/templates/rwsdk/_gitignore +71 -0
  144. package/templates/rwsdk/alchemy.run.ts +29 -0
  145. package/templates/rwsdk/package.json +61 -0
  146. package/templates/rwsdk/prisma/schema.prisma +43 -0
  147. package/templates/rwsdk/src/app/Document.tsx +16 -0
  148. package/templates/rwsdk/src/app/headers.ts +32 -0
  149. package/templates/rwsdk/src/app/pages/Home.tsx +13 -0
  150. package/templates/rwsdk/src/app/pages/user/Login.tsx +79 -0
  151. package/templates/rwsdk/src/app/pages/user/functions.ts +171 -0
  152. package/templates/rwsdk/src/app/pages/user/routes.ts +17 -0
  153. package/templates/rwsdk/src/app/shared/links.ts +3 -0
  154. package/templates/rwsdk/src/client.tsx +3 -0
  155. package/templates/rwsdk/src/db.ts +26 -0
  156. package/templates/rwsdk/src/scripts/seed.ts +21 -0
  157. package/templates/rwsdk/src/session/durableObject.ts +63 -0
  158. package/templates/rwsdk/src/session/store.ts +13 -0
  159. package/templates/rwsdk/src/worker.tsx +63 -0
  160. package/templates/rwsdk/tsconfig.json +47 -0
  161. package/templates/rwsdk/types/env.d.ts +16 -0
  162. package/templates/rwsdk/types/rw.d.ts +5 -0
  163. package/templates/rwsdk/types/vite.d.ts +4 -0
  164. package/templates/rwsdk/vite.config.mts +6 -0
  165. package/templates/rwsdk/wrangler.jsonc +28 -0
  166. package/templates/sveltekit/README.md +38 -0
  167. package/templates/sveltekit/_env +1 -0
  168. package/templates/sveltekit/_env.example +1 -0
  169. package/templates/sveltekit/_gitignore +26 -0
  170. package/templates/sveltekit/_npmrc +1 -0
  171. package/templates/sveltekit/_prettierrc +3 -0
  172. package/templates/sveltekit/alchemy.run.ts +16 -0
  173. package/templates/sveltekit/bun.lock +1099 -0
  174. package/templates/sveltekit/package.json +33 -0
  175. package/templates/sveltekit/src/app.css +1 -0
  176. package/templates/sveltekit/src/app.d.ts +13 -0
  177. package/templates/sveltekit/src/app.html +12 -0
  178. package/templates/sveltekit/src/routes/+layout.svelte +7 -0
  179. package/templates/sveltekit/src/routes/+page.svelte +2 -0
  180. package/templates/sveltekit/static/favicon.png +0 -0
  181. package/templates/sveltekit/svelte.config.js +12 -0
  182. package/templates/sveltekit/tsconfig.json +27 -0
  183. package/templates/sveltekit/types/env.d.ts +16 -0
  184. package/templates/sveltekit/vite.config.ts +6 -0
  185. package/templates/sveltekit/wrangler.jsonc +7 -0
  186. package/templates/tanstack-start/.vscode/settings.json +11 -0
  187. package/templates/tanstack-start/README.md +72 -0
  188. package/templates/tanstack-start/_env +1 -0
  189. package/templates/tanstack-start/_env.example +1 -0
  190. package/templates/tanstack-start/_gitignore +22 -0
  191. package/templates/tanstack-start/_prettierignore +4 -0
  192. package/templates/tanstack-start/alchemy.run.ts +16 -0
  193. package/templates/tanstack-start/package.json +37 -0
  194. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  195. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  196. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  197. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  198. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  199. package/templates/tanstack-start/public/favicon.ico +0 -0
  200. package/templates/tanstack-start/public/favicon.png +0 -0
  201. package/templates/tanstack-start/public/site.webmanifest +19 -0
  202. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +53 -0
  203. package/templates/tanstack-start/src/components/NotFound.tsx +25 -0
  204. package/templates/tanstack-start/src/components/PostError.tsx +5 -0
  205. package/templates/tanstack-start/src/components/UserError.tsx +5 -0
  206. package/templates/tanstack-start/src/router.tsx +22 -0
  207. package/templates/tanstack-start/src/routes/__root.tsx +146 -0
  208. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +10 -0
  209. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +10 -0
  210. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
  211. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +16 -0
  212. package/templates/tanstack-start/src/routes/api/users.$userId.ts +28 -0
  213. package/templates/tanstack-start/src/routes/api/users.ts +59 -0
  214. package/templates/tanstack-start/src/routes/customScript[.]js.ts +10 -0
  215. package/templates/tanstack-start/src/routes/deferred.tsx +62 -0
  216. package/templates/tanstack-start/src/routes/index.tsx +12 -0
  217. package/templates/tanstack-start/src/routes/posts.$postId.tsx +34 -0
  218. package/templates/tanstack-start/src/routes/posts.index.tsx +8 -0
  219. package/templates/tanstack-start/src/routes/posts.tsx +38 -0
  220. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +29 -0
  221. package/templates/tanstack-start/src/routes/redirect.tsx +9 -0
  222. package/templates/tanstack-start/src/routes/users.$userId.tsx +44 -0
  223. package/templates/tanstack-start/src/routes/users.index.tsx +18 -0
  224. package/templates/tanstack-start/src/routes/users.tsx +49 -0
  225. package/templates/tanstack-start/src/styles/app.css +12 -0
  226. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +41 -0
  227. package/templates/tanstack-start/src/utils/posts.tsx +40 -0
  228. package/templates/tanstack-start/src/utils/seo.ts +33 -0
  229. package/templates/tanstack-start/src/utils/users.tsx +5 -0
  230. package/templates/tanstack-start/tsconfig.json +31 -0
  231. package/templates/tanstack-start/types/env.d.ts +16 -0
  232. package/templates/tanstack-start/vite.config.ts +33 -0
  233. package/templates/tanstack-start/wrangler.jsonc +7 -0
  234. package/templates/typescript/README.md +15 -0
  235. package/templates/typescript/_gitignore +37 -0
  236. package/templates/typescript/alchemy.run.ts +15 -0
  237. package/templates/typescript/index.ts +1 -0
  238. package/templates/typescript/package.json +18 -0
  239. package/templates/typescript/src/worker.js +5 -0
  240. package/templates/typescript/src/worker.ts +7 -0
  241. package/templates/typescript/tsconfig.json +21 -0
  242. package/templates/typescript/types/env.d.ts +16 -0
  243. package/templates/vite/README.md +54 -0
  244. package/templates/vite/_env.example +1 -0
  245. package/templates/vite/_gitignore +27 -0
  246. package/templates/vite/alchemy.run.ts +17 -0
  247. package/templates/vite/eslint.config.js +28 -0
  248. package/templates/vite/index.html +13 -0
  249. package/templates/vite/package.json +35 -0
  250. package/templates/vite/public/vite.svg +1 -0
  251. package/templates/vite/src/App.css +42 -0
  252. package/templates/vite/src/App.tsx +35 -0
  253. package/templates/vite/src/assets/react.svg +1 -0
  254. package/templates/vite/src/index.css +68 -0
  255. package/templates/vite/src/main.tsx +10 -0
  256. package/templates/vite/src/vite-env.d.ts +1 -0
  257. package/templates/vite/tsconfig.json +34 -0
  258. package/templates/vite/types/env.d.ts +16 -0
  259. package/templates/vite/vite.config.ts +9 -0
  260. package/templates/vite/worker/index.ts +12 -0
  261. package/templates/vite/wrangler.jsonc +6 -0
  262. package/bin/create-alchemy.ts +0 -1220
@@ -0,0 +1,247 @@
1
+ ---
2
+ description: RedwoodSDK: Request Interruptors
3
+ globs: worker.tsx,src/app/**/routes.ts,src/app/**/*/routes.ts
4
+ alwaysApply: false
5
+ ---
6
+
7
+
8
+ # RedwoodSDK: Request Interruptors
9
+
10
+ You're an expert at Cloudflare, TypeScript, and building web apps with RedwoodSDK. Generate high quality **RedwoodSDK interruptors** (middleware functions) that adhere to the following best practices:
11
+
12
+ ## Guidelines
13
+
14
+ 1. Create focused, single-responsibility interruptors
15
+ 2. Organize interruptors in dedicated files (e.g., `interruptors.ts`, `interceptors.ts`, or `middleware.ts`)
16
+ 3. Compose interruptors to create more complex validation chains
17
+ 4. Use typed parameters and return values
18
+ 5. Include clear error handling and user feedback
19
+
20
+ ## What are Interruptors?
21
+
22
+ Interruptors are middleware functions that run before your route handlers. They can:
23
+
24
+ - Validate user authentication and authorization
25
+ - Transform request data
26
+ - Validate inputs
27
+ - Rate limit requests
28
+ - Log activity
29
+ - Redirect users based on conditions
30
+ - Short-circuit request handling with early responses
31
+
32
+ ## Example Templates
33
+
34
+ ### Basic Interruptor Structure
35
+
36
+ ```tsx
37
+ async function myInterruptor({ request, params, ctx }) {
38
+ // Perform checks or transformations here
39
+
40
+ // Return modified context to pass to the next interruptor or handler
41
+ ctx.someAddedData = "value";
42
+
43
+ // OR return a Response to short-circuit the request
44
+ // return new Response('Unauthorized', { status: 401 });
45
+ }
46
+ ```
47
+
48
+ ### Authentication Interruptors
49
+
50
+ ```tsx
51
+ export async function requireAuth({ request, ctx }) {
52
+ if (!ctx.user) {
53
+ return new Response(null, {
54
+ status: 302,
55
+ headers: { Location: "/user/login" },
56
+ });
57
+ }
58
+ }
59
+
60
+ export async function requireAdmin({ request, ctx }) {
61
+ if (!ctx?.user?.isAdmin) {
62
+ return new Response(null, {
63
+ status: 302,
64
+ headers: { Location: "/user/login" },
65
+ });
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Input Validation Interruptor
71
+
72
+ ```tsx
73
+ import { z } from "zod";
74
+
75
+ // Create a reusable validator interruptor
76
+ export function validateInput(schema) {
77
+ return async function validateInputInterruptor({ request, ctx }) {
78
+ try {
79
+ const data = await request.json();
80
+ const validated = (ctx.data = schema.parse(data));
81
+ } catch (error) {
82
+ return Response.json(
83
+ { error: "Validation failed", details: error.errors },
84
+ { status: 400 },
85
+ );
86
+ }
87
+ };
88
+ }
89
+
90
+ // Usage example with a Zod schema
91
+ const userSchema = z.object({
92
+ name: z.string().min(2),
93
+ email: z.string().email(),
94
+ age: z.number().min(18).optional(),
95
+ });
96
+
97
+ export const validateUser = validateInput(userSchema);
98
+ ```
99
+
100
+ ### Logging Interruptor
101
+
102
+ ```tsx
103
+ export async function logRequests({ request, ctx }) {
104
+ const start = Date.now();
105
+
106
+ // Add a function to the context that will log when called
107
+ ctx.logCompletion: (response) => {
108
+ const duration = Date.now() - start;
109
+ const status = response.status;
110
+ console.log(
111
+ `${request.method} ${request.url} - ${status} (${duration}ms)`,
112
+ );
113
+ },
114
+ };
115
+ }
116
+
117
+ // Usage in a route handler
118
+ route('/', [
119
+ logRequests,
120
+ async ({request, ctx}) => {
121
+ // Call the logging function
122
+ ctx.logCompletion(response);
123
+ return Response.json({ success: true });;
124
+ },
125
+ ]);
126
+ ```
127
+
128
+ ### Composing Multiple Interruptors
129
+
130
+ ```tsx
131
+ import { route } from "rwsdk/router";
132
+ import {
133
+ requireAuth,
134
+ validateUser,
135
+ apiRateLimit,
136
+ logRequests,
137
+ } from "@/app/interruptors";
138
+
139
+ // Combine multiple interruptors
140
+ route("/api/users", [
141
+ logRequests, // Log all requests
142
+ requireAuth, // Ensure user is authenticated
143
+ validateUser, // Validate user input
144
+ async ({ request, ctx }) => {
145
+ // Handler receives validated data and session from interruptors
146
+ const newUser = await db.user.create({
147
+ data: {
148
+ /* ... */,
149
+ createdBy: ctx.user.userId,
150
+ },
151
+ });
152
+
153
+ return Response.json(newUser, { status: 201 });
154
+ },
155
+ ],
156
+ });
157
+ ```
158
+
159
+ ### Role-Based Access Control
160
+
161
+ ```tsx
162
+ import { getSession } from "rwsdk/auth";
163
+
164
+ // Create a function that generates role-based interruptors
165
+ export function hasRole(allowedRoles) {
166
+ return async function hasRoleInterruptor({ request, ctx }) {
167
+ const session = await getSession(request);
168
+
169
+ if (!session) {
170
+ return Response.redirect("/login");
171
+ }
172
+
173
+ if (!allowedRoles.includes(session.role)) {
174
+ return Response.json({ error: "Unauthorized" }, { status: 403 });
175
+ }
176
+
177
+ return { ...ctx, session };
178
+ };
179
+ }
180
+
181
+ // Create specific role-based interruptors
182
+ export const isAdmin = hasRole(["ADMIN"]);
183
+ export const isEditor = hasRole(["ADMIN", "EDITOR"]);
184
+ export const isUser = hasRole(["ADMIN", "EDITOR", "USER"]);
185
+ ```
186
+
187
+ ### Organization with Co-located Interruptors
188
+
189
+ Create a file at `./src/app/interruptors.ts`:
190
+
191
+ ```tsx
192
+ import { getSession } from "rwsdk/auth";
193
+
194
+ // Authentication interruptors
195
+ export async function requireAuth({ request, ctx }) {
196
+ const session = await getSession(request);
197
+
198
+ if (!session) {
199
+ return Response.redirect("/login");
200
+ }
201
+
202
+ return { ...ctx, session };
203
+ }
204
+
205
+ // Role-based interruptors
206
+ export function hasRole(allowedRoles) {
207
+ return async function hasRoleInterruptor({ request, ctx }) {
208
+ const session = await getSession(request);
209
+
210
+ if (!session) {
211
+ return Response.redirect("/login");
212
+ }
213
+
214
+ if (!allowedRoles.includes(session.role)) {
215
+ return Response.json({ error: "Unauthorized" }, { status: 403 });
216
+ }
217
+
218
+ return { ...ctx, session };
219
+ };
220
+ }
221
+
222
+ export const isAdmin = hasRole(["ADMIN"]);
223
+ export const isEditor = hasRole(["ADMIN", "EDITOR"]);
224
+
225
+ // Other common interruptors
226
+ export async function logRequests({ request, ctx }) {
227
+ console.log(`${request.method} ${request.url}`);
228
+ return ctx;
229
+ }
230
+ ```
231
+
232
+ Then import these interruptors in your route files:
233
+
234
+ ```tsx
235
+ // src/app/pages/admin/routes.ts
236
+ import { route } from "rwsdk/router";
237
+ import { isAdmin, logRequests } from "@/app/interruptors";
238
+
239
+ import { AdminDashboard } from "./AdminDashboard";
240
+ import { UserManagement } from "./UserManagement";
241
+
242
+ export const routes = [
243
+ route("/", [isAdmin, logRequests, AdminDashboard]),
244
+ route("/users", [isAdmin, logRequests, UserManagement]),
245
+ ];
246
+ ```
247
+
@@ -0,0 +1,75 @@
1
+ ---
2
+ description: RedwoodSDK: Middleware
3
+ globs: worker.tsx,middleware.ts,middleware.tsx
4
+ alwaysApply: false
5
+ ---
6
+
7
+
8
+ # RedwoodSDK: Middleware
9
+
10
+ You're an expert at Cloudflare, TypeScript, and building web apps with RedwoodSDK. Generate high quality **RedwoodSDK middleware** that adhere to the following best practices:
11
+
12
+ ## Guidelines
13
+
14
+ 1. Create focused, single-responsibility middleware functions
15
+ 2. Organize middleware in dedicated files (e.g., `middleware.ts`, `middleware.tsx`)
16
+ 3. Use typed parameters and return values
17
+ 4. Include clear error handling and logging
18
+ 5. Follow the principle of least privilege
19
+ 6. Implement proper security headers and CORS policies
20
+ 7. Optimize for performance with caching strategies
21
+
22
+ ## What is Middleware?
23
+
24
+ Middleware functions in RedwoodSDK are functions that run on every request before your route handlers. They can:
25
+
26
+ - Add security headers
27
+ - Handle CORS
28
+ - Implement caching strategies
29
+ - Add request/response logging
30
+ - Transform request/response data
31
+ - Implement rate limiting
32
+ - Add performance monitoring
33
+ - Handle error boundaries
34
+ - Setup sessions
35
+ - Authenticate users
36
+
37
+ ## Example Templates
38
+
39
+ ### Basic Middleware Structure
40
+
41
+ ```tsx
42
+ export default defineApp([
43
+ setCommonHeaders(),
44
+ async ({ ctx, request, headers }) => {
45
+ await setupDb(env);
46
+ setupSessionStore(env);
47
+ try {
48
+ // Grab the session's data.
49
+ ctx.session = await sessions.load(request);
50
+ } catch (error) {
51
+ if (error instanceof ErrorResponse && error.code === 401) {
52
+ await sessions.remove(request, headers);
53
+ headers.set("Location", "/user/login");
54
+
55
+ return new Response(null, {
56
+ status: 302,
57
+ headers,
58
+ });
59
+ }
60
+
61
+ throw error;
62
+ }
63
+
64
+ // Populate the ctx with the user's data
65
+ if (ctx.session?.userId) {
66
+ ctx.user = await db.user.findUnique({
67
+ where: {
68
+ id: ctx.session.userId,
69
+ },
70
+ });
71
+ }
72
+ },
73
+ // Route handlers
74
+ ]);
75
+ ```
@@ -0,0 +1,110 @@
1
+ ---
2
+ description: RedwoodSDK: React, React Server Components, and React Server Functions Rules
3
+ globs: src/app/**/*/*.tsx,Document.tsx
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # React, React Server Components, and React Server Functions Rules
8
+
9
+ ## React Server Components (RSC)
10
+
11
+ 1. By default, all components are server components unless explicitly marked as client components.
12
+ 2. Server components are rendered on the server as HTML and streamed to the browser.
13
+ 3. Server components cannot include client-side interactivity (state, effects, event handlers).
14
+ 4. Server components can directly fetch data and include it in the initial payload.
15
+ 5. Server components can be async and can be wrapped in Suspense boundaries.
16
+
17
+ Example:
18
+
19
+ ```tsx
20
+ export default function MyServerComponent() {
21
+ return <div>Hello, from the server!</div>;
22
+ }
23
+ ```
24
+
25
+ ## Client Components
26
+
27
+ 1. Must be explicitly marked with the "use client" directive at the top of the file.
28
+ 2. Required when the component needs:
29
+ - Interactivity (click handlers, state management)
30
+ - Browser APIs
31
+ - Event listeners
32
+ - Client-side effects
33
+ - Client-side routing
34
+ 3. Will be hydrated by React in the browser.
35
+
36
+ Example:
37
+
38
+ ```tsx
39
+ "use client";
40
+
41
+ export default function MyClientComponent() {
42
+ return <button onClick={() => console.log("clicked")}>Click me</button>;
43
+ }
44
+ ```
45
+
46
+ ## Data Fetching in Server Components
47
+
48
+ 1. Server components can directly fetch data without useEffect or other client-side data fetching methods.
49
+ 2. Use Suspense boundaries to handle loading states for async server components.
50
+ 3. Pass context (ctx) through props to child components that need it.
51
+
52
+ Example:
53
+
54
+ ```tsx
55
+ export async function TodoList({ ctx }) {
56
+ const todos = await db.todo.findMany({ where: { userId: ctx.user.id } });
57
+
58
+ return (
59
+ <ol>
60
+ {todos.map((todo) => (
61
+ <li key={todo.id}>{todo.title}</li>
62
+ ))}
63
+ </ol>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Server Functions
69
+
70
+ 1. Must be marked with the "use server" directive at the top of the file.
71
+ 2. Can be imported and used in client components.
72
+ 3. Execute on the server when called from client components.
73
+ 4. Have access to the request context via requestInfo.ctx.
74
+ 5. Can handle form submissions and other server-side operations.
75
+
76
+ Example:
77
+
78
+ ```tsx
79
+ "use server";
80
+
81
+ import { requestInfo } from "rwsdk/worker";
82
+
83
+ export async function addTodo(formData: FormData) {
84
+ const { ctx } = requestInfo;
85
+ const title = formData.get("title");
86
+ await db.todo.create({ data: { title, userId: ctx.user.id } });
87
+ }
88
+ ```
89
+
90
+ ## Context Usage
91
+
92
+ 1. Context is available to all server components and server functions.
93
+ 2. Access context via:
94
+ - requestInfo in server functions:
95
+ ```
96
+ import { requestInfo } from "rwsdk/worker";
97
+ const { ctx } = requestInfo
98
+ ```
99
+ 3. Context is populated by middleware and interruptors and is request-scoped.
100
+
101
+ ## Best Practices
102
+
103
+ 1. Keep server components as the default choice unless client-side interactivity is needed.
104
+ 2. Use client components only when necessary to minimize the JavaScript bundle size.
105
+ 3. Leverage server components for data fetching and initial rendering.
106
+ 4. Use Suspense boundaries appropriately for loading states.
107
+ 5. Keep client components as small as possible, moving server-side logic to server components or server functions.
108
+ 6. Always mark client components with "use client" directive.
109
+ 7. Always mark server functions with "use server" directive.
110
+
@@ -0,0 +1,213 @@
1
+ ---
2
+ description: RedwoodSDK: Request handling and responses
3
+ globs: worker.tsc,src/app/**/routes.ts,src/app/**/*/routes.ts
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # RedwoodSDK: Request handling and responses
8
+
9
+ You're an expert at Cloudflare, TypeScript, and building web apps in React. Generate high quality **RedwoodSDK route handlers** that adhere to the following best practices:
10
+
11
+ ## Guidelines
12
+
13
+ 1. Try to use Web APIs instead of external dependencies (e.g. use fetch instead of Axios, use WebSockets API instead of node-ws)
14
+ 2. Co-locate related routes into a separate `routes.ts` file in `./src/app/pages/<section>` (e.g. keep all "user" routes in `./src/app/pages/user/routes.ts`, all "blog" routes in `./src/app/pages/blog/routes.ts`), and then import them into `defineApp` with the `prefix` function
15
+ 4. Structure response data consistently with proper status codes
16
+ 5. Handle errors gracefully and return appropriate error responses
17
+
18
+ ## Example Templates
19
+
20
+ ### Basic Routing
21
+
22
+ Routes are matched in the order they are defined. Define routes using the `route` function. Trailing slashes are optional and normalized internally.
23
+
24
+ #### Static Path Matching
25
+
26
+ ```tsx
27
+ // Match exact pathnames
28
+ route("/", function handler() {
29
+ return <>Home Page</>
30
+ })
31
+
32
+ route("/about", function handler() {
33
+ return <>About Page</>
34
+ })
35
+
36
+ route("/contact", function handler() {
37
+ return <>Contact Page</>
38
+ })
39
+ ```
40
+
41
+ #### Dynamic Path Parameters
42
+
43
+ ```tsx
44
+ // Match dynamic segments marked with a colon (:)
45
+ route("/users/:id", function handler({ params }) {
46
+ // params.id contains the value from the URL
47
+ return <>User profile for {params.id}</>
48
+ })
49
+
50
+ route("/posts/:postId/comments/:commentId", function handler({ params }) {
51
+ // Access multiple parameters
52
+ return <>Comment {params.commentId} on Post {params.postId}</>
53
+ })
54
+ ```
55
+
56
+ #### Wildcard Path Matching
57
+
58
+ ```tsx
59
+ // Match all remaining segments after the prefix
60
+ route("/files/*", function handler({ params }) {
61
+ // params.$0 contains the wildcard value
62
+ return <>File: {params.$0}</>
63
+ })
64
+
65
+ route("/docs/*/version/*", function handler({ params }) {
66
+ // Multiple wildcards available as params.$0, params.$1, etc.
67
+ return <>Document: {params.$0}, Version: {params.$1}</>
68
+ })
69
+ ```
70
+
71
+ ### Response Types
72
+
73
+ #### Plain Text Response
74
+
75
+ ```tsx
76
+ import { route } from "rwsdk/router";
77
+
78
+ route("/api/status", function handler() {
79
+ return new Response("OK", {
80
+ status: 200,
81
+ headers: { "Content-Type": "text/plain" }
82
+ })
83
+ })
84
+ ```
85
+
86
+ #### JSON Response
87
+
88
+ ```tsx
89
+ import { route } from "rwsdk/router";
90
+
91
+ route("/api/users/:id", function handler({ params }) {
92
+ const userData = { id: params.id, name: "John Doe", email: "john@example.com" }
93
+
94
+ return Response.json(userData, {
95
+ status: 200,
96
+ headers: {
97
+ "Cache-Control": "max-age=60"
98
+ }
99
+ })
100
+ })
101
+ ```
102
+
103
+ #### JSX/React Components Response
104
+
105
+ ```tsx
106
+ import { route } from "rwsdk/router";
107
+ import { UserProfile } from '@/app/components/UserProfile'
108
+
109
+ route("/users/:id", function handler({ params }) {
110
+ return <UserProfile userId={params.id} />
111
+ })
112
+ ```
113
+
114
+ #### Custom Document Template
115
+
116
+ ```tsx
117
+ import { render, route } from "rwsdk/router";
118
+ import { Document } from '@/app/Document'
119
+
120
+ render(Document, [
121
+ route("/", function handler() {
122
+ return <>Home Page</>
123
+ }),
124
+ route("/about", function handler() {
125
+ return <>About Page</>
126
+ })
127
+ ])
128
+ ```
129
+
130
+ ### Error Handling
131
+
132
+ ```tsx
133
+ import { route } from "rwsdk/router";
134
+
135
+ route("/api/posts/:id", async function handler({ params }) {
136
+ try {
137
+ const post = await db.post.findUnique({ where: { id: params.id } })
138
+
139
+ if (!post) {
140
+ return Response.json(
141
+ { error: "Post not found" },
142
+ { status: 404 }
143
+ )
144
+ }
145
+
146
+ return Response.json(post)
147
+ } catch (error) {
148
+ console.error(error)
149
+ return Response.json(
150
+ { error: "Failed to retrieve post" },
151
+ { status: 500 }
152
+ )
153
+ }
154
+ })
155
+ ```
156
+
157
+ ### Organization with Co-located Routes
158
+
159
+ Create a file at `./src/app/pages/blog/routes.ts`:
160
+
161
+ ```tsx
162
+ import { route } from "rwsdk/router";
163
+ import { isAdminUser } from '@/app/interceptors'
164
+
165
+ import { BlogLandingPage } from './BlogLandingPage'
166
+ import { BlogPostPage } from './BlogPostPage'
167
+ import { BlogAdminPage } from './BlogAdminPage'
168
+
169
+ export const routes = [
170
+ route('/', BlogLandingPage),
171
+ route('/post/:postId', BlogPostPage),
172
+ route('/post/:postId/edit', [isAdminUser, BlogAdminPage])
173
+ ]
174
+ ```
175
+
176
+ Then import these routes in your main worker file:
177
+
178
+ ```tsx
179
+ // src/worker.tsx
180
+ import { defineApp, render, route, prefix } from "rwsdk/router";
181
+ import { Document } from '@/app/Document'
182
+ import { HomePage } from '@/app/pages/home/HomePage'
183
+ import { routes as blogRoutes } from '@/app/pages/blog/routes'
184
+
185
+ export default defineApp([
186
+ /* middleware */
187
+ render(Document, [
188
+ route('/', HomePage),
189
+ prefix('/blog', blogRoutes)
190
+ ]),
191
+ ])
192
+ ```
193
+
194
+ ### Advanced: Route with Query Parameters
195
+
196
+ ```tsx
197
+ import { route } from "rwsdk/router";
198
+
199
+ route("/api/search", function handler({ request }) {
200
+ const url = new URL(request.url)
201
+ const query = url.searchParams.get('q') || ''
202
+ const page = parseInt(url.searchParams.get('page') || '1')
203
+ const limit = parseInt(url.searchParams.get('limit') || '10')
204
+
205
+ return Response.json({
206
+ query,
207
+ page,
208
+ limit,
209
+ results: [] // Your search results would go here
210
+ })
211
+ })
212
+ ```
213
+
@@ -0,0 +1,8 @@
1
+ FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
2
+
3
+ EXPOSE 5713
4
+
5
+ RUN corepack enable
6
+ COPY . /app
7
+ WORKDIR /app
8
+
@@ -0,0 +1,21 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
3
+ {
4
+ "name": "RedwoodSDK Standard Starter",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "build": {
7
+ "dockerfile": "./Dockerfile"
8
+ },
9
+
10
+ // Features to add to the dev container. More info: https://containers.dev/features.
11
+ // "features": {},
12
+
13
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
14
+ // Might need to set our ports for 80.
15
+ "forwardPorts": [5713, 5713]
16
+ // Configure tool-specific properties.
17
+ // "customizations": {},
18
+
19
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
20
+ // "remoteUser": "root"
21
+ }