@speclynx/apidom-core 1.12.1

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 (105) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/LICENSE +202 -0
  3. package/LICENSES/AFL-3.0.txt +182 -0
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/LICENSES/BSD-3-Clause.txt +26 -0
  6. package/LICENSES/MIT.txt +9 -0
  7. package/NOTICE +65 -0
  8. package/README.md +881 -0
  9. package/dist/apidom-core.browser.js +14724 -0
  10. package/dist/apidom-core.browser.min.js +1 -0
  11. package/package.json +67 -0
  12. package/src/clone/errors/CloneError.cjs +22 -0
  13. package/src/clone/errors/CloneError.mjs +19 -0
  14. package/src/clone/errors/DeepCloneError.cjs +11 -0
  15. package/src/clone/errors/DeepCloneError.mjs +6 -0
  16. package/src/clone/errors/ShallowCloneError.cjs +11 -0
  17. package/src/clone/errors/ShallowCloneError.mjs +6 -0
  18. package/src/clone/index.cjs +155 -0
  19. package/src/clone/index.mjs +146 -0
  20. package/src/elements/Annotation.cjs +23 -0
  21. package/src/elements/Annotation.mjs +20 -0
  22. package/src/elements/Comment.cjs +15 -0
  23. package/src/elements/Comment.mjs +12 -0
  24. package/src/elements/ParseResult.cjs +53 -0
  25. package/src/elements/ParseResult.mjs +50 -0
  26. package/src/elements/SourceMap.cjs +39 -0
  27. package/src/elements/SourceMap.mjs +36 -0
  28. package/src/identity/errors/ElementIdentityError.cjs +22 -0
  29. package/src/identity/errors/ElementIdentityError.mjs +19 -0
  30. package/src/identity/index.cjs +62 -0
  31. package/src/identity/index.mjs +56 -0
  32. package/src/index.cjs +112 -0
  33. package/src/index.mjs +50 -0
  34. package/src/media-types.cjs +21 -0
  35. package/src/media-types.mjs +18 -0
  36. package/src/merge/deepmerge.cjs +151 -0
  37. package/src/merge/deepmerge.mjs +135 -0
  38. package/src/merge/merge-left.cjs +16 -0
  39. package/src/merge/merge-left.mjs +11 -0
  40. package/src/merge/merge-right.cjs +35 -0
  41. package/src/merge/merge-right.mjs +29 -0
  42. package/src/namespace.cjs +42 -0
  43. package/src/namespace.mjs +35 -0
  44. package/src/predicates/helpers.cjs +85 -0
  45. package/src/predicates/helpers.mjs +77 -0
  46. package/src/predicates/index.cjs +208 -0
  47. package/src/predicates/index.mjs +198 -0
  48. package/src/refractor/index.cjs +46 -0
  49. package/src/refractor/index.mjs +40 -0
  50. package/src/refractor/plugins/dispatcher/index.cjs +71 -0
  51. package/src/refractor/plugins/dispatcher/index.mjs +61 -0
  52. package/src/refractor/plugins/element-identity.cjs +31 -0
  53. package/src/refractor/plugins/element-identity.mjs +26 -0
  54. package/src/refractor/plugins/semantic-element-identity.cjs +38 -0
  55. package/src/refractor/plugins/semantic-element-identity.mjs +33 -0
  56. package/src/refractor/registration.cjs +34 -0
  57. package/src/refractor/registration.mjs +19 -0
  58. package/src/refractor/toolbox.cjs +29 -0
  59. package/src/refractor/toolbox.mjs +21 -0
  60. package/src/transcluder/Transcluder.cjs +111 -0
  61. package/src/transcluder/Transcluder.mjs +107 -0
  62. package/src/transcluder/index.cjs +19 -0
  63. package/src/transcluder/index.mjs +13 -0
  64. package/src/transformers/dehydrate.cjs +15 -0
  65. package/src/transformers/dehydrate.mjs +10 -0
  66. package/src/transformers/from.cjs +34 -0
  67. package/src/transformers/from.mjs +29 -0
  68. package/src/transformers/serializers/json.cjs +11 -0
  69. package/src/transformers/serializers/json.mjs +6 -0
  70. package/src/transformers/serializers/value/ast/ephemeral-array.cjs +21 -0
  71. package/src/transformers/serializers/value/ast/ephemeral-array.mjs +17 -0
  72. package/src/transformers/serializers/value/ast/ephemeral-object.cjs +20 -0
  73. package/src/transformers/serializers/value/ast/ephemeral-object.mjs +16 -0
  74. package/src/transformers/serializers/value/index.cjs +85 -0
  75. package/src/transformers/serializers/value/index.mjs +80 -0
  76. package/src/transformers/serializers/value/visitor.cjs +52 -0
  77. package/src/transformers/serializers/value/visitor.mjs +47 -0
  78. package/src/transformers/serializers/yaml-1-2.cjs +90 -0
  79. package/src/transformers/serializers/yaml-1-2.mjs +85 -0
  80. package/src/transformers/sexprs.cjs +34 -0
  81. package/src/transformers/sexprs.mjs +30 -0
  82. package/src/transformers/to-string.cjs +16 -0
  83. package/src/transformers/to-string.mjs +11 -0
  84. package/src/traversal/filter.cjs +18 -0
  85. package/src/traversal/filter.mjs +14 -0
  86. package/src/traversal/find.cjs +19 -0
  87. package/src/traversal/find.mjs +15 -0
  88. package/src/traversal/findAtOffset.cjs +65 -0
  89. package/src/traversal/findAtOffset.mjs +60 -0
  90. package/src/traversal/index.cjs +19 -0
  91. package/src/traversal/index.mjs +7 -0
  92. package/src/traversal/parents.cjs +38 -0
  93. package/src/traversal/parents.mjs +34 -0
  94. package/src/traversal/reject.cjs +15 -0
  95. package/src/traversal/reject.mjs +10 -0
  96. package/src/traversal/some.cjs +15 -0
  97. package/src/traversal/some.mjs +10 -0
  98. package/src/traversal/traverse.cjs +60 -0
  99. package/src/traversal/traverse.mjs +53 -0
  100. package/src/traversal/visitor.cjs +123 -0
  101. package/src/traversal/visitor.mjs +115 -0
  102. package/src/util.cjs +28 -0
  103. package/src/util.mjs +24 -0
  104. package/types/apidom-core.d.ts +806 -0
  105. package/types/minim.d.ts +236 -0
package/README.md ADDED
@@ -0,0 +1,881 @@
1
+ # @speclynx/apidom-core
2
+
3
+ `apidom-core` is a package that contains tools for manipulating the ApiDOM structures.
4
+
5
+ ## Installation
6
+
7
+ You can install this package via [npm CLI](https://docs.npmjs.com/cli) by running the following command:
8
+
9
+ ```sh
10
+ $ npm install @speclynx/apidom-core
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Base namespace
16
+
17
+ Base namespace consists of [four higher order elements](https://github.com/speclynx/apidom/tree/main/packages/apidom-core/src/elements) implemented on top
18
+ of [primitive ones](https://github.com/refractproject/minim/tree/master/lib/primitives).
19
+
20
+ ```js
21
+ import { createNamespace } from '@speclynx/apidom-core';
22
+
23
+ const namespace = createNamespace();
24
+
25
+ const objectElement = new namespace.elements.Object();
26
+ const commentElement = new namespace.elements.Comment();
27
+ ```
28
+
29
+ It's possible to create namespace instances using another namespaces.
30
+
31
+ ```js
32
+ import { createNamespace } from '@speclynx/apidom-core';
33
+ import openApi3_1Namespace from '@speclynx/apidom-ns-openapi-3-1';
34
+
35
+ const namespace = createNamespace(openApi3_1Namespace);
36
+
37
+ const objectElement = new namespace.elements.Object();
38
+ const openApiElement = new namespace.elements.OpenApi3_1();
39
+ ```
40
+
41
+ When namespace instance is created in this way, it will extend the base namespace
42
+ with the namespace provided as an argument.
43
+
44
+ ---
45
+
46
+ ## Predicates
47
+
48
+ This package exposes [predicates](https://github.com/speclynx/apidom/blob/main/packages/apidom-core/src/predicates/index.ts)
49
+ for all primitive elements and all higher order elements that are part of the base namespace.
50
+
51
+ ```js
52
+ import { CommentElement, isCommentElement } from '@speclynx/apidom-core';
53
+
54
+ const commentElement = new CommentElement();
55
+
56
+ isCommentElement(commentElement); // => true
57
+ ```
58
+
59
+ [Predicate helpers](https://github.com/speclynx/apidom/blob/main/packages/apidom-core/src/predicates/helpers.ts)
60
+ helps in building predicates for this and other packages.
61
+
62
+ ```js
63
+ import { createPredicate } from '@speclynx/apidom-core';
64
+
65
+ const isMyElement = createPredicate(
66
+ ({ hasBasicElementProps, isElementType, primitiveEq }) => {
67
+ return (element) =>
68
+ element instanceof MyElement ||
69
+ (hasBasicElementProps(element) && isElementType('myElement', element) && primitiveEq('object', element));
70
+ },
71
+ );
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Transcluder
77
+
78
+ Transclusion is the inclusion of one ApiDOM fragment into another ApiDOM fragment.
79
+ Our [transcluder](https://github.com/speclynx/apidom/tree/main/packages/apidom-core/src/transcluder) does exactly that and is based on mutating algorithm.
80
+
81
+ ```js
82
+ import { transclude, ArrayElement, NumberElement } from '@speclynx/apidom-core';
83
+
84
+ const element = new ArrayElement([1, 2, 3]);
85
+ const search = element.get(1);
86
+ const replace = new NumberElement(4);
87
+
88
+ transclude(search, replace, element); // => ArrayElement<[1, 4, 3]>
89
+ ```
90
+
91
+ When multiple transclusions are going to be performed use [Transcluder stamp](https://github.com/speclynx/apidom/blob/main/packages/apidom-core/src/transcluder/Transcluder.ts)
92
+ for optimal performance.
93
+
94
+ ```js
95
+ import { Transcluder, ArrayElement, NumberElement } from '@speclynx/apidom-core';
96
+
97
+ const element = new ArrayElement([1, 2, 3]);
98
+ const search = element.get(1);
99
+ const replace = new NumberElement(4);
100
+ const transcluder = Transcluder({ element });
101
+
102
+ transcluder.transclude(search, replace); // => ArrayElement<[1, 4, 3]>
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Shallow merging
108
+
109
+ `mergeRight` and `mergeLeft` functions merge members of two or more ObjectElements shallowly
110
+ and handles shallow merging of ArrayElements as well.
111
+
112
+ ### API
113
+
114
+ #### mergeRight(target, source, [options])
115
+
116
+ Merges two ApiDOM elements target and source shallowly, returning a new merged ApiDOM element with the elements
117
+ from both target and source. If an element at the same key is present for both target and source,
118
+ the value from source will appear in the result. Merging creates a new ApiDOM element,
119
+ so that neither target nor source is modified (operation is immutable).
120
+
121
+ ```js
122
+ import { mergeRight, ObjectElement } from '@speclynx/apidom-core';
123
+
124
+ const x = new ObjectElement({
125
+ foo: { bar: 3 },
126
+ });
127
+
128
+ const y = new ObjectElement({
129
+ foo: { baz: 4 },
130
+ quux: 5,
131
+ });
132
+
133
+ const output = mergeRight(x, y);
134
+ // =>
135
+ // ObjectElement({
136
+ // foo: ObjectElement({
137
+ // baz: 4,
138
+ // }),
139
+ // quux: 5,
140
+ // })
141
+ ```
142
+
143
+ #### mergeRight.all([element1, element2, ...], [options])
144
+
145
+ Merges shallowly any number of ApiDOM elements into a single ApiDOM element.
146
+
147
+ ```js
148
+ import { mergeRight, ObjectElement } from '@speclynx/apidom-core';
149
+
150
+ const foobar = new ObjectElement({ foo: { bar: 3 } });
151
+ const foobaz = new ObjectElement({ foo: { baz: 4 } });
152
+ const bar = new ObjectElement({ bar: 'yay!' });
153
+
154
+ const output = mergeRight.all([ foobar, foobaz, bar ]);
155
+ // => ObjectElement({ foo: { baz: 4 }, bar: 'yay!' })
156
+ ```
157
+
158
+ #### mergeLeft(source, target, [options])
159
+
160
+ Merges two ApiDOM elements source and target shallowly, returning a new merged ApiDOM element with the elements
161
+ from both target and source. If an element at the same key is present for both target and source,
162
+ the value from source will appear in the result. Merging creates a new ApiDOM element,
163
+ so that neither target nor source is modified (operation is immutable).
164
+
165
+ ```js
166
+ import { mergeLeft, ObjectElement } from '@speclynx/apidom-core';
167
+
168
+ const x = new ObjectElement({
169
+ foo: { bar: 3 },
170
+ });
171
+
172
+ const y = new ObjectElement({
173
+ foo: { baz: 4 },
174
+ quux: 5,
175
+ });
176
+
177
+ const output = mergeLeft(x, y);
178
+ // =>
179
+ // ObjectElement({
180
+ // foo: ObjectElement({
181
+ // bar: 3,
182
+ // }),
183
+ // quux: 5,
184
+ // })
185
+ ```
186
+
187
+ #### mergeLeft.all([element1, element2, ...], [options])
188
+
189
+ Merges shallowly any number of ApiDOM elements into a single ApiDOM element.
190
+
191
+ ```js
192
+ import { mergeLeft, ObjectElement } from '@speclynx/apidom-core';
193
+
194
+ const foobar = new ObjectElement({ foo: { bar: 3 } });
195
+ const foobaz = new ObjectElement({ foo: { baz: 4 } });
196
+ const bar = new ObjectElement({ bar: 'yay!' });
197
+
198
+ const output = mergeLeft.all([ foobar, foobaz, bar ]);
199
+ // => ObjectElement({ foo: { baz: 3 }, bar: 'yay!' })
200
+ ```
201
+
202
+ ### Shallow merge Options
203
+
204
+ `mergeRight` and `mergeLeft` take the same options as [deepmerge](#deepmerge-options), except for `customMerge` and `clone`.
205
+
206
+ ## Deep merging
207
+
208
+ `deepmerge` functions merged members of two or more ObjectElements deeply
209
+ and handles deep merging of ArrayElements as well. This deep merge implementation
210
+ is a functional equivalent of [deepmerge](https://www.npmjs.com/package/deepmerge)
211
+ that works equivalently on ApiDOM structures.
212
+
213
+ ### API
214
+
215
+ #### deepmerge(target, source, [options])
216
+
217
+ Merges two ApiDOM elements target and source deeply, returning a new merged ApiDOM element with the elements
218
+ from both target and source. If an element at the same key is present for both target and source,
219
+ the value from source will appear in the result. Merging creates a new ApiDOM element,
220
+ so that neither target nor source is modified (operation is immutable).
221
+
222
+ ```js
223
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
224
+
225
+ const x = new ObjectElement({
226
+ foo: { bar: 3 },
227
+ array: [
228
+ {
229
+ does: 'work',
230
+ too: [1, 2, 3],
231
+ },
232
+ ],
233
+ });
234
+
235
+ const y = new ObjectElement({
236
+ foo: { baz: 4 },
237
+ quux: 5,
238
+ array: [
239
+ {
240
+ does: 'work',
241
+ too: [4, 5, 6],
242
+ },
243
+ {
244
+ really: 'yes',
245
+ },
246
+ ],
247
+ });
248
+
249
+ const output = deepmerge(x, y);
250
+ // =>
251
+ // ObjectElement({
252
+ // foo: ObjectElement({
253
+ // bar: 3,
254
+ // baz: 4,
255
+ // }),
256
+ // array: ArrayElement([
257
+ // ObjectElement({
258
+ // does: 'work',
259
+ // too: [1, 2, 3],
260
+ // }),
261
+ // ObjectElement({
262
+ // does: 'work',
263
+ // too: [4, 5, 6],
264
+ // }),
265
+ // ObjectElement({
266
+ // really: 'yes',
267
+ // }),
268
+ // ]),
269
+ // quux: 5,
270
+ // })
271
+ ```
272
+
273
+ #### deepmerge.all([element1, element2, ...], [options])
274
+
275
+ Merges deeply any number of ApiDOM elements into a single ApiDOM element.
276
+
277
+ ```js
278
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
279
+
280
+ const foobar = new ObjectElement({ foo: { bar: 3 } });
281
+ const foobaz = new ObjectElement({ foo: { baz: 4 } });
282
+ const bar = new ObjectElement({ bar: 'yay!' });
283
+
284
+ const output = deepmerge.all([ foobar, foobaz, bar ]);
285
+ // => ObjectElement({ foo: { bar: 3, baz: 4 }, bar: 'yay!' })
286
+ ```
287
+
288
+ ### Deepmerge Options
289
+
290
+ #### arrayElementMerge
291
+
292
+ There are multiple ways to merge two ArrayElements, below are a few examples, but you can also create your own custom function.
293
+
294
+ Your `arrayElementMerge` function will be called with three arguments: a `target` ArrayElement, the `source` ArrayElement,
295
+ and an `options` object.
296
+
297
+ ```js
298
+ import { deepmerge, ArrayElement } from '@speclynx/apidom-core';
299
+
300
+ const arrayElementMerge = (destination, source, options) => source;
301
+
302
+ const target = new ArrayElement([1, 2, 3]);
303
+ const source = new ArrayElement([3, 2, 1]);
304
+ const output = deepmerge(target, source, { arrayElementMerge });
305
+ // => ArrayElement([3, 2, 1]);
306
+ ```
307
+
308
+ #### objectElementMerge
309
+
310
+ There are multiple ways to merge two ObjectElements, below are a few examples, but you can also create your own custom function.
311
+
312
+ Your `objectElementMerge` function will be called with three arguments: a `target` ObjectElement, the `source` ObjectElement,
313
+ and an `options` object.
314
+
315
+ ```js
316
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
317
+
318
+ const objectElementMerge = (destination, source, options) => source;
319
+
320
+ const target = new ObjectElement({a: 1, b: 2});
321
+ const source = new ObjectElement({c: 3, d: 4});
322
+ const output = deepmerge(target ,source, { objectElementMerge });
323
+ // => ObjectElement({c: 3, d: 4});
324
+ ```
325
+
326
+ #### isMergeableElement
327
+
328
+ By default, deepmerge clones every member from ObjectElement and ArrayElement.
329
+ You may not want this, if your ObjectElements are of special types,
330
+ and you want to copy the whole ObjectElement instead of just copying its member.
331
+
332
+ You can accomplish this by passing in a function for the `isMergeableElement` option.
333
+
334
+ ```js
335
+ import { deepmerge, ObjectElement, isObjectElement } from '@speclynx/apidom-core';
336
+
337
+ class CustomObjectElement extends ObjectElement {
338
+ element = 'custom';
339
+ }
340
+ const instantiatedCustomObjectElement = new CustomObjectElement({ special: 'oh yeah' });
341
+
342
+ const target = new ObjectElement({
343
+ someProperty: {
344
+ cool: 'oh for sure',
345
+ },
346
+ });
347
+ const source = new ObjectElement({
348
+ someProperty: instantiatedCustomObjectElement,
349
+ });
350
+ const isMergeableElement = (element: Element) => isObjectElement(element) && !(element instanceof CustomObjectElement);
351
+
352
+ const output = deepmerge(target, source, {
353
+ isMergeableElement,
354
+ });
355
+ // output.get('someProperty').get('cool'); // => undefined
356
+ // output.get('someProperty').get('special'); // => 'oh yeah'
357
+ // output.get('someProperty') instanceof CustomObjectElement // => true
358
+ ```
359
+
360
+ #### customMerge
361
+
362
+ Specifies a function which can be used to override the default merge behavior for a member, based on the key name.
363
+ The `customMerge` function will be passed the key for each member, and should return the function which should
364
+ be used to merge the values for that member.
365
+ It may also return undefined, in which case the default merge behaviour will be used.
366
+
367
+ ```js
368
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
369
+
370
+ const alex = new ObjectElement({
371
+ name: {
372
+ first: 'Alex',
373
+ last: 'Alexson'
374
+ },
375
+ pets: ['Cat', 'Parrot']
376
+ });
377
+ const tony = new ObjectElement({
378
+ name: {
379
+ first: 'Tony',
380
+ last: 'Tonison'
381
+ },
382
+ pets: ['Dog']
383
+ });
384
+
385
+ const mergeNames = (nameA: ObjectElement, nameB: ObjectElement) =>
386
+ new StringElement(`${toValue(nameA.get('first'))} and ${toValue(nameB.get('first'))}`);
387
+ const customMerge = (key: Element) => (toValue(key) === 'name' ? mergeNames : undefined);
388
+
389
+ const output = deepmerge(alex, tony, { customMerge });
390
+ // output.get('name'); // => StrignElement('Alex and Tony')
391
+ // output.get('pets'); // => ArrayElement(['Cat', 'Parrot', 'Dog'])
392
+ ```
393
+
394
+ #### customMetaMerge
395
+
396
+ Specifies a function which can be used to override the default metadata merge behavior.
397
+ The `customMetaMerge` function will be passed target and source metadata. If not specified,
398
+ the default behavior is to deep copy metadata from target to new merged element.
399
+
400
+ ```js
401
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
402
+
403
+ const alex = new ObjectElement({ name: { first: 'Alex' } }, { metaKey: true });
404
+ const tony = new ObjectElement({ name: { first: 'Tony' } }, { metaKey: false });
405
+
406
+ const customMetaMerge = (targetMeta, sourceMeta) => deepmerge(targetMeta, sourceMeta);
407
+
408
+ const output = deepmerge(alex, tony, { customMetaMerge });
409
+ // output.meta.get('metaKey') // => BooleanElement(false)
410
+ ```
411
+
412
+ #### customAttributesMerge
413
+
414
+ Specifies a function which can be used to override the default attributes merge behavior.
415
+ The `customAttributesMerge` function will be passed target and source attributes. If not specified,
416
+ the default behavior is to deep copy attributes from target to new merged element.
417
+
418
+ ```js
419
+ import { deepmerge, ObjectElement } from '@speclynx/apidom-core';
420
+
421
+ const alex = new ObjectElement({ name: { first: 'Alex' } }, undefined, { attributeKey: true });
422
+ const tony = new ObjectElement({ name: { first: 'Tony' } }, undefined, { attributeKey: false });
423
+
424
+ const customAttributesMerge = (targetMeta, sourceMeta) => deepmerge(targetMeta, sourceMeta);
425
+
426
+ const output = deepmerge(alex, tony, { customAttributesMerge });
427
+ // output.attributs.get('attributeKey') // => BooleanElement(false)
428
+ ```
429
+
430
+ #### clone
431
+
432
+ Defaults to `true`.
433
+
434
+ If `clone` is false then child elements will be copied directly instead of being cloned.
435
+
436
+ ---
437
+
438
+ ## Refractors
439
+
440
+ Refractor is a special layer inside the base namespace that can transform JavaScript structures
441
+ into generic ApiDOM structures built from elements of this base namespace.
442
+
443
+ **Refracting JavaScript structures**:
444
+
445
+ ```js
446
+ import { ObjectElement } from '@speclynx/apidom-core';
447
+
448
+ const object = {
449
+ title: 'my title',
450
+ description: 'my description',
451
+ version: '0.1.0',
452
+ };
453
+
454
+ ObjectElement.refract(object); // => ObjectElement({ title, description, version })
455
+ ```
456
+
457
+ ```js
458
+ import { CommentElement } from '@speclynx/apidom-core';
459
+
460
+ const comment = 'this is comment';
461
+
462
+ CommentElement.refract(comment); // => CommentElement('this is comment')
463
+ ```
464
+
465
+ ### Refractor plugins
466
+
467
+ Refractors can accept plugins as a second argument of refract static method.
468
+
469
+ ```js
470
+ import { ObjectElement, StringElement } from '@speclynx/apidom-core';
471
+
472
+ const object = { a: 'b' };
473
+
474
+ const plugin = ({ predicates, namespace }) => ({
475
+ name: 'plugin',
476
+ pre() {
477
+ console.dir('runs before traversal');
478
+ },
479
+ visitor: {
480
+ ObjectElement(objectElement) {
481
+ objectElement.getMember('a').value = new StringElement('c');
482
+ },
483
+ },
484
+ post() {
485
+ console.dir('runs after traversal');
486
+ },
487
+ });
488
+
489
+ ObjectElement.refract(object, { plugins: [plugin] }); // => ObjectElement({ a = 'c' })
490
+ ```
491
+ You can define as many plugins as needed to enhance the resulting namespaced ApiDOM structure.
492
+ If multiple plugins with the same visitor method are defined, they run in parallel (just like in Babel).
493
+
494
+ #### Element identity plugin
495
+
496
+ `apidom` package comes with `refractorPluginElementIdentity`. When used, this plugin will
497
+ assign unique ID to all elements in ApiDOM tree.
498
+
499
+ ```js
500
+ import { refractorPluginElementIdentity, ObjectElement } from '@speclynx/apidom-core';
501
+
502
+ const objectElement = ObjectElement.refract({ a: 'b' }, {
503
+ plugins: [
504
+ refractorPluginElementIdentity(),
505
+ ]
506
+ });
507
+
508
+ objectElement.id; // 8RaWF9
509
+ objectElement.getMember('a').key.id; // NdHHV7
510
+ objectElement.getMember('a').value.id; // rFGVFP
511
+ ```
512
+
513
+ You can configure the plugin to generate unique IDs in the specific length:
514
+
515
+ ```js
516
+ import { refractorPluginElementIdentity, ObjectElement } from '@speclynx/apidom-core';
517
+
518
+ const objectElement = ObjectElement.refract({ a: 'b' }, {
519
+ plugins: [
520
+ refractorPluginElementIdentity({ length: 36}),
521
+ ]
522
+ });
523
+
524
+ objectElement.id; // OnReGGrO7fMd9ztacvGfwGbOdGKuOFLiQQ1W
525
+ objectElement.getMember('a').key.id; // BN6rHsmqI56SMQ1elshtbgRVECtEWNYS9lmd
526
+ objectElement.getMember('a').value.id; // Ki4tWmf9xw9Lwb8MxkXJq1uONmJrmhXifmsI
527
+ ```
528
+
529
+ #### Semantic element identity plugin
530
+
531
+ `apidom` package comes with `refractorPluginSemanticElementIdentity`. When used, this plugin will
532
+ assign unique ID to all non-primitive elements in ApiDOM tree. Primitive elements include
533
+ `ObjectElement`, `ArrayElement`, `StringElement`, `BooleanElement`, `NullElement` and `NumberElement`.
534
+
535
+ ```js
536
+ import { refractorPluginSemanticElementIdentity, ObjectElement } from '@speclynx/apidom-core';
537
+ import { InfoElement } from '@speclynx/apidom-ns-openapi-3-1';
538
+
539
+ const infoElement = InfoElement.refract({ title: 'title' });
540
+ const objectElement = ObjectElement.refract({ a: 'b', info: infoElement }, {
541
+ plugins: [
542
+ refractorPluginSemanticElementIdentity(),
543
+ ]
544
+ });
545
+
546
+ objectElement.id; // ''
547
+ objectElement.getMember('a').key.id; // ''
548
+ objectElement.getMember('a').value.id; // ''
549
+ objectElement.getMember('info').key.id; // ''
550
+ objectElement.getMember('info').value.id; // '8RaWF9'
551
+ ```
552
+
553
+ You can configure the plugin to generate unique IDs in the specific length:
554
+
555
+ ```js
556
+ import { refractorPluginSemanticElementIdentity, ObjectElement } from '@speclynx/apidom-core';
557
+ import { InfoElement } from '@speclynx/apidom-ns-openapi-3-1';
558
+
559
+ const infoElement = InfoElement.refract({ title: 'title' });
560
+ const objectElement = ObjectElement.refract({ a: 'b', info: infoElement }, {
561
+ plugins: [
562
+ refractorPluginSemanticElementIdentity({ length: 36 }),
563
+ ]
564
+ });
565
+
566
+ objectElement.id; // ''
567
+ objectElement.getMember('a').key.id; // ''
568
+ objectElement.getMember('a').value.id; // ''
569
+ objectElement.getMember('info').key.id; // ''
570
+ objectElement.getMember('info').value.id; // 'OnReGGrO7fMd9ztacvGfwGbOdGKuOFLiQQ1W'
571
+ ```
572
+
573
+ ---
574
+
575
+ ## Traversal
576
+
577
+ `apidom` comes with its own traversal algorithm along with couple of convenient abstractions on top of it.
578
+
579
+ ### visit
580
+
581
+ [visit](https://github.com/speclynx/apidom/blob/main/packages/apidom-core/src/traversal/visitor.ts#L104-L103) will walk through an AST using a depth first traversal, calling
582
+ the visitor's enter function at each node in the traversal, and calling the
583
+ leave function after visiting that node and all of its child nodes.
584
+
585
+ By returning different values from the enter and leave functions, the
586
+ behavior of the visitor can be altered, including skipping over a sub-tree of
587
+ the ApiDOM (by returning false), editing the ApiDOM by returning a value or null
588
+ to remove the value, or to stop the whole traversal by returning [BREAK](https://github.com/speclynx/apidom/blob/main/packages/apidom-core/src/index.ts#L52).
589
+
590
+ When using `visit` to edit an ApiDOM, the original ApiDOM will not be modified, and
591
+ a new version of the ApiDOM with the changes applied will be returned from the
592
+ visit function.
593
+
594
+ ```js
595
+ import { visit, ObjectElement, NumberElement } from '@speclynx/apidom-core';
596
+
597
+ const visitor = {
598
+ NumberElement(numberElement) {
599
+ return new NumberElement(2);
600
+ },
601
+ };
602
+ const element = new ObjectElement({ a: 1 });
603
+
604
+ const newElement = visit(element, visitor); // => ObjectElement<{a: 2}>
605
+ ```
606
+
607
+ This function originally comes from [@speclynx/apidom-ast package](https://github.com/speclynx/apidom/blob/main/packages/apidom-ast/src/visitor.ts)
608
+ and is originally designed to work with [CST](https://en.wikipedia.org/wiki/Parse_tree). `apidom` package
609
+ imports it, specializes it to work with ApiDOM and re-export it.
610
+
611
+ All following algorithms are based on `visit` function.
612
+
613
+ ### filter
614
+
615
+ Finds all elements matching the predicate.
616
+
617
+ ```js
618
+ import { ObjectElement, filter, isNumberElement } from '@speclynx/apidom-core'
619
+
620
+ const objElement = new ObjectElement({ a: 'b', c: 2 });
621
+
622
+ filter(isNumberElement, objElement); // => ArraySlice<[NumberElement<2>]>
623
+ ```
624
+
625
+ ### find
626
+
627
+ Find first element that satisfies the provided predicate.
628
+
629
+ ```js
630
+ import { ObjectElement, find, isMemberElement } from '@speclynx/apidom-core'
631
+
632
+ const objElement = new ObjectElement({ a: 'b', c: 2 });
633
+
634
+ find(isNumberElement, objElement); // => NumberElement<2>
635
+ ```
636
+
637
+ ### findAtOffset
638
+
639
+ ApiDOM nodes can be associated with source maps. This function finds the most inner node at the given offset.
640
+ If includeRightBound is set, also finds nodes that end at the given offset.
641
+
642
+ ```js
643
+ import { findAtOffset } from '@speclynx/apidom-core'
644
+
645
+ findAtOffset(3, elementWithSourceMaps); // => returns most inner node at offset 3
646
+ ```
647
+
648
+ ### reject
649
+
650
+ Complement of [filter](#filter).
651
+
652
+ ```js
653
+ import { ArrayElement, reject, isNumberElement } from '@speclynx/apidom-core'
654
+
655
+ const arrayElement = new ArrayElement([1, 'a']);
656
+
657
+ reject(isNumberElement, arrayElement); // => ArraySlice<[StringElement<'a'>]>
658
+ ```
659
+
660
+ ### some
661
+
662
+ Tests whether at least one element passes the predicate.
663
+
664
+ ```js
665
+ import { ArrayElement, some, isNumberElement } from '@speclynx/apidom-core'
666
+
667
+ const arrayElement = new ArrayElement([1, 'a']);
668
+
669
+ some(isNumberElement, arrayElement); // => true
670
+ ```
671
+
672
+ ### traverse
673
+
674
+ Executes the callback on this element and all descendants.
675
+
676
+ ```js
677
+ import { ArrayElement, traverse } from '@speclynx/apidom-core'
678
+
679
+ const arrayElement = new ArrayElement([1, 'a']);
680
+
681
+ traverse(console.dir, arrayElement); // => prints ArrayElement, NumberElement, StringElement in this order
682
+ ```
683
+
684
+ The execution of the callback can be controlled further by providing a predicate.
685
+
686
+ ```js
687
+ import { ArrayElement, traverse, isNumberElement } from '@speclynx/apidom-core'
688
+
689
+ const arrayElement = new ArrayElement([1, 'a']);
690
+
691
+ traverse({ callback: console.dir, predicate: isNumberElement }, arrayElement); // => prints NumberElement<1>
692
+ ```
693
+
694
+ ### parents
695
+
696
+ Computes upwards edges from every child to its parent.
697
+
698
+ #### ObjectElement example
699
+
700
+ ```js
701
+ import { parents, ObjectElement } from '@speclynx/apidom-core';
702
+
703
+ const objectElement = new ObjectElement({ key: 'value' });
704
+ const memberElement = objectElement.getMember('key');
705
+ const { key: keyElement, value: valueElement } = memberElement;
706
+
707
+ const parentEdges = parents(objectElement); // => WeakMap<ChildElement, ParentElement>
708
+
709
+ parentEdges.get(memberElement) === objectElement; // => true
710
+ parentEdges.get(keyElement) === memberElement; // => true
711
+ parentEdges.get(valueElement) === memberElement; // => true
712
+ ```
713
+
714
+ #### ArrayElement example
715
+
716
+ ```js
717
+ import { parents, ArrayElement, StringElement } from '@speclynx/apidom-core';
718
+
719
+ const itemElement1 = new StringElement('item1');
720
+ const itemElement2 = new StringElement('item2');
721
+ const arrayElement = new ArrayElement([itemElement1, itemElement2]);
722
+
723
+ const parentEdges = parents(arrayElement); // => WeakMap<ChildElement, ParentElement>
724
+
725
+ parentEdges.get(itemElement1) === arrayElement; // => true
726
+ parentEdges.get(itemElement2) === arrayElement; // => true
727
+ ```
728
+
729
+ ---
730
+
731
+ ## Transformers
732
+
733
+ Following functions transforms ApiDOM between its various forms. All transformers (except `toValue`) can accept
734
+ ApiDOM namespace instance as a second argument.
735
+
736
+ ### from
737
+
738
+ Transforms data to an Element from a particular namespace.
739
+
740
+ From a [refracted string](https://github.com/refractproject/refract-spec) form:
741
+
742
+ ```js
743
+ import { from } from '@speclynx/apidom-core';
744
+
745
+ const refractedString = '{"element":"number","content":1}';
746
+
747
+ from(refractedString); // => NumberElement<1>
748
+ ```
749
+
750
+ From a [refracted](https://github.com/refractproject/refract-spec) form:
751
+
752
+ ```js
753
+ import { from } from '@speclynx/apidom-core';
754
+
755
+ const refracted = { element: 'number', content: 1 };
756
+
757
+ from(refracted); // => NumberElement<1>
758
+ ```
759
+
760
+ From a JavaScript form:
761
+
762
+ ```js
763
+ import { from } from '@speclynx/apidom-core';
764
+
765
+ const javascriptForm = 1;
766
+
767
+ from(javascriptForm); // => NumberElement<1>
768
+ ```
769
+
770
+ ### toValue
771
+
772
+ Transforms the ApiDOM into JavaScript POJO. This POJO would be the result of interpreting the ApiDOM
773
+ into JavaScript structure. This function can handle cycles in ApiDOM structure.
774
+
775
+ ```js
776
+ import { toValue, ObjectElement } from '@speclynx/apidom-core';
777
+
778
+ const objElement = new ObjectElement({ a: 'b' });
779
+
780
+ toValue(objElement); // => { a: 'b' }
781
+ ```
782
+
783
+ ### toJSON
784
+
785
+ Transforms the ApiDOM into JSON string.
786
+
787
+ ```js
788
+ import { toJSON, ObjectElement } from '@speclynx/apidom-core';
789
+
790
+ const objElement = new ObjectElement({ a: 'b' });
791
+
792
+ toJSON(objElement); // => '{"a":"b"}'
793
+ ```
794
+
795
+ ### toYAML
796
+
797
+ Transforms the ApiDOM into JSON string.
798
+
799
+ ```js
800
+ import { toYAML, ObjectElement } from '@speclynx/apidom-core';
801
+
802
+ const objElement = new ObjectElement({ a: 'b' });
803
+
804
+ toYAML(objElement);
805
+ /**
806
+ * %YAML 1.2
807
+ * ---
808
+ *
809
+ * "a": "b"
810
+ */
811
+ ```
812
+
813
+ ### dehydrate
814
+
815
+ Creates a [refract representation](https://github.com/refractproject/refract-spec) of the an Element.
816
+
817
+ ```js
818
+ import { dehyrate, NumberElement } from '@speclynx/apidom-core';
819
+
820
+ const numberElement = new NumberElement(1);
821
+
822
+ dehyrate(numberElement); // => { element: 'number', content: 1 }
823
+ ```
824
+
825
+ ### S-Expression
826
+
827
+ Transforms ApiDOM into [symbolic expression](https://en.wikipedia.org/wiki/S-expression).
828
+
829
+ ```js
830
+ import { sexprs, ObjectElement } from '@speclynx/apidom-core';
831
+
832
+ const objectElement = new ObjectElement({ a: 1 });
833
+
834
+ sexprs(objectElement);
835
+ // =>
836
+ // (ObjectElement
837
+ // (MemberElement
838
+ // (StringElement)
839
+ // (NumberElement)))
840
+
841
+
842
+ ```
843
+
844
+ ### toString
845
+
846
+ Create a [refracted string](https://github.com/refractproject/refract-spec) representation of an Element.
847
+
848
+ ```js
849
+ import { toString, NumberElement } from '@speclynx/apidom-core';
850
+
851
+ const numberElement = new NumberElement(1);
852
+
853
+ toString(numberElement); // => '{"element":"number","content":1}'
854
+ ```
855
+
856
+ ## Cloning
857
+
858
+ Following functions provide mechanism for creating shallow and deep copies of ApiDOM elements.
859
+
860
+ ### Shallow cloning
861
+
862
+ Creates shallow clone of ApiDOM element.
863
+
864
+ ```js
865
+ import { cloneShallow, ObjectElement } from '@speclynx/apidom-core';
866
+
867
+ const objectElement = new ObjectElement({ a: 'b' });
868
+ const objectElementShallowClone = cloneShallow(objectElement);
869
+ ```
870
+
871
+ ### Deep cloning
872
+
873
+ Creates deep clone of ApiDOM Element.
874
+
875
+ ```js
876
+ import { cloneDeep, ObjectElement } from '@speclynx/apidom-core';
877
+
878
+ const objectElement = new ObjectElement({ a: 'b' });
879
+ const objectElementDeepClone = cloneDeep(objectElement);
880
+ ```
881
+