cleanup: remove Library + bloated utils where possible (#2142)

This commit is contained in:
Daniel Salazar
2025-12-11 16:55:29 -08:00
committed by GitHub
parent ef9f240a45
commit a103ca9bcb
29 changed files with 166 additions and 499 deletions

View File

@@ -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<string>)`
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
```

View File

@@ -18,7 +18,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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');

View File

@@ -40,20 +40,6 @@ class Extension extends AdvancedBase {
}),
];
randomBrightColor () {
// Bright colors in ANSI (foreground codes 9097)
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 9097)
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.');
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
const BaseService = require('../services/BaseService');
class Library extends BaseService {
//
}
module.exports = Library;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;

View File

@@ -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');
}
});
});

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -16,7 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);

View File

@@ -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) {

View File

@@ -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);
},

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,
});
}

View File

@@ -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');
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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');
}

View File

@@ -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 };
}

View File

@@ -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;

View File

@@ -17,14 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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] =

View File

@@ -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 ) {

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
/**
* 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,
};

View File

@@ -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 ) {