manyfest 1.0.43 → 1.0.44
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 +91 -260
- package/docs/.nojekyll +0 -0
- package/docs/README.md +108 -0
- package/docs/_sidebar.md +17 -0
- package/docs/address-notation.md +244 -0
- package/docs/cover.md +11 -0
- package/docs/hash-translation.md +202 -0
- package/docs/index.html +51 -0
- package/docs/quickstart.md +203 -0
- package/docs/reading.md +339 -0
- package/docs/schema-manipulation.md +186 -0
- package/docs/schema.md +319 -0
- package/docs/validating.md +344 -0
- package/docs/writing.md +300 -0
- package/package.json +1 -1
- package/source/Manyfest-ObjectAddress-CheckAddressExists.js +3 -4
- package/source/Manyfest-ObjectAddress-DeleteValue.js +7 -5
- package/source/Manyfest-ObjectAddress-GetValue.js +2 -6
- package/source/Manyfest-ObjectAddress-Parser.js +3 -3
- package/source/Manyfest-ObjectAddress-SetValue.js +7 -4
- package/source/Manyfest-ParseConditionals.js +0 -13
- package/source/Manyfest.js +28 -15
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Schema Manipulation
|
|
2
|
+
|
|
3
|
+
Manyfest provides tools for remapping, merging and auto-generating schemas. These are useful when adapting a schema to different object shapes, combining schemas from multiple sources, or bootstrapping a schema from sample data.
|
|
4
|
+
|
|
5
|
+
## Address Remapping
|
|
6
|
+
|
|
7
|
+
### resolveAddressMappings
|
|
8
|
+
|
|
9
|
+
Remap descriptors from one set of addresses to another. This permanently mutates the descriptor object.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const descriptors = {
|
|
13
|
+
'Address.Of.a': { Hash: 'a', DataType: 'Number' },
|
|
14
|
+
'Address.Of.b': { Hash: 'b', DataType: 'String' }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mapping = {
|
|
18
|
+
'a': 'New.Address.Of.a',
|
|
19
|
+
'b': 'New.Address.Of.b'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
manifest.schemaManipulations.resolveAddressMappings(descriptors, mapping);
|
|
23
|
+
// descriptors is now:
|
|
24
|
+
// {
|
|
25
|
+
// 'New.Address.Of.a': { Hash: 'a', DataType: 'Number' },
|
|
26
|
+
// 'New.Address.Of.b': { Hash: 'b', DataType: 'String' }
|
|
27
|
+
// }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The mapping keys can be either addresses or hashes. If a key matches a descriptor's address directly, that descriptor is moved. If a key matches a descriptor's hash, the descriptor at that hash's address is moved.
|
|
31
|
+
|
|
32
|
+
If a mapping key matches neither an existing address nor a hash, a new descriptor is created with the key as its hash.
|
|
33
|
+
|
|
34
|
+
### safeResolveAddressMappings
|
|
35
|
+
|
|
36
|
+
The same operation, but returns a new object instead of mutating the original:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const original = {
|
|
40
|
+
'old.path': { Hash: 'value', DataType: 'String' }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const remapped = manifest.schemaManipulations.safeResolveAddressMappings(original, {
|
|
44
|
+
'value': 'new.path'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// original is unchanged
|
|
48
|
+
// remapped is: { 'new.path': { Hash: 'value', DataType: 'String' } }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Merging Schemas
|
|
52
|
+
|
|
53
|
+
### mergeAddressMappings
|
|
54
|
+
|
|
55
|
+
Combine two sets of descriptors. The destination (first argument) takes precedence when both contain the same address:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
const base = {
|
|
59
|
+
'Name': { DataType: 'String' },
|
|
60
|
+
'Email': { DataType: 'String' }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const extra = {
|
|
64
|
+
'Email': { DataType: 'String', Required: true },
|
|
65
|
+
'Phone': { DataType: 'String' }
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const merged = manifest.schemaManipulations.mergeAddressMappings(base, extra);
|
|
69
|
+
// {
|
|
70
|
+
// 'Name': { DataType: 'String' },
|
|
71
|
+
// 'Email': { DataType: 'String' }, <-- base wins (no Required)
|
|
72
|
+
// 'Phone': { DataType: 'String' } <-- added from extra
|
|
73
|
+
// }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Returns a new object. Neither input is mutated.
|
|
77
|
+
|
|
78
|
+
## Auto-Generating Schemas
|
|
79
|
+
|
|
80
|
+
### generateAddressses
|
|
81
|
+
|
|
82
|
+
Generate a schema from a sample object. This recursively walks the object and produces a descriptor for every reachable address:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const sample = {
|
|
86
|
+
name: 'Alice',
|
|
87
|
+
age: 30,
|
|
88
|
+
address: {
|
|
89
|
+
city: 'Portland',
|
|
90
|
+
zip: '97201'
|
|
91
|
+
},
|
|
92
|
+
tags: ['admin', 'user']
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const generated = manifest.objectAddressGeneration.generateAddressses(sample);
|
|
96
|
+
// {
|
|
97
|
+
// 'name': { Address: 'name', Hash: 'name', Name: 'name', DataType: 'String', Default: 'Alice', InSchema: false },
|
|
98
|
+
// 'age': { Address: 'age', Hash: 'age', Name: 'age', DataType: 'Number', Default: 30, InSchema: false },
|
|
99
|
+
// 'address': { Address: 'address', Hash: 'address', Name: 'address', DataType: 'Object', InSchema: false },
|
|
100
|
+
// 'address.city':{ Address: 'address.city', Hash: 'address.city', Name: 'address.city', DataType: 'String', Default: 'Portland', InSchema: false },
|
|
101
|
+
// 'address.zip': { Address: 'address.zip', Hash: 'address.zip', Name: 'address.zip', DataType: 'String', Default: '97201', InSchema: false },
|
|
102
|
+
// 'tags': { Address: 'tags', Hash: 'tags', Name: 'tags', DataType: 'Array', InSchema: false },
|
|
103
|
+
// 'tags[0]': { Address: 'tags[0]', Hash: 'tags[0]', Name: 'tags[0]', DataType: 'String', Default: 'admin', InSchema: false },
|
|
104
|
+
// 'tags[1]': { Address: 'tags[1]', Hash: 'tags[1]', Name: 'tags[1]', DataType: 'String', Default: 'user', InSchema: false }
|
|
105
|
+
// }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Every generated descriptor has `InSchema: false`, which acts as a flag for tooling to prompt a developer to opt-in before including it in a final schema.
|
|
109
|
+
|
|
110
|
+
Data types are inferred from JavaScript types:
|
|
111
|
+
- `string` becomes `String`
|
|
112
|
+
- `number` and `bigint` become `Number`
|
|
113
|
+
- Arrays become `Array` (with each element also generated)
|
|
114
|
+
- Objects become `Object` (with each property also generated)
|
|
115
|
+
- `null` and `undefined` become `Any`
|
|
116
|
+
- Functions and Symbols are skipped
|
|
117
|
+
|
|
118
|
+
## Use Cases
|
|
119
|
+
|
|
120
|
+
### Adapting a Schema to a Different API Shape
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const baseSchema = {
|
|
124
|
+
'user.name': { Hash: 'UserName', DataType: 'String' },
|
|
125
|
+
'user.email': { Hash: 'UserEmail', DataType: 'String' },
|
|
126
|
+
'user.role': { Hash: 'UserRole', DataType: 'String' }
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// API v2 returns data in a flat shape
|
|
130
|
+
const v2Mapping = {
|
|
131
|
+
'UserName': 'display_name',
|
|
132
|
+
'UserEmail': 'email_address',
|
|
133
|
+
'UserRole': 'access_level'
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const v2Schema = manifest.schemaManipulations.safeResolveAddressMappings(baseSchema, v2Mapping);
|
|
137
|
+
// v2Schema addresses are now: display_name, email_address, access_level
|
|
138
|
+
// Hashes remain: UserName, UserEmail, UserRole
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Bootstrapping a Schema from Sample Data
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
// Take a sample API response
|
|
145
|
+
const sampleResponse = await fetch('/api/users/1').then(r => r.json());
|
|
146
|
+
|
|
147
|
+
// Generate all possible addresses
|
|
148
|
+
const generated = manifest.objectAddressGeneration.generateAddressses(sampleResponse);
|
|
149
|
+
|
|
150
|
+
// Pick the ones you need and build a proper schema
|
|
151
|
+
const schema = new libManyfest({
|
|
152
|
+
Scope: 'User',
|
|
153
|
+
Descriptors: {
|
|
154
|
+
'data.id': { ...generated['data.id'], InSchema: true, Required: true },
|
|
155
|
+
'data.name': { ...generated['data.name'], InSchema: true, Name: 'Display Name' },
|
|
156
|
+
'data.email': { ...generated['data.email'], InSchema: true, Name: 'Email' }
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Combining Base and Extension Schemas
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const coreFields = {
|
|
165
|
+
'ID': { DataType: 'Integer', Required: true },
|
|
166
|
+
'Name': { DataType: 'String', Required: true },
|
|
167
|
+
'Created': { DataType: 'DateTime' }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const auditFields = {
|
|
171
|
+
'CreatedBy': { DataType: 'String' },
|
|
172
|
+
'ModifiedBy': { DataType: 'String' },
|
|
173
|
+
'Modified': { DataType: 'DateTime' }
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const fullSchema = manifest.schemaManipulations.mergeAddressMappings(coreFields, auditFields);
|
|
177
|
+
// Contains all six fields
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Notes
|
|
181
|
+
|
|
182
|
+
- `resolveAddressMappings` mutates the input object; use `safeResolveAddressMappings` when you need to preserve the original
|
|
183
|
+
- `mergeAddressMappings` gives precedence to the first (destination) argument on address conflicts
|
|
184
|
+
- `generateAddressses` produces every reachable address including individual array elements -- the output can be large for complex objects
|
|
185
|
+
- Generated schemas use `InSchema: false` as a signal that the descriptor was auto-generated and has not been reviewed
|
|
186
|
+
- Symbols and functions in source objects are silently skipped during generation
|
package/docs/schema.md
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Schema Definition
|
|
2
|
+
|
|
3
|
+
A manyfest schema describes the structure, types and metadata for the elements of an object. Schemas power validation, default population and hash-based lookups.
|
|
4
|
+
|
|
5
|
+
## Access
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const libManyfest = require('manyfest');
|
|
9
|
+
|
|
10
|
+
// From a definition object
|
|
11
|
+
const manifest = new libManyfest({
|
|
12
|
+
Scope: 'Animal',
|
|
13
|
+
Descriptors: {
|
|
14
|
+
'IDAnimal': { Name: 'Database ID', DataType: 'Integer', Default: 0 },
|
|
15
|
+
'Name': { Description: 'The species name.' }
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Or build one programmatically
|
|
20
|
+
const manifest = new libManyfest();
|
|
21
|
+
manifest.scope = 'Animal';
|
|
22
|
+
manifest.addDescriptor('IDAnimal', { Name: 'Database ID', DataType: 'Integer', Default: 0 });
|
|
23
|
+
manifest.addDescriptor('Name', { Description: 'The species name.' });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Scope
|
|
27
|
+
|
|
28
|
+
The scope is a label that identifies what kind of data this manifest describes. It is used in log messages and has no functional effect on address resolution.
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
const manifest = new libManyfest({
|
|
32
|
+
Scope: 'Invoice',
|
|
33
|
+
Descriptors: { /* ... */ }
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
manifest.scope; // 'Invoice'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If no scope is provided, it defaults to `'DEFAULT'`.
|
|
40
|
+
|
|
41
|
+
## Descriptors
|
|
42
|
+
|
|
43
|
+
Each key in the `Descriptors` object is an address. The value is a descriptor object with metadata about that element.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
{
|
|
47
|
+
Scope: 'User',
|
|
48
|
+
Descriptors: {
|
|
49
|
+
'Email': {
|
|
50
|
+
Hash: 'email',
|
|
51
|
+
Name: 'Email Address',
|
|
52
|
+
NameShort: 'Email',
|
|
53
|
+
Description: 'The primary contact email for this user.',
|
|
54
|
+
DataType: 'String',
|
|
55
|
+
Required: true,
|
|
56
|
+
Default: ''
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Descriptor Properties
|
|
63
|
+
|
|
64
|
+
| Property | Type | Description |
|
|
65
|
+
|----------|------|-------------|
|
|
66
|
+
| **Hash** | `string` | Short identifier for hash-based lookups. Defaults to the address if not set. |
|
|
67
|
+
| **Name** | `string` | Human-readable name for the element. |
|
|
68
|
+
| **NameShort** | `string` | Abbreviated name for compact displays (tables, charts, logs). |
|
|
69
|
+
| **Description** | `string` | Longer description of what this element represents. |
|
|
70
|
+
| **DataType** | `string` | The expected data type. Used for validation and default values. |
|
|
71
|
+
| **Required** | `boolean` | When `true`, validation reports an error if this element is missing. |
|
|
72
|
+
| **Default** | `any` | The value returned when the element is not present in an object. |
|
|
73
|
+
| **Address** | `string` | Auto-populated with the descriptor's key if not explicitly set. |
|
|
74
|
+
|
|
75
|
+
All properties are optional. A descriptor can be as minimal as an empty object:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
{
|
|
79
|
+
Descriptors: {
|
|
80
|
+
'Name': {} // Valid -- address is 'Name', hash defaults to 'Name'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Data Types
|
|
86
|
+
|
|
87
|
+
| DataType | JavaScript Type | Default Value | Description |
|
|
88
|
+
|----------|----------------|---------------|-------------|
|
|
89
|
+
| `String` | `string` | `""` | A text value |
|
|
90
|
+
| `Integer` | `number` | `0` | A whole number (no decimal point) |
|
|
91
|
+
| `Float` | `number` | `0.0` | A floating point number |
|
|
92
|
+
| `Number` | `number` | `0` | Any numeric value |
|
|
93
|
+
| `PreciseNumber` | `string` | `"0.0"` | Arbitrary precision number stored as a string |
|
|
94
|
+
| `Boolean` | `boolean` | `false` | `true` or `false` |
|
|
95
|
+
| `Binary` | `number` | `0` | A boolean represented as `1` or `0` |
|
|
96
|
+
| `YesNo` | `string` | - | A boolean represented as `"Y"` or `"N"` |
|
|
97
|
+
| `DateTime` | varies | `0` | A value parsable by JavaScript's `Date` constructor |
|
|
98
|
+
| `Key` | varies | - | A two-part key with ID and GUID (see below) |
|
|
99
|
+
| `Array` | `array` | `[]` | A JavaScript array |
|
|
100
|
+
| `Object` | `object` | `{}` | A JavaScript object |
|
|
101
|
+
| `Null` | `null` | `null` | An explicit null value |
|
|
102
|
+
|
|
103
|
+
### The Key Data Type
|
|
104
|
+
|
|
105
|
+
Keys represent a paired identifier: an integer ID and a string GUID. This is useful for database records that have both a local numeric identifier and a globally unique string identifier.
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
{
|
|
109
|
+
Descriptors: {
|
|
110
|
+
'IDBook': {
|
|
111
|
+
Hash: 'IDBook',
|
|
112
|
+
Name: 'Book Identifier',
|
|
113
|
+
DataType: 'Key',
|
|
114
|
+
KeyRepresentation: 'ID',
|
|
115
|
+
GUIDAddress: 'GUIDBook',
|
|
116
|
+
IDAddress: 'IDBook'
|
|
117
|
+
},
|
|
118
|
+
'GUIDBook': {
|
|
119
|
+
Hash: 'GUIDBook',
|
|
120
|
+
Name: 'Book GUID',
|
|
121
|
+
DataType: 'Key',
|
|
122
|
+
KeyRepresentation: 'GUID',
|
|
123
|
+
GUIDAddress: 'GUIDBook',
|
|
124
|
+
IDAddress: 'IDBook'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The `KeyRepresentation` indicates which half of the pair this descriptor points to. The `GUIDAddress` and `IDAddress` properties link the two together.
|
|
131
|
+
|
|
132
|
+
## Adding Descriptors
|
|
133
|
+
|
|
134
|
+
### addDescriptor
|
|
135
|
+
|
|
136
|
+
Add a single descriptor to an existing manifest:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
manifest.addDescriptor('Profile.Phone', {
|
|
140
|
+
Name: 'Phone Number',
|
|
141
|
+
DataType: 'String',
|
|
142
|
+
Required: false
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
If a descriptor already exists at that address, it is replaced. The `Address` property is auto-set if not provided. The `Hash` property defaults to the address if not provided.
|
|
147
|
+
|
|
148
|
+
Returns `true` on success, `false` if the descriptor is not a valid object.
|
|
149
|
+
|
|
150
|
+
## Retrieving Descriptors
|
|
151
|
+
|
|
152
|
+
### getDescriptor
|
|
153
|
+
|
|
154
|
+
Get a descriptor by its address:
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const desc = manifest.getDescriptor('Profile.Phone');
|
|
158
|
+
// { Name: 'Phone Number', DataType: 'String', Required: false, Address: 'Profile.Phone', Hash: 'Profile.Phone' }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### getDescriptorByHash
|
|
162
|
+
|
|
163
|
+
Get a descriptor by its hash:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const manifest = new libManyfest({
|
|
167
|
+
Scope: 'Config',
|
|
168
|
+
Descriptors: {
|
|
169
|
+
'Database.Connection.Host': { Hash: 'DBHost', DataType: 'String' }
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
manifest.getDescriptorByHash('DBHost');
|
|
174
|
+
// { Hash: 'DBHost', DataType: 'String', Address: 'Database.Connection.Host' }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### eachDescriptor
|
|
178
|
+
|
|
179
|
+
Iterate over all descriptors:
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
manifest.eachDescriptor((pDescriptor) => {
|
|
183
|
+
console.log(`${pDescriptor.Address}: ${pDescriptor.Name || '(unnamed)'}`);
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Serialization
|
|
188
|
+
|
|
189
|
+
### serialize
|
|
190
|
+
|
|
191
|
+
Convert the manifest to a JSON string for storage or transmission:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
const json = manifest.serialize();
|
|
195
|
+
// '{"Scope":"User","Descriptors":{...},"HashTranslations":{...}}'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### deserialize
|
|
199
|
+
|
|
200
|
+
Load a manifest from a JSON string:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const manifest = new libManyfest();
|
|
204
|
+
manifest.deserialize(json);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### getManifest
|
|
208
|
+
|
|
209
|
+
Get the manifest state as a plain object:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
const state = manifest.getManifest();
|
|
213
|
+
// { Scope: 'User', Descriptors: {...}, HashTranslations: {...} }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### clone
|
|
217
|
+
|
|
218
|
+
Create a deep copy of the manifest:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const copy = manifest.clone();
|
|
222
|
+
// Independent copy -- changes to one don't affect the other
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Default Values
|
|
226
|
+
|
|
227
|
+
When a descriptor defines a `Default`, that value is returned by `getValueAtAddress` and `getValueByHash` if the element is missing from the object.
|
|
228
|
+
|
|
229
|
+
When no explicit `Default` is set but a `DataType` is defined, the type's built-in default is used (see the table above).
|
|
230
|
+
|
|
231
|
+
### Overriding Type Defaults
|
|
232
|
+
|
|
233
|
+
The built-in defaults for each type can be overridden at construction time:
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
const manifest = new libManyfest({
|
|
237
|
+
Scope: 'Custom',
|
|
238
|
+
defaultValues: {
|
|
239
|
+
String: '(empty)',
|
|
240
|
+
Integer: -1,
|
|
241
|
+
Boolean: true
|
|
242
|
+
},
|
|
243
|
+
Descriptors: {
|
|
244
|
+
'Name': { DataType: 'String' },
|
|
245
|
+
'Count': { DataType: 'Integer' }
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
manifest.getValueAtAddress({}, 'Name'); // '(empty)'
|
|
250
|
+
manifest.getValueAtAddress({}, 'Count'); // -1
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Strict Mode
|
|
254
|
+
|
|
255
|
+
When `strict` is set to `true`, every described element that is missing from an object is treated as an error during validation, regardless of the `Required` flag:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
const manifest = new libManyfest({
|
|
259
|
+
Scope: 'Config',
|
|
260
|
+
strict: true,
|
|
261
|
+
Descriptors: {
|
|
262
|
+
'Host': { DataType: 'String' },
|
|
263
|
+
'Port': { DataType: 'Integer' }
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
manifest.validate({});
|
|
268
|
+
// Both Host and Port are reported as errors
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Without strict mode, only elements with `Required: true` generate errors when missing.
|
|
272
|
+
|
|
273
|
+
## Use Cases
|
|
274
|
+
|
|
275
|
+
### API Response Schema
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
const apiSchema = new libManyfest({
|
|
279
|
+
Scope: 'WeatherAPI',
|
|
280
|
+
Descriptors: {
|
|
281
|
+
'data.temperature': { Hash: 'Temp', Name: 'Temperature', DataType: 'Float' },
|
|
282
|
+
'data.humidity': { Hash: 'Humidity', Name: 'Humidity', DataType: 'Float' },
|
|
283
|
+
'data.wind.speed': { Hash: 'WindSpeed', Name: 'Wind Speed', DataType: 'Float' },
|
|
284
|
+
'metadata.station': { Hash: 'Station', Name: 'Station ID', DataType: 'String' }
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Different API, same hashes
|
|
289
|
+
const response = fetchWeatherData();
|
|
290
|
+
const temp = apiSchema.getValueByHash(response, 'Temp');
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Form Field Definitions
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
const formSchema = new libManyfest({
|
|
297
|
+
Scope: 'ContactForm',
|
|
298
|
+
Descriptors: {
|
|
299
|
+
'FirstName': { Name: 'First Name', DataType: 'String', Required: true },
|
|
300
|
+
'LastName': { Name: 'Last Name', DataType: 'String', Required: true },
|
|
301
|
+
'Email': { Name: 'Email Address', DataType: 'String', Required: true },
|
|
302
|
+
'Phone': { Name: 'Phone Number', DataType: 'String' },
|
|
303
|
+
'Message': { Name: 'Message', DataType: 'String', Default: '' }
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Use descriptors to generate form labels
|
|
308
|
+
formSchema.eachDescriptor((desc) => {
|
|
309
|
+
console.log(`<label>${desc.Name}${desc.Required ? ' *' : ''}</label>`);
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Notes
|
|
314
|
+
|
|
315
|
+
- The address (the key in `Descriptors`) is the canonical identifier for an element
|
|
316
|
+
- Hashes provide an alias; if no hash is set, the address is used as the hash
|
|
317
|
+
- Multiple descriptors can share the same hash, but the last one wins
|
|
318
|
+
- Descriptors are stored by address, so two descriptors at the same address will overwrite
|
|
319
|
+
- `reset()` clears all descriptors, hashes and the scope
|