mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
pg impl
This commit is contained in:
Generated
+24
-272
@@ -67,7 +67,7 @@ importers:
|
|||||||
version: 24.10.13
|
version: 24.10.13
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
byte-size:
|
byte-size:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
@@ -115,10 +115,10 @@ importers:
|
|||||||
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
vitest-fetch-mock:
|
vitest-fetch-mock:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
yaml:
|
yaml:
|
||||||
specifier: ^2.3.1
|
specifier: ^2.3.1
|
||||||
version: 2.8.2
|
version: 2.8.2
|
||||||
@@ -343,9 +343,6 @@ importers:
|
|||||||
'@extism/extism':
|
'@extism/extism':
|
||||||
specifier: 2.0.0-rc13
|
specifier: 2.0.0-rc13
|
||||||
version: 2.0.0-rc13
|
version: 2.0.0-rc13
|
||||||
'@nestjs/bullmq':
|
|
||||||
specifier: ^11.0.1
|
|
||||||
version: 11.0.4(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(bullmq@5.67.3)
|
|
||||||
'@nestjs/common':
|
'@nestjs/common':
|
||||||
specifier: ^11.0.4
|
specifier: ^11.0.4
|
||||||
version: 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
version: 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||||
@@ -424,9 +421,6 @@ importers:
|
|||||||
body-parser:
|
body-parser:
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.2
|
version: 2.2.2
|
||||||
bullmq:
|
|
||||||
specifier: ^5.51.0
|
|
||||||
version: 5.67.3
|
|
||||||
chokidar:
|
chokidar:
|
||||||
specifier: ^4.0.3
|
specifier: ^4.0.3
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
@@ -670,7 +664,7 @@ importers:
|
|||||||
version: 13.15.10
|
version: 13.15.10
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.14.0
|
specifier: ^9.14.0
|
||||||
version: 9.39.2(jiti@2.6.1)
|
version: 9.39.2(jiti@2.6.1)
|
||||||
@@ -727,7 +721,7 @@ importers:
|
|||||||
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
web:
|
web:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -781,7 +775,7 @@ importers:
|
|||||||
version: 2.6.0
|
version: 2.6.0
|
||||||
fabric:
|
fabric:
|
||||||
specifier: ^6.5.4
|
specifier: ^6.5.4
|
||||||
version: 6.9.1
|
version: 6.9.1(encoding@0.1.13)
|
||||||
geo-coordinates-parser:
|
geo-coordinates-parser:
|
||||||
specifier: ^1.7.4
|
specifier: ^1.7.4
|
||||||
version: 1.7.4
|
version: 1.7.4
|
||||||
@@ -881,7 +875,7 @@ importers:
|
|||||||
version: 6.9.1
|
version: 6.9.1
|
||||||
'@testing-library/svelte':
|
'@testing-library/svelte':
|
||||||
specifier: ^5.2.8
|
specifier: ^5.2.8
|
||||||
version: 5.3.1(svelte@5.50.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 5.3.1(svelte@5.50.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@testing-library/user-event':
|
'@testing-library/user-event':
|
||||||
specifier: ^14.5.2
|
specifier: ^14.5.2
|
||||||
version: 14.6.1(@testing-library/dom@10.4.1)
|
version: 14.6.1(@testing-library/dom@10.4.1)
|
||||||
@@ -905,7 +899,7 @@ importers:
|
|||||||
version: 1.5.6
|
version: 1.5.6
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.0.0
|
specifier: ^17.0.0
|
||||||
version: 17.2.4
|
version: 17.2.4
|
||||||
@@ -968,7 +962,7 @@ importers:
|
|||||||
version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -3402,52 +3396,9 @@ packages:
|
|||||||
'@microsoft/tsdoc@0.16.0':
|
'@microsoft/tsdoc@0.16.0':
|
||||||
resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
|
resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
|
|
||||||
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
|
|
||||||
resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
|
|
||||||
resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
|
|
||||||
resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
|
|
||||||
resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
|
||||||
resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@namnode/store@0.1.0':
|
'@namnode/store@0.1.0':
|
||||||
resolution: {integrity: sha512-4NGTldxKcmY0UuZ7OEkvCjs8ZEoeYB6M2UwMu74pdLiFMKxXbj9HdNk1Qn213bxX1O7bY5h+PLh5DZsTURZkYA==}
|
resolution: {integrity: sha512-4NGTldxKcmY0UuZ7OEkvCjs8ZEoeYB6M2UwMu74pdLiFMKxXbj9HdNk1Qn213bxX1O7bY5h+PLh5DZsTURZkYA==}
|
||||||
|
|
||||||
'@nestjs/bull-shared@11.0.4':
|
|
||||||
resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
|
||||||
'@nestjs/core': ^10.0.0 || ^11.0.0
|
|
||||||
|
|
||||||
'@nestjs/bullmq@11.0.4':
|
|
||||||
resolution: {integrity: sha512-wBzK9raAVG0/6NTMdvLGM4/FQ1lsB35/pYS8L6a0SDgkTiLpd7mAjQ8R692oMx5s7IjvgntaZOuTUrKYLNfIkA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
|
||||||
'@nestjs/core': ^10.0.0 || ^11.0.0
|
|
||||||
bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0
|
|
||||||
|
|
||||||
'@nestjs/cli@11.0.16':
|
'@nestjs/cli@11.0.16':
|
||||||
resolution: {integrity: sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==}
|
resolution: {integrity: sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==}
|
||||||
engines: {node: '>= 20.11'}
|
engines: {node: '>= 20.11'}
|
||||||
@@ -5744,9 +5695,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
|
resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
|
||||||
engines: {node: '>=18.20'}
|
engines: {node: '>=18.20'}
|
||||||
|
|
||||||
bullmq@5.67.3:
|
|
||||||
resolution: {integrity: sha512-eeQobOJn8M0Rj8tcZCVFLrimZgJQallJH1JpclOoyut2nDNkDwTEPMVcZzLeSR2fGeIVbfJTjU96F563Qkge5A==}
|
|
||||||
|
|
||||||
bundle-name@4.1.0:
|
bundle-name@4.1.0:
|
||||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -6241,10 +6189,6 @@ packages:
|
|||||||
crelt@1.0.6:
|
crelt@1.0.6:
|
||||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||||
|
|
||||||
cron-parser@4.9.0:
|
|
||||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
|
||||||
engines: {node: '>=12.0.0'}
|
|
||||||
|
|
||||||
cron@4.4.0:
|
cron@4.4.0:
|
||||||
resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==}
|
resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==}
|
||||||
engines: {node: '>=18.x'}
|
engines: {node: '>=18.x'}
|
||||||
@@ -9104,13 +9048,6 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
msgpackr-extract@3.0.3:
|
|
||||||
resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
msgpackr@1.11.5:
|
|
||||||
resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==}
|
|
||||||
|
|
||||||
multer@2.0.2:
|
multer@2.0.2:
|
||||||
resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==}
|
resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==}
|
||||||
engines: {node: '>= 10.16.0'}
|
engines: {node: '>= 10.16.0'}
|
||||||
@@ -9240,10 +9177,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==}
|
resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==}
|
||||||
engines: {node: '>= 6.13.0'}
|
engines: {node: '>= 6.13.0'}
|
||||||
|
|
||||||
node-gyp-build-optional-packages@5.2.2:
|
|
||||||
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
node-gyp-build@4.8.4:
|
node-gyp-build@4.8.4:
|
||||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -10753,11 +10686,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
semver@7.7.3:
|
|
||||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
semver@7.7.4:
|
semver@7.7.4:
|
||||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -15291,22 +15219,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mapbox-gl: 1.13.3
|
mapbox-gl: 1.13.3
|
||||||
|
|
||||||
'@mapbox/node-pre-gyp@1.0.11':
|
|
||||||
dependencies:
|
|
||||||
detect-libc: 2.1.2
|
|
||||||
https-proxy-agent: 5.0.1
|
|
||||||
make-dir: 3.1.0
|
|
||||||
node-fetch: 2.7.0
|
|
||||||
nopt: 5.0.0
|
|
||||||
npmlog: 5.0.1
|
|
||||||
rimraf: 3.0.2
|
|
||||||
semver: 7.7.4
|
|
||||||
tar: 6.2.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- encoding
|
|
||||||
- supports-color
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
|
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-libc: 2.1.2
|
detect-libc: 2.1.2
|
||||||
@@ -15426,40 +15338,8 @@ snapshots:
|
|||||||
|
|
||||||
'@microsoft/tsdoc@0.16.0': {}
|
'@microsoft/tsdoc@0.16.0': {}
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@namnode/store@0.1.0': {}
|
'@namnode/store@0.1.0': {}
|
||||||
|
|
||||||
'@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)':
|
|
||||||
dependencies:
|
|
||||||
'@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
'@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@nestjs/bullmq@11.0.4(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(bullmq@5.67.3)':
|
|
||||||
dependencies:
|
|
||||||
'@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)
|
|
||||||
'@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
'@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
bullmq: 5.67.3
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@nestjs/cli@11.0.16(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@24.10.13)':
|
'@nestjs/cli@11.0.16(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@24.10.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-devkit/core': 19.2.19(chokidar@4.0.3)
|
'@angular-devkit/core': 19.2.19(chokidar@4.0.3)
|
||||||
@@ -16627,14 +16507,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.50.0
|
svelte: 5.50.0
|
||||||
|
|
||||||
'@testing-library/svelte@5.3.1(svelte@5.50.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
'@testing-library/svelte@5.3.1(svelte@5.50.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@testing-library/dom': 10.4.1
|
'@testing-library/dom': 10.4.1
|
||||||
'@testing-library/svelte-core': 1.0.0(svelte@5.50.0)
|
'@testing-library/svelte-core': 1.0.0(svelte@5.50.0)
|
||||||
svelte: 5.50.0
|
svelte: 5.50.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
'@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
|
'@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17330,7 +17210,7 @@ snapshots:
|
|||||||
|
|
||||||
'@vercel/oidc@3.0.5': {}
|
'@vercel/oidc@3.0.5': {}
|
||||||
|
|
||||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
'@bcoe/v8-coverage': 1.0.2
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
@@ -17345,11 +17225,11 @@ snapshots:
|
|||||||
std-env: 3.10.0
|
std-env: 3.10.0
|
||||||
test-exclude: 7.0.1
|
test-exclude: 7.0.1
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
'@bcoe/v8-coverage': 1.0.2
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
@@ -17364,7 +17244,7 @@ snapshots:
|
|||||||
std-env: 3.10.0
|
std-env: 3.10.0
|
||||||
test-exclude: 7.0.1
|
test-exclude: 7.0.1
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -17987,18 +17867,6 @@ snapshots:
|
|||||||
|
|
||||||
builtin-modules@5.0.0: {}
|
builtin-modules@5.0.0: {}
|
||||||
|
|
||||||
bullmq@5.67.3:
|
|
||||||
dependencies:
|
|
||||||
cron-parser: 4.9.0
|
|
||||||
ioredis: 5.9.2
|
|
||||||
msgpackr: 1.11.5
|
|
||||||
node-abort-controller: 3.1.1
|
|
||||||
semver: 7.7.3
|
|
||||||
tslib: 2.8.1
|
|
||||||
uuid: 11.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
bundle-name@4.1.0:
|
bundle-name@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
run-applescript: 7.1.0
|
run-applescript: 7.1.0
|
||||||
@@ -18084,16 +17952,6 @@ snapshots:
|
|||||||
|
|
||||||
caniuse-lite@1.0.30001769: {}
|
caniuse-lite@1.0.30001769: {}
|
||||||
|
|
||||||
canvas@2.11.2:
|
|
||||||
dependencies:
|
|
||||||
'@mapbox/node-pre-gyp': 1.0.11
|
|
||||||
nan: 2.24.0
|
|
||||||
simple-get: 3.1.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- encoding
|
|
||||||
- supports-color
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
canvas@2.11.2(encoding@0.1.13):
|
canvas@2.11.2(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
|
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
|
||||||
@@ -18487,10 +18345,6 @@ snapshots:
|
|||||||
|
|
||||||
crelt@1.0.6: {}
|
crelt@1.0.6: {}
|
||||||
|
|
||||||
cron-parser@4.9.0:
|
|
||||||
dependencies:
|
|
||||||
luxon: 3.7.2
|
|
||||||
|
|
||||||
cron@4.4.0:
|
cron@4.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/luxon': 3.7.1
|
'@types/luxon': 3.7.1
|
||||||
@@ -19714,10 +19568,10 @@ snapshots:
|
|||||||
|
|
||||||
extend@3.0.2: {}
|
extend@3.0.2: {}
|
||||||
|
|
||||||
fabric@6.9.1:
|
fabric@6.9.1(encoding@0.1.13):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
canvas: 2.11.2
|
canvas: 2.11.2(encoding@0.1.13)
|
||||||
jsdom: 20.0.3(canvas@2.11.2)
|
jsdom: 20.0.3(canvas@2.11.2(encoding@0.1.13))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- encoding
|
- encoding
|
||||||
@@ -20870,7 +20724,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
jsdom@20.0.3(canvas@2.11.2):
|
jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)):
|
||||||
dependencies:
|
dependencies:
|
||||||
abab: 2.0.6
|
abab: 2.0.6
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
@@ -20899,7 +20753,7 @@ snapshots:
|
|||||||
ws: 8.19.0
|
ws: 8.19.0
|
||||||
xml-name-validator: 4.0.0
|
xml-name-validator: 4.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
canvas: 2.11.2
|
canvas: 2.11.2(encoding@0.1.13)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -20936,36 +20790,6 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
jsdom@26.1.0(canvas@2.11.2):
|
|
||||||
dependencies:
|
|
||||||
cssstyle: 4.6.0
|
|
||||||
data-urls: 5.0.0
|
|
||||||
decimal.js: 10.6.0
|
|
||||||
html-encoding-sniffer: 4.0.0
|
|
||||||
http-proxy-agent: 7.0.2
|
|
||||||
https-proxy-agent: 7.0.6
|
|
||||||
is-potential-custom-element-name: 1.0.1
|
|
||||||
nwsapi: 2.2.23
|
|
||||||
parse5: 7.3.0
|
|
||||||
rrweb-cssom: 0.8.0
|
|
||||||
saxes: 6.0.0
|
|
||||||
symbol-tree: 3.2.4
|
|
||||||
tough-cookie: 5.1.2
|
|
||||||
w3c-xmlserializer: 5.0.0
|
|
||||||
webidl-conversions: 7.0.0
|
|
||||||
whatwg-encoding: 3.1.1
|
|
||||||
whatwg-mimetype: 4.0.0
|
|
||||||
whatwg-url: 14.2.0
|
|
||||||
ws: 8.19.0
|
|
||||||
xml-name-validator: 5.0.0
|
|
||||||
optionalDependencies:
|
|
||||||
canvas: 2.11.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- bufferutil
|
|
||||||
- supports-color
|
|
||||||
- utf-8-validate
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
jsep@1.4.0: {}
|
jsep@1.4.0: {}
|
||||||
|
|
||||||
jsesc@3.1.0: {}
|
jsesc@3.1.0: {}
|
||||||
@@ -22089,22 +21913,6 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
msgpackr-extract@3.0.3:
|
|
||||||
dependencies:
|
|
||||||
node-gyp-build-optional-packages: 5.2.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
|
|
||||||
'@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
|
|
||||||
'@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
msgpackr@1.11.5:
|
|
||||||
optionalDependencies:
|
|
||||||
msgpackr-extract: 3.0.3
|
|
||||||
|
|
||||||
multer@2.0.2:
|
multer@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
append-field: 1.0.0
|
append-field: 1.0.0
|
||||||
@@ -22223,11 +22031,6 @@ snapshots:
|
|||||||
emojilib: 2.4.0
|
emojilib: 2.4.0
|
||||||
skin-tone: 2.0.0
|
skin-tone: 2.0.0
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
|
||||||
dependencies:
|
|
||||||
whatwg-url: 5.0.0
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
node-fetch@2.7.0(encoding@0.1.13):
|
node-fetch@2.7.0(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
@@ -22236,11 +22039,6 @@ snapshots:
|
|||||||
|
|
||||||
node-forge@1.3.3: {}
|
node-forge@1.3.3: {}
|
||||||
|
|
||||||
node-gyp-build-optional-packages@5.2.2:
|
|
||||||
dependencies:
|
|
||||||
detect-libc: 2.1.2
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
node-gyp-build@4.8.4: {}
|
node-gyp-build@4.8.4: {}
|
||||||
|
|
||||||
node-gyp@12.2.0:
|
node-gyp@12.2.0:
|
||||||
@@ -23938,8 +23736,6 @@ snapshots:
|
|||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.7.3: {}
|
|
||||||
|
|
||||||
semver@7.7.4: {}
|
semver@7.7.4: {}
|
||||||
|
|
||||||
send@0.19.2:
|
send@0.19.2:
|
||||||
@@ -25379,9 +25175,9 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)):
|
vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -25427,51 +25223,7 @@ snapshots:
|
|||||||
- tsx
|
- tsx
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
|
||||||
'@types/chai': 5.2.3
|
|
||||||
'@vitest/expect': 3.2.4
|
|
||||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
|
||||||
'@vitest/pretty-format': 3.2.4
|
|
||||||
'@vitest/runner': 3.2.4
|
|
||||||
'@vitest/snapshot': 3.2.4
|
|
||||||
'@vitest/spy': 3.2.4
|
|
||||||
'@vitest/utils': 3.2.4
|
|
||||||
chai: 5.3.3
|
|
||||||
debug: 4.4.3
|
|
||||||
expect-type: 1.3.0
|
|
||||||
magic-string: 0.30.21
|
|
||||||
pathe: 2.0.3
|
|
||||||
picomatch: 4.0.3
|
|
||||||
std-env: 3.10.0
|
|
||||||
tinybench: 2.9.0
|
|
||||||
tinyexec: 0.3.2
|
|
||||||
tinyglobby: 0.2.15
|
|
||||||
tinypool: 1.1.1
|
|
||||||
tinyrainbow: 2.0.0
|
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
|
||||||
vite-node: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
|
||||||
why-is-node-running: 2.3.0
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/debug': 4.1.12
|
|
||||||
'@types/node': 24.10.13
|
|
||||||
happy-dom: 20.5.0
|
|
||||||
jsdom: 26.1.0(canvas@2.11.2)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- jiti
|
|
||||||
- less
|
|
||||||
- lightningcss
|
|
||||||
- msw
|
|
||||||
- sass
|
|
||||||
- sass-embedded
|
|
||||||
- stylus
|
|
||||||
- sugarss
|
|
||||||
- supports-color
|
|
||||||
- terser
|
|
||||||
- tsx
|
|
||||||
- yaml
|
|
||||||
|
|
||||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
@@ -25500,7 +25252,7 @@ snapshots:
|
|||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
'@types/node': 25.2.3
|
'@types/node': 25.2.3
|
||||||
happy-dom: 20.5.0
|
happy-dom: 20.5.0
|
||||||
jsdom: 26.1.0(canvas@2.11.2)
|
jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- jiti
|
- jiti
|
||||||
- less
|
- less
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@extism/extism": "2.0.0-rc13",
|
"@extism/extism": "2.0.0-rc13",
|
||||||
"@nestjs/bullmq": "^11.0.1",
|
|
||||||
"@nestjs/common": "^11.0.4",
|
"@nestjs/common": "^11.0.4",
|
||||||
"@nestjs/core": "^11.0.4",
|
"@nestjs/core": "^11.0.4",
|
||||||
"@nestjs/platform-express": "^11.0.4",
|
"@nestjs/platform-express": "^11.0.4",
|
||||||
@@ -62,7 +61,6 @@
|
|||||||
"async-lock": "^1.4.0",
|
"async-lock": "^1.4.0",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
"bullmq": "^5.51.0",
|
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BullModule } from '@nestjs/bullmq';
|
|
||||||
import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common';
|
import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common';
|
||||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
||||||
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
||||||
@@ -22,6 +21,7 @@ import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
|||||||
import { repositories } from 'src/repositories';
|
import { repositories } from 'src/repositories';
|
||||||
import { AppRepository } from 'src/repositories/app.repository';
|
import { AppRepository } from 'src/repositories/app.repository';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
@@ -49,7 +49,7 @@ const commonMiddleware = [
|
|||||||
const apiMiddleware = [FileUploadInterceptor, ...commonMiddleware, { provide: APP_GUARD, useClass: AuthGuard }];
|
const apiMiddleware = [FileUploadInterceptor, ...commonMiddleware, { provide: APP_GUARD, useClass: AuthGuard }];
|
||||||
|
|
||||||
const configRepository = new ConfigRepository();
|
const configRepository = new ConfigRepository();
|
||||||
const { bull, cls, database, otel } = configRepository.getEnv();
|
const { cls, database, otel } = configRepository.getEnv();
|
||||||
|
|
||||||
const commonImports = [
|
const commonImports = [
|
||||||
ClsModule.forRoot(cls.config),
|
ClsModule.forRoot(cls.config),
|
||||||
@@ -57,7 +57,6 @@ const commonImports = [
|
|||||||
OpenTelemetryModule.forRoot(otel),
|
OpenTelemetryModule.forRoot(otel),
|
||||||
];
|
];
|
||||||
|
|
||||||
const bullImports = [BullModule.forRoot(bull.config), BullModule.registerQueue(...bull.queues)];
|
|
||||||
|
|
||||||
export class BaseModule implements OnModuleInit, OnModuleDestroy {
|
export class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -65,6 +64,7 @@ export class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||||||
logger: LoggingRepository,
|
logger: LoggingRepository,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private eventRepository: EventRepository,
|
private eventRepository: EventRepository,
|
||||||
|
private jobRepository: JobRepository,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private telemetryRepository: TelemetryRepository,
|
private telemetryRepository: TelemetryRepository,
|
||||||
private websocketRepository: WebsocketRepository,
|
private websocketRepository: WebsocketRepository,
|
||||||
@@ -91,12 +91,13 @@ export class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
await this.eventRepository.emit('AppShutdown');
|
await this.eventRepository.emit('AppShutdown');
|
||||||
|
await this.jobRepository.onShutdown();
|
||||||
await teardownTelemetry();
|
await teardownTelemetry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [...bullImports, ...commonImports, ScheduleModule.forRoot()],
|
imports: [...commonImports, ScheduleModule.forRoot()],
|
||||||
controllers: [...controllers],
|
controllers: [...controllers],
|
||||||
providers: [...common, ...apiMiddleware, { provide: IWorker, useValue: ImmichWorker.Api }],
|
providers: [...common, ...apiMiddleware, { provide: IWorker, useValue: ImmichWorker.Api }],
|
||||||
})
|
})
|
||||||
@@ -137,13 +138,13 @@ export class MaintenanceModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [...bullImports, ...commonImports],
|
imports: [...commonImports],
|
||||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
|
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
|
||||||
})
|
})
|
||||||
export class MicroservicesModule extends BaseModule {}
|
export class MicroservicesModule extends BaseModule {}
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [...bullImports, ...commonImports],
|
imports: [...commonImports],
|
||||||
providers: [...common, ...commandsAndQuestions, SchedulerRegistry],
|
providers: [...common, ...commandsAndQuestions, SchedulerRegistry],
|
||||||
})
|
})
|
||||||
export class ImmichAdminModule implements OnModuleDestroy {
|
export class ImmichAdminModule implements OnModuleDestroy {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
|
||||||
import { Inject, Injectable, Optional } from '@nestjs/common';
|
import { Inject, Injectable, Optional } from '@nestjs/common';
|
||||||
import { QueueOptions } from 'bullmq';
|
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { validateSync } from 'class-validator';
|
import { validateSync } from 'class-validator';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
@@ -19,7 +17,6 @@ import {
|
|||||||
ImmichWorker,
|
ImmichWorker,
|
||||||
LogFormat,
|
LogFormat,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
QueueName,
|
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
|
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
|
||||||
import { setDifference } from 'src/utils/set';
|
import { setDifference } from 'src/utils/set';
|
||||||
@@ -48,11 +45,6 @@ export interface EnvData {
|
|||||||
thirdPartySupportUrl?: string;
|
thirdPartySupportUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
bull: {
|
|
||||||
config: QueueOptions;
|
|
||||||
queues: RegisterQueueOptions[];
|
|
||||||
};
|
|
||||||
|
|
||||||
cls: {
|
cls: {
|
||||||
config: ClsModuleOptions;
|
config: ClsModuleOptions;
|
||||||
};
|
};
|
||||||
@@ -253,19 +245,6 @@ const getEnv = (): EnvData => {
|
|||||||
thirdPartySupportUrl: dto.IMMICH_THIRD_PARTY_SUPPORT_URL,
|
thirdPartySupportUrl: dto.IMMICH_THIRD_PARTY_SUPPORT_URL,
|
||||||
},
|
},
|
||||||
|
|
||||||
bull: {
|
|
||||||
config: {
|
|
||||||
prefix: 'immich_bull',
|
|
||||||
connection: { ...redisConfig },
|
|
||||||
defaultJobOptions: {
|
|
||||||
attempts: 1,
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
queues: Object.values(QueueName).map((name) => ({ name })),
|
|
||||||
},
|
|
||||||
|
|
||||||
cls: {
|
cls: {
|
||||||
config: {
|
config: {
|
||||||
middleware: {
|
middleware: {
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { getQueueToken } from '@nestjs/bullmq';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ModuleRef, Reflector } from '@nestjs/core';
|
import { ModuleRef, Reflector } from '@nestjs/core';
|
||||||
import { JobsOptions, Queue, Worker } from 'bullmq';
|
import { Kysely, sql } from 'kysely';
|
||||||
import { ClassConstructor } from 'class-transformer';
|
import { ClassConstructor } from 'class-transformer';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
|
import postgres from 'postgres';
|
||||||
import { JobConfig } from 'src/decorators';
|
import { JobConfig } from 'src/decorators';
|
||||||
import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto';
|
import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto';
|
||||||
import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum';
|
import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { QUEUE_TABLE, WriteBuffer } from 'src/repositories/job.write-buffer';
|
||||||
|
import { charToJobName, jobNameToChar, QueueWorker } from 'src/repositories/job.worker';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
import { JobCounts, JobItem, JobOf } from 'src/types';
|
import { JobCounts, JobItem, JobOf } from 'src/types';
|
||||||
|
import { asPostgresConnectionConfig } from 'src/utils/database';
|
||||||
import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc';
|
import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc';
|
||||||
|
|
||||||
type JobMapItem = {
|
type JobMapItem = {
|
||||||
@@ -20,16 +25,73 @@ type JobMapItem = {
|
|||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Status char codes
|
||||||
|
const STATUS_PENDING = 'p';
|
||||||
|
const STATUS_ACTIVE = 'a';
|
||||||
|
const STATUS_FAILED = 'f';
|
||||||
|
|
||||||
|
// Stall timeouts in milliseconds
|
||||||
|
const STALL_LONG = 60 * 60 * 1000; // 1 hour
|
||||||
|
const STALL_MEDIUM = 30 * 60 * 1000; // 30 min
|
||||||
|
const STALL_DEFAULT = 5 * 60 * 1000; // 5 min
|
||||||
|
|
||||||
|
const getStallTimeout = (queueName: QueueName): number => {
|
||||||
|
switch (queueName) {
|
||||||
|
case QueueName.VideoConversion:
|
||||||
|
case QueueName.BackupDatabase:
|
||||||
|
case QueueName.Editor: {
|
||||||
|
return STALL_LONG;
|
||||||
|
}
|
||||||
|
case QueueName.Library:
|
||||||
|
case QueueName.StorageTemplateMigration: {
|
||||||
|
return STALL_MEDIUM;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return STALL_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClaimBatch = (queueName: QueueName): number => {
|
||||||
|
switch (queueName) {
|
||||||
|
case QueueName.VideoConversion:
|
||||||
|
case QueueName.BackupDatabase:
|
||||||
|
case QueueName.StorageTemplateMigration:
|
||||||
|
case QueueName.Editor:
|
||||||
|
case QueueName.FacialRecognition:
|
||||||
|
case QueueName.DuplicateDetection: {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return 100; // will be clamped to slotsAvailable by the worker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map QueueJobStatus to our "char" status codes
|
||||||
|
const STATUS_FILTER: Record<QueueJobStatus, string | null> = {
|
||||||
|
[QueueJobStatus.Active]: STATUS_ACTIVE,
|
||||||
|
[QueueJobStatus.Failed]: STATUS_FAILED,
|
||||||
|
[QueueJobStatus.Waiting]: STATUS_PENDING,
|
||||||
|
[QueueJobStatus.Complete]: null, // completed jobs are deleted
|
||||||
|
[QueueJobStatus.Delayed]: STATUS_PENDING, // delayed = pending with future run_after
|
||||||
|
[QueueJobStatus.Paused]: STATUS_PENDING, // paused queue has pending jobs
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobRepository {
|
export class JobRepository {
|
||||||
private workers: Partial<Record<QueueName, Worker>> = {};
|
private workers: Partial<Record<QueueName, QueueWorker>> = {};
|
||||||
private handlers: Partial<Record<JobName, JobMapItem>> = {};
|
private handlers: Partial<Record<JobName, JobMapItem>> = {};
|
||||||
|
private writeBuffer!: WriteBuffer;
|
||||||
|
private listenConn: postgres.Sql | null = null;
|
||||||
|
private pauseState: Partial<Record<QueueName, boolean>> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
private configRepository: ConfigRepository,
|
private configRepository: ConfigRepository,
|
||||||
private eventRepository: EventRepository,
|
private eventRepository: EventRepository,
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
|
@InjectKysely() private db: Kysely<DB>,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(JobRepository.name);
|
this.logger.setContext(JobRepository.name);
|
||||||
}
|
}
|
||||||
@@ -85,15 +147,53 @@ export class JobRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startWorkers() {
|
startWorkers() {
|
||||||
const { bull } = this.configRepository.getEnv();
|
this.writeBuffer = new WriteBuffer(this.db, (queue) => this.notify(queue));
|
||||||
for (const queueName of Object.values(QueueName)) {
|
|
||||||
this.logger.debug(`Starting worker for queue: ${queueName}`);
|
// Startup sweep: reset any active jobs from a previous crash
|
||||||
this.workers[queueName] = new Worker(
|
const startupPromises = Object.values(QueueName).map(async (queueName) => {
|
||||||
queueName,
|
const tableName = QUEUE_TABLE[queueName];
|
||||||
(job) => this.eventRepository.emit('JobRun', queueName, job as JobItem),
|
await sql`
|
||||||
{ ...bull.config, concurrency: 1 },
|
UPDATE ${sql.table(tableName)}
|
||||||
);
|
SET "status" = ${STATUS_PENDING}::"char", "started_at" = NULL, "expires_at" = NULL
|
||||||
}
|
WHERE "status" = ${STATUS_ACTIVE}::"char"
|
||||||
|
`.execute(this.db);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load pause state and setup workers
|
||||||
|
void Promise.all(startupPromises).then(async () => {
|
||||||
|
// Load pause state from DB
|
||||||
|
const metaRows = await this.db.selectFrom('job_queue_meta').selectAll().execute();
|
||||||
|
for (const row of metaRows) {
|
||||||
|
this.pauseState[row.queue_name as QueueName] = row.is_paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create workers
|
||||||
|
for (const queueName of Object.values(QueueName)) {
|
||||||
|
const worker = new QueueWorker({
|
||||||
|
queueName,
|
||||||
|
tableName: QUEUE_TABLE[queueName],
|
||||||
|
stallTimeout: getStallTimeout(queueName),
|
||||||
|
claimBatch: getClaimBatch(queueName),
|
||||||
|
concurrency: 1,
|
||||||
|
db: this.db,
|
||||||
|
onJob: (job) => this.eventRepository.emit('JobRun', queueName, job),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.pauseState[queueName]) {
|
||||||
|
worker.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workers[queueName] = worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup LISTEN/NOTIFY
|
||||||
|
await this.setupListen();
|
||||||
|
|
||||||
|
// Trigger initial fetch for all workers
|
||||||
|
for (const worker of Object.values(this.workers)) {
|
||||||
|
worker.onNotification();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async run({ name, data }: JobItem) {
|
async run({ name, data }: JobItem) {
|
||||||
@@ -113,44 +213,92 @@ export class JobRepository {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.concurrency = concurrency;
|
worker.setConcurrency(concurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isActive(name: QueueName): Promise<boolean> {
|
isActive(name: QueueName): Promise<boolean> {
|
||||||
const queue = this.getQueue(name);
|
const worker = this.workers[name];
|
||||||
const count = await queue.getActiveCount();
|
return Promise.resolve(worker ? worker.activeJobCount > 0 : false);
|
||||||
return count > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async isPaused(name: QueueName): Promise<boolean> {
|
isPaused(name: QueueName): Promise<boolean> {
|
||||||
return this.getQueue(name).isPaused();
|
return Promise.resolve(this.pauseState[name] ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pause(name: QueueName) {
|
async pause(name: QueueName) {
|
||||||
return this.getQueue(name).pause();
|
this.pauseState[name] = true;
|
||||||
|
await this.db
|
||||||
|
.insertInto('job_queue_meta')
|
||||||
|
.values({ queue_name: name, is_paused: true })
|
||||||
|
.onConflict((oc) => oc.column('queue_name').doUpdateSet({ is_paused: true }))
|
||||||
|
.execute();
|
||||||
|
this.workers[name]?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
resume(name: QueueName) {
|
async resume(name: QueueName) {
|
||||||
return this.getQueue(name).resume();
|
this.pauseState[name] = false;
|
||||||
|
await this.db
|
||||||
|
.insertInto('job_queue_meta')
|
||||||
|
.values({ queue_name: name, is_paused: false })
|
||||||
|
.onConflict((oc) => oc.column('queue_name').doUpdateSet({ is_paused: false }))
|
||||||
|
.execute();
|
||||||
|
this.workers[name]?.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
empty(name: QueueName) {
|
async empty(name: QueueName) {
|
||||||
return this.getQueue(name).drain();
|
const tableName = QUEUE_TABLE[name];
|
||||||
|
await sql`DELETE FROM ${sql.table(tableName)} WHERE "status" = ${STATUS_PENDING}::"char"`.execute(this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(name: QueueName, type: QueueCleanType) {
|
async clear(name: QueueName, _type: QueueCleanType) {
|
||||||
return this.getQueue(name).clean(0, 1000, type);
|
const tableName = QUEUE_TABLE[name];
|
||||||
|
await sql`DELETE FROM ${sql.table(tableName)} WHERE "status" = ${STATUS_FAILED}::"char"`.execute(this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
getJobCounts(name: QueueName): Promise<JobCounts> {
|
async getJobCounts(name: QueueName): Promise<JobCounts> {
|
||||||
return this.getQueue(name).getJobCounts(
|
const tableName = QUEUE_TABLE[name];
|
||||||
'active',
|
const result = await sql<{ status: string; count: string }>`
|
||||||
'completed',
|
SELECT "status", count(*)::text as count FROM ${sql.table(tableName)} GROUP BY "status"
|
||||||
'failed',
|
`.execute(this.db);
|
||||||
'delayed',
|
|
||||||
'waiting',
|
const counts: JobCounts = {
|
||||||
'paused',
|
active: 0,
|
||||||
) as unknown as Promise<JobCounts>;
|
completed: 0,
|
||||||
|
failed: 0,
|
||||||
|
delayed: 0,
|
||||||
|
waiting: 0,
|
||||||
|
paused: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const row of result.rows) {
|
||||||
|
switch (row.status) {
|
||||||
|
case STATUS_PENDING: {
|
||||||
|
counts.waiting = Number(row.count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATUS_ACTIVE: {
|
||||||
|
counts.active = Number(row.count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATUS_FAILED: {
|
||||||
|
counts.failed = Number(row.count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-memory active count may be more accurate than DB for in-flight jobs
|
||||||
|
const worker = this.workers[name];
|
||||||
|
if (worker) {
|
||||||
|
counts.active = worker.activeJobCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pauseState[name]) {
|
||||||
|
counts.paused = counts.waiting;
|
||||||
|
counts.waiting = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getQueueName(name: JobName) {
|
private getQueueName(name: JobName) {
|
||||||
@@ -162,31 +310,24 @@ export class JobRepository {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = [];
|
const bufferItems: { queue: QueueName; row: { name: string; data: unknown; priority: number; dedup_key: string | null; run_after: Date } }[] = [];
|
||||||
const itemsByQueue = {} as Record<string, (JobItem & { data: any; options: JobsOptions | undefined })[]>;
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const queueName = this.getQueueName(item.name);
|
const queueName = this.getQueueName(item.name);
|
||||||
const job = {
|
const options = this.getJobOptions(item);
|
||||||
name: item.name,
|
bufferItems.push({
|
||||||
data: item.data || {},
|
queue: queueName,
|
||||||
options: this.getJobOptions(item) || undefined,
|
row: {
|
||||||
} as JobItem & { data: any; options: JobsOptions | undefined };
|
name: jobNameToChar(item.name),
|
||||||
|
data: item.data || {},
|
||||||
if (job.options?.jobId) {
|
priority: options?.priority ?? 0,
|
||||||
// need to use add() instead of addBulk() for jobId deduplication
|
dedup_key: options?.dedupKey ?? null,
|
||||||
promises.push(this.getQueue(queueName).add(item.name, item.data, job.options));
|
run_after: options?.delay ? new Date(Date.now() + options.delay) : new Date(),
|
||||||
} else {
|
},
|
||||||
itemsByQueue[queueName] = itemsByQueue[queueName] || [];
|
});
|
||||||
itemsByQueue[queueName].push(job);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [queueName, jobs] of Object.entries(itemsByQueue)) {
|
await this.writeBuffer.add(bufferItems);
|
||||||
const queue = this.getQueue(queueName as QueueName);
|
|
||||||
promises.push(queue.addBulk(jobs));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async queue(item: JobItem): Promise<void> {
|
async queue(item: JobItem): Promise<void> {
|
||||||
@@ -209,29 +350,50 @@ export class JobRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async searchJobs(name: QueueName, dto: QueueJobSearchDto): Promise<QueueJobResponseDto[]> {
|
async searchJobs(name: QueueName, dto: QueueJobSearchDto): Promise<QueueJobResponseDto[]> {
|
||||||
const jobs = await this.getQueue(name).getJobs(dto.status ?? Object.values(QueueJobStatus), 0, 1000);
|
const tableName = QUEUE_TABLE[name];
|
||||||
return jobs.map((job) => {
|
const statuses = dto.status ?? Object.values(QueueJobStatus);
|
||||||
const { id, name, timestamp, data } = job;
|
const charStatuses = statuses
|
||||||
return { id, name: name as JobName, timestamp, data };
|
.map((s) => STATUS_FILTER[s])
|
||||||
});
|
.filter((s): s is string => s !== null);
|
||||||
|
|
||||||
|
if (charStatuses.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueStatuses = [...new Set(charStatuses)];
|
||||||
|
|
||||||
|
const rows = await sql<{ id: number; name: string; data: unknown; run_after: Date }>`
|
||||||
|
SELECT "id", "name", "data", "run_after"
|
||||||
|
FROM ${sql.table(tableName)}
|
||||||
|
WHERE "status" = ANY(${sql.val(uniqueStatuses)}::"char"[])
|
||||||
|
ORDER BY "id" DESC
|
||||||
|
LIMIT 1000
|
||||||
|
`.execute(this.db);
|
||||||
|
|
||||||
|
return rows.rows.map((row) => ({
|
||||||
|
id: String(row.id),
|
||||||
|
name: charToJobName(row.name) ?? (row.name as unknown as JobName),
|
||||||
|
data: (row.data ?? {}) as object,
|
||||||
|
timestamp: new Date(row.run_after).getTime(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getJobOptions(item: JobItem): JobsOptions | null {
|
private getJobOptions(item: JobItem): { dedupKey?: string; priority?: number; delay?: number } | null {
|
||||||
switch (item.name) {
|
switch (item.name) {
|
||||||
case JobName.NotifyAlbumUpdate: {
|
case JobName.NotifyAlbumUpdate: {
|
||||||
return {
|
return {
|
||||||
jobId: `${item.data.id}/${item.data.recipientId}`,
|
dedupKey: `${item.data.id}/${item.data.recipientId}`,
|
||||||
delay: item.data?.delay,
|
delay: item.data?.delay,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case JobName.StorageTemplateMigrationSingle: {
|
case JobName.StorageTemplateMigrationSingle: {
|
||||||
return { jobId: item.data.id };
|
return { dedupKey: item.data.id };
|
||||||
}
|
}
|
||||||
case JobName.PersonGenerateThumbnail: {
|
case JobName.PersonGenerateThumbnail: {
|
||||||
return { priority: 1 };
|
return { priority: 1 };
|
||||||
}
|
}
|
||||||
case JobName.FacialRecognitionQueueAll: {
|
case JobName.FacialRecognitionQueueAll: {
|
||||||
return { jobId: JobName.FacialRecognitionQueueAll };
|
return { dedupKey: JobName.FacialRecognitionQueueAll };
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return null;
|
return null;
|
||||||
@@ -239,16 +401,52 @@ export class JobRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getQueue(queue: QueueName): Queue {
|
|
||||||
return this.moduleRef.get<Queue>(getQueueToken(queue), { strict: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
// todo: remove this when asset notifications no longer need it.
|
// todo: remove this when asset notifications no longer need it.
|
||||||
public async removeJob(name: JobName, jobID: string): Promise<void> {
|
public async removeJob(name: JobName, jobID: string): Promise<void> {
|
||||||
const existingJob = await this.getQueue(this.getQueueName(name)).getJob(jobID);
|
const queueName = this.getQueueName(name);
|
||||||
if (existingJob) {
|
const tableName = QUEUE_TABLE[queueName];
|
||||||
await existingJob.remove();
|
await sql`DELETE FROM ${sql.table(tableName)} WHERE "id" = ${Number(jobID)}`.execute(this.db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setupListen(): Promise<void> {
|
||||||
|
const { database } = this.configRepository.getEnv();
|
||||||
|
const pgConfig = asPostgresConnectionConfig(database.config);
|
||||||
|
this.listenConn = postgres({
|
||||||
|
host: pgConfig.host,
|
||||||
|
port: pgConfig.port,
|
||||||
|
username: pgConfig.username,
|
||||||
|
password: pgConfig.password as string | undefined,
|
||||||
|
database: pgConfig.database,
|
||||||
|
ssl: pgConfig.ssl as boolean | undefined,
|
||||||
|
max: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const queueName of Object.values(QueueName)) {
|
||||||
|
await this.listenConn.listen(`jobs:${queueName}`, () => {
|
||||||
|
this.workers[queueName]?.onNotification();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notify(queue: QueueName): Promise<void> {
|
||||||
|
await sql`SELECT pg_notify(${`jobs:${queue}`}, '')`.execute(this.db);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onShutdown(): Promise<void> {
|
||||||
|
// Stop workers
|
||||||
|
const shutdownPromises = Object.values(this.workers).map((worker) => worker.shutdown());
|
||||||
|
await Promise.all(shutdownPromises);
|
||||||
|
|
||||||
|
// Flush write buffer
|
||||||
|
if (this.writeBuffer) {
|
||||||
|
await this.writeBuffer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close LISTEN connection
|
||||||
|
if (this.listenConn) {
|
||||||
|
await this.listenConn.end();
|
||||||
|
this.listenConn = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
import { JobName, QueueName } from 'src/enum';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
import { JobItem } from 'src/types';
|
||||||
|
|
||||||
|
// Job status codes stored as "char" (single-byte PostgreSQL type)
|
||||||
|
const STATUS_PENDING = 'p';
|
||||||
|
const STATUS_ACTIVE = 'a';
|
||||||
|
const STATUS_FAILED = 'f';
|
||||||
|
|
||||||
|
// Bidirectional JobName <-> "char" mapping
|
||||||
|
const JOB_CHAR: Record<string, string> = {};
|
||||||
|
const CHAR_JOB: Record<string, JobName> = {};
|
||||||
|
|
||||||
|
// Assign sequential character codes starting from 0x01
|
||||||
|
let charCode = 1;
|
||||||
|
for (const jobName of Object.values(JobName)) {
|
||||||
|
const char = String.fromCodePoint(charCode++);
|
||||||
|
JOB_CHAR[jobName] = char;
|
||||||
|
CHAR_JOB[char] = jobName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jobNameToChar = (name: JobName): string => JOB_CHAR[name];
|
||||||
|
export const charToJobName = (char: string): JobName | undefined => CHAR_JOB[char];
|
||||||
|
|
||||||
|
type JobRow = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
data: unknown;
|
||||||
|
priority: number;
|
||||||
|
status: string;
|
||||||
|
dedup_key: string | null;
|
||||||
|
run_after: Date;
|
||||||
|
started_at: Date | null;
|
||||||
|
expires_at: Date | null;
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface QueueWorkerOptions {
|
||||||
|
queueName: QueueName;
|
||||||
|
tableName: string;
|
||||||
|
stallTimeout: number;
|
||||||
|
claimBatch: number;
|
||||||
|
concurrency: number;
|
||||||
|
db: Kysely<DB>;
|
||||||
|
onJob: (job: JobItem) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueueWorker {
|
||||||
|
private concurrency: number;
|
||||||
|
private activeCount = 0;
|
||||||
|
private activeJobs = new Map<number, { startedAt: number }>();
|
||||||
|
private hasPending = true;
|
||||||
|
private fetching = false;
|
||||||
|
private paused = false;
|
||||||
|
private stopped = false;
|
||||||
|
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
|
private readonly queueName: QueueName;
|
||||||
|
private readonly tableName: string;
|
||||||
|
private readonly stallTimeout: number;
|
||||||
|
private readonly claimBatch: number;
|
||||||
|
private readonly db: Kysely<DB>;
|
||||||
|
private readonly onJobFn: (job: JobItem) => Promise<void>;
|
||||||
|
|
||||||
|
constructor(options: QueueWorkerOptions) {
|
||||||
|
this.queueName = options.queueName;
|
||||||
|
this.tableName = options.tableName;
|
||||||
|
this.stallTimeout = options.stallTimeout;
|
||||||
|
this.claimBatch = options.claimBatch;
|
||||||
|
this.concurrency = options.concurrency;
|
||||||
|
this.db = options.db;
|
||||||
|
this.onJobFn = options.onJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeJobCount(): number {
|
||||||
|
return this.activeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotification(): void {
|
||||||
|
this.hasPending = true;
|
||||||
|
void this.tryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConcurrency(n: number): void {
|
||||||
|
this.concurrency = n;
|
||||||
|
void this.tryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
pause(): void {
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
resume(): void {
|
||||||
|
this.paused = false;
|
||||||
|
this.hasPending = true;
|
||||||
|
void this.tryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async shutdown(): Promise<void> {
|
||||||
|
this.stopped = true;
|
||||||
|
this.stopHeartbeat();
|
||||||
|
|
||||||
|
// Re-queue active jobs
|
||||||
|
if (this.activeJobs.size > 0) {
|
||||||
|
const ids = [...this.activeJobs.keys()];
|
||||||
|
await sql`
|
||||||
|
UPDATE ${sql.table(this.tableName)}
|
||||||
|
SET "status" = ${STATUS_PENDING}::"char", "started_at" = NULL, "expires_at" = NULL
|
||||||
|
WHERE "id" = ANY(${sql.val(ids)}::bigint[])
|
||||||
|
`.execute(this.db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get slotsAvailable(): number {
|
||||||
|
return Math.max(0, this.concurrency - this.activeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tryFetch(): Promise<void> {
|
||||||
|
if (this.fetching || this.paused || this.stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fetching = true;
|
||||||
|
try {
|
||||||
|
while (this.slotsAvailable > 0 && this.hasPending && !this.stopped) {
|
||||||
|
const limit = Math.min(this.slotsAvailable, this.claimBatch);
|
||||||
|
const jobs = await this.claim(limit);
|
||||||
|
if (jobs.length === 0) {
|
||||||
|
const recovered = await this.recoverStalled();
|
||||||
|
if (recovered === 0) {
|
||||||
|
this.hasPending = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.activeCount += jobs.length;
|
||||||
|
for (const job of jobs) {
|
||||||
|
void this.processJob(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.fetching = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processJob(row: JobRow): Promise<void> {
|
||||||
|
this.activeJobs.set(row.id, { startedAt: Date.now() });
|
||||||
|
this.startHeartbeat();
|
||||||
|
try {
|
||||||
|
const jobName = charToJobName(row.name);
|
||||||
|
if (!jobName) {
|
||||||
|
throw new Error(`Unknown job char code: ${row.name.codePointAt(0)}`);
|
||||||
|
}
|
||||||
|
await this.onJobFn({ name: jobName, data: row.data } as JobItem);
|
||||||
|
// Success: delete completed job and try to fetch next
|
||||||
|
const next = await this.completeAndFetch(row.id, true);
|
||||||
|
this.activeJobs.delete(row.id);
|
||||||
|
if (next) {
|
||||||
|
void this.processJob(next);
|
||||||
|
} else {
|
||||||
|
this.activeCount--;
|
||||||
|
this.hasPending = false;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// Failure: mark as failed and try to fetch next
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
const next = await this.completeAndFetch(row.id, false, errorMsg);
|
||||||
|
this.activeJobs.delete(row.id);
|
||||||
|
if (next) {
|
||||||
|
void this.processJob(next);
|
||||||
|
} else {
|
||||||
|
this.activeCount--;
|
||||||
|
this.hasPending = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (this.activeJobs.size === 0) {
|
||||||
|
this.stopHeartbeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim up to `limit` pending jobs using FOR UPDATE SKIP LOCKED
|
||||||
|
*/
|
||||||
|
private async claim(limit: number): Promise<JobRow[]> {
|
||||||
|
const result = await sql<JobRow>`
|
||||||
|
UPDATE ${sql.table(this.tableName)} SET
|
||||||
|
"status" = ${STATUS_ACTIVE}::"char",
|
||||||
|
"started_at" = now(),
|
||||||
|
"expires_at" = now() + ${sql.lit(`'${this.stallTimeout} milliseconds'`)}::interval
|
||||||
|
WHERE "id" IN (
|
||||||
|
SELECT "id" FROM ${sql.table(this.tableName)}
|
||||||
|
WHERE "status" = ${STATUS_PENDING}::"char" AND "run_after" <= now()
|
||||||
|
ORDER BY "priority" DESC, "id" ASC
|
||||||
|
FOR UPDATE SKIP LOCKED
|
||||||
|
LIMIT ${sql.lit(limit)}
|
||||||
|
)
|
||||||
|
RETURNING *
|
||||||
|
`.execute(this.db);
|
||||||
|
return result.rows as JobRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically complete a job (delete on success, mark failed on failure) and claim the next one.
|
||||||
|
* Uses a CTE to combine operations in a single round-trip.
|
||||||
|
*/
|
||||||
|
private async completeAndFetch(
|
||||||
|
jobId: number,
|
||||||
|
success: boolean,
|
||||||
|
errorMsg?: string,
|
||||||
|
): Promise<JobRow | undefined> {
|
||||||
|
if (success) {
|
||||||
|
const result = await sql<JobRow>`
|
||||||
|
WITH completed AS (
|
||||||
|
DELETE FROM ${sql.table(this.tableName)} WHERE "id" = ${jobId}
|
||||||
|
),
|
||||||
|
next AS (
|
||||||
|
SELECT "id" FROM ${sql.table(this.tableName)}
|
||||||
|
WHERE "status" = ${STATUS_PENDING}::"char" AND "run_after" <= now()
|
||||||
|
ORDER BY "priority" DESC, "id" ASC
|
||||||
|
FOR UPDATE SKIP LOCKED
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
UPDATE ${sql.table(this.tableName)} SET
|
||||||
|
"status" = ${STATUS_ACTIVE}::"char",
|
||||||
|
"started_at" = now(),
|
||||||
|
"expires_at" = now() + ${sql.lit(`'${this.stallTimeout} milliseconds'`)}::interval
|
||||||
|
WHERE "id" = (SELECT "id" FROM next)
|
||||||
|
RETURNING *
|
||||||
|
`.execute(this.db);
|
||||||
|
return (result.rows as JobRow[])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await sql<JobRow>`
|
||||||
|
WITH failed AS (
|
||||||
|
UPDATE ${sql.table(this.tableName)}
|
||||||
|
SET "status" = ${STATUS_FAILED}::"char", "error" = ${errorMsg ?? null}
|
||||||
|
WHERE "id" = ${jobId}
|
||||||
|
),
|
||||||
|
next AS (
|
||||||
|
SELECT "id" FROM ${sql.table(this.tableName)}
|
||||||
|
WHERE "status" = ${STATUS_PENDING}::"char" AND "run_after" <= now()
|
||||||
|
ORDER BY "priority" DESC, "id" ASC
|
||||||
|
FOR UPDATE SKIP LOCKED
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
UPDATE ${sql.table(this.tableName)} SET
|
||||||
|
"status" = ${STATUS_ACTIVE}::"char",
|
||||||
|
"started_at" = now(),
|
||||||
|
"expires_at" = now() + ${sql.lit(`'${this.stallTimeout} milliseconds'`)}::interval
|
||||||
|
WHERE "id" = (SELECT "id" FROM next)
|
||||||
|
RETURNING *
|
||||||
|
`.execute(this.db);
|
||||||
|
return (result.rows as JobRow[])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recover stalled jobs: reset jobs whose expires_at has passed
|
||||||
|
*/
|
||||||
|
private async recoverStalled(): Promise<number> {
|
||||||
|
const result = await sql`
|
||||||
|
UPDATE ${sql.table(this.tableName)}
|
||||||
|
SET "status" = ${STATUS_PENDING}::"char", "started_at" = NULL, "expires_at" = NULL
|
||||||
|
WHERE "status" = ${STATUS_ACTIVE}::"char" AND "expires_at" < now()
|
||||||
|
`.execute(this.db);
|
||||||
|
return Number(result.numAffectedRows ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend expiry for all active jobs (heartbeat)
|
||||||
|
*/
|
||||||
|
private async extendExpiry(): Promise<void> {
|
||||||
|
if (this.activeJobs.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = [...this.activeJobs.keys()];
|
||||||
|
await sql`
|
||||||
|
UPDATE ${sql.table(this.tableName)}
|
||||||
|
SET "expires_at" = now() + ${sql.lit(`'${this.stallTimeout} milliseconds'`)}::interval
|
||||||
|
WHERE "id" = ANY(${sql.val(ids)}::bigint[])
|
||||||
|
`.execute(this.db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startHeartbeat(): void {
|
||||||
|
if (this.heartbeatTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const interval = Math.max(1000, Math.floor(this.stallTimeout / 2));
|
||||||
|
this.heartbeatTimer = setInterval(() => void this.extendExpiry(), interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopHeartbeat(): void {
|
||||||
|
if (this.heartbeatTimer) {
|
||||||
|
clearInterval(this.heartbeatTimer);
|
||||||
|
this.heartbeatTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
import { QueueName } from 'src/enum';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
|
||||||
|
export type InsertRow = {
|
||||||
|
name: string;
|
||||||
|
data: unknown;
|
||||||
|
priority: number;
|
||||||
|
dedup_key: string | null;
|
||||||
|
run_after: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueueTableName = keyof DB & `jobs_${string}`;
|
||||||
|
|
||||||
|
export const QUEUE_TABLE: Record<QueueName, QueueTableName> = {
|
||||||
|
[QueueName.ThumbnailGeneration]: 'jobs_thumbnail_generation',
|
||||||
|
[QueueName.MetadataExtraction]: 'jobs_metadata_extraction',
|
||||||
|
[QueueName.VideoConversion]: 'jobs_video_conversion',
|
||||||
|
[QueueName.FaceDetection]: 'jobs_face_detection',
|
||||||
|
[QueueName.FacialRecognition]: 'jobs_facial_recognition',
|
||||||
|
[QueueName.SmartSearch]: 'jobs_smart_search',
|
||||||
|
[QueueName.DuplicateDetection]: 'jobs_duplicate_detection',
|
||||||
|
[QueueName.BackgroundTask]: 'jobs_background_task',
|
||||||
|
[QueueName.StorageTemplateMigration]: 'jobs_storage_template_migration',
|
||||||
|
[QueueName.Migration]: 'jobs_migration',
|
||||||
|
[QueueName.Search]: 'jobs_search',
|
||||||
|
[QueueName.Sidecar]: 'jobs_sidecar',
|
||||||
|
[QueueName.Library]: 'jobs_library',
|
||||||
|
[QueueName.Notification]: 'jobs_notification',
|
||||||
|
[QueueName.BackupDatabase]: 'jobs_backup_database',
|
||||||
|
[QueueName.Ocr]: 'jobs_ocr',
|
||||||
|
[QueueName.Workflow]: 'jobs_workflow',
|
||||||
|
[QueueName.Editor]: 'jobs_editor',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Deferred = { promise: Promise<void>; resolve: () => void };
|
||||||
|
|
||||||
|
const createDeferred = (): Deferred => {
|
||||||
|
let resolve!: () => void;
|
||||||
|
const promise = new Promise<void>((r) => (resolve = r));
|
||||||
|
return { promise, resolve };
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHUNK_SIZE = 5000;
|
||||||
|
|
||||||
|
export class WriteBuffer {
|
||||||
|
private buffers = new Map<QueueName, InsertRow[]>();
|
||||||
|
private pending: Deferred | null = null;
|
||||||
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private db: Kysely<DB>,
|
||||||
|
private notify: (queue: QueueName) => Promise<void>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async add(items: { queue: QueueName; row: InsertRow }[]): Promise<void> {
|
||||||
|
for (const { queue, row } of items) {
|
||||||
|
let buf = this.buffers.get(queue);
|
||||||
|
if (!buf) {
|
||||||
|
buf = [];
|
||||||
|
this.buffers.set(queue, buf);
|
||||||
|
}
|
||||||
|
buf.push(row);
|
||||||
|
}
|
||||||
|
if (!this.timer) {
|
||||||
|
this.pending = createDeferred();
|
||||||
|
this.timer = setTimeout(() => void this.flush(), 10);
|
||||||
|
}
|
||||||
|
return this.pending!.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async flush(): Promise<void> {
|
||||||
|
const snapshot = this.buffers;
|
||||||
|
this.buffers = new Map();
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
const deferred = this.pending;
|
||||||
|
this.pending = null;
|
||||||
|
|
||||||
|
if (snapshot.size === 0) {
|
||||||
|
deferred?.resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const [queue, rows] of snapshot) {
|
||||||
|
const tableName = QUEUE_TABLE[queue];
|
||||||
|
for (let i = 0; i < rows.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = rows.slice(i, i + CHUNK_SIZE);
|
||||||
|
await this.insertChunk(tableName, chunk);
|
||||||
|
}
|
||||||
|
await this.notify(queue);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
deferred?.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async insertChunk(tableName: string, rows: InsertRow[]): Promise<void> {
|
||||||
|
const names = rows.map((r) => r.name);
|
||||||
|
const datas = rows.map((r) => JSON.stringify(r.data));
|
||||||
|
const priorities = rows.map((r) => r.priority);
|
||||||
|
const dedupKeys = rows.map((r) => r.dedup_key);
|
||||||
|
const runAfters = rows.map((r) => r.run_after.toISOString());
|
||||||
|
|
||||||
|
await sql`
|
||||||
|
INSERT INTO ${sql.table(tableName)} ("name", "data", "priority", "dedup_key", "run_after")
|
||||||
|
SELECT * FROM unnest(
|
||||||
|
${sql.val(names)}::"char"[],
|
||||||
|
${sql.val(datas)}::jsonb[],
|
||||||
|
${sql.val(priorities)}::smallint[],
|
||||||
|
${sql.val(dedupKeys)}::text[],
|
||||||
|
${sql.val(runAfters)}::timestamptz[]
|
||||||
|
)
|
||||||
|
ON CONFLICT ("dedup_key") WHERE "dedup_key" IS NOT NULL AND "status" = 'p'::"char"
|
||||||
|
DO NOTHING
|
||||||
|
`.execute(this.db);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,27 @@ import { AssetTable } from 'src/schema/tables/asset.table';
|
|||||||
import { AuditTable } from 'src/schema/tables/audit.table';
|
import { AuditTable } from 'src/schema/tables/audit.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||||
|
import {
|
||||||
|
JobQueueMetaTable,
|
||||||
|
JobsBackgroundTaskTable,
|
||||||
|
JobsBackupDatabaseTable,
|
||||||
|
JobsDuplicateDetectionTable,
|
||||||
|
JobsEditorTable,
|
||||||
|
JobsFaceDetectionTable,
|
||||||
|
JobsFacialRecognitionTable,
|
||||||
|
JobsLibraryTable,
|
||||||
|
JobsMetadataExtractionTable,
|
||||||
|
JobsMigrationTable,
|
||||||
|
JobsNotificationTable,
|
||||||
|
JobsOcrTable,
|
||||||
|
JobsSearchTable,
|
||||||
|
JobsSidecarTable,
|
||||||
|
JobsSmartSearchTable,
|
||||||
|
JobsStorageTemplateMigrationTable,
|
||||||
|
JobsThumbnailGenerationTable,
|
||||||
|
JobsVideoConversionTable,
|
||||||
|
JobsWorkflowTable,
|
||||||
|
} from 'src/schema/tables/job.table';
|
||||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||||
@@ -135,6 +156,25 @@ export class ImmichDatabase {
|
|||||||
WorkflowTable,
|
WorkflowTable,
|
||||||
WorkflowFilterTable,
|
WorkflowFilterTable,
|
||||||
WorkflowActionTable,
|
WorkflowActionTable,
|
||||||
|
JobsThumbnailGenerationTable,
|
||||||
|
JobsMetadataExtractionTable,
|
||||||
|
JobsVideoConversionTable,
|
||||||
|
JobsFaceDetectionTable,
|
||||||
|
JobsFacialRecognitionTable,
|
||||||
|
JobsSmartSearchTable,
|
||||||
|
JobsDuplicateDetectionTable,
|
||||||
|
JobsBackgroundTaskTable,
|
||||||
|
JobsStorageTemplateMigrationTable,
|
||||||
|
JobsMigrationTable,
|
||||||
|
JobsSearchTable,
|
||||||
|
JobsSidecarTable,
|
||||||
|
JobsLibraryTable,
|
||||||
|
JobsNotificationTable,
|
||||||
|
JobsBackupDatabaseTable,
|
||||||
|
JobsOcrTable,
|
||||||
|
JobsWorkflowTable,
|
||||||
|
JobsEditorTable,
|
||||||
|
JobQueueMetaTable,
|
||||||
];
|
];
|
||||||
|
|
||||||
functions = [
|
functions = [
|
||||||
@@ -252,4 +292,24 @@ export interface DB {
|
|||||||
workflow: WorkflowTable;
|
workflow: WorkflowTable;
|
||||||
workflow_filter: WorkflowFilterTable;
|
workflow_filter: WorkflowFilterTable;
|
||||||
workflow_action: WorkflowActionTable;
|
workflow_action: WorkflowActionTable;
|
||||||
|
|
||||||
|
jobs_thumbnail_generation: JobsThumbnailGenerationTable;
|
||||||
|
jobs_metadata_extraction: JobsMetadataExtractionTable;
|
||||||
|
jobs_video_conversion: JobsVideoConversionTable;
|
||||||
|
jobs_face_detection: JobsFaceDetectionTable;
|
||||||
|
jobs_facial_recognition: JobsFacialRecognitionTable;
|
||||||
|
jobs_smart_search: JobsSmartSearchTable;
|
||||||
|
jobs_duplicate_detection: JobsDuplicateDetectionTable;
|
||||||
|
jobs_background_task: JobsBackgroundTaskTable;
|
||||||
|
jobs_storage_template_migration: JobsStorageTemplateMigrationTable;
|
||||||
|
jobs_migration: JobsMigrationTable;
|
||||||
|
jobs_search: JobsSearchTable;
|
||||||
|
jobs_sidecar: JobsSidecarTable;
|
||||||
|
jobs_library: JobsLibraryTable;
|
||||||
|
jobs_notification: JobsNotificationTable;
|
||||||
|
jobs_backup_database: JobsBackupDatabaseTable;
|
||||||
|
jobs_ocr: JobsOcrTable;
|
||||||
|
jobs_workflow: JobsWorkflowTable;
|
||||||
|
jobs_editor: JobsEditorTable;
|
||||||
|
job_queue_meta: JobQueueMetaTable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,800 @@
|
|||||||
|
import { Column, ConfigurationParameter, Generated, Index, PrimaryColumn, Table } from 'src/sql-tools';
|
||||||
|
|
||||||
|
// Job status values stored as "char" (single-byte PostgreSQL type):
|
||||||
|
// 'p' = pending, 'a' = active, 'c' = completed, 'f' = failed
|
||||||
|
|
||||||
|
@Table('jobs_thumbnail_generation')
|
||||||
|
@Index({ name: 'IDX_jobs_thumbnail_generation_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_thumbnail_generation_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsThumbnailGenerationTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_metadata_extraction')
|
||||||
|
@Index({ name: 'IDX_jobs_metadata_extraction_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_metadata_extraction_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsMetadataExtractionTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_video_conversion')
|
||||||
|
@Index({ name: 'IDX_jobs_video_conversion_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_video_conversion_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsVideoConversionTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_face_detection')
|
||||||
|
@Index({ name: 'IDX_jobs_face_detection_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_face_detection_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsFaceDetectionTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_facial_recognition')
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_facial_recognition_pending',
|
||||||
|
columns: ['priority', 'id'],
|
||||||
|
where: `"status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_facial_recognition_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsFacialRecognitionTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_smart_search')
|
||||||
|
@Index({ name: 'IDX_jobs_smart_search_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_smart_search_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsSmartSearchTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_duplicate_detection')
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_duplicate_detection_pending',
|
||||||
|
columns: ['priority', 'id'],
|
||||||
|
where: `"status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_duplicate_detection_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsDuplicateDetectionTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_background_task')
|
||||||
|
@Index({ name: 'IDX_jobs_background_task_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_background_task_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsBackgroundTaskTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_storage_template_migration')
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_storage_template_migration_pending',
|
||||||
|
columns: ['priority', 'id'],
|
||||||
|
where: `"status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_storage_template_migration_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsStorageTemplateMigrationTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_migration')
|
||||||
|
@Index({ name: 'IDX_jobs_migration_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_migration_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsMigrationTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_search')
|
||||||
|
@Index({ name: 'IDX_jobs_search_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_search_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsSearchTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_sidecar')
|
||||||
|
@Index({ name: 'IDX_jobs_sidecar_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_sidecar_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsSidecarTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_library')
|
||||||
|
@Index({ name: 'IDX_jobs_library_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_library_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsLibraryTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_notification')
|
||||||
|
@Index({ name: 'IDX_jobs_notification_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_notification_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsNotificationTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_backup_database')
|
||||||
|
@Index({ name: 'IDX_jobs_backup_database_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_backup_database_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsBackupDatabaseTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_ocr')
|
||||||
|
@Index({ name: 'IDX_jobs_ocr_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_ocr_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsOcrTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_workflow')
|
||||||
|
@Index({ name: 'IDX_jobs_workflow_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_workflow_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsWorkflowTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table('jobs_editor')
|
||||||
|
@Index({ name: 'IDX_jobs_editor_pending', columns: ['priority', 'id'], where: `"status" = 'p'::"char"` })
|
||||||
|
@Index({
|
||||||
|
name: 'IDX_jobs_editor_dedup',
|
||||||
|
columns: ['dedup_key'],
|
||||||
|
unique: true,
|
||||||
|
where: `"dedup_key" IS NOT NULL AND "status" = 'p'::"char"`,
|
||||||
|
})
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_threshold', value: 100, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_scale_factor', value: 0.01, scope: 'table' })
|
||||||
|
@ConfigurationParameter({ name: 'autovacuum_vacuum_cost_delay', value: 0, scope: 'table' })
|
||||||
|
export class JobsEditorTable {
|
||||||
|
@PrimaryColumn({ type: 'bigint', identity: true })
|
||||||
|
id!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
|
data!: unknown;
|
||||||
|
|
||||||
|
@Column({ type: 'smallint', default: 0 })
|
||||||
|
priority!: Generated<number>;
|
||||||
|
|
||||||
|
@Column({ type: '"char"', default: 'p' })
|
||||||
|
status!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
dedup_key!: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
run_after!: Generated<Date>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
started_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
expires_at!: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
error!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue metadata table
|
||||||
|
@Table('job_queue_meta')
|
||||||
|
export class JobQueueMetaTable {
|
||||||
|
@PrimaryColumn({ type: 'text' })
|
||||||
|
queue_name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: false })
|
||||||
|
is_paused!: Generated<boolean>;
|
||||||
|
}
|
||||||
@@ -9,13 +9,6 @@ const envData: EnvData = {
|
|||||||
logFormat: LogFormat.Console,
|
logFormat: LogFormat.Console,
|
||||||
|
|
||||||
buildMetadata: {},
|
buildMetadata: {},
|
||||||
bull: {
|
|
||||||
config: {
|
|
||||||
connection: {},
|
|
||||||
prefix: 'immich_bull',
|
|
||||||
},
|
|
||||||
queues: [{ name: 'queue-1' }],
|
|
||||||
},
|
|
||||||
|
|
||||||
cls: {
|
cls: {
|
||||||
config: {},
|
config: {},
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ export const newJobRepositoryMock = (): Mocked<RepositoryInterface<JobRepository
|
|||||||
clear: vitest.fn(),
|
clear: vitest.fn(),
|
||||||
waitForQueueCompletion: vitest.fn(),
|
waitForQueueCompletion: vitest.fn(),
|
||||||
removeJob: vitest.fn(),
|
removeJob: vitest.fn(),
|
||||||
|
onShutdown: vitest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user