create-mantiq 0.5.11 → 0.5.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mantiq",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Scaffold a new MantiqJS application",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,12 +1,57 @@
1
- APP_NAME=skeleton
1
+ # Application
2
+ APP_NAME=MantiqJS
2
3
  APP_ENV=local
3
4
  APP_DEBUG=true
5
+ APP_KEY=
4
6
  APP_URL=http://localhost:3000
5
7
  APP_PORT=3000
6
- APP_KEY=
7
8
 
9
+ # Database
8
10
  DB_CONNECTION=sqlite
9
11
  DB_DATABASE=database/database.sqlite
12
+ # DB_HOST=127.0.0.1
13
+ # DB_PORT=5432
14
+ # DB_USERNAME=postgres
15
+ # DB_PASSWORD=
10
16
 
11
- LOG_CHANNEL=stack
17
+ # Session
18
+ SESSION_DRIVER=memory
19
+ SESSION_LIFETIME=120
20
+ SESSION_COOKIE=mantiq_session
21
+
22
+ # Cache
23
+ CACHE_STORE=memory
24
+ # REDIS_HOST=127.0.0.1
25
+ # REDIS_PORT=6379
26
+ # REDIS_PASSWORD=
27
+
28
+ # Queue
12
29
  QUEUE_CONNECTION=sync
30
+
31
+ # Mail
32
+ MAIL_MAILER=log
33
+ MAIL_FROM_ADDRESS=hello@example.com
34
+ MAIL_FROM_NAME=${APP_NAME}
35
+ # MAIL_HOST=localhost
36
+ # MAIL_PORT=587
37
+ # MAIL_USERNAME=
38
+ # MAIL_PASSWORD=
39
+
40
+ # Logging
41
+ LOG_CHANNEL=stack
42
+
43
+ # Hashing
44
+ HASH_DRIVER=bcrypt
45
+ BCRYPT_ROUNDS=10
46
+
47
+ # Broadcasting
48
+ BROADCAST_DRIVER=bun
49
+
50
+ # Filesystem
51
+ FILESYSTEM_DISK=local
52
+
53
+ # Search
54
+ # ALGOLIA_APP_ID=
55
+ # ALGOLIA_SECRET=
56
+ # MEILISEARCH_HOST=http://127.0.0.1:7700
57
+ # MEILISEARCH_KEY=
File without changes
File without changes
@@ -0,0 +1,93 @@
1
+ import { env } from '@mantiq/core'
2
+
3
+ export default {
4
+
5
+ /*
6
+ |--------------------------------------------------------------------------
7
+ | Realtime Enabled
8
+ |--------------------------------------------------------------------------
9
+ |
10
+ | Master switch for the realtime server. When disabled, WebSocket and
11
+ | SSE endpoints are not registered and broadcast events are no-ops.
12
+ |
13
+ */
14
+ enabled: true,
15
+
16
+ /*
17
+ |--------------------------------------------------------------------------
18
+ | Broadcast Driver
19
+ |--------------------------------------------------------------------------
20
+ |
21
+ | The default broadcast driver for sending real-time events to clients.
22
+ | The 'bun' driver works in-process with zero dependencies. Use 'redis'
23
+ | for multi-server deployments.
24
+ |
25
+ | Supported: 'bun', 'redis', 'log', 'null'
26
+ |
27
+ */
28
+ driver: env('BROADCAST_DRIVER', 'bun'),
29
+
30
+ /*
31
+ |--------------------------------------------------------------------------
32
+ | WebSocket Server
33
+ |--------------------------------------------------------------------------
34
+ |
35
+ | Bun's native WebSocket server handles real-time connections. Clients
36
+ | connect to ws://host:port/<path>. The server sends heartbeat pings
37
+ | and closes idle connections after the timeout.
38
+ |
39
+ */
40
+ websocket: {
41
+ path: '/ws',
42
+ maxConnectionsPerUser: 10, // Per-user limit (0 = unlimited)
43
+ maxConnections: 0, // Total limit (0 = unlimited)
44
+ heartbeatInterval: 25_000, // Ping interval (ms)
45
+ heartbeatTimeout: 10_000, // Close if no pong after (ms)
46
+ },
47
+
48
+ /*
49
+ |--------------------------------------------------------------------------
50
+ | Server-Sent Events (SSE) Fallback
51
+ |--------------------------------------------------------------------------
52
+ |
53
+ | SSE provides a fallback for clients that can't use WebSockets
54
+ | (corporate proxies, older browsers). One-directional: server → client.
55
+ |
56
+ */
57
+ sse: {
58
+ enabled: true,
59
+ path: '/_sse',
60
+ keepAliveInterval: 15_000, // Keep-alive ping interval (ms)
61
+ },
62
+
63
+ /*
64
+ |--------------------------------------------------------------------------
65
+ | Presence Channels
66
+ |--------------------------------------------------------------------------
67
+ |
68
+ | Presence channels track which users are currently subscribed.
69
+ | memberTtl controls how long a disconnected user stays in the member
70
+ | list before being removed (handles brief network interruptions).
71
+ |
72
+ */
73
+ presence: {
74
+ memberTtl: 30_000, // Remove member after disconnect (ms)
75
+ },
76
+
77
+ /*
78
+ |--------------------------------------------------------------------------
79
+ | Redis Driver
80
+ |--------------------------------------------------------------------------
81
+ |
82
+ | When using the 'redis' broadcast driver, events are published to
83
+ | Redis pub/sub. This allows multiple server instances to broadcast
84
+ | to each other's connected clients.
85
+ |
86
+ */
87
+ redis: {
88
+ host: env('REDIS_HOST', '127.0.0.1'),
89
+ port: Number(env('REDIS_PORT', '6379')),
90
+ password: env('REDIS_PASSWORD', ''),
91
+ prefix: 'mantiq_realtime:',
92
+ },
93
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Broadcast Channels
3
+ *
4
+ * Define authorization callbacks for broadcast channels here.
5
+ * Private and presence channels require authentication.
6
+ *
7
+ * @example
8
+ * import { broadcast } from '@mantiq/core'
9
+ *
10
+ * // Private channel — only the owner can listen
11
+ * broadcast.channel('orders.{orderId}', (user, orderId) => {
12
+ * return user.id === Order.find(orderId)?.user_id
13
+ * })
14
+ *
15
+ * // Presence channel — returns user info for member tracking
16
+ * broadcast.channel('chat.{roomId}', (user, roomId) => {
17
+ * return { id: user.id, name: user.name }
18
+ * })
19
+ */
20
+
21
+ export default function () {
22
+ // Define your broadcast channel authorization here
23
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Console Routes
3
+ *
4
+ * Define scheduled tasks and console-only commands here.
5
+ * These are loaded automatically by the Discoverer.
6
+ *
7
+ * @example
8
+ * import { schedule } from '@mantiq/core'
9
+ *
10
+ * // Run every hour
11
+ * schedule.command('heartbeat:prune').hourly()
12
+ *
13
+ * // Run daily at midnight
14
+ * schedule.command('queue:prune-failed').daily()
15
+ *
16
+ * // Custom closure
17
+ * schedule.call(async () => {
18
+ * console.log('Running cleanup...')
19
+ * }).everyFiveMinutes()
20
+ */
21
+
22
+ export default function () {
23
+ // Define your scheduled tasks here
24
+ }
File without changes
package/src/templates.ts CHANGED
@@ -93,27 +93,68 @@ export function getTemplates(ctx: TemplateContext): Record<string, string> {
93
93
  }, null, 2) + '\n'
94
94
 
95
95
  // ── .env (dynamic — APP_KEY generated) ─────────────────────────────────
96
- templates['.env'] = `APP_NAME=${ctx.name}
96
+ const envBody = (appKey: string) => `# Application
97
+ APP_NAME=${ctx.name}
97
98
  APP_ENV=local
98
99
  APP_DEBUG=true
99
- APP_KEY=${ctx.appKey}
100
+ APP_KEY=${appKey}
100
101
  APP_URL=http://localhost:3000
102
+ APP_PORT=3000
101
103
 
104
+ # Database
102
105
  DB_CONNECTION=sqlite
103
106
  DB_DATABASE=database/database.sqlite
104
- ${ctx.kit ? '\nVITE_DEV_SERVER_URL=http://localhost:5173' : ''}
105
- `
107
+ # DB_HOST=127.0.0.1
108
+ # DB_PORT=5432
109
+ # DB_USERNAME=postgres
110
+ # DB_PASSWORD=
106
111
 
107
- templates['.env.example'] = `APP_NAME=${ctx.name}
108
- APP_ENV=local
109
- APP_DEBUG=true
110
- APP_KEY=
111
- APP_URL=http://localhost:3000
112
+ # Session
113
+ SESSION_DRIVER=memory
114
+ SESSION_LIFETIME=120
115
+ SESSION_COOKIE=mantiq_session
112
116
 
113
- DB_CONNECTION=sqlite
114
- DB_DATABASE=database/database.sqlite
115
- ${ctx.kit ? '\nVITE_DEV_SERVER_URL=http://localhost:5173' : ''}
117
+ # Cache
118
+ CACHE_STORE=memory
119
+ # REDIS_HOST=127.0.0.1
120
+ # REDIS_PORT=6379
121
+ # REDIS_PASSWORD=
122
+
123
+ # Queue
124
+ QUEUE_CONNECTION=sync
125
+
126
+ # Mail
127
+ MAIL_MAILER=log
128
+ MAIL_FROM_ADDRESS=hello@example.com
129
+ MAIL_FROM_NAME=\${APP_NAME}
130
+ # MAIL_HOST=localhost
131
+ # MAIL_PORT=587
132
+ # MAIL_USERNAME=
133
+ # MAIL_PASSWORD=
134
+
135
+ # Logging
136
+ LOG_CHANNEL=stack
137
+
138
+ # Hashing
139
+ HASH_DRIVER=bcrypt
140
+ BCRYPT_ROUNDS=10
141
+
142
+ # Broadcasting
143
+ BROADCAST_DRIVER=bun
144
+
145
+ # Filesystem
146
+ FILESYSTEM_DISK=local
147
+
148
+ # Search
149
+ # ALGOLIA_APP_ID=
150
+ # ALGOLIA_SECRET=
151
+ # MEILISEARCH_HOST=http://127.0.0.1:7700
152
+ # MEILISEARCH_KEY=
153
+ ${ctx.kit ? '\n# Vite\nVITE_DEV_SERVER_URL=http://localhost:5173' : ''}
116
154
  `
117
155
 
156
+ templates['.env'] = envBody(ctx.appKey)
157
+ templates['.env.example'] = envBody('')
158
+
118
159
  return templates
119
160
  }
package/src/terminal.ts CHANGED
@@ -33,6 +33,8 @@ export class Terminal {
33
33
  write(` ${EMERALD}●${R} ${BOLD}mantiq${R} ${GRAY}│${R} ${DIM}The Bun framework for artisans${R}\n`)
34
34
  write(`\n`)
35
35
  write(` ${GRAY}───────────────────────────────────────────────${R}\n`)
36
+ const version = require('../package.json').version
37
+ write(` ${DIM}v${version}${R}\n`)
36
38
  write('\n\n')
37
39
  }
38
40
 
@@ -1402,6 +1402,14 @@
1402
1402
  {
1403
1403
  "stub": "routes/web.ts.stub",
1404
1404
  "target": "routes/web.ts"
1405
+ },
1406
+ {
1407
+ "stub": "routes/api.ts.stub",
1408
+ "target": "routes/api.ts"
1409
+ },
1410
+ {
1411
+ "stub": "config/app.ts.stub",
1412
+ "target": "config/app.ts"
1405
1413
  }
1406
1414
  ],
1407
1415
  "placeholders": {
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from 'react'
2
+ import { post, put, del } from '@/lib/api'
2
3
  import {
3
4
  Dialog,
4
5
  DialogContent,
@@ -50,15 +51,8 @@ export function AddUserDialog({
50
51
  setSubmitting(true)
51
52
  setError('')
52
53
  try {
53
- const res = await fetch('/api/users', {
54
- method: 'POST',
55
- headers: { 'Content-Type': 'application/json' },
56
- body: JSON.stringify(form),
57
- })
58
- if (!res.ok) {
59
- const data = await res.json().catch(() => null)
60
- throw new Error(data?.message ?? `Request failed (${res.status})`)
61
- }
54
+ const { ok, data } = await post('/api/users', form)
55
+ if (!ok) throw new Error(data?.error ?? 'Request failed')
62
56
  reset()
63
57
  onOpenChange(false)
64
58
  onSuccess()
@@ -165,15 +159,8 @@ export function EditUserDialog({
165
159
  setSubmitting(true)
166
160
  setError('')
167
161
  try {
168
- const res = await fetch(`/api/users/${user.id}`, {
169
- method: 'PUT',
170
- headers: { 'Content-Type': 'application/json' },
171
- body: JSON.stringify(form),
172
- })
173
- if (!res.ok) {
174
- const data = await res.json().catch(() => null)
175
- throw new Error(data?.message ?? `Request failed (${res.status})`)
176
- }
162
+ const { ok, data } = await put(`/api/users/${user.id}`, form)
163
+ if (!ok) throw new Error(data?.error ?? 'Request failed')
177
164
  onOpenChange(false)
178
165
  onSuccess()
179
166
  } catch (err: any) {
@@ -252,14 +239,8 @@ export function DeleteUserDialog({
252
239
  setSubmitting(true)
253
240
  setError('')
254
241
  try {
255
- const res = await fetch(`/api/users/${user.id}`, {
256
- method: 'DELETE',
257
- headers: { 'Content-Type': 'application/json' },
258
- })
259
- if (!res.ok) {
260
- const data = await res.json().catch(() => null)
261
- throw new Error(data?.message ?? `Request failed (${res.status})`)
262
- }
242
+ const { ok, data } = await del(`/api/users/${user.id}`)
243
+ if (!ok) throw new Error(data?.error ?? 'Request failed')
263
244
  onOpenChange(false)
264
245
  onSuccess()
265
246
  } catch (err: any) {
@@ -0,0 +1,36 @@
1
+ import { env } from '@mantiq/core'
2
+
3
+ export default {
4
+
5
+ /*
6
+ |--------------------------------------------------------------------------
7
+ | Application Name
8
+ |--------------------------------------------------------------------------
9
+ */
10
+ name: env('APP_NAME', 'MantiqJS'),
11
+ env: env('APP_ENV', 'production'),
12
+ debug: env('APP_DEBUG', false),
13
+ key: env('APP_KEY', ''),
14
+ url: env('APP_URL', 'http://localhost:3000'),
15
+ port: Number(env('APP_PORT', '3000')),
16
+ basePath: import.meta.dir + '/..',
17
+
18
+ /*
19
+ |--------------------------------------------------------------------------
20
+ | Middleware Groups
21
+ |--------------------------------------------------------------------------
22
+ |
23
+ | Middleware groups are applied automatically based on the route file:
24
+ | routes/web.ts → 'web' group (stateful: sessions, CSRF, cookies)
25
+ | routes/api.ts → 'api' group (stateful: sessions, cookies — for SPA)
26
+ |
27
+ | The 'api' group includes sessions so the SPA can use cookie-based auth
28
+ | for API calls. CSRF is not included — the SPA sends the X-XSRF-TOKEN
29
+ | header instead (set by the web group on page load).
30
+ |
31
+ */
32
+ middlewareGroups: {
33
+ web: ['cors', 'encrypt.cookies', 'session', 'csrf'],
34
+ api: ['cors', 'encrypt.cookies', 'session', 'throttle'],
35
+ },
36
+ }
@@ -0,0 +1,14 @@
1
+ import type { Router } from '@mantiq/core'
2
+ import { json } from '@mantiq/core'
3
+ import { UserController } from '../app/Http/Controllers/UserController.ts'
4
+
5
+ export default function (router: Router) {
6
+ // Public
7
+ router.get('/ping', () => json({ status: 'ok', timestamp: new Date().toISOString() }))
8
+
9
+ // Protected — session auth via cookies (stateful API for SPA)
10
+ router.get('/users', [UserController, 'index']).middleware('auth')
11
+ router.post('/users', [UserController, 'store']).middleware('auth')
12
+ router.put('/users/:id', [UserController, 'update']).middleware('auth')
13
+ router.delete('/users/:id', [UserController, 'destroy']).middleware('auth')
14
+ }
@@ -1,9 +1,7 @@
1
1
  import type { Router } from '@mantiq/core'
2
- import { json } from '@mantiq/core'
3
2
  import { HomeController } from '../app/Http/Controllers/HomeController.ts'
4
3
  import { PageController } from '../app/Http/Controllers/PageController.ts'
5
4
  import { AuthController } from '../app/Http/Controllers/AuthController.ts'
6
- import { UserController } from '../app/Http/Controllers/UserController.ts'
7
5
 
8
6
  export default function (router: Router) {
9
7
  router.get('/', [HomeController, 'index'])
@@ -22,11 +20,4 @@ export default function (router: Router) {
22
20
  router.post('/login', [AuthController, 'login'])
23
21
  router.post('/register', [AuthController, 'register'])
24
22
  router.post('/logout', [AuthController, 'logout']).middleware('auth')
25
-
26
- // API endpoints (stateful — session auth via cookies)
27
- router.get('/api/ping', () => json({ status: 'ok', timestamp: new Date().toISOString() }))
28
- router.get('/api/users', [UserController, 'index']).middleware('auth')
29
- router.post('/api/users', [UserController, 'store']).middleware('auth')
30
- router.put('/api/users/:id', [UserController, 'update']).middleware('auth')
31
- router.delete('/api/users/:id', [UserController, 'destroy']).middleware('auth')
32
23
  }