diff --git a/scripts/openapi.js b/scripts/openapi.js
index a6033cd6..64c7babb 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 f8733809..54d9335d 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 820716b5..894f9331 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 13721c04..379d2775 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 b3efb187..0dac7574 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 9942fef1..f2782bb8 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 49be55e2..9b12c7bb 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 <https://www.gnu.org/licenses/>.
*/
+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 e0908458..88d7ebb0 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 52d241aa..64c4ac96 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 <https://www.gnu.org/licenses/>.
*/
-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 65d41e43..d0176693 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 cb26d4f5..c7cbc535 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 c18e4f79..9aedce11 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 e33613a3..00000000
--- 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 <https://www.gnu.org/licenses/>.
-*/
-
-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 59cf3c65..b9eff51b 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 17a2a628..6771fc40 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 b9fbac3d..b5b0b398 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 e0e622db..cfe7803c 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 5f40adb3..5c2c4fe1 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
index 74de4dcc..74de4dcc 100644
--- a/src/util/util/DateBuilder.test.ts
+++ b/src/extensions/DateBuilder.test.ts
diff --git a/src/util/util/DateBuilder.ts b/src/extensions/DateBuilder.ts
index 58588c8b..58588c8b 100644
--- a/src/util/util/DateBuilder.ts
+++ b/src/extensions/DateBuilder.ts
diff --git a/src/util/util/ElapsedTime.test.ts b/src/extensions/ElapsedTime.test.ts
index b8cbbdce..b8cbbdce 100644
--- a/src/util/util/ElapsedTime.test.ts
+++ b/src/extensions/ElapsedTime.test.ts
diff --git a/src/util/util/ElapsedTime.ts b/src/extensions/ElapsedTime.ts
index cce91280..cce91280 100644
--- a/src/util/util/ElapsedTime.ts
+++ b/src/extensions/ElapsedTime.ts
diff --git a/src/util/util/Random.ts b/src/extensions/Random.ts
index b81e17cd..212e1c9a 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 <https://www.gnu.org/licenses/>.
+*/
+
// 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
index 35618314..35618314 100644
--- a/src/util/util/Stopwatch.test.ts
+++ b/src/extensions/Stopwatch.test.ts
diff --git a/src/util/util/Stopwatch.ts b/src/extensions/Stopwatch.ts
index 1f83c847..1f83c847 100644
--- a/src/util/util/Stopwatch.ts
+++ b/src/extensions/Stopwatch.ts
diff --git a/src/util/util/String.ts b/src/extensions/String.ts
index d0e75700..5c623e30 100644
--- a/src/util/util/String.ts
+++ b/src/extensions/String.ts
@@ -16,7 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-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
index 02195506..02195506 100644
--- a/src/util/util/Timespan.test.ts
+++ b/src/extensions/Timespan.test.ts
diff --git a/src/util/util/Timespan.ts b/src/extensions/Timespan.ts
index ad0c1d10..ad0c1d10 100644
--- a/src/util/util/Timespan.ts
+++ b/src/extensions/Timespan.ts
diff --git a/src/util/util/Url.ts b/src/extensions/Url.ts
index 7fa681bf..7fa681bf 100644
--- a/src/util/util/Url.ts
+++ b/src/extensions/Url.ts
diff --git a/src/extensions/index.ts b/src/extensions/index.ts
index 53bd443c..15563724 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 f075028b..f7c0dd0c 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 9c87aec9..5da55b04 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 ec8e17cf..db13ea8c 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 23e9569b..ad5cd21f 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 10bd5e2c..e8cd9ecb 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 d4f21d61..fb7c209c 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 b50f53dc..e8b810a8 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 5adf6c1e..ff5bb744 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 b001fc75..327d052d 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 <https://www.gnu.org/licenses/>.
+*/
+
+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 b1551389..f717e330 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 <https://www.gnu.org/licenses/>.
+*/
+
+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 7767915a..c64d6dc3 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 <https://www.gnu.org/licenses/>.
*/
-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 1d1705ef..6316adac 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 {
|