From a103ca9bcb1c18d0a1e207659dd5e862bef61cf6 Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Thu, 11 Dec 2025 16:55:29 -0800 Subject: [PATCH] cleanup: remove Library + bloated utils where possible (#2142) --- doc/contributors/extensions/definitions.md | 34 +----- src/backend/src/CoreModule.js | 15 +-- src/backend/src/Extension.js | 28 ++--- src/backend/src/definitions/Library.js | 25 ---- src/backend/src/definitions/SimpleEntity.js | 2 +- src/backend/src/filesystem/FSNodeContext.js | 10 +- src/backend/src/libraries/ArrayUtil.js | 53 -------- src/backend/src/libraries/ArrayUtil.test.ts | 56 --------- src/backend/src/libraries/LibTypeTagged.js | 113 ------------------ src/backend/src/modules/core/LogService.js | 3 +- .../src/modules/puterfs/SizeService.js | 10 +- .../src/om/entitystorage/NotificationES.js | 3 +- .../src/om/entitystorage/ProtectedAppES.js | 9 +- .../src/om/entitystorage/SetOwnerES.js | 3 +- src/backend/src/routers/drivers/call.js | 5 +- src/backend/src/services/CleanEmailService.js | 3 +- .../src/services/FeatureFlagService.js | 3 +- .../src/services/PermissionAPIService.js | 25 ++-- src/backend/src/services/ShareService.js | 4 +- src/backend/src/services/SystemDataService.js | 21 ++-- src/backend/src/services/ai/README.md | 50 +------- src/backend/src/services/ai/utils/Messages.js | 19 ++- src/backend/src/services/auth/AuthService.js | 3 +- .../src/services/drivers/DriverService.js | 3 +- src/backend/src/structured/sequence/share.js | 4 +- .../sequence/share/process_shares.js | 99 +++++++++++++-- .../src/structured/sequence/share/validate.js | 7 +- src/backend/src/util/langutil.js | 49 -------- src/backend/src/util/objutil.js | 6 +- 29 files changed, 166 insertions(+), 499 deletions(-) delete mode 100644 src/backend/src/definitions/Library.js delete mode 100644 src/backend/src/libraries/ArrayUtil.js delete mode 100644 src/backend/src/libraries/ArrayUtil.test.ts delete mode 100644 src/backend/src/libraries/LibTypeTagged.js delete mode 100644 src/backend/src/util/langutil.js diff --git a/doc/contributors/extensions/definitions.md b/doc/contributors/extensions/definitions.md index 4e2dbc80..94e7ab16 100644 --- a/doc/contributors/extensions/definitions.md +++ b/doc/contributors/extensions/definitions.md @@ -11,36 +11,4 @@ const config = use('core.config'); extension.get('/get-origin', { noauth: true }, (req, res) => { res.send(config.origin); }) -``` - -### `core.util.*` - Utility Functions - -These utilities come from `src/backend/src/util` in Puter's repo. -Each file in this directory has its exports auto-loaded into this -namespace. For example, `src/backend/src/util/langutil.js` is available -via `use('core.util.langutil')` or `use.core.util.langutil`. - -#### `core.util.helpers` - Helper Functions - -Common utility functions used throughout Puter's backend. Use with caution as -some of these functions may be deprecated. - -> **note:** the following documentation is incomplete - -#### `core.util.langutil` - Language Helpers - -##### `whatis(thing :any)` - -- Returns `"array"` if `thing` is an array. -- Returns `"null"` if `thing` is `null`. -- Returns `typeof thing` for any other case. - -##### `nou(value :any)` - -Simply a "null or undefined" check. - -##### `can(value :any, capabilities :Array)` - -Checks if something has the specified capabilities. At the time of -writing the only one supported is `iterate`, which will check if -`value[Symbol.iterator]` is truthy +``` \ No newline at end of file diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js index 8a89a4d9..a4263939 100644 --- a/src/backend/src/CoreModule.js +++ b/src/backend/src/CoreModule.js @@ -18,7 +18,6 @@ * along with this program. If not, see . */ const { AdvancedBase } = require('@heyputer/putility'); -const Library = require('./definitions/Library'); const { NotificationES } = require('./om/entitystorage/NotificationES'); const { ProtectedAppES } = require('./om/entitystorage/ProtectedAppES'); const { Context } = require('./util/context'); @@ -41,7 +40,6 @@ const install = async ({ context, services, app, useapi, modapi }) => { useapi.withuse(() => { def('Service', require('./services/BaseService')); def('Module', AdvancedBase); - def('Library', Library); def('core.util.helpers', require('./helpers')); def('core.util.permission', require('./services/auth/permissionUtils.mjs').PermissionUtil); @@ -88,22 +86,11 @@ const install = async ({ context, services, app, useapi, modapi }) => { runtimeModule.exports = useapi.use('core'); }); - useapi.withuse(() => { - const ArrayUtil = require('./libraries/ArrayUtil'); - services.registerService('util-array', ArrayUtil); - - const LibTypeTagged = require('./libraries/LibTypeTagged'); - services.registerService('lib-type-tagged', LibTypeTagged); - }); - modapi.libdir('core.util', './util'); // === SERVICES === - // /!\ IMPORTANT /!\ - // For new services, put the import immediately above the - // call to services.registerService. We'll clean this up - // in a future PR. + // TODO: move these to top level imports or await imports and esm this file const { CommandService } = require('./services/CommandService'); const { HTTPThumbnailService } = require('./services/thumbnails/HTTPThumbnailService'); diff --git a/src/backend/src/Extension.js b/src/backend/src/Extension.js index 312dbe0c..141eaf02 100644 --- a/src/backend/src/Extension.js +++ b/src/backend/src/Extension.js @@ -40,20 +40,6 @@ class Extension extends AdvancedBase { }), ]; - randomBrightColor () { - // Bright colors in ANSI (foreground codes 90–97) - const brightColors = [ - // 91, // Bright Red - 92, // Bright Green - // 93, // Bright Yellow - 94, // Bright Blue - 95, // Bright Magenta - // 96, // Bright Cyan - ]; - - return brightColors[Math.floor(Math.random() * brightColors.length)]; - } - constructor (...a) { super(...a); this.service = null; @@ -97,6 +83,20 @@ class Extension extends AdvancedBase { }; } + randomBrightColor () { + // Bright colors in ANSI (foreground codes 90–97) + const brightColors = [ + // 91, // Bright Red + 92, // Bright Green + // 93, // Bright Yellow + 94, // Bright Blue + 95, // Bright Magenta + // 96, // Bright Cyan + ]; + + return brightColors[Math.floor(Math.random() * brightColors.length)]; + } + example () { console.log('Example method called by an extension.'); } diff --git a/src/backend/src/definitions/Library.js b/src/backend/src/definitions/Library.js deleted file mode 100644 index ffe85268..00000000 --- a/src/backend/src/definitions/Library.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 BaseService = require('../services/BaseService'); - -class Library extends BaseService { - // -} - -module.exports = Library; diff --git a/src/backend/src/definitions/SimpleEntity.js b/src/backend/src/definitions/SimpleEntity.js index d49c29ba..a53e3250 100644 --- a/src/backend/src/definitions/SimpleEntity.js +++ b/src/backend/src/definitions/SimpleEntity.js @@ -24,7 +24,7 @@ module.exports = function SimpleEntity ({ name, methods, fetchers }) { Object.assign(entity, methods); for ( const fetcher_name in fetchers ) { entity[`fetch_${ fetcher_name}`] = async function () { - if ( this.values.hasOwnProperty(fetcher_name) ) { + if ( Object.prototype.hasOwnProperty.call(this.values, fetcher_name) ) { return this.values[fetcher_name]; } const value = await fetchers[fetcher_name].call(this); diff --git a/src/backend/src/filesystem/FSNodeContext.js b/src/backend/src/filesystem/FSNodeContext.js index e21088c0..3fa3d2a9 100644 --- a/src/backend/src/filesystem/FSNodeContext.js +++ b/src/backend/src/filesystem/FSNodeContext.js @@ -47,11 +47,14 @@ const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs'); * @property {string} path the path to the filesystem entry * @property {string} uid the UUID of the filesystem entry */ + +const TYPE_FILE = { label: 'File' }; +const TYPE_DIRECTORY = { label: 'Directory' }; module.exports = class FSNodeContext { static CONCERN = 'filesystem'; - static TYPE_FILE = { label: 'File' }; - static TYPE_DIRECTORY = { label: 'Directory' }; + static TYPE_FILE = TYPE_FILE; + static TYPE_DIRECTORY = TYPE_DIRECTORY; static TYPE_SYMLINK = {}; static TYPE_SHORTCUT = {}; static TYPE_UNDETERMINED = {}; @@ -946,3 +949,6 @@ module.exports = class FSNodeContext { return fsentry; } }; + +module.exports.TYPE_FILE = TYPE_FILE; +module.exports.TYPE_DIRECTORY = TYPE_DIRECTORY; diff --git a/src/backend/src/libraries/ArrayUtil.js b/src/backend/src/libraries/ArrayUtil.js deleted file mode 100644 index de44831c..00000000 --- a/src/backend/src/libraries/ArrayUtil.js +++ /dev/null @@ -1,53 +0,0 @@ -const BaseService = require("../services/BaseService"); - -/* - * 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 . - */ -class ArrayUtil extends (globalThis.use?.Library ?? BaseService) { - /** - * - * @param {*} marked_map - * @param {*} subject - */ - remove_marked_items (marked_map, subject) { - for ( let i = 0 ; i < marked_map.length ; i++ ) { - let ii = marked_map[i]; - // track: type check - if ( ! Number.isInteger(ii) ) { - throw new Error('marked_map can only contain integers'); - } - // track: bounds check - if ( ii < 0 && ii >= subject.length ) { - throw new Error('each item in `marked_map` must be within that bounds ' + - 'of `subject`'); - } - } - - marked_map.sort((a, b) => b - a); - - for ( let i = 0 ; i < marked_map.length ; i++ ) { - let ii = marked_map[i]; - subject.splice(ii, 1); - } - - return subject; - } - -} - -module.exports = ArrayUtil; diff --git a/src/backend/src/libraries/ArrayUtil.test.ts b/src/backend/src/libraries/ArrayUtil.test.ts deleted file mode 100644 index a7214d78..00000000 --- a/src/backend/src/libraries/ArrayUtil.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { createTestKernel } from '../../tools/test.mjs'; -import ArrayUtil from './ArrayUtil.js'; - -describe('ArrayUtil', () => { - it('should remove marked items correctly', async () => { - const testKernel = await createTestKernel({ - serviceMap: { - arrayUtil: ArrayUtil, - }, - }); - - const arrayUtil = testKernel.services?.get('arrayUtil'); - - // inner indices - { - const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - ]; - // 0 1 2 3 4 5 6 7 - const marked_map = [2, 5]; - arrayUtil.remove_marked_items(marked_map, subject); - expect(subject.join('')).toBe('abdegh'); - } - // left edge - { - const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - ]; - // 0 1 2 3 4 5 6 7 - const marked_map = [0]; - arrayUtil.remove_marked_items(marked_map, subject); - expect(subject.join('')).toBe('bcdefgh'); - } - // right edge - { - const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - ]; - // 0 1 2 3 4 5 6 7 - const marked_map = [7]; - arrayUtil.remove_marked_items(marked_map, subject); - expect(subject.join('')).toBe('abcdefg'); - } - // both edges - { - const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - ]; - // 0 1 2 3 4 5 6 7 - const marked_map = [0, 7]; - arrayUtil.remove_marked_items(marked_map, subject); - expect(subject.join('')).toBe('bcdefg'); - } - }); -}); diff --git a/src/backend/src/libraries/LibTypeTagged.js b/src/backend/src/libraries/LibTypeTagged.js deleted file mode 100644 index 707cce17..00000000 --- a/src/backend/src/libraries/LibTypeTagged.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { whatis } = require('../util/langutil'); - -class LibTypeTagged extends use.Library { - process (o) { - const could_be = whatis(o) === 'object' || Array.isArray(o); - if ( ! could_be ) { - return { - $: 'error', - code: 'invalid-type', - message: 'should be object or array', - }; - } - - const intermediate = this.get_intermediate_(o); - - if ( ! intermediate.type ) { - return { - $: 'error', - code: 'missing-type-param', - message: 'type parameter is missing', - }; - } - - return this.intermediate_to_standard_(intermediate); - } - - intermediate_to_standard_ (intermediate) { - const out = {}; - out.$ = intermediate.type; - for ( const k in intermediate.meta ) { - out[`$${ k}`] = intermediate.meta[k]; - } - for ( const k in intermediate.body ) { - out[k] = intermediate.body[k]; - } - return out; - } - - get_intermediate_ (o) { - if ( Array.isArray(o) ) { - return this.process_array_(o); - } - - if ( o['$'] === '$meta-body' ) { - return this.process_structured_(o); - } - - return this.process_standard_(o); - } - - process_array_ (a) { - if ( a.length <= 1 || a.length > 3 ) { - return { - $: 'error', - code: 'invalid-array-length', - message: 'tag-typed arrays should have 1-3 elements', - }; - } - - const [type, body = {}, meta = {}] = a; - - return { $: '$', type, body, meta }; - } - - process_structured_ (o) { - if ( ! o.hasOwnProperty('type') ) { - return { - $: 'error', - code: 'missing-type-property', - message: 'missing "type" property', - }; - } - - return { $: '$', ...o }; - } - - process_standard_ (o) { - const type = o.$; - const meta = {}; - const body = {}; - - for ( const k in o ) { - if ( k === '$' ) continue; - if ( k.startsWith('$') ) { - meta[k.slice(1)] = o[k]; - } else { - body[k] = o[k]; - } - } - - return { $: '$', type, meta, body }; - } -} - -module.exports = LibTypeTagged; \ No newline at end of file diff --git a/src/backend/src/modules/core/LogService.js b/src/backend/src/modules/core/LogService.js index c2621187..82ae9f77 100644 --- a/src/backend/src/modules/core/LogService.js +++ b/src/backend/src/modules/core/LogService.js @@ -30,7 +30,6 @@ const winston = require('winston'); const { Context } = require('../../util/context'); const BaseService = require('../../services/BaseService'); const { stringify_log_entry } = require('./lib/log'); -const { whatis } = require('../../util/langutil'); require('winston-daily-rotate-file'); const WINSTON_LEVELS = { @@ -123,7 +122,7 @@ class LogContext { } for ( const k in fields ) { if ( - whatis(fields[k]) === 'object' && + fields[k] && typeof fields[k].toLogFields === 'function' ) fields[k] = fields[k].toLogFields(); } diff --git a/src/backend/src/modules/puterfs/SizeService.js b/src/backend/src/modules/puterfs/SizeService.js index 650266f6..f3fbc072 100644 --- a/src/backend/src/modules/puterfs/SizeService.js +++ b/src/backend/src/modules/puterfs/SizeService.js @@ -18,10 +18,7 @@ */ const { get_dir_size, id2path, get_user, invalidate_cached_user_by_id } = require('../../helpers'); const BaseService = require('../../services/BaseService'); - const { DB_WRITE } = require('../../services/database/consts'); -const { Context } = require('../../util/context'); -const { nou } = require('../../util/langutil'); // TODO: expose to a utility library class UserParameter { @@ -99,9 +96,6 @@ class SizeService extends BaseService { // TODO: remove fs arg and update all calls async add_node_size (fs, node, user, factor = 1) { - const { - fsEntryService, - } = Context.get('services').values; let sz; if ( node.entry.is_dir ) { @@ -125,7 +119,7 @@ class SizeService extends BaseService { return this.global_config.available_device_storage; } - if ( nou(user.free_storage) ) { + if ( !user.free_storage && user.free_storage !== 0 ) { return this.global_config.storage_capacity; } @@ -160,7 +154,7 @@ class SizeService extends BaseService { const fields_ = Object.keys(entry); const fields = fields_.join(', '); - const placeholders = fields_.map(f => '?').join(', '); + const placeholders = fields_.map(_ => '?').join(', '); const values = fields_.map(f => entry[f]); try { diff --git a/src/backend/src/om/entitystorage/NotificationES.js b/src/backend/src/om/entitystorage/NotificationES.js index aaa28be3..9b9bc639 100644 --- a/src/backend/src/om/entitystorage/NotificationES.js +++ b/src/backend/src/om/entitystorage/NotificationES.js @@ -16,7 +16,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { nou } = require('../../util/langutil'); const { Eq, IsNotNull } = require('../query/query'); const { BaseES } = require('./BaseES'); @@ -49,7 +48,7 @@ class NotificationES extends BaseES { if ( typeof value === 'string' ) { value = JSON.parse(value); } - if ( nou(value) ) { + if ( ! value ) { value = {}; } await entity.set('value', value); diff --git a/src/backend/src/om/entitystorage/ProtectedAppES.js b/src/backend/src/om/entitystorage/ProtectedAppES.js index 7ac03844..03206ad3 100644 --- a/src/backend/src/om/entitystorage/ProtectedAppES.js +++ b/src/backend/src/om/entitystorage/ProtectedAppES.js @@ -28,21 +28,16 @@ class ProtectedAppES extends BaseES { const actor = Context.get('actor'); const services = Context.get('services'); - const to_delete = []; for ( let i = 0 ; i < results.length ; i++ ) { const entity = results[i]; if ( ! await this.check_({ actor, services }, entity) ) { continue; } - - to_delete.push(i); + results[i] = undefined; } - const svc_utilArray = services.get('util-array'); - svc_utilArray.remove_marked_items(to_delete, results); - - return results; + return results.filter(e => e !== undefined); } async read (uid) { diff --git a/src/backend/src/om/entitystorage/SetOwnerES.js b/src/backend/src/om/entitystorage/SetOwnerES.js index d7cd31d7..c1e8edc1 100644 --- a/src/backend/src/om/entitystorage/SetOwnerES.js +++ b/src/backend/src/om/entitystorage/SetOwnerES.js @@ -19,7 +19,6 @@ const { get_user } = require('../../helpers'); const { AppUnderUserActorType, UserActorType } = require('../../services/auth/Actor'); const { Context } = require('../../util/context'); -const { nou } = require('../../util/langutil'); const { BaseES } = require('./BaseES'); class SetOwnerES extends BaseES { @@ -65,7 +64,7 @@ class SetOwnerES extends BaseES { }, async _sanitize_owner (entity) { let owner = await entity.get('owner'); - if ( nou(owner) ) return null; + if ( ! owner ) return null; owner = get_user({ id: owner }); await entity.set('owner', owner); }, diff --git a/src/backend/src/routers/drivers/call.js b/src/backend/src/routers/drivers/call.js index 855d52d8..e292ac81 100644 --- a/src/backend/src/routers/drivers/call.js +++ b/src/backend/src/routers/drivers/call.js @@ -22,7 +22,6 @@ const { FileFacade } = require('../../services/drivers/FileFacade'); const { TypeSpec } = require('../../services/drivers/meta/Construct'); const { TypedValue } = require('../../services/drivers/meta/Runtime'); const { Context } = require('../../util/context'); -const { whatis } = require('../../util/langutil'); const { TeePromise } = require('@heyputer/putility').libs.promise; const { valid_file_size } = require('../../util/validutil'); @@ -143,12 +142,12 @@ _handle_multipart = async (req) => { if ( ! Object.prototype.hasOwnProperty.call(dst, key_parts[i]) ) { dst[key_parts[i]] = {}; } - if ( whatis(dst[key_parts[i]]) !== 'object' ) { + if ( !dst[key_parts[i]] || typeof dst[key_parts[i]] !== 'object' || Array.isArray(dst[key_parts[i]]) ) { throw new Error(`Tried to set member of non-object: ${key_parts[i]} in ${fieldname}`); } dst = dst[key_parts[i]]; } - if ( whatis(value) === 'object' && value.$ === 'file' ) { + if ( value && value.$ === 'file' ) { const fileinfo = value; const { v: size, ok: size_ok } = valid_file_size(fileinfo.size); diff --git a/src/backend/src/services/CleanEmailService.js b/src/backend/src/services/CleanEmailService.js index 2aea5810..06542a7a 100644 --- a/src/backend/src/services/CleanEmailService.js +++ b/src/backend/src/services/CleanEmailService.js @@ -18,7 +18,6 @@ */ // METADATA // {"ai-commented":{"service":"claude"}} -const { can } = require('../util/langutil'); const BaseService = require('./BaseService'); /** @@ -157,7 +156,7 @@ class CleanEmailService extends BaseService { email = this.clean(email); const config = this.global_config; - if ( can(config.blocked_email_domains, 'iterate') ) { + if ( Array.isArray(config.blocked_email_domains) ) { for ( const suffix of config.blocked_email_domains ) { if ( email.endsWith(suffix) ) { return false; diff --git a/src/backend/src/services/FeatureFlagService.js b/src/backend/src/services/FeatureFlagService.js index 27b23a64..a014e84e 100644 --- a/src/backend/src/services/FeatureFlagService.js +++ b/src/backend/src/services/FeatureFlagService.js @@ -19,7 +19,6 @@ // METADATA // {"ai-commented":{"service":"claude"}} const { Context } = require('../util/context'); -const { whatis } = require('../util/langutil'); const { PermissionUtil } = require('./auth/permissionUtils.mjs'); const BaseService = require('./BaseService'); @@ -84,7 +83,7 @@ class FeatureFlagService extends BaseService { let value; const options = {}; for ( const arg of a ) { - if ( whatis(arg) === 'object' ) { + if ( arg && typeof arg === 'object' && !Array.isArray(arg) ) { Object.assign(options, arg); continue; } diff --git a/src/backend/src/services/PermissionAPIService.js b/src/backend/src/services/PermissionAPIService.js index de28499f..13bc1d3a 100644 --- a/src/backend/src/services/PermissionAPIService.js +++ b/src/backend/src/services/PermissionAPIService.js @@ -20,7 +20,6 @@ const { APIError } = require('openai'); const configurable_auth = require('../middleware/configurable_auth'); const { Endpoint } = require('../util/expressutil'); -const { whatis } = require('../util/langutil'); const BaseService = require('./BaseService'); /** @@ -86,18 +85,18 @@ class PermissionAPIService extends BaseService { const extra = req.body.extra ?? {}; const metadata = req.body.metadata ?? {}; - if ( whatis(extra) !== 'object' ) { + if ( !extra || typeof extra !== 'object' || Array.isArray(extra) ) { throw APIError.create('field_invalid', null, { key: 'extra', expected: 'object', - got: whatis(extra), + got: extra, }); } - if ( whatis(metadata) !== 'object' ) { + if ( !metadata || typeof metadata !== 'object' || Array.isArray(metadata) ) { throw APIError.create('field_invalid', null, { key: 'metadata', expected: 'object', - got: whatis(metadata), + got: metadata, }); } @@ -135,21 +134,21 @@ class PermissionAPIService extends BaseService { throw APIError.create('forbidden'); } - if ( whatis(req.body.users) !== 'array' ) { + if ( ! Array.isArray(req.body.users) ) { throw APIError.create('field_invalid', null, { key: 'users', expected: 'array', - got: whatis(req.body.users), + got: req.body.users, }); } for ( let i = 0 ; i < req.body.users.length ; i++ ) { const value = req.body.users[i]; - if ( whatis(value) === 'string' ) continue; + if ( typeof value === 'string' ) continue; throw APIError.create('field_invalid', null, { key: `users[${i}]`, expected: 'string', - got: whatis(value), + got: value, }); } @@ -184,21 +183,21 @@ class PermissionAPIService extends BaseService { throw APIError.create('forbidden'); } - if ( whatis(req.body.users) !== 'array' ) { + if ( Array.isArray(req.body.users) ) { throw APIError.create('field_invalid', null, { key: 'users', expected: 'array', - got: whatis(req.body.users), + got: req.body.users, }); } for ( let i = 0 ; i < req.body.users.length ; i++ ) { const value = req.body.users[i]; - if ( whatis(value) === 'string' ) continue; + if ( typeof value === 'string' ) continue; throw APIError.create('field_invalid', null, { key: `users[${i}]`, expected: 'string', - got: whatis(value), + got: value, }); } diff --git a/src/backend/src/services/ShareService.js b/src/backend/src/services/ShareService.js index 3e86c1eb..63c7833f 100644 --- a/src/backend/src/services/ShareService.js +++ b/src/backend/src/services/ShareService.js @@ -20,9 +20,7 @@ const APIError = require('../api/APIError'); const { get_user } = require('../helpers'); const configurable_auth = require('../middleware/configurable_auth'); -const featureflag = require('../middleware/featureflag.js'); const { Endpoint } = require('../util/expressutil'); -const { whatis } = require('../util/langutil'); const { Actor, UserActorType } = require('./auth/Actor'); const BaseService = require('./BaseService'); const { DB_WRITE } = require('./database/consts'); @@ -365,7 +363,7 @@ class ShareService extends BaseService { throw new Error('email must be a string'); } // track: type check - if ( whatis(data) !== 'object' ) { + if ( !data || typeof data !== 'object' || Array.isArray(data) ) { throw new Error('data must be an object'); } diff --git a/src/backend/src/services/SystemDataService.js b/src/backend/src/services/SystemDataService.js index 1fdceb77..7d7ebaa5 100644 --- a/src/backend/src/services/SystemDataService.js +++ b/src/backend/src/services/SystemDataService.js @@ -20,7 +20,6 @@ // METADATA // {"ai-commented":{"service":"xai"}} const { LLRead } = require('../filesystem/ll_operations/ll_read'); const { Context } = require('../util/context'); -const { whatis } = require('../util/langutil'); const { stream_to_buffer } = require('../util/streamutil'); const BaseService = require('./BaseService'); @@ -46,23 +45,25 @@ class SystemDataService extends BaseService { * For special objects with a '$' property, it performs dereferencing. */ async interpret (data) { - if ( whatis(data) === 'object' && data.$ ) { + if ( data?.$ ) { return await this.#dereference(data); } - if ( whatis(data) === 'object' ) { - const new_o = {}; - for ( const k in data ) { - new_o[k] = await this.interpret(data[k]); - } - return new_o; - } - if ( whatis(data) === 'array' ) { + + if ( Array.isArray(data) ) { const new_a = []; for ( const v of data ) { new_a.push(await this.interpret(v)); } return new_a; } + if ( data && typeof data === 'object' ) { + const new_o = {}; + for ( const k in data ) { + new_o[k] = await this.interpret(data[k]); + } + return new_o; + } + return data; } diff --git a/src/backend/src/services/ai/README.md b/src/backend/src/services/ai/README.md index 0c850a6d..43ab6f13 100644 --- a/src/backend/src/services/ai/README.md +++ b/src/backend/src/services/ai/README.md @@ -275,52 +275,4 @@ Returns the default model identifier for the XAI service This module has external relative imports. When these are removed it may become possible to move this module to an -extension. - -**Imports:** -- `../../api/APIError` -- `../../services/auth/PermissionService` -- `../../services/BaseService` (use.BaseService) -- `../../services/database/consts` -- `../../services/drivers/meta/Construct` -- `../../services/drivers/meta/Runtime` -- `../../util/context` -- `../../services/BaseService` (use.BaseService) -- `../../services/BaseService` (use.BaseService) -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../services/BaseService` (use.BaseService) -- `../../api/APIError` -- `../../services/BaseService` (use.BaseService) -- `../../util/langutil` -- `../../services/drivers/meta/Runtime` -- `../../api/APIError` -- `../../util/promise` -- `../../services/BaseService` (use.BaseService) -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../util/langutil` -- `../../util/promise` -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../util/langutil` -- `../../util/promise` -- `../../api/APIError` -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../util/context` -- `../../util/smolutil` -- `../../util/langutil` -- `../../util/promise` -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../util/context` -- `../../config` -- `../../services/BaseService` (use.BaseService) -- `../../services/drivers/meta/Runtime` -- `../../util/langutil` -- `../../util/promise` -- `../../services/BaseService` (use.BaseService) -- `../../util/langutil` -- `../../services/drivers/meta/Runtime` -- `../../util/promise` +extension. \ No newline at end of file diff --git a/src/backend/src/services/ai/utils/Messages.js b/src/backend/src/services/ai/utils/Messages.js index 7f0dae88..64cad9b8 100644 --- a/src/backend/src/services/ai/utils/Messages.js +++ b/src/backend/src/services/ai/utils/Messages.js @@ -1,4 +1,3 @@ -import { whatis } from '../../../util/langutil.js'; /** * Normalizes a single message into a standardized format with role and content array. @@ -22,7 +21,7 @@ export const normalize_single_message = (message, params = {}) => { content: [message], }; } - if ( whatis(message) !== 'object' ) { + if ( !message || typeof message !== 'object' || Array.isArray(message) ) { throw new Error('each message must be a string or object'); } if ( ! message.role ) { @@ -45,18 +44,18 @@ export const normalize_single_message = (message, params = {}) => { throw new Error('each message must have a \'content\' property'); } } - if ( whatis(message.content) !== 'array' ) { + if ( ! Array.isArray(message.content) ) { message.content = [message.content]; } // Coerce each content block into an object for ( let i = 0 ; i < message.content.length ; i++ ) { - if ( whatis(message.content[i]) === 'string' ) { + if ( typeof message.content[i] === 'string' ) { message.content[i] = { type: 'text', text: message.content[i], }; } - if ( whatis(message.content[i]) !== 'object' ) { + if ( !message || typeof message.content[i] !== 'object' || Array.isArray(message.content[i]) ) { throw new Error('each message content item must be a string or object'); } if ( typeof message.content[i].text === 'string' && !message.content[i].type ) { @@ -158,22 +157,22 @@ export const extract_and_remove_system_messages = (messages) => { */ export const extract_text = (messages) => { return messages.map(m => { - if ( whatis(m) === 'string' ) { + if ( typeof m === 'string' ) { return m; } - if ( whatis(m) !== 'object' ) { + if ( !m || typeof m !== 'object' || Array.isArray(m) ) { return ''; } - if ( whatis(m.content) === 'array' ) { + if ( Array.isArray(m.content) ) { return m.content.map(c => c.text).join(' '); } - if ( whatis(m.content) === 'string' ) { + if ( typeof m.content === 'string' ) { return m.content; } else { const is_text_type = m.content.type === 'text' || !Object.prototype.hasOwnProperty.call(m.content, 'type'); if ( is_text_type ) { - if ( whatis(m.content.text) !== 'string' ) { + if ( typeof m.content.text !== 'string' ) { throw new Error('text content must be a string'); } return m.content.text; diff --git a/src/backend/src/services/auth/AuthService.js b/src/backend/src/services/auth/AuthService.js index f52e6c23..ef884dc0 100644 --- a/src/backend/src/services/auth/AuthService.js +++ b/src/backend/src/services/auth/AuthService.js @@ -24,7 +24,6 @@ const { Context } = require('../../util/context'); const APIError = require('../../api/APIError'); const { DB_WRITE } = require('../database/consts'); const { UUIDFPE } = require('../../util/uuidfpe'); -const { nou } = require('../../util/langutil'); // This constant defines the namespace used for generating app UUIDs from their origins const APP_ORIGIN_UUID_NAMESPACE = '33de3768-8ee0-43e9-9e73-db192b97a5d8'; @@ -102,7 +101,7 @@ class AuthService extends BaseService { const user = await get_user({ uuid: decoded.user_uid }); - if ( nou(user) ) { + if ( ! user ) { throw APIError.create('user_not_found'); } diff --git a/src/backend/src/services/drivers/DriverService.js b/src/backend/src/services/drivers/DriverService.js index 7d5371d2..5f5c1d9f 100644 --- a/src/backend/src/services/drivers/DriverService.js +++ b/src/backend/src/services/drivers/DriverService.js @@ -25,7 +25,6 @@ const BaseService = require('../BaseService'); const { PermissionUtil } = require('../auth/permissionUtils.mjs'); const { Invoker } = require('../../../../putility/src/libs/invoker'); const { get_user } = require('../../helpers'); -const { whatis } = require('../../util/langutil'); const { AdvancedBase } = require('@heyputer/putility'); const strutil = require('@heyputer/putility').libs.string; @@ -578,7 +577,7 @@ class DriverService extends BaseService { throw svc_apiError.create('method_not_found', { interface_name, method_name }); } - if ( method.hasOwnProperty('default_parameter') && whatis(args) !== 'object' ) { + if ( Object.prototype.hasOwnProperty.call(method, 'default_parameter') && (typeof args !== 'object' || Array.isArray(args)) ) { args = { [method.default_parameter]: args }; } diff --git a/src/backend/src/structured/sequence/share.js b/src/backend/src/structured/sequence/share.js index ec0b19e1..0662d312 100644 --- a/src/backend/src/structured/sequence/share.js +++ b/src/backend/src/structured/sequence/share.js @@ -20,7 +20,7 @@ const APIError = require('../../api/APIError'); const { Sequence } = require('../../codex/Sequence'); const config = require('../../config'); const { WorkList } = require('../../util/workutil'); - +const { processSharesSequence } = require('./share/process_shares.js'); const { UsernameNotifSelector } = require('../../services/NotificationService'); const { quot } = require('@heyputer/putility').libs.string; @@ -92,7 +92,7 @@ module.exports = new Sequence([ a.values({ recipients_work, shares_work }); }, require('./share/process_recipients.js'), - require('./share/process_shares.js'), + processSharesSequence, function abort_on_error_if_mode_is_strict (a) { const strict_mode = a.get('strict_mode'); if ( ! strict_mode ) return; diff --git a/src/backend/src/structured/sequence/share/process_shares.js b/src/backend/src/structured/sequence/share/process_shares.js index 59d6bd9f..0b13a7d1 100644 --- a/src/backend/src/structured/sequence/share/process_shares.js +++ b/src/backend/src/structured/sequence/share/process_shares.js @@ -17,14 +17,14 @@ * along with this program. If not, see . */ -const APIError = require('../../../api/APIError'); -const { Sequence } = require('../../../codex/Sequence'); -const config = require('../../../config'); -const { get_user, get_app } = require('../../../helpers'); -const { PermissionUtil } = require('../../../services/auth/permissionUtils.mjs'); -const FSNodeParam = require('../../../api/filesystem/FSNodeParam'); -const { TYPE_DIRECTORY } = require('../../../filesystem/FSNodeContext'); -const { MANAGE_PERM_PREFIX } = require('../../../services/auth/permissionConts.mjs'); +import APIError from '../../../api/APIError.js'; +import { Sequence } from '../../../codex/Sequence.js'; +import config from '../../../config.js'; +import { get_user, get_app } from '../../../helpers.js'; +import { PermissionUtil } from '../../../services/auth/permissionUtils.mjs'; +import FSNodeParam from '../../../api/filesystem/FSNodeParam.js'; +import { TYPE_DIRECTORY } from '../../../filesystem/FSNodeContext.js'; +import { MANAGE_PERM_PREFIX } from '../../../services/auth/permissionConts.mjs'; /* This code is optimized for editors supporting folding. @@ -38,7 +38,84 @@ const { MANAGE_PERM_PREFIX } = require('../../../services/auth/permissionConts.m } */ -module.exports = new Sequence({ +// TODO DS: simplify these into the method +const is_plain_object = (value) => + value !== null && typeof value === 'object' && !Array.isArray(value); + +const error = (code, message) => ({ $: 'error', code, message }); + +const normalize_body = (body) => { + if ( body === undefined ) return {}; + if ( is_plain_object(body) ) return body; + return { value: body }; +}; + +const normalize_meta = (meta) => is_plain_object(meta) ? meta : {}; + +const to_standard = (type, body = {}, meta = {}) => { + if ( ! type ) { + return error('missing-type-param', 'type parameter is missing'); + } + + const prefixed_meta = Object.fromEntries(Object.entries(meta).map(([k, v]) => [`$${k}`, v])); + + return { $: type, ...prefixed_meta, ...body }; +}; + +const process_array = (value) => { + if ( value.length <= 1 || value.length > 3 ) { + return error('invalid-array-length', + 'tag-typed arrays should have 1-3 elements'); + } + + const [type, raw_body, raw_meta] = value; + return to_standard(type, normalize_body(raw_body), normalize_meta(raw_meta)); +}; + +const process_structured = (value) => { + if ( ! Object.prototype.hasOwnProperty.call(value, 'type') ) { + return error('missing-type-property', 'missing "type" property'); + } + + return to_standard(value.type, + normalize_body(value.body), + normalize_meta(value.meta)); +}; + +const process_standard = (value) => { + const meta = {}; + const body = {}; + + for ( const [key, val] of Object.entries(value) ) { + if ( key === '$' ) continue; + if ( key.startsWith('$') ) { + meta[key.slice(1)] = val; + } else { + body[key] = val; + } + } + + return to_standard(value.$, body, meta); +}; + +const parseTypeTagged = (value) => { + const is_object_like = value !== null && typeof value === 'object'; + if ( !is_object_like && !Array.isArray(value) ) { + return error('invalid-type', 'should be object or array'); + } + + if ( Array.isArray(value) ) { + return process_array(value); + } + + if ( value.$ === '$meta-body' ) { + return process_structured(value); + } + + return process_standard(value); +}; + +export const processSharesSequence = new Sequence({ name: 'process shares', beforeEach (a) { const { shares_work } = a.values(); @@ -48,13 +125,11 @@ module.exports = new Sequence({ function validate_share_types (a) { const { result, shares_work } = a.values(); - const lib_typeTagged = a.iget('services').get('lib-type-tagged'); - for ( const item of shares_work.list() ) { const { i } = item; let { value } = item; - const thing = lib_typeTagged.process(value); + const thing = parseTypeTagged(value); if ( thing.$ === 'error' ) { item.invalid = true; result.shares[i] = diff --git a/src/backend/src/structured/sequence/share/validate.js b/src/backend/src/structured/sequence/share/validate.js index bdacad4b..074e4bc0 100644 --- a/src/backend/src/structured/sequence/share/validate.js +++ b/src/backend/src/structured/sequence/share/validate.js @@ -19,7 +19,6 @@ const APIError = require('../../../api/APIError'); const { Sequence } = require('../../../codex/Sequence'); -const { whatis } = require('../../../util/langutil'); /* This code is optimized for editors supporting folding. @@ -46,7 +45,7 @@ module.exports = new Sequence({ throw APIError.create('field_invalid', null, { key: 'metadata', expected: 'object', - got: whatis(metadata), + got: metadata, }); } @@ -68,7 +67,7 @@ module.exports = new Sequence({ throw APIError.create('field_invalid', null, { key: `metadata.${key}`, expected: 'string or number', - got: whatis(value), + got: value, }); } if ( key === 'message' ) { @@ -76,7 +75,7 @@ module.exports = new Sequence({ throw APIError.create('field_invalid', null, { key: `metadata.${key}`, expected: 'string', - got: whatis(value), + got: value, }); } if ( value.length > MAX_MESSAGE_STRING ) { diff --git a/src/backend/src/util/langutil.js b/src/backend/src/util/langutil.js deleted file mode 100644 index e43d187d..00000000 --- a/src/backend/src/util/langutil.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 . - */ -/** - * whatis is an alterative to typeof that reports what - * the type of the value actually is for real. - */ -const whatis = thing => { - if ( Array.isArray(thing) ) return 'array'; - if ( thing === null ) return 'null'; - return typeof thing; -}; - -const nou = v => v === null || v === undefined; - -const can = (v, ...checking) => { - if ( nou(v) ) return false; - const capabilities = {}; - if ( v[Symbol.iterator] ) { - capabilities['iterate'] = true; - } - for ( const to_check of checking ) { - if ( ! capabilities[to_check] ) { - return false; - } - } - return true; -}; - -module.exports = { - whatis, - nou, - can, -}; diff --git a/src/backend/src/util/objutil.js b/src/backend/src/util/objutil.js index b689843d..ec230342 100644 --- a/src/backend/src/util/objutil.js +++ b/src/backend/src/util/objutil.js @@ -1,12 +1,10 @@ -const { whatis } = require('./langutil'); - const DO_NOT_DEFINE = Symbol('DO_NOT_DEFINE'); const createTransformedValues = (input, options = {}, state = {}) => { // initialize state if ( ! state.keys ) state.keys = []; - if ( whatis(input) === 'array' ) { + if ( Array.isArray(input) ) { if ( options.doNotProcessArrays ) { return DO_NOT_DEFINE; } @@ -19,7 +17,7 @@ const createTransformedValues = (input, options = {}, state = {}) => { } return output; } - if ( whatis(input) === 'object' ) { + if ( input && typeof input === 'object' && !Array.isArray(input) ) { const output = {}; Object.setPrototypeOf(output, input); for ( const k in input ) {