zyket 1.2.19 → 1.2.21
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/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +407 -0
- package/README.md +175 -279
- package/bin/cli.js +35 -7
- package/package.json +25 -23
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/skills/build/SKILL.md +89 -0
- package/src/kernel/HTTPServer.js +12 -0
- package/src/services/express/Express.js +6 -7
- package/src/services/index.js +6 -1
- package/src/templates/api-rest/README.md +1 -1
- package/src/templates/api-rest/src/services/auth/auth.js +3 -2
- package/src/templates/realtime-chat/README.md +1 -1
- package/src/templates/realtime-chat/src/services/auth/auth.js +3 -1
- package/src/templates/saas-multitenant/README.md +1 -1
- package/src/templates/saas-multitenant/src/services/auth/auth.js +3 -1
- package/src/utils/EnvManager.js +5 -0
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "zyket",
|
|
12
12
|
"source": "./plugin",
|
|
13
|
-
"description": "
|
|
14
|
-
"version": "1.
|
|
13
|
+
"description": "Build and scaffold Zyket apps — initialize from templates, wire services/auth/security (build), and generate components like routes, handlers, guards, services, and workers (generate).",
|
|
14
|
+
"version": "1.1.0"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# AGENTS.md — Zyket Framework Reference
|
|
2
|
+
|
|
3
|
+
> A complete, accurate reference for AI agents building applications with **Zyket**.
|
|
4
|
+
> Zyket is a service-oriented Node.js framework (Express 5 + Socket.IO + better-auth + Sequelize/BullMQ/MinIO) inspired by Symfony's architecture. Components are auto-discovered from the filesystem; services are wired through a dependency-injection container.
|
|
5
|
+
|
|
6
|
+
This document is the source of truth. When an installed copy exists, read `node_modules/zyket/AGENTS.md`. Always confirm a base class API against the installed source before relying on it.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Quick start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
mkdir my-app && cd my-app
|
|
14
|
+
npm install zyket
|
|
15
|
+
npx zyket init # interactive: pick a template
|
|
16
|
+
# or non-interactive:
|
|
17
|
+
npx zyket init api-rest # default | api-rest | saas-multitenant | realtime-chat
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`init` scaffolds the chosen template's files, generates a `.env` (defaults merged with the template's `.env.example`), writes a `package.json` with the right dependencies, and runs `npm install`. Templates that ship auth need their tables created once:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js
|
|
24
|
+
node index.js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Minimal manual boot:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
// index.js
|
|
31
|
+
const { Kernel } = require("zyket");
|
|
32
|
+
|
|
33
|
+
const kernel = new Kernel({
|
|
34
|
+
services: [
|
|
35
|
+
["auth", require("./src/services/auth"), ["@service_container"]],
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
kernel.boot().then(() => console.log("Booted")).catch(console.error);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. The Kernel
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
new Kernel({ services: [], extensions: [] })
|
|
48
|
+
kernel.boot(clearConsole = true, secretsPath = "<cwd>/.env")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Boot order:
|
|
52
|
+
1. Load `.env` (creates one with defaults if missing, including a random `AUTH_SECRET`).
|
|
53
|
+
2. Start the HTTP server on `PORT` (default 3000). Until the Express service attaches, requests are answered `503 {"success":false,"message":"Server is starting"}` with `Retry-After: 1` — so a health check during boot gets an answer instead of hanging. With `DISABLE_EXPRESS=true` every HTTP request keeps getting that 503.
|
|
54
|
+
3. Register **core services** (auto-selected from env, see §4) then your `services`.
|
|
55
|
+
4. Call `boot({ httpServer })` on every service in order. `express` boots before `socketio` on purpose: engine.io wraps the existing request listener, so reversing them breaks the socket.io polling transport.
|
|
56
|
+
5. Load each `extensions` instance via `extension.load(container)` (after services).
|
|
57
|
+
|
|
58
|
+
`kernel.container` exposes the DI container (`container.get('name')`, `container.has('name')`).
|
|
59
|
+
|
|
60
|
+
**Service registration tuple:** `[name, ClassOrFactory, args]`. Special arg placeholders:
|
|
61
|
+
|
|
62
|
+
| Placeholder | Injected value |
|
|
63
|
+
|-------------|----------------|
|
|
64
|
+
| `"@service_container"` | the DI container |
|
|
65
|
+
| `"@onConnection"` | the kernel's socket-connection callback |
|
|
66
|
+
|
|
67
|
+
Auth is **not** a core service — register it yourself (templates do).
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. Environment variables
|
|
72
|
+
|
|
73
|
+
The generated `.env` (`src/utils/EnvManager.js`) plus auth/vite variables:
|
|
74
|
+
|
|
75
|
+
| Variable | Default | Purpose |
|
|
76
|
+
|----------|---------|---------|
|
|
77
|
+
| `DEBUG` | `true` | Enable debug-level logs |
|
|
78
|
+
| `PORT` | `3000` | HTTP/WS port |
|
|
79
|
+
| `NODE_ENV` | — | `production` enables secure cookies, Vite preview build |
|
|
80
|
+
| `HTTP_JSON_LIMIT` | `10mb` | Express JSON body limit |
|
|
81
|
+
| `SOCKET_MAX_HTTP_BUFFER_SIZE` | `10485760` | Socket.IO max payload bytes |
|
|
82
|
+
| `DISABLE_EXPRESS` | `false` | Turn off the HTTP/REST service |
|
|
83
|
+
| `DISABLE_SOCKET` | `true` | Turn off Socket.IO |
|
|
84
|
+
| `DISABLE_VITE` | `true` | Turn off the Vite frontend |
|
|
85
|
+
| `DISABLE_EVENTS` | `true` | Turn off the events service |
|
|
86
|
+
| `DISABLE_BULLMQ` | `true` | Turn off BullMQ queues/workers |
|
|
87
|
+
| `DISABLE_SCHEDULER` | `true` | Turn off the cron scheduler |
|
|
88
|
+
| `DISABLE_LOGGER` | `false` | Silence the logger |
|
|
89
|
+
| `DISABLE_SWAGGER` | — | `true` removes the `/docs` UI |
|
|
90
|
+
| `SWAGGER_PASSWORD` / `SWAGGER_USER` | — / `admin` | HTTP Basic auth for `/docs` |
|
|
91
|
+
| `SWAGGER_PATH` | `/docs` | Swagger UI mount path |
|
|
92
|
+
| `DATABASE_URL` | `./database.sqlite` | DB connection / sqlite path. Activates the `database` service when set |
|
|
93
|
+
| `DATABASE_DIALECT` | `sqlite` | `sqlite`, `postgresql`, `mariadb`… (auth supports only `sqlite`/`postgresql`) |
|
|
94
|
+
| `CACHE_URL` | `` | `redis://…` for Redis, empty/`memory` for in-memory |
|
|
95
|
+
| `REDIS_URL` | — | Enables the Socket.IO Redis adapter (multi-instance) |
|
|
96
|
+
| `S3_ENDPOINT` `S3_PORT` `S3_USE_SSL` `S3_ACCESS_KEY` `S3_SECRET_KEY` | — | MinIO/S3. The `s3` service activates when endpoint+keys are set |
|
|
97
|
+
| `S3_PUBLIC_BUCKETS` / `S3_PRIVATE_BUCKETS` | — | Comma lists; public buckets get a read-all policy |
|
|
98
|
+
| `LOG_DIRECTORY` | `./logs` | Log file directory |
|
|
99
|
+
| `QUEUES` | `` | Comma list of BullMQ queue names to create |
|
|
100
|
+
| `VITE_ROOT` | `./frontend` | Frontend project root |
|
|
101
|
+
| `VITE_PORT` | `5173` | Vite dev/preview port |
|
|
102
|
+
| `VITE_API_BASE` | `http://localhost:3000` | Frontend → backend base URL |
|
|
103
|
+
| **Auth** (added by the auth service) | | |
|
|
104
|
+
| `AUTH_SECRET` | random 32-byte hex | Session signing secret. **Boot fails if missing/insecure** |
|
|
105
|
+
| `BETTER_AUTH_URL` | `http://localhost:3000` | Public base URL of the auth server |
|
|
106
|
+
| `TRUSTED_ORIGINS` | `http://localhost:5173,http://localhost:3000` | Comma list of allowed origins (CORS + CSRF) |
|
|
107
|
+
| `AUTH_CROSS_DOMAIN` | `false` | `true` → cross-site cookies (`sameSite=none; secure`, needs HTTPS) |
|
|
108
|
+
| `BULLBOARD_ADMIN_PASSWORD` | — | Required to expose the BullBoard dashboard extension |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 4. Services
|
|
113
|
+
|
|
114
|
+
Core services are auto-registered based on env (`src/services/index.js`). Access any with `container.get('<name>')`.
|
|
115
|
+
|
|
116
|
+
| Name | Activated when | Key API |
|
|
117
|
+
|------|----------------|---------|
|
|
118
|
+
| `logger` | always (silent if `DISABLE_LOGGER=true`) | `info/warn/error/debug(msg)` |
|
|
119
|
+
| `template-manager` | always | internal (templates) |
|
|
120
|
+
| `events` | `DISABLE_EVENTS !== 'true'` | `emit(name, payload)`, `emitAsync(name, payload, timeout)` |
|
|
121
|
+
| `database` | `DATABASE_URL` set | `models`, `sequelize`, `Op`, `Sequelize`, `sync()`, `runMigrations(path)`, `loadModel(fn)` |
|
|
122
|
+
| `cache` | always | `get/set/del/keys/expire`, `client` (redis or null) |
|
|
123
|
+
| `s3` | `S3_ENDPOINT` + access + secret | `saveFile/getFile/removeFile/ensureBucket/listBuckets`, `client` (MinIO) |
|
|
124
|
+
| `scheduler` | `DISABLE_SCHEDULER !== 'true'` | discovers `src/schedulers/*` |
|
|
125
|
+
| `bullmq` | `DISABLE_BULLMQ !== 'true'` | `queues`, `addJob(queue, name, data, opts, waitForCompletion)` |
|
|
126
|
+
| `socketio` | `DISABLE_SOCKET !== 'true'` | `io`, `sockets` |
|
|
127
|
+
| `vite` | `VITE_ROOT` set + `DISABLE_VITE !== 'true'` | serves `frontend/` |
|
|
128
|
+
| `express` | `DISABLE_EXPRESS !== 'true'` | `app()`, `registerRoutes(routes)`, `regiterRawAllRoutes(path, handler)` |
|
|
129
|
+
| `auth` | **manual registration** | `client` (better-auth), `getSession(headers)` |
|
|
130
|
+
|
|
131
|
+
> Note: `bullmq`, `scheduler`, `events` default to ON when their `DISABLE_*` var is absent, but the generated `.env` ships them as `true` (off). Set the relevant `DISABLE_*=false` to enable.
|
|
132
|
+
|
|
133
|
+
### Custom service
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const { Service } = require("zyket");
|
|
137
|
+
|
|
138
|
+
module.exports = class MyService extends Service {
|
|
139
|
+
#container;
|
|
140
|
+
constructor(container) { super("my-service"); this.#container = container; }
|
|
141
|
+
async boot({ httpServer } = {}) { /* init */ }
|
|
142
|
+
doThing() { /* ... */ }
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Register: `services: [["my-service", MyService, ["@service_container"]]]`.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 5. Auto-discovered components
|
|
151
|
+
|
|
152
|
+
These are discovered from folders under `src/` — the **filename is the identity**. Use kebab-case, `module.exports = class extends Base`, and do not add constructor params (the loader passes only the derived name).
|
|
153
|
+
|
|
154
|
+
| Component | Folder | Base | Identity from filename |
|
|
155
|
+
|-----------|--------|------|------------------------|
|
|
156
|
+
| Route | `src/routes/` | `Route` | URL path |
|
|
157
|
+
| Middleware | `src/middlewares/` | `Middleware` | referenced manually |
|
|
158
|
+
| Handler | `src/handlers/` | `Handler` | socket event name |
|
|
159
|
+
| Guard | `src/guards/` | `Guard` | guard name |
|
|
160
|
+
| Event | `src/events/` | `Event` | event name |
|
|
161
|
+
| Scheduler | `src/schedulers/` | `Schedule` | label |
|
|
162
|
+
| Worker | `src/workers/` | `Worker` | label |
|
|
163
|
+
| Model | `src/models/` | factory fn | model name |
|
|
164
|
+
|
|
165
|
+
### Routes (`src/routes/**.js`)
|
|
166
|
+
|
|
167
|
+
File path → URL: `index.js`→`/`, `users.js`→`/users`, `users/[id].js`→`/users/:id`. Methods: **`get`, `post`, `put`, `delete`** (PATCH is not dispatched).
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
const { Route, RequireAuthMiddleware } = require("zyket");
|
|
171
|
+
|
|
172
|
+
module.exports = class extends Route {
|
|
173
|
+
middlewares = { post: [new RequireAuthMiddleware()] }; // per-method
|
|
174
|
+
|
|
175
|
+
async get({ container, request, response }) {
|
|
176
|
+
return { items: [] }; // → { success: true, items: [] }
|
|
177
|
+
}
|
|
178
|
+
async post({ container, request }) {
|
|
179
|
+
if (!request.body.name) return { success: false, message: "name required", status: 400 };
|
|
180
|
+
return { created: true, status: 201 };
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Return conventions: object → `{ success: true, ...obj }` JSON; `status` key sets HTTP code; `Buffer` → file download; `new RedirectResponse(url)` → redirect. Thrown errors → `500 { success:false, message:"Internal Server Error" }` (details only in logs).
|
|
186
|
+
|
|
187
|
+
### Middlewares (`src/middlewares/*.js`)
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const { Middleware } = require("zyket");
|
|
191
|
+
module.exports = class extends Middleware {
|
|
192
|
+
async handle({ container, request, response, next }) {
|
|
193
|
+
// respond to block, or call next() to continue
|
|
194
|
+
next();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Socket.IO handlers (`src/handlers/*.js`) & guards (`src/guards/*.js`)
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
// src/handlers/message.js → listens to the "message" event
|
|
203
|
+
const { Handler } = require("zyket");
|
|
204
|
+
module.exports = class extends Handler {
|
|
205
|
+
guards = ["auth"]; // run before handle; throw to block
|
|
206
|
+
async handle({ container, socket, data, io }) {
|
|
207
|
+
io.to("room").emit("message", data);
|
|
208
|
+
return { ok: true }; // returned to the client ack callback
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
// src/guards/auth.js
|
|
215
|
+
const { Guard } = require("zyket");
|
|
216
|
+
module.exports = class extends Guard {
|
|
217
|
+
async handle({ container, socket, io }) {
|
|
218
|
+
if (!ok) throw new Error("Unauthorized"); // blocks the event
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> `connection.js` is special (runs on connect). **Connection-level guards are not awaited and do not reliably block** — enforce connect-time auth inside `connection.js` itself (throwing there disconnects the socket).
|
|
224
|
+
|
|
225
|
+
### Events (`src/events/*.js`)
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
const { Event } = require("zyket");
|
|
229
|
+
module.exports = class extends Event {
|
|
230
|
+
async handle({ container, payload }) { /* ... */ }
|
|
231
|
+
};
|
|
232
|
+
// elsewhere: container.get('events').emit('user-registered', { id })
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Scheduler (`src/schedulers/*.js`)
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
const { Schedule } = require("zyket");
|
|
239
|
+
module.exports = class extends Schedule {
|
|
240
|
+
time = "*/5 * * * *"; // node-cron expression
|
|
241
|
+
async handle({ container }) { /* ... */ }
|
|
242
|
+
};
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Worker (`src/workers/*.js`) — BullMQ
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
const { Worker } = require("zyket");
|
|
249
|
+
module.exports = class extends Worker {
|
|
250
|
+
queueName = "emails"; // must match a name in QUEUES
|
|
251
|
+
instances = 1; // number | array | async fn
|
|
252
|
+
options = { concurrency: 5 }; // BullMQ worker options | fn
|
|
253
|
+
async handle({ container, job, instance, index }) { /* process job.data */ }
|
|
254
|
+
};
|
|
255
|
+
// enqueue: container.get('bullmq').addJob('emails', 'welcome', { to })
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Model (`src/models/*.js`) — Sequelize factory
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
module.exports = ({ sequelize, Sequelize, container }) => {
|
|
262
|
+
const User = sequelize.define("User", {
|
|
263
|
+
email: { type: Sequelize.STRING, allowNull: false },
|
|
264
|
+
});
|
|
265
|
+
User.associate = (models) => { /* User.hasMany(models.Post) */ };
|
|
266
|
+
return User;
|
|
267
|
+
};
|
|
268
|
+
// access: container.get('database').models.User ; create tables: .sync()
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 6. Authentication (better-auth)
|
|
274
|
+
|
|
275
|
+
The `auth` service mounts better-auth at `/api/auth/*` and provides sessions, the **admin** plugin, the **bearer** plugin, and (optionally) the **organization** plugin. Customize by subclassing `AuthService`:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
const { AuthService } = require("zyket");
|
|
279
|
+
|
|
280
|
+
module.exports = class CustomAuthService extends AuthService {
|
|
281
|
+
get organizationEnabled() { return true; } // multi-tenant
|
|
282
|
+
get requireEmailVerification() { return false; }
|
|
283
|
+
get plugins() { return []; } // extra better-auth plugins
|
|
284
|
+
get socialProviders() { return {}; }
|
|
285
|
+
get userAdditionalFields() { return {}; }
|
|
286
|
+
get organizationAdditionalFields() { return {}; }
|
|
287
|
+
async allowUserToCreateOrganization(user) { return true; }
|
|
288
|
+
async sendVerificationEmail({ user, url, token }) { /* email */ }
|
|
289
|
+
async sendResetPasswordEmail({ user, url, token }) { /* email */ }
|
|
290
|
+
async sendInvitationEmail(data) { /* org invite */ }
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Requirements: `DATABASE_DIALECT` must be `sqlite` or `postgresql`. Run `npx @better-auth/cli migrate --config src/services/auth/auth.js` to create the auth tables. The `--config` flag is required: the CLI only auto-discovers `auth.js` under `src/{auth,lib,server,utils}/`, never under `src/services/`.
|
|
295
|
+
|
|
296
|
+
### Protecting routes & sockets
|
|
297
|
+
|
|
298
|
+
| Helper | Use |
|
|
299
|
+
|--------|-----|
|
|
300
|
+
| `RequireAuthMiddleware` | route middleware; requires a session. `new RequireAuthMiddleware(['admin'])` to gate by role. Sets `request.user` / `request.session` |
|
|
301
|
+
| `RequireAdminMiddleware` | route middleware; requires `role === 'admin'` |
|
|
302
|
+
| `AuthGuard` | socket guard; rejects events without a session. Use `module.exports = require('zyket').AuthGuard` in `src/guards/auth.js` |
|
|
303
|
+
| `container.get('auth').getSession(headers)` | resolve a session from raw headers (e.g. `socket.handshake.headers`) without importing better-auth |
|
|
304
|
+
|
|
305
|
+
### Cookies (environment-aware)
|
|
306
|
+
|
|
307
|
+
Default: `sameSite=lax`, `secure` only in production → works over `http://localhost` when frontend (`:5173`) and API (`:3000`) are same-site. For front/back on **different domains**, set `AUTH_CROSS_DOMAIN=true` (HTTPS required) → `sameSite=none; secure` + cross-subdomain cookies.
|
|
308
|
+
|
|
309
|
+
### Frontend auth client
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
import { createAuthClient } from "better-auth/react";
|
|
313
|
+
import { organizationClient, adminClient } from "better-auth/client/plugins";
|
|
314
|
+
|
|
315
|
+
export const client = createAuthClient({
|
|
316
|
+
baseURL: `${import.meta.env.VITE_API_BASE || "http://localhost:3000"}/api/auth/`,
|
|
317
|
+
plugins: [organizationClient(), adminClient()],
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 7. Extensions
|
|
324
|
+
|
|
325
|
+
Post-boot plugins with full container access. Register instances in the Kernel.
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
const { Kernel, BullBoardExtension, InteractiveStorageExtension } = require("zyket");
|
|
329
|
+
|
|
330
|
+
new Kernel({
|
|
331
|
+
extensions: [
|
|
332
|
+
new BullBoardExtension({ path: "/bullboard", middlewares: [] }), // needs BULLBOARD_ADMIN_PASSWORD or middlewares, else it won't mount
|
|
333
|
+
new InteractiveStorageExtension({ path: "/storage", bucketName: "dropbox", middlewares: [/* auth */], maxDeleteBatch: 100 }),
|
|
334
|
+
],
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Custom extension:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
const { Extension } = require("zyket");
|
|
342
|
+
module.exports = class extends Extension {
|
|
343
|
+
constructor(options = {}) { super("MyExtension"); this.options = options; }
|
|
344
|
+
async load(container) {
|
|
345
|
+
if (!container.get("express")) return container.get("logger").warn("no express");
|
|
346
|
+
container.get("express").registerRoutes([/* Route instances */]);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 8. Frontend (Vite + React)
|
|
354
|
+
|
|
355
|
+
When `VITE_ROOT` is set and `DISABLE_VITE=false`, Zyket runs Vite (dev server, or a built preview server in production) for the `frontend/` app. Stack: React 19 + react-router-dom + zustand + Tailwind v4 + better-auth client. The frontend talks to the backend at `VITE_API_BASE`. A project with a frontend must declare its own frontend dependencies (the CLI does this automatically per template).
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 9. Templates
|
|
360
|
+
|
|
361
|
+
| Template | Stack | Use for |
|
|
362
|
+
|----------|-------|---------|
|
|
363
|
+
| `default` | backend + (optional) React frontend | general starter |
|
|
364
|
+
| `api-rest` | backend only, auth + CRUD (`Task`) | clean REST APIs |
|
|
365
|
+
| `saas-multitenant` | orgs/roles + React dashboard, tenant-scoped `Project` | multi-tenant SaaS |
|
|
366
|
+
| `realtime-chat` | Socket.IO + React chat UI, session auth | real-time apps |
|
|
367
|
+
|
|
368
|
+
Each template is self-contained (`index.js`, `.env.example`, `README.md`, and frontend where relevant). `npx zyket init <template>` scaffolds it.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 10. CLI
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npx zyket # interactive menu
|
|
376
|
+
npx zyket init # choose a template, scaffold + install + (default) run
|
|
377
|
+
npx zyket init <template> # scaffold a specific template
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`init` keeps existing files (non-destructive). For non-default templates it prints next steps (auth migrate + run) instead of auto-starting.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 11. Security checklist
|
|
385
|
+
|
|
386
|
+
See `SECURITY.md` for the full audit. Essentials before production:
|
|
387
|
+
|
|
388
|
+
- Keep `.env` out of git; never reuse `AUTH_SECRET` (it's generated per project; boot fails on a known/empty value).
|
|
389
|
+
- Protect every non-public route/socket with `RequireAuthMiddleware` / `AuthGuard`.
|
|
390
|
+
- Set `BULLBOARD_ADMIN_PASSWORD` (or pass `middlewares`) — the dashboard won't mount without auth.
|
|
391
|
+
- Set `SWAGGER_PASSWORD` (or `DISABLE_SWAGGER=true`) in production.
|
|
392
|
+
- Lock `TRUSTED_ORIGINS`; set `AUTH_CROSS_DOMAIN=true` only for real cross-domain HTTPS deployments.
|
|
393
|
+
- Add rate limiting and `helmet` (not bundled).
|
|
394
|
+
- For the storage extension, always pass auth `middlewares`; tune `maxFileSize` / `maxDeleteBatch`.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 12. Build-a-feature recipe
|
|
399
|
+
|
|
400
|
+
1. **Model** — `src/models/Thing.js` (Sequelize factory). Create tables with `container.get('database').sync()` in `index.js` after boot, or migrations.
|
|
401
|
+
2. **Routes** — `src/routes/things/index.js` (GET list, POST create) and `src/routes/things/[id].js` (GET/PUT/DELETE). Gate with `new RequireAuthMiddleware()`.
|
|
402
|
+
3. **Access data** — `const { Thing } = container.get('database').models;`.
|
|
403
|
+
4. **Realtime (optional)** — `src/handlers/<event>.js` with `guards = ["auth"]`; broadcast with `io`.
|
|
404
|
+
5. **Background (optional)** — add the queue to `QUEUES`, `DISABLE_BULLMQ=false`, add `src/workers/<name>.js`, enqueue with `container.get('bullmq').addJob(...)`.
|
|
405
|
+
6. **Run** — `npx @better-auth/cli migrate --config src/services/auth/auth.js` (first time) then `node index.js`.
|
|
406
|
+
|
|
407
|
+
To scaffold any component quickly, use the `generate` skill (`/zyket:generate route things/[id]`).
|
package/README.md
CHANGED
|
@@ -1,279 +1,175 @@
|
|
|
1
|
-
# Zyket
|
|
2
|
-
|
|
3
|
-
Zyket is a Node.js framework
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Getting
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
npx
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
// src/
|
|
98
|
-
const {
|
|
99
|
-
|
|
100
|
-
module.exports = class
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
console.log("MyCustomService has been booted!");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
doSomething() {
|
|
180
|
-
return "Something was done.";
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Register the service in your main `index.js` file:
|
|
186
|
-
|
|
187
|
-
```javascript
|
|
188
|
-
// index.js
|
|
189
|
-
const { Kernel } = require("zyket");
|
|
190
|
-
const MyCustomService = require("./src/services/MyCustomService");
|
|
191
|
-
|
|
192
|
-
const kernel = new Kernel({
|
|
193
|
-
services: [
|
|
194
|
-
// [name, class, [constructor_args]]
|
|
195
|
-
["my-service", MyCustomService, []]
|
|
196
|
-
]
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
kernel.boot();
|
|
200
|
-
```
|
|
201
|
-
Services are reusable components specified in the kernel configuration. Each service must include a boot() function that is executed when the kernel starts.
|
|
202
|
-
|
|
203
|
-
```javascript
|
|
204
|
-
module.exports = class LoggerService {
|
|
205
|
-
this.#container;
|
|
206
|
-
|
|
207
|
-
boot(container, enableLogging = true) {
|
|
208
|
-
this.#container = container;
|
|
209
|
-
console.log("LoggerService Booted");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
info(message) {
|
|
213
|
-
if(!enableLogging) return;
|
|
214
|
-
console.log(`[INFO]: ${message}`);
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Then, when booting the kernel, specify the service:
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
const { Kernel } = require("zyket");
|
|
223
|
-
const LoggerService = require("./LoggerService");
|
|
224
|
-
|
|
225
|
-
const kernel = new Kernel({
|
|
226
|
-
services: [["logger", LoggerService, ['@service_container', true]],
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
kernel.boot().then(() => console.log(`Kernel Booted`));
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Default Services
|
|
233
|
-
Zyket includes some default services that provide essential functionality. These services can be overridden or extended if needed.
|
|
234
|
-
|
|
235
|
-
#### Cache Services
|
|
236
|
-
- **Name** `cache`
|
|
237
|
-
- **Description** Provides caching functionality using a Redis adapter.
|
|
238
|
-
- **Configuration** Add `CACHE_URL` in your `.env` file to activate caching.
|
|
239
|
-
|
|
240
|
-
#### Database Service
|
|
241
|
-
- **Name** `database`
|
|
242
|
-
- **Description** Manages database connections using a MariaDB/Sequelize adapter.
|
|
243
|
-
- **Configuration** Add `DATABASE_URL` in your `.env` file to enable the database connection.
|
|
244
|
-
|
|
245
|
-
#### S3 Service
|
|
246
|
-
- **Name** `s3`
|
|
247
|
-
- **Description** Provides S3-compatible object storage using MinIO.
|
|
248
|
-
- **Configuration** Add the following variables in your `.env` file to enable the service
|
|
249
|
-
- `S3_ENDPOINT`
|
|
250
|
-
- `S3_PORT`
|
|
251
|
-
- `S3_USE_SSL`
|
|
252
|
-
- `S3_ACCESS_KEY`
|
|
253
|
-
- `S3_SECRET_KEY`
|
|
254
|
-
|
|
255
|
-
#### Logger Service
|
|
256
|
-
- **Name** `logger`
|
|
257
|
-
- **Description** Handles logging for the application.
|
|
258
|
-
- **Configuration**
|
|
259
|
-
- Change `LOG_DIRECTORY` in `.env` file to set a custom log directory.
|
|
260
|
-
- Set `DEBUG` in `.env` file to enable or disable debug logging.
|
|
261
|
-
|
|
262
|
-
#### Socket.io Service
|
|
263
|
-
- **Name** `socketio`
|
|
264
|
-
- **Description** Manages the WebSocket server.
|
|
265
|
-
- **Configuration** Add `PORT` in your `.env` file to define the listening port for Socket.io.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
## Contributing
|
|
269
|
-
|
|
270
|
-
We welcome contributions from the community! If you'd like to improve Zyket, feel free to:
|
|
271
|
-
|
|
272
|
-
- Report issues and suggest features on GitHub Issues
|
|
273
|
-
|
|
274
|
-
- Submit pull requests with bug fixes or enhancements
|
|
275
|
-
|
|
276
|
-
- Improve the documentation
|
|
277
|
-
|
|
278
|
-
Let's build a better framework together! 🚀
|
|
279
|
-
|
|
1
|
+
# Zyket
|
|
2
|
+
|
|
3
|
+
Zyket is a service-oriented Node.js framework for building real-time and REST applications with **Express 5**, **Socket.IO**, **better-auth**, and **Sequelize/BullMQ/MinIO**. Inspired by Symfony, it pairs a dependency-injection container with filesystem-based auto-discovery: drop a file in the right folder and the framework wires it up.
|
|
4
|
+
|
|
5
|
+
> **Building with an AI agent?** See **[AGENTS.md](AGENTS.md)** for a complete, machine-readable reference, and **[SECURITY.md](SECURITY.md)** for hardening guidance.
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
mkdir my-app && cd my-app
|
|
11
|
+
npm install zyket
|
|
12
|
+
|
|
13
|
+
# Initialize from a template (interactive picker)
|
|
14
|
+
npx zyket init
|
|
15
|
+
# …or pick one directly:
|
|
16
|
+
npx zyket init api-rest # default | api-rest | saas-multitenant | realtime-chat
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`init` scaffolds the template, generates a `.env`, writes `package.json` with the right dependencies, and installs them. Templates that ship authentication need their tables created once:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js
|
|
23
|
+
node index.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Templates
|
|
27
|
+
|
|
28
|
+
| Template | What you get |
|
|
29
|
+
|----------|--------------|
|
|
30
|
+
| `default` | General starter (backend + optional React frontend) |
|
|
31
|
+
| `api-rest` | Backend-only REST API with auth and a protected CRUD resource |
|
|
32
|
+
| `saas-multitenant` | Multi-tenant SaaS (organizations, roles) + React dashboard |
|
|
33
|
+
| `realtime-chat` | Authenticated real-time chat (Socket.IO) + React UI |
|
|
34
|
+
|
|
35
|
+
### Manual boot
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// index.js
|
|
39
|
+
const { Kernel } = require("zyket");
|
|
40
|
+
|
|
41
|
+
const kernel = new Kernel({
|
|
42
|
+
services: [
|
|
43
|
+
["auth", require("./src/services/auth"), ["@service_container"]],
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
kernel.boot().then(() => console.log("Kernel booted!"));
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
On first run Zyket creates a `.env` and a `src/` directory with boilerplate.
|
|
51
|
+
|
|
52
|
+
## Core concepts
|
|
53
|
+
|
|
54
|
+
- **Routes & Middlewares** — REST endpoints via Express, auto-discovered from `src/routes`.
|
|
55
|
+
- **Handlers & Guards** — Socket.IO events and their authorization, from `src/handlers` / `src/guards`.
|
|
56
|
+
- **Services** — reusable units managed by a DI container (`logger`, `database`, `cache`, `s3`, `events`, `scheduler`, `bullmq`, `socketio`, `express`, `auth`).
|
|
57
|
+
- **Extensions** — post-boot plugins (e.g. BullBoard, interactive storage).
|
|
58
|
+
- **Templates & CLI** — scaffold whole projects or individual components.
|
|
59
|
+
|
|
60
|
+
### Routes
|
|
61
|
+
|
|
62
|
+
File path maps to the URL: `src/routes/index.js` → `/`, `src/routes/users/[id].js` → `/users/:id`. Methods: `get`, `post`, `put`, `delete`.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const { Route, RequireAuthMiddleware } = require("zyket");
|
|
66
|
+
|
|
67
|
+
module.exports = class extends Route {
|
|
68
|
+
middlewares = { post: [new RequireAuthMiddleware()] };
|
|
69
|
+
|
|
70
|
+
async get({ container, request }) {
|
|
71
|
+
return { items: [] }; // → { success: true, items: [] }
|
|
72
|
+
}
|
|
73
|
+
async post({ container, request }) {
|
|
74
|
+
if (!request.body.name) return { success: false, message: "name required", status: 400 };
|
|
75
|
+
return { created: true, status: 201 };
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Return an object (wrapped as `{ success: true, ... }`), set `status` to change the HTTP code, return a `Buffer` for a download, or `new RedirectResponse(url)` to redirect.
|
|
81
|
+
|
|
82
|
+
### Middlewares
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const { Middleware } = require("zyket");
|
|
86
|
+
|
|
87
|
+
module.exports = class extends Middleware {
|
|
88
|
+
async handle({ container, request, response, next }) {
|
|
89
|
+
next(); // respond to block, or next() to continue
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Socket.IO handlers & guards
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
// src/handlers/message.js → "message" event
|
|
98
|
+
const { Handler } = require("zyket");
|
|
99
|
+
|
|
100
|
+
module.exports = class extends Handler {
|
|
101
|
+
guards = ["auth"];
|
|
102
|
+
async handle({ container, socket, data, io }) {
|
|
103
|
+
io.emit("message", data);
|
|
104
|
+
return { ok: true };
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
// src/guards/auth.js — reuse the built-in session guard
|
|
111
|
+
module.exports = require("zyket").AuthGuard;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Authentication
|
|
115
|
+
|
|
116
|
+
Zyket ships first-class auth via [better-auth](https://better-auth.com) mounted at `/api/auth/*`, with the admin, bearer, and (optional) organization plugins. Customize by subclassing `AuthService`; protect routes/sockets with `RequireAuthMiddleware`, `RequireAdminMiddleware`, and `AuthGuard`.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
const { Route, RequireAdminMiddleware } = require("zyket");
|
|
120
|
+
|
|
121
|
+
module.exports = class extends Route {
|
|
122
|
+
middlewares = { get: [new RequireAdminMiddleware()] };
|
|
123
|
+
async get() { return { secret: "admins only" }; }
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Cookies are environment-aware: `sameSite=lax` in local dev (works over `http://localhost`), and `sameSite=none; secure` when `AUTH_CROSS_DOMAIN=true` for cross-domain HTTPS deployments. See [AGENTS.md §6](AGENTS.md#6-authentication-better-auth).
|
|
128
|
+
|
|
129
|
+
## Services
|
|
130
|
+
|
|
131
|
+
Default services activate from environment variables (see [AGENTS.md §3–4](AGENTS.md#3-environment-variables)). Register your own in the Kernel:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const { Service } = require("zyket");
|
|
135
|
+
|
|
136
|
+
module.exports = class MyService extends Service {
|
|
137
|
+
#container;
|
|
138
|
+
constructor(container) { super("my-service"); this.#container = container; }
|
|
139
|
+
async boot() { /* init */ }
|
|
140
|
+
doThing() { return "done"; }
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const kernel = new Kernel({
|
|
146
|
+
services: [["my-service", MyService, ["@service_container"]]],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Default services and their activation:
|
|
151
|
+
|
|
152
|
+
| Service | Env to enable |
|
|
153
|
+
|---------|---------------|
|
|
154
|
+
| `database` | `DATABASE_URL` (+ `DATABASE_DIALECT`, default `sqlite`) |
|
|
155
|
+
| `cache` | always (`CACHE_URL=redis://…` for Redis, else in-memory) |
|
|
156
|
+
| `s3` | `S3_ENDPOINT` + `S3_ACCESS_KEY` + `S3_SECRET_KEY` (MinIO) |
|
|
157
|
+
| `socketio` | `DISABLE_SOCKET=false` |
|
|
158
|
+
| `bullmq` | `DISABLE_BULLMQ=false` + `QUEUES=...` |
|
|
159
|
+
| `scheduler` | `DISABLE_SCHEDULER=false` |
|
|
160
|
+
| `events` | `DISABLE_EVENTS=false` |
|
|
161
|
+
| `vite` | `VITE_ROOT` + `DISABLE_VITE=false` |
|
|
162
|
+
| `auth` | manual registration (requires `sqlite`/`postgresql`) |
|
|
163
|
+
|
|
164
|
+
## Security
|
|
165
|
+
|
|
166
|
+
Zyket bakes in several defaults (random per-project `AUTH_SECRET`, fail-closed BullBoard, configurable payload limits, path-traversal guards on storage). Review **[SECURITY.md](SECURITY.md)** before going to production — notably rate limiting and `helmet` are not bundled.
|
|
167
|
+
|
|
168
|
+
## Tooling for AI agents
|
|
169
|
+
|
|
170
|
+
- **[AGENTS.md](AGENTS.md)** — full framework reference.
|
|
171
|
+
- **Claude Code plugin** (`plugin/`) — skills to scaffold and use Zyket (`generate`, `build`).
|
|
172
|
+
|
|
173
|
+
## Contributing
|
|
174
|
+
|
|
175
|
+
Issues and pull requests are welcome — bug fixes, features, and documentation improvements. Let's build a better framework together. 🚀
|
package/bin/cli.js
CHANGED
|
@@ -118,21 +118,49 @@ function depsForTemplate(template) {
|
|
|
118
118
|
return deps;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// `node-dependency-injection` loads @typescript-eslint/typescript-estree@5 at
|
|
122
|
+
// require time, which reads `ts.SyntaxKind` off whatever `typescript` npm
|
|
123
|
+
// hoisted. TypeScript 7 dropped that CJS shape, so an unpinned tree dies on
|
|
124
|
+
// boot with "Cannot read properties of undefined (reading 'BarBarToken')".
|
|
125
|
+
// zyket depends on typescript ^5 for this reason; the override is what keeps an
|
|
126
|
+
// already-resolved lockfile from pulling 7.x back in.
|
|
127
|
+
const TYPESCRIPT_PIN = ZYKET_PKG.dependencies?.typescript || '^5.9.3';
|
|
128
|
+
|
|
129
|
+
function projectName() {
|
|
130
|
+
const raw = path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[._-]+/, '');
|
|
131
|
+
return raw || 'zyket-app';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// `npm install zyket` already leaves a package.json behind, so merging into an
|
|
135
|
+
// existing file is the normal path, not the exception. Only fill in what's
|
|
136
|
+
// missing — never clobber versions or scripts the user already chose.
|
|
121
137
|
function ensurePackageJson(template) {
|
|
122
138
|
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
let packageJson = {};
|
|
140
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
141
|
+
try {
|
|
142
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) || {};
|
|
143
|
+
} catch {
|
|
144
|
+
console.log('[ZYKET] package.json is not valid JSON — leaving it untouched.');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const defaults = {
|
|
149
|
+
name: projectName(),
|
|
126
150
|
version: '1.0.0',
|
|
127
151
|
description: 'Zyket application',
|
|
128
152
|
main: 'index.js',
|
|
129
|
-
scripts: { dev: 'node index.js' },
|
|
130
153
|
keywords: [],
|
|
131
154
|
author: '',
|
|
132
155
|
license: 'ISC',
|
|
133
|
-
dependencies: depsForTemplate(template),
|
|
134
156
|
};
|
|
135
|
-
|
|
157
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
158
|
+
if (packageJson[key] === undefined) packageJson[key] = value;
|
|
159
|
+
}
|
|
160
|
+
packageJson.scripts = { dev: 'node index.js', ...packageJson.scripts };
|
|
161
|
+
packageJson.dependencies = { ...depsForTemplate(template), ...packageJson.dependencies };
|
|
162
|
+
packageJson.overrides = { typescript: TYPESCRIPT_PIN, ...packageJson.overrides };
|
|
163
|
+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
136
164
|
}
|
|
137
165
|
|
|
138
166
|
function npmInstall() {
|
|
@@ -257,7 +285,7 @@ function npmInstall() {
|
|
|
257
285
|
// Templates that ship auth need their tables created first.
|
|
258
286
|
const usesAuth = fs.existsSync(path.join(process.cwd(), 'src', 'services', 'auth'));
|
|
259
287
|
console.log('\nNext steps:');
|
|
260
|
-
if (usesAuth) console.log(' npx @better-auth/cli migrate # create the auth tables');
|
|
288
|
+
if (usesAuth) console.log(' npx @better-auth/cli migrate --config src/services/auth/auth.js # create the auth tables');
|
|
261
289
|
console.log(' node index.js\n');
|
|
262
290
|
if (fs.existsSync(path.join(process.cwd(), 'README.md'))) {
|
|
263
291
|
console.log('See README.md for template-specific details.\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zyket",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.21",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -13,46 +13,48 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"description": "",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@bull-board/express": "^
|
|
17
|
-
"@tailwindcss/vite": "^4.
|
|
18
|
-
"@vitejs/plugin-react": "^6.0.
|
|
19
|
-
"better-auth": "^1.
|
|
16
|
+
"@bull-board/express": "^8.3.0",
|
|
17
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
18
|
+
"@vitejs/plugin-react": "^6.0.4",
|
|
19
|
+
"better-auth": "^1.6.25",
|
|
20
20
|
"better-sqlite3": "^12.8.0",
|
|
21
|
-
"bullmq": "^5.
|
|
21
|
+
"bullmq": "^5.81.2",
|
|
22
22
|
"colors": "^1.4.0",
|
|
23
23
|
"cors": "^2.8.6",
|
|
24
|
-
"dotenv": "^17.
|
|
24
|
+
"dotenv": "^17.4.2",
|
|
25
25
|
"express": "^5.2.1",
|
|
26
26
|
"express-basic-auth": "^1.2.1",
|
|
27
27
|
"fast-glob": "^3.3.3",
|
|
28
|
-
"mariadb": "^3.5.
|
|
28
|
+
"mariadb": "^3.5.3",
|
|
29
29
|
"minio": "^8.0.7",
|
|
30
|
-
"multer": "^2.
|
|
31
|
-
"node-cron": "^4.
|
|
32
|
-
"node-dependency-injection": "^
|
|
33
|
-
"pg": "^8.
|
|
30
|
+
"multer": "^2.2.0",
|
|
31
|
+
"node-cron": "^4.6.0",
|
|
32
|
+
"node-dependency-injection": "^4.2.2",
|
|
33
|
+
"pg": "^8.22.0",
|
|
34
34
|
"prompts": "^2.4.2",
|
|
35
35
|
"prop-types": "^15.8.1",
|
|
36
|
-
"react": "^19.2.
|
|
37
|
-
"react-dom": "^19.2.
|
|
38
|
-
"react-router-dom": "^7.
|
|
39
|
-
"redis": "^
|
|
36
|
+
"react": "^19.2.8",
|
|
37
|
+
"react-dom": "^19.2.8",
|
|
38
|
+
"react-router-dom": "^7.18.1",
|
|
39
|
+
"redis": "^6.1.0",
|
|
40
40
|
"sequelize": "^6.37.8",
|
|
41
41
|
"socket.io": "^4.8.3",
|
|
42
42
|
"socket.io-client": "^4.8.3",
|
|
43
43
|
"sqlite3": "^6.0.1",
|
|
44
|
-
"swagger-jsdoc": "^6.
|
|
44
|
+
"swagger-jsdoc": "^6.3.0",
|
|
45
45
|
"swagger-ui-express": "^5.0.1",
|
|
46
|
-
"tailwindcss": "^4.
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
46
|
+
"tailwindcss": "^4.3.3",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"umzug": "^3.8.3",
|
|
49
|
+
"vite": "^8.1.5",
|
|
50
|
+
"zustand": "^5.0.14"
|
|
50
51
|
},
|
|
51
52
|
"optionalDependencies": {
|
|
52
53
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
53
|
-
"ioredis": "^5.
|
|
54
|
+
"ioredis": "^5.11.1"
|
|
54
55
|
},
|
|
55
56
|
"overrides": {
|
|
56
|
-
"ajv": "^8.17.1"
|
|
57
|
+
"ajv": "^8.17.1",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
57
59
|
}
|
|
58
60
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zyket",
|
|
3
|
-
"description": "Claude Code plugin for the Zyket framework
|
|
4
|
-
"version": "1.
|
|
3
|
+
"description": "Claude Code plugin for the Zyket framework. `build`: start, configure, and run a Zyket app (templates, services, auth, security). `generate`: scaffold handlers, guards, routes, middlewares, services, events, schedulers, workers, and extensions.",
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "OnchainTech"
|
|
7
7
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build
|
|
3
|
+
description: Build, configure, and run applications with the Zyket framework — start a project from a template, enable services, wire authentication, secure the app, and add features. Use whenever the user wants to use Zyket, create or set up a Zyket project/app, or understand how Zyket works. For scaffolding a single component, use the `generate` skill instead.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Build with Zyket
|
|
8
|
+
|
|
9
|
+
Use this skill to take a user from zero to a running Zyket application, or to extend an existing one. Zyket is a service-oriented Node.js framework (Express 5 + Socket.IO + better-auth + Sequelize/BullMQ/MinIO) with filesystem auto-discovery and a DI container.
|
|
10
|
+
|
|
11
|
+
## Golden rules
|
|
12
|
+
|
|
13
|
+
1. **Read the reference.** After install, the full, authoritative API lives in `node_modules/zyket/AGENTS.md`. Read it before relying on any API; confirm base classes against `node_modules/zyket/` source. Do not invent APIs.
|
|
14
|
+
2. **Filename = identity.** Routes, handlers, guards, events, schedulers, workers, and models are auto-discovered from `src/<folder>/`; the filename determines the URL/event/name. Use kebab-case and `module.exports = class extends Base`. Never add constructor params to these.
|
|
15
|
+
3. **Services activate from `.env`.** A feature that needs sockets/queues/db must have the matching `DISABLE_*=false` / `DATABASE_URL` set.
|
|
16
|
+
4. **Secure by default.** Protect non-public routes/sockets with `RequireAuthMiddleware` / `AuthGuard`. Never weaken cookie/auth settings to "make it work" — use `AUTH_CROSS_DOMAIN` instead.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
### A. Starting a new project
|
|
21
|
+
|
|
22
|
+
1. **Pick a template** by intent:
|
|
23
|
+
|
|
24
|
+
| User wants… | Template |
|
|
25
|
+
|-------------|----------|
|
|
26
|
+
| A clean REST API | `api-rest` |
|
|
27
|
+
| Multi-tenant SaaS (orgs, roles, dashboard) | `saas-multitenant` |
|
|
28
|
+
| Real-time / chat / live updates | `realtime-chat` |
|
|
29
|
+
| A general starter / unsure | `default` |
|
|
30
|
+
|
|
31
|
+
2. **Scaffold:**
|
|
32
|
+
```bash
|
|
33
|
+
npm install zyket
|
|
34
|
+
npx zyket init <template>
|
|
35
|
+
```
|
|
36
|
+
This copies the template, merges its `.env.example` onto sensible defaults, writes `package.json` with the right dependencies, and installs them.
|
|
37
|
+
|
|
38
|
+
3. **If the template uses auth** (`src/services/auth` exists), create the tables once:
|
|
39
|
+
```bash
|
|
40
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
4. **Run:** `node index.js`. The API is on `PORT` (3000); a frontend, if any, on `VITE_PORT` (5173).
|
|
44
|
+
|
|
45
|
+
### B. Enabling a capability in an existing project
|
|
46
|
+
|
|
47
|
+
Flip the env flag, then add the component. Confirm details in `AGENTS.md §3–4`.
|
|
48
|
+
|
|
49
|
+
| Capability | `.env` | Then add |
|
|
50
|
+
|-----------|--------|----------|
|
|
51
|
+
| HTTP routes | `DISABLE_EXPRESS=false` | `src/routes/**.js` |
|
|
52
|
+
| Real-time | `DISABLE_SOCKET=false` | `src/handlers/*.js`, `src/guards/*.js` |
|
|
53
|
+
| Database | `DATABASE_URL=...`, `DATABASE_DIALECT=...` | `src/models/*.js` |
|
|
54
|
+
| Background jobs | `DISABLE_BULLMQ=false`, `QUEUES=emails` | `src/workers/*.js` |
|
|
55
|
+
| Cron | `DISABLE_SCHEDULER=false` | `src/schedulers/*.js` |
|
|
56
|
+
| Cache | `CACHE_URL=redis://…` (or in-memory) | use `container.get('cache')` |
|
|
57
|
+
| Object storage | `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY` | use `container.get('s3')` |
|
|
58
|
+
| Frontend | `DISABLE_VITE=false`, `VITE_ROOT=./frontend` | `frontend/` React app |
|
|
59
|
+
|
|
60
|
+
### C. Adding authentication
|
|
61
|
+
|
|
62
|
+
1. Register the auth service in `index.js`: `["auth", require("./src/services/auth"), ["@service_container"]]`.
|
|
63
|
+
2. Subclass `AuthService` (see `AGENTS.md §6`) to set `organizationEnabled`, `requireEmailVerification`, email senders, etc.
|
|
64
|
+
3. Require `DATABASE_DIALECT` = `sqlite` or `postgresql`; run `npx @better-auth/cli migrate --config src/services/auth/auth.js`.
|
|
65
|
+
4. Protect routes with `new RequireAuthMiddleware()` (or `['admin']` / `RequireAdminMiddleware`) and sockets with the `AuthGuard` (`module.exports = require('zyket').AuthGuard` in `src/guards/auth.js`; enforce connect-time auth inside `src/handlers/connection.js` via `container.get('auth').getSession(socket.handshake.headers)`).
|
|
66
|
+
|
|
67
|
+
### D. Building a feature (recipe)
|
|
68
|
+
|
|
69
|
+
1. Model → `src/models/Thing.js` (Sequelize factory). Create tables with `container.get('database').sync()` after boot, or migrations.
|
|
70
|
+
2. Routes → `src/routes/things/index.js` (GET/POST) and `src/routes/things/[id].js` (GET/PUT/DELETE), gated by `RequireAuthMiddleware`.
|
|
71
|
+
3. Read data via `container.get('database').models.Thing`.
|
|
72
|
+
4. Optional realtime/jobs per the table in B.
|
|
73
|
+
5. Run and verify.
|
|
74
|
+
|
|
75
|
+
Delegate individual component scaffolding to the **`generate`** skill (e.g. `/zyket:generate route things/[id]`).
|
|
76
|
+
|
|
77
|
+
## Common pitfalls
|
|
78
|
+
|
|
79
|
+
- **PATCH routes don't dispatch** — only `get/post/put/delete` methods are wired.
|
|
80
|
+
- **Connection guards don't block** — do connect-time auth inside `connection.js` (throw to disconnect).
|
|
81
|
+
- **`bullmq`/`scheduler`/`events` default ON when the `DISABLE_*` var is absent**, but the generated `.env` ships them as `true`. Set them to `false` explicitly to enable.
|
|
82
|
+
- **Auth fails to boot** if `AUTH_SECRET` is missing or a known placeholder — let the framework generate it (it lands in `.env` when the file is created).
|
|
83
|
+
- **`better-auth migrate` says "No configuration file found"** — the CLI only auto-discovers `auth.js` under `src/{auth,lib,server,utils}/`, never under `src/services/`. Always pass `--config src/services/auth/auth.js`.
|
|
84
|
+
- **Frontend can't resolve `vite`/`react`/`better-auth`** — the project (not just zyket) must declare those deps; `npx zyket init` does this automatically for templates with a frontend.
|
|
85
|
+
- **Boot crashes with `Cannot read properties of undefined (reading 'BarBarToken')`** — a TypeScript 7 got hoisted into the tree, which `node-dependency-injection`'s bundled `typescript-estree@5` can't read. Zyket pins `typescript` to `^5` and `npx zyket init` writes a matching `overrides` block; on a project predating that, add `"overrides": { "typescript": "^5.9.3" }` to `package.json` and reinstall.
|
|
86
|
+
|
|
87
|
+
## Security pass before shipping
|
|
88
|
+
|
|
89
|
+
Run through `SECURITY.md`. Minimum: every private route/socket guarded; `BULLBOARD_ADMIN_PASSWORD` set (or the dashboard won't mount); `SWAGGER_PASSWORD` or `DISABLE_SWAGGER=true`; `TRUSTED_ORIGINS` locked; add rate limiting + `helmet` (not bundled).
|
package/src/kernel/HTTPServer.js
CHANGED
|
@@ -19,6 +19,18 @@ module.exports = class HTTPServer {
|
|
|
19
19
|
}
|
|
20
20
|
console.log(`HTTP Server listening on port ${this.#port}`)
|
|
21
21
|
|
|
22
|
+
// The socket accepts connections from this point on, but Express only
|
|
23
|
+
// attaches its request listener once its service boots. Without a
|
|
24
|
+
// placeholder, anything arriving in between (health checks, an eager
|
|
25
|
+
// client) would be accepted and then never answered — the request hangs
|
|
26
|
+
// forever and the socket leaks. Answer 503 until Express takes over; it
|
|
27
|
+
// replaces this listener when it boots. With DISABLE_EXPRESS=true the
|
|
28
|
+
// placeholder stays, which is also the honest answer.
|
|
29
|
+
this.#server.on('request', (request, response) => {
|
|
30
|
+
response.writeHead(503, { 'Content-Type': 'application/json', 'Retry-After': '1' })
|
|
31
|
+
response.end(JSON.stringify({ success: false, message: 'Server is starting' }))
|
|
32
|
+
})
|
|
33
|
+
|
|
22
34
|
this.#server.listen(this.#port, () => {})
|
|
23
35
|
}
|
|
24
36
|
|
|
@@ -111,7 +111,9 @@ module.exports = class Express extends Service {
|
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
// Attach Express to HTTP server
|
|
114
|
+
// Attach Express to the HTTP server, replacing the kernel's "still
|
|
115
|
+
// booting" placeholder. This happens exactly once: the Express router is
|
|
116
|
+
// mutable, so routes registered later need no re-attachment.
|
|
115
117
|
this.#httpServer.removeAllListeners("request");
|
|
116
118
|
this.#httpServer.on("request", this.#app);
|
|
117
119
|
|
|
@@ -166,16 +168,13 @@ module.exports = class Express extends Service {
|
|
|
166
168
|
});
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
// No re-attachment: the app is already the server's request listener and
|
|
172
|
+
// its router picks up these routes immediately. Re-attaching would wipe
|
|
173
|
+
// engine.io's wrapper and kill the socket.io polling transport.
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
async regiterRawAllRoutes(path, handler) {
|
|
175
177
|
this.#app.all(path, handler);
|
|
176
|
-
|
|
177
|
-
this.#httpServer.removeAllListeners("request");
|
|
178
|
-
this.#httpServer.on("request", this.#app);
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
async #loadCorsOrCreateDefault() {
|
package/src/services/index.js
CHANGED
|
@@ -25,7 +25,12 @@ module.exports = [
|
|
|
25
25
|
s3Activated ? ["s3", S3, ["@service_container", process.env.S3_ENDPOINT, process.env.S3_PORT, process.env.S3_USE_SSL === "true", process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY, process.env.S3_PUBLIC_BUCKETS, process.env.S3_PRIVATE_BUCKETS]] : null,
|
|
26
26
|
schedulerActivated ? ["scheduler", Scheduler, ["@service_container"]] : null,
|
|
27
27
|
bullmqActivated ? ["bullmq", require("./bullmq"), ["@service_container"]] : null,
|
|
28
|
+
// Express must boot BEFORE socketio: engine.io's attach() wraps whatever
|
|
29
|
+
// "request" listener is already on the HTTP server and delegates non-socket
|
|
30
|
+
// requests to it. Booting it the other way round means Express replaces
|
|
31
|
+
// engine.io's listener and the polling transport (the default first hop for
|
|
32
|
+
// every socket.io client) breaks with "xhr poll error".
|
|
33
|
+
expressActivated ? ["express", Express, ["@service_container"]] : null,
|
|
28
34
|
socketActivated ? ["socketio", SocketIO, ["@service_container"]] : null,
|
|
29
35
|
viteActivated ? ["vite", require("./vite"), ["@service_container", process.env.VITE_ROOT, Number(process.env.VITE_PORT) || 5173]] : null,
|
|
30
|
-
expressActivated ? ["express", Express, ["@service_container"]] : null,
|
|
31
36
|
].filter(Boolean);
|
|
@@ -24,7 +24,7 @@ Auth endpoints are mounted by better-auth under `/api/auth/*`.
|
|
|
24
24
|
```bash
|
|
25
25
|
npm install
|
|
26
26
|
# 1) Create the better-auth tables (uses src/services/auth/auth.js)
|
|
27
|
-
npx @better-auth/cli migrate
|
|
27
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js
|
|
28
28
|
# 2) Run
|
|
29
29
|
node index.js
|
|
30
30
|
```
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Your auth configuration for the better-auth CLI. The CLI does not scan
|
|
2
|
+
// src/services/, so point it here explicitly:
|
|
3
|
+
// npx @better-auth/cli generate|migrate --config src/services/auth/auth.js
|
|
3
4
|
const AuthService = require('./index.js');
|
|
4
5
|
|
|
5
6
|
const auth = new AuthService({
|
|
@@ -14,7 +14,7 @@ connect or send messages.
|
|
|
14
14
|
## Setup
|
|
15
15
|
```bash
|
|
16
16
|
npm install
|
|
17
|
-
npx @better-auth/cli migrate
|
|
17
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js
|
|
18
18
|
node index.js
|
|
19
19
|
```
|
|
20
20
|
This starts the API + websocket server on `:3000` and the Vite frontend on `:5173`.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Your auth configuration for the better-auth CLI. The CLI does not scan
|
|
2
|
+
// src/services/, so point it here explicitly:
|
|
3
|
+
// npx @better-auth/cli generate|migrate --config src/services/auth/auth.js
|
|
2
4
|
const AuthService = require('./index.js');
|
|
3
5
|
|
|
4
6
|
const auth = new AuthService({
|
|
@@ -26,7 +26,7 @@ routes, not on re-implementing what better-auth already exposes.
|
|
|
26
26
|
## Setup
|
|
27
27
|
```bash
|
|
28
28
|
npm install
|
|
29
|
-
npx @better-auth/cli migrate # creates user/session/organization/member tables
|
|
29
|
+
npx @better-auth/cli migrate --config src/services/auth/auth.js # creates user/session/organization/member tables
|
|
30
30
|
node index.js
|
|
31
31
|
```
|
|
32
32
|
This starts the API on `:3000` and the Vite dashboard on `:5173`. Open
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Your auth configuration for the better-auth CLI. The CLI does not scan
|
|
2
|
+
// src/services/, so point it here explicitly:
|
|
3
|
+
// npx @better-auth/cli generate|migrate --config src/services/auth/auth.js
|
|
2
4
|
const AuthService = require('./index.js');
|
|
3
5
|
|
|
4
6
|
const auth = new AuthService({
|
package/src/utils/EnvManager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
|
|
3
4
|
module.exports = class EnvManager {
|
|
4
5
|
static load(secretsPath) {
|
|
@@ -43,6 +44,10 @@ module.exports = class EnvManager {
|
|
|
43
44
|
VITE_ROOT: './frontend',
|
|
44
45
|
VITE_PORT: 5173,
|
|
45
46
|
DISABLE_VITE: true,
|
|
47
|
+
// Generated here, not on first boot: the better-auth CLI reads .env when
|
|
48
|
+
// it loads your auth config, so `migrate` has to work before the app has
|
|
49
|
+
// ever run. A per-project random value, never a shared placeholder.
|
|
50
|
+
AUTH_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
return header + Object.entries(envsToCreate).reduce((acc, [key, value]) => {
|