Compare commits
10 Commits
8cad80ec6d
...
db60672370
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db60672370 | ||
|
|
f5db419c91 | ||
|
|
6be924bc86 | ||
| 5e84da3f9f | |||
|
|
7fc28d4018 | ||
|
|
d0dfe8d192 | ||
|
|
a94f0cf4e2 | ||
|
|
c17ad255b5 | ||
|
|
a45d97d173 | ||
|
|
9f4eecb116 |
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -26,5 +26,5 @@ dist-ssr
|
||||||
.env*
|
.env*
|
||||||
.aider*
|
.aider*
|
||||||
|
|
||||||
src/lib/taylor.types.ts
|
src/lib/taylordb.types.ts
|
||||||
src/lib/taylor.client.ts
|
src/lib/taylordb.client.ts
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.0.126",
|
"@opencode-ai/plugin": "1.0.142",
|
||||||
"@types/micromatch": "^4.0.10",
|
"@types/micromatch": "^4.0.10",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,17 @@ const axios = new Axios({
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateAppStatus = async (status: "Errored" | "Active" | "Pending") => {
|
const updateAppStatus = async (status: "Errored" | "Active" | "Pending") => {
|
||||||
// await axios.put(
|
await axios.put(
|
||||||
// "/",
|
"/",
|
||||||
// JSON.stringify({
|
JSON.stringify({
|
||||||
// status,
|
status,
|
||||||
// }),
|
}),
|
||||||
// {
|
{
|
||||||
// headers: {
|
headers: {
|
||||||
// "Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
||||||
|
|
@ -35,7 +35,20 @@ export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
||||||
event: async ({ event }) => {
|
event: async ({ event }) => {
|
||||||
if (event.type !== "session.idle") return;
|
if (event.type !== "session.idle") return;
|
||||||
|
|
||||||
const result = await $`pnpm build`.catch((error) => error);
|
const session = await client.session.get({
|
||||||
|
path: { id: event.properties.sessionID },
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAnyChange =
|
||||||
|
session.data?.summary?.files && session.data.summary.files > 0;
|
||||||
|
|
||||||
|
if (!isAnyChange) {
|
||||||
|
await updateAppStatus("Active");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await $`pnpm build`.quiet().catch((error) => error);
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
if (!client.session["tries"]) {
|
if (!client.session["tries"]) {
|
||||||
|
|
@ -47,8 +60,6 @@ export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
||||||
if (client.session["tries"] > 3) {
|
if (client.session["tries"] > 3) {
|
||||||
await updateAppStatus("Errored");
|
await updateAppStatus("Errored");
|
||||||
|
|
||||||
console.log("CHANGE STATUS TO ERRORED");
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,19 +85,24 @@ export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
||||||
.map(Number);
|
.map(Number);
|
||||||
const newVersion = `${major}.${minor}.${patch + 1}`;
|
const newVersion = `${major}.${minor}.${patch + 1}`;
|
||||||
|
|
||||||
const session = await client.session.get({
|
const messages = await client.session.messages({
|
||||||
path: { id: event.properties.sessionID },
|
path: { id: event.properties.sessionID },
|
||||||
});
|
});
|
||||||
|
|
||||||
const commitMessage =
|
if (!messages.data) {
|
||||||
session.data?.title ?? `feat: release version v${newVersion}`;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await $`git config user.name "Taylor AI"`;
|
const title = messages.data
|
||||||
await $`git config user.email "ai@taylordb.io"`;
|
.reverse()
|
||||||
await $`git add .`;
|
.find(
|
||||||
await $`git commit -m ${commitMessage}`;
|
(message) =>
|
||||||
await $`git tag v${newVersion}`;
|
message.info.role === "user" &&
|
||||||
await $`git push origin main --tags`;
|
message.info.summary &&
|
||||||
|
message.info.summary.title
|
||||||
|
)?.info.summary?.["title"];
|
||||||
|
|
||||||
|
const commitMessage = title ?? `feat: release version v${newVersion}`;
|
||||||
|
|
||||||
packageJson.version = newVersion;
|
packageJson.version = newVersion;
|
||||||
|
|
||||||
|
|
@ -94,19 +110,22 @@ export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
|
||||||
"package.json",
|
"package.json",
|
||||||
JSON.stringify(packageJson, null, 2)
|
JSON.stringify(packageJson, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await $`git config user.name "Taylor AI"`.quiet();
|
||||||
|
await $`git config user.email "ai@taylordb.io"`.quiet();
|
||||||
|
await $`git add .`.quiet();
|
||||||
|
await $`git commit -m ${commitMessage}`.quiet();
|
||||||
|
await $`git tag v${newVersion}`.quiet();
|
||||||
|
await $`git push origin main --tags`.quiet();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to push to git", error);
|
console.error("Failed to push to git", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateAppStatus("Active");
|
await updateAppStatus("Active");
|
||||||
|
|
||||||
console.log("CHANGE STATUS TO ACTIVE");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"chat.message": async () => {
|
"chat.message": async () => {
|
||||||
await updateAppStatus("Pending");
|
await updateAppStatus("Pending");
|
||||||
|
|
||||||
console.log("CHANGE STATUS TO PENDING");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ Upon initialization, this template includes a `/src/lib` directory containing tw
|
||||||
|
|
||||||
**Your first action must be to read both of these files.** This will give you a complete understanding of the database structure, tables, and data types you will be working with.
|
**Your first action must be to read both of these files.** This will give you a complete understanding of the database structure, tables, and data types you will be working with.
|
||||||
|
|
||||||
|
> Note: If `src/lib/taylordb.client.ts` or `src/lib/taylordb.types.ts` are missing in the repo, pause and ask the user to provide/regenerate them. Do not proceed with mock data.
|
||||||
|
|
||||||
### 2. Integrate Directly with TaylorDB
|
### 2. Integrate Directly with TaylorDB
|
||||||
|
|
||||||
You must use the provided TaylorDB client for all data operations. **Do not use mock data under any circumstances.** The UI you build should be fully functional and connected to the live database from the start.
|
You must use the provided TaylorDB client for all data operations. **Do not use mock data under any circumstances.** The UI you build should be fully functional and connected to the live database from the start.
|
||||||
|
|
@ -57,6 +59,8 @@ The development server is already running. You do not need to start it. Focus on
|
||||||
- Handle errors explicitly. **Never ignore TypeScript or lint errors.**
|
- Handle errors explicitly. **Never ignore TypeScript or lint errors.**
|
||||||
- Formatting: 2-space indent, single quotes, semicolons required.
|
- Formatting: 2-space indent, single quotes, semicolons required.
|
||||||
- Remove unused code/comments. Comments must be concise and relevant.
|
- Remove unused code/comments. Comments must be concise and relevant.
|
||||||
- Use TaylorDB query builder with generated types from `src/lib/taylordb.types.ts`; **never modify generated schema files.**
|
- Use TaylorDB query builder with generated types from `../src/lib/taylordb.types.ts`; **never modify generated schema files.**
|
||||||
|
- shadcn/ui is initialized with common components (button, card, input, label, textarea, select, tabs, alert). Add more via `pnpm dlx shadcn@latest add <component>`; files go in `../src/components/ui/`.
|
||||||
|
- Prefer shadcn/ui components and Tailwind tokens for UI. Only hand-roll a component if shadcn does not offer an equivalent; follow shadcn structure (component in `../src/components/ui/`, styling via Tailwind classes, `cn` helper, and theme tokens defined in `../src/index.css`).
|
||||||
|
|
||||||
_No Cursor or Copilot rules found. If added, include their guidelines here._
|
_No Cursor or Copilot rules found. If added, include their guidelines here._
|
||||||
|
|
|
||||||
222
QUERY_BUILDER.md
222
QUERY_BUILDER.md
|
|
@ -1,222 +0,0 @@
|
||||||
# TaylorDB Query Builder
|
|
||||||
|
|
||||||
The official TypeScript query builder for TaylorDB. It provides a type-safe, fluent, and intuitive API for building and executing queries against your TaylorDB database.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Type-Safe Queries**: Leverage your database schema for full type safety and autocompletion, catching errors at compile-time.
|
|
||||||
- **Fluent API**: A clean, chainable interface for building complex queries with ease.
|
|
||||||
- **Full CRUD Support**: Complete implementation for `select`, `insert`, `update`, and `delete` operations.
|
|
||||||
- **Advanced Filtering**: Filter data with a rich set of operators, nested conditions, and cross-table filters on relations.
|
|
||||||
- **Complex Selections**: Fetch related data using `with`, select specific columns, or get all columns with `selectAll`.
|
|
||||||
- **Pagination and Sorting**: Easily paginate and sort your query results.
|
|
||||||
- **Aggregation Queries**: Perform powerful aggregations with grouping and a variety of aggregate functions.
|
|
||||||
- **Batch Operations**: Execute multiple queries in a single, efficient request.
|
|
||||||
- **Real-time Subscriptions**: Subscribe to queries and receive live updates when data changes.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @taylordb/query-builder
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### 1. Generate TypeScript Types
|
|
||||||
|
|
||||||
First, you need to generate a `taylor.types.ts` file from your TaylorDB schema using the CLI. This file will contain the TypeScript definitions for your database schema, enabling the query builder's type-safety features.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @taylordb/cli generate-schema
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Create a Query Builder Instance
|
|
||||||
|
|
||||||
Once you have your types file, you can create a new query builder instance.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createQueryBuilder } from '@taylordb/query-builder';
|
|
||||||
import { TaylorDatabase } from './taylor.types'; // Import the generated types
|
|
||||||
|
|
||||||
const qb = createQueryBuilder<TaylorDatabase>({
|
|
||||||
baseUrl: 'YOUR_TAYLORDB_BASE_URL',
|
|
||||||
apiKey: 'YOUR_TAYLORDB_API_KEY',
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Select Queries
|
|
||||||
|
|
||||||
#### Basic Select
|
|
||||||
|
|
||||||
Select specific columns from a table.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const customers = await qb
|
|
||||||
.selectFrom('customers')
|
|
||||||
.select(['firstName', 'lastName', 'email'])
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `selectAll()` to fetch all columns.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const allCustomerData = await qb
|
|
||||||
.selectFrom('customers')
|
|
||||||
.selectAll()
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Filtering
|
|
||||||
|
|
||||||
Use `where` and `orWhere` to filter your results.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const johns = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(['name', 'email'])
|
|
||||||
.where('name', '=', 'John Doe')
|
|
||||||
.orWhere('email', '=', 'john.doe@example.com')
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also nest `where` clauses for complex logic.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const users = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.where(qb =>
|
|
||||||
qb.where('role', '=', 'admin').orWhere('lastActive', '>', '2023-01-01')
|
|
||||||
)
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fetching Relations
|
|
||||||
|
|
||||||
Include related records from linked tables using `with`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Get users and all fields from their related posts
|
|
||||||
const usersWithPosts = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(['id', 'name'])
|
|
||||||
.with(['posts'])
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also provide a function to customize the subquery for the relation.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Get users and only the title of their published posts
|
|
||||||
const usersWithPublishedPosts = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(['id', 'name'])
|
|
||||||
.with({
|
|
||||||
posts: (qb) => qb.select(['title']).where('isPublished', '=', true),
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cross-Table Filtering
|
|
||||||
|
|
||||||
Filter records based on conditions in a related table.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Get users who have at least one published post
|
|
||||||
const usersWithPublishedPosts = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.where('posts', 'hasAnyOf', qb => qb.where('isPublished', '=', true))
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Sorting and Pagination
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const users = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(['id', 'name'])
|
|
||||||
.orderBy('name', 'asc')
|
|
||||||
.paginate(2, 25) // Page 2, 25 items per page
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Insert Queries
|
|
||||||
|
|
||||||
Insert single or multiple records. Use `returning` to get data back from the new records.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const newUsers = await qb
|
|
||||||
.insertInto('users')
|
|
||||||
.values([
|
|
||||||
{ name: 'John Doe', email: 'john.doe@example.com' },
|
|
||||||
{ name: 'Jane Doe', email: 'jane.doe@example.com' },
|
|
||||||
])
|
|
||||||
.returning(['id', 'name'])
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Queries
|
|
||||||
|
|
||||||
Update records matching a `where` clause.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const { affectedRecords } = await qb
|
|
||||||
.update('users')
|
|
||||||
.set({ name: 'New Name' })
|
|
||||||
.where('id', '=', 1)
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delete Queries
|
|
||||||
|
|
||||||
Delete records matching a `where` clause.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const { affectedRecords } = await qb
|
|
||||||
.deleteFrom('users')
|
|
||||||
.where('id', '=', 1)
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Aggregation Queries
|
|
||||||
|
|
||||||
Perform powerful aggregations on your data.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const userStats = await qb
|
|
||||||
.aggregateFrom('users')
|
|
||||||
.groupBy('role', 'asc')
|
|
||||||
.withAggregates({
|
|
||||||
id: ['count'],
|
|
||||||
age: ['avg', 'sum'],
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Batch Queries
|
|
||||||
|
|
||||||
Execute multiple queries in a single request for improved performance.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const [users, newUser] = await qb.batch([
|
|
||||||
qb.selectFrom('users').select(['id', 'name']),
|
|
||||||
qb.insertInto('users').values({ name: 'New User' }).returning(['id']),
|
|
||||||
]).execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Real-time Subscriptions
|
|
||||||
|
|
||||||
Subscribe to queries and get real-time updates when data changes.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const { unsubscribe } = await qb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(['id', 'name'])
|
|
||||||
.subscribe((users) => {
|
|
||||||
console.log('Users updated:', users);
|
|
||||||
});
|
|
||||||
|
|
||||||
// To stop listening for updates
|
|
||||||
unsubscribe();
|
|
||||||
```
|
|
||||||
106
README.md
106
README.md
|
|
@ -1,73 +1,45 @@
|
||||||
# React + TypeScript + Vite
|
# TaylorDB React Starter
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
React + Vite starter with Tailwind CSS (v4), React Router v6, and shadcn/ui wired up for building custom UIs on TaylorDB.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
## What's included
|
||||||
|
- React Router layout with sample pages (`/`, `/about`, fallback `*`)
|
||||||
|
- Tailwind v4 configured for shadcn (design tokens, dark mode, animations)
|
||||||
|
- shadcn/ui baseline components: `button`, `card`, `input`, `label`, `textarea`, `select`, `tabs`, `alert`
|
||||||
|
- Path aliases via `@` (`@/components`, `@/lib`, etc.)
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
## Getting started
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
```bash
|
||||||
|
pnpm install
|
||||||
## React Compiler
|
pnpm dev
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
## Using the shadcn CLI
|
||||||
|
The project is already initialized with `components.json`. Add more components with:
|
||||||
```js
|
```bash
|
||||||
// eslint.config.js
|
pnpm dlx shadcn@latest add <component>
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
Examples:
|
||||||
|
```bash
|
||||||
|
pnpm dlx shadcn@latest add dialog dropdown-menu table
|
||||||
|
```
|
||||||
|
Generated files go into `src/components/ui/` and use the shared Tailwind tokens in `src/index.css`.
|
||||||
|
|
||||||
|
### Available components
|
||||||
|
- Layout/structure: `card`, `tabs`
|
||||||
|
- Form controls: `input`, `label`, `textarea`, `select`
|
||||||
|
- Feedback: `alert`
|
||||||
|
- Buttons: `button`
|
||||||
|
|
||||||
|
## TaylorDB integration
|
||||||
|
Use the generated TaylorDB client and types (expected in `src/lib/taylordb.client.ts` and `src/lib/taylordb.types.ts`) to query data directly. Do not use mock data.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
- `pnpm dev` — run Vite dev server (HMR)
|
||||||
|
- `pnpm build` — type-check + build
|
||||||
|
- `pnpm lint` — ESLint (strict, no `any`)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Tailwind 4 uses `@tailwindcss/vite`; CSS entry is `@import "tailwindcss";` in `src/index.css`.
|
||||||
|
- Tailwind config is in `tailwind.config.js`; CSS tokens live in `src/index.css`.
|
||||||
|
- Components use `@/lib/utils` for the `cn` helper (clsx + tailwind-merge).
|
||||||
|
|
|
||||||
17
components.json
Normal file
17
components.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "src/components",
|
||||||
|
"utils": "@/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
452
docs/QUERY_BUILDER.md
Normal file
452
docs/QUERY_BUILDER.md
Normal file
|
|
@ -0,0 +1,452 @@
|
||||||
|
This package contains the official TypeScript query builder for TaylorDB. It provides a type-safe and intuitive API for building and executing queries against your TaylorDB database.
|
||||||
|
|
||||||
|
## Available Query Builder Methods
|
||||||
|
|
||||||
|
**IMPORTANT FOR AI AGENTS**: The following is a complete list of available methods. Do not assume methods exist that are not listed here.
|
||||||
|
|
||||||
|
### Query Types (Starting Methods)
|
||||||
|
|
||||||
|
- `selectFrom(tableName)` - Start a SELECT query
|
||||||
|
- `insertInto(tableName)` - Start an INSERT query
|
||||||
|
- `update(tableName)` - Start an UPDATE query
|
||||||
|
- `deleteFrom(tableName)` - Start a DELETE query
|
||||||
|
- `aggregateFrom(tableName)` - Start an aggregation query
|
||||||
|
- `batch(queries)` - Execute multiple queries in parallel
|
||||||
|
- `transaction(callback)` - Execute queries in a transaction
|
||||||
|
|
||||||
|
### Query Chain Methods
|
||||||
|
|
||||||
|
**For SELECT queries (`selectFrom`):**
|
||||||
|
- `.select(fields)` - Specify fields to select (array of field names)
|
||||||
|
- `.selectAll()` - Select all fields
|
||||||
|
- `.where(field, operator, value)` - Add a WHERE condition
|
||||||
|
- `.orderBy(field, direction)` - Sort results ('asc' or 'desc')
|
||||||
|
- `.paginate(page, pageSize)` - Paginate results
|
||||||
|
- `.with(relations)` - Include related records
|
||||||
|
- `.count()` - **Count records matching the query** (returns `Promise<number>` directly)
|
||||||
|
- `.execute()` - Execute query and return array of results
|
||||||
|
- `.executeTakeFirst()` - Execute query and return first result or undefined
|
||||||
|
|
||||||
|
**For INSERT queries (`insertInto`):**
|
||||||
|
- `.values(data)` - Set values to insert
|
||||||
|
- `.execute()` - Execute insert and return array of inserted records
|
||||||
|
- `.executeTakeFirst()` - Execute insert and return first inserted record
|
||||||
|
|
||||||
|
**For UPDATE queries (`update`):**
|
||||||
|
- `.set(data)` - Set values to update
|
||||||
|
- `.where(field, operator, value)` - Add a WHERE condition
|
||||||
|
- `.execute()` - Execute update and return `{ affectedRecords: number }`
|
||||||
|
|
||||||
|
**For DELETE queries (`deleteFrom`):**
|
||||||
|
- `.where(field, operator, value)` - Add a WHERE condition
|
||||||
|
- `.execute()` - Execute delete and return `{ affectedRecords: number }`
|
||||||
|
|
||||||
|
**For AGGREGATION queries (`aggregateFrom`):**
|
||||||
|
- `.groupBy(field, direction)` - Group by a field ('asc' or 'desc')
|
||||||
|
- `.metrics(aggregateFunctions)` - Specify aggregate functions (e.g., `{ total: count('id') }`)
|
||||||
|
- `.where(field, operator, value)` - Add a WHERE condition
|
||||||
|
- `.execute()` - Execute aggregation and return array of grouped results
|
||||||
|
|
||||||
|
### Aggregate Functions
|
||||||
|
|
||||||
|
Import these functions from `@taylordb/query-builder`:
|
||||||
|
- `count(field)` - Count records
|
||||||
|
- `sum(field)` - Sum numeric values
|
||||||
|
- `avg(field)` - Average numeric values
|
||||||
|
- `min(field)` - Minimum value
|
||||||
|
- `max(field)` - Maximum value
|
||||||
|
|
||||||
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
|
**⚠️ CRITICAL: The following methods DO NOT EXIST and will cause errors:**
|
||||||
|
|
||||||
|
- ❌ `.countRecords()` - **DOES NOT EXIST**
|
||||||
|
- ❌ `.getCount()` - **DOES NOT EXIST**
|
||||||
|
- ❌ `.length` - **DOES NOT EXIST** on query builder chains
|
||||||
|
|
||||||
|
**Note**: `.count()` DOES exist for `selectFrom` queries. See the "Counting Records" section below for proper usage.
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Selecting Data
|
||||||
|
|
||||||
|
You can select data from a table using the `selectFrom` method. You can specify which fields to return, and you can filter, sort, and paginate the results.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const customers = await qb
|
||||||
|
.selectFrom('customers')
|
||||||
|
.select(['firstName', 'lastName'])
|
||||||
|
.where('firstName', '=', 'John')
|
||||||
|
.orderBy('lastName', 'asc')
|
||||||
|
.paginate(1, 10)
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Counting Records
|
||||||
|
|
||||||
|
There are two ways to count records in TaylorDB, each suited for different use cases:
|
||||||
|
|
||||||
|
#### Method 1: Using `.count()` on `selectFrom` (Recommended for simple counts)
|
||||||
|
|
||||||
|
**Use this when**: You need a single count value and don't need grouping or multiple metrics.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Count all users - returns a number directly
|
||||||
|
const totalUsers = await qb
|
||||||
|
.selectFrom('users')
|
||||||
|
.count();
|
||||||
|
|
||||||
|
console.log(`Total users: ${totalUsers}`);
|
||||||
|
|
||||||
|
// Count with filters - respects all WHERE conditions
|
||||||
|
const activeUsers = await qb
|
||||||
|
.selectFrom('users')
|
||||||
|
.where('status', '=', 'active')
|
||||||
|
.where('age', '>', 18)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
console.log(`Active adult users: ${activeUsers}`);
|
||||||
|
|
||||||
|
// Count with relation filters
|
||||||
|
const usersWithPosts = await qb
|
||||||
|
.selectFrom('users')
|
||||||
|
.where('posts', 'isNotEmpty')
|
||||||
|
.count();
|
||||||
|
|
||||||
|
console.log(`Users with posts: ${usersWithPosts}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points:**
|
||||||
|
- `.count()` returns `Promise<number>` directly (not an array)
|
||||||
|
- Works with all `selectFrom` chain methods (`.where()`, `.orderBy()`, etc.)
|
||||||
|
- Simple and efficient for single count values
|
||||||
|
- No need to import `count` function or use destructuring
|
||||||
|
|
||||||
|
#### Method 2: Using `aggregateFrom` with `count()` function (For grouped counts or multiple metrics)
|
||||||
|
|
||||||
|
**Use this when**: You need counts grouped by field(s) or multiple aggregate metrics.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { count } from '@taylordb/query-builder';
|
||||||
|
|
||||||
|
// Count grouped by status (returns array of results)
|
||||||
|
const statusCounts = await qb
|
||||||
|
.aggregateFrom('users')
|
||||||
|
.groupBy('status', 'asc')
|
||||||
|
.metrics({
|
||||||
|
total: count('id'),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// Find specific status count
|
||||||
|
const activeCount = statusCounts.find(item => item.status === 'active')?.total || 0;
|
||||||
|
|
||||||
|
// Multiple metrics with grouping
|
||||||
|
const userStats = await qb
|
||||||
|
.aggregateFrom('users')
|
||||||
|
.groupBy('status', 'asc')
|
||||||
|
.metrics({
|
||||||
|
total: count('id'),
|
||||||
|
averageAge: avg('age'),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points:**
|
||||||
|
- Must import `count` function from `@taylordb/query-builder`
|
||||||
|
- Returns an array of grouped results
|
||||||
|
- Use when you need grouping or multiple aggregate functions
|
||||||
|
- More flexible but slightly more verbose for simple counts
|
||||||
|
|
||||||
|
#### When to Use Which Method
|
||||||
|
|
||||||
|
- **Use `.count()`** when: You need a single count value (e.g., total users, active orders, etc.)
|
||||||
|
- **Use `aggregateFrom`** when: You need counts grouped by field(s) or multiple aggregate metrics together
|
||||||
|
|
||||||
|
### Inserting Data
|
||||||
|
|
||||||
|
You can insert data into a table using the `insertInto` method.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const newCustomer = await qb
|
||||||
|
.insertInto('customers')
|
||||||
|
.values({
|
||||||
|
firstName: 'Jane',
|
||||||
|
lastName: 'Doe',
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating Data
|
||||||
|
|
||||||
|
You can update data in a table using the `update` method.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const { affectedRecords } = await qb
|
||||||
|
.update('customers')
|
||||||
|
.set({ lastName: 'Smith' })
|
||||||
|
.where('id', '=', 1)
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting Data
|
||||||
|
|
||||||
|
You can delete data from a table using the `deleteFrom` method.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const { affectedRecords } = await qb
|
||||||
|
.deleteFrom('customers')
|
||||||
|
.where('id', '=', 1)
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aggregation Queries
|
||||||
|
|
||||||
|
You can perform powerful aggregation queries using the `aggregateFrom` method. You can group by one or more fields and specify aggregate functions to apply.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { count, sum, avg, max, min } from '@taylordb/query-builder';
|
||||||
|
|
||||||
|
const aggregates = await qb
|
||||||
|
.aggregateFrom('orders')
|
||||||
|
.groupBy('status', 'asc')
|
||||||
|
.metrics({
|
||||||
|
orderCount: count('id'),
|
||||||
|
totalRevenue: sum('total'),
|
||||||
|
averageOrder: avg('total'),
|
||||||
|
maxOrder: max('total'),
|
||||||
|
minOrder: min('total'),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
You can execute a series of operations within a single atomic transaction. If any operation within the transaction fails, all previous operations will be rolled back.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const newCustomer = await qb.transaction(async tx => {
|
||||||
|
const customer = await tx
|
||||||
|
.insertInto('customers')
|
||||||
|
.values({
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
})
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
throw new Error('Customer creation failed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.insertInto('orders')
|
||||||
|
.values({
|
||||||
|
customerId: customer.id,
|
||||||
|
orderDate: new Date().toISOString(),
|
||||||
|
total: 100,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return customer;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling Attachments
|
||||||
|
|
||||||
|
You can upload files and associate them with your records using the `uploadAttachments` method. This is useful for handling things like user avatars, product images, or any other file-based data.
|
||||||
|
|
||||||
|
First, upload the file(s) to get `Attachment` instances:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const filesToUpload = [
|
||||||
|
{ file: new Blob(['file content']), name: 'avatar.png' },
|
||||||
|
];
|
||||||
|
const attachments = await qb.uploadAttachments(filesToUpload);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can use the returned `Attachment` instances when creating or updating records. The query builder will automatically convert them into the correct format.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create a new customer with an avatar
|
||||||
|
const newCustomer = await qb
|
||||||
|
.insertInto('customers')
|
||||||
|
.values({
|
||||||
|
firstName: 'Jane',
|
||||||
|
lastName: 'Doe',
|
||||||
|
avatar: attachments[0], // Use the Attachment instance
|
||||||
|
})
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
// Update an existing customer's avatar
|
||||||
|
const { affectedRecords } = await qb
|
||||||
|
.update('customers')
|
||||||
|
.set({
|
||||||
|
avatar: attachments[0], // Use the Attachment instance
|
||||||
|
})
|
||||||
|
.where('id', '=', 1)
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Queries
|
||||||
|
|
||||||
|
You can execute multiple queries in a single batch request for improved performance. The result will be a tuple that corresponds to the results of each query in the batch.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const [customers, newCustomer] = await qb
|
||||||
|
.batch([
|
||||||
|
qb.selectFrom('customers').select(['firstName', 'lastName']),
|
||||||
|
qb.insertInto('customers').values({ firstName: 'John', lastName: 'Doe' }),
|
||||||
|
])
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices for Performance
|
||||||
|
|
||||||
|
When working with large databases, optimizing your queries is crucial for fast and efficient data retrieval. Here are some best practices to follow:
|
||||||
|
|
||||||
|
### 1. Select Only Necessary Fields
|
||||||
|
|
||||||
|
To minimize data transfer and improve query speed, always select only the fields you need. Avoid fetching all columns from a table if you only require a subset of them.
|
||||||
|
|
||||||
|
**Bad Practice:**
|
||||||
|
```typescript
|
||||||
|
// Fetches all fields for all customers, which can be slow with large tables.
|
||||||
|
const customers = await qb
|
||||||
|
.selectFrom('customers')
|
||||||
|
.selectAll()
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Good Practice:**
|
||||||
|
```typescript
|
||||||
|
// Fetches only the required fields, leading to a faster and more efficient query.
|
||||||
|
const customers = await qb
|
||||||
|
.selectFrom('customers')
|
||||||
|
.select(['firstName', 'lastName', 'email'])
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Use Aggregations for Metrics and Dashboards
|
||||||
|
|
||||||
|
When building dashboards or calculating metrics (e.g., total sales, user counts), it's more efficient to perform aggregations directly in the database rather than fetching raw data and processing it in your application. The `aggregateFrom` method is optimized for this purpose.
|
||||||
|
|
||||||
|
**Bad Practice (less efficient):**
|
||||||
|
```typescript
|
||||||
|
// Fetches all orders and then calculates the total count in the application.
|
||||||
|
const orders = await qb
|
||||||
|
.selectFrom('orders')
|
||||||
|
.select(['id'])
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const totalOrders = orders.length;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Good Practice (more efficient):**
|
||||||
|
```typescript
|
||||||
|
// For simple counts, use .count() method - simpler and more efficient
|
||||||
|
const totalOrders = await qb
|
||||||
|
.selectFrom('orders')
|
||||||
|
.count();
|
||||||
|
|
||||||
|
// Or if you need grouping, use aggregateFrom
|
||||||
|
import { count } from '@taylordb/query-builder';
|
||||||
|
const [{ totalOrders }] = await qb
|
||||||
|
.aggregateFrom('orders')
|
||||||
|
.metrics({
|
||||||
|
totalOrders: count('id'),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: Counting records grouped by status**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { count } from '@taylordb/query-builder';
|
||||||
|
|
||||||
|
// Count tasks by status - returns array of grouped results
|
||||||
|
const statusCounts = await qb
|
||||||
|
.aggregateFrom('tasks')
|
||||||
|
.groupBy('status', 'asc')
|
||||||
|
.metrics({
|
||||||
|
total: count('id'),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// Extract specific counts
|
||||||
|
const doneCount = statusCounts.find(item => item.status === 'Done')?.total || 0;
|
||||||
|
const pendingCount = statusCounts.find(item => item.status === 'Pending')?.total || 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: Simple counts for individual metrics**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// For simple counts without grouping, use .count() method
|
||||||
|
const totalDone = await qb
|
||||||
|
.selectFrom('tasks')
|
||||||
|
.where('status', '=', 'Done')
|
||||||
|
.count();
|
||||||
|
|
||||||
|
const totalPending = await qb
|
||||||
|
.selectFrom('tasks')
|
||||||
|
.where('status', '=', 'Pending')
|
||||||
|
.count();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative: Using batch queries for multiple filtered counts**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// If you need multiple simple counts, batch queries can be efficient
|
||||||
|
const [totalDone, totalPending] = await qb.batch([
|
||||||
|
qb.selectFrom('tasks').where('status', '=', 'Done').count(),
|
||||||
|
qb.selectFrom('tasks').where('status', '=', 'Pending').count(),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
- Use `.count()` for simple single counts (most efficient)
|
||||||
|
- Use `aggregateFrom` with `groupBy` when you need counts grouped by field(s)
|
||||||
|
- Use `batch` when you need multiple different filtered counts in parallel
|
||||||
|
|
||||||
|
Using aggregations reduces the amount of data transferred over the network and leverages the database's power for calculations, resulting in better performance for your application.
|
||||||
|
|
||||||
|
## Recipes
|
||||||
|
|
||||||
|
### Select with Relations
|
||||||
|
|
||||||
|
You can use the `with` method to fetch related records from a linked table.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Assuming 'customers' has a link field 'orders' to the 'orders' table
|
||||||
|
const customersWithOrders = await qb
|
||||||
|
.selectFrom('customers')
|
||||||
|
.select(['firstName', 'lastName'])
|
||||||
|
.with({
|
||||||
|
orders: qb => qb.select(['orderDate', 'total']),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Filters
|
||||||
|
|
||||||
|
You can filter records in one table based on the values in a linked table.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get all customers who have placed an order with a total greater than 100
|
||||||
|
const highValueCustomers = await qb
|
||||||
|
.selectFrom('customers')
|
||||||
|
.where('orders', 'hasAnyOf', qb => qb.where('total', '>', 100))
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Updates
|
||||||
|
|
||||||
|
You can use `where` clauses to update only the records that match a specific condition.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Update the status of all orders placed before a certain date
|
||||||
|
const { affectedRecords } = await qb
|
||||||
|
.update('orders')
|
||||||
|
.set({ status: 'archived' })
|
||||||
|
.where('orderDate', '<', '2023-01-01')
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import js from '@eslint/js'
|
import js from '@eslint/js'
|
||||||
import globals from 'globals'
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
import tseslint from 'typescript-eslint'
|
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
import globals from 'globals'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(['dist']),
|
||||||
|
|
@ -19,5 +19,6 @@ export default defineConfig([
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
|
ignores: ['dist', './src/lib/*.ts'],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"instructions": [
|
"instructions": [
|
||||||
"src/lib/taylordb.types.ts",
|
"../src/lib/taylordb.types.ts",
|
||||||
"src/lib/taylordb-client.ts",
|
"../src/lib/taylordb-client.ts",
|
||||||
"QUERY_BUILDER.md"
|
"./docs/QUERY_BUILDER.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
28
package.json
28
package.json
|
|
@ -1,33 +1,45 @@
|
||||||
{
|
{
|
||||||
"name": "blank",
|
"name": "blank",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"generate:schema": "pnpx @taylordb/cli generate-schema"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@taylordb/query-builder": "^0.9.13",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
"@taylordb/query-builder": "^0.10.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.561.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0",
|
||||||
|
"react-router-dom": "^6.28.1",
|
||||||
|
"recharts": "^3.5.0",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^5.1.0",
|
"@vitejs/plugin-react": "^5.1.0",
|
||||||
"autoprefixer": "^10.4.22",
|
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"postcss": "^8.5.6",
|
"tailwindcss": "^4.1.18",
|
||||||
"recharts": "^3.5.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"tailwindcss": "3",
|
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.3",
|
"typescript-eslint": "^8.46.3",
|
||||||
"vite": "^7.2.2"
|
"vite": "^7.2.2"
|
||||||
|
|
|
||||||
1720
pnpm-lock.yaml
1720
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +0,0 @@
|
||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
95
src/App.tsx
95
src/App.tsx
|
|
@ -1,5 +1,98 @@
|
||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { NavLink, Outlet } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ to: "/", label: "Home" },
|
||||||
|
{ to: "/about", label: "About" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getInitialTheme = (): "light" | "dark" => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored === "light" || stored === "dark") {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return <div>Hello World</div>;
|
const [theme, setTheme] = useState<"light" | "dark">(getInitialTheme);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.classList.toggle("dark", theme === "dark");
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background text-foreground">
|
||||||
|
<header className="border-b">
|
||||||
|
<div className="container flex h-16 items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-base font-semibold">TaylorDB Starter</span>
|
||||||
|
<nav className="flex items-center gap-1">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
"rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition hover:text-foreground",
|
||||||
|
isActive && "bg-accent text-foreground"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end={item.to === "/"}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
onClick={() =>
|
||||||
|
setTheme((prev) => (prev === "dark" ? "light" : "dark"))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{theme === "dark" ? (
|
||||||
|
<Sun className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Moon className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" asChild>
|
||||||
|
<a href="https://ui.shadcn.com/" target="_blank" rel="noreferrer">
|
||||||
|
shadcn/ui
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button asChild>
|
||||||
|
<a
|
||||||
|
href="https://reactrouter.com/6.30.2"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
React Router V6
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main className="container py-8">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
59
src/components/ui/alert.tsx
Normal file
59
src/components/ui/alert.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
57
src/components/ui/button.tsx
Normal file
57
src/components/ui/button.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
|
import * as React from "react";
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button";
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = "Button";
|
||||||
|
|
||||||
|
export { Button, buttonVariants };
|
||||||
79
src/components/ui/card.tsx
Normal file
79
src/components/ui/card.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-2xl font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
22
src/components/ui/input.tsx
Normal file
22
src/components/ui/input.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
||||||
24
src/components/ui/label.tsx
Normal file
24
src/components/ui/label.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
158
src/components/ui/select.tsx
Normal file
158
src/components/ui/select.tsx
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
))
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
))
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
))
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
))
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
}
|
||||||
55
src/components/ui/tabs.tsx
Normal file
55
src/components/ui/tabs.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
22
src/components/ui/textarea.tsx
Normal file
22
src/components/ui/textarea.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
React.ComponentProps<"textarea">
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
115
src/index.css
115
src/index.css
|
|
@ -1,3 +1,112 @@
|
||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
@import "tw-animate-css";
|
||||||
@tailwind utilities;
|
|
||||||
|
@theme {
|
||||||
|
--color-background: hsl(var(--background));
|
||||||
|
--color-foreground: hsl(var(--foreground));
|
||||||
|
--color-card: hsl(var(--card));
|
||||||
|
--color-card-foreground: hsl(var(--card-foreground));
|
||||||
|
--color-popover: hsl(var(--popover));
|
||||||
|
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
--color-secondary: hsl(var(--secondary));
|
||||||
|
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||||
|
--color-muted: hsl(var(--muted));
|
||||||
|
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||||
|
--color-accent: hsl(var(--accent));
|
||||||
|
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||||
|
--color-destructive: hsl(var(--destructive));
|
||||||
|
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||||
|
--color-border: hsl(var(--border));
|
||||||
|
--color-input: hsl(var(--input));
|
||||||
|
--color-ring: hsl(var(--ring));
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||||
|
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
--primary: 221.2 83.2% 53.3%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 221.2 83.2% 53.3%;
|
||||||
|
--radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
--primary: 217.2 91.2% 59.8%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 224.3 76.3% 48%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
font-feature-settings: "rlig" 1, "calt" 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
max-width: 1400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes accordion-down {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes accordion-up {
|
||||||
|
from {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/lib/utils.ts
Normal file
7
src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { type ClassValue, clsx } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
21
src/main.tsx
21
src/main.tsx
|
|
@ -1,10 +1,27 @@
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./App.tsx";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import AboutPage from "./pages/AboutPage";
|
||||||
|
import HomePage from "./pages/HomePage";
|
||||||
|
import NotFoundPage from "./pages/NotFoundPage";
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <App />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <HomePage /> },
|
||||||
|
{ path: "about", element: <AboutPage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ path: "*", element: <NotFoundPage /> },
|
||||||
|
]);
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<RouterProvider router={router} />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
49
src/pages/AboutPage.tsx
Normal file
49
src/pages/AboutPage.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
const AboutPage = () => {
|
||||||
|
return (
|
||||||
|
<section className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-2xl font-semibold">About this starter</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
This project now ships with React Router v6, Tailwind CSS, and a
|
||||||
|
shadcn/ui component baseline so you can focus on building TaylorDB
|
||||||
|
experiences instead of wiring up UI plumbing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
<div className="rounded-lg border bg-card p-4 shadow-sm">
|
||||||
|
<h2 className="text-lg font-medium">Routing</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Use nested routes with layouts via <code>createBrowserRouter</code>{" "}
|
||||||
|
and <code>Outlet</code>. Add pages in <code>src/pages</code> and
|
||||||
|
register them in the router.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg border bg-card p-4 shadow-sm">
|
||||||
|
<h2 className="text-lg font-medium">UI primitives</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
The shared <code>Button</code> uses shadcn/ui patterns and can be
|
||||||
|
extended with more components via the CLI using{" "}
|
||||||
|
<code>components.json</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="link" asChild>
|
||||||
|
<a
|
||||||
|
href="https://www.npmjs.com/package/@taylordb/query-builder"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Explore TaylorDB Query Builder <ExternalLink className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AboutPage;
|
||||||
58
src/pages/HomePage.tsx
Normal file
58
src/pages/HomePage.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<section className="grid gap-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-sm uppercase tracking-wide text-muted-foreground">
|
||||||
|
Welcome
|
||||||
|
</p>
|
||||||
|
<h1 className="text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||||
|
Build your TaylorDB UI with React Router + shadcn/ui
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-muted-foreground max-w-2xl">
|
||||||
|
Routing, Tailwind, and shadcn/ui are wired up. Start connecting
|
||||||
|
components to your TaylorDB data using the generated client and types.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border bg-card p-4 shadow-sm animate-in fade-in">
|
||||||
|
<p className="text-sm font-medium text-foreground">
|
||||||
|
Animation check: this card fades in using{" "}
|
||||||
|
<code>animate-in fade-in</code> from <code>tw-animate-css</code>.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
If you see a smooth fade on load, the plugin is wired correctly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<Button asChild>
|
||||||
|
<Link to="/about">
|
||||||
|
Learn more
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" asChild>
|
||||||
|
<a href="https://ui.shadcn.com/docs" target="_blank" rel="noreferrer">
|
||||||
|
shadcn/ui docs
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" asChild>
|
||||||
|
<a
|
||||||
|
href="https://reactrouter.com/en/main"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
React Router docs
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
27
src/pages/NotFoundPage.tsx
Normal file
27
src/pages/NotFoundPage.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
const NotFoundPage = () => {
|
||||||
|
return (
|
||||||
|
<section className="space-y-4 text-center">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm uppercase tracking-wide text-muted-foreground">
|
||||||
|
404
|
||||||
|
</p>
|
||||||
|
<h1 className="text-2xl font-semibold">Page not found</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
The page you are looking for does not exist or has moved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Button asChild>
|
||||||
|
<Link to="/">Go back home</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFoundPage;
|
||||||
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: [
|
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["vite/client", "node"],
|
"types": ["vite/client", "node"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|
@ -25,5 +29,5 @@
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["src/lib"]
|
"exclude": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
|
import path from "node:path";
|
||||||
import { PassThrough } from "stream";
|
import { PassThrough } from "stream";
|
||||||
import { defineConfig, type ViteDevServer } from "vite";
|
import { defineConfig, type ViteDevServer } from "vite";
|
||||||
|
|
||||||
|
|
@ -32,6 +34,7 @@ Object.keys(originalConsole).forEach((level) => {
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
react(),
|
react(),
|
||||||
{
|
{
|
||||||
name: "sse-plugin",
|
name: "sse-plugin",
|
||||||
|
|
@ -61,8 +64,18 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: [".develop.taylordb.ai", "localhost", "127.0.0.1"],
|
allowedHosts: [".develop.taylordb.ai", "localhost", "127.0.0.1"],
|
||||||
host: "0.0.0.0", // Listen on all network interfaces
|
host: true,
|
||||||
|
port: 5173,
|
||||||
|
hmr: {
|
||||||
|
protocol: "wss",
|
||||||
|
clientPort: 443,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user