alchemy 0.37.2 → 0.38.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/bin/alchemy.mjs +44306 -48055
- package/bin/alchemy.ts +80 -85
- package/bin/commands/create.ts +263 -0
- package/bin/constants.ts +13 -0
- package/bin/errors.ts +11 -0
- package/bin/services/dependencies.ts +47 -0
- package/bin/services/get-package-version.ts +11 -0
- package/bin/services/package-manager.ts +113 -0
- package/bin/services/template-manager.ts +137 -0
- package/bin/types.ts +83 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +12 -3
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts +2 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +47 -12
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +6 -0
- package/lib/build-date.d.ts.map +1 -0
- package/lib/build-date.js +8 -0
- package/lib/build-date.js.map +1 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -1
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/pipeline.d.ts +1 -1
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +6 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/worker-stub.d.ts +23 -2
- package/lib/cloudflare/worker-stub.d.ts.map +1 -1
- package/lib/cloudflare/worker-stub.js +16 -2
- package/lib/cloudflare/worker-stub.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +4 -3
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +2 -1
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/context.d.ts +2 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/destroy.d.ts +8 -2
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +19 -5
- package/lib/destroy.js.map +1 -1
- package/lib/scope.d.ts +26 -4
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +104 -9
- package/lib/scope.js.map +1 -1
- package/lib/sentry/project.js +1 -1
- package/lib/sentry/project.js.map +1 -1
- package/lib/util/logger.js +1 -1
- package/lib/util/logger.js.map +1 -1
- package/lib/util/mutex.d.ts +9 -0
- package/lib/util/mutex.d.ts.map +1 -0
- package/lib/util/mutex.js +35 -0
- package/lib/util/mutex.js.map +1 -0
- package/package.json +16 -6
- package/src/alchemy.ts +17 -5
- package/src/apply.ts +67 -17
- package/src/build-date.ts +8 -0
- package/src/cloudflare/api.ts +2 -1
- package/src/cloudflare/pipeline.ts +6 -0
- package/src/cloudflare/worker-stub.ts +31 -2
- package/src/cloudflare/worker.ts +4 -3
- package/src/context.ts +2 -2
- package/src/destroy.ts +30 -7
- package/src/scope.ts +145 -10
- package/src/sentry/project.ts +1 -1
- package/src/util/logger.ts +1 -1
- package/src/util/mutex.ts +33 -0
- package/templates/astro/.vscode/extensions.json +4 -0
- package/templates/astro/.vscode/launch.json +11 -0
- package/templates/astro/README.md +48 -0
- package/templates/astro/_env.example +1 -0
- package/templates/astro/_gitignore +27 -0
- package/templates/astro/alchemy.run.ts +16 -0
- package/templates/astro/astro.config.mjs +8 -0
- package/templates/astro/package.json +23 -0
- package/templates/astro/public/favicon.svg +9 -0
- package/templates/astro/src/assets/astro.svg +1 -0
- package/templates/astro/src/assets/background.svg +1 -0
- package/templates/astro/src/components/Welcome.astro +210 -0
- package/templates/astro/src/layouts/Layout.astro +22 -0
- package/templates/astro/src/pages/api/hello.ts +19 -0
- package/templates/astro/src/pages/index.astro +11 -0
- package/templates/astro/tsconfig.json +18 -0
- package/templates/astro/types/env.d.ts +16 -0
- package/templates/astro/wrangler.jsonc +6 -0
- package/templates/nuxt/README.md +75 -0
- package/templates/nuxt/_env +1 -0
- package/templates/nuxt/_env.example +1 -0
- package/templates/nuxt/_gitignore +27 -0
- package/templates/nuxt/alchemy.run.ts +16 -0
- package/templates/nuxt/app.vue +6 -0
- package/templates/nuxt/nuxt.config.ts +13 -0
- package/templates/nuxt/package.json +26 -0
- package/templates/nuxt/public/favicon.ico +0 -0
- package/templates/nuxt/public/robots.txt +2 -0
- package/templates/nuxt/server/api/hello.ts +7 -0
- package/templates/nuxt/server/middleware/auth.ts +5 -0
- package/templates/nuxt/server/middleware/hello.ts +5 -0
- package/templates/nuxt/server/tsconfig.json +3 -0
- package/templates/nuxt/tsconfig.json +15 -0
- package/templates/nuxt/types/env.d.ts +16 -0
- package/templates/react-router/.vscode/settings.json +5 -0
- package/templates/react-router/README.md +79 -0
- package/templates/react-router/_env +1 -0
- package/templates/react-router/_env.example +1 -0
- package/templates/react-router/_gitignore +15 -0
- package/templates/react-router/alchemy.run.ts +17 -0
- package/templates/react-router/app/app.css +15 -0
- package/templates/react-router/app/entry.server.tsx +43 -0
- package/templates/react-router/app/root.tsx +75 -0
- package/templates/react-router/app/routes/home.tsx +17 -0
- package/templates/react-router/app/routes.ts +3 -0
- package/templates/react-router/app/welcome/logo-dark.svg +23 -0
- package/templates/react-router/app/welcome/logo-light.svg +23 -0
- package/templates/react-router/app/welcome/welcome.tsx +90 -0
- package/templates/react-router/package.json +34 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +8 -0
- package/templates/react-router/tsconfig.cloudflare.json +27 -0
- package/templates/react-router/tsconfig.json +17 -0
- package/templates/react-router/tsconfig.node.json +20 -0
- package/templates/react-router/types/env.d.ts +16 -0
- package/templates/react-router/vite.config.ts +14 -0
- package/templates/react-router/workers/app.ts +23 -0
- package/templates/react-router/wrangler.jsonc +6 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-interruptors.mdc +247 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-middleware.mdc +75 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-react.mdc +110 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-request-response.mdc +213 -0
- package/templates/rwsdk/.devcontainer/Dockerfile +8 -0
- package/templates/rwsdk/.devcontainer/devcontainer.json +21 -0
- package/templates/rwsdk/README.md +62 -0
- package/templates/rwsdk/_env +5 -0
- package/templates/rwsdk/_env.example +5 -0
- package/templates/rwsdk/_gitignore +71 -0
- package/templates/rwsdk/alchemy.run.ts +29 -0
- package/templates/rwsdk/package.json +61 -0
- package/templates/rwsdk/prisma/schema.prisma +43 -0
- package/templates/rwsdk/src/app/Document.tsx +16 -0
- package/templates/rwsdk/src/app/headers.ts +32 -0
- package/templates/rwsdk/src/app/pages/Home.tsx +13 -0
- package/templates/rwsdk/src/app/pages/user/Login.tsx +79 -0
- package/templates/rwsdk/src/app/pages/user/functions.ts +171 -0
- package/templates/rwsdk/src/app/pages/user/routes.ts +17 -0
- package/templates/rwsdk/src/app/shared/links.ts +3 -0
- package/templates/rwsdk/src/client.tsx +3 -0
- package/templates/rwsdk/src/db.ts +26 -0
- package/templates/rwsdk/src/scripts/seed.ts +21 -0
- package/templates/rwsdk/src/session/durableObject.ts +63 -0
- package/templates/rwsdk/src/session/store.ts +13 -0
- package/templates/rwsdk/src/worker.tsx +63 -0
- package/templates/rwsdk/tsconfig.json +47 -0
- package/templates/rwsdk/types/env.d.ts +16 -0
- package/templates/rwsdk/types/rw.d.ts +5 -0
- package/templates/rwsdk/types/vite.d.ts +4 -0
- package/templates/rwsdk/vite.config.mts +6 -0
- package/templates/rwsdk/wrangler.jsonc +28 -0
- package/templates/sveltekit/README.md +38 -0
- package/templates/sveltekit/_env +1 -0
- package/templates/sveltekit/_env.example +1 -0
- package/templates/sveltekit/_gitignore +26 -0
- package/templates/sveltekit/_npmrc +1 -0
- package/templates/sveltekit/_prettierrc +3 -0
- package/templates/sveltekit/alchemy.run.ts +16 -0
- package/templates/sveltekit/bun.lock +1099 -0
- package/templates/sveltekit/package.json +33 -0
- package/templates/sveltekit/src/app.css +1 -0
- package/templates/sveltekit/src/app.d.ts +13 -0
- package/templates/sveltekit/src/app.html +12 -0
- package/templates/sveltekit/src/routes/+layout.svelte +7 -0
- package/templates/sveltekit/src/routes/+page.svelte +2 -0
- package/templates/sveltekit/static/favicon.png +0 -0
- package/templates/sveltekit/svelte.config.js +12 -0
- package/templates/sveltekit/tsconfig.json +27 -0
- package/templates/sveltekit/types/env.d.ts +16 -0
- package/templates/sveltekit/vite.config.ts +6 -0
- package/templates/sveltekit/wrangler.jsonc +7 -0
- package/templates/tanstack-start/.vscode/settings.json +11 -0
- package/templates/tanstack-start/README.md +72 -0
- package/templates/tanstack-start/_env +1 -0
- package/templates/tanstack-start/_env.example +1 -0
- package/templates/tanstack-start/_gitignore +22 -0
- package/templates/tanstack-start/_prettierignore +4 -0
- package/templates/tanstack-start/alchemy.run.ts +16 -0
- package/templates/tanstack-start/package.json +37 -0
- package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
- package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
- package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
- package/templates/tanstack-start/public/favicon-16x16.png +0 -0
- package/templates/tanstack-start/public/favicon-32x32.png +0 -0
- package/templates/tanstack-start/public/favicon.ico +0 -0
- package/templates/tanstack-start/public/favicon.png +0 -0
- package/templates/tanstack-start/public/site.webmanifest +19 -0
- package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +53 -0
- package/templates/tanstack-start/src/components/NotFound.tsx +25 -0
- package/templates/tanstack-start/src/components/PostError.tsx +5 -0
- package/templates/tanstack-start/src/components/UserError.tsx +5 -0
- package/templates/tanstack-start/src/router.tsx +22 -0
- package/templates/tanstack-start/src/routes/__root.tsx +146 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +16 -0
- package/templates/tanstack-start/src/routes/api/users.$userId.ts +28 -0
- package/templates/tanstack-start/src/routes/api/users.ts +59 -0
- package/templates/tanstack-start/src/routes/customScript[.]js.ts +10 -0
- package/templates/tanstack-start/src/routes/deferred.tsx +62 -0
- package/templates/tanstack-start/src/routes/index.tsx +12 -0
- package/templates/tanstack-start/src/routes/posts.$postId.tsx +34 -0
- package/templates/tanstack-start/src/routes/posts.index.tsx +8 -0
- package/templates/tanstack-start/src/routes/posts.tsx +38 -0
- package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +29 -0
- package/templates/tanstack-start/src/routes/redirect.tsx +9 -0
- package/templates/tanstack-start/src/routes/users.$userId.tsx +44 -0
- package/templates/tanstack-start/src/routes/users.index.tsx +18 -0
- package/templates/tanstack-start/src/routes/users.tsx +49 -0
- package/templates/tanstack-start/src/styles/app.css +12 -0
- package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +41 -0
- package/templates/tanstack-start/src/utils/posts.tsx +40 -0
- package/templates/tanstack-start/src/utils/seo.ts +33 -0
- package/templates/tanstack-start/src/utils/users.tsx +5 -0
- package/templates/tanstack-start/tsconfig.json +31 -0
- package/templates/tanstack-start/types/env.d.ts +16 -0
- package/templates/tanstack-start/vite.config.ts +33 -0
- package/templates/tanstack-start/wrangler.jsonc +7 -0
- package/templates/typescript/README.md +15 -0
- package/templates/typescript/_gitignore +37 -0
- package/templates/typescript/alchemy.run.ts +15 -0
- package/templates/typescript/index.ts +1 -0
- package/templates/typescript/package.json +18 -0
- package/templates/typescript/src/worker.js +5 -0
- package/templates/typescript/src/worker.ts +7 -0
- package/templates/typescript/tsconfig.json +21 -0
- package/templates/typescript/types/env.d.ts +16 -0
- package/templates/vite/README.md +54 -0
- package/templates/vite/_env.example +1 -0
- package/templates/vite/_gitignore +27 -0
- package/templates/vite/alchemy.run.ts +17 -0
- package/templates/vite/eslint.config.js +28 -0
- package/templates/vite/index.html +13 -0
- package/templates/vite/package.json +35 -0
- package/templates/vite/public/vite.svg +1 -0
- package/templates/vite/src/App.css +42 -0
- package/templates/vite/src/App.tsx +35 -0
- package/templates/vite/src/assets/react.svg +1 -0
- package/templates/vite/src/index.css +68 -0
- package/templates/vite/src/main.tsx +10 -0
- package/templates/vite/src/vite-env.d.ts +1 -0
- package/templates/vite/tsconfig.json +34 -0
- package/templates/vite/types/env.d.ts +16 -0
- package/templates/vite/vite.config.ts +9 -0
- package/templates/vite/worker/index.ts +12 -0
- package/templates/vite/wrangler.jsonc +6 -0
- package/bin/create-alchemy.ts +0 -1220
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({
|
|
4
|
+
component: LayoutComponent,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function LayoutComponent() {
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<div>I'm a nested layout</div>
|
|
11
|
+
<div className="flex gap-2 border-b">
|
|
12
|
+
<Link
|
|
13
|
+
to="/route-a"
|
|
14
|
+
activeProps={{
|
|
15
|
+
className: 'font-bold',
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
Go to route A
|
|
19
|
+
</Link>
|
|
20
|
+
<Link
|
|
21
|
+
to="/route-b"
|
|
22
|
+
activeProps={{
|
|
23
|
+
className: 'font-bold',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
Go to route B
|
|
27
|
+
</Link>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<Outlet />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/_pathlessLayout')({
|
|
4
|
+
component: LayoutComponent,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function LayoutComponent() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="p-2">
|
|
10
|
+
<div className="border-b">I'm a layout</div>
|
|
11
|
+
<div>
|
|
12
|
+
<Outlet />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createServerFileRoute } from '@tanstack/react-start/server'
|
|
2
|
+
import { json } from '@tanstack/react-start'
|
|
3
|
+
import type { User } from '~/utils/users'
|
|
4
|
+
|
|
5
|
+
export const ServerRoute = createServerFileRoute('/api/users/$userId').methods({
|
|
6
|
+
GET: async ({ params, request }) => {
|
|
7
|
+
console.info(`Fetching users by id=${params.userId}... @`, request.url)
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(
|
|
10
|
+
'https://jsonplaceholder.typicode.com/users/' + params.userId,
|
|
11
|
+
)
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
throw new Error('Failed to fetch user')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const user = (await res.json()) as User
|
|
17
|
+
|
|
18
|
+
return json({
|
|
19
|
+
id: user.id,
|
|
20
|
+
name: user.name,
|
|
21
|
+
email: user.email,
|
|
22
|
+
})
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(e)
|
|
25
|
+
return json({ error: 'User not found' }, { status: 404 })
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createServerFileRoute } from '@tanstack/react-start/server'
|
|
2
|
+
import { createMiddleware, json } from '@tanstack/react-start'
|
|
3
|
+
import type { User } from '~/utils/users'
|
|
4
|
+
|
|
5
|
+
const userLoggerMiddleware = createMiddleware({ type: 'request' }).server(
|
|
6
|
+
async ({ next, request }) => {
|
|
7
|
+
console.info('In: /users')
|
|
8
|
+
const result = await next()
|
|
9
|
+
result.response.headers.set('x-users', 'true')
|
|
10
|
+
console.info('Out: /users')
|
|
11
|
+
return result
|
|
12
|
+
},
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const testParentMiddleware = createMiddleware({ type: 'request' }).server(
|
|
16
|
+
async ({ next, request }) => {
|
|
17
|
+
console.info('In: testParentMiddleware')
|
|
18
|
+
const result = await next()
|
|
19
|
+
result.response.headers.set('x-test-parent', 'true')
|
|
20
|
+
console.info('Out: testParentMiddleware')
|
|
21
|
+
return result
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const testMiddleware = createMiddleware({ type: 'request' })
|
|
26
|
+
.middleware([testParentMiddleware])
|
|
27
|
+
.server(async ({ next, request }) => {
|
|
28
|
+
console.info('In: testMiddleware')
|
|
29
|
+
const result = await next()
|
|
30
|
+
result.response.headers.set('x-test', 'true')
|
|
31
|
+
|
|
32
|
+
// if (Math.random() > 0.5) {
|
|
33
|
+
// throw new Response(null, {
|
|
34
|
+
// status: 302,
|
|
35
|
+
// headers: { Location: 'https://www.google.com' },
|
|
36
|
+
// })
|
|
37
|
+
// }
|
|
38
|
+
|
|
39
|
+
console.info('Out: testMiddleware')
|
|
40
|
+
return result
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const ServerRoute = createServerFileRoute('/api/users')
|
|
44
|
+
.middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware])
|
|
45
|
+
.methods({
|
|
46
|
+
GET: async ({ request }) => {
|
|
47
|
+
console.info('Fetching users... @', request.url)
|
|
48
|
+
const res = await fetch('https://jsonplaceholder.typicode.com/users')
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new Error('Failed to fetch users')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = (await res.json()) as Array<User>
|
|
54
|
+
|
|
55
|
+
const list = data.slice(0, 10)
|
|
56
|
+
|
|
57
|
+
return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
|
|
58
|
+
},
|
|
59
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createServerFileRoute } from '@tanstack/react-start/server'
|
|
2
|
+
export const ServerRoute = createServerFileRoute('/customScript.js').methods({
|
|
3
|
+
GET: async ({ request }) => {
|
|
4
|
+
return new Response('console.log("Hello from customScript.js!")', {
|
|
5
|
+
headers: {
|
|
6
|
+
'Content-Type': 'application/javascript',
|
|
7
|
+
},
|
|
8
|
+
})
|
|
9
|
+
},
|
|
10
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Await, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
+
import { Suspense, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
const personServerFn = createServerFn({ method: 'GET' })
|
|
6
|
+
.validator((d: string) => d)
|
|
7
|
+
.handler(({ data: name }) => {
|
|
8
|
+
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const slowServerFn = createServerFn({ method: 'GET' })
|
|
12
|
+
.validator((d: string) => d)
|
|
13
|
+
.handler(async ({ data: name }) => {
|
|
14
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
15
|
+
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const Route = createFileRoute('/deferred')({
|
|
19
|
+
loader: async () => {
|
|
20
|
+
return {
|
|
21
|
+
deferredStuff: new Promise<string>((r) =>
|
|
22
|
+
setTimeout(() => r('Hello deferred!'), 2000),
|
|
23
|
+
),
|
|
24
|
+
deferredPerson: slowServerFn({ data: 'Tanner Linsley' }),
|
|
25
|
+
person: await personServerFn({ data: 'John Doe' }),
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
component: Deferred,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function Deferred() {
|
|
32
|
+
const [count, setCount] = useState(0)
|
|
33
|
+
const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="p-2">
|
|
37
|
+
<div data-testid="regular-person">
|
|
38
|
+
{person.name} - {person.randomNumber}
|
|
39
|
+
</div>
|
|
40
|
+
<Suspense fallback={<div>Loading person...</div>}>
|
|
41
|
+
<Await
|
|
42
|
+
promise={deferredPerson}
|
|
43
|
+
children={(data) => (
|
|
44
|
+
<div data-testid="deferred-person">
|
|
45
|
+
{data.name} - {data.randomNumber}
|
|
46
|
+
</div>
|
|
47
|
+
)}
|
|
48
|
+
/>
|
|
49
|
+
</Suspense>
|
|
50
|
+
<Suspense fallback={<div>Loading stuff...</div>}>
|
|
51
|
+
<Await
|
|
52
|
+
promise={deferredStuff}
|
|
53
|
+
children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
|
|
54
|
+
/>
|
|
55
|
+
</Suspense>
|
|
56
|
+
<div>Count: {count}</div>
|
|
57
|
+
<div>
|
|
58
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPost } from '../utils/posts'
|
|
3
|
+
import { NotFound } from '~/components/NotFound'
|
|
4
|
+
import { PostErrorComponent } from '~/components/PostError'
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
7
|
+
loader: ({ params: { postId } }) => fetchPost({ data: postId }),
|
|
8
|
+
errorComponent: PostErrorComponent,
|
|
9
|
+
component: PostComponent,
|
|
10
|
+
notFoundComponent: () => {
|
|
11
|
+
return <NotFound>Post not found</NotFound>
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function PostComponent() {
|
|
16
|
+
const post = Route.useLoaderData()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="space-y-2">
|
|
20
|
+
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
|
21
|
+
<div className="text-sm">{post.body}</div>
|
|
22
|
+
<Link
|
|
23
|
+
to="/posts/$postId/deep"
|
|
24
|
+
params={{
|
|
25
|
+
postId: post.id,
|
|
26
|
+
}}
|
|
27
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
28
|
+
className="inline-block py-1 text-blue-800 hover:text-blue-600"
|
|
29
|
+
>
|
|
30
|
+
Deep View
|
|
31
|
+
</Link>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPosts } from '../utils/posts'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/posts')({
|
|
5
|
+
loader: async () => fetchPosts(),
|
|
6
|
+
component: PostsComponent,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function PostsComponent() {
|
|
10
|
+
const posts = Route.useLoaderData()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-2 flex gap-2">
|
|
14
|
+
<ul className="list-disc pl-4">
|
|
15
|
+
{[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
|
|
16
|
+
(post) => {
|
|
17
|
+
return (
|
|
18
|
+
<li key={post.id} className="whitespace-nowrap">
|
|
19
|
+
<Link
|
|
20
|
+
to="/posts/$postId"
|
|
21
|
+
params={{
|
|
22
|
+
postId: post.id,
|
|
23
|
+
}}
|
|
24
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
25
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
26
|
+
>
|
|
27
|
+
<div>{post.title.substring(0, 20)}</div>
|
|
28
|
+
</Link>
|
|
29
|
+
</li>
|
|
30
|
+
)
|
|
31
|
+
},
|
|
32
|
+
)}
|
|
33
|
+
</ul>
|
|
34
|
+
<hr />
|
|
35
|
+
<Outlet />
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPost } from '../utils/posts'
|
|
3
|
+
import { PostErrorComponent } from '~/components/PostError'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/posts_/$postId/deep')({
|
|
6
|
+
loader: async ({ params: { postId } }) =>
|
|
7
|
+
fetchPost({
|
|
8
|
+
data: postId,
|
|
9
|
+
}),
|
|
10
|
+
errorComponent: PostErrorComponent,
|
|
11
|
+
component: PostDeepComponent,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
function PostDeepComponent() {
|
|
15
|
+
const post = Route.useLoaderData()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="p-2 space-y-2">
|
|
19
|
+
<Link
|
|
20
|
+
to="/posts"
|
|
21
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
22
|
+
>
|
|
23
|
+
← All Posts
|
|
24
|
+
</Link>
|
|
25
|
+
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
|
26
|
+
<div className="text-sm">{post.body}</div>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { NotFound } from 'src/components/NotFound'
|
|
3
|
+
import { UserErrorComponent } from 'src/components/UserError'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/users/$userId')({
|
|
6
|
+
loader: async ({ params: { userId } }) => {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch('/api/users/' + userId)
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
throw new Error('Unexpected status code')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const data = await res.json()
|
|
14
|
+
|
|
15
|
+
return data
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error('Failed to fetch user')
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
errorComponent: UserErrorComponent,
|
|
21
|
+
component: UserComponent,
|
|
22
|
+
notFoundComponent: () => {
|
|
23
|
+
return <NotFound>User not found</NotFound>
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
function UserComponent() {
|
|
28
|
+
const user = Route.useLoaderData()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="space-y-2">
|
|
32
|
+
<h4 className="text-xl font-bold underline">{user.name}</h4>
|
|
33
|
+
<div className="text-sm">{user.email}</div>
|
|
34
|
+
<div>
|
|
35
|
+
<a
|
|
36
|
+
href={`/api/users/${user.id}`}
|
|
37
|
+
className="text-blue-800 hover:text-blue-600 underline"
|
|
38
|
+
>
|
|
39
|
+
View as JSON
|
|
40
|
+
</a>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
export const Route = createFileRoute('/users/')({
|
|
3
|
+
component: UsersIndexComponent,
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
function UsersIndexComponent() {
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
Select a user or{' '}
|
|
10
|
+
<a
|
|
11
|
+
href="/api/users"
|
|
12
|
+
className="text-blue-800 hover:text-blue-600 underline"
|
|
13
|
+
>
|
|
14
|
+
view as JSON
|
|
15
|
+
</a>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import type { User } from '../utils/users'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/users')({
|
|
5
|
+
loader: async () => {
|
|
6
|
+
const res = await fetch('/api/users')
|
|
7
|
+
|
|
8
|
+
if (!res.ok) {
|
|
9
|
+
throw new Error('Unexpected status code')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const data = (await res.json()) as Array<User>
|
|
13
|
+
|
|
14
|
+
return data
|
|
15
|
+
},
|
|
16
|
+
component: UsersComponent,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function UsersComponent() {
|
|
20
|
+
const users = Route.useLoaderData()
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="p-2 flex gap-2">
|
|
24
|
+
<ul className="list-disc pl-4">
|
|
25
|
+
{[
|
|
26
|
+
...users,
|
|
27
|
+
{ id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
|
|
28
|
+
].map((user) => {
|
|
29
|
+
return (
|
|
30
|
+
<li key={user.id} className="whitespace-nowrap">
|
|
31
|
+
<Link
|
|
32
|
+
to="/users/$userId"
|
|
33
|
+
params={{
|
|
34
|
+
userId: String(user.id),
|
|
35
|
+
}}
|
|
36
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
37
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
38
|
+
>
|
|
39
|
+
<div>{user.name}</div>
|
|
40
|
+
</Link>
|
|
41
|
+
</li>
|
|
42
|
+
)
|
|
43
|
+
})}
|
|
44
|
+
</ul>
|
|
45
|
+
<hr />
|
|
46
|
+
<Outlet />
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--border: var(--color-zinc-200);
|
|
5
|
+
--popover: var(--color-white);
|
|
6
|
+
--popover-foreground: var(--color-zinc-950);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@theme {
|
|
10
|
+
--font-sans: var(--font-sans, Inter), ui-sans-serif, system-ui, sans-serif,
|
|
11
|
+
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
12
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
2
|
+
|
|
3
|
+
const preLogMiddleware = createMiddleware({ type: 'function' })
|
|
4
|
+
.client(async (ctx) => {
|
|
5
|
+
const clientTime = new Date()
|
|
6
|
+
|
|
7
|
+
return ctx.next({
|
|
8
|
+
context: {
|
|
9
|
+
clientTime,
|
|
10
|
+
},
|
|
11
|
+
sendContext: {
|
|
12
|
+
clientTime,
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
.server(async (ctx) => {
|
|
17
|
+
const serverTime = new Date()
|
|
18
|
+
|
|
19
|
+
return ctx.next({
|
|
20
|
+
sendContext: {
|
|
21
|
+
serverTime,
|
|
22
|
+
durationToServer:
|
|
23
|
+
serverTime.getTime() - ctx.context.clientTime.getTime(),
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const logMiddleware = createMiddleware({ type: 'function' })
|
|
29
|
+
.middleware([preLogMiddleware])
|
|
30
|
+
.client(async (ctx) => {
|
|
31
|
+
const res = await ctx.next()
|
|
32
|
+
|
|
33
|
+
const now = new Date()
|
|
34
|
+
console.log('Client Req/Res:', {
|
|
35
|
+
duration: now.getTime() - res.context.clientTime.getTime(),
|
|
36
|
+
durationToServer: res.context.durationToServer,
|
|
37
|
+
durationFromServer: now.getTime() - res.context.serverTime.getTime(),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return res
|
|
41
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { notFound } from '@tanstack/react-router'
|
|
2
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
+
|
|
4
|
+
export type PostType = {
|
|
5
|
+
id: string
|
|
6
|
+
title: string
|
|
7
|
+
body: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const fetchPost = createServerFn()
|
|
11
|
+
.validator((d: string) => d)
|
|
12
|
+
.handler(async ({ data }) => {
|
|
13
|
+
console.info(`Fetching post with id ${data}...`)
|
|
14
|
+
const res = await fetch(
|
|
15
|
+
`https://jsonplaceholder.typicode.com/posts/${data}`,
|
|
16
|
+
)
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
if (res.status === 404) {
|
|
19
|
+
throw notFound()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new Error('Failed to fetch post')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const post = (await res.json()) as PostType
|
|
26
|
+
|
|
27
|
+
return post
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const fetchPosts = createServerFn().handler(async () => {
|
|
31
|
+
console.info('Fetching posts...')
|
|
32
|
+
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error('Failed to fetch posts')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const posts = (await res.json()) as Array<PostType>
|
|
38
|
+
|
|
39
|
+
return posts.slice(0, 10)
|
|
40
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const seo = ({
|
|
2
|
+
title,
|
|
3
|
+
description,
|
|
4
|
+
keywords,
|
|
5
|
+
image,
|
|
6
|
+
}: {
|
|
7
|
+
title: string
|
|
8
|
+
description?: string
|
|
9
|
+
image?: string
|
|
10
|
+
keywords?: string
|
|
11
|
+
}) => {
|
|
12
|
+
const tags = [
|
|
13
|
+
{ title },
|
|
14
|
+
{ name: 'description', content: description },
|
|
15
|
+
{ name: 'keywords', content: keywords },
|
|
16
|
+
{ name: 'twitter:title', content: title },
|
|
17
|
+
{ name: 'twitter:description', content: description },
|
|
18
|
+
{ name: 'twitter:creator', content: '@tannerlinsley' },
|
|
19
|
+
{ name: 'twitter:site', content: '@tannerlinsley' },
|
|
20
|
+
{ name: 'og:type', content: 'website' },
|
|
21
|
+
{ name: 'og:title', content: title },
|
|
22
|
+
{ name: 'og:description', content: description },
|
|
23
|
+
...(image
|
|
24
|
+
? [
|
|
25
|
+
{ name: 'twitter:image', content: image },
|
|
26
|
+
{ name: 'twitter:card', content: 'summary_large_image' },
|
|
27
|
+
{ name: 'og:image', content: image },
|
|
28
|
+
]
|
|
29
|
+
: []),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
return tags
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [
|
|
3
|
+
"alchemy.run.ts",
|
|
4
|
+
"types/**/*.ts",
|
|
5
|
+
"**/*.ts",
|
|
6
|
+
"**/*.tsx"
|
|
7
|
+
],
|
|
8
|
+
"compilerOptions": {
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"module": "ESNext",
|
|
13
|
+
"moduleResolution": "Bundler",
|
|
14
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"target": "ES2022",
|
|
19
|
+
"allowJs": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"baseUrl": ".",
|
|
22
|
+
"paths": {
|
|
23
|
+
"~/*": ["./src/*"]
|
|
24
|
+
},
|
|
25
|
+
"noEmit": true,
|
|
26
|
+
"types": [
|
|
27
|
+
"@cloudflare/workers-types",
|
|
28
|
+
"./types/env.d.ts"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
+
// @see https://alchemy.run/docs/concepts/bindings.html#type-safe-bindings
|
|
3
|
+
|
|
4
|
+
import type { worker } from "../alchemy.run.ts";
|
|
5
|
+
|
|
6
|
+
export type CloudflareEnv = typeof worker.Env;
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
type Env = CloudflareEnv;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module "cloudflare:workers" {
|
|
13
|
+
namespace Cloudflare {
|
|
14
|
+
export interface Env extends CloudflareEnv {}
|
|
15
|
+
}
|
|
16
|
+
}
|