From 7a3365a25c6cd9d5626dfd9eba79b66462c90015 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 31 Mar 2025 22:09:29 -0400 Subject: [PATCH] refactor: begin migrating utility code I'm calling this approach a "re-core"; see src/backend-core-0/README.md for more information about this. --- package-lock.json | 9 +++ src/backend-core-0/README.md | 50 ++++++++++++++++ src/backend-core-0/package.json | 20 +++++++ src/backend-core-0/rollup.config.js | 27 +++++++++ src/backend-core-0/src/exports.js | 1 + src/backend-core-0/src/pdim/validation.js | 73 +++++++++++++++++++++++ src/backend/src/helpers.js | 36 +---------- 7 files changed, 181 insertions(+), 35 deletions(-) create mode 100644 src/backend-core-0/README.md create mode 100644 src/backend-core-0/package.json create mode 100644 src/backend-core-0/rollup.config.js create mode 100644 src/backend-core-0/src/exports.js create mode 100644 src/backend-core-0/src/pdim/validation.js diff --git a/package-lock.json b/package-lock.json index 838606a95..1e7291120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2049,6 +2049,10 @@ "resolved": "src/backend", "link": true }, + "node_modules/@heyputer/backend-core-0": { + "resolved": "src/backend-core-0", + "link": true + }, "node_modules/@heyputer/gui": { "resolved": "src/gui", "link": true @@ -15133,6 +15137,11 @@ "typescript": "^5.1.6" } }, + "src/backend-core-0": { + "name": "@heyputer/backend-core-0", + "version": "1.0.0", + "license": "AGPL-3.0-only" + }, "src/backend/node_modules/lru-cache": { "version": "11.0.2", "license": "ISC", diff --git a/src/backend-core-0/README.md b/src/backend-core-0/README.md new file mode 100644 index 000000000..1bd30f2d8 --- /dev/null +++ b/src/backend-core-0/README.md @@ -0,0 +1,50 @@ +# What is `backend-core-0`? + +The ugly name is intentional. We prefer to refactor incrementally which +means we need a way to "re-core" the backend, and we may do this more +than once simultaneously (hence it's `0` right now). + +"re-core" is a term I just made up, and it means this: +> To find the utility code that is not dependent on other utility code, +> move that into a new package, and then continue this process in multiple +> iterations until the problem being solved is solved. + +The purpose of `backend-core-0` is to move common dependencies for driver +implementations into a new core so that existing driver implementations +can be moved from backend modules (part of the `backend` package) to +extensions (packages added to Puter at runtime). + +What will follow is a log of what was moved here and why. + +## 2025-03-31 + +The AI/LLM driver module depends on constructs related to driver +interfaces. The actual mechanism that facilitates these interfaces, +as well as the interface format, both don't really have a name yet; +I'll call it the "PDIM" (Puter Driver Interface Mechanism) in this log. + +The PDIM depends on some class definitions currently in +`src/backend/src/services/drivers/meta` which are split into the categories +of "Constructs" and "Runtime Entities". A construct is the class +representation of something defined in an interface, including +**Interface** itself, and a RuntimeEntity - well there's only one; +it's a wrapper for runtime-typed values such as "jpeg stream". + +A construct called **Parameter**, which is the class represerntation +of a parameter of an interface that a driver may implement, depends on +a file called `types.js`. This file defines high-level types like String, +URL, File, etc that can be used in Puter drivers. + +Some types depend on utilities in Puter's backend: +- **File** + - filesystem/validation + - `is_valid_uuidv4` from helpers.js +- **URL** + - `is_valid_url` from helpers.js + +These utilities do not have dependencies so they are good candidates +to be moved into this package. Afterwards, it currently apperas that +everything in `drivers/meta` can be moved here, allowing DriverService +to finally be moved to a backend module (right now it's part of backend +core), and driver modules like `puterai` will be closer to being able +to be moved to extensions. diff --git a/src/backend-core-0/package.json b/src/backend-core-0/package.json new file mode 100644 index 000000000..1bda5d5dd --- /dev/null +++ b/src/backend-core-0/package.json @@ -0,0 +1,20 @@ +{ + "name": "@heyputer/backend-core-0", + "version": "1.0.0", + "description": "The ugly name is intentional. We prefer to refactor incrementally which means we need a way to \"re-core\" the backend, and we may do this more than once simultaneously (hence it's `0` right now).", + "type": "module", + "scripts": { + "build": "rollup -c", + "prepare": "npm run build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "exports": { + ".": { + "require": "./dist/cjs/exports.cjs", + "import": "./dist/esm/exports.js" + } + }, + "keywords": [], + "author": "", + "license": "AGPL-3.0-only" +} diff --git a/src/backend-core-0/rollup.config.js b/src/backend-core-0/rollup.config.js new file mode 100644 index 000000000..838911a3a --- /dev/null +++ b/src/backend-core-0/rollup.config.js @@ -0,0 +1,27 @@ +import { defineConfig } from 'rollup'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; + +export default defineConfig([ + // ESM build + { + input: 'src/exports.js', + output: { + dir: 'dist/esm', + format: 'es', + preserveModules: true + }, + plugins: [nodeResolve()] + }, + // CJS build + { + input: 'src/exports.js', + output: { + dir: 'dist/cjs', + format: 'cjs', + preserveModules: true, + entryFileNames: '[name].cjs', + }, + plugins: [nodeResolve(), commonjs()] + } +]); diff --git a/src/backend-core-0/src/exports.js b/src/backend-core-0/src/exports.js new file mode 100644 index 000000000..f331a9463 --- /dev/null +++ b/src/backend-core-0/src/exports.js @@ -0,0 +1 @@ +export * as validation from './pdim/validation'; diff --git a/src/backend-core-0/src/pdim/validation.js b/src/backend-core-0/src/pdim/validation.js new file mode 100644 index 000000000..c3e6e0868 --- /dev/null +++ b/src/backend-core-0/src/pdim/validation.js @@ -0,0 +1,73 @@ +export const is_valid_uuid = ( uuid ) => { + let s = "" + uuid; + s = s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + return !! s; +} + +export const is_valid_uuid4 = ( uuid ) => { + return is_valid_uuid(uuid); +} + +export const is_specifically_uuidv4 = ( uuid ) => { + let s = "" + uuid; + + s = s.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i); + if (!s) { + return false; + } + return true; +} + +export const is_valid_url = ( url ) => { + let s = "" + url; + + try { + new URL(s); + return true; + } catch (e) { + return false; + } +} + +const path_excludes = () => /[\x00-\x1F]/g; + +// this characters are not allowed in path names because +// they might be used to trick the user into thinking +// a filename is different from what it actually is. +const safety_excludes = [ + /[\u202A-\u202E]/, // RTL and LTR override + /[\u200E-\u200F]/, // RTL and LTR mark + /[\u2066-\u2069]/, // RTL and LTR isolate + /[\u2028-\u2029]/, // line and paragraph separator + /[\uFF01-\uFF5E]/, // fullwidth ASCII + /[\u2060]/, // word joiner + /[\uFEFF]/, // zero width no-break space + /[\uFFFE-\uFFFF]/, // non-characters +]; + +export const is_valid_path = (path, { + no_relative_components, + allow_path_fragment, +} = {}) => { + if ( typeof path !== 'string' ) return false; + if ( path.length < 1 ) false; + if ( path_excludes().test(path) ) return false; + for ( const exclude of safety_excludes ) { + if ( exclude.test(path) ) return false; + } + + if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) { + return false; + } + + if ( no_relative_components ) { + const components = path.split('/'); + for ( const component of components ) { + if ( component === '' ) continue; + const name_without_dots = component.replace(/\./g, ''); + if ( name_without_dots.length < 1 ) return false; + } + } + + return true; +} diff --git a/src/backend/src/helpers.js b/src/backend/src/helpers.js index 4abdcce89..836d3d79d 100644 --- a/src/backend/src/helpers.js +++ b/src/backend/src/helpers.js @@ -1194,37 +1194,6 @@ async function jwt_auth(req){ return ancestors; } -function is_valid_uuid ( uuid ) { - let s = "" + uuid; - s = s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - return !! s; -} - -function is_valid_uuid4 ( uuid ) { - return is_valid_uuid(uuid); -} - -function is_specifically_uuidv4 ( uuid ) { - let s = "" + uuid; - - s = s.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i); - if (!s) { - return false; - } - return true; -} - -function is_valid_url ( url ) { - let s = "" + url; - - try { - new URL(s); - return true; - } catch (e) { - return false; - } -} - function hyphenize_confirm_code(email_confirm_code){ email_confirm_code = email_confirm_code.toString(); email_confirm_code = @@ -1679,12 +1648,9 @@ module.exports = { is_empty, is_shared_with, is_shared_with_anyone, - is_valid_uuid4, - is_valid_uuid, - is_specifically_uuidv4, + ...require('@heyputer/backend-core-0').validation, is_temp_users_disabled, is_user_signup_disabled, - is_valid_url, jwt_auth, mv, number_format,