@alxia/create 0.1.3 → 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.
@@ -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
  });
@@ -1,33 +1,33 @@
1
- import { alxia, defineHook } from '@alxia/core';
2
- import { z } from 'zod';
1
+ import { alxia, defineHook } from "@alxia/core";
2
+ import { z } from "zod";
3
3
 
4
4
  const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
5
5
  const NewTodo = z.object({ title: z.string().min(1) });
6
6
 
7
7
  /** Set API_KEY in the environment: this default is for development. */
8
- export const apiKey = Bun.env['API_KEY'] ?? 'dev-key';
8
+ export const apiKey = Bun.env["API_KEY"] ?? "dev-key";
9
9
 
10
10
  // A hook of the routes it is given to: it answers 401 without the key, and
11
11
  // that 401 joins the type of each, so the client reads it.
12
12
  const requireKey = defineHook(({ request, reply }) =>
13
- request.headers.get('x-api-key') === apiKey
14
- ? undefined
15
- : reply(401, { error: 'unauthorized' as const }),
13
+ request.headers.get("x-api-key") === apiKey
14
+ ? undefined
15
+ : reply(401, { error: "unauthorized" as const }),
16
16
  );
17
17
 
18
18
  const todos: z.infer<typeof Todo>[] = [];
19
19
 
20
20
  export const app = alxia()
21
- .decorate({ todos })
22
- .post(
23
- '/todos',
24
- [requireKey],
25
- { body: NewTodo, response: { 201: Todo } },
26
- ({ body, todos, reply }) => {
27
- const todo = { id: todos.length + 1, title: body.title, done: false };
28
- todos.push(todo);
29
- return reply.created(todo);
30
- },
31
- );
21
+ .decorate({ todos })
22
+ .post(
23
+ "/todos",
24
+ [requireKey],
25
+ { body: NewTodo, response: { 201: Todo } },
26
+ ({ body, todos, reply }) => {
27
+ const todo = { id: todos.length + 1, title: body.title, done: false };
28
+ todos.push(todo);
29
+ return reply.created(todo);
30
+ },
31
+ );
32
32
 
33
33
  export type App = typeof app;
@@ -1,4 +1,18 @@
1
- import { app } from './app';
1
+ import { app } from "./app";
2
2
 
3
- const server = app.listen(Number(Bun.env['PORT'] ?? 3000));
3
+ // Stop as the platform asks. In a container Bun is process 1, which a
4
+ // signal with no handler does not stop: `docker stop` would wait.
5
+ for (const signal of ["SIGINT", "SIGTERM"] as const) {
6
+ process.once(signal, () => {
7
+ void app.stop().then(
8
+ () => process.exit(0),
9
+ (error: unknown) => {
10
+ console.error(error);
11
+ process.exit(1);
12
+ },
13
+ );
14
+ });
15
+ }
16
+
17
+ const server = app.listen(Number(Bun.env["PORT"] ?? 3000));
4
18
  console.log(`listening on ${server.url}`);
@@ -1,25 +1,25 @@
1
1
  {
2
- "compilerOptions": {
3
- "types": ["bun"],
4
- "lib": ["ESNext", "DOM"],
5
- "target": "ESNext",
6
- "module": "ESNext",
7
- "moduleDetection": "force",
8
- "moduleResolution": "bundler",
9
- "verbatimModuleSyntax": true,
10
- "noEmit": true,
11
- "skipLibCheck": true,
12
- "resolveJsonModule": true,
13
- "strict": true,
14
- "noImplicitOverride": true,
15
- "noImplicitReturns": true,
16
- "noFallthroughCasesInSwitch": true,
17
- "noUncheckedIndexedAccess": true,
18
- "exactOptionalPropertyTypes": true,
19
- "noPropertyAccessFromIndexSignature": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "forceConsistentCasingInFileNames": true
23
- },
24
- "include": ["src"]
2
+ "compilerOptions": {
3
+ "types": ["bun"],
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "moduleResolution": "bundler",
9
+ "verbatimModuleSyntax": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "strict": true,
14
+ "noImplicitOverride": true,
15
+ "noImplicitReturns": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "noPropertyAccessFromIndexSignature": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "forceConsistentCasingInFileNames": true
23
+ },
24
+ "include": ["src"]
25
25
  }
@@ -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
+ }
@@ -16,8 +16,10 @@ RUN bun run build
16
16
  # command, and --no-install: with no node_modules, Bun would otherwise
17
17
  # fetch a package the build left out from the registry at startup,
18
18
  # where this fails.
19
- # The server listens on PORT (3000) and HOST (0.0.0.0).
20
- FROM oven/bun:1
19
+ # The server listens on PORT (3000) and HOST (0.0.0.0). On Alpine, about
20
+ # 200 MB lighter: build/ is JavaScript, which runs the same on musl. A
21
+ # native addon built for glibc alone needs oven/bun:1 here.
22
+ FROM oven/bun:1-alpine
21
23
  WORKDIR /app
22
24
  ENV NODE_ENV=production
23
25
  COPY --from=build /app/build ./build
@@ -78,6 +78,33 @@ Make sure to deploy the output of `bun run build`
78
78
  │ └── server/ # Server-side code
79
79
  ```
80
80
 
81
+ ## Lint and format
82
+
83
+ [Biome](https://biomejs.dev) lints and formats the project, as `biome.json`
84
+ sets it: Biome's recommended rules, spaces, double quotes, imports
85
+ sorted. What the build and `react-router typegen` write, `build/` and
86
+ `.react-router/`, is skipped.
87
+
88
+ ```bash
89
+ bun run check # lint, format and sort imports, fixing what it can
90
+ bun run lint # lint only
91
+ bun run format # format only, in place
92
+ bun run check:ci # what CI runs: changes nothing, fails on an error
93
+ bun run verify # check:ci, then typecheck, then build
94
+ ```
95
+
96
+ `bun run check:ci`, not `bun ci`: `bun ci` is Bun's frozen-lockfile
97
+ install. In VS Code, `.vscode/` recommends Biome's extension and formats
98
+ on save with it.
99
+
100
+ `@biomejs/biome` is pinned exactly, since a release of Biome may format
101
+ differently. To move it:
102
+
103
+ ```bash
104
+ bun add --dev --exact @biomejs/biome@latest
105
+ bunx biome migrate --write
106
+ ```
107
+
81
108
  ## Styling
82
109
 
83
110
  This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
@@ -0,0 +1,49 @@
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": ["**", "!!**/build", "!!**/.react-router"]
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "space"
14
+ },
15
+ "css": {
16
+ "parser": {
17
+ "tailwindDirectives": true
18
+ }
19
+ },
20
+ "linter": {
21
+ "enabled": true,
22
+ "rules": {
23
+ "preset": "recommended",
24
+ "correctness": {
25
+ "noEmptyPattern": "off"
26
+ }
27
+ }
28
+ },
29
+ "assist": {
30
+ "enabled": true,
31
+ "actions": {
32
+ "source": {
33
+ "organizeImports": "on"
34
+ }
35
+ }
36
+ },
37
+ "overrides": [
38
+ {
39
+ "includes": ["**/app/welcome/**"],
40
+ "linter": {
41
+ "rules": {
42
+ "a11y": {
43
+ "noSvgWithoutTitle": "off"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ]
49
+ }
@@ -1,8 +1,9 @@
1
1
  @import "tailwindcss";
2
2
 
3
3
  @theme {
4
- --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
5
- "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
4
+ --font-sans:
5
+ "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
6
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
6
7
  }
7
8
 
8
9
  html,
@@ -1,5 +1,5 @@
1
- import type { Route } from "./+types/home";
2
1
  import { Welcome } from "../welcome/welcome";
2
+ import type { Route } from "./+types/home";
3
3
 
4
4
  export function meta({}: Route.MetaArgs) {
5
5
  return [
@@ -1,3 +1,3 @@
1
- import { type RouteConfig, index } from "@react-router/dev/routes";
1
+ import { index, type RouteConfig } from "@react-router/dev/routes";
2
2
 
3
3
  export default [index("routes/home.tsx")] satisfies RouteConfig;
@@ -6,7 +6,12 @@
6
6
  "build": "react-router build",
7
7
  "dev": "react-router dev",
8
8
  "start": "bun build/server/index.js",
9
- "typecheck": "react-router typegen && tsc"
9
+ "typecheck": "react-router typegen && tsc",
10
+ "lint": "biome lint",
11
+ "format": "biome format --write",
12
+ "check": "biome check --write",
13
+ "check:ci": "biome ci",
14
+ "verify": "bun run check:ci && bun run typecheck && bun run build"
10
15
  },
11
16
  "dependencies": {
12
17
  "@alxia/core": "workspace:^",
@@ -19,6 +24,7 @@
19
24
  "react-router": "^8.4.0"
20
25
  },
21
26
  "devDependencies": {
27
+ "@biomejs/biome": "2.5.15",
22
28
  "@react-router/dev": "^8.4.0",
23
29
  "@tailwindcss/vite": "^4.2.2",
24
30
  "@types/node": "^22",