@fleetbase/ember-core 0.3.9 → 0.3.11

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.
@@ -212,7 +212,7 @@ export default class UniverseService extends Service.extend(Evented) {
212
212
  * @returns {ApplicationInstance} The application instance
213
213
  */
214
214
  getApplicationInstance() {
215
- return this.applicationInstance ?? window.Fleetbase;
215
+ return this.applicationInstance;
216
216
  }
217
217
 
218
218
  /**
@@ -35,7 +35,7 @@ function automaticServiceResolution(service, target, owner) {
35
35
  }
36
36
 
37
37
  function _getOwner(target) {
38
- return window.Fleetbase ?? getOwner(target);
38
+ return getOwner(target);
39
39
  }
40
40
 
41
41
  export default function injectEngineService(target, engineName, serviceName, options = {}) {
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/services/events';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-core",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-core",
@@ -1,294 +0,0 @@
1
- # Boot Sequence Refactor Guide
2
-
3
- ## Overview
4
-
5
- This guide provides the steps to refactor the application boot sequence to enable true lazy loading and move away from the old `bootEngines` mechanism that loads all extensions at startup.
6
-
7
- ## Understanding the Extension Loading Flow
8
-
9
- The Fleetbase application has a three-tier extension loading system:
10
-
11
- 1. **pnpm Installation**: All extensions are installed via pnpm, making them available to the application
12
- 2. **System Configuration**: Extensions defined in `fleetbase.config.js` or `EXTENSIONS` environment variable are loaded globally
13
- 3. **User Permissions**: Individual users can install/uninstall extensions, which affects what loads for them specifically
14
-
15
- Only extensions that are both installed AND enabled (via config or user permissions) will be initialized.
16
-
17
- ## The Goal
18
-
19
- Stop loading all extension code at boot time. Instead:
20
- - Load only the `extension.js` files (metadata registration)
21
- - Keep engine bundles lazy-loaded (loaded on-demand when routes are visited)
22
- - Preserve the `engines` property required by ember-engines for lazy loading
23
-
24
- ## Key Changes
25
-
26
- 1. **Keep `app.engines` property**: Required by ember-engines for lazy loading
27
- 2. **Create new `initialize-universe` instance initializer**: Loads `extension.js` files and registers metadata
28
- 3. **Remove `bootEngines` calls**: No more manual engine booting at startup
29
-
30
- ## Step-by-Step Guide
31
-
32
- ### Step 1: Update `app.js` to Preserve Engines Property
33
-
34
- The `engines` property is **required** by ember-engines to enable lazy loading. Keep the existing structure but remove any `bootEngines` calls.
35
-
36
- **Current `app.js` (fleetbase/console/app/app.js):**
37
-
38
- ```javascript
39
- import Application from '@ember/application';
40
- import Resolver from 'ember-resolver';
41
- import loadInitializers from 'ember-load-initializers';
42
- import config from '@fleetbase/console/config/environment';
43
- import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
44
- import mapEngines from '@fleetbase/ember-core/utils/map-engines';
45
- import loadRuntimeConfig from '@fleetbase/console/utils/runtime-config';
46
- import applyRouterFix from './utils/router-refresh-patch';
47
-
48
- export default class App extends Application {
49
- modulePrefix = config.modulePrefix;
50
- podModulePrefix = config.podModulePrefix;
51
- Resolver = Resolver;
52
- extensions = [];
53
- engines = {}; // ← KEEP THIS! Required by ember-engines
54
-
55
- async ready() {
56
- applyRouterFix(this);
57
- const extensions = await loadExtensions();
58
-
59
- this.extensions = extensions;
60
- this.engines = mapEngines(extensions); // ← KEEP THIS! Maps extensions to engines
61
- }
62
- }
63
-
64
- document.addEventListener('DOMContentLoaded', async () => {
65
- await loadRuntimeConfig();
66
- loadInitializers(App, config.modulePrefix);
67
-
68
- let fleetbase = App.create();
69
- fleetbase.deferReadiness();
70
- fleetbase.boot();
71
- });
72
- ```
73
-
74
- **What to Keep:**
75
- - ✅ `extensions` property - tracks which extensions are enabled
76
- - ✅ `engines` property - required by ember-engines for lazy loading
77
- - ✅ `loadExtensions()` - determines which extensions to load based on config + user permissions
78
- - ✅ `mapEngines()` - creates the engines object required by ember-engines
79
-
80
- **What Changes:**
81
- - ❌ Remove any `bootEngines()` calls (if present in instance initializers)
82
- - ❌ Remove `initialize-widgets.js` instance initializer (logic moves to `extension.js`)
83
-
84
- ### Step 2: Remove Old Instance Initializers
85
-
86
- Delete the following instance initializers that perform eager engine loading:
87
-
88
- **Files to Delete:**
89
- - `app/instance-initializers/load-extensions.js` (if it calls `bootEngines`)
90
- - `app/instance-initializers/initialize-widgets.js` (widgets now registered via `extension.js`)
91
-
92
- ### Step 3: Create New `initialize-universe` Initializer
93
-
94
- Create a new instance initializer at `app/instance-initializers/initialize-universe.js`:
95
-
96
- ```javascript
97
- import { getOwner } from '@ember/application';
98
- import { scheduleOnce } from '@ember/runloop';
99
-
100
- /**
101
- * Initializes the Universe by loading and executing extension.js files
102
- * from all enabled extensions. This replaces the old bootEngines mechanism.
103
- *
104
- * Key differences from old approach:
105
- * - Only loads extension.js files (small, metadata only)
106
- * - Does NOT load engine bundles (those lazy-load when routes are visited)
107
- * - Respects both system config and user permissions
108
- *
109
- * @param {ApplicationInstance} appInstance The application instance
110
- */
111
- export function initialize(appInstance) {
112
- const universe = appInstance.lookup('service:universe');
113
- const owner = getOwner(appInstance);
114
- const app = owner.application;
115
-
116
- // Set application instance on universe
117
- universe.applicationInstance = appInstance;
118
-
119
- // Get the list of enabled extensions from the app
120
- // This list already respects config + user permissions via loadExtensions()
121
- const extensions = app.extensions || [];
122
-
123
- // Load and execute extension.js from each enabled extension
124
- extensions.forEach(extensionName => {
125
- try {
126
- // Dynamically require the extension.js file
127
- // This is a small file with only metadata, not the full engine bundle
128
- const setupExtension = require(`${extensionName}/extension`).default;
129
-
130
- if (typeof setupExtension === 'function') {
131
- // Execute the extension setup function
132
- // This registers menus, widgets, hooks, etc. as metadata
133
- setupExtension(appInstance, universe);
134
- }
135
- } catch (error) {
136
- // Silently fail if extension.js doesn't exist
137
- // Extensions can migrate gradually to the new pattern
138
- // console.warn(`Could not load extension.js for ${extensionName}:`, error);
139
- }
140
- });
141
-
142
- // Execute any boot callbacks
143
- scheduleOnce('afterRender', universe, 'executeBootCallbacks');
144
- }
145
-
146
- export default {
147
- name: 'initialize-universe',
148
- initialize
149
- };
150
- ```
151
-
152
- ### Step 4: Verify `router.js` Engine Mounting
153
-
154
- Your `prebuild.js` script already handles mounting engines in `router.js`. Verify that engines are mounted like this:
155
-
156
- ```javascript
157
- // This is generated by prebuild.js
158
- this.mount('@fleetbase/fleetops-engine', { as: 'console.fleet-ops' });
159
- this.mount('@fleetbase/customer-portal-engine', { as: 'console.customer-portal' });
160
- ```
161
-
162
- **Important**: The `this.mount()` calls are what enable ember-engines lazy loading. When a user navigates to a route, ember-engines automatically loads the engine bundle on-demand.
163
-
164
- ### Step 5: Migrate Extensions to `extension.js` Pattern
165
-
166
- For each extension, create an `addon/extension.js` file that registers metadata without importing components:
167
-
168
- **Example: FleetOps `addon/extension.js`**
169
-
170
- ```javascript
171
- import { MenuItem, MenuPanel, Widget, ExtensionComponent } from '@fleetbase/ember-core/contracts';
172
-
173
- export default function (app, universe) {
174
- // Register admin menu panel
175
- universe.registerAdminMenuPanel(
176
- 'Fleet-Ops',
177
- new MenuPanel({
178
- title: 'Fleet-Ops',
179
- icon: 'route',
180
- items: [
181
- new MenuItem({
182
- title: 'Navigator App',
183
- icon: 'location-arrow',
184
- component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/navigator-app')
185
- }),
186
- new MenuItem({
187
- title: 'Avatar Management',
188
- icon: 'images',
189
- component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/avatar-management')
190
- })
191
- ]
192
- })
193
- );
194
-
195
- // Register widgets
196
- universe.registerDefaultWidget(
197
- new Widget({
198
- widgetId: 'fleet-ops-metrics',
199
- name: 'Fleet-Ops Metrics',
200
- description: 'Key metrics from Fleet-Ops',
201
- icon: 'truck',
202
- component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics'),
203
- grid_options: { w: 12, h: 12, minW: 8, minH: 12 }
204
- })
205
- );
206
-
207
- // Register hooks
208
- universe.registerHook(
209
- new Hook({
210
- name: 'application:before-model',
211
- handler: (session, router) => {
212
- // Custom logic here
213
- },
214
- priority: 10
215
- })
216
- );
217
- }
218
- ```
219
-
220
- **Key Points:**
221
- - ❌ NO `import MyComponent from './components/my-component'` - this would load the engine!
222
- - ✅ Use `ExtensionComponent` with engine name + path for lazy loading
223
- - ✅ Use contract classes (`MenuItem`, `Widget`, `Hook`) for type safety
224
-
225
- See [UNIVERSE_REFACTOR_MIGRATION_GUIDE.md](./UNIVERSE_REFACTOR_MIGRATION_GUIDE.md) for detailed migration examples.
226
-
227
- ## How Lazy Loading Works with This Approach
228
-
229
- 1. **App Boot**: Application boots with `app.engines` property set
230
- 2. **`initialize-universe`**: Loads small `extension.js` files via `require()`
231
- 3. **Metadata Registration**: Extensions register menus, widgets, hooks (no component code loaded)
232
- 4. **User Navigation**: User navigates to `/console/fleet-ops`
233
- 5. **Ember-Engines**: Detects route is in a mounted engine, lazy-loads the engine bundle
234
- 6. **Component Resolution**: `<LazyEngineComponent>` resolves components from loaded engine
235
-
236
- ## Performance Impact
237
-
238
- | Metric | Before (bootEngines) | After (Lazy Loading) |
239
- |--------|---------------------|---------------------|
240
- | Initial Load Time | 10-40 seconds | <1 second |
241
- | Initial Bundle Size | Core + All Engines | Core + extension.js files |
242
- | Engine Loading | All at boot | On-demand when route visited |
243
- | Memory Usage | All engines in memory | Only visited engines in memory |
244
-
245
- ## Ember-Engines Requirements
246
-
247
- According to [ember-engines documentation](https://github.com/ember-engines/ember-engines):
248
-
249
- > **Lazy loading** - An engine can allow its parent to boot with only its routing map loaded. The rest of the engine can be loaded only as required (i.e. when a route in an engine is visited). This allows applications to boot faster and limit their memory consumption.
250
-
251
- **Required for lazy loading:**
252
- 1. ✅ `app.engines` property must be set (maps extension names to engine modules)
253
- 2. ✅ Engines must be mounted in `router.js` via `this.mount()`
254
- 3. ✅ Engine's `index.js` must have `lazyLoading: true` (default)
255
-
256
- **What breaks lazy loading:**
257
- 1. ❌ Calling `owner.lookup('engine:my-engine')` at boot time
258
- 2. ❌ Importing components from engines in `extension.js`
259
- 3. ❌ Manual `bootEngines()` calls
260
-
261
- ## Troubleshooting
262
-
263
- ### Extension not loading
264
- - Check that extension is in `app.extensions` array
265
- - Verify `extension.js` file exists and exports a function
266
- - Check browser console for errors
267
-
268
- ### Components not rendering
269
- - Ensure `ExtensionComponent` has correct engine name and path
270
- - Verify engine is mounted in `router.js`
271
- - Check that `<LazyEngineComponent>` is used in templates
272
-
273
- ### Engines loading at boot
274
- - Remove any `owner.lookup('engine:...')` calls from initializers
275
- - Remove component imports from `extension.js`
276
- - Verify no `bootEngines()` calls remain
277
-
278
- ## Migration Checklist
279
-
280
- - [ ] Update `app.js` to keep `engines` property
281
- - [ ] Remove old instance initializers (`load-extensions.js`, `initialize-widgets.js`)
282
- - [ ] Create new `initialize-universe.js` instance initializer
283
- - [ ] Verify `router.js` has `this.mount()` calls for all engines
284
- - [ ] Create `extension.js` for each extension
285
- - [ ] Replace component imports with `ExtensionComponent` definitions
286
- - [ ] Test lazy loading in browser dev tools (Network tab)
287
- - [ ] Verify initial bundle size reduction
288
- - [ ] Test all extension functionality still works
289
-
290
- ## References
291
-
292
- - [Ember Engines Guide](https://guides.emberjs.com/v5.6.0/applications/ember-engines/)
293
- - [ember-engines GitHub](https://github.com/ember-engines/ember-engines)
294
- - [Ember Engines RFC](https://github.com/emberjs/rfcs/blob/master/text/0010-engines.md)
@@ -1,318 +0,0 @@
1
- # UniverseService Refactor Migration Guide
2
-
3
- ## Overview
4
-
5
- The UniverseService has been completely refactored to improve performance, maintainability, and developer experience. This guide will help you migrate your extensions to the new architecture.
6
-
7
- ## What Changed?
8
-
9
- ### 1. Service Decomposition
10
-
11
- The monolithic `UniverseService` has been split into specialized services:
12
-
13
- - **ExtensionManager**: Manages lazy loading of engines
14
- - **RegistryService**: Manages all registries using Ember's container
15
- - **MenuService**: Manages menu items and panels
16
- - **WidgetService**: Manages dashboard widgets
17
- - **HookService**: Manages application hooks
18
-
19
- The original `UniverseService` now acts as a facade, delegating to these services while maintaining backward compatibility.
20
-
21
- ### 2. Contract System
22
-
23
- New contract classes provide a fluent, type-safe API:
24
-
25
- - `ExtensionComponent`: Lazy-loadable component definitions
26
- - `MenuItem`: Menu item definitions
27
- - `MenuPanel`: Menu panel definitions
28
- - `Hook`: Hook definitions
29
- - `Widget`: Widget definitions
30
- - `Registry`: Registry namespace definitions
31
-
32
- ### 3. Lazy Loading Architecture
33
-
34
- The old `bootEngines` mechanism has been replaced with on-demand lazy loading:
35
-
36
- - Engines are no longer loaded at boot time
37
- - Components are loaded only when needed
38
- - The `<LazyEngineComponent>` wrapper handles lazy loading automatically
39
-
40
- ## Migration Steps
41
-
42
- ### Step 1: Create `extension.js` File
43
-
44
- Each engine should create a new `addon/extension.js` file to replace the `setupExtension` method in `engine.js`.
45
-
46
- **Before (`addon/engine.js`):**
47
-
48
- ```javascript
49
- import NavigatorAppComponent from './components/admin/navigator-app';
50
-
51
- export default class FleetOpsEngine extends Engine {
52
- setupExtension = function (app, engine, universe) {
53
- universe.registerHeaderMenuItem('Fleet-Ops', 'console.fleet-ops', {
54
- icon: 'route',
55
- priority: 0
56
- });
57
-
58
- universe.registerAdminMenuPanel('Fleet-Ops Config', [
59
- {
60
- title: 'Navigator App',
61
- component: NavigatorAppComponent
62
- }
63
- ]);
64
- };
65
- }
66
- ```
67
-
68
- **After (`addon/extension.js`):**
69
-
70
- ```javascript
71
- import { MenuItem, MenuPanel, ExtensionComponent } from '@fleetbase/ember-core/contracts';
72
-
73
- export default function (app, universe) {
74
- // Register header menu item
75
- universe.registerHeaderMenuItem(
76
- new MenuItem('Fleet-Ops', 'console.fleet-ops')
77
- .withIcon('route')
78
- .withPriority(0)
79
- );
80
-
81
- // Register admin panel with lazy component
82
- universe.registerAdminMenuPanel(
83
- new MenuPanel('Fleet-Ops Config')
84
- .addItem(
85
- new MenuItem('Navigator App')
86
- .withIcon('location-arrow')
87
- .withComponent(
88
- new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/navigator-app')
89
- )
90
- )
91
- );
92
- }
93
- ```
94
-
95
- **After (`addon/engine.js`):**
96
-
97
- ```javascript
98
- // Remove the setupExtension method entirely
99
- export default class FleetOpsEngine extends Engine {
100
- // ... other engine configuration
101
- }
102
- ```
103
-
104
- ### Step 2: Use Contract Classes
105
-
106
- Instead of plain objects, use the new contract classes for better type safety and developer experience.
107
-
108
- **Before:**
109
-
110
- ```javascript
111
- universe.registerWidget({
112
- widgetId: 'fleet-ops-metrics',
113
- name: 'Fleet-Ops Metrics',
114
- icon: 'truck',
115
- component: WidgetComponent,
116
- grid_options: { w: 12, h: 12 }
117
- });
118
- ```
119
-
120
- **After:**
121
-
122
- ```javascript
123
- import { Widget, ExtensionComponent } from '@fleetbase/ember-core/contracts';
124
-
125
- universe.registerDashboardWidgets([
126
- new Widget('fleet-ops-metrics')
127
- .withName('Fleet-Ops Metrics')
128
- .withIcon('truck')
129
- .withComponent(
130
- new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/fleet-ops-key-metrics')
131
- )
132
- .withGridOptions({ w: 12, h: 12 })
133
- ]);
134
- ```
135
-
136
- ### Step 3: Update Component References
137
-
138
- Replace direct component imports with lazy component definitions.
139
-
140
- **Before:**
141
-
142
- ```javascript
143
- import MyComponent from './components/my-component';
144
-
145
- universe.registerMenuItem('my-registry', 'My Item', {
146
- component: MyComponent
147
- });
148
- ```
149
-
150
- **After:**
151
-
152
- ```javascript
153
- import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts';
154
-
155
- universe.registerMenuItem(
156
- 'my-registry',
157
- new MenuItem('My Item')
158
- .withComponent(
159
- new ExtensionComponent('@fleetbase/my-engine', 'components/my-component')
160
- )
161
- );
162
- ```
163
-
164
- ### Step 4: Update Templates Using Registry Components
165
-
166
- Templates that render components from registries need to use the `<LazyEngineComponent>` wrapper.
167
-
168
- **Before:**
169
-
170
- ```handlebars
171
- {{#each this.menuItems as |item|}}
172
- {{component item.component model=@model}}
173
- {{/each}}
174
- ```
175
-
176
- **After:**
177
-
178
- ```handlebars
179
- {{#each this.menuItems as |item|}}
180
- <LazyEngineComponent @componentDef={{item.component}} @model={{@model}} />
181
- {{/each}}
182
- ```
183
-
184
- ### Step 5: Update Hook Registrations
185
-
186
- Use the new `Hook` contract for better hook management.
187
-
188
- **Before:**
189
-
190
- ```javascript
191
- universe.registerHook('application:before-model', (session, router) => {
192
- if (session.isCustomer) {
193
- router.transitionTo('customer-portal');
194
- }
195
- });
196
- ```
197
-
198
- **After:**
199
-
200
- ```javascript
201
- import { Hook } from '@fleetbase/ember-core/contracts';
202
-
203
- universe.registerHook(
204
- new Hook('application:before-model', (session, router) => {
205
- if (session.isCustomer) {
206
- router.transitionTo('customer-portal');
207
- }
208
- })
209
- .withPriority(10)
210
- .withId('customer-redirect')
211
- );
212
- ```
213
-
214
- ## Backward Compatibility
215
-
216
- The refactored `UniverseService` maintains backward compatibility with the old API. You can continue using the old syntax while migrating:
217
-
218
- ```javascript
219
- // Old syntax still works
220
- universe.registerHeaderMenuItem('My Item', 'my.route', { icon: 'star' });
221
-
222
- // New syntax is preferred
223
- universe.registerHeaderMenuItem(
224
- new MenuItem('My Item', 'my.route').withIcon('star')
225
- );
226
- ```
227
-
228
- ## Benefits of Migration
229
-
230
- 1. **Performance**: Sub-second boot times with lazy loading
231
- 2. **Type Safety**: Contract classes provide validation and IDE support
232
- 3. **Maintainability**: Specialized services are easier to understand and modify
233
- 4. **Developer Experience**: Fluent API with method chaining
234
- 5. **Extensibility**: Easy to add new features without breaking changes
235
-
236
- ## Common Patterns
237
-
238
- ### Menu Item with Click Handler
239
-
240
- ```javascript
241
- new MenuItem('Track Order')
242
- .withIcon('barcode')
243
- .withType('link')
244
- .withWrapperClass('btn-block py-1 border')
245
- .withComponent(
246
- new ExtensionComponent('@fleetbase/fleetops-engine', 'components/order-tracking-lookup')
247
- )
248
- .onClick((menuItem) => {
249
- universe.transitionMenuItem('virtual', menuItem);
250
- })
251
- ```
252
-
253
- ### Widget with Refresh Interval
254
-
255
- ```javascript
256
- new Widget('live-metrics')
257
- .withName('Live Metrics')
258
- .withComponent(
259
- new ExtensionComponent('@fleetbase/my-engine', 'components/widget/live-metrics')
260
- .withLoadingComponent('skeletons/widget')
261
- )
262
- .withRefreshInterval(5000)
263
- .asDefault()
264
- ```
265
-
266
- ### Hook with Priority and Once
267
-
268
- ```javascript
269
- new Hook('order:before-save')
270
- .withPriority(10)
271
- .once()
272
- .execute(async (order) => {
273
- await validateOrder(order);
274
- })
275
- ```
276
-
277
- ## Troubleshooting
278
-
279
- ### Component Not Found Error
280
-
281
- If you see "Component not found in engine" errors:
282
-
283
- 1. Check that the component path is correct
284
- 2. Ensure the engine name matches exactly
285
- 3. Verify the component exists in the engine
286
-
287
- ### Loading Spinner Not Showing
288
-
289
- If the loading spinner doesn't appear:
290
-
291
- 1. Check that you're using `<LazyEngineComponent>` in templates
292
- 2. Verify the `componentDef` is a lazy definition object
293
- 3. Ensure the loading component exists
294
-
295
- ### Hooks Not Executing
296
-
297
- If hooks aren't running:
298
-
299
- 1. Check the hook name matches exactly
300
- 2. Verify the hook is registered before it's needed
301
- 3. Use `universe.hookService.getHooks(hookName)` to debug
302
-
303
- ## Support
304
-
305
- For questions or issues with the migration, please:
306
-
307
- 1. Check the contract class documentation in `addon/contracts/`
308
- 2. Review the service documentation in `addon/services/universe/`
309
- 3. Open an issue on GitHub with details about your migration challenge
310
-
311
- ## Timeline
312
-
313
- - **Phase 1**: Refactored services are available, old API still works
314
- - **Phase 2**: Extensions migrate to new `extension.js` pattern
315
- - **Phase 3**: Deprecation warnings for old patterns
316
- - **Phase 4**: Old `setupExtension` pattern removed (future release)
317
-
318
- You can migrate at your own pace. The new architecture is fully backward compatible.