@doc-kit/core 0.0.0
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/package.json +88 -0
- package/shiki.config.mjs +50 -0
- package/src/caching.mjs +183 -0
- package/src/generators/ast/README.md +8 -0
- package/src/generators/ast/constants.mjs +1 -0
- package/src/generators/ast/generate.mjs +125 -0
- package/src/generators/ast/index.mjs +20 -0
- package/src/generators/ast/types.d.ts +7 -0
- package/src/generators/ast-js/README.md +8 -0
- package/src/generators/ast-js/generate.mjs +56 -0
- package/src/generators/ast-js/index.mjs +24 -0
- package/src/generators/ast-js/types.d.ts +5 -0
- package/src/generators/index.mjs +48 -0
- package/src/generators/json-simple/README.md +9 -0
- package/src/generators/json-simple/generate.mjs +33 -0
- package/src/generators/json-simple/index.mjs +23 -0
- package/src/generators/json-simple/types.d.ts +6 -0
- package/src/generators/loader.mjs +96 -0
- package/src/generators/metadata/README.md +8 -0
- package/src/generators/metadata/constants.mjs +68 -0
- package/src/generators/metadata/generate.mjs +40 -0
- package/src/generators/metadata/index.mjs +21 -0
- package/src/generators/metadata/maps/builtin.json +16 -0
- package/src/generators/metadata/maps/mdn.json +1114 -0
- package/src/generators/metadata/types.d.ts +145 -0
- package/src/generators/metadata/utils/parse.mjs +160 -0
- package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
- package/src/generators/metadata/utils/slugger.mjs +39 -0
- package/src/generators/metadata/utils/transformers.mjs +109 -0
- package/src/generators/metadata/utils/visitors.mjs +134 -0
- package/src/generators/metadata/utils/yaml.mjs +58 -0
- package/src/generators/types.d.ts +129 -0
- package/src/generators.mjs +145 -0
- package/src/logger/constants.mjs +34 -0
- package/src/logger/index.mjs +27 -0
- package/src/logger/logger.mjs +177 -0
- package/src/logger/transports/console.mjs +54 -0
- package/src/logger/transports/github.mjs +42 -0
- package/src/logger/transports/index.mjs +11 -0
- package/src/logger/types.d.ts +29 -0
- package/src/logger/utils/colors.mjs +18 -0
- package/src/logger/utils/time.mjs +21 -0
- package/src/parsers/markdown.mjs +49 -0
- package/src/parsers/types.d.ts +7 -0
- package/src/threading/chunk-worker.mjs +23 -0
- package/src/threading/index.mjs +27 -0
- package/src/threading/parallel.mjs +140 -0
- package/src/utils/array.mjs +7 -0
- package/src/utils/configuration/index.mjs +263 -0
- package/src/utils/configuration/templates.mjs +21 -0
- package/src/utils/configuration/types.d.ts +64 -0
- package/src/utils/file.mjs +20 -0
- package/src/utils/generators.mjs +71 -0
- package/src/utils/highlighter.mjs +198 -0
- package/src/utils/html-minifier.mjs +12 -0
- package/src/utils/loaders.mjs +48 -0
- package/src/utils/misc.mjs +72 -0
- package/src/utils/queries/constants.mjs +3 -0
- package/src/utils/queries/index.mjs +93 -0
- package/src/utils/queries/utils.mjs +45 -0
- package/src/utils/remark.mjs +97 -0
- package/src/utils/signature/constants.mjs +12 -0
- package/src/utils/signature/parseList.mjs +133 -0
- package/src/utils/signature/parseSignature.mjs +208 -0
- package/src/utils/signature/types.d.ts +66 -0
- package/src/utils/swc.mjs +34 -0
- package/src/utils/type-annotations/hast.mjs +115 -0
- package/src/utils/type-annotations/mdast.mjs +75 -0
- package/src/utils/type-annotations/remark.mjs +24 -0
- package/src/utils/type-annotations/syntax.mjs +188 -0
- package/src/utils/unist.mjs +75 -0
- package/src/utils/url.mjs +27 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { LogLevel } from './constants.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('./types').Metadata} Metadata
|
|
7
|
+
* @typedef {import('./types').LogMessage} LogMessage
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a logger instance with the specified transport, log level and an
|
|
12
|
+
* optional module name. Child loggers share the parent's log level.
|
|
13
|
+
*
|
|
14
|
+
* @param {import('./types').Transport} transport - Function to handle log output.
|
|
15
|
+
* @param {number} [loggerLevel] - Minimum log level to output.
|
|
16
|
+
* @param {string} [module] - Optional module name for the logger.
|
|
17
|
+
*/
|
|
18
|
+
export const createLogger = (
|
|
19
|
+
transport,
|
|
20
|
+
loggerLevel = LogLevel.info,
|
|
21
|
+
module
|
|
22
|
+
) => {
|
|
23
|
+
/** @type {number} */
|
|
24
|
+
let currentLevel = loggerLevel;
|
|
25
|
+
|
|
26
|
+
/** @type {Set<ReturnType<typeof createLogger>>} */
|
|
27
|
+
const children = new Set();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks if the given log level should be logged based on the current logger
|
|
31
|
+
* level.
|
|
32
|
+
*
|
|
33
|
+
* @param {number} level - Log level to check.
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
const shouldLog = level => level >= currentLevel;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Logs a message at the given level with optional metadata.
|
|
40
|
+
*
|
|
41
|
+
* @param {number} level - Log level for the message.
|
|
42
|
+
* @param {LogMessage} message - Message to log.
|
|
43
|
+
* @param {Metadata} metadata - Additional metadata
|
|
44
|
+
* @returns {void}
|
|
45
|
+
*/
|
|
46
|
+
const log = (level, message, metadata = {}) => {
|
|
47
|
+
if (!shouldLog(level)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Array.isArray(message)) {
|
|
52
|
+
return message.forEach(msg => log(level, msg, metadata));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const timestamp = Date.now();
|
|
56
|
+
|
|
57
|
+
// Extract message string from Error object or use message as-is
|
|
58
|
+
let msg;
|
|
59
|
+
|
|
60
|
+
if (message instanceof Error) {
|
|
61
|
+
msg = message.message;
|
|
62
|
+
|
|
63
|
+
metadata.stack = message.stack;
|
|
64
|
+
} else {
|
|
65
|
+
msg = message;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
transport({
|
|
69
|
+
level,
|
|
70
|
+
message: msg,
|
|
71
|
+
timestamp,
|
|
72
|
+
metadata,
|
|
73
|
+
module,
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Logs an info message.
|
|
79
|
+
*
|
|
80
|
+
* @param {LogMessage} message - Info message to log.
|
|
81
|
+
* @param {Metadata} metadata - Additional metadata
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
const info = (message, metadata = {}) =>
|
|
85
|
+
log(LogLevel.info, message, metadata);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Logs a warning message.
|
|
89
|
+
*
|
|
90
|
+
* @param {LogMessage} message - Warning message to log.
|
|
91
|
+
* @param {Metadata} metadata - Additional metadata
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
const warn = (message, metadata = {}) =>
|
|
95
|
+
log(LogLevel.warn, message, metadata);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Logs an error message or Error object.
|
|
99
|
+
*
|
|
100
|
+
* @param {LogMessage} message - Error message or Error object to log.
|
|
101
|
+
* @param {Metadata} metadata - Additional metadata
|
|
102
|
+
* @returns {void}
|
|
103
|
+
*/
|
|
104
|
+
const error = (message, metadata = {}) =>
|
|
105
|
+
log(LogLevel.error, message, metadata);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Logs a fatal error message or Error object.
|
|
109
|
+
*
|
|
110
|
+
* @param {LogMessage} message - Fatal error message or Error object to log.
|
|
111
|
+
* @param {Metadata} metadata - Additional metadata
|
|
112
|
+
* @returns {void}
|
|
113
|
+
*/
|
|
114
|
+
const fatal = (message, metadata = {}) =>
|
|
115
|
+
log(LogLevel.fatal, message, metadata);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Logs a debug message.
|
|
119
|
+
*
|
|
120
|
+
* @param {LogMessage} message - Debug message to log.
|
|
121
|
+
* @param {Metadata} metadata - Additional metadata
|
|
122
|
+
* @returns {void}
|
|
123
|
+
*/
|
|
124
|
+
const debug = (message, metadata = {}) =>
|
|
125
|
+
log(LogLevel.debug, message, metadata);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a child logger for a specific module. Child loggers share the
|
|
129
|
+
* parent's log level.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} childModule - Module name for the child logger.
|
|
132
|
+
* @returns {ReturnType<typeof createLogger>}
|
|
133
|
+
*/
|
|
134
|
+
const child = childModule => {
|
|
135
|
+
const childLogger = createLogger(transport, currentLevel, childModule);
|
|
136
|
+
|
|
137
|
+
children.add(childLogger);
|
|
138
|
+
|
|
139
|
+
return childLogger;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Sets the log level for this logger instance and all child loggers.
|
|
144
|
+
*
|
|
145
|
+
* @param {number | string} level - Log level (number) or level name (string)
|
|
146
|
+
*/
|
|
147
|
+
const setLogLevel = level => {
|
|
148
|
+
let newLogLevel = level;
|
|
149
|
+
|
|
150
|
+
if (typeof newLogLevel === 'string') {
|
|
151
|
+
newLogLevel = newLogLevel.toLowerCase();
|
|
152
|
+
|
|
153
|
+
if (newLogLevel in LogLevel === false) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
newLogLevel = LogLevel[newLogLevel];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
currentLevel = newLogLevel;
|
|
161
|
+
|
|
162
|
+
// Propagate to all child loggers
|
|
163
|
+
for (const childLogger of children) {
|
|
164
|
+
childLogger.setLogLevel(currentLevel);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
info,
|
|
170
|
+
warn,
|
|
171
|
+
error,
|
|
172
|
+
fatal,
|
|
173
|
+
debug,
|
|
174
|
+
child,
|
|
175
|
+
setLogLevel,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { styleText } from 'node:util';
|
|
4
|
+
|
|
5
|
+
import { prettifyLevel } from '../utils/colors.mjs';
|
|
6
|
+
import { prettifyTimestamp } from '../utils/time.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Logs a formatted message to stdout for human-friendly CLI output.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('../types').TransportContext} context
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
const console = ({ level, message, timestamp, metadata = {}, module }) => {
|
|
15
|
+
const { file, stack, ...rest } = metadata;
|
|
16
|
+
|
|
17
|
+
const time = prettifyTimestamp(timestamp);
|
|
18
|
+
|
|
19
|
+
process.stdout.write(`[${time}]`);
|
|
20
|
+
|
|
21
|
+
const prettyLevel = prettifyLevel(level);
|
|
22
|
+
|
|
23
|
+
process.stdout.write(` ${prettyLevel}`);
|
|
24
|
+
|
|
25
|
+
if (module) {
|
|
26
|
+
process.stdout.write(` (${module})`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.stdout.write(`: ${message}`);
|
|
30
|
+
|
|
31
|
+
if (file) {
|
|
32
|
+
process.stdout.write(` at ${file.path}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (file?.position) {
|
|
36
|
+
const position = `(${file.position.start.line}:${file.position.end.line})`;
|
|
37
|
+
|
|
38
|
+
process.stdout.write(position);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Print remaining metadata inline in purple
|
|
42
|
+
if (Object.keys(rest).length > 0) {
|
|
43
|
+
const metaStr = styleText('magenta', JSON.stringify(rest));
|
|
44
|
+
process.stdout.write(` ${metaStr}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.stdout.write('\n');
|
|
48
|
+
|
|
49
|
+
if (stack) {
|
|
50
|
+
process.stdout.write(stack);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default console;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { debug, notice, warning, error } from '@actions/core';
|
|
4
|
+
|
|
5
|
+
import { LogLevel } from '../constants.mjs';
|
|
6
|
+
import { prettifyLevel } from '../utils/colors.mjs';
|
|
7
|
+
import { prettifyTimestamp } from '../utils/time.mjs';
|
|
8
|
+
|
|
9
|
+
const actions = {
|
|
10
|
+
[LogLevel.debug]: debug,
|
|
11
|
+
[LogLevel.info]: notice,
|
|
12
|
+
[LogLevel.warn]: warning,
|
|
13
|
+
[LogLevel.error]: error,
|
|
14
|
+
[LogLevel.fatal]: error,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Logs messages to GitHub Actions with formatted output and file info with
|
|
19
|
+
* appropriate gh actions based on level.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('../types').TransportContext} context
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
const github = ({ level, message, timestamp, metadata = {}, module }) => {
|
|
25
|
+
const { file } = metadata;
|
|
26
|
+
|
|
27
|
+
const time = prettifyTimestamp(timestamp);
|
|
28
|
+
|
|
29
|
+
const prettyLevel = prettifyLevel(level);
|
|
30
|
+
|
|
31
|
+
const logMessage = `[${time}] ${prettyLevel}${module ? ` (${module})` : ''}: ${message}`;
|
|
32
|
+
|
|
33
|
+
const logFn = actions[level] ?? notice;
|
|
34
|
+
|
|
35
|
+
logFn(logMessage, {
|
|
36
|
+
file: file?.path,
|
|
37
|
+
startLine: file?.position?.start.line,
|
|
38
|
+
endLine: file?.position?.end.line,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default github;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type LogLevel = 'info' | 'warn' | 'error' | 'fatal' | 'trace' | 'debug';
|
|
2
|
+
|
|
3
|
+
export type LogMessage = string | Error | string[];
|
|
4
|
+
|
|
5
|
+
export interface Position {
|
|
6
|
+
start: { line: number };
|
|
7
|
+
end: { line: number };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface File {
|
|
11
|
+
path: string;
|
|
12
|
+
position?: Position;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Metadata {
|
|
16
|
+
file?: File;
|
|
17
|
+
// Stack trace if the message is an error
|
|
18
|
+
stack?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TransportContext {
|
|
22
|
+
level: number;
|
|
23
|
+
message: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
metadata?: Metadata;
|
|
26
|
+
module?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Transport = (context: TransportContext) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { styleText } from 'node:util';
|
|
4
|
+
|
|
5
|
+
import { levelTags, levelToColorMap } from '../constants.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns a styled, uppercase log level tag for CLI output with color mapping
|
|
9
|
+
* for better readability.
|
|
10
|
+
*
|
|
11
|
+
* @param {number} level
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export const prettifyLevel = level => {
|
|
15
|
+
const tag = levelTags[level] ?? String(level);
|
|
16
|
+
|
|
17
|
+
return styleText(levelToColorMap[level], tag.toUpperCase());
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats a Unix timestamp in milliseconds as a human-readable time string
|
|
5
|
+
* in UTC timezone for CLI output.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} timestamp
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export const prettifyTimestamp = timestamp => {
|
|
11
|
+
const date = new Date(timestamp);
|
|
12
|
+
|
|
13
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
14
|
+
timeZone: 'UTC',
|
|
15
|
+
hour12: false,
|
|
16
|
+
hour: '2-digit',
|
|
17
|
+
minute: '2-digit',
|
|
18
|
+
second: '2-digit',
|
|
19
|
+
fractionalSecondDigits: 3,
|
|
20
|
+
}).format(date);
|
|
21
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { coerce } from 'semver';
|
|
4
|
+
|
|
5
|
+
import { loadFromURL } from '../utils/loaders.mjs';
|
|
6
|
+
|
|
7
|
+
// A ReGeX for retrieving Node.js version headers from the CHANGELOG.md
|
|
8
|
+
const NODE_VERSIONS_REGEX = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g;
|
|
9
|
+
|
|
10
|
+
// A ReGeX for retrieving the list items in the index document
|
|
11
|
+
const LIST_ITEM_REGEX = /\* \[(.*?)\]\((.*?)\.md\)/g;
|
|
12
|
+
|
|
13
|
+
// A ReGeX for checking if a Node.js version is an LTS release
|
|
14
|
+
const NODE_LTS_VERSION_REGEX = /Long Term Support/i;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves all Node.js major versions from the provided CHANGELOG.md file
|
|
18
|
+
* and returns an array of objects containing the version and LTS status.
|
|
19
|
+
* @param {string|URL} path Path to changelog
|
|
20
|
+
* @returns {Promise<Array<import('./types').ReleaseEntry>>}
|
|
21
|
+
*/
|
|
22
|
+
export const parseChangelog = async path => {
|
|
23
|
+
const changelog = await loadFromURL(path);
|
|
24
|
+
|
|
25
|
+
const nodeMajors = Array.from(changelog.matchAll(NODE_VERSIONS_REGEX));
|
|
26
|
+
|
|
27
|
+
return nodeMajors.map(match => ({
|
|
28
|
+
version: coerce(match[1]),
|
|
29
|
+
isLts: NODE_LTS_VERSION_REGEX.test(match[2]),
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves all the document titles for sidebar generation.
|
|
35
|
+
*
|
|
36
|
+
* @param {string|URL} path Path to changelog
|
|
37
|
+
* @returns {Promise<Array<{ section: string, api: string }>>}
|
|
38
|
+
*/
|
|
39
|
+
export const parseIndex = async path => {
|
|
40
|
+
if (!path || !path.length) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const index = await loadFromURL(path);
|
|
45
|
+
|
|
46
|
+
const items = Array.from(index.matchAll(LIST_ITEM_REGEX));
|
|
47
|
+
|
|
48
|
+
return items.map(([, section, api]) => ({ section, api }));
|
|
49
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { loadGenerator } from '../generators/loader.mjs';
|
|
2
|
+
import { setConfig } from '../utils/configuration/index.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Processes a chunk of items using the specified generator's processChunk method.
|
|
6
|
+
* This is the worker entry point for Piscina.
|
|
7
|
+
*
|
|
8
|
+
* @param {ParallelTaskOptions} opts - Task options from Piscina
|
|
9
|
+
* @returns {Promise<unknown>} The processed result
|
|
10
|
+
*/
|
|
11
|
+
export default async ({
|
|
12
|
+
generatorSpecifier,
|
|
13
|
+
input,
|
|
14
|
+
itemIndices,
|
|
15
|
+
extra,
|
|
16
|
+
configuration,
|
|
17
|
+
}) => {
|
|
18
|
+
await setConfig(configuration);
|
|
19
|
+
|
|
20
|
+
const generator = await loadGenerator(generatorSpecifier);
|
|
21
|
+
|
|
22
|
+
return generator.processChunk(input, itemIndices, extra);
|
|
23
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Piscina from 'piscina';
|
|
2
|
+
|
|
3
|
+
import logger from '../logger/index.mjs';
|
|
4
|
+
|
|
5
|
+
const poolLogger = logger.child('WorkerPool');
|
|
6
|
+
|
|
7
|
+
const workerScript = import.meta.resolve('./chunk-worker.mjs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a Piscina worker pool for parallel processing.
|
|
11
|
+
*
|
|
12
|
+
* @param {number} threads - Maximum number of worker threads
|
|
13
|
+
* @returns {import('piscina').Piscina} Configured Piscina instance
|
|
14
|
+
*/
|
|
15
|
+
export default function createWorkerPool(threads) {
|
|
16
|
+
poolLogger.debug(`WorkerPool initialized`, {
|
|
17
|
+
threads,
|
|
18
|
+
workerScript,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return new Piscina({
|
|
22
|
+
filename: workerScript,
|
|
23
|
+
minThreads: 0,
|
|
24
|
+
maxThreads: threads,
|
|
25
|
+
idleTimeout: 1_000,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import logger from '../logger/index.mjs';
|
|
4
|
+
|
|
5
|
+
const parallelLogger = logger.child('parallel');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Splits items into chunks of specified size.
|
|
9
|
+
*
|
|
10
|
+
* @param {number} count - Total number of items
|
|
11
|
+
* @param {number} size - Maximum items per chunk
|
|
12
|
+
* @returns {number[][]} Array of index arrays for each chunk
|
|
13
|
+
*/
|
|
14
|
+
const createChunks = (count, size) => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < count; i += size) {
|
|
18
|
+
chunks.push(
|
|
19
|
+
Array.from({ length: Math.min(size, count - i) }, (_, j) => i + j)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return chunks;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Prepares task data for a chunk, slicing input to only include relevant items.
|
|
28
|
+
*
|
|
29
|
+
* @param {unknown[]} fullInput - Full input array
|
|
30
|
+
* @param {number[]} indices - Indices to process
|
|
31
|
+
* @param {Object} extra - Stuff to pass to the worker
|
|
32
|
+
* @param {import('../utils/configuration/types').Configuration} configuration - Serialized options
|
|
33
|
+
* @param {string} generatorSpecifier - Resolved specifier of the generator
|
|
34
|
+
* @param {string} generatorName - Name of the generator
|
|
35
|
+
* @returns {ParallelTaskOptions} Task data for Piscina
|
|
36
|
+
*/
|
|
37
|
+
const createTask = (
|
|
38
|
+
fullInput,
|
|
39
|
+
indices,
|
|
40
|
+
extra,
|
|
41
|
+
configuration,
|
|
42
|
+
generatorSpecifier,
|
|
43
|
+
generatorName
|
|
44
|
+
) => {
|
|
45
|
+
return {
|
|
46
|
+
generatorSpecifier,
|
|
47
|
+
// Only send the items needed for this chunk (reduces serialization overhead)
|
|
48
|
+
input: indices.map(i => fullInput[i]),
|
|
49
|
+
// Remap indices to 0-based for the sliced array
|
|
50
|
+
itemIndices: indices.map((_, i) => i),
|
|
51
|
+
extra,
|
|
52
|
+
// Only pass the needed configuration to this generator
|
|
53
|
+
configuration: {
|
|
54
|
+
[generatorName]: configuration[generatorName],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a parallel worker that distributes work across a Piscina thread pool.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} specifier - Resolved generator specifier (importable from workers)
|
|
63
|
+
* @param {GeneratorMetadata} generator - The loaded generator
|
|
64
|
+
* @param {import('piscina').Piscina} pool - Piscina instance
|
|
65
|
+
* @param {import('../utils/configuration/types').Configuration} configuration - Generator options
|
|
66
|
+
* @returns {ParallelWorker}
|
|
67
|
+
*/
|
|
68
|
+
export default function createParallelWorker(
|
|
69
|
+
specifier,
|
|
70
|
+
generator,
|
|
71
|
+
pool,
|
|
72
|
+
configuration
|
|
73
|
+
) {
|
|
74
|
+
const { threads, chunkSize } = configuration;
|
|
75
|
+
|
|
76
|
+
const { name } = generator;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
/**
|
|
80
|
+
* Processes items in parallel, yielding results as chunks complete.
|
|
81
|
+
*
|
|
82
|
+
* @template T
|
|
83
|
+
* @param {T[]} items - Items to process
|
|
84
|
+
* @param {object} extra - Extra options
|
|
85
|
+
* @yields {R[]} Chunk results as they complete
|
|
86
|
+
*/
|
|
87
|
+
async *stream(items, extra) {
|
|
88
|
+
if (items.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const chunks = createChunks(items.length, chunkSize);
|
|
93
|
+
|
|
94
|
+
parallelLogger.debug(
|
|
95
|
+
`Distributing ${items.length} items across ${chunks.length} chunks`,
|
|
96
|
+
{ generator: name, chunks: chunks.length, chunkSize, threads }
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const runInOneGo = threads <= 1 || items.length <= 2;
|
|
100
|
+
|
|
101
|
+
// Submit all tasks to Piscina - each promise resolves to itself for removal
|
|
102
|
+
const pending = new Set(
|
|
103
|
+
chunks.map(indices => {
|
|
104
|
+
if (runInOneGo) {
|
|
105
|
+
const promise = generator
|
|
106
|
+
.processChunk(items, indices, extra)
|
|
107
|
+
.then(result => ({ promise, result }));
|
|
108
|
+
|
|
109
|
+
return promise;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const promise = pool
|
|
113
|
+
.run(
|
|
114
|
+
createTask(items, indices, extra, configuration, specifier, name)
|
|
115
|
+
)
|
|
116
|
+
.then(result => ({ promise, result }));
|
|
117
|
+
|
|
118
|
+
return promise;
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Yield results as they complete (true parallel collection)
|
|
123
|
+
let completed = 0;
|
|
124
|
+
|
|
125
|
+
while (pending.size > 0) {
|
|
126
|
+
const { promise, result } = await Promise.race(pending);
|
|
127
|
+
|
|
128
|
+
pending.delete(promise);
|
|
129
|
+
|
|
130
|
+
completed++;
|
|
131
|
+
|
|
132
|
+
parallelLogger.debug(`Chunk ${completed}/${chunks.length} completed`, {
|
|
133
|
+
generator: name,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
yield result;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|