mirror of
https://github.com/HeyPuter/puter.git
synced 2026-02-14 01:39:12 -06:00
refactor: add traits and services as drivers
This commit is contained in:
@@ -302,6 +302,9 @@ const install = async ({ services, app, useapi }) => {
|
||||
|
||||
const { AnomalyService } = require('./services/AnomalyService');
|
||||
services.registerService('anomaly', AnomalyService);
|
||||
|
||||
const { HelloWorldService } = require('./services/HelloWorldService');
|
||||
services.registerService('hello-world', HelloWorldService);
|
||||
}
|
||||
|
||||
const install_legacy = async ({ services }) => {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/puter-js-common");
|
||||
const config = require("../config");
|
||||
const { Context } = require("../util/context");
|
||||
const { CompositeError } = require("../util/errorutil");
|
||||
@@ -26,6 +27,7 @@ class Container {
|
||||
constructor ({ logger }) {
|
||||
this.logger = logger;
|
||||
this.instances_ = {};
|
||||
this.implementors_ = {};
|
||||
this.ready = new TeePromise();
|
||||
}
|
||||
/**
|
||||
@@ -37,9 +39,24 @@ class Container {
|
||||
*/
|
||||
registerService (name, cls, args) {
|
||||
const my_config = config.services?.[name] || {};
|
||||
this.instances_[name] = cls.getInstance
|
||||
const instance = cls.getInstance
|
||||
? cls.getInstance({ services: this, config, my_config, name, args })
|
||||
: new cls({ services: this, config, my_config, name, args }) ;
|
||||
this.instances_[name] = instance;
|
||||
|
||||
if ( !(instance instanceof AdvancedBase) ) return;
|
||||
|
||||
const traits = instance.list_traits();
|
||||
for ( const trait of traits ) {
|
||||
if ( ! this.implementors_[trait] ) {
|
||||
this.implementors_[trait] = [];
|
||||
}
|
||||
this.implementors_[trait].push({
|
||||
name,
|
||||
instance,
|
||||
impl: instance.as(trait),
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* patchService allows overriding methods on a service that is already
|
||||
@@ -54,6 +71,15 @@ class Container {
|
||||
const patch_instance = new patch();
|
||||
patch_instance.patch({ original_service, args });
|
||||
}
|
||||
|
||||
// get_implementors returns a list of implementors for the specified
|
||||
// interface name.
|
||||
get_implementors (interface_name) {
|
||||
const internal_list = this.implementors_[interface_name];
|
||||
const clone = [...internal_list];
|
||||
return clone;
|
||||
}
|
||||
|
||||
set (name, instance) { this.instances_[name] = instance; }
|
||||
get (name, opts) {
|
||||
if ( this.instances_[name] ) {
|
||||
|
||||
25
src/backend/src/services/HelloWorldService.js
Normal file
25
src/backend/src/services/HelloWorldService.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const BaseService = require("./BaseService");
|
||||
|
||||
class HelloWorldService extends BaseService {
|
||||
static IMPLEMENTS = {
|
||||
['driver-metadata']: {
|
||||
get_response_meta () {
|
||||
return {
|
||||
driver: 'hello-world',
|
||||
driver_version: 'v1.0.0',
|
||||
driver_interface: 'helloworld',
|
||||
};
|
||||
}
|
||||
},
|
||||
helloworld: {
|
||||
async greet ({ subject }) {
|
||||
if ( subject ) {
|
||||
return `Hello, ${subject}!`;
|
||||
}
|
||||
return `Hello, World!`;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { HelloWorldService };
|
||||
@@ -43,6 +43,10 @@ class MapCollection extends AdvancedBase {
|
||||
del (key) {
|
||||
return this.kv.del(this._mk_key(key));
|
||||
}
|
||||
|
||||
keys () {
|
||||
return this.kv.keys(`registry:map:${this.map_id}:*`);
|
||||
}
|
||||
|
||||
_mk_key (key) {
|
||||
return `registry:map:${this.map_id}:${key}`;
|
||||
@@ -58,6 +62,16 @@ class RegistryService extends BaseService {
|
||||
this.collections_ = {};
|
||||
}
|
||||
|
||||
async ['__on_boot.consolidation'] () {
|
||||
const services = this.services;
|
||||
await services.emit('registry.collections', {
|
||||
svc_registry: this,
|
||||
});
|
||||
await services.emit('registry.entries', {
|
||||
svc_registry: this,
|
||||
});
|
||||
}
|
||||
|
||||
register_collection (name) {
|
||||
if ( this.collections_[name] ) {
|
||||
throw Error(`collection ${name} already exists`);
|
||||
|
||||
@@ -29,7 +29,6 @@ class SelfhostedService extends BaseService {
|
||||
async _init () {
|
||||
const svc_driver = this.services.get('driver');
|
||||
|
||||
svc_driver.register_driver('helloworld', new HelloWorld());
|
||||
svc_driver.register_driver('puter-kvstore', new DBKVStore());
|
||||
svc_driver.register_driver('puter-apps', new EntityStoreImplementation({ service: 'es:app' }));
|
||||
svc_driver.register_driver('puter-subdomains', new EntityStoreImplementation({ service: 'es:subdomain' }));
|
||||
|
||||
@@ -21,6 +21,7 @@ const APIError = require("../../api/APIError");
|
||||
const { DriverError } = require("./DriverError");
|
||||
const { TypedValue } = require("./meta/Runtime");
|
||||
const BaseService = require("../BaseService");
|
||||
const { Driver } = require("../../definitions/Driver");
|
||||
|
||||
/**
|
||||
* DriverService provides the functionality of Puter drivers.
|
||||
@@ -31,10 +32,30 @@ class DriverService extends BaseService {
|
||||
}
|
||||
|
||||
_construct () {
|
||||
this.interfaces = require('./interfaces');
|
||||
this.drivers = {};
|
||||
this.interface_to_implementation = {};
|
||||
}
|
||||
|
||||
async ['__on_registry.collections'] (_, { svc_registry }) {
|
||||
svc_registry.register_collection('interfaces');
|
||||
svc_registry.register_collection('drivers');
|
||||
}
|
||||
async ['__on_registry.entries'] (_, { svc_registry }) {
|
||||
const services = this.services;
|
||||
const col_interfaces = svc_registry.get('interfaces');
|
||||
const col_drivers = svc_registry.get('drivers');
|
||||
{
|
||||
const default_interfaces = require('./interfaces');
|
||||
for ( const k in default_interfaces ) {
|
||||
col_interfaces.set(k, default_interfaces[k]);
|
||||
}
|
||||
}
|
||||
await services.emit('driver.register.interfaces',
|
||||
{ col_interfaces });
|
||||
await services.emit('driver.register.drivers',
|
||||
{ col_drivers });
|
||||
}
|
||||
|
||||
_init () {
|
||||
const svc_registry = this.services.get('registry');
|
||||
svc_registry.register_collection('');
|
||||
@@ -43,9 +64,27 @@ class DriverService extends BaseService {
|
||||
register_driver (interface_name, implementation) {
|
||||
this.interface_to_implementation[interface_name] = implementation;
|
||||
}
|
||||
|
||||
|
||||
get_interface (interface_name) {
|
||||
return this.interfaces[interface_name];
|
||||
const o = {};
|
||||
const col_interfaces = svc_registry.get('interfaces');
|
||||
const keys = col_interfaces.keys();
|
||||
for ( const k of keys ) o[k] = col_interfaces.get(k);
|
||||
return col_interfaces.get(interface_name);
|
||||
}
|
||||
|
||||
get_default_implementation (interface_name) {
|
||||
// If there's a hardcoded implementation, use that
|
||||
// (^ temporary, until all are migrated)
|
||||
if (this.interface_to_implementation.hasOwnProperty(interface_name)) {
|
||||
return this.interface_to_implementation[interface_name];
|
||||
}
|
||||
|
||||
this.log.noticeme('HERE IT IS');
|
||||
const options = this.services.get_implementors(interface_name);
|
||||
this.log.info('test', { options });
|
||||
if ( options.length < 1 ) return;
|
||||
return options[0];
|
||||
}
|
||||
|
||||
async call (...a) {
|
||||
@@ -76,16 +115,33 @@ class DriverService extends BaseService {
|
||||
throw APIError.create('permission_denied');
|
||||
}
|
||||
|
||||
const instance = this.interface_to_implementation[interface_name];
|
||||
const svc_registry = this.services.get('registry');
|
||||
const c_interfaces = svc_registry.get('interfaces');
|
||||
|
||||
const instance = this.get_default_implementation(interface_name);
|
||||
if ( ! instance ) {
|
||||
throw APIError.create('no_implementation_available', null, { interface_name })
|
||||
}
|
||||
const meta = await instance.get_response_meta();
|
||||
const sla_override = await this.maybe_get_sla(interface_name, method);
|
||||
const meta = await (async () => {
|
||||
if ( instance instanceof Driver ) {
|
||||
return await instance.get_response_meta();
|
||||
}
|
||||
if ( ! instance.instance.as('driver-metadata') ) return;
|
||||
const t = instance.instance.as('driver-metadata');
|
||||
return t.get_response_meta();
|
||||
})();
|
||||
try {
|
||||
let result = await instance.call(method, processed_args, sla_override);
|
||||
let result;
|
||||
if ( instance instanceof Driver ) {
|
||||
result = await instance.call(
|
||||
method, processed_args);
|
||||
} else {
|
||||
// TODO: SLA and monthly limits do not apply do drivers
|
||||
// from service traits (yet)
|
||||
result = await instance.impl[method](processed_args);
|
||||
}
|
||||
if ( result instanceof TypedValue ) {
|
||||
const interface_ = this.interfaces[interface_name];
|
||||
const interface_ = c_interfaces.get(interface_name);
|
||||
let desired_type = interface_.methods[method]
|
||||
.result_choices[0].type;
|
||||
const svc_coercion = services.get('coercion');
|
||||
@@ -127,16 +183,12 @@ class DriverService extends BaseService {
|
||||
return this.interfaces;
|
||||
}
|
||||
|
||||
async maybe_get_sla (interface_name, method) {
|
||||
const services = this.services;
|
||||
const fs = services.get('filesystem');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _process_args (interface_name, method_name, args) {
|
||||
const svc_registry = this.services.get('registry');
|
||||
const c_interfaces = svc_registry.get('interfaces');
|
||||
|
||||
// Note: 'interface' is a strict mode reserved word.
|
||||
const interface_ = this.interfaces[interface_name];
|
||||
const interface_ = c_interfaces.get(interface_name);
|
||||
if ( ! interface_ ) {
|
||||
throw APIError.create('interface_not_found', null, { interface_name });
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class AdvancedBase extends FeatureBase {
|
||||
static FEATURES = [
|
||||
require('./features/NodeModuleDIFeature'),
|
||||
require('./features/PropertiesFeature'),
|
||||
require('./features/TraitsFeature'),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,12 @@ const { BasicBase } = require("./BasicBase");
|
||||
class FeatureBase extends BasicBase {
|
||||
constructor (parameters, ...a) {
|
||||
super(parameters, ...a);
|
||||
for ( const feature of this.features ) {
|
||||
|
||||
this._ = {
|
||||
features: this._get_merged_static_array('FEATURES'),
|
||||
};
|
||||
|
||||
for ( const feature of this._.features ) {
|
||||
feature.install_in_instance(
|
||||
this,
|
||||
{
|
||||
@@ -30,10 +35,6 @@ class FeatureBase extends BasicBase {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get features () {
|
||||
return this._get_merged_static_array('FEATURES');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
20
src/puter-js-common/src/features/TraitsFeature.js
Normal file
20
src/puter-js-common/src/features/TraitsFeature.js
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
install_in_instance: (instance, { parameters }) => {
|
||||
const impls = instance._get_merged_static_object('IMPLEMENTS');
|
||||
|
||||
instance._.impls = {};
|
||||
|
||||
for ( const impl_name in impls ) {
|
||||
const impl = impls[impl_name];
|
||||
const bound_impl = {};
|
||||
for ( const method_name in impl ) {
|
||||
const fn = impl[method_name];
|
||||
bound_impl[method_name] = fn.bind(instance);
|
||||
}
|
||||
instance._.impls[impl_name] = bound_impl;
|
||||
}
|
||||
|
||||
instance.as = trait_name => instance._.impls[trait_name];
|
||||
instance.list_traits = () => Object.keys(instance._.impls);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user