@alxia/create 0.1.4 → 0.1.5

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/docs/guide.md CHANGED
@@ -6,6 +6,7 @@ chooses the versions it writes.
6
6
  - [Running it](#running-it)
7
7
  - [The `api` template](#the-api-template)
8
8
  - [The `react-router` template](#the-react-router-template)
9
+ - [Lint and format](#lint-and-format)
9
10
  - [Docker](#docker)
10
11
  - [Versions](#versions)
11
12
  - [In a script or CI](#in-a-script-or-ci)
@@ -45,7 +46,10 @@ What the command line gives is not asked: `bun create @alxia my-app
45
46
  --template react-router` asks nothing. The directory must be empty or not
46
47
  exist yet; `.` writes into the current one, when it is empty, and the next
47
48
  steps then start at `bun dev`. The package name in `package.json` is the
48
- directory's name, lowercased, with `-` for anything npm refuses.
49
+ directory's name, lowercased, with `-` for anything npm refuses, and the
50
+ same name replaces the template's own (`my-api`, `my-app`) in its other
51
+ files, as the README's `docker build -t` and `docker run`: `bun create
52
+ @alxia "Mon Super Projet"` writes `mon-super-projet` in both.
49
53
 
50
54
  `bun create @alxia` is `bunx @alxia/create`: Bun maps `bun create @scope`
51
55
  to the package `@scope/create`, and npm maps `npm create @scope` the same
@@ -53,12 +57,15 @@ way. Any of the three runs the same bin, on Bun.
53
57
 
54
58
  Both templates are files shipped in this package, under
55
59
  `templates/api/` and `templates/react-router/`, and copied as they are:
56
- nothing is downloaded but the dependencies, and only `package.json` is
57
- written again, with the directory's name, alxia's versions and the newest
58
- of the others ([Versions](#versions)). Two files are stored under another
59
- name, since `bun publish` leaves them out of a tarball, and take theirs
60
- back on the copy: `gitignore` is written as `.gitignore`, `_bunfig.toml`
61
- as `bunfig.toml`.
60
+ nothing is downloaded but the dependencies. `package.json` is written
61
+ again, with the directory's name, alxia's versions and the newest of the
62
+ others ([Versions](#versions)); in every other text file, the template's
63
+ own name, as a whole word, becomes the project's. Three files are stored
64
+ under another name and take theirs back on the copy: `gitignore` is
65
+ written as `.gitignore` and `_bunfig.toml` as `bunfig.toml`, since `bun
66
+ publish` leaves those out of a tarball, and `_biome.json` as `biome.json`,
67
+ since alxia's own Biome refuses a second root configuration inside its
68
+ repository ([Lint and format](#lint-and-format)).
62
69
 
63
70
  ## The `api` template
64
71
 
@@ -70,6 +77,8 @@ my-api/
70
77
  │ └── server.ts app.listen(PORT), stopped on SIGTERM
71
78
  ├── package.json
72
79
  ├── tsconfig.json
80
+ ├── biome.json Biome: lint, format, imports sorted
81
+ ├── .vscode/ Biome's extension recommended, format on save
73
82
  ├── Dockerfile bun run build, then dist/ alone, on oven/bun:1-alpine
74
83
  ├── .dockerignore
75
84
  ├── .env.example PORT and API_KEY, for a .env Bun loads
@@ -81,35 +90,35 @@ my-api/
81
90
  body validated by a Zod schema, a declared reply, and a hook of its own.
82
91
 
83
92
  ```ts
84
- import { alxia, defineHook } from '@alxia/core';
85
- import { z } from 'zod';
93
+ import { alxia, defineHook } from "@alxia/core";
94
+ import { z } from "zod";
86
95
 
87
96
  const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
88
97
  const NewTodo = z.object({ title: z.string().min(1) });
89
98
 
90
99
  /** Set API_KEY in the environment: this default is for development. */
91
- export const apiKey = Bun.env['API_KEY'] ?? 'dev-key';
100
+ export const apiKey = Bun.env["API_KEY"] ?? "dev-key";
92
101
 
93
102
  const requireKey = defineHook(({ request, reply }) =>
94
- request.headers.get('x-api-key') === apiKey
95
- ? undefined
96
- : reply(401, { error: 'unauthorized' as const }),
103
+ request.headers.get("x-api-key") === apiKey
104
+ ? undefined
105
+ : reply(401, { error: "unauthorized" as const }),
97
106
  );
98
107
 
99
108
  const todos: z.infer<typeof Todo>[] = [];
100
109
 
101
110
  export const app = alxia()
102
- .decorate({ todos })
103
- .post(
104
- '/todos',
105
- [requireKey],
106
- { body: NewTodo, response: { 201: Todo } },
107
- ({ body, todos, reply }) => {
108
- const todo = { id: todos.length + 1, title: body.title, done: false };
109
- todos.push(todo);
110
- return reply.created(todo);
111
- },
112
- );
111
+ .decorate({ todos })
112
+ .post(
113
+ "/todos",
114
+ [requireKey],
115
+ { body: NewTodo, response: { 201: Todo } },
116
+ ({ body, todos, reply }) => {
117
+ const todo = { id: todos.length + 1, title: body.title, done: false };
118
+ todos.push(todo);
119
+ return reply.created(todo);
120
+ },
121
+ );
113
122
 
114
123
  export type App = typeof app;
115
124
  ```
@@ -126,10 +135,10 @@ JSON body, then `@alxia/client` given the app itself, whose result is typed
126
135
  by status:
127
136
 
128
137
  ```ts
129
- const api = client(app, { headers: { 'x-api-key': apiKey } });
130
- const created = await api.post('/todos', { body: { title: 'Call it typed' } });
138
+ const api = client(app, { headers: { "x-api-key": apiKey } });
139
+ const created = await api.post("/todos", { body: { title: "Call it typed" } });
131
140
  if (created.status !== 201) throw new Error(`got ${created.status}`);
132
- expect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type
141
+ expect(created.data.title).toBe("Call it typed"); // data is the Todo schema's type
133
142
  ```
134
143
 
135
144
  The scripts:
@@ -141,6 +150,10 @@ The scripts:
141
150
  | `bun run typecheck` | `tsc --noEmit` |
142
151
  | `bun run build` | `bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked`: one file, its dependencies bundled |
143
152
  | `bun start` | `bun dist/server.js`: the build, after `bun run build` |
153
+ | `bun run check` | `biome check --write`: lint, format, sort imports, fixing what it can |
154
+ | `bun run lint`, `bun run format` | `biome lint`, `biome format --write` |
155
+ | `bun run check:ci` | `biome ci`: read-only, for CI |
156
+ | `bun run verify` | `check:ci`, `typecheck`, then `test` |
144
157
 
145
158
  `start` runs what `build` wrote, as production and the image do:
146
159
 
@@ -162,9 +175,10 @@ keeps out of git and `.dockerignore` out of the image.
162
175
  `tsconfig.json` holds the settings alxia's own packages are checked under:
163
176
  `strict`, and past it `exactOptionalPropertyTypes`,
164
177
  `noUncheckedIndexedAccess`, `noPropertyAccessFromIndexSignature`,
165
- `noUnusedLocals` and the rest. Hence `Bun.env['API_KEY']` and not
166
- `Bun.env.API_KEY`. Loosen what you would rather not keep: alxia's types
167
- compile under each one, and under none.
178
+ `noUnusedLocals` and the rest. Hence `Bun.env["API_KEY"]` and not
179
+ `Bun.env.API_KEY`, and Biome's `useLiteralKeys`, which would ask for the
180
+ second, is off in `biome.json`. Loosen what you would rather not keep:
181
+ alxia's types compile under each one, and under none.
168
182
 
169
183
  ## The `react-router` template
170
184
 
@@ -195,6 +209,19 @@ before `bun install`. The change, against React Router's files:
195
209
  + plugins: [tailwindcss(), reactRouter(), alxia()],
196
210
  ```
197
211
 
212
+ ```diff
213
+ // package.json
214
+ "scripts": {
215
+ + "lint": "biome lint",
216
+ + "format": "biome format --write",
217
+ + "check": "biome check --write",
218
+ + "check:ci": "biome ci",
219
+ + "verify": "bun run check:ci && bun run typecheck && bun run build"
220
+ },
221
+ "devDependencies": {
222
+ + "@biomejs/biome": "2.5.15",
223
+ ```
224
+
198
225
  ```toml
199
226
  # bunfig.toml, new
200
227
  [run]
@@ -204,7 +231,12 @@ bun = true
204
231
  ```
205
232
 
206
233
  The `Dockerfile` is alxia's, in place of React Router's, which builds and
207
- runs on Node ([Docker](#docker)). Every other file is React Router's:
234
+ runs on Node ([Docker](#docker)). `biome.json` and `.vscode/` are new,
235
+ and the README gains a Lint and format section
236
+ ([Lint and format](#lint-and-format)); Biome formatted the scaffold once,
237
+ which changed three of its files: `app/app.css`'s font list wraps
238
+ differently, and `app/routes.ts` and `app/routes/home.tsx` have their
239
+ imports sorted. Every other file is React Router's:
208
240
  `app/`, `public/`, `tsconfig.json`, `react-router.config.ts`, its
209
241
  `README.md` (with Bun's commands where it wrote npm's: `bun install`,
210
242
  `bun dev`, `bun run build`), `.gitignore` and `.dockerignore`. `package.json` takes the directory's name, and its
@@ -228,6 +260,98 @@ bunx alxia-react-router reveal
228
260
  The [`@alxia/react-router` guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md)
229
261
  goes from there.
230
262
 
263
+ ## Lint and format
264
+
265
+ Both projects lint and format with [Biome](https://biomejs.dev), in one
266
+ style whichever template wrote them. Their `biome.json` is a root
267
+ configuration of its own, extending nothing:
268
+
269
+ ```json
270
+ {
271
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
272
+ "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
273
+ "files": { "includes": ["**", "!!**/build", "!!**/.react-router"] },
274
+ "formatter": { "enabled": true, "indentStyle": "space" },
275
+ "css": { "parser": { "tailwindDirectives": true } },
276
+ "linter": {
277
+ "enabled": true,
278
+ "rules": {
279
+ "preset": "recommended",
280
+ "correctness": { "noEmptyPattern": "off" }
281
+ }
282
+ },
283
+ "assist": {
284
+ "enabled": true,
285
+ "actions": { "source": { "organizeImports": "on" } }
286
+ },
287
+ "overrides": [
288
+ {
289
+ "includes": ["**/app/welcome/**"],
290
+ "linter": { "rules": { "a11y": { "noSvgWithoutTitle": "off" } } }
291
+ }
292
+ ]
293
+ }
294
+ ```
295
+
296
+ That is the `react-router` project's, written here compact. The `api`
297
+ project's skips `dist/` instead of `build/` and `.react-router/`, has no
298
+ CSS settings and no overrides, and turns `complexity.useLiteralKeys` off
299
+ in place of `noEmptyPattern` ([the `api` template](#the-api-template)).
300
+
301
+ - **The style is Biome's default but for spaces**: two spaces, double
302
+ quotes, 80 columns. React Router's scaffold is written so, and so is
303
+ the `package.json` the command writes: formatting the scaffold once
304
+ changed three of its files, where tabs would change every line. The
305
+ `api` project takes the same style, so the two read alike.
306
+ - **`$schema` is the installed Biome's own schema**, so an editor checks
307
+ the file against the version `bun install` put in `node_modules`,
308
+ whichever the command wrote.
309
+ - **What is generated is skipped.** `files.includes` leaves `dist/`, or
310
+ `build/` and `.react-router/`, out, and `vcs.useIgnoreFile` every path
311
+ `.gitignore` names, the project in a git repository or not (with no
312
+ `.gitignore` and no git repository, Biome refuses to run:
313
+ [troubleshooting](troubleshooting.md#-biome-couldnt-find-an-ignore-file-in-the-following-folder-)).
314
+ - **Two rules are off in the `react-router` project, for the scaffold's
315
+ own code.** `noEmptyPattern`: `meta({}: Route.MetaArgs)` is React
316
+ Router's idiom for a route module's function that reads none of its
317
+ arguments. `noSvgWithoutTitle`, under `app/welcome/` alone: the welcome
318
+ page's logos, which a project replaces.
319
+ - **`css.parser.tailwindDirectives`** lets Biome read Tailwind v4's
320
+ `@import "tailwindcss"` and `@theme` in `app/app.css`.
321
+
322
+ `@biomejs/biome` is a devDependency pinned exactly, as Biome recommends,
323
+ since a release may format differently. The command moves it to the
324
+ newest patch of the same minor and keeps it exact: a minor may add a
325
+ recommended rule the template was not checked against
326
+ ([Versions](#versions)). To move it later:
327
+
328
+ ```sh
329
+ bun add --dev --exact @biomejs/biome@latest
330
+ bunx biome migrate --write
331
+ ```
332
+
333
+ | script | runs |
334
+ | --- | --- |
335
+ | `bun run check` | `biome check --write`: lint, format and sort imports, fixing what it can |
336
+ | `bun run lint` | `biome lint` |
337
+ | `bun run format` | `biome format --write` |
338
+ | `bun run check:ci` | `biome ci`: changes nothing, and fails on any error |
339
+ | `bun run verify` | `check:ci`, `typecheck`, then `test` (`api`) or `build` (`react-router`) |
340
+
341
+ The read-only one is `check:ci`, not `ci`: `bun ci` is Bun's
342
+ `bun install --frozen-lockfile`, and a script named `ci` would only run
343
+ as `bun run ci`. A CI job runs:
344
+
345
+ ```sh
346
+ bun install --frozen-lockfile
347
+ bun run verify
348
+ ```
349
+
350
+ `.vscode/extensions.json` recommends Biome's extension, `biomejs.biome`,
351
+ and `.vscode/settings.json` makes it the default formatter, formatting on
352
+ save. Another editor reads `biome.json` through Biome's own extension for
353
+ it.
354
+
231
355
  ## Docker
232
356
 
233
357
  Each project's `Dockerfile` builds it on Bun's official `oven/bun:1`
@@ -319,7 +443,8 @@ are in
319
443
  A template ships the versions it was generated with, and React Router's
320
444
  lags behind its own releases. So before installing, the command
321
445
  asks the registry for every dependency's versions, and writes `^` the
322
- newest one alxia accepts:
446
+ newest one alxia accepts, or the version alone for a dependency the
447
+ template pins exactly:
323
448
 
324
449
  | dependency | moved to the newest within |
325
450
  | --- | --- |
@@ -328,6 +453,7 @@ newest one alxia accepts:
328
453
  | `zod` | `^4.2.0`, `@alxia/zod`'s |
329
454
  | `vite` | `^7.0.0 \|\| ^8.0.0`, `@alxia/react-router`'s |
330
455
  | `react-router`, `@react-router/*` | `^8.0.0`, `@alxia/react-router`'s; the `@react-router/*` packages take `react-router`'s version, which `@react-router/node` pins exactly |
456
+ | `@biomejs/biome` | its own minor, from the exact version the template pins (`~2.5.15`): written exactly, `2.5.16`, never `^` |
331
457
  | anything else: `react`, `isbot`, Tailwind, `@types/*` | no alxia range: npm's `latest` |
332
458
 
333
459
  The command prints each move, and each newer major it left out:
package/docs/roadmap.md CHANGED
@@ -30,6 +30,19 @@ Nothing scheduled yet.
30
30
 
31
31
  ### Next release
32
32
 
33
+ - **Both projects lint and format with Biome.** Each has a `biome.json`
34
+ of its own (recommended rules, spaces and double quotes, imports
35
+ sorted, the build output skipped), `@biomejs/biome` pinned exactly,
36
+ the scripts `lint`, `format`, `check`, `check:ci` and `verify`, and
37
+ `.vscode/` recommending Biome's extension. A new project passes
38
+ `bun run check:ci` with no finding.
39
+ - **The project's name in its README.** The name given to the command,
40
+ normalised, replaces the template's own, as a whole word, in every text
41
+ file, as the README's
42
+ `docker build -t` and `docker run`.
43
+
44
+ ### 0.1.4
45
+
33
46
  - **The images run on Alpine.** Every `Dockerfile`'s final stage is
34
47
  `oven/bun:1-alpine`, the build stages staying on `oven/bun:1`: each
35
48
  image is about 130 MB, where it was about 345 MB. The `api` project
@@ -41,6 +41,12 @@ nothing — the symptom.
41
41
  - [`error: … is linked against glibc (DT_NEEDED libm.so.6), but this Bun build uses musl.`](#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl)
42
42
  - [The project's `@alxia/*` are older than npm's latest](#the-projects-alxia-are-older-than-npms-latest)
43
43
 
44
+ **Biome**
45
+
46
+ - [`bun ci` installs, and checks nothing](#bun-ci-installs-and-checks-nothing)
47
+ - [`× Found a nested root configuration, but there's already a root configuration.`](#-found-a-nested-root-configuration-but-theres-already-a-root-configuration)
48
+ - [`× Biome couldn't find an ignore file in the following folder: …`](#-biome-couldnt-find-an-ignore-file-in-the-following-folder-)
49
+
44
50
  ## Before it runs
45
51
 
46
52
  ### `error: GET https://registry.npmjs.org/@alxia%2fcreate - 404`
@@ -372,3 +378,64 @@ an existing project
372
378
  `bun add @alxia/core@latest @alxia/react-router@latest` (`react-router`), reading
373
379
  [`@alxia/core`'s upgrading page](https://github.com/softistx/alxia/blob/develop/packages/core/docs/upgrading.md)
374
380
  for what a minor changed.
381
+
382
+ ## Biome
383
+
384
+ ### `bun ci` installs, and checks nothing
385
+
386
+ **Symptom:** `bun ci` in a project prints an install, `bun install
387
+ v1.4.2`, and no Biome output; a CI step meant to lint passes whatever the
388
+ code.
389
+
390
+ **Why:** `bun ci` is Bun's own command, `bun install --frozen-lockfile`,
391
+ and a command Bun knows wins over a script. The projects name their
392
+ read-only check `check:ci` for that reason
393
+ ([Lint and format](guide.md#lint-and-format)).
394
+
395
+ **Fix:** run the script by `bun run`:
396
+
397
+ ```sh
398
+ bun run check:ci # biome ci alone
399
+ bun run verify # biome ci, typecheck, then test or build
400
+ ```
401
+
402
+ ### `× Found a nested root configuration, but there's already a root configuration.`
403
+
404
+ **When:** Biome runs from a folder above the project that has its own
405
+ `biome.json`: the project was created inside a monorepo, and `biome ci`
406
+ runs from the monorepo's root.
407
+
408
+ **Why:** the project's `biome.json` is a root configuration, so that it
409
+ works on its own. Biome takes one root per run; a configuration below it
410
+ must say it is nested.
411
+
412
+ **Fix:** to keep the monorepo's settings and add the project's, make the
413
+ project's nested, at the top of its `biome.json`:
414
+
415
+ ```json
416
+ {
417
+ "root": false,
418
+ "extends": "//",
419
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json"
420
+ }
421
+ ```
422
+
423
+ and keep the rest of the file. `"extends": "//"` takes the root's
424
+ settings, which the project's own override. To use the monorepo's
425
+ settings alone, delete the project's `biome.json` and its
426
+ `@biomejs/biome`.
427
+
428
+ ### `× Biome couldn't find an ignore file in the following folder: …`
429
+
430
+ **When:** `bun run check:ci`, `check`, `lint` or `format` in a project
431
+ with no `.gitignore`, deleted or renamed, and outside any git repository.
432
+ Inside one, Biome reads git's ignore rules and runs.
433
+
434
+ **Why:** `biome.json` sets `vcs.useIgnoreFile`, which skips what
435
+ `.gitignore` names. Inside a git repository Biome falls back on git's
436
+ own ignore rules; with no `.gitignore` and no repository, it refuses to
437
+ run rather than skip nothing.
438
+
439
+ **Fix:** put a `.gitignore` back, even an empty one, or set
440
+ `"useIgnoreFile": false` in `biome.json`'s `vcs`: `files.includes` still
441
+ skips the build output.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alxia/create",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Start an alxia app: `bun create @alxia` writes an API with Zod, or React Router's official template served by alxia",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["biomejs.biome"]
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "editor.defaultFormatter": "biomejs.biome",
3
+ "editor.formatOnSave": true,
4
+ "editor.codeActionsOnSave": {
5
+ "source.fixAll.biome": "explicit",
6
+ "source.organizeImports.biome": "explicit"
7
+ }
8
+ }
@@ -7,6 +7,7 @@ An [alxia](https://github.com/softistx/alxia) app with
7
7
  its own hook, `requireKey`, answers 401 without the `x-api-key` header.
8
8
  - `src/server.ts`: listens on `PORT`, 3000 by default.
9
9
  - `src/app.spec.ts`: `app.request()` and `@alxia/client`, no port.
10
+ - `biome.json`: Biome's lint and format settings ([Lint and format](#lint-and-format)).
10
11
 
11
12
  ## Environment
12
13
 
@@ -37,6 +38,32 @@ bun test # src/app.spec.ts: in process, and through the typed client
37
38
  bun run typecheck
38
39
  ```
39
40
 
41
+ ## Lint and format
42
+
43
+ [Biome](https://biomejs.dev) lints and formats the project, as `biome.json`
44
+ sets it: Biome's recommended rules, spaces, double quotes, imports
45
+ sorted. What the build writes, `dist/`, is skipped.
46
+
47
+ ```sh
48
+ bun run check # lint, format and sort imports, fixing what it can
49
+ bun run lint # lint only
50
+ bun run format # format only, in place
51
+ bun run check:ci # what CI runs: changes nothing, fails on an error
52
+ bun run verify # check:ci, then typecheck, then test
53
+ ```
54
+
55
+ `bun run check:ci`, not `bun ci`: `bun ci` is Bun's frozen-lockfile
56
+ install. In VS Code, `.vscode/` recommends Biome's extension and formats
57
+ on save with it.
58
+
59
+ `@biomejs/biome` is pinned exactly, since a release of Biome may format
60
+ differently. To move it:
61
+
62
+ ```sh
63
+ bun add --dev --exact @biomejs/biome@latest
64
+ bunx biome migrate --write
65
+ ```
66
+
40
67
  ## Build
41
68
 
42
69
  `bun run build` bundles the server and its dependencies into one file,
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "includes": ["**", "!!**/dist"]
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "space"
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "preset": "recommended",
19
+ "complexity": {
20
+ "useLiteralKeys": "off"
21
+ }
22
+ }
23
+ },
24
+ "assist": {
25
+ "enabled": true,
26
+ "actions": {
27
+ "source": {
28
+ "organizeImports": "on"
29
+ }
30
+ }
31
+ }
32
+ }
@@ -1,21 +1,27 @@
1
1
  {
2
- "name": "my-api",
3
- "private": true,
4
- "type": "module",
5
- "scripts": {
6
- "dev": "bun --watch src/server.ts",
7
- "build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked",
8
- "start": "bun dist/server.js",
9
- "test": "bun test",
10
- "typecheck": "tsc --noEmit"
11
- },
12
- "dependencies": {
13
- "@alxia/core": "workspace:^",
14
- "zod": "^4.2.0"
15
- },
16
- "devDependencies": {
17
- "@alxia/client": "workspace:^",
18
- "@types/bun": "^1.4.2",
19
- "typescript": "^6.0.3"
20
- }
2
+ "name": "my-api",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bun --watch src/server.ts",
7
+ "build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked",
8
+ "start": "bun dist/server.js",
9
+ "test": "bun test",
10
+ "typecheck": "tsc --noEmit",
11
+ "lint": "biome lint",
12
+ "format": "biome format --write",
13
+ "check": "biome check --write",
14
+ "check:ci": "biome ci",
15
+ "verify": "bun run check:ci && bun run typecheck && bun run test"
16
+ },
17
+ "dependencies": {
18
+ "@alxia/core": "workspace:^",
19
+ "zod": "^4.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "@alxia/client": "workspace:^",
23
+ "@biomejs/biome": "2.5.15",
24
+ "@types/bun": "^1.4.2",
25
+ "typescript": "^6.0.3"
26
+ }
21
27
  }
@@ -1,46 +1,46 @@
1
- import { expect, test } from 'bun:test';
2
- import { client } from '@alxia/client';
3
- import { apiKey, app } from './app';
1
+ import { expect, test } from "bun:test";
2
+ import { client } from "@alxia/client";
3
+ import { apiKey, app } from "./app";
4
4
 
5
- const json = { 'content-type': 'application/json', 'x-api-key': apiKey };
5
+ const json = { "content-type": "application/json", "x-api-key": apiKey };
6
6
 
7
- test('creates a todo from JSON', async () => {
8
- const response = await app.request('/todos', {
9
- method: 'POST',
10
- headers: json,
11
- body: JSON.stringify({ title: 'Write a route' }),
12
- });
13
- expect(response.status).toBe(201);
14
- expect(await response.json()).toEqual({
15
- id: expect.any(Number),
16
- title: 'Write a route',
17
- done: false,
18
- });
7
+ test("creates a todo from JSON", async () => {
8
+ const response = await app.request("/todos", {
9
+ method: "POST",
10
+ headers: json,
11
+ body: JSON.stringify({ title: "Write a route" }),
12
+ });
13
+ expect(response.status).toBe(201);
14
+ expect(await response.json()).toEqual({
15
+ id: expect.any(Number),
16
+ title: "Write a route",
17
+ done: false,
18
+ });
19
19
  });
20
20
 
21
- test('refuses an empty title with a 400 naming it', async () => {
22
- const response = await app.request('/todos', {
23
- method: 'POST',
24
- headers: json,
25
- body: JSON.stringify({ title: '' }),
26
- });
27
- expect(response.status).toBe(400);
28
- expect((await response.json()).issues[0].path).toEqual(['title']);
21
+ test("refuses an empty title with a 400 naming it", async () => {
22
+ const response = await app.request("/todos", {
23
+ method: "POST",
24
+ headers: json,
25
+ body: JSON.stringify({ title: "" }),
26
+ });
27
+ expect(response.status).toBe(400);
28
+ expect((await response.json()).issues[0].path).toEqual(["title"]);
29
29
  });
30
30
 
31
- test('the typed client reads each status the route answers', async () => {
32
- // Given the app itself, the client calls its fetch in process.
33
- const api = client(app, { headers: { 'x-api-key': apiKey } });
34
- const created = await api.post('/todos', {
35
- body: { title: 'Call it typed' },
36
- });
37
- if (created.status !== 201) throw new Error(`got ${created.status}`);
38
- expect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type
31
+ test("the typed client reads each status the route answers", async () => {
32
+ // Given the app itself, the client calls its fetch in process.
33
+ const api = client(app, { headers: { "x-api-key": apiKey } });
34
+ const created = await api.post("/todos", {
35
+ body: { title: "Call it typed" },
36
+ });
37
+ if (created.status !== 201) throw new Error(`got ${created.status}`);
38
+ expect(created.data.title).toBe("Call it typed"); // data is the Todo schema's type
39
39
 
40
- const anonymous = await client(app).post('/todos', {
41
- body: { title: 'No key' },
42
- });
43
- expect(anonymous.status).toBe(401);
44
- if (anonymous.status === 401)
45
- expect(anonymous.data.error).toBe('unauthorized');
40
+ const anonymous = await client(app).post("/todos", {
41
+ body: { title: "No key" },
42
+ });
43
+ expect(anonymous.status).toBe(401);
44
+ if (anonymous.status === 401)
45
+ expect(anonymous.data.error).toBe("unauthorized");
46
46
  });