Skip to content

Commit 7356aca

Browse files
committed
Refactor plugin and metadata loading lifecycle
Reorganize the ObjectQL app initialization to defer package, source, and object loading until after plugins are set up, allowing plugins to register custom loaders and modify metadata before it is loaded. Update documentation to clarify the execution lifecycle and plugin capabilities, including dynamic package management.
1 parent 013062f commit 7356aca

2 files changed

Lines changed: 80 additions & 45 deletions

File tree

docs/guide/plugins.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,24 @@ export interface ObjectQLPlugin {
2323
}
2424
```
2525

26-
## 2. Creating Plugins
26+
## 2. Execution Lifecycle
27+
28+
Understanding valid execution timing is critical.
29+
30+
1. **Phase 1: Construction (`new ObjectQL`)**
31+
* Load Metadata from `packages`.
32+
* Load Metadata from `source` directories (`*.object.yml`).
33+
* Load Metadata from `config.objects`.
34+
* **Result:** `app.metadata` holds all static definitions.
35+
36+
2. **Phase 2: Initialization (`await app.init()`)**
37+
* **Step A: Remotes:** Fetch metadata from remote services.
38+
* **Step B: Plugins (`plugin.setup`)**: <-- **YOU ARE HERE**
39+
* This is the last chance to modify metadata before it touches the DB.
40+
* You can access all objects loaded in Phase 1 & Step A.
41+
* **Step C: Drivers:** Drivers connect and synchronize schema (create tables, etc.).
42+
43+
## 3. Creating Plugins
2744

2845
You can define plugins in two styles: **Object** or **Class**.
2946

@@ -148,7 +165,7 @@ setup(app) {
148165
```
149166

150167
**Example 3: Scanning a Directory**
151-
Plugins can also scan a directory to load `*.object.yml` files, just like the main application. This is useful for bundling a set of objects.
168+
Plugins can also scan a directory to load metadata files, just like the main application. This is useful for bundling a set of objects.
152169

153170
```typescript
154171
import * as path from 'path';
@@ -211,6 +228,19 @@ setup(app) {
211228
}
212229
```
213230

231+
### E. Manage Packages (Presets)
232+
Plugins can dynamically load or unload other packages. This is useful for plugins that act as "features" which bring in a set of dependencies.
233+
234+
```typescript
235+
setup(app) {
236+
// Dynamically load another package
237+
app.addPackage('@objectos/standard-objects');
238+
239+
// Or remove a package if it conflicts with this plugin
240+
app.removePackage('@objectos/legacy-objects');
241+
}
242+
```
243+
214244
## 5. Scope Isolation
215245

216246

packages/core/src/app.ts

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ export class ObjectQL implements IObjectQL {
3131
private hooks: Record<string, HookEntry[]> = {};
3232
private actions: Record<string, ActionEntry> = {};
3333
private pluginsList: ObjectQLPlugin[] = [];
34+
35+
// Store config for lazy loading in init()
36+
private config: ObjectQLConfig;
3437

3538
constructor(config: ObjectQLConfig) {
39+
this.config = config;
3640
this.metadata = config.registry || new MetadataRegistry();
3741
this.loader = new ObjectLoader(this.metadata);
3842
this.datasources = config.datasources || {};
@@ -42,18 +46,7 @@ export class ObjectQL implements IObjectQL {
4246
this.datasources['default'] = createDriverFromConnection(config.connection);
4347
}
4448

45-
// 1. Load Presets/Packages first (Base Layer)
46-
if (config.packages) {
47-
for (const name of config.packages) {
48-
this.addPackage(name);
49-
}
50-
}
51-
if (config.presets) {
52-
for (const name of config.presets) {
53-
this.addPackage(name);
54-
}
55-
}
56-
49+
// Initialize Plugin List (but don't setup yet)
5750
if (config.plugins) {
5851
for (const plugin of config.plugins) {
5952
if (typeof plugin === 'string') {
@@ -63,21 +56,6 @@ export class ObjectQL implements IObjectQL {
6356
}
6457
}
6558
}
66-
67-
// 2. Load Local Sources (Application Layer - can override presets)
68-
if (config.source) {
69-
const sources = Array.isArray(config.source) ? config.source : [config.source];
70-
for (const src of sources) {
71-
this.loader.load(src);
72-
}
73-
}
74-
75-
// 3. Load In-Memory Objects (Dynamic Layer - highest priority)
76-
if (config.objects) {
77-
for (const [key, obj] of Object.entries(config.objects)) {
78-
this.registerObject(obj);
79-
}
80-
}
8159
}
8260

8361
addPackage(name: string) {
@@ -197,21 +175,7 @@ export class ObjectQL implements IObjectQL {
197175
}
198176

199177
async init() {
200-
// -1. Load Remotes
201-
if (this.remotes.length > 0) {
202-
console.log(`Loading ${this.remotes.length} remotes...`);
203-
const results = await Promise.all(this.remotes.map(url => loadRemoteFromUrl(url)));
204-
for (const res of results) {
205-
if (res) {
206-
this.datasources[res.driverName] = res.driver;
207-
for (const obj of res.objects) {
208-
this.registerObject(obj);
209-
}
210-
}
211-
}
212-
}
213-
214-
// 0. Init Plugins
178+
// 0. Init Plugins (This allows plugins to register custom loaders)
215179
for (const plugin of this.pluginsList) {
216180
console.log(`Initializing plugin '${plugin.name}'...`);
217181

@@ -238,9 +202,50 @@ export class ObjectQL implements IObjectQL {
238202
await plugin.setup(app);
239203
}
240204

205+
// 1. Load Presets/Packages (Base Layer) - AFTER plugins, so they can use new loaders
206+
if (this.config.packages) {
207+
for (const name of this.config.packages) {
208+
this.addPackage(name);
209+
}
210+
}
211+
if (this.config.presets) {
212+
for (const name of this.config.presets) {
213+
this.addPackage(name);
214+
}
215+
}
216+
217+
// 2. Load Local Sources (Application Layer)
218+
if (this.config.source) {
219+
const sources = Array.isArray(this.config.source) ? this.config.source : [this.config.source];
220+
for (const src of sources) {
221+
this.loader.load(src);
222+
}
223+
}
224+
225+
// 3. Load In-Memory Objects (Dynamic Layer)
226+
if (this.config.objects) {
227+
for (const [key, obj] of Object.entries(this.config.objects)) {
228+
this.registerObject(obj);
229+
}
230+
}
231+
232+
// 4. Load Remotes
233+
if (this.remotes.length > 0) {
234+
console.log(`Loading ${this.remotes.length} remotes...`);
235+
const results = await Promise.all(this.remotes.map(url => loadRemoteFromUrl(url)));
236+
for (const res of results) {
237+
if (res) {
238+
this.datasources[res.driverName] = res.driver;
239+
for (const obj of res.objects) {
240+
this.registerObject(obj);
241+
}
242+
}
243+
}
244+
}
245+
241246
const objects = this.metadata.list<ObjectConfig>('object');
242247

243-
// 1. Init Drivers (e.g. Sync Schema)
248+
// 5. Init Drivers (e.g. Sync Schema)
244249
// Let's pass all objects to all configured drivers.
245250
for (const [name, driver] of Object.entries(this.datasources)) {
246251
if (driver.init) {

0 commit comments

Comments
 (0)