@speclynx/apidom-datamodel 1.12.2

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.
Files changed (46) hide show
  1. package/LICENSES/AFL-3.0.txt +182 -0
  2. package/LICENSES/Apache-2.0.txt +202 -0
  3. package/LICENSES/BSD-3-Clause.txt +26 -0
  4. package/LICENSES/MIT.txt +9 -0
  5. package/NOTICE +74 -0
  6. package/README.md +479 -0
  7. package/dist/apidom-datamodel.browser.js +2717 -0
  8. package/dist/apidom-datamodel.browser.min.js +1 -0
  9. package/package.json +53 -0
  10. package/src/KeyValuePair.cjs +38 -0
  11. package/src/KeyValuePair.mjs +34 -0
  12. package/src/Namespace.cjs +212 -0
  13. package/src/Namespace.mjs +206 -0
  14. package/src/ObjectSlice.cjs +206 -0
  15. package/src/ObjectSlice.mjs +202 -0
  16. package/src/elements/LinkElement.cjs +44 -0
  17. package/src/elements/LinkElement.mjs +39 -0
  18. package/src/elements/RefElement.cjs +31 -0
  19. package/src/elements/RefElement.mjs +26 -0
  20. package/src/index.cjs +25 -0
  21. package/src/index.mjs +6 -0
  22. package/src/primitives/ArrayElement.cjs +176 -0
  23. package/src/primitives/ArrayElement.mjs +169 -0
  24. package/src/primitives/BooleanElement.cjs +20 -0
  25. package/src/primitives/BooleanElement.mjs +15 -0
  26. package/src/primitives/CollectionElement.cjs +194 -0
  27. package/src/primitives/CollectionElement.mjs +187 -0
  28. package/src/primitives/Element.cjs +438 -0
  29. package/src/primitives/Element.mjs +433 -0
  30. package/src/primitives/MemberElement.cjs +54 -0
  31. package/src/primitives/MemberElement.mjs +49 -0
  32. package/src/primitives/NullElement.cjs +28 -0
  33. package/src/primitives/NullElement.mjs +23 -0
  34. package/src/primitives/NumberElement.cjs +20 -0
  35. package/src/primitives/NumberElement.mjs +15 -0
  36. package/src/primitives/ObjectElement.cjs +222 -0
  37. package/src/primitives/ObjectElement.mjs +216 -0
  38. package/src/primitives/StringElement.cjs +27 -0
  39. package/src/primitives/StringElement.mjs +22 -0
  40. package/src/registration.cjs +87 -0
  41. package/src/registration.mjs +70 -0
  42. package/src/serialisers/JSONSerialiser.cjs +144 -0
  43. package/src/serialisers/JSONSerialiser.mjs +140 -0
  44. package/src/types.cjs +3 -0
  45. package/src/types.mjs +1 -0
  46. package/types/apidom-datamodel.d.ts +887 -0
package/README.md ADDED
@@ -0,0 +1,479 @@
1
+ # @speclynx/apidom-datamodel
2
+
3
+ `@speclynx/apidom-datamodel` provides the foundational data model primitives for ApiDOM.
4
+ It contains the core element classes, namespace system, and serialization utilities
5
+ that form the basis for all ApiDOM structures.
6
+
7
+ The data model is based on SpecLynx-flavored [Refract specification 0.7.0](https://github.com/refractproject/refract-spec),
8
+ a recursive data structure for expressing complex structures, relationships, and metadata.
9
+
10
+ ## Installation
11
+
12
+ You can install this package via [npm CLI](https://docs.npmjs.com/cli) by running the following command:
13
+
14
+ ```sh
15
+ $ npm install @speclynx/apidom-datamodel
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Primitive Elements
21
+
22
+ Primitive elements are the building blocks of ApiDOM. Each element has:
23
+ - `element` - Type identifier (e.g., 'string', 'object', 'array')
24
+ - `content` - The element's value
25
+ - `meta` - Metadata (id, classes, title, description, links)
26
+ - `attributes` - Element-specific properties
27
+
28
+ ### Element
29
+
30
+ Base class that all ApiDOM elements extend.
31
+
32
+ ```js
33
+ import { Element } from '@speclynx/apidom-datamodel';
34
+
35
+ const element = new Element('content');
36
+ element.element = 'custom';
37
+ element.meta.set('id', 'unique-id');
38
+ element.attributes.set('attr', 'value');
39
+ ```
40
+
41
+ Additionally, convenience attributes are exposed on every element as shortcuts for common meta properties:
42
+
43
+ - `id` (StringElement) - Shortcut for `.meta.get('id')`
44
+ - `classes` (ArrayElement) - Shortcut for `.meta.get('classes')`
45
+ - `title` (StringElement) - Shortcut for `.meta.get('title')`
46
+ - `description` (StringElement) - Shortcut for `.meta.get('description')`
47
+ - `links` (ArrayElement) - Shortcut for `.meta.get('links')`
48
+
49
+ ```js
50
+ import { StringElement } from '@speclynx/apidom-datamodel';
51
+
52
+ const element = new StringElement('hello');
53
+ element.id = 'greeting';
54
+ element.title = 'A greeting';
55
+ element.classes = ['important', 'message'];
56
+
57
+ element.id.toValue(); // => 'greeting'
58
+ element.title.toValue(); // => 'A greeting'
59
+ element.classes.toValue(); // => ['important', 'message']
60
+ ```
61
+
62
+ ### StringElement
63
+
64
+ Represents a string value.
65
+
66
+ ```js
67
+ import { StringElement } from '@speclynx/apidom-datamodel';
68
+
69
+ const str = new StringElement('hello');
70
+ str.toValue(); // => 'hello'
71
+ str.length; // => 5
72
+ ```
73
+
74
+ ### NumberElement
75
+
76
+ Represents a numeric value.
77
+
78
+ ```js
79
+ import { NumberElement } from '@speclynx/apidom-datamodel';
80
+
81
+ const num = new NumberElement(42);
82
+ num.toValue(); // => 42
83
+ ```
84
+
85
+ ### BooleanElement
86
+
87
+ Represents a boolean value.
88
+
89
+ ```js
90
+ import { BooleanElement } from '@speclynx/apidom-datamodel';
91
+
92
+ const bool = new BooleanElement(true);
93
+ bool.toValue(); // => true
94
+ ```
95
+
96
+ ### NullElement
97
+
98
+ Represents a null value.
99
+
100
+ ```js
101
+ import { NullElement } from '@speclynx/apidom-datamodel';
102
+
103
+ const nil = new NullElement();
104
+ nil.toValue(); // => null
105
+ ```
106
+
107
+ ### ArrayElement
108
+
109
+ Represents an ordered collection of elements.
110
+
111
+ ```js
112
+ import { ArrayElement } from '@speclynx/apidom-datamodel';
113
+
114
+ const arr = new ArrayElement([1, 2, 3]);
115
+ arr.length; // => 3
116
+ arr.get(0).toValue(); // => 1
117
+ arr.push(4);
118
+ arr.map((item) => item.toValue()); // => [1, 2, 3, 4]
119
+ arr.filter((item) => item.toValue() > 2); // => ArraySlice with 3, 4
120
+ ```
121
+
122
+ ### ObjectElement
123
+
124
+ Represents a collection of key-value pairs.
125
+
126
+ ```js
127
+ import { ObjectElement } from '@speclynx/apidom-datamodel';
128
+
129
+ const obj = new ObjectElement({ name: 'John', age: 30 });
130
+ obj.get('name').toValue(); // => 'John'
131
+ obj.set('email', 'john@example.com');
132
+ obj.keys(); // => ['name', 'age', 'email']
133
+ obj.values(); // => ['John', 30, 'john@example.com']
134
+ obj.hasKey('name'); // => true
135
+ ```
136
+
137
+ ObjectElement supports generics for typed key-value pairs:
138
+
139
+ ```ts
140
+ import { ObjectElement, StringElement, NumberElement } from '@speclynx/apidom-datamodel';
141
+
142
+ const typed = new ObjectElement<StringElement, NumberElement>({ a: 1, b: 2 });
143
+ ```
144
+
145
+ ### MemberElement
146
+
147
+ Represents a key-value pair within an ObjectElement.
148
+
149
+ ```js
150
+ import { MemberElement } from '@speclynx/apidom-datamodel';
151
+
152
+ const member = new MemberElement('key', 'value');
153
+ member.key.toValue(); // => 'key'
154
+ member.value.toValue(); // => 'value'
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Higher-Order Elements
160
+
161
+ ### RefElement
162
+
163
+ Represents a reference to another element by ID.
164
+
165
+ ```js
166
+ import { RefElement, StringElement } from '@speclynx/apidom-datamodel';
167
+
168
+ const target = new StringElement('referenced');
169
+ target.id = 'target-id';
170
+
171
+ const ref = new RefElement('target-id');
172
+ ref.toValue(); // => 'target-id'
173
+ ```
174
+
175
+ ### LinkElement
176
+
177
+ Represents a hyperlink.
178
+
179
+ ```js
180
+ import { LinkElement } from '@speclynx/apidom-datamodel';
181
+
182
+ const link = new LinkElement();
183
+ link.relation = 'self';
184
+ link.href = 'https://example.com';
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Extending Elements
190
+
191
+ You can create custom element types by extending the primitive elements.
192
+ This is useful for creating semantic elements that represent specific data structures.
193
+
194
+ ### Creating a Custom Element
195
+
196
+ ```js
197
+ import { ObjectElement, StringElement } from '@speclynx/apidom-datamodel';
198
+
199
+ class PersonElement extends ObjectElement {
200
+ constructor(content, meta, attributes) {
201
+ super(content, meta, attributes);
202
+ this.element = 'person';
203
+ }
204
+
205
+ get name() {
206
+ return this.get('name');
207
+ }
208
+
209
+ set name(value) {
210
+ this.set('name', value);
211
+ }
212
+
213
+ get email() {
214
+ return this.get('email');
215
+ }
216
+
217
+ set email(value) {
218
+ this.set('email', value);
219
+ }
220
+ }
221
+
222
+ const person = new PersonElement({ name: 'John', email: 'john@example.com' });
223
+ person.element; // => 'person'
224
+ person.name.toValue(); // => 'John'
225
+ person.email.toValue(); // => 'john@example.com'
226
+ ```
227
+
228
+ ### Registering Custom Elements
229
+
230
+ To use custom elements with serialization and namespace features, register them:
231
+
232
+ ```js
233
+ import { Namespace } from '@speclynx/apidom-datamodel';
234
+
235
+ const namespace = new Namespace();
236
+ namespace.register('person', PersonElement);
237
+
238
+ // Now PersonElement will be used when deserializing 'person' elements
239
+ const serialiser = namespace.serialiser;
240
+ const refracted = { element: 'person', content: [] };
241
+ const element = serialiser.deserialise(refracted); // => PersonElement
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Refraction
247
+
248
+ The `refract` function converts JavaScript values to ApiDOM elements.
249
+
250
+ ```js
251
+ import { refract } from '@speclynx/apidom-datamodel';
252
+
253
+ refract('hello'); // => StringElement('hello')
254
+ refract(42); // => NumberElement(42)
255
+ refract(true); // => BooleanElement(true)
256
+ refract(null); // => NullElement()
257
+ refract([1, 2, 3]); // => ArrayElement([NumberElement(1), NumberElement(2), NumberElement(3)])
258
+ refract([1, undefined, 3]); // => ArrayElement([NumberElement(1), NullElement(), NumberElement(3)])
259
+ refract({ a: 'b' }); // => ObjectElement({ a: StringElement('b') })
260
+ refract({ a: undefined }); // => ObjectElement({ a: undefined }) - undefined values are preserved
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Namespace
266
+
267
+ The Namespace class provides a registry for element classes and handles element detection and conversion.
268
+
269
+ ```js
270
+ import { Namespace } from '@speclynx/apidom-datamodel';
271
+
272
+ const namespace = new Namespace();
273
+
274
+ // Convert JavaScript values to elements
275
+ namespace.toElement('hello'); // => StringElement('hello')
276
+ namespace.toElement(42); // => NumberElement(42)
277
+
278
+ // Register custom element classes
279
+ class CustomElement extends namespace.Element {
280
+ element = 'custom';
281
+ }
282
+ namespace.register('custom', CustomElement);
283
+
284
+ // Get element class by name
285
+ const ElementClass = namespace.getElementClass('custom'); // => CustomElement
286
+ ```
287
+
288
+ ### Extending Namespace
289
+
290
+ You can register custom detection functions:
291
+
292
+ ```js
293
+ import { Namespace, Element } from '@speclynx/apidom-datamodel';
294
+
295
+ class DateElement extends Element {
296
+ element = 'date';
297
+ }
298
+
299
+ const namespace = new Namespace();
300
+ namespace.register('date', DateElement);
301
+ namespace.detect((value) => value instanceof Date, DateElement);
302
+
303
+ namespace.toElement(new Date()); // => DateElement
304
+ ```
305
+
306
+ ### Creating Namespace Plugins
307
+
308
+ It is possible to create plugin modules that define elements for custom namespaces.
309
+ Plugin modules should export a `namespace` function that takes an options object
310
+ containing an existing namespace to which you can add your elements:
311
+
312
+ ```js
313
+ import { Namespace } from '@speclynx/apidom-datamodel';
314
+
315
+ // Define your plugin module (normally done in a separate file)
316
+ const plugin = {
317
+ namespace: (options) => {
318
+ const base = options.base;
319
+ const ArrayElement = base.getElementClass('array');
320
+
321
+ // Register custom elements
322
+ base.register('category', ArrayElement);
323
+
324
+ return base;
325
+ }
326
+ };
327
+
328
+ // Create namespace and load the plugin
329
+ const namespace = new Namespace();
330
+ namespace.use(plugin);
331
+
332
+ // Now 'category' element is available
333
+ const CategoryElement = namespace.getElementClass('category');
334
+ ```
335
+
336
+ Plugins can also define a `load` function for additional initialization:
337
+
338
+ ```js
339
+ const plugin = {
340
+ namespace: (options) => {
341
+ // Register elements
342
+ },
343
+ load: (options) => {
344
+ // Additional initialization after namespace setup
345
+ }
346
+ };
347
+ ```
348
+
349
+ ### Serialization
350
+
351
+ The namespace provides serialization methods:
352
+
353
+ ```js
354
+ import { Namespace, ObjectElement } from '@speclynx/apidom-datamodel';
355
+
356
+ const namespace = new Namespace();
357
+ const obj = new ObjectElement({ key: 'value' });
358
+
359
+ // Serialize to refract format
360
+ const refracted = namespace.toRefract(obj);
361
+ // => { element: 'object', content: [...] }
362
+
363
+ // Deserialize from refract format
364
+ const element = namespace.fromRefract(refracted);
365
+ // => ObjectElement({ key: 'value' })
366
+ ```
367
+
368
+ ---
369
+
370
+ ## JSON Serialiser
371
+
372
+ Direct serialization without namespace:
373
+
374
+ ```js
375
+ import { JSONSerialiser, ObjectElement } from '@speclynx/apidom-datamodel';
376
+
377
+ const serialiser = new JSONSerialiser();
378
+ const obj = new ObjectElement({ a: 1 });
379
+
380
+ // Serialize
381
+ const json = serialiser.serialise(obj);
382
+ // => { element: 'object', content: [...] }
383
+
384
+ // Deserialize
385
+ const element = serialiser.deserialise(json);
386
+ // => ObjectElement({ a: 1 })
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Element Operations
392
+
393
+ ### Cloning
394
+
395
+ Elements can be deeply cloned:
396
+
397
+ ```js
398
+ import { ObjectElement } from '@speclynx/apidom-datamodel';
399
+
400
+ const original = new ObjectElement({ nested: { value: 1 } });
401
+ const cloned = original.clone();
402
+
403
+ cloned.get('nested').set('value', 2);
404
+ original.get('nested').get('value').toValue(); // => 1 (unchanged)
405
+ ```
406
+
407
+ ### Freezing
408
+
409
+ Elements can be frozen for immutability. Freezing also sets up parent references:
410
+
411
+ ```js
412
+ import { ObjectElement } from '@speclynx/apidom-datamodel';
413
+
414
+ const obj = new ObjectElement({ a: 1 });
415
+ obj.freeze();
416
+
417
+ obj.isFrozen; // => true
418
+ obj.get('a').parent === obj.getMember('a'); // => true
419
+ ```
420
+
421
+ ### Equality
422
+
423
+ Elements support deep equality checking:
424
+
425
+ ```js
426
+ import { ArrayElement } from '@speclynx/apidom-datamodel';
427
+
428
+ const arr1 = new ArrayElement([1, 2, 3]);
429
+ const arr2 = new ArrayElement([1, 2, 3]);
430
+
431
+ arr1.equals([1, 2, 3]); // => true
432
+ arr1.equals(arr2.toValue()); // => true
433
+ ```
434
+
435
+ ### Creating References
436
+
437
+ Elements with IDs can create references to themselves:
438
+
439
+ ```js
440
+ import { ObjectElement } from '@speclynx/apidom-datamodel';
441
+
442
+ const obj = new ObjectElement({ data: 'value' });
443
+ obj.id = 'my-object';
444
+
445
+ const ref = obj.toRef();
446
+ ref.toValue(); // => 'my-object'
447
+ ```
448
+
449
+ ---
450
+
451
+ ## ObjectSlice
452
+
453
+ ObjectSlice is a utility for working with filtered object members:
454
+
455
+ ```js
456
+ import { ObjectElement } from '@speclynx/apidom-datamodel';
457
+
458
+ const obj = new ObjectElement({ a: 1, b: 2, c: 3 });
459
+ const slice = obj.filter((value) => value.toValue() > 1);
460
+
461
+ slice.keys(); // => ['b', 'c']
462
+ slice.values(); // => [2, 3]
463
+ ```
464
+
465
+ ---
466
+
467
+ ## KeyValuePair
468
+
469
+ Internal structure used by MemberElement to store key-value pairs:
470
+
471
+ ```js
472
+ import { KeyValuePair, StringElement, NumberElement } from '@speclynx/apidom-datamodel';
473
+
474
+ const pair = new KeyValuePair();
475
+ pair.key = new StringElement('name');
476
+ pair.value = new NumberElement(42);
477
+
478
+ pair.clone(); // creates a deep copy
479
+ ```