-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathgenerators.mjs
More file actions
175 lines (153 loc) · 5.44 KB
/
generators.mjs
File metadata and controls
175 lines (153 loc) · 5.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
'use strict';
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
import os from 'os';
import publicGenerators from './generators/index.mjs';
import astJs from './generators/ast-js/index.mjs';
import oramaDb from './generators/orama-db/index.mjs';
const availableGenerators = {
...publicGenerators,
// This one is a little special since we don't want it to run unless we need
// it and we also don't want it to be publicly accessible through the CLI.
'ast-js': astJs,
'orama-db': oramaDb,
};
// Thread pool max limit
const MAX_THREADS = Math.max(1, os.cpus().length - 1);
// If inside a worker thread, perform the generator logic here
if (!isMainThread) {
const { name, dependencyOutput, extra } = workerData;
const generator = availableGenerators[name];
// Execute the generator and send the result back to the parent thread
generator
.generate(dependencyOutput, extra)
.then(result => {
parentPort.postMessage(result);
})
.catch(error => {
parentPort.postMessage({ error });
});
}
/**
* @typedef {{ ast: GeneratorMetadata<ApiDocMetadataEntry, ApiDocMetadataEntry>}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator
* @typedef {AvailableGenerators & AstGenerator} AllGenerators A complete set of the available generators, including the AST one
* @param markdownInput
* @param jsInput
*
* This method creates a system that allows you to register generators
* and then execute them in a specific order, keeping track of the
* generation process, and handling errors that may occur from the
* execution of generating content.
*
* When the final generator is reached, the system will return the
* final generated content.
*
* Generators can output content that can be consumed by other generators;
* Generators can also write to files. These would usually be considered
* the final generators in the chain.
*
* @param {ApiDocMetadataEntry} markdownInput The parsed API doc metadata entries
* @param {Array<import('acorn').Program>} parsedJsFiles
*/
const createGenerator = markdownInput => {
/**
* We store all the registered generators to be processed
* within a Record, so we can access their results at any time whenever needed
* (we store the Promises of the generator outputs)
*
* @type {{ [K in keyof AllGenerators]: ReturnType<AllGenerators[K]['generate']> }}
*/
const cachedGenerators = { ast: Promise.resolve(markdownInput) };
// Keep track of how many threads are currently running
let activeThreads = 0;
const threadQueue = [];
/**
*
* @param name
* @param dependencyOutput
* @param extra
*/
const runInWorker = (name, dependencyOutput, extra) => {
return new Promise((resolve, reject) => {
/**
*
*/
const run = () => {
activeThreads++;
const worker = new Worker(new URL(import.meta.url), {
workerData: { name, dependencyOutput, extra },
});
worker.on('message', result => {
activeThreads--;
processQueue();
if (result && result.error) {
reject(result.error);
} else {
resolve(result);
}
});
worker.on('error', err => {
activeThreads--;
processQueue();
reject(err);
});
};
if (activeThreads >= MAX_THREADS) {
threadQueue.push(run);
} else {
run();
}
});
};
/**
*
*/
const processQueue = () => {
if (threadQueue.length > 0 && activeThreads < MAX_THREADS) {
const next = threadQueue.shift();
next();
}
};
/**
* Runs the Generator engine with the provided top-level input and the given generator options
*
* @param {GeneratorOptions} options The options for the generator runtime
*/
const runGenerators = async ({
generators,
disableParallelism = false,
...extra
}) => {
// Note that this method is blocking, and will only execute one generator per-time
// but it ensures all dependencies are resolved, and that multiple bottom-level generators
// can reuse the already parsed content from the top-level/dependency generators
for (const generatorName of generators) {
const {
dependsOn,
generate,
parallizable = true,
} = availableGenerators[generatorName];
// If the generator dependency has not yet been resolved, we resolve
// the dependency first before running the current generator
if (dependsOn && !(dependsOn in cachedGenerators)) {
await runGenerators({
...extra,
disableParallelism,
generators: [dependsOn],
});
}
// Ensures that the dependency output gets resolved before we run the current
// generator with its dependency output as the input
const dependencyOutput = await cachedGenerators[dependsOn];
// Adds the current generator execution Promise to the cache
cachedGenerators[generatorName] =
disableParallelism || !parallizable
? generate(dependencyOutput, extra) // Run in main thread
: runInWorker(generatorName, dependencyOutput, extra); // Offload to worker thread
}
// Returns the value of the last generator of the current pipeline
// Note that dependencies will be awaited (as shown on line 48)
return cachedGenerators[generators[generators.length - 1]];
};
return { runGenerators };
};
export default createGenerator;