diff --git a/apps/client/src/ee/page-permission/services/page-permission-service.ts b/apps/client/src/ee/page-permission/services/page-permission-service.ts index 087b9b7c..4c29da0b 100644 --- a/apps/client/src/ee/page-permission/services/page-permission-service.ts +++ b/apps/client/src/ee/page-permission/services/page-permission-service.ts @@ -9,29 +9,29 @@ import { } from "@/ee/page-permission/types/page-permission.types"; export async function restrictPage(pageId: string): Promise { - await api.post("/pages/permissions/restrict", { pageId }); + await api.post("/pages/restrict", { pageId }); } export async function addPagePermission( data: IAddPagePermission, ): Promise { - await api.post("/pages/permissions/add-members", data); + await api.post("/pages/add-permission", data); } export async function removePagePermission( data: IRemovePagePermission, ): Promise { - await api.post("/pages/permissions/remove-members", data); + await api.post("/pages/remove-permission", data); } export async function updatePagePermissionRole( data: IUpdatePagePermissionRole, ): Promise { - await api.post("/pages/permissions/change-role", data); + await api.post("/pages/update-permission", data); } export async function unrestrictPage(pageId: string): Promise { - await api.post("/pages/permissions/unrestrict", { pageId }); + await api.post("/pages/remove-restriction", { pageId }); } export async function getPagePermissions( @@ -39,7 +39,7 @@ export async function getPagePermissions( params?: QueryParams, ): Promise> { const req = await api.post>( - "/pages/permissions/members", + "/pages/permissions", { pageId, ...params }, ); return req.data; @@ -48,7 +48,7 @@ export async function getPagePermissions( export async function getPageRestrictionInfo( pageId: string, ): Promise { - const req = await api.post("/pages/permissions/info", { + const req = await api.post("/pages/permission-info", { pageId, }); return req.data; diff --git a/apps/server/package.json b/apps/server/package.json index 0e421a1d..cffdea36 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -111,6 +111,7 @@ "yauzl": "^3.2.0" }, "devDependencies": { + "@clickhouse/client": "^1.17.0", "@eslint/js": "^9.20.0", "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.1", diff --git a/apps/server/src/core/page/dto/page-permission.dto.ts b/apps/server/src/core/page/dto/page-permission.dto.ts deleted file mode 100644 index 94960940..00000000 --- a/apps/server/src/core/page/dto/page-permission.dto.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - ArrayMaxSize, - ArrayMinSize, - IsArray, - IsEnum, - IsNotEmpty, - IsOptional, - IsString, - IsUUID, -} from 'class-validator'; -import { PagePermissionRole } from '../../../common/helpers/types/permission'; - -export class PageIdDto { - @IsString() - @IsNotEmpty() - pageId: string; -} - -export class RestrictPageDto extends PageIdDto {} - -export class AddPagePermissionDto extends PageIdDto { - @IsEnum(PagePermissionRole) - role: string; - - @IsOptional() - @IsArray() - @ArrayMaxSize(25, { - message: 'userIds must be an array with no more than 25 elements', - }) - @IsUUID('all', { each: true }) - userIds?: string[]; - - @IsOptional() - @IsArray() - @ArrayMaxSize(25, { - message: 'groupIds must be an array with no more than 25 elements', - }) - @IsUUID('all', { each: true }) - groupIds?: string[]; -} - -export class RemovePagePermissionDto extends PageIdDto { - @IsOptional() - @IsArray() - @ArrayMaxSize(25, { - message: 'userIds must be an array with no more than 25 elements', - }) - @IsUUID('all', { each: true }) - userIds?: string[]; - - @IsOptional() - @IsArray() - @ArrayMaxSize(25, { - message: 'groupIds must be an array with no more than 25 elements', - }) - @IsUUID('all', { each: true }) - groupIds?: string[]; -} - -export class UpdatePagePermissionRoleDto extends PageIdDto { - @IsEnum(PagePermissionRole) - role: string; - - @IsOptional() - @IsUUID() - userId?: string; - - @IsOptional() - @IsUUID() - groupId?: string; -} - -export class RemovePageRestrictionDto extends PageIdDto {} diff --git a/apps/server/src/core/page/page-permission.controller.ts b/apps/server/src/core/page/page-permission.controller.ts deleted file mode 100644 index 956ac259..00000000 --- a/apps/server/src/core/page/page-permission.controller.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - BadRequestException, - Body, - Controller, - HttpCode, - HttpStatus, - Post, - UseGuards, -} from '@nestjs/common'; -import { PagePermissionService } from './services/page-permission.service'; -import { - AddPagePermissionDto, - PageIdDto, - RemovePagePermissionDto, - RemovePageRestrictionDto, - RestrictPageDto, - UpdatePagePermissionRoleDto, -} from './dto/page-permission.dto'; -import { AuthUser } from '../../common/decorators/auth-user.decorator'; -import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator'; -import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; -import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { User, Workspace } from '@docmost/db/types/entity.types'; - -@UseGuards(JwtAuthGuard) -@Controller('pages/permissions') -export class PagePermissionController { - constructor(private readonly pagePermissionService: PagePermissionService) {} - - @HttpCode(HttpStatus.OK) - @Post('restrict') - async restrictPage( - @Body() dto: RestrictPageDto, - @AuthUser() user: User, - @AuthWorkspace() workspace: Workspace, - ) { - await this.pagePermissionService.restrictPage( - dto.pageId, - user, - workspace.id, - ); - } - - @HttpCode(HttpStatus.OK) - @Post('add-members') - async addPagePermission( - @Body() dto: AddPagePermissionDto, - @AuthUser() user: User, - @AuthWorkspace() workspace: Workspace, - ) { - validateMemberIds(dto); - - await this.pagePermissionService.addPagePermissions( - dto, - user, - workspace.id, - ); - } - - @HttpCode(HttpStatus.OK) - @Post('remove-members') - async removePagePermissions( - @Body() dto: RemovePagePermissionDto, - @AuthUser() user: User, - ) { - validateMemberIds(dto); - - await this.pagePermissionService.removePagePermissions(dto, user); - } - - @HttpCode(HttpStatus.OK) - @Post('change-role') - async updatePagePermissionRole( - @Body() dto: UpdatePagePermissionRoleDto, - @AuthUser() user: User, - ) { - if (!dto.userId && !dto.groupId) { - throw new BadRequestException('userId or groupId is required'); - } - - await this.pagePermissionService.updatePagePermissionRole(dto, user); - } - - @HttpCode(HttpStatus.OK) - @Post('unrestrict') - async removePageRestriction( - @Body() dto: RemovePageRestrictionDto, - @AuthUser() user: User, - ) { - await this.pagePermissionService.removePageRestriction(dto.pageId, user); - } - - @HttpCode(HttpStatus.OK) - @Post('members') - async getPagePermissions( - @Body() dto: PageIdDto, - @Body() pagination: PaginationOptions, - @AuthUser() user: User, - ) { - return this.pagePermissionService.getPagePermissions( - dto.pageId, - user, - pagination, - ); - } - - @HttpCode(HttpStatus.OK) - @Post('info') - async getPageRestrictionInfo( - @Body() dto: PageIdDto, - @AuthUser() user: User, - ) { - return this.pagePermissionService.getPageRestrictionInfo(dto.pageId, user); - } -} - -function validateMemberIds(dto: { userIds?: string[]; groupIds?: string[] }) { - if ( - (!dto.userIds || dto.userIds.length === 0) && - (!dto.groupIds || dto.groupIds.length === 0) - ) { - throw new BadRequestException('userIds or groupIds is required'); - } -} diff --git a/apps/server/src/core/page/page.module.ts b/apps/server/src/core/page/page.module.ts index 93805499..a2042279 100644 --- a/apps/server/src/core/page/page.module.ts +++ b/apps/server/src/core/page/page.module.ts @@ -3,21 +3,14 @@ import { PageService } from './services/page.service'; import { PageController } from './page.controller'; import { PageHistoryService } from './services/page-history.service'; import { TrashCleanupService } from './services/trash-cleanup.service'; -import { PagePermissionService } from './services/page-permission.service'; -import { PagePermissionController } from './page-permission.controller'; import { StorageModule } from '../../integrations/storage/storage.module'; import { CollaborationModule } from '../../collaboration/collaboration.module'; import { WatcherModule } from '../watcher/watcher.module'; @Module({ - controllers: [PageController, PagePermissionController], - providers: [ - PageService, - PageHistoryService, - TrashCleanupService, - PagePermissionService, - ], - exports: [PageService, PageHistoryService, PagePermissionService], + controllers: [PageController], + providers: [PageService, PageHistoryService, TrashCleanupService], + exports: [PageService, PageHistoryService], imports: [StorageModule, CollaborationModule, WatcherModule], }) export class PageModule {} diff --git a/apps/server/src/core/page/services/page-permission.service.ts b/apps/server/src/core/page/services/page-permission.service.ts deleted file mode 100644 index 5edf2563..00000000 --- a/apps/server/src/core/page/services/page-permission.service.ts +++ /dev/null @@ -1,568 +0,0 @@ -import { - BadRequestException, - ForbiddenException, - Injectable, - NotFoundException, -} from '@nestjs/common'; -import { InjectKysely } from 'nestjs-kysely'; -import { KyselyDB } from '@docmost/db/types/kysely.types'; -import { - PagePermissionMember, - PagePermissionRepo, -} from '@docmost/db/repos/page/page-permission.repo'; -import { PageRepo } from '@docmost/db/repos/page/page.repo'; -import { - AddPagePermissionDto, - RemovePagePermissionDto, - UpdatePagePermissionRoleDto, -} from '../dto/page-permission.dto'; -import { Page, User } from '@docmost/db/types/entity.types'; -import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { - PageAccessLevel, - PagePermissionRole, -} from '../../../common/helpers/types/permission'; -import { executeTx } from '@docmost/db/utils'; -import SpaceAbilityFactory from '../../casl/abilities/space-ability.factory'; -import { - SpaceCaslAction, - SpaceCaslSubject, -} from '../../casl/interfaces/space-ability.type'; -import { - CursorPaginationResult, - emptyCursorPaginationResult, -} from '@docmost/db/pagination/cursor-pagination'; -import { WsService } from '../../../ws/ws.service'; -import { WsTreeService } from '../../../ws/ws-tree.service'; - -export type PageRestrictionInfo = { - id: string; - title: string; - hasDirectRestriction: boolean; - hasInheritedRestriction: boolean; - userAccess: { - canView: boolean; - canEdit: boolean; - canManage: boolean; - }; -}; - -@Injectable() -export class PagePermissionService { - constructor( - private pagePermissionRepo: PagePermissionRepo, - private pageRepo: PageRepo, - private spaceAbility: SpaceAbilityFactory, - private wsService: WsService, - private wsTreeService: WsTreeService, - @InjectKysely() private readonly db: KyselyDB, - ) {} - - async restrictPage( - pageId: string, - authUser: User, - workspaceId: string, - ): Promise { - const page = await this.pageRepo.findById(pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - await this.validateWriteAccess(page, authUser); - - const existingAccess = - await this.pagePermissionRepo.findPageAccessByPageId(pageId); - if (existingAccess) { - throw new BadRequestException('Page is already restricted'); - } - - await executeTx(this.db, async (trx) => { - const pageAccess = await this.pagePermissionRepo.insertPageAccess( - { - pageId: pageId, - workspaceId: workspaceId, - accessLevel: PageAccessLevel.RESTRICTED, - creatorId: authUser.id, - }, - trx, - ); - - await this.pagePermissionRepo.insertPagePermissions( - [ - { - pageAccessId: pageAccess.id, - userId: authUser.id, - role: PagePermissionRole.WRITER, - addedById: authUser.id, - }, - ], - trx, - ); - }); - - await this.wsService.invalidateSpaceRestrictionCache(page.spaceId); - await this.wsTreeService.notifyPageRestricted(page, authUser.id); - } - - async addPagePermissions( - dto: AddPagePermissionDto, - authUser: User, - workspaceId: string, - ): Promise { - const page = await this.pageRepo.findById(dto.pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - await this.validateWriteAccess(page, authUser); - - const pageAccess = await this.pagePermissionRepo.findPageAccessByPageId( - dto.pageId, - ); - if (!pageAccess) { - throw new BadRequestException( - 'Page is not restricted. Restrict the page first.', - ); - } - - let validUsers = []; - let validGroups = []; - - if (dto.userIds && dto.userIds.length > 0) { - validUsers = await this.db - .selectFrom('users') - .select(['id']) - .where('id', 'in', dto.userIds) - .where('workspaceId', '=', workspaceId) - .where(({ not, exists, selectFrom }) => - not( - exists( - selectFrom('pagePermissions') - .select('id') - .whereRef('pagePermissions.userId', '=', 'users.id') - .where('pagePermissions.pageAccessId', '=', pageAccess.id), - ), - ), - ) - .execute(); - } - - if (dto.groupIds && dto.groupIds.length > 0) { - validGroups = await this.db - .selectFrom('groups') - .select(['id']) - .where('id', 'in', dto.groupIds) - .where('workspaceId', '=', workspaceId) - .where(({ not, exists, selectFrom }) => - not( - exists( - selectFrom('pagePermissions') - .select('id') - .whereRef('pagePermissions.groupId', '=', 'groups.id') - .where('pagePermissions.pageAccessId', '=', pageAccess.id), - ), - ), - ) - .execute(); - } - - const permissionsToAdd = []; - - for (const user of validUsers) { - permissionsToAdd.push({ - pageAccessId: pageAccess.id, - userId: user.id, - role: dto.role, - addedById: authUser.id, - }); - } - - for (const group of validGroups) { - permissionsToAdd.push({ - pageAccessId: pageAccess.id, - groupId: group.id, - role: dto.role, - addedById: authUser.id, - }); - } - - if (permissionsToAdd.length > 0) { - await this.pagePermissionRepo.insertPagePermissions(permissionsToAdd); - - const notifyUserIds = validUsers.map((u) => u.id); - - if (validGroups.length > 0) { - const groupMembers = await this.db - .selectFrom('groupUsers') - .select('userId') - .where( - 'groupId', - 'in', - validGroups.map((g) => g.id), - ) - .execute(); - notifyUserIds.push(...groupMembers.map((m) => m.userId)); - } - - await this.wsTreeService.notifyPermissionGranted(page, notifyUserIds); - } - } - - async removePagePermissions( - dto: RemovePagePermissionDto, - authUser: User, - ): Promise { - const page = await this.pageRepo.findById(dto.pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - await this.validateWriteAccess(page, authUser); - - const pageAccess = await this.pagePermissionRepo.findPageAccessByPageId( - dto.pageId, - ); - if (!pageAccess) { - throw new BadRequestException('Page is not restricted'); - } - - const userIds = dto.userIds ?? []; - const groupIds = dto.groupIds ?? []; - - await executeTx(this.db, async (trx) => { - if (userIds.length > 0) { - await this.pagePermissionRepo.deletePagePermissionsByUserIds( - pageAccess.id, - userIds, - trx, - ); - } - - if (groupIds.length > 0) { - await this.pagePermissionRepo.deletePagePermissionsByGroupIds( - pageAccess.id, - groupIds, - trx, - ); - } - - const writerCount = - await this.pagePermissionRepo.countWritersByPageAccessId( - pageAccess.id, - trx, - ); - if (writerCount < 1) { - throw new BadRequestException( - 'There must be at least one user with "Can edit" permission', - ); - } - }); - } - - async updatePagePermissionRole( - dto: UpdatePagePermissionRoleDto, - authUser: User, - ): Promise { - const page = await this.pageRepo.findById(dto.pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - await this.validateWriteAccess(page, authUser); - - const pageAccess = await this.pagePermissionRepo.findPageAccessByPageId( - dto.pageId, - ); - if (!pageAccess) { - throw new BadRequestException('Page is not restricted'); - } - - if (!dto.userId && !dto.groupId) { - throw new BadRequestException('Please provide a userId or groupId'); - } - - if (dto.userId) { - const permission = - await this.pagePermissionRepo.findPagePermissionByUserId( - pageAccess.id, - dto.userId, - ); - if (!permission) { - throw new NotFoundException('Permission not found'); - } - - if (permission.role === dto.role) { - return; - } - - if (permission.role === PagePermissionRole.WRITER) { - await this.validateLastWriter(pageAccess.id); - } - - await this.pagePermissionRepo.updatePagePermissionRole( - pageAccess.id, - dto.role, - { userId: dto.userId }, - ); - } else if (dto.groupId) { - const permission = - await this.pagePermissionRepo.findPagePermissionByGroupId( - pageAccess.id, - dto.groupId, - ); - if (!permission) { - throw new NotFoundException('Permission not found'); - } - - if (permission.role === dto.role) { - return; - } - - if (permission.role === PagePermissionRole.WRITER) { - await this.validateLastWriter(pageAccess.id); - } - - await this.pagePermissionRepo.updatePagePermissionRole( - pageAccess.id, - dto.role, - { groupId: dto.groupId }, - ); - } - } - - async removePageRestriction(pageId: string, authUser: User): Promise { - const page = await this.pageRepo.findById(pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - await this.validateWriteAccess(page, authUser); - - const pageAccess = - await this.pagePermissionRepo.findPageAccessByPageId(pageId); - if (!pageAccess) { - throw new BadRequestException('Page is not restricted'); - } - - await this.pagePermissionRepo.deletePageAccess(pageId); - - await this.wsService.invalidateSpaceRestrictionCache(page.spaceId); - } - - async getPagePermissions( - pageId: string, - authUser: User, - pagination: PaginationOptions, - ): Promise> { - const page = await this.pageRepo.findById(pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - const ability = await this.spaceAbility.createForUser( - authUser, - page.spaceId, - ); - // user must be a space member - if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) { - throw new ForbiddenException(); - } - - // user must not have any restriction to view this page - const canView = await this.canViewPage(authUser.id, pageId); - if (!canView) { - throw new ForbiddenException(); - } - - const pageAccess = - await this.pagePermissionRepo.findPageAccessByPageId(pageId); - if (!pageAccess) { - return emptyCursorPaginationResult(pagination.limit); - } - - return this.pagePermissionRepo.getPagePermissionsPaginated( - pageAccess.id, - pagination, - ); - } - - /** - * Get page restriction info for the current user. - * - * Security: User must be a space member. Returns 404 for pages the user cannot view - * to avoid leaking existence of restricted pages. - * - * Performance: Uses single optimized query to get all restriction/access data. - */ - async getPageRestrictionInfo( - pageId: string, - authUser: User, - ): Promise { - const page = await this.pageRepo.findById(pageId); - if (!page) { - throw new NotFoundException('Page not found'); - } - - const ability = await this.spaceAbility.createForUser( - authUser, - page.spaceId, - ); - - if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) { - throw new ForbiddenException(); - } - - const { - hasDirectRestriction, - hasInheritedRestriction, - canAccess, - canEdit, - } = await this.pagePermissionRepo.getUserPageAccessLevel( - authUser.id, - pageId, - ); - - // Security: return 404 to avoid leaking existence of restricted pages - if (!canAccess) { - throw new NotFoundException('Permission not found'); - } - - const canManage = this.computeCanManage(ability, canEdit, canAccess); - - return { - id: page.id, - title: page.title, - hasDirectRestriction, - hasInheritedRestriction, - userAccess: { - canView: canAccess, - canEdit, - canManage, - }, - }; - } - - /** - * Compute if user can manage page permissions based on precomputed access values. - * Mirrors validateWriteAccess logic without throwing. - */ - private computeCanManage( - ability: Awaited>, - canEdit: boolean, - canView: boolean, - ): boolean { - if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) { - return false; - } - - if (canEdit) { - return true; - } - - const isSpaceAdmin = ability.can( - SpaceCaslAction.Manage, - SpaceCaslSubject.Page, - ); - - return isSpaceAdmin && canView; - } - - async validateLastWriter(pageAccessId: string): Promise { - const writerCount = - await this.pagePermissionRepo.countWritersByPageAccessId(pageAccessId); - if (writerCount <= 1) { - throw new BadRequestException( - 'There must be at least one user with "Can edit" permission', - ); - } - } - - /** - * Validate if user can manage page permissions (restrict, add/remove members, etc.) - * - * Requirements: - * 1. User must have space-level Edit permission (minimum baseline) - * 2. For restricted pages, user must have one of: - * - Page-level Writer permission on all restricted ancestors - * - Space Admin role + at least page-level Reader permission (admin elevates) - */ - async validateWriteAccess(page: Page, user: User): Promise { - const ability = await this.spaceAbility.createForUser(user, page.spaceId); - - if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) { - throw new ForbiddenException(); - } - - const { canAccess, canEdit } = await this.canEditPage(user.id, page.id); - if (!canAccess) { - throw new ForbiddenException(); - } - if (canEdit) { - return; - } - - const isSpaceAdmin = ability.can( - SpaceCaslAction.Manage, - SpaceCaslSubject.Page, - ); - if (isSpaceAdmin) { - const canView = await this.canViewPage(user.id, page.id); - if (canView) { - return; - } - } - - throw new ForbiddenException(); - } - - /** - * Check if user can view a page. - * User must have permission (reader or writer) on EVERY restricted ancestor. - * Returns true if: - * - No ancestors are restricted (defer to space permission) - * - User has permission on all restricted ancestors - */ - async canViewPage(userId: string, pageId: string): Promise { - return this.pagePermissionRepo.canUserAccessPage(userId, pageId); - } - - /** - * Check if user can edit a page based on page-level permissions. - * Returns { hasAnyRestriction, canAccess, canEdit } from the nearest restricted ancestor logic. - */ - async canEditPage( - userId: string, - pageId: string, - ): Promise<{ - hasAnyRestriction: boolean; - canAccess: boolean; - canEdit: boolean; - }> { - return this.pagePermissionRepo.canUserEditPage(userId, pageId); - } - - /** - * Check if user has writer permission on the nearest restricted ancestor. - * Used for permission management operations. - */ - async hasWritePermission(userId: string, pageId: string): Promise { - const hasRestriction = - await this.pagePermissionRepo.hasRestrictedAncestor(pageId); - - if (!hasRestriction) { - return false; // no restrictions, defer to space permissions - } - - const { canEdit } = await this.pagePermissionRepo.canUserEditPage( - userId, - pageId, - ); - return canEdit; - } - - async hasPageAccess(pageId: string): Promise { - const pageAccess = - await this.pagePermissionRepo.findPageAccessByPageId(pageId); - return !!pageAccess; - } -} diff --git a/apps/server/src/ee b/apps/server/src/ee index f6791125..028e3172 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit f6791125e3f065e882834c7717b88d411cb6a6b5 +Subproject commit 028e31724e023d230426191eb6d6ef22af350a22 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b48a96e3..3ac2b699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -685,6 +685,9 @@ importers: specifier: ^3.2.0 version: 3.2.0 devDependencies: + '@clickhouse/client': + specifier: ^1.17.0 + version: 1.17.0 '@eslint/js': specifier: ^9.20.0 version: 9.20.0 @@ -1758,6 +1761,13 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@clickhouse/client-common@1.17.0': + resolution: {integrity: sha512-MiwwgXViFAQA2YZkN4ymF1ynzG0K49KeSX9/iOcmJetWkxqSekDdpyp1GjwATWa9R215uQ+hGzJtJujeQVZZIw==} + + '@clickhouse/client@1.17.0': + resolution: {integrity: sha512-Y3DQoamKZ/Iyosoq7Lj7lqpDkQDK4R/5mI52yJs4ZLPIO+d6/CYDqTbFBIb4No3C/AlXUYE4TKhj/kXDpe6rOA==} + engines: {node: '>=16'} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -11971,6 +11981,12 @@ snapshots: '@chevrotain/utils@11.0.3': {} + '@clickhouse/client-common@1.17.0': {} + + '@clickhouse/client@1.17.0': + dependencies: + '@clickhouse/client-common': 1.17.0 + '@colors/colors@1.5.0': optional: true