refactor: begin migrating utility code
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (18.x) (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled

I'm calling this approach a "re-core"; see src/backend-core-0/README.md
for more information about this.
This commit is contained in:
KernelDeimos
2025-03-31 22:09:29 -04:00
parent f2305ff289
commit 7a3365a25c
7 changed files with 181 additions and 35 deletions
+9
View File
@@ -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",
+50
View File
@@ -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.
+20
View File
@@ -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"
}
+27
View File
@@ -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()]
}
]);
+1
View File
@@ -0,0 +1 @@
export * as validation from './pdim/validation';
+73
View File
@@ -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;
}
+1 -35
View File
@@ -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,