ajo-kit 0.1.0
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/LICENSE +15 -0
- package/README.md +468 -0
- package/dist/bin/kit.js +109 -0
- package/dist/chunks/constants-5Eu0naK3.js +168 -0
- package/dist/chunks/discover-D-b8Pqm8.js +34 -0
- package/dist/chunks/headers-BDszzwLw.js +23 -0
- package/dist/chunks/migrate-C9Iytqx4.js +89 -0
- package/dist/chunks/ssr-CCxCGcm6.js +421 -0
- package/dist/client.js +134 -0
- package/dist/database.js +38 -0
- package/dist/index.js +2 -0
- package/dist/mail.js +15 -0
- package/dist/node.js +104 -0
- package/dist/server.js +502 -0
- package/dist/validate.js +15 -0
- package/dist/vite.js +110 -0
- package/package.json +106 -0
- package/src/app.tsx +484 -0
- package/src/cache.ts +85 -0
- package/src/client.tsx +167 -0
- package/src/constants.ts +339 -0
- package/src/database.ts +55 -0
- package/src/discover.ts +42 -0
- package/src/form.ts +39 -0
- package/src/freshness.ts +63 -0
- package/src/head.tsx +125 -0
- package/src/headers.ts +32 -0
- package/src/index.ts +31 -0
- package/src/mail/index.ts +25 -0
- package/src/migrate.ts +132 -0
- package/src/node.ts +134 -0
- package/src/server.tsx +584 -0
- package/src/ssr.ts +8 -0
- package/src/timing.ts +60 -0
- package/src/validate.ts +33 -0
- package/src/virtual.d.ts +10 -0
- package/src/vite.ts +166 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2026, Cristian Falcone
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
14
|
+
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# ajo-kit
|
|
2
|
+
|
|
3
|
+
Full-stack metaframework for [Ajo](https://github.com/cristianfalcone/ajo) with file-based routing, server handlers, form actions, middleware, migrations, and SSE route payload updates.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add ajo ajo-kit
|
|
9
|
+
pnpm add -D vite typescript @types/node
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`ajo-kit` requires `ajo ^0.1.35`, `vite ^8.0.16`, and Node 22.18 or newer.
|
|
13
|
+
TypeScript migrations run through Node's built-in type stripping and use
|
|
14
|
+
erasable TypeScript syntax.
|
|
15
|
+
|
|
16
|
+
## Minimal Setup
|
|
17
|
+
|
|
18
|
+
### `package.json`
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"type": "module",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "kit dev",
|
|
25
|
+
"build": "kit build",
|
|
26
|
+
"start": "kit start"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `vite.config.ts`
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { defineConfig } from 'vite'
|
|
35
|
+
import { kit, jsx } from 'ajo-kit/vite'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
plugins: [...kit()],
|
|
39
|
+
esbuild: jsx,
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `tsconfig.json`
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"compilerOptions": {
|
|
48
|
+
"target": "ESNext",
|
|
49
|
+
"module": "ESNext",
|
|
50
|
+
"moduleResolution": "bundler",
|
|
51
|
+
"allowImportingTsExtensions": true,
|
|
52
|
+
"isolatedModules": true,
|
|
53
|
+
"noEmit": true,
|
|
54
|
+
"jsx": "react-jsx",
|
|
55
|
+
"jsxImportSource": "ajo",
|
|
56
|
+
"strict": true,
|
|
57
|
+
"paths": {
|
|
58
|
+
"/src/*": ["src/*"],
|
|
59
|
+
"@kit": ["node_modules/ajo-kit/src/index.ts"],
|
|
60
|
+
"@kit/*": ["node_modules/ajo-kit/src/*"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `index.html`
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<!DOCTYPE html>
|
|
70
|
+
<html lang="en">
|
|
71
|
+
<head>
|
|
72
|
+
<meta charset="UTF-8">
|
|
73
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
74
|
+
<!-- ssr:head -->
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
<!-- ssr:data -->
|
|
78
|
+
<div id="root"><!-- ssr:root --></div>
|
|
79
|
+
<script src="/src/client" type="module"></script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `src/page.tsx`
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
export default () => (
|
|
88
|
+
<main>
|
|
89
|
+
<h1>Welcome to ajo-kit</h1>
|
|
90
|
+
<p>Edit <code>src/page.tsx</code> to get started.</p>
|
|
91
|
+
</main>
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## CLI
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
kit dev [-p 5173]
|
|
99
|
+
kit build
|
|
100
|
+
kit start [-p 5173]
|
|
101
|
+
|
|
102
|
+
kit migrate up [-d ./database.sqlite]
|
|
103
|
+
kit migrate down [-d ./database.sqlite]
|
|
104
|
+
kit migrate status [-d ./database.sqlite]
|
|
105
|
+
kit migrate create <name>
|
|
106
|
+
|
|
107
|
+
kit seed [-d ./database.sqlite]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Defaults:
|
|
111
|
+
|
|
112
|
+
- database: `./database.sqlite`
|
|
113
|
+
- migrations folder: `db/migrations`
|
|
114
|
+
- seeds folder: `db/seeds`
|
|
115
|
+
|
|
116
|
+
## Routing
|
|
117
|
+
|
|
118
|
+
File-based routes:
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
flowchart LR
|
|
122
|
+
root["src/page.tsx"] --> rootPath["/"]
|
|
123
|
+
about["src/about/page.tsx"] --> aboutPath["/about"]
|
|
124
|
+
blog["src/blog/[id]/page.tsx"] --> blogPath["/blog/:id"]
|
|
125
|
+
docs["src/docs/[...]/page.tsx"] --> docsPath["/docs/*"]
|
|
126
|
+
group["src/(app)/dashboard/page.tsx"] --> dashboard["/dashboard"]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Per-route files:
|
|
130
|
+
|
|
131
|
+
- `page.tsx`: page component
|
|
132
|
+
- `layout.tsx`: shared wrapper for a route branch
|
|
133
|
+
- `handler.ts`: server loaders/actions/api handlers
|
|
134
|
+
- `wares.ts`: middleware for that branch and descendants
|
|
135
|
+
|
|
136
|
+
`page.tsx` and `layout.tsx` modules can export `pending = true` to receive
|
|
137
|
+
`loading=true` while client navigation waits for route data. The page wins
|
|
138
|
+
first; otherwise the innermost pending layout handles it.
|
|
139
|
+
|
|
140
|
+
## Server Handlers
|
|
141
|
+
|
|
142
|
+
`handler.ts` supports:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import type { Request, Response } from '@kit'
|
|
146
|
+
import { send } from '@kit/server'
|
|
147
|
+
import type { Head } from '@kit'
|
|
148
|
+
|
|
149
|
+
export async function layout(req: Request, parent: () => Promise<Record<string, unknown>>) {
|
|
150
|
+
return {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function page(req: Request, parent: () => Promise<Record<string, unknown>>) {
|
|
154
|
+
return {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function head(req: Request, parent: () => Promise<Record<string, unknown>>): Promise<Head> {
|
|
158
|
+
return { title: 'My page' }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const actions = {
|
|
162
|
+
async save(req: Request, res: Response) {
|
|
163
|
+
return { ok: true }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
async get(req: Request, res: Response) {
|
|
169
|
+
send(res, 200, { ok: true })
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Notes:
|
|
175
|
+
|
|
176
|
+
- `default` maps HTTP methods to `/api/<route>`.
|
|
177
|
+
- API handlers in `default` must write/send the HTTP response.
|
|
178
|
+
- `actions` are invoked by `POST /current-route?/actionName`.
|
|
179
|
+
- action `"default"` is used when no `?/name` is provided.
|
|
180
|
+
- `parent()` resolves merged ancestor loader data.
|
|
181
|
+
|
|
182
|
+
## Actions from Client
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { action } from '@kit/client'
|
|
186
|
+
|
|
187
|
+
const Page = function* () {
|
|
188
|
+
const form = action<{ ok: boolean }>('save')
|
|
189
|
+
|
|
190
|
+
while (true) {
|
|
191
|
+
yield (
|
|
192
|
+
<form onsubmit={form.submit}>
|
|
193
|
+
<input name="title" />
|
|
194
|
+
<button disabled={form.loading}>Save</button>
|
|
195
|
+
{form.error && <p>{form.error.message}</p>}
|
|
196
|
+
</form>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
You can also trigger programmatically:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
await form.invoke({ title: 'Hello' })
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If an action returns `{ redirect: '/path' }`, client navigation is triggered automatically.
|
|
209
|
+
Successful non-redirect actions dispatch `ajo:action` with returned JSON detail.
|
|
210
|
+
|
|
211
|
+
## Middleware
|
|
212
|
+
|
|
213
|
+
`wares.ts` exports one middleware or an array:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import type { Middleware } from '@kit'
|
|
217
|
+
|
|
218
|
+
const log: Middleware = (req, _res, next) => {
|
|
219
|
+
console.log(req.method, req.url)
|
|
220
|
+
next()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default log
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Middlewares are collected from route ancestors and applied to both page and API handlers.
|
|
227
|
+
|
|
228
|
+
## SSE Topics (Live Updates)
|
|
229
|
+
|
|
230
|
+
Track topics in loaders, then emit from server code after mutations:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// src/chat/handler.ts
|
|
234
|
+
import { emit } from '@kit/server'
|
|
235
|
+
|
|
236
|
+
export async function page(req) {
|
|
237
|
+
req.track?.('messages')
|
|
238
|
+
return { messages: [] }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export const actions = {
|
|
242
|
+
async create(req) {
|
|
243
|
+
// write to DB...
|
|
244
|
+
emit('messages')
|
|
245
|
+
return { ok: true }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The runtime maintains an SSE stream, revalidates affected routes, and replaces the active route payload when tracked topics change.
|
|
251
|
+
|
|
252
|
+
## Database and Migrations
|
|
253
|
+
|
|
254
|
+
`ajo-kit/database` exports:
|
|
255
|
+
|
|
256
|
+
- `connect(path?)`
|
|
257
|
+
- `db<T>()`
|
|
258
|
+
- `raw()`
|
|
259
|
+
- `close()`
|
|
260
|
+
- `Database` (better-sqlite3)
|
|
261
|
+
- `sql` and Kysely types
|
|
262
|
+
|
|
263
|
+
SSE topic versions, active connections, and update fanout are stored in process
|
|
264
|
+
memory. Multi-process deployments require shared topic coordination and
|
|
265
|
+
fanout. Store SQLite database files on persistent local disk.
|
|
266
|
+
|
|
267
|
+
For non-local production, configure `APP_URL` to the public `http` or `https`
|
|
268
|
+
origin. Applications choose how to configure their database path:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
connect(process.env.DATABASE_PATH ?? './database.sqlite')
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`kit migrate` composes:
|
|
275
|
+
|
|
276
|
+
- app migrations in `db/migrations`
|
|
277
|
+
- plugin migrations discovered from installed `ajo-*` packages that expose `package.json#kit.migrations`
|
|
278
|
+
|
|
279
|
+
Each migration provider uses a contiguous sequence beginning at `0001`, so a
|
|
280
|
+
plugin and the app may both define `0001_initial`. Stored identities use
|
|
281
|
+
`plugin/<package>/<name>` and `project/<name>` in one SQLite history and lock.
|
|
282
|
+
|
|
283
|
+
Every migration exports `up()` and `down()`. `migrate down` rolls back the
|
|
284
|
+
latest executed migration across all providers. `migrate status` rejects
|
|
285
|
+
history entries whose migration is unavailable.
|
|
286
|
+
|
|
287
|
+
`kit seed` runs sorted `db/seeds/*.ts` files that export:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
export async function seed(db) {
|
|
291
|
+
// ...
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Validation
|
|
296
|
+
|
|
297
|
+
`@kit/validate` re-exports common Valibot helpers and provides `parse(schema, data)`, which throws `Invalid` with field-level details.
|
|
298
|
+
|
|
299
|
+
## Plugin Discovery
|
|
300
|
+
|
|
301
|
+
Installed packages named `ajo-*` (except `ajo-kit`) with a `kit` block in `package.json` are auto-discovered:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"kit": {
|
|
306
|
+
"alias": "auth",
|
|
307
|
+
"serverOnly": true,
|
|
308
|
+
"migrations": "./migrations/",
|
|
309
|
+
"commands": "./src/commands.ts"
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
This enables:
|
|
315
|
+
|
|
316
|
+
- `@kit/<alias>` import aliases
|
|
317
|
+
- server-only import protection in Vite
|
|
318
|
+
- automatic migration loading
|
|
319
|
+
- CLI command extension via `register(cli)`
|
|
320
|
+
|
|
321
|
+
## Public Entry Points
|
|
322
|
+
|
|
323
|
+
| Import | API |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `ajo-kit` or `@kit` | Route types, HTTP errors, request helpers, navigation, and formatting |
|
|
326
|
+
| `ajo-kit/server` or `@kit/server` | Server runtime, `send()`, and `emit()` |
|
|
327
|
+
| `ajo-kit/client` or `@kit/client` | Client boot and `action()` |
|
|
328
|
+
| `ajo-kit/validate` or `@kit/validate` | Valibot helpers and `parse()` |
|
|
329
|
+
| `ajo-kit/database` or `@kit/database` | SQLite, Kysely, and database lifecycle |
|
|
330
|
+
| `ajo-kit/mail` or `@kit/mail` | Configurable mail transport |
|
|
331
|
+
| `ajo-kit/vite` | Vite plugin, JSX config, and defaults |
|
|
332
|
+
| `ajo-kit/node` | Programmatic development, build, start, and listen runtime |
|
|
333
|
+
|
|
334
|
+
## Core API
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import {
|
|
338
|
+
Denied,
|
|
339
|
+
Failure,
|
|
340
|
+
Forbidden,
|
|
341
|
+
Invalid,
|
|
342
|
+
Missing,
|
|
343
|
+
ajax,
|
|
344
|
+
api,
|
|
345
|
+
date,
|
|
346
|
+
ip,
|
|
347
|
+
navigate,
|
|
348
|
+
normalize,
|
|
349
|
+
origin,
|
|
350
|
+
} from 'ajo-kit'
|
|
351
|
+
import type {
|
|
352
|
+
Action,
|
|
353
|
+
Entry,
|
|
354
|
+
Fields,
|
|
355
|
+
Head,
|
|
356
|
+
Issue,
|
|
357
|
+
LayoutArgs,
|
|
358
|
+
Middleware,
|
|
359
|
+
PageArgs,
|
|
360
|
+
Parent,
|
|
361
|
+
Request,
|
|
362
|
+
Response,
|
|
363
|
+
User,
|
|
364
|
+
} from 'ajo-kit'
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`Failure` carries an HTTP status. `Missing`, `Forbidden`, `Denied`, and
|
|
368
|
+
`Invalid` represent 404, 403, 401, and 400 responses. `normalize()` converts an
|
|
369
|
+
unknown thrown value into a `Failure`.
|
|
370
|
+
|
|
371
|
+
`ajax()` and `api()` classify requests. `ip()` resolves the client address, and
|
|
372
|
+
`origin()` resolves the trusted application origin. `navigate()` performs
|
|
373
|
+
client navigation, and `date()` formats ISO timestamps.
|
|
374
|
+
|
|
375
|
+
## Server API
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
import { emit, send } from 'ajo-kit/server'
|
|
379
|
+
|
|
380
|
+
send(res, 200, { ok: true })
|
|
381
|
+
emit('posts:list')
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`send()` writes an API response. `emit()` accepts one topic or an array of
|
|
385
|
+
topics and revalidates active routes that track them. Emit after durable writes
|
|
386
|
+
commit.
|
|
387
|
+
|
|
388
|
+
## Head
|
|
389
|
+
|
|
390
|
+
Route `head()` loaders return `Head`. Ancestor and page values are merged for
|
|
391
|
+
SSR and client navigation.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
type Head = {
|
|
395
|
+
title?: string
|
|
396
|
+
meta?: (
|
|
397
|
+
| { name: string; content: string }
|
|
398
|
+
| { property: string; content: string }
|
|
399
|
+
| { httpEquiv: string; content: string }
|
|
400
|
+
)[]
|
|
401
|
+
link?: { rel: string; href: string; [key: string]: string | undefined }[]
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Mail
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { configure, send } from 'ajo-kit/mail'
|
|
409
|
+
import type { Mail, Transport } from 'ajo-kit/mail'
|
|
410
|
+
|
|
411
|
+
const deliver: Transport = async mail => {
|
|
412
|
+
// Send mail with the application's provider.
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
configure(deliver)
|
|
416
|
+
|
|
417
|
+
await send({
|
|
418
|
+
to: 'person@example.com',
|
|
419
|
+
subject: 'Welcome',
|
|
420
|
+
text: 'Welcome to the app.',
|
|
421
|
+
})
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`configure()` registers a `Transport` function. The default transport writes
|
|
425
|
+
mail to stdout.
|
|
426
|
+
|
|
427
|
+
## Vite API
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { jsx, kit } from 'ajo-kit/vite'
|
|
431
|
+
import type { Options } from 'ajo-kit/vite'
|
|
432
|
+
import { defineConfig } from 'vite'
|
|
433
|
+
|
|
434
|
+
const options: Options = {
|
|
435
|
+
guard: [/\/src\/data\//],
|
|
436
|
+
css: ['virtual:uno.css'],
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export default defineConfig({
|
|
440
|
+
plugins: [...kit(options)],
|
|
441
|
+
esbuild: jsx,
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`kit()` configures routes, handlers, aliases, server-only guards, HMR, CSS
|
|
446
|
+
entries, and production SSR. Custom `guard` patterns extend the default client
|
|
447
|
+
graph protection.
|
|
448
|
+
|
|
449
|
+
`css` entries load before application hydration. `jsx` configures Ajo's
|
|
450
|
+
automatic JSX runtime. The exported `defaults` object contains the database,
|
|
451
|
+
migrations, and seeds paths used by the CLI.
|
|
452
|
+
|
|
453
|
+
## Node API
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import { build, compile, dev, listen, start } from 'ajo-kit/node'
|
|
457
|
+
import type { Options } from 'ajo-kit/node'
|
|
458
|
+
|
|
459
|
+
const options: Options = {
|
|
460
|
+
hmr: { overlay: false },
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
await dev(options)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`dev()`, `build()`, and `start()` expose the CLI runtimes programmatically.
|
|
467
|
+
`compile()` fills `<!-- ssr:name -->` HTML slots. `listen()` starts an
|
|
468
|
+
application server and can require a strict port.
|
package/dist/bin/kit.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as discover } from "../chunks/discover-D-b8Pqm8.js";
|
|
3
|
+
import * as url from "node:url";
|
|
4
|
+
import "dotenv/config";
|
|
5
|
+
import sade from "sade";
|
|
6
|
+
import { build, dev, listen, start } from "ajo-kit/node";
|
|
7
|
+
import { defaults } from "ajo-kit/vite";
|
|
8
|
+
//#region packages/ajo-kit/bin/kit.ts
|
|
9
|
+
var ok = "\x1B[32m✓\x1B[0m";
|
|
10
|
+
var fail = "\x1B[31m✗\x1B[0m";
|
|
11
|
+
var pending = "\x1B[33m○\x1B[0m";
|
|
12
|
+
async function database(path, fn) {
|
|
13
|
+
const { connect, db, close } = await import("ajo-kit/database");
|
|
14
|
+
connect(path);
|
|
15
|
+
try {
|
|
16
|
+
await fn(db);
|
|
17
|
+
} finally {
|
|
18
|
+
await close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function report(results, error, empty, suffix = "") {
|
|
22
|
+
for (const r of results ?? []) console.log(`${r.status === "Success" ? ok : fail} ${r.migrationName}${suffix}`);
|
|
23
|
+
if (error) throw error;
|
|
24
|
+
if (!results?.length) console.log(empty);
|
|
25
|
+
}
|
|
26
|
+
var cli = sade("kit");
|
|
27
|
+
cli.command("dev").describe("Start development server").option("-p, --port", "Port number", 5173).action(async (opts) => {
|
|
28
|
+
await listen(await dev(), opts.port);
|
|
29
|
+
});
|
|
30
|
+
cli.command("build").describe("Build for production").action(async () => {
|
|
31
|
+
await build();
|
|
32
|
+
});
|
|
33
|
+
cli.command("start").describe("Start production server").option("-p, --port", "Port number", 5173).action(async (opts) => {
|
|
34
|
+
process.env.NODE_ENV ??= "production";
|
|
35
|
+
await listen(await start(), opts.port);
|
|
36
|
+
});
|
|
37
|
+
cli.command("migrate up").describe("Run pending migrations").option("-d, --database", "Database path", defaults.database).action(async (opts) => {
|
|
38
|
+
await database(opts.database, async (db) => {
|
|
39
|
+
const { migrator } = await import("../chunks/migrate-C9Iytqx4.js");
|
|
40
|
+
const { results, error } = await migrator(db()).migrateToLatest();
|
|
41
|
+
report(results, error, "No pending migrations");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
cli.command("migrate down").describe("Rollback last migration").option("-d, --database", "Database path", defaults.database).action(async (opts) => {
|
|
45
|
+
await database(opts.database, async (db) => {
|
|
46
|
+
const { migrator } = await import("../chunks/migrate-C9Iytqx4.js");
|
|
47
|
+
const { results, error } = await migrator(db()).migrateDown();
|
|
48
|
+
report(results, error, "No migrations to rollback", " (rolled back)");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
cli.command("migrate status").describe("Show migration status").option("-d, --database", "Database path", defaults.database).action(async (opts) => {
|
|
52
|
+
await database(opts.database, async (db) => {
|
|
53
|
+
const { migrationStatus } = await import("../chunks/migrate-C9Iytqx4.js");
|
|
54
|
+
const migrations = await migrationStatus(db());
|
|
55
|
+
for (const m of migrations) console.log(`${m.executedAt ? ok : pending} ${m.name}`);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
cli.command("migrate create <name>").describe("Create a new migration file").action(async (name) => {
|
|
59
|
+
const { mkdirSync, writeFileSync, readdirSync } = await import("node:fs");
|
|
60
|
+
const { join } = await import("node:path");
|
|
61
|
+
const { migrationFile } = await import("../chunks/migrate-C9Iytqx4.js");
|
|
62
|
+
const dir = join(process.cwd(), defaults.migrations);
|
|
63
|
+
mkdirSync(dir, { recursive: true });
|
|
64
|
+
const file = migrationFile(readdirSync(dir), name);
|
|
65
|
+
writeFileSync(join(dir, file), `import type { Kysely } from 'ajo-kit/database'
|
|
66
|
+
|
|
67
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
71
|
+
}
|
|
72
|
+
`);
|
|
73
|
+
console.log(`Created ${join(defaults.migrations, file)}`);
|
|
74
|
+
});
|
|
75
|
+
cli.command("seed").describe("Run database seeds").option("-d, --database", "Database path", defaults.database).action(async (opts) => {
|
|
76
|
+
await database(opts.database, async (db) => {
|
|
77
|
+
const { join } = await import("node:path");
|
|
78
|
+
const { readdirSync } = await import("node:fs");
|
|
79
|
+
const dir = join(process.cwd(), defaults.seeds);
|
|
80
|
+
let files;
|
|
81
|
+
try {
|
|
82
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".ts")).sort();
|
|
83
|
+
} catch {
|
|
84
|
+
files = [];
|
|
85
|
+
}
|
|
86
|
+
if (!files.length) {
|
|
87
|
+
console.log("No seed files found");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
const mod = await import(url.pathToFileURL(join(dir, file)).href);
|
|
92
|
+
if (typeof mod.seed === "function") {
|
|
93
|
+
await mod.seed(db());
|
|
94
|
+
console.log(`${ok} ${file}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
for (const plugin of discover()) {
|
|
100
|
+
if (!plugin.commands) continue;
|
|
101
|
+
try {
|
|
102
|
+
const mod = await import(url.pathToFileURL(plugin.commands).href);
|
|
103
|
+
if (typeof mod.register === "function") mod.register(cli);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Failed to load commands from ${plugin.name}:`, error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
cli.parse(process.argv);
|
|
109
|
+
//#endregion
|