miqro 7.2.1 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +871 -492
- package/build/editor.bundle.js +44 -12
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.d.ts +3 -2
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.js +3 -3
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.js.map +1 -1
- package/build/esm/editor/http/admin/editor/api/fs/read.api.d.ts +3 -1
- package/build/esm/editor/http/admin/editor/api/fs/read.api.js +3 -3
- package/build/esm/editor/http/admin/editor/api/fs/read.api.js.map +1 -1
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.d.ts +4 -1
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.js +3 -3
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.js.map +1 -1
- package/build/esm/src/cluster.js +0 -0
- package/build/esm/src/common/jsx.d.ts +3 -3
- package/build/esm/src/common/jsx.js.map +1 -1
- package/build/esm/src/inflate/inflate-sea.js +2 -2
- package/build/esm/src/inflate/inflate-sea.js.map +1 -1
- package/build/esm/src/inflate/setup-http.js.map +1 -1
- package/build/esm/src/main.js +0 -0
- package/build/esm/src/services/editor.js.map +1 -1
- package/build/esm/src/services/utils/cluster-ws.js +2 -2
- package/build/esm/src/services/utils/cluster-ws.js.map +1 -1
- package/build/esm/src/types.d.ts +5 -4
- package/build/esm/src/types.js +4 -1
- package/build/esm/src/types.js.map +1 -1
- package/build/lib.cjs +444 -174
- package/editor/http/admin/editor/api/fs/delete.api.tsx +3 -3
- package/editor/http/admin/editor/api/fs/read.api.tsx +3 -3
- package/editor/http/admin/editor/api/fs/rename.api.tsx +3 -3
- package/package.json +12 -12
- package/sea/install-esbuild.sh +1 -1
- package/sea/install-nodejs.sh +1 -1
- package/sea/node.version.tag +1 -1
- package/src/common/jsx.ts +3 -3
- package/src/inflate/inflate-sea.ts +2 -2
- package/src/inflate/setup-http.ts +4 -4
- package/src/services/editor.tsx +1 -1
- package/src/services/utils/cluster-ws.ts +2 -2
- package/src/types.ts +21 -41
package/README.md
CHANGED
|
@@ -1,66 +1,133 @@
|
|
|
1
1
|
# miqro
|
|
2
2
|
|
|
3
|
-
**experimental**
|
|
3
|
+
**experimental** Node.js web framework built from scratch with minimal runtime dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
fully typed TypeScript · REST · WebSocket · SQL · JWT · HTTP
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
runtime dependencies: `jose`, `esbuild`, `cookie`, `showdown`
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- **Web Components** using JSX — server-rendered and client-side
|
|
10
|
+
- **static site generation** — `.html.tsx` handlers run against a live DB at build time
|
|
11
|
+
- **REST API** endpoints with request/response validation
|
|
12
|
+
- **database** — `sqlite3`, `node:sqlite`, `postgres` with migrations
|
|
13
|
+
- **cluster** — multi-worker with shared cache and hot reload
|
|
14
|
+
- **NODE:SEA** — single binary, no Node.js required on target machine
|
|
15
|
+
- **built-in editor**, **test runner**, **API doc generation**
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
## packages
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
```
|
|
20
|
+
miqro
|
|
21
|
+
├── @miqro/core router, middleware, session, CORS, logger
|
|
22
|
+
├── @miqro/jsx vDOM, hooks, SSR runtime
|
|
23
|
+
├── @miqro/jsx-dom Web Component define, browser runtime
|
|
24
|
+
├── @miqro/jsx-node node SSR runtime
|
|
25
|
+
├── @miqro/query query builder, ORM, migrations
|
|
26
|
+
├── @miqro/parser schema validation
|
|
27
|
+
├── @miqro/request http client
|
|
28
|
+
├── @miqro/runner cluster manager
|
|
29
|
+
├── @miqro/test test runner
|
|
30
|
+
└── @miqro/test-http http test helper
|
|
31
|
+
```
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
`@miqro/test` and `@miqro/request` are being phased out in favor of `node:test` and built-in `fetch`.
|
|
18
34
|
|
|
19
|
-
-
|
|
35
|
+
package docs: [@miqro/core](core-README.md) · [@miqro/query](query-README.md) · [@miqro/jsx](jsx-README.md) · [@miqro/jsx-dom](jsx-dom-README.md) · [@miqro/jsx-node](jsx-node-README.md) · [@miqro/request](request-README.md) · [@miqro/runner](runner-README.md) · [@miqro/test](test-README.md) · [@miqro/test-http](test-http-README.md) · [@miqro/parser](parser-README.md)
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
## composition
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
services run sequentially. each service runs on every request until a route sends a response.
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"services": ["a/", "b/", "c/"]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
26
46
|
|
|
27
|
-
|
|
47
|
+
```
|
|
48
|
+
request →
|
|
49
|
+
a/ runs (auth.ts, middleware.ts, then http/ routes)
|
|
50
|
+
if no route in a/ matched, b/ runs
|
|
51
|
+
if no route in b/ matched, c/ runs
|
|
52
|
+
```
|
|
28
53
|
|
|
29
|
-
|
|
54
|
+
earlier services establish state. later services consume it.
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
```
|
|
57
|
+
a/auth.ts → sets req.session
|
|
58
|
+
b/db.ts → req.server.db.get("b") available
|
|
59
|
+
c/http/ → has req.session and req.server.db, handles routes
|
|
60
|
+
```
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
`req.session.account` is the tenant identifier. use it to isolate data:
|
|
34
63
|
|
|
35
|
-
|
|
64
|
+
```ts
|
|
65
|
+
// any handler in c/
|
|
66
|
+
const posts = await req.server.db.get("b")
|
|
67
|
+
.select().from("posts")
|
|
68
|
+
.eq("account", req.session.account)
|
|
69
|
+
.yield();
|
|
70
|
+
```
|
|
36
71
|
|
|
37
|
-
|
|
72
|
+
seed shared state in `server.ts` using `isPrimaryWorker()`:
|
|
38
73
|
|
|
39
|
-
```
|
|
74
|
+
```ts
|
|
75
|
+
// a/server.ts
|
|
76
|
+
export default {
|
|
77
|
+
load: async (server) => {
|
|
78
|
+
if (server.isPrimaryWorker()) {
|
|
79
|
+
const config = await server.db.get("b").select().from("config").yield();
|
|
80
|
+
server.cache.set("config", config);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
40
85
|
|
|
41
|
-
|
|
86
|
+
swap `a/` for a different implementation — `b/` and `c/` don't change. they read from `req.session`, not from `a/` directly.
|
|
42
87
|
|
|
43
|
-
|
|
88
|
+
same service folders, different `miqro.json`:
|
|
44
89
|
|
|
90
|
+
```json
|
|
91
|
+
{ "services": ["a1/", "b/", "c/"] } // production auth
|
|
92
|
+
{ "services": ["a2/", "b/", "c/"] } // test auth
|
|
93
|
+
{ "services": ["b/", "c/"] } // no auth
|
|
45
94
|
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
95
|
+
|
|
96
|
+
service folders can be npm packages:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"services": [
|
|
101
|
+
"node_modules/@myorg/auth/service/",
|
|
102
|
+
"node_modules/@myorg/db/service/",
|
|
103
|
+
"services/app/"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
53
106
|
```
|
|
54
107
|
|
|
55
|
-
|
|
108
|
+
## installation
|
|
109
|
+
|
|
110
|
+
### standalone binary (no Node.js required) ( Not Up-To-Date versions )
|
|
111
|
+
|
|
112
|
+
download from [releases](https://github.com/claukers/miqro/releases). (diff release cycle so expect OLD versions published)
|
|
113
|
+
|
|
114
|
+
for newer releases use the npm install and the npx miqro --compile to produce a standalone binary of the app
|
|
115
|
+
|
|
116
|
+
### npm
|
|
56
117
|
|
|
57
|
-
```
|
|
118
|
+
```
|
|
119
|
+
npm install miqro
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## getting started
|
|
58
123
|
|
|
59
|
-
|
|
124
|
+
```
|
|
125
|
+
mkdir -p example/http
|
|
126
|
+
```
|
|
60
127
|
|
|
61
|
-
|
|
128
|
+
`example/http/index.html.tsx`
|
|
62
129
|
|
|
63
|
-
```
|
|
130
|
+
```tsx
|
|
64
131
|
import JSX from "@miqro/jsx";
|
|
65
132
|
|
|
66
133
|
export default (req, res) => {
|
|
@@ -72,631 +139,943 @@ export default (req, res) => {
|
|
|
72
139
|
}
|
|
73
140
|
```
|
|
74
141
|
|
|
75
|
-
|
|
142
|
+
```
|
|
143
|
+
miqro --watch --service example/
|
|
144
|
+
```
|
|
76
145
|
|
|
77
|
-
|
|
146
|
+
open `http://localhost:8080/index.html`.
|
|
78
147
|
|
|
79
|
-
|
|
148
|
+
### generate static files
|
|
80
149
|
|
|
81
|
-
|
|
150
|
+
```
|
|
151
|
+
miqro --inflate --inflate-dir build/ --service example/
|
|
152
|
+
```
|
|
82
153
|
|
|
83
|
-
|
|
154
|
+
output in `build/example/static/`. serve with any http server.
|
|
84
155
|
|
|
85
|
-
|
|
156
|
+
## service folder
|
|
86
157
|
|
|
87
|
-
|
|
158
|
+
```
|
|
159
|
+
app/
|
|
160
|
+
http/ endpoints
|
|
161
|
+
static/ files served as-is
|
|
162
|
+
migration/ db migrations
|
|
163
|
+
test/ tests
|
|
164
|
+
db.ts database config
|
|
165
|
+
ws.ts websocket config
|
|
166
|
+
auth.ts session/auth config
|
|
167
|
+
server.ts lifecycle hooks
|
|
168
|
+
log.ts log transport config
|
|
169
|
+
middleware.ts pre/post route middleware
|
|
170
|
+
catch.ts error handlers
|
|
171
|
+
miqro.json multi-service composition
|
|
172
|
+
```
|
|
88
173
|
|
|
89
|
-
|
|
174
|
+
all files and folders are optional.
|
|
90
175
|
|
|
91
|
-
```
|
|
176
|
+
```
|
|
177
|
+
miqro --service app/
|
|
178
|
+
miqro --service app/ --editor
|
|
179
|
+
```
|
|
92
180
|
|
|
181
|
+
## http folder
|
|
93
182
|
|
|
94
|
-
|
|
183
|
+
files are served by their path in the directory.
|
|
95
184
|
|
|
96
|
-
|
|
185
|
+
```
|
|
186
|
+
http/
|
|
187
|
+
index.html.tsx → GET /index.html
|
|
188
|
+
posts/
|
|
189
|
+
index.html.tsx → GET /posts/index.html
|
|
190
|
+
list.api.ts → GET /posts/list
|
|
191
|
+
js/
|
|
192
|
+
app.min.tsx → GET /js/app.js (minified bundle)
|
|
193
|
+
```
|
|
97
194
|
|
|
98
|
-
|
|
195
|
+
### .html.tsx
|
|
99
196
|
|
|
100
|
-
|
|
197
|
+
server-rendered JSX. runs at request time or at build time with `--inflate`.
|
|
101
198
|
|
|
102
|
-
|
|
199
|
+
```tsx
|
|
200
|
+
import JSX from "@miqro/jsx";
|
|
103
201
|
|
|
104
|
-
|
|
105
|
-
|
|
202
|
+
export default (req, res) => {
|
|
203
|
+
return <html>
|
|
204
|
+
<body>
|
|
205
|
+
<h1>Hello</h1>
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
|
208
|
+
}
|
|
209
|
+
```
|
|
106
210
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// to start the server
|
|
123
|
-
// await app.start();
|
|
124
|
-
// to stop the server
|
|
125
|
-
// await app.stop();
|
|
126
|
-
// to dispose the server and disconnect all node:cluster worker syncronization for safe exiting
|
|
127
|
-
// await app.dispose();
|
|
211
|
+
with database access:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import JSX from "@miqro/jsx";
|
|
215
|
+
|
|
216
|
+
export default async (req, res) => {
|
|
217
|
+
const posts = await req.server.db.get("mydb")
|
|
218
|
+
.select().from("posts").yield();
|
|
219
|
+
|
|
220
|
+
return <html>
|
|
221
|
+
<body>
|
|
222
|
+
{posts.map(p => <h2>{p.title}</h2>)}
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
225
|
+
}
|
|
128
226
|
```
|
|
129
227
|
|
|
130
|
-
|
|
228
|
+
customize path/method with `apiOptions`:
|
|
131
229
|
|
|
132
|
-
|
|
230
|
+
```tsx
|
|
231
|
+
import JSX from "@miqro/jsx";
|
|
232
|
+
import { APIOptions } from "miqro";
|
|
133
233
|
|
|
134
|
-
|
|
234
|
+
export const apiOptions: APIOptions = {
|
|
235
|
+
path: ["/", "/index.html"],
|
|
236
|
+
method: ["GET"]
|
|
237
|
+
};
|
|
135
238
|
|
|
136
|
-
|
|
239
|
+
export default (req, res) => { ... }
|
|
240
|
+
```
|
|
137
241
|
|
|
138
|
-
|
|
242
|
+
### .min.tsx
|
|
139
243
|
|
|
140
|
-
|
|
244
|
+
bundled and minified client-side JavaScript. use to define Web Components.
|
|
141
245
|
|
|
142
|
-
```
|
|
246
|
+
```tsx
|
|
247
|
+
import JSX, { useState } from "@miqro/jsx";
|
|
248
|
+
import { define } from "@miqro/jsx-dom";
|
|
143
249
|
|
|
144
|
-
|
|
250
|
+
function Counter() {
|
|
251
|
+
const [n, setN] = useState(0);
|
|
252
|
+
return <button onClick={() => setN(n + 1)}>count: {n}</button>;
|
|
253
|
+
}
|
|
145
254
|
|
|
146
|
-
|
|
255
|
+
define("my-counter", Counter, {
|
|
256
|
+
observedAttributes: ["initial"],
|
|
257
|
+
shadowInit: false
|
|
258
|
+
});
|
|
259
|
+
```
|
|
147
260
|
|
|
148
|
-
|
|
261
|
+
included in HTML:
|
|
149
262
|
|
|
263
|
+
```tsx
|
|
264
|
+
export default (req, res) => (
|
|
265
|
+
<html>
|
|
266
|
+
<body>
|
|
267
|
+
<my-counter initial="0" />
|
|
268
|
+
<script src="/js/app.js" />
|
|
269
|
+
</body>
|
|
270
|
+
</html>
|
|
271
|
+
);
|
|
150
272
|
```
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
db.ts
|
|
161
|
-
ws.ts
|
|
162
|
-
auth.ts
|
|
163
|
-
server.ts
|
|
273
|
+
|
|
274
|
+
### .api.ts
|
|
275
|
+
|
|
276
|
+
REST endpoint. file path is the route path.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
export default (req, res) => {
|
|
280
|
+
return res.json({ ok: true });
|
|
281
|
+
}
|
|
164
282
|
```
|
|
165
283
|
|
|
166
|
-
|
|
284
|
+
full declaration with validation:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { APIRoute, JSONParser } from "@miqro/core";
|
|
167
288
|
|
|
168
|
-
|
|
289
|
+
export default {
|
|
290
|
+
name: "create post",
|
|
291
|
+
method: "POST",
|
|
292
|
+
middleware: [JSONParser()],
|
|
293
|
+
request: {
|
|
294
|
+
body: {
|
|
295
|
+
title: "string",
|
|
296
|
+
content: "string"
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
response: {
|
|
300
|
+
status: [200],
|
|
301
|
+
body: {
|
|
302
|
+
id: "number"
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
handler: async (req, res) => {
|
|
306
|
+
const post = await req.server.db.get("mydb")
|
|
307
|
+
.insert("posts")
|
|
308
|
+
.values({ title: req.body.title, content: req.body.content })
|
|
309
|
+
.returning("id")
|
|
310
|
+
.yield();
|
|
311
|
+
return res.json({ id: post[0].id });
|
|
312
|
+
}
|
|
313
|
+
} as APIRoute;
|
|
314
|
+
```
|
|
169
315
|
|
|
170
|
-
|
|
316
|
+
`method: "use"` registers the handler for all methods (middleware pattern):
|
|
171
317
|
|
|
172
|
-
|
|
318
|
+
```ts
|
|
319
|
+
import { Router } from "@miqro/core";
|
|
173
320
|
|
|
174
|
-
|
|
321
|
+
const router = new Router();
|
|
322
|
+
router.use(myMiddleware);
|
|
175
323
|
|
|
176
|
-
|
|
324
|
+
export default {
|
|
325
|
+
path: "/admin",
|
|
326
|
+
method: "use",
|
|
327
|
+
handler: router
|
|
328
|
+
}
|
|
329
|
+
```
|
|
177
330
|
|
|
178
|
-
|
|
179
|
-
the files will be server acording to the location in the directory.
|
|
331
|
+
### .html.md
|
|
180
332
|
|
|
181
|
-
|
|
333
|
+
markdown file converted to HTML. served as `text/html`.
|
|
182
334
|
|
|
183
335
|
```
|
|
184
336
|
http/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
style.css
|
|
188
|
-
js/
|
|
189
|
-
script.min.js
|
|
337
|
+
docs/
|
|
338
|
+
guide.html.md → GET /docs/guide
|
|
190
339
|
```
|
|
191
340
|
|
|
192
|
-
|
|
341
|
+
`guide.html.md`:
|
|
193
342
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
3. /js/script.min.js
|
|
343
|
+
```markdown
|
|
344
|
+
# Guide
|
|
197
345
|
|
|
198
|
-
|
|
346
|
+
some **markdown** content.
|
|
347
|
+
```
|
|
199
348
|
|
|
200
|
-
|
|
349
|
+
converted using `showdown`. also works with `--inflate` — outputs static HTML.
|
|
201
350
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
351
|
+
available as `req.server.inflateMDtoHTML(str)` from any handler.
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
## static folder
|
|
355
|
+
|
|
356
|
+
files served as-is, by path.
|
|
205
357
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</html>
|
|
358
|
+
```
|
|
359
|
+
static/
|
|
360
|
+
logo.png → GET /logo.png
|
|
361
|
+
style.css → GET /style.css
|
|
211
362
|
```
|
|
212
363
|
|
|
213
|
-
|
|
364
|
+
## db.ts
|
|
214
365
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
import JSX from "@miqro/jsx";
|
|
218
|
-
import { ServerRequest, ServerResponse, APIOptions } from "miqro";
|
|
219
|
-
import { MyComponent } from "./component.js";
|
|
366
|
+
```ts
|
|
367
|
+
import { DBConfig } from "miqro";
|
|
220
368
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
369
|
+
export default {
|
|
370
|
+
dialect: "node:sqlite", // node:sqlite | sqlite3 | pg
|
|
371
|
+
name: "mydb", // req.server.db.get("mydb")
|
|
372
|
+
storage: "./data.sqlite3", // sqlite only
|
|
373
|
+
// connectionString: "..." // postgres
|
|
374
|
+
} as DBConfig;
|
|
375
|
+
```
|
|
226
376
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
377
|
+
migrations run automatically on startup (primary worker only).
|
|
378
|
+
|
|
379
|
+
## migration folder
|
|
380
|
+
|
|
381
|
+
files named `NNN_name.ts` run in order.
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
// migration/001_create_posts.ts
|
|
385
|
+
import { MigrationModule } from "miqro";
|
|
386
|
+
|
|
387
|
+
export default {
|
|
388
|
+
name: "001_create_posts",
|
|
389
|
+
dbName: "mydb",
|
|
390
|
+
up: async (db) => {
|
|
391
|
+
await db.createTable("posts", {
|
|
392
|
+
id: { type: "integer", primaryKey: true, autoIncrement: true },
|
|
393
|
+
title: { type: "string" },
|
|
394
|
+
content: { type: "string" }
|
|
395
|
+
}).yield();
|
|
396
|
+
}
|
|
397
|
+
} as MigrationModule;
|
|
234
398
|
```
|
|
235
399
|
|
|
236
|
-
|
|
400
|
+
run manually:
|
|
237
401
|
|
|
238
|
-
|
|
402
|
+
```
|
|
403
|
+
miqro --migrate-up --service app/
|
|
404
|
+
miqro --migrate-down --service app/
|
|
405
|
+
```
|
|
239
406
|
|
|
240
|
-
|
|
407
|
+
## cors.ts
|
|
241
408
|
|
|
242
|
-
|
|
409
|
+
```ts
|
|
410
|
+
import { CORSOptions } from "miqro";
|
|
243
411
|
|
|
244
|
-
|
|
245
|
-
|
|
412
|
+
export default {
|
|
413
|
+
origins: ["https://myapp.com", "https://staging.myapp.com"],
|
|
414
|
+
methods: "GET,POST,PUT,DELETE"
|
|
415
|
+
} as CORSOptions;
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
without `cors.ts` all origins are allowed. with it only listed origins are accepted — requests from other origins get `400 Bad Request`.
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
## ORM
|
|
422
|
+
|
|
423
|
+
quick reference. full docs in [@miqro/query](query-README.md).
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { defineModel } from "@miqro/query";
|
|
427
|
+
|
|
428
|
+
const Post = defineModel(db, "posts", {
|
|
429
|
+
id: { type: "integer", primaryKey: true, autoIncrement: true },
|
|
430
|
+
title: { type: "string" },
|
|
431
|
+
published: { type: "boolean" },
|
|
432
|
+
createdAt: { type: "datetime" }
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// create
|
|
436
|
+
await Post.create({ title: "hello", published: false });
|
|
437
|
+
|
|
438
|
+
// findAll
|
|
439
|
+
const posts = await Post.findAll(
|
|
440
|
+
Post.where().eq("published", true).order("createdAt", "DESC"),
|
|
441
|
+
{ limit: 10 }
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// updateAll
|
|
445
|
+
await Post.updateAll({ published: true }, Post.where().eq("id", 1));
|
|
446
|
+
|
|
447
|
+
// deleteAll
|
|
448
|
+
await Post.deleteAll(Post.where().eq("id", 1));
|
|
449
|
+
|
|
450
|
+
// count
|
|
451
|
+
const n = await Post.count(Post.where().eq("published", true));
|
|
452
|
+
|
|
453
|
+
// sync (create table if not exists)
|
|
454
|
+
await Post.sync();
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
access the db directly:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
const db = req.server.db.get("mydb");
|
|
461
|
+
const rows = await db.select().from("posts").eq("published", true).yield();
|
|
462
|
+
const raw = await db.query("SELECT * FROM posts WHERE id = ?", [1]);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
## ws.ts
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { WSConfig } from "miqro";
|
|
470
|
+
|
|
471
|
+
export default {
|
|
472
|
+
path: "/updates", // req.server.ws.get("/updates")
|
|
473
|
+
disabled: false
|
|
474
|
+
} as WSConfig;
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
broadcast from a handler:
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
export default async (req, res) => {
|
|
481
|
+
const ws = req.server.ws.get("/updates");
|
|
482
|
+
await ws.broadcast(JSON.stringify({ type: "update", data: "..." }));
|
|
483
|
+
return res.json({ ok: true });
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### WebSocket client (.min.tsx)
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
246
490
|
import JSX, { useState, useEffect } from "@miqro/jsx";
|
|
247
491
|
import { define } from "@miqro/jsx-dom";
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// create a state variable count
|
|
253
|
-
const [count, setCount] = useState(0);
|
|
254
|
-
// create a effect with a setTimeout that updates count after 1000ms
|
|
492
|
+
|
|
493
|
+
function LiveFeed(props) {
|
|
494
|
+
const [messages, setMessages] = useState([]);
|
|
495
|
+
|
|
255
496
|
useEffect(() => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return () =>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}, [count]);
|
|
266
|
-
// return the jsx to be rendered
|
|
267
|
-
return <p>Count: {count}</p>;
|
|
497
|
+
const ws = new WebSocket(`ws://${location.host}/updates`);
|
|
498
|
+
ws.onmessage = (e) => {
|
|
499
|
+
const msg = JSON.parse(e.data);
|
|
500
|
+
setMessages(prev => [...prev, msg]);
|
|
501
|
+
};
|
|
502
|
+
return () => ws.close();
|
|
503
|
+
}, []);
|
|
504
|
+
|
|
505
|
+
return <ul>{messages.map((m, i) => <li>{m.text}</li>)}</ul>;
|
|
268
506
|
}
|
|
269
507
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<ticker-tag></ticker-tag>
|
|
508
|
+
define("live-feed", LiveFeed, { shadowInit: false });
|
|
509
|
+
```
|
|
274
510
|
|
|
275
|
-
|
|
511
|
+
broadcast from a handler:
|
|
276
512
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
//observedAttributes: ["width", "height", "some-attr"]; // attribues that are observed for changes to re-render the JSX component
|
|
285
|
-
});
|
|
286
|
-
});
|
|
513
|
+
```ts
|
|
514
|
+
// http/publish.api.ts
|
|
515
|
+
export default async (req, res) => {
|
|
516
|
+
const ws = req.server.ws.get("/updates");
|
|
517
|
+
await ws.broadcast(JSON.stringify({ text: req.body.text }));
|
|
518
|
+
return res.json({ ok: true });
|
|
519
|
+
}
|
|
287
520
|
```
|
|
288
521
|
|
|
289
|
-
#### .api.ts
|
|
290
522
|
|
|
291
|
-
|
|
523
|
+
## auth.ts
|
|
292
524
|
|
|
293
|
-
|
|
525
|
+
```ts
|
|
526
|
+
import { AuthConfig, jwt } from "miqro";
|
|
527
|
+
import { createSecretKey } from "node:crypto";
|
|
294
528
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
export default
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
529
|
+
const secret = createSecretKey(Buffer.from(process.env.JWT_SECRET, "hex"));
|
|
530
|
+
|
|
531
|
+
export default {
|
|
532
|
+
verify: async ({ token }) => {
|
|
533
|
+
try {
|
|
534
|
+
const payload = await jwt.verify(token, secret);
|
|
535
|
+
return {
|
|
536
|
+
account: payload.account as string,
|
|
537
|
+
username: payload.username as string,
|
|
538
|
+
groups: payload.groups as string[],
|
|
539
|
+
token
|
|
540
|
+
};
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} as AuthConfig;
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
session available on `req.session`:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
req.session.account // tenant identifier
|
|
552
|
+
req.session.username
|
|
553
|
+
req.session.groups
|
|
554
|
+
req.session.token
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
restrict by group using `policy` on any endpoint:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
export default {
|
|
561
|
+
policy: {
|
|
562
|
+
groups: ["admin"],
|
|
563
|
+
groupPolicy: "at_least_one"
|
|
564
|
+
},
|
|
565
|
+
handler: ...
|
|
301
566
|
}
|
|
302
567
|
```
|
|
303
568
|
|
|
304
|
-
|
|
305
|
-
this example uses the ```server.middleware.json()``` to parse the request body as a json.
|
|
569
|
+
## server.ts
|
|
306
570
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
import {
|
|
571
|
+
lifecycle hooks.
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
import { ServerConfig } from "miqro";
|
|
311
575
|
|
|
312
576
|
export default {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
path: ["/", "/do"],
|
|
316
|
-
method: ["POST"],
|
|
317
|
-
middleware: [server.middleware.json()],
|
|
318
|
-
request: {
|
|
319
|
-
headers: {
|
|
320
|
-
auth: "string"
|
|
321
|
-
},
|
|
322
|
-
query: {
|
|
323
|
-
pagination: "number"
|
|
324
|
-
},
|
|
325
|
-
body: {
|
|
326
|
-
inputValue: "string"
|
|
327
|
-
otherInputValues: "number[]?"
|
|
328
|
-
}
|
|
577
|
+
preload: async (server) => {
|
|
578
|
+
// runs before db connections
|
|
329
579
|
},
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
});
|
|
580
|
+
load: async (server) => {
|
|
581
|
+
// runs after db connections, before listening
|
|
582
|
+
// seed cache here
|
|
583
|
+
const rows = await server.db.get("mydb").select().from("config").yield();
|
|
584
|
+
server.cache.set("config", rows);
|
|
585
|
+
},
|
|
586
|
+
start: async (server) => {
|
|
587
|
+
// runs after listening
|
|
588
|
+
},
|
|
589
|
+
stop: async (server) => {
|
|
590
|
+
// runs on shutdown
|
|
342
591
|
}
|
|
343
|
-
} as
|
|
592
|
+
} as ServerConfig;
|
|
344
593
|
```
|
|
345
594
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
all request's have a property called ```server``` that it can be used to access server shared content.
|
|
595
|
+
`load` runs on all workers. use `server.isPrimaryWorker()` to gate one-time operations.
|
|
349
596
|
|
|
350
|
-
|
|
597
|
+
## middleware.ts
|
|
351
598
|
|
|
352
|
-
|
|
599
|
+
pre and post route middleware for the service.
|
|
353
600
|
|
|
354
|
-
```
|
|
355
|
-
import {
|
|
601
|
+
```ts
|
|
602
|
+
import { MiddlewareConfig } from "miqro";
|
|
356
603
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
cache: CacheInterface; // this cache will be syncronized between all node:cluster workers
|
|
372
|
-
localCache: CacheInterface; // this cache will local to the node:cluster worker
|
|
373
|
-
logger?: Logger;
|
|
374
|
-
isPrimaryWorker: () => boolean;
|
|
375
|
-
openBrowser: (path: string) => void;
|
|
376
|
-
getLogger: (identifier: string, options?: { level?: any; transports?: any[]; formatter?: any; }) => Logger;
|
|
377
|
-
};
|
|
378
|
-
}
|
|
604
|
+
export default {
|
|
605
|
+
middleware: [
|
|
606
|
+
// runs before all routes
|
|
607
|
+
async (req, res) => {
|
|
608
|
+
req.startTime = Date.now();
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
post: [
|
|
612
|
+
// runs after all routes
|
|
613
|
+
async (req, res) => {
|
|
614
|
+
req.logger.debug("took %dms", Date.now() - req.startTime);
|
|
615
|
+
}
|
|
616
|
+
]
|
|
617
|
+
} as MiddlewareConfig;
|
|
379
618
|
```
|
|
380
619
|
|
|
381
|
-
|
|
620
|
+
## catch.ts
|
|
382
621
|
|
|
383
|
-
|
|
384
|
-
import { ServerRequest, ServerResponse } from "miqro";
|
|
622
|
+
error handlers.
|
|
385
623
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
624
|
+
```ts
|
|
625
|
+
import { ErrorConfig } from "miqro";
|
|
626
|
+
|
|
627
|
+
export default {
|
|
628
|
+
catch: [
|
|
629
|
+
async (err, req, res) => {
|
|
630
|
+
req.logger.error(err);
|
|
631
|
+
return res.json({ error: err.message }, {}, 500);
|
|
393
632
|
}
|
|
633
|
+
]
|
|
634
|
+
} as ErrorConfig;
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
## log.ts
|
|
638
|
+
|
|
639
|
+
custom log transport. called for every log message.
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
import { LogConfig } from "miqro";
|
|
643
|
+
|
|
644
|
+
export default {
|
|
645
|
+
level: "error", // only receive messages at this level
|
|
646
|
+
replaceConsoleTransport: false, // keep default console output
|
|
647
|
+
replaceFileTransport: false, // keep default file output
|
|
648
|
+
write: async ({ out, level, identifier }) => {
|
|
649
|
+
// out — formatted log string
|
|
650
|
+
// level — "error" | "warn" | "info" | "debug" | "trace"
|
|
651
|
+
// identifier — route/worker identifier
|
|
652
|
+
await fetch("https://logs.example.com/ingest", {
|
|
653
|
+
method: "POST",
|
|
654
|
+
body: JSON.stringify({ out, level, identifier })
|
|
655
|
+
});
|
|
394
656
|
}
|
|
395
|
-
}
|
|
657
|
+
} as LogConfig;
|
|
396
658
|
```
|
|
397
659
|
|
|
398
|
-
|
|
660
|
+
log levels: `error` → `warn` → `info` → `debug` → `trace` → `none`
|
|
399
661
|
|
|
400
|
-
|
|
662
|
+
default output: console + `./server.log` (or `LOG_FILE` env var).
|
|
401
663
|
|
|
402
|
-
|
|
664
|
+
per-identifier level override via env:
|
|
403
665
|
|
|
404
|
-
|
|
666
|
+
```
|
|
667
|
+
LOG_LEVEL=info
|
|
668
|
+
LOG_LEVEL_POSTS_GET=debug // debug only for GET /posts
|
|
669
|
+
LOG_LEVEL_WORKER_0=trace // trace only for worker 0
|
|
670
|
+
```
|
|
405
671
|
|
|
406
|
-
|
|
672
|
+
**cluster mode:** each worker writes to the same log file independently. at high throughput use pipes instead of `FileTransport`:
|
|
407
673
|
|
|
408
|
-
|
|
674
|
+
```
|
|
675
|
+
miqro --service app/ 2>&1 | tee app.log
|
|
676
|
+
```
|
|
409
677
|
|
|
410
|
-
|
|
678
|
+
or send to an external aggregator via `log.ts` `write`.
|
|
411
679
|
|
|
412
|
-
|
|
680
|
+
## doc.ts
|
|
413
681
|
|
|
414
|
-
|
|
682
|
+
publish API documentation as a static file or live endpoint.
|
|
415
683
|
|
|
416
|
-
|
|
684
|
+
```ts
|
|
685
|
+
import { DocConfig } from "miqro";
|
|
686
|
+
|
|
687
|
+
export default {
|
|
688
|
+
publish: {
|
|
689
|
+
"/api/docs": { type: "MD" }, // markdown
|
|
690
|
+
"/api/schema": { type: "JSON" }, // json
|
|
691
|
+
"/api/docs.html": { type: "HTML" } // html
|
|
692
|
+
}
|
|
693
|
+
} as DocConfig;
|
|
694
|
+
```
|
|
417
695
|
|
|
418
|
-
|
|
696
|
+
with `--inflate` the docs are written to `build/` as static files.
|
|
419
697
|
|
|
420
|
-
for
|
|
698
|
+
at runtime the docs are served as live endpoints — useful for development.
|
|
699
|
+
|
|
700
|
+
generate docs via CLI:
|
|
421
701
|
|
|
422
702
|
```
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
css/
|
|
426
|
-
style.css
|
|
427
|
-
js/
|
|
428
|
-
script.min.js
|
|
703
|
+
miqro --generate-doc --generate-doc-out API.md --service app/
|
|
704
|
+
miqro --generate-doc --generate-doc-type JSON --generate-doc-out api.json --service app/
|
|
429
705
|
```
|
|
430
706
|
|
|
431
|
-
|
|
707
|
+
doc output is derived from `APIRoute` declarations — `name`, `description`, `request`, `response`, `policy` fields.
|
|
432
708
|
|
|
433
|
-
1. /index.html
|
|
434
|
-
2. /css/style.css
|
|
435
|
-
3. /js/script.min.js
|
|
436
709
|
|
|
437
|
-
|
|
710
|
+
## miqro.json
|
|
438
711
|
|
|
439
|
-
|
|
712
|
+
compose multiple services.
|
|
440
713
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
714
|
+
```json
|
|
715
|
+
{
|
|
716
|
+
"name": "myapp",
|
|
717
|
+
"port": "3000",
|
|
718
|
+
"services": [
|
|
719
|
+
"a/",
|
|
720
|
+
"b/",
|
|
721
|
+
"c/"
|
|
722
|
+
],
|
|
723
|
+
"inflateDir": "build/",
|
|
724
|
+
"logFile": false,
|
|
725
|
+
"browser": true
|
|
726
|
+
}
|
|
727
|
+
```
|
|
444
728
|
|
|
445
|
-
|
|
729
|
+
all fields optional except `services`.
|
|
446
730
|
|
|
447
|
-
|
|
731
|
+
```
|
|
732
|
+
name server name — required in cluster mode
|
|
733
|
+
port default: 8080
|
|
734
|
+
services ordered list of service folders
|
|
735
|
+
inflateDir default inflate output directory
|
|
736
|
+
logFile false | true | "./path/server.log"
|
|
737
|
+
browser open browser on start (true | false | "browser-name")
|
|
738
|
+
```
|
|
448
739
|
|
|
449
|
-
|
|
740
|
+
generate default `miqro.json`:
|
|
450
741
|
|
|
451
|
-
|
|
742
|
+
```
|
|
743
|
+
miqro --install-miqrojson
|
|
744
|
+
```
|
|
452
745
|
|
|
453
|
-
|
|
746
|
+
run with miqro.json:
|
|
454
747
|
|
|
455
|
-
|
|
748
|
+
```
|
|
749
|
+
miqro
|
|
750
|
+
```
|
|
456
751
|
|
|
457
|
-
|
|
752
|
+
or override:
|
|
458
753
|
|
|
459
|
-
|
|
754
|
+
```
|
|
755
|
+
miqro --service a/ --service b/
|
|
756
|
+
```
|
|
460
757
|
|
|
461
|
-
|
|
758
|
+
swap implementations by changing the services array. same service folders work across different compositions.
|
|
759
|
+
|
|
760
|
+
## req.server
|
|
761
|
+
|
|
762
|
+
available on every handler.
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
req.server.db.get("name") // Database | null
|
|
766
|
+
req.server.ws.get("/path") // WebSocketServer | undefined
|
|
767
|
+
req.server.cache // ClusterCache — synced across all cluster workers via IPC
|
|
768
|
+
req.server.localCache // LocalCache — in-memory, per worker only
|
|
769
|
+
req.server.cache.set("key", value)
|
|
770
|
+
req.server.cache.get("key")
|
|
771
|
+
req.server.cache.has("key")
|
|
772
|
+
req.server.cache.unset("key")
|
|
773
|
+
req.server.cache.set_add("key", value) // set operations
|
|
774
|
+
req.server.cache.set_has("key", value)
|
|
775
|
+
req.server.cache.set_delete("key", value)
|
|
776
|
+
|
|
777
|
+
req.server.middleware.json() // body parser → req.body
|
|
778
|
+
req.server.middleware.url() // url-encoded body parser → req.body
|
|
779
|
+
req.server.middleware.text() // text body parser → req.body
|
|
780
|
+
req.server.middleware.buffer() // raw buffer → req.buffer
|
|
781
|
+
req.server.middleware.cors(opts) // CORS middleware
|
|
782
|
+
req.server.middleware.session(opts)// auth middleware
|
|
783
|
+
|
|
784
|
+
req.server.jwt.sign(payload, secret, opts)
|
|
785
|
+
req.server.jwt.verify(token, secret, opts)
|
|
786
|
+
req.server.jwt.decode(token)
|
|
787
|
+
|
|
788
|
+
req.server.isPrimaryWorker() // true on worker 0
|
|
789
|
+
req.server.getWorkerNumber() // 0..n
|
|
790
|
+
req.server.getWorkerCount() // total workers
|
|
791
|
+
|
|
792
|
+
req.server.reload() // hot reload
|
|
793
|
+
req.server.restart() // full restart
|
|
794
|
+
req.server.stop() // shutdown
|
|
795
|
+
|
|
796
|
+
req.server.encodeHTML(str)
|
|
797
|
+
req.server.inflateMDtoHTML(str)
|
|
798
|
+
req.server.newParser()
|
|
799
|
+
req.server.newClusterCache(name)
|
|
800
|
+
req.server.newLocalCache(name)
|
|
801
|
+
req.server.getLogger(identifier)
|
|
802
|
+
```
|
|
462
803
|
|
|
463
|
-
|
|
804
|
+
## req
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
req.path // normalized pathname
|
|
808
|
+
req.hash // url hash fragment
|
|
809
|
+
req.searchParams // URLSearchParams
|
|
810
|
+
req.query // parsed query string { [key]: string | string[] }
|
|
811
|
+
req.params // path parameters { [key]: string }
|
|
812
|
+
req.cookies // parsed cookies { [name]: string }
|
|
813
|
+
req.body // parsed body (requires body parser middleware)
|
|
814
|
+
req.buffer // raw body buffer (requires ReadBuffer middleware)
|
|
815
|
+
req.session // set by auth.ts
|
|
816
|
+
req.session.account // tenant identifier
|
|
817
|
+
req.session.username
|
|
818
|
+
req.session.groups // string[]
|
|
819
|
+
req.session.token
|
|
820
|
+
req.uuid // unique request id
|
|
821
|
+
req.startMS // request start timestamp ms
|
|
822
|
+
req.logger // per-request logger — includes path/method/uuid/remoteAddress
|
|
823
|
+
req.results // pipeline accumulator
|
|
824
|
+
```
|
|
464
825
|
|
|
465
|
-
|
|
826
|
+
## test folder
|
|
466
827
|
|
|
467
|
-
|
|
828
|
+
test files named `*.test.ts`.
|
|
468
829
|
|
|
469
|
-
|
|
830
|
+
```ts
|
|
831
|
+
import { describe, it } from "node:test";
|
|
832
|
+
import { strictEqual } from "assert";
|
|
470
833
|
|
|
471
|
-
|
|
834
|
+
describe("posts", () => {
|
|
835
|
+
it("GET /posts returns 200", async () => {
|
|
836
|
+
const res = await test.request({
|
|
837
|
+
url: "/posts",
|
|
838
|
+
method: "GET",
|
|
839
|
+
disableThrow: true
|
|
840
|
+
});
|
|
841
|
+
strictEqual(res.status, 200);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
```
|
|
472
845
|
|
|
473
|
-
|
|
846
|
+
`test.request` hits the running miqro server via Unix socket. no port needed.
|
|
474
847
|
|
|
475
|
-
|
|
848
|
+
test JSX components:
|
|
476
849
|
|
|
477
|
-
|
|
850
|
+
```ts
|
|
851
|
+
import JSX from "@miqro/jsx";
|
|
852
|
+
import { Counter } from "./counter.js";
|
|
478
853
|
|
|
479
|
-
|
|
854
|
+
it("renders counter", test.jsx.test(async (container, root, runtime) => {
|
|
855
|
+
container.render(JSX.createElement(Counter, { initial: 0 }));
|
|
856
|
+
strictEqual(root.innerHTML.includes("0"), true);
|
|
857
|
+
}));
|
|
858
|
+
```
|
|
480
859
|
|
|
481
|
-
|
|
860
|
+
run tests:
|
|
482
861
|
|
|
483
|
-
|
|
862
|
+
```
|
|
863
|
+
miqro --test --service app/
|
|
864
|
+
```
|
|
484
865
|
|
|
485
|
-
##
|
|
866
|
+
## Miqro object
|
|
486
867
|
|
|
487
|
-
|
|
868
|
+
```ts
|
|
869
|
+
import { Miqro } from "miqro";
|
|
488
870
|
|
|
489
|
-
|
|
871
|
+
const app = new Miqro({
|
|
872
|
+
services: ["app/"],
|
|
873
|
+
port: "3000",
|
|
874
|
+
name: "myapp", // required in cluster mode
|
|
875
|
+
hotreload: false,
|
|
876
|
+
editor: false
|
|
877
|
+
});
|
|
490
878
|
|
|
491
|
-
|
|
879
|
+
await app.inflate({ inflateDir: "build/" }); // generate static files
|
|
880
|
+
await app.inflate(); // inflate to memory
|
|
881
|
+
await app.start(); // start server
|
|
882
|
+
await app.stop(); // stop server
|
|
883
|
+
await app.reload(); // hot reload
|
|
884
|
+
await app.restart(); // full restart
|
|
885
|
+
await app.dispose(); // cleanup cluster connections
|
|
886
|
+
```
|
|
492
887
|
|
|
493
|
-
|
|
888
|
+
trigger static generation from a running server:
|
|
494
889
|
|
|
495
|
-
```
|
|
890
|
+
```ts
|
|
891
|
+
export default {
|
|
892
|
+
path: "/publish",
|
|
893
|
+
method: "POST",
|
|
894
|
+
handler: async (req, res) => {
|
|
895
|
+
const generator = new Miqro({ services: ["app/"] });
|
|
896
|
+
await generator.inflate({ inflateDir: "build/" });
|
|
897
|
+
return res.json({ ok: true });
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
```
|
|
496
901
|
|
|
497
902
|
## cluster
|
|
498
903
|
|
|
499
|
-
|
|
904
|
+
```
|
|
905
|
+
npx miqro-cluster --service app/
|
|
906
|
+
CLUSTER_COUNT=4 npx miqro-cluster --service app/
|
|
907
|
+
npx miqro-cluster
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
to run arbitrary scripts in cluster mode:
|
|
911
|
+
|
|
912
|
+
```
|
|
913
|
+
npx miqro-runner server.js
|
|
914
|
+
CLUSTER_COUNT=4 npx miqro-runner server.js
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
## --no-build
|
|
500
918
|
|
|
501
|
-
|
|
919
|
+
use tsc instead of esbuild. run tsc first, then miqro.
|
|
502
920
|
|
|
503
|
-
|
|
921
|
+
generate a tsconfig:
|
|
504
922
|
|
|
505
|
-
|
|
923
|
+
```
|
|
924
|
+
miqro --install-tsconfig
|
|
925
|
+
```
|
|
506
926
|
|
|
507
|
-
|
|
927
|
+
`tsconfig.json`:
|
|
928
|
+
|
|
929
|
+
```json
|
|
930
|
+
{
|
|
931
|
+
"compilerOptions": {
|
|
932
|
+
"target": "es2022",
|
|
933
|
+
"noEmit": true,
|
|
934
|
+
"module": "NodeNext",
|
|
935
|
+
"moduleResolution": "nodenext",
|
|
936
|
+
"lib": ["es2021", "dom"],
|
|
937
|
+
"jsx": "react",
|
|
938
|
+
"jsxFactory": "JSX.createElement",
|
|
939
|
+
"jsxFragmentFactory": "JSX.Fragment"
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
```
|
|
508
943
|
|
|
509
|
-
|
|
944
|
+
`noEmit: true` — tsc type-checks only, esbuild handles transpilation at runtime. remove `noEmit` and set `outDir` to use tsc output with `--no-build`.
|
|
510
945
|
|
|
511
|
-
|
|
946
|
+
development with tsc + miqro:
|
|
512
947
|
|
|
513
|
-
```
|
|
948
|
+
```
|
|
949
|
+
tsc --watch &
|
|
950
|
+
miqro --no-build --watch --service app/
|
|
951
|
+
```
|
|
514
952
|
|
|
515
|
-
|
|
953
|
+
**note:** in `--no-build` mode module-level state is shared across routes within a process. the stateless coding guarantee that esbuild provides does not apply. avoid module-level mutable state.
|
|
516
954
|
|
|
517
|
-
##
|
|
955
|
+
## static site generator
|
|
518
956
|
|
|
519
|
-
|
|
957
|
+
```
|
|
958
|
+
miqro --inflate --inflate-dir build/ --service app/
|
|
959
|
+
```
|
|
520
960
|
|
|
521
|
-
|
|
961
|
+
`.html.tsx` handlers run with a live db connection. output is static HTML written to `build/`.
|
|
522
962
|
|
|
523
|
-
|
|
963
|
+
```
|
|
964
|
+
miqro --inflate --inflate-dir build/ --service app/
|
|
965
|
+
python3 -m http.server 8080 build/app/static/
|
|
966
|
+
```
|
|
524
967
|
|
|
525
|
-
|
|
968
|
+
## https
|
|
526
969
|
|
|
527
|
-
```
|
|
970
|
+
```
|
|
971
|
+
miqro --https --https-key server.key --https-cert server.cert --service app/
|
|
972
|
+
```
|
|
528
973
|
|
|
529
|
-
|
|
974
|
+
with http redirect:
|
|
530
975
|
|
|
531
|
-
```
|
|
976
|
+
```
|
|
977
|
+
miqro --https --https-key server.key --https-cert server.cert --https-redirect 8080 --service app/
|
|
978
|
+
```
|
|
532
979
|
|
|
533
|
-
|
|
980
|
+
starts an additional http server on port 8080 that redirects all requests to https.
|
|
534
981
|
|
|
535
|
-
|
|
982
|
+
via env or `miqro.json`:
|
|
536
983
|
|
|
537
|
-
|
|
984
|
+
```json
|
|
985
|
+
{
|
|
986
|
+
"https": true,
|
|
987
|
+
"httpsKey": "./server.key",
|
|
988
|
+
"httpsCert": "./server.cert",
|
|
989
|
+
"httpsRedirect": "8080"
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
## cli
|
|
995
|
+
|
|
996
|
+
```
|
|
997
|
+
miqro --service app/
|
|
998
|
+
miqro --watch --service app/
|
|
999
|
+
miqro --editor --service app/
|
|
1000
|
+
miqro --test --service app/
|
|
1001
|
+
miqro --migrate-up --service app/
|
|
1002
|
+
miqro --migrate-down --service app/
|
|
1003
|
+
miqro --inflate --inflate-dir build/ --service app/
|
|
1004
|
+
miqro --generate-doc --generate-doc-out API.md --service app/
|
|
1005
|
+
miqro --compile --service app/
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
flags:
|
|
538
1009
|
|
|
539
|
-
|
|
1010
|
+
```
|
|
1011
|
+
--watch auto reload on file changes
|
|
1012
|
+
--hot-reload enable hot-reload with --watch
|
|
1013
|
+
--test run tests
|
|
1014
|
+
--migrate-up run migrations up
|
|
1015
|
+
--migrate-down run migrations down
|
|
1016
|
+
--inflate generate static files
|
|
1017
|
+
--inflate-dir output directory (default: inflated/)
|
|
1018
|
+
--editor run with built-in editor
|
|
1019
|
+
--generate-doc generate API documentation
|
|
1020
|
+
--generate-doc-out output file (default: API.md)
|
|
1021
|
+
--generate-doc-type MD | JSON | HTML (default: MD)
|
|
1022
|
+
--generate-doc-all include all routes
|
|
1023
|
+
--compile build NODE:SEA binary
|
|
1024
|
+
--no-build skip esbuild, use pre-compiled files
|
|
1025
|
+
--no-minify skip minification
|
|
1026
|
+
--inflate-only-assets inflate assets only
|
|
1027
|
+
--inflate-flat inflate into dir directly
|
|
1028
|
+
--inflate-sea inflate with SEA compilation scripts
|
|
1029
|
+
--install-tsconfig create tsconfig.json
|
|
1030
|
+
--install-miqrojson create miqro.json
|
|
1031
|
+
--install install from binary cache (SEA only)
|
|
1032
|
+
--disable-miqrojson ignore miqro.json
|
|
1033
|
+
--log-file override LOG_FILE
|
|
1034
|
+
--browser override BROWSER
|
|
1035
|
+
--config override miqro.json path
|
|
1036
|
+
--port override PORT
|
|
1037
|
+
--name override server name
|
|
1038
|
+
--https serve with https
|
|
1039
|
+
--https-key path to server.key
|
|
1040
|
+
--https-cert path to server.cert
|
|
1041
|
+
--https-redirect http redirect port
|
|
1042
|
+
--inflate-parallel max parallel esbuild instances (default: 1)
|
|
1043
|
+
```
|
|
540
1044
|
|
|
541
|
-
|
|
1045
|
+
environment variables:
|
|
542
1046
|
|
|
543
|
-
|
|
1047
|
+
```
|
|
1048
|
+
PORT default: 8080
|
|
1049
|
+
BROWSER default browser, none to disable
|
|
1050
|
+
LOG_FILE default: ./server.log
|
|
1051
|
+
LOG_LEVEL error | warn | info | debug | trace | none
|
|
1052
|
+
LOG_LEVEL_<IDENTIFIER> per-route log level
|
|
1053
|
+
DB enable db features
|
|
1054
|
+
DB_STORAGE sqlite storage path (default: ./db.sqlite3)
|
|
1055
|
+
DB_DIALECT node:sqlite | sqlite3 | pg
|
|
1056
|
+
DB_CONNECTION connection url (postgres)
|
|
1057
|
+
CLEAR_JSX_CACHE clear esbuild cache (default: 1)
|
|
1058
|
+
JSX_TMP esbuild tmp dir (default: /tmp/jsx_tmp)
|
|
1059
|
+
CLUSTER_COUNT number of cluster workers
|
|
1060
|
+
```
|
|
544
1061
|
|
|
545
|
-
|
|
1062
|
+
## build
|
|
546
1063
|
|
|
547
|
-
|
|
1064
|
+
```
|
|
1065
|
+
npm install
|
|
1066
|
+
npm run build
|
|
1067
|
+
```
|
|
548
1068
|
|
|
549
|
-
|
|
1069
|
+
### SEA binary
|
|
550
1070
|
|
|
551
|
-
|
|
1071
|
+
```
|
|
1072
|
+
npm install
|
|
1073
|
+
npm run precompile
|
|
1074
|
+
npm run compile
|
|
1075
|
+
```
|
|
552
1076
|
|
|
553
|
-
|
|
1077
|
+
binaries in `bin/`.
|
|
554
1078
|
|
|
555
1079
|
```
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
python3 -m http.server 8080
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
### more cli usages
|
|
562
|
-
|
|
563
|
-
```miqro --help```
|
|
564
|
-
|
|
565
|
-
```
|
|
566
|
-
usage: miqro [...FLAGS] --service app/
|
|
567
|
-
|
|
568
|
-
==examples==
|
|
569
|
-
|
|
570
|
-
miqro --watch --service front/
|
|
571
|
-
PORT=8181 miqro --service api/ --service front/
|
|
572
|
-
miqro --test --service front/
|
|
573
|
-
miqro --inflate --service front/
|
|
574
|
-
miqro --generate-doc --generate-doc-out API.md --service front/
|
|
575
|
-
CLUSTER_COUNT=10 miqro-cluster --service api/
|
|
576
|
-
|
|
577
|
-
==flags==
|
|
578
|
-
|
|
579
|
-
-v, --version
|
|
580
|
-
outputs the version number
|
|
581
|
-
-h, --help
|
|
582
|
-
outputs this page.
|
|
583
|
-
--watch
|
|
584
|
-
use to auto reload the server when files change.
|
|
585
|
-
--hot-reload
|
|
586
|
-
enables the hot-reload functionality use with --watch.
|
|
587
|
-
--test
|
|
588
|
-
run the tests for a service.
|
|
589
|
-
--migrate-up
|
|
590
|
-
migrations up.
|
|
591
|
-
--migrate-down
|
|
592
|
-
migrations down.
|
|
593
|
-
--inflate
|
|
594
|
-
inflates the application into a directory using esbuild.
|
|
595
|
-
--inflate-dir
|
|
596
|
-
to set the output directory of the --inflate command. default value is inflated/.
|
|
597
|
-
--editor
|
|
598
|
-
runs the application with a built-in editor.
|
|
599
|
-
--generate-doc
|
|
600
|
-
generates a documentation for the api endpoints of the service.
|
|
601
|
-
--generate-doc-out
|
|
602
|
-
the output file for the generated documentation. default value is API.md.
|
|
603
|
-
--generate-doc-type
|
|
604
|
-
the format of the generated documentation. it can be JSON or MD. default value is MD.
|
|
605
|
-
--generate-doc-all
|
|
606
|
-
outputs all the server routes in the documentation output.
|
|
607
|
-
--compile
|
|
608
|
-
inflates the application and tries to create a NODE SEA binary.
|
|
609
|
-
--no-build
|
|
610
|
-
disables calling esbuild during imports in runtime. Notice that to use jsx you will need to run tsc or esbuild on your jsx files to transpile them to js.
|
|
611
|
-
--no-minify
|
|
612
|
-
disables calling minifing min.js files.
|
|
613
|
-
--inflate-only-assets
|
|
614
|
-
inflates ONLY the application assets. must be used with --inflate.
|
|
615
|
-
--inflate-flat
|
|
616
|
-
inflates files into the inflate-dir directly.
|
|
617
|
-
--inflate-sea
|
|
618
|
-
inflates the application with sea compilation scripts.
|
|
619
|
-
--install-tsconfig
|
|
620
|
-
creates a tsconfig.json configured to use with --install-types.
|
|
621
|
-
--install-miqrojson
|
|
622
|
-
creates a default miqro.json file.
|
|
623
|
-
--install
|
|
624
|
-
creates a node_modules folder from binary cache (only available in sea binary).
|
|
625
|
-
--disable-miqrojson
|
|
626
|
-
disables the load of miqro.json file.
|
|
627
|
-
--log-file
|
|
628
|
-
overrides the default log file from LOG_FILE.
|
|
629
|
-
--browser
|
|
630
|
-
overrides the default browser from BROWSER.
|
|
631
|
-
--config
|
|
632
|
-
overrides the default miqro.json path.
|
|
633
|
-
--port
|
|
634
|
-
overrides the default port from PORT.
|
|
635
|
-
--name
|
|
636
|
-
overrides the default name of the server.
|
|
637
|
-
--https
|
|
638
|
-
serves the server in https instead of http
|
|
639
|
-
--https-key
|
|
640
|
-
point to a server.key file for https.
|
|
641
|
-
--https-cert
|
|
642
|
-
point to a server.cert file for https.
|
|
643
|
-
--https-redirect
|
|
644
|
-
serves an aditional http server that redirects to https. it needs a port number.
|
|
645
|
-
--inflate-parallel
|
|
646
|
-
sets the max parallel esbuild instances. defaults to 1.
|
|
647
|
-
|
|
648
|
-
==environment variables==
|
|
649
|
-
|
|
650
|
-
PORT
|
|
651
|
-
override the default 8080 port.
|
|
652
|
-
BROWSER
|
|
653
|
-
override the default browser. change to none to disable.".
|
|
654
|
-
LOG_FILE
|
|
655
|
-
override the default ./server.log file
|
|
656
|
-
DB
|
|
657
|
-
enable the server.db features
|
|
658
|
-
DB_STORAGE
|
|
659
|
-
override the default local db location ./db.sqlite3
|
|
660
|
-
DB_DIALECT
|
|
661
|
-
override the default node:sqlite
|
|
662
|
-
DB_CONNECTION
|
|
663
|
-
override the default connection url
|
|
664
|
-
CLEAR_JSX_CACHE
|
|
665
|
-
set to 1 or 0 to enable or disable the clearing of the esbuild cache defaults to 1.
|
|
666
|
-
JSX_TMP
|
|
667
|
-
set custom location of esbuild builds defaults to /tmp/jsx_tmp.
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
## development
|
|
671
|
-
|
|
672
|
-
### build node package
|
|
673
|
-
|
|
674
|
-
1. install dependencies
|
|
675
|
-
|
|
676
|
-
```npm install```
|
|
677
|
-
|
|
678
|
-
2. build
|
|
679
|
-
|
|
680
|
-
```npm build```
|
|
681
|
-
|
|
682
|
-
### build binary for running as standalone binary
|
|
683
|
-
|
|
684
|
-
1. install dependencies
|
|
685
|
-
|
|
686
|
-
```npm install```
|
|
687
|
-
|
|
688
|
-
2. install sea deps (esbuild and nodejs binaries)
|
|
689
|
-
|
|
690
|
-
```npm run precompile``` or ```sh ./sea/precompile.sh```
|
|
691
|
-
|
|
692
|
-
3. compile
|
|
693
|
-
|
|
694
|
-
```npm run compile``` or ```sh ./compile.sh```
|
|
695
|
-
|
|
696
|
-
to binaries will be produced in the ```bin/``` folder.
|
|
697
|
-
|
|
698
|
-
example
|
|
699
|
-
|
|
700
|
-
```./bin/linux-x64/miqro --help```
|
|
701
|
-
|
|
702
|
-
you can copy this binary to a computer without Node.js installed and run it to try.
|
|
1080
|
+
./bin/linux-x64/miqro --help
|
|
1081
|
+
```
|