From 37ef4377db1cd13e8e44f117f58bb522e05539f4 Mon Sep 17 00:00:00 2001 From: Akanyane Date: Fri, 6 Nov 2026 17:32:23 -0300 Subject: [PATCH 1/3] feat(create): add React PocketBase add-on --- .changeset/pocketbase-addon.md | 19 + .../react/add-ons/pocketbase/README.md | 39 ++ .../pocketbase/assets/_dot_env.local.append | 2 + .../pocketbase/assets/_dot_gitignore.append | 3 + .../pb_migrations/1761955200_created_todos.js | 46 ++ .../pocketbase/assets/src/lib/pocketbase.ts | 21 + .../assets/src/routes/demo/pocketbase.tsx | 449 ++++++++++++++++++ .../react/add-ons/pocketbase/info.json | 30 ++ .../react/add-ons/pocketbase/package.json | 6 + .../react/add-ons/pocketbase/small-logo.svg | 1 + 10 files changed, 616 insertions(+) create mode 100644 .changeset/pocketbase-addon.md create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/README.md create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_env.local.append create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/lib/pocketbase.ts create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/info.json create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/package.json create mode 100644 packages/create/src/frameworks/react/add-ons/pocketbase/small-logo.svg diff --git a/.changeset/pocketbase-addon.md b/.changeset/pocketbase-addon.md new file mode 100644 index 00000000..6bb96052 --- /dev/null +++ b/.changeset/pocketbase-addon.md @@ -0,0 +1,19 @@ +--- +'@tanstack/cli': minor +'@tanstack/create': minor +--- + +feat(create): add React PocketBase add-on + +`tanstack add pocketbase` (or `--add-ons pocketbase` on `tanstack create`) +wires the PocketBase JS SDK into a React TanStack Start app: + +- `pocketbase` dependency and a `src/lib/pocketbase.ts` client reading + `VITE_POCKETBASE_URL` from `.env.local`, plus a `createPocketBase()` + factory for per-request clients in server functions. +- A `pb_migrations/` JS migration that creates an owner-scoped `todos` + collection when running `./pocketbase serve`, and `pb_data/` added to + `.gitignore`. +- A `/demo/pocketbase` route with email and password sign-in and a private + realtime todo list (create, toggle, delete, live updates across tabs), plus + setup guidance when the server is not reachable. diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/README.md b/packages/create/src/frameworks/react/add-ons/pocketbase/README.md new file mode 100644 index 00000000..6fcefd69 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/README.md @@ -0,0 +1,39 @@ +## Setting up PocketBase + +This project uses [PocketBase](https://pocketbase.io), an open source backend in a single file: an embedded SQLite database, auth, realtime subscriptions, file storage and an admin dashboard. + +1. [Download PocketBase](https://pocketbase.io/docs/) for your platform and place the `pocketbase` binary in the root of this project. +2. Start the server: + + ```bash + ./pocketbase serve + ``` + + On first start, PocketBase applies the migration in `pb_migrations/`, which creates the `todos` collection used by the demo. +3. Open the admin dashboard at http://127.0.0.1:8090/_/ and create your superuser account. +4. Make sure `VITE_POCKETBASE_URL` in `.env.local` points to your PocketBase server (defaults to `http://127.0.0.1:8090`). +5. Run the app, visit `/demo/pocketbase` and create an account. + +### What the add-on includes + +- `src/lib/pocketbase.ts`: PocketBase JS SDK client (`pb`) and a `createPocketBase()` factory for per-request clients in server functions. +- `src/routes/demo/pocketbase.tsx`: email and password sign-in, plus a private todo list with realtime updates. +- `pb_migrations/`: JS migrations applied automatically by `pocketbase serve`. +- `pb_data/` is added to `.gitignore` (it holds your local database and uploaded files). + +### Permissions + +Each todo has an `owner` relation to the built-in `users` collection, and the collection's API rules enforce ownership on the server: + +| Action | Rule | +| -------------------- | --------------------------------------------------------------------------------------------------------- | +| List, view, delete | `@request.auth.id != "" && owner = @request.auth.id` | +| Create | `@request.auth.id != "" && @request.body.owner = @request.auth.id` | +| Update | The list rule, plus `(@request.body.owner:isset = false \|\| @request.body.owner = @request.auth.id)` | + +Realtime subscriptions follow the list rule, so users only receive events for their own todos. Deleting a user also deletes their todos. + +> [!IMPORTANT] +> PocketBase's default `users` collection allows public sign-ups. Review its API rules and enable email verification before deploying. + +Learn more in the [PocketBase docs](https://pocketbase.io/docs/) and the [JS SDK docs](https://github.com/pocketbase/js-sdk). diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_env.local.append b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_env.local.append new file mode 100644 index 00000000..69f90582 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_env.local.append @@ -0,0 +1,2 @@ +# PocketBase server URL (run `./pocketbase serve` locally, default http://127.0.0.1:8090) +VITE_POCKETBASE_URL=http://127.0.0.1:8090 diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append new file mode 100644 index 00000000..4d4f6a57 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append @@ -0,0 +1,3 @@ + +# PocketBase local data +pb_data/ diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js new file mode 100644 index 00000000..25d332d7 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js @@ -0,0 +1,46 @@ +/// + +// Creates the `todos` collection used by the /demo/pocketbase route. +// Applied automatically when running `./pocketbase serve`. +// +// Every todo belongs to the user in `owner`. The API rules below let users +// read and change only their own todos (realtime events follow `listRule`), +// and stop them from creating todos for someone else or handing theirs over. +const ownerOnly = '@request.auth.id != "" && owner = @request.auth.id' + +migrate( + (app) => { + const users = app.findCollectionByNameOrId('users') + + const collection = new Collection({ + type: 'base', + name: 'todos', + listRule: ownerOnly, + viewRule: ownerOnly, + createRule: '@request.auth.id != "" && @request.body.owner = @request.auth.id', + updateRule: `${ownerOnly} && (@request.body.owner:isset = false || @request.body.owner = @request.auth.id)`, + deleteRule: ownerOnly, + fields: [ + { name: 'text', type: 'text', required: true, max: 500 }, + { name: 'completed', type: 'bool' }, + { + name: 'owner', + type: 'relation', + required: true, + maxSelect: 1, + collectionId: users.id, + cascadeDelete: true, + }, + { name: 'created', type: 'autodate', onCreate: true, onUpdate: false }, + { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true }, + ], + indexes: ['CREATE INDEX idx_todos_owner ON todos (owner)'], + }) + + app.save(collection) + }, + (app) => { + const collection = app.findCollectionByNameOrId('todos') + app.delete(collection) + }, +) diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/lib/pocketbase.ts b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/lib/pocketbase.ts new file mode 100644 index 00000000..e6611705 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/lib/pocketbase.ts @@ -0,0 +1,21 @@ +import PocketBase from 'pocketbase' + +export const POCKETBASE_URL = + import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090' + +/** + * Browser-side PocketBase client. + * + * The auth store of this instance is persisted in localStorage, so use it + * from client components only. For server functions, create a fresh client + * per request with `createPocketBase()` so auth state is never shared + * between users. + */ +export const pb = createPocketBase() + +export function createPocketBase() { + const client = new PocketBase(POCKETBASE_URL) + // Avoid cancelling duplicated requests (e.g. React StrictMode double effects). + client.autoCancellation(false) + return client +} diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx new file mode 100644 index 00000000..5994b46f --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx @@ -0,0 +1,449 @@ +import { useEffect, useState } from 'react' +import type { FormEvent } from 'react' +import { createFileRoute } from '@tanstack/react-router' +import { Check, Circle, LogOut, Plus, Trash2 } from 'lucide-react' +import { ClientResponseError } from 'pocketbase' +import type { RecordModel } from 'pocketbase' + +import { POCKETBASE_URL, pb } from '../../lib/pocketbase' + +export const Route = createFileRoute('/demo/pocketbase')({ + ssr: false, + component: PocketBaseDemo, +}) + +interface Todo extends RecordModel { + text: string + completed: boolean + owner: string +} + +const todos = pb.collection('todos') +const users = pb.collection('users') + +function getCurrentUser() { + return pb.authStore.isValid ? pb.authStore.record : null +} + +function getErrorMessage(err: unknown) { + if (err instanceof ClientResponseError) { + if (err.status === 0) return `Could not connect to ${POCKETBASE_URL}.` + // Validation errors carry the useful message per field. + const [fieldError] = Object.values(err.response.data ?? {}) as Array<{ + message?: string + }> + return fieldError?.message ?? err.message + } + return err instanceof Error ? err.message : String(err) +} + +function upsert(list: Array, todo: Todo) { + const exists = list.some((item) => item.id === todo.id) + return exists + ? list.map((item) => (item.id === todo.id ? todo : item)) + : [...list, todo] +} + +function PocketBaseDemo() { + const [user, setUser] = useState(getCurrentUser) + const [serverError, setServerError] = useState(null) + + useEffect(() => pb.authStore.onChange(() => setUser(getCurrentUser())), []) + + useEffect(() => { + let cancelled = false + + pb.health + .check() + .then(async () => { + if (pb.authStore.isValid) await users.authRefresh() + }) + .catch((err: unknown) => { + if (cancelled) return + if ( + err instanceof ClientResponseError && + [401, 403, 404].includes(err.status) + ) { + // The stored session is stale, e.g. after resetting pb_data. + pb.authStore.clear() + } else { + setServerError(getErrorMessage(err)) + } + }) + + return () => { + cancelled = true + } + }, []) + + return ( +
+
+
+
+

PocketBase

+

Todos

+

+ Private, realtime todos from {POCKETBASE_URL} +

+
+
+ + {serverError ? ( + + ) : user ? ( + // Remount per user so one user's todos never render for the next. + + ) : ( + + )} + +
+

+ Built with PocketBase: SQLite, auth, realtime and an admin UI in a + single binary. +

+
+
+
+ ) +} + +function SetupSteps({ error }: { error: string }) { + return ( +
+

PocketBase is not ready

+

{error}

+
    +
  1. + Download PocketBase from{' '} + + pocketbase.io/docs + {' '} + and place the binary in the project root. +
  2. +
  3. + Run ./pocketbase serve (this applies the migration in{' '} + pb_migrations/ and creates the todos{' '} + collection). +
  4. +
  5. + Make sure VITE_POCKETBASE_URL in .env.local{' '} + points to your server. +
  6. +
+
+ ) +} + +function AuthForm() { + const [isSignUp, setIsSignUp] = useState(false) + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + setError(null) + setLoading(true) + try { + if (isSignUp) { + await users.create({ email, password, passwordConfirm: password }) + } + await users.authWithPassword(email, password) + } catch (err) { + setError(getErrorMessage(err)) + setLoading(false) + } + } + + return ( +
+

+ {isSignUp ? 'Create an account' : 'Sign in to see your todos'} +

+ +
void handleSubmit(e)} + className="mt-4 grid gap-4" + > +
+ + setEmail(e.target.value)} + autoComplete="email" + className="demo-input" + required + /> +
+ +
+ + setPassword(e.target.value)} + autoComplete={isSignUp ? 'new-password' : 'current-password'} + className="demo-input" + required + minLength={8} + /> +
+ + {error && ( +
+

{error}

+
+ )} + + +
+ +
+ +
+
+ ) +} + +function TodoList({ + userId, + email, + onLoadError, +}: { + userId: string + email: string + onLoadError: (message: string) => void +}) { + const [items, setItems] = useState | null>(null) + const [actionError, setActionError] = useState(null) + const [newTodo, setNewTodo] = useState('') + + useEffect(() => { + let unsubscribe: (() => Promise) | undefined + let cancelled = false + + // The collection's listRule scopes both the list and the realtime events + // to the signed-in user. + todos + .getFullList({ sort: 'created' }) + .then(async (records) => { + if (cancelled) return + setItems(records) + unsubscribe = await todos.subscribe('*', ({ action, record }) => { + setItems((current) => { + if (!current) return current + if (action === 'delete') { + return current.filter((item) => item.id !== record.id) + } + return upsert(current, record) + }) + }) + if (cancelled) await unsubscribe() + }) + .catch((err: unknown) => { + if (!cancelled) onLoadError(getErrorMessage(err)) + }) + + return () => { + cancelled = true + void unsubscribe?.() + } + }, [onLoadError]) + + const run = (action: () => Promise) => { + setActionError(null) + action().catch((err: unknown) => setActionError(getErrorMessage(err))) + } + + const handleAddTodo = () => { + const text = newTodo.trim() + if (!text) return + run(async () => { + const record = await todos.create({ + text, + completed: false, + owner: userId, + }) + setItems((current) => (current ? upsert(current, record) : [record])) + setNewTodo('') + }) + } + + const handleToggleTodo = (todo: Todo) => { + run(async () => { + const record = await todos.update(todo.id, { + completed: !todo.completed, + }) + setItems((current) => (current ? upsert(current, record) : current)) + }) + } + + const handleRemoveTodo = (id: string) => { + run(async () => { + await todos.delete(id) + setItems((current) => + current ? current.filter((item) => item.id !== id) : current, + ) + }) + } + + const completedCount = items?.filter((todo) => todo.completed).length || 0 + const totalCount = items?.length || 0 + + return ( + <> +
+
+

Signed in as

+

{email}

+ {totalCount > 0 && ( +

+ {completedCount} completed, {totalCount - completedCount}{' '} + remaining +

+ )} +
+ +
+ +
+
+ setNewTodo(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') handleAddTodo() + }} + placeholder="What needs to be done?" + aria-label="New todo" + className="demo-input min-w-0 flex-1" + /> + +
+ {actionError && ( +
+

{actionError}

+
+ )} +
+ +
+ {!items ? ( +
+
+

Loading todos...

+
+ ) : items.length === 0 ? ( +
+ +

No todos yet

+

+ Add your first todo above, then open this page in another tab to + see realtime sync. +

+
+ ) : ( +
+ {items.map((todo) => ( +
+ + + + {todo.text} + + + +
+ ))} +
+ )} +
+ + ) +} diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/info.json b/packages/create/src/frameworks/react/add-ons/pocketbase/info.json new file mode 100644 index 00000000..0272e219 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/info.json @@ -0,0 +1,30 @@ +{ + "name": "PocketBase", + "description": "Open source backend in one file (SQLite, auth, realtime, file storage, admin UI).", + "link": "https://pocketbase.io", + "phase": "add-on", + "type": "add-on", + "category": "database", + "exclusive": ["database", "orm"], + "color": "#16161A", + "priority": 135, + "modes": ["file-router"], + "routes": [ + { + "icon": "Database", + "url": "/demo/pocketbase", + "name": "PocketBase", + "path": "src/routes/demo/pocketbase.tsx", + "jsName": "PocketBaseDemo" + } + ], + "envVars": [ + { + "name": "VITE_POCKETBASE_URL", + "description": "PocketBase server URL", + "required": true, + "secret": false, + "file": ".env.local" + } + ] +} diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/package.json b/packages/create/src/frameworks/react/add-ons/pocketbase/package.json new file mode 100644 index 00000000..489a7b91 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "lucide-react": "^0.577.0", + "pocketbase": "^0.28.1" + } +} diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/small-logo.svg b/packages/create/src/frameworks/react/add-ons/pocketbase/small-logo.svg new file mode 100644 index 00000000..c0405388 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/small-logo.svg @@ -0,0 +1 @@ + From 56fbd084799511e793f0a37adb23ac861d660d46 Mon Sep 17 00:00:00 2001 From: Akanyane Date: Fri, 6 Nov 2026 17:32:23 -0300 Subject: [PATCH 2/3] test(create): add PocketBase smoke test and ignore the server binary --- .changeset/pocketbase-addon.md | 16 +---- .../cli/tests-e2e/pocketbase-smoke.spec.ts | 63 +++++++++++++++++++ .../react/add-ons/pocketbase/README.md | 13 ++-- .../pocketbase/assets/_dot_gitignore.append | 5 +- .../pb_migrations/1761955200_created_todos.js | 3 +- .../assets/src/routes/demo/pocketbase.tsx | 5 +- .../react/add-ons/pocketbase/package.json | 2 +- 7 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 packages/cli/tests-e2e/pocketbase-smoke.spec.ts diff --git a/.changeset/pocketbase-addon.md b/.changeset/pocketbase-addon.md index 6bb96052..c8650314 100644 --- a/.changeset/pocketbase-addon.md +++ b/.changeset/pocketbase-addon.md @@ -1,19 +1,5 @@ --- -'@tanstack/cli': minor '@tanstack/create': minor --- -feat(create): add React PocketBase add-on - -`tanstack add pocketbase` (or `--add-ons pocketbase` on `tanstack create`) -wires the PocketBase JS SDK into a React TanStack Start app: - -- `pocketbase` dependency and a `src/lib/pocketbase.ts` client reading - `VITE_POCKETBASE_URL` from `.env.local`, plus a `createPocketBase()` - factory for per-request clients in server functions. -- A `pb_migrations/` JS migration that creates an owner-scoped `todos` - collection when running `./pocketbase serve`, and `pb_data/` added to - `.gitignore`. -- A `/demo/pocketbase` route with email and password sign-in and a private - realtime todo list (create, toggle, delete, live updates across tabs), plus - setup guidance when the server is not reachable. +Add a React PocketBase add-on. `--add-ons pocketbase` wires the PocketBase JS SDK into a TanStack Start app, with a migration for an owner-scoped `todos` collection and a `/demo/pocketbase` route with email and password sign-in and a private realtime todo list. diff --git a/packages/cli/tests-e2e/pocketbase-smoke.spec.ts b/packages/cli/tests-e2e/pocketbase-smoke.spec.ts new file mode 100644 index 00000000..f649853f --- /dev/null +++ b/packages/cli/tests-e2e/pocketbase-smoke.spec.ts @@ -0,0 +1,63 @@ +import { createServer } from 'node:http' +import { writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { expect, test } from '@playwright/test' +import { attachRuntimeGuards, createReactAppFixture } from './helpers' + +test('@matrix builds and renders the PocketBase add-on with a local server fixture', async ({ + page, +}) => { + const requests: Array = [] + const server = createServer((request, response) => { + requests.push(request.url ?? '') + response.writeHead(request.method === 'OPTIONS' ? 204 : 200, { + 'access-control-allow-origin': '*', + 'access-control-allow-headers': '*', + 'content-type': 'application/json', + }) + response.end( + request.method === 'OPTIONS' + ? undefined + : JSON.stringify({ code: 200, message: 'API is healthy.', data: {} }), + ) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + if (!address || typeof address === 'string') { + throw new Error('Missing PocketBase fixture port') + } + try { + const fixture = await createReactAppFixture({ + appName: 'react-pocketbase-smoke', + addOns: ['pocketbase'], + runQualityGatesChecks: true, + afterCreate: async (appDir) => { + await writeFile( + join(appDir, '.env.local'), + `VITE_POCKETBASE_URL=http://127.0.0.1:${address.port}\n`, + ) + }, + }) + const guards = attachRuntimeGuards(page, fixture.url) + try { + await page.goto(`${fixture.url}/demo/pocketbase`) + await expect( + page.getByRole('heading', { name: 'Sign in to see your todos' }), + ).toBeVisible() + await expect(page.getByLabel('Password')).toBeVisible() + expect(requests.some((url) => url.startsWith('/api/health'))).toBe(true) + } finally { + try { + guards.assertClean() + } finally { + guards.dispose() + await fixture.stop() + await fixture.cleanup() + } + } + } finally { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ) + } +}) diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/README.md b/packages/create/src/frameworks/react/add-ons/pocketbase/README.md index 6fcefd69..36bdb391 100644 --- a/packages/create/src/frameworks/react/add-ons/pocketbase/README.md +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/README.md @@ -10,6 +10,7 @@ This project uses [PocketBase](https://pocketbase.io), an open source backend in ``` On first start, PocketBase applies the migration in `pb_migrations/`, which creates the `todos` collection used by the demo. + 3. Open the admin dashboard at http://127.0.0.1:8090/_/ and create your superuser account. 4. Make sure `VITE_POCKETBASE_URL` in `.env.local` points to your PocketBase server (defaults to `http://127.0.0.1:8090`). 5. Run the app, visit `/demo/pocketbase` and create an account. @@ -19,17 +20,17 @@ This project uses [PocketBase](https://pocketbase.io), an open source backend in - `src/lib/pocketbase.ts`: PocketBase JS SDK client (`pb`) and a `createPocketBase()` factory for per-request clients in server functions. - `src/routes/demo/pocketbase.tsx`: email and password sign-in, plus a private todo list with realtime updates. - `pb_migrations/`: JS migrations applied automatically by `pocketbase serve`. -- `pb_data/` is added to `.gitignore` (it holds your local database and uploaded files). +- `.gitignore` entries for the `pocketbase` binary and `pb_data/`, which holds your local database and uploaded files. ### Permissions Each todo has an `owner` relation to the built-in `users` collection, and the collection's API rules enforce ownership on the server: -| Action | Rule | -| -------------------- | --------------------------------------------------------------------------------------------------------- | -| List, view, delete | `@request.auth.id != "" && owner = @request.auth.id` | -| Create | `@request.auth.id != "" && @request.body.owner = @request.auth.id` | -| Update | The list rule, plus `(@request.body.owner:isset = false \|\| @request.body.owner = @request.auth.id)` | +| Action | Rule | +| ------------------ | ----------------------------------------------------------------------------------------------------- | +| List, view, delete | `@request.auth.id != "" && owner = @request.auth.id` | +| Create | `@request.auth.id != "" && @request.body.owner = @request.auth.id` | +| Update | The list rule, plus `(@request.body.owner:isset = false \|\| @request.body.owner = @request.auth.id)` | Realtime subscriptions follow the list rule, so users only receive events for their own todos. Deleting a user also deletes their todos. diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append index 4d4f6a57..a865a572 100644 --- a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/_dot_gitignore.append @@ -1,3 +1,4 @@ - -# PocketBase local data +# PocketBase server binary and local data +/pocketbase +/pocketbase.exe pb_data/ diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js index 25d332d7..b1f60628 100644 --- a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/pb_migrations/1761955200_created_todos.js @@ -17,7 +17,8 @@ migrate( name: 'todos', listRule: ownerOnly, viewRule: ownerOnly, - createRule: '@request.auth.id != "" && @request.body.owner = @request.auth.id', + createRule: + '@request.auth.id != "" && @request.body.owner = @request.auth.id', updateRule: `${ownerOnly} && (@request.body.owner:isset = false || @request.body.owner = @request.auth.id)`, deleteRule: ownerOnly, fields: [ diff --git a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx index 5994b46f..89e971fe 100644 --- a/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx +++ b/packages/create/src/frameworks/react/add-ons/pocketbase/assets/src/routes/demo/pocketbase.tsx @@ -174,10 +174,7 @@ function AuthForm() { {isSignUp ? 'Create an account' : 'Sign in to see your todos'} -
void handleSubmit(e)} - className="mt-4 grid gap-4" - > + void handleSubmit(e)} className="mt-4 grid gap-4">