@atom.io/template-react-node-backend 0.0.7 → 0.0.9
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +16 -0
- package/dist/assets/index-C281ybwn.js +31 -0
- package/dist/index.html +1 -1
- package/node/server.ts +3 -3
- package/package.json +5 -5
- package/src/App.tsx +51 -30
- package/dist/assets/index-C0R4Gha-.js +0 -31
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>vite-react</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-C281ybwn.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CqTfTEH3.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/node/server.ts
CHANGED
|
@@ -124,10 +124,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
124
124
|
{
|
|
125
125
|
const id = Number.parseInt(searchParams.get(`id`) as string, 10)
|
|
126
126
|
if (Number.isNaN(id)) {
|
|
127
|
-
sendJSON(res, 200,
|
|
127
|
+
sendJSON(res, 200, getAllStmt.all(), true)
|
|
128
128
|
} else {
|
|
129
129
|
const todo = getOneStmt.get(id)
|
|
130
|
-
sendJSON(res, 200,
|
|
130
|
+
sendJSON(res, 200, todo, true)
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
return
|
|
@@ -137,7 +137,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
137
137
|
for await (const chunk of r) body += chunk
|
|
138
138
|
const { lastInsertRowid } = insertStmt.run(body)
|
|
139
139
|
const todo = getOneStmt.get(lastInsertRowid)
|
|
140
|
-
sendJSON(res, 200,
|
|
140
|
+
sendJSON(res, 200, todo, true)
|
|
141
141
|
}
|
|
142
142
|
return
|
|
143
143
|
case `PUT`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atom.io/template-react-node-backend",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"react": "19.2.0",
|
|
10
10
|
"react-dom": "19.2.0",
|
|
11
11
|
"zod": "4.1.12",
|
|
12
|
-
"atom.io": "0.44.
|
|
12
|
+
"atom.io": "0.44.7"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/node": "24.10.1",
|
|
16
|
-
"@types/react": "19.2.
|
|
16
|
+
"@types/react": "19.2.6",
|
|
17
17
|
"@types/react-dom": "19.2.3",
|
|
18
18
|
"@vitejs/plugin-react": "5.1.1",
|
|
19
19
|
"babel-plugin-react-compiler": "1.0.0",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"eslint-plugin-react-refresh": "0.4.24",
|
|
24
24
|
"globals": "16.5.0",
|
|
25
25
|
"typescript": "5.9.3",
|
|
26
|
-
"typescript-eslint": "8.
|
|
27
|
-
"vite": "npm:rolldown-vite@7.2.
|
|
26
|
+
"typescript-eslint": "8.47.0",
|
|
27
|
+
"vite": "npm:rolldown-vite@7.2.6"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"dev": "concurrently vite ./node/server.ts ./node/authenticator.ts",
|
package/src/App.tsx
CHANGED
|
@@ -21,20 +21,42 @@ const todoSchema = z.object({
|
|
|
21
21
|
done: z.union([z.literal(0), z.literal(1)]), // keeps things simple with sqlite
|
|
22
22
|
})
|
|
23
23
|
type Todo = z.infer<typeof todoSchema>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This atom tells us what todos exist.
|
|
27
|
+
*
|
|
28
|
+
* `Loadable` means that the value of this atom may be a `Promise` sometimes.
|
|
29
|
+
*
|
|
30
|
+
* `useLoadable` from `"atom.io/react"` makes this easy to use in components.
|
|
31
|
+
*/
|
|
24
32
|
const todoKeysAtom = atom<Loadable<number[]>, Error>({
|
|
25
33
|
key: `todoKeys`,
|
|
26
34
|
default: async () => {
|
|
27
35
|
const url = new URL(`/todos`, SERVER_URL)
|
|
28
36
|
const response = await fetch(url, { credentials: `include` })
|
|
29
37
|
if (!response.ok) throw new Error(response.status.toString())
|
|
30
|
-
const json =
|
|
31
|
-
const todos = todoSchema.array().parse(json
|
|
38
|
+
const json = await response.json()
|
|
39
|
+
const todos = todoSchema.array().parse(json)
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
🚀 Performance hack!
|
|
43
|
+
We can make our client less chatty by pre-populating our todo atoms.
|
|
44
|
+
Comment out this line and watch the network tab to see the difference.
|
|
45
|
+
*/
|
|
32
46
|
for (const todo of todos) setState(todoAtoms, todo.id, todo)
|
|
47
|
+
|
|
33
48
|
return todos.map((todo) => todo.id)
|
|
34
49
|
},
|
|
35
50
|
catch: [Error],
|
|
36
51
|
})
|
|
37
52
|
|
|
53
|
+
/**
|
|
54
|
+
* This atom family holds the values of individual todos.
|
|
55
|
+
*
|
|
56
|
+
* A todo's default value is fetched from the server, but
|
|
57
|
+
* this only happens when you `resetState`, or `getState`
|
|
58
|
+
* when the atom has never been gotten or set before.
|
|
59
|
+
*/
|
|
38
60
|
const todoAtoms = atomFamily<Loadable<Todo>, number, Error>({
|
|
39
61
|
key: `todos`,
|
|
40
62
|
default: async (id) => {
|
|
@@ -42,13 +64,14 @@ const todoAtoms = atomFamily<Loadable<Todo>, number, Error>({
|
|
|
42
64
|
url.searchParams.set(`id`, id.toString())
|
|
43
65
|
const response = await fetch(url, { credentials: `include` })
|
|
44
66
|
if (!response.ok) throw new Error(response.status.toString())
|
|
45
|
-
const json =
|
|
46
|
-
const todo = todoSchema.parse(json
|
|
67
|
+
const json = await response.json()
|
|
68
|
+
const todo = todoSchema.parse(json)
|
|
47
69
|
return todo
|
|
48
70
|
},
|
|
49
71
|
catch: [Error],
|
|
50
72
|
})
|
|
51
73
|
|
|
74
|
+
/** This selector computes the total number of todos and the number of done todos. */
|
|
52
75
|
const todosStatsSelector = selector<
|
|
53
76
|
Loadable<{
|
|
54
77
|
total: number
|
|
@@ -83,7 +106,7 @@ async function addTodo() {
|
|
|
83
106
|
body: text,
|
|
84
107
|
})
|
|
85
108
|
if (!res.ok) throw new Error(res.status.toString())
|
|
86
|
-
const
|
|
109
|
+
const todo = await res.json()
|
|
87
110
|
const realId = todo.id
|
|
88
111
|
setState(todoAtoms, realId, todo)
|
|
89
112
|
setState(todoKeysAtom, async (loadable) => {
|
|
@@ -112,14 +135,29 @@ async function deleteTodo(todoKey: number) {
|
|
|
112
135
|
})
|
|
113
136
|
}
|
|
114
137
|
|
|
138
|
+
async function toggleTodoDone(todoKey: number, isNowDone: 0 | 1) {
|
|
139
|
+
const url = new URL(`todos`, SERVER_URL)
|
|
140
|
+
url.searchParams.set(`id`, todoKey.toString())
|
|
141
|
+
setState(todoAtoms, todoKey, async (loadable) => {
|
|
142
|
+
const prev = await loadable
|
|
143
|
+
if (Error.isError(prev)) return prev
|
|
144
|
+
return { ...prev, done: isNowDone } satisfies Todo
|
|
145
|
+
})
|
|
146
|
+
await fetch(url, {
|
|
147
|
+
method: `PUT`,
|
|
148
|
+
credentials: `include`,
|
|
149
|
+
body: isNowDone.toString(),
|
|
150
|
+
})
|
|
151
|
+
resetState(todoAtoms, todoKey)
|
|
152
|
+
}
|
|
153
|
+
|
|
115
154
|
const TODO_FALLBACK: Todo = { id: 0, text: ``, done: 0 }
|
|
116
155
|
const SKELETON_KEYS = Array.from({ length: 5 }).map(Math.random)
|
|
117
|
-
for (const
|
|
156
|
+
for (const KEY of SKELETON_KEYS) setState(todoAtoms, KEY, TODO_FALLBACK)
|
|
118
157
|
|
|
119
158
|
export function App(): React.JSX.Element {
|
|
120
159
|
const todoKeys = useLoadable(todoKeysAtom, SKELETON_KEYS)
|
|
121
160
|
const stats = useLoadable(todosStatsSelector, { total: 0, done: 0 })
|
|
122
|
-
|
|
123
161
|
return (
|
|
124
162
|
<>
|
|
125
163
|
{todoKeys.error ? (
|
|
@@ -179,28 +217,11 @@ export function App(): React.JSX.Element {
|
|
|
179
217
|
function Todo({ todoKey }: { todoKey: number }): React.JSX.Element {
|
|
180
218
|
const todo = useLoadable(todoAtoms, todoKey, TODO_FALLBACK)
|
|
181
219
|
const isSuspended = todo.loading || !Number.isInteger(todoKey)
|
|
182
|
-
const toggleDone = useCallback(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
url.searchParams.set(`id`, todo.value.id.toString())
|
|
186
|
-
const nowChecked = e.target.checked ? 1 : 0
|
|
187
|
-
setState(todoAtoms, todoKey, async (loadable) => {
|
|
188
|
-
const prev = await loadable
|
|
189
|
-
if (Error.isError(prev)) return prev
|
|
190
|
-
return { ...prev, done: nowChecked } satisfies Todo
|
|
191
|
-
})
|
|
192
|
-
await fetch(url, {
|
|
193
|
-
method: `PUT`,
|
|
194
|
-
credentials: `include`,
|
|
195
|
-
body: nowChecked.toString(),
|
|
196
|
-
})
|
|
197
|
-
resetState(todoAtoms, todoKey)
|
|
198
|
-
},
|
|
199
|
-
[],
|
|
200
|
-
)
|
|
201
|
-
const deleteThisTodo = useCallback(async () => {
|
|
202
|
-
await deleteTodo(todoKey)
|
|
220
|
+
const toggleDone = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
221
|
+
const isNowDone = e.target.checked ? 1 : 0
|
|
222
|
+
void toggleTodoDone(todoKey, isNowDone)
|
|
203
223
|
}, [])
|
|
224
|
+
const deleteThisTodo = useCallback(() => deleteTodo(todoKey), [])
|
|
204
225
|
return (
|
|
205
226
|
<div className={cn(`todo`, isSuspended && `loading`)}>
|
|
206
227
|
<input
|
|
@@ -220,11 +241,11 @@ function Todo({ todoKey }: { todoKey: number }): React.JSX.Element {
|
|
|
220
241
|
)
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
/** This atom holds the text of the new todo entry. */
|
|
223
245
|
const newTodoTextAtom = atom<string>({
|
|
224
246
|
key: `newTodo`,
|
|
225
247
|
default: ``,
|
|
226
248
|
})
|
|
227
|
-
|
|
228
249
|
function NewTodo(): React.JSX.Element {
|
|
229
250
|
const text = useO(newTodoTextAtom)
|
|
230
251
|
const setText = useI(newTodoTextAtom)
|
|
@@ -250,6 +271,7 @@ function NewTodo(): React.JSX.Element {
|
|
|
250
271
|
)
|
|
251
272
|
}
|
|
252
273
|
|
|
274
|
+
/** This atom holds whether long load times are enabled. */
|
|
253
275
|
const longLoadTimesAtom = atom<Loadable<boolean>>({
|
|
254
276
|
key: `longLoadTimes`,
|
|
255
277
|
default: () =>
|
|
@@ -257,7 +279,6 @@ const longLoadTimesAtom = atom<Loadable<boolean>>({
|
|
|
257
279
|
credentials: `include`,
|
|
258
280
|
}).then(async (res) => res.json()),
|
|
259
281
|
})
|
|
260
|
-
|
|
261
282
|
function LongLoadTimes(): React.JSX.Element {
|
|
262
283
|
const longLoadTimes = useLoadable(longLoadTimesAtom, false)
|
|
263
284
|
const toggle = useCallback(async () => {
|