onelaraveljs 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 (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Development Warnings Helper
3
+ * Provides helpful warnings during development without impacting production
4
+ *
5
+ * @module helpers/devWarnings
6
+ * @author OneLaravel Team
7
+ * @since 2025-12-29
8
+ */
9
+
10
+ /**
11
+ * Check if running in development mode
12
+ * @returns {boolean}
13
+ */
14
+ function isDevelopment() {
15
+ return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
16
+ }
17
+
18
+ /**
19
+ * Warn if condition is false (development only)
20
+ *
21
+ * @param {boolean} condition - Condition to check
22
+ * @param {string} message - Warning message
23
+ * @param {*} data - Optional additional data
24
+ *
25
+ * @example
26
+ * warn(view.mounted, 'Accessing view before mount');
27
+ * warn(!circularRef, 'Circular reference detected', { view: view.path });
28
+ */
29
+ export function warn(condition, message, data = null) {
30
+ if (!condition && isDevelopment()) {
31
+ const fullMessage = `[OneLaravel Warning] ${message}`;
32
+
33
+ if (data) {
34
+ console.warn(fullMessage, data);
35
+ } else {
36
+ console.warn(fullMessage);
37
+ }
38
+
39
+ // Show stack trace in development
40
+ if (console.trace) {
41
+ console.trace();
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Deprecation warning
48
+ *
49
+ * @param {string} oldName - Deprecated feature name
50
+ * @param {string} newName - Replacement feature name
51
+ * @param {string} version - Version when it will be removed
52
+ *
53
+ * @example
54
+ * deprecate('ViewEngine', 'ViewController', '2.0.0');
55
+ */
56
+ export function deprecate(oldName, newName, version = '2.0.0') {
57
+ if (isDevelopment()) {
58
+ console.warn(
59
+ `[OneLaravel Deprecation] ${oldName} is deprecated and will be removed in v${version}. ` +
60
+ `Use ${newName} instead.`
61
+ );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Invariant check - throws error if condition is false
67
+ * Used for critical checks that should never fail
68
+ *
69
+ * @param {boolean} condition - Condition to check
70
+ * @param {string} message - Error message
71
+ * @throws {Error}
72
+ *
73
+ * @example
74
+ * invariant(App, 'App instance is required');
75
+ * invariant(viewId, 'View ID cannot be null');
76
+ */
77
+ export function invariant(condition, message) {
78
+ if (!condition) {
79
+ throw new Error(`[OneLaravel Invariant] ${message}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Log info message (development only)
85
+ *
86
+ * @param {string} message - Info message
87
+ * @param {*} data - Optional data
88
+ */
89
+ export function devLog(message, data = null) {
90
+ if (isDevelopment()) {
91
+ if (data) {
92
+ console.log(`[OneLaravel] ${message}`, data);
93
+ } else {
94
+ console.log(`[OneLaravel] ${message}`);
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Assert type at runtime (development only)
101
+ *
102
+ * @param {*} value - Value to check
103
+ * @param {string} expectedType - Expected type
104
+ * @param {string} name - Variable name
105
+ *
106
+ * @example
107
+ * assertType(viewId, 'string', 'viewId');
108
+ * assertType(config, 'object', 'config');
109
+ */
110
+ export function assertType(value, expectedType, name) {
111
+ if (!isDevelopment()) return;
112
+
113
+ const actualType = typeof value;
114
+
115
+ if (actualType !== expectedType) {
116
+ console.warn(
117
+ `[OneLaravel Type Warning] Expected ${name} to be ${expectedType}, ` +
118
+ `but got ${actualType}`,
119
+ { value }
120
+ );
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Check for circular references (development only)
126
+ *
127
+ * @param {Object} obj - Object to check
128
+ * @param {string} path - Object path for error message
129
+ * @returns {boolean} True if circular reference detected
130
+ *
131
+ * @example
132
+ * if (checkCircular(view, 'view')) {
133
+ * warn(false, 'Circular reference in view');
134
+ * }
135
+ */
136
+ export function checkCircular(obj, path = 'root') {
137
+ if (!isDevelopment()) return false;
138
+
139
+ const seen = new WeakSet();
140
+
141
+ function detect(obj, currentPath) {
142
+ if (obj && typeof obj === 'object') {
143
+ if (seen.has(obj)) {
144
+ console.warn(
145
+ `[OneLaravel Circular Reference] Detected at ${currentPath}`
146
+ );
147
+ return true;
148
+ }
149
+
150
+ seen.add(obj);
151
+
152
+ for (const key in obj) {
153
+ if (obj.hasOwnProperty(key)) {
154
+ if (detect(obj[key], `${currentPath}.${key}`)) {
155
+ return true;
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ return false;
162
+ }
163
+
164
+ return detect(obj, path);
165
+ }
166
+
167
+ /**
168
+ * Performance warning if execution time exceeds threshold
169
+ *
170
+ * @param {Function} fn - Function to measure
171
+ * @param {string} label - Label for measurement
172
+ * @param {number} threshold - Threshold in ms (default: 16ms for 60fps)
173
+ * @returns {*} Function result
174
+ *
175
+ * @example
176
+ * warnIfSlow(() => view.render(), 'render', 16);
177
+ */
178
+ export function warnIfSlow(fn, label, threshold = 16) {
179
+ if (!isDevelopment()) {
180
+ return fn();
181
+ }
182
+
183
+ const start = performance.now();
184
+ const result = fn();
185
+ const duration = performance.now() - start;
186
+
187
+ if (duration > threshold) {
188
+ console.warn(
189
+ `[OneLaravel Performance] ${label} took ${duration.toFixed(2)}ms ` +
190
+ `(threshold: ${threshold}ms)`
191
+ );
192
+ }
193
+
194
+ return result;
195
+ }
196
+
197
+ export default {
198
+ warn,
199
+ deprecate,
200
+ invariant,
201
+ devLog,
202
+ assertType,
203
+ checkCircular,
204
+ warnIfSlow,
205
+ };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Performance Utilities
3
+ * Debounce, throttle, and other performance optimization helpers
4
+ *
5
+ * @module helpers/performance
6
+ * @author OneLaravel Team
7
+ * @since 2025-12-29
8
+ */
9
+
10
+ /**
11
+ * Debounce function - delays execution until after wait time has elapsed
12
+ * since the last invocation
13
+ *
14
+ * @param {Function} fn - Function to debounce
15
+ * @param {number} delay - Delay in milliseconds (default: 300)
16
+ * @returns {Function} Debounced function
17
+ *
18
+ * @example
19
+ * const handleSearch = debounce((query) => {
20
+ * api.search(query);
21
+ * }, 300);
22
+ *
23
+ * input.addEventListener('input', (e) => handleSearch(e.target.value));
24
+ */
25
+ export function debounce(fn, delay = 300) {
26
+ let timeoutId;
27
+
28
+ return function debounced(...args) {
29
+ clearTimeout(timeoutId);
30
+ timeoutId = setTimeout(() => {
31
+ fn.apply(this, args);
32
+ }, delay);
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Throttle function - ensures function is called at most once per time period
38
+ *
39
+ * @param {Function} fn - Function to throttle
40
+ * @param {number} limit - Time limit in milliseconds (default: 100)
41
+ * @returns {Function} Throttled function
42
+ *
43
+ * @example
44
+ * const handleScroll = throttle(() => {
45
+ * updateScrollPosition();
46
+ * }, 100);
47
+ *
48
+ * window.addEventListener('scroll', handleScroll);
49
+ */
50
+ export function throttle(fn, limit = 100) {
51
+ let inThrottle;
52
+
53
+ return function throttled(...args) {
54
+ if (!inThrottle) {
55
+ fn.apply(this, args);
56
+ inThrottle = true;
57
+ setTimeout(() => {
58
+ inThrottle = false;
59
+ }, limit);
60
+ }
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Request Animation Frame wrapper for smooth animations
66
+ *
67
+ * @param {Function} fn - Function to execute on next frame
68
+ * @returns {Function} RAF-wrapped function
69
+ *
70
+ * @example
71
+ * const updateAnimation = raf(() => {
72
+ * element.style.transform = `translateX(${x}px)`;
73
+ * });
74
+ */
75
+ export function raf(fn) {
76
+ let rafId;
77
+
78
+ return function rafWrapped(...args) {
79
+ if (rafId) {
80
+ cancelAnimationFrame(rafId);
81
+ }
82
+ rafId = requestAnimationFrame(() => {
83
+ fn.apply(this, args);
84
+ });
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Memoize function results for performance
90
+ *
91
+ * @param {Function} fn - Function to memoize
92
+ * @param {Function} keyFn - Optional function to generate cache key
93
+ * @returns {Function} Memoized function
94
+ *
95
+ * @example
96
+ * const expensiveCalc = memoize((n) => {
97
+ * return fibonacci(n);
98
+ * });
99
+ */
100
+ export function memoize(fn, keyFn = (...args) => JSON.stringify(args)) {
101
+ const cache = new Map();
102
+
103
+ return function memoized(...args) {
104
+ const key = keyFn(...args);
105
+
106
+ if (cache.has(key)) {
107
+ return cache.get(key);
108
+ }
109
+
110
+ const result = fn.apply(this, args);
111
+ cache.set(key, result);
112
+ return result;
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Batch DOM reads and writes to prevent layout thrashing
118
+ *
119
+ * @example
120
+ * const batcher = new DOMBatcher();
121
+ *
122
+ * // Schedule reads
123
+ * batcher.read(() => {
124
+ * const height = element.offsetHeight;
125
+ * });
126
+ *
127
+ * // Schedule writes
128
+ * batcher.write(() => {
129
+ * element.style.height = `${newHeight}px`;
130
+ * });
131
+ */
132
+ export class DOMBatcher {
133
+ constructor() {
134
+ this.readQueue = [];
135
+ this.writeQueue = [];
136
+ this.scheduled = false;
137
+ }
138
+
139
+ /**
140
+ * Schedule a DOM read operation
141
+ * @param {Function} fn - Read operation
142
+ */
143
+ read(fn) {
144
+ this.readQueue.push(fn);
145
+ this.schedule();
146
+ }
147
+
148
+ /**
149
+ * Schedule a DOM write operation
150
+ * @param {Function} fn - Write operation
151
+ */
152
+ write(fn) {
153
+ this.writeQueue.push(fn);
154
+ this.schedule();
155
+ }
156
+
157
+ /**
158
+ * Schedule batch execution
159
+ * @private
160
+ */
161
+ schedule() {
162
+ if (this.scheduled) return;
163
+
164
+ this.scheduled = true;
165
+ requestAnimationFrame(() => {
166
+ this.flush();
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Execute all queued operations
172
+ * @private
173
+ */
174
+ flush() {
175
+ // Execute all reads first
176
+ while (this.readQueue.length > 0) {
177
+ const fn = this.readQueue.shift();
178
+ fn();
179
+ }
180
+
181
+ // Then execute all writes
182
+ while (this.writeQueue.length > 0) {
183
+ const fn = this.writeQueue.shift();
184
+ fn();
185
+ }
186
+
187
+ this.scheduled = false;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Measure and log performance of a function
193
+ *
194
+ * @param {string} label - Label for the measurement
195
+ * @param {Function} fn - Function to measure
196
+ * @returns {*} Result of the function
197
+ *
198
+ * @example
199
+ * const result = measurePerformance('render', () => {
200
+ * return view.render();
201
+ * });
202
+ */
203
+ export function measurePerformance(label, fn) {
204
+ const start = performance.now();
205
+ const result = fn();
206
+ const end = performance.now();
207
+
208
+ if (typeof result?.then === 'function') {
209
+ return result.then((value) => {
210
+ console.debug(`[Performance] ${label}: ${(end - start).toFixed(2)}ms (async)`);
211
+ return value;
212
+ });
213
+ }
214
+
215
+ console.debug(`[Performance] ${label}: ${(end - start).toFixed(2)}ms`);
216
+ return result;
217
+ }
218
+
219
+ export default {
220
+ debounce,
221
+ throttle,
222
+ raf,
223
+ memoize,
224
+ DOMBatcher,
225
+ measurePerformance,
226
+ };
@@ -0,0 +1,287 @@
1
+ export const uniqId = (prefix = '') => {
2
+ const id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
3
+ const r = Math.random() * 16 | 0;
4
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
5
+ return v.toString(16);
6
+ });
7
+ return prefix + id;
8
+ }
9
+
10
+ export const emptyFn = () => { };
11
+
12
+ export const noop = () => { };
13
+
14
+ export const __hasOwnProp = (obj, key) => {
15
+ return Object.prototype.hasOwnProperty.call(obj, key);
16
+ }
17
+
18
+ export const __defineProps = (obj, props, configs = {}) => {
19
+ Object.entries(props).forEach(([key, value]) => {
20
+ Object.defineProperty(obj, key, {
21
+ value: value,
22
+ writable: configs[key]?.writable ?? false,
23
+ configurable: configs[key]?.configurable ?? false,
24
+ enumerable: configs[key]?.enumerable ?? false,
25
+ });
26
+ });
27
+ };
28
+
29
+ export const __defineProp = (obj, key, PropertyDescriptor = {}) => {
30
+ if (!__hasOwnProp(PropertyDescriptor, 'value') && !(__hasOwnProp(PropertyDescriptor, 'get') && typeof PropertyDescriptor.get === 'function')) {
31
+ throw new Error(`Property ${key} must have a value or get method`);
32
+ }
33
+ if (__hasOwnProp(PropertyDescriptor, 'value')) {
34
+ if (__hasOwnProp(PropertyDescriptor, 'get')) {
35
+ delete PropertyDescriptor.get;
36
+ }
37
+ if (__hasOwnProp(PropertyDescriptor, 'set')) {
38
+ delete PropertyDescriptor.set;
39
+ }
40
+ }
41
+ if (!__hasOwnProp(PropertyDescriptor, 'writable')) {
42
+ if (!__hasOwnProp(PropertyDescriptor, 'get')) {
43
+ PropertyDescriptor.writable = false;
44
+ }
45
+ }
46
+ if (!__hasOwnProp(PropertyDescriptor, 'configurable')) {
47
+ PropertyDescriptor.configurable = false;
48
+ }
49
+ if (!__hasOwnProp(PropertyDescriptor, 'enumerable')) {
50
+ PropertyDescriptor.enumerable = false;
51
+ }
52
+
53
+ return Object.defineProperty(obj, key, PropertyDescriptor);
54
+ }
55
+
56
+ export const __defineProperties = (obj, properties) => {
57
+ Object.entries(properties).forEach(([key, value]) => {
58
+ __defineProp(obj, key, value);
59
+ });
60
+ }
61
+
62
+ export const __defineMethods = (obj, methods) => {
63
+ Object.entries(methods).forEach(([key, value]) => {
64
+ if (typeof value !== 'function') {
65
+ return;
66
+ }
67
+ Object.defineProperty(obj, key, {
68
+ value: value,
69
+ writable: false,
70
+ configurable: false,
71
+ enumerable: false,
72
+ });
73
+ });
74
+ }
75
+
76
+ export const __defineGetters = (obj, getters) => {
77
+ Object.entries(getters).forEach(([key, value]) => {
78
+ Object.defineProperty(obj, key, {
79
+ get: value,
80
+ configurable: false,
81
+ enumerable: false,
82
+ });
83
+ });
84
+ }
85
+ export const deleteProp = (obj, prop) => {
86
+ try {
87
+ delete obj[prop];
88
+ return true;
89
+ } catch (error) {
90
+ console.error('App.deleteProp error:', error);
91
+ return false;
92
+ }
93
+ }
94
+ export const deleteProps = (obj, props) => {
95
+ try {
96
+ props.forEach(prop => {
97
+ delete obj[prop];
98
+ });
99
+ return true;
100
+ } catch (error) {
101
+ console.error('App.deleteProps error:', error);
102
+ return false;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Kiểm tra đối tượng có dữ liệu hay không
108
+ * @param {object} obj biến đối tượng
109
+ * @returns {boolean} true nếu có dữ liệu, false nếu rỗng
110
+ */
111
+ export const hasData = obj => {
112
+ if (!obj || typeof obj !== 'object') return false;
113
+ for (const _ in obj) return true; // fast path
114
+ return false;
115
+ }
116
+
117
+ export const isEmptyObject = obj => hasData(obj) === false;
118
+
119
+
120
+
121
+
122
+ /**
123
+ * Kiểm tra xem chuỗi đầu vào có phải là một thẻ HTML hợp lệ hoặc danh sách các thẻ HTML hợp lệ không.
124
+ * @param {string} input - Chuỗi cần kiểm tra
125
+ * @returns {boolean} true nếu hợp lệ, false nếu không hợp lệ
126
+ */
127
+ export function isHtmlString(input) {
128
+ if (typeof input !== 'string') return false;
129
+ // Loại bỏ khoảng trắng đầu cuối
130
+ const str = input.trim();
131
+ if (!str) return false;
132
+
133
+ // Regex kiểm tra một thẻ HTML đơn
134
+ const singleTagRegex = /^<([a-zA-Z][\w:-]*)(\s+[^<>]*)?>.*<\/\1>$|^<([a-zA-Z][\w:-]*)(\s+[^<>]*)?\/>$/s;
135
+
136
+ // Regex kiểm tra danh sách các thẻ HTML (có thể có nhiều thẻ liền nhau)
137
+ // Sử dụng DOMParser để kiểm tra chắc chắn hơn
138
+ try {
139
+ const parser = new DOMParser();
140
+ // Bọc trong một thẻ cha để DOMParser không tự động thêm <html><body>
141
+ const doc = parser.parseFromString(`<div>${str}</div>`, 'text/html');
142
+ const wrapper = doc.body.firstElementChild;
143
+ if (!wrapper) return false;
144
+ // Nếu tất cả các node con đều là element node (thẻ html)
145
+ for (let node of wrapper.childNodes) {
146
+ if (node.nodeType !== 1) { // 1: ELEMENT_NODE
147
+ // Cho phép text node chỉ nếu nó là khoảng trắng
148
+ if (node.nodeType === 3 && !node.textContent.trim()) continue;
149
+ return false;
150
+ }
151
+ }
152
+ // Nếu có ít nhất một thẻ hợp lệ
153
+ return wrapper.children.length > 0;
154
+ } catch (e) {
155
+ // Nếu DOMParser không hỗ trợ hoặc lỗi, fallback về regex đơn
156
+ return singleTagRegex.test(str);
157
+ }
158
+ }
159
+ /**
160
+ * Escape string for HTML output
161
+ * @param {*} value - Value to escape
162
+ * @returns {string} Escaped string
163
+ */
164
+ export function escapeString(value) {
165
+ if (value === null || value === undefined) {
166
+ return '';
167
+ }
168
+
169
+ const str = String(value);
170
+ return str
171
+ .replace(/&/g, '&amp;')
172
+ .replace(/</g, '&lt;')
173
+ .replace(/>/g, '&gt;')
174
+ .replace(/"/g, '&quot;')
175
+ .replace(/'/g, '&#39;')
176
+ .replace(/`/g, '&#96;');
177
+ ;
178
+ }
179
+
180
+ /**
181
+ * Thay thế tất cả các chuỗi con "find" trong "input" bằng chuỗi "replace".
182
+ * Có 3 trường hợp:
183
+ * 1. find là string, replace là string: thay thế tất cả find thành replace.
184
+ * 2. find là object: key là chuỗi cần tìm, value là chuỗi thay thế.
185
+ * 3. find là object, replace là string (prefix), postfix là string:
186
+ * tìm tất cả prefix+key+postfix, thay thế bằng value.
187
+ * @param {string} input - Chuỗi gốc cần thay thế
188
+ * @param {string|Object} find - Chuỗi hoặc object cần tìm để thay thế
189
+ * @param {string|undefined} replace - Chuỗi thay thế hoặc prefix
190
+ * @param {string|undefined} postfix - Chuỗi hậu tố (chỉ dùng cho dạng 3)
191
+ * @returns {string} Chuỗi sau khi đã thay thế
192
+ */
193
+ export function replaceAll(input, find, replace, postfix) {
194
+ if (typeof input !== 'string' || !find) return input;
195
+
196
+ // Trường hợp 1: find là string, replace là string
197
+ if (typeof find === 'string') {
198
+ if (find === '') return input;
199
+ return input.split(find).join(replace);
200
+ }
201
+
202
+ // Trường hợp 2 & 3: find là object
203
+ if (typeof find === 'object' && find !== null) {
204
+ let result = input;
205
+ // Dạng 3: có prefix (replace) và postfix
206
+
207
+ if (typeof replace === 'string' && typeof postfix === 'string') {
208
+ for (const [key, value] of Object.entries(find)) {
209
+ if (key === '') continue;
210
+ // Escape ký tự đặc biệt trong prefix, key, postfix
211
+ const pattern = (replace + key + postfix).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
212
+ result = result.replace(new RegExp(pattern, 'g'), value);
213
+ }
214
+ }
215
+ // Dạng 3: chỉ có prefix (replace), không có postfix
216
+ else if (typeof replace === 'string' && typeof postfix === 'undefined') {
217
+ for (const [key, value] of Object.entries(find)) {
218
+ if (key === '') continue;
219
+ const pattern = (replace + key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
220
+ result = result.replace(new RegExp(pattern, 'g'), value);
221
+ }
222
+ }
223
+ // Dạng 2: không có prefix, không có postfix
224
+ else {
225
+ for (const [key, value] of Object.entries(find)) {
226
+ if (key === '') continue;
227
+ const pattern = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
228
+ result = result.replace(new RegExp(pattern, 'g'), value);
229
+ }
230
+ }
231
+ return result;
232
+ }
233
+
234
+ // Nếu không khớp trường hợp nào, trả về input gốc
235
+ return input;
236
+ }
237
+
238
+
239
+
240
+ export function withScope(context = {}, callback) {
241
+ if (typeof callback !== 'function') {
242
+ throw new TypeError('withContext expects a function as the second argument.');
243
+ }
244
+
245
+ // Kiểm tra context phải là object
246
+ if (typeof context !== 'object' || context === null) {
247
+ throw new TypeError('Context must be a non-null object.');
248
+ }
249
+
250
+ // Tạo danh sách key và value
251
+ const keys = Object.keys(context);
252
+ const values = Object.values(context);
253
+
254
+ // Tạo hàm mới có các biến cục bộ tương ứng
255
+ const func = new Function(
256
+ ...keys,
257
+ `"use strict"; return (${callback.toString()})();`
258
+ );
259
+
260
+ // Gọi hàm mới trong phạm vi với context
261
+ return func(...values);
262
+ }
263
+
264
+ function withContext(context, fn, self) {
265
+ const keys = Object.keys(context);
266
+ const values = Object.values(context);
267
+ return function (...args) {
268
+ return new Function(...keys, `"use strict"; return (${fn.toString()})(...arguments)`)
269
+ .apply(self ?? this, values.concat(args));
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Execute function and return result
275
+ * @param {Function} fn - Function to execute
276
+ * @returns {string} Result as string
277
+ */
278
+ export function exe(fn, defaultValue = '') {
279
+ try {
280
+ const result = typeof fn === 'function' ? fn() : fn;
281
+ return result !== undefined ? result : defaultValue;
282
+ } catch (error) {
283
+ console.error('App.execute error:', error);
284
+ return defaultValue;
285
+ }
286
+ }
287
+