diff --git a/packages/core/src/memdb.ts b/packages/core/src/memdb.ts index bead5d88b0..f689dba805 100644 --- a/packages/core/src/memdb.ts +++ b/packages/core/src/memdb.ts @@ -189,6 +189,7 @@ export abstract class MemDb extends TxProcessor implements Storage { /** * Only in model find without lookups and sorting. + * Do not clone results, so be aware modifications are not allowed. */ findAllSync(_class: Ref>, query: DocumentQuery, options?: FindOptions): FindResult { let result: WithLookup[] @@ -210,9 +211,13 @@ export abstract class MemDb extends TxProcessor implements Storage { } const total = result.length result = result.slice(0, options?.limit) - const tresult = this.hierarchy.clone(result) as WithLookup[] - const res = tresult.map((it) => this.hierarchy.updateLookupMixin(_class, it, options)) - return toFindResult(res, total) + + return toFindResult( + result.map((it) => { + return baseClass !== _class ? this.hierarchy.as(it, _class) : it + }) as WithLookup[], + total + ) } addDoc (doc: Doc): void { diff --git a/packages/core/src/proxy.ts b/packages/core/src/proxy.ts index 3f5723a51f..fb4dc07b08 100644 --- a/packages/core/src/proxy.ts +++ b/packages/core/src/proxy.ts @@ -1,3 +1,4 @@ +import { PlatformError, unknownError } from '@hcengineering/platform' import { Ref } from '.' import type { Doc, Mixin } from './classes' @@ -26,6 +27,46 @@ export function _createMixinProxy (mixin: Mixin, ancestorProxy: ProxyHandle } } +export function freeze (value: any): any { + if (value != null && typeof value === 'object') { + if (Array.isArray(value)) { + return value.map((it) => freeze(it)) + } + if (value instanceof Map) { + throw new PlatformError(unknownError('Map is not allowed in model')) + } + if (value instanceof Set) { + throw new PlatformError(unknownError('Set is not allowed in model')) + } + return new Proxy(value, _createFreezeProxy(value)) + } + return value +} +/** + * @internal + */ +export function _createFreezeProxy (doc: Doc): ProxyHandler { + return { + get (target: any, property: string, receiver: any): any { + const value = target[property] + return freeze(value) + }, + set (target, p, newValue, receiver): any { + throw new PlatformError(unknownError('Modification is not allowed')) + }, + defineProperty (target, property, attributes): any { + throw new PlatformError(unknownError('Modification is not allowed')) + }, + + deleteProperty (target, p): any { + throw new PlatformError(unknownError('Modification is not allowed')) + }, + setPrototypeOf (target, v): any { + throw new PlatformError(unknownError('Modification is not allowed')) + } + } +} + /** * @internal */ diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 783515867b..895d1b0e1a 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -111,6 +111,9 @@ export function escapeLikeForRegexp (value: string): string { */ export function toFindResult (docs: T[], total?: number, lookupMap?: Record): FindResult { const length = total ?? docs.length + if (Object.keys(lookupMap ?? {}).length === 0) { + lookupMap = undefined + } return Object.assign(docs, { total: length, lookupMap }) } diff --git a/plugins/tracker-resources/src/components/CreateIssue.svelte b/plugins/tracker-resources/src/components/CreateIssue.svelte index 98a070c0b1..cdf5179154 100644 --- a/plugins/tracker-resources/src/components/CreateIssue.svelte +++ b/plugins/tracker-resources/src/components/CreateIssue.svelte @@ -352,10 +352,14 @@ attr: client.getHierarchy().getAttribute(tracker.class.Issue, 'labels') } - $: spaceQuery.query(tracker.class.Project, { _id: _space }, (res) => { - resetDefaultAssigneeId() - currentProject = res[0] - }) + $: if (_space !== undefined) { + spaceQuery.query(tracker.class.Project, { _id: _space }, (res) => { + resetDefaultAssigneeId() + currentProject = res[0] + }) + } else { + currentProject = undefined + } const docCreateManager = DocCreateExtensionManager.create(tracker.class.Issue) @@ -758,7 +762,7 @@ label={tracker.string.Project} bind:space={_space} on:object={(evt) => { - currentProject = evt.detail + currentProject = evt.detail ?? undefined }} kind={'regular'} size={'small'} diff --git a/pods/server/Dockerfile b/pods/server/Dockerfile index cf070ffebe..820b517f80 100644 --- a/pods/server/Dockerfile +++ b/pods/server/Dockerfile @@ -1,7 +1,7 @@ FROM node:20 WORKDIR /usr/src/app -RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm +RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy msgpackr msgpackr-extract --unsafe-perm RUN npm install --ignore-scripts=false --verbose uNetworking/uWebSockets.js#v20.47.0 RUN apt-get update @@ -16,4 +16,4 @@ COPY bundle/bundle.js.map ./ EXPOSE 8080 ENV UWS_HTTP_MAX_HEADERS_SIZE 32768 -CMD node --enable-source-maps --inspect bundle.js +CMD node --enable-source-maps --inspect=0.0.0.0:9229 bundle.js diff --git a/pods/server/package.json b/pods/server/package.json index a031a2a5d4..5270b2b589 100644 --- a/pods/server/package.json +++ b/pods/server/package.json @@ -15,7 +15,7 @@ "_phase:bundle": "rushx bundle", "_phase:docker-build": "rushx docker:build", "_phase:docker-staging": "rushx docker:staging", - "bundle": "mkdir -p bundle && esbuild src/__start.ts --sourcemap=inline --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:snappy --external:utf-8-validate --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external", + "bundle": "mkdir -p bundle && esbuild src/__start.ts --sourcemap=inline --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:snappy --external:utf-8-validate --external:msgpackr-extract --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external", "docker:build": "../../common/scripts/docker_build.sh hardcoreeng/transactor", "docker:tbuild": "docker build -t hardcoreeng/transactor . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor", "docker:abuild": "docker build -t hardcoreeng/transactor . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor", diff --git a/server/middleware/src/lookup.ts b/server/middleware/src/lookup.ts index 8e36ff96ae..0ee632f884 100644 --- a/server/middleware/src/lookup.ts +++ b/server/middleware/src/lookup.ts @@ -71,7 +71,8 @@ export class LookupMiddleware extends BaseMiddleware implements Middleware { for (const d of result) { if (d.$lookup !== undefined) { - const newDoc = clone(d) + const newDoc: any = { ...d } + newDoc.$lookup = clone(d.$lookup) newResult.push(newDoc) for (const [k, v] of Object.entries(d.$lookup)) { if (!Array.isArray(v)) { @@ -83,22 +84,36 @@ export class LookupMiddleware extends BaseMiddleware implements Middleware { } } const lookupMap = Object.fromEntries(Array.from(Object.values(idClassMap)).map((it) => [it.id, it.doc])) - if (Object.keys(lookupMap).length > 0) { - return toFindResult(newResult, result.total, lookupMap) - } + return this.cleanQuery(toFindResult(newResult, result.total, lookupMap), query, lookupMap) } // We need to get rid of simple query parameters matched in documents + return this.cleanQuery(result, query) + } + + private cleanQuery( + result: FindResult, + query: DocumentQuery, + lookupMap?: Record + ): FindResult { + const newResult: T[] = [] for (const doc of result) { + let _doc = doc + let cloned = false for (const [k, v] of Object.entries(query)) { if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') { - if ((doc as any)[k] === v) { + if ((_doc as any)[k] === v) { + if (!cloned) { + _doc = { ...doc } as any + cloned = true + } // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete (doc as any)[k] + delete (_doc as any)[k] } } } + newResult.push(_doc) } - return result + return toFindResult(newResult, result.total, lookupMap) } } diff --git a/server/rpc/src/rpc.ts b/server/rpc/src/rpc.ts index d4df2e8a6b..5d7cf458eb 100644 --- a/server/rpc/src/rpc.ts +++ b/server/rpc/src/rpc.ts @@ -89,7 +89,7 @@ export interface Response { } export class RPCHandler { - packr = new Packr({ structuredClone: true, bundleStrings: true, copyBuffers: true }) + packr = new Packr({ structuredClone: true, bundleStrings: true, copyBuffers: false }) protoSerialize (object: object, binary: boolean): any { if (!binary) { return JSON.stringify(object, replacer) diff --git a/services/github/pod-github/Dockerfile b/services/github/pod-github/Dockerfile index 8b585ad151..130f1903ab 100644 --- a/services/github/pod-github/Dockerfile +++ b/services/github/pod-github/Dockerfile @@ -4,8 +4,6 @@ FROM node:20 WORKDIR /usr/src/app RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm -COPY bundle/bundle.js ./ -COPY bundle/bundle.js.map ./ RUN apt-get update RUN apt-get install libjemalloc2 @@ -13,5 +11,8 @@ RUN apt-get install libjemalloc2 ENV LD_PRELOAD=libjemalloc.so.2 ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true +COPY bundle/bundle.js ./ +COPY bundle/bundle.js.map ./ + EXPOSE 3078 CMD [ "node", "--inspect", "--async-stack-traces", "--enable-source-maps", "bundle.js" ] diff --git a/services/print/pod-print/Dockerfile b/services/print/pod-print/Dockerfile index 93605bb5d4..bb3c29b456 100644 --- a/services/print/pod-print/Dockerfile +++ b/services/print/pod-print/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get update && \ WORKDIR /usr/src/app -COPY bundle/bundle.js ./ RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm +COPY bundle/bundle.js ./ CMD [ "dumb-init", "node", "bundle.js" ] diff --git a/tests/sanity/tests/model/tracker/issues-page.ts b/tests/sanity/tests/model/tracker/issues-page.ts index ebc485a6de..8e4bc9f758 100644 --- a/tests/sanity/tests/model/tracker/issues-page.ts +++ b/tests/sanity/tests/model/tracker/issues-page.ts @@ -1,10 +1,11 @@ import { expect, type Locator } from '@playwright/test' import path from 'path' +import { createIssue, toTime } from '../../tracker/tracker.utils' import { attachScreenshot, iterateLocator } from '../../utils' import { CommonTrackerPage } from './common-tracker-page' import { NewIssue } from './types' -import { createIssue, toTime } from '../../tracker/tracker.utils' +const retryOptions = { intervals: [1000, 1500, 2500], timeout: 60000 } export class IssuesPage extends CommonTrackerPage { modelSelectorAll = (): Locator => this.page.locator('label[data-id="tab-all"]') issues = (): Locator => this.page.locator('text="Issues"') @@ -492,15 +493,14 @@ export class IssuesPage extends CommonTrackerPage { } async searchIssueByName (issueName: string): Promise { - for (let i = 0; i < 5; i++) { + await expect(async () => { await this.inputSearchIcon().click() await this.inputSearch().fill(issueName) const v = await this.inputSearch().inputValue() if (v === issueName) { await this.inputSearch().press('Enter') - break } - } + }).toPass(retryOptions) } async openIssueByName (issueName: string): Promise { @@ -557,9 +557,9 @@ export class IssuesPage extends CommonTrackerPage { } async checkIssuesCount (issueName: string, count: number, timeout?: number): Promise { - await expect(this.issueAnchorByName(issueName)).toHaveCount(count, { - timeout: timeout !== undefined ? timeout * 1000 : undefined - }) + await expect(async () => { + await expect(this.issueAnchorByName(issueName)).toHaveCount(count) + }).toPass(retryOptions) } async selectTemplate (templateName: string): Promise { @@ -573,7 +573,9 @@ export class IssuesPage extends CommonTrackerPage { } async checkAttachmentsCount (issueName: string, count: string): Promise { - await expect(this.attachmentContentButton(issueName)).toHaveText(count) + await expect(async () => { + await expect(this.attachmentContentButton(issueName)).toHaveText(count) + }).toPass(retryOptions) } async addAttachmentToIssue (issueName: string, filePath: string): Promise {