Skip to content

Commit c57a5cd

Browse files
authored
feat(schema): Initial version of schema definitions and resolvers (#2441)
1 parent 4323f98 commit c57a5cd

File tree

18 files changed

+1680
-365
lines changed

18 files changed

+1680
-365
lines changed

_templates/package/new/README.md.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ to: packages/<%= name %>/README.md
55
# @feathersjs/<%= name %>
66

77
[![CI](https://github.com/feathersjs/feathers/workflows/CI/badge.svg)](https://github.com/feathersjs/feathers/actions?query=workflow%3ACI)
8-
[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/socketio)](https://david-dm.org/feathersjs/feathers?path=packages/<%= name %>)
8+
[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/<%= name %>)](https://david-dm.org/feathersjs/feathers?path=packages/<%= name %>)
99
[![Download Status](https://img.shields.io/npm/dm/@feathersjs/<%= name %>.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/<%= name %>)
1010

1111
> <%= description %>

package-lock.json

Lines changed: 749 additions & 354 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/memory/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const _select = (data: any, params: any, ...args: any[]) => {
2121
return base(JSON.parse(JSON.stringify(data)));
2222
};
2323

24-
export class Service<T = any, D = Partial<any>> extends AdapterService<T, D> implements InternalServiceMethods<T> {
24+
export class Service<T = any, D = Partial<T>> extends AdapterService<T, D> implements InternalServiceMethods<T> {
2525
options: MemoryServiceOptions;
2626
store: MemoryServiceStore<T>;
2727
_uId: number;

packages/schema/LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Feathers
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

packages/schema/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# @feathersjs/schema
2+
3+
[![CI](https://github.com/feathersjs/feathers/workflows/CI/badge.svg)](https://github.com/feathersjs/feathers/actions?query=workflow%3ACI)
4+
[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/socketio)](https://david-dm.org/feathersjs/feathers?path=packages/schema)
5+
[![Download Status](https://img.shields.io/npm/dm/@feathersjs/schema.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/schema)
6+
7+
> A common data schema definition format
8+
9+
## Installation
10+
11+
```
12+
npm install @feathersjs/schema --save
13+
```
14+
15+
## Documentation
16+
17+
Refer to the [Feathers documentation](https://docs.feathersjs.com) for more details.
18+
19+
## License
20+
21+
Copyright (c) 2021 [Feathers contributors](https://github.com/feathersjs/feathers/graphs/contributors)
22+
23+
Licensed under the [MIT license](LICENSE).

packages/schema/package.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"name": "@feathersjs/schema",
3+
"description": "A common data schema definition format",
4+
"version": "0.0.0",
5+
"homepage": "https://feathersjs.com",
6+
"main": "lib/",
7+
"keywords": [
8+
"feathers",
9+
"feathers-plugin"
10+
],
11+
"license": "MIT",
12+
"funding": {
13+
"type": "github",
14+
"url": "https://github.com/sponsors/daffl"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git://github.com/feathersjs/feathers.git"
19+
},
20+
"author": {
21+
"name": "Feathers contributors",
22+
"email": "hello@feathersjs.com",
23+
"url": "https://feathersjs.com"
24+
},
25+
"contributors": [],
26+
"bugs": {
27+
"url": "https://github.com/feathersjs/feathers/issues"
28+
},
29+
"engines": {
30+
"node": ">= 12"
31+
},
32+
"files": [
33+
"CHANGELOG.md",
34+
"LICENSE",
35+
"README.md",
36+
"src/**",
37+
"lib/**",
38+
"*.d.ts",
39+
"*.js"
40+
],
41+
"scripts": {
42+
"prepublish": "npm run compile",
43+
"compile": "shx rm -rf lib/ && tsc",
44+
"test": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts"
45+
},
46+
"directories": {
47+
"lib": "lib"
48+
},
49+
"publishConfig": {
50+
"access": "public"
51+
},
52+
"dependencies": {
53+
"@feathersjs/errors": "^5.0.0-pre.9",
54+
"@feathersjs/feathers": "^5.0.0-pre.9",
55+
"ajv": "^8.6.2",
56+
"json-schema": "^0.3.0",
57+
"json-schema-to-ts": "^1.6.4"
58+
},
59+
"devDependencies": {
60+
"@feathersjs/memory": "^5.0.0-pre.9",
61+
"@types/mocha": "^9.0.0",
62+
"@types/node": "^16.6.1",
63+
"mocha": "^9.0.3",
64+
"shx": "^0.3.3",
65+
"typescript": "^4.3.5"
66+
}
67+
}

packages/schema/src/hooks.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { HookContext, NextFunction } from '@feathersjs/feathers';
2+
import { BadRequest } from '../../errors/lib';
3+
import { Resolver } from './resolver';
4+
import { Schema } from './schema';
5+
6+
const getContext = (context: HookContext) => {
7+
return {
8+
...context,
9+
params: {
10+
...context.params,
11+
query: {}
12+
}
13+
}
14+
}
15+
16+
export const resolveQuery = <T> (resolver: Resolver<T, HookContext>) =>
17+
async (context: HookContext, next: NextFunction) => {
18+
const ctx = getContext(context);
19+
const data = context?.params?.query || {};
20+
const query = await resolver.resolve(data, ctx, {
21+
originalContext: context
22+
});
23+
24+
context.params = {
25+
...context.params,
26+
query
27+
}
28+
29+
return next();
30+
};
31+
32+
export const resolveData = <T> (resolver: Resolver<T, HookContext>) =>
33+
async (context: HookContext, next: NextFunction) => {
34+
const ctx = getContext(context);
35+
const data = context.data;
36+
const status = {
37+
originalContext: context
38+
};
39+
40+
if (Array.isArray(data)) {
41+
context.data = await Promise.all(data.map(current =>
42+
resolver.resolve(current, ctx, status)
43+
));
44+
} else {
45+
context.data = await resolver.resolve(data, ctx, status);
46+
}
47+
48+
return next();
49+
};
50+
51+
export const resolveResult = <T> (resolver: Resolver<T, HookContext>) =>
52+
async (context: HookContext, next: NextFunction) => {
53+
const { $resolve: properties, ...query } = context.params?.query || {};
54+
const { resolve } = context.params;
55+
const status = {
56+
originalContext: context,
57+
...resolve,
58+
properties
59+
};
60+
61+
context.params = {
62+
...context.params,
63+
query
64+
}
65+
66+
await next();
67+
68+
const ctx = getContext(context);
69+
const data = context.method === 'find' && context.result.data
70+
? context.result.data
71+
: context.result;
72+
73+
if (Array.isArray(data)) {
74+
context.result = await Promise.all(data.map(current =>
75+
resolver.resolve(current, ctx, status)
76+
));
77+
} else {
78+
context.result = await resolver.resolve(data, ctx, status);
79+
}
80+
};
81+
82+
export const validateQuery = (schema: Schema<any>) =>
83+
async (context: HookContext, next: NextFunction) => {
84+
const data = context?.params?.query || {};
85+
86+
try {
87+
const query = await schema.validate(data);
88+
89+
context.params = {
90+
...context.params,
91+
query
92+
}
93+
94+
return next();
95+
} catch (error: any) {
96+
throw (error.ajv ? new BadRequest(error.message, error.errors) : error);
97+
}
98+
};
99+
100+
export const validateData = (schema: Schema<any>) =>
101+
async (context: HookContext, next: NextFunction) => {
102+
const data = context.data;
103+
104+
try {
105+
if (Array.isArray(data)) {
106+
context.data = await Promise.all(data.map(current =>
107+
schema.validate(current)
108+
));
109+
} else {
110+
context.data = await schema.validate(data);
111+
}
112+
} catch (error: any) {
113+
throw (error.ajv ? new BadRequest(error.message, error.errors) : error);
114+
}
115+
116+
return next();
117+
};

packages/schema/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ResolverStatus } from './resolver';
2+
3+
export * from './schema';
4+
export * from './resolver';
5+
export * from './hooks';
6+
export * from './query';
7+
8+
export type Infer<S extends { _type: any }> = S['_type'];
9+
10+
declare module '@feathersjs/feathers/lib/declarations' {
11+
interface Params {
12+
resolve?: ResolverStatus<any, HookContext>;
13+
}
14+
}

packages/schema/src/query.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { JSONSchema } from 'json-schema-to-ts';
2+
3+
export const queryProperty = <T extends JSONSchema> (definition: T) => ({
4+
oneOf: [
5+
definition,
6+
{
7+
type: 'object',
8+
additionalProperties: false,
9+
properties: {
10+
$gt: definition,
11+
$gte: definition,
12+
$lt: definition,
13+
$lte: definition,
14+
$ne: definition,
15+
$in: {
16+
type: 'array',
17+
items: definition
18+
},
19+
$nin: {
20+
type: 'array',
21+
items: definition
22+
}
23+
}
24+
}
25+
]
26+
} as const);
27+
28+
export const queryArray = <T extends readonly string[]> (fields: T) => ({
29+
type: 'array',
30+
items: {
31+
type: 'string',
32+
enum: fields
33+
}
34+
} as const);

0 commit comments

Comments
 (0)