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.
@@ -0,0 +1,203 @@
1
+ # Quickstart
2
+
3
+ Get up and running with manyfest in under five minutes. This guide covers installation, basic schema definition, and the most common operations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install manyfest
9
+ ```
10
+
11
+ ## Your First Manifest
12
+
13
+ A manifest describes the shape of your data. Create one by passing a scope and a set of descriptors:
14
+
15
+ ```javascript
16
+ const libManyfest = require('manyfest');
17
+
18
+ const userManifest = new libManyfest({
19
+ Scope: 'User',
20
+ Descriptors: {
21
+ 'Name': { DataType: 'String', Required: true },
22
+ 'Email': { DataType: 'String', Required: true },
23
+ 'Profile.Age': { DataType: 'Integer' },
24
+ 'Profile.Bio': { DataType: 'String', Default: '' }
25
+ }
26
+ });
27
+ ```
28
+
29
+ The `Scope` is just a label for logging. Each key in `Descriptors` is an address -- a dot-notation path into your objects.
30
+
31
+ ## Reading Values
32
+
33
+ Read from nested objects safely, without worrying about missing intermediate properties:
34
+
35
+ ```javascript
36
+ const user = {
37
+ Name: 'Alice',
38
+ Email: 'alice@example.com',
39
+ Profile: { Age: 30, Bio: 'Engineer' }
40
+ };
41
+
42
+ userManifest.getValueAtAddress(user, 'Profile.Age'); // 30
43
+ userManifest.getValueAtAddress(user, 'Profile.Bio'); // 'Engineer'
44
+ userManifest.getValueAtAddress(user, 'Profile.Phone'); // undefined (no error!)
45
+
46
+ // Read from an empty object -- returns the default if one is defined
47
+ userManifest.getValueAtAddress({}, 'Profile.Bio'); // '' (the Default)
48
+ ```
49
+
50
+ ## Writing Values
51
+
52
+ Write to any depth and manyfest creates the structure for you:
53
+
54
+ ```javascript
55
+ const newUser = {};
56
+
57
+ userManifest.setValueAtAddress(newUser, 'Name', 'Bob');
58
+ userManifest.setValueAtAddress(newUser, 'Email', 'bob@example.com');
59
+ userManifest.setValueAtAddress(newUser, 'Profile.Age', 25);
60
+ userManifest.setValueAtAddress(newUser, 'Profile.Bio', 'Designer');
61
+
62
+ console.log(newUser);
63
+ // {
64
+ // Name: 'Bob',
65
+ // Email: 'bob@example.com',
66
+ // Profile: { Age: 25, Bio: 'Designer' }
67
+ // }
68
+ ```
69
+
70
+ No need to manually create `newUser.Profile = {}` first.
71
+
72
+ ## Validating Objects
73
+
74
+ Check an object against its schema:
75
+
76
+ ```javascript
77
+ const result = userManifest.validate({ Name: 'Carol' });
78
+
79
+ console.log(result);
80
+ // {
81
+ // Error: true,
82
+ // Errors: [
83
+ // 'Element at address "Email" is flagged REQUIRED but is not set in the object.'
84
+ // ],
85
+ // MissingElements: ['Email', 'Profile.Age', 'Profile.Bio']
86
+ // }
87
+ ```
88
+
89
+ Validation never throws. Missing required elements produce entries in `Errors`. All missing elements (required or not) appear in `MissingElements`.
90
+
91
+ ## Hash Shortcuts
92
+
93
+ Give an element a short hash to avoid typing long addresses:
94
+
95
+ ```javascript
96
+ const manifest = new libManyfest({
97
+ Scope: 'Config',
98
+ Descriptors: {
99
+ 'Database.Connection.Host': { Hash: 'DBHost', DataType: 'String', Default: 'localhost' },
100
+ 'Database.Connection.Port': { Hash: 'DBPort', DataType: 'Integer', Default: 5432 }
101
+ }
102
+ });
103
+
104
+ const config = {
105
+ Database: { Connection: { Host: 'db.example.com', Port: 3306 } }
106
+ };
107
+
108
+ manifest.getValueByHash(config, 'DBHost'); // 'db.example.com'
109
+ manifest.getValueByHash(config, 'DBPort'); // 3306
110
+ ```
111
+
112
+ ## Populating Defaults
113
+
114
+ Fill in missing values from your schema:
115
+
116
+ ```javascript
117
+ const record = { Name: 'Alice', Email: 'alice@example.com' };
118
+
119
+ userManifest.populateDefaults(record);
120
+ // record is now:
121
+ // {
122
+ // Name: 'Alice',
123
+ // Email: 'alice@example.com',
124
+ // Profile: { Bio: '' }
125
+ // }
126
+ // Only 'Profile.Bio' was added because it has a Default defined.
127
+ // Existing values were not overwritten.
128
+ ```
129
+
130
+ ## Without a Schema
131
+
132
+ Manyfest works perfectly well without a pre-defined schema. Create an empty instance and use it purely for safe object navigation:
133
+
134
+ ```javascript
135
+ const manifest = new libManyfest();
136
+
137
+ const data = { deeply: { nested: { value: 42 } } };
138
+
139
+ manifest.getValueAtAddress(data, 'deeply.nested.value'); // 42
140
+ manifest.getValueAtAddress(data, 'deeply.missing.value'); // undefined
141
+
142
+ manifest.setValueAtAddress(data, 'deeply.new.path', 'hello');
143
+ // data.deeply.new.path is now 'hello'
144
+ ```
145
+
146
+ ## Array Access
147
+
148
+ Access array elements with bracket notation:
149
+
150
+ ```javascript
151
+ const manifest = new libManyfest();
152
+
153
+ const data = {
154
+ users: [
155
+ { name: 'Alice', role: 'admin' },
156
+ { name: 'Bob', role: 'user' }
157
+ ]
158
+ };
159
+
160
+ manifest.getValueAtAddress(data, 'users[0].name'); // 'Alice'
161
+ manifest.getValueAtAddress(data, 'users[1].role'); // 'user'
162
+ ```
163
+
164
+ ## Building Schemas Programmatically
165
+
166
+ Add descriptors one at a time if you don't have the full schema up front:
167
+
168
+ ```javascript
169
+ const manifest = new libManyfest();
170
+ manifest.scope = 'Order';
171
+
172
+ manifest.addDescriptor('OrderID', { DataType: 'Integer', Required: true });
173
+ manifest.addDescriptor('Customer.Name', { DataType: 'String', Required: true });
174
+ manifest.addDescriptor('Customer.Email', { DataType: 'String' });
175
+ manifest.addDescriptor('Total', { DataType: 'Float', Default: 0.0 });
176
+
177
+ // Now use it just like a pre-defined manifest
178
+ const order = manifest.populateDefaults({});
179
+ // { Total: 0 }
180
+ ```
181
+
182
+ ## Serialization
183
+
184
+ Save and restore manifests:
185
+
186
+ ```javascript
187
+ // Serialize to JSON string
188
+ const saved = userManifest.serialize();
189
+
190
+ // Deserialize later
191
+ const restored = new libManyfest();
192
+ restored.deserialize(saved);
193
+ ```
194
+
195
+ ## Next Steps
196
+
197
+ - [Reading Values](reading.md) -- full guide to address notation, defaults, array access, boxed properties, back-navigation and more
198
+ - [Writing Values](writing.md) -- setting values, populating defaults, deleting values
199
+ - [Validating Objects](validating.md) -- required fields, strict mode, data type checking
200
+ - [Schema Definition](schema.md) -- descriptors, data types, scope, and the full descriptor object
201
+ - [Address Notation](address-notation.md) -- the complete address syntax reference
202
+ - [Hash Translation](hash-translation.md) -- reusing schemas with different hash mappings
203
+ - [Schema Manipulation](schema-manipulation.md) -- remapping and merging schemas
@@ -0,0 +1,339 @@
1
+ # Reading Values from Objects
2
+
3
+ Manyfest provides safe, flexible access to values within complex object structures using dot-notation address paths. No more chaining `&&` checks or catching exceptions from deeply nested property access.
4
+
5
+ ## Access
6
+
7
+ ```javascript
8
+ const libManyfest = require('manyfest');
9
+
10
+ // Create a manifest (schema optional for read operations)
11
+ const manifest = new libManyfest();
12
+
13
+ // Or with a schema definition
14
+ const manifest = new libManyfest({
15
+ Scope: 'User',
16
+ Descriptors: {
17
+ 'Name': { Hash: 'name', DataType: 'String' },
18
+ 'Profile.Email': { Hash: 'email', DataType: 'String', Default: 'none' }
19
+ }
20
+ });
21
+ ```
22
+
23
+ ## Core Concepts
24
+
25
+ ### Address Notation
26
+
27
+ Addresses use dot-separated paths to navigate nested objects:
28
+
29
+ ```javascript
30
+ const data = {
31
+ user: {
32
+ profile: {
33
+ name: 'Alice',
34
+ scores: [95, 88, 72]
35
+ }
36
+ }
37
+ };
38
+
39
+ manifest.getValueAtAddress(data, 'user.profile.name'); // 'Alice'
40
+ manifest.getValueAtAddress(data, 'user.profile.scores'); // [95, 88, 72]
41
+ manifest.getValueAtAddress(data, 'user.profile.scores[1]'); // 88
42
+ ```
43
+
44
+ Paths are case-sensitive and follow the exact structure of the object.
45
+
46
+ ## Getting Values
47
+
48
+ ### getValueAtAddress
49
+
50
+ Resolve a value at any depth using a dot-notation address:
51
+
52
+ ```javascript
53
+ const data = { a: { b: { c: 'deep value' } } };
54
+
55
+ manifest.getValueAtAddress(data, 'a.b.c'); // 'deep value'
56
+ manifest.getValueAtAddress(data, 'a.b'); // { c: 'deep value' }
57
+ manifest.getValueAtAddress(data, 'a'); // { b: { c: 'deep value' } }
58
+ manifest.getValueAtAddress(data, 'x.y.z'); // undefined
59
+ ```
60
+
61
+ When a descriptor exists for the address and defines a `Default` value, the default is returned instead of `undefined`:
62
+
63
+ ```javascript
64
+ const manifest = new libManyfest({
65
+ Scope: 'Config',
66
+ Descriptors: {
67
+ 'Theme': { DataType: 'String', Default: 'light' }
68
+ }
69
+ });
70
+
71
+ manifest.getValueAtAddress({}, 'Theme'); // 'light'
72
+ ```
73
+
74
+ ### getValueByHash
75
+
76
+ Retrieve a value using a hash rather than a direct address. When descriptors define hash-to-address mappings, this lets you use friendly short names:
77
+
78
+ ```javascript
79
+ const manifest = new libManyfest({
80
+ Scope: 'Animal',
81
+ Descriptors: {
82
+ 'MedicalStats.Temps.CET': { Hash: 'ComfET', DataType: 'Float' },
83
+ 'MedicalStats.Temps.MaxET': { Hash: 'MaxET', DataType: 'Float' }
84
+ }
85
+ });
86
+
87
+ const animal = {
88
+ MedicalStats: {
89
+ Temps: { CET: 98.6, MaxET: 104.2 }
90
+ }
91
+ };
92
+
93
+ manifest.getValueByHash(animal, 'ComfET'); // 98.6
94
+ manifest.getValueByHash(animal, 'MaxET'); // 104.2
95
+ ```
96
+
97
+ If the hash is not found in the descriptor table, manyfest treats it as a direct address and attempts resolution that way. This means `getValueByHash` works even without a schema defined.
98
+
99
+ ### checkAddressExists
100
+
101
+ Check whether a value exists at an address without retrieving it:
102
+
103
+ ```javascript
104
+ const data = { a: { b: 'value' } };
105
+
106
+ manifest.checkAddressExists(data, 'a.b'); // true
107
+ manifest.checkAddressExists(data, 'a.c'); // false
108
+ manifest.checkAddressExists(data, 'x.y.z'); // false
109
+ ```
110
+
111
+ ### checkAddressExistsByHash
112
+
113
+ The hash-based equivalent:
114
+
115
+ ```javascript
116
+ manifest.checkAddressExistsByHash(data, 'ComfET'); // true or false
117
+ ```
118
+
119
+ ## Array Access
120
+
121
+ Access array elements with bracket notation:
122
+
123
+ ```javascript
124
+ const data = {
125
+ students: [
126
+ { name: 'Alice', grade: 95 },
127
+ { name: 'Bob', grade: 88 },
128
+ { name: 'Carol', grade: 72 }
129
+ ]
130
+ };
131
+
132
+ manifest.getValueAtAddress(data, 'students[0]'); // { name: 'Alice', grade: 95 }
133
+ manifest.getValueAtAddress(data, 'students[1].name'); // 'Bob'
134
+ manifest.getValueAtAddress(data, 'students[2].grade'); // 72
135
+ ```
136
+
137
+ ### Set Access
138
+
139
+ Empty brackets return the full array, optionally filtered:
140
+
141
+ ```javascript
142
+ manifest.getValueAtAddress(data, 'students[]');
143
+ // Returns all student objects
144
+ ```
145
+
146
+ ## Boxed Properties
147
+
148
+ Properties with special characters in their keys (dots, spaces, dashes) can be accessed using bracket notation with quotes:
149
+
150
+ ```javascript
151
+ const data = {
152
+ 'my-special-key': 'value1',
153
+ 'another key': 'value2',
154
+ nested: {
155
+ 'some.dotted.key': 'value3'
156
+ }
157
+ };
158
+
159
+ manifest.getValueAtAddress(data, '["my-special-key"]'); // 'value1'
160
+ manifest.getValueAtAddress(data, "['another key']"); // 'value2'
161
+ manifest.getValueAtAddress(data, 'nested["some.dotted.key"]'); // 'value3'
162
+ ```
163
+
164
+ Single quotes, double quotes and backticks are all supported inside brackets.
165
+
166
+ ## Back-Navigation
167
+
168
+ Navigate backward through the object hierarchy using `..` sequences:
169
+
170
+ ```javascript
171
+ const data = {
172
+ Bundle: {
173
+ Contract: {
174
+ IDContract: 500,
175
+ Project: {
176
+ IDProject: 42
177
+ }
178
+ }
179
+ }
180
+ };
181
+
182
+ // From IDContract, go back up and into Project
183
+ manifest.getValueAtAddress(data, 'Bundle.Contract.IDContract...Project.IDProject');
184
+ // Navigates: IDContract -> back to Contract -> back to Bundle -> Project.IDProject
185
+ ```
186
+
187
+ Each `.` in the `..` sequence navigates one level up from the current position.
188
+
189
+ ## Object Set Access
190
+
191
+ Access properties across all keys of an object using `{}`:
192
+
193
+ ```javascript
194
+ const data = {
195
+ departments: {
196
+ engineering: { budget: 50000 },
197
+ marketing: { budget: 30000 },
198
+ sales: { budget: 40000 }
199
+ }
200
+ };
201
+
202
+ manifest.getValueAtAddress(data, 'departments{}.budget');
203
+ // Returns: { 'departments.engineering.budget': 50000, 'departments.marketing.budget': 30000, 'departments.sales.budget': 40000 }
204
+ ```
205
+
206
+ ## Function Resolution
207
+
208
+ Address paths can include function calls on the object:
209
+
210
+ ```javascript
211
+ const data = {
212
+ items: [3, 1, 4, 1, 5],
213
+ getTotal: function() { return this.items.reduce((a, b) => a + b, 0); }
214
+ };
215
+
216
+ manifest.getValueAtAddress(data, 'getTotal()'); // 14
217
+ ```
218
+
219
+ Functions can also receive arguments resolved from addresses:
220
+
221
+ ```javascript
222
+ const data = {
223
+ multiplier: 10,
224
+ scale: function(value) { return value * this.multiplier; },
225
+ baseValue: 5
226
+ };
227
+
228
+ manifest.getValueAtAddress(data, 'scale(baseValue)'); // 50
229
+ ```
230
+
231
+ ## Default Values
232
+
233
+ When a descriptor includes a `Default` property, that value is returned if the address resolves to `undefined`:
234
+
235
+ ```javascript
236
+ const manifest = new libManyfest({
237
+ Scope: 'Settings',
238
+ Descriptors: {
239
+ 'Theme': { DataType: 'String', Default: 'dark' },
240
+ 'FontSize': { DataType: 'Integer', Default: 14 },
241
+ 'Language': { DataType: 'String', Default: 'en' }
242
+ }
243
+ });
244
+
245
+ const settings = { Theme: 'light' };
246
+
247
+ manifest.getValueAtAddress(settings, 'Theme'); // 'light' (exists in object)
248
+ manifest.getValueAtAddress(settings, 'FontSize'); // 14 (default)
249
+ manifest.getValueAtAddress(settings, 'Language'); // 'en' (default)
250
+ ```
251
+
252
+ When no explicit `Default` is defined but a `DataType` is set, the type's built-in default is used:
253
+
254
+ | DataType | Default Value |
255
+ |----------|--------------|
256
+ | String | `""` |
257
+ | Number | `0` |
258
+ | Integer | `0` |
259
+ | Float | `0.0` |
260
+ | PreciseNumber | `"0.0"` |
261
+ | Boolean | `false` |
262
+ | Binary | `0` |
263
+ | DateTime | `0` |
264
+ | Array | `[]` |
265
+ | Object | `{}` |
266
+ | Null | `null` |
267
+
268
+ ## Use Cases
269
+
270
+ ### Safe Deep Property Access
271
+
272
+ ```javascript
273
+ function getUserCity(userData) {
274
+ const manifest = new libManyfest();
275
+ return manifest.getValueAtAddress(userData, 'profile.address.city');
276
+ }
277
+
278
+ // No error even if profile or address is missing
279
+ getUserCity({}); // undefined
280
+ getUserCity({ profile: { address: { city: 'Portland' } } }); // 'Portland'
281
+ ```
282
+
283
+ ### Configuration with Fallbacks
284
+
285
+ ```javascript
286
+ const configManifest = new libManyfest({
287
+ Scope: 'AppConfig',
288
+ Descriptors: {
289
+ 'Server.Port': { DataType: 'Integer', Default: 8080 },
290
+ 'Server.Host': { DataType: 'String', Default: 'localhost' },
291
+ 'Database.ConnectionString': { DataType: 'String', Default: 'mongodb://localhost/app' }
292
+ }
293
+ });
294
+
295
+ function getConfig(config, key) {
296
+ return configManifest.getValueByHash(config, key);
297
+ }
298
+
299
+ const config = { Server: { Port: 3000 } };
300
+ getConfig(config, 'Server.Port'); // 3000
301
+ getConfig(config, 'Server.Host'); // 'localhost'
302
+ getConfig(config, 'Database.ConnectionString'); // 'mongodb://localhost/app'
303
+ ```
304
+
305
+ ### Data Extraction with Hash Mapping
306
+
307
+ ```javascript
308
+ const apiManifest = new libManyfest({
309
+ Scope: 'APIResponse',
310
+ Descriptors: {
311
+ 'data.user.display_name': { Hash: 'UserName', DataType: 'String' },
312
+ 'data.user.contact.primary_email': { Hash: 'Email', DataType: 'String' },
313
+ 'data.metadata.created_at': { Hash: 'Created', DataType: 'DateTime' }
314
+ }
315
+ });
316
+
317
+ const response = {
318
+ data: {
319
+ user: {
320
+ display_name: 'Alice',
321
+ contact: { primary_email: 'alice@example.com' }
322
+ },
323
+ metadata: { created_at: '2024-01-15' }
324
+ }
325
+ };
326
+
327
+ apiManifest.getValueByHash(response, 'UserName'); // 'Alice'
328
+ apiManifest.getValueByHash(response, 'Email'); // 'alice@example.com'
329
+ apiManifest.getValueByHash(response, 'Created'); // '2024-01-15'
330
+ ```
331
+
332
+ ## Notes
333
+
334
+ - Address paths are case-sensitive
335
+ - Accessing a non-existent path returns `undefined` (no exceptions thrown)
336
+ - Array indices are zero-based
337
+ - `getValueByHash` falls back to treating the hash as a direct address if no mapping is found
338
+ - Back-navigation (`..`) resolves relative to the root object
339
+ - Functions in address paths are called with `apply`, bound to their containing object