From 73ed816918a58d2189e64f27ea7c64ae3f42d977 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:09:51 +0200 Subject: [PATCH 01/54] =?UTF-8?q?MILAB-6720:=20structure=20refresh=20?= =?UTF-8?q?=E2=80=94=20SDK=201.81,=20block-tools=202.13,=20slim=20facade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated output of `pnpm run upgrade-sdk` (block-tools structure refresh). No hand edits in this commit. - SDK catalog: model/ui 1.79.14 -> 1.81.1, workflow-tengo 6.6.3 -> 6.8.2, block-tools 2.11.0 -> 2.13.0, test 1.79.14 -> 1.81.3, tengo-builder 4.0.8 -> 4.0.22, package-builder 3.13.0 -> 3.15.0, ts-builder 1.5.2 -> 1.6.2, ts-configs 1.2.3 -> 1.4.0 - block/ reshaped into the slim facade: index.d.ts/index.js replaced by src/ (index.ts, block-extra.ts, AGENTS.ts, agents-extra.ts) + tsconfig - root build scripts replaced: build/build:dev -> build:dev-local, build:dev-remote, build:dev-no-software, build:dev-binary-existing, build:release; PL_PKG_DEV -> PL_BUILD_CHANNEL/VARIANT/LOCATION - CI: build-script-name build -> build:dev-local, adds build-before-publish-script-name build:release, require-package-path-bump, hz-ubuntu-dind runner --- .github/workflows/build.yaml | 20 +- .structure | 2 +- block/index.d.ts | 6 - block/index.js | 8 - block/package.json | 37 +- block/src/AGENTS.ts | 5 + block/src/agents-extra.ts | 8 + block/src/block-extra.ts | 9 + block/src/index.ts | 55 ++ block/tsconfig.json | 4 + model/package.json | 1 + package.json | 17 +- pnpm-lock.yaml | 1448 +++++++++++++++++++++------------- pnpm-workspace.yaml | 18 +- test/.oxfmtrc.json | 2 +- turbo.json | 20 +- ui/package.json | 1 + workflow/package.json | 1 + 18 files changed, 1056 insertions(+), 606 deletions(-) delete mode 100644 block/index.d.ts delete mode 100644 block/index.js create mode 100644 block/src/AGENTS.ts create mode 100644 block/src/agents-extra.ts create mode 100644 block/src/block-extra.ts create mode 100644 block/src/index.ts create mode 100644 block/tsconfig.json diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 43e8b7f..13981ad 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -11,7 +11,7 @@ on: workflow_dispatch: {} jobs: init: - runs-on: ubuntu-latest + runs-on: hz-ubuntu-dind steps: - uses: milaboratory/github-ci/actions/context/init@v4 with: @@ -25,7 +25,9 @@ jobs: app-name: 'Block: Import Vdj' app-name-slug: 'block-import-vdj' node-version: '20.x' - build-script-name: 'build' + gha-runner-label: hz-ubuntu-dind + build-script-name: 'build:dev-local' + build-before-publish-script-name: 'build:release' pnpm-recursive-build: false test: true @@ -37,6 +39,11 @@ jobs: package-path: 'block' create-tag: 'true' + # Require the published `block` package to be bumped by a changeset on + # PRs (empty changeset or the `skip-changelog` label waives it). Needs + # the input to exist on the pinned `@v4` reusable workflow. + require-package-path-bump: true + npmrc-config: | { "registries": { @@ -56,6 +63,15 @@ jobs: "AWS_CI_IAM_MONOREPO_SIMPLE_ROLE": ${{ toJSON(secrets.AWS_CI_IAM_MONOREPO_SIMPLE_ROLE) }}, "AWS_CI_TURBOREPO_S3_BUCKET": ${{ toJSON(secrets.AWS_CI_TURBOREPO_S3_BUCKET) }}, + + "HZ_CI_TURBO_S3_BUCKET": ${{ toJSON(vars.HZ_CI_TURBO_S3_BUCKET) }}, + "HZ_CI_TURBO_S3_ENDPOINT": ${{ toJSON(vars.HZ_CI_TURBO_S3_ENDPOINT) }}, + "HZ_CI_TURBO_S3_REGION": ${{ toJSON(vars.HZ_CI_TURBO_S3_REGION) }}, + "HZ_CI_TURBO_S3_ACCESS_KEY": ${{ toJSON(secrets.HZ_CI_TURBO_S3_ACCESS_KEY) }}, + "HZ_CI_TURBO_S3_SECRET_KEY": ${{ toJSON(secrets.HZ_CI_TURBO_S3_SECRET_KEY) }}, + "HZ_CI_CACHE_S3_ACCESS_KEY": ${{ toJSON(secrets.HZ_CI_CACHE_S3_ACCESS_KEY) }}, + "HZ_CI_CACHE_S3_SECRET_KEY": ${{ toJSON(secrets.HZ_CI_CACHE_S3_SECRET_KEY) }}, + "PL_REGISTRY_PLATFORMA_OPEN_UPLOAD_URL": ${{ toJSON(secrets.PL_REGISTRY_PLOPEN_UPLOAD_URL) }}, "QUAY_USERNAME": ${{ toJSON(secrets.QUAY_USERNAME) }}, "QUAY_ROBOT_TOKEN": ${{ toJSON(secrets.QUAY_ROBOT_TOKEN) }} } diff --git a/.structure b/.structure index 491d734..218abba 100644 --- a/.structure +++ b/.structure @@ -1 +1 @@ -{"version":1} \ No newline at end of file +{"version":2} \ No newline at end of file diff --git a/block/index.d.ts b/block/index.d.ts deleted file mode 100644 index d2bd596..0000000 --- a/block/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare const blockSpec: { - type: "dev-v2"; - folder: string; -}; - -export { blockSpec }; diff --git a/block/index.js b/block/index.js deleted file mode 100644 index 469ef77..0000000 --- a/block/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const blockSpec = { - type: "dev-v2", - folder: __dirname, -}; - -module.exports = { - blockSpec, -}; diff --git a/block/package.json b/block/package.json index 1e52556..aed16a5 100644 --- a/block/package.json +++ b/block/package.json @@ -2,25 +2,38 @@ "name": "@platforma-open/milaboratories.import-vdj", "version": "1.6.5", "files": [ - "index.d.ts", - "index.js" + "dist", + "block-pack" ], + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "sources": "./src/index.ts", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, "scripts": { "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json,vue}\"", - "build": "shx rm -rf ./block-pack && block-tools pack", - "mark-stable": "block-tools mark-stable -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'", - "prepublishOnly": "block-tools pack && block-tools publish -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'", - "do-pack": "shx rm -f *.tgz && block-tools pack && pnpm pack && shx mv *.tgz package.tgz" + "build": "ts-builder build --target block-facade && block-tools pack", + "prepublishOnly": "block-tools publish -r s3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1 --registry-serve-url https://blocks.pl-open.science", + "do-pack": "shx rm -f package.tgz && pnpm pack && shx mv *.tgz package.tgz", + "check": "ts-builder type-check --target block-facade" }, - "dependencies": { + "dependencies": {}, + "devDependencies": { + "@milaboratories/ts-builder": "catalog:", + "@milaboratories/ts-configs": "catalog:", "@platforma-open/milaboratories.import-vdj.model": "workspace:*", "@platforma-open/milaboratories.import-vdj.ui": "workspace:*", "@platforma-open/milaboratories.import-vdj.workflow": "workspace:*", - "@platforma-sdk/model": "catalog:" - }, - "devDependencies": { "@platforma-sdk/block-tools": "catalog:", - "shx": "catalog:" + "@platforma-sdk/model": "catalog:", + "shx": "catalog:", + "typescript": "catalog:" }, "packageManager": "pnpm@9.14.4", "block": { @@ -51,4 +64,4 @@ "marketplaceRanking": 16900 } } -} \ No newline at end of file +} diff --git a/block/src/AGENTS.ts b/block/src/AGENTS.ts new file mode 100644 index 0000000..5cf2589 --- /dev/null +++ b/block/src/AGENTS.ts @@ -0,0 +1,5 @@ +// This file is managed by `block-tools structure`. Do not edit by hand. +// Narrow MCP / AI surface. + +export type { BlockContract, BlockOutputs, BlockData } from "./index"; +export * from "./agents-extra"; diff --git a/block/src/agents-extra.ts b/block/src/agents-extra.ts new file mode 100644 index 0000000..228811f --- /dev/null +++ b/block/src/agents-extra.ts @@ -0,0 +1,8 @@ +// Author-owned. `block-tools structure` does not modify this file. +// MCP / AI extension surface. Add types and functions the agent +// surface should expose. +// +// In the future this file will host JS functions the MCP runtime +// can execute. The `.d.ts` declarations stay visible to the agent; +// the JS bodies execute in the MCP code-execution context (the +// agent sees the types but not the implementation). diff --git a/block/src/block-extra.ts b/block/src/block-extra.ts new file mode 100644 index 0000000..fc00cdd --- /dev/null +++ b/block/src/block-extra.ts @@ -0,0 +1,9 @@ +// Author-owned. `block-tools structure` does not modify this file. +// Add block-specific helper types or values the consumer surface +// should expose. Anything you `export` here flows out via the +// main entry (./index re-exports this file). +// +// Do NOT redefine: BlockContract, BlockOutputs, BlockData, +// BlockPointer, platforma, or the block-named Block* +// aliases — those names come from ./index and `export *` from this +// file would shadow them. diff --git a/block/src/index.ts b/block/src/index.ts new file mode 100644 index 0000000..fc8f330 --- /dev/null +++ b/block/src/index.ts @@ -0,0 +1,55 @@ +// This file is managed by `block-tools structure`. Do not edit by hand. +// Author content lives in ./block-extra.ts. + +import { platforma } from "@platforma-open/milaboratories.import-vdj.model"; +import { + InferOutputsType, + InferDataType, + InferHrefType, +} from "@platforma-sdk/model"; + +export { platforma }; + +export type BlockContract = { + outputs: InferOutputsType; + data: InferDataType; + href: InferHrefType; +}; + +export type BlockOutputs = BlockContract["outputs"]; +export type BlockData = BlockContract["data"]; + +// import.meta.url is a file: URL (always forward-slash, even on Windows: +// file:///C:/…). We expose URLs, NOT paths — the facade stays dependency-free +// and loadable in minimal engines (e.g. QuickJS), and each consumer converts +// at its own edge with the right tool (fileURLToPath in Node), where Windows +// drive letters / %-encoding / UNC are handled correctly. The bundled entry +// sits one dir under the package root (dist/index.js, or src/index.ts in dev), +// so the root is two URL segments up. The structurer owns this layout — +// consumers read these URLs, they never reconstruct /block-pack. +// +// TypeScript ships `ImportMeta.url` only in the `dom`/`webworker` libs; the +// facade tsconfig is lib-minimal (no `dom`, no `@types/node`) by design. We +// type the one ESM-standard member we use with a local cast rather than a +// `declare global` — a global augmentation would leak into the published +// `dist/index.d.ts` and clash with `@types/node`'s `ImportMeta` in full-Node +// consumers (test packages, the Middle Layer). +const selfUrl = (import.meta as ImportMeta & { url: string }).url; +const dirUrl = selfUrl.slice(0, selfUrl.lastIndexOf("/")); +const rootUrl = dirUrl.slice(0, dirUrl.lastIndexOf("/")); + +export const BlockPointer = { + type: "from-pack-v2" as const, + packUrl: rootUrl + "/block-pack", + rootUrl, +} as const; + +// Block-named aliases for readable cross-block imports in tests and +// consumer code. Same types / same runtime value as the universal +// names above; the aliases avoid `as`-renames at the import site. +export type ImportVdjBlockContract = BlockContract; +export type ImportVdjBlockOutputs = BlockOutputs; +export type ImportVdjBlockData = BlockData; +export const ImportVdjBlockPointer = BlockPointer; + +export * from "./block-extra"; diff --git a/block/tsconfig.json b/block/tsconfig.json new file mode 100644 index 0000000..8b5ec69 --- /dev/null +++ b/block/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@milaboratories/ts-configs/block/facade", + "include": ["src/**/*"] +} diff --git a/model/package.json b/model/package.json index 47bbe40..8126bf9 100644 --- a/model/package.json +++ b/model/package.json @@ -1,6 +1,7 @@ { "name": "@platforma-open/milaboratories.import-vdj.model", "version": "1.10.2", + "private": true, "description": "Block model", "type": "module", "main": "dist/index.cjs", diff --git a/package.json b/package.json index 6304a89..3305d7f 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,8 @@ "index.js" ], "scripts": { - "build": "turbo run build", - "build:dev": "env PL_PKG_DEV=local turbo run build", - "test": "env PL_PKG_DEV=local turbo run test --concurrency 1", - "test:dry-run": "env PL_PKG_DEV=local turbo run test --dry-run=json", + "test": "env PL_BUILD_CHANNEL=dev PL_BUILD_VARIANT=binary PL_BUILD_LOCATION=local turbo run test --concurrency 1", + "test:dry-run": "env PL_BUILD_CHANNEL=dev PL_BUILD_VARIANT=binary PL_BUILD_LOCATION=local turbo run test --dry-run=json", "mark-stable": "turbo run mark-stable", "watch": "turbo watch build", "changeset": "changeset", @@ -16,7 +14,12 @@ "fmt": "turbo run fmt", "check": "turbo run check", "do-pack": "turbo run do-pack", - "upgrade-sdk": "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm i && pnpm fmt" + "upgrade-sdk": "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm i && pnpm fmt", + "build:dev-local": "env PL_BUILD_CHANNEL=dev PL_BUILD_VARIANT=all PL_BUILD_LOCATION=local turbo run build", + "build:dev-remote": "env PL_BUILD_CHANNEL=dev PL_BUILD_VARIANT=all PL_BUILD_LOCATION=remote turbo run build", + "build:dev-no-software": "env PL_BUILD_CHANNEL=dev PL_BUILD_VARIANT=none turbo run build", + "build:dev-binary-existing": "env PL_BUILD_CHANNEL=dev PL_BUILD_USE_PUBLISHED=true turbo run build", + "build:release": "env PL_BUILD_CHANNEL=release PL_BUILD_VARIANT=all PL_BUILD_LOCATION=remote turbo run build" }, "devDependencies": { "@changesets/cli": "catalog:", @@ -29,8 +32,8 @@ "oxfmt": "*", "oxlint": "*" }, - "packageManager": "pnpm@9.14.4", + "packageManager": "pnpm@9.12.0", "pnpm": { "overrides": {} } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96e90cc..b060b2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,35 +13,38 @@ catalogs: specifier: 1.14.2 version: 1.14.2 '@milaboratories/ts-builder': - specifier: 1.5.2 - version: 1.5.2 + specifier: 1.6.2 + version: 1.6.2 '@milaboratories/ts-configs': - specifier: 1.2.3 - version: 1.2.3 + specifier: 1.4.0 + version: 1.4.0 '@platforma-sdk/block-tools': - specifier: 2.11.0 - version: 2.11.0 + specifier: 2.13.0 + version: 2.13.0 '@platforma-sdk/model': - specifier: 1.79.14 - version: 1.79.14 + specifier: 1.81.1 + version: 1.81.1 '@platforma-sdk/tengo-builder': - specifier: 4.0.8 - version: 4.0.8 + specifier: 4.0.22 + version: 4.0.22 '@platforma-sdk/test': - specifier: 1.79.14 - version: 1.79.14 + specifier: 1.81.3 + version: 1.81.3 '@platforma-sdk/ui-vue': - specifier: 1.79.14 - version: 1.79.14 + specifier: 1.81.1 + version: 1.81.1 '@platforma-sdk/workflow-tengo': - specifier: 6.6.3 - version: 6.6.3 + specifier: 6.8.2 + version: 6.8.2 shx: specifier: ^0.4.0 version: 0.4.0 turbo: specifier: ^2.5.8 version: 2.6.3 + typescript: + specifier: ~5.6.3 + version: 5.6.3 vitest: specifier: ~4.0.18 version: 4.0.18 @@ -65,10 +68,10 @@ importers: version: 2.29.8(@types/node@25.0.1) '@milaboratories/ts-builder': specifier: 'catalog:' - version: 1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) + version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) '@platforma-sdk/block-tools': specifier: 'catalog:' - version: 2.11.0(@types/node@25.0.1) + version: 2.13.0(@types/node@25.0.1) shx: specifier: 'catalog:' version: 0.4.0 @@ -77,7 +80,13 @@ importers: version: 2.6.3 block: - dependencies: + devDependencies: + '@milaboratories/ts-builder': + specifier: 'catalog:' + version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.6.3))(yaml@2.8.2) + '@milaboratories/ts-configs': + specifier: 'catalog:' + version: 1.4.0 '@platforma-open/milaboratories.import-vdj.model': specifier: workspace:* version: link:../model @@ -87,16 +96,18 @@ importers: '@platforma-open/milaboratories.import-vdj.workflow': specifier: workspace:* version: link:../workflow - '@platforma-sdk/model': - specifier: 'catalog:' - version: 1.79.14 - devDependencies: '@platforma-sdk/block-tools': specifier: 'catalog:' - version: 2.11.0(@types/node@25.0.1) + version: 2.13.0(@types/node@25.0.1) + '@platforma-sdk/model': + specifier: 'catalog:' + version: 1.81.1 shx: specifier: 'catalog:' version: 0.4.0 + typescript: + specifier: 'catalog:' + version: 5.6.3 model: dependencies: @@ -105,7 +116,7 @@ importers: version: 1.14.2 '@platforma-sdk/model': specifier: 'catalog:' - version: 1.79.14 + version: 1.81.1 '@types/node': specifier: '*' version: 25.0.1 @@ -115,13 +126,13 @@ importers: devDependencies: '@milaboratories/ts-builder': specifier: 'catalog:' - version: 1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) + version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) '@milaboratories/ts-configs': specifier: 'catalog:' - version: 1.2.3 + version: 1.4.0 '@platforma-sdk/block-tools': specifier: 'catalog:' - version: 2.11.0(@types/node@25.0.1) + version: 2.13.0(@types/node@25.0.1) test: dependencies: @@ -134,13 +145,13 @@ importers: devDependencies: '@milaboratories/ts-builder': specifier: 'catalog:' - version: 1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) + version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2) '@milaboratories/ts-configs': specifier: 'catalog:' - version: 1.2.3 + version: 1.4.0 '@platforma-sdk/test': specifier: 'catalog:' - version: 1.79.14(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) + version: 1.81.3(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) vitest: specifier: 'catalog:' version: 4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2) @@ -152,10 +163,10 @@ importers: version: link:../model '@platforma-sdk/model': specifier: 'catalog:' - version: 1.79.14 + version: 1.81.1 '@platforma-sdk/ui-vue': specifier: 'catalog:' - version: 1.79.14(@bytecodealliance/preview2-shim@0.17.8)(typescript@5.9.3) + version: 1.81.1(@bytecodealliance/preview2-shim@0.17.8)(typescript@5.9.3) '@types/node': specifier: '*' version: 25.0.1 @@ -171,23 +182,23 @@ importers: version: 1.14.2 '@milaboratories/ts-builder': specifier: 'catalog:' - version: 1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.24(typescript@5.9.3))(yaml@2.8.2) + version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.24(typescript@5.9.3))(yaml@2.8.2) '@milaboratories/ts-configs': specifier: 'catalog:' - version: 1.2.3 + version: 1.4.0 workflow: dependencies: '@platforma-sdk/workflow-tengo': specifier: 'catalog:' - version: 6.6.3 + version: 6.8.2 devDependencies: '@platforma-sdk/tengo-builder': specifier: 'catalog:' - version: 4.0.8 + version: 4.0.22 '@platforma-sdk/test': specifier: 'catalog:' - version: 1.79.14(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) + version: 1.81.3(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) shx: specifier: 'catalog:' version: 0.4.0 @@ -275,6 +286,10 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + '@aws-sdk/client-ecr-public@3.859.0': + resolution: {integrity: sha512-pPY85lrGBNWynofNC6Er49W6aA4JvOOKC9RDbS8rWR8pBPEG6p0B82VQKFMR+3JYRogpfQf5hzU47um96EslFA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/client-s3@3.859.0': resolution: {integrity: sha512-oFLHZX1X6o54ZlweubtSVvQDz15JiNrgDD7KeMZT2MwxiI3axPcHzTo2uizjj5mgNapmYjRmQS5c1c63dvruVA==} engines: {node: '>=18.0.0'} @@ -315,6 +330,12 @@ packages: resolution: {integrity: sha512-8iULWsH83iZDdUuiDsRb83M0NqIlXjlDbJUIddVsIrfWp4NmanKw77SV6yOZ66nuJjPsn9j7RDb9bfEPCy5SWA==} engines: {node: '>=18.0.0'} + '@aws-sdk/lib-storage@3.859.0': + resolution: {integrity: sha512-VfaEhih4MMxD6llibXEnrS4HyExhLpIFAvy1nq490IGO/Z6JCI81kmA3rB376XahMhoS9CVN0eAoeryTYHFHGw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.859.0 + '@aws-sdk/middleware-bucket-endpoint@3.840.0': resolution: {integrity: sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==} engines: {node: '>=18.0.0'} @@ -419,9 +440,9 @@ packages: resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/generator@8.0.0-rc.3': - resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-compilation-targets@7.29.7': resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} @@ -445,17 +466,17 @@ packages: resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0-rc.3': - resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.0-rc.3': - resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-identifier@8.0.4': + resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} @@ -470,9 +491,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.0-rc.3': - resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/parser@8.0.4': + resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + engines: {node: ^22.18.0 || >=24.11.0} hasBin: true '@babel/runtime@7.28.4': @@ -491,9 +512,9 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.0-rc.3': - resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/types@8.0.4': + resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + engines: {node: ^22.18.0 || >=24.11.0} '@bufbuild/protobuf@2.10.1': resolution: {integrity: sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==} @@ -731,12 +752,12 @@ packages: cpu: [x64] os: [win32] - '@grpc/grpc-js@1.13.4': - resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} engines: {node: '>=12.10.0'} - '@grpc/proto-loader@0.7.15': - resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} hasBin: true @@ -894,20 +915,20 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jitl/quickjs-ffi-types@0.31.0': - resolution: {integrity: sha512-1yrgvXlmXH2oNj3eFTrkwacGJbmM0crwipA3ohCrjv52gBeDaD7PsTvFYinlAnqU8iPME3LGP437yk05a2oejw==} + '@jitl/quickjs-ffi-types@0.32.0': + resolution: {integrity: sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==} - '@jitl/quickjs-wasmfile-debug-asyncify@0.31.0': - resolution: {integrity: sha512-YkdzQdr1uaftFhgEnTRjTTZHk2SFZdpWO7XhOmRVbi6CEVsH9g5oNF8Ta1q3OuSJHRwwT8YsuR1YzEiEIJEk6w==} + '@jitl/quickjs-wasmfile-debug-asyncify@0.32.0': + resolution: {integrity: sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw==} - '@jitl/quickjs-wasmfile-debug-sync@0.31.0': - resolution: {integrity: sha512-8XvloaaWBONqcHXYs5tWOjdhQVxzULilIfB2hvZfS6S+fI4m2+lFiwQy7xeP8ExHmiZ7D8gZGChNkdLgjGfknw==} + '@jitl/quickjs-wasmfile-debug-sync@0.32.0': + resolution: {integrity: sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q==} - '@jitl/quickjs-wasmfile-release-asyncify@0.31.0': - resolution: {integrity: sha512-uz0BbQYTxNsFkvkurd7vk2dOg57ElTBLCuvNtRl4rgrtbC++NIndD5qv2+AXb6yXDD3Uy1O2PCwmoaH0eXgEOg==} + '@jitl/quickjs-wasmfile-release-asyncify@0.32.0': + resolution: {integrity: sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q==} - '@jitl/quickjs-wasmfile-release-sync@0.31.0': - resolution: {integrity: sha512-hYduecOByj9AsAfsJhZh5nA6exokmuFC8cls39+lYmTCGY51bgjJJJwReEu7Ff7vBWaQCL6TeDdVlnp2WYz0jw==} + '@jitl/quickjs-wasmfile-release-sync@0.32.0': + resolution: {integrity: sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg==} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -952,88 +973,96 @@ packages: '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} - '@milaboratories/computable@2.9.5': - resolution: {integrity: sha512-rzLJ6fjXcNL8jb6vexGR4d8wUGvrwC2CyjkMLU5GFy+7Q6lg1wTrJnu8fzDVhS2OjEEVbD+RTE7AGvK5pYYSMQ==} + '@milaboratories/columns-collection-driver@0.2.3': + resolution: {integrity: sha512-3rNWmuQGvEaBEzGMHIWeOi1p8j8sMDpnPKNUQvT0Ji7/ArvnfM5HK8dywxB+n3mLK8c11lW16+auM5xuydJXGw==} + + '@milaboratories/computable@2.9.8': + resolution: {integrity: sha512-X0ZtxOnIJlAd9Y7CyBtWSKHgVuTKXbIryMwJaoYSEz0gY3JZVnsD0f59J/lwe3Key0/lSYh3EbL/pP5jACIzfQ==} engines: {node: '>=22.19.0'} '@milaboratories/helpers@1.14.2': resolution: {integrity: sha512-zOkD4aWNbembwI+AsPTz7nmWC1VPA4rwGBc+Nd5jPpKVh7rCtZwQrlOP5mVqRVMKIAjb1nU8kzzCGfqNPqfJjA==} engines: {node: '>=22'} - '@milaboratories/pf-driver@1.7.11': - resolution: {integrity: sha512-p9dANGg5VfSMIM3ysEUjQ86IfoPRqnww0kmhuaPI4h98kb+7CGbUz/XM0DFq4jALua1fTC3TxuMIY8JV8SJT8Q==} + '@milaboratories/helpers@1.14.5': + resolution: {integrity: sha512-Kiy0g7sEmFQxDwtnmTrdJ8XdUK0IDAB5ZnPCNEbK7lPGHIa+xG3kzfeR4y44QBdrYaK0NwG63D8Fc7dlv9KItg==} + engines: {node: '>=22'} + + '@milaboratories/pf-driver@1.9.0': + resolution: {integrity: sha512-OhMsf1jNMp31wRCrXHMU75WQj48Nc/lDeYTo4/6R6+FLYBYkK9yWgsSH+02V4WCKbUqsyF1Xy0+O+qsYjpqUDA==} engines: {node: '>=22.19.0'} - '@milaboratories/pf-spec-driver@1.4.13': - resolution: {integrity: sha512-1ylUmz0AgPSRE5DfcNxydv4JVaj4hwCQ1VCW+BWElSIRvlL9IaS4FWSckechQVDb7El9vSTRkrJE5IPx9T2z7A==} + '@milaboratories/pf-spec-driver@1.5.0': + resolution: {integrity: sha512-XWXrKCyCwcqS8EcukuyDqvMq0D6qt61zrkb3qGpWKyy0SVQNzXC2JL0ThCM2uGI7QQelgV0aagcg8xU5pGKWPw==} - '@milaboratories/pframes-rs-node@1.1.50': - resolution: {integrity: sha512-XshCE3Z9+tAKszlWCSDYJnpYRTIsag8v3YcjPmP6//W3dXDDrqi6gBqaGI0u58V9C3PLCjP3zC85vmtI5L26BQ==} + '@milaboratories/pf-spec@1.0.0': + resolution: {integrity: sha512-JIOhJdDHcKDItjlxOT4wVmnjejoazM69qK5ylu+g5q65RIcH/R5MF2OGiu9nV7QbLEXszll+EVnvx6jYhuE44w==} + peerDependencies: + '@bytecodealliance/preview2-shim': ^0.20.1 - '@milaboratories/pframes-rs-serv@1.1.50': - resolution: {integrity: sha512-XFWRsTGLCfQZhpRlWAmmBfznWyKL72dXY8UFTK+gmI2SOFdxQb8CJc0v/6LgNohwTKRTZZr59K9eRP3v8gCK6A==} - hasBin: true + '@milaboratories/pframes-rs-node@1.1.56': + resolution: {integrity: sha512-H6qltcR+HHb2kAy0U0zP90rqmv/MZGGkdXIyf89FUZTpoHOlXG4Ahxq7wXp9vviUczci4cLl2L0Q+dL2XGcsUA==} - '@milaboratories/pframes-rs-wasip2@1.1.50': - resolution: {integrity: sha512-FHv6Cksyl9q9plIhRUzNP0cQ03aZVltNh+haZ5KGVNHh9e5PgP9ndnTP06bQ1JRyQ/6Thf1NF5yw1lBXp6J+uw==} + '@milaboratories/pframes-rs-serv@1.1.56': + resolution: {integrity: sha512-Pi0CRuvkfL+PqdgQ8im0MW5tqVjUvJ8hJbOG7v5pS45adg8tuq4TyrAoWN7un9ZWQ6KRLypUD+7eXCwhjOwADg==} + hasBin: true - '@milaboratories/pframes-rs-wasm@1.1.50': - resolution: {integrity: sha512-D7YvFltjAk+OLCC/5/U741Hipxof4v/t0cJbk+y38MJjx6PyGQhAyVP/J4XfbJgdFdTGdDidRCNGYLt/Jn8l3w==} - peerDependencies: - '@bytecodealliance/preview2-shim': 0.17.9 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.5 + '@milaboratories/pframes-rs-wasip2@1.1.56': + resolution: {integrity: sha512-54bhC6XCAVO09J/sqVwEKA4hhTa27BDDNdH8BE+6+LvjBVkg/qq2/f0MzdrVijrmagbr97F1zgj5wIgUqwCSoA==} - '@milaboratories/pl-client@3.11.4': - resolution: {integrity: sha512-tsbZLVBC3qr3bcJ1mAvkKSJSjrOkh+OzI4EACOZ8V9eS1M08ooDF5L2JkhUPceCsNr78vgkffZFwwHIbPMtHrA==} + '@milaboratories/pl-client@3.14.6': + resolution: {integrity: sha512-IPzh5bSibr02DMKpwlCW6TuHVxaDe2o6tjaEJ4P2y5lshRbe5SBR/ne/G/N7E7KEKxmpWq7ZapOCLkLyvqFFDw==} engines: {node: '>=22.19.0'} - '@milaboratories/pl-config@1.8.2': - resolution: {integrity: sha512-FG9rlAKDf1rwlg+3G9OG2bCKISNp6ajM27W7ODDSsWE4MSCVxhYB172UusbohY2RDmaD9GI5pDKO5O9uoHQCCA==} + '@milaboratories/pl-config@1.8.5': + resolution: {integrity: sha512-XnfYXSSkRxeImQ21k6I8y5apisvagcSgMGfEeyRxNdSGwUVJbbI8TwJk+XBEDQ4lErW8oqtLxKjFQuHJMzRUoQ==} - '@milaboratories/pl-deployments@3.0.7': - resolution: {integrity: sha512-llf3PeNr7/+t7I4LDwgSzqt+fcrV8YWbzv1LWJXUXOL2VF3wArBoSLmzFr3SiRD5hIUcCDEA3QHWXwXTzth0kQ==} + '@milaboratories/pl-deployments@3.0.15': + resolution: {integrity: sha512-+i3bdvfPCXLZ789f5fRdtrbil3/RFyzSBNMkULcnyKY5u6GRZLjWDBC5aSE75PRTWx3Fxf743Fj9L3B6loMSWw==} engines: {node: '>=22.19.0'} - '@milaboratories/pl-drivers@1.16.0': - resolution: {integrity: sha512-9yAJ8YhE4fxFgqEllrHCsXCBdGNSF7c0SCQ2PIi2tYRR3++Dll7g+zOA0WDaOB+8eBAxYiqFvhsdfbCizwCCqw==} + '@milaboratories/pl-drivers@1.16.15': + resolution: {integrity: sha512-mVKIEHVOeAPdwSZZk+URAFD6zoyPiAARinpDfihDvkFcrlTLIzeoOvnQNPG5a8jqc0GiW+a9mnTrfjG7+n82Ww==} engines: {node: '>=22'} '@milaboratories/pl-error-like@1.12.10': resolution: {integrity: sha512-iHmnLG5lxJqcymUmEL8onCDGEADk1S/5RNACQ5yfTCNcCkUc+7hYrDHQpFmWXPPGC9sG+NTBxt4wr5m8UsLm2g==} - '@milaboratories/pl-errors@1.4.22': - resolution: {integrity: sha512-A4jEhyXEeHb03aRpUGWxILGTfNwYii/BgmoZ3TVv5q0iWnApoiVUoy/MXtaed4QeUs8FZlOhZfOO5AKq1yaXrQ==} + '@milaboratories/pl-errors@1.4.35': + resolution: {integrity: sha512-rq5Lg9G7ax+LtMPdrkZg+Pdqk28JViiS/KIcQfk0Cg1ttZoAnhDhFIh6rAFy2eGybPtlzXqM32/ODKUSMawc8Q==} - '@milaboratories/pl-healthcheck@1.0.1': - resolution: {integrity: sha512-eiYd/TFm18SW4EY8vkI3c+fcPnjUu3Hd1GnPLWCN8U+dNmp/tnrzfeCgaY4xO/s0vkHNX9E7IrsQiMg2Ioz4rw==} + '@milaboratories/pl-healthcheck@1.0.5': + resolution: {integrity: sha512-ZkQti4VU2FapJBFRtZGfR5NDPXEAEFgTf/NfemypgY0aA75fFPi4/c3BpXX5K7dht+JOgHqkMrBHxKIuE2T+fA==} engines: {node: '>=22.19.0'} '@milaboratories/pl-http@1.2.4': resolution: {integrity: sha512-QKmhx+WEvJCV9dUy/SBdQk/ApaJ5ewBFgm/b+XPlS10SusAdqUUTGvK5+hq8YSuUMXlHb/dk++UtI5YlDuDl2Q==} - '@milaboratories/pl-middle-layer@1.64.32': - resolution: {integrity: sha512-tBBTm3EsVfJQDTsaUbG/gD889etZGS2qmOS//51l+YCtWzEmPmnNDvMHlI5rmIYOtuQJH91ZmvFEPj3nGCweqw==} + '@milaboratories/pl-middle-layer@1.66.19': + resolution: {integrity: sha512-rFBmJDdBcIrdDHz730p3sL8+7uUs7VimXYGQo9IbayxmAlp5mnft1XlPUTuVbwx5QeRNGv4Cwya1T+7WL1wSmw==} engines: {node: '>=22.19.0'} - '@milaboratories/pl-model-backend@1.4.7': - resolution: {integrity: sha512-OSe9s7HJ5bVbggG9gF5/sPnvW3gqQsICeIQeT+pe2i/HvxNoL/YNvvJhcNaGc3D+TNKzu4GIlaWaRQfiYffzNQ==} + '@milaboratories/pl-model-backend@1.4.20': + resolution: {integrity: sha512-AkV+PhmQms6WPwR3E75THrkD9fFE7OpfsIUC297HvnFUoCFXR6GOMGPworA/qipXReTmv192AqBpuaLdgAngyg==} - '@milaboratories/pl-model-common@1.46.1': - resolution: {integrity: sha512-ABOz/w7dA4t2zUpZHXI74MTNW6LmPFSLxgfhOPFdO6vodIY8draVN+ag2U21WlYIoSgdJwSXJrXJWdu1cefrIg==} + '@milaboratories/pl-model-common@1.46.2': + resolution: {integrity: sha512-VEeauisApYScvCS8lnK3zpFJ520xuTAodKJmjR8ulHcMrWMyWMfHEdGb7j5OMD0mM/OwTgmQrrJ5eB7Xd+xoOQ==} - '@milaboratories/pl-model-middle-layer@1.30.5': - resolution: {integrity: sha512-TSpemA65REZay1ObnuwV3QLIjN46Iluy0HUu/aiHMQ1d1BFAG2vrgvonQqTnxp7UKQYOr9RGJg/QDYG/+UNKMw==} + '@milaboratories/pl-model-common@1.47.3': + resolution: {integrity: sha512-tXmKujm+6ru/Fh9hr9H3iRBWbS+JE0Q7FNualtzJpijaB3SNtScR4erLTamdANv5RYNn7kbYpDWr6wAjAOajrg==} - '@milaboratories/pl-model-middle-layer@1.30.6': - resolution: {integrity: sha512-x6cj1sNAayz80odDf6RbXnJDgj01Lc+NB+5IVyMk65o3cGt6ml0IbiqaDXGMJYUDkt1JSAj3EQpPha2YDDfYQg==} + '@milaboratories/pl-model-middle-layer@1.30.7': + resolution: {integrity: sha512-rs9x3Ron4ujR/UOdEgB8WUB1SvZ8ZAScT1Av/e4or+iiQ/CzhmK9nqtYamHVwKV+JgwIFbXQwxGvIHDVz955dQ==} - '@milaboratories/pl-tree@1.12.12': - resolution: {integrity: sha512-IREZbJZ1F7QerUyzcfPg0HQCae/FPGXoNbxwsecLC+JiPAVaRdcz+uNTpBlPVNEeI9BT88fJmDhfjts5bbeE7A==} + '@milaboratories/pl-model-middle-layer@1.31.0': + resolution: {integrity: sha512-D1vyGABBtCYyp2IhKd9eMoZtKUj1wUYpIzz4Xz5k+bfJWGEF8GmZGiOvO4oOvrsEsXlG4NyO249MnBKmHFcppQ==} + + '@milaboratories/pl-tree@1.13.6': + resolution: {integrity: sha512-R3wnMjbCNfAsyGJe3FQOqEyrNePd9ZCwkjDj2ps7d9oeiJ7QX6hYkIVuMlq8IQNYOJybfHc/dFEvWeYE3uAAZg==} engines: {node: '>=22.19.0'} - '@milaboratories/ptabler-expression-js@1.2.30': - resolution: {integrity: sha512-6yRo0T6rpt14WEC++F+uuFGunnGF8ApCeKnvkysTsKxo+hALGPIQbobcTFN1gtQADnK6DI6huWQ6MxQpjBgbuQ==} + '@milaboratories/ptabler-expression-js@1.2.37': + resolution: {integrity: sha512-urVRk4b5Jse555euNNITlOJ437McZVtBnC5h/E6O+iHm+hfNIi6OP1aAD5ai4jOpHllm85zW5Ne7hVyyd71bJw==} '@milaboratories/resolve-helper@1.1.3': resolution: {integrity: sha512-38/dW/XRZQREOxAOOKtO0lzEWPCP/DH0qhB3q1kYcGoN++5V92/zbVwbYrMDeDcjTyo+D62iIep+sKXeWHa7Uw==} @@ -1046,22 +1075,19 @@ packages: os: [darwin, linux, win32] hasBin: true - '@milaboratories/ts-builder@1.5.2': - resolution: {integrity: sha512-aSqGW/3JdlQrnIYJVQR2z9bO+iYSGHg84xzXW0kfVo15dewvlfckhVs/4YVhTTZdO5xbgfhEsk370q1FcOgNaw==} + '@milaboratories/ts-builder@1.6.2': + resolution: {integrity: sha512-6pS36U5EL2IBLIpoP0/5oSFA9jzjcJmYhgEODzt5kglovTzJuh15nEmJMeChr0OpFtGFjQT6o37qHerDGETIpA==} hasBin: true - '@milaboratories/ts-configs@1.2.3': - resolution: {integrity: sha512-NALtuzTbSzAz2Uk7X1sWq3rBo4XAV/vAQ1Mf7IfiNiv1euZ+0+TF0NDJMNiEcU6BT/Q55dATSKhIY2r680ljXw==} - - '@milaboratories/ts-helpers-oclif@1.1.42': - resolution: {integrity: sha512-qbhoxfOXG3SeODsgEcedf4WMHVJVFXdgBgK2zIvkB+vC5OTBjZKo0MSo1ILRXovR5C319BCo0no7SNZY8l+3vw==} + '@milaboratories/ts-configs@1.4.0': + resolution: {integrity: sha512-VzU9D+RiggsG4VYteJSBN4o8IjYae9GbZ8MofikMJQLOI1Ir9/pVyPXXGnG/TAGvmcjbK/psAiZVGSvI5MBPGg==} - '@milaboratories/ts-helpers@1.8.3': - resolution: {integrity: sha512-HK3AmNZl2fOzMHot2+ihKsOC+fluwhl5261VTn1zGS3LRpmC9bCk/po8SzsVmg79ccI5nESFTdz+BRaLsXncmQ==} + '@milaboratories/ts-helpers@1.8.6': + resolution: {integrity: sha512-ef01tARUl+0Urt3x8HHAByrhYHg4Rnn7WiFyJ+joZFZl1T1pUKTgfB7Zxc8Cn06HIl4i6H7s5OSymjZePIGqfQ==} engines: {node: '>=22.19.0'} - '@milaboratories/uikit@2.15.8': - resolution: {integrity: sha512-kyG9VJI7zeX6XQIu+93qFJFTvoOeoW7AqNdtkrTmSHet4FNCdYfp05DKA2lku6WuiYg/bHfpEhRLbu7US8Fs9g==} + '@milaboratories/uikit@2.15.23': + resolution: {integrity: sha512-FrZPJsa3jry4juVofR42IPCHv+oKPAcdBL+OidX0/S2lhIAsCIrEKBcAN3yzWd2yvyAPb3qzt0hbwpuTY0UVlw==} '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} @@ -1089,16 +1115,15 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oclif/core@4.8.0': - resolution: {integrity: sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==} - engines: {node: '>=18.0.0'} - '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} '@oxc-project/types@0.132.0': resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1405,14 +1430,14 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@platforma-open/milaboratories.software-ptabler.schema@1.15.14': - resolution: {integrity: sha512-BBbmfUi3fS40HmMbYKGuRNgSPo4kAk/vk+dUK3fyabbjGncKZTm9W4vZlqH2JJX0VECIROR4FrvGvF91yRyVwg==} + '@platforma-open/milaboratories.software-ptabler.schema@1.15.21': + resolution: {integrity: sha512-3EWO64poe5VcBIgVIdIFHwN7nPi000kPuaRmlVt3xnnQZqXUYDFNZfZ2Mc9/mE1Eq3FLwQxcy/5mng7LvgWnXg==} - '@platforma-open/milaboratories.software-ptabler@2.1.2': - resolution: {integrity: sha512-pCPA9oa4MPZ8mw7tJRjp4Zy9e/xJL9+tWKlSVXd+PbSkAqZoPTirf1izWV/NEZhhM7w8przq4jGhbqj6ukkCRQ==} + '@platforma-open/milaboratories.software-ptabler@2.1.8': + resolution: {integrity: sha512-28dKwcKNIB/8OYH6l/li7Qa/aE2NgDVtNUmN8v6jZsLKT59ogFclz8ZrQ+OiQFzHQCLBlWBt5TdIHfHPxBaNFw==} - '@platforma-open/milaboratories.software-ptexter@1.2.2': - resolution: {integrity: sha512-I08YpKQ4+esLrfpVra5UJ+bznI9Zzba6OW0rKK407a1EUmanvYMVrCbM9gqnm6CHbv2vQxNGSaO8p1LoBh+9gA==} + '@platforma-open/milaboratories.software-ptexter@1.2.4': + resolution: {integrity: sha512-4ZqhqksSNVKWhEB9WwrL0rU89G+00ulzH1urYyNA4EdBVGA4mwbKbw0K/zcwce/oUYYMdRl+epXmr0w9wHyNzA==} '@platforma-open/milaboratories.software-small-binaries.hello-world-py@1.0.10': resolution: {integrity: sha512-16BGRLIrsVW+YIaXwWLX6Nbm80PS5mQJEclHhjNbRrRTLHPaKI4RygZfBC0lLNzvHBiTXU4Qkh1+WmdovkshDg==} @@ -1432,30 +1457,33 @@ packages: '@platforma-open/milaboratories.software-small-binaries@2.1.1': resolution: {integrity: sha512-KN1PR7YgUUfx1dxh/TtcoWpSZbOXbRxXzQuJ505qHuXuE0spYwC7bXnvJsEYbEwRcPMa0foTrjBnPNORE4yr+Q==} - '@platforma-sdk/block-tools@2.11.0': - resolution: {integrity: sha512-zJAPVOsgB/XvnlmrhiJUAHDl+MBSolo87nLIQVc+r6oQDDrpySUX4ztuOv/W9i9tnhjoeVeLtbySifmhdAn+Rg==} + '@platforma-sdk/block-tools@2.13.0': + resolution: {integrity: sha512-xKiTmNLjfxCnnHRr9Q4ct3HdjLfkbLztvxpU++/pek0R8Bj/Lezeoji+eLpxAAYYUxjMr8jLj935eLK5MqKrSg==} hasBin: true '@platforma-sdk/blocks-deps-updater@2.2.0': resolution: {integrity: sha512-p9lBxhFXM9WoRsrJO7dfkiXSK+1m63yIn1sKhBO71eMbhrLMyVYHEOeNf3w5OCdbRF5QsNhXzWuiTmFK3zHFsA==} hasBin: true - '@platforma-sdk/model@1.79.14': - resolution: {integrity: sha512-ywF+pV3twqbZfuGv+fphE7HKZzYY9/xYIM9gvhM3Ps/qusIf98997C8Ickky1uHd6AHk8YUX78HCR4hJkD7FyQ==} + '@platforma-sdk/model@1.81.1': + resolution: {integrity: sha512-pyCcCzMu+L0ELnNQkpmYMNw+A70WWCXng29WxOVllMTvFvr6/BaH4OUOA2q8ocseUlKm6qFQiRxmZTe53AcvlQ==} - '@platforma-sdk/tengo-builder@4.0.8': - resolution: {integrity: sha512-FOMj0LCBRWHKuKKOHUxqZX3uhaC4OayGzw0YBxbye0b7d7uwhH2KjdrkO70uHRy64z4U85pjIR2TrwAQC8qUgQ==} + '@platforma-sdk/package-builder-lib@1.3.0': + resolution: {integrity: sha512-CdBjmNo6E1fBxKYWaXa49L/L2WLURxs2f1TAqxLIZlHRE4DZ6E1TEj3jNNKESWp+/9rwtLkTAzmTzNPrDgz+2Q==} + + '@platforma-sdk/tengo-builder@4.0.22': + resolution: {integrity: sha512-8+zDYDFFI2tQS7dplTcxgQdUwzt8+IO++sJiVbwBgwSg1Giyc2qMThEYPLK3QNFArgCsIDR6wQuS8t66pJsRPQ==} engines: {node: '>=22'} hasBin: true - '@platforma-sdk/test@1.79.14': - resolution: {integrity: sha512-YswflyuQRmZ6pgkR0+gL6YN38bt/MFuugnBG6NJpjkda/SSoXaiIb3HcBNw0Y+mpyAQPHMoSyHr7ZyZGpvZVDA==} + '@platforma-sdk/test@1.81.3': + resolution: {integrity: sha512-LSDJoQqpxX2G8W7ZshnYWroqNQiFoxXQlka4pEJTnPEO8YISW3lWknBhqsxMp2GyepQNoyl2WO1bfiQz+ssT5A==} - '@platforma-sdk/ui-vue@1.79.14': - resolution: {integrity: sha512-KRc+4YOKKes1uwpKBgx9PeJozpGOS2uLt4XljgFupnoU4PyNZjw2I8ZTLtvq0Ld9o5T/ieUCRgiXgx4i4O+QBg==} + '@platforma-sdk/ui-vue@1.81.1': + resolution: {integrity: sha512-xFnehLOXDHYwDFrdtiU/NBjV0l6pEVLkthfnBjR+g2pSrHJGEL9JDrY3DdzoNUSxU4fal+Ak5aoAJR5v8mOeLg==} - '@platforma-sdk/workflow-tengo@6.6.3': - resolution: {integrity: sha512-2H83hEd+t8pIcSpz0anmWb0wesEcoXCYl9HSOcfQL9b0ro6EDf+PwyafXtmbXl2kMtkpccigbhDGZdDhoMpr7w==} + '@platforma-sdk/workflow-tengo@6.8.2': + resolution: {integrity: sha512-AHR/Y+vbyfda17A7xY2uH9TlXiBpM8242tTO6+lj8phA4/KS6mez4GLu4ZEaASlZpX6YkQsUs5LUxYQU7pXeiQ==} '@protobuf-ts/grpc-transport@2.11.1': resolution: {integrity: sha512-l6wrcFffY+tuNnuyrNCkRM8hDIsAZVLA8Mn7PKdVyYxITosYh60qW663p9kL6TWXYuDCL3oxH8ih3vLKTDyhtg==} @@ -1482,29 +1510,26 @@ packages: '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} '@rolldown/binding-android-arm64@1.0.2': resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} @@ -1512,72 +1537,144 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.2': resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.2': resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.2': resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.2': resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.2': resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-ppc64-gnu@1.0.2': resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + '@rolldown/binding-linux-s390x-gnu@1.0.2': resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.2': resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.2': resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.2': resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.2': resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1589,12 +1686,24 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.2': resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} @@ -2271,6 +2380,10 @@ packages: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -2338,10 +2451,6 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2358,9 +2467,13 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@3.17.0: - resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} - engines: {node: '>=14'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -2383,9 +2496,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-kit@3.0.0-beta.1: - resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} - engines: {node: '>=20.19.0'} + ast-kit@3.0.0: + resolution: {integrity: sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==} + engines: {node: ^22.18.0 || >=24.11.0} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -2500,12 +2613,22 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-fill@1.0.0: resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buildcheck@0.0.7: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} @@ -2555,14 +2678,6 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - clean-stack@3.0.1: - resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} - engines: {node: '>=10'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -2601,20 +2716,24 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2641,6 +2760,15 @@ packages: resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} engines: {node: '>=10.0.0'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -2752,9 +2880,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dts-resolver@2.1.3: - resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} - engines: {node: '>=20.19.0'} + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} peerDependencies: oxc-resolver: '>=11.0.0' peerDependenciesMeta: @@ -2773,11 +2901,6 @@ packages: engines: {node: '>=14'} hasBin: true - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -2831,10 +2954,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -2846,9 +2965,17 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@1.0.0: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} @@ -2917,9 +3044,6 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2977,10 +3101,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -2993,8 +3113,9 @@ packages: resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} engines: {node: '>=6'} - get-tsconfig@4.14.0: - resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -3079,10 +3200,6 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -3109,11 +3226,6 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3157,10 +3269,6 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -3185,11 +3293,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jake@10.9.4: - resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} - engines: {node: '>=10'} - hasBin: true - jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -3244,6 +3347,10 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -3314,10 +3421,6 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - local-pkg@1.1.2: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} @@ -3489,6 +3592,10 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + npm-run-path@2.0.2: resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} engines: {node: '>=4'} @@ -3500,6 +3607,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3681,11 +3792,15 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} pump@3.0.3: @@ -3708,11 +3823,11 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quickjs-emscripten-core@0.31.0: - resolution: {integrity: sha512-oQz8p0SiKDBc1TC7ZBK2fr0GoSHZKA0jZIeXxsnCyCs4y32FStzCW4d1h6E1sE0uHDMbGITbk2zhNaytaoJwXQ==} + quickjs-emscripten-core@0.32.0: + resolution: {integrity: sha512-QFnPfjFey8EqknSrSxe1hZrf1/8z7/6s1QzGOmKo6++02r7QRRX7ZoyNaZh7JuVjWsVW87KnQrbZqnHkOAzUyg==} - quickjs-emscripten@0.31.0: - resolution: {integrity: sha512-K7Yt78aRPLjPcqv3fIuLW1jW3pvwO21B9pmFOolsjM/57ZhdVXBr51GqJpalgBlkPu9foAvhEAuuQPnvIGvLvQ==} + quickjs-emscripten@0.32.0: + resolution: {integrity: sha512-So0Sqw869y/S2oE3Nuc0uT3Dhqgvsj8FSrwBdsuTosVsG8ME5/OcudU1GxsrIFdFABgy17GHnTVO9TYV/bLQcA==} engines: {node: '>=16.0.0'} rc@1.2.8: @@ -3730,6 +3845,13 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -3764,15 +3886,15 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown-plugin-dts@0.23.2: - resolution: {integrity: sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==} - engines: {node: '>=20.19.0'} + rolldown-plugin-dts@0.26.0: + resolution: {integrity: sha512-e+kEPtUiDES0htk5iqkSeF4EzAV7R+vugGB44iPDuw1Kw9E+WyL1VG7PaV0IIjGHLiacztMBcMTyrr8ON9CT1Q==} + engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: '@ts-macro/tsc': ^0.3.6 '@typescript/native-preview': '>=7.0.0-dev.20260325.1' - rolldown: ^1.0.0-rc.12 + rolldown: ^1.0.0 typescript: ^5.0.0 || ^6.0.0 - vue-tsc: ~3.2.0 + vue-tsc: ~3.2.0 || ~3.3.0 peerDependenciesMeta: '@ts-macro/tsc': optional: true @@ -3788,6 +3910,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-copy@3.5.0: resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} engines: {node: '>=8.3'} @@ -3932,6 +4059,9 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -4122,10 +4252,6 @@ packages: tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -4140,6 +4266,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -4438,10 +4569,6 @@ packages: engines: {node: '>=8'} hasBin: true - widest-line@3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - winston-transport@4.9.0: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} @@ -4450,9 +4577,6 @@ packages: resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} engines: {node: '>= 12.0.0'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -4506,6 +4630,10 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -4597,6 +4725,50 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + '@aws-sdk/client-ecr-public@3.859.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.858.0 + '@aws-sdk/credential-provider-node': 3.859.0 + '@aws-sdk/middleware-host-header': 3.840.0 + '@aws-sdk/middleware-logger': 3.840.0 + '@aws-sdk/middleware-recursion-detection': 3.840.0 + '@aws-sdk/middleware-user-agent': 3.858.0 + '@aws-sdk/region-config-resolver': 3.840.0 + '@aws-sdk/types': 3.840.0 + '@aws-sdk/util-endpoints': 3.848.0 + '@aws-sdk/util-user-agent-browser': 3.840.0 + '@aws-sdk/util-user-agent-node': 3.858.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.7 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/middleware-retry': 4.4.14 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.10 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.13 + '@smithy/util-defaults-mode-node': 4.2.16 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-s3@3.859.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 @@ -4810,6 +4982,17 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/lib-storage@3.859.0(@aws-sdk/client-s3@3.859.0)': + dependencies: + '@aws-sdk/client-s3': 3.859.0 + '@smithy/abort-controller': 4.2.5 + '@smithy/middleware-endpoint': 4.3.14 + '@smithy/smithy-client': 4.9.10 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + '@aws-sdk/middleware-bucket-endpoint@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 @@ -5037,7 +5220,7 @@ snapshots: '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5052,10 +5235,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/generator@8.0.0-rc.3': + '@babel/generator@8.0.0': dependencies: - '@babel/parser': 8.0.0-rc.3 - '@babel/types': 8.0.0-rc.3 + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 '@types/jsesc': 2.5.1 @@ -5089,11 +5272,11 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-string-parser@8.0.0-rc.3': {} + '@babel/helper-string-parser@8.0.0': {} '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-identifier@8.0.0-rc.3': {} + '@babel/helper-validator-identifier@8.0.4': {} '@babel/helper-validator-option@7.29.7': {} @@ -5106,9 +5289,9 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/parser@8.0.0-rc.3': + '@babel/parser@8.0.4': dependencies: - '@babel/types': 8.0.0-rc.3 + '@babel/types': 8.0.4 '@babel/runtime@7.28.4': {} @@ -5126,7 +5309,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5135,10 +5318,10 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@8.0.0-rc.3': + '@babel/types@8.0.4': dependencies: - '@babel/helper-string-parser': 8.0.0-rc.3 - '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 '@bufbuild/protobuf@2.10.1': {} @@ -5398,16 +5581,16 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@grpc/grpc-js@1.13.4': + '@grpc/grpc-js@1.14.4': dependencies: - '@grpc/proto-loader': 0.7.15 + '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 - '@grpc/proto-loader@0.7.15': + '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.4 + protobufjs: 7.6.5 yargs: 17.7.2 '@inquirer/ansi@1.0.2': {} @@ -5556,23 +5739,23 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jitl/quickjs-ffi-types@0.31.0': {} + '@jitl/quickjs-ffi-types@0.32.0': {} - '@jitl/quickjs-wasmfile-debug-asyncify@0.31.0': + '@jitl/quickjs-wasmfile-debug-asyncify@0.32.0': dependencies: - '@jitl/quickjs-ffi-types': 0.31.0 + '@jitl/quickjs-ffi-types': 0.32.0 - '@jitl/quickjs-wasmfile-debug-sync@0.31.0': + '@jitl/quickjs-wasmfile-debug-sync@0.32.0': dependencies: - '@jitl/quickjs-ffi-types': 0.31.0 + '@jitl/quickjs-ffi-types': 0.32.0 - '@jitl/quickjs-wasmfile-release-asyncify@0.31.0': + '@jitl/quickjs-wasmfile-release-asyncify@0.32.0': dependencies: - '@jitl/quickjs-ffi-types': 0.31.0 + '@jitl/quickjs-ffi-types': 0.32.0 - '@jitl/quickjs-wasmfile-release-sync@0.31.0': + '@jitl/quickjs-wasmfile-release-sync@0.32.0': dependencies: - '@jitl/quickjs-ffi-types': 0.31.0 + '@jitl/quickjs-ffi-types': 0.32.0 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -5660,23 +5843,30 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} - '@milaboratories/computable@2.9.5': + '@milaboratories/columns-collection-driver@0.2.3': + dependencies: + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pl-model-common': 1.47.3 + + '@milaboratories/computable@2.9.8': dependencies: '@milaboratories/pl-error-like': 1.12.10 - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/ts-helpers': 1.8.6 '@types/node': 24.5.2 utility-types: 3.11.0 '@milaboratories/helpers@1.14.2': {} - '@milaboratories/pf-driver@1.7.11(@bytecodealliance/preview2-shim@0.17.8)': + '@milaboratories/helpers@1.14.5': {} + + '@milaboratories/pf-driver@1.9.0(@bytecodealliance/preview2-shim@0.17.8)': dependencies: - '@milaboratories/helpers': 1.14.2 - '@milaboratories/pframes-rs-node': 1.1.50 - '@milaboratories/pframes-rs-wasm': 1.1.50(@bytecodealliance/preview2-shim@0.17.8)(@milaboratories/pl-model-common@1.46.1)(@milaboratories/pl-model-middle-layer@1.30.6) - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pf-spec': 1.0.0(@bytecodealliance/preview2-shim@0.17.8) + '@milaboratories/pframes-rs-node': 1.1.56 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-model-middle-layer': 1.31.0 + '@milaboratories/ts-helpers': 1.8.6 es-toolkit: 1.43.0 lru-cache: 11.2.4 transitivePeerDependencies: @@ -5684,52 +5874,50 @@ snapshots: - encoding - supports-color - '@milaboratories/pf-spec-driver@1.4.13(@bytecodealliance/preview2-shim@0.17.8)': + '@milaboratories/pf-spec-driver@1.5.0(@bytecodealliance/preview2-shim@0.17.8)': dependencies: - '@milaboratories/helpers': 1.14.2 - '@milaboratories/pframes-rs-wasm': 1.1.50(@bytecodealliance/preview2-shim@0.17.8)(@milaboratories/pl-model-common@1.46.1)(@milaboratories/pl-model-middle-layer@1.30.6) - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pf-spec': 1.0.0(@bytecodealliance/preview2-shim@0.17.8) + '@milaboratories/pl-model-common': 1.47.3 '@noble/hashes': 2.2.0 transitivePeerDependencies: - '@bytecodealliance/preview2-shim' - '@milaboratories/pframes-rs-node@1.1.50': + '@milaboratories/pf-spec@1.0.0(@bytecodealliance/preview2-shim@0.17.8)': + dependencies: + '@bytecodealliance/preview2-shim': 0.17.8 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-model-middle-layer': 1.31.0 + + '@milaboratories/pframes-rs-node@1.1.56': dependencies: '@mapbox/node-pre-gyp': 2.0.3 '@milaboratories/helpers': 1.14.2 - '@milaboratories/pframes-rs-serv': 1.1.50 - '@milaboratories/pl-model-common': 1.46.1 + '@milaboratories/pframes-rs-serv': 1.1.56 + '@milaboratories/pl-model-common': 1.46.2 ulid: 3.0.2 transitivePeerDependencies: - encoding - supports-color - '@milaboratories/pframes-rs-serv@1.1.50': + '@milaboratories/pframes-rs-serv@1.1.56': dependencies: '@milaboratories/helpers': 1.14.2 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.5 + '@milaboratories/pl-model-common': 1.46.2 + '@milaboratories/pl-model-middle-layer': 1.30.7 better-sqlite3: 12.10.0 commander: 14.0.3 selfsigned: 5.5.0 - '@milaboratories/pframes-rs-wasip2@1.1.50': {} + '@milaboratories/pframes-rs-wasip2@1.1.56': {} - '@milaboratories/pframes-rs-wasm@1.1.50(@bytecodealliance/preview2-shim@0.17.8)(@milaboratories/pl-model-common@1.46.1)(@milaboratories/pl-model-middle-layer@1.30.6)': + '@milaboratories/pl-client@3.14.6': dependencies: - '@bytecodealliance/preview2-shim': 0.17.8 - '@milaboratories/pframes-rs-wasip2': 1.1.50 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 - - '@milaboratories/pl-client@3.11.4': - dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.4 '@milaboratories/pl-http': 1.2.4 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/ts-helpers': 1.8.3 - '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.13.4) + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/ts-helpers': 1.8.6 + '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.14.4) '@protobuf-ts/runtime': 2.11.1 '@protobuf-ts/runtime-rpc': 2.11.1 canonicalize: 2.1.0 @@ -5741,19 +5929,19 @@ snapshots: utility-types: 3.11.0 yaml: 2.8.2 - '@milaboratories/pl-config@1.8.2': + '@milaboratories/pl-config@1.8.5': dependencies: - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/ts-helpers': 1.8.6 upath: 2.0.1 yaml: 2.8.2 - '@milaboratories/pl-deployments@3.0.7': + '@milaboratories/pl-deployments@3.0.15': dependencies: - '@milaboratories/pl-config': 1.8.2 - '@milaboratories/pl-healthcheck': 1.0.1 + '@milaboratories/pl-config': 1.8.5 + '@milaboratories/pl-healthcheck': 1.0.5 '@milaboratories/pl-http': 1.2.4 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/ts-helpers': 1.8.6 decompress: 4.2.1 ssh2: 1.17.0 tar: 7.5.2 @@ -5762,16 +5950,16 @@ snapshots: yaml: 2.8.2 zod: 3.25.76 - '@milaboratories/pl-drivers@1.16.0': + '@milaboratories/pl-drivers@1.16.15': dependencies: - '@grpc/grpc-js': 1.13.4 - '@milaboratories/computable': 2.9.5 - '@milaboratories/helpers': 1.14.2 - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-tree': 1.12.12 - '@milaboratories/ts-helpers': 1.8.3 - '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.13.4) + '@grpc/grpc-js': 1.14.4 + '@milaboratories/computable': 2.9.8 + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-tree': 1.13.6 + '@milaboratories/ts-helpers': 1.8.6 + '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.14.4) '@protobuf-ts/plugin': 2.11.1 '@protobuf-ts/runtime': 2.11.1 '@protobuf-ts/runtime-rpc': 2.11.1 @@ -5793,17 +5981,17 @@ snapshots: json-stringify-safe: 5.0.1 zod: 3.25.76 - '@milaboratories/pl-errors@1.4.22': + '@milaboratories/pl-errors@1.4.35': dependencies: - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/ts-helpers': 1.8.6 zod: 3.25.76 - '@milaboratories/pl-healthcheck@1.0.1': + '@milaboratories/pl-healthcheck@1.0.5': dependencies: - '@grpc/grpc-js': 1.13.4 - '@milaboratories/ts-helpers': 1.8.3 - '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.13.4) + '@grpc/grpc-js': 1.14.4 + '@milaboratories/ts-helpers': 1.8.6 + '@protobuf-ts/grpc-transport': 2.11.1(@grpc/grpc-js@1.14.4) '@protobuf-ts/runtime': 2.11.1 '@protobuf-ts/runtime-rpc': 2.11.1 @@ -5811,33 +5999,33 @@ snapshots: dependencies: undici: 7.16.0 - '@milaboratories/pl-middle-layer@1.64.32(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)': - dependencies: - '@milaboratories/computable': 2.9.5 - '@milaboratories/helpers': 1.14.2 - '@milaboratories/pf-driver': 1.7.11(@bytecodealliance/preview2-shim@0.17.8) - '@milaboratories/pf-spec-driver': 1.4.13(@bytecodealliance/preview2-shim@0.17.8) - '@milaboratories/pframes-rs-node': 1.1.50 - '@milaboratories/pframes-rs-wasm': 1.1.50(@bytecodealliance/preview2-shim@0.17.8)(@milaboratories/pl-model-common@1.46.1)(@milaboratories/pl-model-middle-layer@1.30.6) - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/pl-deployments': 3.0.7 - '@milaboratories/pl-drivers': 1.16.0 - '@milaboratories/pl-errors': 1.4.22 + '@milaboratories/pl-middle-layer@1.66.19(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)': + dependencies: + '@milaboratories/columns-collection-driver': 0.2.3 + '@milaboratories/computable': 2.9.8 + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pf-driver': 1.9.0(@bytecodealliance/preview2-shim@0.17.8) + '@milaboratories/pf-spec-driver': 1.5.0(@bytecodealliance/preview2-shim@0.17.8) + '@milaboratories/pframes-rs-node': 1.1.56 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/pl-deployments': 3.0.15 + '@milaboratories/pl-drivers': 1.16.15 + '@milaboratories/pl-errors': 1.4.35 '@milaboratories/pl-http': 1.2.4 - '@milaboratories/pl-model-backend': 1.4.7 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 - '@milaboratories/pl-tree': 1.12.12 + '@milaboratories/pl-model-backend': 1.4.20 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-model-middle-layer': 1.31.0 + '@milaboratories/pl-tree': 1.13.6 '@milaboratories/resolve-helper': 1.1.3 - '@milaboratories/ts-helpers': 1.8.3 - '@platforma-sdk/block-tools': 2.11.0(@types/node@25.0.1) - '@platforma-sdk/model': 1.79.14 - '@platforma-sdk/workflow-tengo': 6.6.3 + '@milaboratories/ts-helpers': 1.8.6 + '@platforma-sdk/block-tools': 2.13.0(@types/node@25.0.1) + '@platforma-sdk/model': 1.81.1 + '@platforma-sdk/workflow-tengo': 6.8.2 canonicalize: 2.1.0 denque: 2.1.0 es-toolkit: 1.43.0 lru-cache: 11.2.4 - quickjs-emscripten: 0.31.0 + quickjs-emscripten: 0.32.0 semver: 7.7.3 undici: 7.16.0 utility-types: 3.11.0 @@ -5853,48 +6041,56 @@ snapshots: - react-native-b4a - supports-color - '@milaboratories/pl-model-backend@1.4.7': + '@milaboratories/pl-model-backend@1.4.20': dependencies: - '@milaboratories/pl-client': 3.11.4 + '@milaboratories/pl-client': 3.14.6 canonicalize: 2.1.0 zod: 3.25.76 - '@milaboratories/pl-model-common@1.46.1': + '@milaboratories/pl-model-common@1.46.2': dependencies: '@milaboratories/helpers': 1.14.2 '@milaboratories/pl-error-like': 1.12.10 canonicalize: 2.1.0 zod: 3.25.76 - '@milaboratories/pl-model-middle-layer@1.30.5': + '@milaboratories/pl-model-common@1.47.3': + dependencies: + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pl-error-like': 1.12.10 + canonicalize: 2.1.0 + es-toolkit: 1.43.0 + zod: 3.25.76 + + '@milaboratories/pl-model-middle-layer@1.30.7': dependencies: '@milaboratories/helpers': 1.14.2 - '@milaboratories/pl-model-common': 1.46.1 + '@milaboratories/pl-model-common': 1.46.2 es-toolkit: 1.43.0 utility-types: 3.11.0 zod: 3.25.76 - '@milaboratories/pl-model-middle-layer@1.30.6': + '@milaboratories/pl-model-middle-layer@1.31.0': dependencies: - '@milaboratories/helpers': 1.14.2 - '@milaboratories/pl-model-common': 1.46.1 + '@milaboratories/helpers': 1.14.5 + '@milaboratories/pl-model-common': 1.47.3 es-toolkit: 1.43.0 utility-types: 3.11.0 zod: 3.25.76 - '@milaboratories/pl-tree@1.12.12': + '@milaboratories/pl-tree@1.13.6': dependencies: - '@milaboratories/computable': 2.9.5 - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/pl-errors': 1.4.22 - '@milaboratories/ts-helpers': 1.8.3 + '@milaboratories/computable': 2.9.8 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/pl-errors': 1.4.35 + '@milaboratories/ts-helpers': 1.8.6 denque: 2.1.0 utility-types: 3.11.0 zod: 3.25.76 - '@milaboratories/ptabler-expression-js@1.2.30': + '@milaboratories/ptabler-expression-js@1.2.37': dependencies: - '@platforma-open/milaboratories.software-ptabler.schema': 1.15.14 + '@platforma-open/milaboratories.software-ptabler.schema': 1.15.21 '@milaboratories/resolve-helper@1.1.3': {} @@ -5902,17 +6098,17 @@ snapshots: '@milaboratories/tengo-tester@1.6.4': {} - '@milaboratories/ts-builder@1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.24(typescript@5.9.3))(yaml@2.8.2)': + '@milaboratories/ts-builder@1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.24(typescript@5.9.3))(yaml@2.8.2)': dependencies: - '@milaboratories/ts-configs': 1.2.3 + '@milaboratories/ts-configs': 1.4.0 '@vitejs/plugin-vue': 6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.24(typescript@5.9.3)) - commander: 12.1.0 + commander: 15.0.0 jsonc-parser: 3.3.1 oxfmt: 0.35.0 oxlint: 1.63.0 oxlint-plugin-eslint: 1.63.0 - rolldown: 1.0.2 - rolldown-plugin-dts: 0.23.2(rolldown@1.0.2)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)) + rolldown: 1.2.4 + rolldown-plugin-dts: 0.26.0(rolldown@1.2.4)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)) rollup-plugin-copy: 3.5.0 rollup-plugin-sourcemaps2: 0.5.6(@types/node@25.0.1)(rollup@4.53.3) typescript: 5.9.3 @@ -5943,17 +6139,17 @@ snapshots: - vue - yaml - '@milaboratories/ts-builder@1.5.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2)': + '@milaboratories/ts-builder@1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.6.3))(yaml@2.8.2)': dependencies: - '@milaboratories/ts-configs': 1.2.3 - '@vitejs/plugin-vue': 6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) - commander: 12.1.0 + '@milaboratories/ts-configs': 1.4.0 + '@vitejs/plugin-vue': 6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.6.3)) + commander: 15.0.0 jsonc-parser: 3.3.1 oxfmt: 0.35.0 oxlint: 1.63.0 oxlint-plugin-eslint: 1.63.0 - rolldown: 1.0.2 - rolldown-plugin-dts: 0.23.2(rolldown@1.0.2)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)) + rolldown: 1.2.4 + rolldown-plugin-dts: 0.26.0(rolldown@1.2.4)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)) rollup-plugin-copy: 3.5.0 rollup-plugin-sourcemaps2: 0.5.6(@types/node@25.0.1)(rollup@4.53.3) typescript: 5.9.3 @@ -5984,23 +6180,59 @@ snapshots: - vue - yaml - '@milaboratories/ts-configs@1.2.3': {} - - '@milaboratories/ts-helpers-oclif@1.1.42': + '@milaboratories/ts-builder@1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.25(typescript@5.9.3))(yaml@2.8.2)': dependencies: - '@milaboratories/ts-helpers': 1.8.3 - '@oclif/core': 4.8.0 + '@milaboratories/ts-configs': 1.4.0 + '@vitejs/plugin-vue': 6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) + commander: 15.0.0 + jsonc-parser: 3.3.1 + oxfmt: 0.35.0 + oxlint: 1.63.0 + oxlint-plugin-eslint: 1.63.0 + rolldown: 1.2.4 + rolldown-plugin-dts: 0.26.0(rolldown@1.2.4)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)) + rollup-plugin-copy: 3.5.0 + rollup-plugin-sourcemaps2: 0.5.6(@types/node@25.0.1)(rollup@4.53.3) + typescript: 5.9.3 + vite: 8.0.14(@types/node@25.0.1)(yaml@2.8.2) + vite-plugin-commonjs: 0.10.4 + vite-plugin-dts: 4.5.4(@types/node@25.0.1)(rollup@4.53.3)(typescript@5.9.3)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) + vite-plugin-externalize-deps: 0.10.0(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) + vite-plugin-lib-inject-css: 2.2.2(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) + vue-tsc: 3.3.5(typescript@5.9.3) + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - esbuild + - jiti + - less + - oxc-resolver + - oxlint-tsgolint + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - vue + - yaml - '@milaboratories/ts-helpers@1.8.3': + '@milaboratories/ts-configs@1.4.0': {} + + '@milaboratories/ts-helpers@1.8.6': dependencies: - '@milaboratories/helpers': 1.14.2 + '@milaboratories/helpers': 1.14.5 canonicalize: 2.1.0 denque: 2.1.0 - '@milaboratories/uikit@2.15.8(typescript@5.9.3)': + '@milaboratories/uikit@2.15.23(typescript@5.9.3)': dependencies: - '@milaboratories/helpers': 1.14.2 - '@platforma-sdk/model': 1.79.14 + '@milaboratories/helpers': 1.14.5 + '@platforma-sdk/model': 1.81.1 '@types/d3-array': 3.2.2 '@types/d3-axis': 3.0.6 '@types/d3-scale': 4.0.9 @@ -6054,31 +6286,12 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@oclif/core@4.8.0': - dependencies: - ansi-escapes: 4.3.2 - ansis: 3.17.0 - clean-stack: 3.0.1 - cli-spinners: 2.9.2 - debug: 4.4.3(supports-color@8.1.1) - ejs: 3.1.10 - get-package-type: 0.1.0 - indent-string: 4.0.0 - is-wsl: 2.2.0 - lilconfig: 3.1.3 - minimatch: 9.0.5 - semver: 7.7.3 - string-width: 4.2.3 - supports-color: 8.1.1 - tinyglobby: 0.2.16 - widest-line: 3.1.0 - wordwrap: 1.0.0 - wrap-ansi: 7.0.0 - '@one-ini/wasm@0.1.1': {} '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.144.0': {} + '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -6310,13 +6523,13 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@platforma-open/milaboratories.software-ptabler.schema@1.15.14': + '@platforma-open/milaboratories.software-ptabler.schema@1.15.21': dependencies: - '@milaboratories/pl-model-common': 1.46.1 + '@milaboratories/pl-model-common': 1.47.3 - '@platforma-open/milaboratories.software-ptabler@2.1.2': {} + '@platforma-open/milaboratories.software-ptabler@2.1.8': {} - '@platforma-open/milaboratories.software-ptexter@1.2.2': {} + '@platforma-open/milaboratories.software-ptexter@1.2.4': {} '@platforma-open/milaboratories.software-small-binaries.hello-world-py@1.0.10': {} @@ -6336,20 +6549,21 @@ snapshots: '@platforma-open/milaboratories.software-small-binaries.mnz-client': 1.6.5 '@platforma-open/milaboratories.software-small-binaries.table-converter': 1.3.5 - '@platforma-sdk/block-tools@2.11.0(@types/node@25.0.1)': + '@platforma-sdk/block-tools@2.13.0(@types/node@25.0.1)': dependencies: + '@aws-sdk/client-ecr-public': 3.859.0 '@aws-sdk/client-s3': 3.859.0 '@inquirer/prompts': 7.10.1(@types/node@25.0.1) '@milaboratories/pl-http': 1.2.4 - '@milaboratories/pl-model-backend': 1.4.7 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 + '@milaboratories/pl-model-backend': 1.4.20 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-model-middle-layer': 1.31.0 '@milaboratories/resolve-helper': 1.1.3 - '@milaboratories/ts-helpers': 1.8.3 - '@milaboratories/ts-helpers-oclif': 1.1.42 - '@oclif/core': 4.8.0 + '@milaboratories/ts-helpers': 1.8.6 '@platforma-sdk/blocks-deps-updater': 2.2.0 + '@platforma-sdk/package-builder-lib': 1.3.0 canonicalize: 2.1.0 + commander: 15.0.0 lru-cache: 11.2.4 mime-types: 2.1.35 tar: 7.5.2 @@ -6359,41 +6573,59 @@ snapshots: transitivePeerDependencies: - '@types/node' - aws-crt + - bare-abort-controller + - react-native-b4a '@platforma-sdk/blocks-deps-updater@2.2.0': dependencies: yaml: 2.8.2 - '@platforma-sdk/model@1.79.14': + '@platforma-sdk/model@1.81.1': dependencies: - '@milaboratories/helpers': 1.14.2 + '@milaboratories/helpers': 1.14.5 '@milaboratories/pl-error-like': 1.12.10 - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/pl-model-middle-layer': 1.30.6 - '@milaboratories/ptabler-expression-js': 1.2.30 + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/pl-model-middle-layer': 1.31.0 + '@milaboratories/ptabler-expression-js': 1.2.37 canonicalize: 2.1.0 es-toolkit: 1.43.0 fast-json-patch: 3.1.1 + lru-cache: 11.2.4 utility-types: 3.11.0 zod: 3.25.76 - '@platforma-sdk/tengo-builder@4.0.8': + '@platforma-sdk/package-builder-lib@1.3.0': + dependencies: + '@aws-sdk/client-s3': 3.859.0 + '@aws-sdk/lib-storage': 3.859.0(@aws-sdk/client-s3@3.859.0) + '@milaboratories/resolve-helper': 1.1.3 + archiver: 7.0.1 + undici: 7.16.0 + winston: 3.19.0 + yaml: 2.8.2 + zod: 3.25.76 + transitivePeerDependencies: + - aws-crt + - bare-abort-controller + - react-native-b4a + + '@platforma-sdk/tengo-builder@4.0.22': dependencies: - '@milaboratories/pl-model-backend': 1.4.7 + '@milaboratories/pl-model-backend': 1.4.20 '@milaboratories/resolve-helper': 1.1.3 '@milaboratories/tengo-tester': 1.6.4 - '@milaboratories/ts-helpers': 1.8.3 - '@oclif/core': 4.8.0 + '@milaboratories/ts-helpers': 1.8.6 + commander: 15.0.0 winston: 3.19.0 - '@platforma-sdk/test@1.79.14(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2))': + '@platforma-sdk/test@1.81.3(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2))': dependencies: - '@milaboratories/computable': 2.9.5 - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/pl-middle-layer': 1.64.32(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1) - '@milaboratories/pl-tree': 1.12.12 - '@platforma-sdk/model': 1.79.14 - '@vitest/coverage-istanbul': 4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) + '@milaboratories/computable': 2.9.8 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/pl-middle-layer': 1.66.19(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1) + '@milaboratories/pl-tree': 1.13.6 + '@platforma-sdk/model': 1.81.1 + '@vitest/coverage-istanbul': 4.1.7(vitest@4.1.7) vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) transitivePeerDependencies: - '@bytecodealliance/preview2-shim' @@ -6416,13 +6648,13 @@ snapshots: - supports-color - vite - '@platforma-sdk/test@1.79.14(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))': + '@platforma-sdk/test@1.81.3(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))': dependencies: - '@milaboratories/computable': 2.9.5 - '@milaboratories/pl-client': 3.11.4 - '@milaboratories/pl-middle-layer': 1.64.32(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1) - '@milaboratories/pl-tree': 1.12.12 - '@platforma-sdk/model': 1.79.14 + '@milaboratories/computable': 2.9.8 + '@milaboratories/pl-client': 3.14.6 + '@milaboratories/pl-middle-layer': 1.66.19(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1) + '@milaboratories/pl-tree': 1.13.6 + '@platforma-sdk/model': 1.81.1 '@vitest/coverage-istanbul': 4.1.7(vitest@4.1.7) vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) transitivePeerDependencies: @@ -6446,12 +6678,13 @@ snapshots: - supports-color - vite - '@platforma-sdk/ui-vue@1.79.14(@bytecodealliance/preview2-shim@0.17.8)(typescript@5.9.3)': + '@platforma-sdk/ui-vue@1.81.1(@bytecodealliance/preview2-shim@0.17.8)(typescript@5.9.3)': dependencies: - '@milaboratories/pf-spec-driver': 1.4.13(@bytecodealliance/preview2-shim@0.17.8) - '@milaboratories/pl-model-common': 1.46.1 - '@milaboratories/uikit': 2.15.8(typescript@5.9.3) - '@platforma-sdk/model': 1.79.14 + '@milaboratories/columns-collection-driver': 0.2.3 + '@milaboratories/pf-spec-driver': 1.5.0(@bytecodealliance/preview2-shim@0.17.8) + '@milaboratories/pl-model-common': 1.47.3 + '@milaboratories/uikit': 2.15.23(typescript@5.9.3) + '@platforma-sdk/model': 1.81.1 '@types/d3-format': 3.0.4 '@types/node': 24.5.2 '@types/semver': 7.7.1 @@ -6482,17 +6715,17 @@ snapshots: - typescript - universal-cookie - '@platforma-sdk/workflow-tengo@6.6.3': + '@platforma-sdk/workflow-tengo@6.8.2': dependencies: - '@milaboratories/pframes-rs-wasip2': 1.1.50 + '@milaboratories/pframes-rs-wasip2': 1.1.56 '@milaboratories/software-pframes-conv': 2.2.9 - '@platforma-open/milaboratories.software-ptabler': 2.1.2 - '@platforma-open/milaboratories.software-ptexter': 1.2.2 + '@platforma-open/milaboratories.software-ptabler': 2.1.8 + '@platforma-open/milaboratories.software-ptexter': 1.2.4 '@platforma-open/milaboratories.software-small-binaries': 2.1.1 - '@protobuf-ts/grpc-transport@2.11.1(@grpc/grpc-js@1.13.4)': + '@protobuf-ts/grpc-transport@2.11.1(@grpc/grpc-js@1.14.4)': dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.4 '@protobuf-ts/runtime': 2.11.1 '@protobuf-ts/runtime-rpc': 2.11.1 @@ -6519,61 +6752,94 @@ snapshots: '@protobufjs/base64@1.1.2': {} - '@protobufjs/codegen@2.0.4': {} + '@protobufjs/codegen@2.0.5': {} - '@protobufjs/eventemitter@1.1.0': {} + '@protobufjs/eventemitter@1.1.1': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.0': {} - '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.2': {} '@rolldown/binding-android-arm64@1.0.2': optional: true + '@rolldown/binding-android-arm64@1.2.4': + optional: true + '@rolldown/binding-darwin-arm64@1.0.2': optional: true + '@rolldown/binding-darwin-arm64@1.2.4': + optional: true + '@rolldown/binding-darwin-x64@1.0.2': optional: true + '@rolldown/binding-darwin-x64@1.2.4': + optional: true + '@rolldown/binding-freebsd-x64@1.0.2': optional: true + '@rolldown/binding-freebsd-x64@1.2.4': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-arm64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.2': optional: true + '@rolldown/binding-linux-arm64-musl@1.2.4': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.2': optional: true + '@rolldown/binding-linux-s390x-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-x64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.2': optional: true + '@rolldown/binding-linux-x64-musl@1.2.4': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.2': optional: true + '@rolldown/binding-openharmony-arm64@1.2.4': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.2': dependencies: '@emnapi/core': 1.10.0 @@ -6584,9 +6850,15 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.2': optional: true + '@rolldown/binding-win32-arm64-msvc@1.2.4': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.2': optional: true + '@rolldown/binding-win32-x64-msvc@1.2.4': + optional: true + '@rolldown/pluginutils@1.0.0-rc.2': {} '@rolldown/pluginutils@1.0.1': {} @@ -7120,7 +7392,7 @@ snapshots: '@typescript/vfs@1.6.2(typescript@5.4.5)': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -7131,27 +7403,17 @@ snapshots: vite: 8.0.14(@types/node@25.0.1)(yaml@2.8.2) vue: 3.5.24(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.6.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 vite: 8.0.14(@types/node@25.0.1)(yaml@2.8.2) - vue: 3.5.25(typescript@5.9.3) + vue: 3.5.25(typescript@5.6.3) - '@vitest/coverage-istanbul@4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2))': + '@vitejs/plugin-vue@6.0.5(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))': dependencies: - '@babel/core': 7.29.7 - '@istanbuljs/schema': 0.1.3 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.3 - obug: 2.1.1 - tinyrainbow: 3.1.0 - vitest: 4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color + '@rolldown/pluginutils': 1.0.0-rc.2 + vite: 8.0.14(@types/node@25.0.1)(yaml@2.8.2) + vue: 3.5.25(typescript@5.9.3) '@vitest/coverage-istanbul@4.1.7(vitest@4.1.7)': dependencies: @@ -7165,7 +7427,7 @@ snapshots: magicast: 0.5.3 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) + vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -7395,6 +7657,12 @@ snapshots: '@vue/shared': 3.5.24 vue: 3.5.24(typescript@5.9.3) + '@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.6.3))': + dependencies: + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + vue: 3.5.25(typescript@5.6.3) + '@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.9.3))': dependencies: '@vue/compiler-ssr': 3.5.25 @@ -7437,6 +7705,10 @@ snapshots: abbrev@3.0.1: {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn@8.15.0: {} ag-charts-community@12.1.2: @@ -7508,10 +7780,6 @@ snapshots: ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -7522,7 +7790,28 @@ snapshots: ansi-styles@6.2.3: {} - ansis@3.17.0: {} + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a argparse@1.0.10: dependencies: @@ -7544,9 +7833,9 @@ snapshots: assertion-error@2.0.1: {} - ast-kit@3.0.0-beta.1: + ast-kit@3.0.0: dependencies: - '@babel/parser': 8.0.0-rc.3 + '@babel/parser': 8.0.4 estree-walker: 3.0.3 pathe: 2.0.3 @@ -7663,13 +7952,25 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} + buffer-fill@1.0.0: {} + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buildcheck@0.0.7: optional: true @@ -7708,12 +8009,6 @@ snapshots: ci-info@3.9.0: {} - clean-stack@3.0.1: - dependencies: - escape-string-regexp: 4.0.0 - - cli-spinners@2.9.2: {} - cli-width@4.1.0: {} cliui@8.0.1: @@ -7747,14 +8042,22 @@ snapshots: commander@10.0.1: {} - commander@12.1.0: {} - commander@14.0.3: {} + commander@15.0.0: {} + commander@2.20.3: {} compare-versions@6.1.1: {} + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -7778,6 +8081,13 @@ snapshots: nan: 2.24.0 optional: true + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -7828,11 +8138,9 @@ snapshots: de-indent@1.0.2: {} - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 decompress-response@6.0.0: dependencies: @@ -7896,7 +8204,7 @@ snapshots: dependencies: path-type: 4.0.0 - dts-resolver@2.1.3: {} + dts-resolver@3.0.0: {} dunder-proto@1.0.1: dependencies: @@ -7913,10 +8221,6 @@ snapshots: minimatch: 9.0.1 semver: 7.7.3 - ejs@3.1.10: - dependencies: - jake: 10.9.4 - electron-to-chromium@1.5.267: {} emoji-regex@8.0.0: {} @@ -7981,8 +8285,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@4.0.0: {} - esprima@4.0.1: {} estree-walker@2.0.2: {} @@ -7991,12 +8293,16 @@ snapshots: dependencies: '@types/estree': 1.0.8 + event-target-shim@5.0.1: {} + events-universal@1.0.1: dependencies: bare-events: 2.8.2 transitivePeerDependencies: - bare-abort-controller + events@3.3.0: {} + execa@1.0.0: dependencies: cross-spawn: 6.0.6 @@ -8059,10 +8365,6 @@ snapshots: file-uri-to-path@1.0.0: {} - filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -8127,8 +8429,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-package-type@0.1.0: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -8143,7 +8443,7 @@ snapshots: dependencies: pump: 3.0.3 - get-tsconfig@4.14.0: + get-tsconfig@5.0.0-beta.5: dependencies: resolve-pkg-maps: 1.0.0 @@ -8218,7 +8518,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -8236,8 +8536,6 @@ snapshots: import-lazy@4.0.0: {} - indent-string@4.0.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -8257,8 +8555,6 @@ snapshots: dependencies: hasown: 2.0.2 - is-docker@2.2.1: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -8287,10 +8583,6 @@ snapshots: is-windows@1.0.2: {} - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - isarray@1.0.0: {} isarray@2.0.5: {} @@ -8316,12 +8608,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jake@10.9.4: - dependencies: - async: 3.2.6 - filelist: 1.0.4 - picocolors: 1.1.1 - jju@1.4.0: {} js-beautify@1.15.4: @@ -8369,6 +8655,10 @@ snapshots: kuler@2.0.0: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + lightningcss-android-arm64@1.32.0: optional: true @@ -8418,8 +8708,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - lilconfig@3.1.3: {} - local-pkg@1.1.2: dependencies: mlly: 1.8.0 @@ -8568,6 +8856,8 @@ snapshots: dependencies: abbrev: 3.0.1 + normalize-path@3.0.0: {} + npm-run-path@2.0.2: dependencies: path-key: 2.0.1 @@ -8576,6 +8866,8 @@ snapshots: obug@2.1.1: {} + obug@2.1.4: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8772,20 +9064,21 @@ snapshots: process-nextick-args@2.0.1: {} + process@0.11.10: {} + proto-list@1.2.4: {} - protobufjs@7.5.4: + protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 + '@protobufjs/utf8': 1.1.2 '@types/node': 25.0.1 long: 5.3.2 @@ -8806,17 +9099,17 @@ snapshots: queue-microtask@1.2.3: {} - quickjs-emscripten-core@0.31.0: + quickjs-emscripten-core@0.32.0: dependencies: - '@jitl/quickjs-ffi-types': 0.31.0 + '@jitl/quickjs-ffi-types': 0.32.0 - quickjs-emscripten@0.31.0: + quickjs-emscripten@0.32.0: dependencies: - '@jitl/quickjs-wasmfile-debug-asyncify': 0.31.0 - '@jitl/quickjs-wasmfile-debug-sync': 0.31.0 - '@jitl/quickjs-wasmfile-release-asyncify': 0.31.0 - '@jitl/quickjs-wasmfile-release-sync': 0.31.0 - quickjs-emscripten-core: 0.31.0 + '@jitl/quickjs-wasmfile-debug-asyncify': 0.32.0 + '@jitl/quickjs-wasmfile-debug-sync': 0.32.0 + '@jitl/quickjs-wasmfile-release-asyncify': 0.32.0 + '@jitl/quickjs-wasmfile-release-sync': 0.32.0 + quickjs-emscripten-core: 0.32.0 rc@1.2.8: dependencies: @@ -8848,6 +9141,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + rechoir@0.6.2: dependencies: resolve: 1.22.11 @@ -8872,19 +9177,17 @@ snapshots: reusify@1.1.0: {} - rolldown-plugin-dts@0.23.2(rolldown@1.0.2)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)): + rolldown-plugin-dts@0.26.0(rolldown@1.2.4)(typescript@5.9.3)(vue-tsc@3.3.5(typescript@5.9.3)): dependencies: - '@babel/generator': 8.0.0-rc.3 - '@babel/helper-validator-identifier': 8.0.0-rc.3 - '@babel/parser': 8.0.0-rc.3 - '@babel/types': 8.0.0-rc.3 - ast-kit: 3.0.0-beta.1 + '@babel/generator': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + '@babel/parser': 8.0.4 + ast-kit: 3.0.0 birpc: 4.0.0 - dts-resolver: 2.1.3 - get-tsconfig: 4.14.0 - obug: 2.1.1 - picomatch: 4.0.4 - rolldown: 1.0.2 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.4 + rolldown: 1.2.4 optionalDependencies: typescript: 5.9.3 vue-tsc: 3.3.5(typescript@5.9.3) @@ -8912,6 +9215,26 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.2 '@rolldown/binding-win32-x64-msvc': 1.0.2 + rolldown@1.2.4: + dependencies: + '@oxc-project/types': 0.144.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 + rollup-plugin-copy@3.5.0: dependencies: '@types/fs-extra': 8.1.5 @@ -9064,6 +9387,11 @@ snapshots: std-env@4.1.0: {} + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -9268,8 +9596,6 @@ snapshots: tweetnacl@0.14.5: {} - type-fest@0.21.3: {} - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -9280,6 +9606,8 @@ snapshots: typescript@5.4.5: {} + typescript@5.6.3: {} + typescript@5.8.2: {} typescript@5.9.3: {} @@ -9334,7 +9662,7 @@ snapshots: '@volar/typescript': 2.4.28 '@vue/language-core': 2.2.0(typescript@5.9.3) compare-versions: 6.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 @@ -9451,7 +9779,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.0.1 - '@vitest/coverage-istanbul': 4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) + '@vitest/coverage-istanbul': 4.1.7(vitest@4.1.7) transitivePeerDependencies: - msw @@ -9503,6 +9831,16 @@ snapshots: optionalDependencies: typescript: 5.9.3 + vue@3.5.25(typescript@5.6.3): + dependencies: + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-sfc': 3.5.25 + '@vue/runtime-dom': 3.5.25 + '@vue/server-renderer': 3.5.25(vue@3.5.25(typescript@5.6.3)) + '@vue/shared': 3.5.25 + optionalDependencies: + typescript: 5.6.3 + vue@3.5.25(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.25 @@ -9543,10 +9881,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - widest-line@3.1.0: - dependencies: - string-width: 4.2.3 - winston-transport@4.9.0: dependencies: logform: 2.7.0 @@ -9567,8 +9901,6 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.9.0 - wordwrap@1.0.0: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -9620,4 +9952,10 @@ snapshots: yoctocolors-cjs@2.1.3: {} + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6387dde..fca351a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,16 +6,16 @@ packages: - workflow catalog: - "@platforma-sdk/model": 1.79.14 - "@platforma-sdk/ui-vue": 1.79.14 - "@platforma-sdk/workflow-tengo": 6.6.3 - "@platforma-sdk/block-tools": 2.11.0 - "@platforma-sdk/test": 1.79.14 - "@platforma-sdk/tengo-builder": 4.0.8 - "@platforma-sdk/package-builder": 3.13.0 + "@platforma-sdk/model": 1.81.1 + "@platforma-sdk/ui-vue": 1.81.1 + "@platforma-sdk/workflow-tengo": 6.8.2 + "@platforma-sdk/block-tools": 2.13.0 + "@platforma-sdk/test": 1.81.3 + "@platforma-sdk/tengo-builder": 4.0.22 + "@platforma-sdk/package-builder": 3.15.0 "@platforma-sdk/blocks-deps-updater": 2.2.0 - "@milaboratories/ts-builder": 1.5.2 - "@milaboratories/ts-configs": 1.2.3 + "@milaboratories/ts-builder": 1.6.2 + "@milaboratories/ts-configs": 1.4.0 "@milaboratories/helpers": 1.14.2 "vue": 3.5.24 diff --git a/test/.oxfmtrc.json b/test/.oxfmtrc.json index 7eff5e7..6f74854 100644 --- a/test/.oxfmtrc.json +++ b/test/.oxfmtrc.json @@ -1,4 +1,4 @@ { "extends": ["node_modules/@milaboratories/ts-builder/configs/oxfmt.json"], - "ignorePatterns": ["dist", "coverage", "CHANGELOG.md"] + "ignorePatterns": ["dist", "coverage", "CHANGELOG.md", ".test_auth.json"] } diff --git a/turbo.json b/turbo.json index a0530ea..d079cd3 100644 --- a/turbo.json +++ b/turbo.json @@ -12,13 +12,23 @@ "build": { "dependsOn": ["^build", "check"], "inputs": ["$TURBO_DEFAULT$"], - "env": ["PL_PKG_DEV", "PL_DOCKER_REGISTRY_PUSH_TO"], + "env": [ + "PL_DOCKER_REGISTRY_PUSH_TO", + "PL_BUILD_CHANNEL", + "PL_BUILD_VARIANT", + "PL_BUILD_LOCATION", + "PL_BUILD_USE_PUBLISHED", + "PL_DEV_DOCKER_PUSH_URL", + "PL_DEV_DOCKER_PULL_URL", + "PL_DEV_BINARY_UPLOAD_URL", + "PL_RELEASE_DOCKER_PUSH_URL", + "PL_RELEASE_DOCKER_PULL_URL", + "PL_RELEASE_BINARY_UPLOAD_URL", + "PL_REGISTRY_PLATFORMA_OPEN_UPLOAD_URL" + ], + "passThroughEnv": ["AWS_*", "PL_AWS_*"], "outputs": ["./dist/**", "./block-pack/**", "./pkg-*.tgz"] }, - "build:dev": { - "dependsOn": ["build"], - "outputs": ["./dist/**"] - }, "do-pack": { "dependsOn": ["build"], "outputs": ["package.tgz"] diff --git a/ui/package.json b/ui/package.json index 204294f..1d85a4d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,7 @@ { "name": "@platforma-open/milaboratories.import-vdj.ui", "version": "1.11.3", + "private": true, "type": "module", "scripts": { "dev": "ts-builder serve --target block-ui", diff --git a/workflow/package.json b/workflow/package.json index f5becee..fa490b2 100644 --- a/workflow/package.json +++ b/workflow/package.json @@ -1,6 +1,7 @@ { "name": "@platforma-open/milaboratories.import-vdj.workflow", "version": "1.15.4", + "private": true, "description": "Block Workflow", "type": "module", "scripts": { From 9187c9c1325fdc228ccdb384fac94634b5cac110 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:12:40 +0200 Subject: [PATCH 02/54] MILAB-6720: fix author code for the SDK 1.81 / facade upgrade Four fixes the structure refresh could not make itself. - model: createPlDataTableV2 now requires PColumn[], while PColumnCollection.getColumns() is typed with data possibly undefined. Narrow with a type guard. Runtime behaviour unchanged: without `dontWaitAllData` getColumns returns undefined for the whole request when any data is incomplete, so a returned array already has data everywhere. - model/ui: export the model as `platforma`, not `model`. The generated block facade (block/src/index.ts, structurer-owned) imports that name, and it is the name every V3 block uses. Renamed rather than aliased to avoid two names for one value. - catalog: pin @milaboratories/helpers to 1.14.5 to match what @platforma-sdk/model 1.81.1 depends on. With 1.14.2 two copies resolved and the model's inferred type referenced the non-direct one, failing TS2742. - package.json: restore packageManager pnpm@9.14.4. The refresh reverted it to 9.12.0, undoing c93ad7e ("Set pnpm to Platforma version") and leaving root disagreeing with block/package.json, which still said 9.14.4. --- model/src/index.ts | 23 +++++++++++++++++++---- package.json | 2 +- pnpm-lock.yaml | 30 +++++++++++++++++++++++------- pnpm-workspace.yaml | 4 +++- ui/src/app.ts | 4 ++-- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/model/src/index.ts b/model/src/index.ts index 54c80eb..ba57ee1 100644 --- a/model/src/index.ts +++ b/model/src/index.ts @@ -1,4 +1,10 @@ -import type { InferOutputsType, PlDataTableStateV2, PlRef } from "@platforma-sdk/model"; +import type { + InferOutputsType, + PColumn, + PColumnDataUniversal, + PlDataTableStateV2, + PlRef, +} from "@platforma-sdk/model"; import { BlockModel, createPlDataTableStateV2, @@ -40,7 +46,9 @@ export type ColumnDescription = { description: string; }; -export const model = BlockModel.create() +// Named `platforma` because the structurer-generated block facade +// (block/src/index.ts) imports that name. Every V3 block uses it too. +export const platforma = BlockModel.create() .withArgs({ defaultBlockLabel: "", @@ -283,7 +291,14 @@ export const model = BlockModel.create() return undefined; } - const withLabels = new PColumnCollection().addColumns(pCols).getColumns(() => true) ?? []; + // SDK 1.81: getColumns() is typed PColumn[]. + // Without `dontWaitAllData` it returns undefined for the whole request when any + // column's data is incomplete, so a returned array already has data everywhere — + // the filter narrows the type and is a runtime no-op. Behaviour is unchanged from + // the previous `?? []`. + const withLabels = ( + new PColumnCollection().addColumns(pCols).getColumns(() => true) ?? [] + ).filter((c): c is PColumn => c.data !== undefined); return createPlDataTableV2(ctx, withLabels, ctx.uiState.tableState); }) @@ -296,4 +311,4 @@ export const model = BlockModel.create() .done(2); -export type BlockOutputs = InferOutputsType; +export type BlockOutputs = InferOutputsType; diff --git a/package.json b/package.json index 3305d7f..a9901f2 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "oxfmt": "*", "oxlint": "*" }, - "packageManager": "pnpm@9.12.0", + "packageManager": "pnpm@9.14.4", "pnpm": { "overrides": {} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b060b2b..f974810 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,8 +10,8 @@ catalogs: specifier: ^2.29.6 version: 2.29.8 '@milaboratories/helpers': - specifier: 1.14.2 - version: 1.14.2 + specifier: 1.14.5 + version: 1.14.5 '@milaboratories/ts-builder': specifier: 1.6.2 version: 1.6.2 @@ -113,7 +113,7 @@ importers: dependencies: '@milaboratories/helpers': specifier: 'catalog:' - version: 1.14.2 + version: 1.14.5 '@platforma-sdk/model': specifier: 'catalog:' version: 1.81.1 @@ -179,7 +179,7 @@ importers: devDependencies: '@milaboratories/helpers': specifier: 'catalog:' - version: 1.14.2 + version: 1.14.5 '@milaboratories/ts-builder': specifier: 'catalog:' version: 1.6.2(@types/node@25.0.1)(rollup@4.53.3)(vue@3.5.24(typescript@5.9.3))(yaml@2.8.2) @@ -6625,7 +6625,7 @@ snapshots: '@milaboratories/pl-middle-layer': 1.66.19(@bytecodealliance/preview2-shim@0.17.8)(@types/node@25.0.1) '@milaboratories/pl-tree': 1.13.6 '@platforma-sdk/model': 1.81.1 - '@vitest/coverage-istanbul': 4.1.7(vitest@4.1.7) + '@vitest/coverage-istanbul': 4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) transitivePeerDependencies: - '@bytecodealliance/preview2-shim' @@ -7415,6 +7415,22 @@ snapshots: vite: 8.0.14(@types/node@25.0.1)(yaml@2.8.2) vue: 3.5.25(typescript@5.9.3) + '@vitest/coverage-istanbul@4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.29.7 + '@istanbuljs/schema': 0.1.3 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + tinyrainbow: 3.1.0 + vitest: 4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-istanbul@4.1.7(vitest@4.1.7)': dependencies: '@babel/core': 7.29.7 @@ -7427,7 +7443,7 @@ snapshots: magicast: 0.5.3 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@8.0.14(@types/node@25.0.1)(yaml@2.8.2)) + vitest: 4.1.7(@types/node@25.0.1)(@vitest/coverage-istanbul@4.1.7)(vite@7.2.7(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -9779,7 +9795,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.0.1 - '@vitest/coverage-istanbul': 4.1.7(vitest@4.1.7) + '@vitest/coverage-istanbul': 4.1.7(vitest@4.0.18(@types/node@25.0.1)(lightningcss@1.32.0)(yaml@2.8.2)) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fca351a..f45c525 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,9 @@ catalog: "@platforma-sdk/blocks-deps-updater": 2.2.0 "@milaboratories/ts-builder": 1.6.2 "@milaboratories/ts-configs": 1.4.0 - "@milaboratories/helpers": 1.14.2 + # Must match the version @platforma-sdk/model depends on, otherwise the model's + # inferred type references a non-direct copy and the build fails TS2742. + "@milaboratories/helpers": 1.14.5 "vue": 3.5.24 diff --git a/ui/src/app.ts b/ui/src/app.ts index c3b80e3..3e42e37 100644 --- a/ui/src/app.ts +++ b/ui/src/app.ts @@ -1,9 +1,9 @@ -import { model } from "@platforma-open/milaboratories.import-vdj.model"; +import { platforma } from "@platforma-open/milaboratories.import-vdj.model"; import { defineApp } from "@platforma-sdk/ui-vue"; import MainPage from "./pages/MainPage.vue"; import { watch } from "vue"; -export const sdkPlugin = defineApp(model, () => { +export const sdkPlugin = defineApp(platforma, () => { return { routes: { "/": () => MainPage, From 88c20b95d6cfb3a6c0644a048156645d7a8ccaf8 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:13:01 +0200 Subject: [PATCH 03/54] MILAB-6720: changeset for the SDK 1.81 upgrade --- .changeset/sdk-181-structure-refresh.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .changeset/sdk-181-structure-refresh.md diff --git a/.changeset/sdk-181-structure-refresh.md b/.changeset/sdk-181-structure-refresh.md new file mode 100644 index 0000000..e04391f --- /dev/null +++ b/.changeset/sdk-181-structure-refresh.md @@ -0,0 +1,19 @@ +--- +'@platforma-open/milaboratories.import-vdj.workflow': minor +'@platforma-open/milaboratories.import-vdj.model': minor +'@platforma-open/milaboratories.import-vdj.ui': minor +'@platforma-open/milaboratories.import-vdj': minor +--- + +Upgrade the SDK and refresh the block structure + +Catalog moves to `@platforma-sdk/model`/`ui-vue` 1.81.1, `workflow-tengo` 6.8.2, +`block-tools` 2.13.0, `tengo-builder` 4.0.22, `package-builder` 3.15.0, `test` 1.81.3. +`block/` becomes the slim facade (bundled `dist/` + `block-pack/`, `ImportVdjBlockPointer` +export), and the root build scripts move from `PL_PKG_DEV` to +`PL_BUILD_CHANNEL`/`PL_BUILD_VARIANT`/`PL_BUILD_LOCATION` — `build:dev` is now +`build:dev-local`. + +Author-visible change: the model is exported as `platforma` instead of `model`, matching +the generated facade and every V3 block. No behaviour change to import, column emission or +the block's outputs. From f653c1c0d2cddd9ee3b8196910f31a3af3000496 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:29:33 +0200 Subject: [PATCH 04/54] MILAB-6720: locate FR/CDR regions at import with ANARCI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the region-annotation capability as a standalone, callable unit. Nothing in the import pipeline invokes it yet — that lands with the bare-set emission path. software/region-annotation/ — python, two entrypoints: fasta.py one FASTA record per (key, chain) that carries a sequence main.py projects ANARCI numbering onto region ranges, emits regions + status regions.py holds the pure logic and the per-scheme range table workflow/src/annotate-regions.tpl.tengo — fasta -> ANARCI --scheme -> regions. Four things differ deliberately from redefine-clonotypes' anarci-numbering, which this is forked from: - All seven regions, FR4 included. The three region lists in the workspace are each short in a different way and FR4 is the one missing everywhere. - A per-chain status column with a closed three-member enum, never null. The block this is forked from gives an unnumbered record empty region strings, which reads downstream as a located region that happens to be empty. - Chain vocabulary is A/B (declared by the mapping slot), not H/KL (inferred by ANARCI). Lookup keys on the full "key|chain" FASTA id across both buckets, so the two chains of one paired record cannot collide, and a record whose declared chain disagrees with ANARCI's bucket is annotated under the declared chain using the bucket's range table. Reconciling declared against inferred is out of scope. - Amino acid only; a bare set carries no nucleotide variable domain. Both ANARCI bucket CSVs are pre-created with an Id header before the run so the exec can always save both, following the same trick in antibody-tcr-lead-selection/workflow/src/assembling-fasta.tpl.tengo. Verified by software/region-annotation/test_region_annotation.py against synthetic ANARCI CSVs: region arithmetic per bucket, all four status paths, single-chain sets, and header-only buckets. Not in CI — it needs python with polars, which the node test lane does not provide. --- .changeset/region-annotation-at-import.md | 18 ++ pnpm-lock.yaml | 115 +++++++++++ pnpm-workspace.yaml | 7 + software/region-annotation/package.json | 60 ++++++ .../src/__pycache__/chains.cpython-312.pyc | Bin 0 -> 2790 bytes .../src/__pycache__/regions.cpython-312.pyc | Bin 0 -> 5915 bytes software/region-annotation/src/chains.py | 54 +++++ software/region-annotation/src/fasta.py | 59 ++++++ software/region-annotation/src/main.py | 145 ++++++++++++++ software/region-annotation/src/regions.py | 188 ++++++++++++++++++ .../region-annotation/src/requirements.txt | 1 + .../test_region_annotation.py | 167 ++++++++++++++++ workflow/package.json | 2 + workflow/src/annotate-regions.tpl.tengo | 100 ++++++++++ 14 files changed, 916 insertions(+) create mode 100644 .changeset/region-annotation-at-import.md create mode 100644 software/region-annotation/package.json create mode 100644 software/region-annotation/src/__pycache__/chains.cpython-312.pyc create mode 100644 software/region-annotation/src/__pycache__/regions.cpython-312.pyc create mode 100644 software/region-annotation/src/chains.py create mode 100644 software/region-annotation/src/fasta.py create mode 100644 software/region-annotation/src/main.py create mode 100644 software/region-annotation/src/regions.py create mode 100644 software/region-annotation/src/requirements.txt create mode 100644 software/region-annotation/test_region_annotation.py create mode 100644 workflow/src/annotate-regions.tpl.tengo diff --git a/.changeset/region-annotation-at-import.md b/.changeset/region-annotation-at-import.md new file mode 100644 index 0000000..063ddd2 --- /dev/null +++ b/.changeset/region-annotation-at-import.md @@ -0,0 +1,18 @@ +--- +'@platforma-open/milaboratories.import-vdj.region-annotation': minor +'@platforma-open/milaboratories.import-vdj.workflow': minor +'@platforma-open/milaboratories.import-vdj': minor +--- + +Add ANARCI region annotation to the import block + +A new `region-annotation` software package and an `annotate-regions` template locate FR1–FR4 +and CDR1–CDR3 in amino-acid antibody variable domains, with the numbering scheme (`imgt`, +`kabat`, `chothia`) as the scientist's choice. ANARCI is the shared published artifact +already used elsewhere in the workspace, reused rather than introduced. + +Each chain of each record also gets a region-annotation status — `Annotated`, +`Not applicable` or `Failed`, never empty — so a record whose boundaries could not be located +says so instead of silently receiving empty region strings. + +Not yet wired into the import pipeline; the template is callable but no path invokes it. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f974810..230fbc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,12 +18,21 @@ catalogs: '@milaboratories/ts-configs': specifier: 1.4.0 version: 1.4.0 + '@platforma-open/milaboratories.runenv-python-3': + specifier: ^1.1.16 + version: 1.11.6 + '@platforma-open/milaboratories.software-anarci': + specifier: ^0.0.3 + version: 0.0.3 '@platforma-sdk/block-tools': specifier: 2.13.0 version: 2.13.0 '@platforma-sdk/model': specifier: 1.81.1 version: 1.81.1 + '@platforma-sdk/package-builder': + specifier: 3.15.0 + version: 3.15.0 '@platforma-sdk/tengo-builder': specifier: 4.0.22 version: 4.0.22 @@ -134,6 +143,15 @@ importers: specifier: 'catalog:' version: 2.13.0(@types/node@25.0.1) + software/region-annotation: + devDependencies: + '@platforma-open/milaboratories.runenv-python-3': + specifier: 'catalog:' + version: 1.11.6 + '@platforma-sdk/package-builder': + specifier: 'catalog:' + version: 3.15.0 + test: dependencies: this-block: @@ -189,6 +207,12 @@ importers: workflow: dependencies: + '@platforma-open/milaboratories.import-vdj.region-annotation': + specifier: workspace:* + version: link:../software/region-annotation + '@platforma-open/milaboratories.software-anarci': + specifier: 'catalog:' + version: 0.0.3 '@platforma-sdk/workflow-tengo': specifier: 'catalog:' version: 6.8.2 @@ -1430,6 +1454,45 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@platforma-open/milaboratories.runenv-python-3.12.10-atls@1.2.7': + resolution: {integrity: sha512-vJiU68NKgyupW5meeWB/pZZ8y5wewPV6/EcRQ2BwUhSyikDQrn83XzBlc/nTu9UmbYmBqQTrt59x0qKtcC0ILw==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-clustering@0.1.1': + resolution: {integrity: sha512-hnr1tf0dwoqTyjtUVZcwQm13O0V8kz4jCUz+yvOgdvWMrbg4o/h1KbghtM8HivI+tRoowc629QJWdy0bc7inQA==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-h5ad@1.1.5': + resolution: {integrity: sha512-cuGk6NMBocvNePoMTnDW4z8eIF5RQdDcgwuPHg9atW9/veOOp02p5jk0EwxmnbMSVV0yDwRMd+0crQAQqW9bCA==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-humanness@0.2.0': + resolution: {integrity: sha512-YjKFPXe+caABLre5oZTwAxFHvVCEb5K8rVTEIUBhFYMtfLipVIix0LXi6LxWoOYdhoEeKgIc4zlWFDKs1xaEoQ==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-parapred@1.1.0': + resolution: {integrity: sha512-nEM4eEj7pFT6yi+P5028qtgK5v9A9H0XdVHDtrKX7xW5Jipc1RIliOEU3gzaH0E7UAzPFQGSqShlwakRcGXwdQ==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-pgen@0.2.0': + resolution: {integrity: sha512-+4pEVwRZaBAm0716NoNdK9cfF4E7jtsRUpcAWYCuPbbZJ3Q7bgfgq8bEhcck8No97l9jWN2rAeJegA9UQy5FgQ==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-rapids@1.7.2': + resolution: {integrity: sha512-rCG+R9WjdopEtEgJcSWNRIWhpo1ABGnA7rPn0BF6ufvLOtKXOGtaGENI7BJlkEaLb2Cbh2XUUgS5l2r5WGhIRw==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-sccoda@1.3.6': + resolution: {integrity: sha512-NAacPe2uFxf5zqDAa70bblP4ZicvA52lVyzhV7IzlyeEIClAsGatLbitMnRghc2TepwYhQY4EbvElkKDFaSSGw==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-scientific-slim@1.1.0': + resolution: {integrity: sha512-CSLEjBYUdHDf66QAfgQ9jo+MoW63Wr0ygdIncJE2siO2jMUzYCqgCNd8bMjaMfZa3YBC9bHca3Cjxbp4VYc54A==} + + '@platforma-open/milaboratories.runenv-python-3.12.10-torch-cuda@0.2.0': + resolution: {integrity: sha512-PRGkgsVY08tQvoucU8fJmeCF+x9XjiHHjNTbxWqddqx2EZpiUvmylkxtU8i8xJW13cesAI+xHv3NsVUXQaRDqA==} + + '@platforma-open/milaboratories.runenv-python-3.12.10@1.3.24': + resolution: {integrity: sha512-5ne8Fmlhu1YKB5RYgq4c/AvYzb+sUY4hzv/bKIj0E3aDgJdMYO6M6lMicDzQTMagrGRu9FIhDewIgo++TX3+Yw==} + + '@platforma-open/milaboratories.runenv-python-3@1.11.6': + resolution: {integrity: sha512-H3IR86pq0T4UOR66BZEYIkMmlhuyAiOnAP3O8y5M6A2C2UH/oCLGVYqrFF7XHWgjmacvZ7Qcz4KDtIxgJpFCMw==} + + '@platforma-open/milaboratories.software-anarci@0.0.3': + resolution: {integrity: sha512-BtzWsu32K/V7Sw3uCS/lzkILoI+JVHYl7mE3PNf0UEj1ZImjlRFuMFbo/t8LpywYTAGdUIwR9cu2i6ayq/hDGQ==} + '@platforma-open/milaboratories.software-ptabler.schema@1.15.21': resolution: {integrity: sha512-3EWO64poe5VcBIgVIdIFHwN7nPi000kPuaRmlVt3xnnQZqXUYDFNZfZ2Mc9/mE1Eq3FLwQxcy/5mng7LvgWnXg==} @@ -1471,6 +1534,10 @@ packages: '@platforma-sdk/package-builder-lib@1.3.0': resolution: {integrity: sha512-CdBjmNo6E1fBxKYWaXa49L/L2WLURxs2f1TAqxLIZlHRE4DZ6E1TEj3jNNKESWp+/9rwtLkTAzmTzNPrDgz+2Q==} + '@platforma-sdk/package-builder@3.15.0': + resolution: {integrity: sha512-YzRiBYAaKvwnUcJgl6JMB3XLJuhlH72L8RZE3CDt2GM8hYRH4zaDzomfC5IHYNodK63RMaduzcvE10ICruI1hQ==} + hasBin: true + '@platforma-sdk/tengo-builder@4.0.22': resolution: {integrity: sha512-8+zDYDFFI2tQS7dplTcxgQdUwzt8+IO++sJiVbwBgwSg1Giyc2qMThEYPLK3QNFArgCsIDR6wQuS8t66pJsRPQ==} engines: {node: '>=22'} @@ -6523,6 +6590,44 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@platforma-open/milaboratories.runenv-python-3.12.10-atls@1.2.7': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-clustering@0.1.1': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-h5ad@1.1.5': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-humanness@0.2.0': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-parapred@1.1.0': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-pgen@0.2.0': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-rapids@1.7.2': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-sccoda@1.3.6': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-scientific-slim@1.1.0': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10-torch-cuda@0.2.0': {} + + '@platforma-open/milaboratories.runenv-python-3.12.10@1.3.24': {} + + '@platforma-open/milaboratories.runenv-python-3@1.11.6': + dependencies: + '@platforma-open/milaboratories.runenv-python-3.12.10': 1.3.24 + '@platforma-open/milaboratories.runenv-python-3.12.10-atls': 1.2.7 + '@platforma-open/milaboratories.runenv-python-3.12.10-clustering': 0.1.1 + '@platforma-open/milaboratories.runenv-python-3.12.10-h5ad': 1.1.5 + '@platforma-open/milaboratories.runenv-python-3.12.10-humanness': 0.2.0 + '@platforma-open/milaboratories.runenv-python-3.12.10-parapred': 1.1.0 + '@platforma-open/milaboratories.runenv-python-3.12.10-pgen': 0.2.0 + '@platforma-open/milaboratories.runenv-python-3.12.10-rapids': 1.7.2 + '@platforma-open/milaboratories.runenv-python-3.12.10-sccoda': 1.3.6 + '@platforma-open/milaboratories.runenv-python-3.12.10-scientific-slim': 1.1.0 + '@platforma-open/milaboratories.runenv-python-3.12.10-torch-cuda': 0.2.0 + + '@platforma-open/milaboratories.software-anarci@0.0.3': {} + '@platforma-open/milaboratories.software-ptabler.schema@1.15.21': dependencies: '@milaboratories/pl-model-common': 1.47.3 @@ -6609,6 +6714,16 @@ snapshots: - bare-abort-controller - react-native-b4a + '@platforma-sdk/package-builder@3.15.0': + dependencies: + '@platforma-sdk/package-builder-lib': 1.3.0 + commander: 15.0.0 + winston: 3.19.0 + transitivePeerDependencies: + - aws-crt + - bare-abort-controller + - react-native-b4a + '@platforma-sdk/tengo-builder@4.0.22': dependencies: '@milaboratories/pl-model-backend': 1.4.20 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f45c525..ec94425 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - block - model + - software/region-annotation - test - ui - workflow @@ -20,6 +21,12 @@ catalog: # inferred type references a non-direct copy and the build fails TS2742. "@milaboratories/helpers": 1.14.5 + # Region annotation. ANARCI is a shared, already-published artifact — reused rather + # than introduced here, per the spec's requirement that the instrument already exist + # in the workspace. Versions follow blocks/redefine-clonotypes, its other consumer. + "@platforma-open/milaboratories.runenv-python-3": ^1.1.16 + "@platforma-open/milaboratories.software-anarci": ^0.0.3 + "vue": 3.5.24 "typescript": ~5.6.3 diff --git a/software/region-annotation/package.json b/software/region-annotation/package.json new file mode 100644 index 0000000..a055bf8 --- /dev/null +++ b/software/region-annotation/package.json @@ -0,0 +1,60 @@ +{ + "name": "@platforma-open/milaboratories.import-vdj.region-annotation", + "version": "1.0.0", + "description": "Locate FR/CDR region boundaries in amino-acid antibody variable domains from ANARCI numbering", + "scripts": { + "build": "pl-pkg build", + "prepublishOnly": "pl-pkg prepublish", + "do-pack": "rm -f *.tgz && pl-pkg build && pnpm pack && mv platforma-open*.tgz package.tgz", + "changeset": "changeset", + "version-packages": "changeset version" + }, + "files": [ + "./dist/**/*" + ], + "dependencies": {}, + "devDependencies": { + "@platforma-sdk/package-builder": "catalog:", + "@platforma-open/milaboratories.runenv-python-3": "catalog:" + }, + "block-software": { + "entrypoints": { + "fasta": { + "binary": { + "artifact": { + "type": "python", + "registry": "platforma-open", + "environment": "@platforma-open/milaboratories.runenv-python-3:3.12.10", + "dependencies": { + "toolset": "pip", + "requirements": "requirements.txt" + }, + "root": "./src" + }, + "cmd": [ + "python", + "{pkg}/fasta.py" + ] + } + }, + "main": { + "binary": { + "artifact": { + "type": "python", + "registry": "platforma-open", + "environment": "@platforma-open/milaboratories.runenv-python-3:3.12.10", + "dependencies": { + "toolset": "pip", + "requirements": "requirements.txt" + }, + "root": "./src" + }, + "cmd": [ + "python", + "{pkg}/main.py" + ] + } + } + } + } +} diff --git a/software/region-annotation/src/__pycache__/chains.cpython-312.pyc b/software/region-annotation/src/__pycache__/chains.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b06003b117fa7f11cc4ef1dd3b79abaaa2c1c569 GIT binary patch literal 2790 zcma)7&2Jk;6yIHcG_@0_ZQ2y*2h)I{DX|Uep{SxD$0};ns+H;#P=#cVcgFE1>z!q0 zHmR#r1kpo|h6|CPBHUWRfg}F|7grow_Ed>~z^I&%ka%y_TT&z>M%ta-`FQhtzxUp6 zex8~tSkPALpRc|?VOhW7PulY1kEeIx<133>Je!8G33kBj0ZU-Zc@a(qiw9w* z&_6UkL;rFttKOwvK-Psz+mTOYpQtXCoRM~)w1w^x9%$JQg%@a5DHN8woV0!6t`QY- zx~530)*@BXT5OTiT~62g@Z)JTTPb=>n5hX3R%)Xohu|$(64wR zP$UW%tV33whqO?@cxbwoY$id}YjcU%7^eLhbN2f&!yH4u{c zjl*7`u8yb=zJ%ZlZdK2S3M!<*IcbfNRlIRi|xfYXHqxLC7J6;C<{ww z;1%1KV(0ReIA{2b^OEaG2CfZLX~VU+^ywtLzOpv0uc5B*ISp&xxm{lh5YRH@+&GcD z!e{6$Vx&DU5M}CmjI2}Xp_^%49Q_*Mvj5LtpQ zo=P^RUL1k36SxLHQZH(Q&fue+#;|y5m^~#Az|8*+_2Pe{H>qqG*~2nXz;*-lZSu7f6k#S5;AzBmou|tW4)FBMX94$4D&xDtSc~n=G|%ul zP0Dd^o=tQp1g7aRc^u|nhi9B(qmng!X#2+C)ZpUa+^~3Tm_3$+Wq*Pewmg?@&w?Yz z!MSBC#|Cj(-77yZhwb~MvsQ7}zZ!GCAMf}WX7AW{oI9CKM;10gOg1wbE7P=fTiX51 zroEZzpnPR-*OmfMd$__eNmI946>{CEpdzQ|<`&4@9B4CK8Z_veYZ9a%@z8UvR~z$LiA5=q+BlTZ#fw)OaVfc# zn~OKBl6(wqk#6mQ&k8?T-eQxk2C5&a3_%QKKPq4`6@5(bUhT&1)!fTYV zZToj;($0=fTE)_~+J0#(|K!N=-E$A8-`JWQ<*k|7fgT*&Dvk1v{qoM)kp`WPfDB@p_Wpfv@|cJ<#{>no%g1F^FC2X3Pk=Jao$h7^8wEbOouAB4_g&ZZl|I4_( zFQxeqm1ubP&qwQN{gS5xj`rr4Hjw^zp~oZ8MjBc<>MCNd{<;Rg79B9(Oa`b-!zhmo zx@buz$No~DKR||P7+Srwp06j92(-44m6_3CaQZG~tJbonCzWLik~LFNm8Gnn;Bt*6MYEuV zC9`R&12PS0P!g)HEK((_Yu1QGO^bs4XpAPT)aq#P1zzTCM7HG%TEepBE1GH9fomBQ zsHSZ1TsD)U5x*^6ym8W&r!K%3;3*DIAspv$9N~mr7u9vcQelbv4YP)&sF_SkOQ?$} zYWps#T8fg0=LOo&i%$3eK8$$K!N(AvcJLX*Q3sz#yzF2Nan`|i5r4|DEnc=ge|jb2 zVNKZB?F{Ha+~?qa#3vnm3MzJpV+M{dytw7G#}O|%IEnZU$Bs;A{0lz5*J;O!pM63lJJc7@{?M-fjr_${bd8^>HU=Y6nQHTjL z@M27eNZJ2_?&xgv#?;I)Q<;jr4~EH@MolG6EtRO2sw9k5Hm#e=lEzGH80}tH44u~6 zl?-J{#xOOsO>hPU%JtgM-Ki>g1^Nq2GnISG1~}C+RSC>AEtP>!+|#UOa2lc|!8L{! zPA&{93#Z`o+x*`t3nx#Wj4nh*x#$~QZzf7hE1C|QN`Y7gqJ}}RmRLsb)3fPC%FrKq zk2CYWbn8Ph8Zodr$UqGo%YJ3{5cS^hxAZkm*tV>Cr| zl``WtIJ!EPVa5tLt2vei2mN?#5oqMLIi{sE2D3))l9dru-I!@CS@+=ZV>Pl-9Y+LB zjhQS_Yc+vV>_$#CaaWNhy9mpp2bkz|dEs|qp{={L^y}7v^*})^y!CCU{^6PRGsTOg z>0gC!#wIas;`tPXP2YHy^U?q4?s+g? zxbyJd`n}?vpWpxaxw0JDn1c_ygyJNS0-mkvNopqc>k++^d-fFopl$d%1^gMfcFmIm zUq#yCo-M7(IVlHz`myIW5HhRF$ykFAAIf#huIRVXoiNzZbEkk{!FB}yzrX?9R=TZ+u_p1N4M64 z1#iLJ4mB00o`t$g<6EI_u({8!zLbP;4_I7FXEh*%n;bp(88tPMrLS2W>VxZG6u=ik zPWV_ffr*H?vdeSOQ7dj%Lp*mmOU>HCBu-|?-?lhni=%LEI2zM35s|$QttN`8-K$SC z<4HBcK7^)^u@B=~Uf2$|e0H_Sz6mLnkg^r(-O$USiLyN5>^cZ4h;vUd?Lh4WW&&4r zO8@{r!h5jR>(}^DN_aH5vX4N;Yf?^13V-c+;FpCpc}?1*X{2D+k>KjD*Dl<<=G*5` zKG$WK_~3xG>s&dn+rH+{`Saof#j0~Rmh7O$gdge?kOSc;wj-pNuu0XtYys0yLPS#NYVo|!>(9wTdkc~gaA z+UM?xd;R|P6w5GbTZ!Uf6n+>(Pp!VAsLCSl%%qqGI?)ye=-EVy8WxJat1=DqGldwK zbGmzXa!cLd+t@ceVPtiSG6*3SEUVOF6DSgXz#$>pF2V=`LaFHlL+(hC0Q(8tlP%rX z9ADx5$7#U!p;mOl{u~F$D~6^=>exr{gJDo|C`M#<9V7=Sh=Y{ab-Vt<{n4SK8!*^J zJ4MCu;xKRhVpdC$xT9c`VaAigq5lA%yzu)@;q>Iz;N(9hpEhsY+3fu)`!t!KDGyG5 z*L`&3_~!DjyH8gForSvM>2E{L#bXc7ZHHUGZRjd#TMa`6X*<+V_!Det?%9ww-rs5- z1{w=B7bc-?JJ4JltOPo~2`H6-vT^9|yT0oB+n!$qPF7olo-@BcD2%s~gRaa$Al4m2m%NvJ!f`EWiEztHZ*vbKk$}6vC%X^sn{c`NQ%r-#v0(k?nwM zMs}SORUi*}jYg1#IgR`Q!2e7mMBJm1eT=!Elt2a+*LlrAPPQ=K@UrBI0p)z^$$9eF z?t8tRM;j!cJtFbud_W~N`D=b_mppR*{|jaC)n8x5Z#C>+b&midp6owCP`HBcFURQ< z%V6t}h1RS>QFU-jbrteD)l!zZK~hrlsRdWd3krXqp)V7fNP%AwWgmOI;Yq!{AP+KF zoWK`U?x~P58Wwk|nvvEjGm9@J5a_)3fHL5L1}UZELkqjtyTG?F?O3 z#Gx#npCW72$V*wwCT=4KfTP06VI6ofI2`l(KAcsDA)AHgDP-Q-k}H7#LBif^885sm zYg==Y5YWw@b{vMXIIZxE3Y2Q9Y2XIFi#_RT%FvUX;r5-drgk^|9;#6>gEMn;urO|2H*8Ybhgy@lNu=)x}J9Y0 z9MkskW1DtUZ9sGuW=7|xF90j!Gne7pn)5760J6P&b9)qQ`}uz^C^0cD!HdS?uxp_9 z&Y%q6%8%jC{44kXvGxjG{afMw$CrwC3O^}&)^C_;LQycR;`Q9)7a^$yQ^}vyifXM(Dt^*8Xy+zbyBE|7t*}AK)43dN6uMj&#`m zc$^rCc$}y3w$ED40CO_rB8G&L#vcTR$)EJNNJ+{nv z-1eF9GJ`NB-=?dk?T<}ep1wLA&>#IkE z*2CMaN48s>?~(0R$fvtrHU=7k)z%;CYYe_@YpM5DhhHB6BD#KPa!ou4=93%#Ka-n$ zFJt1_DbbU!FIdH?Qfn!(F|jFa-h4X#3+d^ZC-L&oyOqAP&TCWTo-kj&N4= zy!=EIAWV0C)7)2S?%Qe}*a{A!R_ert(uIwd$1_y{;Aa1(`6RM~u-Yz($4lc?0jiBN zn^Rw%-9cCld&Q=bR287wnA_~z%s!3%qVMSkPkvfHK3N$!$2d36WJRGUQc zt>WcU?9tV#0C0osU{wutil?`mT8ptqbyW%Q^M(V(@pY~01q^pp-&|}d($dsMOX>0> zque}D2@h8N*cT9d!9ribEDU{mYo`ud-suwuwi}wO64cKd50$(nvN8Tx+icxTJn1NR zpQ^NvR~jd(Ug$>2ZN<4#Un#p0+w9x;;PFq(ouieuu}b(yRX+{@@u9**Q7XoYf%VDK RcsbNv@%H?#4*IwO{x_!L1{?qY literal 0 HcmV?d00001 diff --git a/software/region-annotation/src/chains.py b/software/region-annotation/src/chains.py new file mode 100644 index 0000000..e027332 --- /dev/null +++ b/software/region-annotation/src/chains.py @@ -0,0 +1,54 @@ +"""Chain vocabulary shared by both entrypoints. + +The block speaks `A` / `B` (heavy / light) because that is the vocabulary of the +`pl7.app/vdj/scClonotypeChain` column domain the emitted columns carry. ANARCI speaks +`H` / `KL` and writes one CSV per bucket. The two are related but not the same thing: +`A` / `B` is *declared* by the mapping slot the scientist assigned a column to, while +`H` / `KL` is *inferred* by ANARCI from the sequence itself. + +Reconciling a declared chain against an inferred one is explicitly out of scope for this +spec, so this package never compares them. It records regions under the declared chain, +and takes the numbering (and therefore the range table) from whichever bucket ANARCI +actually put the sequence in. See `regions.py` for why. +""" + +# Declared chains, in emission order. +CHAINS = ["A", "B"] + +CHAIN_LABELS = {"A": "heavy", "B": "light"} + +# ANARCI's own buckets. It writes `_H.csv` and `_KL.csv`. +ANARCI_BUCKETS = ["H", "KL"] + + +def sequence_column(chain: str) -> str: + """Input TSV column holding the amino-acid variable domain of `chain`.""" + return f"{chain}_sequence" + + +def region_column(chain: str, region: str) -> str: + """Output TSV column holding one located region of `chain`.""" + return f"{chain}_{region}_aa" + + +def status_column(chain: str) -> str: + """Output TSV column holding the region-annotation status of `chain`.""" + return f"{chain}_regionAnnotationStatus" + + +def fasta_id(key: str, chain: str) -> str: + """FASTA record id. + + The chain is part of the id, not just the bucket the record lands in. Keying the + lookup on the record key alone would collide the two chains of one paired record, + which is only safe if you already assume declared chain == ANARCI bucket. + """ + return f"{key}|{chain}" + + +def parse_fasta_id(record_id: str) -> tuple[str, str]: + """Inverse of `fasta_id`. Returns ("", "") for anything unparseable.""" + key, sep, chain = (record_id or "").strip().rpartition("|") + if not sep or not key or chain not in CHAINS: + return "", "" + return key, chain diff --git a/software/region-annotation/src/fasta.py b/software/region-annotation/src/fasta.py new file mode 100644 index 0000000..e5529a5 --- /dev/null +++ b/software/region-annotation/src/fasta.py @@ -0,0 +1,59 @@ +"""Turn the per-record sequence TSV into one FASTA for ANARCI. + +Input TSV: a key column plus one `_sequence` column per mapped chain. +Output FASTA: one record per (key, chain) that actually carries a sequence. + +Records with no sequence for a chain are simply not written. They are not an error — +that chain has nothing to number, and `main.py` reports them as `Not applicable` from +the same input TSV. +""" + +import argparse +import sys + +import polars as pl + +from chains import CHAINS, fasta_id, sequence_column + + +def to_fasta(input_tsv: str, key_column: str, output_fasta: str) -> None: + df = pl.read_csv(input_tsv, separator="\t", infer_schema_length=0) + + if key_column not in df.columns: + print(f"Key column '{key_column}' not found in {input_tsv}", file=sys.stderr) + sys.exit(2) + + present = [c for c in CHAINS if sequence_column(c) in df.columns] + if not present: + expected = ", ".join(sequence_column(c) for c in CHAINS) + print(f"No sequence column found in {input_tsv}; expected one of: {expected}", file=sys.stderr) + sys.exit(2) + + written = 0 + with open(output_fasta, "w") as out: + for row in df.iter_rows(named=True): + key = (row.get(key_column) or "").strip() + if not key: + continue + for chain in present: + seq = (row.get(sequence_column(chain)) or "").strip() + if not seq: + continue + out.write(f">{fasta_id(key, chain)}\n{seq}\n") + written += 1 + + print(f"Wrote {written} sequences for chains [{', '.join(present)}] to {output_fasta}") + + +def main() -> None: + p = argparse.ArgumentParser(description="Build a FASTA of variable domains for ANARCI") + p.add_argument("--input_tsv", required=True, help="Input TSV: key column + _sequence columns") + p.add_argument("--key_column", required=True, help="Name of the record key column") + p.add_argument("--output_fasta", required=True, help="Output FASTA path") + args = p.parse_args() + + to_fasta(args.input_tsv, args.key_column, args.output_fasta) + + +if __name__ == "__main__": + main() diff --git a/software/region-annotation/src/main.py b/software/region-annotation/src/main.py new file mode 100644 index 0000000..ec8f728 --- /dev/null +++ b/software/region-annotation/src/main.py @@ -0,0 +1,145 @@ +"""Build the region + status TSV from ANARCI's per-bucket CSVs. + +Input: + --input_tsv the same per-record TSV `fasta.py` consumed: key column plus one + `_sequence` column per mapped chain. + --h_csv/--kl_csv ANARCI's `_H.csv` / `_KL.csv`. Either may be absent. + --scheme imgt | kabat | chothia — the convention the scientist chose. + +Output: one row per record, and for each mapped chain seven `__aa` columns +plus one `_regionAnnotationStatus`. Status is never empty; regions are empty for +any chain whose status is not `Annotated`. + +Why the lookup keys on `key|chain` rather than on the record key +--------------------------------------------------------------- +ANARCI decides for itself whether a sequence is H or KL and writes it to that bucket's CSV. +The chain this block emits under is the one the scientist *declared* by assigning a column +to a slot. Those can disagree, and reconciling them is out of scope for this spec — there +is no status value for it and no threshold has been agreed for how much disagreement should +stop a run. + +So this script does not compare them. It finds the record by its full `key|chain` id in +either bucket, and uses the range table of the bucket it was found in, because that is the +numbering space ANARCI actually produced. Using the declared chain's table on numbering from +the other bucket would silently shift every boundary; dropping the record to `Failed` would +throw away a chain the instrument numbered perfectly well. + +Keying on the record key alone — as the block this is forked from does — collapses the two +chains of one paired record onto one entry, which is only safe if declared chain and ANARCI +bucket are assumed to agree. +""" + +import argparse +import os +import sys +from typing import Dict, List, Optional, Tuple + +import polars as pl + +from chains import ANARCI_BUCKETS, CHAINS, parse_fasta_id, region_column, sequence_column, status_column +from regions import REGION_RANGES, REGIONS, SCHEMES, annotate, parse_positions + +# (key, chain) -> (bucket, residues) +Numbering = Dict[Tuple[str, str], Tuple[str, List[str]]] + + +def load_anarci_csv(path: Optional[str], bucket: str, into: Numbering, positions: Dict[str, List[str]]) -> None: + """Merge one ANARCI CSV into `into`, keyed by the (key, chain) parsed from its Id.""" + if not path or not os.path.exists(path): + return + + df = pl.read_csv(path, infer_schema_length=0) + if "Id" not in df.columns: + print(f"{path}: no 'Id' column, skipping", file=sys.stderr) + return + + pos_labels = parse_positions(df.columns) + if not pos_labels: + # Expected, not an anomaly: the workflow pre-creates both bucket CSVs with just an + # `Id` header so the exec can always save both, and ANARCI overwrites only the + # buckets it actually found. A header-only file means this bucket was empty. + print(f"{path}: no sequences in the {bucket} bucket") + return + positions[bucket] = pos_labels + + for row in df.select(["Id"] + pos_labels).iter_rows(named=True): + key, chain = parse_fasta_id(row.get("Id") or "") + if not key: + continue + entry = (key, chain) + if entry in into: + # First numbering wins. ANARCI can emit more than one domain hit per record; + # the first is the primary one, and a mapping assigning two sequences to one + # chain is refused upstream, so there is exactly one per (key, chain). + continue + into[entry] = (bucket, [(row.get(p) or "").strip() for p in pos_labels]) + + +def main() -> None: + p = argparse.ArgumentParser(description="Locate FR/CDR regions from ANARCI numbering") + p.add_argument("--input_tsv", required=True, help="Per-record TSV: key column + _sequence columns") + p.add_argument("--key_column", required=True, help="Name of the record key column") + p.add_argument("--scheme", required=True, choices=SCHEMES, help="Numbering scheme") + p.add_argument("--h_csv", required=False, help="ANARCI H-bucket CSV") + p.add_argument("--kl_csv", required=False, help="ANARCI KL-bucket CSV") + p.add_argument("--out_tsv", required=True, help="Output TSV path") + args = p.parse_args() + + df = pl.read_csv(args.input_tsv, separator="\t", infer_schema_length=0) + if args.key_column not in df.columns: + print(f"Key column '{args.key_column}' not found in {args.input_tsv}", file=sys.stderr) + sys.exit(2) + + chains = [c for c in CHAINS if sequence_column(c) in df.columns] + if not chains: + expected = ", ".join(sequence_column(c) for c in CHAINS) + print(f"No sequence column found in {args.input_tsv}; expected one of: {expected}", file=sys.stderr) + sys.exit(2) + + numbering: Numbering = {} + positions: Dict[str, List[str]] = {} + for bucket, path in zip(ANARCI_BUCKETS, [args.h_csv, args.kl_csv]): + load_anarci_csv(path, bucket, numbering, positions) + + columns = [args.key_column] + for chain in chains: + columns.extend(region_column(chain, region) for region in REGIONS) + columns.append(status_column(chain)) + + rows: List[List[str]] = [] + status_counts: Dict[str, int] = {} + + for row in df.iter_rows(named=True): + key = (row.get(args.key_column) or "").strip() + if not key: + continue + + out_row: List[str] = [key] + for chain in chains: + sequence = (row.get(sequence_column(chain)) or "").strip() + + found = numbering.get((key, chain)) + if found is None: + located, status = annotate(sequence, None, None) + else: + bucket, residues = found + located, status = annotate( + sequence, + (positions[bucket], residues), + REGION_RANGES[args.scheme][bucket], + ) + + out_row.extend(located[region] for region in REGIONS) + out_row.append(status) + status_counts[status] = status_counts.get(status, 0) + 1 + + rows.append(out_row) + + pl.DataFrame(rows, schema=columns, orient="row").write_csv(args.out_tsv, separator="\t") + + summary = ", ".join(f"{s}: {n}" for s, n in sorted(status_counts.items())) + print(f"Wrote {len(rows)} records x chains [{', '.join(chains)}] to {args.out_tsv} ({summary})") + + +if __name__ == "__main__": + main() diff --git a/software/region-annotation/src/regions.py b/software/region-annotation/src/regions.py new file mode 100644 index 0000000..59b0c84 --- /dev/null +++ b/software/region-annotation/src/regions.py @@ -0,0 +1,188 @@ +"""Pure region-location logic. No IO, no argparse — see `main.py` for the CLI. + +Everything here is a function of its arguments so it can be unit-tested directly. +""" + +import re +from typing import Dict, List, Optional, Tuple + +# All seven regions. FR4 is included deliberately: the region list is the thing that has +# historically been short in three different places, and a set imported without FR4 gets +# no FR4 liability scored downstream, silently. +REGIONS = ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"] + +# Closed status vocabulary. Exactly these three values, never null. +STATUS_ANNOTATED = "Annotated" +STATUS_NOT_APPLICABLE = "Not applicable" +STATUS_FAILED = "Failed" + +# Per-scheme, per-ANARCI-bucket region boundaries, inclusive, in the scheme's own +# numbering space. +# +# Copied verbatim from blocks/redefine-clonotypes/software/anarci-numbering/src/main.py. +# ANARCI supplies the numbering; the region *definitions* are this table, so numbering and +# region boundaries are one choice here rather than two. Kept as a copy rather than shared: +# the two callers want different outputs, and sharing would put a change to +# redefine-clonotypes on this block's critical path. Consolidation candidate — if you +# change a boundary here, change it there too. +REGION_RANGES: Dict[str, Dict[str, Dict[str, Tuple[int, int]]]] = { + "imgt": { + "H": { + "FR1": (1, 26), + "CDR1": (27, 38), + "FR2": (39, 55), + "CDR2": (56, 65), + "FR3": (66, 104), + "CDR3": (105, 117), + "FR4": (118, 129), + }, + "KL": { + "FR1": (1, 26), + "CDR1": (27, 38), + "FR2": (39, 55), + "CDR2": (56, 65), + "FR3": (66, 104), + "CDR3": (105, 117), + "FR4": (118, 129), + }, + }, + "kabat": { + "H": { + "FR1": (1, 30), + "CDR1": (31, 35), + "FR2": (36, 49), + "CDR2": (50, 65), + "FR3": (66, 94), + "CDR3": (95, 102), + "FR4": (103, 113), + }, + "KL": { + "FR1": (1, 23), + "CDR1": (24, 34), + "FR2": (35, 49), + "CDR2": (50, 56), + "FR3": (57, 88), + "CDR3": (89, 97), + "FR4": (98, 107), + }, + }, + "chothia": { + "H": { + "FR1": (1, 25), + "CDR1": (26, 32), + "FR2": (33, 52), + "CDR2": (53, 55), + "FR3": (56, 94), + "CDR3": (95, 102), + "FR4": (103, 113), + }, + "KL": { + "FR1": (1, 23), + "CDR1": (24, 34), + "FR2": (35, 49), + "CDR2": (50, 56), + "FR3": (57, 88), + "CDR3": (89, 97), + "FR4": (98, 107), + }, + }, +} + +SCHEMES = sorted(REGION_RANGES.keys()) + + +def parse_positions(fields: List[str]) -> List[str]: + """ANARCI's CSV carries metadata columns first, then one column per position. + + Position columns are the ones whose header starts with a digit (`1`, `2`, … `111A`). + Returns them in file order, which is numbering order. + """ + for i, field in enumerate(fields): + if re.match(r"^\d", field): + return fields[i:] + return [] + + +def position_number(label: str) -> Optional[int]: + """`"111A"` -> 111. Insertion codes share the number of the position they follow.""" + m = re.match(r"^(\d+)", label) + return int(m.group(1)) if m else None + + +def region_for_pos(num: int, ranges: Dict[str, Tuple[int, int]]) -> Optional[str]: + for region in REGIONS: + start, end = ranges[region] + if start <= num <= end: + return region + return None + + +def is_gap(residue: Optional[str]) -> bool: + return (residue or "").strip() in {"", "-", "."} + + +def build_regions( + pos_labels: List[str], + residues: List[str], + ranges: Dict[str, Tuple[int, int]], +) -> Dict[str, str]: + """Assemble one amino-acid string per region from ANARCI's aligned residues. + + Gaps are dropped, so the result is the ungapped subsequence of each region. Positions + outside every region range are dropped too. + + Amino acid only: a bare set carries no nucleotide variable domain, so there is no + nucleotide counterpart to project onto these boundaries. + """ + collected: Dict[str, List[str]] = {r: [] for r in REGIONS} + + for pos_label, residue in zip(pos_labels, residues): + if is_gap(residue): + continue + num = position_number(pos_label) + if num is None: + continue + region = region_for_pos(num, ranges) + if region is None: + continue + collected[region].append(residue.strip()) + + return {r: "".join(collected[r]) for r in REGIONS} + + +def annotate( + sequence: str, + numbering: Optional[Tuple[List[str], List[str]]], + ranges: Optional[Dict[str, Tuple[int, int]]], +) -> Tuple[Dict[str, str], str]: + """Locate one chain's regions and say what happened. + + `sequence` — the declared amino-acid variable domain, "" when the chain was not + supplied for this record. + `numbering` — (position labels, residues) from whichever ANARCI bucket contained this + record, or None if ANARCI did not number it. + `ranges` — the range table of that same bucket, or None alongside `numbering`. + + Returns (region -> value, status). A chain that is not `Annotated` carries **no + value** in any region — the empty string here becomes an absent cell in the TSV and a + null in the imported p-column. It is never an empty sequence presented as a result. + """ + empty = {r: "" for r in REGIONS} + + if not (sequence or "").strip(): + # Nothing was supplied for this chain, so there is nothing the input could have + # supported an answer for. Not a failure of the instrument. + return empty, STATUS_NOT_APPLICABLE + + if numbering is None or ranges is None: + # A sequence was supplied and ANARCI did not return numbering for it. + return empty, STATUS_FAILED + + pos_labels, residues = numbering + located = build_regions(pos_labels, residues, ranges) + + if not any(located.values()): + # Numbered, but nothing landed inside any region range. + return empty, STATUS_FAILED + + return located, STATUS_ANNOTATED diff --git a/software/region-annotation/src/requirements.txt b/software/region-annotation/src/requirements.txt new file mode 100644 index 0000000..bc3df6d --- /dev/null +++ b/software/region-annotation/src/requirements.txt @@ -0,0 +1 @@ +polars-lts-cpu==1.33.1 diff --git a/software/region-annotation/test_region_annotation.py b/software/region-annotation/test_region_annotation.py new file mode 100644 index 0000000..78393b7 --- /dev/null +++ b/software/region-annotation/test_region_annotation.py @@ -0,0 +1,167 @@ +"""End-to-end check of both entrypoints against a synthetic ANARCI CSV. + +Not wired into `pnpm test`: it needs a python interpreter with polars, which the block's +node/turbo test lane does not provide. Run it by hand from this directory: + + uv venv .venv && uv pip install --python .venv/bin/python polars + .venv/bin/python test_region_annotation.py + +Residues are deterministic per position, so every region's expected content is computable +and the assertions pin the actual boundary arithmetic rather than just the shape. +""" + +import csv +import os +import subprocess +import sys +import tempfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +SRC = os.path.join(HERE, "src") + +AAS = "ACDEFGHIKLMNPQRSTVWY" +MAXPOS = 130 +POSITIONS = [str(i) for i in range(1, MAXPOS + 1)] + +REGIONS = ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"] + +# kabat is used throughout because its H and KL ranges differ, which is what makes the +# "declared chain vs ANARCI bucket" assertion meaningful. Under imgt they are identical. +KABAT = { + "H": {"FR1": (1, 30), "CDR1": (31, 35), "FR2": (36, 49), "CDR2": (50, 65), + "FR3": (66, 94), "CDR3": (95, 102), "FR4": (103, 113)}, + "KL": {"FR1": (1, 23), "CDR1": (24, 34), "FR2": (35, 49), "CDR2": (50, 56), + "FR3": (57, 88), "CDR3": (89, 97), "FR4": (98, 107)}, +} + +ANARCI_META = ["domain_no", "hmm_species", "chain_type", "e-value", "score", + "seqstart_index", "seqend_index", "v_gene", "v_identity", "j_gene", "j_identity"] + + +def residue(i): + return AAS[(i - 1) % len(AAS)] + + +def expected(lo, hi): + return "".join(residue(i) for i in range(lo, hi + 1) if i <= MAXPOS) + + +def write_anarci_csv(path, ids, all_gaps=()): + """A stand-in for ANARCI's `--csv` output: metadata columns, then one per position.""" + with open(path, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["Id"] + ANARCI_META + POSITIONS) + for rid in ids: + meta = ["1", "human", "H", "1e-60", "150.0", "0", "120", "IGHV3-23", "0.9", "IGHJ4", "0.9"] + res = ["-"] * len(POSITIONS) if rid in all_gaps else [residue(i) for i in range(1, MAXPOS + 1)] + w.writerow([rid] + meta + res) + + +def write_header_only_csv(path): + """What the workflow pre-creates, and what an empty ANARCI bucket leaves behind.""" + with open(path, "w") as f: + f.write("Id\n") + + +def run(*argv): + r = subprocess.run([sys.executable] + list(argv), capture_output=True, text=True) + if r.returncode != 0: + raise AssertionError(f"{argv[0]} failed:\n{r.stdout}\n{r.stderr}") + return r.stdout.strip() + + +def main(): + work = tempfile.mkdtemp(prefix="region-annotation-test-") + + in_tsv = os.path.join(work, "in.tsv") + with open(in_tsv, "w", newline="") as f: + w = csv.writer(f, delimiter="\t") + w.writerow(["variantKey", "A_sequence", "B_sequence"]) + w.writerow(["K1", "EVQLVQ", "DIQMTQ"]) # both chains numbered + w.writerow(["K2", "EVQLVQ", ""]) # B not supplied + w.writerow(["K3", "EVQLVQ", ""]) # A supplied, ANARCI returned nothing + w.writerow(["K4", "EVQLVQ", ""]) # A numbered, every position a gap + w.writerow(["K5", "DIQMTQ", ""]) # A declared, ANARCI bucketed it KL + w.writerow(["", "EVQLVQ", ""]) # blank key + + h_csv = os.path.join(work, "anarci.csv_H.csv") + kl_csv = os.path.join(work, "anarci.csv_KL.csv") + write_anarci_csv(h_csv, ["K1|A", "K2|A", "K4|A"], all_gaps={"K4|A"}) + write_anarci_csv(kl_csv, ["K1|B", "K5|A"]) + + fasta = os.path.join(work, "out.fasta") + print(run(os.path.join(SRC, "fasta.py"), "--input_tsv", in_tsv, + "--key_column", "variantKey", "--output_fasta", fasta)) + + ids = [line[1:].strip() for line in open(fasta) if line.startswith(">")] + assert ids == ["K1|A", "K1|B", "K2|A", "K3|A", "K4|A", "K5|A"], ids + print(" fasta: chain is in the id, unsupplied chains skipped, blank key dropped") + + out_tsv = os.path.join(work, "out.tsv") + print(run(os.path.join(SRC, "main.py"), "--input_tsv", in_tsv, "--key_column", "variantKey", + "--scheme", "kabat", "--h_csv", h_csv, "--kl_csv", kl_csv, "--out_tsv", out_tsv)) + + rows = list(csv.DictReader(open(out_tsv), delimiter="\t")) + by_key = {r["variantKey"]: r for r in rows} + assert [r["variantKey"] for r in rows] == ["K1", "K2", "K3", "K4", "K5"], list(by_key) + + want_header = (["variantKey"] + + [f"A_{r}_aa" for r in REGIONS] + ["A_regionAnnotationStatus"] + + [f"B_{r}_aa" for r in REGIONS] + ["B_regionAnnotationStatus"]) + assert list(rows[0].keys()) == want_header, list(rows[0].keys()) + print(" header: seven regions per chain including FR4, plus a status per chain") + + for chain, bucket in (("A", "H"), ("B", "KL")): + for region in REGIONS: + got = by_key["K1"][f"{chain}_{region}_aa"] + exp = expected(*KABAT[bucket][region]) + assert got == exp, f"K1 {chain} {region}: {got!r} != {exp!r}" + assert by_key["K1"][f"{chain}_regionAnnotationStatus"] == "Annotated" + print(" K1: both chains Annotated, all seven regions match their bucket's kabat ranges") + + assert by_key["K2"]["B_regionAnnotationStatus"] == "Not applicable" + assert all(by_key["K2"][f"B_{r}_aa"] == "" for r in REGIONS) + print(" K2: chain not supplied -> Not applicable, no region values") + + assert by_key["K3"]["A_regionAnnotationStatus"] == "Failed" + assert all(by_key["K3"][f"A_{r}_aa"] == "" for r in REGIONS) + print(" K3: supplied but unnumbered -> Failed, no region values (not empty strings)") + + assert by_key["K4"]["A_regionAnnotationStatus"] == "Failed" + assert all(by_key["K4"][f"A_{r}_aa"] == "" for r in REGIONS) + print(" K4: numbered but nothing located -> Failed, no region values") + + # The declared chain labels the column; the bucket ANARCI chose supplies the ranges. + assert by_key["K5"]["A_regionAnnotationStatus"] == "Annotated" + for region in REGIONS: + assert by_key["K5"][f"A_{region}_aa"] == expected(*KABAT["KL"][region]), region + assert by_key["K5"]["A_FR1_aa"] != expected(*KABAT["H"]["FR1"]) + print(" K5: declared A, bucketed KL -> Annotated under A, with KL ranges") + + for key, row in by_key.items(): + for chain in ("A", "B"): + assert row[f"{chain}_regionAnnotationStatus"], f"{key} {chain}: empty status" + print(" status is never empty") + + # A single-chain set, with the other bucket left header-only as the workflow creates it. + single_tsv = os.path.join(work, "single.tsv") + with open(single_tsv, "w", newline="") as f: + w = csv.writer(f, delimiter="\t") + w.writerow(["variantKey", "A_sequence"]) + w.writerow(["S1", "EVQLVQ"]) + empty_kl = os.path.join(work, "empty_KL.csv") + write_header_only_csv(empty_kl) + single_out = os.path.join(work, "single_out.tsv") + print(run(os.path.join(SRC, "main.py"), "--input_tsv", single_tsv, "--key_column", "variantKey", + "--scheme", "imgt", "--h_csv", h_csv, "--kl_csv", empty_kl, "--out_tsv", single_out)) + single = list(csv.DictReader(open(single_out), delimiter="\t")) + assert list(single[0].keys()) == ["variantKey"] + [f"A_{r}_aa" for r in REGIONS] + ["A_regionAnnotationStatus"] + assert single[0]["A_regionAnnotationStatus"] == "Failed" # S1 is not in the H csv + print(" single-chain set: only that chain's columns emitted, header-only bucket tolerated") + + print("\nALL ASSERTIONS PASSED") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/workflow/package.json b/workflow/package.json index fa490b2..1c24bcc 100644 --- a/workflow/package.json +++ b/workflow/package.json @@ -10,6 +10,8 @@ "check": "pl-tengo check" }, "dependencies": { + "@platforma-open/milaboratories.import-vdj.region-annotation": "workspace:*", + "@platforma-open/milaboratories.software-anarci": "catalog:", "@platforma-sdk/workflow-tengo": "catalog:" }, "devDependencies": { diff --git a/workflow/src/annotate-regions.tpl.tengo b/workflow/src/annotate-regions.tpl.tengo new file mode 100644 index 0000000..7e752db --- /dev/null +++ b/workflow/src/annotate-regions.tpl.tengo @@ -0,0 +1,100 @@ +// Locate FR/CDR region boundaries in amino-acid antibody variable domains. +// +// Input: a per-record TSV carrying the key column plus one `_sequence` column per +// mapped chain (`A` heavy, `B` light), and the numbering scheme the scientist chose. +// Output: `regions` — a TSV with seven `__aa` columns per chain plus one +// `_regionAnnotationStatus`. +// +// Three steps: build a FASTA, number it with ANARCI, project the numbering onto region +// ranges. ANARCI is the shared, already-published artifact — reused, not introduced here. +// The convention is passed through as `--scheme`, which is the whole reason ANARCI is the +// instrument rather than repseqio: repseqio's boundaries are whatever its reference library +// defines, with nothing to ask for. + +self := import("@platforma-sdk/workflow-tengo:tpl") +exec := import("@platforma-sdk/workflow-tengo:exec") +assets := import("@platforma-sdk/workflow-tengo:assets") +ll := import("@platforma-sdk/workflow-tengo:ll") + +anarciSw := assets.importSoftware("@platforma-open/milaboratories.software-anarci:main") +fastaSw := assets.importSoftware("@platforma-open/milaboratories.import-vdj.region-annotation:fasta") +regionsSw := assets.importSoftware("@platforma-open/milaboratories.import-vdj.region-annotation:main") + +// One day in ms. Region annotation of a given set of sequences under a given scheme is +// deterministic, so the result is worth holding on to. +CACHE_MILLIS := 24 * 60 * 60 * 1000 + +self.defineOutputs("regions") + +self.body(func(inputs) { + inputTsv := inputs.inputTsv + keyColumn := inputs.keyColumn + scheme := inputs.scheme + + ll.assert(inputTsv != undefined, "annotate-regions: inputTsv is required") + ll.assert(keyColumn != undefined && keyColumn != "", "annotate-regions: keyColumn is required") + ll.assert( + scheme == "imgt" || scheme == "kabat" || scheme == "chothia", + "annotate-regions: unsupported numbering scheme %v (expected imgt, kabat or chothia)", + scheme + ) + + fastaCmd := exec.builder(). + software(fastaSw). + cpu(1). + mem("4GiB"). + addFile("input.tsv", inputTsv). + arg("--input_tsv").arg("input.tsv"). + arg("--key_column").arg(keyColumn). + arg("--output_fasta").arg("input.fasta"). + saveFile("input.fasta"). + printErrStreamToStdout(). + cache(CACHE_MILLIS). + run() + + // ANARCI decides for itself whether each sequence is H or KL and writes one CSV per + // bucket it found. Both are pre-created with just an `Id` header so the exec can always + // save both — otherwise a set whose sequences all land in one bucket, or which ANARCI + // cannot number at all, leaves saveFile looking for a file that was never written. + // + // Both buckets are always read, whichever chains were mapped: the chain a column was + // assigned to is *declared*, ANARCI's bucket is *inferred*, and the two can disagree. + // Reconciling them is out of scope, so the numbering step resolves each record by its + // full `key|chain` id across both buckets rather than assuming they agree. + anarciCmd := exec.builder(). + software(anarciSw). + addFile("input.fasta", fastaCmd.getFile("input.fasta")). + arg("-i").arg("input.fasta"). + arg("--scheme").arg(scheme). + arg("--ncpu").argWithVar("{system.cpu}"). + arg("-o").arg("anarci.csv").arg("--csv"). + writeFile("anarci.csv_H.csv", "Id\n"). + writeFile("anarci.csv_KL.csv", "Id\n"). + saveFile("anarci.csv_H.csv"). + saveFile("anarci.csv_KL.csv"). + printErrStreamToStdout(). + cache(CACHE_MILLIS). + run() + + regionsCmd := exec.builder(). + software(regionsSw). + cpu(1). + mem("4GiB"). + addFile("input.tsv", inputTsv). + addFile("anarci.csv_H.csv", anarciCmd.getFile("anarci.csv_H.csv")). + addFile("anarci.csv_KL.csv", anarciCmd.getFile("anarci.csv_KL.csv")). + arg("--input_tsv").arg("input.tsv"). + arg("--key_column").arg(keyColumn). + arg("--scheme").arg(scheme). + arg("--h_csv").arg("anarci.csv_H.csv"). + arg("--kl_csv").arg("anarci.csv_KL.csv"). + arg("--out_tsv").arg("regions.tsv"). + saveFile("regions.tsv"). + printErrStreamToStdout(). + cache(CACHE_MILLIS). + run() + + return { + regions: regionsCmd.getFile("regions.tsv") + } +}) From 939019b15877b0b633c7e75abaa341425aa0b61f Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:43:17 +0200 Subject: [PATCH 05/54] MILAB-6720: count records ANARCI numbered as the other chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chain a column is emitted under is declared by the mapping slot; ANARCI infers H/KL from the sequence itself. Those can disagree, and the annotation is correct either way — the record is labelled with its declared chain and its boundaries are read in the coordinate space of the bucket ANARCI actually used. What that leaves unguarded is the likeliest mapping mistake there is. Map the two sequence columns to the wrong slots and the import succeeds completely: every record Annotated, every value internally consistent, every chain label wrong, and Sequence Liabilities then scores heavy-chain positional rules against light chains. Nothing else in the output reveals it, because with chain fixed by the slot there is nothing to cross-check it against. So the numbering step now emits per-chain outcome counts, including `chainDisagreed`: annotated records whose bucket was not the one their declared chain implies. Reported through a new `stats` output; nothing acts on it. Counting is not reconciling. The spec defers reconciliation and the threshold at which disagreement should stop a run, on the stated grounds that the rate has never been measured. This is that measurement, not the decision it feeds. A few disagreements are odd rows; nearly all of them on both chains are swapped columns, and the number tells those apart on its own. Also untracks two .pyc files that slipped into the previous commit and ignores __pycache__. A stale .pyc inside a dev block archive shadows the edited source, so the backend keeps running the old code with no sign of it. --- .gitignore | 5 + .../src/__pycache__/chains.cpython-312.pyc | Bin 2790 -> 0 bytes .../src/__pycache__/regions.cpython-312.pyc | Bin 5915 -> 0 bytes software/region-annotation/src/chains.py | 5 + software/region-annotation/src/main.py | 92 +++++++++++++++--- .../test_region_annotation.py | 20 +++- workflow/src/annotate-regions.tpl.tengo | 14 ++- 7 files changed, 122 insertions(+), 14 deletions(-) delete mode 100644 software/region-annotation/src/__pycache__/chains.cpython-312.pyc delete mode 100644 software/region-annotation/src/__pycache__/regions.cpython-312.pyc diff --git a/.gitignore b/.gitignore index 2b0c0ad..5f7a80b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ ui/tsconfig.app.tsbuildinfo ui/tsconfig.node.tsbuildinfo software/**/*.tgz .vscode/sftp.json + +# Python bytecode. A stale .pyc shadows an edited source file inside a dev block +# archive, so the backend silently runs the old code. +__pycache__/ +*.py[cod] diff --git a/software/region-annotation/src/__pycache__/chains.cpython-312.pyc b/software/region-annotation/src/__pycache__/chains.cpython-312.pyc deleted file mode 100644 index b06003b117fa7f11cc4ef1dd3b79abaaa2c1c569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2790 zcma)7&2Jk;6yIHcG_@0_ZQ2y*2h)I{DX|Uep{SxD$0};ns+H;#P=#cVcgFE1>z!q0 zHmR#r1kpo|h6|CPBHUWRfg}F|7grow_Ed>~z^I&%ka%y_TT&z>M%ta-`FQhtzxUp6 zex8~tSkPALpRc|?VOhW7PulY1kEeIx<133>Je!8G33kBj0ZU-Zc@a(qiw9w* z&_6UkL;rFttKOwvK-Psz+mTOYpQtXCoRM~)w1w^x9%$JQg%@a5DHN8woV0!6t`QY- zx~530)*@BXT5OTiT~62g@Z)JTTPb=>n5hX3R%)Xohu|$(64wR zP$UW%tV33whqO?@cxbwoY$id}YjcU%7^eLhbN2f&!yH4u{c zjl*7`u8yb=zJ%ZlZdK2S3M!<*IcbfNRlIRi|xfYXHqxLC7J6;C<{ww z;1%1KV(0ReIA{2b^OEaG2CfZLX~VU+^ywtLzOpv0uc5B*ISp&xxm{lh5YRH@+&GcD z!e{6$Vx&DU5M}CmjI2}Xp_^%49Q_*Mvj5LtpQ zo=P^RUL1k36SxLHQZH(Q&fue+#;|y5m^~#Az|8*+_2Pe{H>qqG*~2nXz;*-lZSu7f6k#S5;AzBmou|tW4)FBMX94$4D&xDtSc~n=G|%ul zP0Dd^o=tQp1g7aRc^u|nhi9B(qmng!X#2+C)ZpUa+^~3Tm_3$+Wq*Pewmg?@&w?Yz z!MSBC#|Cj(-77yZhwb~MvsQ7}zZ!GCAMf}WX7AW{oI9CKM;10gOg1wbE7P=fTiX51 zroEZzpnPR-*OmfMd$__eNmI946>{CEpdzQ|<`&4@9B4CK8Z_veYZ9a%@z8UvR~z$LiA5=q+BlTZ#fw)OaVfc# zn~OKBl6(wqk#6mQ&k8?T-eQxk2C5&a3_%QKKPq4`6@5(bUhT&1)!fTYV zZToj;($0=fTE)_~+J0#(|K!N=-E$A8-`JWQ<*k|7fgT*&Dvk1v{qoM)kp`WPfDB@p_Wpfv@|cJ<#{>no%g1F^FC2X3Pk=Jao$h7^8wEbOouAB4_g&ZZl|I4_( zFQxeqm1ubP&qwQN{gS5xj`rr4Hjw^zp~oZ8MjBc<>MCNd{<;Rg79B9(Oa`b-!zhmo zx@buz$No~DKR||P7+Srwp06j92(-44m6_3CaQZG~tJbonCzWLik~LFNm8Gnn;Bt*6MYEuV zC9`R&12PS0P!g)HEK((_Yu1QGO^bs4XpAPT)aq#P1zzTCM7HG%TEepBE1GH9fomBQ zsHSZ1TsD)U5x*^6ym8W&r!K%3;3*DIAspv$9N~mr7u9vcQelbv4YP)&sF_SkOQ?$} zYWps#T8fg0=LOo&i%$3eK8$$K!N(AvcJLX*Q3sz#yzF2Nan`|i5r4|DEnc=ge|jb2 zVNKZB?F{Ha+~?qa#3vnm3MzJpV+M{dytw7G#}O|%IEnZU$Bs;A{0lz5*J;O!pM63lJJc7@{?M-fjr_${bd8^>HU=Y6nQHTjL z@M27eNZJ2_?&xgv#?;I)Q<;jr4~EH@MolG6EtRO2sw9k5Hm#e=lEzGH80}tH44u~6 zl?-J{#xOOsO>hPU%JtgM-Ki>g1^Nq2GnISG1~}C+RSC>AEtP>!+|#UOa2lc|!8L{! zPA&{93#Z`o+x*`t3nx#Wj4nh*x#$~QZzf7hE1C|QN`Y7gqJ}}RmRLsb)3fPC%FrKq zk2CYWbn8Ph8Zodr$UqGo%YJ3{5cS^hxAZkm*tV>Cr| zl``WtIJ!EPVa5tLt2vei2mN?#5oqMLIi{sE2D3))l9dru-I!@CS@+=ZV>Pl-9Y+LB zjhQS_Yc+vV>_$#CaaWNhy9mpp2bkz|dEs|qp{={L^y}7v^*})^y!CCU{^6PRGsTOg z>0gC!#wIas;`tPXP2YHy^U?q4?s+g? zxbyJd`n}?vpWpxaxw0JDn1c_ygyJNS0-mkvNopqc>k++^d-fFopl$d%1^gMfcFmIm zUq#yCo-M7(IVlHz`myIW5HhRF$ykFAAIf#huIRVXoiNzZbEkk{!FB}yzrX?9R=TZ+u_p1N4M64 z1#iLJ4mB00o`t$g<6EI_u({8!zLbP;4_I7FXEh*%n;bp(88tPMrLS2W>VxZG6u=ik zPWV_ffr*H?vdeSOQ7dj%Lp*mmOU>HCBu-|?-?lhni=%LEI2zM35s|$QttN`8-K$SC z<4HBcK7^)^u@B=~Uf2$|e0H_Sz6mLnkg^r(-O$USiLyN5>^cZ4h;vUd?Lh4WW&&4r zO8@{r!h5jR>(}^DN_aH5vX4N;Yf?^13V-c+;FpCpc}?1*X{2D+k>KjD*Dl<<=G*5` zKG$WK_~3xG>s&dn+rH+{`Saof#j0~Rmh7O$gdge?kOSc;wj-pNuu0XtYys0yLPS#NYVo|!>(9wTdkc~gaA z+UM?xd;R|P6w5GbTZ!Uf6n+>(Pp!VAsLCSl%%qqGI?)ye=-EVy8WxJat1=DqGldwK zbGmzXa!cLd+t@ceVPtiSG6*3SEUVOF6DSgXz#$>pF2V=`LaFHlL+(hC0Q(8tlP%rX z9ADx5$7#U!p;mOl{u~F$D~6^=>exr{gJDo|C`M#<9V7=Sh=Y{ab-Vt<{n4SK8!*^J zJ4MCu;xKRhVpdC$xT9c`VaAigq5lA%yzu)@;q>Iz;N(9hpEhsY+3fu)`!t!KDGyG5 z*L`&3_~!DjyH8gForSvM>2E{L#bXc7ZHHUGZRjd#TMa`6X*<+V_!Det?%9ww-rs5- z1{w=B7bc-?JJ4JltOPo~2`H6-vT^9|yT0oB+n!$qPF7olo-@BcD2%s~gRaa$Al4m2m%NvJ!f`EWiEztHZ*vbKk$}6vC%X^sn{c`NQ%r-#v0(k?nwM zMs}SORUi*}jYg1#IgR`Q!2e7mMBJm1eT=!Elt2a+*LlrAPPQ=K@UrBI0p)z^$$9eF z?t8tRM;j!cJtFbud_W~N`D=b_mppR*{|jaC)n8x5Z#C>+b&midp6owCP`HBcFURQ< z%V6t}h1RS>QFU-jbrteD)l!zZK~hrlsRdWd3krXqp)V7fNP%AwWgmOI;Yq!{AP+KF zoWK`U?x~P58Wwk|nvvEjGm9@J5a_)3fHL5L1}UZELkqjtyTG?F?O3 z#Gx#npCW72$V*wwCT=4KfTP06VI6ofI2`l(KAcsDA)AHgDP-Q-k}H7#LBif^885sm zYg==Y5YWw@b{vMXIIZxE3Y2Q9Y2XIFi#_RT%FvUX;r5-drgk^|9;#6>gEMn;urO|2H*8Ybhgy@lNu=)x}J9Y0 z9MkskW1DtUZ9sGuW=7|xF90j!Gne7pn)5760J6P&b9)qQ`}uz^C^0cD!HdS?uxp_9 z&Y%q6%8%jC{44kXvGxjG{afMw$CrwC3O^}&)^C_;LQycR;`Q9)7a^$yQ^}vyifXM(Dt^*8Xy+zbyBE|7t*}AK)43dN6uMj&#`m zc$^rCc$}y3w$ED40CO_rB8G&L#vcTR$)EJNNJ+{nv z-1eF9GJ`NB-=?dk?T<}ep1wLA&>#IkE z*2CMaN48s>?~(0R$fvtrHU=7k)z%;CYYe_@YpM5DhhHB6BD#KPa!ou4=93%#Ka-n$ zFJt1_DbbU!FIdH?Qfn!(F|jFa-h4X#3+d^ZC-L&oyOqAP&TCWTo-kj&N4= zy!=EIAWV0C)7)2S?%Qe}*a{A!R_ert(uIwd$1_y{;Aa1(`6RM~u-Yz($4lc?0jiBN zn^Rw%-9cCld&Q=bR287wnA_~z%s!3%qVMSkPkvfHK3N$!$2d36WJRGUQc zt>WcU?9tV#0C0osU{wutil?`mT8ptqbyW%Q^M(V(@pY~01q^pp-&|}d($dsMOX>0> zque}D2@h8N*cT9d!9ribEDU{mYo`ud-suwuwi}wO64cKd50$(nvN8Tx+icxTJn1NR zpQ^NvR~jd(Ug$>2ZN<4#Un#p0+w9x;;PFq(ouieuu}b(yRX+{@@u9**Q7XoYf%VDK RcsbNv@%H?#4*IwO{x_!L1{?qY diff --git a/software/region-annotation/src/chains.py b/software/region-annotation/src/chains.py index e027332..6f5f4ae 100644 --- a/software/region-annotation/src/chains.py +++ b/software/region-annotation/src/chains.py @@ -20,6 +20,11 @@ # ANARCI's own buckets. It writes `_H.csv` and `_KL.csv`. ANARCI_BUCKETS = ["H", "KL"] +# The bucket a declared chain is expected to land in, used *only* to count disagreements — +# never to choose a range table, and never to stop a run. ANARCI merges kappa and lambda +# into one KL bucket, and `B` is light regardless of which, so the pairing is total. +EXPECTED_BUCKET = {"A": "H", "B": "KL"} + def sequence_column(chain: str) -> str: """Input TSV column holding the amino-acid variable domain of `chain`.""" diff --git a/software/region-annotation/src/main.py b/software/region-annotation/src/main.py index ec8f728..f71cabd 100644 --- a/software/region-annotation/src/main.py +++ b/software/region-annotation/src/main.py @@ -18,11 +18,16 @@ is no status value for it and no threshold has been agreed for how much disagreement should stop a run. -So this script does not compare them. It finds the record by its full `key|chain` id in -either bucket, and uses the range table of the bucket it was found in, because that is the -numbering space ANARCI actually produced. Using the declared chain's table on numbering from -the other bucket would silently shift every boundary; dropping the record to `Failed` would -throw away a chain the instrument numbered perfectly well. +So this script does not *act* on the comparison. It finds the record by its full `key|chain` +id in either bucket, and uses the range table of the bucket it was found in, because that is +the numbering space ANARCI actually produced. Using the declared chain's table on numbering +from the other bucket would silently shift every boundary; dropping the record to `Failed` +would throw away a chain the instrument numbered perfectly well. + +It does *count* the disagreements, into `--out_stats`. Counting is not reconciling: nothing +here refuses a run or changes an output because of it. Without the count, mapping the two +sequence columns to the wrong slots produces a clean import in which every record is +annotated and every chain label is wrong, and nothing else in the output would reveal it. Keying on the record key alone — as the block this is forked from does — collapses the two chains of one paired record onto one entry, which is only safe if declared chain and ANARCI @@ -36,8 +41,25 @@ import polars as pl -from chains import ANARCI_BUCKETS, CHAINS, parse_fasta_id, region_column, sequence_column, status_column -from regions import REGION_RANGES, REGIONS, SCHEMES, annotate, parse_positions +from chains import ( + ANARCI_BUCKETS, + CHAINS, + EXPECTED_BUCKET, + parse_fasta_id, + region_column, + sequence_column, + status_column, +) +from regions import ( + REGION_RANGES, + REGIONS, + SCHEMES, + STATUS_ANNOTATED, + STATUS_FAILED, + STATUS_NOT_APPLICABLE, + annotate, + parse_positions, +) # (key, chain) -> (bucket, residues) Numbering = Dict[Tuple[str, str], Tuple[str, List[str]]] @@ -75,6 +97,38 @@ def load_anarci_csv(path: Optional[str], bucket: str, into: Numbering, positions into[entry] = (bucket, [(row.get(p) or "").strip() for p in pos_labels]) +def write_stats(path: str, chains: List[str], tally: Dict[str, Dict[str, int]]) -> None: + """One row per declared chain, counting outcomes and chain disagreements. + + `chainDisagreed` is the number of *annotated* records whose sequence ANARCI bucketed as + the other chain — a light chain in the slot declared heavy, or the reverse. It is a + subset of `annotated`: those records are annotated correctly, under their declared chain, + using the ranges of the bucket ANARCI chose. + + It is counted and reported, and nothing acts on it. Reconciling a declared chain against + an inferred one is out of scope for this spec, and the threshold at which disagreement + should stop a run turns on a rate nobody has measured. This is that measurement — the + missing input to that decision, not the decision. + + Reading it: a handful of disagreements is a few odd rows in the file. Nearly all of them, + on both chains, means the sequence columns were mapped to the wrong slots — in which case + every record is annotated consistently and labelled wrongly, and no other signal in the + output would show it. + """ + header = ["chain", "annotated", "notApplicable", "failed", "chainDisagreed"] + rows = [ + [ + chain, + str(tally[chain][STATUS_ANNOTATED]), + str(tally[chain][STATUS_NOT_APPLICABLE]), + str(tally[chain][STATUS_FAILED]), + str(tally[chain]["chainDisagreed"]), + ] + for chain in chains + ] + pl.DataFrame(rows, schema=header, orient="row").write_csv(path, separator="\t") + + def main() -> None: p = argparse.ArgumentParser(description="Locate FR/CDR regions from ANARCI numbering") p.add_argument("--input_tsv", required=True, help="Per-record TSV: key column + _sequence columns") @@ -83,6 +137,7 @@ def main() -> None: p.add_argument("--h_csv", required=False, help="ANARCI H-bucket CSV") p.add_argument("--kl_csv", required=False, help="ANARCI KL-bucket CSV") p.add_argument("--out_tsv", required=True, help="Output TSV path") + p.add_argument("--out_stats", required=False, help="Optional per-chain outcome counts TSV") args = p.parse_args() df = pl.read_csv(args.input_tsv, separator="\t", infer_schema_length=0) @@ -107,7 +162,12 @@ def main() -> None: columns.append(status_column(chain)) rows: List[List[str]] = [] - status_counts: Dict[str, int] = {} + # Per declared chain: the three statuses, plus how many of the annotated ones ANARCI + # bucketed as the *other* chain. See `write_stats` for what that number is for. + tally: Dict[str, Dict[str, int]] = { + c: {STATUS_ANNOTATED: 0, STATUS_NOT_APPLICABLE: 0, STATUS_FAILED: 0, "chainDisagreed": 0} + for c in chains + } for row in df.iter_rows(named=True): key = (row.get(args.key_column) or "").strip() @@ -128,17 +188,27 @@ def main() -> None: (positions[bucket], residues), REGION_RANGES[args.scheme][bucket], ) + if status == STATUS_ANNOTATED and bucket != EXPECTED_BUCKET[chain]: + tally[chain]["chainDisagreed"] += 1 out_row.extend(located[region] for region in REGIONS) out_row.append(status) - status_counts[status] = status_counts.get(status, 0) + 1 + tally[chain][status] += 1 rows.append(out_row) pl.DataFrame(rows, schema=columns, orient="row").write_csv(args.out_tsv, separator="\t") + if args.out_stats: + write_stats(args.out_stats, chains, tally) - summary = ", ".join(f"{s}: {n}" for s, n in sorted(status_counts.items())) - print(f"Wrote {len(rows)} records x chains [{', '.join(chains)}] to {args.out_tsv} ({summary})") + for chain in chains: + t = tally[chain] + print( + f"chain {chain}: {t[STATUS_ANNOTATED]} annotated, " + f"{t[STATUS_NOT_APPLICABLE]} not applicable, {t[STATUS_FAILED]} failed, " + f"{t['chainDisagreed']} numbered as the other chain" + ) + print(f"Wrote {len(rows)} records to {args.out_tsv}") if __name__ == "__main__": diff --git a/software/region-annotation/test_region_annotation.py b/software/region-annotation/test_region_annotation.py index 78393b7..db8c9ac 100644 --- a/software/region-annotation/test_region_annotation.py +++ b/software/region-annotation/test_region_annotation.py @@ -98,8 +98,10 @@ def main(): print(" fasta: chain is in the id, unsupplied chains skipped, blank key dropped") out_tsv = os.path.join(work, "out.tsv") + stats_tsv = os.path.join(work, "stats.tsv") print(run(os.path.join(SRC, "main.py"), "--input_tsv", in_tsv, "--key_column", "variantKey", - "--scheme", "kabat", "--h_csv", h_csv, "--kl_csv", kl_csv, "--out_tsv", out_tsv)) + "--scheme", "kabat", "--h_csv", h_csv, "--kl_csv", kl_csv, + "--out_tsv", out_tsv, "--out_stats", stats_tsv)) rows = list(csv.DictReader(open(out_tsv), delimiter="\t")) by_key = {r["variantKey"]: r for r in rows} @@ -143,6 +145,22 @@ def main(): assert row[f"{chain}_regionAnnotationStatus"], f"{key} {chain}: empty status" print(" status is never empty") + stats = {r["chain"]: r for r in csv.DictReader(open(stats_tsv), delimiter="\t")} + assert list(stats) == ["A", "B"], list(stats) + # A: K1 K2 K5 annotated, K3 K4 failed, none unsupplied. + assert stats["A"] == {"chain": "A", "annotated": "3", "notApplicable": "0", + "failed": "2", "chainDisagreed": "1"}, stats["A"] + # B: only K1 supplied and annotated; K2..K5 unsupplied. K1|B is in KL, which is where a + # declared B belongs, so nothing disagrees. + assert stats["B"] == {"chain": "B", "annotated": "1", "notApplicable": "4", + "failed": "0", "chainDisagreed": "0"}, stats["B"] + print(" stats: per-chain counts correct; K5 is the one chainDisagreed, and it is" + " counted without changing its status or its values") + + # The count must not leak into the dataset — K5 is a normal Annotated record. + assert by_key["K5"]["A_regionAnnotationStatus"] == "Annotated" + print(" stats: disagreement is reported, not acted on") + # A single-chain set, with the other bucket left header-only as the workflow creates it. single_tsv = os.path.join(work, "single.tsv") with open(single_tsv, "w", newline="") as f: diff --git a/workflow/src/annotate-regions.tpl.tengo b/workflow/src/annotate-regions.tpl.tengo index 7e752db..66cd39a 100644 --- a/workflow/src/annotate-regions.tpl.tengo +++ b/workflow/src/annotate-regions.tpl.tengo @@ -4,6 +4,12 @@ // mapped chain (`A` heavy, `B` light), and the numbering scheme the scientist chose. // Output: `regions` — a TSV with seven `__aa` columns per chain plus one // `_regionAnnotationStatus`. +// `stats` — one row per chain: annotated / notApplicable / failed counts, plus +// `chainDisagreed`, the number of annotated records ANARCI numbered as the *other* +// chain. Nothing acts on that number; it exists because the alternative is that +// mapping the sequence columns to the wrong slots imports cleanly with every chain +// label wrong, and because the threshold at which such disagreement should stop a +// run is deferred precisely for want of a measured rate. // // Three steps: build a FASTA, number it with ANARCI, project the numbering onto region // ranges. ANARCI is the shared, already-published artifact — reused, not introduced here. @@ -24,7 +30,7 @@ regionsSw := assets.importSoftware("@platforma-open/milaboratories.import-vdj.re // deterministic, so the result is worth holding on to. CACHE_MILLIS := 24 * 60 * 60 * 1000 -self.defineOutputs("regions") +self.defineOutputs("regions", "stats") self.body(func(inputs) { inputTsv := inputs.inputTsv @@ -89,12 +95,16 @@ self.body(func(inputs) { arg("--h_csv").arg("anarci.csv_H.csv"). arg("--kl_csv").arg("anarci.csv_KL.csv"). arg("--out_tsv").arg("regions.tsv"). + arg("--out_stats").arg("region-stats.tsv"). saveFile("regions.tsv"). + saveFileContent("region-stats.tsv"). printErrStreamToStdout(). cache(CACHE_MILLIS). run() return { - regions: regionsCmd.getFile("regions.tsv") + regions: regionsCmd.getFile("regions.tsv"), + // Content, not a file handle: a handful of rows the model reads inline. + stats: regionsCmd.getFileContent("region-stats.tsv") } }) From 85e98267e780bc3f50c3024004f9718a12a899f2 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:43:53 +0200 Subject: [PATCH 06/54] MILAB-6720: note the disagreement count in the changeset --- .changeset/region-annotation-at-import.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.changeset/region-annotation-at-import.md b/.changeset/region-annotation-at-import.md index 063ddd2..b83eea2 100644 --- a/.changeset/region-annotation-at-import.md +++ b/.changeset/region-annotation-at-import.md @@ -15,4 +15,9 @@ Each chain of each record also gets a region-annotation status — `Annotated`, `Not applicable` or `Failed`, never empty — so a record whose boundaries could not be located says so instead of silently receiving empty region strings. +The template also reports per-chain outcome counts, including how many annotated records +ANARCI numbered as the *other* chain. Chain is declared by the mapping slot and inferred by +ANARCI, and those can disagree; the count makes a wrong slot assignment visible instead of +letting it import cleanly with every chain label wrong. Nothing acts on the number. + Not yet wired into the import pipeline; the template is callable but no path invokes it. From d0a024b1b91ee4cf435af9050e67a58dd1d8ebf3 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Mon, 17 Aug 2026 19:51:31 +0200 Subject: [PATCH 07/54] MILAB-6720: transcribe the bare-set column contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The axis and column specs a bare set is emitted on, in one library, with the atom that fixes each one named above it. This is transcription rather than design — every domain key, annotation and order priority is settled in the spec corpus. The shape worth stating once: every property column sits on [pl7.app/sampleId, pl7.app/variantKey], and there is ONE frame, not one per chain. The two chains of a paired record are separated by the scClonotypeChain column domain, never by an axis and never by a separate frame. That is the whole difference from the per-chain path the other seven formats take. Nothing emits these yet; the import path that consumes them lands next. Three judgement calls inside the transcription, all noted in the source: - Labels render Heavy/Light while domains stay A/B. The atoms write in both positions, but the one shipped paired producer in the workspace does it this way (mixcr-scfv-clonotyping/workflow/src/mixcr-exports.lib.tengo:173-178). A literal reading would put "A CDR3 aa" in front of the scientist. - The label column's own spec is not fixed by any atom — the atom settles the value, not the spec. It mirrors the block's existing clonotypeLabel column. - The status column carries no scClonotypeChain/index key while the sequence columns do. That is the atom as written, and it reads correctly: the status is a property of the chain's annotation attempt, not of one of its sequences. 23 tengo unit tests cover it, and the workflow package now runs pl-tengo test, so they execute in CI. They pin the things that are silent when wrong rather than the things that throw: that the run-id key claims VDJ and not peptide or amplicon; that the two chains produce distinct specs, since identical ones would dedupe into a single column; that the convention appears in BOTH the domain and the annotation, because dropping the annotation still computes liabilities and computes them against the wrong positions; that exactly one column claims to be the anchor and primary abundance; that the synthetic abundance carries no unit and no synthetic marker; that sampleCount and the cross-sample aggregates are absent; and that assembly is canonical, since map iteration there would break downstream dedup with no error. --- .changeset/bare-set-column-contract.md | 17 ++ workflow/package.json | 3 +- workflow/src/bare-set-specs.lib.tengo | 374 +++++++++++++++++++++++++ workflow/src/bare-set-specs.test.tengo | 279 ++++++++++++++++++ 4 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 .changeset/bare-set-column-contract.md create mode 100644 workflow/src/bare-set-specs.lib.tengo create mode 100644 workflow/src/bare-set-specs.test.tengo diff --git a/.changeset/bare-set-column-contract.md b/.changeset/bare-set-column-contract.md new file mode 100644 index 0000000..21066c9 --- /dev/null +++ b/.changeset/bare-set-column-contract.md @@ -0,0 +1,17 @@ +--- +'@platforma-open/milaboratories.import-vdj.workflow': minor +'@platforma-open/milaboratories.import-vdj': minor +--- + +Add the bare-set column contract, with tengo unit tests + +`bare-set-specs.lib.tengo` holds the axis and column specs a bare imported set emits: one +`pl7.app/variantKey` axis carrying the VDJ run id, the amino-acid variable domain per chain, +seven region columns per chain, a per-chain region-annotation status, the synthetic +abundance, and the record label. Chains are separated by the +`pl7.app/vdj/scClonotypeChain` column domain, so the set is one frame rather than one per +chain. + +Nothing emits these yet — the library is consumed by the import path that lands next. + +Also enables `pl-tengo test` for the workflow package, so these specs are covered in CI. diff --git a/workflow/package.json b/workflow/package.json index 1c24bcc..88b565d 100644 --- a/workflow/package.json +++ b/workflow/package.json @@ -7,7 +7,8 @@ "scripts": { "build": "shx rm -rf dist && pl-tengo build", "format": "/usr/bin/env emacs --script ./format.el || echo 'No emacs.'", - "check": "pl-tengo check" + "check": "pl-tengo check", + "test": "pl-tengo test" }, "dependencies": { "@platforma-open/milaboratories.import-vdj.region-annotation": "workspace:*", diff --git a/workflow/src/bare-set-specs.lib.tengo b/workflow/src/bare-set-specs.lib.tengo new file mode 100644 index 0000000..68dfeb0 --- /dev/null +++ b/workflow/src/bare-set-specs.lib.tengo @@ -0,0 +1,374 @@ +// Column and axis specs for a bare imported set — one paired record per row, keyed on the +// scientist's own identifier, annotated at import. +// +// This library is a transcription, not a design. Every spec here is fixed verbatim by the +// spec corpus at docs/text/work/projects/imported-vdj-sequence-annotations; the atom that +// fixes each one is named above it. If you are tempted to adjust a domain key, an +// annotation or an order priority, the atom is the thing to change first. +// +// Shape, in one paragraph: every property column sits on +// `[pl7.app/sampleId, pl7.app/variantKey]`. There is ONE frame, not one per chain — the two +// chains of a paired record are separated by the `pl7.app/vdj/scClonotypeChain` **column** +// domain, never by an axis and never by a separate frame. That is the whole difference from +// the per-chain path the other formats take. + +maps := import("@platforma-sdk/workflow-tengo:maps") +ll := import("@platforma-sdk/workflow-tengo:ll") + +// Declared chains, in emission order. `A` heavy, `B` light — the vocabulary of +// `pl7.app/vdj/scClonotypeChain`, which `sequence-properties` already reads. +CHAINS := ["A", "B"] + +// Labels use Heavy/Light while domains use A/B. The atoms write `` in both +// positions, but the workspace's one shipped paired producer renders labels as +// "Heavy CDR3 aa" and keeps A/B for the domain +// (blocks/mixcr-scfv-clonotyping/workflow/src/mixcr-exports.lib.tengo:173-178,271). +// Following it: a literal reading would put "A CDR3 aa" in front of the scientist. +CHAIN_LABELS := { + "A": "Heavy", + "B": "Light" +} + +// Seven regions. Order here is emission order and is fixed so the assembled column list is +// canonical across runs. +REGIONS := ["FR1", "CDR1", "CDR2", "FR2", "FR3", "FR4", "CDR3"] + +// [[bare-set-region-columns]] fixes these. Two inherited quirks are carried verbatim +// rather than silently corrected, because both belong to the shared spec map every reader +// draws from: CDR2 sorts ahead of FR2, inverting the biological order, and CDR3 sits four +// orders of magnitude above the rest so it lands beside the identity columns. +REGION_TABLE := { + "FR1": { order: "46950", visibility: "optional" }, + "CDR1": { order: "46940", visibility: "optional" }, + "CDR2": { order: "46935", visibility: "optional" }, + "FR2": { order: "46930", visibility: "optional" }, + "FR3": { order: "46910", visibility: "optional" }, + "FR4": { order: "46900", visibility: "optional" }, + "CDR3": { order: "99000", visibility: "default" } +} + +SCHEMES := ["imgt", "kabat", "chothia"] + +// --- TSV column names ------------------------------------------------------------------- +// Single source of truth shared with software/region-annotation, whose output these specs +// are imported from. Changing one without the other silently produces empty columns. + +keyColumn := "variantKey" +labelColumn := "recordLabel" +abundanceColumn := "clonotypePresence" + +sequenceColumn := func(chain) { return chain + "_sequence" } +regionColumn := func(chain, region) { return chain + "_" + region + "_aa" } +statusColumn := func(chain) { return chain + "_regionAnnotationStatus" } + +// --- helpers ---------------------------------------------------------------------------- + +// Matches the block's existing convention for table-view annotations. +a := func(order, visibility, spec) { + return maps.merge(spec, { + "pl7.app/table/orderPriority": order, + "pl7.app/table/visibility": visibility + }) +} + +validateChain := func(chain) { + ll.assert( + chain == "A" || chain == "B", + "bare-set-specs: unknown chain %v (expected A or B)", chain + ) +} + +validateScheme := func(scheme) { + ll.assert( + scheme == "imgt" || scheme == "kabat" || scheme == "chothia", + "bare-set-specs: unknown numbering scheme %v (expected imgt, kabat or chothia)", scheme + ) +} + +// --- the key axis ------------------------------------------------------------------------- + +/** + * The single key axis, identical on every emitted table. [[bare-set-key-axis]]. + * + * The value is a sha256 of the identity column the scientist selected, so it is opaque; the + * readable name rides on the label column instead ([[record-label-is-the-identifier]]). + * + * `pl7.app/vdj/clonotypingRunId` is load-bearing, not decoration: it is what makes a set on + * this shared axis antibody rather than peptide or amplicon ([[modality-from-run-id-key]]). + * Stamping another modality's run-id key does not mislabel the dataset — it *makes* it that + * modality, to every reader. + * + * No `…/structure` domain key: that belongs to the `scClonotypeKey` vocabulary this axis no + * longer uses, and no `variantKey` producer in the workspace emits one. + * + * The annotation set mirrors peptide-extraction's `variantKey` axis + * (blocks/peptide-extraction/workflow/src/column-specs.lib.tengo:15-35) with the peptide run + * id exchanged for the VDJ one. + */ +keyAxis := func(blockId) { + return { + column: keyColumn, + naRegex: "", + spec: { + type: "String", + name: "pl7.app/variantKey", + domain: { + "pl7.app/vdj/receptor": "IG", + "pl7.app/vdj/clonotypingRunId": blockId + }, + annotations: { + "pl7.app/label": "Record ID", + "pl7.app/table/fontFamily": "monospace", + "pl7.app/table/visibility": "default", + "pl7.app/table/orderPriority": "110000", + "pl7.app/segmentedBy": "[\"pl7.app/vdj/clonotypingRunId\"]" + } + } + } +} + +// --- the columns --------------------------------------------------------------------------- + +/** + * The amino-acid variable domain, one column per mapped chain. [[bare-set-main-sequence-column]]. + * + * Name, alphabet, feature and the table annotations are the block's existing `vdj-aa` spec, + * unchanged. The chain domain key and the two `pl7.app/vdj/…` annotations are the whole delta. + * + * The chain key is what makes the two columns distinct specs — two columns whose specs were + * identical would dedupe into one, and the heavy and light sequences would stop being + * separately addressable. + * + * `/index` is `"primary"` because a mapping assigning two columns to one chain is refused + * ([[two-variants-of-a-chain-refused]]), so it is a constant rather than a dimension. + * + * No convention key: a convention describes a located boundary, and a whole variable domain + * has none ([[convention-travels-in-the-domain]]). + */ +mainSequenceColumn := func(chain) { + validateChain(chain) + return { + id: "main-sequence-" + chain, + column: sequenceColumn(chain), + allowNA: true, + spec: { + name: "pl7.app/vdj/sequence", + valueType: "String", + domain: { + "pl7.app/alphabet": "aminoacid", + "pl7.app/vdj/feature": "VDJRegionInFrame", + "pl7.app/vdj/scClonotypeChain": chain, + "pl7.app/vdj/scClonotypeChain/index": "primary" + }, + annotations: a("46870", "optional", { + "pl7.app/label": CHAIN_LABELS[chain] + " VDJRegionInFrame aa", + "pl7.app/table/fontFamily": "monospace", + "pl7.app/vdj/isMainSequence": "true", + "pl7.app/vdj/isAssemblingFeature": "true" + }) + } + } +} + +/** + * One located region of one chain. Seven per chain, fourteen in all. + * [[bare-set-region-columns]]. + * + * The convention is emitted **twice** — as a domain key for identity and as the existing + * annotation for the one consumer that reads it ([[region-convention-key-and-annotation]]). + * Dropping the annotation is the tidier option and it silently degrades a downstream result: + * Sequence Liabilities collects the convention by string-comparing the annotation across its + * input sequences and forwards it as `--numbering-schema`; absent, it agrees on the empty + * string, omits the flag, and scores FR3 and CDR3 against scheme-blind coordinates. The + * liabilities are still computed, and computed against the wrong positions. + * + * A chain with no located boundaries carries **no value** in any of its seven — not an empty + * string ([[unnumberable-record-is-identified]]); its state is read from the status column. + * That is what `allowNA` is for here. + */ +regionColumnSpec := func(chain, region, scheme) { + validateChain(chain) + validateScheme(scheme) + entry := REGION_TABLE[region] + ll.assert(entry != undefined, "bare-set-specs: unknown region %v", region) + + return { + id: "region-" + chain + "-" + region, + column: regionColumn(chain, region), + allowNA: true, + spec: { + name: "pl7.app/vdj/sequence", + valueType: "String", + domain: { + "pl7.app/alphabet": "aminoacid", + "pl7.app/vdj/feature": region, + "pl7.app/vdj/numberingSchema": scheme, + "pl7.app/vdj/scClonotypeChain": chain, + "pl7.app/vdj/scClonotypeChain/index": "primary" + }, + annotations: a(entry.order, entry.visibility, { + "pl7.app/label": CHAIN_LABELS[chain] + " " + region + " aa", + "pl7.app/table/fontFamily": "monospace", + "pl7.app/vdj/numberingSchema": scheme + }) + } + } +} + +/** + * Whether this chain's boundaries could be located, one column per chain per record. + * [[bare-set-region-status-column]]. + * + * Per chain rather than per record because the two chains are annotated independently: one + * status per record could not express a heavy chain located and a light chain failed, and + * would have to pick one outcome to report. + * + * Never null — it carries the success member too, so absence of a verdict is not a state. + * `String` is not a preference: `xsv.importFile` rejects `Boolean`. + * + * Note the domain carries no `/index` key, unlike the sequence columns. That is the atom as + * written: the status is a property of the chain's annotation attempt, not of one of its + * sequences. + * + * `46895` puts it between FR4 (46900) and `pl7.app/vdj/isotype` (46890), so the verdict sits + * with the boundaries it describes rather than among the identity columns. + */ +statusColumnSpec := func(chain, scheme) { + validateChain(chain) + validateScheme(scheme) + return { + id: "region-status-" + chain, + column: statusColumn(chain), + allowNA: false, + spec: { + name: "pl7.app/vdj/regionAnnotationStatus", + valueType: "String", + domain: { + "pl7.app/vdj/numberingSchema": scheme, + "pl7.app/vdj/scClonotypeChain": chain + }, + annotations: a("46895", "default", { + "pl7.app/label": CHAIN_LABELS[chain] + " region annotation status", + "pl7.app/isDiscreteFilter": "true", + "pl7.app/discreteValues": "[\"Annotated\",\"Not applicable\",\"Failed\"]" + }) + } + } +} + +/** + * The synthetic abundance. [[bare-set-synthetic-abundance-column]]. + * + * The value is the literal 1 on every row, never null. One per **record**, not per chain — + * presence is a property of the record, and the record is the pair. + * + * This annotation set is exactly what makes Clustering run, which is the whole point of the + * column: that block's bundle does an `addSingle` for the triple isAbundance / + * normalized:"false" / isPrimary:"true", and `isAnchor` is what makes the dataset selectable + * at all in the other four consumers ([[anchor-carries-selectability]]). + * + * **No `pl7.app/abundance/unit`, and none may be added** — nothing was measured, so `reads` + * or `molecules` would each be a false claim of exactly the kind this design already accepts + * one of. **No marker declaring the value synthetic**, by decision: minting one would be a + * claim on documented platform vocabulary in service of a column meant to be short-lived. + * + * The column is transitional. Nothing on the data distinguishes it from a measured + * abundance, so if it outlives the plan that removes it there is no way to find it by query. + */ +abundanceColumnSpec := func() { + return { + id: "clonotype-presence", + column: abundanceColumn, + allowNA: false, + spec: { + name: "pl7.app/vdj/clonotypePresence", + valueType: "Int", + domain: {}, + annotations: a("90000", "default", { + "pl7.app/label": "Present in Sample", + "pl7.app/min": "1", + "pl7.app/isAbundance": "true", + "pl7.app/abundance/normalized": "false", + "pl7.app/abundance/isPrimary": "true", + "pl7.app/isAnchor": "true" + }) + } + } +} + +/** + * The record's readable name — the identity column's value, verbatim. + * [[record-label-is-the-identifier]]. + * + * It replaces the `C-XXXXX` label import mints from the key; nothing shows the minted form. + * With the key hashed, this is the only place the scientist's own name appears. + * + * The spec shape mirrors the block's existing `clonotypeLabel` column rather than being + * minted here. NOT FIXED BY AN ATOM: the atom settles the value, not the spec — the label + * string below is chosen to agree with the key axis's own "Record ID". + */ +labelColumnSpec := func() { + return { + id: "record-label", + column: labelColumn, + allowNA: false, + spec: { + name: "pl7.app/label", + valueType: "String", + annotations: a("109000", "default", { + "pl7.app/label": "Record ID", + "pl7.app/description": "The identifier this record carried in the imported file." + }) + } + } +} + +// --- assembly ----------------------------------------------------------------------------- + +/** + * Every property column a bare set emits, in canonical order. + * + * `chains` is the declared chains actually mapped, a subset of CHAINS in CHAINS order. + * Iteration is over arrays throughout, so the assembled list is byte-identical for equal + * inputs and the pure templates downstream dedup. + */ +allColumns := func(chains, scheme) { + ll.assert(len(chains) > 0, "bare-set-specs: at least one chain must be mapped") + validateScheme(scheme) + + columns := [labelColumnSpec(), abundanceColumnSpec()] + + for chain in chains { + columns = append(columns, mainSequenceColumn(chain)) + } + for chain in chains { + for region in REGIONS { + columns = append(columns, regionColumnSpec(chain, region, scheme)) + } + columns = append(columns, statusColumnSpec(chain, scheme)) + } + + return columns +} + +export { + CHAINS: CHAINS, + CHAIN_LABELS: CHAIN_LABELS, + REGIONS: REGIONS, + SCHEMES: SCHEMES, + + keyColumn: keyColumn, + labelColumn: labelColumn, + abundanceColumn: abundanceColumn, + sequenceColumn: sequenceColumn, + regionColumn: regionColumn, + statusColumn: statusColumn, + + keyAxis: keyAxis, + mainSequenceColumn: mainSequenceColumn, + regionColumnSpec: regionColumnSpec, + statusColumnSpec: statusColumnSpec, + abundanceColumnSpec: abundanceColumnSpec, + labelColumnSpec: labelColumnSpec, + allColumns: allColumns +} diff --git a/workflow/src/bare-set-specs.test.tengo b/workflow/src/bare-set-specs.test.tengo new file mode 100644 index 0000000..ba07162 --- /dev/null +++ b/workflow/src/bare-set-specs.test.tengo @@ -0,0 +1,279 @@ +test := import("@platforma-sdk/workflow-tengo:test") +specs := import(":bare-set-specs") + +byId := func(columns, id) { + for c in columns { + if c.id == id { + return c + } + } + return undefined +} + +ids := func(columns) { + out := [] + for c in columns { + out = append(out, c.id) + } + return out +} + +// --- key axis --------------------------------------------------------------------------- + +Test_keyAxis_isVariantKeyWithRunIdAndReceptor := func() { + axis := specs.keyAxis("block-42") + + test.isEqual("pl7.app/variantKey", axis.spec.name, "key axis name") + test.isEqual("String", axis.spec.type, "key axis type") + test.isEqual({ + "pl7.app/vdj/receptor": "IG", + "pl7.app/vdj/clonotypingRunId": "block-42" + }, axis.spec.domain, "key axis domain") +} + +// The run-id key is what makes a set on this shared axis antibody rather than peptide or +// amplicon. A reader that takes modality from the axis *name* is wrong; this is the carrier. +Test_keyAxis_carriesTheVdjRunIdAndNotAnotherModalitys := func() { + domain := specs.keyAxis("b1").spec.domain + + test.isEqual("b1", domain["pl7.app/vdj/clonotypingRunId"], "vdj run id") + test.isTrue(domain["pl7.app/peptide/extractionRunId"] == undefined, "must not claim peptide") + test.isTrue(domain["pl7.app/repertoire/extractionRunId"] == undefined, "must not claim amplicon") +} + +// `scClonotypeKey/structure` belongs to the vocabulary this axis no longer uses, and no +// variantKey producer in the workspace emits a structure key. +Test_keyAxis_carriesNoStructureKey := func() { + domain := specs.keyAxis("b1").spec.domain + + test.isTrue(domain["pl7.app/vdj/scClonotypeKey/structure"] == undefined, "no structure key") + test.isEqual(2, len(domain), "exactly two domain keys") +} + +// --- main sequence ---------------------------------------------------------------------- + +Test_mainSequence_carriesChainAndIndexAndTheTwoAnnotations := func() { + col := specs.mainSequenceColumn("A") + + test.isEqual("pl7.app/vdj/sequence", col.spec.name, "name") + test.isEqual("aminoacid", col.spec.domain["pl7.app/alphabet"], "alphabet") + test.isEqual("VDJRegionInFrame", col.spec.domain["pl7.app/vdj/feature"], "feature") + test.isEqual("A", col.spec.domain["pl7.app/vdj/scClonotypeChain"], "chain") + test.isEqual("primary", col.spec.domain["pl7.app/vdj/scClonotypeChain/index"], "chain index") + + // Without these two, Lead Selection produces no output and raises no error. + test.isEqual("true", col.spec.annotations["pl7.app/vdj/isMainSequence"], "isMainSequence") + test.isEqual("true", col.spec.annotations["pl7.app/vdj/isAssemblingFeature"], "isAssemblingFeature") +} + +// A whole variable domain has no located boundary, so it carries no convention. +Test_mainSequence_carriesNoConventionKey := func() { + col := specs.mainSequenceColumn("B") + + test.isTrue(col.spec.domain["pl7.app/vdj/numberingSchema"] == undefined, "no scheme in domain") + test.isTrue(col.spec.annotations["pl7.app/vdj/numberingSchema"] == undefined, "no scheme annotation") +} + +// Two columns whose specs were identical would dedupe into one, and the heavy and light +// sequences would stop being separately addressable. The chain key is what prevents that. +Test_mainSequence_chainsAreDistinctSpecs := func() { + a := specs.mainSequenceColumn("A") + b := specs.mainSequenceColumn("B") + + test.isTrue(a.spec.domain != b.spec.domain, "chain A and B must not share a domain") + test.isTrue(a.column != b.column, "chain A and B must not share a source column") +} + +// --- region columns ---------------------------------------------------------------------- + +// FR4 is the region every list in the workspace has been missing. +Test_regions_areSevenAndIncludeFR4 := func() { + test.isEqual(7, len(specs.REGIONS), "seven regions") + + seen := {} + for r in specs.REGIONS { + seen[r] = true + } + for r in ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"] { + test.isTrue(seen[r] == true, "region " + r + " must be present") + } +} + +// The convention is emitted twice on purpose: the domain copy buys distinct identity, the +// annotation copy is what Sequence Liabilities reads to pick its coordinate map. +Test_region_carriesConventionInDomainAndAnnotation := func() { + col := specs.regionColumnSpec("A", "CDR3", "kabat") + + test.isEqual("kabat", col.spec.domain["pl7.app/vdj/numberingSchema"], "scheme in domain") + test.isEqual("kabat", col.spec.annotations["pl7.app/vdj/numberingSchema"], "scheme in annotation") +} + +Test_region_carriesChainAndFeatureAndIndex := func() { + col := specs.regionColumnSpec("B", "FR4", "imgt") + + test.isEqual("pl7.app/vdj/sequence", col.spec.name, "name") + test.isEqual("FR4", col.spec.domain["pl7.app/vdj/feature"], "feature") + test.isEqual("B", col.spec.domain["pl7.app/vdj/scClonotypeChain"], "chain") + test.isEqual("primary", col.spec.domain["pl7.app/vdj/scClonotypeChain/index"], "chain index") + test.isEqual("aminoacid", col.spec.domain["pl7.app/alphabet"], "alphabet") +} + +// Two inherited quirks carried verbatim rather than silently corrected: CDR2 sorts ahead of +// FR2, and CDR3 sits four orders of magnitude above the rest. +Test_region_ordersAndVisibilityMatchTheContract := func() { + expect := { + "FR1": ["46950", "optional"], + "CDR1": ["46940", "optional"], + "CDR2": ["46935", "optional"], + "FR2": ["46930", "optional"], + "FR3": ["46910", "optional"], + "FR4": ["46900", "optional"], + "CDR3": ["99000", "default"] + } + + for region in specs.REGIONS { + want := expect[region] + col := specs.regionColumnSpec("A", region, "imgt") + test.isEqual(want[0], col.spec.annotations["pl7.app/table/orderPriority"], region + " order") + test.isEqual(want[1], col.spec.annotations["pl7.app/table/visibility"], region + " visibility") + } +} + +// A chain with no located boundaries carries no value — not an empty string. +Test_region_allowsNA := func() { + test.isTrue(specs.regionColumnSpec("A", "FR1", "imgt").allowNA, "regions must allow NA") +} + +// A convention-blind query matches every convention, but two sets drawn under different +// conventions must stay separately selectable. Only the domain gives that. +Test_region_schemesProduceDistinctDomains := func() { + imgt := specs.regionColumnSpec("A", "CDR1", "imgt") + kabat := specs.regionColumnSpec("A", "CDR1", "kabat") + + test.isTrue(imgt.spec.domain != kabat.spec.domain, "schemes must not share a domain") +} + +// --- status column ------------------------------------------------------------------------- + +Test_status_isAClosedThreeMemberEnum := func() { + col := specs.statusColumnSpec("A", "imgt") + + test.isEqual("pl7.app/vdj/regionAnnotationStatus", col.spec.name, "name") + test.isEqual("String", col.spec.valueType, "String, because xsv.importFile rejects Boolean") + test.isEqual("true", col.spec.annotations["pl7.app/isDiscreteFilter"], "discrete filter") + test.isEqual( + "[\"Annotated\",\"Not applicable\",\"Failed\"]", + col.spec.annotations["pl7.app/discreteValues"], + "closed enum, success member included" + ) +} + +// A null status would restore the ambiguity the column exists to remove. +Test_status_isNeverNull := func() { + test.isFalse(specs.statusColumnSpec("B", "imgt").allowNA, "status must not allow NA") +} + +Test_status_isPerChainAndPerScheme := func() { + a := specs.statusColumnSpec("A", "imgt") + b := specs.statusColumnSpec("B", "imgt") + k := specs.statusColumnSpec("A", "kabat") + + test.isTrue(a.spec.domain != b.spec.domain, "one status per chain") + test.isTrue(a.spec.domain != k.spec.domain, "one status per scheme") + test.isEqual("46895", a.spec.annotations["pl7.app/table/orderPriority"], "sits between FR4 and isotype") +} + +// --- synthetic abundance --------------------------------------------------------------------- + +// This exact annotation set is what makes Clustering's bundle query match and what makes the +// dataset selectable in the other four consumers. +Test_abundance_carriesTheTripleAndTheAnchor := func() { + col := specs.abundanceColumnSpec() + ann := col.spec.annotations + + test.isEqual("pl7.app/vdj/clonotypePresence", col.spec.name, "name") + test.isEqual("Int", col.spec.valueType, "Int: one value, nothing derived from it") + test.isEqual("true", ann["pl7.app/isAbundance"], "isAbundance") + test.isEqual("false", ann["pl7.app/abundance/normalized"], "normalized false") + test.isEqual("true", ann["pl7.app/abundance/isPrimary"], "isPrimary") + test.isEqual("true", ann["pl7.app/isAnchor"], "isAnchor") + test.isFalse(col.allowNA, "never null") +} + +// Nothing was measured, so a unit would be a false claim. None may be added. +Test_abundance_carriesNoUnitAndNoSyntheticMarker := func() { + ann := specs.abundanceColumnSpec().spec.annotations + + test.isTrue(ann["pl7.app/abundance/unit"] == undefined, "no unit") + test.isTrue(ann["pl7.app/abundance/synthetic"] == undefined, "no synthetic marker, by decision") + test.isEqual(0, len(specs.abundanceColumnSpec().spec.domain), "empty domain") +} + +// --- assembly --------------------------------------------------------------------------------- + +Test_allColumns_pairedSetEmitsTwentyColumns := func() { + columns := specs.allColumns(["A", "B"], "imgt") + + // label + abundance + 2 main sequences + 14 regions + 2 statuses + test.isEqual(20, len(columns), "paired set column count") +} + +Test_allColumns_singleChainEmitsHalfTheChainColumns := func() { + columns := specs.allColumns(["A"], "imgt") + + // label + abundance + 1 main sequence + 7 regions + 1 status + test.isEqual(11, len(columns), "single chain column count") + test.isTrue(byId(columns, "main-sequence-B") == undefined, "no B columns when B is unmapped") + test.isTrue(byId(columns, "region-status-B") == undefined, "no B status when B is unmapped") +} + +// sampleCount is never emitted: a count of how often a record repeats must not be presented +// as an abundance, and the synthetic column's cross-sample aggregates are suppressed too. +Test_allColumns_omitsSampleCountAndAggregates := func() { + for c in specs.allColumns(["A", "B"], "imgt") { + test.isTrue(c.spec.name != "pl7.app/vdj/sampleCount", "sampleCount must not be emitted") + test.isTrue( + c.spec.name != "pl7.app/vdj/clonotypePresenceTotal", + "no cross-sample total of the synthetic abundance" + ) + test.isTrue( + c.spec.name != "pl7.app/vdj/clonotypePresenceMean", + "no cross-sample mean of the synthetic abundance" + ) + } +} + +// Exactly one column may claim to be the anchor and primary abundance. +Test_allColumns_hasExactlyOneAnchor := func() { + anchors := 0 + primaries := 0 + for c in specs.allColumns(["A", "B"], "imgt") { + if c.spec.annotations["pl7.app/isAnchor"] == "true" { + anchors = anchors + 1 + } + if c.spec.annotations["pl7.app/abundance/isPrimary"] == "true" { + primaries = primaries + 1 + } + } + + test.isEqual(1, anchors, "exactly one anchor column") + test.isEqual(1, primaries, "exactly one primary abundance") +} + +// Pure templates downstream dedup on their inputs, so equal inputs must give byte-equal +// output. Map iteration anywhere in assembly would break this silently. +Test_allColumns_isCanonical := func() { + first := ids(specs.allColumns(["A", "B"], "kabat")) + second := ids(specs.allColumns(["A", "B"], "kabat")) + + test.isEqual(first, second, "column order must be stable across calls") +} + +// Every emitted column must be addressable on its own: no two may share a source column. +Test_allColumns_sourceColumnsAreUnique := func() { + seen := {} + for c in specs.allColumns(["A", "B"], "imgt") { + test.isTrue(seen[c.column] == undefined, "duplicate source column " + c.column) + seen[c.column] = true + } +} From 704a96e1c1eeb6f1178077fbcb9b02f15b32d152 Mon Sep 17 00:00:00 2001 From: Elena Erokhina Date: Tue, 18 Aug 2026 12:03:19 +0200 Subject: [PATCH 08/54] MILAB-6720: import a bare paired set through the existing door MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the emission path end to end: mapping -> unpivot -> key -> ANARCI -> one frame. Builds and type-checks; it has not yet been run against a backend, so nothing here is verified behaviour. The path is selected by the presence of a chain-slotted sequence mapping and an identity column, not by a format the scientist picks. Whether a set is bare is something the block works out from what was mapped — asking up front was the rejected alternative, and the UI accordingly offers the slots rather than a mode. Structure: import-bare-set.tpl.tengo per sample: unpivot, mint the key, presence=1, then annotate. No V/J filter, no locus matching, no groupBy collapse. process-bare-set.tpl.tengo one processColumn pass into one frame. annotate-regions.lib.tengo was a template; now a library, because its only caller is a processColumn body, and a tpl.light body runs execs but does not render templates. bare-set-specs.lib.tengo gains columnsBySource, so the process template does not infer from column names which TSV a column comes from. One pass, not two. The per-chain path takes a second processColumn only to compute sampleCount and the CountTotal/FractionMean pair across samples, and a bare set emits none of those. Since every emitted column sits on [sampleId, variantKey] with nothing collapsing across samples, the second pass has no work left. The suppression is therefore structural rather than a filter: the pass that computed those columns is absent. Model: bareSet is additive to BlockArgs, so no persisted state changes shape and no existing project needs a migration. It is also the one field V3 will re-home, which keeps that migration mechanical. --- .changeset/bare-set-import-path.md | 18 ++ model/src/index.ts | 42 +++ ui/src/pages/MainPage.vue | 292 ++++++++++++------ ...s.tpl.tengo => annotate-regions.lib.tengo} | 29 +- workflow/src/bare-set-specs.lib.tengo | 34 +- workflow/src/import-bare-set.tpl.tengo | 121 ++++++++ workflow/src/main.tpl.tengo | 45 +++ workflow/src/process-bare-set.tpl.tengo | 120 +++++++ 8 files changed, 587 insertions(+), 114 deletions(-) create mode 100644 .changeset/bare-set-import-path.md rename workflow/src/{annotate-regions.tpl.tengo => annotate-regions.lib.tengo} (87%) create mode 100644 workflow/src/import-bare-set.tpl.tengo create mode 100644 workflow/src/process-bare-set.tpl.tengo diff --git a/.changeset/bare-set-import-path.md b/.changeset/bare-set-import-path.md new file mode 100644 index 0000000..7bbcf78 --- /dev/null +++ b/.changeset/bare-set-import-path.md @@ -0,0 +1,18 @@ +--- +'@platforma-open/milaboratories.import-vdj.workflow': minor +'@platforma-open/milaboratories.import-vdj.model': minor +'@platforma-open/milaboratories.import-vdj.ui': minor +'@platforma-open/milaboratories.import-vdj': minor +--- + +Import bare paired sequence sets + +A file of receptor sequences with no gene calls, no region boundaries and no count can now be +imported as a custom format. Each row becomes one record holding both chains, keyed on the +hash of an identity column the scientist selects and labelled with that column's value. +Regions are located by ANARCI during the import, under a numbering scheme the scientist +chooses, and every record carries a per-chain annotation status. + +The custom-format validity rule no longer demands a V gene, a J gene and an abundance for +such a set; it requires a sequence mapped to a chain and an identity column instead. The +other formats are untouched. diff --git a/model/src/index.ts b/model/src/index.ts index ba57ee1..a8006cf 100644 --- a/model/src/index.ts +++ b/model/src/index.ts @@ -29,6 +29,36 @@ export type BlockArgs = { customMapping?: Record; primaryCountType?: "read" | "umi"; secondaryCountType?: "read" | "umi"; + /** + * Present only for a bare paired set: sequences with no gene calls, no region boundaries + * and no count, imported as one record per row and annotated at import. + * + * Its presence is what selects the bare path in the workflow. The scientist still picks + * "Custom" as the format — whether a set is bare is something the block discovers from the + * mapping, not something it asks for up front. + * + * Additive to the existing args on purpose: no persisted state changes shape, so no + * project saved before this needs a migration. + */ + bareSet?: BareSetMapping; +}; + +export type BareSetChain = "A" | "B"; + +export type BareSetMapping = { + /** + * The column whose value identifies the record. Required, never inferred: the record key + * is its hash and the record label is its value, so a set without one has nothing to key on. + */ + identity: string; + /** + * Amino-acid variable domain per chain — `A` heavy, `B` light. The chain comes from the + * slot the column was assigned to, so the file needs no chain column and nothing is matched + * against a locus map. A row carrying both is unpivoted into one record, not split into two. + */ + sequences: Partial>; + /** The numbering convention ANARCI is asked for, and the one recorded on every region. */ + scheme: "imgt" | "kabat" | "chothia"; }; export type UiState = { @@ -73,6 +103,18 @@ export const platforma = BlockModel.create() if (!Array.isArray(chains) || chains.length === 0) return false; if (format === "custom") { + // A bare set drops the V gene, the J gene and the abundance — it supplies none of them, + // and the rule that demanded all three could not tell a bare set from a malformed + // repertoire export. What it requires instead is a sequence mapped to a chain and an + // identity column, because the key is the identity's hash and the label is its value. + const bare = ctx.args.bareSet; + if (bare !== undefined) { + const hasIdentity = !!bare.identity; + const hasChainSequence = !!bare.sequences?.A || !!bare.sequences?.B; + const hasScheme = !!bare.scheme; + return hasIdentity && hasChainSequence && hasScheme; + } + const m = customMapping ?? {}; const hasSeq = !!m["cdr3-nt"] || !!m["cdr3-aa"]; const hasV = !!m["v-gene"]; diff --git a/ui/src/pages/MainPage.vue b/ui/src/pages/MainPage.vue index 0f23e35..bd412ad 100644 --- a/ui/src/pages/MainPage.vue +++ b/ui/src/pages/MainPage.vue @@ -1,4 +1,5 @@