This commit is contained in:
shafi54 2026-05-17 23:24:32 +05:30
parent dc19233ab0
commit 7c9fb6b060
67 changed files with 10072 additions and 0 deletions

View file

@ -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
View 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
View 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

Binary file not shown.

21
apps/backend/package.json Normal file
View 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
View 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}`)

View 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')

View 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),
}
}
}

View file

@ -0,0 +1,4 @@
export const env = {
PORT: process.env.PORT || '4004',
SQLITE_PATH: process.env.SQLITE_PATH,
} as const

View 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

View 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"]
}
}

View 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
View 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
View 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
View 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).

View 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"
}
}
}

View 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>

View 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"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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>,
)
}

View 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>()

View 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>
}
}

View 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 />,
},
]}
/>
</>
)
}

View 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>
)
}

View file

@ -0,0 +1,17 @@
@import "tailwindcss";
* {
box-sizing: border-box;
}
html,
body,
#app {
min-height: 100%;
}
body {
margin: 0;
}

View 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
}
}

View 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

@ -0,0 +1 @@
Subproject commit 4b1f0916c80f839aaf6523cf72fd7e36355887a1

2883
bun.lock Normal file

File diff suppressed because it is too large Load diff

3
bunfig.toml Normal file
View file

@ -0,0 +1,3 @@
[install]
# Hoisted installs help avoid multiple React copies across workspaces.
linker = "hoisted"

1
index.ts Normal file
View file

@ -0,0 +1 @@
console.log("Hello via Bun!");

16
package.json Normal file
View 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
View 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"
}
}

View 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'

View 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'

View 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 }
}),
})

View file

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src"]
}

View 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

View file

@ -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
);

View 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": {}
}
}

View file

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1778999618092,
"tag": "0000_clumsy_morgan_stark",
"breakpoints": true
}
]
}

View 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"
}
}

View 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 }
}

View 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'

View file

@ -0,0 +1,3 @@
export const env = {
SQLITE_PATH: process.env.SQLITE_PATH,
} as const

View 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 })
}

View file

@ -0,0 +1 @@
export * from './storageSpaces'

View 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('[]'),
})

View 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(),
}
}

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"types": ["node", "bun"]
},
"include": ["src", "drizzle.config.ts"]
}

View 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"
}
}

View 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()
}

View file

@ -0,0 +1,4 @@
export * from './query'
export * from './store'
export * from './provider'
export * from './hooks/storageSpaces'

View 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>
)
}

View file

@ -0,0 +1,12 @@
import { QueryClient } from '@tanstack/react-query'
export function createQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
},
},
})
}

View 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 }),
}))

View 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`,
}),
],
})
}

View file

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src"]
}

View file

@ -0,0 +1,6 @@
{
"name": "@repo/shared",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts"
}

View 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'

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"types": ["bun"]
},
"include": ["src"]
}

5655
session-ses_1d40.md Normal file

File diff suppressed because one or more lines are too long

28
tsconfig.json Normal file
View 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
}
}