@speclynx/apidom-core 4.0.3 → 4.0.4

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.
@@ -1,102 +0,0 @@
1
- import { Element } from '@speclynx/apidom-datamodel';
2
- import {
3
- traverse,
4
- traverseAsync,
5
- mergeVisitors,
6
- mergeVisitorsAsync,
7
- } from '@speclynx/apidom-traverse';
8
- import { mergeDeepRight, propOr } from 'ramda';
9
- import { invokeArgs } from 'ramda-adjunct';
10
-
11
- import createToolbox from '../../toolbox.ts';
12
-
13
- /**
14
- * @public
15
- */
16
- export interface DispatchPluginsOptions {
17
- toolboxCreator: typeof createToolbox;
18
- visitorOptions: {
19
- exposeEdits: boolean;
20
- };
21
- }
22
-
23
- /**
24
- * @public
25
- */
26
- export interface DispatchPluginsSync {
27
- <T extends Element, U extends Element = Element>(
28
- element: T,
29
- plugins: ((toolbox: any) => object)[],
30
- options?: Record<string, unknown>,
31
- ): U;
32
- [key: symbol]: DispatchPluginsAsync;
33
- }
34
-
35
- /**
36
- * @public
37
- */
38
- export interface DispatchPluginsAsync {
39
- <T extends Element, U extends Element = Element>(
40
- element: T,
41
- plugins: ((toolbox: any) => object)[],
42
- options?: Record<string, unknown>,
43
- ): Promise<U>;
44
- }
45
-
46
- const defaultDispatchPluginsOptions: DispatchPluginsOptions = {
47
- toolboxCreator: createToolbox,
48
- visitorOptions: {
49
- exposeEdits: true,
50
- },
51
- };
52
-
53
- /**
54
- * @public
55
- */
56
- export const dispatchPluginsSync: DispatchPluginsSync = ((element, plugins, options = {}) => {
57
- if (plugins.length === 0) return element;
58
-
59
- const mergedOptions = mergeDeepRight(
60
- defaultDispatchPluginsOptions,
61
- options,
62
- ) as DispatchPluginsOptions;
63
- const { toolboxCreator, visitorOptions } = mergedOptions;
64
- const toolbox = toolboxCreator();
65
- const pluginsSpecs = plugins.map((plugin) => plugin(toolbox));
66
- const mergedPluginsVisitor = mergeVisitors(
67
- pluginsSpecs.map(propOr({}, 'visitor')) as object[],
68
- visitorOptions,
69
- );
70
-
71
- pluginsSpecs.forEach(invokeArgs(['pre'], []));
72
- const newElement = traverse(element, mergedPluginsVisitor);
73
- pluginsSpecs.forEach(invokeArgs(['post'], []));
74
- return newElement;
75
- }) as DispatchPluginsSync;
76
-
77
- export const dispatchPluginsAsync: DispatchPluginsAsync = (async (
78
- element,
79
- plugins,
80
- options = {},
81
- ) => {
82
- if (plugins.length === 0) return element;
83
-
84
- const mergedOptions = mergeDeepRight(
85
- defaultDispatchPluginsOptions,
86
- options,
87
- ) as DispatchPluginsOptions;
88
- const { toolboxCreator, visitorOptions } = mergedOptions;
89
- const toolbox = toolboxCreator();
90
- const pluginsSpecs = plugins.map((plugin) => plugin(toolbox));
91
- const mergedPluginsVisitor = mergeVisitorsAsync(
92
- pluginsSpecs.map(propOr({}, 'visitor')) as object[],
93
- visitorOptions,
94
- );
95
-
96
- await Promise.allSettled(pluginsSpecs.map(invokeArgs(['pre'], [])));
97
- const newElement = await traverseAsync(element, mergedPluginsVisitor);
98
- await Promise.allSettled(pluginsSpecs.map(invokeArgs(['post'], [])));
99
- return newElement;
100
- }) as DispatchPluginsAsync;
101
-
102
- dispatchPluginsSync[Symbol.for('nodejs.util.promisify.custom')] = dispatchPluginsAsync;
@@ -1,31 +0,0 @@
1
- import { Element } from '@speclynx/apidom-datamodel';
2
- import { Path } from '@speclynx/apidom-traverse';
3
-
4
- import { IdentityManager } from '../../identity/index.ts';
5
-
6
- /**
7
- * Plugin for decorating every element in ApiDOM tree with UUID.
8
- * @public
9
- */
10
-
11
- const plugin =
12
- ({ length = 6 } = {}) =>
13
- () => {
14
- let identityManager: IdentityManager | null;
15
-
16
- return {
17
- pre() {
18
- identityManager = new IdentityManager({ length });
19
- },
20
- visitor: {
21
- enter(path: Path<Element>) {
22
- path.node.id = identityManager!.identify(path.node);
23
- },
24
- },
25
- post() {
26
- identityManager = null;
27
- },
28
- };
29
- };
30
-
31
- export default plugin;
@@ -1,32 +0,0 @@
1
- import { Element, isPrimitiveElement } from '@speclynx/apidom-datamodel';
2
- import { Path } from '@speclynx/apidom-traverse';
3
-
4
- import { IdentityManager } from '../../identity/index.ts';
5
-
6
- /**
7
- * Plugin for decorating every semantic element in ApiDOM tree with UUID.
8
- * @public
9
- */
10
- const plugin =
11
- ({ length = 6 } = {}) =>
12
- () => {
13
- let identityManager: IdentityManager | null;
14
-
15
- return {
16
- pre() {
17
- identityManager = new IdentityManager({ length });
18
- },
19
- visitor: {
20
- enter(path: Path<Element>) {
21
- if (!isPrimitiveElement(path.node)) {
22
- path.node.id = identityManager!.identify(path.node);
23
- }
24
- },
25
- },
26
- post() {
27
- identityManager = null;
28
- },
29
- };
30
- };
31
-
32
- export default plugin;
@@ -1,88 +0,0 @@
1
- import {
2
- Namespace,
3
- isElement,
4
- isStringElement,
5
- isNumberElement,
6
- isNullElement,
7
- isBooleanElement,
8
- isArrayElement,
9
- isObjectElement,
10
- isMemberElement,
11
- isPrimitiveElement,
12
- isLinkElement,
13
- isRefElement,
14
- isAnnotationElement,
15
- isCommentElement,
16
- isParseResultElement,
17
- isSourceMapElement,
18
- hasElementStyle,
19
- hasElementSourceMap,
20
- includesSymbols,
21
- includesClasses,
22
- } from '@speclynx/apidom-datamodel';
23
-
24
- import defaultNamespace from '../namespace.ts';
25
-
26
- /**
27
- * @public
28
- */
29
- export interface Predicates {
30
- isElement: typeof isElement;
31
- isStringElement: typeof isStringElement;
32
- isNumberElement: typeof isNumberElement;
33
- isNullElement: typeof isNullElement;
34
- isBooleanElement: typeof isBooleanElement;
35
- isArrayElement: typeof isArrayElement;
36
- isObjectElement: typeof isObjectElement;
37
- isMemberElement: typeof isMemberElement;
38
- isPrimitiveElement: typeof isPrimitiveElement;
39
- isLinkElement: typeof isLinkElement;
40
- isRefElement: typeof isRefElement;
41
- isAnnotationElement: typeof isAnnotationElement;
42
- isCommentElement: typeof isCommentElement;
43
- isParseResultElement: typeof isParseResultElement;
44
- isSourceMapElement: typeof isSourceMapElement;
45
- hasElementStyle: typeof hasElementStyle;
46
- hasElementSourceMap: typeof hasElementSourceMap;
47
- includesSymbols: typeof includesSymbols;
48
- includesClasses: typeof includesClasses;
49
- }
50
-
51
- const predicates: Predicates = {
52
- isElement,
53
- isStringElement,
54
- isNumberElement,
55
- isNullElement,
56
- isBooleanElement,
57
- isArrayElement,
58
- isObjectElement,
59
- isMemberElement,
60
- isPrimitiveElement,
61
- isLinkElement,
62
- isRefElement,
63
- isAnnotationElement,
64
- isCommentElement,
65
- isParseResultElement,
66
- isSourceMapElement,
67
- hasElementStyle,
68
- hasElementSourceMap,
69
- includesSymbols,
70
- includesClasses,
71
- };
72
-
73
- /**
74
- * @public
75
- */
76
- export interface Toolbox {
77
- predicates: Predicates;
78
- namespace: Namespace;
79
- }
80
-
81
- /**
82
- * @public
83
- */
84
- const createToolbox = (): Toolbox => {
85
- return { predicates, namespace: defaultNamespace };
86
- };
87
-
88
- export default createToolbox;
@@ -1,68 +0,0 @@
1
- import { mapObjIndexed, path, has, propSatisfies } from 'ramda';
2
- import { isPlainObject, trimCharsStart, isString } from 'ramda-adjunct';
3
-
4
- /**
5
- * Base type for resolved specification objects.
6
- * Extend this in namespace packages to add specific type information.
7
- * @public
8
- */
9
- export interface ResolvedSpecification {
10
- elementMap: Record<string, string[]>;
11
- }
12
-
13
- const specCache = new WeakMap<object, Record<string, unknown>>();
14
-
15
- /**
16
- * Resolves a specification object by:
17
- * 1. Dereferencing all $ref pointers
18
- * 2. Building elementMap from objects with `element` property
19
- * 3. Caching results for efficiency
20
- *
21
- * @public
22
- */
23
- export const resolveSpecification = <T extends ResolvedSpecification = ResolvedSpecification>(
24
- specification: Record<string, unknown>,
25
- ): T => {
26
- if (specCache.has(specification)) {
27
- return specCache.get(specification)! as T;
28
- }
29
-
30
- const elementMap: Record<string, string[]> = {};
31
-
32
- const traverse = (
33
- obj: Record<string, unknown>,
34
- root: Record<string, unknown>,
35
- currentPath: string[],
36
- ): Record<string, unknown> => {
37
- // Collect element mapping
38
- if (typeof obj.element === 'string') {
39
- elementMap[obj.element] = obj.$visitor ? [...currentPath, '$visitor'] : currentPath;
40
- }
41
-
42
- return mapObjIndexed((val, key) => {
43
- const newPath = [...currentPath, key as string];
44
-
45
- if (isPlainObject(val) && has('$ref', val) && propSatisfies(isString, '$ref', val)) {
46
- const $ref = path(['$ref'], val);
47
- const pointer = trimCharsStart('#/', $ref as string);
48
- const resolved = path(pointer.split('/'), root);
49
- // merge extra properties (e.g. alias) from the $ref object into the resolved value
50
- const { $ref: _, ...rest } = val as Record<string, unknown>;
51
- if (Object.keys(rest).length > 0 && isPlainObject(resolved)) {
52
- return { ...(resolved as Record<string, unknown>), ...rest };
53
- }
54
- return resolved;
55
- }
56
- if (isPlainObject(val)) {
57
- return traverse(val as Record<string, unknown>, root, newPath);
58
- }
59
- return val;
60
- }, obj);
61
- };
62
-
63
- const resolved = traverse(specification, specification, []);
64
- resolved.elementMap = elementMap;
65
-
66
- specCache.set(specification, resolved);
67
- return resolved as T;
68
- };
@@ -1,147 +0,0 @@
1
- import {
2
- ArrayElement,
3
- Element,
4
- MemberElement,
5
- ObjectElement,
6
- isObjectElement,
7
- isArrayElement,
8
- isMemberElement,
9
- } from '@speclynx/apidom-datamodel';
10
- import { isUndefined } from 'ramda-adjunct';
11
-
12
- const computeEdges = (element: Element, edges = new WeakMap()): WeakMap<Element, any> => {
13
- if (isMemberElement(element)) {
14
- // @ts-ignore
15
- edges.set(element.key, element);
16
- // @ts-ignore
17
- computeEdges(element.key, edges);
18
- // @ts-ignore
19
- edges.set(element.value, element);
20
- // @ts-ignore
21
- computeEdges(element.value, edges);
22
- } else {
23
- element.children.forEach((childElement: Element): void => {
24
- edges.set(childElement, element);
25
- computeEdges(childElement, edges);
26
- });
27
- }
28
-
29
- return edges;
30
- };
31
-
32
- const transcludeChildOfMemberElement = (
33
- search: Element,
34
- replace: Element,
35
- edges: WeakMap<Element, any>,
36
- ): void => {
37
- const memberElement: MemberElement = edges.get(search);
38
-
39
- if (!isMemberElement(memberElement)) {
40
- return;
41
- }
42
-
43
- if (memberElement.key === search) {
44
- memberElement.key = replace;
45
- edges.delete(search);
46
- edges.set(replace, memberElement);
47
- }
48
-
49
- if (memberElement.value === search) {
50
- memberElement.value = replace;
51
- edges.delete(search);
52
- edges.set(replace, memberElement);
53
- }
54
- };
55
-
56
- const transcludeChildOfObjectElement = (
57
- search: Element,
58
- replace: MemberElement,
59
- edges: WeakMap<Element, any>,
60
- ): void => {
61
- const objectElement: ObjectElement = edges.get(search);
62
-
63
- if (!isObjectElement(objectElement)) {
64
- return;
65
- }
66
-
67
- objectElement.content = objectElement.map(
68
- (value: Element, key: Element, member: MemberElement): MemberElement => {
69
- if (member === search) {
70
- edges.delete(search);
71
- edges.set(replace, objectElement);
72
- return replace;
73
- }
74
- return member;
75
- },
76
- );
77
- };
78
-
79
- const transcludeChildOfArrayElement = (
80
- search: Element,
81
- replace: Element,
82
- edges: WeakMap<Element, any>,
83
- ): void => {
84
- const arrayElement: ArrayElement = edges.get(search);
85
-
86
- if (!isArrayElement(arrayElement)) {
87
- return;
88
- }
89
-
90
- arrayElement.content = arrayElement.map((element: Element): Element => {
91
- if (element === search) {
92
- edges.delete(search);
93
- edges.set(replace, arrayElement);
94
- return replace;
95
- }
96
- return element;
97
- });
98
- };
99
-
100
- /**
101
- * This is a mutating stamp. If you don't want your Element to be mutated,
102
- * clone in before passing it to initializer of this stamp.
103
- * @public
104
- */
105
-
106
- class Transcluder {
107
- protected element: Element;
108
-
109
- protected edges!: WeakMap<Element, Element | undefined>;
110
-
111
- constructor({ element }: { element: Element }) {
112
- this.element = element;
113
- }
114
-
115
- public transclude(search: Element, replace: Element): Element | undefined {
116
- // shortcut 1. - replacing entire ApiDOM tree
117
- if (search === this.element) return replace;
118
- // shortcut 2. - replacing nothing
119
- if (search === replace) return this.element;
120
-
121
- this.edges = this.edges ?? computeEdges(this.element);
122
-
123
- const parent = this.edges.get(search);
124
-
125
- if (isUndefined(parent)) {
126
- return undefined;
127
- }
128
-
129
- /**
130
- * This predicate must be first because ObjectElement extends ArrayElement.
131
- * isArrayElement returns true for ObjectElements.
132
- * (classical problems with polymorphism)
133
- */
134
- if (isObjectElement(parent)) {
135
- // @ts-ignore
136
- transcludeChildOfObjectElement(search, replace, this.edges);
137
- } else if (isArrayElement(parent)) {
138
- transcludeChildOfArrayElement(search, replace, this.edges);
139
- } else if (isMemberElement(parent)) {
140
- transcludeChildOfMemberElement(search, replace, this.edges);
141
- }
142
-
143
- return this.element;
144
- }
145
- }
146
-
147
- export default Transcluder;
@@ -1,15 +0,0 @@
1
- import { Element } from '@speclynx/apidom-datamodel';
2
-
3
- import Transcluder from './Transcluder.ts';
4
-
5
- /**
6
- * This is a mutating function. If you don't want your Element to be mutated,
7
- * clone in before passing it to this function.
8
- * @public
9
- */
10
- export const transclude = (search: Element, replace: Element, element: Element) => {
11
- const transcluder = new Transcluder({ element });
12
- return transcluder.transclude(search, replace);
13
- };
14
-
15
- export default Transcluder;
@@ -1,14 +0,0 @@
1
- import { Element, Namespace } from '@speclynx/apidom-datamodel';
2
-
3
- import defaultNamespaceInstance from '../namespace.ts';
4
-
5
- /**
6
- * Creates a refract representation of an Element.
7
- * https://github.com/refractproject/refract-spec
8
- * @public
9
- */
10
- const dehydrate = (element: Element, namespace: Namespace = defaultNamespaceInstance): unknown => {
11
- return namespace.toRefract(element);
12
- };
13
-
14
- export default dehydrate;
@@ -1,34 +0,0 @@
1
- import { has } from 'ramda';
2
- import { isPlainObject, isString } from 'ramda-adjunct';
3
- import { Element, Namespace } from '@speclynx/apidom-datamodel';
4
-
5
- import defaultNamespace from '../namespace.ts';
6
-
7
- /**
8
- * Transforms data to an Element from a particular namespace.
9
- *
10
- * The name of the function was originally `from`,
11
- * but it was renamed to `fromFn` to avoid issues with Parcel.js:
12
- *
13
- * - https://github.com/parcel-bundler/parcel/issues/9473
14
- * - https://github.com/swagger-api/swagger-ui/issues/9466#issuecomment-1881053410
15
- * @public
16
- */
17
- const fromFn = (data: unknown, namespace: Namespace = defaultNamespace): Element | undefined => {
18
- if (isString(data)) {
19
- // JSON serialized refract
20
- try {
21
- return namespace.fromRefract(JSON.parse(data));
22
- } catch {
23
- // noop
24
- }
25
- }
26
- if (isPlainObject(data) && has('element', data)) {
27
- // refract javascript structure
28
- return namespace.fromRefract(data);
29
- }
30
-
31
- return namespace.toElement(data);
32
- };
33
-
34
- export default fromFn;
@@ -1,107 +0,0 @@
1
- import {
2
- Element,
3
- isElement,
4
- isObjectElement,
5
- isArrayElement,
6
- isRefElement,
7
- isLinkElement,
8
- isStringElement,
9
- isNumberElement,
10
- } from '@speclynx/apidom-datamodel';
11
-
12
- import toValue from './value.ts';
13
-
14
- interface JSONElementStyle {
15
- indent?: number;
16
- rawContent?: string;
17
- }
18
-
19
- const getStyle = (element: Element): JSONElementStyle => {
20
- return (element.style?.json ?? {}) as JSONElementStyle;
21
- };
22
-
23
- /**
24
- * @public
25
- */
26
- export interface JSONSerializerOptions {
27
- /** Preserve original formatting styles from `element.style.json` */
28
- preserveStyle?: boolean;
29
- }
30
-
31
- /**
32
- * Builds a POJO from an ApiDOM element tree. Numbers with rawContent
33
- * are replaced with sentinel strings; all other values go through toValue().
34
- */
35
- const toPojo = (element: Element, sentinels: Map<string, string>): unknown => {
36
- const visited = new WeakSet<object>();
37
-
38
- const convert = (node: unknown): unknown => {
39
- if (!isElement(node)) return node;
40
-
41
- if (visited.has(node as object)) return null;
42
- visited.add(node as object);
43
-
44
- if (isObjectElement(node)) {
45
- const obj: Record<string, unknown> = {};
46
- node.forEach((value, key) => {
47
- const k = isElement(key) ? toValue(key) : key;
48
- if (typeof k === 'string') obj[k] = convert(value);
49
- });
50
- return obj;
51
- }
52
-
53
- if (isArrayElement(node)) {
54
- const arr: unknown[] = [];
55
- node.forEach((item) => arr.push(convert(item)));
56
- return arr;
57
- }
58
-
59
- if (isRefElement(node)) return String(toValue(node));
60
- if (isLinkElement(node)) return isStringElement(node.href) ? toValue(node.href) : '';
61
-
62
- // number with rawContent — substitute with sentinel
63
- if (isNumberElement(node)) {
64
- const style = getStyle(node);
65
- if (typeof style.rawContent === 'string') {
66
- const sentinel = `\0RAW${sentinels.size}\0`;
67
- sentinels.set(sentinel, style.rawContent);
68
- return sentinel;
69
- }
70
- }
71
-
72
- return toValue(node);
73
- };
74
-
75
- return convert(element);
76
- };
77
-
78
- /**
79
- * @public
80
- */
81
- const serializer = (
82
- element: Element,
83
- replacer?: (this: unknown, key: string, value: unknown) => unknown,
84
- space?: string | number,
85
- options?: JSONSerializerOptions,
86
- ): string => {
87
- if (options?.preserveStyle) {
88
- const style = getStyle(element);
89
- const indent =
90
- typeof space === 'number' ? space : typeof style.indent === 'number' ? style.indent : 0;
91
-
92
- const sentinels = new Map<string, string>();
93
- const pojo = toPojo(element, sentinels);
94
- let serialized = JSON.stringify(pojo, null, indent);
95
-
96
- // replace quoted sentinels with raw number representations
97
- for (const [sentinel, raw] of sentinels) {
98
- serialized = serialized.replace(JSON.stringify(sentinel), raw);
99
- }
100
-
101
- return serialized;
102
- }
103
-
104
- return JSON.stringify(toValue(element), replacer, space);
105
- };
106
-
107
- export default serializer;