@brust/create-app 2.0.0-alpha.2 → 2.0.0-alpha.3

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brust/create-app",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-alpha.3",
4
4
  "description": "Scaffold a brust v2 app — bun create @brust/app <name>",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/dep.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
3
3
  import { dirname, join, relative, resolve } from 'node:path'
4
4
 
5
- /** = scripts/release-bump.ts PLATS. */
5
+ /** = scripts/release-files.ts PLATS. */
6
6
  export const PLATS = ['darwin-x64', 'darwin-arm64', 'linux-x64-gnu', 'linux-arm64-gnu', 'linux-x64-musl', 'linux-arm64-musl'] as const
7
7
 
8
8
  export interface BrustDep {
@@ -44,6 +44,7 @@ export function resolveBrustDep(o: { explicit?: string; targetDir: string; pkgRo
44
44
  if (inWorkspace(root, o.targetDir)) return { kind: 'workspace', spec: 'workspace:*' }
45
45
  // `file:` alone fails: core's own workspace:* deps do not resolve outside the workspace globs, so override them.
46
46
  const overrides: Record<string, string> = { '@brust/runtime-dom': `file:${root}/packages/runtime-dom` }
47
+ overrides['@brust/core-cf'] = `file:${root}/npm/core-cf`
47
48
  for (const p of PLATS) overrides[`@brust/native-${p}`] = `file:${root}/npm/${p}`
48
49
  return { kind: 'file', spec: `file:${root}/packages/brust`, overrides }
49
50
  }
@@ -9,7 +9,7 @@ bun install
9
9
  bun run dev # brust build routes.tsx && brust start -> http://127.0.0.1:1337
10
10
  ```
11
11
 
12
- Scripts: `dev` (build then start), `build`, `start`, `typecheck`.
12
+ Scripts: `dev` (build then start), `build`, `start`, `typecheck`, `dev:cf` (build for Cloudflare Workers and run `wrangler dev`), `deploy:cf` (build and `wrangler deploy`).
13
13
 
14
14
  ## Layout
15
15
 
@@ -18,6 +18,7 @@ Scripts: `dev` (build then start), `build`, `start`, `typecheck`.
18
18
  - `components/Counter.tsx` — a `useState` counter; the compiler makes it a native component (no React in the browser).
19
19
  - `actions.ts` — an Elysia 2 app on `brust()`, served by Rust under `/_brust/action` (enabled by `[actions]` in `brust.toml`).
20
20
  - `public/` — static files, served at `/public/...`.
21
+ - `wrangler.jsonc` — your Cloudflare bindings (D1, KV, vars); brust owns `main`, `assets` and `compatibility_date`.
21
22
 
22
23
  ## The hello action
23
24
 
@@ -1,2 +1,3 @@
1
1
  node_modules/
2
2
  dist/
3
+ .wrangler/
@@ -7,7 +7,9 @@
7
7
  "dev": "brust build routes.tsx && brust start",
8
8
  "build": "brust build routes.tsx",
9
9
  "start": "brust start",
10
- "typecheck": "bun check"
10
+ "typecheck": "bun check",
11
+ "dev:cf": "brust dev routes.tsx --target cloudflare",
12
+ "deploy:cf": "brust build routes.tsx --target cloudflare && wrangler deploy -c dist/cf/wrangler.jsonc"
11
13
  },
12
14
  "dependencies": {
13
15
  "@brust/core": __BRUST_DEP__,
@@ -19,6 +21,7 @@
19
21
  "devDependencies": {
20
22
  "@types/bun": "^1.4.0",
21
23
  "@types/react": "^19.2.0",
22
- "typescript": "^7.0.2"
24
+ "typescript": "^7.0.2",
25
+ "wrangler": "^4.149.0"
23
26
  }
24
27
  }
@@ -0,0 +1,9 @@
1
+ // Your Cloudflare config for `bun run dev:cf` / `bun run deploy:cf`.
2
+ // `brust build --target cloudflare` merges this file into dist/cf/wrangler.jsonc and owns these keys:
3
+ // main, assets (the ASSETS binding), compatibility_date, no_bundle, build, rules, site.
4
+ {
5
+ "name": "__PROJECT_NAME__"
6
+ // "d1_databases": [{ "binding": "DB", "database_name": "__PROJECT_NAME__", "database_id": "<id>" }],
7
+ // "kv_namespaces": [{ "binding": "KV", "id": "<id>" }],
8
+ // "vars": { "GREETING": "hello" }
9
+ }
@@ -26,10 +26,25 @@ at most 6 members and a seventh add answers `409` (`TeamFull`). The team store (
26
26
 
27
27
  - `routes.tsx` — `/`, `/pokedex`, `/pokemon/{name}` (L1 60 s, `?nocache` bypass), `/type-chart` (L1 1 h), `/unified`, and a
28
28
  root-level `*` 404 outside the layout. Pages that show the team carry the cache tag `team`.
29
- - `lib/` — loaders over the snapshot, `team-db.ts` (sqlite, server-only), `team-client.ts` + `api.ts` (the typed actions client).
29
+ - `lib/` — loaders over the snapshot, `team-db.ts` (the team interface: sqlite natively, D1 on Cloudflare via `[cloudflare.alias]`; server-only), `team-client.ts` + `api.ts` (the typed actions client).
30
30
  - `actions.ts` — the Elysia 2 team app on `brust()`; `stores/` — `team` and `unified` (`defineStore`).
31
31
  - `components/`, `pages/` — plain React functions.
32
32
 
33
+ ## Cloudflare Workers (preview)
34
+
35
+ ```bash
36
+ bun run cf:migrate:local # create the local D1 table (state in .wrangler/)
37
+ bun run dev:cf # build for Workers and run `wrangler dev` (rebuilds on change)
38
+ ```
39
+
40
+ `brust build --target cloudflare` swaps `lib/team-db.ts` for the D1 version (`lib/team-db.d1.ts`) through `[cloudflare.alias]`
41
+ in `brust.toml`; your bindings live in `wrangler.jsonc`. To deploy: `wrangler d1 create __PROJECT_NAME__-team`, paste the
42
+ `database_id` into the **app-root** `wrangler.jsonc` (not `dist/cf/`, which is regenerated), `bun run cf:migrate:remote`,
43
+ then `bun run deploy:cf`.
44
+
45
+ Preview note: a team change refreshes cached team pages only in the isolate that served the action; other isolates catch up
46
+ within the route `ttl` (60 s for `/pokemon/*`, 1 h for `/type-chart`) until the cross-isolate invalidation lands.
47
+
33
48
  ## Regenerating the stylesheet (never hand-edit `public/app.css`)
34
49
 
35
50
  ```bash
@@ -1,3 +1,5 @@
1
1
  node_modules/
2
2
  dist/
3
3
  data/team.sqlite*
4
+ .wrangler/
5
+ .dev.vars*
@@ -13,21 +13,21 @@ export interface TeamDeps {
13
13
  }
14
14
 
15
15
  export function teamApp({ db, invalidate }: TeamDeps = { db: teamDb, invalidate: cache.invalidate }) {
16
- const reply = () => ({ team: db().list(), max: MAX_TEAM })
16
+ const reply = async () => ({ team: await db().list(), max: MAX_TEAM })
17
17
  // R5: cached pages that render the team carry the tag `team`; drop them after a real change.
18
18
  const changed = () => invalidate({ tags: ['team'] })
19
19
  return new Elysia({ adapter: brust() })
20
20
  .get('/team', () => reply())
21
- .post('/team', { body: t.Object({ id: t.Integer({ minimum: 1 }) }) }, ({ body }) => {
21
+ .post('/team', { body: t.Object({ id: t.Integer({ minimum: 1 }) }) }, async ({ body }) => {
22
22
  const p = pokemonById(body.id)
23
23
  if (!p) return problem(404, { title: 'UnknownPokemon', detail: `no Pokémon #${body.id}` })
24
- const r = db().add(p)
24
+ const r = await db().add(p)
25
25
  if (r === 'full') return problem(409, { title: 'TeamFull', detail: `the team already has ${MAX_TEAM} members`, max: MAX_TEAM })
26
26
  if (r === 'added') changed()
27
27
  return reply()
28
28
  })
29
- .delete('/team/:id', { params: t.Object({ id: t.Integer({ minimum: 1 }) }) }, ({ params }) => {
30
- if (db().remove(params.id)) changed()
29
+ .delete('/team/:id', { params: t.Object({ id: t.Integer({ minimum: 1 }) }) }, async ({ params }) => {
30
+ if (await db().remove(params.id)) changed()
31
31
  return reply()
32
32
  })
33
33
  }
@@ -3,6 +3,26 @@ address = "127.0.0.1"
3
3
  port = 1337
4
4
 
5
5
  [build]
6
- server_only = ["lib/team-db.ts"]
6
+ server_only = ["lib/team-db"]
7
7
 
8
8
  [actions]
9
+
10
+ # `brust build --target cloudflare` swaps the sqlite roster for the D1 one (the whole facade module).
11
+ [cloudflare.alias]
12
+ "lib/team-db.ts" = "lib/team-db.d1.ts"
13
+
14
+ # A shared second cache tier (needs `bun add @brust/cache-redis`; see the brust docs, "Redis / Dragonfly"):
15
+ # [cache]
16
+ # adapter = "redis" # or "dragonfly"
17
+ # [cache.redis]
18
+ # url = "env:REDIS_URL"
19
+
20
+ # Or, on Cloudflare Workers: a shared second cache tier (KV bodies + a Durable Object invalidation broadcast). Off by default.
21
+ # Needs: bun add @brust/cache-cloudflare, plus the KV and Durable Object bindings in wrangler.jsonc (see the comment there).
22
+ # [cache]
23
+ # adapter = "cloudflare-kv"
24
+ # broadcast = "cloudflare-do"
25
+ # epoch_interval_ms = 5000
26
+ #
27
+ # [cache.cloudflare]
28
+ # kv_min_ttl_s = 300
@@ -0,0 +1,24 @@
1
+ // Ambient types for the Cloudflare Workers surface this example uses (lib/team-db.d1.ts): just the subset of D1 that
2
+ // is called, so `bun check` needs no @cloudflare/workers-types (whose global Request/Response clash with lib DOM).
3
+ interface D1Meta {
4
+ changes: number
5
+ last_row_id: number
6
+ }
7
+ interface D1Result<T = unknown> {
8
+ success: boolean
9
+ results: T[]
10
+ meta: D1Meta
11
+ }
12
+ interface D1PreparedStatement {
13
+ bind(...values: unknown[]): D1PreparedStatement
14
+ all<T = Record<string, unknown>>(): Promise<D1Result<T>>
15
+ first<T = Record<string, unknown>>(): Promise<T | null>
16
+ run(): Promise<D1Result>
17
+ }
18
+ interface D1Database {
19
+ prepare(query: string): D1PreparedStatement
20
+ batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>
21
+ }
22
+ declare module 'cloudflare:workers' {
23
+ export const env: { TEAM_DB?: D1Database }
24
+ }
@@ -9,8 +9,8 @@ import type { BrowseData, DetailData, HomeData, TypeChartCellVM, TypeChartData,
9
9
 
10
10
  const FEATURED = [{ id: 1, name: 'bulbasaur' }, { id: 4, name: 'charmander' }, { id: 7, name: 'squirtle' }, { id: 25, name: 'pikachu' }, { id: 39, name: 'jigglypuff' }, { id: 94, name: 'gengar' }, { id: 143, name: 'snorlax' }, { id: 150, name: 'mewtwo' }]
11
11
  /** Layout loader: seeds the request's store scope, so every layout page paints the DB roster (D4/E7). */
12
- export function layoutLoader(): void {
13
- team.members.set(teamDb().list())
12
+ export async function layoutLoader(): Promise<void> {
13
+ team.members.set(await teamDb().list())
14
14
  team.max.set(MAX_TEAM)
15
15
  }
16
16
  const NAV = [{ href: '/', label: 'Home' }, { href: '/pokedex', label: 'Pokédex' }, { href: '/type-chart', label: 'Type chart' }, { href: '/unified', label: 'Unified' }]
@@ -0,0 +1,47 @@
1
+ // examples/pokedex/lib/team-db.d1.ts — the TeamDb on Cloudflare D1. `brust.toml` [cloudflare.alias] swaps lib/team-db.ts for
2
+ // this file in `brust build --target cloudflare`. The binding is read per call (never at import: the build smoke-imports the
3
+ // bundle with an empty env) and nothing is cached across requests.
4
+ import { env } from 'cloudflare:workers'
5
+ import { MAX_TEAM } from './team'
6
+ import type { AddResult, TeamDb } from './team-db'
7
+ import type { TeamMember } from './types'
8
+
9
+ export type { AddResult, TeamDb }
10
+
11
+ interface Row { id: number; name: string; display_name: string; num: string; types: string; artwork: string }
12
+
13
+ const LIST = 'SELECT id, name, display_name, num, types, artwork FROM team ORDER BY added_at, rowid'
14
+ // ONE statement decides the cap: the count check and the insert cannot be split across awaits on D1 (no transaction handle),
15
+ // so the insert is conditional. added_at is a clock that never repeats, as in the sqlite twin.
16
+ const INSERT =
17
+ 'INSERT INTO team (id, name, display_name, num, types, artwork, added_at) ' +
18
+ 'SELECT ?1, ?2, ?3, ?4, ?5, ?6, MAX(?7, (SELECT COALESCE(MAX(added_at), 0) + 1 FROM team)) ' +
19
+ 'WHERE (SELECT COUNT(*) FROM team) < ?8 ON CONFLICT(id) DO NOTHING'
20
+ const HAS = 'SELECT 1 AS one FROM team WHERE id = ?1'
21
+
22
+ export function openTeamDb(d1: D1Database): TeamDb {
23
+ return {
24
+ async list() {
25
+ const { results } = await d1.prepare(LIST).all<Row>()
26
+ return results.map((r) => ({ id: r.id, name: r.name, displayName: r.display_name, types: JSON.parse(r.types) as string[], artwork: r.artwork, num: r.num }))
27
+ },
28
+ async add(m: TeamMember): Promise<AddResult> {
29
+ // One batch = one transaction: added (1 row) | present (the id exists, even when full) | full.
30
+ const [ins, has] = await d1.batch([
31
+ d1.prepare(INSERT).bind(m.id, m.name, m.displayName, m.num, JSON.stringify(m.types), m.artwork, Date.now(), MAX_TEAM),
32
+ d1.prepare(HAS).bind(m.id),
33
+ ])
34
+ if (ins!.meta.changes === 1) return 'added'
35
+ return has!.results.length > 0 ? 'present' : 'full'
36
+ },
37
+ async remove(id: number) {
38
+ return (await d1.prepare('DELETE FROM team WHERE id = ?1').bind(id).run()).meta.changes > 0
39
+ },
40
+ }
41
+ }
42
+
43
+ export function teamDb(): TeamDb {
44
+ const d1 = env.TEAM_DB
45
+ if (!d1) throw new Error('TEAM_DB D1 binding missing: add it to wrangler.jsonc (see README)')
46
+ return openTeamDb(d1)
47
+ }
@@ -0,0 +1,49 @@
1
+ // examples/pokedex/lib/team-db.sqlite.ts — the native TeamDb (server-only via brust.toml [build] server_only = lib/team-db).
2
+ // The team is ONE shared roster, as in 0.1.x, but persisted: every worker thread opens the same file (WAL), so workers never
3
+ // diverge (M3-F E7). Methods return Promises so the interface matches D1 (lib/team-db.d1.ts); the work itself stays synchronous.
4
+ import { Database } from 'bun:sqlite'
5
+ import { mkdirSync } from 'node:fs'
6
+ import { dirname, join } from 'node:path'
7
+ import { MAX_TEAM } from './team'
8
+ import type { AddResult, TeamDb } from './team-db'
9
+ import type { TeamMember } from './types'
10
+
11
+ export type SqliteTeamDb = TeamDb & { close(): void }
12
+
13
+ export const TEAM_DDL =
14
+ 'CREATE TABLE IF NOT EXISTS team (id INTEGER PRIMARY KEY, name TEXT NOT NULL, display_name TEXT NOT NULL, num TEXT NOT NULL, types TEXT NOT NULL, artwork TEXT NOT NULL, added_at INTEGER NOT NULL)'
15
+
16
+ interface Row { id: number; name: string; display_name: string; num: string; types: string; artwork: string }
17
+
18
+ export function openTeamDb(file: string): SqliteTeamDb {
19
+ if (file !== ':memory:') mkdirSync(dirname(file), { recursive: true })
20
+ const db = new Database(file, { create: true })
21
+ db.exec('PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000')
22
+ db.exec(TEAM_DDL)
23
+ const listQ = db.query<Row, []>('SELECT id, name, display_name, num, types, artwork FROM team ORDER BY added_at, rowid')
24
+ const hasQ = db.query<{ one: number }, [number]>('SELECT 1 AS one FROM team WHERE id = ?')
25
+ const countQ = db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM team')
26
+ const insertQ = db.query('INSERT INTO team (id, name, display_name, num, types, artwork, added_at) VALUES (?, ?, ?, ?, ?, ?, MAX(?, (SELECT COALESCE(MAX(added_at), 0) + 1 FROM team)))')
27
+ const deleteQ = db.query('DELETE FROM team WHERE id = ?')
28
+ // added_at is a clock that never repeats (MAX(now, last + 1)): rowid IS the Pokémon id, so it cannot break same-ms ties.
29
+ // count check + insert under ONE write lock (.immediate): two workers adding at five members cannot both succeed.
30
+ const addTx = db.transaction((m: TeamMember): AddResult => {
31
+ if (hasQ.get(m.id)) return 'present'
32
+ if ((countQ.get()?.n ?? 0) >= MAX_TEAM) return 'full'
33
+ insertQ.run(m.id, m.name, m.displayName, m.num, JSON.stringify(m.types), m.artwork, Date.now())
34
+ return 'added'
35
+ })
36
+ return {
37
+ list: async () => listQ.all().map((r) => ({ id: r.id, name: r.name, displayName: r.display_name, types: JSON.parse(r.types) as string[], artwork: r.artwork, num: r.num })),
38
+ add: async (m) => addTx.immediate(m),
39
+ remove: async (id) => deleteQ.run(id).changes > 0,
40
+ close: () => db.close(),
41
+ }
42
+ }
43
+
44
+ let shared: SqliteTeamDb | null = null
45
+ export const teamDbPath = (): string => process.env.POKEDEX_TEAM_DB || join(process.cwd(), 'data/team.sqlite')
46
+ /** Opened lazily on first use: `brust build` imports routes.tsx, and building must not create the file. */
47
+ export function teamDb(): SqliteTeamDb {
48
+ return (shared ??= openTeamDb(teamDbPath()))
49
+ }
@@ -1,52 +1,13 @@
1
- // examples/pokedex/lib/team-db.ts — server-only (brust.toml [build] server_only). The team is ONE shared roster, as in
2
- // 0.1.x, but persisted: every worker thread opens the same file (WAL), so workers never diverge (M3-F E7).
3
- import { Database } from 'bun:sqlite'
4
- import { mkdirSync } from 'node:fs'
5
- import { dirname, join } from 'node:path'
6
- import { MAX_TEAM } from './team'
1
+ // examples/pokedex/lib/team-db.ts — the TeamDb interface (async: D1 has no sync API) and the native default.
2
+ // `brust build --target cloudflare` swaps this whole module for lib/team-db.d1.ts via brust.toml [cloudflare.alias],
3
+ // so app imports (`./lib/team-db`) never change. Server-only (brust.toml [build] server_only = lib/team-db).
7
4
  import type { TeamMember } from './types'
8
5
 
9
6
  export type AddResult = 'added' | 'present' | 'full'
10
7
  export interface TeamDb {
11
- list(): TeamMember[]
12
- add(m: TeamMember): AddResult
13
- remove(id: number): boolean
14
- close(): void
8
+ list(): Promise<TeamMember[]>
9
+ add(m: TeamMember): Promise<AddResult>
10
+ remove(id: number): Promise<boolean>
15
11
  }
16
12
 
17
- interface Row { id: number; name: string; display_name: string; num: string; types: string; artwork: string }
18
-
19
- export function openTeamDb(file: string): TeamDb {
20
- if (file !== ':memory:') mkdirSync(dirname(file), { recursive: true })
21
- const db = new Database(file, { create: true })
22
- db.exec('PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000')
23
- db.exec(
24
- 'CREATE TABLE IF NOT EXISTS team (id INTEGER PRIMARY KEY, name TEXT NOT NULL, display_name TEXT NOT NULL, num TEXT NOT NULL, types TEXT NOT NULL, artwork TEXT NOT NULL, added_at INTEGER NOT NULL)',
25
- )
26
- const listQ = db.query<Row, []>('SELECT id, name, display_name, num, types, artwork FROM team ORDER BY added_at, rowid')
27
- const hasQ = db.query<{ one: number }, [number]>('SELECT 1 AS one FROM team WHERE id = ?')
28
- const countQ = db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM team')
29
- const insertQ = db.query('INSERT INTO team (id, name, display_name, num, types, artwork, added_at) VALUES (?, ?, ?, ?, ?, ?, MAX(?, (SELECT COALESCE(MAX(added_at), 0) + 1 FROM team)))')
30
- const deleteQ = db.query('DELETE FROM team WHERE id = ?')
31
- // added_at is a clock that never repeats (MAX(now, last + 1)): rowid IS the Pokémon id, so it cannot break same-ms ties.
32
- // count check + insert under ONE write lock (.immediate): two workers adding at five members cannot both succeed.
33
- const addTx = db.transaction((m: TeamMember): AddResult => {
34
- if (hasQ.get(m.id)) return 'present'
35
- if ((countQ.get()?.n ?? 0) >= MAX_TEAM) return 'full'
36
- insertQ.run(m.id, m.name, m.displayName, m.num, JSON.stringify(m.types), m.artwork, Date.now())
37
- return 'added'
38
- })
39
- return {
40
- list: () => listQ.all().map((r) => ({ id: r.id, name: r.name, displayName: r.display_name, types: JSON.parse(r.types) as string[], artwork: r.artwork, num: r.num })),
41
- add: (m) => addTx.immediate(m),
42
- remove: (id) => deleteQ.run(id).changes > 0,
43
- close: () => db.close(),
44
- }
45
- }
46
-
47
- let shared: TeamDb | null = null
48
- export const teamDbPath = (): string => process.env.POKEDEX_TEAM_DB || join(process.cwd(), 'data/team.sqlite')
49
- /** Opened lazily on first use: `brust build` imports routes.tsx, and building must not create the file. */
50
- export function teamDb(): TeamDb {
51
- return (shared ??= openTeamDb(teamDbPath()))
52
- }
13
+ export { openTeamDb, type SqliteTeamDb, teamDb, teamDbPath } from './team-db.sqlite'
@@ -0,0 +1,2 @@
1
+ -- pokedex team roster (lib/team-db.sqlite.ts TEAM_DDL; the same table on D1)
2
+ CREATE TABLE IF NOT EXISTS team (id INTEGER PRIMARY KEY, name TEXT NOT NULL, display_name TEXT NOT NULL, num TEXT NOT NULL, types TEXT NOT NULL, artwork TEXT NOT NULL, added_at INTEGER NOT NULL);
@@ -6,6 +6,10 @@
6
6
  "scripts": {
7
7
  "dev": "brust build routes.tsx && brust start",
8
8
  "build": "brust build routes.tsx",
9
+ "dev:cf": "wrangler d1 migrations apply TEAM_DB --local --persist-to .wrangler/state && brust dev routes.tsx --target cloudflare",
10
+ "deploy:cf": "brust build routes.tsx --target cloudflare && wrangler deploy -c dist/cf/wrangler.jsonc",
11
+ "cf:migrate:local": "wrangler d1 migrations apply TEAM_DB --local --persist-to .wrangler/state",
12
+ "cf:migrate:remote": "wrangler d1 migrations apply TEAM_DB --remote",
9
13
  "start": "brust start",
10
14
  "snapshot": "bun scripts/snapshot.ts",
11
15
  "typecheck": "bun check"
@@ -20,6 +24,7 @@
20
24
  "devDependencies": {
21
25
  "@types/bun": "^1.4.0",
22
26
  "@types/react": "^19.2.0",
23
- "typescript": "^7.0.2"
27
+ "typescript": "^7.0.2",
28
+ "wrangler": "^4.149.0"
24
29
  }
25
30
  }
@@ -0,0 +1,9 @@
1
+ // Bindings for `brust build --target cloudflare` (merged into dist/cf/wrangler.jsonc).
2
+ // brust owns main, assets and compatibility_date. Add the database_id from `wrangler d1 create pokedex-team` before deploy.
3
+ {
4
+ // To enable the shared cache tier (brust.toml [cache]), add these beside d1_databases:
5
+ // "kv_namespaces": [{ "binding": "BRUST_CACHE", "id": "<wrangler kv namespace create brust-cache>" }],
6
+ // "durable_objects": { "bindings": [{ "name": "BRUST_CACHE_DO", "class_name": "BrustCacheDO" }] },
7
+ // "migrations": [{ "tag": "brust-cache-v1", "new_sqlite_classes": ["BrustCacheDO"] }],
8
+ "d1_databases": [{ "binding": "TEAM_DB", "database_name": "pokedex-team", "migrations_dir": "migrations" }]
9
+ }