From 8c41d23a9cf079030979b234cab41e9f7da2862d Mon Sep 17 00:00:00 2001 From: hzhang Date: Fri, 15 May 2026 18:47:36 +0100 Subject: [PATCH] refactor: migrate to ES modules package.json type=module, tsconfig module/moduleResolution=NodeNext, target es2022, explicit .js on all relative imports. Center: jsonwebtoken & bcryptjs switched to default imports (ESM/CJS interop). Verified: builds, boots, full auth + plugin round-trip work under ESM. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 1 + src/app.module.ts | 24 ++++++++++++------------ src/channels/channels.controller.ts | 2 +- src/channels/channels.module.ts | 10 +++++----- src/channels/channels.service.ts | 8 ++++---- src/channels/turn-shuffle.ts | 2 +- src/channels/turn.module.ts | 2 +- src/channels/turn.service.ts | 6 +++--- src/common/api-key.guard.ts | 2 +- src/common/metrics.controller.ts | 2 +- src/common/request-context.middleware.ts | 2 +- src/data-source.ts | 2 +- src/database.config.ts | 24 ++++++++++++------------ src/events/events.module.ts | 2 +- src/events/events.service.ts | 2 +- src/guilds/guilds.controller.ts | 2 +- src/guilds/guilds.module.ts | 6 +++--- src/guilds/guilds.service.ts | 2 +- src/health.integration.spec.ts | 4 ++-- src/main.ts | 6 +++--- src/members/members.controller.ts | 2 +- src/members/members.module.ts | 6 +++--- src/members/members.service.ts | 2 +- src/messaging/messaging.controller.ts | 24 ++++++++++++------------ src/messaging/messaging.module.ts | 10 +++++----- src/messaging/pagination.util.spec.ts | 2 +- src/realtime/realtime.gateway.ts | 6 +++--- src/realtime/realtime.module.ts | 2 +- tsconfig.json | 5 +++-- 29 files changed, 86 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index d993f66..1ed7486 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "fabric-backend-guild", "version": "0.1.0", "private": true, + "type": "module", "description": "Fabric Guild Node service", "scripts": { "build": "tsc -p tsconfig.build.json", diff --git a/src/app.module.ts b/src/app.module.ts index b55fc8d..33adfcd 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,18 +1,18 @@ import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { buildTypeOrmConfig } from './database.config'; -import { HealthController } from './common/health.controller'; -import { MetricsController } from './common/metrics.controller'; -import { MetricsService } from './common/metrics.service'; -import { ApiKeyGuard } from './common/api-key.guard'; -import { GuildsModule } from './guilds/guilds.module'; -import { ChannelsModule } from './channels/channels.module'; -import { TurnModule } from './channels/turn.module'; -import { MessagingModule } from './messaging/messaging.module'; -import { EventsModule } from './events/events.module'; -import { RealtimeModule } from './realtime/realtime.module'; -import { MembersModule } from './members/members.module'; +import { buildTypeOrmConfig } from './database.config.js'; +import { HealthController } from './common/health.controller.js'; +import { MetricsController } from './common/metrics.controller.js'; +import { MetricsService } from './common/metrics.service.js'; +import { ApiKeyGuard } from './common/api-key.guard.js'; +import { GuildsModule } from './guilds/guilds.module.js'; +import { ChannelsModule } from './channels/channels.module.js'; +import { TurnModule } from './channels/turn.module.js'; +import { MessagingModule } from './messaging/messaging.module.js'; +import { EventsModule } from './events/events.module.js'; +import { RealtimeModule } from './realtime/realtime.module.js'; +import { MembersModule } from './members/members.module.js'; @Module({ imports: [ diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index f131d09..28b0221 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Param, Post, Query, Req, UnauthorizedException } from '@nestjs/common'; -import { ChannelsService } from './channels.service'; +import { ChannelsService } from './channels.service.js'; // ApiKeyGuard attaches the introspected Center user id onto the request. type AuthedRequest = { userId?: string }; diff --git a/src/channels/channels.module.ts b/src/channels/channels.module.ts index b0a23e0..93c3f52 100644 --- a/src/channels/channels.module.ts +++ b/src/channels/channels.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ChannelsController } from './channels.controller'; -import { Channel } from '../entities/channel.entity'; -import { ChannelMember } from '../entities/channel-member.entity'; -import { WakeMapping } from '../entities/wake-mapping.entity'; -import { ChannelsService } from './channels.service'; +import { ChannelsController } from './channels.controller.js'; +import { Channel } from '../entities/channel.entity.js'; +import { ChannelMember } from '../entities/channel-member.entity.js'; +import { WakeMapping } from '../entities/wake-mapping.entity.js'; +import { ChannelsService } from './channels.service.js'; @Module({ imports: [TypeOrmModule.forFeature([Channel, ChannelMember, WakeMapping])], diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index 1d66861..79d1c48 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -1,10 +1,10 @@ import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; -import { Channel } from '../entities/channel.entity'; -import { ChannelMember } from '../entities/channel-member.entity'; -import { WakeMapping } from '../entities/wake-mapping.entity'; -import { TurnService } from './turn.service'; +import { Channel } from '../entities/channel.entity.js'; +import { ChannelMember } from '../entities/channel-member.entity.js'; +import { WakeMapping } from '../entities/wake-mapping.entity.js'; +import { TurnService } from './turn.service.js'; const X_TYPES = ['general', 'work', 'report', 'discuss', 'triage', 'custom'] as const; type XType = (typeof X_TYPES)[number]; diff --git a/src/channels/turn-shuffle.ts b/src/channels/turn-shuffle.ts index 95f1f06..8082790 100644 --- a/src/channels/turn-shuffle.ts +++ b/src/channels/turn-shuffle.ts @@ -1,4 +1,4 @@ -import { RoundEvent } from '../entities/channel-turn-state.entity'; +import { RoundEvent } from '../entities/channel-turn-state.entity.js'; export type ShuffleResult = { paused: true } | { paused: false; newOrder: string[] }; diff --git a/src/channels/turn.module.ts b/src/channels/turn.module.ts index 8d96dc7..b853202 100644 --- a/src/channels/turn.module.ts +++ b/src/channels/turn.module.ts @@ -1,5 +1,5 @@ import { Global, Module } from '@nestjs/common'; -import { TurnService } from './turn.service'; +import { TurnService } from './turn.service.js'; @Global() @Module({ diff --git a/src/channels/turn.service.ts b/src/channels/turn.service.ts index e7cf711..73e5188 100644 --- a/src/channels/turn.service.ts +++ b/src/channels/turn.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { DataSource, EntityManager } from 'typeorm'; -import { ChannelTurnState, TurnFrame } from '../entities/channel-turn-state.entity'; -import { ChannelMember } from '../entities/channel-member.entity'; -import { computeShuffle } from './turn-shuffle'; +import { ChannelTurnState, TurnFrame } from '../entities/channel-turn-state.entity.js'; +import { ChannelMember } from '../entities/channel-member.entity.js'; +import { computeShuffle } from './turn-shuffle.js'; // wakeupUserId: the single user who should receive wakeup=true on the // resulting push (null = nobody / paused). For commands, `ack` present means diff --git a/src/common/api-key.guard.ts b/src/common/api-key.guard.ts index 195ec48..ac7b9d8 100644 --- a/src/common/api-key.guard.ts +++ b/src/common/api-key.guard.ts @@ -4,7 +4,7 @@ import { Injectable, UnauthorizedException, } from '@nestjs/common'; -import { introspectGuildToken } from './center-auth'; +import { introspectGuildToken } from './center-auth.js'; @Injectable() export class ApiKeyGuard implements CanActivate { diff --git a/src/common/metrics.controller.ts b/src/common/metrics.controller.ts index 0054d6b..61510d1 100644 --- a/src/common/metrics.controller.ts +++ b/src/common/metrics.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get } from '@nestjs/common'; -import { MetricsService } from './metrics.service'; +import { MetricsService } from './metrics.service.js'; @Controller('metrics') export class MetricsController { diff --git a/src/common/request-context.middleware.ts b/src/common/request-context.middleware.ts index d5d63eb..a232a30 100644 --- a/src/common/request-context.middleware.ts +++ b/src/common/request-context.middleware.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; -import { MetricsService } from './metrics.service'; +import { MetricsService } from './metrics.service.js'; type ReqWithId = Request & { requestId?: string }; diff --git a/src/data-source.ts b/src/data-source.ts index 521bcdd..17f8669 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import { DataSource, DataSourceOptions } from 'typeorm'; -import { buildTypeOrmConfig } from './database.config'; +import { buildTypeOrmConfig } from './database.config.js'; const cfg = buildTypeOrmConfig(); diff --git a/src/database.config.ts b/src/database.config.ts index 62e2ee2..5d25642 100644 --- a/src/database.config.ts +++ b/src/database.config.ts @@ -1,16 +1,16 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { Guild } from './entities/guild.entity'; -import { Channel } from './entities/channel.entity'; -import { ChannelMember } from './entities/channel-member.entity'; -import { WakeMapping } from './entities/wake-mapping.entity'; -import { ChannelTurnState } from './entities/channel-turn-state.entity'; -import { Message } from './entities/message.entity'; -import { DmConversation } from './entities/dm-conversation.entity'; -import { DmParticipant } from './entities/dm-participant.entity'; -import { GuildRole } from './entities/guild-role.entity'; -import { GuildMember } from './entities/guild-member.entity'; -import { GuildMemberRole } from './entities/guild-member-role.entity'; -import { IdempotencyRecord } from './entities/idempotency-record.entity'; +import { Guild } from './entities/guild.entity.js'; +import { Channel } from './entities/channel.entity.js'; +import { ChannelMember } from './entities/channel-member.entity.js'; +import { WakeMapping } from './entities/wake-mapping.entity.js'; +import { ChannelTurnState } from './entities/channel-turn-state.entity.js'; +import { Message } from './entities/message.entity.js'; +import { DmConversation } from './entities/dm-conversation.entity.js'; +import { DmParticipant } from './entities/dm-participant.entity.js'; +import { GuildRole } from './entities/guild-role.entity.js'; +import { GuildMember } from './entities/guild-member.entity.js'; +import { GuildMemberRole } from './entities/guild-member-role.entity.js'; +import { IdempotencyRecord } from './entities/idempotency-record.entity.js'; export const buildTypeOrmConfig = (): TypeOrmModuleOptions => ({ type: 'mysql', diff --git a/src/events/events.module.ts b/src/events/events.module.ts index e9a657e..849de68 100644 --- a/src/events/events.module.ts +++ b/src/events/events.module.ts @@ -1,5 +1,5 @@ import { Global, Module } from '@nestjs/common'; -import { EventsService } from './events.service'; +import { EventsService } from './events.service.js'; @Global() @Module({ diff --git a/src/events/events.service.ts b/src/events/events.service.ts index fa26bbd..e982df8 100644 --- a/src/events/events.service.ts +++ b/src/events/events.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { createHmac, randomUUID } from 'crypto'; -import { FabricEventEnvelope } from './event-envelope'; +import { FabricEventEnvelope } from './event-envelope.js'; type RetryTask = { envelope: FabricEventEnvelope; diff --git a/src/guilds/guilds.controller.ts b/src/guilds/guilds.controller.ts index 0878189..a97fa65 100644 --- a/src/guilds/guilds.controller.ts +++ b/src/guilds/guilds.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Post } from '@nestjs/common'; -import { GuildsService } from './guilds.service'; +import { GuildsService } from './guilds.service.js'; @Controller('guilds') export class GuildsController { diff --git a/src/guilds/guilds.module.ts b/src/guilds/guilds.module.ts index 234ca23..2a976b9 100644 --- a/src/guilds/guilds.module.ts +++ b/src/guilds/guilds.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { GuildsController } from './guilds.controller'; -import { Guild } from '../entities/guild.entity'; -import { GuildsService } from './guilds.service'; +import { GuildsController } from './guilds.controller.js'; +import { Guild } from '../entities/guild.entity.js'; +import { GuildsService } from './guilds.service.js'; @Module({ imports: [TypeOrmModule.forFeature([Guild])], diff --git a/src/guilds/guilds.service.ts b/src/guilds/guilds.service.ts index 2f04de7..ea954c7 100644 --- a/src/guilds/guilds.service.ts +++ b/src/guilds/guilds.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { Guild } from '../entities/guild.entity'; +import { Guild } from '../entities/guild.entity.js'; @Injectable() export class GuildsService { diff --git a/src/health.integration.spec.ts b/src/health.integration.spec.ts index 149c748..4aa5317 100644 --- a/src/health.integration.spec.ts +++ b/src/health.integration.spec.ts @@ -3,7 +3,7 @@ import { Test } from '@nestjs/testing'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { DataSource } from 'typeorm'; -import { Channel } from './entities/channel.entity'; +import { Channel } from './entities/channel.entity.js'; process.env.DB_HOST = '127.0.0.1'; process.env.DB_PORT = '3308'; @@ -18,7 +18,7 @@ describe('guild integration (mysql + api)', () => { let dataSource: DataSource; beforeAll(async () => { - const { AppModule } = await import('./app.module'); + const { AppModule } = await import('./app.module.js'); const moduleRef = await Test.createTestingModule({ imports: [AppModule], }).compile(); diff --git a/src/main.ts b/src/main.ts index b359079..f02a523 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,9 +2,9 @@ import 'reflect-metadata'; import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { AppModule } from './app.module'; -import { createRequestContextMiddleware } from './common/request-context.middleware'; -import { MetricsService } from './common/metrics.service'; +import { AppModule } from './app.module.js'; +import { createRequestContextMiddleware } from './common/request-context.middleware.js'; +import { MetricsService } from './common/metrics.service.js'; function requireEnv(name: string): string { const value = process.env[name]; diff --git a/src/members/members.controller.ts b/src/members/members.controller.ts index 8b1c6f5..3da2de6 100644 --- a/src/members/members.controller.ts +++ b/src/members/members.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get, Query } from '@nestjs/common'; -import { MembersService } from './members.service'; +import { MembersService } from './members.service.js'; @Controller('members') export class MembersController { diff --git a/src/members/members.module.ts b/src/members/members.module.ts index 42a389e..288773e 100644 --- a/src/members/members.module.ts +++ b/src/members/members.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { GuildMember } from '../entities/guild-member.entity'; -import { MembersController } from './members.controller'; -import { MembersService } from './members.service'; +import { GuildMember } from '../entities/guild-member.entity.js'; +import { MembersController } from './members.controller.js'; +import { MembersService } from './members.service.js'; @Module({ imports: [TypeOrmModule.forFeature([GuildMember])], diff --git a/src/members/members.service.ts b/src/members/members.service.ts index ed7f6f9..d58f10b 100644 --- a/src/members/members.service.ts +++ b/src/members/members.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { GuildMember } from '../entities/guild-member.entity'; +import { GuildMember } from '../entities/guild-member.entity.js'; @Injectable() export class MembersService { diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index f38693f..d230427 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -13,18 +13,18 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, Repository } from 'typeorm'; -import { CreateMessageDto } from './dto.create-message.dto'; -import { Channel } from '../entities/channel.entity'; -import { Message } from '../entities/message.entity'; -import { IdempotencyRecord } from '../entities/idempotency-record.entity'; -import { WakeMapping } from '../entities/wake-mapping.entity'; -import { parseSlashCommand } from '../channels/slash-commands'; -import { parseMentions, extractNameMentions, replaceNameMentions } from '../channels/mentions'; -import { resolveUserNames } from '../common/center-auth'; -import { TurnService } from '../channels/turn.service'; -import { EventsService } from '../events/events.service'; -import { clampLimit, computeNextExpectedSeq } from './pagination.util'; -import { RealtimeGateway } from '../realtime/realtime.gateway'; +import { CreateMessageDto } from './dto.create-message.dto.js'; +import { Channel } from '../entities/channel.entity.js'; +import { Message } from '../entities/message.entity.js'; +import { IdempotencyRecord } from '../entities/idempotency-record.entity.js'; +import { WakeMapping } from '../entities/wake-mapping.entity.js'; +import { parseSlashCommand } from '../channels/slash-commands.js'; +import { parseMentions, extractNameMentions, replaceNameMentions } from '../channels/mentions.js'; +import { resolveUserNames } from '../common/center-auth.js'; +import { TurnService } from '../channels/turn.service.js'; +import { EventsService } from '../events/events.service.js'; +import { clampLimit, computeNextExpectedSeq } from './pagination.util.js'; +import { RealtimeGateway } from '../realtime/realtime.gateway.js'; const EDIT_WINDOW_MS = 15 * 60 * 1000; const DEFAULT_PAGE_LIMIT = 50; diff --git a/src/messaging/messaging.module.ts b/src/messaging/messaging.module.ts index 0ac6d6a..b6f5eac 100644 --- a/src/messaging/messaging.module.ts +++ b/src/messaging/messaging.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { MessagingController } from './messaging.controller'; -import { Channel } from '../entities/channel.entity'; -import { Message } from '../entities/message.entity'; -import { IdempotencyRecord } from '../entities/idempotency-record.entity'; -import { WakeMapping } from '../entities/wake-mapping.entity'; +import { MessagingController } from './messaging.controller.js'; +import { Channel } from '../entities/channel.entity.js'; +import { Message } from '../entities/message.entity.js'; +import { IdempotencyRecord } from '../entities/idempotency-record.entity.js'; +import { WakeMapping } from '../entities/wake-mapping.entity.js'; @Module({ imports: [TypeOrmModule.forFeature([Channel, Message, IdempotencyRecord, WakeMapping])], diff --git a/src/messaging/pagination.util.spec.ts b/src/messaging/pagination.util.spec.ts index d38573b..db6242d 100644 --- a/src/messaging/pagination.util.spec.ts +++ b/src/messaging/pagination.util.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { clampLimit, computeNextExpectedSeq } from './pagination.util'; +import { clampLimit, computeNextExpectedSeq } from './pagination.util.js'; describe('pagination utils', () => { it('clamps limit safely', () => { diff --git a/src/realtime/realtime.gateway.ts b/src/realtime/realtime.gateway.ts index 2b5a01a..f575e43 100644 --- a/src/realtime/realtime.gateway.ts +++ b/src/realtime/realtime.gateway.ts @@ -9,7 +9,7 @@ import { } from '@nestjs/websockets'; import { Logger } from '@nestjs/common'; import { Server, Socket } from 'socket.io'; -import { introspectGuildToken } from '../common/center-auth'; +import { introspectGuildToken } from '../common/center-auth.js'; type XType = 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom'; @@ -189,7 +189,7 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect wakeUserIds: ctx.wakeUserIds, mentionUserIds: ctx.mentionUserIds, }); - s.emit('message.created', { ...data, wakeup }); + s.emit('message.created', { ...data, channelId, wakeup }); } } @@ -204,7 +204,7 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect for (const s of sockets) { const recipientUserId = typeof s.data.userId === 'string' ? s.data.userId : `anon:${s.id}`; const wakeup = wakeupUserId !== null && recipientUserId === wakeupUserId; - s.emit('message.created', { ...data, wakeup }); + s.emit('message.created', { ...data, channelId, wakeup }); } } } diff --git a/src/realtime/realtime.module.ts b/src/realtime/realtime.module.ts index 56f58c0..12460ff 100644 --- a/src/realtime/realtime.module.ts +++ b/src/realtime/realtime.module.ts @@ -1,5 +1,5 @@ import { Global, Module } from '@nestjs/common'; -import { RealtimeGateway } from './realtime.gateway'; +import { RealtimeGateway } from './realtime.gateway.js'; @Global() @Module({ diff --git a/tsconfig.json b/tsconfig.json index fc317e0..1558f9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "module": "commonjs", - "target": "es2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "es2022", "strict": true, "esModuleInterop": true, "experimentalDecorators": true,