enh
This commit is contained in:
parent
dc19233ab0
commit
7c9fb6b060
67 changed files with 10072 additions and 0 deletions
13
README.md
13
README.md
|
|
@ -1,2 +1,15 @@
|
||||||
# health-petal
|
# health-petal
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.10. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
|
|
|
||||||
34
apps/backend/.gitignore
vendored
Normal file
34
apps/backend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
15
apps/backend/README.md
Normal file
15
apps/backend/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# backend
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.10. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
BIN
apps/backend/dev.db
Normal file
BIN
apps/backend/dev.db
Normal file
Binary file not shown.
21
apps/backend/package.json
Normal file
21
apps/backend/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"module": "index.ts",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun --watch src/index.ts"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@trpc/server": "^11.6.0",
|
||||||
|
"data-manager-sqlite": "*",
|
||||||
|
"hono": "^4.12.18",
|
||||||
|
"zod": "^3.25.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
apps/backend/src/index.ts
Normal file
43
apps/backend/src/index.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { cors } from 'hono/cors'
|
||||||
|
import { env } from './lib/env-exporter'
|
||||||
|
import { appRouter } from './trpc/router'
|
||||||
|
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
'*',
|
||||||
|
cors({
|
||||||
|
origin: (origin) => {
|
||||||
|
// Allow local dev UIs.
|
||||||
|
if (origin === 'http://localhost:3000') return origin
|
||||||
|
if (origin === 'http://localhost:3001') return origin
|
||||||
|
// Non-browser clients (no Origin header)
|
||||||
|
if (!origin) return '*'
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
allowHeaders: ['content-type'],
|
||||||
|
allowMethods: ['GET', 'POST', 'OPTIONS'],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.get('/health', (c) => c.json({ ok: true }))
|
||||||
|
|
||||||
|
app.all('/trpc/*', (c) =>
|
||||||
|
fetchRequestHandler({
|
||||||
|
endpoint: '/trpc',
|
||||||
|
req: c.req.raw,
|
||||||
|
router: appRouter,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export { app }
|
||||||
|
|
||||||
|
const port = Number(env.PORT || 4004)
|
||||||
|
Bun.serve({
|
||||||
|
port,
|
||||||
|
fetch: app.fetch,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Backend listening on http://localhost:${port}`)
|
||||||
5
apps/backend/src/lib/data-manager-instance.ts
Normal file
5
apps/backend/src/lib/data-manager-instance.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { env } from './env-exporter'
|
||||||
|
import { DataManager } from './data-manager'
|
||||||
|
|
||||||
|
// Singleton container for all backend data access.
|
||||||
|
export const dataManager = new DataManager(env.SQLITE_PATH || 'dev.db')
|
||||||
28
apps/backend/src/lib/data-manager.ts
Normal file
28
apps/backend/src/lib/data-manager.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
createStorageSpacesRepo,
|
||||||
|
runMigrations,
|
||||||
|
type StorageSpacesRepo,
|
||||||
|
} from 'data-manager-sqlite'
|
||||||
|
|
||||||
|
import type { StorageSpacesService } from '../trpc/router'
|
||||||
|
|
||||||
|
export class DataManager {
|
||||||
|
readonly storageSpaces: StorageSpacesService
|
||||||
|
readonly close: () => void
|
||||||
|
|
||||||
|
constructor(sqlitePath: string) {
|
||||||
|
const { repo, sqlite, close } = createStorageSpacesRepo({ sqlitePath })
|
||||||
|
runMigrations(sqlite)
|
||||||
|
|
||||||
|
this.close = close
|
||||||
|
|
||||||
|
// Keep the service surface stable for the router.
|
||||||
|
this.storageSpaces = {
|
||||||
|
getStorageSpaces: () => repo.getStorageSpaces(),
|
||||||
|
getStorageSpaceById: (id) => repo.getStorageSpaceById(id),
|
||||||
|
createStorageSpace: (input) => repo.createStorageSpace(input),
|
||||||
|
updateStorageSpace: (id, patch) => repo.updateStorageSpace(id, patch),
|
||||||
|
deleteStorageSpace: (id) => repo.deleteStorageSpace(id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
apps/backend/src/lib/env-exporter.ts
Normal file
4
apps/backend/src/lib/env-exporter.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const env = {
|
||||||
|
PORT: process.env.PORT || '4004',
|
||||||
|
SQLITE_PATH: process.env.SQLITE_PATH,
|
||||||
|
} as const
|
||||||
92
apps/backend/src/trpc/router.ts
Normal file
92
apps/backend/src/trpc/router.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { dataManager } from '../lib/data-manager-instance'
|
||||||
|
|
||||||
|
export const StorageSpaceSchema = z.object({
|
||||||
|
id: z.number().int(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
image_urls: z.array(z.string()),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type StorageSpace = z.infer<typeof StorageSpaceSchema>
|
||||||
|
|
||||||
|
export type StorageSpacesService = {
|
||||||
|
getStorageSpaces: () => Promise<StorageSpace[]>
|
||||||
|
getStorageSpaceById: (id: number) => Promise<StorageSpace | null>
|
||||||
|
createStorageSpace: (input: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls: string[]
|
||||||
|
}) => Promise<StorageSpace>
|
||||||
|
updateStorageSpace: (
|
||||||
|
id: number,
|
||||||
|
patch: {
|
||||||
|
name?: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls?: string[]
|
||||||
|
},
|
||||||
|
) => Promise<StorageSpace | null>
|
||||||
|
deleteStorageSpace: (id: number) => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = initTRPC.create()
|
||||||
|
|
||||||
|
const storageSpacesRouter = t.router({
|
||||||
|
getStorageSpaces: t.procedure.output(z.array(StorageSpaceSchema)).query(() => {
|
||||||
|
return dataManager.storageSpaces.getStorageSpaces()
|
||||||
|
}),
|
||||||
|
|
||||||
|
getStorageSpaceById: t.procedure
|
||||||
|
.input(z.object({ id: z.number().int() }))
|
||||||
|
.output(StorageSpaceSchema.nullable())
|
||||||
|
.query(({ input }) => {
|
||||||
|
return dataManager.storageSpaces.getStorageSpaceById(input.id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
addStorageSpace: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
image_urls: z.array(z.string()).default([]),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(StorageSpaceSchema)
|
||||||
|
.mutation(({ input }) => {
|
||||||
|
return dataManager.storageSpaces.createStorageSpace({
|
||||||
|
name: input.name,
|
||||||
|
description: input.description ?? null,
|
||||||
|
image_urls: input.image_urls,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateStorageSpace: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
id: z.number().int(),
|
||||||
|
name: z.string().min(1).optional(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
image_urls: z.array(z.string()).optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(StorageSpaceSchema.nullable())
|
||||||
|
.mutation(({ input }) => {
|
||||||
|
const { id, ...patch } = input
|
||||||
|
return dataManager.storageSpaces.updateStorageSpace(id, patch)
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteStorageSpace: t.procedure
|
||||||
|
.input(z.object({ id: z.number().int() }))
|
||||||
|
.output(z.object({ ok: z.boolean() }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const ok = await dataManager.storageSpaces.deleteStorageSpace(input.id)
|
||||||
|
return { ok }
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const appRouter = t.router({
|
||||||
|
storageSpaces: storageSpacesRouter,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter
|
||||||
29
apps/backend/tsconfig.json
Normal file
29
apps/backend/tsconfig.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"types": ["bun"]
|
||||||
|
}
|
||||||
|
}
|
||||||
18
apps/pharmanager/.cta.json
Normal file
18
apps/pharmanager/.cta.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"projectName": "pharmanager",
|
||||||
|
"mode": "file-router",
|
||||||
|
"typescript": true,
|
||||||
|
"packageManager": "bun",
|
||||||
|
"includeExamples": false,
|
||||||
|
"tailwind": true,
|
||||||
|
"addOnOptions": {},
|
||||||
|
"envVarValues": {},
|
||||||
|
"git": false,
|
||||||
|
"install": true,
|
||||||
|
"routerOnly": true,
|
||||||
|
"version": 1,
|
||||||
|
"framework": "react",
|
||||||
|
"chosenAddOns": [
|
||||||
|
"biome"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
apps/pharmanager/.gitignore
vendored
Normal file
13
apps/pharmanager/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.env
|
||||||
|
.nitro
|
||||||
|
.tanstack
|
||||||
|
.wrangler
|
||||||
|
.output
|
||||||
|
.vinxi
|
||||||
|
__unconfig*
|
||||||
|
todos.json
|
||||||
35
apps/pharmanager/.vscode/settings.json
vendored
Normal file
35
apps/pharmanager/.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"files.readonlyInclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports.biome": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
204
apps/pharmanager/README.md
Normal file
204
apps/pharmanager/README.md
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
Welcome to your new TanStack Start app!
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
To run this application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
bun --bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building For Production
|
||||||
|
|
||||||
|
To build this application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun --bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun --bun run test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
|
||||||
|
|
||||||
|
### Removing Tailwind CSS
|
||||||
|
|
||||||
|
If you prefer not to use Tailwind CSS:
|
||||||
|
|
||||||
|
1. Remove the demo pages in `src/routes/demo/`
|
||||||
|
2. Replace the Tailwind import in `src/styles.css` with your own styles
|
||||||
|
3. Remove `tailwindcss()` from the plugins array in `vite.config.ts`
|
||||||
|
4. Uninstall the packages: `bun install @tailwindcss/vite tailwindcss -D`
|
||||||
|
|
||||||
|
## Linting & Formatting
|
||||||
|
|
||||||
|
This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available:
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun --bun run lint
|
||||||
|
bun --bun run format
|
||||||
|
bun --bun run check
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`.
|
||||||
|
|
||||||
|
### Adding A Route
|
||||||
|
|
||||||
|
To add a new route to your application just add a new file in the `./src/routes` directory.
|
||||||
|
|
||||||
|
TanStack will automatically generate the content of the route file for you.
|
||||||
|
|
||||||
|
Now that you have two routes you can use a `Link` component to navigate between them.
|
||||||
|
|
||||||
|
### Adding Links
|
||||||
|
|
||||||
|
To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
```
|
||||||
|
|
||||||
|
Then anywhere in your JSX you can use it like so:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Link to="/about">About</Link>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a link that will navigate to the `/about` route.
|
||||||
|
|
||||||
|
More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
|
||||||
|
|
||||||
|
### Using A Layout
|
||||||
|
|
||||||
|
In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`.
|
||||||
|
|
||||||
|
Here is an example layout that includes a header:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createRootRoute({
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{ charSet: 'utf-8' },
|
||||||
|
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||||
|
{ title: 'My App' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
shellComponent: ({ children }) => (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<HeadContent />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<Link to="/">Home</Link>
|
||||||
|
<Link to="/about">About</Link>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
{children}
|
||||||
|
<Scripts />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
|
||||||
|
|
||||||
|
## Server Functions
|
||||||
|
|
||||||
|
TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createServerFn } from '@tanstack/react-start'
|
||||||
|
|
||||||
|
const getServerTime = createServerFn({
|
||||||
|
method: 'GET',
|
||||||
|
}).handler(async () => {
|
||||||
|
return new Date().toISOString()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use in a component
|
||||||
|
function MyComponent() {
|
||||||
|
const [time, setTime] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getServerTime().then(setTime)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div>Server time: {time}</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
|
||||||
|
You can create API routes by using the `server` property in your route definitions:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { json } from '@tanstack/react-start'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/api/hello')({
|
||||||
|
server: {
|
||||||
|
handlers: {
|
||||||
|
GET: () => json({ message: 'Hello, World!' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Fetching
|
||||||
|
|
||||||
|
There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/people')({
|
||||||
|
loader: async () => {
|
||||||
|
const response = await fetch('https://swapi.dev/api/people')
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
|
component: PeopleComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function PeopleComponent() {
|
||||||
|
const data = Route.useLoaderData()
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{data.results.map((person) => (
|
||||||
|
<li key={person.name}>{person.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
|
||||||
|
|
||||||
|
# Demo files
|
||||||
|
|
||||||
|
Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
|
||||||
|
|
||||||
|
# Learn More
|
||||||
|
|
||||||
|
You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
|
||||||
|
|
||||||
|
For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start).
|
||||||
36
apps/pharmanager/biome.json
Normal file
36
apps/pharmanager/biome.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"includes": [
|
||||||
|
"**/src/**/*",
|
||||||
|
"**/.vscode/**/*",
|
||||||
|
"**/index.html",
|
||||||
|
"**/vite.config.ts",
|
||||||
|
"!**/src/routeTree.gen.ts",
|
||||||
|
"!**/src/styles.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab"
|
||||||
|
},
|
||||||
|
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
apps/pharmanager/index.html
Normal file
13
apps/pharmanager/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>pharmanager</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
apps/pharmanager/package.json
Normal file
52
apps/pharmanager/package.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"name": "pharmanager",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"imports": {
|
||||||
|
"#/*": "./src/*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev --port 3000",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test": "vitest run",
|
||||||
|
"format": "biome format",
|
||||||
|
"lint": "biome lint",
|
||||||
|
"check": "biome check"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@tanstack/react-devtools": "latest",
|
||||||
|
"@tanstack/react-router": "latest",
|
||||||
|
"@tanstack/react-router-devtools": "latest",
|
||||||
|
"@tanstack/router-plugin": "^1.132.0",
|
||||||
|
"lucide-react": "^0.545.0",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"shared-react": "*",
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.4.5",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@tanstack/devtools-vite": "latest",
|
||||||
|
"@tanstack/router-plugin": "latest",
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@types/react": "^19.2.0",
|
||||||
|
"@types/react-dom": "^19.2.0",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
|
"typescript": "^6.0.2",
|
||||||
|
"vite": "^8.0.0",
|
||||||
|
"vitest": "^4.1.5",
|
||||||
|
"@repo/shared": "*"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"lightningcss"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apps/pharmanager/public/favicon.ico
Normal file
BIN
apps/pharmanager/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
apps/pharmanager/public/logo192.png
Normal file
BIN
apps/pharmanager/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
apps/pharmanager/public/logo512.png
Normal file
BIN
apps/pharmanager/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
apps/pharmanager/public/manifest.json
Normal file
25
apps/pharmanager/public/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "TanStack App",
|
||||||
|
"name": "Create TanStack App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
3
apps/pharmanager/public/robots.txt
Normal file
3
apps/pharmanager/public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
27
apps/pharmanager/src/main.tsx
Normal file
27
apps/pharmanager/src/main.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
||||||
|
import { routeTree } from './routeTree.gen'
|
||||||
|
import { TrpcProvider } from 'shared-react'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
routeTree,
|
||||||
|
defaultPreload: 'intent',
|
||||||
|
scrollRestoration: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: typeof router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('app')!
|
||||||
|
|
||||||
|
if (!rootElement.innerHTML) {
|
||||||
|
const root = ReactDOM.createRoot(rootElement)
|
||||||
|
root.render(
|
||||||
|
<TrpcProvider baseUrl="http://localhost:3001">
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</TrpcProvider>,
|
||||||
|
)
|
||||||
|
}
|
||||||
59
apps/pharmanager/src/routeTree.gen.ts
Normal file
59
apps/pharmanager/src/routeTree.gen.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
const IndexRoute = IndexRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
export interface FileRoutesByFullPath {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesByTo {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesById {
|
||||||
|
__root__: typeof rootRouteImport
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRouteTypes {
|
||||||
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
fullPaths: '/'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to: '/'
|
||||||
|
id: '__root__' | '/'
|
||||||
|
fileRoutesById: FileRoutesById
|
||||||
|
}
|
||||||
|
export interface RootRouteChildren {
|
||||||
|
IndexRoute: typeof IndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface FileRoutesByPath {
|
||||||
|
'/': {
|
||||||
|
id: '/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/'
|
||||||
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
|
IndexRoute: IndexRoute,
|
||||||
|
}
|
||||||
|
export const routeTree = rootRouteImport
|
||||||
|
._addFileChildren(rootRouteChildren)
|
||||||
|
._addFileTypes<FileRouteTypes>()
|
||||||
19
apps/pharmanager/src/router.tsx
Normal file
19
apps/pharmanager/src/router.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
|
||||||
|
import { routeTree } from './routeTree.gen'
|
||||||
|
|
||||||
|
export function getRouter() {
|
||||||
|
const router = createTanStackRouter({
|
||||||
|
routeTree,
|
||||||
|
scrollRestoration: true,
|
||||||
|
defaultPreload: 'intent',
|
||||||
|
defaultPreloadStaleTime: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: ReturnType<typeof getRouter>
|
||||||
|
}
|
||||||
|
}
|
||||||
28
apps/pharmanager/src/routes/__root.tsx
Normal file
28
apps/pharmanager/src/routes/__root.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Outlet, createRootRoute } from '@tanstack/react-router'
|
||||||
|
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
||||||
|
import { TanStackDevtools } from '@tanstack/react-devtools'
|
||||||
|
|
||||||
|
import '../styles.css'
|
||||||
|
|
||||||
|
export const Route = createRootRoute({
|
||||||
|
component: RootComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RootComponent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Outlet />
|
||||||
|
<TanStackDevtools
|
||||||
|
config={{
|
||||||
|
position: 'bottom-right',
|
||||||
|
}}
|
||||||
|
plugins={[
|
||||||
|
{
|
||||||
|
name: 'TanStack Router',
|
||||||
|
render: <TanStackRouterDevtoolsPanel />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
45
apps/pharmanager/src/routes/index.tsx
Normal file
45
apps/pharmanager/src/routes/index.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import type { Person } from '@repo/shared'
|
||||||
|
import { useCounterStore, useGetStorageSpaces } from 'shared-react'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/')({ component: Home })
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const shafi:Person = {age: 32, name: 'Shafi'}
|
||||||
|
const count = useCounterStore((s) => s.count)
|
||||||
|
const inc = useCounterStore((s) => s.inc)
|
||||||
|
const spaces = useGetStorageSpaces()
|
||||||
|
return (
|
||||||
|
<div className="p-8">
|
||||||
|
<h1 className="text-4xl font-bold">Welcome to TanStack Start</h1>
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
{shafi.name} is {shafi.age} years old.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mt-6 rounded bg-black px-4 py-2 text-white"
|
||||||
|
onClick={inc}
|
||||||
|
>
|
||||||
|
Count: {count}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<h2 className="text-2xl font-semibold">Storage Spaces</h2>
|
||||||
|
{spaces.isLoading ? (
|
||||||
|
<p className="mt-2">Loading…</p>
|
||||||
|
) : spaces.error ? (
|
||||||
|
<p className="mt-2 text-red-600">Failed to load storage spaces</p>
|
||||||
|
) : (
|
||||||
|
<ul className="mt-2 list-disc pl-5">
|
||||||
|
{spaces.data?.map((s) => (
|
||||||
|
<li key={s.id}>
|
||||||
|
{s.name}
|
||||||
|
{s.description ? `: ${s.description}` : ''}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
17
apps/pharmanager/src/styles.css
Normal file
17
apps/pharmanager/src/styles.css
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
28
apps/pharmanager/tsconfig.json
Normal file
28
apps/pharmanager/tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "ESNext",
|
||||||
|
"paths": {
|
||||||
|
"#/*": ["./src/*"],
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["vite/client", "bun"],
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
}
|
||||||
|
}
|
||||||
19
apps/pharmanager/vite.config.ts
Normal file
19
apps/pharmanager/vite.config.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { devtools } from '@tanstack/devtools-vite'
|
||||||
|
|
||||||
|
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
||||||
|
|
||||||
|
import viteReact from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
const config = defineConfig({
|
||||||
|
resolve: { tsconfigPaths: true },
|
||||||
|
plugins: [
|
||||||
|
devtools(),
|
||||||
|
tailwindcss(),
|
||||||
|
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
|
||||||
|
viteReact(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default config
|
||||||
1
apps/user-ui
Submodule
1
apps/user-ui
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 4b1f0916c80f839aaf6523cf72fd7e36355887a1
|
||||||
3
bunfig.toml
Normal file
3
bunfig.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[install]
|
||||||
|
# Hoisted installs help avoid multiple React copies across workspaces.
|
||||||
|
linker = "hoisted"
|
||||||
1
index.ts
Normal file
1
index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
console.log("Hello via Bun!");
|
||||||
16
package.json
Normal file
16
package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "health-petal",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"apps/*",
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
packages/api/package.json
Normal file
12
packages/api/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "api",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"@trpc/server": "^11.6.0",
|
||||||
|
"zod": "^3.25.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/api/src/index.ts
Normal file
4
packages/api/src/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export { appRouter } from './router'
|
||||||
|
export type { AppRouter } from './router'
|
||||||
|
export type { TrpcContext } from './router'
|
||||||
|
export type { StorageSpace, StorageSpacesService } from './router/storageSpaces'
|
||||||
11
packages/api/src/router/index.ts
Normal file
11
packages/api/src/router/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { storageSpacesRouter, type TrpcContext } from './storageSpaces'
|
||||||
|
|
||||||
|
const t = initTRPC.context<TrpcContext>().create()
|
||||||
|
|
||||||
|
export const appRouter = t.router({
|
||||||
|
storageSpaces: storageSpacesRouter,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter
|
||||||
|
export type { TrpcContext } from './storageSpaces'
|
||||||
86
packages/api/src/router/storageSpaces.ts
Normal file
86
packages/api/src/router/storageSpaces.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const StorageSpaceSchema = z.object({
|
||||||
|
id: z.number().int(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
image_urls: z.array(z.string()),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type StorageSpace = z.infer<typeof StorageSpaceSchema>
|
||||||
|
|
||||||
|
export type StorageSpacesService = {
|
||||||
|
getStorageSpaces: () => Promise<StorageSpace[]>
|
||||||
|
getStorageSpaceById: (id: number) => Promise<StorageSpace | null>
|
||||||
|
createStorageSpace: (input: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls: string[]
|
||||||
|
}) => Promise<StorageSpace>
|
||||||
|
updateStorageSpace: (id: number, patch: {
|
||||||
|
name?: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls?: string[]
|
||||||
|
}) => Promise<StorageSpace | null>
|
||||||
|
deleteStorageSpace: (id: number) => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TrpcContext = {
|
||||||
|
storageSpaces: StorageSpacesService
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = initTRPC.context<TrpcContext>().create()
|
||||||
|
|
||||||
|
export const storageSpacesRouter = t.router({
|
||||||
|
getStorageSpaces: t.procedure.output(z.array(StorageSpaceSchema)).query(({ ctx }) => {
|
||||||
|
return ctx.storageSpaces.getStorageSpaces()
|
||||||
|
}),
|
||||||
|
|
||||||
|
getStorageSpaceById: t.procedure
|
||||||
|
.input(z.object({ id: z.number().int() }))
|
||||||
|
.output(StorageSpaceSchema.nullable())
|
||||||
|
.query(({ ctx, input }) => {
|
||||||
|
return ctx.storageSpaces.getStorageSpaceById(input.id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
addStorageSpace: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
image_urls: z.array(z.string()).default([]),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(StorageSpaceSchema)
|
||||||
|
.mutation(({ ctx, input }) => {
|
||||||
|
return ctx.storageSpaces.createStorageSpace({
|
||||||
|
name: input.name,
|
||||||
|
description: input.description ?? null,
|
||||||
|
image_urls: input.image_urls,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateStorageSpace: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
id: z.number().int(),
|
||||||
|
name: z.string().min(1).optional(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
image_urls: z.array(z.string()).optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(StorageSpaceSchema.nullable())
|
||||||
|
.mutation(({ ctx, input }) => {
|
||||||
|
const { id, ...patch } = input
|
||||||
|
return ctx.storageSpaces.updateStorageSpace(id, patch)
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteStorageSpace: t.procedure
|
||||||
|
.input(z.object({ id: z.number().int() }))
|
||||||
|
.output(z.object({ ok: z.boolean() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const ok = await ctx.storageSpaces.deleteStorageSpace(input.id)
|
||||||
|
return { ok }
|
||||||
|
}),
|
||||||
|
})
|
||||||
4
packages/api/tsconfig.json
Normal file
4
packages/api/tsconfig.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
16
packages/data-manager-sqlite/drizzle.config.ts
Normal file
16
packages/data-manager-sqlite/drizzle.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Config } from 'drizzle-kit'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { env } from './src/lib/env-exporter'
|
||||||
|
|
||||||
|
// Default to the backend dev database in the monorepo.
|
||||||
|
const defaultDbPath = path.resolve(import.meta.dir, '../../apps/backend/dev.db')
|
||||||
|
const dbPath = env.SQLITE_PATH || defaultDbPath
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: './src/schema/index.ts',
|
||||||
|
out: './drizzle',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
dbCredentials: {
|
||||||
|
url: dbPath,
|
||||||
|
},
|
||||||
|
} satisfies Config
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE `storage_spaces` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
`image_urls` text DEFAULT '[]' NOT NULL
|
||||||
|
);
|
||||||
57
packages/data-manager-sqlite/drizzle/meta/0000_snapshot.json
Normal file
57
packages/data-manager-sqlite/drizzle/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "1fe82ac3-40f3-4846-8d99-eddabf4580a2",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"storage_spaces": {
|
||||||
|
"name": "storage_spaces",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"image_urls": {
|
||||||
|
"name": "image_urls",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/data-manager-sqlite/drizzle/meta/_journal.json
Normal file
13
packages/data-manager-sqlite/drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1778999618092,
|
||||||
|
"tag": "0000_clumsy_morgan_stark",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
packages/data-manager-sqlite/package.json
Normal file
22
packages/data-manager-sqlite/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "data-manager-sqlite",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"migrate:generate": "drizzle-kit generate",
|
||||||
|
"migrate:push": "drizzle-kit push",
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"drizzle-orm": "^0.44.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"drizzle-kit": "^0.31.4",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@types/bun": "^1.3.14",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
packages/data-manager-sqlite/src/db.ts
Normal file
24
packages/data-manager-sqlite/src/db.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Database } from 'bun:sqlite'
|
||||||
|
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { mkdirSync } from 'node:fs'
|
||||||
|
import { env } from './lib/env-exporter'
|
||||||
|
|
||||||
|
export type DbOptions = {
|
||||||
|
sqlitePath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDb(options: DbOptions = {}) {
|
||||||
|
const configured = options.sqlitePath || env.SQLITE_PATH || 'dev.db'
|
||||||
|
const sqlitePath = path.isAbsolute(configured)
|
||||||
|
? configured
|
||||||
|
: path.resolve(process.cwd(), configured)
|
||||||
|
|
||||||
|
// Ensure parent directory exists (eg. when using ./data/dev.db)
|
||||||
|
mkdirSync(path.dirname(sqlitePath), { recursive: true })
|
||||||
|
|
||||||
|
const sqlite = new Database(sqlitePath)
|
||||||
|
const db = drizzle(sqlite)
|
||||||
|
|
||||||
|
return { db, sqlite, sqlitePath }
|
||||||
|
}
|
||||||
8
packages/data-manager-sqlite/src/index.ts
Normal file
8
packages/data-manager-sqlite/src/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export { createDb } from './db'
|
||||||
|
export { runMigrations } from './migrate'
|
||||||
|
export {
|
||||||
|
createStorageSpacesRepo,
|
||||||
|
type StorageSpace,
|
||||||
|
type StorageSpacesRepo,
|
||||||
|
} from './storageSpaces'
|
||||||
|
export { storageSpaces } from './schema/storageSpaces'
|
||||||
3
packages/data-manager-sqlite/src/lib/env-exporter.ts
Normal file
3
packages/data-manager-sqlite/src/lib/env-exporter.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const env = {
|
||||||
|
SQLITE_PATH: process.env.SQLITE_PATH,
|
||||||
|
} as const
|
||||||
12
packages/data-manager-sqlite/src/migrate.ts
Normal file
12
packages/data-manager-sqlite/src/migrate.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import path from 'node:path'
|
||||||
|
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
||||||
|
import { migrate } from 'drizzle-orm/bun-sqlite/migrator'
|
||||||
|
import type { Database } from 'bun:sqlite'
|
||||||
|
|
||||||
|
export function runMigrations(sqlite: Database, opts?: { migrationsFolder?: string }) {
|
||||||
|
const migrationsFolder =
|
||||||
|
opts?.migrationsFolder || path.resolve(import.meta.dir, '../drizzle')
|
||||||
|
|
||||||
|
const db = drizzle(sqlite)
|
||||||
|
migrate(db, { migrationsFolder })
|
||||||
|
}
|
||||||
1
packages/data-manager-sqlite/src/schema/index.ts
Normal file
1
packages/data-manager-sqlite/src/schema/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './storageSpaces'
|
||||||
9
packages/data-manager-sqlite/src/schema/storageSpaces.ts
Normal file
9
packages/data-manager-sqlite/src/schema/storageSpaces.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const storageSpaces = sqliteTable('storage_spaces', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
// JSON array string of image URLs
|
||||||
|
imageUrls: text('image_urls').notNull().default('[]'),
|
||||||
|
})
|
||||||
125
packages/data-manager-sqlite/src/storageSpaces.ts
Normal file
125
packages/data-manager-sqlite/src/storageSpaces.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
import type { Database } from 'bun:sqlite'
|
||||||
|
|
||||||
|
import { createDb } from './db'
|
||||||
|
import { storageSpaces } from './schema/storageSpaces'
|
||||||
|
|
||||||
|
export type StorageSpace = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string | null
|
||||||
|
image_urls: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseImageUrls(raw: string): string[] {
|
||||||
|
try {
|
||||||
|
const v = JSON.parse(raw)
|
||||||
|
return Array.isArray(v) && v.every((x) => typeof x === 'string') ? v : []
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeImageUrls(urls: string[]): string {
|
||||||
|
return JSON.stringify(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStorageSpace(row: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string | null
|
||||||
|
imageUrls: string
|
||||||
|
}): StorageSpace {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
image_urls: parseImageUrls(row.imageUrls),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorageSpacesRepo = {
|
||||||
|
getStorageSpaces: () => Promise<StorageSpace[]>
|
||||||
|
getStorageSpaceById: (id: number) => Promise<StorageSpace | null>
|
||||||
|
createStorageSpace: (input: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls: string[]
|
||||||
|
}) => Promise<StorageSpace>
|
||||||
|
updateStorageSpace: (id: number, patch: {
|
||||||
|
name?: string
|
||||||
|
description?: string | null
|
||||||
|
image_urls?: string[]
|
||||||
|
}) => Promise<StorageSpace | null>
|
||||||
|
deleteStorageSpace: (id: number) => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStorageSpacesRepo(opts?: { sqlitePath?: string }): {
|
||||||
|
repo: StorageSpacesRepo
|
||||||
|
sqlite: Database
|
||||||
|
close: () => void
|
||||||
|
} {
|
||||||
|
const { db, sqlite } = createDb({ sqlitePath: opts?.sqlitePath })
|
||||||
|
|
||||||
|
const repo: StorageSpacesRepo = {
|
||||||
|
async getStorageSpaces() {
|
||||||
|
const rows = await db.select().from(storageSpaces).all()
|
||||||
|
return rows.map(toStorageSpace)
|
||||||
|
},
|
||||||
|
|
||||||
|
async getStorageSpaceById(id) {
|
||||||
|
const row = await db
|
||||||
|
.select()
|
||||||
|
.from(storageSpaces)
|
||||||
|
.where(eq(storageSpaces.id, id))
|
||||||
|
.get()
|
||||||
|
return row ? toStorageSpace(row) : null
|
||||||
|
},
|
||||||
|
|
||||||
|
async createStorageSpace(input) {
|
||||||
|
const created = await db
|
||||||
|
.insert(storageSpaces)
|
||||||
|
.values({
|
||||||
|
name: input.name,
|
||||||
|
description: input.description ?? null,
|
||||||
|
imageUrls: serializeImageUrls(input.image_urls),
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
|
||||||
|
return toStorageSpace(created)
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateStorageSpace(id, patch) {
|
||||||
|
const updated = await db
|
||||||
|
.update(storageSpaces)
|
||||||
|
.set({
|
||||||
|
...(patch.name !== undefined ? { name: patch.name } : {}),
|
||||||
|
...(patch.description !== undefined ? { description: patch.description } : {}),
|
||||||
|
...(patch.image_urls !== undefined
|
||||||
|
? { imageUrls: serializeImageUrls(patch.image_urls) }
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
.where(eq(storageSpaces.id, id))
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
|
||||||
|
return updated ? toStorageSpace(updated) : null
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteStorageSpace(id) {
|
||||||
|
const deleted = await db
|
||||||
|
.delete(storageSpaces)
|
||||||
|
.where(eq(storageSpaces.id, id))
|
||||||
|
.returning({ id: storageSpaces.id })
|
||||||
|
.get()
|
||||||
|
return Boolean(deleted)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
repo,
|
||||||
|
sqlite,
|
||||||
|
close: () => sqlite.close(),
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/data-manager-sqlite/tsconfig.json
Normal file
7
packages/data-manager-sqlite/tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node", "bun"]
|
||||||
|
},
|
||||||
|
"include": ["src", "drizzle.config.ts"]
|
||||||
|
}
|
||||||
18
packages/shared-react/package.json
Normal file
18
packages/shared-react/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "shared-react",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "19.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.87.4",
|
||||||
|
"@trpc/client": "^11.6.0",
|
||||||
|
"@trpc/react-query": "^11.6.0",
|
||||||
|
"@repo/shared": "*",
|
||||||
|
"zustand": "^5.0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
packages/shared-react/src/hooks/storageSpaces.ts
Normal file
21
packages/shared-react/src/hooks/storageSpaces.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { trpc } from '../trpc'
|
||||||
|
|
||||||
|
export function useGetStorageSpaces() {
|
||||||
|
return trpc.storageSpaces.getStorageSpaces.useQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGetStorageSpaceById(id: number) {
|
||||||
|
return trpc.storageSpaces.getStorageSpaceById.useQuery({ id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAddStorageSpace() {
|
||||||
|
return trpc.storageSpaces.addStorageSpace.useMutation()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdateStorageSpace() {
|
||||||
|
return trpc.storageSpaces.updateStorageSpace.useMutation()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteStorageSpace() {
|
||||||
|
return trpc.storageSpaces.deleteStorageSpace.useMutation()
|
||||||
|
}
|
||||||
4
packages/shared-react/src/index.ts
Normal file
4
packages/shared-react/src/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './query'
|
||||||
|
export * from './store'
|
||||||
|
export * from './provider'
|
||||||
|
export * from './hooks/storageSpaces'
|
||||||
20
packages/shared-react/src/provider.tsx
Normal file
20
packages/shared-react/src/provider.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import { createTrpcClient, trpc } from './trpc'
|
||||||
|
|
||||||
|
const apiUrl = 'http://192.168.100.113:4004'
|
||||||
|
|
||||||
|
export function TrpcProvider(props: {
|
||||||
|
baseUrl: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [queryClient] = React.useState(() => new QueryClient())
|
||||||
|
// const [client] = React.useState(() => createTrpcClient(props.baseUrl))
|
||||||
|
const [client] = React.useState(() => createTrpcClient(apiUrl))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<trpc.Provider client={client} queryClient={queryClient}>
|
||||||
|
<QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider>
|
||||||
|
</trpc.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
packages/shared-react/src/query.ts
Normal file
12
packages/shared-react/src/query.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
export function createQueryClient() {
|
||||||
|
return new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: 1,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
15
packages/shared-react/src/store.ts
Normal file
15
packages/shared-react/src/store.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
export type CounterStore = {
|
||||||
|
count: number
|
||||||
|
inc: () => void
|
||||||
|
dec: () => void
|
||||||
|
reset: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCounterStore = create<CounterStore>((set) => ({
|
||||||
|
count: 0,
|
||||||
|
inc: () => set((s) => ({ count: s.count + 1 })),
|
||||||
|
dec: () => set((s) => ({ count: s.count - 1 })),
|
||||||
|
reset: () => set({ count: 0 }),
|
||||||
|
}))
|
||||||
15
packages/shared-react/src/trpc.ts
Normal file
15
packages/shared-react/src/trpc.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { createTRPCReact } from '@trpc/react-query'
|
||||||
|
import { httpBatchLink } from '@trpc/client'
|
||||||
|
import type { AppRouter } from '@repo/shared'
|
||||||
|
|
||||||
|
export const trpc = createTRPCReact<AppRouter>()
|
||||||
|
|
||||||
|
export function createTrpcClient(baseUrl: string) {
|
||||||
|
return trpc.createClient({
|
||||||
|
links: [
|
||||||
|
httpBatchLink({
|
||||||
|
url: `${baseUrl}/trpc`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
4
packages/shared-react/tsconfig.json
Normal file
4
packages/shared-react/tsconfig.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
6
packages/shared/package.json
Normal file
6
packages/shared/package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@repo/shared",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts"
|
||||||
|
}
|
||||||
18
packages/shared/src/index.ts
Normal file
18
packages/shared/src/index.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
export type Person = {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const person:Person = {
|
||||||
|
name: 'Shafi',
|
||||||
|
age: 26
|
||||||
|
}
|
||||||
|
|
||||||
|
export function greetPerson(person: Person) {
|
||||||
|
return `hello ${person.name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// tRPC contract types (router lives in apps/backend)
|
||||||
|
export type {
|
||||||
|
AppRouter,
|
||||||
|
} from '../../../apps/backend/src/trpc/router'
|
||||||
7
packages/shared/tsconfig.json
Normal file
7
packages/shared/tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["bun"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
5655
session-ses_1d40.md
Normal file
5655
session-ses_1d40.md
Normal file
File diff suppressed because one or more lines are too long
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue