From b7defab2d233d03fdf3b3aff00bfc0fdf48f41b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Dub=C3=A9?= Date: Mon, 31 Mar 2025 19:32:38 -0400 Subject: [PATCH] refactor: Migrate interfaces.js to new registration mechanism (#1239) * refactor: migrate interfaces.js to new registration mechanism - Created EntityStoreInterfaceService for crud-q interface\n- Created AnalyticsInterfaceService for puter-analytics interface\n- Added InterfacesModule to load these services\n- Removed interfaces.js\n\nCloses #1131 ai: true * chore: remove interfaces.js file ai: true * fix: DRY CRUD interfaces This comment is flagged as AI-generated, but Claude rate-limited before it could actually make the commit so this commit was made by hand. Well, while I'm writing this commit message I may as well mention that Claud's rate limits are relentless and it has become impossible to use Claude for some purposes as a result. ai: true * refactor: replace interfaces module with separate Module.js files for entitystore and analytics - Removed interfaces module\n- Added EntityStoreModule.js to entitystore module\n- Added AnalyticsModule.js to analytics module\n- Updated main index.js to use the new modules directly\n\nai: true ai: true * fix: modules exported and registered incorrectly * feat: add KVStoreModule for puter-kvstore interface - Created KVStoreModule.js\n- Created KVStoreInterfaceService.js to register the puter-kvstore interface\n- Updated exports.js to include the new module\n\nai: true * fix: remove index.js from kvstore module - Removed unnecessary index.js file from kvstore module\n\nai: true * fix: remove index.js files from analytics and entitystore modules - Removed unnecessary index.js files from analytics and entitystore modules\n\nai: true * fix: cleanup mycoder mistakes again ...because it actually threw out my previous commit where I already did this. --- src/backend/exports.js | 7 + .../analytics/AnalyticsInterfaceService.js | 64 ++++ .../src/modules/analytics/AnalyticsModule.js | 37 +++ .../EntityStoreInterfaceService.js | 128 ++++++++ .../modules/entitystore/EntityStoreModule.js | 37 +++ .../kvstore/KVStoreInterfaceService.js | 105 ++++++ .../src/modules/kvstore/KVStoreModule.js | 37 +++ .../src/services/drivers/DriverService.js | 6 - .../src/services/drivers/interfaces.js | 299 ------------------ 9 files changed, 415 insertions(+), 305 deletions(-) create mode 100644 src/backend/src/modules/analytics/AnalyticsInterfaceService.js create mode 100644 src/backend/src/modules/analytics/AnalyticsModule.js create mode 100644 src/backend/src/modules/entitystore/EntityStoreInterfaceService.js create mode 100644 src/backend/src/modules/entitystore/EntityStoreModule.js create mode 100644 src/backend/src/modules/kvstore/KVStoreInterfaceService.js create mode 100644 src/backend/src/modules/kvstore/KVStoreModule.js delete mode 100644 src/backend/src/services/drivers/interfaces.js diff --git a/src/backend/exports.js b/src/backend/exports.js index ab339c5ed..fe4767925 100644 --- a/src/backend/exports.js +++ b/src/backend/exports.js @@ -40,6 +40,9 @@ const { PuterExecModule } = require("./src/modules/puterexec/PuterExecModule.js" const { MailModule } = require("./src/modules/mail/MailModule.js"); const { ConvertModule } = require("./src/modules/convert/ConvertModule.js"); const { CaptchaModule } = require("./src/modules/captcha/CaptchaModule.js"); +const { EntityStoreModule } = require("./src/modules/entitystore/EntityStoreModule.js"); +const { AnalyticsModule } = require("./src/modules/analytics/AnalyticsModule.js"); +const { KVStoreModule } = require("./src/modules/kvstore/KVStoreModule.js"); module.exports = { helloworld: () => { @@ -63,6 +66,9 @@ module.exports = { TemplateModule, AppsModule, CaptchaModule, + EntityStoreModule, + AnalyticsModule, + KVStoreModule, ], // Pre-built modules @@ -79,6 +85,7 @@ module.exports = { MailModule, ConvertModule, CaptchaModule, + KVStoreModule, // Development modules PerfMonModule, diff --git a/src/backend/src/modules/analytics/AnalyticsInterfaceService.js b/src/backend/src/modules/analytics/AnalyticsInterfaceService.js new file mode 100644 index 000000000..1102d1365 --- /dev/null +++ b/src/backend/src/modules/analytics/AnalyticsInterfaceService.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const BaseService = require("../../services/BaseService"); + +/** +* Service class that manages Analytics interface registrations. +* Handles registration of the puter-analytics interface. +* @extends BaseService +*/ +class AnalyticsInterfaceService extends BaseService { + /** + * Service class for managing Analytics interface registrations. + * Extends the base service to provide analytics interface management. + */ + async ['__on_driver.register.interfaces'] () { + const svc_registry = this.services.get('registry'); + const col_interfaces = svc_registry.get('interfaces'); + + // Register the puter-analytics interface + col_interfaces.set('puter-analytics', { + no_sdk: true, + description: 'Analytics.', + methods: { + create_trace: { + description: 'Get a trace UID.', + parameters: { + trace_id: { type: 'string', optional: true }, + }, + result: { type: 'string' } + }, + record: { + description: 'Record an event.', + parameters: { + trace_id: { type: 'string', optional: true }, + tags: { type: 'json' }, + fields: { type: 'json' }, + }, + result: { type: 'void' } + } + } + }); + } +} + +module.exports = { + AnalyticsInterfaceService +}; \ No newline at end of file diff --git a/src/backend/src/modules/analytics/AnalyticsModule.js b/src/backend/src/modules/analytics/AnalyticsModule.js new file mode 100644 index 000000000..2343bc760 --- /dev/null +++ b/src/backend/src/modules/analytics/AnalyticsModule.js @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const { AdvancedBase } = require("@heyputer/putility"); +const { AnalyticsInterfaceService } = require("./AnalyticsInterfaceService"); + +/** + * A module for registering analytics interfaces. + */ +class AnalyticsModule extends AdvancedBase { + async install(context) { + const services = context.get('services'); + + // Register interface services + services.registerService('analytics-interface', AnalyticsInterfaceService); + } +} + +module.exports = { + AnalyticsModule, +}; \ No newline at end of file diff --git a/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js new file mode 100644 index 000000000..53bd6a9ec --- /dev/null +++ b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// METADATA // {"ai-commented":{"service":"claude"}} +const BaseService = require("../../services/BaseService"); + +/** +* Service class that manages Entity Store interface registrations. +* Handles registration of the crud-q interface which is used by various +* entity storage services. +* @extends BaseService +*/ +class EntityStoreInterfaceService extends BaseService { + /** + * Service class for managing Entity Store interface registrations. + * Extends the base service to provide entity storage interface management. + */ + async ['__on_driver.register.interfaces'] () { + const svc_registry = this.services.get('registry'); + const col_interfaces = svc_registry.get('interfaces'); + + // Define the standard CRUD interface methods that will be reused + const crudMethods = { + create: { + parameters: { + object: { + type: 'json', + subtype: 'object', + required: true, + }, + options: { type: 'json' }, + } + }, + read: { + parameters: { + uid: { type: 'string' }, + id: { type: 'json' }, + params: { type: 'json' }, + } + }, + select: { + parameters: { + predicate: { type: 'json' }, + offset: { type: 'number' }, + limit: { type: 'number' }, + params: { type: 'json' }, + } + }, + update: { + parameters: { + id: { type: 'json' }, + object: { + type: 'json', + subtype: 'object', + required: true, + }, + options: { type: 'json' }, + } + }, + upsert: { + parameters: { + id: { type: 'json' }, + object: { + type: 'json', + subtype: 'object', + required: true, + }, + options: { type: 'json' }, + } + }, + delete: { + parameters: { + uid: { type: 'string' }, + id: { type: 'json' }, + } + }, + }; + + // Register the crud-q interface + col_interfaces.set('crud-q', { + methods: { ...crudMethods } + }); + + // Register entity-specific interfaces that use crud-q + const entityInterfaces = [ + { + name: 'puter-apps', + description: 'Manage a developer\'s apps on Puter.' + }, + { + name: 'puter-subdomains', + description: 'Manage subdomains on Puter.' + }, + { + name: 'puter-notifications', + description: 'Read notifications on Puter.' + } + ]; + + // Register each entity interface with the same CRUD methods + for (const entity of entityInterfaces) { + col_interfaces.set(entity.name, { + description: entity.description, + methods: { ...crudMethods } + }); + } + } +} + +module.exports = { + EntityStoreInterfaceService +}; \ No newline at end of file diff --git a/src/backend/src/modules/entitystore/EntityStoreModule.js b/src/backend/src/modules/entitystore/EntityStoreModule.js new file mode 100644 index 000000000..fa42c8789 --- /dev/null +++ b/src/backend/src/modules/entitystore/EntityStoreModule.js @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const { AdvancedBase } = require("@heyputer/putility"); +const { EntityStoreInterfaceService } = require("./EntityStoreInterfaceService"); + +/** + * A module for registering entity store interfaces. + */ +class EntityStoreModule extends AdvancedBase { + async install(context) { + const services = context.get('services'); + + // Register interface services + services.registerService('entitystore-interface', EntityStoreInterfaceService); + } +} + +module.exports = { + EntityStoreModule, +}; \ No newline at end of file diff --git a/src/backend/src/modules/kvstore/KVStoreInterfaceService.js b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js new file mode 100644 index 000000000..5f022b712 --- /dev/null +++ b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const BaseService = require("../../services/BaseService"); + +/** +* Service class that manages KVStore interface registrations. +* Handles registration of the puter-kvstore interface. +* @extends BaseService +*/ +class KVStoreInterfaceService extends BaseService { + /** + * Service class for managing KVStore interface registrations. + * Extends the base service to provide key-value store interface management. + */ + async ['__on_driver.register.interfaces'] () { + const svc_registry = this.services.get('registry'); + const col_interfaces = svc_registry.get('interfaces'); + + // Register the puter-kvstore interface + col_interfaces.set('puter-kvstore', { + description: 'A simple key-value store.', + methods: { + get: { + description: 'Get a value by key.', + parameters: { + key: { type: 'json', required: true }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'json' }, + }, + set: { + description: 'Set a value by key.', + parameters: { + key: { type: 'string', required: true, }, + value: { type: 'json' }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'void' }, + }, + del: { + description: 'Delete a value by key.', + parameters: { + key: { type: 'string' }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'void' }, + }, + list: { + description: 'List all key-value pairs.', + parameters: { + as: { + type: 'string', + }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'array' }, + }, + flush: { + description: 'Delete all key-value pairs.', + parameters: {}, + result: { type: 'void' }, + }, + incr: { + description: 'Increment a value by key.', + parameters: { + key: { type: 'string', required: true, }, + amount: { type: 'number' }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'number' }, + }, + decr: { + description: 'Increment a value by key.', + parameters: { + key: { type: 'string', required: true, }, + amount: { type: 'number' }, + app_uid: { type: 'string', optional: true }, + }, + result: { type: 'number' }, + }, + } + }); + } +} + +module.exports = { + KVStoreInterfaceService +}; \ No newline at end of file diff --git a/src/backend/src/modules/kvstore/KVStoreModule.js b/src/backend/src/modules/kvstore/KVStoreModule.js new file mode 100644 index 000000000..55dd59709 --- /dev/null +++ b/src/backend/src/modules/kvstore/KVStoreModule.js @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const { AdvancedBase } = require("@heyputer/putility"); +const { KVStoreInterfaceService } = require("./KVStoreInterfaceService"); + +/** + * A module for registering key-value store interfaces. + */ +class KVStoreModule extends AdvancedBase { + async install(context) { + const services = context.get('services'); + + // Register interface services + services.registerService('kvstore-interface', KVStoreInterfaceService); + } +} + +module.exports = { + KVStoreModule, +}; \ No newline at end of file diff --git a/src/backend/src/services/drivers/DriverService.js b/src/backend/src/services/drivers/DriverService.js index 82a07c4fd..58d41a693 100644 --- a/src/backend/src/services/drivers/DriverService.js +++ b/src/backend/src/services/drivers/DriverService.js @@ -111,12 +111,6 @@ class DriverService extends BaseService { const col_interfaces = svc_registry.get('interfaces'); const col_drivers = svc_registry.get('drivers'); const col_types = svc_registry.get('types'); - { - const default_interfaces = require('./interfaces'); - for ( const k in default_interfaces ) { - col_interfaces.set(k, default_interfaces[k]); - } - } { const types = this.modules.types; for ( const k in types ) { diff --git a/src/backend/src/services/drivers/interfaces.js b/src/backend/src/services/drivers/interfaces.js deleted file mode 100644 index 2871ea4d4..000000000 --- a/src/backend/src/services/drivers/interfaces.js +++ /dev/null @@ -1,299 +0,0 @@ -// METADATA // {"ai-commented":{"service":"xai"}} -/* - * Copyright (C) 2024-present Puter Technologies Inc. - * - * This file is part of Puter. - * - * Puter is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -const ENTITY_STORAGE_INTERFACE = { - methods: { - create: { - parameters: { - object: { - type: 'json', - subtype: 'object', - required: true, - }, - options: { type: 'json' }, - } - }, - read: { - parameters: { - uid: { type: 'string' }, - id: { type: 'json' }, - params: { type: 'json' }, - } - }, - select: { - parameters: { - predicate: { type: 'json' }, - offset: { type: 'number' }, - limit: { type: 'number' }, - params: { type: 'json' }, - } - }, - update: { - parameters: { - id: { type: 'json' }, - object: { - type: 'json', - subtype: 'object', - required: true, - }, - options: { type: 'json' }, - } - }, - upsert: { - parameters: { - id: { type: 'json' }, - object: { - type: 'json', - subtype: 'object', - required: true, - }, - options: { type: 'json' }, - } - }, - delete: { - parameters: { - uid: { type: 'string' }, - id: { type: 'json' }, - } - }, - }, -} - -module.exports = { - 'hello-world': { - description: 'A simple driver that returns a greeting.', - methods: { - greet: { - description: 'Returns a greeting.', - parameters: { - subject: { - type: 'string', - optional: true, - }, - }, - result: { type: 'string' }, - } - } - }, - // Note: these are all prefixed with 'puter-' to avoid name collisions - // with possible future support for user-contributed driver interfaces. - 'puter-ocr': { - description: 'Optical character recognition.', - methods: { - recognize: { - description: 'Recognize text in an image or document.', - parameters: { - source: { - type: 'file', - }, - }, - result: { type: 'image' }, - }, - }, - }, - 'puter-kvstore': { - description: 'A simple key-value store.', - methods: { - get: { - description: 'Get a value by key.', - parameters: { - key: { type: 'json', required: true }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'json' }, - }, - set: { - description: 'Set a value by key.', - parameters: { - key: { type: 'string', required: true, }, - value: { type: 'json' }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'void' }, - }, - del: { - description: 'Delete a value by key.', - parameters: { - key: { type: 'string' }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'void' }, - }, - list: { - description: 'List all key-value pairs.', - parameters: { - as: { - type: 'string', - }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'array' }, - }, - flush: { - description: 'Delete all key-value pairs.', - parameters: {}, - result: { type: 'void' }, - }, - incr: { - description: 'Increment a value by key.', - parameters: { - key: { type: 'string', required: true, }, - amount: { type: 'number' }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'number' }, - }, - decr: { - description: 'Increment a value by key.', - parameters: { - key: { type: 'string', required: true, }, - amount: { type: 'number' }, - app_uid: { type: 'string', optional: true }, - }, - result: { type: 'number' }, - }, - /* - expireat: { - description: 'Set a key\'s time-to-live.', - parameters: { - key: { type: 'string', required: true, }, - timestamp: { type: 'number', required: true, }, - app_uid: { type: 'string', optional: true }, - }, - }, - expire: { - description: 'Set a key\'s time-to-live.', - parameters: { - key: { type: 'string', required: true, }, - ttl: { type: 'number', required: true, }, - app_uid: { type: 'string', optional: true }, - }, - } - */ - } - }, - 'puter-chat-completion': { - description: 'Chatbot.', - methods: { - complete: { - description: 'Get completions for a chat log.', - parameters: { - messages: { type: 'json' }, - vision: { type: 'flag' }, - }, - result: { type: 'json' } - } - } - }, - 'puter-image-generation': { - description: 'AI Image Generation.', - methods: { - generate: { - description: 'Generate an image from a prompt.', - parameters: { - prompt: { type: 'string' }, - }, - result_choices: [ - { - names: ['image'], - type: { - $: 'stream', - content_type: 'image', - } - }, - { - names: ['url'], - type: { - $: 'string:url:web', - content_type: 'image', - } - }, - ], - result: { - description: 'URL of the generated image.', - type: 'string' - } - } - } - }, - 'puter-tts': { - description: 'Text-to-speech.', - methods: { - list_voices: { - description: 'List available voices.', - parameters: {}, - }, - synthesize: { - description: 'Synthesize speech from text.', - parameters: { - text: { type: 'string' }, - voice: { type: 'string' }, - language: { type: 'string' }, - ssml: { type: 'flag' }, - }, - result_choices: [ - { - names: ['audio'], - type: { - $: 'stream', - content_type: 'audio', - } - }, - ] - }, - } - }, - 'puter-analytics': { - no_sdk: true, - description: 'Analytics.', - methods: { - create_trace: { - description: 'Get a trace UID.', - parameters: { - trace_id: { type: 'string', optional: true }, - }, - result: { type: 'string' } - }, - record: { - description: 'Record an event.', - parameters: { - trace_id: { type: 'string', optional: true }, - tags: { type: 'json' }, - fields: { type: 'json' }, - }, - result: { type: 'void' } - } - } - }, - 'puter-apps': { - ...ENTITY_STORAGE_INTERFACE, - description: 'Manage a developer\'s apps on Puter.', - }, - 'puter-subdomains': { - ...ENTITY_STORAGE_INTERFACE, - description: 'Manage subdomains on Puter.', - }, - 'puter-notifications': { - ...ENTITY_STORAGE_INTERFACE, - description: 'Read notifications on Puter.', - }, - 'crud-q': { - ...ENTITY_STORAGE_INTERFACE, - }, -};