@nerest/nerest 0.0.2 → 0.0.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 +2 -0
- package/dist/server/index.js +20 -3
- package/dist/server/swagger.d.ts +2 -0
- package/dist/server/swagger.js +50 -0
- package/package.json +3 -1
- package/server/index.ts +38 -16
- package/server/swagger.ts +58 -0
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ The app directory may contain an `examples` subdirectory with example JSON files
|
|
|
30
30
|
|
|
31
31
|
The app directory should contain a `schema.json` file that describes the schema of a request body for this specific app in the [JSON Schema language](https://json-schema.org/). All requests sent to this app, and app examples from the `examples` subdirectory will be validated against this schema. `ajv` and `ajv-formats` are used for validation, so all JSON Schema features implemented by them are supported.
|
|
32
32
|
|
|
33
|
+
OpenAPI specification is compiled automatically based on the provided schemas and becomes available at `/api/json`. It can also be explored through Swagger UI that becomes available at `/api`.
|
|
34
|
+
|
|
33
35
|
## Development
|
|
34
36
|
|
|
35
37
|
Run the build script to build the framework.
|
package/dist/server/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const apps_1 = require("./apps");
|
|
|
13
13
|
const render_1 = require("./render");
|
|
14
14
|
const preview_1 = require("./preview");
|
|
15
15
|
const validator_1 = require("./validator");
|
|
16
|
+
const swagger_1 = require("./swagger");
|
|
16
17
|
// TODO: this turned out to be a dev server, production server
|
|
17
18
|
// will most likely be implemented separately
|
|
18
19
|
async function createServer() {
|
|
@@ -52,6 +53,7 @@ async function createServer() {
|
|
|
52
53
|
// we can use both to validate request bodies and examples against
|
|
53
54
|
// app schemas
|
|
54
55
|
app.setValidatorCompiler(({ schema }) => validator_1.validator.compile(schema));
|
|
56
|
+
await (0, swagger_1.setupSwagger)(app);
|
|
55
57
|
for (const appEntry of Object.values(apps)) {
|
|
56
58
|
const { name, entry, examples, schema } = appEntry;
|
|
57
59
|
const routeOptions = {};
|
|
@@ -59,7 +61,15 @@ async function createServer() {
|
|
|
59
61
|
// TODO: disallow apps without schemas in production build
|
|
60
62
|
if (schema) {
|
|
61
63
|
routeOptions.schema = {
|
|
62
|
-
|
|
64
|
+
// Use description as Swagger summary, since summary is visible
|
|
65
|
+
// even when the route is collapsed in the UI
|
|
66
|
+
summary: schema.description,
|
|
67
|
+
body: {
|
|
68
|
+
...schema,
|
|
69
|
+
// Mix examples into the schema so they become accessible
|
|
70
|
+
// in the Swagger UI
|
|
71
|
+
examples: Object.values(examples),
|
|
72
|
+
},
|
|
63
73
|
};
|
|
64
74
|
}
|
|
65
75
|
// POST /api/{name} -> render app with request.body as props
|
|
@@ -79,7 +89,14 @@ async function createServer() {
|
|
|
79
89
|
}
|
|
80
90
|
// GET /api/{name}/examples/{example} -> render a preview page
|
|
81
91
|
// with a predefined example body
|
|
82
|
-
|
|
92
|
+
const exampleRoute = `/api/${name}/examples/${exampleName}`;
|
|
93
|
+
app.get(exampleRoute, {
|
|
94
|
+
schema: {
|
|
95
|
+
// Add a clickable link to the example route in route's Swagger
|
|
96
|
+
// description so it's easier to navigate to
|
|
97
|
+
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
98
|
+
},
|
|
99
|
+
}, async (_, reply) => {
|
|
83
100
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
84
101
|
fixStacktrace: true,
|
|
85
102
|
});
|
|
@@ -90,7 +107,7 @@ async function createServer() {
|
|
|
90
107
|
}
|
|
91
108
|
}
|
|
92
109
|
// TODO: only do this locally, load from CDN in production
|
|
93
|
-
app.register(static_1.default, {
|
|
110
|
+
await app.register(static_1.default, {
|
|
94
111
|
root: path_1.default.join(root, 'dist'),
|
|
95
112
|
// TODO: maybe use @fastify/cors instead
|
|
96
113
|
setHeaders(res) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupSwagger = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
10
|
+
const swagger_ui_1 = __importDefault(require("@fastify/swagger-ui"));
|
|
11
|
+
// Setup automatic OpenAPI specification compilation and enable
|
|
12
|
+
// Swagger UI at the `/api` route
|
|
13
|
+
async function setupSwagger(app) {
|
|
14
|
+
let appInfo = {};
|
|
15
|
+
try {
|
|
16
|
+
const packageJson = fs_1.default.readFileSync(path_1.default.join(process.cwd(), 'package.json'), { encoding: 'utf-8' });
|
|
17
|
+
appInfo = JSON.parse(packageJson);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
// We only use package.json info to setup Swagger info and links,
|
|
21
|
+
// if we are unable to load them -- that's fine
|
|
22
|
+
}
|
|
23
|
+
const homepage = appInfo.homepage ||
|
|
24
|
+
(typeof appInfo.repository === 'string'
|
|
25
|
+
? appInfo.repository
|
|
26
|
+
: appInfo.repository?.url);
|
|
27
|
+
await app.register(swagger_1.default, {
|
|
28
|
+
openapi: {
|
|
29
|
+
info: {
|
|
30
|
+
title: appInfo.name ?? 'Nerest micro frontend',
|
|
31
|
+
description: appInfo.description,
|
|
32
|
+
version: appInfo.version ?? '',
|
|
33
|
+
contact: homepage
|
|
34
|
+
? {
|
|
35
|
+
name: 'Homepage',
|
|
36
|
+
url: homepage,
|
|
37
|
+
}
|
|
38
|
+
: undefined,
|
|
39
|
+
},
|
|
40
|
+
externalDocs: {
|
|
41
|
+
url: 'https://github.com/nerestjs/nerest',
|
|
42
|
+
description: 'Built with Nerest',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
await app.register(swagger_ui_1.default, {
|
|
47
|
+
routePrefix: '/api',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
exports.setupSwagger = setupSwagger;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerest/nerest",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "React micro frontend framework",
|
|
5
5
|
"homepage": "https://github.com/nerestjs/nerest#readme",
|
|
6
6
|
"repository": {
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@fastify/static": "^6.9.0",
|
|
50
|
+
"@fastify/swagger": "^8.3.1",
|
|
51
|
+
"@fastify/swagger-ui": "^1.4.0",
|
|
50
52
|
"ajv": "^8.12.0",
|
|
51
53
|
"ajv-formats": "^2.1.1",
|
|
52
54
|
"fast-uri": "^2.2.0",
|
package/server/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { loadApps } from './apps';
|
|
|
14
14
|
import { renderApp } from './render';
|
|
15
15
|
import { renderPreviewPage } from './preview';
|
|
16
16
|
import { validator } from './validator';
|
|
17
|
+
import { setupSwagger } from './swagger';
|
|
17
18
|
|
|
18
19
|
// TODO: this turned out to be a dev server, production server
|
|
19
20
|
// will most likely be implemented separately
|
|
@@ -60,6 +61,8 @@ export async function createServer() {
|
|
|
60
61
|
// app schemas
|
|
61
62
|
app.setValidatorCompiler(({ schema }) => validator.compile(schema));
|
|
62
63
|
|
|
64
|
+
await setupSwagger(app);
|
|
65
|
+
|
|
63
66
|
for (const appEntry of Object.values(apps)) {
|
|
64
67
|
const { name, entry, examples, schema } = appEntry;
|
|
65
68
|
|
|
@@ -69,7 +72,15 @@ export async function createServer() {
|
|
|
69
72
|
// TODO: disallow apps without schemas in production build
|
|
70
73
|
if (schema) {
|
|
71
74
|
routeOptions.schema = {
|
|
72
|
-
|
|
75
|
+
// Use description as Swagger summary, since summary is visible
|
|
76
|
+
// even when the route is collapsed in the UI
|
|
77
|
+
summary: schema.description as string,
|
|
78
|
+
body: {
|
|
79
|
+
...schema,
|
|
80
|
+
// Mix examples into the schema so they become accessible
|
|
81
|
+
// in the Swagger UI
|
|
82
|
+
examples: Object.values(examples),
|
|
83
|
+
},
|
|
73
84
|
};
|
|
74
85
|
}
|
|
75
86
|
|
|
@@ -98,25 +109,36 @@ export async function createServer() {
|
|
|
98
109
|
|
|
99
110
|
// GET /api/{name}/examples/{example} -> render a preview page
|
|
100
111
|
// with a predefined example body
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
reply
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const exampleRoute = `/api/${name}/examples/${exampleName}`;
|
|
113
|
+
app.get(
|
|
114
|
+
exampleRoute,
|
|
115
|
+
{
|
|
116
|
+
schema: {
|
|
117
|
+
// Add a clickable link to the example route in route's Swagger
|
|
118
|
+
// description so it's easier to navigate to
|
|
119
|
+
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
async (_, reply) => {
|
|
123
|
+
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
124
|
+
fixStacktrace: true,
|
|
125
|
+
});
|
|
126
|
+
const { html, assets } = renderApp(
|
|
127
|
+
appEntry,
|
|
128
|
+
ssrComponent.default,
|
|
129
|
+
example as Record<string, unknown>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
reply.type('text/html');
|
|
133
|
+
|
|
134
|
+
return renderPreviewPage(html, assets);
|
|
135
|
+
}
|
|
136
|
+
);
|
|
115
137
|
}
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
// TODO: only do this locally, load from CDN in production
|
|
119
|
-
app.register(fastifyStatic, {
|
|
141
|
+
await app.register(fastifyStatic, {
|
|
120
142
|
root: path.join(root, 'dist'),
|
|
121
143
|
// TODO: maybe use @fastify/cors instead
|
|
122
144
|
setHeaders(res: ServerResponse) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import type { FastifyInstance } from 'fastify';
|
|
4
|
+
import fastifySwagger from '@fastify/swagger';
|
|
5
|
+
import fastifySwaggerUi from '@fastify/swagger-ui';
|
|
6
|
+
|
|
7
|
+
// Setup automatic OpenAPI specification compilation and enable
|
|
8
|
+
// Swagger UI at the `/api` route
|
|
9
|
+
export async function setupSwagger(app: FastifyInstance) {
|
|
10
|
+
let appInfo: {
|
|
11
|
+
name?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
homepage?: string;
|
|
15
|
+
repository?: string | { url?: string };
|
|
16
|
+
} = {};
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const packageJson = fs.readFileSync(
|
|
20
|
+
path.join(process.cwd(), 'package.json'),
|
|
21
|
+
{ encoding: 'utf-8' }
|
|
22
|
+
);
|
|
23
|
+
appInfo = JSON.parse(packageJson);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// We only use package.json info to setup Swagger info and links,
|
|
26
|
+
// if we are unable to load them -- that's fine
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const homepage =
|
|
30
|
+
appInfo.homepage ||
|
|
31
|
+
(typeof appInfo.repository === 'string'
|
|
32
|
+
? appInfo.repository
|
|
33
|
+
: appInfo.repository?.url);
|
|
34
|
+
|
|
35
|
+
await app.register(fastifySwagger, {
|
|
36
|
+
openapi: {
|
|
37
|
+
info: {
|
|
38
|
+
title: appInfo.name ?? 'Nerest micro frontend',
|
|
39
|
+
description: appInfo.description,
|
|
40
|
+
version: appInfo.version ?? '',
|
|
41
|
+
contact: homepage
|
|
42
|
+
? {
|
|
43
|
+
name: 'Homepage',
|
|
44
|
+
url: homepage,
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
},
|
|
48
|
+
externalDocs: {
|
|
49
|
+
url: 'https://github.com/nerestjs/nerest',
|
|
50
|
+
description: 'Built with Nerest',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await app.register(fastifySwaggerUi, {
|
|
56
|
+
routePrefix: '/api',
|
|
57
|
+
});
|
|
58
|
+
}
|