routesync 1.0.26 → 1.0.27

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.
Files changed (2) hide show
  1. package/README.md +245 -206
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Stop writing API clients by hand.
4
4
 
5
- RouteSync syncs your Laravel (or PHP) routes to a fully-typed frontend SDK — complete with TypeScript types, a camelCase mapper, and React/Vue Query hooks. One command. Zero boilerplate.
5
+ RouteSync syncs your Laravel (or PHP) routes to a fully-typed frontend SDK — complete with TypeScript types, a camelCase mapper, React/Vue Query hooks, and Next.js Server Actions. One command. Zero boilerplate.
6
6
 
7
7
  ---
8
8
 
@@ -13,21 +13,26 @@ You've been there. The backend ships a new endpoint. You update the route, write
13
13
  RouteSync does all of that. You point it at `routes/api.php` and it generates the whole thing.
14
14
 
15
15
  ```bash
16
- npx routesync sync --input routes/api.php --output src/api --baseURL https://api.myapp.com/api
17
- ```
16
+ # Step 1 in your Laravel folder
17
+ npx routesync scan --input routes/api.php --models
18
18
 
19
+ # Step 2 — in your frontend folder
20
+ npx routesync generate --manifest routesync.manifest.json --output src/api --next-actions --zod
19
21
  ```
20
- routesync sync
21
-
22
- ✔ Scanning Laravel routes (24 routes)
23
- ✔ Generating types
24
- ✔ Generating SDK
25
- ✔ Generating hooks
26
22
 
27
- Sync complete! → src/api
23
+ ```
24
+ ✔ Found 35 routes, 19 models → routesync.manifest.json
25
+
26
+ ✔ SDK generated → src/api
27
+ api.ts Typed API client
28
+ types.ts TypeScript interfaces (from real DB columns)
29
+ hooks.ts React Query hooks
30
+ actions.ts Next.js Server Actions
31
+ schemas.ts Zod validation schemas
32
+ index.ts Barrel export
28
33
  ```
29
34
 
30
- That's it. Your frontend has a typed client, response types, and ready-to-use hooks — and you didn't write any of it.
35
+ That's it. Your frontend has a typed client, real DB-derived types, Zod schemas, and ready-to-use hooks — and you didn't write any of it.
31
36
 
32
37
  ---
33
38
 
@@ -35,10 +40,10 @@ That's it. Your frontend has a typed client, response types, and ready-to-use ho
35
40
 
36
41
  | Package | What it does |
37
42
  |---|---|
38
- | `@routesync/sdk` | The core developer API. `defineApi`, `createService`, `resource`. |
43
+ | `@routesync/sdk` | The core developer API. `defineApi`, `endpoint`, `resource`, `createService`. |
39
44
  | `@routesync/core` | HTTP client engine, auth, path resolution, error handling. |
40
- | `@routesync/cli` | Scans routes, generates types + SDK + hooks. |
41
- | `@routesync/react` | React Query hooks factory built on `createService`. |
45
+ | `@routesync/cli` | Scans routes + models, generates types + SDK + hooks + actions. |
46
+ | `@routesync/react` | `useApiQuery` / `useApiMutation` hooks built on TanStack Query. |
42
47
  | `@routesync/vue` | Vue Query composables, same idea. |
43
48
 
44
49
  ---
@@ -46,304 +51,338 @@ That's it. Your frontend has a typed client, response types, and ready-to-use ho
46
51
  ## Install
47
52
 
48
53
  ```bash
49
- # SDK only (manual route definitions)
50
- npm install @routesync/sdk
54
+ # SDK + React hooks
55
+ npm install routesync @tanstack/react-query
51
56
 
52
- # With React hooks
53
- npm install @routesync/react @tanstack/react-query
57
+ # Vue composables
58
+ npm install routesync @tanstack/vue-query
54
59
 
55
- # With Vue composables
56
- npm install @routesync/vue @tanstack/vue-query
57
-
58
- # CLI (route scanner + code generator)
59
- npm install -g @routesync/cli
60
+ # With Zod validation
61
+ npm install routesync zod
60
62
  ```
61
63
 
62
64
  ---
63
65
 
64
- ## Usage
65
-
66
- ### Option A — Define routes manually
66
+ ## Full Workflow (Laravel + Next.js)
67
67
 
68
- Good if you don't have a Laravel backend, or want full control.
68
+ ### 1. Scan routes & models
69
69
 
70
- ```ts
71
- import { defineApi } from '@routesync/sdk'
72
-
73
- export const api = defineApi(
74
- {
75
- auth: {
76
- login: { method: 'POST', path: '/login' },
77
- logout: { method: 'POST', path: '/logout', auth: true },
78
- },
79
- products: {
80
- list: { method: 'GET', path: '/products' },
81
- detail: { method: 'GET', path: '/products/:id' },
82
- create: { method: 'POST', path: '/products', auth: true },
83
- },
84
- },
85
- { baseURL: 'https://api.myapp.com/api' }
86
- )
87
- ```
88
-
89
- Then call it:
90
-
91
- ```ts
92
- // GET /products?page=1&search=kaos
93
- await api.products.list({ query: { page: 1, search: 'kaos' } })
70
+ Run this from your **Laravel project root**:
94
71
 
95
- // GET /products/42
96
- await api.products.detail({ params: { id: 42 } })
97
-
98
- // POST /products
99
- await api.products.create({ body: { name: 'Kaos Polos', price: 89000 } })
72
+ ```bash
73
+ npx routesync scan --input routes/api.php --models
100
74
  ```
101
75
 
102
- Path params resolve automatically. `:id` the value you pass. No string concatenation, no manual URL building.
103
-
104
- ---
76
+ | Option | Default | Description |
77
+ |---|---|---|
78
+ | `--input` | `routes/api.php` | Path to your Laravel routes file |
79
+ | `--output` | `routesync.manifest.json` | Where to save the manifest |
80
+ | `--baseURL` | `http://localhost/api` | API base URL |
81
+ | `--models` | off | Also scan `app/Models/` for real DB column types |
82
+
83
+ > **`--models` requirement:** PHP must be available in your terminal and your database must be accessible (`.env` configured). The scanner runs a temporary PHP script via Laravel's bootstrap to read `Schema::getColumns()` from each Eloquent model.
84
+
85
+ > **Important — manifest location:** The manifest is saved in whichever folder you run `scan` from. If you run it from your Laravel root, copy the manifest to your frontend folder before running `generate`:
86
+ >
87
+ > ```bash
88
+ > # Windows PowerShell
89
+ > copy ..\routesync.manifest.json .
90
+ >
91
+ > # macOS / Linux
92
+ > cp ../backend/routesync.manifest.json .
93
+ > ```
105
94
 
106
- ### Option B Auto-generate from Laravel
95
+ ### 2. Generate the SDK
107
96
 
108
- Point the CLI at your routes file and let it generate everything.
97
+ Run this from your **frontend project root**:
109
98
 
110
99
  ```bash
111
- npx routesync sync \
112
- --input ../laravel-backend/routes/api.php \
100
+ npx routesync generate \
101
+ --manifest routesync.manifest.json \
113
102
  --output src/api \
114
- --baseURL https://api.myapp.com/api
103
+ --next-actions \
104
+ --zod
115
105
  ```
116
106
 
117
- Generated output:
107
+ | Option | Default | Description |
108
+ |---|---|---|
109
+ | `--manifest` | `routesync.manifest.json` | Path to manifest from step 1 |
110
+ | `--output` | `src/api` | Output folder |
111
+ | `--next-actions` | off | Generate `actions.ts` (Next.js Server Actions) |
112
+ | `--zod` | off | Generate `schemas.ts` (Zod validation) |
113
+ | `--no-hooks` | off | Skip generating `hooks.ts` |
114
+ | `--msw` | off | Generate MSW mock handlers |
115
+
116
+ > **Windows PowerShell note:** Do not use backslash `\` for line continuation — PowerShell treats it differently. Run the command on a single line:
117
+ >
118
+ > ```powershell
119
+ > npx routesync generate --manifest routesync.manifest.json --output src/api --next-actions --zod
120
+ > ```
121
+
122
+ ### Generated files
118
123
 
119
124
  ```
120
125
  src/api/
121
- ├── api.ts typed API client
122
- ├── types.ts ← TypeScript interfaces
123
- └── hooks.ts React Query hooks
124
- ```
125
-
126
- To keep it in sync during development:
127
-
128
- ```bash
129
- npx routesync watch --input routes/api.php --output src/api
126
+ ├── api.ts defineApi() with all endpoints + Contract types
127
+ ├── types.ts ← TypeScript interfaces (real DB columns when --models used)
128
+ ├── hooks.ts useApiQuery / useApiMutation per endpoint
129
+ ├── actions.ts ← Next.js Server Actions (--next-actions)
130
+ ├── schemas.ts ← Zod schemas from FormRequest rules (--zod)
131
+ ├── index.ts ← Barrel re-export
132
+ └── core/
133
+ └── models.ts ← Raw Eloquent model interfaces (when --models used)
130
134
  ```
131
135
 
132
- ---
136
+ ### 3. Initialize the client
133
137
 
134
- ### Authentication
138
+ Call `createClient` once at app startup (e.g. in your layout or provider):
135
139
 
136
140
  ```ts
137
- import { createClient } from '@routesync/sdk'
141
+ // src/lib/api-client.ts
142
+ import { createClient } from 'routesync'
138
143
 
139
- const { setToken, clearToken } = createClient({
140
- baseURL: 'https://api.myapp.com/api'
144
+ createClient({
145
+ baseURL: process.env.NEXT_PUBLIC_API_URL!, // e.g. http://localhost:8000/api
146
+ withCredentials: true,
141
147
  })
142
-
143
- // After login:
144
- setToken(response.data.token)
145
-
146
- // On logout:
147
- clearToken()
148
148
  ```
149
149
 
150
- Any route with `auth: true` in its definition automatically gets `Authorization: Bearer TOKEN` injected. You don't think about it again.
150
+ ### 4. Use in components
151
151
 
152
- ---
153
-
154
- ### React Query Hooks
155
-
156
- ```ts
157
- import { createService } from '@routesync/sdk'
158
- import { createHooks } from '@routesync/react'
159
- import { z } from 'zod'
160
-
161
- const productSchema = z.object({
162
- id: z.number(),
163
- product_name: z.string(),
164
- price: z.number(),
165
- })
152
+ ```tsx
153
+ import { useApiQuery, useApiMutation } from 'routesync/react'
154
+ import { api } from '@/api/api'
166
155
 
167
- const productService = createService(client, '/products', {
168
- entitySchema: productSchema,
169
- listSchema: z.array(productSchema),
170
- })
156
+ // GET fetch data
157
+ function ProdukList() {
158
+ const { data, isLoading } = useApiQuery(api.produk.get, {
159
+ query: { page: 1, search: 'kaos' }
160
+ })
171
161
 
172
- const { useList, useDetail, useCreate } = createHooks(productService, 'products')
173
- ```
162
+ if (isLoading) return <p>Loading...</p>
163
+ return <ul>{data?.map(p => <li key={p.id}>{p.nama}</li>)}</ul>
164
+ }
174
165
 
175
- In your component:
166
+ // GET with path params
167
+ function ProdukDetail({ id }: { id: string }) {
168
+ const { data } = useApiQuery(api.produk.getId, { params: { id } })
169
+ return <div>{data?.nama}</div>
170
+ }
176
171
 
177
- ```tsx
178
- function ProductList() {
179
- const { data, isLoading } = useList({ page: 1 })
180
- const mutation = useCreate()
172
+ // POST / mutation
173
+ function AddToCart({ produkItemId }: { produkItemId: string }) {
174
+ const mutation = useApiMutation(api.cart.postItems)
181
175
 
182
176
  return (
183
- <>
184
- {data?.map(p => <div key={p.id}>{p.productName}</div>)}
185
- <button onClick={() => mutation.mutate({ productName: 'Kaos Baru' })}>
186
- Add Product
187
- </button>
188
- </>
177
+ <button onClick={() => mutation.mutate({ body: { produk_item_id: produkItemId, qty: 1 } })}>
178
+ Tambah ke Keranjang
179
+ </button>
189
180
  )
190
181
  }
191
182
  ```
192
183
 
193
- Backend returns `product_name`. Your component sees `productName`. The mapper runs automatically in both directions — request payload goes snake_case before it hits the server, response comes back camelCase before it hits your component.
184
+ ### 5. Use Server Actions (Next.js)
194
185
 
195
- ---
186
+ ```ts
187
+ // In a Server Component or form action
188
+ import { produkGetAction, cartPostItemsAction } from '@/api/actions'
196
189
 
197
- ### Vue Composables
190
+ // GET — no params needed
191
+ const result = await produkGetAction({ query: { page: 1 } })
192
+ if (result.success) console.log(result.data)
198
193
 
199
- ```ts
200
- import { createVueComposables } from '@routesync/vue'
194
+ // POST — with body
195
+ const result = await cartPostItemsAction({ body: { produk_item_id: '5', qty: 1 } })
201
196
 
202
- const { useList, useCreate } = createVueComposables(productService, 'products')
197
+ // GET with path params params are required
198
+ const result = await produkGetIdAction({ params: { id: '42' } })
203
199
  ```
204
200
 
205
- Same API, Vue-native.
201
+ ---
202
+
203
+ ## Data Transformation
204
+
205
+ RouteSync handles all data mapping automatically:
206
+
207
+ | Direction | What happens | Where |
208
+ |---|---|---|
209
+ | Response (backend → frontend) | `snake_case` → `camelCase` keys | `HttpClient` interceptor |
210
+ | Request (frontend → backend) | `camelCase` → `snake_case` keys | `HttpClient` interceptor |
211
+ | Response unwrap | `{ data: T, message, meta }` → `T` | `HttpClient` `.get()` / `.post()` etc. |
212
+ | Zod validation | Parse + validate response shape | Per-endpoint `responseSchema` |
213
+
214
+ No extra config needed. `product_name` from Laravel becomes `productName` in your component automatically.
206
215
 
207
216
  ---
208
217
 
209
- ### Resource groups
218
+ ## Manual Route Definitions
210
219
 
211
- When you have multiple endpoints on the same resource, `resource()` lets you set shared defaults (auth, headers) once:
220
+ If you don't have a Laravel backend, define routes manually:
212
221
 
213
222
  ```ts
214
- import { defineApi, resource } from '@routesync/sdk'
223
+ import { defineApi, endpoint, resource } from 'routesync'
224
+
225
+ createClient({ baseURL: 'https://api.myapp.com/api' })
215
226
 
216
- const api = defineApi({
227
+ export const api = defineApi({
228
+ // Basic endpoint
229
+ auth: {
230
+ login: endpoint<{ token: string }>({ method: 'POST', path: '/login' }),
231
+ logout: endpoint({ method: 'POST', path: '/logout', auth: true }),
232
+ },
233
+
234
+ // With path params
235
+ produk: {
236
+ list: endpoint<ProdukItem[]>({ method: 'GET', path: '/produk' }),
237
+ detail: endpoint<ProdukItem, { id: string }>({ method: 'GET', path: '/produk/:id' }),
238
+ create: endpoint<ProdukItem, unknown, CreateProdukBody>({
239
+ method: 'POST', path: '/produk', auth: true
240
+ }),
241
+ },
242
+
243
+ // resource() — shared auth/headers for a group
217
244
  cart: resource({
218
- auth: true, // applies to all endpoints below
245
+ auth: true,
219
246
  endpoints: {
220
- list: { method: 'GET', path: '/cart/items' },
221
- show: { method: 'GET', path: '/cart/items/:id' },
222
- add: { method: 'POST', path: '/cart/items' },
223
- update: { method: 'PATCH', path: '/cart/items/:id' },
224
- remove: { method: 'DELETE', path: '/cart/items/:id' },
225
- checkout: { method: 'POST', path: '/cart/checkout' },
247
+ get: { method: 'GET', path: '/cart' },
248
+ add: { method: 'POST', path: '/cart/items' },
249
+ update: { method: 'PATCH', path: '/cart/items/:id' },
250
+ remove: { method: 'DELETE', path: '/cart/items/:id' },
226
251
  }
227
252
  })
228
- }, config)
253
+ })
229
254
  ```
230
255
 
231
- ---
256
+ `endpoint<TResponse, TParams, TBody>` — generic order:
257
+ 1. **TResponse** — shape returned by the backend
258
+ 2. **TParams** — path params like `{ id: string }`
259
+ 3. **TBody** — POST/PUT/PATCH body shape
232
260
 
233
- ### Schema validation + custom mapping
261
+ ---
234
262
 
235
- You can attach Zod schemas to any endpoint for validation, and a mapper for custom transformations:
263
+ ## Authentication
236
264
 
237
265
  ```ts
238
- import { z } from 'zod'
239
-
240
- const api = defineApi({
241
- products: {
242
- list: {
243
- method: 'GET',
244
- path: '/products',
245
- schema: z.array(productSchema),
246
- mapper: (data) => data.map(normalizeProduct),
247
- }
248
- }
249
- }, config)
266
+ import { createClient } from 'routesync'
267
+
268
+ const client = createClient({ baseURL: 'https://api.myapp.com/api' })
269
+
270
+ // After login — set token
271
+ client.setToken(response.token)
272
+
273
+ // On logout — clear token
274
+ client.removeToken()
250
275
  ```
251
276
 
252
- > **Note on Laravel Auto-generation:** When using `routesync sync` or `routesync scan` with the `--zod` flag, RouteSync uses PHP Reflection to automatically generate Zod schemas based on your backend validation rules.
253
- >
254
- > **Important:** To ensure your schemas are detected automatically, you **must use Laravel `FormRequest` classes**. Inline `$request->validate([...])` calls inside Controller methods cannot be reliably extracted.
255
- >
256
- > ```php
257
- > // ✅ DO THIS: RouteSync will generate Zod schemas automatically
258
- > public function store(StoreProductRequest $request)
259
- >
260
- > // ❌ AVOID THIS: Validation rules will be ignored
261
- > public function store(Request $request) {
262
- > $request->validate([...]);
263
- > }
264
- > ```
277
+ Any endpoint with `auth: true` automatically gets `Authorization: Bearer TOKEN` injected. For Next.js Server Actions, the generated `actions.ts` reads the token from cookies automatically via `getAuthHeaders()`.
265
278
 
266
279
  ---
267
280
 
268
- ### Auto-generate TanStack hooks from defineApi
281
+ ## React Query Hooks
269
282
 
270
- If you're using `defineApi` (instead of `createService`), you can generate hooks for the whole API at once:
283
+ ### Auto-generated hooks (from CLI)
271
284
 
272
285
  ```ts
273
- import { defineApi } from '@routesync/sdk'
274
- import { generateHooks } from '@routesync/sdk'
286
+ import { useApiQuery, useApiMutation } from 'routesync/react'
287
+ import { api } from '@/api/api'
275
288
 
276
- const api = defineApi({ products: { list: ..., create: ... } }, config)
277
- const { useProductsList, useProductsCreate } = generateHooks(api)
289
+ // GET
290
+ const { data, isLoading } = useApiQuery(api.orders.get)
291
+
292
+ // GET with params + query
293
+ const { data } = useApiQuery(api.orders.getId, { params: { id: '1' } })
294
+
295
+ // Mutation
296
+ const mutation = useApiMutation(api.cart.postItems)
297
+ mutation.mutate({ body: { produk_item_id: '5', qty: 1 } })
278
298
  ```
279
299
 
280
- Hook names are derived from group + action: `products.list` → `useProductsList`. GET/DELETE become `useQuery`, everything else becomes `useMutation`. Mutations auto-invalidate their group's queries on success.
300
+ ### Generate hooks for entire api at once
281
301
 
282
- ---
302
+ ```ts
303
+ import { generateHooks } from 'routesync'
283
304
 
284
- ### Multiple backends
305
+ const hooks = generateHooks(api)
306
+ const { useOrdersGet, useCartPostItems } = hooks
307
+ // GET/DELETE → useQuery, everything else → useMutation
308
+ ```
285
309
 
286
- RouteSync isn't Laravel-only. Anything with a REST API works:
310
+ ### createHooks per group
287
311
 
288
312
  ```ts
289
- const laravel = defineApi(routes, { baseURL: 'https://laravel.myapp.com/api' })
290
- const express = defineApi(routes, { baseURL: 'https://express.myapp.com/api' })
291
- const php = defineApi(routes, { baseURL: 'https://php.myapp.com/api' })
313
+ import { createHooks } from 'routesync/react'
314
+
315
+ const cartHooks = createHooks(api.cart)
316
+ const { usePostItems, usePatchItemsProdukItemId } = cartHooks
292
317
  ```
293
318
 
294
- The CLI also supports OpenAPI and native PHP alongside Laravel.
319
+ ---
320
+
321
+ ## Zod Schema Validation
322
+
323
+ When using `--zod` with `routesync generate`, `schemas.ts` is generated from your Laravel `FormRequest` rules.
324
+
325
+ > **Requirement:** You must use Laravel `FormRequest` classes for rules to be detected:
326
+ >
327
+ > ```php
328
+ > // ✅ RouteSync will auto-generate Zod schema
329
+ > public function store(StoreProductRequest $request) { ... }
330
+ >
331
+ > // ❌ Rules will not be detected
332
+ > public function store(Request $request) {
333
+ > $request->validate([...]);
334
+ > }
335
+ > ```
295
336
 
296
337
  ---
297
338
 
298
- ## CLI reference
339
+ ## CLI Reference
299
340
 
300
341
  ```bash
301
- # Scan routes → route manifest JSON
342
+ # Scan Laravel routes only
302
343
  npx routesync scan --input routes/api.php
303
344
 
345
+ # Scan routes + Eloquent models (recommended)
346
+ npx routesync scan --input routes/api.php --models
347
+
304
348
  # Generate SDK from manifest
305
- npx routesync generate --manifest routesync.json --output src/api
349
+ npx routesync generate --manifest routesync.manifest.json --output src/api
306
350
 
307
- # Scan + generate in one step
308
- npx routesync sync --input routes/api.php --output src/api --baseURL http://localhost/api
351
+ # Generate everything
352
+ npx routesync generate --manifest routesync.manifest.json --output src/api --next-actions --zod
309
353
 
310
- # Watch mode — auto-syncs on file change
354
+ # Watch mode — auto re-generates on route file change
311
355
  npx routesync watch --input routes/api.php --output src/api
312
356
  ```
313
357
 
314
358
  ---
315
359
 
316
- ## How it works
360
+ ## How It Works
317
361
 
318
362
  ```
319
- Laravel routes/api.php
320
-
321
- routesync sync
322
-
323
- Route manifest (JSON)
324
-
325
- SDK + types + hooks
326
-
327
- React / Vue / Next.js / anywhere
363
+ routes/api.php + app/Models/
364
+ routesync scan --models
365
+ routesync.manifest.json
366
+ routesync generate
367
+ src/api/
368
+ ├── api.ts ← defineApi + endpoints
369
+ ├── types.ts ← interfaces from DB columns
370
+ ├── hooks.ts ← TanStack Query hooks
371
+ ├── actions.ts ← Next.js Server Actions
372
+ └── schemas.ts ← Zod schemas
373
+
374
+ React / Vue / Next.js
328
375
  ```
329
376
 
330
- The CLI parses your route file, builds a language-agnostic manifest, then feeds it to the generators. Each generator is independent — you can swap them out or write your own on top of the manifest format.
331
-
332
- ---
333
-
334
- ## Roadmap
335
-
336
- - [ ] OpenAPI export from the manifest
337
- - [ ] SWR adapter alongside React Query
338
- - [ ] Solid.js composables
339
- - [ ] First-class Next.js Server Actions integration
340
- - [ ] VSCode extension — IntelliSense on `api.` without importing
377
+ The CLI parses your route file via PHP reflection (using Laravel's own bootstrap), builds a language-agnostic manifest, then feeds it to independent generators. Each generator can be used standalone.
341
378
 
342
379
  ---
343
380
 
344
381
  ## Requirements
345
382
 
346
- - Node.js >= 18
383
+ - Node.js >= 20
384
+ - PHP available in PATH (for `scan --models`)
385
+ - Laravel project with database accessible (for `scan --models`)
347
386
 
348
387
  ## License
349
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routesync",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "Laravel routes to typed frontend SDKs.",
5
5
  "main": "./dist/sdk.js",
6
6
  "module": "./dist/sdk.mjs",