functional-models 2.1.13 → 3.0.0
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 +484 -301
- package/errors.d.ts +2 -5
- package/errors.js.map +1 -1
- package/index.d.ts +6 -7
- package/index.js +24 -20
- package/index.js.map +1 -1
- package/lib.d.ts +15 -4051
- package/lib.js +120 -8
- package/lib.js.map +1 -1
- package/models.d.ts +13 -3
- package/models.js +73 -38
- package/models.js.map +1 -1
- package/package.json +34 -21
- package/properties.d.ts +439 -23639
- package/properties.js +237 -103
- package/properties.js.map +1 -1
- package/serialization.d.ts +2 -2
- package/serialization.js +1 -1
- package/serialization.js.map +1 -1
- package/types.d.ts +692 -0
- package/types.js +45 -0
- package/types.js.map +1 -0
- package/utils.d.ts +4 -2
- package/utils.js +43 -2
- package/utils.js.map +1 -1
- package/validation.d.ts +18 -12164
- package/validation.js +30 -28
- package/validation.js.map +1 -1
- package/constants.d.ts +0 -14
- package/constants.js +0 -19
- package/constants.js.map +0 -1
- package/interfaces.d.ts +0 -147
- package/interfaces.js +0 -4
- package/interfaces.js.map +0 -1
- package/lazy.d.ts +0 -3
- package/lazy.js +0 -51
- package/lazy.js.map +0 -1
- package/methods.d.ts +0 -6
- package/methods.js +0 -18
- package/methods.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,392 +4,575 @@
|
|
|
4
4
|

|
|
5
5
|
[](https://coveralls.io/github/monolithst/functional-models?branch=master)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<br>
|
|
8
|
+
<img src="./docs/images/chocolate.png" alt="drawing" width="200"/>
|
|
9
|
+
</br>
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Making System Building Fun
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Does this sound like you?
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
> I want to code once, use it everywhere, and auto-generate my entire system
|
|
16
|
+
|
|
17
|
+
If so this is the framework for you.
|
|
18
|
+
|
|
19
|
+
Functional Models empowers the creation of pure TypeScript/JavaScript function based models that can be used on a client, a web frontend, and/or a backend all the same time. Use this library to create models that can be reused <b>EVERYWHERE</b>.
|
|
20
|
+
|
|
21
|
+
Write validation code, metadata, property descriptions, and more! Functional Models is fully supportive of both TypeScript and JavaScript. In fact, the typescript empowers some really sweet dynamic type checking, and autocomplete!
|
|
22
|
+
|
|
23
|
+
This framework was born out of the enjoyment and power of working with Django models, but, restricting their "god-like abilities" which can cause developers to make a spaghetti system that is nearly impossible to optimize or improve without starting from scratch.
|
|
24
|
+
|
|
25
|
+
<b>Functional models is ooey gooey framework for building and using awesome models EVERYWHERE.</b>
|
|
14
26
|
|
|
15
27
|
# Primary Features
|
|
16
28
|
|
|
17
|
-
- Define models that have properties
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- Supports
|
|
29
|
+
- Define models that have robust properties and are scoped to a namespace or app
|
|
30
|
+
- Robust typing system for TypeScript goodness.
|
|
31
|
+
- Same modeling code can be used on front end and backends.
|
|
32
|
+
- Validate model data
|
|
33
|
+
- ORM ready via the [functional-models-orm](https://github.com/monolithst/functional-models-orm) package. Available Datastores: DynamoDb, Mongo, in-memory, elastic/opensearch, Sqlite, Postgres, Mysql
|
|
34
|
+
- Most common properties out of the box.
|
|
35
|
+
- Supports "foreign keys", 1 to 1 as well as 1 to many (via an Array).
|
|
36
|
+
- Models support custom primary key name. (id is used by default)
|
|
24
37
|
- Supports different model namings, (plural, singular, display), and the ability to customize them.
|
|
38
|
+
- Add Api Information that can be used for auto-generating frontend and backend code as well as documentation.
|
|
39
|
+
|
|
40
|
+
# Table Of Contents
|
|
41
|
+
|
|
42
|
+
- [Version 3.0 Updates](#the-big-30-updates)
|
|
43
|
+
- [Simple JavasScript Example](#simple-javascript-example-usage)
|
|
44
|
+
- [Simple TypeScript Example](#simple-typescript-example-usage)
|
|
45
|
+
- [Validation](#validation)
|
|
46
|
+
- [Properties](#properties)
|
|
47
|
+
- [List of Properties](#list-of-properties-out-of-the-box)
|
|
48
|
+
|
|
49
|
+
## The Big 3.0 Updates
|
|
50
|
+
|
|
51
|
+
Version 3 is a major update that changes most of the primary interfaces from Version 2. This version should be simpler to extend (see the companion library [Functional Models Orm](https://github.com/monolithst/functional-models-orm)) making models much easier to reuse across front and back ends. Here is a non-exhaustive list.
|
|
52
|
+
|
|
53
|
+
- Model/ModelInstance/ModelFactory types have been reworked, so that they are simpler and much easier to extend
|
|
54
|
+
- Some "automagical" stuff has been removed, because experience has shown them to be more of a hassle than they were worth.
|
|
55
|
+
- Interfaces for ModelType/ModelInstance/ModelDefinitions have been reworked
|
|
56
|
+
- API Endpoint information can be added to a ModelDefinition
|
|
57
|
+
- Additional Value Types added for better differentiation downstream. (Date/Datetime, Text/BigText, etc)
|
|
58
|
+
- Removes dependency on date-fns to lighten the install, and prevent duplicate dependencies.
|
|
59
|
+
- Memoized computations to reduce expensive recalculations.
|
|
60
|
+
- Promise types allowed in model type. (Perfect for asynchronous loaded properties)
|
|
25
61
|
|
|
26
62
|
## Simple JavaScript Example Usage
|
|
27
63
|
|
|
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
|
-
|
|
64
|
+
```javascript
|
|
65
|
+
const {
|
|
66
|
+
Model,
|
|
67
|
+
DatetimeProperty,
|
|
68
|
+
NumberProperty,
|
|
69
|
+
TextProperty,
|
|
70
|
+
PrimaryKeyUuidProperty,
|
|
71
|
+
} = require('functional-models')
|
|
72
|
+
|
|
73
|
+
// Create your model. Our recommended standard is to use a plural uppercase name for your variable. (You are creating a Model factory)
|
|
74
|
+
const Trucks = Model({
|
|
75
|
+
pluralName: 'Trucks',
|
|
76
|
+
namespace: '@my-package/cars',
|
|
77
|
+
properties: {
|
|
78
|
+
id: PrimaryKeyUuidProperty(),
|
|
79
|
+
make: TextProperty({ maxLength: 20, minLength: 3, required: true }),
|
|
80
|
+
model: TextProperty({ maxLength: 20, minLength: 3, required: true }),
|
|
81
|
+
color: TextProperty({
|
|
82
|
+
maxLength: 10,
|
|
83
|
+
minLength: 3,
|
|
84
|
+
choices: ['red', 'green', 'blue', 'black', 'white'],
|
|
85
|
+
}),
|
|
86
|
+
year: NumberProperty({ maxValue: 2500, minValue: 1900 }),
|
|
87
|
+
lastModified: DatetimeProperty({ autoNow: true }),
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Create an instance of the model. In this case, you don't need 'id', because it gets created automatically with UniquePropertyId()
|
|
92
|
+
const myTruck = Trucks.create({
|
|
93
|
+
make: 'Ford',
|
|
94
|
+
model: 'F-150',
|
|
95
|
+
color: 'white',
|
|
96
|
+
year: 2013,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Get the properties of the model instance.
|
|
100
|
+
console.log(myTruck.get.id()) // a auto generated uuid
|
|
101
|
+
console.log(myTruck.get.make()) // 'Ford'
|
|
102
|
+
console.log(myTruck.get.model()) // 'F-150'
|
|
103
|
+
console.log(myTruck.get.color()) // 'white'
|
|
104
|
+
console.log(myTruck.get.year()) // 2013
|
|
105
|
+
|
|
106
|
+
// Get a raw javascript object representation of the model.
|
|
107
|
+
const obj = await myTruck.toObj()
|
|
108
|
+
console.log(obj)
|
|
109
|
+
/*
|
|
110
|
+
{
|
|
111
|
+
"id": "3561e6c5-422d-46c7-954f-f7261b11d3d4",
|
|
112
|
+
"make": "Ford",
|
|
113
|
+
"model": "F-150",
|
|
114
|
+
"color": "white",
|
|
115
|
+
"year": 2013
|
|
116
|
+
}
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
// Create a copy of the model from the raw javascript object.
|
|
120
|
+
const sameTruck = Truck.create(obj)
|
|
121
|
+
console.log(myTruck.get.id()) // same as above.
|
|
122
|
+
console.log(myTruck.get.make()) // 'Ford'
|
|
123
|
+
console.log(myTruck.get.model()) // 'F-150'
|
|
124
|
+
console.log(myTruck.get.color()) // 'white'
|
|
125
|
+
console.log(myTruck.get.year()) // 2013
|
|
126
|
+
|
|
127
|
+
// Validate the model. Undefined, means no errors.
|
|
128
|
+
const errors = await sameTruck.validate()
|
|
129
|
+
console.log(errors) // undefined
|
|
130
|
+
|
|
131
|
+
const newTruck = Truck({
|
|
132
|
+
make: 'Ford',
|
|
133
|
+
model: 'F-150',
|
|
134
|
+
color: 'white',
|
|
135
|
+
year: 20130,
|
|
136
|
+
})
|
|
137
|
+
const errors2 = await newTruck.validate()
|
|
138
|
+
console.log(errors2)
|
|
139
|
+
|
|
140
|
+
// Key is the property's name, and an array of validation errors for that property.
|
|
141
|
+
// {"year": ['Value is too long']}
|
|
142
|
+
```
|
|
93
143
|
|
|
94
144
|
## Simple TypeScript Example Usage
|
|
95
145
|
|
|
96
|
-
While functional-
|
|
146
|
+
While functional-models works very well and easy without TypeScript, using typescript empowers
|
|
97
147
|
modern code completion engines to show the properties/methods on models and model instances.
|
|
98
|
-
Libraries built
|
|
148
|
+
Libraries built on top of functional-models is encouraged to use TypeScript, while applications,
|
|
99
149
|
may or may not be as useful, given the overhead of typing. NOTE: Behind the covers functional-models
|
|
100
150
|
typing, is extremely strict, and verbose, which can make it somewhat difficult to work with, but
|
|
101
|
-
it provides the backbone of expressive and clear typing.
|
|
102
|
-
|
|
103
|
-
import {
|
|
104
|
-
BaseModel as Model,
|
|
105
|
-
DateProperty,
|
|
106
|
-
NumberProperty,
|
|
107
|
-
TextProperty,
|
|
108
|
-
} from 'functional-models'
|
|
109
|
-
|
|
110
|
-
// Create your model's type
|
|
111
|
-
type TruckType = {
|
|
112
|
-
/*
|
|
113
|
-
* id: string # this property is provided for free. No need to put. If using a custom
|
|
114
|
-
* primary key, the property needs to be explicit.
|
|
115
|
-
*/
|
|
116
|
-
make: string,
|
|
117
|
-
model: string,
|
|
118
|
-
color: string,
|
|
119
|
-
year?: number, // NOTE: Clearly indicates a property is optional.
|
|
120
|
-
lastModified: Date,
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Create your model. Standard is to use a plural name.
|
|
124
|
-
const Trucks = Model<TruckType>('Trucks', {
|
|
125
|
-
properties: {
|
|
126
|
-
// id: UniqueId(), # this property is provided for free!
|
|
127
|
-
make: TextProperty({ maxLength: 20, minLength: 3, required: true}),
|
|
128
|
-
model: TextProperty({ maxLength: 20, minLength: 3, required: true}),
|
|
129
|
-
color: TextProperty({
|
|
130
|
-
maxLength: 10,
|
|
131
|
-
minLength: 3,
|
|
132
|
-
choices: ['red', 'green', 'blue', 'black', 'white'],
|
|
133
|
-
}),
|
|
134
|
-
year: NumberProperty({ maxValue: 2500, minValue: 1900}),
|
|
135
|
-
lastModified: DateProperty({ autoNow: true}),
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
// Create an instance of the model.
|
|
140
|
-
const myTruck = Trucks.create({ make: 'Ford', model: 'F-150', color: 'white', year: 2013})
|
|
141
|
-
|
|
142
|
-
// Will cause a typescript error because "make" is a string, not a number.
|
|
143
|
-
const myTruck2 = Trucks.create({ make: 5, model: 'F-150', color: 'white', year: 2013})
|
|
144
|
-
|
|
145
|
-
// Will NOT cause a typescript error because year is optional.
|
|
146
|
-
const myTruck = Trucks.create({ make: 'Ford', model: 'F-150', color: 'white' })
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Will cause a typescript error because model is not implemented, even though its required.
|
|
150
|
-
const Trucks2 = Model<TruckType>('Trucks2', {
|
|
151
|
-
properties: {
|
|
152
|
-
make: TextProperty({ maxLength: 20, minLength: 3, required: true}),
|
|
153
|
-
color: TextProperty({
|
|
154
|
-
maxLength: 10,
|
|
155
|
-
minLength: 3,
|
|
156
|
-
choices: ['red', 'green', 'blue', 'black', 'white'],
|
|
157
|
-
}),
|
|
158
|
-
year: NumberProperty({ maxValue: 2500, minValue: 1900}),
|
|
159
|
-
lastModified: DateProperty({ autoNow: true}),
|
|
160
|
-
}
|
|
161
|
-
})
|
|
151
|
+
it provides the backbone of expressive and clear typing that "just works" for nearly all situations.
|
|
162
152
|
|
|
163
|
-
|
|
153
|
+
```typescript
|
|
154
|
+
import {
|
|
155
|
+
Model,
|
|
156
|
+
DatetimeProperty,
|
|
157
|
+
NumberProperty,
|
|
158
|
+
TextProperty,
|
|
159
|
+
PrimaryKeyUuidProperty,
|
|
160
|
+
} from 'functional-models'
|
|
161
|
+
|
|
162
|
+
// Create an object type. NOTE: Singular Uppercase
|
|
163
|
+
type VehicleMake = {
|
|
164
|
+
id: string // Our primary key
|
|
165
|
+
name: string // A simple text name of the maker
|
|
166
|
+
}
|
|
164
167
|
|
|
165
|
-
|
|
168
|
+
// Create your main type that has reference to another type.
|
|
169
|
+
type Vehicle = {
|
|
170
|
+
id: string // Our primary key
|
|
171
|
+
make: ModelReference<VehicleMake> // A reference to another model.
|
|
172
|
+
model: string // A simple text field
|
|
173
|
+
color: Promise<string> // A property that requires asynchronous to create.
|
|
174
|
+
year?: number // An optional number property. We enforce this is an integer in the Model Definition
|
|
175
|
+
lastModified?: DateValueType // A Date|string
|
|
176
|
+
history?: string // A complex text data type.
|
|
177
|
+
}
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
// Create a model for the VehicleMake type. NOTE: Plural and Uppercase.
|
|
180
|
+
const VehicleMakes = Model<VehicleMake>({
|
|
181
|
+
pluralName: 'VehicleMakes',
|
|
182
|
+
namespace: '@my-package/cars',
|
|
183
|
+
properties: {
|
|
184
|
+
id: PrimaryKeyUuidProperty(),
|
|
185
|
+
name: TextProperty({ required: true }),
|
|
186
|
+
},
|
|
187
|
+
})
|
|
168
188
|
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
// Create a model for the Vehicle type
|
|
190
|
+
const Vehicles = Model<Vehicle>({
|
|
191
|
+
pluralName: 'Vehicles',
|
|
192
|
+
namespace: '@my-package/cars',
|
|
193
|
+
properties: {
|
|
194
|
+
id: PrimaryKeyUuidProperty(),
|
|
195
|
+
model: TextProperty({
|
|
196
|
+
maxLength: 20,
|
|
197
|
+
minLength: 3,
|
|
198
|
+
required: true,
|
|
199
|
+
}),
|
|
200
|
+
color: TextProperty({
|
|
201
|
+
maxLength: 10,
|
|
202
|
+
minLength: 3,
|
|
203
|
+
choices: ['red', 'green', 'blue', 'black', 'white'],
|
|
204
|
+
}),
|
|
205
|
+
year: IntegerProperty({
|
|
206
|
+
maxValue: 2500,
|
|
207
|
+
minValue: 1900,
|
|
208
|
+
}),
|
|
209
|
+
make: ModelReferenceProperty<VehicleMake>(VehicleMakes, { required: true }),
|
|
210
|
+
history: BigTextProperty({ required: false }),
|
|
211
|
+
lastModified: DatetimeProperty({ autoNow: true }),
|
|
212
|
+
},
|
|
213
|
+
})
|
|
171
214
|
|
|
172
|
-
|
|
215
|
+
// Create an instance of our Make. NOTE: The 'id' is to tell the create factory to ignore the id field as it is auto-generated.
|
|
216
|
+
const ford = VehicleMakes.create<'id'>({
|
|
217
|
+
name: 'Ford',
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// GOOD: Create an instance of the model.
|
|
221
|
+
const myTruck = Vehicles.create<'id'>({
|
|
222
|
+
make: ford,
|
|
223
|
+
model: 'F-150',
|
|
224
|
+
color: 'white',
|
|
225
|
+
year: 2013,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// GOOD: You can also use the id to set the make
|
|
229
|
+
const myTruck2 = Vehicles.create<'id'>({
|
|
230
|
+
make: ford.get.id(),
|
|
231
|
+
model: 'F-150',
|
|
232
|
+
color: 'white',
|
|
233
|
+
year: 2013,
|
|
234
|
+
})
|
|
173
235
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
236
|
+
// GOOD: Will NOT cause a typescript error because year is optional.
|
|
237
|
+
const myTruck4 = Vehicles.create<'id'>({
|
|
238
|
+
make: ford,
|
|
239
|
+
model: 'F-150',
|
|
240
|
+
color: 'white',
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// ERROR: Will cause a typescript error because year must be a string.
|
|
244
|
+
const myTruck3 = Vehicles.create<'id'>({
|
|
245
|
+
make: ford,
|
|
246
|
+
model: 'F-150',
|
|
247
|
+
color: 'white',
|
|
248
|
+
year: '2013', // must be a string
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Validation
|
|
253
|
+
|
|
254
|
+
Validation is baked into the functional-models framework. Both individual properties and an entire model instance can be covered by validators. The following are the interfaces for a validator. Validation overall is a combination of property validator components as well as model validator components. These components combine to create a complete validation picture of a model.
|
|
255
|
+
|
|
256
|
+
When calling validate, you either get undefined (for passing), or you get an object that shows you the errors at both the model and the individual property level.
|
|
257
|
+
|
|
258
|
+
Here is an example of validating different model instances:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// Call .validate() on the instance, and await its result.
|
|
262
|
+
const errors = await myModelInstance.validate()
|
|
263
|
+
|
|
264
|
+
console.log(errors)
|
|
265
|
+
|
|
266
|
+
/*
|
|
267
|
+
* {
|
|
268
|
+
* "overall": [
|
|
269
|
+
* "This is a custom model validator that failed for the entire model",
|
|
270
|
+
* "Here is a second model failing message",
|
|
271
|
+
* ],
|
|
272
|
+
* "aDateProperty": [
|
|
273
|
+
* "A value is required",
|
|
274
|
+
* "Value is not a date",
|
|
275
|
+
* ],
|
|
276
|
+
* "anArrayProperty": [
|
|
277
|
+
* "BadChoice is not a valid choice",
|
|
278
|
+
* ],
|
|
279
|
+
* // the 'otherProperty' did not fail, and therefore is not shown here.
|
|
280
|
+
* }
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
// Here is one that passes
|
|
284
|
+
const errors2 = await anotherInstance.validate()
|
|
285
|
+
|
|
286
|
+
console.log(errors)
|
|
287
|
+
/*
|
|
288
|
+
undefined
|
|
289
|
+
*/
|
|
290
|
+
```
|
|
190
291
|
|
|
191
292
|
### Property validators
|
|
192
293
|
|
|
193
|
-
A property validator validates the value of a property. The inputs are the value, a model instance, the JavaScript object representation of the model, and optional configurations that are passed into the validator. The return can either be a string error or undefined if there are no errors. This function can be asynchronous, such as doing database lookups.
|
|
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
|
-
|
|
294
|
+
A property validator validates the value of a property. The inputs are the value, a model instance, the JavaScript object representation of the model, and optional configurations that are passed into the validator. The return can either be a string error or undefined if there are no errors. This function can be asynchronous, such as doing database lookups. An implementation in `functional-models-orm` does a "unique together" database query to make sure that only one entry has the value of two or more properties.
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
/**
|
|
298
|
+
* An example validator function that only allows the value of 5.
|
|
299
|
+
* @constructor
|
|
300
|
+
* @param {string} value - The value to be tested.
|
|
301
|
+
* @param {string} instance - A model instance, which can be used for cross referencing.
|
|
302
|
+
* @param {string} instanceData - The JavaScript object representation of the model.
|
|
303
|
+
* @param {string} context - An optional context object passed in as part of validating.
|
|
304
|
+
* @return {string|undefined} - If error, returns a string, otherwise returns undefined.
|
|
305
|
+
*/
|
|
306
|
+
const valueIsFiveValidator = (
|
|
307
|
+
value, // any kind of value.
|
|
308
|
+
instance, // A ModelInstance,
|
|
309
|
+
instanceData, // JavaScript object representation,
|
|
310
|
+
context = {}
|
|
311
|
+
) => {
|
|
312
|
+
return value === 5 ? undefined : 'Value is not 5'
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// A simpler more realistic implementation
|
|
316
|
+
const valueIsFiveValidator2 = value => {
|
|
317
|
+
return value === 5 ? undefined : 'Value is not 5'
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* An example async validator function that checks a database using an object passed into the configurations.
|
|
322
|
+
* @constructor
|
|
323
|
+
* @param {string} value - The value to be tested.
|
|
324
|
+
* @param {string} instance - A model instance, which can be used for cross referencing.
|
|
325
|
+
* @param {string} context - The JavaScript object representation of the model.
|
|
326
|
+
* @param {string} configurations - An optional context object passed in as part of validating.
|
|
327
|
+
* @return {Promise<string|undefined>} - Returns a promise, If error, returns a string, otherwise returns undefined.
|
|
328
|
+
*/
|
|
329
|
+
const checkDatabaseError = async (
|
|
330
|
+
value, // any kind of value.
|
|
331
|
+
instance, // A ModelInstance,
|
|
332
|
+
instanceData, // JavaScript object representation,
|
|
333
|
+
context = {}
|
|
334
|
+
) => {
|
|
335
|
+
const result = await context.someDatabaseObj.check(value)
|
|
336
|
+
if (result) {
|
|
337
|
+
return 'Some sort of database error'
|
|
338
|
+
}
|
|
339
|
+
return undefined
|
|
340
|
+
}
|
|
341
|
+
```
|
|
236
342
|
|
|
237
343
|
### Model Validators
|
|
238
344
|
|
|
239
345
|
Model validators allows one to check values across a model, ensuring that multiple values work together. The inputs are the model instance, the JavaScript object representation, and optional configurations. The return can either be a string error or undefined if there are no errors. This function can be asynchronous, such as doing database lookups.
|
|
240
346
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
347
|
+
```javascript
|
|
348
|
+
/**
|
|
349
|
+
* An example model validator that checks to see if two properties have the same value.
|
|
350
|
+
* @constructor
|
|
351
|
+
* @param {string} instance - A model instance, used for cross referencing.
|
|
352
|
+
* @param {string} instanceData - The JavaScript object representation of the model.
|
|
353
|
+
* @param {string} context - An optional context object passed in as part of validating.
|
|
354
|
+
* @return {string|undefined} - If error, returns a string, otherwise returns undefined.
|
|
355
|
+
*/
|
|
356
|
+
const checkForDuplicateValues = (
|
|
357
|
+
instance, // A ModelInstance,
|
|
358
|
+
instanceData, // JavaScript object representation,
|
|
359
|
+
context = {}
|
|
360
|
+
) => {
|
|
361
|
+
if (instanceData.firstProperty === instanceData.secondProperty) {
|
|
362
|
+
return 'Both properties must have different values'
|
|
363
|
+
}
|
|
364
|
+
return undefined
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Properties
|
|
261
369
|
|
|
262
370
|
There are numerous properties that are supported out of the box that cover most data modeling needs. It is also very easy to create custom properties that encapsulate unique choices
|
|
263
371
|
validation requirements, etc.
|
|
264
372
|
|
|
265
|
-
|
|
373
|
+
## List of Properties Out-Of-The-Box
|
|
266
374
|
|
|
267
|
-
|
|
375
|
+
### Dates
|
|
268
376
|
|
|
269
|
-
|
|
377
|
+
#### DateProperty
|
|
270
378
|
|
|
271
|
-
|
|
379
|
+
A property for handling dates. (Without time)
|
|
272
380
|
|
|
273
|
-
|
|
274
|
-
not an arbitrary id, but rather a mixture of properties that make up a unique instance. This is often
|
|
275
|
-
useful for when creating a "key" property.
|
|
381
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DateProperty.html)
|
|
276
382
|
|
|
277
|
-
|
|
278
|
-
any model that has a NaturalIdProperty should be deleted and then re-created rather than "updated" if
|
|
279
|
-
any property changes that make up the key composition.
|
|
383
|
+
#### DatetimeProperty
|
|
280
384
|
|
|
281
|
-
|
|
385
|
+
A property for handling dates with times.
|
|
282
386
|
|
|
283
|
-
|
|
387
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DatetimeProperty.html)
|
|
388
|
+
|
|
389
|
+
### Arrays
|
|
284
390
|
|
|
285
391
|
#### ArrayProperty
|
|
286
392
|
|
|
287
|
-
|
|
393
|
+
A property that can handle multiple values. If you want it to only have a single type, look below at the [SingleTypeArrayProperty](#singletypearrayproperty).
|
|
288
394
|
|
|
289
|
-
|
|
395
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.ArrayProperty.html)
|
|
396
|
+
|
|
397
|
+
#### SingleTypeArrayProperty
|
|
398
|
+
|
|
399
|
+
A property that can handle multiple values of the same type. This is enforced via validation and by typing.
|
|
400
|
+
|
|
401
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.SingleTypeArrayProperty.html)
|
|
402
|
+
|
|
403
|
+
### Objects
|
|
404
|
+
|
|
405
|
+
#### ObjectProperty
|
|
290
406
|
|
|
291
|
-
A property
|
|
407
|
+
A property that can handle "JSON compliant" objects. Simple objects.
|
|
408
|
+
|
|
409
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.ObjectProperty.html)
|
|
410
|
+
|
|
411
|
+
### Text
|
|
292
412
|
|
|
293
413
|
#### TextProperty
|
|
294
414
|
|
|
295
|
-
A text
|
|
415
|
+
A property for simple text values. If you want to hold large values look at [BigTextProperty](#bigtextproperty).
|
|
296
416
|
|
|
297
|
-
|
|
417
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.TextProperty.html)
|
|
418
|
+
|
|
419
|
+
#### BigTextProperty
|
|
420
|
+
|
|
421
|
+
A property for holding large text values.
|
|
422
|
+
|
|
423
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.BigTextProperty.html)
|
|
424
|
+
|
|
425
|
+
#### EmailProperty
|
|
298
426
|
|
|
299
|
-
A property that
|
|
427
|
+
A property that holds Emails.
|
|
428
|
+
|
|
429
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.EmailProperty.html)
|
|
430
|
+
|
|
431
|
+
### Numbers
|
|
432
|
+
|
|
433
|
+
#### IntegerProperty
|
|
434
|
+
|
|
435
|
+
A property that holds integer values. (No floating point).
|
|
436
|
+
|
|
437
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.IntegerProperty.html)
|
|
438
|
+
|
|
439
|
+
#### YearProperty
|
|
440
|
+
|
|
441
|
+
An integer property that holds year values.
|
|
442
|
+
|
|
443
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.YearProperty.html)
|
|
300
444
|
|
|
301
445
|
#### NumberProperty
|
|
302
446
|
|
|
303
|
-
A property
|
|
447
|
+
A property that holds floating point numbers.
|
|
304
448
|
|
|
305
|
-
|
|
449
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.NumberProperty.html)
|
|
306
450
|
|
|
307
|
-
|
|
451
|
+
### Misc
|
|
308
452
|
|
|
309
|
-
####
|
|
453
|
+
#### ConstantValueProperty
|
|
310
454
|
|
|
311
|
-
|
|
455
|
+
A property that has a single value that is hardcoded and can never be changed. Good for encoding values like the model name in the data.
|
|
456
|
+
|
|
457
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.ConstantValueProperty.html)
|
|
312
458
|
|
|
313
459
|
#### BooleanProperty
|
|
314
460
|
|
|
315
|
-
A true or false
|
|
461
|
+
A property that can hold a true or a false.
|
|
462
|
+
|
|
463
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.BooleanProperty.html)
|
|
464
|
+
|
|
465
|
+
### Keys / Primary / Foreign
|
|
466
|
+
|
|
467
|
+
#### PrimaryKeyUuidProperty
|
|
468
|
+
|
|
469
|
+
A property that holds a uuid as a primary key. It is automatically created if not provided.
|
|
470
|
+
|
|
471
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.PrimaryKeyUuidProperty.html)
|
|
316
472
|
|
|
317
473
|
#### ModelReferenceProperty
|
|
318
474
|
|
|
319
|
-
A property that
|
|
475
|
+
A property that holds a reference to another model instance. (In database-speak a foreign key). When code requests the value for this property, it is fetched and returns an object. However, when `.toObj()` is called on the model, this reference turns into a id. (number or string)
|
|
476
|
+
|
|
477
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.ModelReferenceProperty.html)
|
|
320
478
|
|
|
321
479
|
#### AdvancedModelReferenceProperty
|
|
322
480
|
|
|
323
|
-
|
|
481
|
+
The underlying implementation for {@link ModelReferenceProperty} that allows Model and ModelInstance expansions. This should only be used if there are certain expanded features that a referenced model needs to have.
|
|
482
|
+
|
|
483
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.AdvancedModelReferenceProperty.html)
|
|
484
|
+
|
|
485
|
+
### Calculated At RunTime
|
|
324
486
|
|
|
325
487
|
#### DenormalizedProperty
|
|
326
488
|
|
|
327
|
-
A
|
|
489
|
+
A property that provides a denormalized value. This is the underlying property for other (simpler) denormalized values, and allows you to build your own customized denormalization.
|
|
490
|
+
|
|
491
|
+
All denormalized properties are calculated once and then never again unless requested.
|
|
492
|
+
|
|
493
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DenormalizedProperty.html)
|
|
494
|
+
|
|
495
|
+
#### DenormalizedTextProperty
|
|
496
|
+
|
|
497
|
+
A text property that is denormalized.
|
|
498
|
+
|
|
499
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DenormalizedTextProperty.html)
|
|
500
|
+
|
|
501
|
+
#### DenormalizedNumberProperty
|
|
502
|
+
|
|
503
|
+
A number property that is denormalized and calculated when it doesn't exist.
|
|
504
|
+
|
|
505
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DenormalizedNumberProperty.html)
|
|
506
|
+
|
|
507
|
+
#### DenormalizedIntegerProperty
|
|
328
508
|
|
|
329
|
-
|
|
509
|
+
An integer property that is denormalized and calculated when it doesn't exist.
|
|
330
510
|
|
|
331
|
-
|
|
511
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.DenormalizedIntegerProperty.html)
|
|
332
512
|
|
|
333
|
-
|
|
513
|
+
#### NaturalIdProperty
|
|
514
|
+
|
|
515
|
+
A property that represents an id that is composed of other properties on an object. It is "natural" in the sense that it is not an arbitrary id, but rather a mixture of properties that make up a unique instance. This is often useful as a Primary Key where the data is uniquely represented by the combination of multiple values.
|
|
334
516
|
|
|
335
|
-
|
|
517
|
+
This can be useful to optimize a situation where you have a "unique together" requirement for model in a database, where there can only be one model that has the same of multiple properties.
|
|
336
518
|
|
|
337
|
-
|
|
519
|
+
NOTE: This property is never automatically updated if the properties changed. It is recommended that
|
|
520
|
+
any model that has a NaturalIdProperty should be deleted and then re-created rather than "updated" if
|
|
521
|
+
any property changes that make up the key composition.
|
|
338
522
|
|
|
339
|
-
|
|
523
|
+
For example if you are making a model for a `Species` and `Genera`, where we want to dynamically create the latinName for Species which a combination of the Genera's latin name with a latin name ending in this format: `GeneraLatinName speciesLatinName`
|
|
340
524
|
|
|
341
525
|
```typescript
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Your base data
|
|
346
|
-
type Greeting = {
|
|
347
|
-
name: 'Dolly',
|
|
348
|
-
greeting: 'Hello',
|
|
349
|
-
displayName?: string
|
|
526
|
+
type Genus = {
|
|
527
|
+
latinName: string // our key. Only one Genus can have a latinName.
|
|
528
|
+
commonName: string
|
|
350
529
|
}
|
|
351
530
|
|
|
352
|
-
|
|
353
|
-
|
|
531
|
+
const Genera = Model<Genus>({
|
|
532
|
+
pluralName: 'Genera',
|
|
533
|
+
singularName: 'Genus',
|
|
534
|
+
primaryKeyName: 'latinName',
|
|
354
535
|
properties: {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
536
|
+
latinName: TextProperty({ required: true }),
|
|
537
|
+
commonName: TextProperty({ required: true }),
|
|
538
|
+
},
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
type SpeciesType = {
|
|
542
|
+
latinName: Promise<string>
|
|
543
|
+
genusLatinName: string // We are using a string here, but a ModelReference would probably be better.
|
|
544
|
+
speciesName: string // We are going to combine this with the genusLatinName
|
|
545
|
+
commonName: string
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const Species = Model<SpeciesType>({
|
|
549
|
+
pluralName: 'Species',
|
|
550
|
+
singularName: 'Species',
|
|
551
|
+
primaryKeyName: 'latinName',
|
|
552
|
+
properties: {
|
|
553
|
+
// We want to combine genusLatinName with speciesName with a space between.
|
|
554
|
+
latinName: NaturalIdProperty({
|
|
555
|
+
propertyKeys: ['genusLatinName', 'speciesName'],
|
|
556
|
+
joiner: ' ',
|
|
359
557
|
}),
|
|
360
|
-
|
|
558
|
+
genusLatinName: TextProperty({ required: true }),
|
|
559
|
+
speciesName: TextProperty({ required: true }),
|
|
560
|
+
commonName: TextProperty({ required: true }),
|
|
561
|
+
},
|
|
361
562
|
})
|
|
362
563
|
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
564
|
+
const apples = Genus.create({ latinName: 'Malus', commonName: 'Apples' })
|
|
565
|
+
const domesticApples = Species.create<'latinName'>({
|
|
566
|
+
commonName: 'Apples',
|
|
567
|
+
genusLatinName: apples.latinName,
|
|
568
|
+
speciesName: 'domestica',
|
|
367
569
|
})
|
|
368
570
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
571
|
+
const id = await domesticApples.get.latinName()
|
|
572
|
+
console.info(id)
|
|
573
|
+
// Malus domestica
|
|
574
|
+
```
|
|
372
575
|
|
|
373
|
-
|
|
374
|
-
const data = await instance.toObj()
|
|
375
|
-
console.info(data) // { name: 'Dolly', greeting: 'Hello', displayName: 'Hello Dolly' }
|
|
576
|
+
In this situation, the latinName for species is not passed in, but calculated from the two other properties. This becomes the primary key for this object, which is unique.
|
|
376
577
|
|
|
377
|
-
|
|
378
|
-
const newData = {
|
|
379
|
-
...data,
|
|
380
|
-
name: 'Fred',
|
|
381
|
-
}
|
|
382
|
-
const instanceBad = Model<Greeting>.create(newData)
|
|
383
|
-
const badValue = await instance.get.displayName()
|
|
384
|
-
console.info(badValue) // Hello Dolly - this does not change!!!
|
|
385
|
-
|
|
386
|
-
// "A better way"
|
|
387
|
-
const newDataGood = {
|
|
388
|
-
...data,
|
|
389
|
-
name: 'Fred',
|
|
390
|
-
displayName: undefined
|
|
391
|
-
}
|
|
392
|
-
const instanceGood = Model<Greeting>.create(newDataGood)
|
|
393
|
-
const goodValue = await instance.get.displayName()
|
|
394
|
-
console.info(goodValue) // Hello Fred - Expected
|
|
395
|
-
```
|
|
578
|
+
[Documentation](https://monolithst.github.io/functional-models/functions/index.properties.NaturalIdProperty.html)
|