@riligar/agents-kit 1.19.0 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,18 +4,18 @@ Consistent naming across all RiLiGar projects.
4
4
 
5
5
  ## Quick Reference
6
6
 
7
- | Element | Convention | Example |
8
- | --- | --- | --- |
9
- | Components | PascalCase | `UserProfile`, `NavBar` |
10
- | Functions | camelCase | `getUserData`, `handleSubmit` |
11
- | Variables | camelCase | `userName`, `isLoading` |
12
- | Constants | SCREAMING_SNAKE | `API_URL`, `MAX_RETRIES` |
13
- | Files (components) | PascalCase | `UserProfile.jsx` |
14
- | Files (utilities) | camelCase | `formatDate.js` |
15
- | Directories | kebab-case | `user-profile/`, `api-utils/` |
16
- | CSS classes | kebab-case | `nav-bar`, `user-card` |
17
- | Database tables | snake_case | `user_profiles`, `order_items` |
18
- | API endpoints | kebab-case | `/api/user-profiles`, `/api/order-items` |
7
+ | Element | Convention | Example |
8
+ | ------------------ | --------------- | ---------------------------------------- |
9
+ | Components | PascalCase | `UserProfile`, `NavBar` |
10
+ | Functions | camelCase | `getUserData`, `handleSubmit` |
11
+ | Variables | camelCase | `userName`, `isLoading` |
12
+ | Constants | SCREAMING_SNAKE | `API_URL`, `MAX_RETRIES` |
13
+ | Files (components) | PascalCase | `UserProfile.jsx` |
14
+ | Files (utilities) | camelCase | `formatDate.js` |
15
+ | Directories | kebab-case | `user-profile/`, `api-utils/` |
16
+ | CSS classes | kebab-case | `nav-bar`, `user-card` |
17
+ | database tables | snake_case | `user_profiles`, `order_items` |
18
+ | API endpoints | kebab-case | `/api/user-profiles`, `/api/order-items` |
19
19
 
20
20
  ## Components
21
21
 
@@ -59,8 +59,8 @@ const MAX_RETRY_ATTEMPTS = 3
59
59
  const DEFAULT_PAGE_SIZE = 20
60
60
 
61
61
  // Bad
62
- const apiBaseUrl = 'https://api.example.com' // camelCase
63
- const maxRetryAttempts = 3 // camelCase
62
+ const apiBaseUrl = 'https://api.example.com' // camelCase
63
+ const maxRetryAttempts = 3 // camelCase
64
64
  ```
65
65
 
66
66
  ## Booleans
@@ -131,10 +131,10 @@ src/
131
131
  └── api.js
132
132
  ```
133
133
 
134
- ## Database and API
134
+ ## database and API
135
135
 
136
136
  ```javascript
137
- // Database tables - snake_case
137
+ // database tables - snake_case
138
138
  // user_profiles, order_items, payment_transactions
139
139
 
140
140
  // API endpoints - kebab-case
@@ -162,7 +162,7 @@ const htmlContent = '<div>...</div>'
162
162
  const xmlParser = new Parser()
163
163
 
164
164
  // Bad
165
- const userID = 123 // ID should be Id
165
+ const userID = 123 // ID should be Id
166
166
  const APIURL = '/api' // Should be ApiUrl
167
167
  const HTMLContent = '<div>...</div>'
168
168
  ```
@@ -11,10 +11,10 @@ This skill provides a complete workflow for integrating authentication and permi
11
11
 
12
12
  ### 1. Installation
13
13
 
14
- Install the SDK using bun (preferred) or npm:
14
+ Install the SDK and its core dependencies (Mantine & Tabler Icons) using bun:
15
15
 
16
16
  ```bash
17
- bun add @riligar/auth-react
17
+ bun add @riligar/auth-react @mantine/core @mantine/hooks @mantine/notifications @tabler/icons-react
18
18
  ```
19
19
 
20
20
  ### 2. Environment Variables
@@ -34,19 +34,29 @@ VITE_AUTH_API_KEY=pk_live_your_public_key
34
34
 
35
35
  ### 3. AuthProvider Setup
36
36
 
37
- Wrap your application (usually in `main.jsx` or `App.jsx`) with the `AuthProvider`.
37
+ Wrap your application (usually in `main.jsx` or `App.jsx`) with the `AuthProvider` and `MantineProvider`.
38
38
 
39
39
  ```jsx
40
40
  import { AuthProvider } from '@riligar/auth-react'
41
+ import { MantineProvider } from '@mantine/core'
41
42
 
42
43
  ReactDOM.createRoot(document.getElementById('root')).render(
43
- <AuthProvider apiKey={import.meta.env.VITE_AUTH_API_KEY}>
44
- <App />
45
- </AuthProvider>
44
+ <MantineProvider theme={yourTheme}>
45
+ <AuthProvider apiKey={import.meta.env.VITE_AUTH_API_KEY}>
46
+ <App />
47
+ </AuthProvider>
48
+ </MantineProvider>
46
49
  )
47
50
  ```
48
51
 
49
- For more setup patterns, see [setup-snippets.js](assets/setup-snippets.js).
52
+ ## Ready-to-Use Templates
53
+
54
+ Use these assets to quickly scaffold a complete authentication flow:
55
+
56
+ - **Routing Architecture**: Comprehensive [routes.jsx](assets/routes.jsx) showing Public vs Protected patterns.
57
+ - **Login & Signup**: Generic [signin.jsx](assets/signin.jsx) and [signup.jsx](assets/signup.jsx) templates.
58
+ - **Email Verification**: [verify-email.jsx](assets/verify-email.jsx) handler.
59
+ - **Utility Wrappers**: [auth-loader.jsx](assets/auth-loader.jsx), [require-feature.jsx](assets/require-feature.jsx), and [layout.jsx](assets/layout.jsx).
50
60
 
51
61
  ## Specialized Guides
52
62
 
@@ -64,25 +74,21 @@ Use `useAuth()` to access user data and authentication status.
64
74
  const { user, isAuthenticated, loading } = useAuth()
65
75
  ```
66
76
 
67
- ### Adding a Login Form
68
-
69
- Simply drop the `<SignIn />` component. Use `variant="modal"` if you want it in a popup.
70
-
71
- ```jsx
72
- <SignIn />
73
- // or
74
- <SignIn variant="modal" opened={isOpen} onClose={() => setIsOpen(false)} />
75
- ```
76
-
77
77
  ### Protecting a Dashboard
78
78
 
79
- Wrap your sub-routes with `<Protect />`.
79
+ The recommended pattern is to use the **Protected Group** pattern with React Router as shown in [routes.jsx](assets/routes.jsx).
80
80
 
81
81
  ```jsx
82
- <Route element={<Protect redirectTo="/login" />}>
82
+ // Nested routes under <Protect /> guarantee that all sub-routes are guarded.
83
+ <Route element={<Protect fallback={<AuthLoader />} />}>
83
84
  <Route
84
- path="/dashboard"
85
- element={<Dashboard />}
86
- />
85
+ path="/"
86
+ element={<Layout />}
87
+ >
88
+ <Route
89
+ index
90
+ element={<Home />}
91
+ />
92
+ </Route>
87
93
  </Route>
88
94
  ```
@@ -0,0 +1,31 @@
1
+ import { Center, Loader, Stack, Text } from '@mantine/core'
2
+
3
+ /**
4
+ * AuthLoader Component
5
+ *
6
+ * Standard loading screen used during authentication state transitions
7
+ * (e.g., checking tokens on initial load or redirects).
8
+ */
9
+ export default function AuthLoader() {
10
+ return (
11
+ <Center h="100vh">
12
+ <Stack
13
+ align="center"
14
+ gap="md"
15
+ >
16
+ <Loader
17
+ size="lg"
18
+ variant="dots"
19
+ color="brand"
20
+ />
21
+ <Text
22
+ size="sm"
23
+ c="dimmed"
24
+ fw={500}
25
+ >
26
+ Verificando acesso...
27
+ </Text>
28
+ </Stack>
29
+ </Center>
30
+ )
31
+ }
@@ -0,0 +1,54 @@
1
+ import { AppShell, Burger, Group, Text } from '@mantine/core'
2
+ import { useDisclosure } from '@mantine/hooks'
3
+ import { Outlet } from 'react-router-dom'
4
+
5
+ /**
6
+ * Layout Component
7
+ *
8
+ * Standard Dashboard layout using Mantine AppShell.
9
+ * Provides the structure for sidebars, headers, and main content area.
10
+ */
11
+ export default function Layout() {
12
+ const [opened, { toggle }] = useDisclosure()
13
+
14
+ return (
15
+ <AppShell
16
+ header={{ height: 60 }}
17
+ navbar={{
18
+ width: 300,
19
+ breakpoint: 'sm',
20
+ collapsed: { mobile: !opened },
21
+ }}
22
+ padding="md"
23
+ >
24
+ <AppShell.Header>
25
+ <Group
26
+ h="100%"
27
+ px="md"
28
+ >
29
+ <Burger
30
+ opened={opened}
31
+ onClick={toggle}
32
+ hiddenFrom="sm"
33
+ size="sm"
34
+ />
35
+ <Text fw={700}>Dashboard</Text>
36
+ </Group>
37
+ </AppShell.Header>
38
+
39
+ <AppShell.Navbar p="md">
40
+ {/* Navigation links go here */}
41
+ <Text
42
+ size="sm"
43
+ c="dimmed"
44
+ >
45
+ Navigation Menu
46
+ </Text>
47
+ </AppShell.Navbar>
48
+
49
+ <AppShell.Main>
50
+ <Outlet />
51
+ </AppShell.Main>
52
+ </AppShell>
53
+ )
54
+ }
@@ -0,0 +1,33 @@
1
+ import { useAuth } from '@riligar/auth-react'
2
+ import { Navigate } from 'react-router-dom'
3
+ import AuthLoader from './auth-loader'
4
+
5
+ /**
6
+ * RequireFeature Wrapper
7
+ *
8
+ * Protects routes based on specific user attributes or feature flags.
9
+ * This is an example of how to extend authentication with business logic.
10
+ */
11
+ export default function RequireFeature({ children }) {
12
+ const { user, isAuthenticated, loading } = useAuth()
13
+
14
+ if (loading) {
15
+ return <AuthLoader />
16
+ }
17
+
18
+ if (!isAuthenticated) {
19
+ return (
20
+ <Navigate
21
+ to="/auth/signin"
22
+ replace
23
+ />
24
+ )
25
+ }
26
+
27
+ // Example logic: Ensure user has a setup complete or a specific role
28
+ // if (!user.onboardingCompleted) {
29
+ // return <Navigate to="/onboarding" replace />
30
+ // }
31
+
32
+ return children
33
+ }
@@ -13,33 +13,38 @@ Use these components to control access to specific parts of your application.
13
13
 
14
14
  ## Usage Patterns
15
15
 
16
- ### Protecting Routes (React Router)
16
+ ### Protecting Routes (createBrowserRouter)
17
+
18
+ The most robust way to protect routes in a Riligar app is using the **Protected Group** pattern with `createBrowserRouter`. This ensures consistent layout and authentication guards.
17
19
 
18
20
  ```jsx
19
- import { Protect, SignIn } from '@riligar/auth-react'
20
- import { Routes, Route } from 'react-router-dom'
21
-
22
- ;<Routes>
23
- <Route
24
- path="/login"
25
- element={<SignIn />}
26
- />
27
-
28
- <Route element={<Protect redirectTo="/login" />}>
29
- <Route
30
- path="/dashboard"
31
- element={<Dashboard />}
32
- />
33
- <Route
34
- path="/settings"
35
- element={<Settings />}
36
- />
37
- </Route>
38
- </Routes>
21
+ import { createBrowserRouter, Navigate } from 'react-router-dom'
22
+ import { Protect } from '@riligar/auth-react'
23
+ import AuthLoader from '../components/auth-loader.jsx'
24
+ import Layout from '../components/layout.jsx'
25
+
26
+ export const router = createBrowserRouter([
27
+ // Public paths
28
+ { path: '/auth/signin', element: <SignIn /> },
29
+
30
+ // Protected paths
31
+ {
32
+ element: <Protect fallback={<AuthLoader />} />,
33
+ children: [
34
+ {
35
+ path: '/',
36
+ element: <Layout />,
37
+ children: [{ index: true, element: <Dashboard /> }],
38
+ },
39
+ ],
40
+ },
41
+ ])
39
42
  ```
40
43
 
41
44
  ### Conditional UI Elements
42
45
 
46
+ Use these wrappers inside your components (headers, sidebars, etc.) for granular visibility.
47
+
43
48
  ```jsx
44
49
  import { SignedIn, SignedOut, AuthLoading } from '@riligar/auth-react'
45
50
 
@@ -1,29 +1,29 @@
1
1
  ---
2
2
  name: riligar-dev-database
3
- description: Database patterns for RiLiGar using Drizzle ORM + bun:sqlite. Use when setting up database connections, defining schemas, creating migrations, or writing queries. Covers SQLite on Fly.io volumes with the drizzle-kit workflow.
3
+ description: database patterns for RiLiGar using Drizzle ORM + bun:sqlite. Use when setting up database connections, defining schemas, creating migrations, or writing queries. Covers SQLite on Fly.io volumes with the drizzle-kit workflow.
4
4
  ---
5
5
 
6
- # Database — Drizzle + bun:sqlite
6
+ # database — Drizzle + bun:sqlite
7
7
 
8
8
  > SQLite nativo no Bun. Zero drivers externos. Base de dados no volume do Fly.io (`/app/data`).
9
9
 
10
10
  ## Referências
11
11
 
12
- | Arquivo | Quando usar |
13
- | --- | --- |
14
- | [connection.md](references/connection.md) | Setup inicial: instalação, db.js, drizzle.config |
15
- | [schema.md](references/schema.md) | Definir tabelas, tipos de colunas, relações |
16
- | [migrations.md](references/migrations.md) | Criar e executar migrations com drizzle-kit |
17
- | [queries.md](references/queries.md) | Select, insert, update, delete, queries com relações |
12
+ | Arquivo | Quando usar |
13
+ | ----------------------------------------- | ---------------------------------------------------- |
14
+ | [connection.md](references/connection.md) | Setup inicial: instalação, db.js, drizzle.config |
15
+ | [schema.md](references/schema.md) | Definir tabelas, tipos de colunas, relações |
16
+ | [migrations.md](references/migrations.md) | Criar e executar migrations com drizzle-kit |
17
+ | [queries.md](references/queries.md) | Select, insert, update, delete, queries com relações |
18
18
 
19
19
  ## Quick Start
20
20
 
21
21
  ```javascript
22
22
  // database/db.js
23
23
  import { drizzle } from 'drizzle-orm/bun-sqlite'
24
- import Database from 'bun:sqlite'
24
+ import database from 'bun:sqlite'
25
25
 
26
- const sqlite = new Database(process.env.DB_PATH ?? './data/database.db')
26
+ const sqlite = new database(process.env.DB_PATH ?? './database/database.db')
27
27
  const db = drizzle({ client: sqlite })
28
28
 
29
29
  export { db }
@@ -31,15 +31,15 @@ export { db }
31
31
 
32
32
  ## Regras
33
33
 
34
- - **Caminho do banco:** `/app/data/database.db` em produção (volume Fly.io). `./data/database.db` em desenvolvimento.
34
+ - **Caminho do banco:** `/app/data/database.db` em produção (volume Fly.io). `./database/database.db` em desenvolvimento.
35
35
  - **Migrations sempre:** Use `drizzle-kit generate` + `drizzle-kit migrate`. Nunca edite migrations à mão.
36
36
  - **Schema único:** Todas as tabelas em `database/schema.js`.
37
37
  - **Migrations no startup:** Use `migrate()` no `index.js` antes de `.listen()`.
38
38
 
39
39
  ## Related Skills
40
40
 
41
- | Need | Skill |
42
- | --- | --- |
43
- | **Backend (Elysia)** | @[.agent/skills/riligar-dev-manager] |
41
+ | Need | Skill |
42
+ | --------------------- | ------------------------------------- |
43
+ | **Backend (Elysia)** | @[.agent/skills/riligar-dev-manager] |
44
44
  | **Payments (Stripe)** | @[.agent/skills/riligar-infra-stripe] |
45
- | **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
45
+ | **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
@@ -1,4 +1,4 @@
1
- # Database Connection
1
+ # database Connection
2
2
 
3
3
  ## Installation
4
4
 
@@ -14,15 +14,15 @@ No additional drivers needed — `bun:sqlite` is built into Bun.
14
14
  ```javascript
15
15
  // database/db.js
16
16
  import { drizzle } from 'drizzle-orm/bun-sqlite'
17
- import Database from 'bun:sqlite'
17
+ import database from 'bun:sqlite'
18
18
 
19
- const sqlite = new Database(process.env.DB_PATH ?? './data/database.db')
19
+ const sqlite = new database(process.env.DB_PATH ?? './database/database.db')
20
20
  const db = drizzle({ client: sqlite })
21
21
 
22
22
  export { db }
23
23
  ```
24
24
 
25
- - Em **desenvolvimento**: `DB_PATH` não é definido → usa `./data/database.db`
25
+ - Em **desenvolvimento**: `DB_PATH` não é definido → usa `./database/database.db`
26
26
  - Em **produção** (Fly.io): `fly secrets set DB_PATH=/app/data/database.db`
27
27
 
28
28
  ## drizzle.config.js
@@ -36,7 +36,7 @@ export default defineConfig({
36
36
  schema: './database/schema.js',
37
37
  out: './database/migrations',
38
38
  dbCredentials: {
39
- url: process.env.DB_PATH ?? './data/database.db',
39
+ url: process.env.DB_PATH ?? './database/database.db',
40
40
  },
41
41
  })
42
42
  ```
@@ -53,9 +53,7 @@ import { db } from './database/db'
53
53
  await migrate(db, { migrationsFolder: './database/migrations' })
54
54
 
55
55
  // ... resto do setup
56
- const app = new Elysia()
57
- .use(routes)
58
- .listen(3000)
56
+ const app = new Elysia().use(routes).listen(3000)
59
57
  ```
60
58
 
61
59
  Isso garante que o banco esteja sempre atualizado quando o servidor inicia no Fly.io.
@@ -17,10 +17,12 @@ import { eq, and, or, desc, asc } from 'drizzle-orm'
17
17
  const allUsers = await db.select().from(users)
18
18
 
19
19
  // Campos específicos
20
- const names = await db.select({
21
- id: users.id,
22
- name: users.name,
23
- }).from(users)
20
+ const names = await db
21
+ .select({
22
+ id: users.id,
23
+ name: users.name,
24
+ })
25
+ .from(users)
24
26
  ```
25
27
 
26
28
  ### Filtrar
@@ -30,23 +32,24 @@ const names = await db.select({
30
32
  const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1)
31
33
 
32
34
  // Múltiplos filtros (AND)
33
- const results = await db.select().from(users).where(
34
- and(
35
- eq(users.active, true),
36
- eq(users.plan, 'pro')
37
- )
38
- )
35
+ const results = await db
36
+ .select()
37
+ .from(users)
38
+ .where(and(eq(users.active, true), eq(users.plan, 'pro')))
39
39
 
40
40
  // OR
41
- const results = await db.select().from(users).where(
42
- or(eq(users.id, '1'), eq(users.id, '2'))
43
- )
41
+ const results = await db
42
+ .select()
43
+ .from(users)
44
+ .where(or(eq(users.id, '1'), eq(users.id, '2')))
44
45
  ```
45
46
 
46
47
  ### Ordenar e Paginar
47
48
 
48
49
  ```javascript
49
- const page = await db.select().from(posts)
50
+ const page = await db
51
+ .select()
52
+ .from(posts)
50
53
  .orderBy(desc(posts.createdAt))
51
54
  .limit(10)
52
55
  .offset(pageIndex * 10)
@@ -59,9 +62,7 @@ const page = await db.select().from(posts)
59
62
  await db.insert(users).values({ name: 'Dan', email: 'dan@email.com' })
60
63
 
61
64
  // Com retorno
62
- const [user] = await db.insert(users)
63
- .values({ name: 'Dan', email: 'dan@email.com' })
64
- .returning()
65
+ const [user] = await db.insert(users).values({ name: 'Dan', email: 'dan@email.com' }).returning()
65
66
 
66
67
  // Múltiplos
67
68
  await db.insert(users).values([
@@ -70,7 +71,8 @@ await db.insert(users).values([
70
71
  ])
71
72
 
72
73
  // Upsert (conflict handling)
73
- await db.insert(users)
74
+ await db
75
+ .insert(users)
74
76
  .values({ id: '1', name: 'Dan' })
75
77
  .onConflictDoUpdate({
76
78
  target: users.id,
@@ -81,15 +83,10 @@ await db.insert(users)
81
83
  ## Update
82
84
 
83
85
  ```javascript
84
- await db.update(users)
85
- .set({ name: 'Mr. Dan', updatedAt: new Date() })
86
- .where(eq(users.id, userId))
86
+ await db.update(users).set({ name: 'Mr. Dan', updatedAt: new Date() }).where(eq(users.id, userId))
87
87
 
88
88
  // Com retorno
89
- const [updated] = await db.update(users)
90
- .set({ plan: 'pro' })
91
- .where(eq(users.id, userId))
92
- .returning()
89
+ const [updated] = await db.update(users).set({ plan: 'pro' }).where(eq(users.id, userId)).returning()
93
90
  ```
94
91
 
95
92
  ## Delete
@@ -98,9 +95,7 @@ const [updated] = await db.update(users)
98
95
  await db.delete(users).where(eq(users.id, userId))
99
96
 
100
97
  // Com retorno
101
- const [deleted] = await db.delete(users)
102
- .where(eq(users.id, userId))
103
- .returning()
98
+ const [deleted] = await db.delete(users).where(eq(users.id, userId)).returning()
104
99
  ```
105
100
 
106
101
  ## Queries com Relações
@@ -159,7 +154,8 @@ export async function createUser(data) {
159
154
  }
160
155
 
161
156
  export async function updateUser(id, data) {
162
- const [user] = await db.update(users)
157
+ const [user] = await db
158
+ .update(users)
163
159
  .set({ ...data, updatedAt: new Date() })
164
160
  .where(eq(users.id, id))
165
161
  .returning()
@@ -8,37 +8,41 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
8
8
  import { relations } from 'drizzle-orm'
9
9
 
10
10
  export const users = sqliteTable('users', {
11
- id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
11
+ id: text('id')
12
+ .primaryKey()
13
+ .$defaultFn(() => crypto.randomUUID()),
12
14
  email: text('email').notNull().unique(),
13
15
  name: text('name'),
14
- createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
16
+ createdAt: integer('created_at', { mode: 'timestamp' })
17
+ .notNull()
18
+ .$defaultFn(() => new Date()),
15
19
  updatedAt: integer('updated_at', { mode: 'timestamp' }),
16
20
  })
17
21
  ```
18
22
 
19
23
  ## Tipos de Colunas
20
24
 
21
- | Tipo | Uso | Exemplo |
22
- | --- | --- | --- |
23
- | `text()` | Strings, UUIDs, JSON | `text('name').notNull()` |
24
- | `integer()` | Números, booleans, timestamps | `integer('age')` |
25
- | `real()` | Decimais (float) | `real('price')` |
26
- | `blob()` | Dados binários | `blob('avatar')` |
27
- | `numeric()` | Valores numéricos precisos | `numeric('amount')` |
25
+ | Tipo | Uso | Exemplo |
26
+ | ----------- | ----------------------------- | ------------------------ |
27
+ | `text()` | Strings, UUIDs, JSON | `text('name').notNull()` |
28
+ | `integer()` | Números, booleans, timestamps | `integer('age')` |
29
+ | `real()` | Decimais (float) | `real('price')` |
30
+ | `blob()` | Dados binários | `blob('avatar')` |
31
+ | `numeric()` | Valores numéricos precisos | `numeric('amount')` |
28
32
 
29
33
  ### Modes do `integer()`
30
34
 
31
35
  ```javascript
32
- integer('count') // número
33
- integer('active', { mode: 'boolean' }) // boolean (0/1)
34
- integer('created_at', { mode: 'timestamp' }) // Date (segundos)
36
+ integer('count') // número
37
+ integer('active', { mode: 'boolean' }) // boolean (0/1)
38
+ integer('created_at', { mode: 'timestamp' }) // Date (segundos)
35
39
  integer('created_at', { mode: 'timestamp_ms' }) // Date (milissegundos)
36
40
  ```
37
41
 
38
42
  ### JSON via `text()`
39
43
 
40
44
  ```javascript
41
- text('metadata', { mode: 'json' }) // armazena JSON como text
45
+ text('metadata', { mode: 'json' }) // armazena JSON como text
42
46
  ```
43
47
 
44
48
  ## Primary Key
@@ -55,11 +59,17 @@ id: integer('id').primaryKey({ autoIncrement: true }),
55
59
 
56
60
  ```javascript
57
61
  export const posts = sqliteTable('posts', {
58
- id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
59
- authorId: text('author_id').notNull().references(() => users.id),
62
+ id: text('id')
63
+ .primaryKey()
64
+ .$defaultFn(() => crypto.randomUUID()),
65
+ authorId: text('author_id')
66
+ .notNull()
67
+ .references(() => users.id),
60
68
  title: text('title').notNull(),
61
69
  body: text('body'),
62
- createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
70
+ createdAt: integer('created_at', { mode: 'timestamp' })
71
+ .notNull()
72
+ .$defaultFn(() => new Date()),
63
73
  })
64
74
  ```
65
75
 
@@ -84,8 +94,12 @@ export const postsRelations = relations(posts, ({ one }) => ({
84
94
 
85
95
  ```javascript
86
96
  export const usersToGroups = sqliteTable('users_to_groups', {
87
- userId: text('user_id').notNull().references(() => users.id),
88
- groupId: text('group_id').notNull().references(() => groups.id),
97
+ userId: text('user_id')
98
+ .notNull()
99
+ .references(() => users.id),
100
+ groupId: text('group_id')
101
+ .notNull()
102
+ .references(() => groups.id),
89
103
  })
90
104
 
91
105
  export const usersRelations = relations(users, ({ many }) => ({
@@ -22,21 +22,21 @@ const app = new Elysia()
22
22
  .post('/users', ({ body }) => createUser(body), {
23
23
  body: t.Object({
24
24
  name: t.String(),
25
- email: t.String({ format: 'email' })
26
- })
25
+ email: t.String({ format: 'email' }),
26
+ }),
27
27
  })
28
28
  .listen(3000)
29
29
  ```
30
30
 
31
31
  ## Content Map
32
32
 
33
- | File | Description | When to Read |
34
- | --- | --- | --- |
35
- | [elysia-basics.md](references/elysia-basics.md) | Setup, routes, handlers, context | Starting new project |
36
- | [elysia-plugins.md](references/elysia-plugins.md) | Plugins, guards, modular design | Organizing code |
37
- | [elysia-validation.md](references/elysia-validation.md) | TypeBox validation (body, query, params) | Input validation |
38
- | [elysia-lifecycle.md](references/elysia-lifecycle.md) | Hooks (onBeforeHandle, onError, etc.) | Middleware, auth checks |
39
- | [elysia-patterns.md](references/elysia-patterns.md) | REST patterns, responses, pagination | API design |
33
+ | File | Description | When to Read |
34
+ | ------------------------------------------------------- | ---------------------------------------- | ----------------------- |
35
+ | [elysia-basics.md](references/elysia-basics.md) | Setup, routes, handlers, context | Starting new project |
36
+ | [elysia-plugins.md](references/elysia-plugins.md) | Plugins, guards, modular design | Organizing code |
37
+ | [elysia-validation.md](references/elysia-validation.md) | TypeBox validation (body, query, params) | Input validation |
38
+ | [elysia-lifecycle.md](references/elysia-lifecycle.md) | Hooks (onBeforeHandle, onError, etc.) | Middleware, auth checks |
39
+ | [elysia-patterns.md](references/elysia-patterns.md) | REST patterns, responses, pagination | API design |
40
40
 
41
41
  ## Project Structure
42
42
 
@@ -61,13 +61,13 @@ src/
61
61
 
62
62
  ## Dependencies
63
63
 
64
- | Pacote | Versão | Descrição |
65
- |---|---|---|
66
- | `bun` | latest | Runtime |
67
- | `elysia` | latest | Framework HTTP |
68
- | `bun:sqlite` | builtin | SQLite driver |
69
- | `drizzle-orm` | latest | ORM |
70
- | `bun:s3` | latest | S3/R2 Storage |
64
+ | Pacote | Versão | Descrição |
65
+ | ------------- | ------- | -------------- |
66
+ | `bun` | latest | Runtime |
67
+ | `elysia` | latest | Framework HTTP |
68
+ | `bun:sqlite` | builtin | SQLite driver |
69
+ | `drizzle-orm` | latest | ORM |
70
+ | `bun:s3` | latest | S3/R2 Storage |
71
71
 
72
72
  ## Core Patterns
73
73
 
@@ -84,8 +84,8 @@ export const userRoutes = new Elysia({ prefix: '/users' })
84
84
  .post('/', ({ body }) => createUser(body), {
85
85
  body: t.Object({
86
86
  name: t.String({ minLength: 1 }),
87
- email: t.String({ format: 'email' })
88
- })
87
+ email: t.String({ format: 'email' }),
88
+ }),
89
89
  })
90
90
  ```
91
91
 
@@ -112,11 +112,11 @@ console.log(`Server running at ${app.server?.url}`)
112
112
 
113
113
  ## Related Skills
114
114
 
115
- | Need | Skill |
116
- | --- | --- |
115
+ | Need | Skill |
116
+ | ------------------ | ---------------------------------------- |
117
117
  | **Authentication** | @[.agent/skills/riligar-dev-auth-elysia] |
118
- | **Database** | @[.agent/skills/riligar-dev-database] |
119
- | **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
118
+ | **database** | @[.agent/skills/riligar-dev-database] |
119
+ | **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
120
120
 
121
121
  ## Decision Checklist
122
122
 
@@ -126,14 +126,14 @@ Before building an API:
126
126
  - [ ] Planned validation for all inputs?
127
127
  - [ ] Error handling configured?
128
128
  - [ ] Auth middleware needed? → Use `riligar-dev-auth-elysia`
129
- - [ ] Database connection setup? → Use `riligar-dev-database`
129
+ - [ ] database connection setup? → Use `riligar-dev-database`
130
130
 
131
131
  ## Anti-Patterns
132
132
 
133
- | Don't | Do |
134
- | --- | --- |
135
- | Put business logic in handlers | Extract to `services/` |
136
- | Skip input validation | Use TypeBox (`t.Object`) |
137
- | Ignore error handling | Use `onError` lifecycle |
138
- | Create monolithic files | Split into plugins |
133
+ | Don't | Do |
134
+ | -------------------------------- | ------------------------ |
135
+ | Put business logic in handlers | Extract to `services/` |
136
+ | Skip input validation | Use TypeBox (`t.Object`) |
137
+ | Ignore error handling | Use `onError` lifecycle |
138
+ | Create monolithic files | Split into plugins |
139
139
  | Use verbs in routes (`/getUser`) | Use nouns (`/users/:id`) |
@@ -21,8 +21,9 @@ Pergunte ao usuário:
21
21
  > Você pode encontrá-las em: https://dashboard.stripe.com/apikeys
22
22
  >
23
23
  > Por favor, me forneça:
24
- > 1. **Publishable Key** (pk_live_... ou pk_test_...)
25
- > 2. **Secret Key** (sk_live_... ou sk_test_...)
24
+ >
25
+ > 1. **Publishable Key** (pk*live*... ou pk*test*...)
26
+ > 2. **Secret Key** (sk*live*... ou sk*test*...)
26
27
 
27
28
  Aguarde as chaves antes de prosseguir.
28
29
 
@@ -36,6 +37,7 @@ Após receber as chaves, pergunte:
36
37
  > 2. **Quais planos/produtos** você quer oferecer?
37
38
  >
38
39
  > Exemplo de resposta:
40
+ >
39
41
  > - Assinatura mensal
40
42
  > - Plano Starter: R$ 29/mês (5 projetos, suporte email)
41
43
  > - Plano Pro: R$ 99/mês (ilimitado, suporte prioritário)
@@ -57,17 +59,17 @@ async function setupProducts() {
57
59
  {
58
60
  name: 'Plano Starter',
59
61
  description: '5 projetos, suporte email',
60
- price: 2900, // R$ 29,00 em centavos
62
+ price: 2900, // R$ 29,00 em centavos
61
63
  interval: 'month',
62
- features: ['5 projetos', 'Suporte email', '1GB storage']
64
+ features: ['5 projetos', 'Suporte email', '1GB storage'],
63
65
  },
64
66
  {
65
67
  name: 'Plano Pro',
66
68
  description: 'Ilimitado, suporte prioritário',
67
- price: 9900, // R$ 99,00 em centavos
69
+ price: 9900, // R$ 99,00 em centavos
68
70
  interval: 'month',
69
- features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage']
70
- }
71
+ features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage'],
72
+ },
71
73
  ]
72
74
 
73
75
  console.log('Criando produtos no Stripe...\n')
@@ -76,14 +78,14 @@ async function setupProducts() {
76
78
  const stripeProduct = await stripe.products.create({
77
79
  name: product.name,
78
80
  description: product.description,
79
- metadata: { features: JSON.stringify(product.features) }
81
+ metadata: { features: JSON.stringify(product.features) },
80
82
  })
81
83
 
82
84
  const stripePrice = await stripe.prices.create({
83
85
  product: stripeProduct.id,
84
86
  unit_amount: product.price,
85
87
  currency: 'brl',
86
- recurring: product.interval ? { interval: product.interval } : undefined
88
+ recurring: product.interval ? { interval: product.interval } : undefined,
87
89
  })
88
90
 
89
91
  console.log(`✓ ${product.name}`)
@@ -111,6 +113,7 @@ Após executar o script, peça:
111
113
  Com as chaves e Price IDs, configure os arquivos de ambiente:
112
114
 
113
115
  **Backend: `.env.development` e `.env.production`**
116
+
114
117
  ```bash
115
118
  # .env.development (chaves de teste)
116
119
  STRIPE_SECRET_KEY=sk_test_...
@@ -122,6 +125,7 @@ STRIPE_WEBHOOK_SECRET=whsec_...
122
125
  ```
123
126
 
124
127
  **Frontend: `.env.development` e `.env.production`**
128
+
125
129
  ```bash
126
130
  # .env.development
127
131
  VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
@@ -130,7 +134,7 @@ VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
130
134
  VITE_STRIPE_PUBLISHABLE_KEY=pk_live_...
131
135
  ```
132
136
 
133
- ### Step 6: Configurar Database
137
+ ### Step 6: Configurar database
134
138
 
135
139
  Gere a migration para adicionar campos do Stripe:
136
140
 
@@ -153,29 +157,29 @@ Instrua o usuário:
153
157
  > 2. Clique em "Add endpoint"
154
158
  > 3. URL: `https://seu-dominio.com/api/webhook`
155
159
  > 4. Selecione os eventos:
156
- > - `checkout.session.completed`
157
- > - `customer.subscription.updated`
158
- > - `customer.subscription.deleted`
159
- > - `invoice.paid`
160
- > - `invoice.payment_failed`
161
- > 5. Copie o "Signing secret" (whsec_...)
160
+ > - `checkout.session.completed`
161
+ > - `customer.subscription.updated`
162
+ > - `customer.subscription.deleted`
163
+ > - `invoice.paid`
164
+ > - `invoice.payment_failed`
165
+ > 5. Copie o "Signing secret" (whsec\_...)
162
166
  > 6. Adicione ao `.env.development` e `.env.production`
163
167
 
164
168
  ### Step 8: Gerar Código
165
169
 
166
170
  Gere todos os arquivos necessários usando os templates de [assets/](assets/):
167
171
 
168
- | Arquivo | Baseado em |
169
- | --- | --- |
170
- | `plugins/stripe.js` | stripe-server.js (seção 1) |
171
- | `routes/billing.js` | stripe-server.js (seção 2) |
172
- | `routes/webhook.js` | stripe-server.js (seção 3) |
173
- | `services/billing.js` | stripe-server.js (seção 4) |
174
- | `config/stripe-prices.js` | Price IDs coletados (Step 9) |
175
- | `config/plans.js` | PLAN_MAP + PLAN_LIMITS (Step 9) |
176
- | `pages/Pricing.jsx` | stripe-client.js (seção 3) |
177
- | `components/BillingSettings.jsx` | stripe-client.js (seção 4) |
178
- | `hooks/useSubscription.js` | stripe-client.js (seção 2) |
172
+ | Arquivo | Baseado em |
173
+ | -------------------------------- | ------------------------------- |
174
+ | `plugins/stripe.js` | stripe-server.js (seção 1) |
175
+ | `routes/billing.js` | stripe-server.js (seção 2) |
176
+ | `routes/webhook.js` | stripe-server.js (seção 3) |
177
+ | `services/billing.js` | stripe-server.js (seção 4) |
178
+ | `config/stripe-prices.js` | Price IDs coletados (Step 9) |
179
+ | `config/plans.js` | PLAN_MAP + PLAN_LIMITS (Step 9) |
180
+ | `pages/Pricing.jsx` | stripe-client.js (seção 3) |
181
+ | `components/BillingSettings.jsx` | stripe-client.js (seção 4) |
182
+ | `hooks/useSubscription.js` | stripe-client.js (seção 2) |
179
183
 
180
184
  ### Step 9: Criar Configs de Planos e Preços
181
185
 
@@ -188,24 +192,24 @@ export const STRIPE_PRICES = {
188
192
  priceId: 'price_COLETADO_STARTER',
189
193
  name: 'Starter',
190
194
  price: 29,
191
- features: ['5 projetos', 'Suporte email', '1GB storage']
195
+ features: ['5 projetos', 'Suporte email', '1GB storage'],
192
196
  },
193
197
  pro: {
194
198
  priceId: 'price_COLETADO_PRO',
195
199
  name: 'Pro',
196
200
  price: 99,
197
- features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage']
201
+ features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage'],
198
202
  },
199
203
  enterprise: {
200
204
  priceId: 'price_COLETADO_ENTERPRISE',
201
205
  name: 'Enterprise',
202
206
  price: 299,
203
- features: ['Tudo do Pro', 'Storage ilimitado', 'SLA garantido']
204
- }
207
+ features: ['Tudo do Pro', 'Storage ilimitado', 'SLA garantido'],
208
+ },
205
209
  }
206
210
 
207
- export const getPrice = (plan) => STRIPE_PRICES[plan]
208
- export const getPriceId = (plan) => STRIPE_PRICES[plan]?.priceId
211
+ export const getPrice = plan => STRIPE_PRICES[plan]
212
+ export const getPriceId = plan => STRIPE_PRICES[plan]?.priceId
209
213
  ```
210
214
 
211
215
  **B) Arquivo de mapeamento e limites (config/plans.js):**
@@ -215,9 +219,9 @@ export const getPriceId = (plan) => STRIPE_PRICES[plan]?.priceId
215
219
 
216
220
  // Mapeia Price IDs do Stripe para nomes de planos internos
217
221
  export const PLAN_MAP = {
218
- 'price_COLETADO_STARTER': 'starter',
219
- 'price_COLETADO_PRO': 'pro',
220
- 'price_COLETADO_ENTERPRISE': 'enterprise',
222
+ price_COLETADO_STARTER: 'starter',
223
+ price_COLETADO_PRO: 'pro',
224
+ price_COLETADO_ENTERPRISE: 'enterprise',
221
225
  }
222
226
 
223
227
  // Define limites de features por plano
@@ -283,12 +287,12 @@ stripe trigger checkout.session.completed
283
287
 
284
288
  ## Specialized Guides
285
289
 
286
- | Guide | Content |
287
- | --- | --- |
288
- | [stripe-elysia.md](references/stripe-elysia.md) | Backend routes completas |
289
- | [stripe-react.md](references/stripe-react.md) | Componentes React/Mantine |
290
- | [stripe-webhooks.md](references/stripe-webhooks.md) | Handlers de eventos |
291
- | [stripe-database.md](references/stripe-database.md) | Schema Drizzle |
290
+ | Guide | Content |
291
+ | --------------------------------------------------- | ------------------------- |
292
+ | [stripe-elysia.md](references/stripe-elysia.md) | Backend routes completas |
293
+ | [stripe-react.md](references/stripe-react.md) | Componentes React/Mantine |
294
+ | [stripe-webhooks.md](references/stripe-webhooks.md) | Handlers de eventos |
295
+ | [stripe-database.md](references/stripe-database.md) | Schema Drizzle |
292
296
 
293
297
  ---
294
298
 
@@ -1,6 +1,6 @@
1
- # Stripe Database Patterns (Drizzle)
1
+ # Stripe database Patterns (Drizzle)
2
2
 
3
- Database schemas and queries for Stripe billing with SQLite and Drizzle ORM.
3
+ database schemas and queries for Stripe billing with SQLite and Drizzle ORM.
4
4
 
5
5
  ## Schema
6
6
 
@@ -18,13 +18,15 @@ export const users = sqliteTable('users', {
18
18
  // Stripe fields
19
19
  stripeCustomerId: text('stripe_customer_id').unique(),
20
20
  stripeSubscriptionId: text('stripe_subscription_id').unique(),
21
- plan: text('plan').default('free'), // free, starter, pro, enterprise
22
- subscriptionStatus: text('subscription_status'), // active, past_due, canceled, trialing
21
+ plan: text('plan').default('free'), // free, starter, pro, enterprise
22
+ subscriptionStatus: text('subscription_status'), // active, past_due, canceled, trialing
23
23
  currentPeriodEnd: integer('current_period_end', { mode: 'timestamp' }),
24
24
  cancelAtPeriodEnd: integer('cancel_at_period_end', { mode: 'boolean' }).default(false),
25
25
 
26
- createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
27
- updatedAt: integer('updated_at', { mode: 'timestamp' })
26
+ createdAt: integer('created_at', { mode: 'timestamp' })
27
+ .notNull()
28
+ .$defaultFn(() => new Date()),
29
+ updatedAt: integer('updated_at', { mode: 'timestamp' }),
28
30
  })
29
31
  ```
30
32
 
@@ -32,13 +34,19 @@ export const users = sqliteTable('users', {
32
34
 
33
35
  ```javascript
34
36
  export const invoices = sqliteTable('invoices', {
35
- id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
36
- userId: text('user_id').references(() => users.id).notNull(),
37
+ id: text('id')
38
+ .primaryKey()
39
+ .$defaultFn(() => crypto.randomUUID()),
40
+ userId: text('user_id')
41
+ .references(() => users.id)
42
+ .notNull(),
37
43
  stripeInvoiceId: text('stripe_invoice_id').unique().notNull(),
38
- amount: integer('amount').notNull(), // in cents
39
- status: text('status').notNull(), // paid, open, void
40
- url: text('url'), // hosted invoice URL
41
- createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date())
44
+ amount: integer('amount').notNull(), // in cents
45
+ status: text('status').notNull(), // paid, open, void
46
+ url: text('url'), // hosted invoice URL
47
+ createdAt: integer('created_at', { mode: 'timestamp' })
48
+ .notNull()
49
+ .$defaultFn(() => new Date()),
42
50
  })
43
51
  ```
44
52
 
@@ -46,10 +54,12 @@ export const invoices = sqliteTable('invoices', {
46
54
 
47
55
  ```javascript
48
56
  export const webhookEvents = sqliteTable('webhook_events', {
49
- id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
57
+ id: text('id')
58
+ .primaryKey()
59
+ .$defaultFn(() => crypto.randomUUID()),
50
60
  stripeEventId: text('stripe_event_id').unique().notNull(),
51
61
  type: text('type').notNull(),
52
- processedAt: integer('processed_at', { mode: 'timestamp' }).notNull()
62
+ processedAt: integer('processed_at', { mode: 'timestamp' }).notNull(),
53
63
  })
54
64
  ```
55
65
 
@@ -61,10 +71,10 @@ export const products = sqliteTable('products', {
61
71
  stripePriceId: text('stripe_price_id').unique().notNull(),
62
72
  name: text('name').notNull(),
63
73
  description: text('description'),
64
- amount: integer('amount').notNull(), // in cents
65
- interval: text('interval'), // month, year, null for one-time
74
+ amount: integer('amount').notNull(), // in cents
75
+ interval: text('interval'), // month, year, null for one-time
66
76
  active: integer('active', { mode: 'boolean' }).default(true),
67
- features: text('features', { mode: 'json' }) // ["feature1", "feature2"]
77
+ features: text('features', { mode: 'json' }), // ["feature1", "feature2"]
68
78
  })
69
79
  ```
70
80
 
@@ -78,10 +88,7 @@ import { users } from '../database/schema'
78
88
  import { eq } from 'drizzle-orm'
79
89
 
80
90
  export async function getUserWithSubscription(userId) {
81
- const [user] = await db.select()
82
- .from(users)
83
- .where(eq(users.id, userId))
84
- .limit(1)
91
+ const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1)
85
92
 
86
93
  return user
87
94
  }
@@ -91,10 +98,11 @@ export async function getUserWithSubscription(userId) {
91
98
 
92
99
  ```javascript
93
100
  export async function hasActiveSubscription(userId) {
94
- const [user] = await db.select({
95
- status: users.subscriptionStatus,
96
- periodEnd: users.currentPeriodEnd
97
- })
101
+ const [user] = await db
102
+ .select({
103
+ status: users.subscriptionStatus,
104
+ periodEnd: users.currentPeriodEnd,
105
+ })
98
106
  .from(users)
99
107
  .where(eq(users.id, userId))
100
108
  .limit(1)
@@ -110,10 +118,7 @@ export async function hasActiveSubscription(userId) {
110
118
 
111
119
  ```javascript
112
120
  export async function getUserByStripeCustomer(stripeCustomerId) {
113
- const [user] = await db.select()
114
- .from(users)
115
- .where(eq(users.stripeCustomerId, stripeCustomerId))
116
- .limit(1)
121
+ const [user] = await db.select().from(users).where(eq(users.stripeCustomerId, stripeCustomerId)).limit(1)
117
122
 
118
123
  return user
119
124
  }
@@ -123,10 +128,7 @@ export async function getUserByStripeCustomer(stripeCustomerId) {
123
128
 
124
129
  ```javascript
125
130
  export async function getUserBySubscription(stripeSubscriptionId) {
126
- const [user] = await db.select()
127
- .from(users)
128
- .where(eq(users.stripeSubscriptionId, stripeSubscriptionId))
129
- .limit(1)
131
+ const [user] = await db.select().from(users).where(eq(users.stripeSubscriptionId, stripeSubscriptionId)).limit(1)
130
132
 
131
133
  return user
132
134
  }
@@ -136,7 +138,8 @@ export async function getUserBySubscription(stripeSubscriptionId) {
136
138
 
137
139
  ```javascript
138
140
  export async function updateUserSubscription(userId, data) {
139
- await db.update(users)
141
+ await db
142
+ .update(users)
140
143
  .set({
141
144
  stripeCustomerId: data.customerId,
142
145
  stripeSubscriptionId: data.subscriptionId,
@@ -144,7 +147,7 @@ export async function updateUserSubscription(userId, data) {
144
147
  subscriptionStatus: data.status,
145
148
  currentPeriodEnd: data.periodEnd,
146
149
  cancelAtPeriodEnd: data.cancelAtPeriodEnd,
147
- updatedAt: new Date()
150
+ updatedAt: new Date(),
148
151
  })
149
152
  .where(eq(users.id, userId))
150
153
  }
@@ -154,12 +157,13 @@ export async function updateUserSubscription(userId, data) {
154
157
 
155
158
  ```javascript
156
159
  export async function cancelSubscriptionInDb(subscriptionId) {
157
- await db.update(users)
160
+ await db
161
+ .update(users)
158
162
  .set({
159
163
  plan: 'free',
160
164
  subscriptionStatus: 'canceled',
161
165
  stripeSubscriptionId: null,
162
- updatedAt: new Date()
166
+ updatedAt: new Date(),
163
167
  })
164
168
  .where(eq(users.stripeSubscriptionId, subscriptionId))
165
169
  }
@@ -171,11 +175,7 @@ export async function cancelSubscriptionInDb(subscriptionId) {
171
175
  import { desc } from 'drizzle-orm'
172
176
 
173
177
  export async function getUserInvoices(userId, limit = 10) {
174
- return db.select()
175
- .from(invoices)
176
- .where(eq(invoices.userId, userId))
177
- .orderBy(desc(invoices.createdAt))
178
- .limit(limit)
178
+ return db.select().from(invoices).where(eq(invoices.userId, userId)).orderBy(desc(invoices.createdAt)).limit(limit)
179
179
  }
180
180
  ```
181
181
 
@@ -183,10 +183,7 @@ export async function getUserInvoices(userId, limit = 10) {
183
183
 
184
184
  ```javascript
185
185
  export async function isWebhookProcessed(eventId) {
186
- const [event] = await db.select()
187
- .from(webhookEvents)
188
- .where(eq(webhookEvents.stripeEventId, eventId))
189
- .limit(1)
186
+ const [event] = await db.select().from(webhookEvents).where(eq(webhookEvents.stripeEventId, eventId)).limit(1)
190
187
 
191
188
  return !!event
192
189
  }
@@ -195,7 +192,7 @@ export async function markWebhookProcessed(eventId, type) {
195
192
  await db.insert(webhookEvents).values({
196
193
  stripeEventId: eventId,
197
194
  type,
198
- processedAt: new Date()
195
+ processedAt: new Date(),
199
196
  })
200
197
  }
201
198
  ```
@@ -274,42 +271,39 @@ export const billingService = {
274
271
 
275
272
  // Get user
276
273
  async getUser(userId) {
277
- const [user] = await db.select()
278
- .from(users)
279
- .where(eq(users.id, userId))
280
- .limit(1)
274
+ const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1)
281
275
  return user
282
276
  },
283
277
 
284
278
  // Link Stripe customer
285
279
  async linkStripeCustomer(userId, stripeCustomerId) {
286
- await db.update(users)
287
- .set({ stripeCustomerId, updatedAt: new Date() })
288
- .where(eq(users.id, userId))
280
+ await db.update(users).set({ stripeCustomerId, updatedAt: new Date() }).where(eq(users.id, userId))
289
281
  },
290
282
 
291
283
  // Activate subscription
292
284
  async activateSubscription(userId, { subscriptionId, plan, periodEnd }) {
293
- await db.update(users)
285
+ await db
286
+ .update(users)
294
287
  .set({
295
288
  stripeSubscriptionId: subscriptionId,
296
289
  plan,
297
290
  subscriptionStatus: 'active',
298
291
  currentPeriodEnd: periodEnd,
299
292
  cancelAtPeriodEnd: false,
300
- updatedAt: new Date()
293
+ updatedAt: new Date(),
301
294
  })
302
295
  .where(eq(users.id, userId))
303
296
  },
304
297
 
305
298
  // Deactivate subscription
306
299
  async deactivateSubscription(subscriptionId) {
307
- await db.update(users)
300
+ await db
301
+ .update(users)
308
302
  .set({
309
303
  plan: 'free',
310
304
  subscriptionStatus: 'canceled',
311
305
  stripeSubscriptionId: null,
312
- updatedAt: new Date()
306
+ updatedAt: new Date(),
313
307
  })
314
308
  .where(eq(users.stripeSubscriptionId, subscriptionId))
315
309
  },
@@ -321,9 +315,9 @@ export const billingService = {
321
315
  stripeInvoiceId: stripeInvoice.id,
322
316
  amount: stripeInvoice.amount_paid,
323
317
  status: stripeInvoice.status,
324
- url: stripeInvoice.hosted_invoice_url
318
+ url: stripeInvoice.hosted_invoice_url,
325
319
  })
326
- }
320
+ },
327
321
  }
328
322
  ```
329
323
 
@@ -334,28 +328,28 @@ export const billingService = {
334
328
  const planFeatures = {
335
329
  free: {
336
330
  projects: 1,
337
- storage: 100 * 1024 * 1024, // 100MB
331
+ storage: 100 * 1024 * 1024, // 100MB
338
332
  apiCalls: 100,
339
- support: 'community'
333
+ support: 'community',
340
334
  },
341
335
  starter: {
342
336
  projects: 5,
343
- storage: 1 * 1024 * 1024 * 1024, // 1GB
337
+ storage: 1 * 1024 * 1024 * 1024, // 1GB
344
338
  apiCalls: 1000,
345
- support: 'email'
339
+ support: 'email',
346
340
  },
347
341
  pro: {
348
342
  projects: Infinity,
349
- storage: 10 * 1024 * 1024 * 1024, // 10GB
343
+ storage: 10 * 1024 * 1024 * 1024, // 10GB
350
344
  apiCalls: 10000,
351
- support: 'priority'
345
+ support: 'priority',
352
346
  },
353
347
  enterprise: {
354
348
  projects: Infinity,
355
349
  storage: Infinity,
356
350
  apiCalls: Infinity,
357
- support: 'dedicated'
358
- }
351
+ support: 'dedicated',
352
+ },
359
353
  }
360
354
 
361
355
  export function getFeatureLimits(plan) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riligar/agents-kit",
3
- "version": "1.19.0",
3
+ "version": "1.20.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },