mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 08:30:39 +00:00
dev: extension registry and lifecycle
Adds `register`, `registry`, `preinit`, and related symbols to the available extension globals.
This commit is contained in:
@@ -54,6 +54,27 @@ class Extension extends AdvancedBase {
|
||||
this.log_context[lvl](...a);
|
||||
}
|
||||
});
|
||||
|
||||
this.only_one_preinit_fn = null;
|
||||
this.only_one_init_fn = null;
|
||||
|
||||
this.registry = {
|
||||
register: this.register.bind(this),
|
||||
of: (typeKey) => {
|
||||
return {
|
||||
named: name => {
|
||||
if ( arguments.length === 0 ) {
|
||||
return this.registry_[typeKey].named;
|
||||
}
|
||||
return this.registry_[typeKey].named[name];
|
||||
},
|
||||
all: () => [
|
||||
...Object.values(this.registry_[typeKey].named),
|
||||
...this.registry_[typeKey].anonymous,
|
||||
],
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
example () {
|
||||
@@ -95,7 +116,53 @@ class Extension extends AdvancedBase {
|
||||
}
|
||||
return log_context;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register anonymous or named data to a particular type/category.
|
||||
* @param {string} typeKey Type of data being registered
|
||||
* @param {string} [key] Key of data being registered
|
||||
* @param {any} data The data to be registered
|
||||
*/
|
||||
register (typeKey, keyOrData, data) {
|
||||
if ( ! this.registry_[typeKey] ) {
|
||||
this.registry_[typeKey] = {
|
||||
named: {},
|
||||
anonymous: [],
|
||||
};
|
||||
}
|
||||
|
||||
const typeRegistry = this.registry_[typeKey];
|
||||
|
||||
if ( arguments.length <= 1 ) {
|
||||
throw new Error('you must specify what to register');
|
||||
}
|
||||
|
||||
if ( arguments.length === 2 ) {
|
||||
data = keyOrData;
|
||||
if ( Array.isArray(data) ) {
|
||||
for ( const datum of data ) {
|
||||
typeRegistry.anonymous.push(datum);
|
||||
}
|
||||
return;
|
||||
}
|
||||
typeRegistry.anonymous.push(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = keyOrData;
|
||||
typeRegistry.named[key] = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for .register()
|
||||
* @param {string} typeKey Type of data being registered
|
||||
* @param {string} [key] Key of data being registered
|
||||
* @param {any} data The data to be registered
|
||||
*/
|
||||
reg (...a) {
|
||||
this.register(...a);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will create a GET endpoint on the default service.
|
||||
* @param {*} path - route for the endpoint
|
||||
@@ -140,6 +207,38 @@ class Extension extends AdvancedBase {
|
||||
});
|
||||
}
|
||||
|
||||
preinit (callback) {
|
||||
this.on('preinit', callback);
|
||||
}
|
||||
set preinit (callback) {
|
||||
if ( this.only_one_preinit_fn === null ) {
|
||||
this.on('preinit', (...a) => {
|
||||
this.only_one_preinit_fn(...a);
|
||||
});
|
||||
}
|
||||
if ( callback === null ) {
|
||||
this.only_one_preinit_fn = () => {};
|
||||
}
|
||||
this.only_one_preinit_fn = callback;
|
||||
}
|
||||
|
||||
init (callback) {
|
||||
this.on('init', callback);
|
||||
}
|
||||
set init (callback) {
|
||||
if ( this.only_one_init_fn === null ) {
|
||||
this.on('init', (...a) => {
|
||||
this.only_one_init_fn(...a);
|
||||
});
|
||||
}
|
||||
if ( callback === null ) {
|
||||
this.only_one_init_fn = () => {};
|
||||
}
|
||||
this.only_one_init_fn = callback;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* This method will create the "default service" for an extension.
|
||||
* This is specifically for Puter extensions that do not define their
|
||||
|
||||
@@ -90,20 +90,27 @@ class ExtensionService extends BaseService {
|
||||
const db = this.services.get('database').get(DB_WRITE, 'extension');
|
||||
this.state.values.set('db', db);
|
||||
|
||||
// Propagate all events not from extensions to `core.`
|
||||
// Propagate all events from Puter's event bus to extensions
|
||||
const svc_event = this.services.get('event');
|
||||
svc_event.on_all(async (key, data, meta = {}) => {
|
||||
meta.from_outside_of_extension = true;
|
||||
|
||||
// register for both `core.` and the extension name
|
||||
const promises = [];
|
||||
promises.push(
|
||||
this.state.extension.emit(`core.${key}`, data, meta));
|
||||
|
||||
// push event to the extension's event bus
|
||||
promises.push(
|
||||
this.state.extension.emit(key, data, meta));
|
||||
|
||||
// legacy: older extensions prefix "core." to events from Puter
|
||||
promises.push(
|
||||
this.state.extension.emit(`core.${key}`, data, meta));
|
||||
|
||||
// future: going to remove 'boot.' prefix from lifecycle events
|
||||
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
// Propagate all events from extension to Puter's event bus
|
||||
this.state.extension.on_all(async (key, data, meta) => {
|
||||
if ( meta.from_outside_of_extension ) return;
|
||||
|
||||
@@ -141,6 +148,18 @@ class ExtensionService extends BaseService {
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
this.state.extension.emit('preinit');
|
||||
}
|
||||
|
||||
['__on_boot.consolidation'] (...a) {
|
||||
this.state.extension.emit('init', ...a);
|
||||
}
|
||||
['__on_boot.activation'] (...a) {
|
||||
this.state.extension.emit('activate', ...a);
|
||||
}
|
||||
['__on_boot.ready'] (...a) {
|
||||
this.state.extension.emit('ready', ...a);
|
||||
}
|
||||
|
||||
['__on_install.routes'] (_, { app }) {
|
||||
|
||||
@@ -47,6 +47,8 @@ class Kernel extends AdvancedBase {
|
||||
});
|
||||
|
||||
this.entry_path = entry_path;
|
||||
this.extensionExports = {};
|
||||
this.registry = {};
|
||||
}
|
||||
|
||||
add_module (module) {
|
||||
@@ -118,6 +120,8 @@ class Kernel extends AdvancedBase {
|
||||
services,
|
||||
config,
|
||||
logger: this.bootLogger,
|
||||
extensionExports: this.extensionExports,
|
||||
registry: this.registry,
|
||||
args,
|
||||
}, 'app');
|
||||
globalThis.root_context = root_context;
|
||||
@@ -222,6 +226,7 @@ class Kernel extends AdvancedBase {
|
||||
globalThis.__puter_extension_globals__ = {
|
||||
extensionObjectRegistry: {},
|
||||
useapi: this.useapi,
|
||||
global_config: require('./config'),
|
||||
};
|
||||
|
||||
// Install the mods...
|
||||
@@ -251,6 +256,7 @@ class Kernel extends AdvancedBase {
|
||||
});
|
||||
}
|
||||
})();
|
||||
if ( process.env.SYNC_MOD_INSTALL ) await p;
|
||||
mod_installation_promises.push(p);
|
||||
}
|
||||
|
||||
@@ -307,8 +313,13 @@ class Kernel extends AdvancedBase {
|
||||
|
||||
await prependToJSFiles(mod_package_dir, [
|
||||
`const { use, def } = globalThis.__puter_extension_globals__.useapi;`,
|
||||
`const { use: puter } = globalThis.__puter_extension_globals__.useapi;`,
|
||||
`const extension = globalThis.__puter_extension_globals__` +
|
||||
`.extensionObjectRegistry[${JSON.stringify(extension_id)}];`,
|
||||
`const config = extension.config;`,
|
||||
`const registry = extension.registry;`,
|
||||
`const register = registry.register;`,
|
||||
`const global_config = globalThis.__puter_extension_globals__.global_config`,
|
||||
].join('\n') + '\n');
|
||||
|
||||
const mod_require_dir = path_.join(process.cwd(), mod_package_dir);
|
||||
@@ -342,7 +353,25 @@ class Kernel extends AdvancedBase {
|
||||
exportObject = await maybe_promise;
|
||||
} else exportObject = maybe_promise;
|
||||
|
||||
// TODO: do something with exportObject
|
||||
const extension_name = exportObject?.name ?? mod_packageJSON.name;
|
||||
this.extensionExports[extension_name] = exportObject;
|
||||
mod.extension.registry = this.registry;
|
||||
mod.extension.name = extension_name;
|
||||
|
||||
if ( exportObject.construct ) {
|
||||
mod.extension.on('construct', exportObject.construct);
|
||||
}
|
||||
if ( exportObject.preinit ) {
|
||||
mod.extension.on('preinit', exportObject.preinit);
|
||||
}
|
||||
|
||||
if ( exportObject.init ) {
|
||||
mod.extension.on('init', exportObject.init);
|
||||
}
|
||||
|
||||
Object.defineProperty(mod.extension, 'config', {
|
||||
get: () => require('./config').services?.[extension_name] ?? {},
|
||||
});
|
||||
|
||||
// This is where the 'install' event gets triggered
|
||||
await mod.install(mod_context);
|
||||
|
||||
Reference in New Issue
Block a user