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 +224 -177
- package/dist/cli.js +8996 -275
- package/dist/core.d.mts +12 -1
- package/dist/core.d.ts +12 -1
- package/dist/core.js +50 -3
- package/dist/core.mjs +50 -3
- package/dist/sdk.d.mts +4 -0
- package/dist/sdk.d.ts +4 -0
- package/dist/sdk.js +50 -3
- package/dist/sdk.mjs +50 -3
- package/dist/vue.d.mts +4 -0
- package/dist/vue.d.ts +4 -0
- package/package.json +1 -25
package/README.md
CHANGED
|
@@ -1,289 +1,336 @@
|
|
|
1
1
|
# RouteSync
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Stop writing API clients by hand.
|
|
4
4
|
|
|
5
|
-
|
|
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 |
|
|
36
|
+
| Package | What it does |
|
|
12
37
|
|---|---|
|
|
13
|
-
| `@routesync/
|
|
14
|
-
| `@routesync/
|
|
15
|
-
| `@routesync/cli` |
|
|
16
|
-
| `@routesync/react` | React Query hooks factory |
|
|
17
|
-
| `@routesync/vue` | Vue Query composables
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
89
|
+
Then call it:
|
|
91
90
|
|
|
92
91
|
```ts
|
|
93
|
-
//
|
|
94
|
-
await api.
|
|
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
|
-
//
|
|
104
|
-
await api.
|
|
95
|
+
// GET /products/42
|
|
96
|
+
await api.products.detail({ params: { id: 42 } })
|
|
105
97
|
|
|
106
|
-
//
|
|
107
|
-
await api.
|
|
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
|
-
|
|
104
|
+
---
|
|
116
105
|
|
|
117
|
-
|
|
118
|
-
npm install -g @routesync/cli
|
|
119
|
-
```
|
|
106
|
+
### Option B — Auto-generate from Laravel
|
|
120
107
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
150
|
-
├── types.ts
|
|
151
|
-
└── hooks.ts
|
|
121
|
+
├── api.ts ← typed API client
|
|
122
|
+
├── types.ts ← TypeScript interfaces
|
|
123
|
+
└── hooks.ts ← React Query hooks
|
|
152
124
|
```
|
|
153
125
|
|
|
154
|
-
|
|
126
|
+
To keep it in sync during development:
|
|
155
127
|
|
|
156
128
|
```bash
|
|
157
|
-
npx routesync
|
|
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
|
-
|
|
134
|
+
### Authentication
|
|
166
135
|
|
|
167
|
-
```
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
id: number
|
|
179
|
-
product_name: string
|
|
180
|
-
created_at: string
|
|
181
|
-
}
|
|
152
|
+
---
|
|
182
153
|
|
|
183
|
-
|
|
154
|
+
### React Query Hooks
|
|
184
155
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
156
|
+
```ts
|
|
157
|
+
import { createService } from '@routesync/sdk'
|
|
158
|
+
import { createHooks } from '@routesync/react'
|
|
159
|
+
import { z } from 'zod'
|
|
188
160
|
|
|
189
|
-
const
|
|
161
|
+
const productSchema = z.object({
|
|
190
162
|
id: z.number(),
|
|
191
163
|
product_name: z.string(),
|
|
192
|
-
|
|
164
|
+
price: z.number(),
|
|
193
165
|
})
|
|
194
166
|
|
|
195
|
-
const
|
|
196
|
-
|
|
167
|
+
const productService = createService(client, '/products', {
|
|
168
|
+
entitySchema: productSchema,
|
|
169
|
+
listSchema: z.array(productSchema),
|
|
197
170
|
})
|
|
198
171
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
listSchema: z.array(backendProductSchema),
|
|
202
|
-
createSchema: productInputSchema
|
|
203
|
-
})
|
|
172
|
+
const { useList, useDetail, useCreate } = createHooks(productService, 'products')
|
|
173
|
+
```
|
|
204
174
|
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
const { data, isLoading } = useList({ page: 1 })
|
|
209
|
-
const mutation = useCreate()
|
|
197
|
+
### Vue Composables
|
|
210
198
|
|
|
211
|
-
|
|
212
|
-
|
|
199
|
+
```ts
|
|
200
|
+
import { createVueComposables } from '@routesync/vue'
|
|
213
201
|
|
|
214
|
-
|
|
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
|
-
|
|
209
|
+
### Resource groups
|
|
221
210
|
|
|
222
|
-
|
|
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 {
|
|
214
|
+
import { defineApi, resource } from '@routesync/sdk'
|
|
228
215
|
|
|
229
|
-
const
|
|
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
|
-
|
|
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 {
|
|
238
|
+
import { z } from 'zod'
|
|
238
239
|
|
|
239
|
-
const
|
|
240
|
-
|
|
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
|
-
|
|
244
|
-
setToken(response.data.token)
|
|
252
|
+
---
|
|
245
253
|
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
+
### Multiple backends
|
|
271
|
+
|
|
272
|
+
RouteSync isn't Laravel-only. Anything with a REST API works:
|
|
255
273
|
|
|
256
274
|
```ts
|
|
257
|
-
|
|
258
|
-
const
|
|
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
|
-
|
|
261
|
-
|
|
293
|
+
# Scan + generate in one step
|
|
294
|
+
npx routesync sync --input routes/api.php --output src/api --baseURL http://localhost/api
|
|
262
295
|
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
##
|
|
302
|
+
## How it works
|
|
270
303
|
|
|
271
304
|
```
|
|
272
|
-
Laravel
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Route
|
|
277
|
-
|
|
278
|
-
SDK
|
|
279
|
-
|
|
280
|
-
|
|
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
|