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,301 @@
1
+ /**
2
+ * Enhanced View System with Server-Side Rendering Support
3
+ * Handles view identification, hydration, and DOM mapping
4
+ */
5
+
6
+ import { ViewIdentifier } from './ViewIdentifier.js';
7
+
8
+ export class ViewSystem {
9
+ constructor() {
10
+ this.identifier = new ViewIdentifier();
11
+ this.hydratedViews = new Set();
12
+ this.viewInstances = new Map();
13
+ this.isServerRendered = false;
14
+ this.hydrationComplete = false;
15
+ }
16
+
17
+ /**
18
+ * Initialize the view system
19
+ */
20
+ init() {
21
+ this.detectServerRendering();
22
+ this.identifier.init();
23
+
24
+ if (this.isServerRendered) {
25
+ this.hydrateServerRenderedViews();
26
+ }
27
+
28
+ this.setupGlobalEventListeners();
29
+ }
30
+
31
+ /**
32
+ * Detect if page was server-rendered
33
+ */
34
+ detectServerRendering() {
35
+ const container = document.querySelector('#app-root, #app, [data-server-rendered]');
36
+ this.isServerRendered = container && container.getAttribute('data-server-rendered') === 'true';
37
+
38
+ console.log(`🔍 Server rendering detected: ${this.isServerRendered}`);
39
+ }
40
+
41
+ /**
42
+ * Hydrate server-rendered views
43
+ */
44
+ hydrateServerRenderedViews() {
45
+ console.log('🚀 Starting hydration of server-rendered views...');
46
+
47
+ // Get all view elements
48
+ const viewElements = this.identifier.getAllViewElements();
49
+
50
+ viewElements.forEach(element => {
51
+ const viewInfo = this.identifier.extractViewInfo(element);
52
+ if (viewInfo) {
53
+ this.hydrateView(element, viewInfo);
54
+ }
55
+ });
56
+
57
+ // Mark hydration as complete
58
+ this.hydrationComplete = true;
59
+ this.emitHydrationComplete();
60
+
61
+ console.log('✅ Hydration complete');
62
+ }
63
+
64
+ /**
65
+ * Hydrate a specific view
66
+ */
67
+ hydrateView(element, viewInfo) {
68
+ try {
69
+ // Create view instance if not exists
70
+ if (!this.viewInstances.has(viewInfo.id)) {
71
+ const viewInstance = this.createViewInstance(viewInfo);
72
+ this.viewInstances.set(viewInfo.id, viewInstance);
73
+ }
74
+
75
+ const viewInstance = this.viewInstances.get(viewInfo.id);
76
+
77
+ // Attach view instance to element
78
+ element._spaViewInstance = viewInstance;
79
+
80
+ // Mark as hydrated
81
+ this.hydratedViews.add(viewInfo.id);
82
+
83
+ // Trigger view lifecycle
84
+ this.triggerViewLifecycle(viewInstance, 'mounted');
85
+
86
+ console.log(`✅ Hydrated view: ${viewInfo.name} (${viewInfo.id})`);
87
+
88
+ } catch (error) {
89
+ console.error(`❌ Failed to hydrate view ${viewInfo.name}:`, error);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Create view instance
95
+ */
96
+ createViewInstance(viewInfo) {
97
+ return {
98
+ id: viewInfo.id,
99
+ name: viewInfo.name,
100
+ path: viewInfo.path,
101
+ type: viewInfo.type,
102
+ element: viewInfo.element,
103
+ isHydrated: false,
104
+ lifecycle: {
105
+ mounted: false,
106
+ updated: false,
107
+ destroyed: false
108
+ },
109
+ data: {},
110
+ methods: {}
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Trigger view lifecycle events
116
+ */
117
+ triggerViewLifecycle(viewInstance, event) {
118
+ const element = viewInstance.element;
119
+
120
+ // Dispatch custom events
121
+ const customEvent = new CustomEvent(`spa:view:${event}`, {
122
+ detail: {
123
+ view: viewInstance,
124
+ timestamp: Date.now()
125
+ }
126
+ });
127
+ element.dispatchEvent(customEvent);
128
+
129
+ // Update lifecycle state
130
+ viewInstance.lifecycle[event] = true;
131
+
132
+ // Call view methods if available
133
+ if (viewInstance.methods[event]) {
134
+ try {
135
+ viewInstance.methods[event].call(viewInstance);
136
+ } catch (error) {
137
+ console.error(`Error in view ${event} method:`, error);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Setup global event listeners
144
+ */
145
+ setupGlobalEventListeners() {
146
+ // Listen for view changes
147
+ document.addEventListener('spa:view:changed', (event) => {
148
+ this.onViewChanged(event.detail.view);
149
+ });
150
+
151
+ // Listen for navigation events
152
+ document.addEventListener('spa:navigation:start', () => {
153
+ this.pauseViewUpdates();
154
+ });
155
+
156
+ document.addEventListener('spa:navigation:complete', () => {
157
+ this.resumeViewUpdates();
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Handle view change
163
+ */
164
+ onViewChanged(viewData) {
165
+ console.log(`🔄 View changed to: ${viewData.info.name}`);
166
+
167
+ // Update current view tracking
168
+ this.identifier.setCurrentView(viewData.info.id);
169
+
170
+ // Trigger view enter event
171
+ this.triggerViewLifecycle(viewData, 'enter');
172
+ }
173
+
174
+ /**
175
+ * Pause view updates during navigation
176
+ */
177
+ pauseViewUpdates() {
178
+ this.viewInstances.forEach(viewInstance => {
179
+ viewInstance.isPaused = true;
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Resume view updates after navigation
185
+ */
186
+ resumeViewUpdates() {
187
+ this.viewInstances.forEach(viewInstance => {
188
+ viewInstance.isPaused = false;
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Get view by ID
194
+ */
195
+ getView(viewId) {
196
+ return this.viewInstances.get(viewId);
197
+ }
198
+
199
+ /**
200
+ * Get all hydrated views
201
+ */
202
+ getHydratedViews() {
203
+ return Array.from(this.hydratedViews).map(id => this.viewInstances.get(id));
204
+ }
205
+
206
+ /**
207
+ * Get views by type
208
+ */
209
+ getViewsByType(type) {
210
+ const views = [];
211
+ this.viewInstances.forEach(viewInstance => {
212
+ if (viewInstance.type === type) {
213
+ views.push(viewInstance);
214
+ }
215
+ });
216
+ return views;
217
+ }
218
+
219
+ /**
220
+ * Update view data
221
+ */
222
+ updateViewData(viewId, data) {
223
+ const viewInstance = this.viewInstances.get(viewId);
224
+ if (viewInstance) {
225
+ viewInstance.data = { ...viewInstance.data, ...data };
226
+ this.triggerViewLifecycle(viewInstance, 'updated');
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Destroy view
232
+ */
233
+ destroyView(viewId) {
234
+ const viewInstance = this.viewInstances.get(viewId);
235
+ if (viewInstance) {
236
+ this.triggerViewLifecycle(viewInstance, 'destroyed');
237
+ this.hydratedViews.delete(viewId);
238
+ this.viewInstances.delete(viewId);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Emit hydration complete event
244
+ */
245
+ emitHydrationComplete() {
246
+ const event = new CustomEvent('spa:hydration:complete', {
247
+ detail: {
248
+ hydratedViews: this.hydratedViews.size,
249
+ totalViews: this.viewInstances.size,
250
+ timestamp: Date.now()
251
+ }
252
+ });
253
+ document.dispatchEvent(event);
254
+ }
255
+
256
+ /**
257
+ * Get system status
258
+ */
259
+ getStatus() {
260
+ return {
261
+ isServerRendered: this.isServerRendered,
262
+ hydrationComplete: this.hydrationComplete,
263
+ totalViews: this.viewInstances.size,
264
+ hydratedViews: this.hydratedViews.size,
265
+ identifier: this.identifier.getDebugInfo()
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Enable debug mode
271
+ */
272
+ enableDebugMode() {
273
+ this.identifier.enableDebugMode();
274
+ console.log('🔧 Debug mode enabled');
275
+ }
276
+
277
+ /**
278
+ * Disable debug mode
279
+ */
280
+ disableDebugMode() {
281
+ this.identifier.disableDebugMode();
282
+ console.log('🔧 Debug mode disabled');
283
+ }
284
+
285
+ /**
286
+ * Export system data for debugging
287
+ */
288
+ exportSystemData() {
289
+ return {
290
+ status: this.getStatus(),
291
+ views: this.identifier.exportViewData(),
292
+ instances: Array.from(this.viewInstances.entries()).map(([id, instance]) => ({
293
+ id,
294
+ name: instance.name,
295
+ type: instance.type,
296
+ isHydrated: this.hydratedViews.has(id),
297
+ lifecycle: instance.lifecycle
298
+ }))
299
+ };
300
+ }
301
+ }
@@ -0,0 +1,4 @@
1
+ // Auto-generated proxy to context-specific registry
2
+ // This file re-exports from templates.web.js
3
+
4
+ export { ViewTemplates } from '../templates.web.js';
@@ -0,0 +1,239 @@
1
+ import OneDOM from '../OneDOM.js';
2
+ import DOMBatcher from '../DOMBatcher.js';
3
+ import logger from '../services/LoggerService.js';
4
+
5
+ export default class BindingHelper {
6
+
7
+ /**
8
+ * Start Two-Way Binding Listeners
9
+ */
10
+ static startBindingEventListener(controller) {
11
+ const state = controller._internal.binding;
12
+ const selector = `[data-binding][data-view-id="${controller.id}"]`;
13
+ const inputs = document.querySelectorAll(selector);
14
+
15
+ if (inputs.length === 0) return;
16
+
17
+ inputs.forEach(input => {
18
+ if (!input.isConnected) return;
19
+
20
+ // Check WeakMap to see if already attached
21
+ if (state.elementMap.has(input)) return;
22
+
23
+ const stateKey = input.getAttribute('data-binding');
24
+ if (!stateKey) return;
25
+
26
+ // Sync initial state (State -> Element)
27
+ BindingHelper.syncStateToElement(controller, input, stateKey);
28
+
29
+ // Create Handler (Element -> State)
30
+ const handler = (event) => BindingHelper.pushElementToState(controller, input, stateKey);
31
+
32
+ const tag = input.tagName.toLowerCase();
33
+ const eventType = tag === 'select' ? 'change' : 'input';
34
+
35
+ input.addEventListener(eventType, handler);
36
+
37
+ // Store in WeakMap for efficient lookup/garbage collection prevention
38
+ state.elementMap.set(input, { eventType, handler, stateKey });
39
+
40
+ // Store in array for manual cleanup in destroy()
41
+ state.listeners.push({ element: input, eventType, handler });
42
+ });
43
+
44
+ state.isStarted = true;
45
+ }
46
+
47
+ /**
48
+ * Start Class Binding Listeners
49
+ */
50
+ static startClassBindingEventListener(controller) {
51
+ const state = controller._internal.binding;
52
+
53
+ if (state.isClassReady || !state.classConfigs || state.classConfigs.length === 0) {
54
+ return;
55
+ }
56
+
57
+ const removeClassBindingIDs = [];
58
+
59
+ state.classConfigs.forEach(binding => {
60
+ const { id, config, states, initialiClass = '' } = binding;
61
+ // logic to find element...
62
+ const selector = `[data-one-class-id="${id}"]`;
63
+ const element = document.querySelector(selector);
64
+
65
+ if (!element) {
66
+ removeClassBindingIDs.push(id);
67
+ return;
68
+ }
69
+ binding.element = element;
70
+
71
+ state.classListeners.push({ id, states, config, element, initialiClass });
72
+
73
+ // Initial class application
74
+ const classOperations = [];
75
+
76
+ // Initial classes
77
+ String(initialiClass).split(' ').forEach(cls => {
78
+ if (cls.trim()) classOperations.push({ element, action: 'add', className: cls });
79
+ });
80
+
81
+ // Config classes
82
+ Object.entries(config).forEach(([className, classConfig]) => {
83
+ const { checker } = classConfig;
84
+ if (typeof checker === 'function') {
85
+ const shouldAdd = checker.call(controller.view); // Bind to view
86
+ classOperations.push({ element, action: shouldAdd ? 'add' : 'remove', className });
87
+ }
88
+ });
89
+
90
+ if (classOperations.length > 0) {
91
+ DOMBatcher.write(() => {
92
+ classOperations.forEach(({ element, action, className }) => {
93
+ element.classList[action](className);
94
+ });
95
+ });
96
+ }
97
+ });
98
+
99
+ state.isClassReady = true;
100
+ }
101
+
102
+ /**
103
+ * Push Element value to State (Input -> State)
104
+ */
105
+ static pushElementToState(controller, element, stateKey) {
106
+ const state = controller._internal.binding;
107
+
108
+ // Get or Create flags from WeakMap
109
+ let flags = state.elementFlags.get(element);
110
+ if (!flags) {
111
+ flags = { pushing: false, syncing: false };
112
+ state.elementFlags.set(element, flags);
113
+ }
114
+
115
+ if (flags.syncing || flags.pushing) return;
116
+ flags.pushing = true;
117
+
118
+ try {
119
+ const value = OneDOM.getInputValue(element);
120
+ // Update state on controller
121
+ controller.states.__.updateStateAddressKey(stateKey, value);
122
+ } catch (e) {
123
+ logger.error(`Error pushing element state ${stateKey}`, e);
124
+ } finally {
125
+ // Clear flag asynchronously
126
+ Promise.resolve().then(() => {
127
+ const currentFlags = state.elementFlags.get(element);
128
+ if (currentFlags) currentFlags.pushing = false;
129
+ });
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Sync State value to Element (State -> Element)
135
+ */
136
+ static syncStateToElement(controller, element, stateKey) {
137
+ const state = controller._internal.binding;
138
+
139
+ let flags = state.elementFlags.get(element);
140
+ if (!flags) {
141
+ flags = { pushing: false, syncing: false };
142
+ state.elementFlags.set(element, flags);
143
+ }
144
+
145
+ if (flags.pushing || flags.syncing) return;
146
+
147
+ const stateValue = controller.states.__.getStateByAddressKey(stateKey);
148
+ const currentValue = OneDOM.getInputValue(element);
149
+
150
+ if (currentValue == stateValue) return; // Loose equality check
151
+
152
+ flags.syncing = true;
153
+ try {
154
+ OneDOM.setInputValue(element, stateValue);
155
+ } finally {
156
+ flags.syncing = false;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Notify state changes to all bindings
162
+ * @param {Object} controller
163
+ * @param {Array} changedKeys
164
+ */
165
+ static notifyStateChanges(controller, changedKeys) {
166
+ if (!controller.isReadyToStateChangeListen) return;
167
+
168
+ // Handle explicit subscribeStates method on controller if exists
169
+ // ... (legacy logic support if needed) ...
170
+
171
+ const state = controller._internal.binding;
172
+
173
+ // 1. Notify Input Bindings
174
+ if (state.listeners.length > 0) {
175
+ state.listeners.forEach(({ element, stateKey }) => {
176
+ // Check if stateKey or its parent is in changedKeys
177
+ // Simplification: exact match or simple includes
178
+ // In real impl, need robust key matching (e.g. 'user.name' matches 'user')
179
+ const rootKey = stateKey.split('.')[0];
180
+ if (changedKeys.includes(rootKey) || changedKeys.includes(stateKey)) {
181
+ BindingHelper.syncStateToElement(controller, element, stateKey);
182
+ }
183
+ });
184
+ }
185
+
186
+ // 2. Notify Class Bindings
187
+ if (state.isClassReady && state.classListeners.length > 0) {
188
+ const classOperations = [];
189
+ state.classListeners.forEach(listener => {
190
+ const { states, config, element } = listener;
191
+ if (!element.isConnected) return;
192
+
193
+ const shouldUpdate = states.some(key => changedKeys.includes(key));
194
+ if (!shouldUpdate) return;
195
+
196
+ Object.entries(config).forEach(([className, classConfig]) => {
197
+ const { states: classStates, checker } = classConfig;
198
+ if (classStates.some(k => changedKeys.includes(k))) {
199
+ const shouldAdd = checker.call(controller.view);
200
+ classOperations.push({ element, action: shouldAdd ? 'add' : 'remove', className });
201
+ }
202
+ });
203
+ });
204
+
205
+ if (classOperations.length > 0) {
206
+ DOMBatcher.write(() => {
207
+ classOperations.forEach(({ element, action, className }) => {
208
+ element.classList[action](className);
209
+ });
210
+ });
211
+ }
212
+ }
213
+
214
+ // 3. Notify Attribute Bindings (omitted for brevity, similar logic)
215
+ // ...
216
+ }
217
+
218
+ /**
219
+ * Destroy binding state
220
+ */
221
+ static destroy(controller) {
222
+ const state = controller._internal.binding;
223
+
224
+ // Remove listeners
225
+ state.listeners.forEach(({ element, eventType, handler }) => {
226
+ try {
227
+ if (element.isConnected) element.removeEventListener(eventType, handler);
228
+ } catch (e) { }
229
+ });
230
+
231
+ state.listeners = [];
232
+ state.classListeners = [];
233
+ state.attrListeners = [];
234
+ state.isStarted = false;
235
+ state.isClassReady = false;
236
+
237
+ // WeakMaps (elementFlags, elementMap) will be GC'd automatically when controller._internal dies
238
+ }
239
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ConfigHelper
3
+ * Handles configuration processing
4
+ */
5
+ import { __defineProps, __defineMethods } from '../../helpers/utils.js';
6
+
7
+ export default class ConfigHelper {
8
+ static processDefinedProperties(controller, config) {
9
+ let definedProps = {};
10
+ let definedMethods = {};
11
+
12
+ if (config.__props__ && config.__props__.length > 0) {
13
+ config.__props__.forEach(prop => {
14
+ if (typeof config[prop] === 'function') {
15
+ definedMethods[prop] = config[prop].bind(controller);
16
+ }
17
+ else if (typeof config[prop] !== 'undefined') {
18
+ definedProps[prop] = config[prop];
19
+ }
20
+ });
21
+ }
22
+
23
+ __defineProps(controller, definedProps, {
24
+ writable: true,
25
+ configurable: true,
26
+ enumerable: true,
27
+ });
28
+
29
+ __defineMethods(controller, definedMethods);
30
+ }
31
+
32
+ static commitConstructorData(controller) {
33
+ if (typeof controller.config.commitConstructorData === 'function') {
34
+ controller.config.commitConstructorData.call(controller.view);
35
+ }
36
+ }
37
+ }