@xyd-js/uniform 0.1.0-xyd.10

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/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ // Define the new PluginV type with a callback function that returns another function
2
+ import {Reference} from "./types";
3
+
4
+ // Define the new PluginV type with a callback function that returns another function
5
+ export type UniformPlugin<T> = (cb: (cb: () => T) => void) => (ref: Reference) => void;
6
+
7
+ // Utility type to infer if a type is an array and avoid wrapping it into an array twice
8
+ type NormalizeArray<T> = T extends Array<infer U> ? U[] : T;
9
+
10
+ // Infer the return type of the plugin callback properly and normalize array types
11
+ type PluginResult<T extends UniformPlugin<any>> = T extends UniformPlugin<infer R> ? R : never;
12
+
13
+ // Merge all plugin return types into a single object and normalize arrays
14
+ type MergePluginResults<T extends UniformPlugin<any>[]> = {
15
+ [K in keyof UnionToIntersection<PluginResult<T[number]>>]: NormalizeArray<UnionToIntersection<PluginResult<T[number]>>[K]>
16
+ };
17
+
18
+ // Utility type to handle intersection to an object type
19
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
20
+
21
+ // Implement the uniform function
22
+ export default function uniform<T extends UniformPlugin<any>[]>(
23
+ references: Reference[],
24
+ config: { plugins: T }
25
+ ) {
26
+ // Infer the merged result type from all plugins
27
+ type ResultType = MergePluginResults<T>;
28
+
29
+ // Initialize the response with a type-safe out object
30
+ const response: {
31
+ references: Reference[]
32
+ out: { [K in keyof ResultType]: ResultType[K] }
33
+ } = {
34
+ references,
35
+ out: {} as { [K in keyof ResultType]: ResultType[K] }
36
+ };
37
+
38
+
39
+ const finishCallbacks = new Set();
40
+
41
+ config.plugins.forEach((plugin) => {
42
+ const call = plugin(cb => {
43
+ finishCallbacks.add(cb);
44
+ })
45
+
46
+ references.forEach((ref) => {
47
+ call(ref)
48
+ });
49
+ })
50
+
51
+ finishCallbacks.forEach(cb => {
52
+ if (typeof cb === "function") {
53
+ const resp = cb()
54
+ if (typeof resp !== "object") {
55
+ throw new Error(`Invalid callback return type: ${typeof resp}`)
56
+ }
57
+
58
+ response.out = {
59
+ ...response.out,
60
+ ...resp
61
+ }
62
+ } else {
63
+ throw new Error(`Invalid callback type: ${typeof cb}`)
64
+ }
65
+ });
66
+
67
+ return response;
68
+ }
69
+
70
+ // Example usage
71
+ // const examplePlugin: UniformPlugin<{ value: boolean }> = (cb) => {
72
+ // return (ref: Reference) => {
73
+ // };
74
+ // };
75
+ // function examplePlugin(cb: (cb: () => { value: boolean }) => void) {
76
+ // cb(() => ({
77
+ // value: true,
78
+ // }));
79
+ //
80
+ // return (ref: Reference) => {
81
+ //
82
+ // };
83
+ // }
84
+ // const response = uniform([/* references */], {
85
+ // plugins: [examplePlugin],
86
+ // });
87
+ // response.out
@@ -0,0 +1,67 @@
1
+ import {remark} from "remark";
2
+ import remarkStringify from "remark-stringify";
3
+
4
+ import {Reference} from "../types";
5
+
6
+ import {definitions, examples, heading, root} from "./utils";
7
+
8
+ // TODO: any
9
+ export function compile(ast: any) {
10
+ return remark()
11
+ // .use(unlimitedHeadings)
12
+ .use(remarkStringify, {
13
+ bullet: '-',
14
+ fences: true,
15
+ listItemIndent: 'one',
16
+ incrementListMarker: false,
17
+ handlers: {
18
+ heading(node) {
19
+ return `${"#".repeat(node.depth)} ${node.children[0].value}`;
20
+ },
21
+ },
22
+ })
23
+ .stringify(root(ast));
24
+ }
25
+
26
+ export function referenceAST(ref: Reference) {
27
+ const md = []
28
+
29
+ const mdHeading = heading(
30
+ ref.title,
31
+ ref.canonical,
32
+ ref.description,
33
+ ref.category,
34
+ ref.type,
35
+ ref.context
36
+ )
37
+
38
+ md.push(
39
+ mdHeading.title,
40
+ )
41
+
42
+ if (mdHeading?.description?.length) {
43
+ md.push(...mdHeading.description)
44
+ }
45
+
46
+ md.push(
47
+ mdHeading.canonical,
48
+ )
49
+
50
+ if (mdHeading.category) {
51
+ md.push(mdHeading.category)
52
+ }
53
+
54
+ if (mdHeading.type) {
55
+ md.push(mdHeading.type)
56
+ }
57
+
58
+ if (mdHeading?.context?.length) {
59
+ md.push(...mdHeading.context)
60
+ }
61
+
62
+ const mdExamples = examples(ref.examples)
63
+ const mdDefinitions = definitions(ref.definitions)
64
+ md.push(...mdExamples, ...mdDefinitions)
65
+
66
+ return md;
67
+ }
@@ -0,0 +1,258 @@
1
+ import { fromMarkdown } from "mdast-util-from-markdown";
2
+ import {u} from "unist-builder";
3
+
4
+ import {
5
+ ExampleRoot,
6
+ Definition,
7
+ ReferenceCategory,
8
+ ReferenceContext,
9
+ ReferenceType,
10
+ DefinitionProperty
11
+ } from "../types";
12
+
13
+ // START_DEPTH_LEVEL is the start depth level for the markdown AST
14
+ // starts from 2 because 1 is reserved for the title
15
+ const START_DEPTH_LEVEL = 2
16
+
17
+ // TODO: fix any
18
+ export function root(ast: any) {
19
+ return u('root', ast);
20
+ }
21
+
22
+ export function heading(
23
+ title: string,
24
+ canonical: string,
25
+ description: string,
26
+ refCategory?: ReferenceCategory,
27
+ refType?: ReferenceType,
28
+ refContext?: ReferenceContext
29
+ ) {
30
+ const uTitle = u(
31
+ 'heading',
32
+ {depth: START_DEPTH_LEVEL},
33
+ [u('text', `!!references ${title}`)]
34
+ )
35
+
36
+ const uCanonical = u(
37
+ 'heading',
38
+ {depth: uTitle.depth + 1},
39
+ [u('text', `!canonical ${canonical}`)]
40
+ )
41
+
42
+ let uDesc = [
43
+ u(
44
+ 'heading',
45
+ {depth: uTitle.depth + 1},
46
+ [u('text', `!description`),]
47
+ ),
48
+ u('paragraph', [u('text', description)])
49
+ ]
50
+
51
+ let uRefCategory
52
+ if (refCategory) {
53
+ uRefCategory = u(
54
+ 'heading',
55
+ {depth: uTitle.depth + 1},
56
+ [u('text', `!category ${refCategory}`)]
57
+ )
58
+ }
59
+
60
+ let uRefType
61
+ if (refType) {
62
+ uRefType = u(
63
+ 'heading',
64
+ {depth: uTitle.depth + 1},
65
+ [u('text', `!type ${refType}`)]
66
+ )
67
+ }
68
+
69
+ let uContext = []
70
+
71
+ if (refContext && Object.keys(refContext)) {
72
+ uContext.push(u(
73
+ 'heading',
74
+ {depth: uTitle.depth + 1},
75
+ [
76
+ u('text', `!context`),
77
+ ]
78
+ ))
79
+
80
+
81
+ for (const [key, value] of Object.entries(refContext)) {
82
+ uContext.push(u(
83
+ 'heading',
84
+ {depth: uContext[0].depth + 1},
85
+ [u('text', `!${key} ${value}`)]
86
+ )
87
+ )
88
+ }
89
+ }
90
+
91
+ return {
92
+ title: uTitle,
93
+ canonical: uCanonical,
94
+ description: uDesc,
95
+ category: uRefCategory || null,
96
+ type: uRefType || null,
97
+ context: uContext || null
98
+ }
99
+ }
100
+
101
+ export function examples(examples: ExampleRoot) {
102
+ const md = []
103
+
104
+ const uExampleRoot = u(
105
+ 'heading',
106
+ {depth: START_DEPTH_LEVEL + 1},
107
+ [u('text', `!examples`)]
108
+ )
109
+ md.push(uExampleRoot)
110
+
111
+ examples.groups.forEach(group => {
112
+ const uExampleGroups = u(
113
+ 'heading',
114
+ {depth: uExampleRoot.depth + 1},
115
+ [u('text', `!!groups`)]
116
+ )
117
+ md.push(uExampleGroups)
118
+
119
+ const uGroupDescription = u(
120
+ 'heading',
121
+ {depth: uExampleGroups.depth + 1},
122
+ [u('text', `!description ${group.description}`)]
123
+ )
124
+ md.push(uGroupDescription)
125
+
126
+ group.examples.forEach(example => {
127
+ const uExamples = u(
128
+ 'heading',
129
+ {depth: uExampleGroups.depth + 1},
130
+ [u('text', `!!examples`)]
131
+ )
132
+ md.push(uExamples)
133
+
134
+ const codeBlock = u(
135
+ 'heading',
136
+ {depth: uExamples.depth + 1},
137
+ [u('text', `!codeblock`)]
138
+ )
139
+ md.push(codeBlock)
140
+
141
+ const codeBlockTitle = u(
142
+ 'heading',
143
+ {depth: codeBlock.depth + 1},
144
+ [u('text', `!title ${example.codeblock.title}`)]
145
+ )
146
+ md.push(codeBlockTitle)
147
+
148
+ const tabs = u(
149
+ 'heading',
150
+ {depth: codeBlock.depth + 1},
151
+ [u('text', `!!tabs`)]
152
+ )
153
+ md.push(tabs)
154
+
155
+ example.codeblock.tabs.forEach(tab => {
156
+ const code = u('code', {
157
+ lang: tab.language,
158
+ meta: `!code ${tab.title}`
159
+ }, tab.code);
160
+
161
+ md.push(code)
162
+ })
163
+
164
+ })
165
+ })
166
+
167
+ return md
168
+ }
169
+
170
+ export function definitions(definitions: Definition[]) {
171
+ const md: any[] = []
172
+
173
+ definitions.forEach(definition => {
174
+ const uDefinition = u(
175
+ 'heading',
176
+ {depth: START_DEPTH_LEVEL + 1},
177
+ [u('text', `!!definitions`)]
178
+ )
179
+ md.push(uDefinition)
180
+
181
+ md.push(u(
182
+ 'heading',
183
+ {depth: uDefinition.depth + 1},
184
+ [u('text', `!title ${definition.title}`)]
185
+ ))
186
+
187
+ definition.properties.forEach(prop => {
188
+ properties(
189
+ uDefinition.depth + 1,
190
+ {
191
+ name: prop.name,
192
+ type: prop.type,
193
+ description: prop.description,
194
+ context: prop.context,
195
+ properties: prop.properties // TODO: fix ts
196
+ },
197
+ md,
198
+ )
199
+ })
200
+ })
201
+
202
+ return md
203
+ }
204
+
205
+ // TODO: any[]
206
+ export function properties(
207
+ depth: number,
208
+ props: DefinitionProperty,
209
+ output: any[] = []
210
+ ) {
211
+ const paramName = props.name;
212
+
213
+ const propTitle = `!!properties ${paramName}`; // TODO: check if `!!properties is enough`
214
+ const uPropTitle = u('heading', {depth}, [u('text', propTitle)]);
215
+ const uPropName = u('paragraph', {depth}, [u('text', `!name ${paramName}`)]);
216
+ const uPropType = u('paragraph', {depth}, [u('text', `!type ${props.type}`)]);
217
+ const markdownAst = fromMarkdown(props.description || '');
218
+ const uPropDesc = u("paragraph", { depth }, markdownAst.children);
219
+ const uContext = []
220
+
221
+ if (props.context && Object.keys(props.context)) {
222
+ uContext.push(u(
223
+ 'heading',
224
+ {depth: depth + 1},
225
+ [
226
+ u('text', `!context`),
227
+ ]
228
+ ))
229
+
230
+ for (const [key, value] of Object.entries(props.context)) {
231
+ uContext.push(u(
232
+ 'heading',
233
+ {depth: uContext[0].depth + 1},
234
+ [u('text', `!${key} ${value}`)]
235
+ )
236
+ )
237
+ }
238
+ }
239
+
240
+ output.push(
241
+ uPropTitle,
242
+ uPropName,
243
+ uPropType,
244
+ uPropDesc,
245
+ ...uContext
246
+ );
247
+
248
+ if (props.properties) {
249
+ const deepDepth = depth + 1
250
+
251
+ for (const prop of props.properties) {
252
+ properties(deepDepth, prop, output)
253
+ }
254
+ }
255
+ }
256
+
257
+
258
+
package/src/types.ts ADDED
@@ -0,0 +1,137 @@
1
+ // TODO: concept only
2
+ export enum ReferenceCategory {
3
+ // for React
4
+ COMPONENTS = "components",
5
+ HOOKS = "hooks",
6
+ // end for React
7
+
8
+ // for API
9
+ REST = "rest",
10
+ GRAPHQL = "graphql",
11
+ // end for API
12
+
13
+ // for code
14
+ FUNCTIONS = "functions",
15
+ //
16
+ }
17
+
18
+ // TODO: concept only
19
+ export enum ReferenceType {
20
+ // for React
21
+ COMPONENT = "component",
22
+ HOOK = "hook",
23
+ // end for React
24
+
25
+ // for API
26
+ REST_HTTP_GET = "rest_get",
27
+ REST_HTTP_POST = "rest_post",
28
+ REST_HTTP_PUT = "rest_put",
29
+ REST_HTTP_PATCH = "rest_patch",
30
+ REST_HTTP_DELETE = "rest_delete",
31
+ // ---
32
+ GRAPHQL_QUERY = "graphql_query",
33
+ GRAPHQL_MUTATION = "graphql_mutation",
34
+ // end for API
35
+
36
+ // for code
37
+ FUNCTION_JS = "function_js",
38
+ // end for code
39
+ }
40
+
41
+ export interface GraphQLReferenceContext {
42
+ }
43
+
44
+ // TODO: custom value?
45
+ export interface OpenAPIReferenceContext {
46
+ method: string;
47
+
48
+ path: string;
49
+ }
50
+
51
+ export type ReferenceContext = GraphQLReferenceContext | OpenAPIReferenceContext;
52
+
53
+ export interface ExampleRoot {
54
+ groups: ExampleGroup[];
55
+ }
56
+
57
+ export interface ExampleGroup {
58
+ description?: string;
59
+
60
+ examples: Example[];
61
+ }
62
+
63
+ export interface Example {
64
+ description?: string; // TODO: replace with title ?
65
+
66
+ codeblock: CodeBlock;
67
+ }
68
+
69
+ export interface CodeBlock {
70
+ title?: string;
71
+
72
+ tabs: CodeBlockTab[];
73
+ }
74
+
75
+ export interface GraphQLExampleContext {
76
+ schema?: any; // TODO:
77
+ }
78
+
79
+ export interface OpenAPIExampleContext {
80
+ status?: number;
81
+
82
+ content?: string;
83
+ }
84
+
85
+ export type ExampleContext = GraphQLExampleContext | OpenAPIExampleContext;
86
+
87
+ export interface CodeBlockTab {
88
+ // title of the tab e.g "JavaScript"
89
+ title: string;
90
+
91
+ // code in the tab e.g "console.log('Hello World')"
92
+ code: string
93
+
94
+ // language of the code e.g "js"
95
+ language: string;
96
+
97
+ // context of the generation method e.g openapi or graphql
98
+ context?: ExampleContext;
99
+ }
100
+
101
+ export interface Reference {
102
+ title: string;
103
+ description: string;
104
+ canonical: string;
105
+
106
+ definitions: Definition[]
107
+ examples: ExampleRoot
108
+
109
+ category?: ReferenceCategory; // TODO: do we need that?
110
+ type?: ReferenceType; // TODO: do we need that?
111
+ context?: ReferenceContext;
112
+ }
113
+
114
+ export interface Definition {
115
+ title: string;
116
+
117
+ properties: DefinitionProperty[];
118
+
119
+
120
+ type?: string;
121
+
122
+ id?: string;
123
+
124
+ description?: string;
125
+ }
126
+
127
+ export interface DefinitionProperty {
128
+ name: string;
129
+
130
+ type: string;
131
+
132
+ description: string;
133
+
134
+ context?: any // TODO: better type
135
+
136
+ properties?: DefinitionProperty[];
137
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,123 @@
1
+ import path from 'path';
2
+ import matter from 'gray-matter';
3
+ import {Sidebar, FrontMatter, PageFrontMatter} from "@xyd-js/core";
4
+
5
+ import {Reference} from "./types";
6
+ import uniform from "./index";
7
+
8
+ // interface UniformFrontMatter extends FrontMatter { // TODO: it's concept only
9
+ // scopes?: string
10
+ // }
11
+
12
+ type GroupMap = {
13
+ [key: string]: {
14
+ __groups: GroupMap
15
+ pages: Set<string>
16
+ }
17
+ }
18
+
19
+ export interface pluginNavigationOptions {
20
+ urlPrefix: string
21
+ }
22
+
23
+ export function pluginNavigation(options: pluginNavigationOptions) {
24
+ if (!options.urlPrefix) {
25
+ throw new Error("urlPrefix is required")
26
+ }
27
+
28
+ return function pluginNavigationInner(cb: (cb: () => {
29
+ pageFrontMatter: PageFrontMatter
30
+ sidebar: Sidebar[]
31
+ }) => void) {
32
+ const pageFrontMatter: PageFrontMatter = {}
33
+ const groupMaps: GroupMap = {}
34
+
35
+ cb(() => {
36
+ return {
37
+ pageFrontMatter: pageFrontMatter,
38
+ sidebar: convertGroupMapsToNavigations(groupMaps) as Sidebar[]
39
+ }
40
+ })
41
+
42
+ return (ref: Reference) => {
43
+ const content = matter(ref.description || "") // TODO: pluginMatter before?
44
+
45
+ if (content.data) {
46
+ const data = content.data as FrontMatter
47
+
48
+ const pagePath = path.join(options.urlPrefix, ref.canonical)
49
+
50
+ if (data.title) {
51
+ pageFrontMatter[pagePath] = {
52
+ title: data.title,
53
+ }
54
+ }
55
+
56
+ if (data.group) {
57
+ if (typeof content?.data?.group === "string") {
58
+ // TODO: seek nested group (it's not always from 0)
59
+ throw new Error("group as string is not supported yet")
60
+ }
61
+
62
+ content.data.group.reduce((groups: GroupMap, group: string, i: number) => {
63
+ if (!groups[group]) {
64
+ groups[group] = {
65
+ __groups: {},
66
+ pages: new Set()
67
+ }
68
+ }
69
+
70
+ if (i === content.data.group.length - 1) {
71
+ groups[group].pages.add(pagePath)
72
+ }
73
+
74
+ return groups[group].__groups
75
+ }, groupMaps)
76
+ }
77
+
78
+ // back description to original without frontmatter
79
+ ref.description = content.content
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+
86
+ function convertGroupMapsToNavigations(groupMaps: GroupMap): Sidebar[] {
87
+ const nav: Sidebar[] = []
88
+
89
+ Object.keys(groupMaps).map((groupName) => {
90
+ const current = groupMaps[groupName]
91
+
92
+ const pages: string[] | Sidebar[] = []
93
+
94
+ current.pages.forEach((page: string) => {
95
+ pages.push(page)
96
+ })
97
+
98
+ if (Object.keys(current.__groups).length) {
99
+ const subNav: Sidebar = {
100
+ group: groupName,
101
+ pages: convertGroupMapsToNavigations(current.__groups)
102
+ }
103
+
104
+ nav.push(subNav)
105
+ } else {
106
+ nav.push({
107
+ group: groupName,
108
+ pages,
109
+ })
110
+ }
111
+ })
112
+
113
+ return nav
114
+ }
115
+
116
+ // example usage:
117
+ // const response = uniform([/* references */], {
118
+ // plugins: [pluginNavigation({
119
+ // urlPrefix: "/docs"
120
+ // })],
121
+ // });
122
+
123
+
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "incremental": true,
14
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
15
+ },
16
+ "include": ["examples/basic-example/**/*.ts", "src/**/*.ts"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }