@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 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.
@@ -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
- body: schema,
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
- app.get(`/api/${name}/examples/${exampleName}`, async (_, reply) => {
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,2 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ export declare function setupSwagger(app: FastifyInstance): Promise<void>;
@@ -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.2",
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
- body: schema,
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
- app.get(`/api/${name}/examples/${exampleName}`, async (_, reply) => {
102
- const ssrComponent = await viteSsr.ssrLoadModule(entry, {
103
- fixStacktrace: true,
104
- });
105
- const { html, assets } = renderApp(
106
- appEntry,
107
- ssrComponent.default,
108
- example as Record<string, unknown>
109
- );
110
-
111
- reply.type('text/html');
112
-
113
- return renderPreviewPage(html, assets);
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
+ }