@gaurav_bhandari/common-frontend-services 1.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.
Files changed (56) hide show
  1. package/README.md +40 -0
  2. package/dist/bin/cli.js +22 -0
  3. package/dist/src/cli/commands/add.js +104 -0
  4. package/dist/src/cli/commands/list.js +19 -0
  5. package/dist/src/cli/registry.js +125 -0
  6. package/package.json +39 -0
  7. package/src/services/API-Service/baseApiService.ts +296 -0
  8. package/src/services/API-Service/csrfAPIService.ts +93 -0
  9. package/src/services/API-Service/docs/WhatIsBaseApiService.md +121 -0
  10. package/src/services/API-Service/docs/WhatIsCsrfApiService.md +67 -0
  11. package/src/services/Architecture-Reports-Service/architecture-checklist.md +54 -0
  12. package/src/services/Architecture-Reports-Service/report-generation-prompt.md +60 -0
  13. package/src/services/Authorization-Service/docs/WhatIsRBACService.md +112 -0
  14. package/src/services/Authorization-Service/permissions.ts +16 -0
  15. package/src/services/Authorization-Service/rbacService.ts +63 -0
  16. package/src/services/Authorization-Service/rolePermissions.ts +10 -0
  17. package/src/services/Authorization-Service/userPermissions/adminPermissions.ts +13 -0
  18. package/src/services/Cache-Service/README.md +50 -0
  19. package/src/services/Cache-Service/cacheService.ts +142 -0
  20. package/src/services/Cache-Service/simple-explanation.md +55 -0
  21. package/src/services/Cache-Service/sw-strategies.ts +51 -0
  22. package/src/services/Date-Service/dateService.ts +93 -0
  23. package/src/services/Date-Service/docs/WhatIsDateService.md +63 -0
  24. package/src/services/Environment-Config-Service/docs/WhatIsEnvironmentConfigService.md +68 -0
  25. package/src/services/Environment-Config-Service/environmentConfigService.ts +112 -0
  26. package/src/services/Environment-Config-Service/simple-explanation.md +49 -0
  27. package/src/services/Feature-Flag-Service/docs/WhatIsFeatureFlagService.md +98 -0
  28. package/src/services/Feature-Flag-Service/featureFlagService.ts +91 -0
  29. package/src/services/File-Service/docs/WhatIsFileService.md +78 -0
  30. package/src/services/File-Service/fileService.ts +224 -0
  31. package/src/services/File-Service/simple-explanation.md +49 -0
  32. package/src/services/Google-Login-Service/README.md +48 -0
  33. package/src/services/Google-Login-Service/googleLoginService.ts +209 -0
  34. package/src/services/GraphQl-Service/docs/WhatIsGraphQLService.md +101 -0
  35. package/src/services/GraphQl-Service/graphqlService.ts +81 -0
  36. package/src/services/Logger-Service/README.md +44 -0
  37. package/src/services/Logger-Service/loggerService.ts +125 -0
  38. package/src/services/Logger-Service/simple-explanation.md +43 -0
  39. package/src/services/Logger-Service/verify-logger.ts +62 -0
  40. package/src/services/Monitoring-Service/README.md +48 -0
  41. package/src/services/Monitoring-Service/monitoringService.ts +229 -0
  42. package/src/services/Monitoring-Service/simple-explanation.md +48 -0
  43. package/src/services/Monitoring-Service/verify-monitoring.ts +74 -0
  44. package/src/services/Security-Reports-Service/report-generation-prompt.md +52 -0
  45. package/src/services/Security-Reports-Service/security-checklist.md +66 -0
  46. package/src/services/Storage-Service/docs/WhatIsStorageService.md +70 -0
  47. package/src/services/Storage-Service/indexedDBService.ts +103 -0
  48. package/src/services/Storage-Service/localStorageService.ts +42 -0
  49. package/src/services/Storage-Service/sessionStorageService.ts +42 -0
  50. package/src/services/Validation-Service/form.ts +66 -0
  51. package/src/services/Validation-Service/rules.ts +338 -0
  52. package/src/services/Worker-Service/docs/WhatIsWorkerService.md +57 -0
  53. package/src/services/Worker-Service/workerService.ts +122 -0
  54. package/src/services/i18n-Service/docs/WhatIsI18nService.md +108 -0
  55. package/src/services/i18n-Service/i18nService.ts +127 -0
  56. package/src/services/i18n-Service/locales/en.json +17 -0
@@ -0,0 +1,42 @@
1
+ export class LocalStorageService {
2
+ private static instance: LocalStorageService;
3
+
4
+ private constructor() {}
5
+
6
+ public static getInstance(): LocalStorageService {
7
+ if (!LocalStorageService.instance) {
8
+ LocalStorageService.instance = new LocalStorageService();
9
+ }
10
+ return LocalStorageService.instance;
11
+ }
12
+
13
+ public setItem<T>(key: string, value: T): void {
14
+ try {
15
+ const serialized = JSON.stringify(value);
16
+ localStorage.setItem(key, serialized);
17
+ } catch (error) {
18
+ console.error("LocalStorage setItem failed:", error);
19
+ }
20
+ }
21
+
22
+ public getItem<T>(key: string, defaultValue: T | null = null): T | null {
23
+ try {
24
+ const serialized = localStorage.getItem(key);
25
+ if (!serialized) return defaultValue;
26
+ return JSON.parse(serialized) as T;
27
+ } catch (error) {
28
+ console.error("LocalStorage getItem failed:", error);
29
+ return defaultValue;
30
+ }
31
+ }
32
+
33
+ public removeItem(key: string): void {
34
+ localStorage.removeItem(key);
35
+ }
36
+
37
+ public clear(): void {
38
+ localStorage.clear();
39
+ }
40
+ }
41
+
42
+ export const localStorageService = LocalStorageService.getInstance();
@@ -0,0 +1,42 @@
1
+ export class SessionStorageService {
2
+ private static instance: SessionStorageService;
3
+
4
+ private constructor() {}
5
+
6
+ public static getInstance(): SessionStorageService {
7
+ if (!SessionStorageService.instance) {
8
+ SessionStorageService.instance = new SessionStorageService();
9
+ }
10
+ return SessionStorageService.instance;
11
+ }
12
+
13
+ public setItem<T>(key: string, value: T): void {
14
+ try {
15
+ const serialized = JSON.stringify(value);
16
+ sessionStorage.setItem(key, serialized);
17
+ } catch (error) {
18
+ console.error("SessionStorage setItem failed:", error);
19
+ }
20
+ }
21
+
22
+ public getItem<T>(key: string, defaultValue: T | null = null): T | null {
23
+ try {
24
+ const serialized = sessionStorage.getItem(key);
25
+ if (!serialized) return defaultValue;
26
+ return JSON.parse(serialized) as T;
27
+ } catch (error) {
28
+ console.error("SessionStorage getItem failed:", error);
29
+ return defaultValue;
30
+ }
31
+ }
32
+
33
+ public removeItem(key: string): void {
34
+ sessionStorage.removeItem(key);
35
+ }
36
+
37
+ public clear(): void {
38
+ sessionStorage.clear();
39
+ }
40
+ }
41
+
42
+ export const sessionStorageService = SessionStorageService.getInstance();
@@ -0,0 +1,66 @@
1
+ import type { RuleFn } from "./rules";
2
+
3
+ export interface FieldConfig<T = any> {
4
+ rules: RuleFn<T>[];
5
+ value?: T;
6
+ }
7
+
8
+ export interface ValidationResult {
9
+ isValid: boolean;
10
+ errors: Record<string, string[]>;
11
+ }
12
+
13
+ export class FormValidator<T extends Record<string, any>> {
14
+ private schema: Record<keyof T, RuleFn[]>;
15
+
16
+ constructor(schema: Record<keyof T, RuleFn[]>) {
17
+ this.schema = schema;
18
+ }
19
+
20
+ /**
21
+ * Validate a full data object against the schema
22
+ */
23
+ public validate(data: T): ValidationResult {
24
+ const errors: Record<string, string[]> = {};
25
+ let isValid = true;
26
+
27
+ for (const key in this.schema) {
28
+ const fieldRules = this.schema[key];
29
+ const value = data[key];
30
+ const fieldErrors: string[] = [];
31
+
32
+ for (const rule of fieldRules) {
33
+ const result = rule(value);
34
+ if (result !== true) {
35
+ fieldErrors.push(
36
+ typeof result === "string" ? result : "Invalid value",
37
+ );
38
+ isValid = false;
39
+ }
40
+ }
41
+
42
+ if (fieldErrors.length > 0) {
43
+ errors[key] = fieldErrors;
44
+ }
45
+ }
46
+
47
+ return { isValid, errors };
48
+ }
49
+
50
+ /**
51
+ * Validate a single field
52
+ */
53
+ public validateField(key: keyof T, value: any): string[] {
54
+ const rules = this.schema[key] || [];
55
+ const errors: string[] = [];
56
+
57
+ for (const rule of rules) {
58
+ const result = rule(value);
59
+ if (result !== true) {
60
+ errors.push(typeof result === "string" ? result : "Invalid value");
61
+ }
62
+ }
63
+
64
+ return errors;
65
+ }
66
+ }
@@ -0,0 +1,338 @@
1
+ // All rules return true or a string (error message) to plug directly into v-text-field `rules` prop
2
+
3
+ export type RuleFn<T = any> = (value: T) => true | string;
4
+
5
+ const isEmpty = (v: unknown) =>
6
+ v === null ||
7
+ v === undefined ||
8
+ (typeof v === "string" && v.trim() === "") ||
9
+ (Array.isArray(v) && v.length === 0);
10
+
11
+ export const required =
12
+ (msg = "This field is required"): RuleFn =>
13
+ (value) => {
14
+ return !isEmpty(value) || msg;
15
+ };
16
+
17
+ export const email =
18
+ (msg = "Please enter a valid email"): RuleFn<string> =>
19
+ (value) => {
20
+ if (isEmpty(value)) return true; // allow empty unless required is also used
21
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value).trim()) || msg;
22
+ };
23
+
24
+ // At least 6 chars, 1 uppercase, 1 lowercase, 1 number, 1 special character
25
+ export const password =
26
+ (
27
+ msg = "Password must be at least 6 characters long and include uppercase, lowercase, numbers and a special character",
28
+ ): RuleFn<string> =>
29
+ (value) => {
30
+ if (isEmpty(value)) return true;
31
+ const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9\s])[^\s]{6,}$/;
32
+ return re.test(String(value).trim()) || msg;
33
+ };
34
+
35
+ // check for leading or trailing spaces
36
+ export const noLeadingTrailingSpaces =
37
+ (msg = "No leading or trailing spaces allowed"): RuleFn<string> =>
38
+ (value) => {
39
+ if (isEmpty(value)) return true;
40
+ const str = String(value);
41
+ return str === str.trim() || msg;
42
+ };
43
+
44
+ // international phone (10+ digits)
45
+ export const phone =
46
+ (msg = "Please enter a valid phone number"): RuleFn<string> =>
47
+ (value) => {
48
+ if (isEmpty(value)) return true;
49
+ return /^\d{10,15}$/.test(String(value).trim()) || msg;
50
+ };
51
+
52
+ // Builders for custom needs
53
+ export const minLength =
54
+ (len: number, msg?: string): RuleFn<string> =>
55
+ (value) => {
56
+ if (isEmpty(value)) return true;
57
+ return (
58
+ String(value).length >= len || msg || `Must be at least ${len} characters`
59
+ );
60
+ };
61
+
62
+ export const maxLength =
63
+ (len: number, msg?: string): RuleFn<string> =>
64
+ (value) => {
65
+ if (isEmpty(value)) return true;
66
+ return (
67
+ String(value).length <= len || msg || `Must be at most ${len} characters`
68
+ );
69
+ };
70
+
71
+ export const pattern =
72
+ (re: RegExp, msg = "Invalid value"): RuleFn<string> =>
73
+ (value) => {
74
+ if (isEmpty(value)) return true;
75
+ return re.test(String(value)) || msg;
76
+ };
77
+
78
+ export const equals =
79
+ (
80
+ other: () => string | number | boolean | null | undefined,
81
+ msg = "Values do not match",
82
+ ): RuleFn<any> =>
83
+ (value) => {
84
+ return value === other() || msg;
85
+ };
86
+
87
+ export const notEquals =
88
+ (
89
+ other: () => string | number | boolean | null | undefined,
90
+ msg = "Values must not match",
91
+ ): RuleFn<any> =>
92
+ (value) => {
93
+ return value !== other() || msg;
94
+ };
95
+
96
+ // File-specific validation rules
97
+ export const fileSize =
98
+ (maxBytes: number, msg?: string): RuleFn<File> =>
99
+ (file) => {
100
+ if (!file) return true;
101
+ const maxMB = Math.round((maxBytes / (1024 * 1024)) * 10) / 10;
102
+ return (
103
+ file.size <= maxBytes || msg || `File size must be less than ${maxMB}MB`
104
+ );
105
+ };
106
+
107
+ export const minFileSize =
108
+ (minBytes: number, msg?: string): RuleFn<File> =>
109
+ (file) => {
110
+ if (!file) return true;
111
+ const minMB = Math.round((minBytes / (1024 * 1024)) * 10) / 10;
112
+ return (
113
+ file.size >= minBytes || msg || `File size must be at least ${minMB}MB`
114
+ );
115
+ };
116
+
117
+ export const fileType =
118
+ (allowedTypes: string[], msg?: string): RuleFn<File> =>
119
+ (file) => {
120
+ if (!file) return true;
121
+ return (
122
+ allowedTypes.includes(file.type) ||
123
+ msg ||
124
+ `File type ${file.type} is not allowed`
125
+ );
126
+ };
127
+
128
+ export const fileExtension =
129
+ (allowedExtensions: string[], msg?: string): RuleFn<File> =>
130
+ (file) => {
131
+ if (!file) return true;
132
+ const fileExt = "." + file.name.split(".").pop()?.toLowerCase();
133
+ const allowedExtsLower = allowedExtensions.map((ext) => ext.toLowerCase());
134
+ return (
135
+ allowedExtsLower.includes(fileExt) ||
136
+ msg ||
137
+ `File extension ${fileExt} is not allowed`
138
+ );
139
+ };
140
+
141
+ export const imageFile =
142
+ (msg = "Please select a valid image file"): RuleFn<File> =>
143
+ (file) => {
144
+ if (!file) return true;
145
+ return file.type.startsWith("image/") || msg;
146
+ };
147
+
148
+ export const pdfFile =
149
+ (msg = "Please select a valid PDF file"): RuleFn<File> =>
150
+ (file) => {
151
+ if (!file) return true;
152
+ return file.type === "application/pdf" || msg;
153
+ };
154
+
155
+ // Helper to combine multiple rules into one rule function (short-circuits on first error)
156
+ export const composeRules =
157
+ (...rules: RuleFn[]): RuleFn =>
158
+ (value) => {
159
+ for (const rule of rules) {
160
+ const res = rule(value);
161
+ if (res !== true) return res;
162
+ }
163
+ return true;
164
+ };
165
+
166
+ export const numericValue =
167
+ (msg = "Please enter a valid number"): RuleFn<string> =>
168
+ (value) => {
169
+ if (isEmpty(value)) return true;
170
+ const numberRegex = /^\d*$/;
171
+ return numberRegex.test(String(value)) || msg;
172
+ };
173
+
174
+ // Date range validation rules
175
+ export const dateRange =
176
+ (
177
+ _msg = "Please select a valid date range",
178
+ ): RuleFn<{ from: string | null; to: string | null }> =>
179
+ (value) => {
180
+ if (!value || (isEmpty(value.from) && isEmpty(value.to))) return true;
181
+
182
+ // If only one date is selected, both should be selected
183
+ if ((value.from && !value.to) || (!value.from && value.to)) {
184
+ return "Please select both from and to dates";
185
+ }
186
+
187
+ // If both dates are selected, validate the range
188
+ if (value.from && value.to) {
189
+ const fromDate = new Date(value.from);
190
+ const toDate = new Date(value.to);
191
+
192
+ if (fromDate >= toDate) {
193
+ return "From date must be before to date";
194
+ }
195
+ }
196
+
197
+ return true;
198
+ };
199
+
200
+ export const dateRangeRequired =
201
+ (
202
+ msg = "Date range is required",
203
+ ): RuleFn<{ from: string | null; to: string | null }> =>
204
+ (value) => {
205
+ if (!value || isEmpty(value.from) || isEmpty(value.to)) {
206
+ return msg;
207
+ }
208
+ return true;
209
+ };
210
+
211
+ export const maxDateRange =
212
+ (
213
+ maxDays: number,
214
+ msg?: string,
215
+ ): RuleFn<{ from: string | null; to: string | null }> =>
216
+ (value) => {
217
+ if (!value || isEmpty(value.from) || isEmpty(value.to)) return true;
218
+
219
+ const fromDate = new Date(value.from as string);
220
+ const toDate = new Date(value.to as string);
221
+ const diffTime = toDate.getTime() - fromDate.getTime();
222
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
223
+
224
+ return (
225
+ diffDays <= maxDays || msg || `Date range cannot exceed ${maxDays} days`
226
+ );
227
+ };
228
+
229
+ export const positiveNumber =
230
+ (msg = "Please enter a positive number"): RuleFn<string | number> =>
231
+ (value) => {
232
+ if (isEmpty(value)) return true;
233
+ const num =
234
+ typeof value === "number"
235
+ ? value
236
+ : parseFloat(String(value).replace(/,/g, ""));
237
+ return (!isNaN(num) && num > 0) || msg;
238
+ };
239
+
240
+ export const nonNegativeNumber =
241
+ (msg = "Please enter a non-negative number"): RuleFn<string | number> =>
242
+ (value) => {
243
+ if (isEmpty(value)) return true;
244
+ const num =
245
+ typeof value === "number"
246
+ ? value
247
+ : parseFloat(String(value).replace(/,/g, ""));
248
+ return (!isNaN(num) && num >= 0) || msg;
249
+ };
250
+
251
+ export const integerValue =
252
+ (msg = "Please enter a whole number"): RuleFn<string | number> =>
253
+ (value) => {
254
+ if (isEmpty(value)) return true;
255
+ const num =
256
+ typeof value === "number"
257
+ ? value
258
+ : parseFloat(String(value).replace(/,/g, ""));
259
+ return (!isNaN(num) && Number.isInteger(num)) || msg;
260
+ };
261
+
262
+ export const minValue =
263
+ (min: number, msg?: string): RuleFn<string | number> =>
264
+ (value) => {
265
+ if (isEmpty(value)) return true;
266
+ const num =
267
+ typeof value === "number"
268
+ ? value
269
+ : parseFloat(String(value).replace(/,/g, ""));
270
+ return (
271
+ (!isNaN(num) && num >= min) || msg || `Value must be at least ${min}`
272
+ );
273
+ };
274
+
275
+ export const maxValue =
276
+ (max: number, msg?: string): RuleFn<string | number> =>
277
+ (value) => {
278
+ if (isEmpty(value)) return true;
279
+ const num =
280
+ typeof value === "number"
281
+ ? value
282
+ : parseFloat(String(value).replace(/,/g, ""));
283
+ return (!isNaN(num) && num <= max) || msg || `Value must be at most ${max}`;
284
+ };
285
+
286
+ export const halfStepNumber =
287
+ (msg = "Please enter a number in 0.5 increments"): RuleFn<string | number> =>
288
+ (value) => {
289
+ if (isEmpty(value)) return true;
290
+ const num =
291
+ typeof value === "number"
292
+ ? value
293
+ : parseFloat(String(value).replace(/,/g, ""));
294
+ return (!isNaN(num) && (num * 2) % 1 === 0) || msg;
295
+ };
296
+
297
+ export const isNumber =
298
+ (msg = "Please enter a valid number"): RuleFn<string | number> =>
299
+ (value) => {
300
+ if (isEmpty(value)) return true;
301
+ const num =
302
+ typeof value === "number"
303
+ ? value
304
+ : parseFloat(String(value).replace(/,/g, ""));
305
+ return !isNaN(num) || msg;
306
+ };
307
+
308
+ export const Rules = {
309
+ required,
310
+ email,
311
+ password,
312
+ noLeadingTrailingSpaces,
313
+ phone,
314
+ minLength,
315
+ maxLength,
316
+ pattern,
317
+ equals,
318
+ composeRules,
319
+ numericValue,
320
+ dateRange,
321
+ dateRangeRequired,
322
+ maxDateRange,
323
+ positiveNumber,
324
+ nonNegativeNumber,
325
+ integerValue,
326
+ minValue,
327
+ maxValue,
328
+ halfStepNumber,
329
+ isNumber,
330
+ fileSize,
331
+ minFileSize,
332
+ fileType,
333
+ fileExtension,
334
+ imageFile,
335
+ pdfFile,
336
+ };
337
+
338
+ export type { RuleFn as ValidationRuleFn };
@@ -0,0 +1,57 @@
1
+ # WorkerService
2
+
3
+ The `WorkerService` allows you to offload heavy computations to a background thread without creating separate worker files. It creates workers dynamically using Blob URLs.
4
+
5
+ ## Overview
6
+
7
+ Use this service when you have CPU-intensive tasks that would otherwise block the main thread and freeze the UI (e.g., sorting large arrays, image processing, complex filtering).
8
+
9
+ ### Key Features
10
+
11
+ - **Dynamic Execution**: Pass a function and data, get a Promise back.
12
+ - **No Setup**: No need to configure webpack/vite worker loaders for simple tasks.
13
+ - **Type Safety**: Generic support for input and output types.
14
+
15
+ ## How to Use
16
+
17
+ ### 1. One-off Execution
18
+
19
+ Execute a function in a worker and get the result.
20
+
21
+ **⚠️ IMPORTANT LIMITATION**: The function passed to `execute` MUST be pure and self-contained. It **cannot** access variables from the outer scope (closures) because it is serialized to a string and run in a separate context.
22
+
23
+ ```typescript
24
+ import { workerService } from "@/Worker-Service/workerService";
25
+
26
+ // Example: Sorting a large array
27
+ const largeArray = [
28
+ /* ... 100k items ... */
29
+ ];
30
+
31
+ const sorted = await workerService.execute((data) => {
32
+ // This code runs in the worker
33
+ return data.sort((a, b) => a - b);
34
+ }, largeArray);
35
+ ```
36
+
37
+ ### 2. Long-lived Worker
38
+
39
+ If you need a worker that stays alive (e.g., for processing a stream of data), use `createWorker`.
40
+
41
+ ```typescript
42
+ const worker = workerService.createWorker(() => {
43
+ self.onmessage = (e) => {
44
+ const result = e.data * 2;
45
+ self.postMessage(result);
46
+ };
47
+ });
48
+
49
+ worker.onmessage = (e) => console.log(e.data);
50
+ worker.postMessage(10); // Logs: 20
51
+ ```
52
+
53
+ ## Best Practices
54
+
55
+ 1. **Pure Functions**: Ensure your worker function relies _only_ on its arguments.
56
+ 2. **Data Transfer**: Data passed to workers is cloned (structured clone algorithm). Avoid passing non-cloneable objects (like DOM elements or functions).
57
+ 3. **Error Handling**: The `execute` method wraps errors, so use standard `try/catch`.
@@ -0,0 +1,122 @@
1
+ export interface WorkerOptions {
2
+ timeout?: number;
3
+ }
4
+
5
+ export class WorkerService {
6
+ private static instance: WorkerService;
7
+
8
+ private constructor() {}
9
+
10
+ public static getInstance(): WorkerService {
11
+ if (!WorkerService.instance) {
12
+ WorkerService.instance = new WorkerService();
13
+ }
14
+ return WorkerService.instance;
15
+ }
16
+
17
+ /**
18
+ * Execute a function in a web worker.
19
+ * NOTE: The function `fn` must be strictly self-contained (no external closures).
20
+ *
21
+ * @param fn The function to execute.
22
+ * @param data The data to pass to the function.
23
+ * @param options Execution options (e.g., timeout).
24
+ */
25
+ public execute<T, R>(
26
+ fn: (data: T) => R,
27
+ data: T,
28
+ options?: WorkerOptions,
29
+ ): Promise<R> {
30
+ return new Promise((resolve, reject) => {
31
+ const code = `
32
+ self.onmessage = function(e) {
33
+ try {
34
+ const fn = ${fn.toString()};
35
+ const result = fn(e.data);
36
+ self.postMessage({ result, error: null });
37
+ } catch (error) {
38
+ self.postMessage({ result: null, error: error instanceof Error ? error.message : String(error) });
39
+ }
40
+ };
41
+ `;
42
+
43
+ const blob = new Blob([code], { type: "application/javascript" });
44
+ const url = URL.createObjectURL(blob);
45
+ const worker = new Worker(url);
46
+ let timeoutId: any;
47
+
48
+ const cleanup = () => {
49
+ if (timeoutId) clearTimeout(timeoutId);
50
+ worker.terminate();
51
+ URL.revokeObjectURL(url);
52
+ };
53
+
54
+ worker.onmessage = (e) => {
55
+ const { result, error } = e.data;
56
+ cleanup();
57
+ if (error) {
58
+ reject(new Error(`Worker execution failed: ${error}`));
59
+ } else {
60
+ resolve(result);
61
+ }
62
+ };
63
+
64
+ worker.onerror = (e) => {
65
+ cleanup();
66
+ reject(new Error(`Worker error: ${e.message}`));
67
+ };
68
+
69
+ if (options?.timeout) {
70
+ timeoutId = setTimeout(() => {
71
+ cleanup();
72
+ reject(new Error(`Worker timed out after ${options.timeout}ms`));
73
+ }, options.timeout);
74
+ }
75
+
76
+ worker.postMessage(data);
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Run a worker from a script file URL.
82
+ * @param scriptUrl URL of the worker script.
83
+ * @param data Data to pass.
84
+ * @param options Execution options.
85
+ */
86
+ public runWorker<T, R>(
87
+ scriptUrl: string,
88
+ data: T,
89
+ options?: WorkerOptions,
90
+ ): Promise<R> {
91
+ return new Promise((resolve, reject) => {
92
+ const worker = new Worker(scriptUrl);
93
+ let timeoutId: any;
94
+
95
+ const cleanup = () => {
96
+ if (timeoutId) clearTimeout(timeoutId);
97
+ worker.terminate();
98
+ };
99
+
100
+ worker.onmessage = (e) => {
101
+ cleanup();
102
+ resolve(e.data as R);
103
+ };
104
+
105
+ worker.onerror = (e) => {
106
+ cleanup();
107
+ reject(new Error(`Worker error: ${e.message}`));
108
+ };
109
+
110
+ if (options?.timeout) {
111
+ timeoutId = setTimeout(() => {
112
+ cleanup();
113
+ reject(new Error(`Worker timed out after ${options.timeout}ms`));
114
+ }, options.timeout);
115
+ }
116
+
117
+ worker.postMessage(data);
118
+ });
119
+ }
120
+ }
121
+
122
+ export const workerService = WorkerService.getInstance();