routesync 1.0.0 → 1.0.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/README.md CHANGED
@@ -1,289 +1,336 @@
1
1
  # RouteSync
2
2
 
3
- > Laravel routes to typed frontend SDKs.
3
+ > Stop writing API clients by hand.
4
4
 
5
- Modern API SDK ecosystem for Laravel and PHP with typed clients, hooks, route syncing, and code generation.
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.
6
+
7
+ ---
8
+
9
+ ## Why
10
+
11
+ You've been there. The backend ships a new endpoint. You update the route, write a fetch wrapper, add the TypeScript type, hook it into React Query, map `snake_case` to `camelCase`, and fifteen minutes later you're still not done.
12
+
13
+ RouteSync does all of that. You point it at `routes/api.php` and it generates the whole thing.
14
+
15
+ ```bash
16
+ npx routesync sync --input routes/api.php --output src/api --baseURL https://api.myapp.com/api
17
+ ```
18
+
19
+ ```
20
+ routesync sync
21
+
22
+ ✔ Scanning Laravel routes (24 routes)
23
+ ✔ Generating types
24
+ ✔ Generating SDK
25
+ ✔ Generating hooks
26
+
27
+ Sync complete! → src/api
28
+ ```
29
+
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.
6
31
 
7
32
  ---
8
33
 
9
34
  ## Packages
10
35
 
11
- | Package | Description |
36
+ | Package | What it does |
12
37
  |---|---|
13
- | `@routesync/core` | HTTP client engine, auth, routing, error handling |
14
- | `@routesync/sdk` | Developer-facing API: `defineApi`, `createService` |
15
- | `@routesync/cli` | CLI: scan routes, generate SDK, types, hooks |
16
- | `@routesync/react` | React Query hooks factory |
17
- | `@routesync/vue` | Vue Query composables factory |
38
+ | `@routesync/sdk` | The core developer API. `defineApi`, `createService`, `resource`. |
39
+ | `@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`. |
42
+ | `@routesync/vue` | Vue Query composables, same idea. |
18
43
 
19
44
  ---
20
45
 
21
- ## Quick Start
22
-
23
- ### 1. Install
46
+ ## Install
24
47
 
25
48
  ```bash
49
+ # SDK only (manual route definitions)
26
50
  npm install @routesync/sdk
51
+
52
+ # With React hooks
53
+ npm install @routesync/react @tanstack/react-query
54
+
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
27
60
  ```
28
61
 
29
- ### 2. Define your API
62
+ ---
63
+
64
+ ## Usage
65
+
66
+ ### Option A — Define routes manually
67
+
68
+ Good if you don't have a Laravel backend, or want full control.
30
69
 
31
70
  ```ts
32
- import { defineApi, resource } from '@routesync/sdk'
71
+ import { defineApi } from '@routesync/sdk'
33
72
 
34
- // CartSchema, CartMapper, CheckoutSchema, and CheckoutMapper are your own
35
- // endpoint-level contracts.
36
73
  export const api = defineApi(
37
74
  {
38
75
  auth: {
39
76
  login: { method: 'POST', path: '/login' },
40
- logout: { method: 'POST', path: '/logout', auth: true }
41
- },
42
- produk: {
43
- list: { method: 'GET', path: '/produk' },
44
- detail: { method: 'GET', path: '/produk/:id' }
77
+ logout: { method: 'POST', path: '/logout', auth: true },
45
78
  },
46
- cart: {
47
- addItem: { method: 'POST', path: '/cart/items', auth: true },
48
- removeItem: { method: 'DELETE', path: '/cart/items/:produkItemId', auth: true }
79
+ products: {
80
+ list: { method: 'GET', path: '/products' },
81
+ detail: { method: 'GET', path: '/products/:id' },
82
+ create: { method: 'POST', path: '/products', auth: true },
49
83
  },
50
- cartItems: resource({
51
- path: '/cart/items',
52
- endpoints: {
53
- list: {
54
- method: 'GET',
55
- schema: CartSchema.list,
56
- mapper: CartMapper.list
57
- },
58
- show: {
59
- method: 'GET',
60
- path: '/:id',
61
- schema: CartSchema.show,
62
- mapper: CartMapper.show
63
- },
64
- create: {
65
- method: 'POST',
66
- schema: CartSchema.create,
67
- mapper: CartMapper.create
68
- },
69
- update: {
70
- method: 'PATCH',
71
- path: '/:id',
72
- schema: CartSchema.update,
73
- mapper: CartMapper.update
74
- },
75
- checkout: {
76
- method: 'POST',
77
- path: '/checkout',
78
- schema: CheckoutSchema.create,
79
- mapper: CheckoutMapper.create
80
- }
81
- }
82
- }
83
84
  },
84
- {
85
- baseURL: 'http://localhost:8000/api'
86
- }
85
+ { baseURL: 'https://api.myapp.com/api' }
87
86
  )
88
87
  ```
89
88
 
90
- ### 3. Use it
89
+ Then call it:
91
90
 
92
91
  ```ts
93
- // Login
94
- await api.auth.login({ body: { email, password } })
95
-
96
- // Get product list
97
- await api.produk.list({ query: { page: 1, search: 'kaos' } })
98
-
99
- // Get product detail (auto path param)
100
- await api.produk.detail({ params: { id: 10 } })
101
- // → GET /produk/10
92
+ // GET /products?page=1&search=kaos
93
+ await api.products.list({ query: { page: 1, search: 'kaos' } })
102
94
 
103
- // Add to cart
104
- await api.cart.addItem({ body: { produk_id: 1, qty: 2 } })
95
+ // GET /products/42
96
+ await api.products.detail({ params: { id: 42 } })
105
97
 
106
- // Remove from cart
107
- await api.cart.removeItem({ params: { produkItemId: 5 } })
108
- // → DELETE /cart/items/5
98
+ // POST /products
99
+ await api.products.create({ body: { name: 'Kaos Polos', price: 89000 } })
109
100
  ```
110
101
 
111
- ---
112
-
113
- ## CLI: Auto-generate from Laravel Routes
102
+ Path params resolve automatically. `:id` → the value you pass. No string concatenation, no manual URL building.
114
103
 
115
- ### Install CLI
104
+ ---
116
105
 
117
- ```bash
118
- npm install -g @routesync/cli
119
- ```
106
+ ### Option B — Auto-generate from Laravel
120
107
 
121
- ### Sync in one command
108
+ Point the CLI at your routes file and let it generate everything.
122
109
 
123
110
  ```bash
124
111
  npx routesync sync \
125
- --input routes/api.php \
112
+ --input ../laravel-backend/routes/api.php \
126
113
  --output src/api \
127
114
  --baseURL https://api.myapp.com/api
128
115
  ```
129
116
 
130
- Output:
131
-
132
- ```
133
- routesync sync
134
-
135
- ✔ Scanning Laravel routes (24 routes)
136
- ✔ Generating types
137
- ✔ Generating SDK
138
- ✔ Generating hooks
139
-
140
- Sync complete!
141
-
142
- Output: src/api
143
- ```
144
-
145
- Generated files:
117
+ Generated output:
146
118
 
147
119
  ```
148
120
  src/api/
149
- ├── api.ts Typed API client
150
- ├── types.ts Response/request types
151
- └── hooks.ts ← React Query hooks
121
+ ├── api.ts typed API client
122
+ ├── types.ts TypeScript interfaces
123
+ └── hooks.ts ← React Query hooks
152
124
  ```
153
125
 
154
- ### Available Commands
126
+ To keep it in sync during development:
155
127
 
156
128
  ```bash
157
- npx routesync scan # Scan routes manifest JSON
158
- npx routesync generate # Generate SDK from manifest
159
- npx routesync sync # Scan + generate in one step
160
- npx routesync watch # Watch and auto-sync on changes
129
+ npx routesync watch --input routes/api.php --output src/api
161
130
  ```
162
131
 
163
132
  ---
164
133
 
165
- ## React Query Hooks
134
+ ### Authentication
166
135
 
167
- ```bash
168
- npm install @routesync/react
136
+ ```ts
137
+ import { createClient } from '@routesync/sdk'
138
+
139
+ const { setToken, clearToken } = createClient({
140
+ baseURL: 'https://api.myapp.com/api'
141
+ })
142
+
143
+ // After login:
144
+ setToken(response.data.token)
145
+
146
+ // On logout:
147
+ clearToken()
169
148
  ```
170
149
 
171
- ```ts
172
- import { z } from 'zod'
173
- import { createHooks } from '@routesync/react'
174
- import { createService } from '@routesync/sdk'
175
- import type { CamelCasedPropertiesDeep } from '@routesync/sdk'
150
+ Any route with `auth: true` in its definition automatically gets `Authorization: Bearer TOKEN` injected. You don't think about it again.
176
151
 
177
- type BackendProduct = {
178
- id: number
179
- product_name: string
180
- created_at: string
181
- }
152
+ ---
182
153
 
183
- type Product = CamelCasedPropertiesDeep<BackendProduct>
154
+ ### React Query Hooks
184
155
 
185
- type ProductInput = {
186
- productName: string
187
- }
156
+ ```ts
157
+ import { createService } from '@routesync/sdk'
158
+ import { createHooks } from '@routesync/react'
159
+ import { z } from 'zod'
188
160
 
189
- const backendProductSchema = z.object({
161
+ const productSchema = z.object({
190
162
  id: z.number(),
191
163
  product_name: z.string(),
192
- created_at: z.string()
164
+ price: z.number(),
193
165
  })
194
166
 
195
- const productInputSchema = z.object({
196
- productName: z.string().min(1)
167
+ const productService = createService(client, '/products', {
168
+ entitySchema: productSchema,
169
+ listSchema: z.array(productSchema),
197
170
  })
198
171
 
199
- const productService = createService<Product, ProductInput>(client, '/produk', {
200
- entitySchema: backendProductSchema,
201
- listSchema: z.array(backendProductSchema),
202
- createSchema: productInputSchema
203
- })
172
+ const { useList, useDetail, useCreate } = createHooks(productService, 'products')
173
+ ```
204
174
 
205
- const { useList, useDetail, useCreate } = createHooks(productService, 'produk')
175
+ In your component:
176
+
177
+ ```tsx
178
+ function ProductList() {
179
+ const { data, isLoading } = useList({ page: 1 })
180
+ const mutation = useCreate()
181
+
182
+ 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
+ </>
189
+ )
190
+ }
191
+ ```
192
+
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.
194
+
195
+ ---
206
196
 
207
- // In component:
208
- const { data, isLoading } = useList({ page: 1 })
209
- const mutation = useCreate()
197
+ ### Vue Composables
210
198
 
211
- // Frontend camelCase payload is validated with Zod, then sent as snake_case.
212
- mutation.mutate({ productName: 'Product Baru' })
199
+ ```ts
200
+ import { createVueComposables } from '@routesync/vue'
213
201
 
214
- // Backend snake_case response becomes frontend camelCase.
215
- data?.[0].productName
202
+ const { useList, useCreate } = createVueComposables(productService, 'products')
216
203
  ```
217
204
 
205
+ Same API, Vue-native.
206
+
218
207
  ---
219
208
 
220
- ## Vue Query Composables
209
+ ### Resource groups
221
210
 
222
- ```bash
223
- npm install @routesync/vue
224
- ```
211
+ When you have multiple endpoints on the same resource, `resource()` lets you set shared defaults (auth, headers) once:
225
212
 
226
213
  ```ts
227
- import { createVueComposables } from '@routesync/vue'
214
+ import { defineApi, resource } from '@routesync/sdk'
228
215
 
229
- const { useList, useCreate } = createVueComposables(productService, 'produk')
216
+ const api = defineApi({
217
+ cart: resource({
218
+ auth: true, // applies to all endpoints below
219
+ 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' },
226
+ }
227
+ })
228
+ }, config)
230
229
  ```
231
230
 
232
231
  ---
233
232
 
234
- ## Authorization
233
+ ### Schema validation + custom mapping
234
+
235
+ You can attach Zod schemas to any endpoint for validation, and a mapper for custom transformations:
235
236
 
236
237
  ```ts
237
- import { createClient } from '@routesync/sdk'
238
+ import { z } from 'zod'
238
239
 
239
- const { setToken, clearToken } = createClient({
240
- baseURL: 'https://api.myapp.com/api'
241
- })
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)
250
+ ```
242
251
 
243
- // After login:
244
- setToken(response.data.token)
252
+ ---
245
253
 
246
- // After logout:
247
- clearToken()
254
+ ### Auto-generate TanStack hooks from defineApi
255
+
256
+ If you're using `defineApi` (instead of `createService`), you can generate hooks for the whole API at once:
257
+
258
+ ```ts
259
+ import { defineApi } from '@routesync/sdk'
260
+ import { generateHooks } from '@routesync/sdk'
261
+
262
+ const api = defineApi({ products: { list: ..., create: ... } }, config)
263
+ const { useProductsList, useProductsCreate } = generateHooks(api)
248
264
  ```
249
265
 
250
- Routes with `auth: true` automatically get `Authorization: Bearer TOKEN` injected.
266
+ 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.
251
267
 
252
268
  ---
253
269
 
254
- ## Multi-Backend Support
270
+ ### Multiple backends
271
+
272
+ RouteSync isn't Laravel-only. Anything with a REST API works:
255
273
 
256
274
  ```ts
257
- // Laravel
258
- const laravel = defineApi(routes, { baseURL: 'https://laravel-app.com/api' })
275
+ const laravel = defineApi(routes, { baseURL: 'https://laravel.myapp.com/api' })
276
+ const express = defineApi(routes, { baseURL: 'https://express.myapp.com/api' })
277
+ const php = defineApi(routes, { baseURL: 'https://php.myapp.com/api' })
278
+ ```
279
+
280
+ The CLI also supports OpenAPI and native PHP alongside Laravel.
281
+
282
+ ---
283
+
284
+ ## CLI reference
285
+
286
+ ```bash
287
+ # Scan routes → route manifest JSON
288
+ npx routesync scan --input routes/api.php
289
+
290
+ # Generate SDK from manifest
291
+ npx routesync generate --manifest routesync.json --output src/api
259
292
 
260
- // CodeIgniter
261
- const ci = defineApi(routes, { baseURL: 'https://ci-app.com/api' })
293
+ # Scan + generate in one step
294
+ npx routesync sync --input routes/api.php --output src/api --baseURL http://localhost/api
262
295
 
263
- // Native PHP
264
- const php = defineApi(routes, { baseURL: 'https://native-php.com/api' })
296
+ # Watch mode — auto-syncs on file change
297
+ npx routesync watch --input routes/api.php --output src/api
265
298
  ```
266
299
 
267
300
  ---
268
301
 
269
- ## Ecosystem Flow
302
+ ## How it works
270
303
 
271
304
  ```
272
- Laravel Backend
273
-
274
- npx routesync sync
275
-
276
- Route Manifest (JSON)
277
-
278
- SDK Generator
279
-
280
- Typed Client + Types + Hooks
281
-
282
- React / Vue / Next.js / Mobile / AI Agent
305
+ Laravel routes/api.php
306
+
307
+ routesync sync
308
+
309
+ Route manifest (JSON)
310
+
311
+ SDK + types + hooks
312
+
313
+ React / Vue / Next.js / anywhere
283
314
  ```
284
315
 
316
+ 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.
317
+
285
318
  ---
286
319
 
320
+ ## Roadmap
321
+
322
+ - [ ] OpenAPI export from the manifest
323
+ - [ ] SWR adapter alongside React Query
324
+ - [ ] Solid.js composables
325
+ - [ ] First-class Next.js Server Actions integration
326
+ - [ ] VSCode extension — IntelliSense on `api.` without importing
327
+
328
+ ---
329
+
330
+ ## Requirements
331
+
332
+ - Node.js >= 18
333
+
287
334
  ## License
288
335
 
289
- MIT
336
+ MIT