mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-09 19:27:44 +02:00
Sync with foundation
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -26,16 +26,16 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "~7.0.3",
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild": "^0.25.10",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -42,6 +42,7 @@ import type {
|
||||
LoginInfo,
|
||||
LoginInfoByToken,
|
||||
LoginInfoRequestData,
|
||||
InviteInfo,
|
||||
LoginInfoWithWorkspaces,
|
||||
MailboxInfo,
|
||||
MailboxOptions,
|
||||
@@ -124,7 +125,9 @@ export interface AccountClient {
|
||||
}
|
||||
) => Promise<string>
|
||||
checkJoin: (inviteId: string) => Promise<WorkspaceLoginInfo>
|
||||
joinByToken: (inviteId: string) => Promise<WorkspaceLoginInfo>
|
||||
checkAutoJoin: (inviteId: string, firstName?: string, lastName?: string) => Promise<WorkspaceLoginInfo>
|
||||
getInviteInfo: (inviteId: string) => Promise<InviteInfo>
|
||||
getWorkspaceInfo: (updateLastVisit?: boolean) => Promise<WorkspaceInfoWithStatus>
|
||||
getWorkspacesInfo: (workspaces: WorkspaceUuid[]) => Promise<WorkspaceInfoWithStatus[]>
|
||||
updateLastVisit: (workspaces: WorkspaceUuid[]) => Promise<void>
|
||||
@@ -577,6 +580,15 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async joinByToken (inviteId: string): Promise<WorkspaceLoginInfo> {
|
||||
const request = {
|
||||
method: 'joinByToken' as const,
|
||||
params: { inviteId }
|
||||
}
|
||||
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async checkAutoJoin (inviteId: string, firstName?: string, lastName?: string): Promise<WorkspaceLoginInfo> {
|
||||
const request = {
|
||||
method: 'checkAutoJoin' as const,
|
||||
@@ -586,6 +598,15 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getInviteInfo (inviteId: string): Promise<InviteInfo> {
|
||||
const request = {
|
||||
method: 'getInviteInfo' as const,
|
||||
params: { inviteId }
|
||||
}
|
||||
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getWorkspacesInfo (ids: WorkspaceUuid[]): Promise<WorkspaceInfoWithStatus[]> {
|
||||
const request = {
|
||||
method: 'getWorkspacesInfo' as const,
|
||||
|
||||
@@ -80,6 +80,11 @@ export interface WorkspaceInviteInfo {
|
||||
name?: string
|
||||
}
|
||||
|
||||
/** Public invite details from getInviteInfo (no auth required). */
|
||||
export interface InviteInfo {
|
||||
workspaceName: string | null
|
||||
}
|
||||
|
||||
export interface OtpInfo {
|
||||
sent: boolean
|
||||
retryOn: Timestamp
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -40,12 +40,12 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.8.0",
|
||||
"@types/node": "^22.15.29",
|
||||
"ts-node": "^10.9.2",
|
||||
"@types/node": "^22.18.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@types/snappyjs": "^0.7.1",
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/snappyjs": "^0.7.1",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"ws": "^8.18.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "~7.0.3",
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild": "^0.25.10",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Umožňuje uživatelům archivovat prostor",
|
||||
"AutoJoin": "Automatické připojení",
|
||||
"AutoJoinDescr": "Automaticky připojit nové zaměstnance k tomuto prostoru",
|
||||
"RBAC": "Řízení přístupu na základě rolí",
|
||||
"RBACDescr": "Vyžadovat řízení přístupu na základě rolí pro provádění akcí v tomto prostoru",
|
||||
"BlobSize": "Velikost",
|
||||
"BlobContentType": "Typ obsahu",
|
||||
"Relation": "Vztah",
|
||||
"Relations": "Vztahy",
|
||||
"AddRelation": "Přidat vztah",
|
||||
"PersonId": "Osoba",
|
||||
"AccountId": "Účet"
|
||||
"AccountId": "Účet",
|
||||
"Version": "Verze"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Gewährt Benutzern die Möglichkeit, den Arbeitsbereich zu archivieren",
|
||||
"AutoJoin": "Automatisch beitreten",
|
||||
"AutoJoinDescr": "Neue Mitarbeiter automatisch diesem Arbeitsbereich hinzufügen",
|
||||
"RBAC": "Rollenbasierte Zugriffskontrolle",
|
||||
"RBACDescr": "Erfordert rollenbasierten Zugriff, um Aktionen in diesem Arbeitsbereich auszuführen",
|
||||
"BlobSize": "Größe",
|
||||
"BlobContentType": "Inhaltstyp",
|
||||
"Relation": "Beziehung",
|
||||
"Relations": "Beziehungen",
|
||||
"AddRelation": "Beziehung hinzufügen",
|
||||
"PersonId": "Person",
|
||||
"AccountId": "Konto"
|
||||
"AccountId": "Konto",
|
||||
"Version": "Version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Grants users ability to archive the space",
|
||||
"AutoJoin": "Auto join",
|
||||
"AutoJoinDescr": "Automatically join new employees to this space",
|
||||
"RBAC": "Role-based access control",
|
||||
"RBACDescr": "Require role-based access to perform actions in this space",
|
||||
"BlobSize": "Size",
|
||||
"BlobContentType": "Content type",
|
||||
"Relation": "Relation",
|
||||
"Relations": "Relations",
|
||||
"AddRelation": "Add relation",
|
||||
"PersonId": "Person",
|
||||
"AccountId": "Account"
|
||||
"AccountId": "Account",
|
||||
"Version": "Version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,15 @@
|
||||
"ArchiveSpaceDescription": "Concede a los usuarios la capacidad de archivar el espacio",
|
||||
"AutoJoin": "Auto unirse",
|
||||
"AutoJoinDescr": "Unirse automáticamente a los nuevos empleados a este espacio",
|
||||
"RBAC": "Control de acceso basado en roles",
|
||||
"RBACDescr": "Requerir acceso basado en roles para realizar acciones en este espacio",
|
||||
"BlobSize": "Tamaño",
|
||||
"BlobContentType": "Tipo de contenido",
|
||||
"Relation": "Relación",
|
||||
"Relations": "Relaciones",
|
||||
"AddRelation": "Añadir relación",
|
||||
"PersonId": "Id. de persona",
|
||||
"AccountId": "Cuenta"
|
||||
"AccountId": "Cuenta",
|
||||
"Version": "Versión"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Accorde aux utilisateurs la capacité d'archiver l'espace",
|
||||
"AutoJoin": "Rejoindre automatiquement",
|
||||
"AutoJoinDescr": "Ajouter automatiquement les nouveaux employés à cet espace",
|
||||
"RBAC": "Contrôle d'accès basé sur les rôles",
|
||||
"RBACDescr": "Exiger un accès basé sur les rôles pour effectuer des actions dans cet espace",
|
||||
"BlobSize": "Taille",
|
||||
"BlobContentType": "Type de contenu",
|
||||
"Relation": "Relation",
|
||||
"Relations": "Relations",
|
||||
"AddRelation": "Ajouter une relation",
|
||||
"PersonId": "Id de personne",
|
||||
"AccountId": "Compte"
|
||||
"AccountId": "Compte",
|
||||
"Version": "Version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Concede agli utenti la possibilità di archiviare lo spazio",
|
||||
"AutoJoin": "Partecipazione automatica",
|
||||
"AutoJoinDescr": "Aggiungi automaticamente i nuovi dipendenti a questo spazio",
|
||||
"RBAC": "Controllo accessi basato sui ruoli",
|
||||
"RBACDescr": "Richiedi accesso basato sui ruoli per eseguire azioni in questo spazio",
|
||||
"BlobSize": "Dimensione",
|
||||
"BlobContentType": "Tipo di contenuto",
|
||||
"Relation": "Relazione",
|
||||
"Relations": "Relazioni",
|
||||
"AddRelation": "Aggiungi relazione",
|
||||
"PersonId": "ID persona",
|
||||
"AccountId": "ID account"
|
||||
"AccountId": "ID account",
|
||||
"Version": "Versione"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "ユーザーにスペースをアーカイブする権限を付与します",
|
||||
"AutoJoin": "自動参加",
|
||||
"AutoJoinDescr": "新しいユーザーを自動的にこのスペースに参加させます",
|
||||
"RBAC": "ロールベースアクセス制御",
|
||||
"RBACDescr": "このスペースでの操作を実行するためにロールベースのアクセスを要求します",
|
||||
"BlobSize": "サイズ",
|
||||
"BlobContentType": "コンテンツタイプ",
|
||||
"Relation": "関係",
|
||||
"Relations": "関係一覧",
|
||||
"AddRelation": "関係を追加",
|
||||
"PersonId": "人物",
|
||||
"AccountId": "アカウント"
|
||||
"AccountId": "アカウント",
|
||||
"Version": "バージョン"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"string": {
|
||||
"Id": "Id",
|
||||
"Space": "Espaço",
|
||||
"Spaces": "Espaços",
|
||||
"SpacesDescription": "Gerenciamento do tipo de espaço para todos os espaços",
|
||||
"Modified": "Modificado",
|
||||
"ModifiedDate": "Data de modificação",
|
||||
"ModifiedBy": "Modificado por",
|
||||
"Class": "Classe",
|
||||
"AttachedTo": "Anexado à",
|
||||
"AttachedToClass": "Classe anexada",
|
||||
"Name": "Nome",
|
||||
"Description": "Descrição",
|
||||
"Private": "Privado",
|
||||
"Archived": "Arquivado",
|
||||
"ClassLabel": "Tipo",
|
||||
"ClassPropertyLabel": "Legenda",
|
||||
"String": "Texto",
|
||||
"Markup": "Marcação",
|
||||
"Number": "Número",
|
||||
"Boolean": "Caixa de seleção",
|
||||
"Timestamp": "Marca de data/hora",
|
||||
"Date": "Data",
|
||||
"IntlString": "Texto internacional",
|
||||
"Ref": "Referência",
|
||||
"Collection": "Coleção",
|
||||
"Array": "Seleção múltipla",
|
||||
"Enum": "Seleção",
|
||||
"Members": "Membros",
|
||||
"Hyperlink": "URL",
|
||||
"MarkupBlobRef": "Colaborativo",
|
||||
"Object": "Objeto",
|
||||
"System": "Sistema",
|
||||
"CreatedBy": "Criado por",
|
||||
"CreatedDate": "Data de criação",
|
||||
"Status": "Estado",
|
||||
"StatusCategory": "Categoria do estado",
|
||||
"Account": "Conta",
|
||||
"Rank": "Ranking",
|
||||
"Owners": "Proprietários",
|
||||
"Permission": "Permissão",
|
||||
"CreateObject": "Criar objeto",
|
||||
"UpdateObject": "Atualizar objeto",
|
||||
"DeleteObject": "Apagar objeto",
|
||||
"ForbidDeleteObject": "Proibir apagar objeto",
|
||||
"UpdateSpace": "Atualizar espaço",
|
||||
"ArchiveSpace": "Arquivar espaço",
|
||||
"CreateObjectDescription": "Concede aos usuários a capacidade de criar objetos no espaço",
|
||||
"UpdateObjectDescription": "Concede aos usuários a capacidade de atualizar objetos no espaço",
|
||||
"DeleteObjectDescription": "Concede aos usuários a capacidade de apagar objetos no espaço",
|
||||
"ForbidDeleteObjectDescription": "Proíbe aos usuários a capacidade de apagar objetos no espaço",
|
||||
"UpdateSpaceDescription": "Concede aos usuários a capacidade de atualizar o espaço",
|
||||
"ArchiveSpaceDescription": "Concede aos usuários a capacidade de arquivar o espaço",
|
||||
"AutoJoin": "Auto adesão",
|
||||
"AutoJoinDescr": "Adesão automática de novos funcionários a este espaço",
|
||||
"RBAC": "Controle de acesso baseado em funções",
|
||||
"RBACDescr": "Exigir acesso baseado em funções para executar ações neste espaço",
|
||||
"BlobSize": "Tamanho",
|
||||
"BlobContentType": "Tipo de conteúdo",
|
||||
"Relation": "Relação",
|
||||
"Relations": "Relações",
|
||||
"AddRelation": "Adicionar relação",
|
||||
"PersonId": "ID de pessoa",
|
||||
"AccountId": "Conta",
|
||||
"Version": "Versão"
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,15 @@
|
||||
"ArchiveSpaceDescription": "Concede aos usuários a capacidade de arquivar o espaço",
|
||||
"AutoJoin": "Auto adesão",
|
||||
"AutoJoinDescr": "Adesão automática de novos funcionários a este espaço",
|
||||
"RBAC": "Controle de acesso baseado em funções",
|
||||
"RBACDescr": "Exigir acesso baseado em funções para executar ações neste espaço",
|
||||
"BlobSize": "Tamanho",
|
||||
"BlobContentType": "Tipo de conteúdo",
|
||||
"Relation": "Relação",
|
||||
"Relations": "Relações",
|
||||
"AddRelation": "Adicionar relação",
|
||||
"PersonId": "ID de pessoa",
|
||||
"AccountId": "Conta"
|
||||
"AccountId": "Conta",
|
||||
"Version": "Versão"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Дает пользователям разрешение архивировать пространство",
|
||||
"AutoJoin": "Автоприсоединение",
|
||||
"AutoJoinDescr": "Автоматически присоединять новых сотрудников к этому пространству",
|
||||
"RBAC": "Ролевой доступ",
|
||||
"RBACDescr": "Требовать ролевой доступ для выполнения действий в этом пространстве",
|
||||
"BlobSize": "Размер",
|
||||
"BlobContentType": "Тип контента",
|
||||
"Relation": "Связь",
|
||||
"Relations": "Связи",
|
||||
"AddRelation": "Добавить связь",
|
||||
"PersonId": "Персона",
|
||||
"AccountId": "Аккаунт"
|
||||
"AccountId": "Аккаунт",
|
||||
"Version": "Версия"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "Kullanıcılara alanı arşivleme yetkisi verir",
|
||||
"AutoJoin": "Otomatik katıl",
|
||||
"AutoJoinDescr": "Yeni çalışanları bu alana otomatik olarak ekle",
|
||||
"RBAC": "Rol tabanlı erişim kontrolü",
|
||||
"RBACDescr": "Bu alanda işlemleri gerçekleştirmek için rol tabanlı erişim gerektir",
|
||||
"BlobSize": "Boyut",
|
||||
"BlobContentType": "İçerik türü",
|
||||
"Relation": "İlişki",
|
||||
"Relations": "İlişkiler",
|
||||
"AddRelation": "İlişki ekle",
|
||||
"PersonId": "Kişi",
|
||||
"AccountId": "Hesap"
|
||||
"AccountId": "Hesap",
|
||||
"Version": "Sürüm"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,15 @@
|
||||
"ArchiveSpaceDescription": "授予用户归档空间的权限",
|
||||
"AutoJoin": "自动加入",
|
||||
"AutoJoinDescr": "自动将新员工加入此空间",
|
||||
"RBAC": "基于角色的访问控制",
|
||||
"RBACDescr": "要求基于角色的访问权限以在此空间中执行操作",
|
||||
"BlobSize": "大小",
|
||||
"BlobContentType": "內容類型",
|
||||
"Relation": "关系",
|
||||
"Relations": "关系",
|
||||
"AddRelation": "添加关系",
|
||||
"PersonId": "人员 ID",
|
||||
"AccountId": "帐户"
|
||||
"AccountId": "帐户",
|
||||
"Version": "版本"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import { type Client, type DomainParams, type DomainRequestOptions, type DomainResult } from '..'
|
||||
import type { Class, Doc, Obj, OperationDomain, Ref } from '../classes'
|
||||
import type { Class, Doc, Obj, OperationDomain, Ref, Space } from '../classes'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { ModelDb, TxDb } from '../memdb'
|
||||
@@ -293,6 +293,155 @@ describe('memdb', () => {
|
||||
expect(result2).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('check associations', async () => {
|
||||
const { model } = await createModel()
|
||||
const operations = new TxOperations(model, core.account.System)
|
||||
const association = await operations.findOne(core.class.Association, {})
|
||||
if (association == null) {
|
||||
throw new Error('Association not found')
|
||||
}
|
||||
|
||||
const spaces = await operations.findAll(core.class.Space, {})
|
||||
expect(spaces).toHaveLength(2)
|
||||
|
||||
const first = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
spaces[0]._id,
|
||||
spaces[0]._class,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg'
|
||||
}
|
||||
)
|
||||
|
||||
const second = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
first,
|
||||
test.class.TestComment,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg2'
|
||||
}
|
||||
)
|
||||
|
||||
await operations.createDoc(core.class.Relation, '' as Ref<Space>, {
|
||||
docA: first,
|
||||
docB: second,
|
||||
association: association._id
|
||||
})
|
||||
|
||||
const r = await operations.findAll(
|
||||
test.class.TestComment,
|
||||
{ _id: first },
|
||||
{
|
||||
associations: [[association._id, 1]]
|
||||
}
|
||||
)
|
||||
expect(r.length).toEqual(1)
|
||||
expect((r[0].$associations?.[association._id + '_b'][0] as any)?._id).toEqual(second)
|
||||
})
|
||||
|
||||
it('check deep associations', async () => {
|
||||
const { model } = await createModel()
|
||||
const operations = new TxOperations(model, core.account.System)
|
||||
const association = await operations.findOne(core.class.Association, {})
|
||||
if (association == null) {
|
||||
throw new Error('Association not found')
|
||||
}
|
||||
|
||||
const spaces = await operations.findAll(core.class.Space, {})
|
||||
expect(spaces).toHaveLength(2)
|
||||
|
||||
const zero = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
spaces[0]._id,
|
||||
spaces[0]._class,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg'
|
||||
}
|
||||
)
|
||||
|
||||
const first = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
spaces[0]._id,
|
||||
spaces[0]._class,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg'
|
||||
}
|
||||
)
|
||||
|
||||
const second = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
first,
|
||||
test.class.TestComment,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg2'
|
||||
}
|
||||
)
|
||||
|
||||
const second2 = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
first,
|
||||
test.class.TestComment,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg2'
|
||||
}
|
||||
)
|
||||
|
||||
const third = await operations.addCollection(
|
||||
test.class.TestComment,
|
||||
core.space.Model,
|
||||
spaces[0]._id,
|
||||
spaces[0]._class,
|
||||
'comments',
|
||||
{
|
||||
message: 'msg3'
|
||||
}
|
||||
)
|
||||
await operations.createDoc(core.class.Relation, '' as Ref<Space>, {
|
||||
docA: first,
|
||||
docB: second,
|
||||
association: association._id
|
||||
})
|
||||
|
||||
await operations.createDoc(core.class.Relation, '' as Ref<Space>, {
|
||||
docA: first,
|
||||
docB: second2,
|
||||
association: association._id
|
||||
})
|
||||
|
||||
await operations.createDoc(core.class.Relation, '' as Ref<Space>, {
|
||||
docA: second,
|
||||
docB: third,
|
||||
association: association._id
|
||||
})
|
||||
|
||||
const r = await operations.findAll(
|
||||
test.class.TestComment,
|
||||
{ _id: { $in: [zero, first] } },
|
||||
{
|
||||
associations: [[association._id, 1, [[association._id, 1]]]]
|
||||
}
|
||||
)
|
||||
expect(r.length).toEqual(2)
|
||||
expect(r[1].$associations?.[`${association._id}_b`]).toHaveLength(2)
|
||||
expect((r[1].$associations?.[`${association._id}_b`][0] as any)?._id).toEqual(second)
|
||||
expect(r[1].$associations?.[`${association._id}_b`][1]?.$associations?.[`${association._id}_b`]).toHaveLength(0)
|
||||
expect(
|
||||
(r[1].$associations?.[`${association._id}_b`][0]?.$associations?.[`${association._id}_b`][0] as any)?._id
|
||||
).toEqual(third)
|
||||
})
|
||||
|
||||
it('lookups', async () => {
|
||||
const { model } = await createModel()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import type { IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { Arr, AttachedDoc, Class, Data, Doc, Interface, Mixin, Obj, Ref, Space } from '../classes'
|
||||
import { ClassifierKind, DOMAIN_MODEL } from '../classes'
|
||||
import { ClassifierKind, DOMAIN_MODEL, DOMAIN_RELATION } from '../classes'
|
||||
import core from '../component'
|
||||
import type { DocumentUpdate, TxCUD, TxCreateDoc, TxRemoveDoc, TxUpdateDoc } from '../tx'
|
||||
import { DOMAIN_TX, TxFactory } from '../tx'
|
||||
@@ -252,6 +252,34 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
})
|
||||
)
|
||||
|
||||
txes.push(
|
||||
createClass(core.class.Relation, {
|
||||
label: 'Relation' as IntlString,
|
||||
extends: core.class.Doc,
|
||||
kind: ClassifierKind.CLASS,
|
||||
domain: DOMAIN_RELATION
|
||||
})
|
||||
)
|
||||
|
||||
txes.push(
|
||||
createClass(core.class.Association, {
|
||||
label: 'Association' as IntlString,
|
||||
extends: core.class.Doc,
|
||||
kind: ClassifierKind.CLASS,
|
||||
domain: DOMAIN_MODEL
|
||||
})
|
||||
)
|
||||
|
||||
txes.push(
|
||||
createDoc(core.class.Association, {
|
||||
nameA: 'my-assoc',
|
||||
nameB: 'my-assoc',
|
||||
classA: test.class.TestComment,
|
||||
classB: test.class.TestComment,
|
||||
type: '1:1'
|
||||
})
|
||||
)
|
||||
|
||||
txes.push(
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp1',
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
//
|
||||
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { Tx } from '.'
|
||||
import type { DocumentQuery } from './storage'
|
||||
import { type WorkspaceDataId, type WorkspaceUuid } from './utils'
|
||||
import { Tx } from '.'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -490,6 +490,7 @@ export interface SystemSpace extends Space {}
|
||||
*/
|
||||
export interface TypedSpace extends Space {
|
||||
type: Ref<SpaceType>
|
||||
restricted?: boolean // if true user must have permission to any txes
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,6 +552,14 @@ export interface Permission extends Doc {
|
||||
icon?: Asset
|
||||
}
|
||||
|
||||
export interface AttributePermission extends Permission {
|
||||
attribute: Ref<AnyAttribute>
|
||||
}
|
||||
|
||||
export interface ClassPermission extends Permission {
|
||||
targetClass: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -935,8 +944,10 @@ export type SocialKey = Pick<SocialId, 'type' | 'value'>
|
||||
|
||||
export interface ClassCollaborators<T extends Doc> extends Doc {
|
||||
attachedTo: Ref<Class<T>>
|
||||
allFields?: boolean // for all (PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]) attributes
|
||||
fields: (keyof T)[] // PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]
|
||||
provideSecurity?: boolean // If true, will provide security for collaborators
|
||||
provideAttachedSecurity?: boolean // If true, will provide security for collaborators of attached doc
|
||||
}
|
||||
|
||||
export interface Collaborator extends AttachedDoc {
|
||||
|
||||
@@ -15,18 +15,23 @@
|
||||
import type { Asset, IntlString, Metadata, Plugin, StatusCode } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { BenchmarkDoc } from './benchmark'
|
||||
import { AccountRole, TxAccessLevel } from './classes'
|
||||
import type {
|
||||
Account,
|
||||
AccountUuid,
|
||||
AnyAttribute,
|
||||
ArrOf,
|
||||
Association,
|
||||
AttachedDoc,
|
||||
AttributePermission,
|
||||
Blob,
|
||||
Class,
|
||||
ClassCollaborators,
|
||||
ClassPermission,
|
||||
Collaborator,
|
||||
Collection,
|
||||
Configuration,
|
||||
ConfigurationElement,
|
||||
CustomSequence,
|
||||
Doc,
|
||||
DomainIndexConfiguration,
|
||||
Enum,
|
||||
@@ -49,7 +54,6 @@ import type {
|
||||
Relation,
|
||||
Role,
|
||||
Sequence,
|
||||
CustomSequence,
|
||||
Space,
|
||||
SpaceType,
|
||||
SpaceTypeDescriptor,
|
||||
@@ -60,11 +64,9 @@ import type {
|
||||
TypeAny,
|
||||
TypedSpace,
|
||||
UserStatus,
|
||||
Version,
|
||||
AccountUuid,
|
||||
ClassCollaborators,
|
||||
Collaborator
|
||||
Version
|
||||
} from './classes'
|
||||
import { AccountRole, TxAccessLevel } from './classes'
|
||||
import { type Status, type StatusCategory } from './status'
|
||||
import type {
|
||||
Tx,
|
||||
@@ -78,6 +80,7 @@ import type {
|
||||
TxUpdateDoc,
|
||||
TxWorkspaceEvent
|
||||
} from './tx'
|
||||
import { VersionableClass } from './versioning'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -129,6 +132,8 @@ export default plugin(coreId, {
|
||||
SpaceType: '' as Ref<Class<SpaceType>>,
|
||||
Role: '' as Ref<Class<Role>>,
|
||||
Permission: '' as Ref<Class<Permission>>,
|
||||
AttributePermission: '' as Ref<Class<AttributePermission>>,
|
||||
ClassPermission: '' as Ref<Class<ClassPermission>>,
|
||||
Type: '' as Ref<Class<Type<any>>>,
|
||||
TypeRelation: '' as Ref<Class<Type<string>>>,
|
||||
TypeString: '' as Ref<Class<Type<string>>>,
|
||||
@@ -195,7 +200,8 @@ export default plugin(coreId, {
|
||||
IndexConfiguration: '' as Ref<Mixin<IndexingConfiguration<Doc>>>,
|
||||
SpacesTypeData: '' as Ref<Mixin<Space>>,
|
||||
TransientConfiguration: '' as Ref<Mixin<TransientConfiguration>>,
|
||||
TxAccessLevel: '' as Ref<Mixin<TxAccessLevel>>
|
||||
TxAccessLevel: '' as Ref<Mixin<TxAccessLevel>>,
|
||||
VersionableClass: '' as Ref<Mixin<VersionableClass>>
|
||||
},
|
||||
space: {
|
||||
Tx: '' as Ref<Space>,
|
||||
@@ -287,7 +293,10 @@ export default plugin(coreId, {
|
||||
UpdateSpaceDescription: '' as IntlString,
|
||||
ArchiveSpaceDescription: '' as IntlString,
|
||||
AutoJoin: '' as IntlString,
|
||||
AutoJoinDescr: '' as IntlString
|
||||
AutoJoinDescr: '' as IntlString,
|
||||
RBAC: '' as IntlString,
|
||||
RBACDescr: '' as IntlString,
|
||||
Version: '' as IntlString
|
||||
},
|
||||
descriptor: {
|
||||
SpacesType: '' as Ref<SpaceTypeDescriptor>
|
||||
|
||||
@@ -43,5 +43,6 @@ export * from './common'
|
||||
export * from './time'
|
||||
export * from './benchmark'
|
||||
export * from './collaborators'
|
||||
export * from './versioning'
|
||||
|
||||
export default core
|
||||
|
||||
@@ -159,7 +159,7 @@ export abstract class MemDb extends TxProcessor implements Storage {
|
||||
private async getAssociationValue<T extends Doc>(
|
||||
doc: T,
|
||||
associations: AssociationQuery[]
|
||||
): Promise<Record<string, Doc[]>> {
|
||||
): Promise<Record<string, WithLookup<Doc>[]>> {
|
||||
const result: Record<string, Doc[]> = {}
|
||||
for (const association of associations) {
|
||||
const _id = association[0]
|
||||
@@ -170,8 +170,11 @@ export abstract class MemDb extends TxProcessor implements Storage {
|
||||
const key2 = !isReverse ? 'docB' : 'docA'
|
||||
const _class = !isReverse ? assoc.classB : assoc.classA
|
||||
const relations = await this.findAll(core.class.Relation, { association: _id, [key]: doc._id })
|
||||
const objects = await this.findAll(_class, { _id: { $in: relations.map((r) => r[key2]) } })
|
||||
result[_id] = objects
|
||||
let objects = await this.findAll(_class, { _id: { $in: relations.map((r) => r[key2]) } })
|
||||
if (association[2] !== undefined) {
|
||||
objects = toFindResult(await this.fillAssociations(objects, association[2]), objects.length)
|
||||
}
|
||||
result[`${_id}_${!isReverse ? 'b' : 'a'}`] = objects
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -246,6 +249,7 @@ export abstract class MemDb extends TxProcessor implements Storage {
|
||||
// We need to filter instances without mixin was set
|
||||
result = result.filter((r) => (r as any)[_class] !== undefined)
|
||||
}
|
||||
if (options?.sort !== undefined) resultSort(result, options?.sort, _class, this.hierarchy, this)
|
||||
const total = result.length
|
||||
result = result.slice(0, options?.limit)
|
||||
|
||||
|
||||
@@ -531,6 +531,17 @@ export class ApplyOperations extends TxOperations {
|
||||
if (typeof window === 'object' && window !== null && this.measureName != null) {
|
||||
console.log(`measure ${this.measureName}`, dnow - st, 'server time', result.serverTime)
|
||||
}
|
||||
if (!result.success) {
|
||||
console.warn('ops.commit() failed', {
|
||||
scope: this.scope,
|
||||
measureName: this.measureName,
|
||||
matchCount: this.matches.length,
|
||||
notMatchCount: this.notMatches.length,
|
||||
txCount: this.txes.length,
|
||||
matches: this.matches.map((m) => ({ _class: m._class, query: m.query })),
|
||||
notMatches: this.notMatches.map((m) => ({ _class: m._class, query: m.query }))
|
||||
})
|
||||
}
|
||||
this.txes = []
|
||||
return {
|
||||
result: result.success,
|
||||
|
||||
@@ -114,6 +114,9 @@ const predicates: Record<string, PredicateFactory> = {
|
||||
$size: (o, propertyKey) => {
|
||||
return (docs) =>
|
||||
execPredicate(docs, propertyKey, (value) => {
|
||||
if (value == null) {
|
||||
return false
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error('$size predicate requires array')
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { Asset, Resource } from '@hcengineering/platform'
|
||||
import type { Association, AttachedDoc, Class, Doc, Domain, Ref, Space } from './classes'
|
||||
import type { Tx } from './tx'
|
||||
import type { KeysByType } from './utils'
|
||||
import { VersionableDoc } from './versioning'
|
||||
|
||||
export type ArraySizeSelector =
|
||||
| {
|
||||
@@ -128,7 +129,7 @@ export type Projection<T extends Doc> = {
|
||||
[P in keyof T]?: 0 | 1
|
||||
}
|
||||
|
||||
export type AssociationQuery = [Ref<Association>, 1 | -1]
|
||||
export type AssociationQuery = [Ref<Association>, 1 | -1] | [Ref<Association>, 1 | -1, AssociationQuery[]]
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -205,7 +206,7 @@ export type LookupData<T extends Doc> = Partial<RefsAsDocs<T>>
|
||||
*/
|
||||
export type WithLookup<T extends Doc> = T & {
|
||||
$lookup?: LookupData<T>
|
||||
$associations?: Record<string, Doc[]>
|
||||
$associations?: Record<string, WithLookup<Doc>[]>
|
||||
$source?: {
|
||||
$score: number // Score for document result
|
||||
[key: string]: any
|
||||
@@ -269,7 +270,9 @@ export interface SearchResultDoc {
|
||||
description?: string
|
||||
emojiIcon?: string
|
||||
score?: number
|
||||
doc: Pick<Doc, '_id' | '_class' | 'createdOn'> & Partial<Pick<AttachedDoc, 'attachedTo' | 'attachedToClass'>>
|
||||
doc: Pick<Doc, '_id' | '_class' | 'createdOn'> &
|
||||
Partial<Pick<AttachedDoc, 'attachedTo' | 'attachedToClass'>> &
|
||||
Partial<Pick<VersionableDoc, 'baseId'>>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -211,7 +211,7 @@ export function fillDefaults<T extends Doc> (
|
||||
const baseClass = hierarchy.isDerived(_class, core.class.AttachedDoc) ? core.class.AttachedDoc : core.class.Doc
|
||||
const attributes = hierarchy.getAllAttributes(_class, baseClass)
|
||||
for (const attribute of attributes) {
|
||||
if (attribute[1].defaultValue !== undefined) {
|
||||
if (attribute[1].defaultValue != null) {
|
||||
if ((object as any)[attribute[0]] === undefined) {
|
||||
// Clone default value as it might be an object (e.g. array)
|
||||
;(object as any)[attribute[0]] = structuredClone(attribute[1].defaultValue)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Class, Doc, PersonId, Ref } from './classes'
|
||||
|
||||
export interface VersionableDoc extends Doc {
|
||||
baseId?: Ref<Doc>
|
||||
version?: number
|
||||
isLatest?: boolean
|
||||
docCreatedBy?: PersonId
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
export interface VersionableClass extends Class<Doc> {
|
||||
enabled: boolean
|
||||
}
|
||||
@@ -26,18 +26,18 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild": "^0.25.10",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.9.3",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('performance', () => {
|
||||
|
||||
// Overhead should be reasonable (typically < 50% for simple operations)
|
||||
// This is informational rather than a strict assertion
|
||||
expect(overheadPercentage).toBeLessThan(200)
|
||||
expect(overheadPercentage).toBeLessThan(400)
|
||||
})
|
||||
|
||||
it('should measure overhead with deep nested contexts', async () => {
|
||||
@@ -154,7 +154,7 @@ describe('performance', () => {
|
||||
console.log(` Overhead: ${overhead.toFixed(2)}ms (${overheadPercentage.toFixed(2)}%)`)
|
||||
|
||||
// NoMetricsContext should have very low overhead
|
||||
expect(overheadPercentage).toBeLessThan(50)
|
||||
expect(overheadPercentage).toBeLessThan(100)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -195,7 +195,7 @@ describe('performance', () => {
|
||||
console.log(` Collected operations: ${aggregated.measurements.request?.operations ?? 0}`)
|
||||
|
||||
expect(aggregated.measurements.request).toBeDefined()
|
||||
expect(overheadPercentage).toBeLessThan(100) // Should be less than 100% overhead
|
||||
expect(overheadPercentage).toBeLessThan(200) // Should be less than 100% overhead
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
"@types/toposort": "^2.0.3",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"status": {
|
||||
"LoadingPlugin": "Carregando plugin {plugin}...",
|
||||
"UnknownError": "Erro desconhecido: {message}",
|
||||
"InvalidId": "Id inválido: {id}",
|
||||
"BadRequest": "Pedido inválido",
|
||||
"Forbidden": "Proibido",
|
||||
"Conflict": "Conflito",
|
||||
"ExpiredLink": "Este link de convite expirou",
|
||||
"Unauthorized": "Não autorizado",
|
||||
"UnknownMethod": "Método desconhecido: {method}",
|
||||
"InternalServerError": "Erro interno do servidor",
|
||||
"MaintenanceWarning": "Manutenção programada dentro de",
|
||||
"MaintenanceWarningTime": "{time, plural, =1 {menos de um minuto} other {# minutos}}",
|
||||
"AccountNotFound": "Conta não encontrada",
|
||||
"AccountNotConfirmed": "Conta não confirmada",
|
||||
"WorkspaceNotFound": "Espaço de trabalho não encontrado",
|
||||
"InvalidPassword": "Senha inválida",
|
||||
"AccountAlreadyExists": "Conta já existe",
|
||||
"WorkspaceRateLimit": "O servidor está ocupado. Por favor, espere um pouco e tente novamente",
|
||||
"AccountAlreadyConfirmed": "Conta já confirmada",
|
||||
"WorkspaceAlreadyExists": "Espaço de trabalho já existe",
|
||||
"InvalidOtp": "Código inválido",
|
||||
"PasswordLoginLocked": "O login com senha está bloqueado devido a muitas tentativas incorretas. Use um método de login OTP para desbloquear sua conta.",
|
||||
"InviteNotFound": "Convite com e-mail:{email} não encontrado.",
|
||||
"WorkspaceLimitReached": "Você atingiu o limite de espaços de trabalho. Entre em contato conosco...",
|
||||
"ReadOnlyAccount": "Demonstração anônima de convidado",
|
||||
"SystemAccount": "Conta do sistema",
|
||||
"SocialIdAlreadyExists": "ID social já existe"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.9.3",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.54.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
+97
-18
@@ -16,6 +16,7 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
Association,
|
||||
AssociationQuery,
|
||||
BulkUpdateEvent,
|
||||
Class,
|
||||
Client,
|
||||
@@ -780,7 +781,9 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
const docs = q.result.getDocs()
|
||||
for (const doc of docs) {
|
||||
const docToUpdate = doc.$associations?.[association._id]?.find((it) => it._id === tx.objectId)
|
||||
const docToUpdate =
|
||||
doc.$associations?.[`${association._id}_a`]?.find((it) => it._id === tx.objectId) ??
|
||||
doc.$associations?.[`${association._id}_b`]?.find((it) => it._id === tx.objectId)
|
||||
if (docToUpdate !== undefined) {
|
||||
if (tx._class === core.class.TxMixin) {
|
||||
TxProcessor.updateMixin4Doc(docToUpdate, tx as TxMixin<Doc, Doc>)
|
||||
@@ -1114,31 +1117,90 @@ export class LiveQuery implements WithTx, Client {
|
||||
if (q.options?.associations === undefined) return
|
||||
if (doc._class !== core.class.Relation) return
|
||||
const relation = doc as Relation
|
||||
const assoc = q.options.associations.find((p) => p[0] === relation.association)
|
||||
const assoc = findAssociation(q.options.associations, relation.association)
|
||||
if (assoc !== undefined) {
|
||||
if (q.result instanceof Promise) {
|
||||
q.result = await q.result
|
||||
}
|
||||
const direct = assoc[1] === 1
|
||||
const res = q.result.findDoc(direct ? relation.docA : relation.docB)
|
||||
if (res === undefined) return
|
||||
const association = this.getModel().findObject(assoc[0])
|
||||
if (association === undefined) return
|
||||
const docToPush = await this.findOne(direct ? association.classB : association.classA, {
|
||||
_id: direct ? relation.docB : relation.docA
|
||||
})
|
||||
if (docToPush === undefined) return
|
||||
const arr = res?.$associations?.[relation.association] ?? []
|
||||
arr.push(docToPush)
|
||||
if (res?.$associations === undefined) {
|
||||
res.$associations = {}
|
||||
const res = await this.fillRelationDoc(q.result, q.result.getDocs(), q.options.associations, relation)
|
||||
if (res) {
|
||||
this.queriesToUpdate.set(q.id, q)
|
||||
}
|
||||
res.$associations[relation.association] = arr
|
||||
q.result.updateDoc(res, false)
|
||||
this.queriesToUpdate.set(q.id, q)
|
||||
}
|
||||
}
|
||||
|
||||
async fillRelationDoc (
|
||||
qRes: ResultArray,
|
||||
docs: WithLookup<Doc>[],
|
||||
associations: AssociationQuery[] | undefined,
|
||||
relation: Relation
|
||||
): Promise<boolean> {
|
||||
if (associations === undefined) return false
|
||||
let result = false
|
||||
for (const assoc of associations) {
|
||||
if (assoc[0] === relation.association) {
|
||||
const association = this.getModel().findObject(assoc[0])
|
||||
if (association === undefined) continue
|
||||
const sourceId = assoc[1] === 1 ? relation.docA : relation.docB
|
||||
const targetId = assoc[1] === 1 ? relation.docB : relation.docA
|
||||
const direct = assoc[1] === 1
|
||||
for (const doc of docs) {
|
||||
if (doc._id === sourceId) {
|
||||
const nestedAssoc = assoc[2]
|
||||
const options: FindOptions<Doc> | undefined =
|
||||
nestedAssoc !== undefined
|
||||
? {
|
||||
associations: nestedAssoc
|
||||
}
|
||||
: undefined
|
||||
const docToPush = await this.findOne(
|
||||
direct ? association.classB : association.classA,
|
||||
{
|
||||
_id: targetId
|
||||
},
|
||||
options
|
||||
)
|
||||
if (docToPush === undefined) continue
|
||||
if (doc.$associations === undefined) {
|
||||
doc.$associations = {}
|
||||
}
|
||||
const key = direct ? 'b' : 'a'
|
||||
const arr = doc.$associations?.[`${assoc[0]}_${key}`] ?? []
|
||||
const exists = arr.findIndex((p) => p._id === docToPush._id)
|
||||
if (exists !== -1) {
|
||||
arr[exists] = docToPush
|
||||
} else {
|
||||
arr.push(docToPush)
|
||||
}
|
||||
doc.$associations[`${assoc[0]}_${key}`] = arr
|
||||
if (qRes.findDoc(doc._id) !== undefined) {
|
||||
qRes.updateDoc(doc, false)
|
||||
}
|
||||
result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const assoc of associations) {
|
||||
if (assoc[2] !== undefined) {
|
||||
const key = assoc[1] === 1 ? 'b' : 'a'
|
||||
for (const doc of docs) {
|
||||
const arr = doc.$associations?.[`${assoc[0]}_${key}`] ?? []
|
||||
if (arr.length > 0) {
|
||||
const res = await this.fillRelationDoc(qRes, arr, assoc[2], relation)
|
||||
if (res) {
|
||||
result = true
|
||||
if (qRes.findDoc(doc._id) !== undefined) {
|
||||
qRes.updateDoc(doc, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private async handleDocAddLookup (q: Query, doc: Doc): Promise<void> {
|
||||
if (q.options?.lookup === undefined) return
|
||||
const lookup = q.options.lookup
|
||||
@@ -1608,3 +1670,20 @@ function getNestedLookup (lookup: Ref<Class<Doc>> | [Ref<Class<Doc>>, Lookup<Doc
|
||||
function getLookupClass (lookup: Ref<Class<Doc>> | [Ref<Class<Doc>>, Lookup<Doc>]): Ref<Class<Doc>> {
|
||||
return Array.isArray(lookup) ? lookup[0] : lookup
|
||||
}
|
||||
|
||||
function findAssociation (
|
||||
associations: AssociationQuery[],
|
||||
association: Ref<Association>
|
||||
): AssociationQuery | undefined {
|
||||
for (const assoc of associations) {
|
||||
if (assoc[0] === association) return assoc
|
||||
}
|
||||
for (const assoc of associations) {
|
||||
if (assoc[2] !== undefined) {
|
||||
const res = findAssociation(assoc[2], association)
|
||||
if (res !== undefined) {
|
||||
return assoc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "~7.0.3",
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild": "^0.25.10",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadMultipart, uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class DatalakeStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class DatalakeStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/blob/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
const url = concatLink(this.baseUrl, `/meta/${encodeURIComponent(workspace)}/${encodeURIComponent(file)}`)
|
||||
try {
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class FrontStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class FrontStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class HulylakeStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class HulylakeStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/api/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface FileStorageUploadOptions {
|
||||
/** @public */
|
||||
export interface FileStorage {
|
||||
getFileUrl: (workspace: string, file: string, filename?: string) => string
|
||||
getCookiePath: (workspace: string) => string
|
||||
getFileMeta: (token: string, workspace: string, file: string) => Promise<Record<string, any>>
|
||||
uploadFile: (
|
||||
token: string,
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/markdown-it": "~13.0.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"fast-equals": "^5.2.2",
|
||||
"y-prosemirror": "^1.3.7",
|
||||
"eslint-plugin-svelte": "^2.35.1"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
Reference in New Issue
Block a user