-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
267 lines (233 loc) · 7.59 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// @flow
export type MapOf<A, B> = { [key: A]: B };
// User-provided schema
export type Schema = {
models: MapOf<ModelName, Model>,
authRules?: Array<AuthRule>,
// can have additional specs
};
// Preprocessed schema (required by some plugins)
export type ProcessedSchema = {
models: MapOf<ModelName, ProcessedModel>,
authRules: Array<AuthRule>,
};
// ====================================
// Model
// ====================================
export type ModelName = string;
export type Model = {
description?: Description,
fields?: MapOf<FieldName, Field>,
relations?: MapOf<FieldName, Relation>,
existsInServer?: boolean, // default: true
existsInClient?: boolean, // default: true
// Include other models in this one
includes?: MapOf<ModelName, true>,
// This model is only for inclusion in other models
isIncludeOnly?: boolean,
};
export type ProcessedModel = {
description?: Description,
fields: MapOf<FieldName, ProcessedField>,
relations: MapOf<FieldName, ProcessedRelation>,
existsInServer: boolean,
existsInClient: boolean,
singular: string,
plural: string,
};
// ====================================
// Field
// ====================================
export type FieldName = string;
export type FieldType =
| 'string'
| 'boolean'
| 'uuid'
| 'json'
| 'number'
| 'date';
export type Field =
| { ...FieldBase, type: 'string', isLong?: boolean, defaultValue?: string }
| { ...FieldBase, type: 'boolean', defaultValue?: boolean }
| { ...FieldBase, type: 'uuid', defaultValue?: string }
| { ...FieldBase, type: 'json', defaultValue?: any }
| { ...FieldBase, type: 'number', isFloat?: boolean, defaultValue?: number }
| {
...FieldBase,
type: 'date',
noDate?: boolean,
noTime?: boolean,
defaultValue?: Date,
};
export type FieldBase = {
...Validations,
description?: Description,
existsInServer?: boolean, // default: true
existsInClient?: boolean, // default: true
isMassAssignable?: boolean, // default: true
isPrimaryKey?: boolean,
isAutoIncremented?: boolean,
};
export type ProcessedField = Field;
// ====================================
// Relation
// ====================================
// Relations are defined whenever a FK should appear. The FK field will have the name
// of the relation + `Id`, and the type of the referenced PK (hence, no `type` should
// be specified).
// Inverse relations (useful in many cases to define the way to traverse a 1 -> N relationship)
// are created by default. Specify `inverse` if you want to disable the inverse
// relation (`inverse: null`) or you want to configure some of its parameters
export type Relation =
| true
| {
...Validations,
description?: Description,
model?: ModelName, // default: inferred from the relation name + isPlural field
isPlural?: boolean, // default: false
fkName?: string,
inverse?: // default: true
| boolean
| {
description?: Description,
isPlural?: boolean, // default: true
name?: FieldName, // default: inferred from model name + isPlural field
},
// Disable referential integrity in Sequelize?
// Needed due to Sequelize limitation, which cannot handle two tables
// with FKs to one other
// See http://docs.sequelizejs.com/en/latest/docs/associations/#foreign-keys_1
sequelizeSkipReferentialIntegrity?: boolean,
};
export type ProcessedRelation = {
...Validations,
description?: Description,
type: FieldType,
model: ModelName,
isPlural: boolean,
fkName: string,
isInverse: boolean,
inverseName: ?FieldName,
// See above
sequelizeSkipReferentialIntegrity?: boolean,
};
// ====================================
// Validations
// ====================================
export type Validations = {
// Generic
isRequired?: boolean,
isUnique?: boolean,
isOneOf?: Array<any>,
// Strings
hasAtLeastChars?: number,
hasAtMostChars?: number,
hasLengthWithinRange?: [number, number], // min, max
isEmail?: boolean,
isUrl?: boolean,
isIp?: boolean, // v4/v6
isCreditCard?: boolean,
matchesPattern?: [string, string], // pattern, modifiers
// Numbers
isGte?: number,
isLte?: number,
isWithinRange?: [number, number], // min, max
// Custom, e.g. `val => { if (val === 3) throw new Error('Val must be 3!'); }`
satisfies?: string, // SECURITY NOTICE: HANDLE CAREFULLY
};
// ====================================
// AuthRule
// ====================================
export type AuthRule = {
// who?
viewerId?: AuthRuleSingularValue<any>,
roleNames?: AuthRulePluralValue<string>,
// what?
operation?: AuthOperation,
baseId?: ?AuthRuleSingularValue<any>, // `null` for "null -> node"
baseType?: ?AuthRuleSingularValue<string>, // `null` for "null -> node"
targetName?: AuthRuleSingularValue<string>, // a model or a field/relation (think graph-wise)
targetId?: AuthRuleSingularValue<any>,
targetType?: AuthRuleSingularValue<string>,
targetBefore?: AuthRuleSingularValue<any>,
targetAfter?: AuthRuleSingularValue<any>, // [only for `write`s]
// where?
isClientSide?: AuthRuleSingularValue<boolean>,
// can? (if multiple, all checks must pass)
can: boolean | AuthCheck | Array<AuthCheck>,
};
export type AuthRuleSingularValue<T> =
| T
| { $is: T }
| { $isnt: T }
| { $in: Array<T> }
| { $notIn: Array<T> };
export type AuthRulePluralValue<T> =
| { $include: T }
| { $dontInclude: T }
| { $includeAny: Array<T> }
| { $dontIncludeAny: Array<T> };
export type AuthCheck =
// Check that we reach the viewer ID following a certain route from the target
// (e.g. from a Company instance, route `project|users|edges|node|id`)
| { type: 'targetBefore->viewerId', route: AuthRoute }
| { type: 'targetAfter->viewerId', route: AuthRoute }
// Check that we reach the viewer ID following a certain route from the root
// (e.g. from the root, route `adminIds`, think Firebase)
| { type: 'root->viewerId', route: AuthRoute }
// Check that we reach the target ID following a certain route from the viewer
// (e.g. from the viewer, route `_id`, when the user wants to edit his own account)
| { type: 'viewer->targetId', route: AuthRoute }
// A function, or a stringified version of it (will be `eval`'ed')
// e.g. `''req => req.operation === 'READ'`
| { type: 'satisfies', fn: AuthFunction | string };
export type AuthRequest = {
// who?
viewerId: any,
viewer: any,
roleNames: Array<string>,
// what?
operation: AuthOperation,
baseId: ?any,
baseType: ?string,
base: ?any,
targetName: string,
targetArgs: any,
targetId: any,
targetType: string,
targetBefore?: any,
targetAfter?: any,
// where?
isClientSide: boolean,
// checkRoute
checkRoute: (
fromNode: any,
route: AuthRoute,
checkValue: any
) => Promise<boolean>,
// ...may have additional helpers, depending on the plugin
};
export type AuthRoute = Array<string>;
export type AuthOperation = 'read' | 'write';
export type AuthFunction = (
req: AuthRequest
) => AuthResponse | Promise<AuthResponse>;
export type AuthResponse = boolean | null;
// (`null` meaning undecided yet, e.g. ask me again later with the `fieldValue`)
// ====================================
// Helper types
// ====================================
type Description = string;
// ====================================
// Processors
// ====================================
export type InputProcessor = (options: Object) => Promise<Schema>;
export type OutputProcessor = (
schema: Schema,
options: Object,
utils: SchemaUtils
) => Promise<any>;
export type SchemaUtils = {
preprocessedSchema: ProcessedSchema,
authorizer: any,
};