chore(sync): sync updates from docmost/docmost

This commit is contained in:
Forgejo Actions 2026-10-04 04:00:30 +00:00
commit b48cb476b4
23 changed files with 327 additions and 76 deletions

View file

@ -1,7 +1,7 @@
FROM node:26-slim AS base
LABEL org.opencontainers.image.source="https://github.com/docmost/docmost"
RUN npm install -g pnpm@11.25.0
RUN npm install -g pnpm@11.28.2
FROM base AS builder

View file

@ -471,7 +471,9 @@ export const collabExtensions: CollabExtensions = (provider, user) => [
CollaborationCaret.configure({
provider,
user: {
id: user.id,
name: user.name,
avatarUrl: user.avatarUrl,
color: randomElement(userColors),
},
}),

View file

@ -97,7 +97,7 @@
"nestjs-cls": "6.2.0",
"nestjs-kysely": "3.1.2",
"nestjs-pino": "4.6.1",
"nodemailer": "9.1.1",
"nodemailer": "10.0.10",
"openid-client": "6.8.2",
"otpauth": "9.5.0",
"p-limit": "7.3.0",
@ -120,7 +120,7 @@
"tlds": "1.261.0",
"tmp-promise": "3.0.3",
"typesense": "3.0.5",
"undici": "7.29.0",
"undici": "7.29.1",
"ws": "8.21.3",
"yauzl": "3.4.0",
"zod": "4.3.6"
@ -136,7 +136,6 @@
"@types/jest": "^30.0.0",
"@types/mime-types": "^3.0.1",
"@types/node": "^25.5.0",
"@types/nodemailer": "^7.0.11",
"@types/passport-google-oauth20": "^2.0.17",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.3",

View file

@ -146,8 +146,14 @@ export class CollaborationGateway {
eventName: TName,
documentName: string,
payload: Parameters<CollabEventHandlers[TName]>[1],
onlyIfOpen = false,
) {
return this.redisSync?.handleEvent(eventName, documentName, payload);
return this.redisSync?.handleEvent(
eventName,
documentName,
payload,
onlyIfOpen,
);
}
openDirectConnection(documentName: string, context?: any) {

View file

@ -21,6 +21,17 @@ export class CollaborationHandler {
getHandlers(hocuspocus: Hocuspocus) {
return {
getConnectedUserIds: async (documentName: string) => {
const document = hocuspocus.documents.get(documentName);
if (!document) return [];
const userIds = new Set<string>();
for (const state of document.awareness.getStates().values()) {
const userId = state?.user?.id;
if (typeof userId === 'string') userIds.add(userId);
}
return [...userIds];
},
alterState: async (documentName: string, payload: { pageId: string }) => {
// dummy
// this.logger.log('Processing', documentName, payload);

View file

@ -247,3 +247,7 @@ export function jsonToMarkdown(tiptapJson: any): string {
const html = jsonToHtml(tiptapJson);
return htmlToMarkdown(html);
}
export function isRenderableObject(value: unknown): boolean {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

View file

@ -1,5 +1,6 @@
import {
afterUnloadDocumentPayload,
beforeHandleAwarenessPayload,
Extension,
onChangePayload,
onLoadDocumentPayload,
@ -8,7 +9,12 @@ import {
import * as Y from 'yjs';
import { Injectable, Logger } from '@nestjs/common';
import { TiptapTransformer } from '@hocuspocus/transformer';
import { getPageId, jsonToText, tiptapExtensions } from '../collaboration.util';
import {
getPageId,
isRenderableObject,
jsonToText,
tiptapExtensions,
} from '../collaboration.util';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
@ -132,6 +138,8 @@ export class PersistenceExtension implements Extension {
return;
}
await this.collabHistory.addContributors(pageId, editingUserIds);
let contributorIds = undefined;
try {
const existingContributors = page.contributorIds || [];
@ -162,6 +170,10 @@ export class PersistenceExtension implements Extension {
});
} catch (err) {
this.logger.error(`Failed to update page ${pageId}`, err);
page = null;
editingUserIds.forEach((userId) =>
this.trackContributor(documentName, userId),
);
}
if (page) {
@ -184,8 +196,6 @@ export class PersistenceExtension implements Extension {
}
if (page) {
await this.collabHistory.addContributors(pageId, editingUserIds);
const mentions = extractMentions(tiptapJson);
const userMentions = extractUserMentions(mentions);
@ -217,12 +227,45 @@ export class PersistenceExtension implements Extension {
}
}
// Drop malformed awareness before it is broadcast
async beforeHandleAwareness({
states,
context,
}: beforeHandleAwarenessPayload) {
const user = context?.user;
for (const [clientId, state] of states) {
if (!isRenderableObject(state)) {
states.delete(clientId);
continue;
}
if ('user' in state && !isRenderableObject(state.user)) {
delete state.user;
}
if (state.user && user) {
state.user.id = user.id;
state.user.avatarUrl = user.avatarUrl ?? null;
}
if (
'cursor' in state &&
state.cursor !== null &&
!isRenderableObject(state.cursor)
) {
delete state.cursor;
}
}
}
async onChange(data: onChangePayload) {
const documentName = data.documentName;
const userId = data.context?.user?.id;
if (!userId) return;
this.trackContributor(data.documentName, userId);
}
private trackContributor(documentName: string, userId: string) {
if (!this.contributors.has(documentName)) {
this.contributors.set(documentName, new Set());
}

View file

@ -235,13 +235,11 @@ export class RedisSyncExtension<TCE extends CustomEvents> implements Extension {
};
async maintainLock(documentName: string) {
clearInterval(this.locks[documentName]);
this.locks[documentName] = setInterval(() => {
this.pub.set(
this.getKey(documentName),
this.serverId,
'PX',
this.lockTTL,
);
this.pub
.set(this.getKey(documentName), this.serverId, 'PX', this.lockTTL)
.catch(() => {});
}, this.lockTTL / 2);
}

View file

@ -37,6 +37,14 @@ async function bootstrap() {
const logger = new Logger('CollabServer');
process.on('unhandledRejection', (reason, promise) => {
logger.error(`UnhandledRejection, reason: ${reason}`, promise);
});
process.on('uncaughtException', (error) => {
logger.error('UncaughtException:', error);
});
const port = process.env.COLLAB_PORT || 3001;
const host = process.env.HOST || '0.0.0.0';
await app.listen(port, host, () => {

View file

@ -6,9 +6,10 @@ import { CommentNotificationService } from './services/comment.notification';
import { PageNotificationService } from './services/page.notification';
import { VerificationNotificationService } from './services/verification.notification';
import { PageUpdateEmailRateLimiter } from './services/page-update-email-rate-limiter';
import { CollaborationModule } from '../../collaboration/collaboration.module';
@Module({
imports: [],
imports: [CollaborationModule],
controllers: [NotificationController],
providers: [
NotificationService,

View file

@ -21,6 +21,7 @@ import { PageUpdateDigestEmail } from '@docmost/transactional/emails/page-update
import { PermissionGrantedEmail } from '@docmost/transactional/emails/permission-granted-email';
import { getPageTitle } from '../../../common/helpers';
import { QueueJob, QueueName } from '../../../integrations/queue/constants';
import { CollaborationGateway } from '../../../collaboration/collaboration.gateway';
const PAGE_UPDATE_COOLDOWN_HOURS = 7;
const DIGEST_DELAY_MS = 12 * 60 * 60 * 1000; // 12 hours
@ -37,6 +38,7 @@ export class PageNotificationService {
private readonly pagePermissionRepo: PagePermissionRepo,
private readonly watcherRepo: WatcherRepo,
private readonly rateLimiter: PageUpdateEmailRateLimiter,
private readonly collaborationGateway: CollaborationGateway,
@InjectQueue(QueueName.NOTIFICATION_QUEUE) private notificationQueue: Queue,
) {}
@ -186,8 +188,22 @@ export class PageNotificationService {
if (watcherIds.length === 0) return;
const actorSet = new Set(actorIds);
const candidateIds = watcherIds.filter((id) => !actorSet.has(id));
let connectedUserIds: string[] = [];
try {
connectedUserIds =
(await this.collaborationGateway.handleYjsEvent(
'getConnectedUserIds',
`page.${pageId}`,
undefined,
true,
)) ?? [];
} catch (err) {
this.logger.warn(
`Failed to get connected users for page ${pageId}: ${err?.['message']}`,
);
}
const excludedIds = new Set([...actorIds, ...connectedUserIds]);
const candidateIds = watcherIds.filter((id) => !excludedIds.has(id));
if (candidateIds.length === 0) return;
const eligibleUsers = await this.getEligiblePageUpdateUsers(candidateIds);
@ -370,13 +386,14 @@ export class PageNotificationService {
const pages = spaceFilteredPages.filter((p) => accessiblePageIds.has(p.id));
if (pages.length === 0) return;
const actors = actorIds.length > 0
? await this.db
.selectFrom('users')
.select(['id', 'name'])
.where('id', 'in', actorIds)
.execute()
: [];
const actors =
actorIds.length > 0
? await this.db
.selectFrom('users')
.select(['id', 'name'])
.where('id', 'in', actorIds)
.execute()
: [];
const actorMap = new Map(actors.map((a) => [a.id, a.name]));
const pageActors = new Map<string, Set<string>>();

View file

@ -56,6 +56,7 @@ import { markdownToHtml } from '@docmost/editor-ext';
import { WatcherService } from '../../watcher/watcher.service';
import { sql } from 'kysely';
import { TransclusionService } from '../transclusion/transclusion.service';
import { LabelRepo } from '@docmost/db/repos/label/label.repo';
@Injectable()
export class PageService {
@ -74,6 +75,7 @@ export class PageService {
private collaborationGateway: CollaborationGateway,
private readonly watcherService: WatcherService,
private readonly transclusionService: TransclusionService,
private readonly labelRepo: LabelRepo,
) {}
async findById(
@ -715,7 +717,13 @@ export class PageService {
}),
);
await this.db.insertInto('pages').values(insertablePages).execute();
await executeTx(this.db, async (trx) => {
await trx.insertInto('pages').values(insertablePages).execute();
await this.labelRepo.copyLabelsToPages(
new Map([...pageMap].map(([oldId, entry]) => [oldId, entry.newPageId])),
trx,
);
});
// Extract transclusions from every duplicated page and persist them in
// one statement. Duplication bypasses Yjs onStoreDocument; brand-new

View file

@ -6,11 +6,15 @@ import {
IsOptional,
IsString,
IsUUID,
MaxLength,
} from 'class-validator';
export const SEARCH_QUERY_MAX_LENGTH = 200;
export class SearchDTO {
@IsOptional()
@IsString()
@MaxLength(SEARCH_QUERY_MAX_LENGTH)
query?: string;
@IsOptional()
@ -61,6 +65,7 @@ export class SearchPublicSpaceDTO extends SearchDTO {
export class SearchSuggestionDTO {
@IsString()
@MaxLength(SEARCH_QUERY_MAX_LENGTH)
query: string;
@IsOptional()

View file

@ -55,11 +55,6 @@ export class SearchService {
: sql<number>`ts_rank(tsv, to_tsquery('english', f_unaccent(${searchQuery})))`.as(
'rank',
);
const highlightColumn = browseByFilters || titleOnly
? sql<string>`''`.as('highlight')
: sql<string>`ts_headline('english', text_content, to_tsquery('english', f_unaccent(${searchQuery})),'MinWords=9, MaxWords=10, MaxFragments=3')`.as(
'highlight',
);
let queryResults = this.db
.selectFrom('pages')
@ -73,7 +68,6 @@ export class SearchService {
'createdAt',
'updatedAt',
rankColumn,
highlightColumn,
])
.$if(!browseByFilters && !titleOnly, (qb) =>
qb.where(
@ -189,10 +183,32 @@ export class SearchService {
results = results.filter((r: any) => accessibleSet.has(r.id));
}
if (!browseByFilters && !titleOnly && results.length > 0) {
const highlights = await this.db
.selectFrom('pages')
.select([
'id',
sql<string>`ts_headline('english', substring(text_content, 1, 100000), to_tsquery('english', f_unaccent(${searchQuery})),'MinWords=9, MaxWords=10, MaxFragments=3')`.as(
'highlight',
),
])
.where(
'id',
'in',
results.map((r: any) => r.id),
)
.execute();
const highlightById = new Map(highlights.map((h) => [h.id, h.highlight]));
for (const result of results) {
result.highlight = highlightById.get(result.id) ?? '';
}
}
//@ts-ignore
const searchResults = results.map((result: SearchResponseDto) => {
result.wholeWord = true
if (!result.highlight) {
result.highlight = '';
result.matchedText = [];
return result;
}

View file

@ -176,6 +176,32 @@ export class LabelRepo {
.execute();
}
async copyLabelsToPages(
pageIdMap: Map<string, string>,
trx?: KyselyTransaction,
): Promise<void> {
if (pageIdMap.size === 0) return;
const db = dbOrTx(this.db, trx);
const sourceLabels = await db
.selectFrom('pageLabels')
.select(['pageId', 'labelId'])
.where('pageId', 'in', [...pageIdMap.keys()])
.execute();
if (sourceLabels.length === 0) return;
await db
.insertInto('pageLabels')
.values(
sourceLabels.map((row) => ({
pageId: pageIdMap.get(row.pageId),
labelId: row.labelId,
})),
)
.onConflict((oc) => oc.doNothing())
.execute();
}
async removeLabelFromPage(
pageId: string,
labelId: string,

@ -1 +1 @@
Subproject commit 5e7120dcc86a344f5af09ccda0a29d5784659938
Subproject commit 8190e2182cbbcc5e291217a13147fcd246ede3fe

View file

@ -0,0 +1,54 @@
import { htmlToMarkdown } from '@docmost/editor-ext';
const colgroup =
'<colgroup><col style="min-width: 25px;" /><col style="min-width: 25px;" /></colgroup>';
describe('htmlToMarkdown', () => {
it('uses the table header row as the markdown header', () => {
const markdown = htmlToMarkdown(
`<table>${colgroup}<tbody>` +
'<tr><th><p>Name</p></th><th><p>Role</p></th></tr>' +
'<tr><td><p>Ada</p></td><td><p>Engineer</p></td></tr>' +
'</tbody></table>',
);
expect(markdown.trim().split('\n')).toEqual([
'| Name | Role |',
'| --- | --- |',
'| Ada | Engineer |',
]);
});
it('adds an empty header when the table has no header row', () => {
const markdown = htmlToMarkdown(
`<table>${colgroup}<tbody>` +
'<tr><td><p>Ada</p></td><td><p>Engineer</p></td></tr>' +
'<tr><td><p>Alan</p></td><td><p>Mathematician</p></td></tr>' +
'</tbody></table>',
);
expect(markdown.trim().split('\n')).toEqual([
'| | |',
'| --- | --- |',
'| Ada | Engineer |',
'| Alan | Mathematician |',
]);
});
it('marks details blocks so their content is parsed as markdown', () => {
const markdown = htmlToMarkdown(
'<details><summary data-type="detailsSummary">Title</summary>' +
'<div data-type="detailsContent"><p>Some <strong>bold</strong></p></div>' +
'</details>',
);
expect(markdown.trim().split('\n')).toEqual([
'<details markdown="1">',
'<summary>Title</summary>',
'',
'Some **bold**',
'',
'</details>',
]);
});
});

View file

@ -0,0 +1,51 @@
import { Reflector } from '@nestjs/core';
import { ThrottlerStorageService } from '@nestjs/throttler';
import { JwtType } from '../../core/auth/dto/jwt-payload';
import { UserThrottlerGuard } from './user-throttler.guard';
const guard = new UserThrottlerGuard(
[],
new ThrottlerStorageService(),
new Reflector(),
);
function getTracker(req: Record<string, any>): Promise<string> {
return guard['getTracker'](req);
}
describe('UserThrottlerGuard.getTracker', () => {
it('tracks a signed-in request by its user id', async () => {
const req = {
ip: '203.0.113.7',
user: {
user: { id: 'user_1' },
workspace: { id: 'ws_1' },
authType: JwtType.ACCESS,
},
};
await expect(getTracker(req)).resolves.toBe('user:user_1');
});
it('falls back to the client ip when nobody is signed in', async () => {
const req = { ip: '203.0.113.7', user: null };
await expect(getTracker(req)).resolves.toBe('203.0.113.7');
});
it('falls back to the socket address when the ip is empty', async () => {
const req = {
ip: '',
user: null,
socket: { remoteAddress: '198.51.100.4' },
};
await expect(getTracker(req)).resolves.toBe('198.51.100.4');
});
it('uses a constant tracker for an unidentifiable client', async () => {
const req = { ip: '', user: null, socket: {} };
await expect(getTracker(req)).resolves.toBe('unknown');
});
});

View file

@ -1,13 +1,19 @@
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
type AuthedRequest = { user?: { id?: string } };
type AuthedRequest = {
user?: { user?: { id?: string } } | null;
socket?: { remoteAddress?: string };
};
@Injectable()
export class UserThrottlerGuard extends ThrottlerGuard {
protected async getTracker(req: AuthedRequest): Promise<string> {
const userId = req.user?.id;
const userId = req.user?.user?.id;
if (userId) return `user:${userId}`;
return super.getTracker(req as Parameters<ThrottlerGuard['getTracker']>[0]);
const ip = await super.getTracker(
req as Parameters<ThrottlerGuard['getTracker']>[0],
);
return ip || req.socket?.remoteAddress || 'unknown';
}
}

View file

@ -95,5 +95,5 @@
"packages/*"
]
},
"packageManager": "pnpm@11.25.0"
"packageManager": "pnpm@11.28.2"
}

View file

@ -37,7 +37,11 @@ export function htmlToMarkdown(html: string): string {
footnoteRef,
footnotesList,
]);
return turndownService.turndown(html).replaceAll('<br>', ' ');
const htmlWithoutColgroups = html.replace(
/<colgroup\b[^>]*>[\s\S]*?<\/colgroup>/gi,
'',
);
return turndownService.turndown(htmlWithoutColgroups).replaceAll('<br>', ' ');
}
function listParagraph(turndownService: _TurndownService) {
@ -146,7 +150,7 @@ function preserveDetail(turndownService: _TurndownService) {
)
.join('');
return `\n<details>\n${detailSummary}\n\n${detailsContent}\n\n</details>\n`;
return `\n<details markdown="1">\n${detailSummary}\n\n${detailsContent}\n\n</details>\n`;
},
});
}

View file

@ -10,7 +10,7 @@ overrides:
ws: 8.21.3
dompurify: 3.4.13
mermaid: 11.16.1
undici: 7.29.0
undici: 7.29.1
tmp: 0.2.7
nanoid@^3: 3.3.18
lodash-es: 4.18.1
@ -21,7 +21,7 @@ overrides:
yaml@>=2.0.0 <2.8.3: 2.8.3
brace-expansion@^5: 5.0.9
axios: 1.18.1
ip-address: 10.3.1
ip-address: 10.5.1
fast-uri: 3.1.7
form-data@>=4.0.0 <4.0.6: 4.0.6
nanoid@>=4.0.0 <5.1.16: 5.1.16
@ -29,6 +29,7 @@ overrides:
'@opentelemetry/core@>=2.0.0 <2.8.0': 2.9.0
js-yaml@>=3.0.0 <3.15.2: 3.15.2
js-yaml@>=4.0.0 <4.3.2: 4.3.2
smol-toml@<1.7.1: 1.7.1
patchedDependencies:
scimmy@1.3.5: 775d80f86830b2c5dd1a250c9802c10f8fc3da3c7898373de5aa0c23993d1673
@ -694,8 +695,8 @@ importers:
specifier: 4.6.1
version: 4.6.1(@nestjs/common@11.2.1(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.2.2))(pino-http@11.0.0)(pino@10.1.0)(rxjs@7.8.2)
nodemailer:
specifier: 9.1.1
version: 9.1.1
specifier: 10.0.10
version: 10.0.10
openid-client:
specifier: 6.8.2
version: 6.8.2
@ -763,8 +764,8 @@ importers:
specifier: 3.0.5
version: 3.0.5(@babel/runtime@7.29.2)(debug@4.4.3(supports-color@7.2.0))(supports-color@10.2.2)
undici:
specifier: 7.29.0
version: 7.29.0
specifier: 7.29.1
version: 7.29.1
ws:
specifier: 8.21.3
version: 8.21.3
@ -805,9 +806,6 @@ importers:
'@types/node':
specifier: ^25.5.0
version: 25.5.0
'@types/nodemailer':
specifier: ^7.0.11
version: 7.0.11
'@types/passport-google-oauth20':
specifier: ^2.0.17
version: 2.0.17
@ -4633,9 +4631,6 @@ packages:
'@types/node@25.5.0':
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
'@types/nodemailer@7.0.11':
resolution: {integrity: sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==}
'@types/oauth@0.9.6':
resolution: {integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==}
@ -6906,8 +6901,8 @@ packages:
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
engines: {node: '>=12.22.0'}
ip-address@10.3.1:
resolution: {integrity: sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==}
ip-address@10.5.1:
resolution: {integrity: sha512-EXujUp9jyOI/chPgtqk6uy7fDq8AeCB/WlfEuPg9LN0fN9lzKAKfuDYi60SMhHwgUiEhZvVYsbGZN+RUU1INiA==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1:
@ -7984,9 +7979,9 @@ packages:
resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==}
engines: {node: '>=18'}
nodemailer@9.1.1:
resolution: {integrity: sha512-izw9mVKFix6YSnC9eLgV6g1opl9DUlRio9ZNcq+Wu9Ujn2UwF+8Nl0B8nz22kEC+CTZCvinkxwJ0DeFbb6NwcQ==}
engines: {node: '>=6.0.0'}
nodemailer@10.0.10:
resolution: {integrity: sha512-He9XskOFms62SyAKkLu8CcGcYHAo+BSHSsORI8s4PJH8qZgZY4L4lDfvyN0twvmbpyG+eGrxpyBlskj9d9rJ8A==}
engines: {node: '>=20.0.0'}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
@ -9058,8 +9053,8 @@ packages:
resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==}
deprecated: Unsupported
smol-toml@1.6.1:
resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
smol-toml@1.7.1:
resolution: {integrity: sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==}
engines: {node: '>= 18'}
socket.io-adapter@2.5.4:
@ -9570,8 +9565,8 @@ packages:
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
undici@7.29.0:
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
undici@7.29.1:
resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
engines: {node: '>=20.18.1'}
unicode-canonical-property-names-ecmascript@2.0.0:
@ -10163,7 +10158,7 @@ snapshots:
'@ai-sdk/provider': 3.0.16
'@standard-schema/spec': 1.1.0
eventsource-parser: 3.1.1
undici: 7.29.0
undici: 7.29.1
zod: 4.3.6
'@ai-sdk/provider@3.0.16':
@ -14362,7 +14357,7 @@ snapshots:
'@turbopuffer/turbopuffer@2.8.0':
dependencies:
pako: 2.2.0
undici: 7.29.0
undici: 7.29.1
'@tybys/wasm-util@0.10.2':
dependencies:
@ -14640,10 +14635,6 @@ snapshots:
dependencies:
undici-types: 7.18.2
'@types/nodemailer@7.0.11':
dependencies:
'@types/node': 25.5.0
'@types/oauth@0.9.6':
dependencies:
'@types/node': 25.5.0
@ -15617,7 +15608,7 @@ snapshots:
parse5: 7.3.0
parse5-htmlparser2-tree-adapter: 7.1.0
parse5-parser-stream: 7.1.2
undici: 7.29.0
undici: 7.29.1
whatwg-mimetype: 4.0.0
chevrotain-allstar@0.3.1(chevrotain@11.0.3):
@ -16684,7 +16675,7 @@ snapshots:
express-rate-limit@8.2.2(express@5.2.1(supports-color@10.2.2)):
dependencies:
express: 5.2.1(supports-color@10.2.2)
ip-address: 10.3.1
ip-address: 10.5.1
express@5.2.1(supports-color@10.2.2):
dependencies:
@ -17269,7 +17260,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
ip-address@10.3.1: {}
ip-address@10.5.1: {}
ipaddr.js@1.9.1: {}
@ -18418,7 +18409,7 @@ snapshots:
node-releases@2.0.54: {}
nodemailer@9.1.1: {}
nodemailer@10.0.10: {}
normalize-path@3.0.0: {}
@ -18537,7 +18528,7 @@ snapshots:
safe-buffer: 5.2.1
semver: 7.8.4
signal-exit: 3.0.7
smol-toml: 1.6.1
smol-toml: 1.7.1
string-width: 4.2.3
string_decoder: 1.3.0
strip-ansi: 6.0.1
@ -19776,7 +19767,7 @@ snapshots:
sliced@1.0.1: {}
smol-toml@1.6.1: {}
smol-toml@1.7.1: {}
socket.io-adapter@2.5.4(supports-color@10.2.2):
dependencies:
@ -20340,7 +20331,7 @@ snapshots:
undici-types@7.18.2: {}
undici@7.29.0: {}
undici@7.29.1: {}
unicode-canonical-property-names-ecmascript@2.0.0: {}

View file

@ -9,7 +9,7 @@ overrides:
ws: 8.21.3
dompurify: 3.4.13
mermaid: 11.16.1
undici: 7.29.0
undici: 7.29.1
tmp: 0.2.7
nanoid@^3: 3.3.18
lodash-es: 4.18.1
@ -20,7 +20,7 @@ overrides:
yaml@>=2.0.0 <2.8.3: 2.8.3
brace-expansion@^5: 5.0.9
axios: 1.18.1
ip-address: 10.3.1
ip-address: 10.5.1
fast-uri: 3.1.7
form-data@>=4.0.0 <4.0.6: 4.0.6
nanoid@>=4.0.0 <5.1.16: 5.1.16
@ -28,6 +28,7 @@ overrides:
'@opentelemetry/core@>=2.0.0 <2.8.0': 2.9.0
js-yaml@>=3.0.0 <3.15.2: 3.15.2
js-yaml@>=4.0.0 <4.3.2: 4.3.2
smol-toml@<1.7.1: 1.7.1
shamefullyHoist: true
minimumReleaseAge: 4320
allowBuilds: