bunigniter 0.2.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/dist/LICENSE +21 -0
  4. package/dist/README.md +229 -0
  5. package/dist/base/controller.ts +324 -0
  6. package/dist/base/index.ts +5 -0
  7. package/dist/base/service.ts +21 -0
  8. package/dist/cli/index.ts +318 -0
  9. package/dist/cli/list-routes.ts +72 -0
  10. package/dist/cli/repl.ts +461 -0
  11. package/dist/cli/templates.ts +283 -0
  12. package/dist/client/index.ts +159 -0
  13. package/dist/db/drizzle.ts +550 -0
  14. package/dist/db/validators.ts +229 -0
  15. package/dist/edge-builder.ts +120 -0
  16. package/dist/edge.ts +69 -0
  17. package/dist/helpers/cache.ts +173 -0
  18. package/dist/helpers/cors.ts +103 -0
  19. package/dist/helpers/csrf.ts +155 -0
  20. package/dist/helpers/debug.ts +158 -0
  21. package/dist/helpers/env.ts +147 -0
  22. package/dist/helpers/handler.ts +158 -0
  23. package/dist/helpers/http.ts +194 -0
  24. package/dist/helpers/image.ts +217 -0
  25. package/dist/helpers/jwt.ts +147 -0
  26. package/dist/helpers/logger.ts +96 -0
  27. package/dist/helpers/mail.ts +272 -0
  28. package/dist/helpers/middleware-loader.ts +116 -0
  29. package/dist/helpers/middleware.ts +57 -0
  30. package/dist/helpers/modules.ts +115 -0
  31. package/dist/helpers/openapi.ts +140 -0
  32. package/dist/helpers/pagination.ts +159 -0
  33. package/dist/helpers/queue.ts +186 -0
  34. package/dist/helpers/request-context.ts +13 -0
  35. package/dist/helpers/request.ts +376 -0
  36. package/dist/helpers/schedule.ts +173 -0
  37. package/dist/helpers/session-middleware.ts +89 -0
  38. package/dist/helpers/session.ts +286 -0
  39. package/dist/helpers/sse.ts +90 -0
  40. package/dist/helpers/throttle.ts +156 -0
  41. package/dist/helpers/upload.ts +417 -0
  42. package/dist/helpers/validator.ts +287 -0
  43. package/dist/helpers/ws.ts +123 -0
  44. package/dist/index.ts +221 -0
  45. package/dist/package.json +70 -0
  46. package/dist/router/file-router.ts +541 -0
  47. package/dist/router/server-router.ts +103 -0
  48. package/dist/view/page.ts +96 -0
  49. package/dist/view/renderer.tsx +390 -0
  50. package/dist/view/view-response.ts +10 -0
  51. package/package.json +70 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Request — CodeIgniter-style request proxy for Bunigniter.
3
+ *
4
+ * Wraps Elysia's Context to provide a familiar input API
5
+ * inspired by CodeIgniter 3/4, Laravel, and AdonisJS.
6
+ *
7
+ * Usage in a Controller:
8
+ * ```ts
9
+ * this.request.input('name', 'guest')
10
+ * this.request.only(['name', 'email'])
11
+ * this.request.has('name')
12
+ * this.request.isAjax()
13
+ * this.request.ip()
14
+ * ```
15
+ */
16
+ import type { Context } from "elysia";
17
+
18
+ /** Dot-notation access to nested object properties. */
19
+ function getFrom(obj: any, key: string, defaultValue?: any): any {
20
+ if (!key) return obj;
21
+ const keys = key.split(".");
22
+ let val = obj;
23
+ for (const k of keys) {
24
+ if (val === null || val === undefined) return defaultValue;
25
+ val = val[k];
26
+ }
27
+ return val !== undefined ? val : defaultValue;
28
+ }
29
+
30
+ /**
31
+ * Request proxy — wraps Elysia Context.
32
+ *
33
+ * Created fresh per-request inside the Controller `request` getter.
34
+ * Does not cache anything; delegates to Context properties directly.
35
+ */
36
+ export class RequestProxy {
37
+ constructor(private ctx: Context) {}
38
+
39
+ // ─── Phase 1: MVP ─────────────────────────────────────────
40
+
41
+ /**
42
+ * Retrieve an input item from the request (POST + GET merged).
43
+ *
44
+ * POST takes priority over GET. Supports dot-notation for nested keys.
45
+ *
46
+ * @param key - Field name or dot-notation path. Omitting returns all input.
47
+ * @param defaultValue - Value to return if the key is not present.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * this.request.input('name', 'guest')
52
+ * this.request.input('user.email')
53
+ * this.request.input() // → { name: '...', page: '1' }
54
+ * ```
55
+ */
56
+ input(key?: string, defaultValue?: any): any {
57
+ if (key === undefined) {
58
+ return { ...(this.ctx.body ?? {}), ...(this.ctx.query ?? {}) };
59
+ }
60
+ const body = this.ctx.body ?? {};
61
+ const query = this.ctx.query ?? {};
62
+ const fromBody = getFrom(body, key);
63
+ return fromBody !== undefined
64
+ ? fromBody
65
+ : getFrom(query, key, defaultValue);
66
+ }
67
+
68
+ /**
69
+ * Retrieve a query string item (GET).
70
+ *
71
+ * @param key - Field name or dot-notation path. Omitting returns all query params.
72
+ * @param defaultValue - Value to return if the key is not present.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * this.request.get('page', 1)
77
+ * this.request.get() // → { page: '1', limit: '10' }
78
+ * ```
79
+ */
80
+ get(key?: string, defaultValue?: any): any {
81
+ if (key === undefined) return this.ctx.query;
82
+ return getFrom(this.ctx.query, key, defaultValue);
83
+ }
84
+
85
+ /**
86
+ * Retrieve a POST / body item.
87
+ *
88
+ * @param key - Field name or dot-notation path. Omitting returns full body.
89
+ * @param defaultValue - Value to return if the key is not present.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * this.request.post('name')
94
+ * this.request.post('user.email')
95
+ * this.request.post() // → { name: '...', email: '...' }
96
+ * ```
97
+ */
98
+ post(key?: string, defaultValue?: any): any {
99
+ if (key === undefined) return this.ctx.body;
100
+ return getFrom(this.ctx.body, key, defaultValue);
101
+ }
102
+
103
+ /**
104
+ * Retrieve only the specified keys from input (mass-assignment protection).
105
+ *
106
+ * @param keys - Array of field names to extract.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * this.request.only(['name', 'email'])
111
+ * // → { name: '...', email: '...' }
112
+ * ```
113
+ */
114
+ only(keys: string[]): Record<string, any> {
115
+ const result: Record<string, any> = {};
116
+ const data = { ...(this.ctx.body ?? {}), ...(this.ctx.query ?? {}) };
117
+ for (const key of keys) {
118
+ result[key] = getFrom(data, key);
119
+ }
120
+ return result;
121
+ }
122
+
123
+ /**
124
+ * Determine if the request contains a given key.
125
+ *
126
+ * @param key - Field name to check.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * if (this.request.has('email')) { ... }
131
+ * ```
132
+ */
133
+ has(key: string): boolean {
134
+ const data = { ...(this.ctx.body ?? {}), ...(this.ctx.query ?? {}) };
135
+ return getFrom(data, key) !== undefined;
136
+ }
137
+
138
+ /**
139
+ * Determine if the request contains a non-empty value for a given key.
140
+ *
141
+ * @param key - Field name to check.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * if (this.request.filled('name')) { ... }
146
+ * ```
147
+ */
148
+ filled(key: string): boolean {
149
+ const data = { ...(this.ctx.body ?? {}), ...(this.ctx.query ?? {}) };
150
+ const val = getFrom(data, key);
151
+ return val !== undefined && val !== null && val !== "";
152
+ }
153
+
154
+ /**
155
+ * Get the HTTP method of the request (uppercase).
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * if (this.request.method() === 'POST') { ... }
160
+ * ```
161
+ */
162
+ method(): string {
163
+ return this.ctx.request.method;
164
+ }
165
+
166
+ /**
167
+ * Determine if the request is an AJAX request.
168
+ *
169
+ * Checks the `X-Requested-With` header.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * if (this.request.isAjax()) { return this.json(data) }
174
+ * ```
175
+ */
176
+ isAjax(): boolean {
177
+ const header = this.ctx.headers["x-requested-with"];
178
+ return (
179
+ typeof header === "string" && header.toLowerCase() === "xmlhttprequest"
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Get the client IP address.
185
+ *
186
+ * Uses Bun's `server.requestIP()` when available.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const ip = this.request.ip()
191
+ * ```
192
+ */
193
+ ip(): string | undefined {
194
+ try {
195
+ const server = this.ctx.server as any;
196
+ if (typeof server?.requestIP === "function") {
197
+ const info = server.requestIP(this.ctx.request);
198
+ return info?.address;
199
+ }
200
+ } catch {
201
+ // Silently fall through
202
+ }
203
+ return undefined;
204
+ }
205
+
206
+ // ─── Phase 2: Productivity ────────────────────────────────
207
+
208
+ /**
209
+ * Retrieve input as a boolean value.
210
+ *
211
+ * Returns `true` for: `'true'`, `'1'`, `'yes'`, `'on'`, `true`.
212
+ *
213
+ * @param key - Field name.
214
+ * @param defaultValue - Default when missing (default: `false`).
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * this.request.boolean('active') // true / false
219
+ * this.request.boolean('subscribe', true)
220
+ * ```
221
+ */
222
+ boolean(key: string, defaultValue: boolean = false): boolean {
223
+ const val = this.input(key);
224
+ if (val === undefined || val === null) return defaultValue;
225
+ if (typeof val === "boolean") return val;
226
+ return ["true", "1", "yes", "on"].includes(String(val).toLowerCase());
227
+ }
228
+
229
+ /**
230
+ * Retrieve input as an integer value.
231
+ *
232
+ * @param key - Field name.
233
+ * @param defaultValue - Default when missing or NaN (default: `0`).
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * this.request.integer('age') // 25
238
+ * this.request.integer('page', 1) // 1 (default)
239
+ * ```
240
+ */
241
+ integer(key: string, defaultValue: number = 0): number {
242
+ const val = this.input(key);
243
+ if (val === undefined || val === null) return defaultValue;
244
+ const num = Number(val);
245
+ return Number.isNaN(num) ? defaultValue : Math.floor(num);
246
+ }
247
+
248
+ /**
249
+ * Retrieve the JSON body (or a specific key from it).
250
+ *
251
+ * Supports dot-notation for nested JSON access.
252
+ *
253
+ * @param key - Dot-notation key path. Omitting returns the full parsed body.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * this.request.json() // { user: { name: 'Alice' } }
258
+ * this.request.json('user.name') // 'Alice'
259
+ * ```
260
+ */
261
+ json(key?: string): any {
262
+ const body = this.ctx.body;
263
+ if (!body || typeof body !== "object") return undefined;
264
+ if (key === undefined) return body;
265
+ return getFrom(body, key);
266
+ }
267
+
268
+ /**
269
+ * Extract the Bearer token from the Authorization header.
270
+ *
271
+ * @returns The token string, or `null` if not present.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * const token = this.request.bearerToken()
276
+ * ```
277
+ */
278
+ bearerToken(): string | null {
279
+ const auth = this.ctx.headers["authorization"];
280
+ if (!auth || typeof auth !== "string") return null;
281
+ const match = auth.match(/^Bearer\s+(.+)$/i);
282
+ return match ? match[1] : null;
283
+ }
284
+
285
+ /**
286
+ * Get the User-Agent string.
287
+ *
288
+ * @returns The User-Agent string, or empty string if not set.
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * const ua = this.request.userAgent()
293
+ * ```
294
+ */
295
+ userAgent(): string {
296
+ const ua = this.ctx.headers["user-agent"];
297
+ return typeof ua === "string" ? ua : "";
298
+ }
299
+
300
+ /**
301
+ * Retrieve a cookie value.
302
+ *
303
+ * @param key - Cookie name.
304
+ * @param defaultValue - Value to return if the cookie is not present.
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * const theme = this.request.cookie('theme', 'light')
309
+ * ```
310
+ */
311
+ cookie(key: string, defaultValue?: string): string | undefined {
312
+ const cookieObj = this.ctx.cookie?.[key];
313
+ if (!cookieObj) return defaultValue;
314
+ const val = cookieObj.value;
315
+ return val !== undefined && val !== null ? String(val) : defaultValue;
316
+ }
317
+
318
+ /**
319
+ * Retrieve a server / environment variable.
320
+ *
321
+ * Emulates PHP's `$_SERVER` using available Elysia/Bun APIs.
322
+ * Handles common keys: `REMOTE_ADDR`, `REQUEST_METHOD`,
323
+ * `HTTP_USER_AGENT`, `SERVER_NAME`, `SERVER_PORT`,
324
+ * `REQUEST_URI`, `QUERY_STRING`, and `HTTP_*` headers.
325
+ *
326
+ * @param key - Server variable name.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * this.request.server('REMOTE_ADDR') // '::1'
331
+ * this.request.server('REQUEST_METHOD') // 'POST'
332
+ * ```
333
+ */
334
+ server(key: string): string | undefined {
335
+ switch (key) {
336
+ case "REMOTE_ADDR":
337
+ return this.ip();
338
+ case "REQUEST_METHOD":
339
+ return this.method();
340
+ case "HTTP_USER_AGENT":
341
+ return this.userAgent();
342
+ case "SERVER_NAME": {
343
+ try {
344
+ return new URL(this.ctx.request.url).hostname;
345
+ } catch {
346
+ return undefined;
347
+ }
348
+ }
349
+ case "SERVER_PORT": {
350
+ try {
351
+ return String(new URL(this.ctx.request.url).port || "80");
352
+ } catch {
353
+ return undefined;
354
+ }
355
+ }
356
+ case "REQUEST_URI":
357
+ return this.ctx.request.url;
358
+ case "QUERY_STRING": {
359
+ try {
360
+ return new URL(this.ctx.request.url).search.replace(/^\?/, "");
361
+ } catch {
362
+ return undefined;
363
+ }
364
+ }
365
+ default: {
366
+ // Handle HTTP_* keys → map to headers
367
+ if (key.startsWith("HTTP_")) {
368
+ const headerKey = key.slice(5).replace(/_/g, "-").toLowerCase();
369
+ const header = this.ctx.headers[headerKey];
370
+ return typeof header === "string" ? header : undefined;
371
+ }
372
+ return undefined;
373
+ }
374
+ }
375
+ }
376
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Schedule — cron-like task scheduler for periodic jobs.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * // routes/schedule.ts
7
+ * import { schedule } from 'bunigniter/helpers/schedule'
8
+ *
9
+ * // Run every 5 seconds
10
+ * schedule.every(5000).do(async () => {
11
+ * console.log('5 seconds elapsed')
12
+ * })
13
+ *
14
+ * // Run every minute (cron-style)
15
+ * schedule.cron('* * * * *').do(async () => {
16
+ * console.log('Every minute')
17
+ * })
18
+ *
19
+ * // Run once at startup then every hour
20
+ * schedule.every(3600000).do(async () => {
21
+ * await fetch('https://api.example.com/health')
22
+ * })
23
+ * ```
24
+ */
25
+ export interface ScheduleTask {
26
+ name: string
27
+ interval: number
28
+ fn: () => Promise<void> | void
29
+ running: boolean
30
+ timer?: Timer
31
+ }
32
+
33
+ const tasks: ScheduleTask[] = []
34
+ let taskIdCounter = 0
35
+
36
+ class ScheduleBuilder {
37
+ private _interval: number
38
+ private _name: string
39
+
40
+ constructor(interval: number, name?: string) {
41
+ this._interval = interval
42
+ this._name = name ?? `task_${++taskIdCounter}`
43
+ }
44
+
45
+ /** Register the task function and start the schedule. */
46
+ do(fn: () => Promise<void> | void): ScheduleTask {
47
+ const task: ScheduleTask = {
48
+ name: this._name,
49
+ interval: this._interval,
50
+ fn,
51
+ running: true,
52
+ }
53
+
54
+ // Execute immediately, then repeat
55
+ const run = async () => {
56
+ if (!task.running) return
57
+ try {
58
+ await fn()
59
+ } catch (err) {
60
+ console.error(`[schedule] ${task.name} error:`, err)
61
+ }
62
+ }
63
+
64
+ // First run after 1 tick (let app boot first)
65
+ setTimeout(() => {
66
+ run()
67
+ task.timer = setInterval(run, task.interval)
68
+ }, 100)
69
+
70
+ tasks.push(task)
71
+ console.log(`[schedule] ${task.name} — every ${formatInterval(task.interval)}`)
72
+ return task
73
+ }
74
+ }
75
+
76
+ /** Simple cron parser — supports 5-field cron expressions. */
77
+ function parseCron(expr: string): number[] {
78
+ const fields = expr.trim().split(/\s+/)
79
+ if (fields.length !== 5) throw new Error(`Invalid cron expression: "${expr}". Use 5 fields: minute hour day month weekday`)
80
+
81
+ const now = new Date()
82
+ const results: number[] = []
83
+
84
+ // Generate next N execution times (up to 10)
85
+ for (let attempt = 0; attempt < 10; attempt++) {
86
+ const target = new Date(now.getTime() + attempt * 60000)
87
+ const minute = target.getMinutes()
88
+ const hour = target.getHours()
89
+ const day = target.getDate()
90
+ const month = target.getMonth() + 1
91
+ const weekday = target.getDay()
92
+
93
+ if (matchField(fields[0], minute) &&
94
+ matchField(fields[1], hour) &&
95
+ matchField(fields[2], day) &&
96
+ matchField(fields[3], month) &&
97
+ matchField(fields[4], weekday)) {
98
+ results.push(target.getTime())
99
+ }
100
+ }
101
+
102
+ return results
103
+ }
104
+
105
+ function matchField(field: string, value: number): boolean {
106
+ if (field === '*') return true
107
+ if (field.includes(',')) return field.split(',').some(f => matchField(f.trim(), value))
108
+ if (field.includes('/')) {
109
+ const [, step] = field.split('/')
110
+ return value % Number(step) === 0
111
+ }
112
+ if (field.includes('-')) {
113
+ const [s, e] = field.split('-')
114
+ return value >= Number(s) && value <= Number(e)
115
+ }
116
+ return Number(field) === value
117
+ }
118
+
119
+ function formatInterval(ms: number): string {
120
+ if (ms < 1000) return `${ms}ms`
121
+ if (ms < 60000) return `${Math.round(ms / 1000)}s`
122
+ if (ms < 3600000) return `${Math.round(ms / 60000)}m`
123
+ return `${Math.round(ms / 3600000)}h`
124
+ }
125
+
126
+ export const schedule = {
127
+ /** Run every N milliseconds. */
128
+ every(ms: number, name?: string): ScheduleBuilder {
129
+ return new ScheduleBuilder(ms, name)
130
+ },
131
+
132
+ /** Run on a cron schedule (5-field cron expression). */
133
+ cron(expr: string, name?: string): ScheduleBuilder {
134
+ // For MVP, treat cron as "every minute" with additional matching
135
+ return new ScheduleBuilder(60000, name ?? `cron_${expr.replace(/\s+/g, '_')}`)
136
+ },
137
+
138
+ /** Run at a specific time (HH:MM in local time). */
139
+ daily(time: string, name?: string): ScheduleBuilder {
140
+ const [h, m] = time.split(':').map(Number)
141
+ const now = new Date()
142
+ const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, m, 0)
143
+ const delay = target.getTime() - now.getTime()
144
+ const msUntilNext = delay > 0 ? delay : delay + 86400000
145
+ return new ScheduleBuilder(86400000, name ?? `daily_${time}`)
146
+ },
147
+
148
+ /** Stop all scheduled tasks. */
149
+ stopAll(): void {
150
+ for (const task of tasks) {
151
+ task.running = false
152
+ if (task.timer) clearInterval(task.timer)
153
+ }
154
+ tasks.length = 0
155
+ },
156
+
157
+ /** List all active tasks. */
158
+ list(): ScheduleTask[] {
159
+ return tasks.filter(t => t.running)
160
+ },
161
+
162
+ /** Stop a specific task by name. */
163
+ stop(name: string): void {
164
+ const task = tasks.find(t => t.name === name)
165
+ if (task) {
166
+ task.running = false
167
+ if (task.timer) clearInterval(task.timer)
168
+ }
169
+ },
170
+ }
171
+
172
+ export { formatInterval as _formatInterval }
173
+ function _formatInterval(ms: number): string { return formatInterval(ms) }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Session middleware — makes session available in Elysia context.
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * import { sessionMiddleware } from './helpers/session'
7
+ * app.use(sessionMiddleware({ key: env('APP_KEY') }))
8
+ * ```
9
+ *
10
+ * Then in a controller:
11
+ * ```ts
12
+ * this.session.set('user_id', 1)
13
+ * const uid = this.session.get('user_id')
14
+ * ```
15
+ */
16
+ import { Elysia } from 'elysia'
17
+ import { Session, type SessionConfig } from './session'
18
+
19
+ /**
20
+ * Create an Elysia plugin that injects session into the context.
21
+ */
22
+ export function sessionMiddleware(config?: SessionConfig) {
23
+ const app = new Elysia({ name: 'nexus-session' })
24
+
25
+ app.derive(async ({ request, cookie: cookieJar, set }: any) => {
26
+ const sessionConfig: SessionConfig = { ...config }
27
+ const session = new Session(sessionConfig)
28
+ const cookieName = sessionConfig.name ?? 'nexus_session'
29
+
30
+ // Load session from cookie
31
+ const rawCookie = cookieJar?.[cookieName]?.value ?? request.headers?.get('cookie')
32
+ ?.split(';')
33
+ ?.find((c: string) => c.trim().startsWith(cookieName + '='))
34
+ ?.split('=')[1]
35
+
36
+ session.load(rawCookie)
37
+
38
+ return { session }
39
+ })
40
+
41
+ // Save session cookie on response
42
+ app.afterResponse(async ({ session: sess, cookie: cookieJar, set }: any) => {
43
+ if (!sess) return
44
+ const serialized = sess.serialize()
45
+ if (!serialized) return
46
+
47
+ const cookieName = sess.cookieName
48
+ if (cookieJar?.[cookieName]) {
49
+ cookieJar[cookieName].value = serialized.value
50
+ cookieJar[cookieName].maxAge = serialized.maxAge
51
+ cookieJar[cookieName].path = serialized.options.path
52
+ cookieJar[cookieName].httpOnly = serialized.options.httpOnly
53
+ cookieJar[cookieName].secure = serialized.options.secure
54
+ cookieJar[cookieName].sameSite = serialized.options.sameSite
55
+ } else {
56
+ // Fall back to set-cookie header
57
+ set.headers ??= {}
58
+ set.headers['Set-Cookie'] = `${cookieName}=${serialized.value}; Max-Age=${serialized.maxAge}; Path=${serialized.options.path}; HttpOnly; SameSite=${serialized.options.sameSite}`
59
+ }
60
+ })
61
+
62
+ return app
63
+ }
64
+
65
+ /**
66
+ * Simple auth — stores user info in session.
67
+ */
68
+ export function authMiddleware() {
69
+ const app = new Elysia({ name: 'nexus-auth' })
70
+
71
+ app.derive(async ({ session }: any) => {
72
+ return {
73
+ auth: {
74
+ user: () => session?.get('user'),
75
+ login: (user: any) => {
76
+ session?.set('user', user)
77
+ session?.regenerate()
78
+ },
79
+ logout: () => {
80
+ session?.delete('user')
81
+ session?.clear()
82
+ },
83
+ check: () => !!session?.get('user'),
84
+ }
85
+ }
86
+ })
87
+
88
+ return app
89
+ }