@igniter-js/cli 0.1.0 → 0.1.2
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.
- package/dist/index.js +12 -1
- package/dist/templates/copilot.feature.instructions.hbs +145 -0
- package/dist/templates/copilot.igniter.instructions.hbs +753 -0
- package/dist/templates/copilot.next.instructions.hbs +67 -0
- package/dist/templates/copilot.review.instructions.hbs +42 -0
- package/dist/templates/copilot.test.instructions.hbs +55 -0
- package/dist/templates/eslintrc.hbs +5 -2
- package/dist/templates/feature.controller.hbs +3 -1
- package/dist/templates/feature.interface.hbs +33 -23
- package/dist/templates/globals.hbs +1 -1
- package/dist/templates/layout.hbs +15 -22
- package/dist/templates/page.hbs +76 -239
- package/dist/templates/route.hbs +4 -0
- package/dist/templates/vscode.settings.hbs +50 -0
- package/dist/utils/consts.d.ts +5 -7
- package/dist/utils/consts.js +11 -8
- package/dist/utils/handlebars-helpers.js +20 -0
- package/dist/utils/prisma-schema-parser.d.ts +1 -0
- package/dist/utils/prisma-schema-parser.js +92 -29
- package/package.json +1 -1
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: https://www.npmjs.com/package/@igniter-js/core?activeTab=readme
|
|
3
|
+
url: https://www.npmjs.com/package/@igniter-js/core?activeTab=readme
|
|
4
|
+
timestamp: 2025-02-26T18:35:32.625Z
|
|
5
|
+
---
|
|
6
|
+
```markdown
|
|
7
|
+
# Igniter
|
|
8
|
+
|
|
9
|
+
Igniter is a modern, type-safe HTTP framework designed to streamline the development of scalable TypeScript applications. It combines the flexibility of traditional HTTP frameworks with the power of full-stack type safety, making it the ideal choice for teams building robust web applications.
|
|
10
|
+
|
|
11
|
+
## Why Igniter?
|
|
12
|
+
|
|
13
|
+
* **Type Safety Without Compromise**: End-to-end type safety from your API routes to your client code, catching errors before they reach production
|
|
14
|
+
* **Framework Agnostic**: Seamlessly integrates with Next.js, Express, Fastify, or any Node.js framework
|
|
15
|
+
* **Developer Experience First**: Built with TypeScript best practices and modern development patterns in mind
|
|
16
|
+
* **Production Ready**: Being used in production by companies of all sizes
|
|
17
|
+
* **Minimal Boilerplate**: Get started quickly without sacrificing scalability
|
|
18
|
+
* **Flexible Architecture**: Adapts to your project's needs, from small APIs to large-scale applications
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
* 🎯 Full TypeScript Support: End-to-end type safety from your API routes to your client code
|
|
23
|
+
* 🚀 Modern Architecture: Built with modern TypeScript features and best practices
|
|
24
|
+
* 🔒 Type-Safe Routing: Route parameters and query strings are fully typed
|
|
25
|
+
* 🔌 Middleware System: Powerful and flexible middleware support with full type inference
|
|
26
|
+
* 🎭 Context Sharing: Share context between middlewares and route handlers
|
|
27
|
+
* 🔄 Built-in Error Handling: Comprehensive error handling with type-safe error responses
|
|
28
|
+
* 🍪 Cookie Management: Built-in cookie handling with signing support
|
|
29
|
+
* 📦 Framework Agnostic: Works with any Node.js framework (Express, Fastify, Next.js, etc.)
|
|
30
|
+
|
|
31
|
+
## Getting Started
|
|
32
|
+
|
|
33
|
+
### Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @igniter-js/core
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# or
|
|
41
|
+
yarn add @igniter-js/core
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# or
|
|
46
|
+
pnpm add @igniter-js/core
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# or
|
|
51
|
+
bun add @igniter-js/core
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Quick Start Guide
|
|
55
|
+
|
|
56
|
+
Building an API with Igniter is straightforward and intuitive. Here's how to get started:
|
|
57
|
+
|
|
58
|
+
### Project Structure
|
|
59
|
+
|
|
60
|
+
Igniter promotes a feature-based architecture that scales with your application:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
src/
|
|
64
|
+
├── igniter.ts # Core initialization
|
|
65
|
+
├── igniter.client.ts # Client implementation
|
|
66
|
+
├── igniter.context.ts # Context management
|
|
67
|
+
├── igniter.router.ts # Router configuration
|
|
68
|
+
├── features/ # Application features
|
|
69
|
+
│ └── [feature]/
|
|
70
|
+
│ ├── presentation/ # Feature presentation layer
|
|
71
|
+
│ │ ├── components/ # Feature-specific components
|
|
72
|
+
│ │ ├── hooks/ # Custom hooks
|
|
73
|
+
│ │ ├── contexts/ # Feature contexts
|
|
74
|
+
│ │ └── utils/ # Utility functions
|
|
75
|
+
│ ├── controllers/ # Feature controllers
|
|
76
|
+
│ │ └── [feature].controller.ts
|
|
77
|
+
│ ├── procedures/ # Feature procedures/middleware
|
|
78
|
+
│ │ └── [feature].procedure.ts
|
|
79
|
+
│ ├── [feature].interfaces.ts # Type definitions(interfaces, entities, inputs and outputs)
|
|
80
|
+
│ └── index.ts # Feature exports
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Understanding the Structure
|
|
84
|
+
|
|
85
|
+
* Feature-based Organization: Each feature is self-contained with its own controllers, procedures, and types
|
|
86
|
+
* Clear Separation of Concerns: Presentation, business logic, and data access are clearly separated
|
|
87
|
+
* Scalable Architecture: Easy to add new features without affecting existing ones
|
|
88
|
+
* Maintainable Codebase: Consistent structure makes it easy for teams to navigate and maintain
|
|
89
|
+
|
|
90
|
+
1. Initialize Igniter
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// src/igniter.ts
|
|
94
|
+
|
|
95
|
+
import { Igniter } from "@igniter-js/core";
|
|
96
|
+
import type { IgniterAppContext } from "./igniter.context";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @description Initialize the Igniter Router
|
|
100
|
+
* @see https://igniter.felipebarcelospro.github.io/docs/getting-started/installation
|
|
101
|
+
*/
|
|
102
|
+
export const igniter = Igniter.context<IgniterAppContext>().create()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
2. Define your App Global Context
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// src/igniter.context
|
|
109
|
+
import { prisma } from "@/lib/db";
|
|
110
|
+
import { Invariant } from "@/utils";
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @description Create the context of the application
|
|
114
|
+
* @see https://igniter.felipebarcelospro.github.io/docs/getting-started/installation
|
|
115
|
+
*/
|
|
116
|
+
export const createIgniterAppContext = () => {
|
|
117
|
+
return {
|
|
118
|
+
providers: {
|
|
119
|
+
database: prisma,
|
|
120
|
+
rules: Invariant.initialize('Igniter')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @description The context of the application
|
|
127
|
+
* @see https://igniter.felipebarcelospro.github.io/docs/getting-started/installation
|
|
128
|
+
*/
|
|
129
|
+
export type IgniterAppContext = Awaited<ReturnType<typeof createIgniterAppContext>>;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
3. Create your first controller
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// src/features/user/controllers/user.controller.ts
|
|
136
|
+
import { igniter } from '@/igniter'
|
|
137
|
+
|
|
138
|
+
export const userController = igniter.controller({
|
|
139
|
+
path: '/users',
|
|
140
|
+
actions: {
|
|
141
|
+
// Query action (GET)
|
|
142
|
+
list: igniter.query({
|
|
143
|
+
path: '/',
|
|
144
|
+
use: [auth()],
|
|
145
|
+
query: z.object({
|
|
146
|
+
page: z.number().optional(),
|
|
147
|
+
limit: z.number().optional()
|
|
148
|
+
}),
|
|
149
|
+
handler: async (ctx) => {
|
|
150
|
+
return ctx.response.success({
|
|
151
|
+
users: [
|
|
152
|
+
{ id: 1, name: 'John Doe' }
|
|
153
|
+
]
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}),
|
|
157
|
+
|
|
158
|
+
// Mutation action (POST)
|
|
159
|
+
create: igniter.mutation({
|
|
160
|
+
path: '/',
|
|
161
|
+
method: 'POST',
|
|
162
|
+
use: [auth()],
|
|
163
|
+
body: z.object({
|
|
164
|
+
name: z.string(),
|
|
165
|
+
email: z.string().email()
|
|
166
|
+
}),
|
|
167
|
+
handler: async (ctx) => {
|
|
168
|
+
const { name, email } = ctx.request.body
|
|
169
|
+
|
|
170
|
+
return ctx.response.created({
|
|
171
|
+
id: '1',
|
|
172
|
+
name,
|
|
173
|
+
email
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
4. Initialize Igniter Router with your framework
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// src/igniter.router.ts
|
|
185
|
+
import { igniter } from '@/igniter'
|
|
186
|
+
import { userController } from '@/features/user'
|
|
187
|
+
|
|
188
|
+
export const AppRouter = igniter.router({
|
|
189
|
+
baseURL: 'http://localhost:3000',
|
|
190
|
+
basePATH: '/api/v1',
|
|
191
|
+
controllers: {
|
|
192
|
+
users: userController
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Use with any HTTP framework
|
|
197
|
+
// Example with Express:
|
|
198
|
+
import { AppRouter } from '@/igniter.router'
|
|
199
|
+
|
|
200
|
+
app.use(async (req, res) => {
|
|
201
|
+
const response = await AppRouter.handler(req)
|
|
202
|
+
res.status(response.status).json(response)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Example with Bun:
|
|
206
|
+
import { AppRouter } from '@/igniter.router'
|
|
207
|
+
|
|
208
|
+
Bun.serve({
|
|
209
|
+
fetch: AppRouter.handler
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Example with Next Route Handlers:
|
|
213
|
+
// src/app/api/v1/[[...all]]/route.ts
|
|
214
|
+
import { AppRouter } from '@/igniter.router'
|
|
215
|
+
import { nextRouteHandlerAdapter } from '@igniter-js/core/adapters/next'
|
|
216
|
+
|
|
217
|
+
export const { GET, POST, PUT, DELETE } = nextRouteHandlerAdapter(AppRouter)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Core Concepts
|
|
221
|
+
|
|
222
|
+
### Application Context
|
|
223
|
+
|
|
224
|
+
The context system is the backbone of your application:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
type AppContext = {
|
|
228
|
+
db: Database
|
|
229
|
+
user?: User
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const igniter = Igniter.context<AppContext>().create()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Best Practices for Context
|
|
236
|
+
|
|
237
|
+
* Keep context focused and specific to your application needs
|
|
238
|
+
* Use TypeScript interfaces to define context shape
|
|
239
|
+
* Consider splitting large contexts into domain-specific contexts
|
|
240
|
+
* Avoid storing request-specific data in global context
|
|
241
|
+
|
|
242
|
+
### Procedures (Middleware)
|
|
243
|
+
|
|
244
|
+
Procedures provide a powerful way to handle cross-cutting concerns:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { igniter } from '@/igniter'
|
|
248
|
+
|
|
249
|
+
const auth = igniter.procedure({
|
|
250
|
+
handler: async (_, ctx) => {
|
|
251
|
+
const token = ctx.request.headers.get('authorization')
|
|
252
|
+
if (!token) {
|
|
253
|
+
return ctx.response.unauthorized()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const user = await verifyToken(token)
|
|
257
|
+
return { user }
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Use in actions
|
|
262
|
+
const protectedAction = igniter.query({
|
|
263
|
+
path: '/protected',
|
|
264
|
+
use: [auth()],
|
|
265
|
+
handler: (ctx) => {
|
|
266
|
+
// ctx.context.user is typed!
|
|
267
|
+
return ctx.response.success({ user: ctx.context.user })
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Common Use Cases for Procedures
|
|
273
|
+
|
|
274
|
+
* Authentication and Authorization
|
|
275
|
+
* Request Validation
|
|
276
|
+
* Logging and Monitoring
|
|
277
|
+
* Error Handling
|
|
278
|
+
* Performance Tracking
|
|
279
|
+
* Data Transformation
|
|
280
|
+
|
|
281
|
+
### Controllers and Actions
|
|
282
|
+
|
|
283
|
+
Controllers organize related functionality:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { igniter } from '@/igniter'
|
|
287
|
+
|
|
288
|
+
const userController = igniter.controller({
|
|
289
|
+
path: 'users',
|
|
290
|
+
actions: {
|
|
291
|
+
list: igniter.query({
|
|
292
|
+
path: '/',
|
|
293
|
+
handler: (ctx) => ctx.response.success({ users: [] })
|
|
294
|
+
}),
|
|
295
|
+
|
|
296
|
+
get: igniter.query({
|
|
297
|
+
path: '/:id',
|
|
298
|
+
handler: (ctx) => {
|
|
299
|
+
// ctx.request.params.id is typed!
|
|
300
|
+
return ctx.response.success({ user: { id: ctx.request.params.id } })
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Controller Best Practices
|
|
308
|
+
|
|
309
|
+
* Group related actions together
|
|
310
|
+
* Keep controllers focused on a single resource or domain
|
|
311
|
+
* Use meaningful names that reflect the resource
|
|
312
|
+
* Implement proper error handling
|
|
313
|
+
* Follow RESTful conventions where appropriate
|
|
314
|
+
|
|
315
|
+
### Type-Safe Responses
|
|
316
|
+
|
|
317
|
+
Igniter provides a robust response system:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
handler: async (ctx) => {
|
|
321
|
+
// Success responses
|
|
322
|
+
ctx.response.success({ data: 'ok' })
|
|
323
|
+
ctx.response.created({ id: 1 })
|
|
324
|
+
ctx.response.noContent()
|
|
325
|
+
|
|
326
|
+
// Error responses
|
|
327
|
+
ctx.response.badRequest('Invalid input')
|
|
328
|
+
ctx.response.unauthorized()
|
|
329
|
+
ctx.response.forbidden('Access denied')
|
|
330
|
+
ctx.response.notFound('Resource not found')
|
|
331
|
+
|
|
332
|
+
// Custom responses
|
|
333
|
+
ctx.response.status(418).setHeader('X-Custom', 'value').json({ message: "I'm a teapot" })
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Cookie Management
|
|
338
|
+
|
|
339
|
+
Secure cookie handling made easy:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
handler: async (ctx) => {
|
|
343
|
+
// Set cookies
|
|
344
|
+
await ctx.response.setCookie('session', 'value', {
|
|
345
|
+
httpOnly: true,
|
|
346
|
+
secure: true,
|
|
347
|
+
sameSite: 'strict'
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// Set signed cookies
|
|
351
|
+
await ctx.response.setSignedCookie('token', 'sensitive-data', 'secret-key')
|
|
352
|
+
|
|
353
|
+
// Get cookies
|
|
354
|
+
const session = ctx.request.cookies.get('session')
|
|
355
|
+
const token = await ctx.request.cookies.getSigned('token', 'secret-key')
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### React Client Integration
|
|
360
|
+
|
|
361
|
+
The Igniter React client provides a seamless integration with your frontend:
|
|
362
|
+
|
|
363
|
+
#### Setup
|
|
364
|
+
|
|
365
|
+
First, create your API client:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// src/igniter.client.ts
|
|
369
|
+
import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client';
|
|
370
|
+
import { AppRouter } from './igniter.router';
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Client for Igniter
|
|
374
|
+
*
|
|
375
|
+
* This client is used to fetch data on the client-side
|
|
376
|
+
* It uses the createIgniterClient function to create a client instance
|
|
377
|
+
*
|
|
378
|
+
*/
|
|
379
|
+
export const api = createIgniterClient(AppRouter);
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Query client for Igniter
|
|
383
|
+
*
|
|
384
|
+
* This client provides access to the Igniter query functions
|
|
385
|
+
* and handles data fetching with respect to the application router.
|
|
386
|
+
* It will enable the necessary hooks for query management.
|
|
387
|
+
*/
|
|
388
|
+
export const useQueryClient = useIgniterQueryClient<typeof AppRouter>;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Then, wrap your app with the Igniter provider:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// app/providers.tsx
|
|
395
|
+
import { IgniterProvider } from '@igniter-js/core/client'
|
|
396
|
+
|
|
397
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
398
|
+
return (
|
|
399
|
+
<IgniterProvider>
|
|
400
|
+
{children}
|
|
401
|
+
</IgniterProvider>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Queries
|
|
407
|
+
|
|
408
|
+
Use the `useQuery` hook for data fetching with automatic caching and revalidation:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { api } from '@/igniter.client'
|
|
412
|
+
|
|
413
|
+
function UsersList() {
|
|
414
|
+
const listUsers = api.users.list.useQuery({
|
|
415
|
+
// Optional configuration
|
|
416
|
+
initialData: [], // Initial data while loading
|
|
417
|
+
staleTime: 1000 * 60, // Data stays fresh for 1 minute
|
|
418
|
+
refetchInterval: 1000 * 30, // Refetch every 30 seconds
|
|
419
|
+
refetchOnWindowFocus: true, // Refetch when window regains focus
|
|
420
|
+
refetchOnMount: true, // Refetch when component mounts
|
|
421
|
+
refetchOnReconnect: true, // Refetch when reconnecting
|
|
422
|
+
onLoading: (isLoading) => console.log('Loading:', isLoading),
|
|
423
|
+
onRequest: (response) => console.log('Data received:', response)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
if (loading) return <div>Loading...</div>
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<div>
|
|
430
|
+
<button onClick={() => refetch()}>Refresh</button>
|
|
431
|
+
{users.map(user => (
|
|
432
|
+
<div key={user.id}>{user.name}</div>
|
|
433
|
+
))}
|
|
434
|
+
</div>
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### Mutations
|
|
440
|
+
|
|
441
|
+
Use the `useMutation` hook for data modifications:
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
function CreateUserForm() {
|
|
445
|
+
const createUser = api.users.create.useMutation({
|
|
446
|
+
// Optional configuration
|
|
447
|
+
defaultValues: { name: '', email: '' },
|
|
448
|
+
onLoading: (isLoading) => console.log('Loading:', isLoading),
|
|
449
|
+
onRequest: (response) => console.log('Created user:', response)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
453
|
+
e.preventDefault()
|
|
454
|
+
try {
|
|
455
|
+
await createUser.mutate({
|
|
456
|
+
body: {
|
|
457
|
+
name: 'John Doe',
|
|
458
|
+
email: 'john@example.com'
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
// Handle success
|
|
462
|
+
} catch (error) {
|
|
463
|
+
// Handle error
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<form onSubmit={handleSubmit}>
|
|
469
|
+
{/* Form fields */}
|
|
470
|
+
<button type="submit" disabled={createUser.loading}>
|
|
471
|
+
{createUser.loading ? 'Creating...' : 'Create User'}
|
|
472
|
+
</button>
|
|
473
|
+
</form>
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Cache Invalidation
|
|
479
|
+
|
|
480
|
+
Invalidate queries manually or automatically after mutations:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
function AdminPanel() {
|
|
484
|
+
const queryClient = useIgniterQueryClient()
|
|
485
|
+
|
|
486
|
+
// Invalidate specific queries
|
|
487
|
+
const invalidateUsers = () => {
|
|
488
|
+
queryClient.invalidate('users.list')
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Invalidate multiple queries
|
|
492
|
+
const invalidateAll = () => {
|
|
493
|
+
queryClient.invalidate([
|
|
494
|
+
'users.list',
|
|
495
|
+
'users.get'
|
|
496
|
+
])
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
<button onClick={invalidateUsers}>
|
|
501
|
+
Refresh Users
|
|
502
|
+
</button>
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### Automatic Type Inference
|
|
508
|
+
|
|
509
|
+
The client provides full type inference for your API:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// All these types are automatically inferred
|
|
513
|
+
type User = InferOutput<typeof api.users.get>
|
|
514
|
+
type CreateUserInput = InferInput<typeof api.users.create>
|
|
515
|
+
type QueryKeys = InferCacheKeysFromRouter<typeof router>
|
|
516
|
+
|
|
517
|
+
// TypeScript will show errors for invalid inputs
|
|
518
|
+
api.users.create.useMutation({
|
|
519
|
+
onRequest: (data) => {
|
|
520
|
+
data.id // ✅ Typed as string
|
|
521
|
+
data.invalid // ❌ TypeScript error
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Server Actions (Next.js App Router)
|
|
527
|
+
|
|
528
|
+
Use direct server calls with React Server Components:
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// app/users/page.tsx
|
|
532
|
+
import { api } from '@/igniter.client'
|
|
533
|
+
|
|
534
|
+
export default async function UsersPage() {
|
|
535
|
+
const users = await api.users.list.call()
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div>
|
|
539
|
+
{users.map(user => (
|
|
540
|
+
<div key={user.id}>{user.name}</div>
|
|
541
|
+
))}
|
|
542
|
+
</div>
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Use with Server Actions:
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// app/users/actions.ts
|
|
551
|
+
'use server'
|
|
552
|
+
|
|
553
|
+
import { api } from '@/igniter.client'
|
|
554
|
+
|
|
555
|
+
export async function createUser(formData: FormData) {
|
|
556
|
+
const name = formData.get('name') as string
|
|
557
|
+
const email = formData.get('email') as string
|
|
558
|
+
|
|
559
|
+
return api.users.create.call({
|
|
560
|
+
body: { name, email }
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// app/users/create-form.tsx
|
|
565
|
+
export function CreateUserForm() {
|
|
566
|
+
return (
|
|
567
|
+
<form action={createUser}>
|
|
568
|
+
<input name="name" />
|
|
569
|
+
<input name="email" type="email" />
|
|
570
|
+
<button type="submit">Create User</button>
|
|
571
|
+
</form>
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Combine Server and Client Components:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// app/users/hybrid-page.tsx
|
|
580
|
+
import { api } from '@/igniter.client'
|
|
581
|
+
|
|
582
|
+
// Server Component
|
|
583
|
+
async function UsersList() {
|
|
584
|
+
const users = await api.users.list.call()
|
|
585
|
+
return (
|
|
586
|
+
<div>
|
|
587
|
+
{users.map(user => (
|
|
588
|
+
<div key={user.id}>{user.name}</div>
|
|
589
|
+
))}
|
|
590
|
+
</div>
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Client Component
|
|
595
|
+
'use client'
|
|
596
|
+
function UserCount() {
|
|
597
|
+
const { count } = api.users.count.useQuery()
|
|
598
|
+
return <div>Total Users: {count}</div>
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Main Page Component
|
|
602
|
+
export default function UsersPage() {
|
|
603
|
+
return (
|
|
604
|
+
<div>
|
|
605
|
+
<UserCount />
|
|
606
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
607
|
+
<UsersList />
|
|
608
|
+
</Suspense>
|
|
609
|
+
</div>
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Performance Optimization
|
|
615
|
+
|
|
616
|
+
* Caching Strategy: Configure caching behavior per query
|
|
617
|
+
* Automatic Revalidation: Keep data fresh with smart revalidation
|
|
618
|
+
* Prefetching: Improve perceived performance
|
|
619
|
+
* Optimistic Updates: Provide instant feedback
|
|
620
|
+
* Parallel Queries: Handle multiple requests efficiently
|
|
621
|
+
|
|
622
|
+
## Error Handling and Recovery
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
function UserProfile() {
|
|
626
|
+
const { data, error, retry } = api.users.get.useQuery({
|
|
627
|
+
onError: (error) => {
|
|
628
|
+
console.error('Failed to fetch user:', error)
|
|
629
|
+
},
|
|
630
|
+
retry: 3, // Retry failed requests
|
|
631
|
+
retryDelay: 1000, // Wait 1 second between retries
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
if (error) {
|
|
635
|
+
return (
|
|
636
|
+
<div>
|
|
637
|
+
Error loading profile
|
|
638
|
+
<button onClick={retry}>Try Again</button>
|
|
639
|
+
</div>
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return <div>{/* ... */}</div>
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
## Advanced Usage
|
|
648
|
+
|
|
649
|
+
### Server-Side Rendering
|
|
650
|
+
|
|
651
|
+
Use direct server calls with React Server Components:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
// app/users/page.tsx
|
|
655
|
+
import { api } from '@/igniter.client'
|
|
656
|
+
|
|
657
|
+
export default async function UsersPage() {
|
|
658
|
+
const users = await api.users.list.call()
|
|
659
|
+
|
|
660
|
+
return (
|
|
661
|
+
<div>
|
|
662
|
+
{users.map(user => (
|
|
663
|
+
<div key={user.id}>{user.name}</div>
|
|
664
|
+
))}
|
|
665
|
+
</div>
|
|
666
|
+
)
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Testing
|
|
671
|
+
|
|
672
|
+
Igniter is designed with testability in mind:
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
import { router } from '@/igniter.router'
|
|
676
|
+
|
|
677
|
+
describe('User API', () => {
|
|
678
|
+
it('should create a user', async () => {
|
|
679
|
+
const result = await router.users.create.call({
|
|
680
|
+
body: {
|
|
681
|
+
name: 'Test User',
|
|
682
|
+
email: 'test@example.com'
|
|
683
|
+
}
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
expect(result.status).toBe(201)
|
|
687
|
+
expect(result.data).toHaveProperty('id')
|
|
688
|
+
})
|
|
689
|
+
})
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
## Security Best Practices
|
|
693
|
+
|
|
694
|
+
* Use procedures for authentication and authorization
|
|
695
|
+
* Implement rate limiting
|
|
696
|
+
* Validate all inputs
|
|
697
|
+
* Use secure cookie options
|
|
698
|
+
* Handle errors safely
|
|
699
|
+
* Implement CORS properly
|
|
700
|
+
|
|
701
|
+
## Performance Monitoring
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
import { igniter } from '@/igniter'
|
|
705
|
+
|
|
706
|
+
const monitor = igniter.procedure({
|
|
707
|
+
handler: async (_, ctx) => {
|
|
708
|
+
const start = performance.now()
|
|
709
|
+
|
|
710
|
+
// Wait for the next middleware/handler
|
|
711
|
+
const result = await ctx.next()
|
|
712
|
+
|
|
713
|
+
const duration = performance.now() - start
|
|
714
|
+
console.log(`${ctx.request.method} ${ctx.request.path} - ${duration}ms`)
|
|
715
|
+
|
|
716
|
+
return result
|
|
717
|
+
}
|
|
718
|
+
})
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
## TypeScript Configuration
|
|
722
|
+
|
|
723
|
+
Recommended tsconfig.json settings:
|
|
724
|
+
|
|
725
|
+
```json
|
|
726
|
+
{
|
|
727
|
+
"compilerOptions": {
|
|
728
|
+
"target": "ES2020",
|
|
729
|
+
"lib": ["ES2020"],
|
|
730
|
+
"module": "CommonJS",
|
|
731
|
+
"strict": true,
|
|
732
|
+
"esModuleInterop": true,
|
|
733
|
+
"skipLibCheck": true,
|
|
734
|
+
"forceConsistentCasingInFileNames": true
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
## Contributing
|
|
740
|
+
|
|
741
|
+
We welcome contributions! Please see our [contributing guidelines](https://igniter.felipebarcelospro.github.io/docs/contributing) for details.
|
|
742
|
+
|
|
743
|
+
## Support and Community
|
|
744
|
+
|
|
745
|
+
* 📚 [Documentation](https://igniter.felipebarcelospro.github.io/docs/)
|
|
746
|
+
* 💬 [Discord Community](https://discord.gg/f9366666)
|
|
747
|
+
* 🐛 [Issue Tracker](https://github.com/felipebarcelospro/igniter/issues)
|
|
748
|
+
* 🤝 [Contributing Guidelines](https://igniter.felipebarcelospro.github.io/docs/contributing)
|
|
749
|
+
|
|
750
|
+
## License
|
|
751
|
+
|
|
752
|
+
MIT License - see the [LICENSE](https://github.com/felipebarcelospro/igniter/blob/main/LICENSE) file for details.
|
|
753
|
+
```
|