@openaddresses/batch-schema 9.4.0 → 10.0.1

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 (56) hide show
  1. package/.eslintrc.json +5 -47
  2. package/.github/workflows/release.yml +5 -2
  3. package/CHANGELOG.md +8 -0
  4. package/LICENSE +1 -1
  5. package/README.md +21 -17
  6. package/dist/index.js +3 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/lib/api.js +46 -0
  9. package/dist/lib/api.js.map +1 -0
  10. package/{lib → dist/lib}/openapi.js +35 -29
  11. package/dist/lib/openapi.js.map +1 -0
  12. package/dist/lib/schema.js +237 -0
  13. package/dist/lib/schema.js.map +1 -0
  14. package/dist/lib/types.js +2 -0
  15. package/dist/lib/types.js.map +1 -0
  16. package/dist/test/schema.srv.test.js +98 -0
  17. package/dist/test/schema.srv.test.js.map +1 -0
  18. package/docs/assets/style.css +1354 -87
  19. package/docs/index.html +74 -792
  20. package/{routes/schema.js → lib/api.ts} +11 -4
  21. package/lib/openapi.ts +122 -0
  22. package/lib/schema.ts +293 -0
  23. package/lib/types.ts +32 -0
  24. package/package.json +16 -11
  25. package/test/{schema.srv.test.js → schema.srv.test.ts} +9 -20
  26. package/tsconfig.json +27 -0
  27. package/docs/assets/anchor.js +0 -350
  28. package/docs/assets/bass-addons.css +0 -12
  29. package/docs/assets/bass.css +0 -544
  30. package/docs/assets/fonts/EOT/SourceCodePro-Bold.eot +0 -0
  31. package/docs/assets/fonts/EOT/SourceCodePro-Regular.eot +0 -0
  32. package/docs/assets/fonts/LICENSE.txt +0 -93
  33. package/docs/assets/fonts/OTF/SourceCodePro-Bold.otf +0 -0
  34. package/docs/assets/fonts/OTF/SourceCodePro-Regular.otf +0 -0
  35. package/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf +0 -0
  36. package/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf +0 -0
  37. package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff +0 -0
  38. package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff +0 -0
  39. package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff +0 -0
  40. package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff +0 -0
  41. package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 +0 -0
  42. package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 +0 -0
  43. package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 +0 -0
  44. package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 +0 -0
  45. package/docs/assets/fonts/source-code-pro.css +0 -23
  46. package/docs/assets/github.css +0 -123
  47. package/docs/assets/site.js +0 -168
  48. package/docs/assets/split.css +0 -15
  49. package/docs/assets/split.js +0 -782
  50. package/lib/middleware.js +0 -159
  51. package/lib/param.js +0 -29
  52. package/lib/schema.js +0 -254
  53. package/schema/req.query.ListSchema.json +0 -25
  54. package/schema/res.ListSchema.json +0 -47
  55. package/test/schema.test.js +0 -33
  56. /package/{index.js → index.ts} +0 -0
@@ -1,9 +1,15 @@
1
1
  import Err from '@openaddresses/batch-error';
2
+ import Schema from './schema.js'
3
+ import { OpenAPIV3 as Doc } from 'openapi-types'
4
+ import { Type } from '@sinclair/typebox'
2
5
 
3
- export default async function router(schema) {
6
+ export default async function router(schema: Schema) {
4
7
  await schema.get('/schema', {
5
- query: 'req.query.ListSchema.json',
6
- res: 'res.ListSchema.json',
8
+ query: Type.Object({
9
+ method: Type.Optional(Type.Uppercase(Type.Enum(Doc.HttpMethods))),
10
+ url: Type.Optional(Type.String())
11
+ }),
12
+ res: Type.Object({}),
7
13
  description: `
8
14
  List all JSON Schemas in use
9
15
  With no parameters this API will return a list of all the endpoints that have a form of schema validation
@@ -26,7 +32,8 @@ export default async function router(schema) {
26
32
  });
27
33
 
28
34
  await schema.get('/openapi', {
29
- description: 'Return a OpenAPI Schema for the API'
35
+ description: 'Return a OpenAPI Schema for the API',
36
+ res: Type.Object({})
30
37
  }, async (req, res) => {
31
38
  try {
32
39
  return res.json(schema.docs.base);
package/lib/openapi.ts ADDED
@@ -0,0 +1,122 @@
1
+ import { OpenAPIV3 as Doc } from 'openapi-types'
2
+ import { RequestValidation } from './types.js';
3
+
4
+ /**
5
+ * @class
6
+ */
7
+ export default class Docs {
8
+ base: Doc.Document;
9
+
10
+ constructor(info?: {
11
+ title: string;
12
+ version: string;
13
+ [k: string]: string;
14
+ }) {
15
+ this.base = {
16
+ openapi: '3.0.3',
17
+ info: info || {
18
+ title: 'OpenAPI Schema',
19
+ version: '1.0.0'
20
+ },
21
+ paths: {}
22
+ };
23
+ }
24
+
25
+ push(
26
+ parsed: {
27
+ method: Doc.HttpMethods
28
+ path: string;
29
+ },
30
+ schemas: RequestValidation<any, any, any, any>
31
+ ): void {
32
+ const path: Array<string> = [];
33
+
34
+ for (let sec of parsed.path.split('/')) {
35
+ for (const lit of ['.', '-']) { // These chars are used literally and can't be part of a param name
36
+ const mini: Array<string> = [];
37
+ for (const s of sec.split(lit)) {
38
+ if (s[0] === ':') mini.push(`{${s}}`);
39
+ else mini.push(s);
40
+ }
41
+ sec = mini.join(lit);
42
+ }
43
+
44
+ path.push(sec);
45
+ }
46
+
47
+ const pathstr = path.join('/');
48
+
49
+ // @ts-ignore
50
+ if (this.base.paths[pathstr] && this.base.paths[pathstr][parsed.method]) throw new Error(`Duplicate Path: ${parsed.method}: ${parsed.path}`);
51
+
52
+ if (!schemas.private) {
53
+ if (!this.base.paths[pathstr]) this.base.paths[pathstr] = {};
54
+
55
+ const document: Doc.OperationObject = {
56
+ summary: schemas.description || 'No Description',
57
+ parameters: [],
58
+ tags: [schemas.group || 'Default'],
59
+ responses: {}
60
+ };
61
+
62
+ for (const key in schemas) {
63
+ if (key[0] === ':') {
64
+ if (!document.parameters) document.parameters = [];
65
+ document.parameters.push({
66
+ in: 'path',
67
+ name: key,
68
+ schema: {
69
+ type: schemas[key]
70
+ },
71
+ required: true
72
+ });
73
+ }
74
+ }
75
+
76
+ if (schemas.query && schemas.query.type === 'object') {
77
+ for (const name in (schemas.query.properties || {})) {
78
+ const query = schemas.query.properties[name];
79
+ if (!document.parameters) document.parameters = [];
80
+ document.parameters.push({
81
+ in: 'query',
82
+ name,
83
+ schema: query,
84
+ required: query.required || (schemas.query.required || []).includes(name),
85
+ description: query.description || 'No Description'
86
+ });
87
+ }
88
+ }
89
+
90
+ if (schemas.res) {
91
+ const response: Doc.ResponseObject = {
92
+ description: 'Successful Response',
93
+ content: {
94
+ 'application/json': {
95
+ schema: schemas.res
96
+ }
97
+ }
98
+ }
99
+ document.responses['200'] = response;
100
+ } else {
101
+ const response: Doc.ResponseObject = {
102
+ description: 'Successful Response'
103
+ }
104
+ document.responses['200'] = response;
105
+ }
106
+
107
+ if (schemas.body) {
108
+ document.requestBody = {
109
+ required: true,
110
+ content: {
111
+ 'application/json': {
112
+ schema: schemas.body
113
+ }
114
+ }
115
+ };
116
+ }
117
+
118
+ // @ts-ignore
119
+ this.base.paths[pathstr][parsed.method] = document;
120
+ }
121
+ }
122
+ }
package/lib/schema.ts ADDED
@@ -0,0 +1,293 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import morgan from 'morgan';
4
+ import bodyparser from 'body-parser';
5
+ import Err from '@openaddresses/batch-error';
6
+ import { Static, TSchema } from '@sinclair/typebox';
7
+ import { OpenAPIV3 as Doc } from 'openapi-types'
8
+ import { Router, RequestHandler } from 'express'
9
+ import { TypeCompiler } from '@sinclair/typebox/compiler';
10
+ import type { ValueError } from '@sinclair/typebox/errors';
11
+ import { RequestValidation } from './types.js';
12
+
13
+ import SchemaAPI from './api.js';
14
+ import Docs from './openapi.js';
15
+
16
+ export type ErrorListItem = { type: 'Body' | 'Query' | 'Params'; errors: ValueError[] };
17
+
18
+ /**
19
+ * @class
20
+ *
21
+ * @param {Object} router Express Router Object
22
+ * @param {Object} opts Options Object
23
+ * @param {boolean|string} [opts.morgan=true] disable logging with false
24
+ * @param {number} [opts.limit=50] body size limit in mb
25
+ *
26
+ * @param {URL} [opts.apidoc] apidoc file location
27
+ */
28
+ export default class Schemas {
29
+ router: Router;
30
+ docs: Docs;
31
+ schemas: Map<string, RequestValidation<any, any, any, any>>
32
+
33
+ constructor(router: Router, opts: {
34
+ logging?: boolean;
35
+ limit?: number;
36
+ } = {}) {
37
+ if (!router) throw new Error('Router Param Required');
38
+
39
+ this.router = router;
40
+
41
+ if (opts.logging !== false) this.router.use(morgan('combined'));
42
+ this.router.use(bodyparser.urlencoded({ extended: true }));
43
+ this.router.use(bodyparser.json({ limit: `${opts.limit || 50}mb` }));
44
+
45
+ this.docs = new Docs();
46
+ this.schemas = new Map();
47
+ }
48
+
49
+ async api() {
50
+ await SchemaAPI(this);
51
+ }
52
+
53
+ /**
54
+ * Load routes directory. All .js files in the given directory will be loaded (ES Module)
55
+ * and the default method run
56
+ *
57
+ * @param {String} dirpath Directory to load
58
+ * @param {object} config Config Object
59
+ * @param {Object} opts Options Object
60
+ * @param {boolean} opts.silent Squelch StdOut
61
+ */
62
+ async load<T>(dirpath: string | URL, config: T, opts: {
63
+ silent: boolean;
64
+ } = {
65
+ silent: false
66
+ }) {
67
+ if (dirpath instanceof URL) dirpath = dirpath.pathname;
68
+ else dirpath = String(dirpath);
69
+
70
+ const routes: Array<Promise<(schema: Schemas, config: T) => void>> = [];
71
+
72
+ // Load dynamic routes directory
73
+ for (const r of fs.readdirSync(dirpath)) {
74
+ if (!opts.silent) console.log(`ok - loaded routes/${r}`);
75
+ if (!['.ts', '.js'].includes(path.parse(r).ext)) continue;
76
+ routes.push((await import(path.resolve(dirpath, r))).default(this, config));
77
+ }
78
+
79
+ await Promise.all(routes);
80
+
81
+ this.not_found();
82
+ }
83
+
84
+ /**
85
+ * Load a blueprint into the router
86
+ *
87
+ * @param {Object} bp_class Blueprint Class with blueprint() fn
88
+ * @param {object} config Config Object
89
+ * @param {Object} opts Options Object
90
+ * @param {boolean} opts.silent Squelch StdOut
91
+ */
92
+ async blueprint<T>(bp_class, config?: T, opts: {
93
+ silent: boolean;
94
+ } = { silent: false }) {
95
+ if (!opts.silent) console.log(`ok - loaded ${bp_class.name}`);
96
+ await bp_class.blueprint(this, config);
97
+ }
98
+
99
+ async get<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema, TResponse extends TSchema>(
100
+ path: string,
101
+ opts: RequestValidation<TParams, TQuery, TBody, TResponse> = {},
102
+ handler: RequestHandler<Static<TParams>, any, Static<TBody>, Static<TQuery>>
103
+ ) {
104
+ try {
105
+ this.docs.push({ method: Doc.HttpMethods.GET, path: path }, opts);
106
+ this.schemas.set(`GET ${path}`, opts);
107
+
108
+ const paramsValidation = opts.params && TypeCompiler.Compile(opts.params);
109
+ const queryValidation = opts.query && TypeCompiler.Compile(opts.query);
110
+ if (opts.body) throw new Error(`Body not allowed`);
111
+
112
+ const _handler: RequestHandler = (req, res, next) => {
113
+ const errors: Array<ErrorListItem> = [];
114
+ if (paramsValidation && !paramsValidation.Check(req.params)) errors.push({ type: 'Params', errors: Array.from(paramsValidation.Errors(req.params)) });
115
+ if (queryValidation && !queryValidation.Check(req.query)) errors.push({ type: 'Query', errors: Array.from(queryValidation.Errors(req.query)) });
116
+ if (errors.length) return Err.respond(new Err(400, null, 'Validation Error'), res, errors);
117
+
118
+ return handler(req, res, next);
119
+ };
120
+
121
+ this.router.get(path, _handler);
122
+ } catch (err) {
123
+ throw new Error(`Get: ${path}: ` + err)
124
+ }
125
+ }
126
+
127
+ async delete<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema, TResponse extends TSchema>(
128
+ path: string,
129
+ opts: RequestValidation<TParams, TQuery, TBody, TResponse> = {},
130
+ handler: RequestHandler<Static<TParams>, any, Static<TBody>, Static<TQuery>>
131
+ ) {
132
+ try {
133
+ this.docs.push({ method: Doc.HttpMethods.DELETE, path: path }, opts);
134
+ this.schemas.set(`DELETE ${path}`, opts);
135
+
136
+ const paramsValidation = opts.params && TypeCompiler.Compile(opts.params);
137
+ const queryValidation = opts.query && TypeCompiler.Compile(opts.query);
138
+ if (opts.body) throw new Error(`Body not allowed`);
139
+
140
+ const _handler: RequestHandler = (req, res, next) => {
141
+ const errors: Array<ErrorListItem> = [];
142
+ if (paramsValidation && !paramsValidation.Check(req.params)) errors.push({ type: 'Params', errors: Array.from(paramsValidation.Errors(req.params)) });
143
+ if (queryValidation && !queryValidation.Check(req.query)) errors.push({ type: 'Query', errors: Array.from(queryValidation.Errors(req.query)) });
144
+ if (errors.length) return Err.respond(new Err(400, null, 'Validation Error'), res, errors);
145
+
146
+ return handler(req, res, next);
147
+ };
148
+
149
+ this.router.get(path, _handler);
150
+ } catch (err) {
151
+ throw new Error(`Delete: ${path}: ` + String(err))
152
+ }
153
+ }
154
+
155
+ async post<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema, TResponse extends TSchema>(
156
+ path: string,
157
+ opts: RequestValidation<TParams, TQuery, TBody, TResponse> = {},
158
+ handler: RequestHandler<Static<TParams>, any, Static<TBody>, Static<TQuery>>
159
+ ) {
160
+ try {
161
+ this.docs.push({ method: Doc.HttpMethods.POST, path: path }, opts);
162
+ this.schemas.set(`POST ${path}`, opts);
163
+
164
+ const paramsValidation = opts.params && TypeCompiler.Compile(opts.params);
165
+ const queryValidation = opts.query && TypeCompiler.Compile(opts.query);
166
+ const bodyValidation = opts.body && TypeCompiler.Compile(opts.body);
167
+
168
+ const _handler: RequestHandler = (req, res, next) => {
169
+ const errors: Array<ErrorListItem> = [];
170
+ if (paramsValidation && !paramsValidation.Check(req.params)) errors.push({ type: 'Params', errors: Array.from(paramsValidation.Errors(req.params)) });
171
+ if (queryValidation && !queryValidation.Check(req.query)) errors.push({ type: 'Query', errors: Array.from(queryValidation.Errors(req.query)) });
172
+ if (bodyValidation && !bodyValidation.Check(req.body)) errors.push({ type: 'Body', errors: Array.from(bodyValidation.Errors(req.body)) });
173
+ if (errors.length) return Err.respond(new Err(400, null, 'Validation Error'), res, errors);
174
+
175
+ return handler(req, res, next);
176
+ };
177
+
178
+ this.router.get(path, _handler);
179
+ } catch (err) {
180
+ throw new Error(`Post: ${path}: ` + String(err))
181
+ }
182
+ }
183
+
184
+ async patch<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema, TResponse extends TSchema>(
185
+ path: string,
186
+ opts: RequestValidation<TParams, TQuery, TBody, TResponse> = {},
187
+ handler: RequestHandler<Static<TParams>, any, Static<TBody>, Static<TQuery>>
188
+ ) {
189
+ try {
190
+ this.docs.push({ method: Doc.HttpMethods.PATCH, path: path }, opts);
191
+ this.schemas.set(`PATCH ${path}`, opts);
192
+
193
+ const paramsValidation = opts.params && TypeCompiler.Compile(opts.params);
194
+ const queryValidation = opts.query && TypeCompiler.Compile(opts.query);
195
+ const bodyValidation = opts.body && TypeCompiler.Compile(opts.body);
196
+
197
+ const _handler: RequestHandler = (req, res, next) => {
198
+ const errors: Array<ErrorListItem> = [];
199
+ if (paramsValidation && !paramsValidation.Check(req.params)) errors.push({ type: 'Params', errors: Array.from(paramsValidation.Errors(req.params)) });
200
+ if (queryValidation && !queryValidation.Check(req.query)) errors.push({ type: 'Query', errors: Array.from(queryValidation.Errors(req.query)) });
201
+ if (bodyValidation && !bodyValidation.Check(req.body)) errors.push({ type: 'Body', errors: Array.from(bodyValidation.Errors(req.body)) });
202
+ if (errors.length) return Err.respond(new Err(400, null, 'Validation Error'), res, errors);
203
+
204
+ return handler(req, res, next);
205
+ };
206
+
207
+ this.router.get(path, _handler);
208
+ } catch (err) {
209
+ throw new Error(`Patch: ${path}: ` + String(err))
210
+ }
211
+ }
212
+
213
+ async put<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema, TResponse extends TSchema>(
214
+ path: string,
215
+ opts: RequestValidation<TParams, TQuery, TBody, TResponse> = {},
216
+ handler: RequestHandler<Static<TParams>, any, Static<TBody>, Static<TQuery>>
217
+ ) {
218
+ try {
219
+ this.docs.push({ method: Doc.HttpMethods.PUT, path: path }, opts);
220
+ this.schemas.set(`PUT ${path}`, opts);
221
+
222
+ const paramsValidation = opts.params && TypeCompiler.Compile(opts.params);
223
+ const queryValidation = opts.query && TypeCompiler.Compile(opts.query);
224
+ const bodyValidation = opts.body && TypeCompiler.Compile(opts.body);
225
+
226
+ const _handler: RequestHandler = (req, res, next) => {
227
+ const errors: Array<ErrorListItem> = [];
228
+ if (paramsValidation && !paramsValidation.Check(req.params)) errors.push({ type: 'Params', errors: Array.from(paramsValidation.Errors(req.params)) });
229
+ if (queryValidation && !queryValidation.Check(req.query)) errors.push({ type: 'Query', errors: Array.from(queryValidation.Errors(req.query)) });
230
+ if (bodyValidation && !bodyValidation.Check(req.body)) errors.push({ type: 'Body', errors: Array.from(bodyValidation.Errors(req.body)) });
231
+ if (errors.length) return Err.respond(new Err(400, null, 'Validation Error'), res, errors);
232
+
233
+ return handler(req, res, next);
234
+ };
235
+
236
+ this.router.get(path, _handler);
237
+ } catch (err) {
238
+ throw new Error(`Put: ${path}: ` + String(err))
239
+ }
240
+ }
241
+
242
+ not_found() {
243
+ this.router.all('*', (req, res) => {
244
+ return res.status(404).json({
245
+ status: 404,
246
+ message: 'API endpoint does not exist!',
247
+ messages: []
248
+ });
249
+ });
250
+ }
251
+
252
+
253
+ query(method: Doc.HttpMethods, url: string): {
254
+ query?: object;
255
+ body?: object;
256
+ res?: object;
257
+ } {
258
+ if (!this.schemas.has(`${method} ${url}`)) {
259
+ return {};
260
+ }
261
+
262
+ const schema = JSON.parse(JSON.stringify(this.schemas.get(`${method} ${url}`)));
263
+
264
+ return {
265
+ query: schema.query,
266
+ body: schema.body,
267
+ res: schema.res
268
+ };
269
+ }
270
+
271
+ list(): {
272
+ [k: string]: {
273
+ body: boolean;
274
+ query: boolean;
275
+ res: boolean;
276
+ }
277
+ } {
278
+ const lite = {};
279
+
280
+ for (const key of this.schemas.keys()) {
281
+ const schema = this.schemas.get(key);
282
+ if (!schema) continue;
283
+
284
+ lite[key] = {
285
+ body: !!schema.body,
286
+ query: !!schema.query,
287
+ res: !!schema.res
288
+ };
289
+ }
290
+
291
+ return lite;
292
+ }
293
+ }
package/lib/types.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { Static, TSchema } from '@sinclair/typebox';
2
+ import type { Request } from 'express';
3
+ import type { ParamsDictionary } from 'express-serve-static-core';
4
+
5
+ export type TypedRequest<TParams extends TSchema, TQuery extends TSchema, TBody extends TSchema> = Request<
6
+ Static<TParams>,
7
+ any,
8
+ Static<TBody>,
9
+ Static<TQuery>
10
+ >;
11
+
12
+ export type TypedRequestBody<TBody extends TSchema> = Request<ParamsDictionary, any, Static<TBody>, any>;
13
+ export type TypedRequestParams<TParams extends TSchema> = Request<Static<TParams>, any, any, any>;
14
+ export type TypedRequestQuery<TQuery extends TSchema> = Request<ParamsDictionary, any, any, Static<TQuery>>;
15
+
16
+ export type TypedResponseBody<TResponse extends TSchema> = Static<TResponse>;
17
+
18
+ export type RequestValidation<
19
+ TParams extends TSchema,
20
+ TQuery extends TSchema,
21
+ TBody extends TSchema,
22
+ TResponse extends TSchema
23
+ > = {
24
+ private?: boolean;
25
+ name?: string;
26
+ group?: string;
27
+ description?: string;
28
+ params?: TParams;
29
+ query?: TQuery;
30
+ body?: TBody;
31
+ res?: TResponse
32
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "type": "module",
4
4
  "root": true,
5
5
  "main": "index.js",
6
- "version": "9.4.0",
6
+ "version": "10.0.1",
7
7
  "description": "Strongly Validated JSON Schema support for address",
8
8
  "repository": "git@github.com:openaddresses/batch-schema",
9
9
  "author": "ingalls <nick@ingalls.ca>",
@@ -13,28 +13,33 @@
13
13
  "node": ">= 18"
14
14
  },
15
15
  "scripts": {
16
- "test": "tape test/**.test.js",
17
- "lint": "eslint index.js lib/**.js routes/**.js test/**.js",
18
- "doc": "documentation build index.js -f html -o docs && documentation lint index.js"
16
+ "test": "ts-node-test test/**.test.ts",
17
+ "lint": "eslint index.ts lib/**.ts test/**.ts",
18
+ "build": "tsc",
19
+ "doc": "typedoc"
19
20
  },
20
21
  "dependencies": {
21
- "@apidevtools/json-schema-ref-parser": "^10.0.0",
22
22
  "@openaddresses/batch-error": "^2.1.2",
23
- "ajv": "^8.6.3",
24
- "ajv-formats": "^2.1.1",
23
+ "@sinclair/typebox": "^0.32.14",
24
+ "@types/body-parser": "^1.19.5",
25
25
  "body-parser": "^1.20.0",
26
26
  "express": "^4.17.1",
27
- "express-json-validator-middleware": "^3.0.0",
28
27
  "glob": "^10.0.0",
29
- "morgan": "^1.10.0"
28
+ "morgan": "^1.10.0",
29
+ "openapi-types": "^12.1.3"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@openaddresses/batch-generic": "x.x.x"
33
33
  },
34
34
  "devDependencies": {
35
- "documentation": "^14.0.1",
35
+ "@types/express": "^4.17.21",
36
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
37
+ "@typescript-eslint/parser": "^7.0.1",
36
38
  "eslint": "^8.0.1",
37
39
  "eslint-plugin-node": "^11.0.0",
38
- "tape": "^5.3.1"
40
+ "tape": "^5.3.1",
41
+ "ts-node-test": "^0.4.3",
42
+ "typedoc": "^0.25.8",
43
+ "typescript": "^5.3.3"
39
44
  }
40
45
  }
@@ -3,20 +3,15 @@ import express from 'express';
3
3
  import Schema from '../index.js';
4
4
 
5
5
  const app = express();
6
- const schema = new Schema(express.Router(), {
7
- api: true
8
- });
6
+ const schema = new Schema(express.Router());
9
7
 
10
8
  app.use('/api', schema.router);
11
9
 
12
- let server = false;
10
+ let server;
13
11
 
14
12
  test('start', (t) => {
15
- server = app.listen(2000, async (err) => {
13
+ server = app.listen(2000, async () => {
16
14
  await schema.api();
17
- schema.error();
18
-
19
- t.error(err, 'no errors');
20
15
  t.end();
21
16
  });
22
17
  });
@@ -35,7 +30,7 @@ test('GET: api/schema', async (t) => {
35
30
  'GET /openapi': {
36
31
  body: false,
37
32
  query: false,
38
- res: false
33
+ res: true
39
34
  }
40
35
  });
41
36
  } catch (err) {
@@ -53,16 +48,10 @@ test('GET: api/schema?method=FAKE', async (t) => {
53
48
 
54
49
  t.deepEquals(await res.json(), {
55
50
  status: 400,
56
- message: 'validation error',
57
- messages: [{
58
- keyword: 'enum',
59
- instancePath: '/method',
60
- schemaPath: '#/properties/method/enum',
61
- params: {
62
- allowedValues: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']
63
- },
64
- message: 'must be equal to one of the allowed values'
65
- }]
51
+ message: 'Validation Error',
52
+ messages: [
53
+ {"type":"Query","errors":[{"type":62,"schema":{"anyOf":[{"const":"GET","type":"string"},{"const":"PUT","type":"string"},{"const":"POST","type":"string"},{"const":"DELETE","type":"string"},{"const":"OPTIONS","type":"string"},{"const":"HEAD","type":"string"},{"const":"PATCH","type":"string"},{"const":"TRACE","type":"string"}]},"path":"/method","value":"fake","message":"Expected union value"}]}
54
+ ]
66
55
  });
67
56
  } catch (err) {
68
57
  t.error(err);
@@ -107,7 +96,7 @@ test('GET: api/schema?method=GET&url=/schema', async (t) => {
107
96
  const res = await fetch('http://localhost:2000/api/schema?method=GET&url=/schema');
108
97
 
109
98
  t.equals(res.status, 200, 'http: 200');
110
- t.deepEquals(Object.keys(await res.json()).sort(), ['query', 'body', 'res'].sort());
99
+ t.deepEquals(Object.keys(await res.json()).sort(), ['query', 'res'].sort());
111
100
 
112
101
  } catch (err) {
113
102
  t.error(err, 'no error');
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "ts-node": {
3
+ "esm": true
4
+ },
5
+ "compilerOptions": {
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "module": "es2022",
9
+ "target": "es2022",
10
+ "esModuleInterop": true,
11
+ "noImplicitAny": false,
12
+ "moduleResolution": "node",
13
+ "sourceMap": true,
14
+ "outDir": "dist",
15
+ "baseUrl": ".",
16
+ "paths": {
17
+ "*": [
18
+ "node_modules/*"
19
+ ]
20
+ }
21
+ },
22
+ "include": [
23
+ "index.ts",
24
+ "lib/**.ts",
25
+ "test/**.ts"
26
+ ]
27
+ }