diff --git a/scripts/openapi.js b/scripts/openapi.js
index a6033cd6a..64c7babbf 100644
--- a/scripts/openapi.js
+++ b/scripts/openapi.js
@@ -18,7 +18,7 @@
process.env.LOG_ROUTES = "false";
-const { Stopwatch } = require("../dist/util/util/Stopwatch");
+const { Stopwatch } = require("../dist/extensions/Stopwatch");
const totalSw = Stopwatch.startNew();
require("module-alias/register");
@@ -26,7 +26,6 @@ const getRouteDescriptions = require("./util/getRouteDescriptions");
const path = require("path");
const fs = require("fs");
const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication");
-require("../dist/extensions");
const { bgRedBright, bgYellow, black, bgYellowBright, blue, white } = require("picocolors");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
diff --git a/scripts/schema.js b/scripts/schema.js
index f8733809b..54d9335d0 100644
--- a/scripts/schema.js
+++ b/scripts/schema.js
@@ -19,7 +19,7 @@
/*
Regenerates the `spacebarchat/server/assets/schemas.json` file, used for API/Gateway input validation.
*/
-const { Stopwatch } = require("../dist/util/util/Stopwatch");
+const { Stopwatch } = require("../dist/extensions/Stopwatch");
const totalSw = Stopwatch.startNew();
const conWarn = console.warn;
diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts
index 820716b5d..894f9331f 100644
--- a/src/api/routes/applications/index.ts
+++ b/src/api/routes/applications/index.ts
@@ -19,8 +19,9 @@
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { Application } from "@spacebar/database";
-import { Config, createAppBotUser, trimSpecial } from "@spacebar/util";
+import { Config, createAppBotUser } from "@spacebar/util";
import { ApplicationCreateSchema } from "@spacebar/schemas";
+import { trimSpecial } from "@spacebar/extensions";
const router: Router = Router({ mergeParams: true });
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index 13721c042..379d2775e 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -22,9 +22,10 @@ import { HTTPError } from "lambert-server/HTTPError";
import { MoreThan } from "typeorm";
import { route, verifyCaptcha } from "@spacebar/api";
import { Invite, User, ValidRegistrationToken } from "@spacebar/database";
-import { Config, FieldErrors, generateToken, IpDataClient, AbuseIpDbClient, TimeSpan, Stopwatch } from "@spacebar/util";
+import { Config, FieldErrors, generateToken, IpDataClient, AbuseIpDbClient } from "@spacebar/util";
import { RegisterSchema } from "@spacebar/schemas";
import { BcryptWorkerPool } from "../../../util/util/workers/bcrypt/BcryptWorkerPool";
+import { Stopwatch, TimeSpan } from "@spacebar/extensions";
const router: Router = Router({ mergeParams: true });
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index b3efb1878..0dac75747 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -21,8 +21,9 @@ import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server/HTTPError";
import { route } from "@spacebar/api";
import { Application, Channel, User, Webhook } from "@spacebar/database";
-import { Config, DiscordApiErrors, handleFile, trimSpecial, ValidateName } from "@spacebar/util";
+import { Config, DiscordApiErrors, handleFile, ValidateName } from "@spacebar/util";
import { isTextChannel, WebhookCreateSchema, WebhookType } from "@spacebar/schemas";
+import { trimSpecial } from "@spacebar/extensions";
const router: Router = Router({ mergeParams: true });
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 9942fef16..f2782bb85 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -18,8 +18,9 @@
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server/HTTPError";
-import { generateCode, route } from "@spacebar/api";
+import { route } from "@spacebar/api";
import { Guild, Template } from "@spacebar/database";
+import { generateCode } from "@spacebar/extensions";
const router: Router = Router({ mergeParams: true });
diff --git a/src/api/routes/users/#user_id/delete.ts b/src/api/routes/users/#user_id/delete.ts
index 49be55e22..9b12c7bbb 100644
--- a/src/api/routes/users/#user_id/delete.ts
+++ b/src/api/routes/users/#user_id/delete.ts
@@ -16,12 +16,13 @@
along with this program. If not, see .
*/
+import { Request, Response, Router } from "express";
+import { Not } from "typeorm";
import { route } from "@spacebar/api";
import { Channel, Emoji, Guild, InstanceBan, Member, Recipient, Sticker, User, UserSettingsProtos } from "@spacebar/database";
-import { ChannelDeleteEvent, ChannelRecipientRemoveEvent, emitEvent, Stopwatch, UserDeleteEvent } from "@spacebar/util";
-import { Request, Response, Router } from "express";
+import { ChannelDeleteEvent, ChannelRecipientRemoveEvent, emitEvent, UserDeleteEvent } from "@spacebar/util";
import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas";
-import { Not } from "typeorm";
+import { Stopwatch } from "@spacebar/extensions";
const router = Router({ mergeParams: true });
diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts
index e09084582..88d7ebb0b 100644
--- a/src/api/routes/users/@me/mentions.ts
+++ b/src/api/routes/users/@me/mentions.ts
@@ -20,7 +20,8 @@ import { Request, Response, Router } from "express";
import { In, LessThan, FindOptionsWhere } from "typeorm";
import { route } from "@spacebar/api";
import { Message, Member, Channel, Attachment } from "@spacebar/database";
-import { Snowflake, Permissions, NewUrlUserSignatureData, Stopwatch } from "@spacebar/util";
+import { Snowflake, Permissions, NewUrlUserSignatureData } from "@spacebar/util";
+import { Stopwatch } from "@spacebar/extensions";
const router: Router = Router({ mergeParams: true });
diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 52d241aa0..64c4ac961 100644
--- a/src/api/util/handlers/Instance.ts
+++ b/src/api/util/handlers/Instance.ts
@@ -16,9 +16,9 @@
along with this program. If not, see .
*/
-import { Session } from "@spacebar/database";
-import { TimeSpan } from "@spacebar/util";
import { setInterval } from "node:timers";
+import { Session } from "@spacebar/database";
+import { TimeSpan } from "@spacebar/extensions";
export async function initInstance() {
// TODO: clean up database and delete tombstone data
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 65d41e431..d0176693a 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -20,7 +20,7 @@ import { HTTPError } from "lambert-server/HTTPError";
import { Equal, In, Or } from "typeorm";
import { fillMessageUrlEmbeds, randomString } from "@spacebar/api";
import { getDatabase, Application, Attachment, Channel, CloudAttachment, Guild, Member, Message, ReadState, Role, Session, Sticker, User, Webhook } from "@spacebar/database";
-import { mathLogBase, arrayDistributeSequentially } from "@spacebar/extensions";
+import { mathLogBase, arrayDistributeSequentially, Stopwatch } from "@spacebar/extensions";
import {
Config,
DiscordApiErrors,
@@ -36,7 +36,6 @@ import {
Permissions,
ROLE_MENTION,
Snowflake,
- Stopwatch,
TraceNode,
TraceRoot,
TraceSubTree,
diff --git a/src/api/util/index.ts b/src/api/util/index.ts
index cb26d4f54..c7cbc535f 100644
--- a/src/api/util/index.ts
+++ b/src/api/util/index.ts
@@ -22,7 +22,6 @@ export * from "./handlers/Message";
export * from "./utility/passwordStrength";
export * from "./utility/RandomInviteID";
export * from "./handlers/route";
-export * from "./utility/String";
export * from "./handlers/Voice";
export * from "./utility/captcha";
export * from "./utility/EmbedHandlers";
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index c18e4f791..9aedce119 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -22,8 +22,8 @@ import { yellow } from "picocolors";
import probe from "probe-image-size";
import { FindOptionsWhere, In } from "typeorm";
import { EmbedCache, Message } from "@spacebar/database";
-import { sleep, arrayDistinctBy, arrayGroupBy } from "@spacebar/extensions";
-import { Config, emitEvent, MessageUpdateEvent, normalizeUrl, OrmUtils } from "@spacebar/util";
+import { sleep, arrayDistinctBy, arrayGroupBy, normalizeUrl } from "@spacebar/extensions";
+import { Config, emitEvent, MessageUpdateEvent, OrmUtils } from "@spacebar/util";
import { Embed, EmbedImage, EmbedType } from "@spacebar/schemas";
export function getDefaultFetchOptions(): RequestInit {
diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
deleted file mode 100644
index e33613a37..000000000
--- a/src/api/util/utility/String.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
-
- This program 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 .
-*/
-
-import { Request } from "express";
-import { ntob } from "./Base64";
-import { FieldErrors, Random } from "@spacebar/util";
-
-export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
- if (str.length < min || str.length > max) {
- throw FieldErrors({
- [key]: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
- length: `${min} - ${max}`,
- }),
- },
- });
- }
-}
-
-export function generateCode() {
- return ntob(Date.now() + Random.nextInt(0, 10000));
-}
diff --git a/src/bundle/start.ts b/src/bundle/start.ts
index 59cf3c659..b9eff51b0 100644
--- a/src/bundle/start.ts
+++ b/src/bundle/start.ts
@@ -29,7 +29,8 @@ import cluster, { Worker } from "node:cluster";
import os from "node:os";
import "reflect-metadata";
import { red, bold, yellow, cyan, blueBright, redBright } from "picocolors";
-import { centerString, getRevInfoOrFail, Logo } from "@spacebar/util";
+import { centerString } from "@spacebar/extensions";
+import { getRevInfoOrFail, Logo } from "@spacebar/util";
import { initStats } from "./stats";
const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1;
diff --git a/src/database/entities/Channel.ts b/src/database/entities/Channel.ts
index 17a2a6284..6771fc407 100644
--- a/src/database/entities/Channel.ts
+++ b/src/database/entities/Channel.ts
@@ -20,7 +20,7 @@ import { HTTPError } from "lambert-server/HTTPError";
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
import { DmChannelDTO } from "../../util/dtos";
import { ChannelCreateEvent, ChannelRecipientRemoveEvent, ThreadCreateEvent, ThreadMembersUpdateEvent } from "../../util/interfaces";
-import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, Config, DiscordApiErrors } from "../../util/util";
+import { InvisibleCharacters, Snowflake, emitEvent, getPermission, Permissions, Config, DiscordApiErrors } from "../../util/util";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Invite } from "./Invite";
@@ -35,6 +35,7 @@ import { Member } from "./Member";
import { ChannelPermissionOverwrite, ChannelType, PublicChannel, PublicUserProjection, ThreadMetadata } from "@spacebar/schemas";
import { OrmUtils } from "../../util/imports";
import { ThreadMember } from "./ThreadMember";
+import { trimSpecial } from "@spacebar/extensions";
@Entity({
name: "channels",
diff --git a/src/database/entities/Member.ts b/src/database/entities/Member.ts
index b9fbac3d6..b5b0b3982 100644
--- a/src/database/entities/Member.ts
+++ b/src/database/entities/Member.ts
@@ -21,13 +21,14 @@ import { BeforeInsert, BeforeUpdate, Column, Entity, Index, JoinColumn, JoinTabl
import { Ban, Channel, PublicGuildRelations } from "./index";
import { ReadyGuildDTO } from "../../util/dtos";
import { GuildCreateEvent, GuildDeleteEvent, GuildMemberAddEvent, GuildMemberRemoveEvent, GuildMemberUpdateEvent, MessageCreateEvent } from "../../util/interfaces";
-import { Config, emitEvent, DiscordApiErrors, Stopwatch } from "../../util/util";
+import { Config, emitEvent, DiscordApiErrors } from "../../util/util";
import { BaseClassWithoutId } from "./BaseClass";
import { Guild } from "./Guild";
import { Message } from "./Message";
import { Role } from "./Role";
import { User } from "./User";
import { AvatarDecorationData, Collectibles, DisplayNameStyle, PublicMember, PublicMemberProjection, UserGuildSettings } from "@spacebar/schemas";
+import { Stopwatch } from "@spacebar/extensions";
export const MemberPrivateProjection: (keyof Member)[] = [
"id",
diff --git a/src/database/entities/Session.ts b/src/database/entities/Session.ts
index e0e622dbb..cfe7803c3 100644
--- a/src/database/entities/Session.ts
+++ b/src/database/entities/Session.ts
@@ -17,12 +17,13 @@
*/
import crypto from "node:crypto";
+import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm";
+import { randomUpperString } from "@spacebar/api";
+import { DateBuilder, TimeSpan } from "@spacebar/extensions";
import { User } from "./User";
import { BaseClassWithoutId } from "./BaseClass";
-import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm";
import { Activity, ClientStatus, GatewaySession, GatewaySessionClientInfo, PrivateStatus } from "../../util/interfaces";
-import { randomUpperString } from "@spacebar/api";
-import { DateBuilder, IpDataClient, TimeSpan } from "../../util/util";
+import { IpDataClient } from "../../util/util";
@Entity({
name: "sessions",
diff --git a/src/database/entities/User.ts b/src/database/entities/User.ts
index 5f40adb36..5c2c4fe12 100644
--- a/src/database/entities/User.ts
+++ b/src/database/entities/User.ts
@@ -18,8 +18,8 @@
import { Request } from "express";
import { Column, Entity, JoinColumn, OneToMany, OneToOne } from "typeorm";
-import { Config, Email, FieldErrors, Snowflake, Stopwatch, trimSpecial } from "@spacebar/util";
-import { Random } from "@spacebar/util/util/Random";
+import { Config, Email, FieldErrors, Snowflake } from "@spacebar/util";
+import { Stopwatch, trimSpecial, Random } from "@spacebar/extensions";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { ConnectedAccount } from "./ConnectedAccount";
diff --git a/src/util/util/DateBuilder.test.ts b/src/extensions/DateBuilder.test.ts
similarity index 100%
rename from src/util/util/DateBuilder.test.ts
rename to src/extensions/DateBuilder.test.ts
diff --git a/src/util/util/DateBuilder.ts b/src/extensions/DateBuilder.ts
similarity index 100%
rename from src/util/util/DateBuilder.ts
rename to src/extensions/DateBuilder.ts
diff --git a/src/util/util/ElapsedTime.test.ts b/src/extensions/ElapsedTime.test.ts
similarity index 100%
rename from src/util/util/ElapsedTime.test.ts
rename to src/extensions/ElapsedTime.test.ts
diff --git a/src/util/util/ElapsedTime.ts b/src/extensions/ElapsedTime.ts
similarity index 100%
rename from src/util/util/ElapsedTime.ts
rename to src/extensions/ElapsedTime.ts
diff --git a/src/util/util/Random.ts b/src/extensions/Random.ts
similarity index 78%
rename from src/util/util/Random.ts
rename to src/extensions/Random.ts
index b81e17cd7..212e1c9aa 100644
--- a/src/util/util/Random.ts
+++ b/src/extensions/Random.ts
@@ -1,3 +1,21 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2026 Spacebar and Spacebar Contributors
+
+ This program 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 .
+*/
+
// Inspired by dotnet: https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-9.0#methods
export class Random {
public static nextInt(min?: number, max?: number): number {
diff --git a/src/util/util/Stopwatch.test.ts b/src/extensions/Stopwatch.test.ts
similarity index 100%
rename from src/util/util/Stopwatch.test.ts
rename to src/extensions/Stopwatch.test.ts
diff --git a/src/util/util/Stopwatch.ts b/src/extensions/Stopwatch.ts
similarity index 100%
rename from src/util/util/Stopwatch.ts
rename to src/extensions/Stopwatch.ts
diff --git a/src/util/util/String.ts b/src/extensions/String.ts
similarity index 66%
rename from src/util/util/String.ts
rename to src/extensions/String.ts
index d0e757003..5c623e307 100644
--- a/src/util/util/String.ts
+++ b/src/extensions/String.ts
@@ -16,7 +16,11 @@
along with this program. If not, see .
*/
-import { SPECIAL_CHAR } from "./Regex";
+import { Request } from "express";
+import { SPECIAL_CHAR } from "@spacebar/util/util/Regex";
+import { Random } from "@spacebar/extensions/Random";
+import { ntob } from "@spacebar/api";
+import { FieldErrors } from "@spacebar/util";
export function trimSpecial(str?: string): string {
if (!str) return "";
@@ -44,3 +48,21 @@ export function stringGlobToRegexp(str: string, flags?: string): RegExp {
const escaped = str.replace(".", "\\.").replace("?", ".").replace("*", ".*");
return new RegExp(escaped, flags);
}
+
+export function stringCheckLength(str: string, min: number, max: number, key: string, req: Request) {
+ if (str.length < min || str.length > max) {
+ throw FieldErrors({
+ [key]: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ // TODO: remove dependency on request (add generic field?)
+ message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
+ length: `${min} - ${max}`,
+ }),
+ },
+ });
+ }
+}
+
+export function generateCode() {
+ return ntob(Date.now() + Random.nextInt(0, 10000));
+}
diff --git a/src/util/util/Timespan.test.ts b/src/extensions/Timespan.test.ts
similarity index 100%
rename from src/util/util/Timespan.test.ts
rename to src/extensions/Timespan.test.ts
diff --git a/src/util/util/Timespan.ts b/src/extensions/Timespan.ts
similarity index 100%
rename from src/util/util/Timespan.ts
rename to src/extensions/Timespan.ts
diff --git a/src/util/util/Url.ts b/src/extensions/Url.ts
similarity index 100%
rename from src/util/util/Url.ts
rename to src/extensions/Url.ts
diff --git a/src/extensions/index.ts b/src/extensions/index.ts
index 53bd443c8..155637247 100644
--- a/src/extensions/index.ts
+++ b/src/extensions/index.ts
@@ -17,7 +17,14 @@
*/
export * from "./Array";
+export * from "./DateBuilder";
+export * from "./ElapsedTime";
export * from "./Math";
+export * from "./Random";
+export * from "./Stopwatch";
+export * from "./String";
+export * from "./Timespan";
+export * from "./Url";
// TODO: move to a separate file
export async function sleep(ms: number) {
diff --git a/src/gateway/opcodes/GuildSync.ts b/src/gateway/opcodes/GuildSync.ts
index f075028b6..f7c0dd0c9 100644
--- a/src/gateway/opcodes/GuildSync.ts
+++ b/src/gateway/opcodes/GuildSync.ts
@@ -18,9 +18,10 @@
import { In } from "typeorm";
import { Member, Session } from "@spacebar/database";
+import { Stopwatch, timePromise } from "@spacebar/extensions";
import { WebSocket, Payload, OPCODES, Send, handleOffloadedGatewayRequest } from "@spacebar/gateway";
import { PublicMember } from "@spacebar/schemas";
-import { Presence, timePromise, Stopwatch, Config, getMostRelevantSession } from "@spacebar/util";
+import { Presence, Config, getMostRelevantSession } from "@spacebar/util";
// TODO: only show roles/members that have access to this channel
// TODO: config: to list all members (even those who are offline) sorted by role, or just those who are online
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index 9c87aec91..5da55b046 100644
--- a/src/gateway/opcodes/Identify.ts
+++ b/src/gateway/opcodes/Identify.ts
@@ -19,7 +19,7 @@
import { In, Not } from "typeorm";
import { PreloadedUserSettings } from "discord-protos";
import { Capabilities, CLOSECODES, OPCODES, Payload, Send, setupListener, WebSocket } from "@spacebar/gateway";
-import { arrayGroupBy } from "@spacebar/extensions";
+import { arrayGroupBy, ElapsedTime, Stopwatch, timeFunction, timePromise } from "@spacebar/extensions";
import {
getDatabase,
Application,
@@ -43,7 +43,6 @@ import {
checkToken,
Config,
CurrentTokenFormatVersion,
- ElapsedTime,
emitEvent,
EVENTEnum,
generateToken,
@@ -55,9 +54,6 @@ import {
ReadyGuildDTO,
ReadyUserGuildSettingsEntries,
SessionsReplace,
- Stopwatch,
- timeFunction,
- timePromise,
TraceNode,
TraceRoot,
} from "@spacebar/util";
diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts
index ec8e17cfb..db13ea8c1 100644
--- a/src/gateway/opcodes/RequestGuildMembers.ts
+++ b/src/gateway/opcodes/RequestGuildMembers.ts
@@ -18,9 +18,10 @@
import { FindManyOptions, ILike, In, MoreThan } from "typeorm";
import { getDatabase, Member, Session } from "@spacebar/database";
+import { DateBuilder } from "@spacebar/extensions";
import { WebSocket, Payload, OPCODES, Send, handleOffloadedGatewayRequest } from "@spacebar/gateway";
import { RequestGuildMembersSchema } from "@spacebar/schemas";
-import { Config, DateBuilder, getPermission, GuildMembersChunkEvent, Presence } from "@spacebar/util";
+import { Config, getPermission, GuildMembersChunkEvent, Presence } from "@spacebar/util";
import { check } from "./instanceOf";
export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) {
diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts
index 23e9569bf..ad5cd21f2 100644
--- a/src/gateway/util/Utils.ts
+++ b/src/gateway/util/Utils.ts
@@ -17,7 +17,8 @@
*/
import { Session, VoiceState } from "@spacebar/database";
-import { Event, TimeSpan } from "@spacebar/util";
+import { TimeSpan } from "@spacebar/extensions";
+import { Event } from "@spacebar/util";
import { WebSocket } from "./WebSocket";
import { OPCODES } from "./Constants";
import { Send } from "./Send";
diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 10bd5e2c5..e8cd9ecb2 100644
--- a/src/util/util/Token.ts
+++ b/src/util/util/Token.ts
@@ -25,7 +25,7 @@ import { existsSync } from "node:fs";
// TODO: dont use deprecated APIs lol
import { FindOptionsRelationByString, FindOptionsSelectByString } from "typeorm";
import { randomUpperString } from "@spacebar/api";
-import { TimeSpan } from "./Timespan";
+import { TimeSpan } from "../../extensions/Timespan";
import { HTTPError } from "lambert-server/HTTPError";
import path from "node:path";
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index d4f21d618..fb7c209c2 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -22,9 +22,7 @@ export * from "./BitField";
export * from "./cdn";
export * from "./Config";
export * from "./Constants";
-export * from "./DateBuilder";
export * from "./email";
-export * from "./ElapsedTime";
export * from "./ipc/Event";
export * from "./FieldError";
export * from "./Intents";
@@ -39,9 +37,6 @@ export * from "./ipc/RabbitMQ";
export * from "./Regex";
export * from "./Rights";
export * from "./Snowflake";
-export * from "./Stopwatch";
-export * from "./String";
-export * from "./Timespan";
export * from "./Token";
export * from "./TraverseDirectory";
export * from "./WebAuthn";
@@ -49,7 +44,5 @@ export * from "./ChannelFlags";
export * from "./Gifs";
export * from "./Application";
export * from "./NameValidation";
-export * from "./Random";
-export * from "./Url";
export * from "./Version";
export * from "./Presence";
diff --git a/src/util/util/ipc/writer/UnixSocketWriter.ts b/src/util/util/ipc/writer/UnixSocketWriter.ts
index b50f53dcb..e8b810a8b 100644
--- a/src/util/util/ipc/writer/UnixSocketWriter.ts
+++ b/src/util/util/ipc/writer/UnixSocketWriter.ts
@@ -20,11 +20,12 @@ import net, { Socket } from "node:net";
import fs, { FSWatcher } from "node:fs";
import path from "node:path";
import { red } from "picocolors";
-import { BaseEventWriter } from "./BaseEventWriter";
-import { Event, Stopwatch } from "@spacebar/util";
-import { ProcessLifecycle } from "../../ProcessLifecycle";
-import { Monitoring } from "../../../monitoring/Monitoring";
import { Gauge } from "prom-client";
+import { Stopwatch } from "@spacebar/extensions";
+import { Event } from "@spacebar/util";
+import { Monitoring } from "../../../monitoring/Monitoring";
+import { ProcessLifecycle } from "../../ProcessLifecycle";
+import { BaseEventWriter } from "./BaseEventWriter";
export class UnixSocketWriter extends BaseEventWriter {
private static openConnectionsMetric: Gauge;
diff --git a/src/util/util/json/JsonSerializer.test.ts b/src/util/util/json/JsonSerializer.test.ts
index 5adf6c1ee..ff5bb7440 100644
--- a/src/util/util/json/JsonSerializer.test.ts
+++ b/src/util/util/json/JsonSerializer.test.ts
@@ -2,7 +2,7 @@ import { JsonSerializer } from "./JsonSerializer";
import { describe, it } from "node:test";
import { strict as assert } from "node:assert";
import fs from "node:fs/promises";
-import { Stopwatch } from "../Stopwatch";
+import { Stopwatch } from "../../../extensions/Stopwatch";
import { JsonValue } from "@protobuf-ts/runtime";
describe("JsonSerializer", () => {
diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
index b001fc75f..327d052d0 100644
--- a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
+++ b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
@@ -1,4 +1,23 @@
-import { Config, DateBuilder } from "@spacebar/util";
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program 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 .
+*/
+
+import { DateBuilder } from "@spacebar/extensions";
+import { Config } from "@spacebar/util";
import { AbuseIpDbBlacklistResponse, AbuseIpDbCheckResponse } from "./AbuseIpDbSampleResponses";
export class AbuseIpDbClient {
diff --git a/src/util/util/networking/ipdata/IpDataClient.ts b/src/util/util/networking/ipdata/IpDataClient.ts
index b15513890..f717e3306 100644
--- a/src/util/util/networking/ipdata/IpDataClient.ts
+++ b/src/util/util/networking/ipdata/IpDataClient.ts
@@ -1,4 +1,23 @@
-import { Config, DateBuilder } from "@spacebar/util";
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2026 Spacebar and Spacebar Contributors
+
+ This program 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 .
+*/
+
+import { DateBuilder } from "@spacebar/extensions";
+import { Config } from "@spacebar/util";
import { IpDataIpLookupResponse } from "./IpDataSampleResponses";
export class IpDataClient {
diff --git a/src/util/util/networking/stopforumspam/StopForumSpamClient.ts b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
index 7767915ac..c64d6dc32 100644
--- a/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
+++ b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-import { DateBuilder } from "@spacebar/util";
+import { DateBuilder } from "@spacebar/extensions";
// https://www.stopforumspam.com/usage
export class StopForumSpamClient {
diff --git a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
index 1d1705ef0..6316adac7 100644
--- a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
+++ b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
@@ -1,6 +1,6 @@
import { isMainThread, parentPort, Worker } from "node:worker_threads";
import { ProcessLifecycle } from "../../ProcessLifecycle";
-import { Stopwatch } from "../../Stopwatch";
+import { Stopwatch } from "../../../../extensions/Stopwatch";
import bcrypt from "bcrypt";
export class BcryptWorkerPool {