create-croissant 0.1.6 → 0.1.8

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.6",
6
+ "version": "0.1.8",
7
7
  "description": "Scaffold a new project using the Croissant Stack",
8
8
  "repository": {
9
9
  "type": "git",
@@ -18,7 +18,9 @@
18
18
  "@noble/ciphers": "^2.2.0",
19
19
  "@orpc/client": "^1.14.0",
20
20
  "@orpc/server": "^1.14.0",
21
+ "@orpc/tanstack-query": "^1.14.0",
21
22
  "@tailwindcss/vite": "^4.2.4",
23
+ "@tanstack/react-query": "^5.99.2",
22
24
  "@tanstack/react-router": "^1.132.0",
23
25
  "@tanstack/react-start": "^1.132.0",
24
26
  "@tanstack/router-plugin": "^1.132.0",
@@ -1,5 +1,6 @@
1
1
  import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"
2
2
  import { SidebarProvider, SidebarTrigger } from "@workspace/ui/components/sidebar"
3
+ import { Toaster } from "@workspace/ui/components/sonner"
3
4
 
4
5
  import appCss from "@workspace/ui/globals.css?url"
5
6
  import { AppSidebar } from "@/components/app-sidebar"
@@ -45,6 +46,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
45
46
  {children}
46
47
  </div>
47
48
  </main>
49
+ <Toaster />
48
50
  </SidebarProvider>
49
51
  <Scripts />
50
52
  </body>
@@ -1,5 +1,11 @@
1
1
  import * as React from "react"
2
2
  import { createFileRoute } from "@tanstack/react-router"
3
+ import { Check, Pencil, Plus, Trash2 } from "lucide-react"
4
+ import { toast } from "sonner"
5
+
6
+ import { Button } from "@workspace/ui/components/button"
7
+ import { Input } from "@workspace/ui/components/input"
8
+
3
9
  import { orpc } from "../lib/orpc"
4
10
 
5
11
  export const Route = createFileRoute("/client-orpc")({
@@ -7,35 +13,179 @@ export const Route = createFileRoute("/client-orpc")({
7
13
  })
8
14
 
9
15
  function ClientORPC() {
10
- const [data, setData] = React.useState<any>(null)
16
+ const [planets, setPlanets] = React.useState<Array<any>>([])
11
17
  const [loading, setLoading] = React.useState(true)
18
+ const [editingId, setEditingId] = React.useState<number | null>(null)
19
+
20
+ // Form states
21
+ const [name, setName] = React.useState("")
22
+ const [description, setDescription] = React.useState("")
23
+ const [distance, setDistance] = React.useState("0")
24
+ const [diameter, setDiameter] = React.useState("0")
12
25
 
13
- React.useEffect(() => {
14
- const fetchData = async () => {
15
- try {
16
- const res = await orpc.hello({ name: "Client User" })
17
- setData(res)
18
- } catch (err) {
19
- console.error(err)
20
- } finally {
21
- setLoading(false)
22
- }
26
+ const fetchPlanets = async () => {
27
+ try {
28
+ const res = await orpc.getPlanets()
29
+ setPlanets(res)
30
+ } catch (err) {
31
+ console.error(err)
32
+ } finally {
33
+ setLoading(false)
23
34
  }
35
+ }
24
36
 
25
- fetchData()
37
+ React.useEffect(() => {
38
+ fetchPlanets()
26
39
  }, [])
27
40
 
41
+ const resetForm = () => {
42
+ setName("")
43
+ setDescription("")
44
+ setDistance("0")
45
+ setDiameter("0")
46
+ setEditingId(null)
47
+ }
48
+
49
+ const handleAdd = async () => {
50
+ const toastId = toast.loading("Adding planet...")
51
+ try {
52
+ await orpc.createPlanet({
53
+ name,
54
+ description,
55
+ distanceFromSun: parseFloat(distance),
56
+ diameter: parseFloat(diameter),
57
+ hasRings: false,
58
+ })
59
+ await fetchPlanets()
60
+ resetForm()
61
+ toast.success("Planet added successfully", { id: toastId })
62
+ } catch (err: any) {
63
+ console.error(err)
64
+ toast.error(err.message || "Failed to add planet", { id: toastId })
65
+ }
66
+ }
67
+
68
+ const handleUpdate = async (id: number) => {
69
+ const toastId = toast.loading("Updating planet...")
70
+ try {
71
+ await orpc.updatePlanet({
72
+ id,
73
+ name,
74
+ description,
75
+ distanceFromSun: parseFloat(distance),
76
+ diameter: parseFloat(diameter),
77
+ hasRings: false,
78
+ })
79
+ await fetchPlanets()
80
+ resetForm()
81
+ toast.success("Planet updated successfully", { id: toastId })
82
+ } catch (err: any) {
83
+ console.error(err)
84
+ toast.error(err.message || "Failed to update planet", { id: toastId })
85
+ }
86
+ }
87
+
88
+ const handleDelete = async (id: number) => {
89
+ if (!confirm("Are you sure you want to delete this planet?")) return
90
+ const toastId = toast.loading("Deleting planet...")
91
+ try {
92
+ await orpc.deletePlanet({ id })
93
+ await fetchPlanets()
94
+ toast.success("Planet deleted successfully", { id: toastId })
95
+ } catch (err: any) {
96
+ console.error(err)
97
+ toast.error(err.message || "Failed to delete planet", { id: toastId })
98
+ }
99
+ }
100
+
101
+ const startEdit = (planet: any) => {
102
+ setEditingId(planet.id)
103
+ setName(planet.name)
104
+ setDescription(planet.description || "")
105
+ setDistance(planet.distanceFromSun.toString())
106
+ setDiameter(planet.diameter.toString())
107
+ }
108
+
28
109
  return (
29
- <div className="flex flex-col gap-4">
30
- <h1 className="text-2xl font-bold">Client + oRPC Example</h1>
31
- <p>This page fetches data on the client using the oRPC client.</p>
32
-
33
- <div className="rounded-lg border p-4">
34
- <h2 className="font-semibold">Message from oRPC (Client-side):</h2>
110
+ <div className="flex flex-col gap-8">
111
+ <div>
112
+ <h1 className="text-2xl font-bold mb-2">Client + oRPC CRUD</h1>
113
+ <p className="text-muted-foreground">Manage planets directly from the client using oRPC mutations.</p>
114
+ </div>
115
+
116
+ <div className="rounded-lg border p-6 bg-muted/30">
117
+ <h2 className="font-semibold mb-4 flex items-center gap-2">
118
+ {editingId ? <Pencil className="h-4 w-4" /> : <Plus className="h-4 w-4" />}
119
+ {editingId ? "Edit Planet" : "Add New Planet"}
120
+ </h2>
121
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
122
+ <div className="space-y-2">
123
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Name</label>
124
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Mars" />
125
+ </div>
126
+ <div className="space-y-2">
127
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Description</label>
128
+ <Input value={description} onChange={(e) => setDescription(e.target.value)} placeholder="The red planet" />
129
+ </div>
130
+ <div className="space-y-2">
131
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Distance (M km)</label>
132
+ <Input type="number" value={distance} onChange={(e) => setDistance(e.target.value)} />
133
+ </div>
134
+ <div className="space-y-2">
135
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Diameter (km)</label>
136
+ <Input type="number" value={diameter} onChange={(e) => setDiameter(e.target.value)} />
137
+ </div>
138
+ </div>
139
+ <div className="mt-6 flex gap-2">
140
+ {editingId ? (
141
+ <>
142
+ <Button onClick={() => handleUpdate(editingId)} className="flex items-center gap-2">
143
+ <Check className="h-4 w-4" /> Save Changes
144
+ </Button>
145
+ <Button variant="outline" onClick={resetForm}>
146
+ Cancel
147
+ </Button>
148
+ </>
149
+ ) : (
150
+ <Button onClick={handleAdd} className="flex items-center gap-2">
151
+ <Plus className="h-4 w-4" /> Add Planet
152
+ </Button>
153
+ )}
154
+ </div>
155
+ </div>
156
+
157
+ <div className="space-y-4">
158
+ <h2 className="font-semibold text-lg">Current Planets</h2>
35
159
  {loading ? (
36
- <p>Loading...</p>
160
+ <p>Loading planets...</p>
161
+ ) : planets.length === 0 ? (
162
+ <p className="text-gray-500 italic">No planets found.</p>
37
163
  ) : (
38
- <p>{data?.message}</p>
164
+ <div className="grid grid-cols-1 gap-3">
165
+ {planets.map((planet) => (
166
+ <div key={planet.id} className="flex items-center justify-between rounded-lg border p-4 bg-background shadow-sm hover:shadow-md transition-shadow">
167
+ <div className="flex-1">
168
+ <div className="flex items-center gap-2">
169
+ <span className="font-bold text-lg">{planet.name}</span>
170
+ <span className="text-xs bg-muted px-2 py-0.5 rounded-full text-muted-foreground">ID: {planet.id}</span>
171
+ </div>
172
+ <p className="text-sm text-muted-foreground">{planet.description || "No description provided."}</p>
173
+ <div className="mt-2 flex gap-4 text-xs text-muted-foreground font-mono">
174
+ <span>Distance: {planet.distanceFromSun} M km</span>
175
+ <span>Diameter: {planet.diameter} km</span>
176
+ </div>
177
+ </div>
178
+ <div className="flex gap-2 ml-4">
179
+ <Button variant="outline" size="icon" onClick={() => startEdit(planet)}>
180
+ <Pencil className="h-4 w-4" />
181
+ </Button>
182
+ <Button variant="destructive" size="icon" onClick={() => handleDelete(planet.id)}>
183
+ <Trash2 className="h-4 w-4" />
184
+ </Button>
185
+ </div>
186
+ </div>
187
+ ))}
188
+ </div>
39
189
  )}
40
190
  </div>
41
191
  </div>
@@ -1,29 +1,52 @@
1
1
  import { createFileRoute } from "@tanstack/react-router"
2
+ import { orpc } from "../lib/orpc"
2
3
 
3
4
  export const Route = createFileRoute("/isr")({
4
- loader: () => {
5
+ loader: async () => {
5
6
  // In a real ISR scenario, this would be cached on the server
6
- // For this example, we'll just show the time it was "generated"
7
+ // For this example, we'll fetch planets via oRPC
8
+ const planets = await orpc.getPlanets()
9
+
7
10
  return {
8
11
  generatedAt: new Date().toISOString(),
12
+ planets,
9
13
  message: "This page is an example of ISR. In a production build with proper configuration, this data would be cached and updated in the background.",
10
14
  }
11
15
  },
16
+ headers: () => ({
17
+ // Cache at CDN for 10 seconds, allow stale content for up to 1 minute
18
+ "Cache-Control": "public, max-age=10, s-maxage=10, stale-while-revalidate=60",
19
+ }),
12
20
  component: ISRExample,
13
21
  })
14
22
 
15
23
  function ISRExample() {
16
- const { generatedAt, message } = Route.useLoaderData()
24
+ const { generatedAt, message, planets } = Route.useLoaderData()
17
25
 
18
26
  return (
19
27
  <div className="flex flex-col gap-4">
20
- <h1 className="text-2xl font-bold">ISR Example</h1>
28
+ <h1 className="text-2xl font-bold">ISR Example + oRPC</h1>
21
29
  <p>{message}</p>
22
30
 
23
31
  <div className="rounded-lg border p-4">
24
- <h2 className="font-semibold">Generated At:</h2>
32
+ <h2 className="font-semibold">Generated At (Server-side):</h2>
25
33
  <p className="font-mono">{generatedAt}</p>
26
34
  </div>
35
+
36
+ <div className="rounded-lg border p-4">
37
+ <h2 className="font-semibold mb-4">Planets (Fetched via oRPC):</h2>
38
+ {planets.length === 0 ? (
39
+ <p className="text-gray-500 italic">No planets found.</p>
40
+ ) : (
41
+ <ul className="grid grid-cols-1 sm:grid-cols-2 gap-2">
42
+ {planets.map((planet) => (
43
+ <li key={planet.id} className="rounded-md border p-3 bg-muted/50">
44
+ <span className="font-bold">{planet.name}</span> - {planet.description}
45
+ </li>
46
+ ))}
47
+ </ul>
48
+ )}
49
+ </div>
27
50
 
28
51
  <p className="text-sm text-muted-foreground">
29
52
  Refresh the page to see if the time updates. In a true ISR setup, it would only update after a certain interval.
@@ -1,35 +1,189 @@
1
- import { createFileRoute } from "@tanstack/react-router"
1
+ import * as React from "react"
2
+ import { createFileRoute, useRouter } from "@tanstack/react-router"
3
+ import { Check, Pencil, Plus, Trash2 } from "lucide-react"
4
+ import { toast } from "sonner"
5
+
6
+ import { Button } from "@workspace/ui/components/button"
7
+ import { Input } from "@workspace/ui/components/input"
8
+
2
9
  import { orpc } from "../lib/orpc"
3
10
 
4
11
  export const Route = createFileRoute("/ssr-orpc")({
5
12
  loader: async () => {
6
- const data = await orpc.hello({ name: "SSR User" })
7
13
  const planets = await orpc.getPlanets()
8
- return { data, planets }
14
+ return { planets }
9
15
  },
10
16
  component: SSRORPC,
11
17
  })
12
18
 
13
19
  function SSRORPC() {
14
- const { data, planets } = Route.useLoaderData()
20
+ const { planets } = Route.useLoaderData()
21
+ const router = useRouter()
22
+ const [editingId, setEditingId] = React.useState<number | null>(null)
23
+
24
+ // Form states
25
+ const [name, setName] = React.useState("")
26
+ const [description, setDescription] = React.useState("")
27
+ const [distance, setDistance] = React.useState("0")
28
+ const [diameter, setDiameter] = React.useState("0")
29
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
30
+
31
+ const resetForm = () => {
32
+ setName("")
33
+ setDescription("")
34
+ setDistance("0")
35
+ setDiameter("0")
36
+ setEditingId(null)
37
+ }
38
+
39
+ const handleAdd = async () => {
40
+ setIsSubmitting(true)
41
+ const toastId = toast.loading("Adding planet...")
42
+ try {
43
+ await orpc.createPlanet({
44
+ name,
45
+ description,
46
+ distanceFromSun: parseFloat(distance),
47
+ diameter: parseFloat(diameter),
48
+ hasRings: false,
49
+ })
50
+ await router.invalidate()
51
+ resetForm()
52
+ toast.success("Planet added successfully", { id: toastId })
53
+ } catch (err: any) {
54
+ console.error(err)
55
+ toast.error(err.message || "Failed to add planet", { id: toastId })
56
+ } finally {
57
+ setIsSubmitting(false)
58
+ }
59
+ }
60
+
61
+ const handleUpdate = async (id: number) => {
62
+ setIsSubmitting(true)
63
+ const toastId = toast.loading("Updating planet...")
64
+ try {
65
+ await orpc.updatePlanet({
66
+ id,
67
+ name,
68
+ description,
69
+ distanceFromSun: parseFloat(distance),
70
+ diameter: parseFloat(diameter),
71
+ hasRings: false,
72
+ })
73
+ await router.invalidate()
74
+ resetForm()
75
+ toast.success("Planet updated successfully", { id: toastId })
76
+ } catch (err: any) {
77
+ console.error(err)
78
+ toast.error(err.message || "Failed to update planet", { id: toastId })
79
+ } finally {
80
+ setIsSubmitting(false)
81
+ }
82
+ }
83
+
84
+ const handleDelete = async (id: number) => {
85
+ if (!confirm("Are you sure you want to delete this planet?")) return
86
+ setIsSubmitting(true)
87
+ const toastId = toast.loading("Deleting planet...")
88
+ try {
89
+ await orpc.deletePlanet({ id })
90
+ await router.invalidate()
91
+ toast.success("Planet deleted successfully", { id: toastId })
92
+ } catch (err: any) {
93
+ console.error(err)
94
+ toast.error(err.message || "Failed to delete planet", { id: toastId })
95
+ } finally {
96
+ setIsSubmitting(false)
97
+ }
98
+ }
99
+
100
+ const startEdit = (planet: any) => {
101
+ setEditingId(planet.id)
102
+ setName(planet.name)
103
+ setDescription(planet.description || "")
104
+ setDistance(planet.distanceFromSun.toString())
105
+ setDiameter(planet.diameter.toString())
106
+ }
15
107
 
16
108
  return (
17
- <div className="flex flex-col gap-4">
18
- <h1 className="text-2xl font-bold">SSR + oRPC Example</h1>
19
- <p>This page fetches data on the server using oRPC in a loader.</p>
20
-
21
- <div className="rounded-lg border p-4">
22
- <h2 className="font-semibold">Message from oRPC:</h2>
23
- <p>{data.message}</p>
109
+ <div className="flex flex-col gap-8">
110
+ <div>
111
+ <h1 className="text-2xl font-bold mb-2">SSR + oRPC CRUD</h1>
112
+ <p className="text-muted-foreground">Manage planets using SSR loaders for fetching and oRPC mutations for actions.</p>
113
+ </div>
114
+
115
+ <div className="rounded-lg border p-6 bg-muted/30">
116
+ <h2 className="font-semibold mb-4 flex items-center gap-2">
117
+ {editingId ? <Pencil className="h-4 w-4" /> : <Plus className="h-4 w-4" />}
118
+ {editingId ? "Edit Planet (SSR)" : "Add New Planet (SSR)"}
119
+ </h2>
120
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
121
+ <div className="space-y-2">
122
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Name</label>
123
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Mars" disabled={isSubmitting} />
124
+ </div>
125
+ <div className="space-y-2">
126
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Description</label>
127
+ <Input value={description} onChange={(e) => setDescription(e.target.value)} placeholder="The red planet" disabled={isSubmitting} />
128
+ </div>
129
+ <div className="space-y-2">
130
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Distance (M km)</label>
131
+ <Input type="number" value={distance} onChange={(e) => setDistance(e.target.value)} disabled={isSubmitting} />
132
+ </div>
133
+ <div className="space-y-2">
134
+ <label className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Diameter (km)</label>
135
+ <Input type="number" value={diameter} onChange={(e) => setDiameter(e.target.value)} disabled={isSubmitting} />
136
+ </div>
137
+ </div>
138
+ <div className="mt-6 flex gap-2">
139
+ {editingId ? (
140
+ <>
141
+ <Button onClick={() => handleUpdate(editingId)} className="flex items-center gap-2" disabled={isSubmitting}>
142
+ <Check className="h-4 w-4" /> {isSubmitting ? "Saving..." : "Save Changes"}
143
+ </Button>
144
+ <Button variant="outline" onClick={resetForm} disabled={isSubmitting}>
145
+ Cancel
146
+ </Button>
147
+ </>
148
+ ) : (
149
+ <Button onClick={handleAdd} className="flex items-center gap-2" disabled={isSubmitting}>
150
+ <Plus className="h-4 w-4" /> {isSubmitting ? "Adding..." : "Add Planet"}
151
+ </Button>
152
+ )}
153
+ </div>
24
154
  </div>
25
155
 
26
- <div className="rounded-lg border p-4">
27
- <h2 className="font-semibold">Planets from DB:</h2>
28
- <ul className="list-disc ml-6">
29
- {planets.map((planet) => (
30
- <li key={planet.id}>{planet.name}</li>
31
- ))}
32
- </ul>
156
+ <div className="space-y-4">
157
+ <h2 className="font-semibold text-lg">Current Planets (SSR Fetched)</h2>
158
+ {planets.length === 0 ? (
159
+ <p className="text-gray-500 italic">No planets found in the database.</p>
160
+ ) : (
161
+ <div className="grid grid-cols-1 gap-3">
162
+ {planets.map((planet) => (
163
+ <div key={planet.id} className="flex items-center justify-between rounded-lg border p-4 bg-background shadow-sm hover:shadow-md transition-shadow">
164
+ <div className="flex-1">
165
+ <div className="flex items-center gap-2">
166
+ <span className="font-bold text-lg">{planet.name}</span>
167
+ <span className="text-xs bg-muted px-2 py-0.5 rounded-full text-muted-foreground">ID: {planet.id}</span>
168
+ </div>
169
+ <p className="text-sm text-muted-foreground">{planet.description || "No description provided."}</p>
170
+ <div className="mt-2 flex gap-4 text-xs text-muted-foreground font-mono">
171
+ <span>Distance: {planet.distanceFromSun} M km</span>
172
+ <span>Diameter: {planet.diameter} km</span>
173
+ </div>
174
+ </div>
175
+ <div className="flex gap-2 ml-4">
176
+ <Button variant="outline" size="icon" onClick={() => startEdit(planet)} disabled={isSubmitting}>
177
+ <Pencil className="h-4 w-4" />
178
+ </Button>
179
+ <Button variant="destructive" size="icon" onClick={() => handleDelete(planet.id)} disabled={isSubmitting}>
180
+ <Trash2 className="h-4 w-4" />
181
+ </Button>
182
+ </div>
183
+ </div>
184
+ ))}
185
+ </div>
186
+ )}
33
187
  </div>
34
188
  </div>
35
189
  )
@@ -8,7 +8,11 @@ const config = defineConfig({
8
8
  plugins: [
9
9
  nitro(),
10
10
  tailwindcss(),
11
- tanstackStart(),
11
+ tanstackStart({
12
+ prerender: {
13
+ filter: ({ path }) => path === "/isr",
14
+ },
15
+ }),
12
16
  viteReact(),
13
17
  ],
14
18
  resolve: {
@@ -1,5 +1,6 @@
1
1
  import { ORPCError, os } from '@orpc/server'
2
2
  import { db, schema } from '@workspace/db'
3
+ import { eq } from 'drizzle-orm'
3
4
  import { z } from 'zod'
4
5
  import type { Session } from '@workspace/auth/lib/auth'
5
6
 
@@ -47,6 +48,68 @@ export const router = o.router({
47
48
  email: context.session.user.email,
48
49
  }
49
50
  }),
51
+
52
+ createPlanet: o
53
+ .input(
54
+ z.object({
55
+ name: z.string().min(1),
56
+ description: z.string().optional(),
57
+ distanceFromSun: z.number(),
58
+ diameter: z.number(),
59
+ hasRings: z.boolean().default(false),
60
+ atmosphere: z.string().optional(),
61
+ }),
62
+ )
63
+ .handler(async ({ input }) => {
64
+ const [newPlanet] = await db.insert(planets).values(input).returning()
65
+ return newPlanet
66
+ }),
67
+
68
+ updatePlanet: o
69
+ .input(
70
+ z.object({
71
+ id: z.number(),
72
+ name: z.string().min(1),
73
+ description: z.string().optional(),
74
+ distanceFromSun: z.number(),
75
+ diameter: z.number(),
76
+ hasRings: z.boolean(),
77
+ atmosphere: z.string().optional(),
78
+ }),
79
+ )
80
+ .handler(async ({ input }) => {
81
+ const { id, ...data } = input
82
+ const results = await db
83
+ .update(planets)
84
+ .set({ ...data, updatedAt: new Date() })
85
+ .where(eq(planets.id, id))
86
+ .returning()
87
+
88
+ if (results.length === 0) {
89
+ throw new ORPCError('NOT_FOUND')
90
+ }
91
+
92
+ return results[0]
93
+ }),
94
+
95
+ deletePlanet: o
96
+ .input(
97
+ z.object({
98
+ id: z.number(),
99
+ }),
100
+ )
101
+ .handler(async ({ input }) => {
102
+ const results = await db
103
+ .delete(planets)
104
+ .where(eq(planets.id, input.id))
105
+ .returning()
106
+
107
+ if (results.length === 0) {
108
+ throw new ORPCError('NOT_FOUND')
109
+ }
110
+
111
+ return results[0]
112
+ }),
50
113
  })
51
114
 
52
115
  export type AppRouter = typeof router