mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Performance metrics (#619)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
11ddaad88d
commit
806400f5f2
24
.vscode/launch.json
vendored
24
.vscode/launch.json
vendored
@ -11,7 +11,9 @@
|
|||||||
"args": ["src/__start.ts"],
|
"args": ["src/__start.ts"],
|
||||||
"env": {
|
"env": {
|
||||||
"ELASTIC_URL": "http://localhost:9200",
|
"ELASTIC_URL": "http://localhost:9200",
|
||||||
"MONGO_URL": "mongodb://localhost:27017"
|
"MONGO_URL": "mongodb://localhost:27017",
|
||||||
|
"APM_SERVER_URL2": "http://localhost:8200",
|
||||||
|
"METRICS_CONSOLE": "true" // Show metrics in console evert 30 seconds.
|
||||||
},
|
},
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
@ -45,5 +47,25 @@
|
|||||||
"cwd": "${workspaceRoot}/dev/generator",
|
"cwd": "${workspaceRoot}/dev/generator",
|
||||||
"protocol": "inspector"
|
"protocol": "inspector"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug tool",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
// "args": ["src/index.ts", "import-xml", "ws1", "/Users/haiodo/Develop/private/hardware/suho/Кандидаты/Кандидаты.xml"],
|
||||||
|
"args": ["src/index.ts", "restore-workspace", "ws1", "../../temp/ws1/"],
|
||||||
|
"env": {
|
||||||
|
"MINIO_ACCESS_KEY":"minioadmin",
|
||||||
|
"MINIO_SECRET_KEY":"minioadmin",
|
||||||
|
"MINIO_ENDPOINT":"localhost",
|
||||||
|
"MONGO_URL":"mongodb://localhost:27017",
|
||||||
|
"TRANSACTOR_URL":"ws:/localhost:3333",
|
||||||
|
"TELEGRAM_DATABASE":"telegram-service",
|
||||||
|
"ELASTIC_URL":"http://localhost:9200",
|
||||||
|
},
|
||||||
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register" ],
|
||||||
|
"sourceMaps": true,
|
||||||
|
"cwd": "${workspaceRoot}/dev/tool",
|
||||||
|
"protocol": "inspector"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,14 @@
|
|||||||
"safeForSimultaneousRushProcesses": true,
|
"safeForSimultaneousRushProcesses": true,
|
||||||
"shellCommand": "node templates/apply.js"
|
"shellCommand": "node templates/apply.js"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"commandKind": "global",
|
||||||
|
"name": "ts-clean",
|
||||||
|
"summary": "Clean tsconfig.tsbuildinfo",
|
||||||
|
"description": "Clean typescript incremental cache",
|
||||||
|
"safeForSimultaneousRushProcesses": true,
|
||||||
|
"shellCommand": "find .|grep tsconfig.tsbuildinfo | xargs rm | pwd"
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,6 +125,7 @@ specifiers:
|
|||||||
css-loader: ^5.2.1
|
css-loader: ^5.2.1
|
||||||
deep-equal: ^2.0.5
|
deep-equal: ^2.0.5
|
||||||
dotenv-webpack: ^7.0.2
|
dotenv-webpack: ^7.0.2
|
||||||
|
elastic-apm-node: ~3.26.0
|
||||||
eslint: ^7.32.0
|
eslint: ^7.32.0
|
||||||
eslint-plugin-import: ^2.25.3
|
eslint-plugin-import: ^2.25.3
|
||||||
eslint-plugin-node: ^11.1.0
|
eslint-plugin-node: ^11.1.0
|
||||||
@ -293,6 +294,7 @@ dependencies:
|
|||||||
css-loader: 5.2.7_webpack@5.65.0
|
css-loader: 5.2.7_webpack@5.65.0
|
||||||
deep-equal: 2.0.5
|
deep-equal: 2.0.5
|
||||||
dotenv-webpack: 7.0.3_webpack@5.65.0
|
dotenv-webpack: 7.0.3_webpack@5.65.0
|
||||||
|
elastic-apm-node: 3.26.0
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-plugin-import: 2.25.3_eslint@7.32.0
|
eslint-plugin-import: 2.25.3_eslint@7.32.0
|
||||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||||
@ -673,6 +675,20 @@ packages:
|
|||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@elastic/ecs-helpers/1.1.0:
|
||||||
|
resolution: {integrity: sha512-MDLb2aFeGjg46O5mLpdCzT5yOUDnXToJSrco2ShqGIXxNJaM8uJjX+4nd+hRYV4Vex8YJyDtOFEVBldQct6ndg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
fast-json-stringify: 2.7.12
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@elastic/ecs-pino-format/1.3.0:
|
||||||
|
resolution: {integrity: sha512-U8D57gPECYoRCcwREsrXKBtqeyFFF/KAwHi4rG1u/oQhAg91Kzw8ZtUQJXD/DMDieLOqtbItFr2FRBWI3t3wog==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
'@elastic/ecs-helpers': 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@elastic/elasticsearch/7.16.0:
|
/@elastic/elasticsearch/7.16.0:
|
||||||
resolution: {integrity: sha512-lMY2MFZZFG3om7QNHninxZZOXYx3NdIUwEISZxqaI9dXPoL3DNhU31keqjvx1gN6T74lGXAzrRNP4ag8CJ/VXw==}
|
resolution: {integrity: sha512-lMY2MFZZFG3om7QNHninxZZOXYx3NdIUwEISZxqaI9dXPoL3DNhU31keqjvx1gN6T74lGXAzrRNP4ag8CJ/VXw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -2323,6 +2339,10 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/after-all-results/2.0.0:
|
||||||
|
resolution: {integrity: sha1-asL8ICtQD4jaj09VMM+hAPTGotA=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ajv-errors/1.0.1_ajv@6.12.6:
|
/ajv-errors/1.0.1_ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==}
|
resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2584,6 +2604,12 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/async-cache/1.1.0:
|
||||||
|
resolution: {integrity: sha1-SppaidBl7F2OUlS9nulrp2xTK1o=}
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 4.1.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/async-each/1.0.3:
|
/async-each/1.0.3:
|
||||||
resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==}
|
resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2592,6 +2618,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
|
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/async-value-promise/1.1.1:
|
||||||
|
resolution: {integrity: sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==}
|
||||||
|
dependencies:
|
||||||
|
async-value: 1.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/async-value/1.2.2:
|
||||||
|
resolution: {integrity: sha1-hFF6Hny2saW14YH6Mb4QQ3t/sSU=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/async/2.6.3:
|
/async/2.6.3:
|
||||||
resolution: {integrity: sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==}
|
resolution: {integrity: sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2612,6 +2648,11 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/atomic-sleep/1.0.0:
|
||||||
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/autoprefixer/10.4.0_postcss@8.4.5:
|
/autoprefixer/10.4.0_postcss@8.4.5:
|
||||||
resolution: {integrity: sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==}
|
resolution: {integrity: sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@ -2745,6 +2786,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/basic-auth/2.0.1:
|
||||||
|
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/batch/0.6.1:
|
/batch/0.6.1:
|
||||||
resolution: {integrity: sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=}
|
resolution: {integrity: sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2769,6 +2817,10 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/binary-search/1.3.6:
|
||||||
|
resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/bindings/1.5.0:
|
/bindings/1.5.0:
|
||||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@ -2852,6 +2904,12 @@ packages:
|
|||||||
fill-range: 7.0.1
|
fill-range: 7.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/breadth-filter/2.0.0:
|
||||||
|
resolution: {integrity: sha512-thQShDXnFWSk2oVBixRCyrWsFoV5tfOpWKHmxwafHQDNxCfDBk539utpvytNjmlFrTMqz41poLwJvA1MW3z0MQ==}
|
||||||
|
dependencies:
|
||||||
|
object.entries: 1.1.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/brfs/2.0.2:
|
/brfs/2.0.2:
|
||||||
resolution: {integrity: sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==}
|
resolution: {integrity: sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -3329,6 +3387,14 @@ packages:
|
|||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/console-log-level/1.4.1:
|
||||||
|
resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/container-info/1.1.0:
|
||||||
|
resolution: {integrity: sha512-eD2zLAmxGS2kmL4f1jY8BdOqnmpL6X70kvzTBW/9FIQnxoxiBJ4htMsTmtPLPWRs7NHYFvqKQ1VtppV08mdsQA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/content-disposition/0.5.4:
|
/content-disposition/0.5.4:
|
||||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -3908,6 +3974,61 @@ packages:
|
|||||||
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
|
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/elastic-apm-http-client/10.3.0:
|
||||||
|
resolution: {integrity: sha512-BAqB7k5JA/x09L8BVj04WRoknRptmW2rLAoHQVrPvPhUm/IgNz63wPfiBuhWVE//Hl7xEpURO5pMV6az0UArkA==}
|
||||||
|
engines: {node: ^8.6.0 || 10 || >=12}
|
||||||
|
dependencies:
|
||||||
|
breadth-filter: 2.0.0
|
||||||
|
container-info: 1.1.0
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
fast-safe-stringify: 2.1.1
|
||||||
|
fast-stream-to-buffer: 1.0.0
|
||||||
|
object-filter-sequence: 1.0.0
|
||||||
|
readable-stream: 3.6.0
|
||||||
|
stream-chopper: 3.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/elastic-apm-node/3.26.0:
|
||||||
|
resolution: {integrity: sha512-MwYFlBTlcHI8GGQXLnnEm70JJ4RRFkHCY1D3Wt2027l8T/Ye5tgssMSiKyRbjb9bVdibbte73Xn8HF8i35UaxA==}
|
||||||
|
engines: {node: ^8.6.0 || 10 || 12 || 14 || 15 || 16 || 17}
|
||||||
|
dependencies:
|
||||||
|
'@elastic/ecs-pino-format': 1.3.0
|
||||||
|
after-all-results: 2.0.0
|
||||||
|
async-cache: 1.1.0
|
||||||
|
async-value-promise: 1.1.1
|
||||||
|
basic-auth: 2.0.1
|
||||||
|
cookie: 0.4.1
|
||||||
|
core-util-is: 1.0.3
|
||||||
|
elastic-apm-http-client: 10.3.0
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
error-callsites: 2.0.4
|
||||||
|
error-stack-parser: 2.0.6
|
||||||
|
escape-string-regexp: 4.0.0
|
||||||
|
fast-safe-stringify: 2.1.1
|
||||||
|
http-headers: 3.0.2
|
||||||
|
is-native: 1.0.1
|
||||||
|
load-source-map: 2.0.0
|
||||||
|
lru-cache: 6.0.0
|
||||||
|
measured-reporting: 1.51.1
|
||||||
|
monitor-event-loop-delay: 1.0.0
|
||||||
|
object-filter-sequence: 1.0.0
|
||||||
|
object-identity-map: 1.0.2
|
||||||
|
original-url: 1.2.3
|
||||||
|
pino: 6.13.3
|
||||||
|
read-pkg-up: 7.0.1
|
||||||
|
relative-microtime: 2.0.0
|
||||||
|
require-in-the-middle: 5.1.0
|
||||||
|
semver: 6.3.0
|
||||||
|
set-cookie-serde: 1.0.0
|
||||||
|
shallow-clone-shim: 2.0.0
|
||||||
|
sql-summary: 1.0.1
|
||||||
|
traceparent: 1.0.0
|
||||||
|
traverse: 0.6.6
|
||||||
|
unicode-byte-truncate: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/electron-to-chromium/1.4.24:
|
/electron-to-chromium/1.4.24:
|
||||||
resolution: {integrity: sha512-erwx5r69B/WFfFuF2jcNN0817BfDBdC4765kQ6WltOMuwsimlQo3JTEq0Cle+wpHralwdeX3OfAtw/mHxPK0Wg==}
|
resolution: {integrity: sha512-erwx5r69B/WFfFuF2jcNN0817BfDBdC4765kQ6WltOMuwsimlQo3JTEq0Cle+wpHralwdeX3OfAtw/mHxPK0Wg==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3980,12 +4101,23 @@ packages:
|
|||||||
prr: 1.0.1
|
prr: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/error-callsites/2.0.4:
|
||||||
|
resolution: {integrity: sha512-V877Ch4FC4FN178fDK1fsrHN4I1YQIBdtjKrHh3BUHMnh3SMvwUVrqkaOgDpUuevgSNna0RBq6Ox9SGlxYrigA==}
|
||||||
|
engines: {node: '>=6.x'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/error-ex/1.3.2:
|
/error-ex/1.3.2:
|
||||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.2.1
|
is-arrayish: 0.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/error-stack-parser/2.0.6:
|
||||||
|
resolution: {integrity: sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==}
|
||||||
|
dependencies:
|
||||||
|
stackframe: 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/es-abstract/1.19.1:
|
/es-abstract/1.19.1:
|
||||||
resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==}
|
resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -4684,10 +4816,35 @@ packages:
|
|||||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fast-json-stringify/2.7.12:
|
||||||
|
resolution: {integrity: sha512-4hjwZDPmgj/ZUKXhEWovGPciE/5mWtAIQQxN+2VBDFun7DRTk2oOItbu9ZZp6kqj+eZ/u7z+dgBgM74cfGRnBQ==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
dependencies:
|
||||||
|
ajv: 6.12.6
|
||||||
|
deepmerge: 4.2.2
|
||||||
|
rfdc: 1.3.0
|
||||||
|
string-similarity: 4.0.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fast-levenshtein/2.0.6:
|
/fast-levenshtein/2.0.6:
|
||||||
resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}
|
resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fast-redact/3.0.2:
|
||||||
|
resolution: {integrity: sha512-YN+CYfCVRVMUZOUPeinHNKgytM1wPI/C/UCLEi56EsY2dwwvI00kIJHJoI7pMVqGoMew8SMZ2SSfHKHULHXDsg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/fast-safe-stringify/2.1.1:
|
||||||
|
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/fast-stream-to-buffer/1.0.0:
|
||||||
|
resolution: {integrity: sha512-bI/544WUQlD2iXBibQbOMSmG07Hay7YrpXlKaeGTPT7H7pC0eitt3usak5vUwEvCGK/O7rUAM3iyQValGU22TQ==}
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fast-xml-parser/3.21.1:
|
/fast-xml-parser/3.21.1:
|
||||||
resolution: {integrity: sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==}
|
resolution: {integrity: sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4699,6 +4856,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==}
|
resolution: {integrity: sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fastify-warning/0.2.0:
|
||||||
|
resolution: {integrity: sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fastq/1.13.0:
|
/fastq/1.13.0:
|
||||||
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
|
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4807,6 +4968,10 @@ packages:
|
|||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/flatstr/1.0.12:
|
||||||
|
resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/flatted/3.2.4:
|
/flatted/3.2.4:
|
||||||
resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==}
|
resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -4861,6 +5026,10 @@ packages:
|
|||||||
mime-types: 2.1.34
|
mime-types: 2.1.34
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/forwarded-parse/2.1.2:
|
||||||
|
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/forwarded/0.2.0:
|
/forwarded/0.2.0:
|
||||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -5268,6 +5437,12 @@ packages:
|
|||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/http-headers/3.0.2:
|
||||||
|
resolution: {integrity: sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw==}
|
||||||
|
dependencies:
|
||||||
|
next-line: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/http-parser-js/0.5.5:
|
/http-parser-js/0.5.5:
|
||||||
resolution: {integrity: sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==}
|
resolution: {integrity: sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -5612,6 +5787,11 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-finite/1.1.0:
|
||||||
|
resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-fullwidth-code-point/2.0.0:
|
/is-fullwidth-code-point/2.0.0:
|
||||||
resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=}
|
resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -5648,15 +5828,32 @@ packages:
|
|||||||
is-extglob: 2.1.1
|
is-extglob: 2.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-integer/1.0.7:
|
||||||
|
resolution: {integrity: sha1-a96Bqs3feLZZtmKdYpytxRqIbVw=}
|
||||||
|
dependencies:
|
||||||
|
is-finite: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-map/2.0.2:
|
/is-map/2.0.2:
|
||||||
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
|
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-native/1.0.1:
|
||||||
|
resolution: {integrity: sha1-zRjMFi6EUNaDtbq+eayZwUVElnU=}
|
||||||
|
dependencies:
|
||||||
|
is-nil: 1.0.1
|
||||||
|
to-source-code: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-negative-zero/2.0.2:
|
/is-negative-zero/2.0.2:
|
||||||
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
|
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-nil/1.0.1:
|
||||||
|
resolution: {integrity: sha1-LauingtYUGOHXntTnQcfWxWTeWk=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-number-object/1.0.6:
|
/is-number-object/1.0.6:
|
||||||
resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==}
|
resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -6619,6 +6816,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-JWw1HHMx54g8mEsG7JwI8I/xh7qeJbF6L9u1dQOYW91RdRqDYpnTn1UaNXYkmLD967Vk0xGuyHzuRnkSApby3w==}
|
resolution: {integrity: sha512-JWw1HHMx54g8mEsG7JwI8I/xh7qeJbF6L9u1dQOYW91RdRqDYpnTn1UaNXYkmLD967Vk0xGuyHzuRnkSApby3w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/load-source-map/2.0.0:
|
||||||
|
resolution: {integrity: sha512-QNZzJ2wMrTmCdeobMuMNEXHN1QGk8HG6louEkzD/zwQ7EU2RarrzlhQ4GnUYEFzLhK+Jq7IGyF/qy+XYBSO7AQ==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
dependencies:
|
||||||
|
source-map: 0.7.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/loader-runner/4.2.0:
|
/loader-runner/4.2.0:
|
||||||
resolution: {integrity: sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==}
|
resolution: {integrity: sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==}
|
||||||
engines: {node: '>=6.11.5'}
|
engines: {node: '>=6.11.5'}
|
||||||
@ -6700,6 +6904,13 @@ packages:
|
|||||||
'@sinonjs/commons': 1.8.3
|
'@sinonjs/commons': 1.8.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/lru-cache/4.1.5:
|
||||||
|
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
|
||||||
|
dependencies:
|
||||||
|
pseudomap: 1.0.2
|
||||||
|
yallist: 2.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lru-cache/6.0.0:
|
/lru-cache/6.0.0:
|
||||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -6748,6 +6959,10 @@ packages:
|
|||||||
object-visit: 1.0.1
|
object-visit: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/mapcap/1.0.0:
|
||||||
|
resolution: {integrity: sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/md5.js/1.3.5:
|
/md5.js/1.3.5:
|
||||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
|
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6760,6 +6975,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/measured-core/1.51.1:
|
||||||
|
resolution: {integrity: sha512-DZQP9SEwdqqYRvT2slMK81D/7xwdxXosZZBtLVfPSo6y5P672FBTbzHVdN4IQyUkUpcVOR9pIvtUy5Ryl7NKyg==}
|
||||||
|
engines: {node: '>= 5.12'}
|
||||||
|
dependencies:
|
||||||
|
binary-search: 1.3.6
|
||||||
|
optional-js: 2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/measured-reporting/1.51.1:
|
||||||
|
resolution: {integrity: sha512-JCt+2u6XT1I5lG3SuYqywE0e62DJuAzBcfMzWGUhIYtPQV2Vm4HiYt/durqmzsAbZV181CEs+o/jMKWJKkYIWw==}
|
||||||
|
engines: {node: '>= 5.12'}
|
||||||
|
dependencies:
|
||||||
|
console-log-level: 1.4.1
|
||||||
|
mapcap: 1.0.0
|
||||||
|
measured-core: 1.51.1
|
||||||
|
optional-js: 2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/media-typer/0.3.0:
|
/media-typer/0.3.0:
|
||||||
resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
|
resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -6935,6 +7168,10 @@ packages:
|
|||||||
minimist: 1.2.5
|
minimist: 1.2.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/module-details-from-path/1.0.3:
|
||||||
|
resolution: {integrity: sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mongodb-connection-string-url/2.3.2:
|
/mongodb-connection-string-url/2.3.2:
|
||||||
resolution: {integrity: sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==}
|
resolution: {integrity: sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6953,6 +7190,10 @@ packages:
|
|||||||
saslprep: 1.0.3
|
saslprep: 1.0.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/monitor-event-loop-delay/1.0.0:
|
||||||
|
resolution: {integrity: sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mri/1.2.0:
|
/mri/1.2.0:
|
||||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -7029,6 +7270,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/next-line/1.1.0:
|
||||||
|
resolution: {integrity: sha1-/K5XhTBStqm66CCOQN19PC0wRgM=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/next-tick/1.0.0:
|
/next-tick/1.0.0:
|
||||||
resolution: {integrity: sha1-yobR/ogoFpsBICCOPchCS524NCw=}
|
resolution: {integrity: sha1-yobR/ogoFpsBICCOPchCS524NCw=}
|
||||||
dev: false
|
dev: false
|
||||||
@ -7137,6 +7382,16 @@ packages:
|
|||||||
kind-of: 3.2.2
|
kind-of: 3.2.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/object-filter-sequence/1.0.0:
|
||||||
|
resolution: {integrity: sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/object-identity-map/1.0.2:
|
||||||
|
resolution: {integrity: sha512-a2XZDGyYTngvGS67kWnqVdpoaJWsY7C1GhPJvejWAFCsUioTAaiTu8oBad7c6cI4McZxr4CmvnZeycK05iav5A==}
|
||||||
|
dependencies:
|
||||||
|
object.entries: 1.1.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/object-inspect/1.12.0:
|
/object-inspect/1.12.0:
|
||||||
resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==}
|
resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -7171,6 +7426,15 @@ packages:
|
|||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/object.entries/1.1.5:
|
||||||
|
resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.2
|
||||||
|
define-properties: 1.1.3
|
||||||
|
es-abstract: 1.19.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/object.pick/1.3.0:
|
/object.pick/1.3.0:
|
||||||
resolution: {integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=}
|
resolution: {integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -7232,6 +7496,10 @@ packages:
|
|||||||
is-wsl: 1.1.0
|
is-wsl: 1.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/optional-js/2.3.0:
|
||||||
|
resolution: {integrity: sha512-B0LLi+Vg+eko++0z/b8zIv57kp7HKEzaPJo7LowJXMUKYdf+3XJGu/cw03h/JhIOsLnP+cG5QnTHAuicjA5fMw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/optionator/0.8.3:
|
/optionator/0.8.3:
|
||||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -7260,6 +7528,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==}
|
resolution: {integrity: sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/original-url/1.2.3:
|
||||||
|
resolution: {integrity: sha512-BYm+pKYLtS4mVe/mgT3YKGtWV5HzN/XKiaIu1aK4rsxyjuHeTW9N+xVBEpJcY1onB3nccfH0RbzUEoimMqFUHQ==}
|
||||||
|
dependencies:
|
||||||
|
forwarded-parse: 2.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/original/1.0.2:
|
/original/1.0.2:
|
||||||
resolution: {integrity: sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==}
|
resolution: {integrity: sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7488,6 +7762,23 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/pino-std-serializers/3.2.0:
|
||||||
|
resolution: {integrity: sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/pino/6.13.3:
|
||||||
|
resolution: {integrity: sha512-tJy6qVgkh9MwNgqX1/oYi3ehfl2Y9H0uHyEEMsBe74KinESIjdMrMQDWpcZPpPicg3VV35d/GLQZmo4QgU2Xkg==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
fast-redact: 3.0.2
|
||||||
|
fast-safe-stringify: 2.1.1
|
||||||
|
fastify-warning: 0.2.0
|
||||||
|
flatstr: 1.0.12
|
||||||
|
pino-std-serializers: 3.2.0
|
||||||
|
quick-format-unescaped: 4.0.4
|
||||||
|
sonic-boom: 1.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/pirates/4.0.4:
|
/pirates/4.0.4:
|
||||||
resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==}
|
resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -7782,6 +8073,10 @@ packages:
|
|||||||
resolution: {integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=}
|
resolution: {integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/pseudomap/1.0.2:
|
||||||
|
resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/psl/1.8.0:
|
/psl/1.8.0:
|
||||||
resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}
|
resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -7844,6 +8139,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/quick-format-unescaped/4.0.4:
|
||||||
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/quote-stream/1.0.2:
|
/quote-stream/1.0.2:
|
||||||
resolution: {integrity: sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=}
|
resolution: {integrity: sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -7853,6 +8152,10 @@ packages:
|
|||||||
through2: 2.0.5
|
through2: 2.0.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/random-poly-fill/1.0.1:
|
||||||
|
resolution: {integrity: sha512-bMOL0hLfrNs52+EHtIPIXxn2PxYwXb0qjnKruTjXiM/sKfYqj506aB2plFwWW1HN+ri724bAVVGparh4AtlJKw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/randombytes/2.1.0:
|
/randombytes/2.1.0:
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7989,6 +8292,10 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/relative-microtime/2.0.0:
|
||||||
|
resolution: {integrity: sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/remove-trailing-separator/1.1.0:
|
/remove-trailing-separator/1.1.0:
|
||||||
resolution: {integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8=}
|
resolution: {integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8=}
|
||||||
dev: false
|
dev: false
|
||||||
@ -8063,6 +8370,16 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/require-in-the-middle/5.1.0:
|
||||||
|
resolution: {integrity: sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ==}
|
||||||
|
dependencies:
|
||||||
|
debug: 4.3.3
|
||||||
|
module-details-from-path: 1.0.3
|
||||||
|
resolve: 1.20.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/require-main-filename/2.0.0:
|
/require-main-filename/2.0.0:
|
||||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -8150,6 +8467,10 @@ packages:
|
|||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/rfdc/1.3.0:
|
||||||
|
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/rimraf/2.7.1:
|
/rimraf/2.7.1:
|
||||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -8413,6 +8734,10 @@ packages:
|
|||||||
resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}
|
resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/set-cookie-serde/1.0.0:
|
||||||
|
resolution: {integrity: sha512-Vq8e5GsupfJ7okHIvEPcfs5neCo7MZ1ZuWrO3sllYi3DOWt6bSSCpADzqXjz3k0fXehnoFIrmmhty9IN6U6BXQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/set-value/2.0.1:
|
/set-value/2.0.1:
|
||||||
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
|
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -8439,6 +8764,10 @@ packages:
|
|||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/shallow-clone-shim/2.0.0:
|
||||||
|
resolution: {integrity: sha512-YRNymdiL3KGOoS67d73TEmk4tdPTO9GSMCoiphQsTcC9EtC+AOmMPjkyBkRoCJfW9ASsaZw1craaiw1dPN2D3Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/shallow-clone/3.0.1:
|
/shallow-clone/3.0.1:
|
||||||
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
|
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8571,6 +8900,13 @@ packages:
|
|||||||
websocket-driver: 0.7.4
|
websocket-driver: 0.7.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/sonic-boom/1.4.1:
|
||||||
|
resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==}
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep: 1.0.0
|
||||||
|
flatstr: 1.0.12
|
||||||
|
dev: false
|
||||||
|
|
||||||
/sorcery/0.10.0:
|
/sorcery/0.10.0:
|
||||||
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
|
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -8701,6 +9037,10 @@ packages:
|
|||||||
resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}
|
resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/sql-summary/1.0.1:
|
||||||
|
resolution: {integrity: sha512-IpCr2tpnNkP3Jera4ncexsZUp0enJBLr+pHCyTweMUBrbJsTgQeLWx1FXLhoBj/MvcnUQpkgOn2EY8FKOkUzww==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/sshpk/1.16.1:
|
/sshpk/1.16.1:
|
||||||
resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==}
|
resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -8728,6 +9068,10 @@ packages:
|
|||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/stackframe/1.2.0:
|
||||||
|
resolution: {integrity: sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/static-eval/2.1.0:
|
/static-eval/2.1.0:
|
||||||
resolution: {integrity: sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==}
|
resolution: {integrity: sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8771,6 +9115,12 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/stream-chopper/3.0.1:
|
||||||
|
resolution: {integrity: sha512-f7h+ly8baAE26iIjcp3VbnBkbIRGtrvV0X0xxFM/d7fwLTYnLzDPTXRKNxa2HZzohOrc96NTrR+FaV3mzOelNA==}
|
||||||
|
dependencies:
|
||||||
|
readable-stream: 3.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/streamsearch/0.1.2:
|
/streamsearch/0.1.2:
|
||||||
resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=}
|
resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@ -8789,6 +9139,10 @@ packages:
|
|||||||
strip-ansi: 5.2.0
|
strip-ansi: 5.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/string-similarity/4.0.4:
|
||||||
|
resolution: {integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/string-width/3.1.0:
|
/string-width/3.1.0:
|
||||||
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
|
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -9311,6 +9665,12 @@ packages:
|
|||||||
safe-regex: 1.1.0
|
safe-regex: 1.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/to-source-code/1.0.2:
|
||||||
|
resolution: {integrity: sha1-3RNr2x4dvYC76s8IiZJnjpBwv+o=}
|
||||||
|
dependencies:
|
||||||
|
is-nil: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/toidentifier/1.0.1:
|
/toidentifier/1.0.1:
|
||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@ -9355,6 +9715,16 @@ packages:
|
|||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/traceparent/1.0.0:
|
||||||
|
resolution: {integrity: sha512-b/hAbgx57pANQ6cg2eBguY3oxD6FGVLI1CC2qoi01RmHR7AYpQHPXTig9FkzbWohEsVuHENZHP09aXuw3/LM+w==}
|
||||||
|
dependencies:
|
||||||
|
random-poly-fill: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/traverse/0.6.6:
|
||||||
|
resolution: {integrity: sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/true-case-path/2.2.1:
|
/true-case-path/2.2.1:
|
||||||
resolution: {integrity: sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==}
|
resolution: {integrity: sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -9555,6 +9925,13 @@ packages:
|
|||||||
which-boxed-primitive: 1.0.2
|
which-boxed-primitive: 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/unicode-byte-truncate/1.0.0:
|
||||||
|
resolution: {integrity: sha1-qm8PNHUZP+IMMgrJIT425i6HZKc=}
|
||||||
|
dependencies:
|
||||||
|
is-integer: 1.0.7
|
||||||
|
unicode-substring: 0.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/unicode-properties/1.3.1:
|
/unicode-properties/1.3.1:
|
||||||
resolution: {integrity: sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==}
|
resolution: {integrity: sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9562,6 +9939,10 @@ packages:
|
|||||||
unicode-trie: 2.0.0
|
unicode-trie: 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/unicode-substring/0.1.0:
|
||||||
|
resolution: {integrity: sha1-YSDOPDkDhdvND2DDK5BlxBgdSzY=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/unicode-trie/0.3.1:
|
/unicode-trie/0.3.1:
|
||||||
resolution: {integrity: sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=}
|
resolution: {integrity: sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -10152,6 +10533,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/yallist/2.1.2:
|
||||||
|
resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/yallist/4.0.0:
|
/yallist/4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -11823,7 +12208,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/server.tgz:
|
file:projects/server.tgz:
|
||||||
resolution: {integrity: sha512-UBFOAisptjpTNIZoU6hWJVBKTk7mftZU0Dz+pJiVhMBJSD+iW393yv4v9GedEpor5rIYDGGcps7IVCXZlr76tw==, tarball: file:projects/server.tgz}
|
resolution: {integrity: sha512-KX5c6GAIbdrpb9vSiwP/vGovTeHgbcjvlpKZUCmN7/iqGz3S5vSxySnO8wsqJm8fEurl8CAzmcKp73LvHPmSzQ==, tarball: file:projects/server.tgz}
|
||||||
name: '@rush-temp/server'
|
name: '@rush-temp/server'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11833,6 +12218,7 @@ packages:
|
|||||||
'@types/ws': 7.4.7
|
'@types/ws': 7.4.7
|
||||||
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
|
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
|
||||||
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
|
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
|
||||||
|
elastic-apm-node: 3.26.0
|
||||||
esbuild: 0.12.29
|
esbuild: 0.12.29
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
|
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
|
||||||
|
@ -13,24 +13,25 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Class, ClientConnection, Doc, DocumentQuery, FindOptions, FindResult, Ref, ServerStorage, Tx, TxHander, TxResult } from '@anticrm/core'
|
import { Class, ClientConnection, Doc, DocumentQuery, FindOptions, FindResult, Ref, ServerStorage, Tx, TxHander, TxResult, DOMAIN_TX, MeasureMetricsContext } from '@anticrm/core'
|
||||||
import { DOMAIN_TX } from '@anticrm/core'
|
|
||||||
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
|
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
|
||||||
import { protoDeserialize, protoSerialize } from '@anticrm/platform'
|
import { protoDeserialize, protoSerialize } from '@anticrm/platform'
|
||||||
import type { DbConfiguration } from '@anticrm/server-core'
|
import type { DbConfiguration } from '@anticrm/server-core'
|
||||||
import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
||||||
|
|
||||||
class ServerStorageWrapper implements ClientConnection {
|
class ServerStorageWrapper implements ClientConnection {
|
||||||
constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {}
|
measureCtx = new MeasureMetricsContext('client', {})
|
||||||
|
constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {
|
||||||
|
}
|
||||||
|
|
||||||
findAll <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
findAll <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
||||||
const [c, q, o] = protoDeserialize(protoSerialize([_class, query, options]))
|
const [c, q, o] = protoDeserialize(protoSerialize([_class, query, options]))
|
||||||
return this.storage.findAll(c, q, o)
|
return this.storage.findAll(this.measureCtx, c, q, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<TxResult> {
|
async tx (tx: Tx): Promise<TxResult> {
|
||||||
const _tx = protoDeserialize(protoSerialize(tx))
|
const _tx = protoDeserialize(protoSerialize(tx))
|
||||||
const [result, derived] = await this.storage.tx(_tx)
|
const [result, derived] = await this.storage.tx(this.measureCtx, _tx)
|
||||||
for (const tx of derived) { this.handler(tx) }
|
for (const tx of derived) { this.handler(tx) }
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@ services:
|
|||||||
- ELASTICSEARCH_PORT_NUMBER=9200
|
- ELASTICSEARCH_PORT_NUMBER=9200
|
||||||
- BITNAMI_DEBUG=true
|
- BITNAMI_DEBUG=true
|
||||||
- discovery.type=single-node
|
- discovery.type=single-node
|
||||||
|
healthcheck:
|
||||||
|
interval: 20s
|
||||||
|
retries: 10
|
||||||
|
test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
|
||||||
account:
|
account:
|
||||||
image: anticrm/account
|
image: anticrm/account
|
||||||
links:
|
links:
|
||||||
@ -66,6 +70,48 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- ELASTIC_URL=http://elastic:9200
|
- ELASTIC_URL=http://elastic:9200
|
||||||
- MONGO_URL=mongodb://mongodb:27017
|
- MONGO_URL=mongodb://mongodb:27017
|
||||||
|
- METRICS_CONSOLE=true
|
||||||
|
# - APM_SERVER_URL=http://apm-server:8200
|
||||||
|
# apm-server:
|
||||||
|
# image: docker.elastic.co/apm/apm-server:7.14.2
|
||||||
|
# depends_on:
|
||||||
|
# elastic:
|
||||||
|
# condition: service_healthy
|
||||||
|
# kibana:
|
||||||
|
# condition: service_healthy
|
||||||
|
# elastic:
|
||||||
|
# condition: service_healthy
|
||||||
|
# cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
|
||||||
|
# cap_drop: ["ALL"]
|
||||||
|
# ports:
|
||||||
|
# - 8200:8200
|
||||||
|
# command: >
|
||||||
|
# apm-server -e
|
||||||
|
# -E apm-server.rum.enabled=true
|
||||||
|
# -E setup.kibana.host=kibana:5601
|
||||||
|
# -E setup.template.settings.index.number_of_replicas=0
|
||||||
|
# -E apm-server.kibana.enabled=true
|
||||||
|
# -E apm-server.kibana.host=kibana:5601
|
||||||
|
# -E output.elasticsearch.hosts=["elastic:9200"]
|
||||||
|
# healthcheck:
|
||||||
|
# interval: 10s
|
||||||
|
# retries: 12
|
||||||
|
# test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/
|
||||||
|
# kibana:
|
||||||
|
# image: docker.elastic.co/kibana/kibana:7.14.2
|
||||||
|
# depends_on:
|
||||||
|
# elastic:
|
||||||
|
# condition: service_healthy
|
||||||
|
# environment:
|
||||||
|
# ELASTICSEARCH_URL: http://elastic:9200
|
||||||
|
# ELASTICSEARCH_HOSTS: http://elastic:9200
|
||||||
|
# ports:
|
||||||
|
# - 5601:5601
|
||||||
|
# healthcheck:
|
||||||
|
# interval: 10s
|
||||||
|
# retries: 20
|
||||||
|
# test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:5601/api/status
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db:
|
db:
|
||||||
files:
|
files:
|
||||||
|
@ -56,6 +56,7 @@ program
|
|||||||
.command('gen-recruit <workspace> <count>')
|
.command('gen-recruit <workspace> <count>')
|
||||||
.description('generate a bunch of random candidates with attachemnts and comments.')
|
.description('generate a bunch of random candidates with attachemnts and comments.')
|
||||||
.option('-r, --random', 'generate random ids. So every call will add count <count> more candidates.', false)
|
.option('-r, --random', 'generate random ids. So every call will add count <count> more candidates.', false)
|
||||||
|
.option('-l, --lite', 'use same pdf and same account for applicant and candidates', false)
|
||||||
.action(async (workspace: string, count: number, cmd) => {
|
.action(async (workspace: string, count: number, cmd) => {
|
||||||
return await generateContacts(transactorUrl, workspace, {
|
return await generateContacts(transactorUrl, workspace, {
|
||||||
contacts: count,
|
contacts: count,
|
||||||
@ -65,7 +66,8 @@ program
|
|||||||
min: 1, max: 3, deleteFactor: 20
|
min: 1, max: 3, deleteFactor: 20
|
||||||
},
|
},
|
||||||
vacancy: 3,
|
vacancy: 3,
|
||||||
applicantUpdateFactor: 70
|
applicants: { min: 50, max: 200, applicantUpdateFactor: 70 },
|
||||||
|
lite: (cmd.lite as boolean)
|
||||||
}, minio)
|
}, minio)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import { Ref, TxOperations } from '@anticrm/core'
|
import { MeasureContext, Ref, TxOperations } from '@anticrm/core'
|
||||||
import task, { DoneState, genRanks, Kanban, SpaceWithStates, State } from '@anticrm/task'
|
import task, { DoneState, genRanks, Kanban, SpaceWithStates, State } from '@anticrm/task'
|
||||||
import { findOrUpdate } from './utils'
|
import { findOrUpdate } from './utils'
|
||||||
|
|
||||||
export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, client: TxOperations): Promise<Ref<State>[]> {
|
export async function createUpdateSpaceKanban (ctx: MeasureContext, spaceId: Ref<SpaceWithStates>, client: TxOperations): Promise<Ref<State>[]> {
|
||||||
const rawStates = [
|
const rawStates = [
|
||||||
{ color: '#7C6FCD', name: 'Initial' },
|
{ color: '#7C6FCD', name: 'Initial' },
|
||||||
{ color: '#6F7BC5', name: 'Intermidiate' },
|
{ color: '#6F7BC5', name: 'Intermidiate' },
|
||||||
@ -22,14 +22,15 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
|
const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
|
||||||
await findOrUpdate(client, spaceId, task.class.State,
|
|
||||||
|
await ctx.with('find-or-update', {}, (ctx) => findOrUpdate(ctx, client, spaceId, task.class.State,
|
||||||
sid,
|
sid,
|
||||||
{
|
{
|
||||||
title: st.name,
|
title: st.name,
|
||||||
color: st.color,
|
color: st.color,
|
||||||
rank
|
rank
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
states.push(sid)
|
states.push(sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,21 +48,20 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sid = ('generated-' + spaceId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
|
const sid = ('generated-' + spaceId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
|
||||||
await findOrUpdate(client, spaceId, st.class,
|
await ctx.with('gen-done-state', {}, (ctx) => findOrUpdate(ctx, client, spaceId, st.class,
|
||||||
sid,
|
sid,
|
||||||
{
|
{
|
||||||
title: st.title,
|
title: st.title,
|
||||||
rank
|
rank
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
await findOrUpdate(client, spaceId,
|
await ctx.with('create-kanban', {}, (ctx) => findOrUpdate(ctx, client, spaceId, task.class.Kanban,
|
||||||
task.class.Kanban,
|
|
||||||
('generated-' + spaceId + '.kanban') as Ref<Kanban>,
|
('generated-' + spaceId + '.kanban') as Ref<Kanban>,
|
||||||
{
|
{
|
||||||
attachedTo: spaceId
|
attachedTo: spaceId
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
return states
|
return states
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import contact from '@anticrm/contact'
|
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||||
import core, { AttachedData, Data, generateId, Ref, TxOperations } from '@anticrm/core'
|
import core, {
|
||||||
|
AttachedData,
|
||||||
|
Data,
|
||||||
|
generateId,
|
||||||
|
MeasureContext,
|
||||||
|
MeasureMetricsContext, metricsToString, Ref,
|
||||||
|
TxOperations
|
||||||
|
} from '@anticrm/core'
|
||||||
import recruit from '@anticrm/model-recruit'
|
import recruit from '@anticrm/model-recruit'
|
||||||
import { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
|
import { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
|
||||||
import { genRanks } from '@anticrm/task'
|
import { genRanks, State } from '@anticrm/task'
|
||||||
import faker from 'faker'
|
import faker from 'faker'
|
||||||
import jpeg, { BufferRet } from 'jpeg-js'
|
import jpeg, { BufferRet } from 'jpeg-js'
|
||||||
import { Client } from 'minio'
|
import { Client } from 'minio'
|
||||||
@ -11,7 +18,6 @@ import { addComments, CommentOptions } from './comments'
|
|||||||
import { connect } from './connect'
|
import { connect } from './connect'
|
||||||
import { createUpdateSpaceKanban } from './kanban'
|
import { createUpdateSpaceKanban } from './kanban'
|
||||||
import { findOrUpdate, findOrUpdateAttached } from './utils'
|
import { findOrUpdate, findOrUpdateAttached } from './utils'
|
||||||
|
|
||||||
export interface RecruitOptions {
|
export interface RecruitOptions {
|
||||||
random: boolean // random id prefix.
|
random: boolean // random id prefix.
|
||||||
contacts: number // how many contacts to add
|
contacts: number // how many contacts to add
|
||||||
@ -21,15 +27,25 @@ export interface RecruitOptions {
|
|||||||
// Attachment generation control
|
// Attachment generation control
|
||||||
attachments: AttachmentOptions
|
attachments: AttachmentOptions
|
||||||
|
|
||||||
|
applicants: {
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
applicantUpdateFactor: number
|
applicantUpdateFactor: number
|
||||||
}
|
}
|
||||||
|
lite: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export async function generateContacts (transactorUrl: string, dbName: string, options: RecruitOptions, minio: Client): Promise<void> {
|
export async function generateContacts (
|
||||||
|
transactorUrl: string,
|
||||||
|
dbName: string,
|
||||||
|
options: RecruitOptions,
|
||||||
|
minio: Client
|
||||||
|
): Promise<void> {
|
||||||
const connection = await connect(transactorUrl, dbName)
|
const connection = await connect(transactorUrl, dbName)
|
||||||
|
|
||||||
const accounts = await connection.findAll(contact.class.EmployeeAccount, {})
|
const accounts = await connection.findAll(contact.class.EmployeeAccount, {})
|
||||||
const accountIds = accounts.map(a => a._id)
|
const accountIds = accounts.map((a) => a._id)
|
||||||
const emoloyeeIds = accounts.map(a => a.employee)
|
const emoloyeeIds = accounts.map((a) => a.employee)
|
||||||
|
|
||||||
const account = faker.random.arrayElement(accounts)
|
const account = faker.random.arrayElement(accounts)
|
||||||
|
|
||||||
@ -37,19 +53,180 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
|
|||||||
|
|
||||||
const candidates: Ref<Candidate>[] = []
|
const candidates: Ref<Candidate>[] = []
|
||||||
|
|
||||||
|
const ctx = new MeasureMetricsContext('recruit', { contacts: options.contacts })
|
||||||
|
|
||||||
for (let i = 0; i < options.contacts; i++) {
|
for (let i = 0; i < options.contacts; i++) {
|
||||||
|
await ctx.with('candidate', {}, (ctx) => genCandidate(ctx, i, minio, dbName, options, candidates, client))
|
||||||
|
}
|
||||||
|
// Work on Vacancy/Applications.
|
||||||
|
for (let i = 0; i < options.vacancy; i++) {
|
||||||
|
await ctx.with('vacancy', {}, (ctx) =>
|
||||||
|
genVacansyApplicants(ctx, accountIds, options, i, client, minio, dbName, candidates, emoloyeeIds)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.close()
|
||||||
|
ctx.end()
|
||||||
|
|
||||||
|
console.info(metricsToString(ctx.metrics, 'Client'))
|
||||||
|
}
|
||||||
|
async function genVacansyApplicants (
|
||||||
|
ctx: MeasureContext,
|
||||||
|
accountIds: Ref<EmployeeAccount>[],
|
||||||
|
options: RecruitOptions,
|
||||||
|
i: number,
|
||||||
|
client: TxOperations,
|
||||||
|
minio: Client,
|
||||||
|
dbName: string,
|
||||||
|
candidates: Ref<Candidate>[],
|
||||||
|
emoloyeeIds: Ref<Employee>[]
|
||||||
|
): Promise<void> {
|
||||||
|
const vacancy: Data<Vacancy> = {
|
||||||
|
name: faker.company.companyName(),
|
||||||
|
description: faker.lorem.sentences(2),
|
||||||
|
fullDescription: faker.lorem.sentences(10),
|
||||||
|
location: faker.address.city(),
|
||||||
|
company: faker.company.companyName(),
|
||||||
|
members: accountIds,
|
||||||
|
private: false,
|
||||||
|
archived: false
|
||||||
|
}
|
||||||
|
const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref<Vacancy>
|
||||||
|
|
||||||
|
console.log('Creating vacandy', vacancy.name)
|
||||||
|
|
||||||
|
// Update or create candidate
|
||||||
|
await ctx.with('update', {}, (ctx) =>
|
||||||
|
findOrUpdate(ctx, client, core.space.Model, recruit.class.Vacancy, vacancyId, vacancy)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('Vacandy generated', vacancy.name)
|
||||||
|
|
||||||
|
if (!options.lite) {
|
||||||
|
await ctx.with('add-attachments', {}, () =>
|
||||||
|
addAttachments(
|
||||||
|
options.attachments,
|
||||||
|
client,
|
||||||
|
minio,
|
||||||
|
dbName,
|
||||||
|
vacancyId,
|
||||||
|
vacancyId,
|
||||||
|
recruit.class.Vacancy,
|
||||||
|
'attachments'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Vacandy attachments generated', vacancy.name)
|
||||||
|
|
||||||
|
const states = await ctx.with('create-kanbad', {}, (ctx) => createUpdateSpaceKanban(ctx, vacancyId, client))
|
||||||
|
|
||||||
|
console.log('States generated', vacancy.name)
|
||||||
|
|
||||||
|
const applicantsForCount = options.applicants.min + faker.datatype.number(options.applicants.max)
|
||||||
|
|
||||||
|
const applicantsFor = faker.random.arrayElements(candidates, applicantsForCount)
|
||||||
|
const rankGen = genRanks(candidates.length)
|
||||||
|
for (const candidateId of applicantsFor) {
|
||||||
|
await ctx.with('applicant', {}, (ctx) =>
|
||||||
|
genApplicant(ctx, vacancyId, candidateId, emoloyeeIds, states, client, options, minio, dbName, rankGen)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genApplicant (
|
||||||
|
ctx: MeasureContext,
|
||||||
|
vacancyId: Ref<Vacancy>,
|
||||||
|
candidateId: Ref<Candidate>,
|
||||||
|
emoloyeeIds: Ref<Employee>[],
|
||||||
|
states: Ref<State>[],
|
||||||
|
client: TxOperations,
|
||||||
|
options: RecruitOptions,
|
||||||
|
minio: Client,
|
||||||
|
dbName: string,
|
||||||
|
rankGen: Generator<string, void, unknown>
|
||||||
|
): Promise<void> {
|
||||||
|
const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant>
|
||||||
|
const rank = rankGen.next().value
|
||||||
|
|
||||||
|
const applicant: AttachedData<Applicant> = {
|
||||||
|
number: faker.datatype.number(),
|
||||||
|
assignee: faker.random.arrayElement(emoloyeeIds),
|
||||||
|
state: faker.random.arrayElement(states),
|
||||||
|
doneState: null,
|
||||||
|
rank: rank as string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update or create candidate
|
||||||
|
await findOrUpdateAttached(ctx, client, vacancyId, recruit.class.Applicant, applicantId, applicant, {
|
||||||
|
attachedTo: candidateId,
|
||||||
|
attachedClass: recruit.class.Candidate,
|
||||||
|
collection: 'applications'
|
||||||
|
})
|
||||||
|
|
||||||
|
await ctx.with('add-comment', {}, () =>
|
||||||
|
addComments(options.comments, client, vacancyId, applicantId, recruit.class.Vacancy, 'comments')
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!options.lite) {
|
||||||
|
await ctx.with('add-attachment', {}, () =>
|
||||||
|
addAttachments(
|
||||||
|
options.attachments,
|
||||||
|
client,
|
||||||
|
minio,
|
||||||
|
dbName,
|
||||||
|
vacancyId,
|
||||||
|
applicantId,
|
||||||
|
recruit.class.Applicant,
|
||||||
|
'attachments'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faker.datatype.number(100) > options.applicants.applicantUpdateFactor) {
|
||||||
|
await ctx.with('update-collection', {}, () =>
|
||||||
|
client.updateCollection(
|
||||||
|
recruit.class.Applicant,
|
||||||
|
vacancyId,
|
||||||
|
applicantId,
|
||||||
|
candidateId,
|
||||||
|
recruit.class.Applicant,
|
||||||
|
'applications',
|
||||||
|
{
|
||||||
|
state: faker.random.arrayElement(states)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const liteAvatar = generateAvatar(0)
|
||||||
|
|
||||||
|
// @measure('Candidate')
|
||||||
|
async function genCandidate (
|
||||||
|
ctx: MeasureContext,
|
||||||
|
i: number,
|
||||||
|
minio: Client,
|
||||||
|
dbName: string,
|
||||||
|
options: RecruitOptions,
|
||||||
|
candidates: Ref<Candidate>[],
|
||||||
|
client: TxOperations
|
||||||
|
): Promise<void> {
|
||||||
const fName = faker.name.firstName()
|
const fName = faker.name.firstName()
|
||||||
const lName = faker.name.lastName()
|
const lName = faker.name.lastName()
|
||||||
|
|
||||||
const { imgId, jpegImageData } = generateAvatar(i)
|
const { imgId, jpegImageData } = options.lite ? liteAvatar : generateAvatar(i)
|
||||||
await minio.putObject(dbName, imgId, jpegImageData.data, jpegImageData.data.length, { 'Content-Type': 'image/jpeg' })
|
|
||||||
|
if (!options.lite) {
|
||||||
|
await ctx.with('avatar', {}, () =>
|
||||||
|
minio.putObject(dbName, imgId, jpegImageData.data, jpegImageData.data.length, { 'Content-Type': 'image/jpeg' })
|
||||||
|
)
|
||||||
|
}
|
||||||
const candidate: Data<Candidate> = {
|
const candidate: Data<Candidate> = {
|
||||||
name: fName + ',' + lName,
|
name: fName + ',' + lName,
|
||||||
city: faker.address.city(),
|
city: faker.address.city(),
|
||||||
title: faker.name.title(),
|
title: faker.name.title(),
|
||||||
channels: [
|
channels: [{ provider: contact.channelProvider.Email, value: faker.internet.email(fName, lName) }],
|
||||||
{ provider: contact.channelProvider.Email, value: faker.internet.email(fName, lName) }
|
|
||||||
],
|
|
||||||
onsite: faker.datatype.boolean(),
|
onsite: faker.datatype.boolean(),
|
||||||
remote: faker.datatype.boolean(),
|
remote: faker.datatype.boolean(),
|
||||||
avatar: imgId,
|
avatar: imgId,
|
||||||
@ -59,77 +236,38 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
|
|||||||
candidates.push(candidateId)
|
candidates.push(candidateId)
|
||||||
|
|
||||||
// Update or create candidate
|
// Update or create candidate
|
||||||
await findOrUpdate(client, recruit.space.CandidatesPublic, recruit.class.Candidate, candidateId, candidate)
|
await ctx.with('find-update', {}, () =>
|
||||||
|
findOrUpdate(ctx, client, recruit.space.CandidatesPublic, recruit.class.Candidate, candidateId, candidate)
|
||||||
|
)
|
||||||
|
|
||||||
await addComments(options.comments, client, recruit.space.CandidatesPublic, candidateId, recruit.class.Candidate, 'comments')
|
await ctx.with('add-comment', {}, () =>
|
||||||
|
addComments(
|
||||||
|
options.comments,
|
||||||
|
client,
|
||||||
|
recruit.space.CandidatesPublic,
|
||||||
|
candidateId,
|
||||||
|
recruit.class.Candidate,
|
||||||
|
'comments'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
await addAttachments(options.attachments, client, minio, dbName, recruit.space.CandidatesPublic, candidateId, recruit.class.Candidate, 'attachments')
|
if (!options.lite) {
|
||||||
|
await ctx.with('add-attachment', {}, () =>
|
||||||
console.log('Candidate', fName, lName, ' generated')
|
addAttachments(
|
||||||
|
options.attachments,
|
||||||
|
client,
|
||||||
|
minio,
|
||||||
|
dbName,
|
||||||
|
recruit.space.CandidatesPublic,
|
||||||
|
candidateId,
|
||||||
|
recruit.class.Candidate,
|
||||||
|
'attachments'
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Work on Vacancy/Applications.
|
console.log('Candidate', candidates.length, fName, lName, ' generated')
|
||||||
for (let i = 0; i < options.vacancy; i++) {
|
|
||||||
const vacancy: Data<Vacancy> = {
|
|
||||||
name: faker.company.companyName(),
|
|
||||||
description: faker.lorem.sentences(2),
|
|
||||||
fullDescription: faker.lorem.sentences(10),
|
|
||||||
location: faker.address.city(),
|
|
||||||
company: faker.company.companyName(),
|
|
||||||
members: accountIds,
|
|
||||||
archived: false,
|
|
||||||
private: false
|
|
||||||
}
|
|
||||||
const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref<Vacancy>
|
|
||||||
|
|
||||||
console.log('Creating vacancy', vacancy.name)
|
|
||||||
// Update or create candidate
|
|
||||||
await findOrUpdate(client, core.space.Model, recruit.class.Vacancy, vacancyId, vacancy)
|
|
||||||
|
|
||||||
console.log('Vacandy generated', vacancy.name)
|
|
||||||
|
|
||||||
await addAttachments(options.attachments, client, minio, dbName, vacancyId, vacancyId, recruit.class.Vacancy, 'attachments')
|
|
||||||
|
|
||||||
console.log('Vacandy attachments generated', vacancy.name)
|
|
||||||
|
|
||||||
const states = await createUpdateSpaceKanban(vacancyId, client)
|
|
||||||
|
|
||||||
console.log('States generated', vacancy.name)
|
|
||||||
|
|
||||||
const rankGen = genRanks(candidates.length)
|
|
||||||
for (const candidateId of candidates) {
|
|
||||||
const rank = rankGen.next().value
|
|
||||||
|
|
||||||
if (rank === undefined) {
|
|
||||||
throw Error('Failed to generate rank')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant>
|
|
||||||
|
|
||||||
const applicant: AttachedData<Applicant> = {
|
|
||||||
number: faker.datatype.number(),
|
|
||||||
assignee: faker.random.arrayElement(emoloyeeIds),
|
|
||||||
state: faker.random.arrayElement(states),
|
|
||||||
doneState: null,
|
|
||||||
rank
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update or create candidate
|
|
||||||
await findOrUpdateAttached(client, vacancyId, recruit.class.Applicant, applicantId, applicant, { attachedTo: candidateId, attachedClass: recruit.class.Candidate, collection: 'applications' })
|
|
||||||
|
|
||||||
await addComments(options.comments, client, vacancyId, applicantId, recruit.class.Vacancy, 'comments')
|
|
||||||
|
|
||||||
await addAttachments(options.attachments, client, minio, dbName, vacancyId, applicantId, recruit.class.Applicant, 'attachments')
|
|
||||||
|
|
||||||
if (faker.datatype.number(100) > options.applicantUpdateFactor) {
|
|
||||||
await client.updateCollection(recruit.class.Applicant, vacancyId, applicantId, candidateId, recruit.class.Applicant, 'applications', {
|
|
||||||
state: faker.random.arrayElement(states)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.close()
|
|
||||||
}
|
|
||||||
function generateAvatar (pos: number): { imgId: string, jpegImageData: BufferRet } {
|
function generateAvatar (pos: number): { imgId: string, jpegImageData: BufferRet } {
|
||||||
const imgId = generateId()
|
const imgId = generateId()
|
||||||
const width = 128
|
const width = 128
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AttachedData, AttachedDoc, Class, Data, Doc, DocumentUpdate, Ref, Space, TxOperations } from '@anticrm/core'
|
import { AttachedData, AttachedDoc, Class, Data, Doc, DocumentUpdate, MeasureContext, Ref, Space, TxOperations } from '@anticrm/core'
|
||||||
|
|
||||||
export async function findOrUpdate<T extends Doc> (client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: Data<T>): Promise<void> {
|
export async function findOrUpdate<T extends Doc> (ctx: MeasureContext, client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: Data<T>): Promise<void> {
|
||||||
const existingObj = await client.findOne<Doc>(_class, { _id: objectId, space })
|
const existingObj = await client.findOne<Doc>(_class, { _id: objectId, space })
|
||||||
if (existingObj !== undefined) {
|
if (existingObj !== undefined) {
|
||||||
await client.updateDoc(_class, space, objectId, data)
|
await client.updateDoc(_class, space, objectId, data)
|
||||||
@ -8,7 +8,7 @@ export async function findOrUpdate<T extends Doc> (client: TxOperations, space:
|
|||||||
await client.createDoc(_class, space, data, objectId)
|
await client.createDoc(_class, space, data, objectId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function findOrUpdateAttached<T extends AttachedDoc> (client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: AttachedData<T>, attached: {attachedTo: Ref<Doc>, attachedClass: Ref<Class<Doc>>, collection: string}): Promise<void> {
|
export async function findOrUpdateAttached<T extends AttachedDoc> (ctx: MeasureContext, client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: AttachedData<T>, attached: {attachedTo: Ref<Doc>, attachedClass: Ref<Class<Doc>>, collection: string}): Promise<void> {
|
||||||
const existingObj = await client.findOne<Doc>(_class, { _id: objectId, space })
|
const existingObj = await client.findOne<Doc>(_class, { _id: objectId, space })
|
||||||
if (existingObj !== undefined) {
|
if (existingObj !== undefined) {
|
||||||
await client.updateCollection(_class, space, objectId, attached.attachedTo, attached.attachedClass, attached.collection, data as unknown as DocumentUpdate<T>)
|
await client.updateCollection(_class, space, objectId, attached.attachedTo, attached.attachedClass, attached.collection, data as unknown as DocumentUpdate<T>)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { DOMAIN_TX } from '@anticrm/core'
|
import { DOMAIN_TX, MeasureMetricsContext } from '@anticrm/core'
|
||||||
import type { Ref, Doc, TxResult } from '@anticrm/core'
|
import type { Ref, Doc, TxResult } from '@anticrm/core'
|
||||||
import { start as startJsonRpc } from '@anticrm/server-ws'
|
import { start as startJsonRpc } from '@anticrm/server-ws'
|
||||||
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
|
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
|
||||||
@ -48,7 +48,7 @@ async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
|||||||
export async function start (port: number, host?: string): Promise<void> {
|
export async function start (port: number, host?: string): Promise<void> {
|
||||||
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
|
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
|
||||||
|
|
||||||
startJsonRpc(() => {
|
startJsonRpc(new MeasureMetricsContext('server', {}), () => {
|
||||||
const conf: DbConfiguration = {
|
const conf: DbConfiguration = {
|
||||||
domains: {
|
domains: {
|
||||||
[DOMAIN_TX]: 'InMemoryTx'
|
[DOMAIN_TX]: 'InMemoryTx'
|
||||||
|
@ -17,14 +17,10 @@
|
|||||||
import core, {
|
import core, {
|
||||||
Account,
|
Account,
|
||||||
Class,
|
Class,
|
||||||
Doc,
|
Doc, DocumentQuery,
|
||||||
FindOptions,
|
DOMAIN_TX, FindOptions, FindResult,
|
||||||
DocumentQuery,
|
|
||||||
DOMAIN_TX,
|
|
||||||
FindResult,
|
|
||||||
generateId,
|
generateId,
|
||||||
Hierarchy,
|
Hierarchy, MeasureMetricsContext, ModelDb,
|
||||||
ModelDb,
|
|
||||||
Ref,
|
Ref,
|
||||||
ServerStorage,
|
ServerStorage,
|
||||||
Tx,
|
Tx,
|
||||||
@ -36,10 +32,11 @@ import core, {
|
|||||||
TxResult,
|
TxResult,
|
||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import { Client as ElasticClient } from '@elastic/elasticsearch'
|
|
||||||
import { Db, MongoClient } from 'mongodb'
|
|
||||||
import { Client } from 'minio'
|
|
||||||
import { createElasticAdapter } from '@anticrm/elastic'
|
import { createElasticAdapter } from '@anticrm/elastic'
|
||||||
|
import { DOMAIN_ATTACHMENT } from '@anticrm/model-attachment'
|
||||||
|
import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
|
||||||
|
import { addLocation } from '@anticrm/platform'
|
||||||
|
import { serverChunterId } from '@anticrm/server-chunter'
|
||||||
import {
|
import {
|
||||||
createServerStorage,
|
createServerStorage,
|
||||||
DbAdapter,
|
DbAdapter,
|
||||||
@ -48,11 +45,10 @@ import {
|
|||||||
IndexedDoc,
|
IndexedDoc,
|
||||||
TxAdapter
|
TxAdapter
|
||||||
} from '@anticrm/server-core'
|
} from '@anticrm/server-core'
|
||||||
import { DOMAIN_ATTACHMENT } from '@anticrm/model-attachment'
|
|
||||||
import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
|
|
||||||
import { serverChunterId } from '@anticrm/server-chunter'
|
|
||||||
import { serverRecruitId } from '@anticrm/server-recruit'
|
import { serverRecruitId } from '@anticrm/server-recruit'
|
||||||
import { addLocation } from '@anticrm/platform'
|
import { Client as ElasticClient } from '@elastic/elasticsearch'
|
||||||
|
import { Client } from 'minio'
|
||||||
|
import { Db, MongoClient } from 'mongodb'
|
||||||
import { listMinioObjects } from './minio'
|
import { listMinioObjects } from './minio'
|
||||||
|
|
||||||
export async function rebuildElastic (
|
export async function rebuildElastic (
|
||||||
@ -106,8 +102,9 @@ async function restoreElastic (mongoUrl: string, dbName: string, minio: Client,
|
|||||||
const storage = await createStorage(mongoUrl, elasticUrl, dbName)
|
const storage = await createStorage(mongoUrl, elasticUrl, dbName)
|
||||||
const txes = (await db.collection<Tx>(DOMAIN_TX).find().sort({ _id: 1 }).toArray())
|
const txes = (await db.collection<Tx>(DOMAIN_TX).find().sort({ _id: 1 }).toArray())
|
||||||
const data = txes.filter((tx) => tx.objectSpace !== core.space.Model)
|
const data = txes.filter((tx) => tx.objectSpace !== core.space.Model)
|
||||||
|
const metricsCtx = new MeasureMetricsContext('elastic', {})
|
||||||
for (const tx of data) {
|
for (const tx of data) {
|
||||||
await storage.tx(tx)
|
await storage.tx(metricsCtx, tx)
|
||||||
}
|
}
|
||||||
if (await minio.bucketExists(dbName)) {
|
if (await minio.bucketExists(dbName)) {
|
||||||
const minioObjects = await listMinioObjects(minio, dbName)
|
const minioObjects = await listMinioObjects(minio, dbName)
|
||||||
|
@ -223,8 +223,10 @@ export async function restoreWorkspace (
|
|||||||
const collection = db.collection(c.name)
|
const collection = db.collection(c.name)
|
||||||
await collection.deleteMany({})
|
await collection.deleteMany({})
|
||||||
const data = JSON.parse((await readFile(fileName + c.name + '.json')).toString()) as Document[]
|
const data = JSON.parse((await readFile(fileName + c.name + '.json')).toString()) as Document[]
|
||||||
|
if (data.length > 0) {
|
||||||
await collection.insertMany(data)
|
await collection.insertMany(data)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Restore minio objects')
|
console.log('Restore minio objects')
|
||||||
if (await minio.bucketExists(dbName)) {
|
if (await minio.bucketExists(dbName)) {
|
||||||
|
@ -22,5 +22,6 @@ export * from './client'
|
|||||||
export * from './operator'
|
export * from './operator'
|
||||||
export * from './query'
|
export * from './query'
|
||||||
export * from './server'
|
export * from './server'
|
||||||
|
export * from './measurements'
|
||||||
|
|
||||||
export { default, coreId } from './component'
|
export { default, coreId } from './component'
|
||||||
|
58
packages/core/src/measurements/context.ts
Normal file
58
packages/core/src/measurements/context.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Basic performance metrics suite.
|
||||||
|
|
||||||
|
import { childMetrics, measure, newMetrics } from './metrics'
|
||||||
|
import { MeasureContext, MeasureLogger, Metrics, ParamType } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class MeasureMetricsContext implements MeasureContext {
|
||||||
|
private readonly name: string
|
||||||
|
private readonly params: Record<string, ParamType>
|
||||||
|
logger: MeasureLogger
|
||||||
|
metrics: Metrics
|
||||||
|
private readonly done: () => void
|
||||||
|
|
||||||
|
constructor (name: string, params: Record<string, ParamType>, metrics: Metrics = newMetrics()) {
|
||||||
|
this.name = name
|
||||||
|
this.params = params
|
||||||
|
this.metrics = metrics
|
||||||
|
this.done = measure(metrics, params)
|
||||||
|
|
||||||
|
this.logger = {
|
||||||
|
info: (msg, args) => {
|
||||||
|
console.info(msg, ...args)
|
||||||
|
},
|
||||||
|
error: (msg, args) => {
|
||||||
|
console.error(msg, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newChild (name: string, params: Record<string, ParamType>): MeasureContext {
|
||||||
|
return new MeasureMetricsContext(name, params, childMetrics(this.metrics, [name]))
|
||||||
|
}
|
||||||
|
|
||||||
|
async with<T>(name: string, params: Record<string, ParamType>, op: (ctx: MeasureContext) => T | Promise<T>): Promise<T> {
|
||||||
|
const c = this.newChild(name, params)
|
||||||
|
try {
|
||||||
|
let value = op(c)
|
||||||
|
if (value instanceof Promise) {
|
||||||
|
value = await value
|
||||||
|
}
|
||||||
|
c.end()
|
||||||
|
return value
|
||||||
|
} catch (err: any) {
|
||||||
|
await c.error(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async error (err: Error | string): Promise<void> {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
end (): void {
|
||||||
|
this.done()
|
||||||
|
}
|
||||||
|
}
|
3
packages/core/src/measurements/index.ts
Normal file
3
packages/core/src/measurements/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './context'
|
||||||
|
export * from './metrics'
|
||||||
|
export * from './types'
|
156
packages/core/src/measurements/metrics.ts
Normal file
156
packages/core/src/measurements/metrics.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// Basic performance metrics suite.
|
||||||
|
|
||||||
|
import { MetricsData } from '.'
|
||||||
|
import { Metrics, ParamType } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const globals: Metrics = newMetrics()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function newMetrics (): Metrics {
|
||||||
|
return {
|
||||||
|
operations: 0,
|
||||||
|
time: 0,
|
||||||
|
measurements: {},
|
||||||
|
params: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure with tree expansion. Operation counter will be added only to leaf's.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function measure (metrics: Metrics, params: Record<string, ParamType>): () => void {
|
||||||
|
const st = Date.now()
|
||||||
|
return () => {
|
||||||
|
const ed = Date.now()
|
||||||
|
// Update params if required
|
||||||
|
for (const [k, v] of Object.entries(params)) {
|
||||||
|
let params = metrics.params[k]
|
||||||
|
if (params === undefined) {
|
||||||
|
params = {}
|
||||||
|
metrics.params[k] = params
|
||||||
|
}
|
||||||
|
const vKey = `${v?.toString() ?? ''}`
|
||||||
|
let param = params[vKey]
|
||||||
|
if (param === undefined) {
|
||||||
|
param = {
|
||||||
|
operations: 0,
|
||||||
|
time: 0
|
||||||
|
}
|
||||||
|
params[vKey] = param
|
||||||
|
}
|
||||||
|
param.time += ed - st
|
||||||
|
param.operations++
|
||||||
|
}
|
||||||
|
// Update leaf data
|
||||||
|
metrics.time += ed - st
|
||||||
|
metrics.operations++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function childMetrics (root: Metrics, path: string[]): Metrics {
|
||||||
|
const segments = path
|
||||||
|
let oop = root
|
||||||
|
for (const p of segments) {
|
||||||
|
const v = oop.measurements[p] ?? { operations: 0, time: 0, measurements: {}, params: {} }
|
||||||
|
oop.measurements[p] = v
|
||||||
|
oop = v
|
||||||
|
}
|
||||||
|
return oop
|
||||||
|
}
|
||||||
|
|
||||||
|
function aggregate (m: Metrics): Metrics {
|
||||||
|
const ms = aggregateMetrics(m.measurements)
|
||||||
|
|
||||||
|
// Use child overage, if there is no top level value specified.
|
||||||
|
const keysLen = Object.keys(ms).length
|
||||||
|
const childAverage = m.time === 0 && keysLen > 0
|
||||||
|
const sumVal: Metrics | undefined = childAverage
|
||||||
|
? Object.values(ms).reduce((p, v) => {
|
||||||
|
p.operations += v.operations
|
||||||
|
p.time += v.time
|
||||||
|
return p
|
||||||
|
}, {
|
||||||
|
operations: 0,
|
||||||
|
time: 0,
|
||||||
|
measurements: ms,
|
||||||
|
params: {}
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
if (sumVal !== undefined) {
|
||||||
|
return {
|
||||||
|
...sumVal,
|
||||||
|
measurements: ms,
|
||||||
|
params: m.params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...m,
|
||||||
|
measurements: ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function aggregateMetrics (m: Record<string, Metrics>): Record<string, Metrics> {
|
||||||
|
const result: Record<string, Metrics> = {}
|
||||||
|
for (const [k, v] of Object.entries(m).sort((a, b) => b[1].time - a[1].time)) {
|
||||||
|
result[k] = aggregate(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLen (val: string, sep: string, len = 50): string {
|
||||||
|
while (val.length < len) {
|
||||||
|
val += sep
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
function printMetricsChildren (params: Record<string, Metrics>, offset: number): string {
|
||||||
|
let r = ''
|
||||||
|
if (Object.keys(params).length > 0) {
|
||||||
|
r += '\n' + toLen('', ' ', offset)
|
||||||
|
r += Object.entries(params)
|
||||||
|
.map(([k, vv]) => toString(k, vv, offset))
|
||||||
|
.join('\n' + toLen('', ' ', offset))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
function printMetricsParams (params: Record<string, Record<string, MetricsData>>, offset: number): string {
|
||||||
|
let r = ''
|
||||||
|
const joinP = (key: string, data: Record<string, MetricsData>): string[] => {
|
||||||
|
return Object.entries(data).map(([k, vv]) =>
|
||||||
|
`${toLen('', ' ', offset)}${toLen(key + '=' + k, '-', 70 - offset)}: avg ${vv.time / (vv.operations > 0 ? vv.operations : 1)} total: ${vv.time} ops: ${vv.operations}`.trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const joinParams = Object.entries(params).reduce<string[]>((p, c) => [...p, ...joinP(c[0], c[1])], [])
|
||||||
|
if (Object.keys(joinParams).length > 0) {
|
||||||
|
r += '\n' + toLen('', ' ', offset)
|
||||||
|
r += joinParams
|
||||||
|
.join('\n' + toLen('', ' ', offset))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
function toString (name: string, m: Metrics, offset: number): string {
|
||||||
|
let r = `${toLen('', ' ', offset)}${toLen(name, '-', 70 - offset)}: avg ${m.time / (m.operations > 0 ? m.operations : 1)} total: ${m.time} ops: ${m.operations}`.trim()
|
||||||
|
r += printMetricsParams(m.params, offset + 4)
|
||||||
|
r += printMetricsChildren(m.measurements, offset + 4)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function metricsToString (metrics: Metrics, name = 'System'): string {
|
||||||
|
return toString(name, aggregate(metrics), 0)
|
||||||
|
}
|
45
packages/core/src/measurements/types.ts
Normal file
45
packages/core/src/measurements/types.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type ParamType = string | number | boolean | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface MetricsData {
|
||||||
|
operations: number
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface Metrics extends MetricsData {
|
||||||
|
params: Record<string, Record<string, MetricsData>>
|
||||||
|
measurements: Record<string, Metrics>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface MeasureLogger {
|
||||||
|
info: (message: string, ...args: any[]) => void
|
||||||
|
error: (message: string, ...args: any[]) => void
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface MeasureContext {
|
||||||
|
// Create a child metrics context
|
||||||
|
newChild: (name: string, params: Record<string, ParamType>) => MeasureContext
|
||||||
|
|
||||||
|
with: <T>(name: string, params: Record<string, ParamType>, op: (ctx: MeasureContext) => T | Promise<T>) => Promise<T>
|
||||||
|
|
||||||
|
logger: MeasureLogger
|
||||||
|
|
||||||
|
// Capture error
|
||||||
|
error: (err: Error | string | any) => Promise<void>
|
||||||
|
|
||||||
|
// Mark current context as complete
|
||||||
|
end: () => void
|
||||||
|
}
|
@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { MeasureContext } from '.'
|
||||||
import type { Doc, Class, Ref } from './classes'
|
import type { Doc, Class, Ref } from './classes'
|
||||||
import type { DocumentQuery, FindOptions, FindResult, TxResult } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, TxResult } from './storage'
|
||||||
import type { Tx } from './tx'
|
import type { Tx } from './tx'
|
||||||
@ -23,9 +24,10 @@ import type { Tx } from './tx'
|
|||||||
*/
|
*/
|
||||||
export interface ServerStorage {
|
export interface ServerStorage {
|
||||||
findAll: <T extends Doc>(
|
findAll: <T extends Doc>(
|
||||||
|
ctx: MeasureContext,
|
||||||
_class: Ref<Class<T>>,
|
_class: Ref<Class<T>>,
|
||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
) => Promise<FindResult<T>>
|
) => Promise<FindResult<T>>
|
||||||
tx: (tx: Tx) => Promise<[TxResult, Tx[]]>
|
tx: (ctx: MeasureContext, tx: Tx) => Promise<[TxResult, Tx[]]>
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { IntlString, Resources } from '@anticrm/platform'
|
import { IntlString, Resources } from '@anticrm/platform'
|
||||||
import ModelView from './components/ModelView.svelte'
|
import ModelView from './components/ModelView.svelte'
|
||||||
import QueryView from './components/QueryView.svelte'
|
import QueryView from './components/QueryView.svelte'
|
||||||
import core, { Class, Client, Doc, DocumentQuery, FindOptions, Ref, FindResult, Hierarchy, ModelDb, Tx, TxResult, WithLookup } from '@anticrm/core'
|
import core, { Class, Client, Doc, DocumentQuery, FindOptions, Ref, FindResult, Hierarchy, ModelDb, Tx, TxResult, WithLookup, Metrics } from '@anticrm/core'
|
||||||
import { Builder } from '@anticrm/model'
|
import { Builder } from '@anticrm/model'
|
||||||
import workbench from '@anticrm/workbench'
|
import workbench from '@anticrm/workbench'
|
||||||
import view from '@anticrm/view'
|
import view from '@anticrm/view'
|
||||||
@ -45,6 +45,9 @@ class ModelClient implements Client {
|
|||||||
this.notify?.(tx)
|
this.notify?.(tx)
|
||||||
console.info('devmodel# notify=>', tx, this.client.getModel())
|
console.info('devmodel# notify=>', tx, this.client.getModel())
|
||||||
notifications.push(tx)
|
notifications.push(tx)
|
||||||
|
if (notifications.length > 500) {
|
||||||
|
notifications.shift()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +65,9 @@ class ModelClient implements Client {
|
|||||||
const result = await this.client.findOne(_class, query, options)
|
const result = await this.client.findOne(_class, query, options)
|
||||||
console.info('devmodel# findOne=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel())
|
console.info('devmodel# findOne=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel())
|
||||||
queries.push({ _class, query, options, result: result !== undefined ? [result] : [], findOne: true })
|
queries.push({ _class, query, options, result: result !== undefined ? [result] : [], findOne: true })
|
||||||
|
if (queries.length > 100) {
|
||||||
|
queries.shift()
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +75,9 @@ class ModelClient implements Client {
|
|||||||
const result = await this.client.findAll(_class, query, options)
|
const result = await this.client.findAll(_class, query, options)
|
||||||
console.info('devmodel# findAll=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel())
|
console.info('devmodel# findAll=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel())
|
||||||
queries.push({ _class, query, options, result, findOne: false })
|
queries.push({ _class, query, options, result, findOne: false })
|
||||||
|
if (queries.length > 100) {
|
||||||
|
queries.shift()
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +85,9 @@ class ModelClient implements Client {
|
|||||||
const result = await this.client.tx(tx)
|
const result = await this.client.tx(tx)
|
||||||
console.info('devmodel# tx=>', tx, result)
|
console.info('devmodel# tx=>', tx, result)
|
||||||
transactions.push({ tx, result })
|
transactions.push({ tx, result })
|
||||||
|
if (transactions.length > 100) {
|
||||||
|
transactions.shift()
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,22 +14,25 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import type { AttachedDoc, Class, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc } from '@anticrm/core'
|
||||||
import core, {
|
import core, {
|
||||||
Hierarchy,
|
|
||||||
AnyAttribute,
|
AnyAttribute,
|
||||||
Storage,
|
Collection,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
FindResult,
|
FindResult,
|
||||||
TxProcessor,
|
Hierarchy,
|
||||||
|
MeasureContext,
|
||||||
|
PropertyType,
|
||||||
|
Tx,
|
||||||
|
TxBulkWrite,
|
||||||
|
TxCollectionCUD,
|
||||||
TxMixin,
|
TxMixin,
|
||||||
|
TxProcessor,
|
||||||
TxPutBag,
|
TxPutBag,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc
|
||||||
Collection
|
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import type { AttachedDoc, TxUpdateDoc, TxCreateDoc, Doc, Ref, Class, Obj, TxResult } from '@anticrm/core'
|
import type { FullTextAdapter, IndexedDoc, WithFind } from './types'
|
||||||
|
|
||||||
import type { IndexedDoc, FullTextAdapter, WithFind } from './types'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const NO_INDEX = [] as AnyAttribute[]
|
const NO_INDEX = [] as AnyAttribute[]
|
||||||
@ -37,32 +40,76 @@ const NO_INDEX = [] as AnyAttribute[]
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export class FullTextIndex extends TxProcessor implements Storage {
|
export class FullTextIndex implements WithFind {
|
||||||
private readonly indexes = new Map<Ref<Class<Obj>>, AnyAttribute[]>()
|
private readonly indexes = new Map<Ref<Class<Obj>>, AnyAttribute[]>()
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly hierarchy: Hierarchy,
|
private readonly hierarchy: Hierarchy,
|
||||||
private readonly adapter: FullTextAdapter,
|
private readonly adapter: FullTextAdapter,
|
||||||
private readonly dbStorage: WithFind
|
private readonly dbStorage: WithFind
|
||||||
) {
|
) {}
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async txPutBag (tx: TxPutBag<any>): Promise<TxResult> {
|
protected async txPutBag (ctx: MeasureContext, tx: TxPutBag<any>): Promise<TxResult> {
|
||||||
console.log('FullTextIndex.txPutBag: Method not implemented.')
|
// console.log('FullTextIndex.txPutBag: Method not implemented.')
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
protected async txRemoveDoc (ctx: MeasureContext, tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
||||||
console.log('FullTextIndex.txRemoveDoc: Method not implemented.')
|
// console.log('FullTextIndex.txRemoveDoc: Method not implemented.')
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected txMixin (tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
protected txMixin (ctx: MeasureContext, tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
||||||
throw new Error('Method not implemented.')
|
throw new Error('Method not implemented.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
||||||
|
switch (tx._class) {
|
||||||
|
case core.class.TxCreateDoc:
|
||||||
|
return await this.txCreateDoc(ctx, tx as TxCreateDoc<Doc>)
|
||||||
|
case core.class.TxCollectionCUD:
|
||||||
|
return await this.txCollectionCUD(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>)
|
||||||
|
case core.class.TxUpdateDoc:
|
||||||
|
return await this.txUpdateDoc(ctx, tx as TxUpdateDoc<Doc>)
|
||||||
|
case core.class.TxRemoveDoc:
|
||||||
|
return await this.txRemoveDoc(ctx, tx as TxRemoveDoc<Doc>)
|
||||||
|
case core.class.TxMixin:
|
||||||
|
return await this.txMixin(ctx, tx as TxMixin<Doc, Doc>)
|
||||||
|
case core.class.TxPutBag:
|
||||||
|
return await this.txPutBag(ctx, tx as TxPutBag<PropertyType>)
|
||||||
|
case core.class.TxBulkWrite:
|
||||||
|
return await this.txBulkWrite(ctx, tx as TxBulkWrite)
|
||||||
|
}
|
||||||
|
throw new Error('TxProcessor: unhandled transaction class: ' + tx._class)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected txCollectionCUD (ctx: MeasureContext, tx: TxCollectionCUD<Doc, AttachedDoc>): Promise<TxResult> {
|
||||||
|
// We need update only create transactions to contain attached, attachedToClass.
|
||||||
|
if (tx.tx._class === core.class.TxCreateDoc) {
|
||||||
|
const createTx = tx.tx as TxCreateDoc<AttachedDoc>
|
||||||
|
const d: TxCreateDoc<AttachedDoc> = {
|
||||||
|
...createTx,
|
||||||
|
attributes: {
|
||||||
|
...createTx.attributes,
|
||||||
|
attachedTo: tx.objectId,
|
||||||
|
attachedToClass: tx.objectClass,
|
||||||
|
collection: tx.collection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.txCreateDoc(ctx, d)
|
||||||
|
}
|
||||||
|
return this.tx(ctx, tx.tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async txBulkWrite (ctx: MeasureContext, bulkTx: TxBulkWrite): Promise<TxResult> {
|
||||||
|
for (const tx of bulkTx.txes) {
|
||||||
|
await this.tx(ctx, tx)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
async findAll<T extends Doc>(
|
async findAll<T extends Doc>(
|
||||||
|
ctx: MeasureContext,
|
||||||
_class: Ref<Class<T>>,
|
_class: Ref<Class<T>>,
|
||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
@ -78,7 +125,7 @@ export class FullTextIndex extends TxProcessor implements Storage {
|
|||||||
ids.push(doc.attachedTo)
|
ids.push(doc.attachedTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await this.dbStorage.findAll(_class, { _id: { $in: ids as any }, ...mainQuery }, options) // TODO: remove `as any`
|
return await this.dbStorage.findAll(ctx, _class, { _id: { $in: ids as any }, ...mainQuery }, options) // TODO: remove `as any`
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFullTextAttributes (clazz: Ref<Class<Obj>>): AnyAttribute[] | undefined {
|
private getFullTextAttributes (clazz: Ref<Class<Obj>>): AnyAttribute[] | undefined {
|
||||||
@ -102,14 +149,14 @@ export class FullTextIndex extends TxProcessor implements Storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
||||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
const attributes = this.getFullTextAttributes(tx.objectClass)
|
||||||
const doc = TxProcessor.createDoc2Doc(tx)
|
const doc = TxProcessor.createDoc2Doc(tx)
|
||||||
let parentContent: any[] = []
|
let parentContent: any[] = []
|
||||||
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
||||||
const attachedDoc = doc as AttachedDoc
|
const attachedDoc = doc as AttachedDoc
|
||||||
const parentDoc = (
|
const parentDoc = (
|
||||||
await this.dbStorage.findAll(attachedDoc.attachedToClass, { _id: attachedDoc.attachedTo }, { limit: 1 })
|
await this.dbStorage.findAll(ctx, attachedDoc.attachedToClass, { _id: attachedDoc.attachedTo }, { limit: 1 })
|
||||||
)[0]
|
)[0]
|
||||||
if (parentDoc !== undefined) {
|
if (parentDoc !== undefined) {
|
||||||
const parentAttributes = this.getFullTextAttributes(parentDoc._class)
|
const parentAttributes = this.getFullTextAttributes(parentDoc._class)
|
||||||
@ -140,7 +187,7 @@ export class FullTextIndex extends TxProcessor implements Storage {
|
|||||||
return await this.adapter.index(indexedDoc)
|
return await this.adapter.index(indexedDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
protected async txUpdateDoc (ctx: MeasureContext, tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
||||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
const attributes = this.getFullTextAttributes(tx.objectClass)
|
||||||
let result = {}
|
let result = {}
|
||||||
if (attributes === undefined) return result
|
if (attributes === undefined) return result
|
||||||
@ -157,7 +204,7 @@ export class FullTextIndex extends TxProcessor implements Storage {
|
|||||||
}
|
}
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
result = await this.adapter.update(tx.objectId, update)
|
result = await this.adapter.update(tx.objectId, update)
|
||||||
await this.updateAttachedDocs(tx, update)
|
await this.updateAttachedDocs(ctx, tx, update)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -167,14 +214,14 @@ export class FullTextIndex extends TxProcessor implements Storage {
|
|||||||
return attributes.map((attr) => (doc as any)[attr.name]?.toString() ?? '')
|
return attributes.map((attr) => (doc as any)[attr.name]?.toString() ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAttachedDocs (tx: TxUpdateDoc<Doc>, update: any): Promise<void> {
|
private async updateAttachedDocs (ctx: MeasureContext, tx: TxUpdateDoc<Doc>, update: any): Promise<void> {
|
||||||
const doc = (await this.dbStorage.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
const doc = (await this.dbStorage.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||||
if (doc === undefined) return
|
if (doc === undefined) return
|
||||||
const attributes = this.hierarchy.getAllAttributes(doc._class)
|
const attributes = this.hierarchy.getAllAttributes(doc._class)
|
||||||
for (const attribute of attributes.values()) {
|
for (const attribute of attributes.values()) {
|
||||||
if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
||||||
const collection = attribute.type as Collection<AttachedDoc>
|
const collection = attribute.type as Collection<AttachedDoc>
|
||||||
const allAttached = await this.dbStorage.findAll(collection.of, { attachedTo: tx.objectId })
|
const allAttached = await this.dbStorage.findAll(ctx, collection.of, { attachedTo: tx.objectId })
|
||||||
if (allAttached.length === 0) continue
|
if (allAttached.length === 0) continue
|
||||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
const attributes = this.getFullTextAttributes(tx.objectClass)
|
||||||
const shift = attributes?.length ?? 0
|
const shift = attributes?.length ?? 0
|
||||||
|
@ -14,10 +14,32 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import core, { ServerStorage, Domain, Tx, TxCUD, Doc, Ref, Class, DocumentQuery, FindResult, FindOptions, Storage, TxBulkWrite, TxResult, TxCollectionCUD, AttachedDoc, DOMAIN_MODEL, Hierarchy, DOMAIN_TX, ModelDb, TxFactory } from '@anticrm/core'
|
import core, {
|
||||||
import type { FullTextAdapterFactory, FullTextAdapter } from './types'
|
AttachedDoc,
|
||||||
|
Class,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
Domain,
|
||||||
|
DOMAIN_MODEL,
|
||||||
|
DOMAIN_TX,
|
||||||
|
FindOptions,
|
||||||
|
FindResult,
|
||||||
|
Hierarchy,
|
||||||
|
MeasureContext,
|
||||||
|
ModelDb,
|
||||||
|
Ref,
|
||||||
|
ServerStorage,
|
||||||
|
Storage,
|
||||||
|
Tx,
|
||||||
|
TxBulkWrite,
|
||||||
|
TxCollectionCUD,
|
||||||
|
TxCUD,
|
||||||
|
TxFactory,
|
||||||
|
TxResult
|
||||||
|
} from '@anticrm/core'
|
||||||
import { FullTextIndex } from './fulltext'
|
import { FullTextIndex } from './fulltext'
|
||||||
import { Triggers } from './triggers'
|
import { Triggers } from './triggers'
|
||||||
|
import type { FullTextAdapter, FullTextAdapterFactory } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -87,7 +109,7 @@ class TServerStorage implements ServerStorage {
|
|||||||
return adapter
|
return adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private async routeTx (tx: Tx): Promise<TxResult> {
|
private async routeTx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
||||||
if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) {
|
if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) {
|
||||||
const txCUD = tx as TxCUD<Doc>
|
const txCUD = tx as TxCUD<Doc>
|
||||||
const domain = this.hierarchy.getDomain(txCUD.objectClass)
|
const domain = this.hierarchy.getDomain(txCUD.objectClass)
|
||||||
@ -96,7 +118,7 @@ class TServerStorage implements ServerStorage {
|
|||||||
if (this.hierarchy.isDerived(tx._class, core.class.TxBulkWrite)) {
|
if (this.hierarchy.isDerived(tx._class, core.class.TxBulkWrite)) {
|
||||||
const bulkWrite = tx as TxBulkWrite
|
const bulkWrite = tx as TxBulkWrite
|
||||||
for (const tx of bulkWrite.txes) {
|
for (const tx of bulkWrite.txes) {
|
||||||
await this.tx(tx)
|
await this.tx(ctx, tx)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('not implemented (routeTx)')
|
throw new Error('not implemented (routeTx)')
|
||||||
@ -105,7 +127,7 @@ class TServerStorage implements ServerStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processCollection (tx: Tx): Promise<Tx[]> {
|
async processCollection (ctx: MeasureContext, tx: Tx): Promise<Tx[]> {
|
||||||
if (tx._class === core.class.TxCollectionCUD) {
|
if (tx._class === core.class.TxCollectionCUD) {
|
||||||
const colTx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
const colTx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||||
const _id = colTx.objectId
|
const _id = colTx.objectId
|
||||||
@ -120,10 +142,14 @@ class TServerStorage implements ServerStorage {
|
|||||||
|
|
||||||
const isCreateTx = colTx.tx._class === core.class.TxCreateDoc
|
const isCreateTx = colTx.tx._class === core.class.TxCreateDoc
|
||||||
if (isCreateTx || colTx.tx._class === core.class.TxRemoveDoc) {
|
if (isCreateTx || colTx.tx._class === core.class.TxRemoveDoc) {
|
||||||
attachedTo = (await this.findAll(_class, { _id }, { limit: 1 }))[0]
|
attachedTo = (await this.findAll(ctx, _class, { _id }, { limit: 1 }))[0]
|
||||||
if (attachedTo !== undefined) {
|
if (attachedTo !== undefined) {
|
||||||
const txFactory = new TxFactory(tx.modifiedBy)
|
const txFactory = new TxFactory(tx.modifiedBy)
|
||||||
return [txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, { $inc: { [colTx.collection]: isCreateTx ? 1 : -1 } })]
|
return [
|
||||||
|
txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, {
|
||||||
|
$inc: { [colTx.collection]: isCreateTx ? 1 : -1 }
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,21 +157,28 @@ class TServerStorage implements ServerStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findAll<T extends Doc>(
|
async findAll<T extends Doc>(
|
||||||
|
ctx: MeasureContext,
|
||||||
clazz: Ref<Class<T>>,
|
clazz: Ref<Class<T>>,
|
||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
): Promise<FindResult<T>> {
|
): Promise<FindResult<T>> {
|
||||||
|
return await ctx.with('find-all', {}, (ctx) => {
|
||||||
const domain = this.hierarchy.getDomain(clazz)
|
const domain = this.hierarchy.getDomain(clazz)
|
||||||
console.log('server findall', query)
|
|
||||||
if (Object.keys(query)[0] === '$search') {
|
if (Object.keys(query)[0] === '$search') {
|
||||||
return await this.fulltext.findAll(clazz, query, options)
|
return ctx.with('full-text-find-all', {}, (ctx) => this.fulltext.findAll(ctx, clazz, query, options))
|
||||||
}
|
}
|
||||||
return await this.getAdapter(domain).findAll(clazz, query, options)
|
return ctx.with('db-find-all', { _class: clazz, domain }, () =>
|
||||||
|
this.getAdapter(domain).findAll(clazz, query, options)
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<[TxResult, Tx[]]> {
|
async tx (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> {
|
||||||
// store tx
|
// store tx
|
||||||
await this.getAdapter(DOMAIN_TX).tx(tx)
|
const _class = txClass(tx)
|
||||||
|
const objClass = txObjectClass(tx)
|
||||||
|
return await ctx.with('tx', { _class, objClass }, async (ctx) => {
|
||||||
|
await ctx.with('domain-tx', { _class, objClass }, async () => await this.getAdapter(DOMAIN_TX).tx(tx))
|
||||||
|
|
||||||
if (tx.objectSpace === core.space.Model) {
|
if (tx.objectSpace === core.space.Model) {
|
||||||
// maintain hiearachy and triggers
|
// maintain hiearachy and triggers
|
||||||
@ -153,22 +186,42 @@ class TServerStorage implements ServerStorage {
|
|||||||
await this.triggers.tx(tx)
|
await this.triggers.tx(tx)
|
||||||
await this.modelDb.tx(tx)
|
await this.modelDb.tx(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let derived: Tx[] = []
|
||||||
|
let result: TxResult = {}
|
||||||
// store object
|
// store object
|
||||||
const result = await this.routeTx(tx)
|
result = await ctx.with('route-tx', { _class, objClass }, (ctx) => this.routeTx(ctx, tx))
|
||||||
// invoke triggers and store derived objects
|
// invoke triggers and store derived objects
|
||||||
const derived = [...await this.processCollection(tx), ...await this.triggers.apply(tx.modifiedBy, tx, this.findAll.bind(this), this.hierarchy)]
|
derived = [
|
||||||
|
...(await ctx.with('process-collection', { _class }, () => this.processCollection(ctx, tx))),
|
||||||
|
...(await ctx.with('process-triggers', {}, (ctx) =>
|
||||||
|
this.triggers.apply(tx.modifiedBy, tx, this.findAll.bind(this, ctx), this.hierarchy)
|
||||||
|
))
|
||||||
|
]
|
||||||
|
|
||||||
for (const tx of derived) {
|
for (const tx of derived) {
|
||||||
await this.routeTx(tx)
|
await ctx.with('derived-route-tx', { _class: txClass(tx) }, (ctx) => this.routeTx(ctx, tx))
|
||||||
}
|
|
||||||
// index object
|
|
||||||
await this.fulltext.tx(tx)
|
|
||||||
// index derived objects
|
|
||||||
for (const tx of derived) {
|
|
||||||
await this.fulltext.tx(tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [result, derived]
|
// index object
|
||||||
|
await ctx.with('fulltext', { _class, objClass }, (ctx) => this.fulltext.tx(ctx, tx))
|
||||||
|
// index derived objects
|
||||||
|
for (const tx of derived) {
|
||||||
|
await ctx.with('derived-fulltext', { _class: txClass(tx) }, (ctx) => this.fulltext.tx(ctx, tx))
|
||||||
}
|
}
|
||||||
|
return [result, derived]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function txObjectClass (tx: Tx): string {
|
||||||
|
return tx._class === core.class.TxCollectionCUD
|
||||||
|
? (tx as TxCollectionCUD<Doc, AttachedDoc>).tx.objectClass
|
||||||
|
: (tx as TxCUD<Doc>).objectClass
|
||||||
|
}
|
||||||
|
|
||||||
|
function txClass (tx: Tx): string {
|
||||||
|
return tx._class === core.class.TxCollectionCUD ? (tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class : tx._class
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Tx, Ref, Doc, Class, Space, Timestamp, Account, FindResult, DocumentQuery, FindOptions, TxResult } from '@anticrm/core'
|
import type { Tx, Ref, Doc, Class, Space, Timestamp, Account, FindResult, DocumentQuery, FindOptions, TxResult, MeasureContext } from '@anticrm/core'
|
||||||
import { TxFactory, Hierarchy } from '@anticrm/core'
|
import { TxFactory, Hierarchy } from '@anticrm/core'
|
||||||
import type { Resource } from '@anticrm/platform'
|
import type { Resource } from '@anticrm/platform'
|
||||||
|
|
||||||
@ -88,5 +88,5 @@ export interface Token {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface WithFind {
|
export interface WithFind {
|
||||||
findAll: <T extends Doc> (clazz: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>) => Promise<FindResult<T>>
|
findAll: <T extends Doc> (ctx: MeasureContext, clazz: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>) => Promise<FindResult<T>>
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,7 @@ class ElasticAdapter implements FullTextAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(result)
|
|
||||||
const hits = result.body.hits.hits as any[]
|
const hits = result.body.hits.hits as any[]
|
||||||
console.log('hits', hits)
|
|
||||||
return hits.map(hit => hit._source)
|
return hits.map(hit => hit._source)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(JSON.stringify(err, null, 2))
|
console.error(JSON.stringify(err, null, 2))
|
||||||
@ -64,34 +62,28 @@ class ElasticAdapter implements FullTextAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async index (doc: IndexedDoc): Promise<TxResult> {
|
async index (doc: IndexedDoc): Promise<TxResult> {
|
||||||
console.log('eastic: index', doc)
|
|
||||||
if (doc.data === undefined) {
|
if (doc.data === undefined) {
|
||||||
try {
|
try {
|
||||||
const resp = await this.client.index({
|
await this.client.index({
|
||||||
index: this.db,
|
index: this.db,
|
||||||
id: doc.id,
|
id: doc.id,
|
||||||
type: '_doc',
|
type: '_doc',
|
||||||
body: doc
|
body: doc
|
||||||
})
|
})
|
||||||
console.log('resp', resp)
|
|
||||||
console.log('error', (resp.meta as any)?.body?.error)
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('elastic-exception', err)
|
console.error('elastic-exception', err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('attachment pipeline')
|
|
||||||
try {
|
try {
|
||||||
const resp = await this.client.index({
|
await this.client.index({
|
||||||
index: this.db,
|
index: this.db,
|
||||||
id: doc.id,
|
id: doc.id,
|
||||||
type: '_doc',
|
type: '_doc',
|
||||||
pipeline: 'attachment',
|
pipeline: 'attachment',
|
||||||
body: doc
|
body: doc
|
||||||
})
|
})
|
||||||
console.log('resp', resp)
|
|
||||||
console.log('error', (resp.meta as any)?.body?.error)
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('elastic-exception', err)
|
console.error('elastic-exception', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
@ -99,16 +91,15 @@ class ElasticAdapter implements FullTextAdapter {
|
|||||||
|
|
||||||
async update (id: Ref<Doc>, update: Record<string, any>): Promise<TxResult> {
|
async update (id: Ref<Doc>, update: Record<string, any>): Promise<TxResult> {
|
||||||
try {
|
try {
|
||||||
const resp = await this.client.update({
|
await this.client.update({
|
||||||
index: this.db,
|
index: this.db,
|
||||||
id,
|
id,
|
||||||
body: {
|
body: {
|
||||||
doc: update
|
doc: update
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log('update', resp)
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('elastic-exception', err)
|
console.error('elastic-exception', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import core, {
|
import core, {
|
||||||
Class,
|
Class,
|
||||||
Client,
|
Client,
|
||||||
|
ClientConnection,
|
||||||
createClient,
|
createClient,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
@ -24,13 +25,13 @@ import core, {
|
|||||||
FindOptions,
|
FindOptions,
|
||||||
FindResult,
|
FindResult,
|
||||||
generateId,
|
generateId,
|
||||||
Hierarchy, ModelDb,
|
Hierarchy, ModelDb, Ref,
|
||||||
Ref,
|
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
Space,
|
Space,
|
||||||
Tx,
|
Tx,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxResult
|
TxResult,
|
||||||
|
MeasureMetricsContext
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import { createServerStorage, DbAdapter, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
import { createServerStorage, DbAdapter, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
@ -155,13 +156,14 @@ describe('mongo operations', () => {
|
|||||||
workspace: dbId
|
workspace: dbId
|
||||||
}
|
}
|
||||||
const serverStorage = await createServerStorage(conf)
|
const serverStorage = await createServerStorage(conf)
|
||||||
|
const ctx = new MeasureMetricsContext('client', {})
|
||||||
client = await createClient(async (handler) => {
|
client = await createClient(async (handler) => {
|
||||||
return {
|
const st: ClientConnection = {
|
||||||
findAll: async (_class, query, options) => await serverStorage.findAll(_class, query, options),
|
findAll: async (_class, query, options) => await serverStorage.findAll(ctx, _class, query, options),
|
||||||
tx: async (tx) => await serverStorage.tx(tx),
|
tx: async (tx) => (await serverStorage.tx(ctx, tx))[0],
|
||||||
close: async () => {}
|
close: async () => {}
|
||||||
}
|
}
|
||||||
|
return st
|
||||||
})
|
})
|
||||||
|
|
||||||
operations = new TxOperations(client, core.account.System)
|
operations = new TxOperations(client, core.account.System)
|
||||||
|
@ -13,24 +13,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type {
|
import core, {
|
||||||
Class,
|
Class,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery, DOMAIN_MODEL, DOMAIN_TX, FindOptions,
|
||||||
FindOptions,
|
FindResult, Hierarchy, isOperator, ModelDb, Ref, SortingOrder, Tx,
|
||||||
FindResult,
|
|
||||||
Ref,
|
|
||||||
Tx,
|
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxMixin,
|
TxMixin, TxProcessor, TxPutBag,
|
||||||
TxPutBag,
|
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxResult,
|
TxResult,
|
||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import core, { DOMAIN_MODEL, DOMAIN_TX, Hierarchy, isOperator, ModelDb, SortingOrder, TxProcessor } from '@anticrm/core'
|
|
||||||
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
||||||
import { Db, Document, Filter, Sort } from 'mongodb'
|
import { Collection, Db, Document, Filter, Sort } from 'mongodb'
|
||||||
import { getMongoClient } from './utils'
|
import { getMongoClient } from './utils'
|
||||||
|
|
||||||
function translateDoc (doc: Doc): Document {
|
function translateDoc (doc: Doc): Document {
|
||||||
@ -270,12 +265,13 @@ class MongoAdapter extends MongoAdapterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override tx (tx: Tx): Promise<TxResult> {
|
override async tx (tx: Tx): Promise<TxResult> {
|
||||||
return super.tx(tx)
|
return await super.tx(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
||||||
|
txColl: Collection | undefined
|
||||||
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
||||||
throw new Error('Method not implemented.')
|
throw new Error('Method not implemented.')
|
||||||
}
|
}
|
||||||
@ -297,10 +293,18 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async tx (tx: Tx): Promise<TxResult> {
|
override async tx (tx: Tx): Promise<TxResult> {
|
||||||
await this.db.collection(DOMAIN_TX).insertOne(translateDoc(tx))
|
await this.txCollection().insertOne(translateDoc(tx))
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private txCollection (): Collection {
|
||||||
|
if (this.txColl !== undefined) {
|
||||||
|
return this.txColl
|
||||||
|
}
|
||||||
|
this.txColl = this.db.collection(DOMAIN_TX)
|
||||||
|
return this.txColl
|
||||||
|
}
|
||||||
|
|
||||||
async getModel (): Promise<Tx[]> {
|
async getModel (): Promise<Tx[]> {
|
||||||
return await this.db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
|
return await this.db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"@anticrm/server-recruit": "~0.6.0",
|
"@anticrm/server-recruit": "~0.6.0",
|
||||||
"@anticrm/server-recruit-resources": "~0.6.0",
|
"@anticrm/server-recruit-resources": "~0.6.0",
|
||||||
"@anticrm/mongo": "~0.6.1",
|
"@anticrm/mongo": "~0.6.1",
|
||||||
"@anticrm/elastic": "~0.6.0"
|
"@anticrm/elastic": "~0.6.0",
|
||||||
|
"elastic-apm-node": "~3.26.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// Add this to the VERY top of the first file loaded in your app
|
||||||
import { start } from '.'
|
import { start } from '.'
|
||||||
|
|
||||||
const url = process.env.MONGO_URL
|
const url = process.env.MONGO_URL
|
||||||
@ -39,4 +40,3 @@ const close = (): void => {
|
|||||||
}
|
}
|
||||||
process.on('SIGINT', close)
|
process.on('SIGINT', close)
|
||||||
process.on('SIGTERM', close)
|
process.on('SIGTERM', close)
|
||||||
process.on('exit', close)
|
|
||||||
|
85
server/server/src/apm.ts
Normal file
85
server/server/src/apm.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { MeasureContext, MeasureLogger, ParamType } from '@anticrm/core'
|
||||||
|
import apm, { Agent, Span, Transaction } from 'elastic-apm-node'
|
||||||
|
|
||||||
|
export let metricsContext: MeasureContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function createAPMAgent (apmUrl: string): Agent {
|
||||||
|
const agent: Agent = apm.start({
|
||||||
|
|
||||||
|
// Override the service name from package.json
|
||||||
|
// Allowed characters: a-z, A-Z, 0-9, -, _, and space
|
||||||
|
serviceName: 'transactor',
|
||||||
|
|
||||||
|
// Use if APM Server requires a secret token
|
||||||
|
secretToken: '',
|
||||||
|
|
||||||
|
// Set the custom APM Server URL (default: http://localhost:8200)
|
||||||
|
serverUrl: apmUrl,
|
||||||
|
logLevel: 'trace'
|
||||||
|
})
|
||||||
|
return agent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class APMMeasureContext implements MeasureContext {
|
||||||
|
logger: MeasureLogger
|
||||||
|
private readonly transaction?: Transaction | Span
|
||||||
|
private readonly parent?: Transaction | Span
|
||||||
|
constructor (private readonly agent: Agent, name: string, params: Record<string, ParamType>, parent?: Transaction | Span, noTransaction?: boolean) {
|
||||||
|
this.parent = parent
|
||||||
|
this.logger = {
|
||||||
|
info: (msg, args) => {
|
||||||
|
agent.logger.info(msg, args)
|
||||||
|
},
|
||||||
|
error: (msg, args) => {
|
||||||
|
agent.logger.error(msg, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(noTransaction ?? false)) {
|
||||||
|
if (this.parent === undefined) {
|
||||||
|
this.transaction = agent.startTransaction(name) ?? undefined
|
||||||
|
} else {
|
||||||
|
this.transaction = agent.startSpan(name, { childOf: this.parent }) ?? undefined
|
||||||
|
}
|
||||||
|
for (const [k, v] of Object.entries(params)) {
|
||||||
|
this.transaction?.setLabel(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newChild (name: string, params: Record<string, ParamType>): MeasureContext {
|
||||||
|
return new APMMeasureContext(this.agent, name, params, this.transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
async with<T>(name: string, params: Record<string, ParamType>, op: (ctx: MeasureContext) => T | Promise<T>): Promise<T> {
|
||||||
|
const c = this.newChild(name, params)
|
||||||
|
try {
|
||||||
|
let value = op(c)
|
||||||
|
if (value instanceof Promise) {
|
||||||
|
value = await value
|
||||||
|
}
|
||||||
|
c.end()
|
||||||
|
return value
|
||||||
|
} catch (err: any) {
|
||||||
|
await c.error(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async error (err: any): Promise<void> {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
this.agent.captureError(err, () => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
end (): void {
|
||||||
|
this.transaction?.end()
|
||||||
|
}
|
||||||
|
}
|
46
server/server/src/metrics.ts
Normal file
46
server/server/src/metrics.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { MeasureContext, MeasureMetricsContext, metricsToString, newMetrics } from '@anticrm/core'
|
||||||
|
import { APMMeasureContext, createAPMAgent } from './apm'
|
||||||
|
import { writeFile } from 'fs/promises'
|
||||||
|
|
||||||
|
const apmUrl = process.env.APM_SERVER_URL
|
||||||
|
const metricsFile = process.env.METRICS_FILE
|
||||||
|
const metricsConsole = (process.env.METRICS_CONSOLE ?? 'false') === 'true'
|
||||||
|
|
||||||
|
const METRICS_UPDATE_INTERVAL = 30000
|
||||||
|
|
||||||
|
export let metricsContext: MeasureContext
|
||||||
|
|
||||||
|
if (apmUrl === undefined) {
|
||||||
|
console.info('please provide apm server url for monitoring')
|
||||||
|
|
||||||
|
const metrics = newMetrics()
|
||||||
|
metricsContext = new MeasureMetricsContext('System', {}, metrics)
|
||||||
|
|
||||||
|
if (metricsFile !== undefined || metricsConsole) {
|
||||||
|
console.info('storing measurements into local file', metricsFile)
|
||||||
|
let oldMetricsValue = ''
|
||||||
|
|
||||||
|
const intTimer = setInterval(() => {
|
||||||
|
const val = metricsToString(metrics)
|
||||||
|
if (val !== oldMetricsValue) {
|
||||||
|
oldMetricsValue = val
|
||||||
|
if (metricsFile !== undefined) {
|
||||||
|
writeFile(metricsFile, val).catch((err) => console.error(err))
|
||||||
|
}
|
||||||
|
if (metricsConsole) {
|
||||||
|
console.info('METRICS:', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, METRICS_UPDATE_INTERVAL)
|
||||||
|
|
||||||
|
const closeTimer = (): void => {
|
||||||
|
clearInterval(intTimer)
|
||||||
|
}
|
||||||
|
process.on('SIGINT', closeTimer)
|
||||||
|
process.on('SIGTERM', closeTimer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('using APM', apmUrl)
|
||||||
|
metricsContext = new APMMeasureContext(createAPMAgent(apmUrl), 'root', {}, undefined, true)
|
||||||
|
}
|
@ -24,6 +24,7 @@ import type { DbConfiguration, DbAdapter } from '@anticrm/server-core'
|
|||||||
import { addLocation } from '@anticrm/platform'
|
import { addLocation } from '@anticrm/platform'
|
||||||
import { serverChunterId } from '@anticrm/server-chunter'
|
import { serverChunterId } from '@anticrm/server-chunter'
|
||||||
import { serverRecruitId } from '@anticrm/server-recruit'
|
import { serverRecruitId } from '@anticrm/server-recruit'
|
||||||
|
import { metricsContext } from './metrics'
|
||||||
|
|
||||||
class NullDbAdapter implements DbAdapter {
|
class NullDbAdapter implements DbAdapter {
|
||||||
async init (model: Tx[]): Promise<void> {}
|
async init (model: Tx[]): Promise<void> {}
|
||||||
@ -42,7 +43,7 @@ export function start (dbUrl: string, fullTextUrl: string, port: number, host?:
|
|||||||
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
|
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
|
||||||
addLocation(serverRecruitId, () => import('@anticrm/server-recruit-resources'))
|
addLocation(serverRecruitId, () => import('@anticrm/server-recruit-resources'))
|
||||||
|
|
||||||
return startJsonRpc((workspace: string) => {
|
return startJsonRpc(metricsContext, (workspace: string) => {
|
||||||
const conf: DbConfiguration = {
|
const conf: DbConfiguration = {
|
||||||
domains: {
|
domains: {
|
||||||
[DOMAIN_TX]: 'MongoTx',
|
[DOMAIN_TX]: 'MongoTx',
|
||||||
|
@ -20,18 +20,20 @@ import type { Token } from '@anticrm/server-core'
|
|||||||
import { encode } from 'jwt-simple'
|
import { encode } from 'jwt-simple'
|
||||||
import WebSocket from 'ws'
|
import WebSocket from 'ws'
|
||||||
|
|
||||||
import type { Doc, Ref, Class, DocumentQuery, FindOptions, FindResult, Tx, TxResult } from '@anticrm/core'
|
import type { Doc, Ref, Class, DocumentQuery, FindOptions, FindResult, Tx, TxResult, MeasureContext } from '@anticrm/core'
|
||||||
|
import { MeasureMetricsContext } from '@anticrm/core'
|
||||||
|
|
||||||
describe('server', () => {
|
describe('server', () => {
|
||||||
disableLogging()
|
disableLogging()
|
||||||
|
|
||||||
start(async () => ({
|
start(new MeasureMetricsContext('test', {}), async () => ({
|
||||||
findAll: async <T extends Doc>(
|
findAll: async <T extends Doc>(
|
||||||
|
ctx: MeasureContext,
|
||||||
_class: Ref<Class<T>>,
|
_class: Ref<Class<T>>,
|
||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
): Promise<FindResult<T>> => ([]),
|
): Promise<FindResult<T>> => ([]),
|
||||||
tx: async (tx: Tx): Promise<[TxResult, Tx[]]> => ([{}, []])
|
tx: async (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> => ([{}, []])
|
||||||
}), 3333)
|
}), 3333)
|
||||||
|
|
||||||
function connect (): WebSocket {
|
function connect (): WebSocket {
|
||||||
|
@ -14,19 +14,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { readRequest, serialize, Response } from '@anticrm/platform'
|
import { Class, Doc, DocumentQuery, FindOptions, FindResult, MeasureContext, Ref, ServerStorage, Tx, TxResult } from '@anticrm/core'
|
||||||
|
import { readRequest, Response, serialize, unknownError } from '@anticrm/platform'
|
||||||
import type { Token } from '@anticrm/server-core'
|
import type { Token } from '@anticrm/server-core'
|
||||||
import { createServer, IncomingMessage } from 'http'
|
import { createServer, IncomingMessage } from 'http'
|
||||||
import WebSocket, { Server } from 'ws'
|
|
||||||
import { decode } from 'jwt-simple'
|
import { decode } from 'jwt-simple'
|
||||||
|
import WebSocket, { Server } from 'ws'
|
||||||
|
|
||||||
import type { Doc, Ref, Class, FindOptions, FindResult, Tx, DocumentQuery, Storage, ServerStorage, TxResult } from '@anticrm/core'
|
let LOGGING_ENABLED = false
|
||||||
|
|
||||||
let LOGGING_ENABLED = true
|
|
||||||
|
|
||||||
export function disableLogging (): void { LOGGING_ENABLED = false }
|
export function disableLogging (): void { LOGGING_ENABLED = false }
|
||||||
|
|
||||||
class Session implements Storage {
|
class Session {
|
||||||
constructor (
|
constructor (
|
||||||
private readonly manager: SessionManager,
|
private readonly manager: SessionManager,
|
||||||
private readonly token: Token,
|
private readonly token: Token,
|
||||||
@ -35,15 +34,16 @@ class Session implements Storage {
|
|||||||
|
|
||||||
async ping (): Promise<string> { console.log('ping'); return 'pong!' }
|
async ping (): Promise<string> { console.log('ping'); return 'pong!' }
|
||||||
|
|
||||||
async findAll <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
async findAll <T extends Doc>(ctx: MeasureContext, _class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
||||||
return await this.storage.findAll(_class, query, options)
|
return await this.storage.findAll(ctx, _class, query, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<TxResult> {
|
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
||||||
const [result, derived] = await this.storage.tx(tx)
|
const [result, derived] = await this.storage.tx(ctx, tx)
|
||||||
|
|
||||||
this.manager.broadcast(this, this.token, { result: tx })
|
this.manager.broadcast(this, this.token, { result: tx })
|
||||||
for (const tx of derived) {
|
for (const dtx of derived) {
|
||||||
this.manager.broadcast(null, this.token, { result: tx })
|
this.manager.broadcast(null, this.token, { result: dtx })
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -101,15 +101,19 @@ class SessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRequest<S> (service: S, ws: WebSocket, msg: string): Promise<void> {
|
async function handleRequest<S extends Session> (ctx: MeasureContext, service: S, ws: WebSocket, msg: string): Promise<void> {
|
||||||
const request = readRequest(msg)
|
const request = readRequest(msg)
|
||||||
const f = (service as any)[request.method]
|
const f = (service as any)[request.method]
|
||||||
try {
|
try {
|
||||||
const result = await f.apply(service, request.params)
|
const params = [ctx, ...request.params]
|
||||||
const resp = { id: request.id, result }
|
const result = await f.apply(service, params)
|
||||||
|
const resp: Response<any> = { id: request.id, result }
|
||||||
ws.send(serialize(resp))
|
ws.send(serialize(resp))
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const resp = { id: request.id, error: err }
|
const resp: Response<any> = {
|
||||||
|
id: request.id,
|
||||||
|
error: unknownError(err)
|
||||||
|
}
|
||||||
ws.send(serialize(resp))
|
ws.send(serialize(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +124,7 @@ async function handleRequest<S> (service: S, ws: WebSocket, msg: string): Promis
|
|||||||
* @param port -
|
* @param port -
|
||||||
* @param host -
|
* @param host -
|
||||||
*/
|
*/
|
||||||
export function start (storageFactory: (workspace: string) => Promise<ServerStorage>, port: number, host?: string): () => void {
|
export function start (ctx: MeasureContext, storageFactory: (workspace: string) => Promise<ServerStorage>, port: number, host?: string): () => void {
|
||||||
console.log(`starting server on port ${port} ...`)
|
console.log(`starting server on port ${port} ...`)
|
||||||
|
|
||||||
const sessions = new SessionManager()
|
const sessions = new SessionManager()
|
||||||
@ -133,11 +137,11 @@ export function start (storageFactory: (workspace: string) => Promise<ServerStor
|
|||||||
ws.on('message', (msg: string) => { buffer.push(msg) })
|
ws.on('message', (msg: string) => { buffer.push(msg) })
|
||||||
const session = await sessions.addSession(ws, token, storageFactory)
|
const session = await sessions.addSession(ws, token, storageFactory)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
ws.on('message', async (msg: string) => await handleRequest(session, ws, msg))
|
ws.on('message', async (msg: string) => await handleRequest(ctx, session, ws, msg))
|
||||||
ws.on('close', (code: number, reason: string) => sessions.close(ws, token, code, reason))
|
ws.on('close', (code: number, reason: string) => sessions.close(ws, token, code, reason))
|
||||||
|
|
||||||
for (const msg of buffer) {
|
for (const msg of buffer) {
|
||||||
await handleRequest(session, ws, msg)
|
await handleRequest(ctx, session, ws, msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user