@usebruno/filestore 0.1.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.
Files changed (40) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +50 -0
  3. package/dist/cjs/formats/bru/index.d.ts +6 -0
  4. package/dist/cjs/index.d.ts +15 -0
  5. package/dist/cjs/index.js +2 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/types.d.ts +142 -0
  8. package/dist/cjs/workers/WorkerQueue/index.d.ts +26 -0
  9. package/dist/cjs/workers/formats/bru/index.d.ts +6 -0
  10. package/dist/cjs/workers/index.d.ts +15 -0
  11. package/dist/cjs/workers/types.d.ts +142 -0
  12. package/dist/cjs/workers/worker-script.d.ts +1 -0
  13. package/dist/cjs/workers/worker-script.js +2 -0
  14. package/dist/cjs/workers/worker-script.js.map +1 -0
  15. package/dist/cjs/workers/workers/WorkerQueue/index.d.ts +26 -0
  16. package/dist/cjs/workers/workers/index.d.ts +10 -0
  17. package/dist/cjs/workers/workers/worker-script.d.ts +1 -0
  18. package/dist/esm/formats/bru/index.d.ts +6 -0
  19. package/dist/esm/index.d.ts +15 -0
  20. package/dist/esm/index.js +2 -0
  21. package/dist/esm/index.js.map +1 -0
  22. package/dist/esm/types.d.ts +142 -0
  23. package/dist/esm/workers/WorkerQueue/index.d.ts +26 -0
  24. package/dist/esm/workers/formats/bru/index.d.ts +6 -0
  25. package/dist/esm/workers/index.d.ts +15 -0
  26. package/dist/esm/workers/types.d.ts +142 -0
  27. package/dist/esm/workers/worker-script.d.ts +1 -0
  28. package/dist/esm/workers/worker-script.js +2 -0
  29. package/dist/esm/workers/worker-script.js.map +1 -0
  30. package/dist/esm/workers/workers/WorkerQueue/index.d.ts +26 -0
  31. package/dist/esm/workers/workers/index.d.ts +10 -0
  32. package/dist/esm/workers/workers/worker-script.d.ts +1 -0
  33. package/package.json +47 -0
  34. package/src/formats/bru/index.ts +203 -0
  35. package/src/index.ts +100 -0
  36. package/src/types/bruno-lang.d.ts +9 -0
  37. package/src/types.ts +141 -0
  38. package/src/workers/WorkerQueue/index.ts +114 -0
  39. package/src/workers/index.ts +86 -0
  40. package/src/workers/worker-script.ts +27 -0
@@ -0,0 +1,203 @@
1
+ import * as _ from 'lodash';
2
+ import {
3
+ bruToJsonV2,
4
+ jsonToBruV2,
5
+ bruToEnvJsonV2,
6
+ envJsonToBruV2,
7
+ collectionBruToJson as _collectionBruToJson,
8
+ jsonToCollectionBru as _jsonToCollectionBru
9
+ } from '@usebruno/lang';
10
+
11
+ export const bruRequestToJson = (data: string | any, parsed: boolean = false): any => {
12
+ try {
13
+ const json = parsed ? data : bruToJsonV2(data);
14
+
15
+ let requestType = _.get(json, 'meta.type');
16
+ if (requestType === 'http') {
17
+ requestType = 'http-request';
18
+ } else if (requestType === 'graphql') {
19
+ requestType = 'graphql-request';
20
+ } else {
21
+ requestType = 'http-request';
22
+ }
23
+
24
+ const sequence = _.get(json, 'meta.seq');
25
+ const transformedJson = {
26
+ type: requestType,
27
+ name: _.get(json, 'meta.name'),
28
+ seq: !_.isNaN(sequence) ? Number(sequence) : 1,
29
+ settings: _.get(json, 'settings', {}),
30
+ tags: _.get(json, 'meta.tags', []),
31
+ request: {
32
+ method: _.upperCase(_.get(json, 'http.method')),
33
+ url: _.get(json, 'http.url'),
34
+ params: _.get(json, 'params', []),
35
+ headers: _.get(json, 'headers', []),
36
+ auth: _.get(json, 'auth', {}),
37
+ body: _.get(json, 'body', {}),
38
+ script: _.get(json, 'script', {}),
39
+ vars: _.get(json, 'vars', {}),
40
+ assertions: _.get(json, 'assertions', []),
41
+ tests: _.get(json, 'tests', ''),
42
+ docs: _.get(json, 'docs', '')
43
+ }
44
+ };
45
+
46
+ transformedJson.request.auth.mode = _.get(json, 'http.auth', 'none');
47
+ transformedJson.request.body.mode = _.get(json, 'http.body', 'none');
48
+
49
+ return transformedJson;
50
+ } catch (e) {
51
+ return Promise.reject(e);
52
+ }
53
+ };
54
+
55
+ export const jsonRequestToBru = (json: any): string => {
56
+ try {
57
+ let type = _.get(json, 'type');
58
+ if (type === 'http-request') {
59
+ type = 'http';
60
+ } else if (type === 'graphql-request') {
61
+ type = 'graphql';
62
+ } else {
63
+ type = 'http';
64
+ }
65
+
66
+ const sequence = _.get(json, 'seq');
67
+ const bruJson = {
68
+ meta: {
69
+ name: _.get(json, 'name'),
70
+ type: type,
71
+ seq: !_.isNaN(sequence) ? Number(sequence) : 1,
72
+ tags: _.get(json, 'tags', []),
73
+ },
74
+ http: {
75
+ method: _.lowerCase(_.get(json, 'request.method')),
76
+ url: _.get(json, 'request.url'),
77
+ auth: _.get(json, 'request.auth.mode', 'none'),
78
+ body: _.get(json, 'request.body.mode', 'none')
79
+ },
80
+ params: _.get(json, 'request.params', []),
81
+ headers: _.get(json, 'request.headers', []),
82
+ auth: _.get(json, 'request.auth', {}),
83
+ body: _.get(json, 'request.body', {}),
84
+ script: _.get(json, 'request.script', {}),
85
+ vars: {
86
+ req: _.get(json, 'request.vars.req', []),
87
+ res: _.get(json, 'request.vars.res', [])
88
+ },
89
+ assertions: _.get(json, 'request.assertions', []),
90
+ tests: _.get(json, 'request.tests', ''),
91
+ settings: _.get(json, 'settings', {}),
92
+ docs: _.get(json, 'request.docs', '')
93
+ };
94
+
95
+ const bru = jsonToBruV2(bruJson);
96
+ return bru;
97
+ } catch (error) {
98
+ throw error;
99
+ }
100
+ };
101
+
102
+ export const bruCollectionToJson = (data: string | any, parsed: boolean = false): any => {
103
+ try {
104
+ const json = parsed ? data : _collectionBruToJson(data);
105
+
106
+ const transformedJson: any = {
107
+ request: {
108
+ headers: _.get(json, 'headers', []),
109
+ auth: _.get(json, 'auth', {}),
110
+ script: _.get(json, 'script', {}),
111
+ vars: _.get(json, 'vars', {}),
112
+ tests: _.get(json, 'tests', '')
113
+ },
114
+ settings: _.get(json, 'settings', {}),
115
+ docs: _.get(json, 'docs', '')
116
+ };
117
+
118
+ // add meta if it exists
119
+ // this is only for folder bru file
120
+ if (json.meta) {
121
+ transformedJson.meta = {
122
+ name: json.meta.name
123
+ };
124
+
125
+ // Include seq if it exists
126
+ if (json.meta.seq !== undefined) {
127
+ const sequence = json.meta.seq;
128
+ transformedJson.meta.seq = !isNaN(sequence) ? Number(sequence) : 1;
129
+ }
130
+ }
131
+
132
+ return transformedJson;
133
+ } catch (error) {
134
+ return Promise.reject(error);
135
+ }
136
+ };
137
+
138
+ export const jsonCollectionToBru = (json: any, isFolder?: boolean): string => {
139
+ try {
140
+ const collectionBruJson: any = {
141
+ headers: _.get(json, 'request.headers', []),
142
+ script: {
143
+ req: _.get(json, 'request.script.req', ''),
144
+ res: _.get(json, 'request.script.res', '')
145
+ },
146
+ vars: {
147
+ req: _.get(json, 'request.vars.req', []),
148
+ res: _.get(json, 'request.vars.res', [])
149
+ },
150
+ tests: _.get(json, 'request.tests', ''),
151
+ auth: _.get(json, 'request.auth', {}),
152
+ docs: _.get(json, 'docs', '')
153
+ };
154
+
155
+ // add meta if it exists
156
+ // this is only for folder bru file
157
+ if (json?.meta) {
158
+ collectionBruJson.meta = {
159
+ name: json.meta.name
160
+ };
161
+
162
+ // Include seq if it exists
163
+ if (json.meta.seq !== undefined) {
164
+ const sequence = json.meta.seq;
165
+ collectionBruJson.meta.seq = !isNaN(sequence) ? Number(sequence) : 1;
166
+ }
167
+ }
168
+
169
+ if (!isFolder) {
170
+ collectionBruJson.auth = _.get(json, 'request.auth', {});
171
+ }
172
+
173
+ return _jsonToCollectionBru(collectionBruJson);
174
+ } catch (error) {
175
+ throw error;
176
+ }
177
+ };
178
+
179
+ export const bruEnvironmentToJson = (bru: string): any => {
180
+ try {
181
+ const json = bruToEnvJsonV2(bru);
182
+
183
+ // the app env format requires each variable to have a type
184
+ // this need to be evaluated and safely removed
185
+ // i don't see it being used in schema validation
186
+ if (json && json.variables && json.variables.length) {
187
+ _.each(json.variables, (v: any) => (v.type = 'text'));
188
+ }
189
+
190
+ return json;
191
+ } catch (error) {
192
+ return Promise.reject(error);
193
+ }
194
+ };
195
+
196
+ export const jsonEnvironmentToBru = (json: any): string => {
197
+ try {
198
+ const bru = envJsonToBruV2(json);
199
+ return bru;
200
+ } catch (error) {
201
+ throw error;
202
+ }
203
+ };
package/src/index.ts ADDED
@@ -0,0 +1,100 @@
1
+ import {
2
+ bruRequestToJson,
3
+ jsonRequestToBru,
4
+ bruCollectionToJson,
5
+ jsonCollectionToBru,
6
+ bruEnvironmentToJson,
7
+ jsonEnvironmentToBru
8
+ } from './formats/bru';
9
+ import { dotenvToJson } from '@usebruno/lang';
10
+ import BruParserWorker from './workers';
11
+ import {
12
+ ParseOptions,
13
+ StringifyOptions,
14
+ ParsedRequest,
15
+ ParsedCollection,
16
+ ParsedEnvironment
17
+ } from './types';
18
+
19
+ export const parseRequest = (content: string, options: ParseOptions = { format: 'bru' }): any => {
20
+ if (options.format === 'bru') {
21
+ return bruRequestToJson(content);
22
+ }
23
+ throw new Error(`Unsupported format: ${options.format}`);
24
+ };
25
+
26
+ export const stringifyRequest = (requestObj: ParsedRequest, options: StringifyOptions = { format: 'bru' }): string => {
27
+ if (options.format === 'bru') {
28
+ return jsonRequestToBru(requestObj);
29
+ }
30
+ throw new Error(`Unsupported format: ${options.format}`);
31
+ };
32
+
33
+ let globalWorkerInstance: BruParserWorker | null = null;
34
+
35
+ const getWorkerInstance = (): BruParserWorker => {
36
+ if (!globalWorkerInstance) {
37
+ globalWorkerInstance = new BruParserWorker();
38
+ }
39
+ return globalWorkerInstance;
40
+ };
41
+
42
+ export const parseRequestViaWorker = async (content: string): Promise<any> => {
43
+ const fileParserWorker = getWorkerInstance();
44
+ return await fileParserWorker.parseRequest(content);
45
+ };
46
+
47
+ export const stringifyRequestViaWorker = async (requestObj: any): Promise<string> => {
48
+ const fileParserWorker = getWorkerInstance();
49
+ return await fileParserWorker.stringifyRequest(requestObj);
50
+ };
51
+
52
+ export const parseCollection = (content: string, options: ParseOptions = { format: 'bru' }): any => {
53
+ if (options.format === 'bru') {
54
+ return bruCollectionToJson(content);
55
+ }
56
+ throw new Error(`Unsupported format: ${options.format}`);
57
+ };
58
+
59
+ export const stringifyCollection = (collectionObj: ParsedCollection, options: StringifyOptions = { format: 'bru' }): string => {
60
+ if (options.format === 'bru') {
61
+ return jsonCollectionToBru(collectionObj, false);
62
+ }
63
+ throw new Error(`Unsupported format: ${options.format}`);
64
+ };
65
+
66
+ export const parseFolder = (content: string, options: ParseOptions = { format: 'bru' }): any => {
67
+ if (options.format === 'bru') {
68
+ return bruCollectionToJson(content);
69
+ }
70
+ throw new Error(`Unsupported format: ${options.format}`);
71
+ };
72
+
73
+ export const stringifyFolder = (folderObj: any, options: StringifyOptions = { format: 'bru' }): string => {
74
+ if (options.format === 'bru') {
75
+ return jsonCollectionToBru(folderObj, true);
76
+ }
77
+ throw new Error(`Unsupported format: ${options.format}`);
78
+ };
79
+
80
+ export const parseEnvironment = (content: string, options: ParseOptions = { format: 'bru' }): any => {
81
+ if (options.format === 'bru') {
82
+ return bruEnvironmentToJson(content);
83
+ }
84
+ throw new Error(`Unsupported format: ${options.format}`);
85
+ };
86
+
87
+ export const stringifyEnvironment = (envObj: ParsedEnvironment, options: StringifyOptions = { format: 'bru' }): string => {
88
+ if (options.format === 'bru') {
89
+ return jsonEnvironmentToBru(envObj);
90
+ }
91
+ throw new Error(`Unsupported format: ${options.format}`);
92
+ };
93
+
94
+
95
+ export const parseDotEnv = (content: string): Record<string, string> => {
96
+ return dotenvToJson(content);
97
+ };
98
+
99
+ export { BruParserWorker };
100
+ export * from './types';
@@ -0,0 +1,9 @@
1
+ declare module '@usebruno/lang' {
2
+ export function bruToJsonV2(bruContent: string): any;
3
+ export function jsonToBruV2(jsonData: any): string;
4
+ export function bruToEnvJsonV2(bruContent: string): any;
5
+ export function envJsonToBruV2(jsonData: any): string;
6
+ export function collectionBruToJson(bruContent: string): any;
7
+ export function jsonToCollectionBru(jsonData: any): string;
8
+ export function dotenvToJson(envContent: string): Record<string, string>;
9
+ }
package/src/types.ts ADDED
@@ -0,0 +1,141 @@
1
+ export interface ParseOptions {
2
+ format?: 'bru' | 'yaml';
3
+ }
4
+
5
+ export interface StringifyOptions {
6
+ format?: 'bru' | 'yaml';
7
+ }
8
+
9
+ export interface RequestBody {
10
+ mode?: string;
11
+ raw?: string;
12
+ formUrlEncoded?: Array<{ name: string; value: string; enabled: boolean }>;
13
+ multipartForm?: Array<{ name: string; value: string; type: string; enabled: boolean }>;
14
+ json?: string;
15
+ xml?: string;
16
+ sparql?: string;
17
+ graphql?: {
18
+ query?: string;
19
+ variables?: string;
20
+ };
21
+ }
22
+
23
+ export interface AuthConfig {
24
+ mode?: string;
25
+ basic?: {
26
+ username?: string;
27
+ password?: string;
28
+ };
29
+ bearer?: {
30
+ token?: string;
31
+ };
32
+ apikey?: {
33
+ key?: string;
34
+ value?: string;
35
+ placement?: string;
36
+ };
37
+ awsv4?: {
38
+ accessKeyId?: string;
39
+ secretAccessKey?: string;
40
+ sessionToken?: string;
41
+ service?: string;
42
+ region?: string;
43
+ profileName?: string;
44
+ };
45
+ oauth2?: {
46
+ grantType?: string;
47
+ callbackUrl?: string;
48
+ authorizationUrl?: string;
49
+ accessTokenUrl?: string;
50
+ clientId?: string;
51
+ clientSecret?: string;
52
+ scope?: string;
53
+ state?: string;
54
+ pkce?: boolean;
55
+ };
56
+ }
57
+
58
+ export interface RequestParam {
59
+ name: string;
60
+ value: string;
61
+ enabled: boolean;
62
+ }
63
+
64
+ export interface RequestHeader {
65
+ name: string;
66
+ value: string;
67
+ enabled: boolean;
68
+ }
69
+
70
+ export interface RequestAssertion {
71
+ name: string;
72
+ value: string;
73
+ enabled: boolean;
74
+ }
75
+
76
+ export interface RequestVars {
77
+ req?: Array<{ name: string; value: string; enabled: boolean }>;
78
+ res?: Array<{ name: string; value: string; enabled: boolean }>;
79
+ }
80
+
81
+ export interface RequestScript {
82
+ req?: string;
83
+ res?: string;
84
+ }
85
+
86
+ export interface RequestSettings {
87
+ [key: string]: any;
88
+ }
89
+
90
+ export interface RequestData {
91
+ method: string;
92
+ url: string;
93
+ params: RequestParam[];
94
+ headers: RequestHeader[];
95
+ auth: AuthConfig;
96
+ body: RequestBody;
97
+ script: RequestScript;
98
+ vars: RequestVars;
99
+ assertions: RequestAssertion[];
100
+ tests: string;
101
+ docs: string;
102
+ }
103
+
104
+ export interface ParsedRequest {
105
+ type: 'http-request' | 'graphql-request';
106
+ name: string;
107
+ seq: number;
108
+ settings: RequestSettings;
109
+ tags: string[];
110
+ request: RequestData;
111
+ }
112
+
113
+ export interface ParsedCollection {
114
+ name: string;
115
+ type?: string;
116
+ version?: string;
117
+ [key: string]: any;
118
+ }
119
+
120
+ export interface EnvironmentVariable {
121
+ name: string;
122
+ value: string;
123
+ enabled: boolean;
124
+ }
125
+
126
+ export interface ParsedEnvironment {
127
+ variables: EnvironmentVariable[];
128
+ }
129
+
130
+ export interface WorkerTask {
131
+ data: any;
132
+ priority: number;
133
+ scriptPath: string;
134
+ taskType?: 'parse' | 'stringify';
135
+ resolve?: (value: any) => void;
136
+ reject?: (reason?: any) => void;
137
+ }
138
+
139
+ export interface Lane {
140
+ maxSize: number;
141
+ }
@@ -0,0 +1,114 @@
1
+ import { Worker } from 'node:worker_threads';
2
+
3
+ interface QueuedTask {
4
+ priority: number;
5
+ scriptPath: string;
6
+ data: any;
7
+ taskType: 'parse' | 'stringify';
8
+ resolve?: (value: any) => void;
9
+ reject?: (reason?: any) => void;
10
+ }
11
+
12
+ class WorkerQueue {
13
+ private queue: QueuedTask[];
14
+ private isProcessing: boolean;
15
+ private workers: Record<string, Worker>;
16
+
17
+ constructor() {
18
+ this.queue = [];
19
+ this.isProcessing = false;
20
+ this.workers = {};
21
+ }
22
+
23
+ async getWorkerForScriptPath(scriptPath: string) {
24
+ if (!this.workers) this.workers = {};
25
+ let worker = this.workers[scriptPath];
26
+ if (!worker || worker.threadId === -1) {
27
+ this.workers[scriptPath] = worker = new Worker(scriptPath);
28
+ }
29
+ return worker;
30
+ }
31
+
32
+ async enqueue(task: QueuedTask) {
33
+ const { priority, scriptPath, data, taskType } = task;
34
+
35
+ return new Promise((resolve, reject) => {
36
+ this.queue.push({ priority, scriptPath, data, taskType, resolve, reject });
37
+ this.queue?.sort((taskX, taskY) => taskX?.priority - taskY?.priority);
38
+ this.processQueue();
39
+ });
40
+ }
41
+
42
+ async processQueue() {
43
+ if (this.isProcessing || this.queue.length === 0){
44
+ return;
45
+ }
46
+
47
+ this.isProcessing = true;
48
+ const { scriptPath, data, taskType, resolve, reject } = this.queue.shift() as QueuedTask;
49
+
50
+ try {
51
+ const result = await this.runWorker({ scriptPath, data, taskType });
52
+ resolve?.(result);
53
+ } catch (error) {
54
+ reject?.(error);
55
+ } finally {
56
+ this.isProcessing = false;
57
+ this.processQueue();
58
+ }
59
+ }
60
+
61
+ async runWorker({ scriptPath, data, taskType }: { scriptPath: string; data: any; taskType: 'parse' | 'stringify' }) {
62
+ return new Promise(async (resolve, reject) => {
63
+ let worker = await this.getWorkerForScriptPath(scriptPath);
64
+
65
+ const messageHandler = (data: any) => {
66
+ worker.off('message', messageHandler);
67
+ worker.off('error', errorHandler);
68
+ worker.off('exit', exitHandler);
69
+
70
+ if (data?.error) {
71
+ reject(new Error(data?.error));
72
+ } else {
73
+ resolve(data);
74
+ }
75
+ };
76
+
77
+ const errorHandler = (error: Error) => {
78
+ worker.off('message', messageHandler);
79
+ worker.off('error', errorHandler);
80
+ worker.off('exit', exitHandler);
81
+ reject(error);
82
+ };
83
+
84
+ const exitHandler = (code: number) => {
85
+ worker.off('message', messageHandler);
86
+ worker.off('error', errorHandler);
87
+ worker.off('exit', exitHandler);
88
+ // Remove dead worker from cache
89
+ delete this.workers[scriptPath];
90
+ reject(new Error(`Worker stopped with exit code ${code}`));
91
+ };
92
+
93
+ worker.on('message', messageHandler);
94
+ worker.on('error', errorHandler);
95
+ worker.on('exit', exitHandler);
96
+
97
+ worker.postMessage({ taskType, data });
98
+ });
99
+ }
100
+
101
+ async cleanup() {
102
+ const promises = Object.values(this.workers).map(worker => {
103
+ if (worker.threadId !== -1) {
104
+ return worker.terminate();
105
+ }
106
+ return Promise.resolve();
107
+ });
108
+
109
+ await Promise.allSettled(promises);
110
+ this.workers = {};
111
+ }
112
+ }
113
+
114
+ export default WorkerQueue;
@@ -0,0 +1,86 @@
1
+ import WorkerQueue from './WorkerQueue';
2
+ import { Lane } from '../types';
3
+ import path from 'node:path';
4
+
5
+ const sizeInMB = (size: number): number => {
6
+ return size / (1024 * 1024);
7
+ }
8
+
9
+ const getSize = (data: any): number => {
10
+ return sizeInMB(typeof data === 'string' ? Buffer.byteLength(data, 'utf8') : Buffer.byteLength(JSON.stringify(data), 'utf8'));
11
+ }
12
+
13
+ /**
14
+ * Lanes are used to determine which worker queue to use based on the size of the data.
15
+ *
16
+ * The first lane is for smaller files (<0.1MB), the second lane is for larger files (>=0.1MB).
17
+ * This helps with parsing performance.
18
+ */
19
+ const LANES: Lane[] = [{
20
+ maxSize: 0.005
21
+ },{
22
+ maxSize: 0.1
23
+ },{
24
+ maxSize: 1
25
+ },{
26
+ maxSize: 10
27
+ },{
28
+ maxSize: 100
29
+ }];
30
+
31
+ interface WorkerQueueWithSize {
32
+ maxSize: number;
33
+ workerQueue: WorkerQueue;
34
+
35
+ }
36
+
37
+ class BruParserWorker {
38
+ private workerQueues: WorkerQueueWithSize[];
39
+
40
+ constructor() {
41
+ this.workerQueues = LANES?.map(lane => ({
42
+ maxSize: lane?.maxSize,
43
+ workerQueue: new WorkerQueue()
44
+ }));
45
+ }
46
+
47
+ private getWorkerQueue(size: number): WorkerQueue {
48
+ // Find the first queue that can handle the given size
49
+ // or fallback to the last queue for largest files
50
+ const queueForSize = this.workerQueues.find((queue) =>
51
+ queue.maxSize >= size
52
+ );
53
+
54
+ return queueForSize?.workerQueue ?? this.workerQueues[this.workerQueues.length - 1].workerQueue;
55
+ }
56
+
57
+ private async enqueueTask({ data, taskType }: { data: any; taskType: 'parse' | 'stringify' }): Promise<any> {
58
+ const size = getSize(data);
59
+ const workerQueue = this.getWorkerQueue(size);
60
+ const workerScriptPath = path.join(__dirname, './workers/worker-script.js');
61
+
62
+ return workerQueue.enqueue({
63
+ data,
64
+ priority: size,
65
+ scriptPath: workerScriptPath,
66
+ taskType,
67
+ });
68
+ }
69
+
70
+ async parseRequest(data: any): Promise<any> {
71
+ return this.enqueueTask({ data, taskType: 'parse' });
72
+ }
73
+
74
+ async stringifyRequest(data: any): Promise<any> {
75
+ return this.enqueueTask({ data, taskType: 'stringify' });
76
+ }
77
+
78
+ async cleanup(): Promise<void> {
79
+ const cleanupPromises = this.workerQueues.map(({ workerQueue }) =>
80
+ workerQueue.cleanup()
81
+ );
82
+ await Promise.allSettled(cleanupPromises);
83
+ }
84
+ }
85
+
86
+ export default BruParserWorker;
@@ -0,0 +1,27 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import { bruRequestToJson, jsonRequestToBru } from '../formats/bru';
3
+
4
+ interface WorkerMessage {
5
+ taskType: 'parse' | 'stringify';
6
+ data: any;
7
+ }
8
+
9
+ parentPort?.on('message', async (message: WorkerMessage) => {
10
+ try {
11
+ const { taskType, data } = message;
12
+ let result: any;
13
+
14
+ if (taskType === 'parse') {
15
+ result = bruRequestToJson(data);
16
+ } else if (taskType === 'stringify') {
17
+ result = jsonRequestToBru(data);
18
+ } else {
19
+ throw new Error(`Unknown task type: ${taskType}`);
20
+ }
21
+
22
+ parentPort?.postMessage(result);
23
+ } catch (error: any) {
24
+ console.error('Worker error:', error);
25
+ parentPort?.postMessage({ error: error?.message });
26
+ }
27
+ });