diff --git a/src/backend/src/filesystem/batch/BatchExecutor.js b/src/backend/src/filesystem/batch/BatchExecutor.js
index 062766a6d..5229fc7f8 100644
--- a/src/backend/src/filesystem/batch/BatchExecutor.js
+++ b/src/backend/src/filesystem/batch/BatchExecutor.js
@@ -22,7 +22,7 @@ const commands = require('./commands').commands;
const APIError = require('../../api/APIError');
const { Context } = require('../../util/context');
const config = require('../../config');
-const { TeePromise } = require('../../util/promise');
+const { TeePromise } = require('@heyputer/putility');
const { WorkUnit } = require('../../modules/core/lib/expect');
class BatchExecutor extends AdvancedBase {
diff --git a/src/backend/src/filesystem/hl_operations/hl_write.js b/src/backend/src/filesystem/hl_operations/hl_write.js
index c0f711014..dc78996e6 100644
--- a/src/backend/src/filesystem/hl_operations/hl_write.js
+++ b/src/backend/src/filesystem/hl_operations/hl_write.js
@@ -23,7 +23,7 @@ const StringParam = require("../../api/filesystem/StringParam");
const UserParam = require("../../api/filesystem/UserParam");
const config = require("../../config");
const { chkperm, validate_fsentry_name } = require("../../helpers");
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require("@heyputer/putility").libs.promise;
const { pausing_tee, logging_stream, offset_write_stream, stream_to_the_void } = require("../../util/streamutil");
const { TYPE_DIRECTORY } = require("../FSNodeContext");
const { LLRead } = require("../ll_operations/ll_read");
diff --git a/src/backend/src/fun/dev-console-ui-utils.js b/src/backend/src/fun/dev-console-ui-utils.js
index 102370a1a..27c5392b7 100644
--- a/src/backend/src/fun/dev-console-ui-utils.js
+++ b/src/backend/src/fun/dev-console-ui-utils.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
const config = require('../config');
-const { TeePromise } = require('../util/promise');
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const es_import_promise = new TeePromise();
let stringLength;
diff --git a/src/backend/src/modules/puterai/ClaudeService.js b/src/backend/src/modules/puterai/ClaudeService.js
index b211928a9..36f05cebf 100644
--- a/src/backend/src/modules/puterai/ClaudeService.js
+++ b/src/backend/src/modules/puterai/ClaudeService.js
@@ -5,7 +5,7 @@ const { whatis } = require("../../util/langutil");
const { PassThrough } = require("stream");
const { TypedValue } = require("../../services/drivers/meta/Runtime");
const APIError = require("../../api/APIError");
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const PUTER_PROMPT = `
You are running on an open-source platform called Puter,
diff --git a/src/backend/src/modules/puterai/GroqAIService.js b/src/backend/src/modules/puterai/GroqAIService.js
index 010d0f251..454aad74a 100644
--- a/src/backend/src/modules/puterai/GroqAIService.js
+++ b/src/backend/src/modules/puterai/GroqAIService.js
@@ -3,7 +3,7 @@ const { PassThrough } = require("stream");
const BaseService = require("../../services/BaseService");
const { TypedValue } = require("../../services/drivers/meta/Runtime");
const { nou } = require("../../util/langutil");
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
/**
diff --git a/src/backend/src/modules/puterai/MistralAIService.js b/src/backend/src/modules/puterai/MistralAIService.js
index fc7302653..ccca80fbd 100644
--- a/src/backend/src/modules/puterai/MistralAIService.js
+++ b/src/backend/src/modules/puterai/MistralAIService.js
@@ -5,7 +5,7 @@ const { TypedValue } = require("../../services/drivers/meta/Runtime");
const { nou } = require("../../util/langutil");
const axios = require('axios');
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
/**
diff --git a/src/backend/src/modules/puterai/OpenAICompletionService.js b/src/backend/src/modules/puterai/OpenAICompletionService.js
index 19c0e2e60..881cd7f62 100644
--- a/src/backend/src/modules/puterai/OpenAICompletionService.js
+++ b/src/backend/src/modules/puterai/OpenAICompletionService.js
@@ -6,7 +6,7 @@ const { TypedValue } = require('../../services/drivers/meta/Runtime');
const { Context } = require('../../util/context');
const SmolUtil = require('../../util/smolutil');
const { nou } = require('../../util/langutil');
-const { TeePromise } = require('../../util/promise');
+const { TeePromise } = require('@heyputer/putility').libs.promise;
/**
diff --git a/src/backend/src/modules/puterai/TogetherAIService.js b/src/backend/src/modules/puterai/TogetherAIService.js
index 9747cafde..6ca2967b8 100644
--- a/src/backend/src/modules/puterai/TogetherAIService.js
+++ b/src/backend/src/modules/puterai/TogetherAIService.js
@@ -3,7 +3,7 @@ const { PassThrough } = require("stream");
const BaseService = require("../../services/BaseService");
const { TypedValue } = require("../../services/drivers/meta/Runtime");
const { nou } = require("../../util/langutil");
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
/**
diff --git a/src/backend/src/modules/puterai/XAIService.js b/src/backend/src/modules/puterai/XAIService.js
index cead87281..58e5c9290 100644
--- a/src/backend/src/modules/puterai/XAIService.js
+++ b/src/backend/src/modules/puterai/XAIService.js
@@ -4,7 +4,7 @@ const BaseService = require("../../services/BaseService");
const { whatis, nou } = require("../../util/langutil");
const { PassThrough } = require("stream");
const { TypedValue } = require("../../services/drivers/meta/Runtime");
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const PUTER_PROMPT = `
You are running on an open-source platform called Puter,
diff --git a/src/backend/src/modules/selfhosted/DefaultUserService.js b/src/backend/src/modules/selfhosted/DefaultUserService.js
index a0de195f4..774d6cc78 100644
--- a/src/backend/src/modules/selfhosted/DefaultUserService.js
+++ b/src/backend/src/modules/selfhosted/DefaultUserService.js
@@ -22,7 +22,7 @@ const { NodePathSelector } = require("../../filesystem/node/selectors");
const { surrounding_box } = require("../../fun/dev-console-ui-utils");
const { get_user, invalidate_cached_user } = require("../../helpers");
const { Context } = require("../../util/context");
-const { asyncSafeSetInterval } = require("../../util/promise");
+const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise;
const { buffer_to_stream } = require("../../util/streamutil");
const BaseService = require("../../services/BaseService");
const { Actor, UserActorType } = require("../../services/auth/Actor");
diff --git a/src/backend/src/routers/drivers/call.js b/src/backend/src/routers/drivers/call.js
index 9e472d7a2..f0453b995 100644
--- a/src/backend/src/routers/drivers/call.js
+++ b/src/backend/src/routers/drivers/call.js
@@ -23,7 +23,7 @@ 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("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { valid_file_size } = require("../../util/validutil");
let _handle_multipart;
diff --git a/src/backend/src/routers/filesystem_api/batch/all.js b/src/backend/src/routers/filesystem_api/batch/all.js
index 2bcf594d6..96dccdf16 100644
--- a/src/backend/src/routers/filesystem_api/batch/all.js
+++ b/src/backend/src/routers/filesystem_api/batch/all.js
@@ -23,7 +23,7 @@ const PathResolver = require("./PathResolver");
const { Context } = require("../../../util/context");
const Busboy = require('busboy');
const { BatchExecutor } = require("../../../filesystem/batch/BatchExecutor");
-const { TeePromise } = require("../../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { EWMA, MovingMode } = require("../../../util/opmath");
const { get_app } = require('../../../helpers');
const { valid_file_size } = require("../../../util/validutil");
diff --git a/src/backend/src/routers/filesystem_api/write.js b/src/backend/src/routers/filesystem_api/write.js
index de0f4f65b..df4d1366b 100644
--- a/src/backend/src/routers/filesystem_api/write.js
+++ b/src/backend/src/routers/filesystem_api/write.js
@@ -23,7 +23,7 @@ const { HLWrite } = require('../../filesystem/hl_operations/hl_write.js');
const { boolify } = require('../../util/hl_types.js');
const { Context } = require('../../util/context.js');
const Busboy = require('busboy');
-const { TeePromise } = require('../../util/promise.js');
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const APIError = require('../../api/APIError.js');
const { valid_file_size } = require('../../util/validutil.js');
diff --git a/src/backend/src/services/AppInformationService.js b/src/backend/src/services/AppInformationService.js
index c38be268c..d82c163b3 100644
--- a/src/backend/src/services/AppInformationService.js
+++ b/src/backend/src/services/AppInformationService.js
@@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { asyncSafeSetInterval } = require("../util/promise");
+const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise;
const { MINUTE, SECOND } = require("@heyputer/putility").libs.time;
const { origin_from_url } = require("../util/urlutil");
const { DB_READ } = require("./database/consts");
diff --git a/src/backend/src/services/Container.js b/src/backend/src/services/Container.js
index c3500be8c..ec82c72a6 100644
--- a/src/backend/src/services/Container.js
+++ b/src/backend/src/services/Container.js
@@ -21,7 +21,7 @@ const { AdvancedBase } = require("@heyputer/putility");
const config = require("../config");
const { Context } = require("../util/context");
const { CompositeError } = require("../util/errorutil");
-const { TeePromise } = require("../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
// 17 lines of code instead of an entire dependency-injection framework
/**
diff --git a/src/backend/src/services/LocalDiskStorageService.js b/src/backend/src/services/LocalDiskStorageService.js
index 464b64fa1..a9d01080e 100644
--- a/src/backend/src/services/LocalDiskStorageService.js
+++ b/src/backend/src/services/LocalDiskStorageService.js
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
const { LocalDiskStorageStrategy } = require("../filesystem/strategies/storage_a/LocalDiskStorageStrategy");
-const { TeePromise } = require("../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { progress_stream, size_limit_stream } = require("../util/streamutil");
const BaseService = require("./BaseService");
diff --git a/src/backend/src/services/NotificationService.js b/src/backend/src/services/NotificationService.js
index 76edcb2b0..99905e636 100644
--- a/src/backend/src/services/NotificationService.js
+++ b/src/backend/src/services/NotificationService.js
@@ -20,7 +20,7 @@
const APIError = require("../api/APIError");
const auth2 = require("../middleware/auth2");
const { Endpoint } = require("../util/expressutil");
-const { TeePromise } = require("../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const BaseService = require("./BaseService");
const { DB_WRITE } = require("./database/consts");
diff --git a/src/backend/src/services/SUService.js b/src/backend/src/services/SUService.js
index 0a7bb6322..c085c3a06 100644
--- a/src/backend/src/services/SUService.js
+++ b/src/backend/src/services/SUService.js
@@ -1,7 +1,7 @@
// METADATA // {"ai-commented":{"service":"openai-completion","model":"gpt-4o-mini"}}
const { get_user } = require("../helpers");
const { Context } = require("../util/context");
-const { TeePromise } = require("../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { Actor, UserActorType } = require("./auth/Actor");
const BaseService = require("./BaseService");
diff --git a/src/backend/src/services/SessionService.js b/src/backend/src/services/SessionService.js
index a083426ba..551d7380f 100644
--- a/src/backend/src/services/SessionService.js
+++ b/src/backend/src/services/SessionService.js
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
const { invalidate_cached_user, get_user } = require("../helpers");
-const { asyncSafeSetInterval } = require("../util/promise");
+const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise;
const { MINUTE, SECOND } = require("@heyputer/putility").libs.time;
const BaseService = require("./BaseService");
const { DB_WRITE } = require("./database/consts");
diff --git a/src/backend/src/services/abuse-prevention/EdgeRateLimitService.js b/src/backend/src/services/abuse-prevention/EdgeRateLimitService.js
index bb4809950..8d374dac3 100644
--- a/src/backend/src/services/abuse-prevention/EdgeRateLimitService.js
+++ b/src/backend/src/services/abuse-prevention/EdgeRateLimitService.js
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
const { Context } = require("../../util/context");
-const { asyncSafeSetInterval } = require("../../util/promise");
+const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise;
const { quot } = require('@heyputer/putility').libs.string;
const { MINUTE, HOUR } = require('@heyputer/putility').libs.time;
diff --git a/src/backend/src/services/fs/FSLockService.js b/src/backend/src/services/fs/FSLockService.js
index e787a5c62..aeffff590 100644
--- a/src/backend/src/services/fs/FSLockService.js
+++ b/src/backend/src/services/fs/FSLockService.js
@@ -18,7 +18,6 @@
* along with this program. If not, see .
*/
const { RWLock } = require("../../util/lockutil");
-const { TeePromise } = require("../../util/promise");
const BaseService = require("../BaseService");
// Constant representing the read lock mode used for distinguishing between read and write operations.
diff --git a/src/backend/src/services/runtime-analysis/ServerHealthService.js b/src/backend/src/services/runtime-analysis/ServerHealthService.js
index c64dbbd17..34fadf8b7 100644
--- a/src/backend/src/services/runtime-analysis/ServerHealthService.js
+++ b/src/backend/src/services/runtime-analysis/ServerHealthService.js
@@ -18,9 +18,8 @@
* along with this program. If not, see .
*/
const BaseService = require("../BaseService");
-const { SECOND } = require("@heyputer/putility").libs.time;
+const { time, promise } = require("@heyputer/putility").libs;
const { parse_meminfo } = require("../../util/linux");
-const { asyncSafeSetInterval, TeePromise } = require("../../util/promise");
/**
@@ -135,11 +134,11 @@ class ServerHealthService extends BaseService {
* @param {none} - No parameters are passed to this method.
* @returns {void}
*/
- asyncSafeSetInterval(async () => {
+ promise.asyncSafeSetInterval(async () => {
this.log.tick('service checks');
const check_failures = [];
for ( const { name, fn, chainable } of this.checks_ ) {
- const p_timeout = new TeePromise();
+ const p_timeout = new promise.TeePromise();
/**
* Creates a TeePromise to handle potential timeouts during health checks.
*
@@ -147,7 +146,7 @@ class ServerHealthService extends BaseService {
*/
const timeout = setTimeout(() => {
p_timeout.reject(new Error('Health check timed out'));
- }, 5 * SECOND);
+ }, 5 * time.SECOND);
try {
await Promise.race([
fn(),
@@ -180,7 +179,7 @@ class ServerHealthService extends BaseService {
}
this.failures_ = check_failures;
- }, 10 * SECOND, null, {
+ }, 10 * time.SECOND, null, {
onBehindSchedule: (drift) => {
svc_alarm.create(
'health-checks-behind-schedule',
diff --git a/src/backend/src/services/thumbnails/HTTPThumbnailService.js b/src/backend/src/services/thumbnails/HTTPThumbnailService.js
index 2ce892377..b4a6dd4d5 100644
--- a/src/backend/src/services/thumbnails/HTTPThumbnailService.js
+++ b/src/backend/src/services/thumbnails/HTTPThumbnailService.js
@@ -22,7 +22,7 @@
const axios = require('axios');
-const { TeePromise } = require("../../util/promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { AdvancedBase } = require('@heyputer/putility');
const FormData = require("form-data");
const { stream_to_the_void, buffer_to_stream } = require('../../util/streamutil');
diff --git a/src/backend/src/traits/SyncFeature.js b/src/backend/src/traits/SyncFeature.js
index b88c5ca43..633521cd9 100644
--- a/src/backend/src/traits/SyncFeature.js
+++ b/src/backend/src/traits/SyncFeature.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { Lock } = require("../util/promise");
+const { Lock } = require('@heyputer/putility').libs.promise;
class SyncFeature {
constructor (method_include_list) {
diff --git a/src/backend/src/util/lockutil.js b/src/backend/src/util/lockutil.js
index b0862600e..dde83572b 100644
--- a/src/backend/src/util/lockutil.js
+++ b/src/backend/src/util/lockutil.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { TeePromise } = require("./promise");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
class RWLock {
static TYPE_READ = Symbol('read');
diff --git a/src/backend/src/util/otelutil.js b/src/backend/src/util/otelutil.js
index ecebdb469..e8aef304f 100644
--- a/src/backend/src/util/otelutil.js
+++ b/src/backend/src/util/otelutil.js
@@ -23,7 +23,7 @@
const { context, trace, SpanStatusCode } = require('@opentelemetry/api');
const { Context } = require('./context');
-const { TeePromise } = require('./promise');
+const { TeePromise } = require('@heyputer/putility').libs.promise;
/*
parallel span example from GPT-4:
diff --git a/src/backend/src/util/promise.js b/src/backend/src/util/promise.js
deleted file mode 100644
index 137b19c37..000000000
--- a/src/backend/src/util/promise.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2024 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 TeePromise {
- static STATUS_PENDING = Symbol('pending');
- static STATUS_RUNNING = {};
- static STATUS_DONE = Symbol('done');
- constructor () {
- this.status_ = this.constructor.STATUS_PENDING;
- this.donePromise = new Promise((resolve, reject) => {
- this.doneResolve = resolve;
- this.doneReject = reject;
- });
- }
- get status () {
- return this.status_;
- }
- set status (status) {
- this.status_ = status;
- if ( status === this.constructor.STATUS_DONE ) {
- this.doneResolve();
- }
- }
- resolve (value) {
- this.status_ = this.constructor.STATUS_DONE;
- this.doneResolve(value);
- }
- awaitDone () {
- return this.donePromise;
- }
- then (fn, ...a) {
- return this.donePromise.then(fn, ...a);
- }
-
- reject (err) {
- this.status_ = this.constructor.STATUS_DONE;
- this.doneReject(err);
- }
-
- /**
- * @deprecated use then() instead
- */
- onComplete(fn) {
- return this.then(fn);
- }
-}
-
-class Lock {
- constructor() {
- this._locked = false;
- this._waiting = [];
- }
-
- async acquire(callback) {
- await new Promise(resolve => {
- if ( ! this._locked ) {
- this._locked = true;
- resolve();
- } else {
- this._waiting.push({
- resolve,
- });
- }
- })
- if ( callback ) {
- let retval;
- try {
- retval = await callback();
- } finally {
- this.release();
- }
- return retval;
- }
- }
-
- release() {
- if (this._waiting.length > 0) {
- const { resolve } = this._waiting.shift();
- resolve();
- } else {
- this._locked = false;
- }
- }
-}
-
-/**
- * @callback behindScheduleCallback
- * @param {number} drift - The number of milliseconds that the callback was
- * called behind schedule.
- * @returns {boolean} - If the callback returns true, the timer will be
- * cancelled.
- */
-
-/**
- * When passing an async callback to setInterval, it's possible for the
- * callback to be called again before the previous invocation has finished.
- *
- * This function wraps setInterval and ensures that the callback is not
- * called again until the previous invocation has finished.
- *
- * @param {Function} callback - The function to call when the timer elapses.
- * @param {number} delay - The minimum number of milliseconds between invocations.
- * @param {?Array} args - Additional arguments to pass to setInterval.
- * @param {?Object} options - Additional options.
- * @param {behindScheduleCallback} options.onBehindSchedule - A callback to call when the callback is called behind schedule.
- */
-const asyncSafeSetInterval = async (callback, delay, args, options) => {
- args = args ?? [];
- options = options ?? {};
- const { onBehindSchedule } = options;
-
- const sleep = (ms) => new Promise(rslv => setTimeout(rslv, ms));
-
- for ( ;; ) {
- await sleep(delay);
-
- const ts_start = Date.now();
- await callback(...args);
- const ts_end = Date.now();
-
- const runtime = ts_end - ts_start;
- const sleep_time = delay - runtime;
-
- if ( sleep_time < 0 ) {
- if ( onBehindSchedule ) {
- const cancel = await onBehindSchedule(-sleep_time);
- if ( cancel ) {
- return;
- }
- }
- } else {
- await sleep(sleep_time);
- }
- }
-}
-
-module.exports = {
- TeePromise,
- Lock,
- asyncSafeSetInterval,
-};
diff --git a/src/backend/src/util/streamutil.js b/src/backend/src/util/streamutil.js
index 2f75f8fb2..bd06b7af0 100644
--- a/src/backend/src/util/streamutil.js
+++ b/src/backend/src/util/streamutil.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
const { PassThrough, Readable, Transform } = require('stream');
-const { TeePromise } = require('./promise');
+const { TeePromise } = require('@heyputer/putility').libs.promise;
const { EWMA } = require('./opmath');
class StreamBuffer extends TeePromise {