Skip to content

Commit 2cd6810

Browse files
committed
refactor: add traits and services as drivers
1 parent 8beda66 commit 2cd6810

File tree

9 files changed

+164
-23
lines changed

9 files changed

+164
-23
lines changed

src/backend/src/CoreModule.js

+3
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ const install = async ({ services, app, useapi }) => {
302302

303303
const { AnomalyService } = require('./services/AnomalyService');
304304
services.registerService('anomaly', AnomalyService);
305+
306+
const { HelloWorldService } = require('./services/HelloWorldService');
307+
services.registerService('hello-world', HelloWorldService);
305308
}
306309

307310
const install_legacy = async ({ services }) => {

src/backend/src/services/Container.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* You should have received a copy of the GNU Affero General Public License
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
19+
const { AdvancedBase } = require("@heyputer/puter-js-common");
1920
const config = require("../config");
2021
const { Context } = require("../util/context");
2122
const { CompositeError } = require("../util/errorutil");
@@ -26,6 +27,7 @@ class Container {
2627
constructor ({ logger }) {
2728
this.logger = logger;
2829
this.instances_ = {};
30+
this.implementors_ = {};
2931
this.ready = new TeePromise();
3032
}
3133
/**
@@ -37,9 +39,24 @@ class Container {
3739
*/
3840
registerService (name, cls, args) {
3941
const my_config = config.services?.[name] || {};
40-
this.instances_[name] = cls.getInstance
42+
const instance = cls.getInstance
4143
? cls.getInstance({ services: this, config, my_config, name, args })
4244
: new cls({ services: this, config, my_config, name, args }) ;
45+
this.instances_[name] = instance;
46+
47+
if ( !(instance instanceof AdvancedBase) ) return;
48+
49+
const traits = instance.list_traits();
50+
for ( const trait of traits ) {
51+
if ( ! this.implementors_[trait] ) {
52+
this.implementors_[trait] = [];
53+
}
54+
this.implementors_[trait].push({
55+
name,
56+
instance,
57+
impl: instance.as(trait),
58+
});
59+
}
4360
}
4461
/**
4562
* patchService allows overriding methods on a service that is already
@@ -54,6 +71,15 @@ class Container {
5471
const patch_instance = new patch();
5572
patch_instance.patch({ original_service, args });
5673
}
74+
75+
// get_implementors returns a list of implementors for the specified
76+
// interface name.
77+
get_implementors (interface_name) {
78+
const internal_list = this.implementors_[interface_name];
79+
const clone = [...internal_list];
80+
return clone;
81+
}
82+
5783
set (name, instance) { this.instances_[name] = instance; }
5884
get (name, opts) {
5985
if ( this.instances_[name] ) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const BaseService = require("./BaseService");
2+
3+
class HelloWorldService extends BaseService {
4+
static IMPLEMENTS = {
5+
['driver-metadata']: {
6+
get_response_meta () {
7+
return {
8+
driver: 'hello-world',
9+
driver_version: 'v1.0.0',
10+
driver_interface: 'helloworld',
11+
};
12+
}
13+
},
14+
helloworld: {
15+
async greet ({ subject }) {
16+
if ( subject ) {
17+
return `Hello, ${subject}!`;
18+
}
19+
return `Hello, World!`;
20+
}
21+
},
22+
}
23+
}
24+
25+
module.exports = { HelloWorldService };

src/backend/src/services/RegistryService.js

+14
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ class MapCollection extends AdvancedBase {
4343
del (key) {
4444
return this.kv.del(this._mk_key(key));
4545
}
46+
47+
keys () {
48+
return this.kv.keys(`registry:map:${this.map_id}:*`);
49+
}
4650

4751
_mk_key (key) {
4852
return `registry:map:${this.map_id}:${key}`;
@@ -58,6 +62,16 @@ class RegistryService extends BaseService {
5862
this.collections_ = {};
5963
}
6064

65+
async ['__on_boot.consolidation'] () {
66+
const services = this.services;
67+
await services.emit('registry.collections', {
68+
svc_registry: this,
69+
});
70+
await services.emit('registry.entries', {
71+
svc_registry: this,
72+
});
73+
}
74+
6175
register_collection (name) {
6276
if ( this.collections_[name] ) {
6377
throw Error(`collection ${name} already exists`);

src/backend/src/services/SelfhostedService.js

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class SelfhostedService extends BaseService {
2929
async _init () {
3030
const svc_driver = this.services.get('driver');
3131

32-
svc_driver.register_driver('helloworld', new HelloWorld());
3332
svc_driver.register_driver('puter-kvstore', new DBKVStore());
3433
svc_driver.register_driver('puter-apps', new EntityStoreImplementation({ service: 'es:app' }));
3534
svc_driver.register_driver('puter-subdomains', new EntityStoreImplementation({ service: 'es:subdomain' }));

src/backend/src/services/drivers/DriverService.js

+68-16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const APIError = require("../../api/APIError");
2121
const { DriverError } = require("./DriverError");
2222
const { TypedValue } = require("./meta/Runtime");
2323
const BaseService = require("../BaseService");
24+
const { Driver } = require("../../definitions/Driver");
2425

2526
/**
2627
* DriverService provides the functionality of Puter drivers.
@@ -31,10 +32,30 @@ class DriverService extends BaseService {
3132
}
3233

3334
_construct () {
34-
this.interfaces = require('./interfaces');
35+
this.drivers = {};
3536
this.interface_to_implementation = {};
3637
}
3738

39+
async ['__on_registry.collections'] (_, { svc_registry }) {
40+
svc_registry.register_collection('interfaces');
41+
svc_registry.register_collection('drivers');
42+
}
43+
async ['__on_registry.entries'] (_, { svc_registry }) {
44+
const services = this.services;
45+
const col_interfaces = svc_registry.get('interfaces');
46+
const col_drivers = svc_registry.get('drivers');
47+
{
48+
const default_interfaces = require('./interfaces');
49+
for ( const k in default_interfaces ) {
50+
col_interfaces.set(k, default_interfaces[k]);
51+
}
52+
}
53+
await services.emit('driver.register.interfaces',
54+
{ col_interfaces });
55+
await services.emit('driver.register.drivers',
56+
{ col_drivers });
57+
}
58+
3859
_init () {
3960
const svc_registry = this.services.get('registry');
4061
svc_registry.register_collection('');
@@ -43,9 +64,27 @@ class DriverService extends BaseService {
4364
register_driver (interface_name, implementation) {
4465
this.interface_to_implementation[interface_name] = implementation;
4566
}
46-
67+
4768
get_interface (interface_name) {
48-
return this.interfaces[interface_name];
69+
const o = {};
70+
const col_interfaces = svc_registry.get('interfaces');
71+
const keys = col_interfaces.keys();
72+
for ( const k of keys ) o[k] = col_interfaces.get(k);
73+
return col_interfaces.get(interface_name);
74+
}
75+
76+
get_default_implementation (interface_name) {
77+
// If there's a hardcoded implementation, use that
78+
// (^ temporary, until all are migrated)
79+
if (this.interface_to_implementation.hasOwnProperty(interface_name)) {
80+
return this.interface_to_implementation[interface_name];
81+
}
82+
83+
this.log.noticeme('HERE IT IS');
84+
const options = this.services.get_implementors(interface_name);
85+
this.log.info('test', { options });
86+
if ( options.length < 1 ) return;
87+
return options[0];
4988
}
5089

5190
async call (...a) {
@@ -76,16 +115,33 @@ class DriverService extends BaseService {
76115
throw APIError.create('permission_denied');
77116
}
78117

79-
const instance = this.interface_to_implementation[interface_name];
118+
const svc_registry = this.services.get('registry');
119+
const c_interfaces = svc_registry.get('interfaces');
120+
121+
const instance = this.get_default_implementation(interface_name);
80122
if ( ! instance ) {
81123
throw APIError.create('no_implementation_available', null, { interface_name })
82124
}
83-
const meta = await instance.get_response_meta();
84-
const sla_override = await this.maybe_get_sla(interface_name, method);
125+
const meta = await (async () => {
126+
if ( instance instanceof Driver ) {
127+
return await instance.get_response_meta();
128+
}
129+
if ( ! instance.instance.as('driver-metadata') ) return;
130+
const t = instance.instance.as('driver-metadata');
131+
return t.get_response_meta();
132+
})();
85133
try {
86-
let result = await instance.call(method, processed_args, sla_override);
134+
let result;
135+
if ( instance instanceof Driver ) {
136+
result = await instance.call(
137+
method, processed_args);
138+
} else {
139+
// TODO: SLA and monthly limits do not apply do drivers
140+
// from service traits (yet)
141+
result = await instance.impl[method](processed_args);
142+
}
87143
if ( result instanceof TypedValue ) {
88-
const interface_ = this.interfaces[interface_name];
144+
const interface_ = c_interfaces.get(interface_name);
89145
let desired_type = interface_.methods[method]
90146
.result_choices[0].type;
91147
const svc_coercion = services.get('coercion');
@@ -127,16 +183,12 @@ class DriverService extends BaseService {
127183
return this.interfaces;
128184
}
129185

130-
async maybe_get_sla (interface_name, method) {
131-
const services = this.services;
132-
const fs = services.get('filesystem');
133-
134-
return false;
135-
}
136-
137186
async _process_args (interface_name, method_name, args) {
187+
const svc_registry = this.services.get('registry');
188+
const c_interfaces = svc_registry.get('interfaces');
189+
138190
// Note: 'interface' is a strict mode reserved word.
139-
const interface_ = this.interfaces[interface_name];
191+
const interface_ = c_interfaces.get(interface_name);
140192
if ( ! interface_ ) {
141193
throw APIError.create('interface_not_found', null, { interface_name });
142194
}

src/puter-js-common/src/AdvancedBase.js

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class AdvancedBase extends FeatureBase {
2525
static FEATURES = [
2626
require('./features/NodeModuleDIFeature'),
2727
require('./features/PropertiesFeature'),
28+
require('./features/TraitsFeature'),
2829
]
2930
}
3031

src/puter-js-common/src/bases/FeatureBase.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ const { BasicBase } = require("./BasicBase");
2121
class FeatureBase extends BasicBase {
2222
constructor (parameters, ...a) {
2323
super(parameters, ...a);
24-
for ( const feature of this.features ) {
24+
25+
this._ = {
26+
features: this._get_merged_static_array('FEATURES'),
27+
};
28+
29+
for ( const feature of this._.features ) {
2530
feature.install_in_instance(
2631
this,
2732
{
@@ -30,10 +35,6 @@ class FeatureBase extends BasicBase {
3035
)
3136
}
3237
}
33-
34-
get features () {
35-
return this._get_merged_static_array('FEATURES');
36-
}
3738
}
3839

3940
module.exports = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
install_in_instance: (instance, { parameters }) => {
3+
const impls = instance._get_merged_static_object('IMPLEMENTS');
4+
5+
instance._.impls = {};
6+
7+
for ( const impl_name in impls ) {
8+
const impl = impls[impl_name];
9+
const bound_impl = {};
10+
for ( const method_name in impl ) {
11+
const fn = impl[method_name];
12+
bound_impl[method_name] = fn.bind(instance);
13+
}
14+
instance._.impls[impl_name] = bound_impl;
15+
}
16+
17+
instance.as = trait_name => instance._.impls[trait_name];
18+
instance.list_traits = () => Object.keys(instance._.impls);
19+
},
20+
};

0 commit comments

Comments
 (0)