@pylonsync/create-pylon 0.3.283 → 0.3.285

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.
Files changed (89) hide show
  1. package/package.json +1 -1
  2. package/templates/_root/AGENTS.md +2 -2
  3. package/templates/agency/AGENTS.md +57 -2
  4. package/templates/agency/CLAUDE.md +7 -0
  5. package/templates/agency/package.json +5 -1
  6. package/templates/agency/tests/button.test.tsx +16 -0
  7. package/templates/agency/tests/example.test.ts +12 -0
  8. package/templates/agency/tests/setup.ts +5 -0
  9. package/templates/ai-chat/AGENTS.md +57 -2
  10. package/templates/ai-chat/CLAUDE.md +7 -0
  11. package/templates/ai-chat/package.json +5 -1
  12. package/templates/ai-chat/tests/example.test.ts +12 -0
  13. package/templates/ai-chat/tests/setup.ts +5 -0
  14. package/templates/ai-studio/AGENTS.md +57 -2
  15. package/templates/ai-studio/CLAUDE.md +7 -0
  16. package/templates/ai-studio/package.json +5 -1
  17. package/templates/ai-studio/tests/example.test.ts +12 -0
  18. package/templates/ai-studio/tests/setup.ts +5 -0
  19. package/templates/barebones/AGENTS.md +57 -2
  20. package/templates/barebones/CLAUDE.md +7 -0
  21. package/templates/barebones/package.json +5 -1
  22. package/templates/barebones/tests/button.test.tsx +16 -0
  23. package/templates/barebones/tests/example.test.ts +12 -0
  24. package/templates/barebones/tests/setup.ts +5 -0
  25. package/templates/chat/AGENTS.md +57 -2
  26. package/templates/chat/CLAUDE.md +7 -0
  27. package/templates/chat/package.json +5 -1
  28. package/templates/chat/tests/button.test.tsx +16 -0
  29. package/templates/chat/tests/example.test.ts +12 -0
  30. package/templates/chat/tests/setup.ts +5 -0
  31. package/templates/consumer/AGENTS.md +57 -2
  32. package/templates/consumer/CLAUDE.md +7 -0
  33. package/templates/consumer/package.json +5 -1
  34. package/templates/consumer/tests/button.test.tsx +16 -0
  35. package/templates/consumer/tests/example.test.ts +12 -0
  36. package/templates/consumer/tests/setup.ts +5 -0
  37. package/templates/creator/AGENTS.md +57 -2
  38. package/templates/creator/CLAUDE.md +7 -0
  39. package/templates/creator/package.json +5 -1
  40. package/templates/creator/tests/button.test.tsx +16 -0
  41. package/templates/creator/tests/example.test.ts +12 -0
  42. package/templates/creator/tests/setup.ts +5 -0
  43. package/templates/default/AGENTS.md +57 -2
  44. package/templates/default/CLAUDE.md +7 -0
  45. package/templates/default/package.json +5 -1
  46. package/templates/default/tests/button.test.tsx +16 -0
  47. package/templates/default/tests/example.test.ts +23 -0
  48. package/templates/default/tests/setup.ts +5 -0
  49. package/templates/directory/AGENTS.md +57 -2
  50. package/templates/directory/CLAUDE.md +7 -0
  51. package/templates/directory/package.json +5 -1
  52. package/templates/directory/tests/button.test.tsx +16 -0
  53. package/templates/directory/tests/example.test.ts +12 -0
  54. package/templates/directory/tests/setup.ts +5 -0
  55. package/templates/local-service/AGENTS.md +57 -2
  56. package/templates/local-service/CLAUDE.md +7 -0
  57. package/templates/local-service/package.json +5 -1
  58. package/templates/local-service/tests/button.test.tsx +16 -0
  59. package/templates/local-service/tests/example.test.ts +12 -0
  60. package/templates/local-service/tests/setup.ts +5 -0
  61. package/templates/marketplace/AGENTS.md +57 -2
  62. package/templates/marketplace/CLAUDE.md +7 -0
  63. package/templates/marketplace/package.json +5 -1
  64. package/templates/marketplace/tests/example.test.ts +12 -0
  65. package/templates/marketplace/tests/setup.ts +5 -0
  66. package/templates/restaurant/AGENTS.md +57 -2
  67. package/templates/restaurant/CLAUDE.md +7 -0
  68. package/templates/restaurant/package.json +5 -1
  69. package/templates/restaurant/tests/button.test.tsx +16 -0
  70. package/templates/restaurant/tests/example.test.ts +12 -0
  71. package/templates/restaurant/tests/setup.ts +5 -0
  72. package/templates/shop/AGENTS.md +57 -2
  73. package/templates/shop/CLAUDE.md +7 -0
  74. package/templates/shop/package.json +5 -1
  75. package/templates/shop/tests/button.test.tsx +16 -0
  76. package/templates/shop/tests/example.test.ts +12 -0
  77. package/templates/shop/tests/setup.ts +5 -0
  78. package/templates/todo/AGENTS.md +57 -2
  79. package/templates/todo/CLAUDE.md +7 -0
  80. package/templates/todo/package.json +5 -1
  81. package/templates/todo/tests/button.test.tsx +16 -0
  82. package/templates/todo/tests/example.test.ts +12 -0
  83. package/templates/todo/tests/setup.ts +5 -0
  84. package/templates/waitlist/AGENTS.md +57 -2
  85. package/templates/waitlist/CLAUDE.md +7 -0
  86. package/templates/waitlist/package.json +5 -1
  87. package/templates/waitlist/tests/button.test.tsx +16 -0
  88. package/templates/waitlist/tests/example.test.ts +12 -0
  89. package/templates/waitlist/tests/setup.ts +5 -0
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — working in a Pylon project
2
2
 
3
- Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is the **llms-full.txt** at https://docs.pylonsync.com/llms-full.txt. Read it before guessing an API name.
4
4
 
5
5
  ## Directory conventions
6
6
 
@@ -37,6 +37,61 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
37
37
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
38
  - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
39
 
40
+ ## Testing
41
+
42
+ `pylon test` discovers every `*.test.ts` / `*.test.tsx` file under `tests/` (or `functions/`) and runs it with **Bun's test runner** (`import { test, expect } from "bun:test"`). Run the suite with `pylon test` (or `npm test`); filter with `pylon test <substring>`. This template ships `bunfig.toml` + `tests/setup.ts` (registers happy-dom) so component tests render out of the box, plus starter tests under `tests/` — replace them with your own.
43
+
44
+ **Tier 1 — pure logic (reach for this first).** Keep the decisions that matter — access/plan gating, pricing, credit math, validation, formatting — in pure functions in `lib/`, and test those exhaustively. No server, instant, and it's where the real bugs live. Keep your `query`/`mutation`/`action` handlers as thin wrappers around them, so the logic is testable without a running app.
45
+
46
+ ```ts
47
+ import { expect, test } from "bun:test";
48
+ import { productBySlug } from "../lib/site.config";
49
+
50
+ test("unknown slug → undefined", () => {
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+ ```
54
+
55
+ **Tier 2 — React components.** `@testing-library/react` + happy-dom are already wired (`tests/setup.ts`). Render and assert. The template uses the classic JSX transform, so add `import React from "react"` in `.tsx` tests. For a component that reads Pylon data hooks, **mock the boundary**, then dynamic-`import` the component so the mock is in place first:
56
+
57
+ ```tsx
58
+ import { test, expect, mock } from "bun:test";
59
+ import React from "react";
60
+ import { render, screen } from "@testing-library/react";
61
+
62
+ mock.module("@pylonsync/react", () => ({
63
+ db: { useQuery: () => ({ data: [{ id: "1", name: "Acme" }], loading: false }) },
64
+ }));
65
+ const { OrgList } = await import("../app/orgs/org-list"); // your component
66
+
67
+ test("renders orgs from the query", () => {
68
+ render(<OrgList />);
69
+ expect(screen.getByText("Acme")).toBeDefined();
70
+ });
71
+ ```
72
+
73
+ **Tier 3 — functions over HTTP (only when Tier 1 can't cover it).** A handler's full behavior (policies, `ctx.db`, auth) lives in the running app. Start `pylon dev` in another terminal and call the API; `resetDb()` from `@pylonsync/functions` clears the in-memory DB between cases (no-ops if the server's down, refuses production).
74
+
75
+ ```ts
76
+ import { afterEach, expect, test } from "bun:test";
77
+ import { resetDb } from "@pylonsync/functions";
78
+
79
+ const BASE = "http://localhost:4321";
80
+ afterEach(() => resetDb(BASE)); // or installTestIsolation(BASE) once at top-of-file
81
+
82
+ test("createThing then read it back", async () => {
83
+ const t = await fetch(`${BASE}/api/fn/createThing`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ name: "hello" }),
87
+ }).then((r) => r.json());
88
+ const rows = await fetch(`${BASE}/api/entities/Thing`).then((r) => r.json());
89
+ expect(rows.some((r: { id: string }) => r.id === t.id)).toBe(true);
90
+ });
91
+ ```
92
+
93
+ `pylon test:security` is a separate adversarial probe — it hits a running app and reports auth/policy holes (run `pylon dev`, then `pylon test:security`).
94
+
40
95
  ## Use the CLI — don't guess
41
96
 
42
97
  | Need | Command |
@@ -58,4 +113,4 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
58
113
 
59
114
  `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
115
 
61
- For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
116
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **https://docs.pylonsync.com/llms-full.txt**.
@@ -0,0 +1,7 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this Pylon project lives in **AGENTS.md** — the cross-editor
4
+ standard that Claude Code, Cursor, Codex, and others all read. It's imported
5
+ below so Claude Code picks it up automatically:
6
+
7
+ @AGENTS.md
@@ -6,7 +6,8 @@
6
6
  "scripts": {
7
7
  "dev": "pylon dev",
8
8
  "deploy": "pylon deploy",
9
- "check": "tsc --noEmit"
9
+ "check": "tsc --noEmit",
10
+ "test": "pylon test"
10
11
  },
11
12
  "dependencies": {
12
13
  "@pylonsync/react": "^__PYLON_VERSION__",
@@ -26,6 +27,9 @@
26
27
  },
27
28
  "devDependencies": {
28
29
  "@pylonsync/cli": "^__PYLON_VERSION__",
30
+ "@happy-dom/global-registrator": "^20.10.0",
31
+ "@testing-library/dom": "^10.4.0",
32
+ "@testing-library/react": "^16.3.0",
29
33
  "@types/node": "^22.0.0",
30
34
  "@types/react": "^19.0.0",
31
35
  "@types/react-dom": "^19.0.0",
@@ -0,0 +1,16 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ // This project uses the classic JSX transform, so .tsx tests import React.
3
+ import React from "react";
4
+ import { render, screen, cleanup } from "@testing-library/react";
5
+ import { Button } from "../components/ui/button";
6
+
7
+ afterEach(cleanup);
8
+
9
+ // A React component test. happy-dom + @testing-library/react are wired via
10
+ // bunfig.toml — render a component and assert on the DOM. For a component that
11
+ // reads Pylon data hooks (db.useQuery, callFn), mock the boundary first — see
12
+ // AGENTS.md → Testing (Tier 2).
13
+ test("Button renders its label", () => {
14
+ render(<Button>Click me</Button>);
15
+ expect(screen.getByRole("button", { name: "Click me" })).toBeDefined();
16
+ });
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ // Your first test. Run the suite with: pylon test (or: npm test)
4
+ //
5
+ // `pylon test` discovers every *.test.ts file under tests/ (or functions/) and
6
+ // runs it with Bun's test runner against an in-memory Pylon. Replace this
7
+ // placeholder with tests for your functions and helpers — see AGENTS.md →
8
+ // Testing for the pure-logic and HTTP-against-`pylon dev` patterns.
9
+
10
+ test("placeholder — replace me with a real test", () => {
11
+ expect(true).toBe(true);
12
+ });
@@ -0,0 +1,5 @@
1
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
2
+
3
+ // Make document/window available so @testing-library/react can render. Loaded
4
+ // via bunfig.toml's [test] preload, before any test file runs.
5
+ GlobalRegistrator.register();
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — working in a Pylon project
2
2
 
3
- Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is the **llms-full.txt** at https://docs.pylonsync.com/llms-full.txt. Read it before guessing an API name.
4
4
 
5
5
  ## Directory conventions
6
6
 
@@ -37,6 +37,61 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
37
37
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
38
  - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
39
 
40
+ ## Testing
41
+
42
+ `pylon test` discovers every `*.test.ts` / `*.test.tsx` file under `tests/` (or `functions/`) and runs it with **Bun's test runner** (`import { test, expect } from "bun:test"`). Run the suite with `pylon test` (or `npm test`); filter with `pylon test <substring>`. This template ships `bunfig.toml` + `tests/setup.ts` (registers happy-dom) so component tests render out of the box, plus starter tests under `tests/` — replace them with your own.
43
+
44
+ **Tier 1 — pure logic (reach for this first).** Keep the decisions that matter — access/plan gating, pricing, credit math, validation, formatting — in pure functions in `lib/`, and test those exhaustively. No server, instant, and it's where the real bugs live. Keep your `query`/`mutation`/`action` handlers as thin wrappers around them, so the logic is testable without a running app.
45
+
46
+ ```ts
47
+ import { expect, test } from "bun:test";
48
+ import { productBySlug } from "../lib/site.config";
49
+
50
+ test("unknown slug → undefined", () => {
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+ ```
54
+
55
+ **Tier 2 — React components.** `@testing-library/react` + happy-dom are already wired (`tests/setup.ts`). Render and assert. The template uses the classic JSX transform, so add `import React from "react"` in `.tsx` tests. For a component that reads Pylon data hooks, **mock the boundary**, then dynamic-`import` the component so the mock is in place first:
56
+
57
+ ```tsx
58
+ import { test, expect, mock } from "bun:test";
59
+ import React from "react";
60
+ import { render, screen } from "@testing-library/react";
61
+
62
+ mock.module("@pylonsync/react", () => ({
63
+ db: { useQuery: () => ({ data: [{ id: "1", name: "Acme" }], loading: false }) },
64
+ }));
65
+ const { OrgList } = await import("../app/orgs/org-list"); // your component
66
+
67
+ test("renders orgs from the query", () => {
68
+ render(<OrgList />);
69
+ expect(screen.getByText("Acme")).toBeDefined();
70
+ });
71
+ ```
72
+
73
+ **Tier 3 — functions over HTTP (only when Tier 1 can't cover it).** A handler's full behavior (policies, `ctx.db`, auth) lives in the running app. Start `pylon dev` in another terminal and call the API; `resetDb()` from `@pylonsync/functions` clears the in-memory DB between cases (no-ops if the server's down, refuses production).
74
+
75
+ ```ts
76
+ import { afterEach, expect, test } from "bun:test";
77
+ import { resetDb } from "@pylonsync/functions";
78
+
79
+ const BASE = "http://localhost:4321";
80
+ afterEach(() => resetDb(BASE)); // or installTestIsolation(BASE) once at top-of-file
81
+
82
+ test("createThing then read it back", async () => {
83
+ const t = await fetch(`${BASE}/api/fn/createThing`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ name: "hello" }),
87
+ }).then((r) => r.json());
88
+ const rows = await fetch(`${BASE}/api/entities/Thing`).then((r) => r.json());
89
+ expect(rows.some((r: { id: string }) => r.id === t.id)).toBe(true);
90
+ });
91
+ ```
92
+
93
+ `pylon test:security` is a separate adversarial probe — it hits a running app and reports auth/policy holes (run `pylon dev`, then `pylon test:security`).
94
+
40
95
  ## Use the CLI — don't guess
41
96
 
42
97
  | Need | Command |
@@ -58,4 +113,4 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
58
113
 
59
114
  `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
115
 
61
- For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
116
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **https://docs.pylonsync.com/llms-full.txt**.
@@ -0,0 +1,7 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this Pylon project lives in **AGENTS.md** — the cross-editor
4
+ standard that Claude Code, Cursor, Codex, and others all read. It's imported
5
+ below so Claude Code picks it up automatically:
6
+
7
+ @AGENTS.md
@@ -6,7 +6,8 @@
6
6
  "scripts": {
7
7
  "dev": "pylon dev",
8
8
  "deploy": "pylon deploy",
9
- "check": "tsc --noEmit"
9
+ "check": "tsc --noEmit",
10
+ "test": "pylon test"
10
11
  },
11
12
  "dependencies": {
12
13
  "@pylonsync/react": "^__PYLON_VERSION__",
@@ -26,6 +27,9 @@
26
27
  },
27
28
  "devDependencies": {
28
29
  "@pylonsync/cli": "^__PYLON_VERSION__",
30
+ "@happy-dom/global-registrator": "^20.10.0",
31
+ "@testing-library/dom": "^10.4.0",
32
+ "@testing-library/react": "^16.3.0",
29
33
  "@types/node": "^22.0.0",
30
34
  "@types/react": "^19.0.0",
31
35
  "@types/react-dom": "^19.0.0",
@@ -0,0 +1,16 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ // This project uses the classic JSX transform, so .tsx tests import React.
3
+ import React from "react";
4
+ import { render, screen, cleanup } from "@testing-library/react";
5
+ import { Button } from "../components/ui/button";
6
+
7
+ afterEach(cleanup);
8
+
9
+ // A React component test. happy-dom + @testing-library/react are wired via
10
+ // bunfig.toml — render a component and assert on the DOM. For a component that
11
+ // reads Pylon data hooks (db.useQuery, callFn), mock the boundary first — see
12
+ // AGENTS.md → Testing (Tier 2).
13
+ test("Button renders its label", () => {
14
+ render(<Button>Click me</Button>);
15
+ expect(screen.getByRole("button", { name: "Click me" })).toBeDefined();
16
+ });
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ // Your first test. Run the suite with: pylon test (or: npm test)
4
+ //
5
+ // `pylon test` discovers every *.test.ts file under tests/ (or functions/) and
6
+ // runs it with Bun's test runner against an in-memory Pylon. Replace this
7
+ // placeholder with tests for your functions and helpers — see AGENTS.md →
8
+ // Testing for the pure-logic and HTTP-against-`pylon dev` patterns.
9
+
10
+ test("placeholder — replace me with a real test", () => {
11
+ expect(true).toBe(true);
12
+ });
@@ -0,0 +1,5 @@
1
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
2
+
3
+ // Make document/window available so @testing-library/react can render. Loaded
4
+ // via bunfig.toml's [test] preload, before any test file runs.
5
+ GlobalRegistrator.register();
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — working in a Pylon project
2
2
 
3
- Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is the **llms-full.txt** at https://docs.pylonsync.com/llms-full.txt. Read it before guessing an API name.
4
4
 
5
5
  ## Directory conventions
6
6
 
@@ -37,6 +37,61 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
37
37
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
38
  - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
39
 
40
+ ## Testing
41
+
42
+ `pylon test` discovers every `*.test.ts` / `*.test.tsx` file under `tests/` (or `functions/`) and runs it with **Bun's test runner** (`import { test, expect } from "bun:test"`). Run the suite with `pylon test` (or `npm test`); filter with `pylon test <substring>`. This template ships `bunfig.toml` + `tests/setup.ts` (registers happy-dom) so component tests render out of the box, plus starter tests under `tests/` — replace them with your own.
43
+
44
+ **Tier 1 — pure logic (reach for this first).** Keep the decisions that matter — access/plan gating, pricing, credit math, validation, formatting — in pure functions in `lib/`, and test those exhaustively. No server, instant, and it's where the real bugs live. Keep your `query`/`mutation`/`action` handlers as thin wrappers around them, so the logic is testable without a running app.
45
+
46
+ ```ts
47
+ import { expect, test } from "bun:test";
48
+ import { productBySlug } from "../lib/site.config";
49
+
50
+ test("unknown slug → undefined", () => {
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+ ```
54
+
55
+ **Tier 2 — React components.** `@testing-library/react` + happy-dom are already wired (`tests/setup.ts`). Render and assert. The template uses the classic JSX transform, so add `import React from "react"` in `.tsx` tests. For a component that reads Pylon data hooks, **mock the boundary**, then dynamic-`import` the component so the mock is in place first:
56
+
57
+ ```tsx
58
+ import { test, expect, mock } from "bun:test";
59
+ import React from "react";
60
+ import { render, screen } from "@testing-library/react";
61
+
62
+ mock.module("@pylonsync/react", () => ({
63
+ db: { useQuery: () => ({ data: [{ id: "1", name: "Acme" }], loading: false }) },
64
+ }));
65
+ const { OrgList } = await import("../app/orgs/org-list"); // your component
66
+
67
+ test("renders orgs from the query", () => {
68
+ render(<OrgList />);
69
+ expect(screen.getByText("Acme")).toBeDefined();
70
+ });
71
+ ```
72
+
73
+ **Tier 3 — functions over HTTP (only when Tier 1 can't cover it).** A handler's full behavior (policies, `ctx.db`, auth) lives in the running app. Start `pylon dev` in another terminal and call the API; `resetDb()` from `@pylonsync/functions` clears the in-memory DB between cases (no-ops if the server's down, refuses production).
74
+
75
+ ```ts
76
+ import { afterEach, expect, test } from "bun:test";
77
+ import { resetDb } from "@pylonsync/functions";
78
+
79
+ const BASE = "http://localhost:4321";
80
+ afterEach(() => resetDb(BASE)); // or installTestIsolation(BASE) once at top-of-file
81
+
82
+ test("createThing then read it back", async () => {
83
+ const t = await fetch(`${BASE}/api/fn/createThing`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ name: "hello" }),
87
+ }).then((r) => r.json());
88
+ const rows = await fetch(`${BASE}/api/entities/Thing`).then((r) => r.json());
89
+ expect(rows.some((r: { id: string }) => r.id === t.id)).toBe(true);
90
+ });
91
+ ```
92
+
93
+ `pylon test:security` is a separate adversarial probe — it hits a running app and reports auth/policy holes (run `pylon dev`, then `pylon test:security`).
94
+
40
95
  ## Use the CLI — don't guess
41
96
 
42
97
  | Need | Command |
@@ -58,4 +113,4 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
58
113
 
59
114
  `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
115
 
61
- For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
116
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **https://docs.pylonsync.com/llms-full.txt**.
@@ -0,0 +1,7 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this Pylon project lives in **AGENTS.md** — the cross-editor
4
+ standard that Claude Code, Cursor, Codex, and others all read. It's imported
5
+ below so Claude Code picks it up automatically:
6
+
7
+ @AGENTS.md
@@ -6,7 +6,8 @@
6
6
  "scripts": {
7
7
  "dev": "pylon dev",
8
8
  "deploy": "pylon deploy",
9
- "check": "tsc --noEmit"
9
+ "check": "tsc --noEmit",
10
+ "test": "pylon test"
10
11
  },
11
12
  "dependencies": {
12
13
  "@pylonsync/react": "^__PYLON_VERSION__",
@@ -27,6 +28,9 @@
27
28
  },
28
29
  "devDependencies": {
29
30
  "@pylonsync/cli": "^__PYLON_VERSION__",
31
+ "@happy-dom/global-registrator": "^20.10.0",
32
+ "@testing-library/dom": "^10.4.0",
33
+ "@testing-library/react": "^16.3.0",
30
34
  "@types/node": "^22.0.0",
31
35
  "@types/react": "^19.0.0",
32
36
  "@types/react-dom": "^19.0.0",
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ // Your first test. Run the suite with: pylon test (or: npm test)
4
+ //
5
+ // `pylon test` discovers every *.test.ts file under tests/ (or functions/) and
6
+ // runs it with Bun's test runner against an in-memory Pylon. Replace this
7
+ // placeholder with tests for your functions and helpers — see AGENTS.md →
8
+ // Testing for the pure-logic and HTTP-against-`pylon dev` patterns.
9
+
10
+ test("placeholder — replace me with a real test", () => {
11
+ expect(true).toBe(true);
12
+ });
@@ -0,0 +1,5 @@
1
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
2
+
3
+ // Make document/window available so @testing-library/react can render. Loaded
4
+ // via bunfig.toml's [test] preload, before any test file runs.
5
+ GlobalRegistrator.register();
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — working in a Pylon project
2
2
 
3
- Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is the **llms-full.txt** at https://docs.pylonsync.com/llms-full.txt. Read it before guessing an API name.
4
4
 
5
5
  ## Directory conventions
6
6
 
@@ -37,6 +37,61 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
37
37
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
38
  - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
39
 
40
+ ## Testing
41
+
42
+ `pylon test` discovers every `*.test.ts` / `*.test.tsx` file under `tests/` (or `functions/`) and runs it with **Bun's test runner** (`import { test, expect } from "bun:test"`). Run the suite with `pylon test` (or `npm test`); filter with `pylon test <substring>`. This template ships `bunfig.toml` + `tests/setup.ts` (registers happy-dom) so component tests render out of the box, plus starter tests under `tests/` — replace them with your own.
43
+
44
+ **Tier 1 — pure logic (reach for this first).** Keep the decisions that matter — access/plan gating, pricing, credit math, validation, formatting — in pure functions in `lib/`, and test those exhaustively. No server, instant, and it's where the real bugs live. Keep your `query`/`mutation`/`action` handlers as thin wrappers around them, so the logic is testable without a running app.
45
+
46
+ ```ts
47
+ import { expect, test } from "bun:test";
48
+ import { productBySlug } from "../lib/site.config";
49
+
50
+ test("unknown slug → undefined", () => {
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+ ```
54
+
55
+ **Tier 2 — React components.** `@testing-library/react` + happy-dom are already wired (`tests/setup.ts`). Render and assert. The template uses the classic JSX transform, so add `import React from "react"` in `.tsx` tests. For a component that reads Pylon data hooks, **mock the boundary**, then dynamic-`import` the component so the mock is in place first:
56
+
57
+ ```tsx
58
+ import { test, expect, mock } from "bun:test";
59
+ import React from "react";
60
+ import { render, screen } from "@testing-library/react";
61
+
62
+ mock.module("@pylonsync/react", () => ({
63
+ db: { useQuery: () => ({ data: [{ id: "1", name: "Acme" }], loading: false }) },
64
+ }));
65
+ const { OrgList } = await import("../app/orgs/org-list"); // your component
66
+
67
+ test("renders orgs from the query", () => {
68
+ render(<OrgList />);
69
+ expect(screen.getByText("Acme")).toBeDefined();
70
+ });
71
+ ```
72
+
73
+ **Tier 3 — functions over HTTP (only when Tier 1 can't cover it).** A handler's full behavior (policies, `ctx.db`, auth) lives in the running app. Start `pylon dev` in another terminal and call the API; `resetDb()` from `@pylonsync/functions` clears the in-memory DB between cases (no-ops if the server's down, refuses production).
74
+
75
+ ```ts
76
+ import { afterEach, expect, test } from "bun:test";
77
+ import { resetDb } from "@pylonsync/functions";
78
+
79
+ const BASE = "http://localhost:4321";
80
+ afterEach(() => resetDb(BASE)); // or installTestIsolation(BASE) once at top-of-file
81
+
82
+ test("createThing then read it back", async () => {
83
+ const t = await fetch(`${BASE}/api/fn/createThing`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ name: "hello" }),
87
+ }).then((r) => r.json());
88
+ const rows = await fetch(`${BASE}/api/entities/Thing`).then((r) => r.json());
89
+ expect(rows.some((r: { id: string }) => r.id === t.id)).toBe(true);
90
+ });
91
+ ```
92
+
93
+ `pylon test:security` is a separate adversarial probe — it hits a running app and reports auth/policy holes (run `pylon dev`, then `pylon test:security`).
94
+
40
95
  ## Use the CLI — don't guess
41
96
 
42
97
  | Need | Command |
@@ -58,4 +113,4 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
58
113
 
59
114
  `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
115
 
61
- For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
116
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **https://docs.pylonsync.com/llms-full.txt**.
@@ -0,0 +1,7 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this Pylon project lives in **AGENTS.md** — the cross-editor
4
+ standard that Claude Code, Cursor, Codex, and others all read. It's imported
5
+ below so Claude Code picks it up automatically:
6
+
7
+ @AGENTS.md
@@ -6,7 +6,8 @@
6
6
  "scripts": {
7
7
  "dev": "pylon dev",
8
8
  "deploy": "pylon deploy",
9
- "check": "tsc --noEmit"
9
+ "check": "tsc --noEmit",
10
+ "test": "pylon test"
10
11
  },
11
12
  "dependencies": {
12
13
  "@pylonsync/react": "^__PYLON_VERSION__",
@@ -26,6 +27,9 @@
26
27
  },
27
28
  "devDependencies": {
28
29
  "@pylonsync/cli": "^__PYLON_VERSION__",
30
+ "@happy-dom/global-registrator": "^20.10.0",
31
+ "@testing-library/dom": "^10.4.0",
32
+ "@testing-library/react": "^16.3.0",
29
33
  "@types/node": "^22.0.0",
30
34
  "@types/react": "^19.0.0",
31
35
  "@types/react-dom": "^19.0.0",
@@ -0,0 +1,16 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ // This project uses the classic JSX transform, so .tsx tests import React.
3
+ import React from "react";
4
+ import { render, screen, cleanup } from "@testing-library/react";
5
+ import { Button } from "../components/ui/button";
6
+
7
+ afterEach(cleanup);
8
+
9
+ // A React component test. happy-dom + @testing-library/react are wired via
10
+ // bunfig.toml — render a component and assert on the DOM. For a component that
11
+ // reads Pylon data hooks (db.useQuery, callFn), mock the boundary first — see
12
+ // AGENTS.md → Testing (Tier 2).
13
+ test("Button renders its label", () => {
14
+ render(<Button>Click me</Button>);
15
+ expect(screen.getByRole("button", { name: "Click me" })).toBeDefined();
16
+ });
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ // Your first test. Run the suite with: pylon test (or: npm test)
4
+ //
5
+ // `pylon test` discovers every *.test.ts file under tests/ (or functions/) and
6
+ // runs it with Bun's test runner against an in-memory Pylon. Replace this
7
+ // placeholder with tests for your functions and helpers — see AGENTS.md →
8
+ // Testing for the pure-logic and HTTP-against-`pylon dev` patterns.
9
+
10
+ test("placeholder — replace me with a real test", () => {
11
+ expect(true).toBe(true);
12
+ });
@@ -0,0 +1,5 @@
1
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
2
+
3
+ // Make document/window available so @testing-library/react can render. Loaded
4
+ // via bunfig.toml's [test] preload, before any test file runs.
5
+ GlobalRegistrator.register();
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — working in a Pylon project
2
2
 
3
- Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is the **llms-full.txt** at https://docs.pylonsync.com/llms-full.txt. Read it before guessing an API name.
4
4
 
5
5
  ## Directory conventions
6
6
 
@@ -37,6 +37,61 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
37
37
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
38
  - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
39
 
40
+ ## Testing
41
+
42
+ `pylon test` discovers every `*.test.ts` / `*.test.tsx` file under `tests/` (or `functions/`) and runs it with **Bun's test runner** (`import { test, expect } from "bun:test"`). Run the suite with `pylon test` (or `npm test`); filter with `pylon test <substring>`. This template ships `bunfig.toml` + `tests/setup.ts` (registers happy-dom) so component tests render out of the box, plus starter tests under `tests/` — replace them with your own.
43
+
44
+ **Tier 1 — pure logic (reach for this first).** Keep the decisions that matter — access/plan gating, pricing, credit math, validation, formatting — in pure functions in `lib/`, and test those exhaustively. No server, instant, and it's where the real bugs live. Keep your `query`/`mutation`/`action` handlers as thin wrappers around them, so the logic is testable without a running app.
45
+
46
+ ```ts
47
+ import { expect, test } from "bun:test";
48
+ import { productBySlug } from "../lib/site.config";
49
+
50
+ test("unknown slug → undefined", () => {
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+ ```
54
+
55
+ **Tier 2 — React components.** `@testing-library/react` + happy-dom are already wired (`tests/setup.ts`). Render and assert. The template uses the classic JSX transform, so add `import React from "react"` in `.tsx` tests. For a component that reads Pylon data hooks, **mock the boundary**, then dynamic-`import` the component so the mock is in place first:
56
+
57
+ ```tsx
58
+ import { test, expect, mock } from "bun:test";
59
+ import React from "react";
60
+ import { render, screen } from "@testing-library/react";
61
+
62
+ mock.module("@pylonsync/react", () => ({
63
+ db: { useQuery: () => ({ data: [{ id: "1", name: "Acme" }], loading: false }) },
64
+ }));
65
+ const { OrgList } = await import("../app/orgs/org-list"); // your component
66
+
67
+ test("renders orgs from the query", () => {
68
+ render(<OrgList />);
69
+ expect(screen.getByText("Acme")).toBeDefined();
70
+ });
71
+ ```
72
+
73
+ **Tier 3 — functions over HTTP (only when Tier 1 can't cover it).** A handler's full behavior (policies, `ctx.db`, auth) lives in the running app. Start `pylon dev` in another terminal and call the API; `resetDb()` from `@pylonsync/functions` clears the in-memory DB between cases (no-ops if the server's down, refuses production).
74
+
75
+ ```ts
76
+ import { afterEach, expect, test } from "bun:test";
77
+ import { resetDb } from "@pylonsync/functions";
78
+
79
+ const BASE = "http://localhost:4321";
80
+ afterEach(() => resetDb(BASE)); // or installTestIsolation(BASE) once at top-of-file
81
+
82
+ test("createThing then read it back", async () => {
83
+ const t = await fetch(`${BASE}/api/fn/createThing`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ name: "hello" }),
87
+ }).then((r) => r.json());
88
+ const rows = await fetch(`${BASE}/api/entities/Thing`).then((r) => r.json());
89
+ expect(rows.some((r: { id: string }) => r.id === t.id)).toBe(true);
90
+ });
91
+ ```
92
+
93
+ `pylon test:security` is a separate adversarial probe — it hits a running app and reports auth/policy holes (run `pylon dev`, then `pylon test:security`).
94
+
40
95
  ## Use the CLI — don't guess
41
96
 
42
97
  | Need | Command |
@@ -58,4 +113,4 @@ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like fram
58
113
 
59
114
  `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
115
 
61
- For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
116
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **https://docs.pylonsync.com/llms-full.txt**.