cursedbelt-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/dist/server/activity/index.d.ts +36 -0
- package/dist/server/activity/index.js +36 -0
- package/dist/server/activity/migrations.d.ts +21 -0
- package/dist/server/activity/migrations.js +55 -0
- package/dist/server/activity/shareSource.d.ts +11 -0
- package/dist/server/activity/shareSource.js +45 -0
- package/dist/server/activity/store.d.ts +123 -0
- package/dist/server/activity/store.js +178 -0
- package/dist/server/app.d.ts +56 -0
- package/dist/server/app.js +45 -0
- package/dist/server/audit/index.d.ts +2 -0
- package/dist/server/audit/index.js +2 -0
- package/dist/server/audit/migrations.d.ts +2 -0
- package/dist/server/audit/migrations.js +79 -0
- package/dist/server/audit/sqliteSink.d.ts +36 -0
- package/dist/server/audit/sqliteSink.js +95 -0
- package/dist/server/audit/sqliteSink.test.d.ts +1 -0
- package/dist/server/audit/sqliteSink.test.js +180 -0
- package/dist/server/auth/cookie.d.ts +22 -0
- package/dist/server/auth/cookie.js +35 -0
- package/dist/server/auth/csrf.d.ts +42 -0
- package/dist/server/auth/csrf.js +36 -0
- package/dist/server/auth/fileTokenSigner.d.ts +10 -0
- package/dist/server/auth/fileTokenSigner.js +22 -0
- package/dist/server/auth/getOnly.d.ts +20 -0
- package/dist/server/auth/getOnly.js +27 -0
- package/dist/server/auth/index.d.ts +12 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/jwt.d.ts +18 -0
- package/dist/server/auth/jwt.js +56 -0
- package/dist/server/auth/loginThrottle.d.ts +118 -0
- package/dist/server/auth/loginThrottle.js +204 -0
- package/dist/server/auth/middleware.d.ts +14 -0
- package/dist/server/auth/middleware.js +47 -0
- package/dist/server/auth/password.d.ts +14 -0
- package/dist/server/auth/password.js +65 -0
- package/dist/server/auth/sessionStore.d.ts +19 -0
- package/dist/server/auth/sessionStore.js +221 -0
- package/dist/server/auth/totp.d.ts +34 -0
- package/dist/server/auth/totp.js +81 -0
- package/dist/server/auth/types.d.ts +133 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/vault.d.ts +27 -0
- package/dist/server/auth/vault.js +57 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/context.js +1 -0
- package/dist/server/ctgr.d.ts +32 -0
- package/dist/server/ctgr.js +69 -0
- package/dist/server/db/kysely.d.ts +14 -0
- package/dist/server/db/kysely.js +16 -0
- package/dist/server/db/plumbingTypes.d.ts +116 -0
- package/dist/server/db/plumbingTypes.js +12 -0
- package/dist/server/dropzone/duplicates.d.ts +82 -0
- package/dist/server/dropzone/duplicates.js +90 -0
- package/dist/server/dropzone/index.d.ts +13 -0
- package/dist/server/dropzone/index.js +13 -0
- package/dist/server/dropzone/scan.d.ts +166 -0
- package/dist/server/dropzone/scan.js +272 -0
- package/dist/server/errors.d.ts +7 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/guard/argon2Binding.d.ts +97 -0
- package/dist/server/guard/argon2Binding.js +138 -0
- package/dist/server/guard/index.d.ts +4 -0
- package/dist/server/guard/index.js +6 -0
- package/dist/server/guard/kdfWorker.d.ts +101 -0
- package/dist/server/guard/kdfWorker.js +301 -0
- package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
- package/dist/server/guard/kdfWorkerEntry.js +15 -0
- package/dist/server/guard/passwordVerifier.d.ts +65 -0
- package/dist/server/guard/passwordVerifier.js +122 -0
- package/dist/server/guard/revocationStore.d.ts +132 -0
- package/dist/server/guard/revocationStore.js +211 -0
- package/dist/server/guard/sessionSigner.d.ts +132 -0
- package/dist/server/guard/sessionSigner.js +303 -0
- package/dist/server/guard/verifier.d.ts +21 -0
- package/dist/server/guard/verifier.js +1 -0
- package/dist/server/incidents/channels.d.ts +161 -0
- package/dist/server/incidents/channels.js +357 -0
- package/dist/server/incidents/checks.d.ts +61 -0
- package/dist/server/incidents/checks.js +55 -0
- package/dist/server/incidents/index.d.ts +51 -0
- package/dist/server/incidents/index.js +51 -0
- package/dist/server/incidents/migrations.d.ts +20 -0
- package/dist/server/incidents/migrations.js +58 -0
- package/dist/server/incidents/notifier.d.ts +79 -0
- package/dist/server/incidents/notifier.js +141 -0
- package/dist/server/incidents/reporter.d.ts +60 -0
- package/dist/server/incidents/reporter.js +49 -0
- package/dist/server/incidents/routes.d.ts +91 -0
- package/dist/server/incidents/routes.js +202 -0
- package/dist/server/incidents/store.d.ts +75 -0
- package/dist/server/incidents/store.js +176 -0
- package/dist/server/incidents/types.d.ts +107 -0
- package/dist/server/incidents/types.js +32 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +53 -0
- package/dist/server/jobs/fireQueue.d.ts +12 -0
- package/dist/server/jobs/fireQueue.js +37 -0
- package/dist/server/jobs/index.d.ts +6 -0
- package/dist/server/jobs/index.js +10 -0
- package/dist/server/jobs/logger.d.ts +10 -0
- package/dist/server/jobs/logger.js +14 -0
- package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
- package/dist/server/jobs/plainjobAdapter.js +48 -0
- package/dist/server/jobs/polling.d.ts +17 -0
- package/dist/server/jobs/polling.js +79 -0
- package/dist/server/jobs/runner.d.ts +24 -0
- package/dist/server/jobs/runner.js +186 -0
- package/dist/server/jobs/timeout.d.ts +28 -0
- package/dist/server/jobs/timeout.js +47 -0
- package/dist/server/jobs/types.d.ts +167 -0
- package/dist/server/jobs/types.js +12 -0
- package/dist/server/master-lock/guard.d.ts +53 -0
- package/dist/server/master-lock/guard.js +205 -0
- package/dist/server/master-lock/index.d.ts +21 -0
- package/dist/server/master-lock/index.js +21 -0
- package/dist/server/master-lock/lockPage.d.ts +34 -0
- package/dist/server/master-lock/lockPage.js +281 -0
- package/dist/server/master-lock/masterLock.d.ts +294 -0
- package/dist/server/master-lock/masterLock.js +523 -0
- package/dist/server/master-lock/principals.d.ts +76 -0
- package/dist/server/master-lock/principals.js +157 -0
- package/dist/server/master-lock/seed.d.ts +47 -0
- package/dist/server/master-lock/seed.js +93 -0
- package/dist/server/master-lock/store.d.ts +19 -0
- package/dist/server/master-lock/store.js +20 -0
- package/dist/server/media-bun/image.d.ts +7 -0
- package/dist/server/media-bun/image.js +96 -0
- package/dist/server/media-bun/index.d.ts +4 -0
- package/dist/server/media-bun/index.js +10 -0
- package/dist/server/media-bun/streaming.d.ts +86 -0
- package/dist/server/media-bun/streaming.js +237 -0
- package/dist/server/media-bun/video.d.ts +35 -0
- package/dist/server/media-bun/video.js +181 -0
- package/dist/server/metrics/metricsBuffer.d.ts +39 -0
- package/dist/server/metrics/metricsBuffer.js +36 -0
- package/dist/server/metrics/normalizeRoute.d.ts +5 -0
- package/dist/server/metrics/normalizeRoute.js +58 -0
- package/dist/server/middleware/correlationId.d.ts +9 -0
- package/dist/server/middleware/correlationId.js +20 -0
- package/dist/server/middleware/corsAllowlist.d.ts +38 -0
- package/dist/server/middleware/corsAllowlist.js +37 -0
- package/dist/server/middleware/errorEnvelope.d.ts +17 -0
- package/dist/server/middleware/errorEnvelope.js +32 -0
- package/dist/server/middleware/index.d.ts +9 -0
- package/dist/server/middleware/index.js +10 -0
- package/dist/server/middleware/localOrigin.d.ts +71 -0
- package/dist/server/middleware/localOrigin.js +119 -0
- package/dist/server/middleware/rateLimit.d.ts +56 -0
- package/dist/server/middleware/rateLimit.js +86 -0
- package/dist/server/middleware/requestLogger.d.ts +29 -0
- package/dist/server/middleware/requestLogger.js +47 -0
- package/dist/server/middleware/securityHeaders.d.ts +29 -0
- package/dist/server/middleware/securityHeaders.js +44 -0
- package/dist/server/middleware/zValidator.d.ts +29 -0
- package/dist/server/middleware/zValidator.js +57 -0
- package/dist/server/migration/runner.d.ts +42 -0
- package/dist/server/migration/runner.js +70 -0
- package/dist/server/migration/types.d.ts +16 -0
- package/dist/server/migration/types.js +1 -0
- package/dist/server/net/outboundGuard.d.ts +30 -0
- package/dist/server/net/outboundGuard.js +199 -0
- package/dist/server/notifications/hub.d.ts +67 -0
- package/dist/server/notifications/hub.js +187 -0
- package/dist/server/notifications/index.d.ts +47 -0
- package/dist/server/notifications/index.js +46 -0
- package/dist/server/notifications/migrations.d.ts +13 -0
- package/dist/server/notifications/migrations.js +20 -0
- package/dist/server/notifications/routes.d.ts +58 -0
- package/dist/server/notifications/routes.js +113 -0
- package/dist/server/notifications/service.d.ts +58 -0
- package/dist/server/notifications/service.js +41 -0
- package/dist/server/paths/appDataHome.d.ts +19 -0
- package/dist/server/paths/appDataHome.js +68 -0
- package/dist/server/private-media/crypto.d.ts +37 -0
- package/dist/server/private-media/crypto.js +111 -0
- package/dist/server/private-media/index-store.d.ts +31 -0
- package/dist/server/private-media/index-store.js +36 -0
- package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
- package/dist/server/private-media/mountPrivateRoutes.js +219 -0
- package/dist/server/private-media/pipeline.d.ts +37 -0
- package/dist/server/private-media/pipeline.js +97 -0
- package/dist/server/private-media/unlock.d.ts +52 -0
- package/dist/server/private-media/unlock.js +55 -0
- package/dist/server/resume/index.d.ts +2 -0
- package/dist/server/resume/index.js +2 -0
- package/dist/server/resume/routes.d.ts +41 -0
- package/dist/server/resume/routes.js +50 -0
- package/dist/server/resume/store.d.ts +37 -0
- package/dist/server/resume/store.js +38 -0
- package/dist/server/retention/archive.d.ts +78 -0
- package/dist/server/retention/archive.js +284 -0
- package/dist/server/retention/fleet.d.ts +50 -0
- package/dist/server/retention/fleet.js +66 -0
- package/dist/server/retention/index.d.ts +38 -0
- package/dist/server/retention/index.js +38 -0
- package/dist/server/retention/policy.d.ts +263 -0
- package/dist/server/retention/policy.js +264 -0
- package/dist/server/retention/store.d.ts +101 -0
- package/dist/server/retention/store.js +251 -0
- package/dist/server/retention/sweeper.d.ts +69 -0
- package/dist/server/retention/sweeper.js +289 -0
- package/dist/server/retention/target.d.ts +143 -0
- package/dist/server/retention/target.js +438 -0
- package/dist/server/search/index.d.ts +2 -0
- package/dist/server/search/index.js +6 -0
- package/dist/server/search/pgSearchProvider.d.ts +91 -0
- package/dist/server/search/pgSearchProvider.js +330 -0
- package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
- package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
- package/dist/server/search/pgSearchSchema.d.ts +81 -0
- package/dist/server/search/pgSearchSchema.js +89 -0
- package/dist/server/sharing/index.d.ts +18 -0
- package/dist/server/sharing/index.js +16 -0
- package/dist/server/sharing/migrations.d.ts +6 -0
- package/dist/server/sharing/migrations.js +79 -0
- package/dist/server/sharing/router.d.ts +111 -0
- package/dist/server/sharing/router.js +93 -0
- package/dist/server/sharing/sql.d.ts +67 -0
- package/dist/server/sharing/sql.js +88 -0
- package/dist/server/sharing/store.d.ts +173 -0
- package/dist/server/sharing/store.js +484 -0
- package/dist/server/sqlite/backup.d.ts +48 -0
- package/dist/server/sqlite/backup.js +97 -0
- package/dist/server/sqlite/browse.d.ts +42 -0
- package/dist/server/sqlite/browse.js +84 -0
- package/dist/server/sqlite/export.d.ts +32 -0
- package/dist/server/sqlite/export.js +42 -0
- package/dist/server/sqlite/fts5.d.ts +106 -0
- package/dist/server/sqlite/fts5.js +154 -0
- package/dist/server/sqlite/health.d.ts +44 -0
- package/dist/server/sqlite/health.js +52 -0
- package/dist/server/sqlite/import.d.ts +36 -0
- package/dist/server/sqlite/import.js +108 -0
- package/dist/server/sqlite/index.d.ts +9 -0
- package/dist/server/sqlite/index.js +15 -0
- package/dist/server/sqlite/readonlySql.d.ts +15 -0
- package/dist/server/sqlite/readonlySql.js +65 -0
- package/dist/server/sqlite/stats.d.ts +22 -0
- package/dist/server/sqlite/stats.js +32 -0
- package/dist/server/sqlite/viewer.d.ts +17 -0
- package/dist/server/sqlite/viewer.js +56 -0
- package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
- package/dist/server/storage/binaryServerProcessing.js +81 -0
- package/dist/server/storage/binaryServerStore.d.ts +3 -0
- package/dist/server/storage/binaryServerStore.js +95 -0
- package/dist/server/storage/diskStore.d.ts +2 -0
- package/dist/server/storage/diskStore.js +88 -0
- package/dist/server/storage/files/adapter.d.ts +33 -0
- package/dist/server/storage/files/adapter.js +203 -0
- package/dist/server/storage/files/catalogue.d.ts +72 -0
- package/dist/server/storage/files/catalogue.js +1033 -0
- package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
- package/dist/server/storage/files/chunkedUpload.js +70 -0
- package/dist/server/storage/files/fileRoutes.d.ts +51 -0
- package/dist/server/storage/files/fileRoutes.js +405 -0
- package/dist/server/storage/files/index.d.ts +9 -0
- package/dist/server/storage/files/index.js +11 -0
- package/dist/server/storage/files/migrations.d.ts +2 -0
- package/dist/server/storage/files/migrations.js +163 -0
- package/dist/server/storage/files/mimeByExt.d.ts +1 -0
- package/dist/server/storage/files/mimeByExt.js +13 -0
- package/dist/server/storage/files/stack.d.ts +136 -0
- package/dist/server/storage/files/stack.js +132 -0
- package/dist/server/storage/files/storageOps.d.ts +54 -0
- package/dist/server/storage/files/storageOps.js +110 -0
- package/dist/server/storage/files/types.d.ts +261 -0
- package/dist/server/storage/files/types.js +22 -0
- package/dist/server/storage/imageThumbnail.d.ts +3 -0
- package/dist/server/storage/imageThumbnail.js +50 -0
- package/dist/server/storage/index.d.ts +15 -0
- package/dist/server/storage/index.js +27 -0
- package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
- package/dist/server/storage/mountMediaRoutes.js +141 -0
- package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
- package/dist/server/storage/objectStoreMediaStore.js +57 -0
- package/dist/server/storage/s3ObjectStore.d.ts +63 -0
- package/dist/server/storage/s3ObjectStore.js +187 -0
- package/dist/server/storage/sources.d.ts +47 -0
- package/dist/server/storage/sources.js +148 -0
- package/dist/server/storage/streamRange.d.ts +19 -0
- package/dist/server/storage/streamRange.js +69 -0
- package/dist/server/storage/streamToken.d.ts +16 -0
- package/dist/server/storage/streamToken.js +54 -0
- package/dist/server/storage/transcodeJobs.d.ts +65 -0
- package/dist/server/storage/transcodeJobs.js +104 -0
- package/dist/server/storage/types.d.ts +193 -0
- package/dist/server/storage/types.js +41 -0
- package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
- package/dist/server/storage/videoDerivativeKeys.js +149 -0
- package/dist/server/storage/videoProcessor.d.ts +30 -0
- package/dist/server/storage/videoProcessor.js +148 -0
- package/dist/server/sync/alarm.d.ts +34 -0
- package/dist/server/sync/alarm.js +91 -0
- package/dist/server/sync/commands.d.ts +87 -0
- package/dist/server/sync/commands.js +241 -0
- package/dist/server/sync/engine.d.ts +62 -0
- package/dist/server/sync/engine.js +184 -0
- package/dist/server/sync/http.d.ts +106 -0
- package/dist/server/sync/http.js +243 -0
- package/dist/server/sync/index.d.ts +41 -0
- package/dist/server/sync/index.js +41 -0
- package/dist/server/sync/opLog.d.ts +61 -0
- package/dist/server/sync/opLog.js +96 -0
- package/dist/server/sync/planner.d.ts +31 -0
- package/dist/server/sync/planner.js +30 -0
- package/dist/server/sync/status.d.ts +63 -0
- package/dist/server/sync/status.js +49 -0
- package/dist/server/sync/timer.d.ts +52 -0
- package/dist/server/sync/timer.js +170 -0
- package/dist/server/sync/tokens.d.ts +25 -0
- package/dist/server/sync/tokens.js +51 -0
- package/dist/server/sync/types.d.ts +101 -0
- package/dist/server/sync/types.js +29 -0
- package/dist/server/ws/eventBus.d.ts +14 -0
- package/dist/server/ws/eventBus.js +42 -0
- package/dist/server/ws/hub.d.ts +20 -0
- package/dist/server/ws/hub.js +154 -0
- package/dist/server/ws/index.d.ts +5 -0
- package/dist/server/ws/index.js +7 -0
- package/dist/server/ws/presence.d.ts +11 -0
- package/dist/server/ws/presence.js +20 -0
- package/dist/server/ws/room.d.ts +17 -0
- package/dist/server/ws/room.js +23 -0
- package/dist/server/ws/types.d.ts +122 -0
- package/dist/server/ws/types.js +1 -0
- package/package.json +234 -0
- package/src/declaredDepsAreImported.spec.ts +87 -0
- package/src/leafSubpathsImportNothing.spec.ts +358 -0
- package/src/lockfileIsTracked.spec.ts +50 -0
- package/src/namedSubpathsResolve.spec.ts +122 -0
- package/src/noPathDeps.spec.ts +24 -0
- package/src/publishShape.spec.ts +89 -0
- package/src/server/activity/index.ts +59 -0
- package/src/server/activity/migrations.ts +67 -0
- package/src/server/activity/shareSource.ts +57 -0
- package/src/server/activity/store.spec.ts +255 -0
- package/src/server/activity/store.ts +368 -0
- package/src/server/app.spec.ts +243 -0
- package/src/server/app.ts +89 -0
- package/src/server/audit/index.ts +2 -0
- package/src/server/audit/migrations.ts +88 -0
- package/src/server/audit/sqliteSink.test.ts +200 -0
- package/src/server/audit/sqliteSink.ts +162 -0
- package/src/server/auth/cookie.spec.ts +46 -0
- package/src/server/auth/cookie.ts +42 -0
- package/src/server/auth/csrf.spec.ts +81 -0
- package/src/server/auth/csrf.ts +83 -0
- package/src/server/auth/fileTokenSigner.ts +40 -0
- package/src/server/auth/getOnly.spec.ts +53 -0
- package/src/server/auth/getOnly.ts +32 -0
- package/src/server/auth/index.ts +51 -0
- package/src/server/auth/jwt.spec.ts +106 -0
- package/src/server/auth/jwt.ts +75 -0
- package/src/server/auth/loginThrottle.spec.ts +213 -0
- package/src/server/auth/loginThrottle.ts +261 -0
- package/src/server/auth/middleware.spec.ts +148 -0
- package/src/server/auth/middleware.ts +56 -0
- package/src/server/auth/password.spec.ts +48 -0
- package/src/server/auth/password.ts +71 -0
- package/src/server/auth/sessionStore.spec.ts +189 -0
- package/src/server/auth/sessionStore.ts +261 -0
- package/src/server/auth/totp.spec.ts +73 -0
- package/src/server/auth/totp.ts +92 -0
- package/src/server/auth/types.ts +146 -0
- package/src/server/auth/vault.spec.ts +51 -0
- package/src/server/auth/vault.ts +71 -0
- package/src/server/context.ts +37 -0
- package/src/server/ctgr.spec.ts +138 -0
- package/src/server/ctgr.ts +108 -0
- package/src/server/db/kysely.spec.ts +62 -0
- package/src/server/db/kysely.ts +21 -0
- package/src/server/db/plumbingTypes.ts +126 -0
- package/src/server/dropzone/duplicates.ts +121 -0
- package/src/server/dropzone/index.ts +30 -0
- package/src/server/dropzone/scan.spec.ts +255 -0
- package/src/server/dropzone/scan.ts +342 -0
- package/src/server/errors.spec.ts +76 -0
- package/src/server/errors.ts +22 -0
- package/src/server/guard/argon2Binding.spec.ts +117 -0
- package/src/server/guard/argon2Binding.ts +195 -0
- package/src/server/guard/argon2Kat.spec.ts +250 -0
- package/src/server/guard/guard.spec.ts +390 -0
- package/src/server/guard/index.ts +6 -0
- package/src/server/guard/kdfWorker.spec.ts +283 -0
- package/src/server/guard/kdfWorker.ts +367 -0
- package/src/server/guard/kdfWorkerEntry.ts +16 -0
- package/src/server/guard/passwordVerifier.spec.ts +71 -0
- package/src/server/guard/passwordVerifier.ts +175 -0
- package/src/server/guard/revocationStore.spec.ts +135 -0
- package/src/server/guard/revocationStore.ts +291 -0
- package/src/server/guard/sessionSigner.ts +425 -0
- package/src/server/guard/verifier.ts +27 -0
- package/src/server/incidents/channels.ts +487 -0
- package/src/server/incidents/checks.ts +120 -0
- package/src/server/incidents/incidents.spec.ts +463 -0
- package/src/server/incidents/index.ts +96 -0
- package/src/server/incidents/migrations.ts +66 -0
- package/src/server/incidents/notifier.ts +215 -0
- package/src/server/incidents/reporter.ts +110 -0
- package/src/server/incidents/routes.ts +314 -0
- package/src/server/incidents/store.ts +329 -0
- package/src/server/incidents/types.ts +116 -0
- package/src/server/index.ts +248 -0
- package/src/server/jobs/fireQueue.spec.ts +85 -0
- package/src/server/jobs/fireQueue.ts +37 -0
- package/src/server/jobs/index.ts +26 -0
- package/src/server/jobs/logger.ts +16 -0
- package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
- package/src/server/jobs/plainjobAdapter.ts +54 -0
- package/src/server/jobs/polling.spec.ts +219 -0
- package/src/server/jobs/polling.ts +81 -0
- package/src/server/jobs/runner.spec.ts +213 -0
- package/src/server/jobs/runner.ts +213 -0
- package/src/server/jobs/timeout.spec.ts +35 -0
- package/src/server/jobs/timeout.ts +53 -0
- package/src/server/jobs/types.ts +188 -0
- package/src/server/master-lock/guard.spec.ts +347 -0
- package/src/server/master-lock/guard.ts +290 -0
- package/src/server/master-lock/index.ts +44 -0
- package/src/server/master-lock/lockPage.spec.ts +88 -0
- package/src/server/master-lock/lockPage.ts +313 -0
- package/src/server/master-lock/masterLock.spec.ts +338 -0
- package/src/server/master-lock/masterLock.ts +627 -0
- package/src/server/master-lock/principals.spec.ts +378 -0
- package/src/server/master-lock/principals.ts +175 -0
- package/src/server/master-lock/seed.spec.ts +69 -0
- package/src/server/master-lock/seed.ts +112 -0
- package/src/server/master-lock/store.ts +35 -0
- package/src/server/media-bun/image.spec.ts +115 -0
- package/src/server/media-bun/image.ts +134 -0
- package/src/server/media-bun/index.ts +52 -0
- package/src/server/media-bun/streaming.spec.ts +134 -0
- package/src/server/media-bun/streaming.ts +347 -0
- package/src/server/media-bun/video.spec.ts +115 -0
- package/src/server/media-bun/video.ts +232 -0
- package/src/server/metrics/metricsBuffer.ts +92 -0
- package/src/server/metrics/normalizeRoute.spec.ts +51 -0
- package/src/server/metrics/normalizeRoute.ts +63 -0
- package/src/server/middleware/correlationId.ts +24 -0
- package/src/server/middleware/corsAllowlist.spec.ts +118 -0
- package/src/server/middleware/corsAllowlist.ts +69 -0
- package/src/server/middleware/errorEnvelope.ts +38 -0
- package/src/server/middleware/index.ts +19 -0
- package/src/server/middleware/localOrigin.spec.ts +168 -0
- package/src/server/middleware/localOrigin.ts +139 -0
- package/src/server/middleware/rateLimit.spec.ts +72 -0
- package/src/server/middleware/rateLimit.ts +125 -0
- package/src/server/middleware/requestLogger.ts +95 -0
- package/src/server/middleware/securityHeaders.ts +78 -0
- package/src/server/middleware/zValidator.spec.ts +251 -0
- package/src/server/middleware/zValidator.ts +114 -0
- package/src/server/migration/runner.spec.ts +88 -0
- package/src/server/migration/runner.ts +87 -0
- package/src/server/migration/types.ts +17 -0
- package/src/server/net/outboundGuard.spec.ts +90 -0
- package/src/server/net/outboundGuard.ts +205 -0
- package/src/server/notifications/hub.ts +245 -0
- package/src/server/notifications/index.ts +77 -0
- package/src/server/notifications/migrations.ts +39 -0
- package/src/server/notifications/routes.ts +177 -0
- package/src/server/notifications/service.spec.ts +305 -0
- package/src/server/notifications/service.ts +112 -0
- package/src/server/paths/appDataHome.spec.ts +87 -0
- package/src/server/paths/appDataHome.ts +84 -0
- package/src/server/private-media/blobRoute.spec.ts +165 -0
- package/src/server/private-media/crypto.spec.ts +81 -0
- package/src/server/private-media/crypto.ts +155 -0
- package/src/server/private-media/gatePolicy.spec.ts +98 -0
- package/src/server/private-media/index-store.ts +83 -0
- package/src/server/private-media/mountPrivateRoutes.ts +265 -0
- package/src/server/private-media/pipeline.spec.ts +111 -0
- package/src/server/private-media/pipeline.ts +170 -0
- package/src/server/private-media/routes.spec.ts +265 -0
- package/src/server/private-media/unlock.ts +112 -0
- package/src/server/resume/index.ts +8 -0
- package/src/server/resume/resume.spec.ts +118 -0
- package/src/server/resume/routes.ts +92 -0
- package/src/server/resume/store.ts +82 -0
- package/src/server/retention/archive.spec.ts +181 -0
- package/src/server/retention/archive.ts +353 -0
- package/src/server/retention/fleet.ts +104 -0
- package/src/server/retention/index.ts +101 -0
- package/src/server/retention/policy.spec.ts +132 -0
- package/src/server/retention/policy.ts +392 -0
- package/src/server/retention/store.spec.ts +236 -0
- package/src/server/retention/store.ts +338 -0
- package/src/server/retention/sweeper.spec.ts +321 -0
- package/src/server/retention/sweeper.ts +367 -0
- package/src/server/retention/target.spec.ts +592 -0
- package/src/server/retention/target.ts +613 -0
- package/src/server/search/index.ts +12 -0
- package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
- package/src/server/search/pgSearchProvider.ts +421 -0
- package/src/server/search/pgSearchSchema.ts +147 -0
- package/src/server/sharing/index.ts +65 -0
- package/src/server/sharing/migrations.ts +128 -0
- package/src/server/sharing/router.spec.ts +167 -0
- package/src/server/sharing/router.ts +194 -0
- package/src/server/sharing/sql.ts +109 -0
- package/src/server/sharing/store.spec.ts +408 -0
- package/src/server/sharing/store.ts +737 -0
- package/src/server/sqlite/backup.spec.ts +114 -0
- package/src/server/sqlite/backup.ts +154 -0
- package/src/server/sqlite/browse.spec.ts +70 -0
- package/src/server/sqlite/browse.ts +120 -0
- package/src/server/sqlite/export.spec.ts +43 -0
- package/src/server/sqlite/export.ts +74 -0
- package/src/server/sqlite/fts5.spec.ts +175 -0
- package/src/server/sqlite/fts5.ts +255 -0
- package/src/server/sqlite/health.spec.ts +79 -0
- package/src/server/sqlite/health.ts +105 -0
- package/src/server/sqlite/import.spec.ts +75 -0
- package/src/server/sqlite/import.ts +141 -0
- package/src/server/sqlite/index.ts +64 -0
- package/src/server/sqlite/readonlySql.spec.ts +58 -0
- package/src/server/sqlite/readonlySql.ts +67 -0
- package/src/server/sqlite/stats.spec.ts +21 -0
- package/src/server/sqlite/stats.ts +61 -0
- package/src/server/sqlite/viewer.spec.ts +127 -0
- package/src/server/sqlite/viewer.ts +72 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
- package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
- package/src/server/storage/binaryServerProcessing.ts +127 -0
- package/src/server/storage/binaryServerStore.ts +102 -0
- package/src/server/storage/chunked.integration.spec.ts +185 -0
- package/src/server/storage/diskStore.ts +83 -0
- package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
- package/src/server/storage/files/adapter.photo.spec.ts +128 -0
- package/src/server/storage/files/adapter.spec.ts +121 -0
- package/src/server/storage/files/adapter.ts +255 -0
- package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
- package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
- package/src/server/storage/files/catalogue.private.spec.ts +106 -0
- package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
- package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
- package/src/server/storage/files/catalogue.spec.ts +133 -0
- package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
- package/src/server/storage/files/catalogue.ts +1326 -0
- package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
- package/src/server/storage/files/chunkedUpload.ts +146 -0
- package/src/server/storage/files/collections.spec.ts +294 -0
- package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
- package/src/server/storage/files/fileRoutes.spec.ts +327 -0
- package/src/server/storage/files/fileRoutes.ts +462 -0
- package/src/server/storage/files/index.ts +66 -0
- package/src/server/storage/files/migrations.ts +187 -0
- package/src/server/storage/files/mimeByExt.ts +14 -0
- package/src/server/storage/files/stack.spec.ts +258 -0
- package/src/server/storage/files/stack.ts +284 -0
- package/src/server/storage/files/storageOps.spec.ts +126 -0
- package/src/server/storage/files/storageOps.ts +213 -0
- package/src/server/storage/files/types.ts +285 -0
- package/src/server/storage/imageThumbnail.spec.ts +81 -0
- package/src/server/storage/imageThumbnail.ts +60 -0
- package/src/server/storage/index.ts +122 -0
- package/src/server/storage/mountMediaRoutes.ts +231 -0
- package/src/server/storage/objectStore.spec.ts +272 -0
- package/src/server/storage/objectStoreMediaStore.ts +63 -0
- package/src/server/storage/s3ObjectStore.ts +239 -0
- package/src/server/storage/sources.ts +245 -0
- package/src/server/storage/storage.spec.ts +219 -0
- package/src/server/storage/streamRange.ts +92 -0
- package/src/server/storage/streamToken.ts +76 -0
- package/src/server/storage/transcode.integration.spec.ts +134 -0
- package/src/server/storage/transcodeJobs.spec.ts +152 -0
- package/src/server/storage/transcodeJobs.ts +171 -0
- package/src/server/storage/types.ts +230 -0
- package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
- package/src/server/storage/videoDerivativeKeys.ts +172 -0
- package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
- package/src/server/storage/videoProcessor.ts +229 -0
- package/src/server/sync/alarm.spec.ts +149 -0
- package/src/server/sync/alarm.ts +137 -0
- package/src/server/sync/commands.spec.ts +145 -0
- package/src/server/sync/commands.ts +361 -0
- package/src/server/sync/engine.spec.ts +496 -0
- package/src/server/sync/engine.ts +255 -0
- package/src/server/sync/http.ts +316 -0
- package/src/server/sync/httpRemote.spec.ts +158 -0
- package/src/server/sync/index.ts +90 -0
- package/src/server/sync/opLog.spec.ts +110 -0
- package/src/server/sync/opLog.ts +189 -0
- package/src/server/sync/planner.spec.ts +53 -0
- package/src/server/sync/planner.ts +62 -0
- package/src/server/sync/status.spec.ts +92 -0
- package/src/server/sync/status.ts +108 -0
- package/src/server/sync/timer.spec.ts +150 -0
- package/src/server/sync/timer.ts +207 -0
- package/src/server/sync/tokens.spec.ts +38 -0
- package/src/server/sync/tokens.ts +94 -0
- package/src/server/sync/types.ts +108 -0
- package/src/server/ws/eventBus.spec.ts +67 -0
- package/src/server/ws/eventBus.ts +46 -0
- package/src/server/ws/hub.integration.spec.ts +93 -0
- package/src/server/ws/hub.spec.ts +282 -0
- package/src/server/ws/hub.ts +168 -0
- package/src/server/ws/index.ts +20 -0
- package/src/server/ws/presence.spec.ts +27 -0
- package/src/server/ws/presence.ts +26 -0
- package/src/server/ws/room.spec.ts +36 -0
- package/src/server/ws/room.ts +29 -0
- package/src/server/ws/types.ts +134 -0
- package/src/skippedTestsAreAnswered.spec.ts +215 -0
- package/src/testTmpRoot.spec.ts +102 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { ApiError } from '../errors';
|
|
4
|
+
import { createJobRunner } from './runner';
|
|
5
|
+
import { JobTimeoutError } from './timeout';
|
|
6
|
+
|
|
7
|
+
const JOB_RUNS_DDL = `CREATE TABLE job_runs (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
job_name TEXT NOT NULL,
|
|
10
|
+
started_at TEXT NOT NULL,
|
|
11
|
+
ended_at TEXT,
|
|
12
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
13
|
+
error TEXT
|
|
14
|
+
)`;
|
|
15
|
+
|
|
16
|
+
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
17
|
+
const quietLogger = { error() {}, warn() {}, info() {}, debug() {} };
|
|
18
|
+
const runCount = (db: Database, name: string) =>
|
|
19
|
+
(db.query('SELECT COUNT(*) AS n FROM job_runs WHERE job_name = ?').get(name) as { n: number }).n;
|
|
20
|
+
|
|
21
|
+
describe('createJobRunner', () => {
|
|
22
|
+
let db: Database;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
db = new Database(':memory:');
|
|
25
|
+
db.run(JOB_RUNS_DDL);
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => db.close());
|
|
28
|
+
|
|
29
|
+
test('runOnce executes the job immediately and audits an ok run (acceptance)', async () => {
|
|
30
|
+
let ran = false;
|
|
31
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
32
|
+
runner.register({
|
|
33
|
+
name: 'once',
|
|
34
|
+
async run() {
|
|
35
|
+
ran = true;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
runner.start();
|
|
39
|
+
await runner.runOnce('once');
|
|
40
|
+
expect(ran).toBe(true);
|
|
41
|
+
|
|
42
|
+
await runner.stop();
|
|
43
|
+
const last = runner.getLastRun('once');
|
|
44
|
+
expect(last?.status).toBe('ok');
|
|
45
|
+
expect(last?.endedAt).toBeTruthy();
|
|
46
|
+
expect(last?.error).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('getLastRun returns null before any run, then the latest run', async () => {
|
|
50
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
51
|
+
runner.register({ name: 'j', async run() {} });
|
|
52
|
+
expect(runner.getLastRun('j')).toBeNull();
|
|
53
|
+
await runner.runOnce('j');
|
|
54
|
+
await runner.runOnce('j');
|
|
55
|
+
expect(runCount(db, 'j')).toBe(2);
|
|
56
|
+
expect(runner.getLastRun('j')?.status).toBe('ok');
|
|
57
|
+
await runner.stop();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('a throwing job is audited as error and runOnce rejects', async () => {
|
|
61
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
62
|
+
runner.register({
|
|
63
|
+
name: 'boom',
|
|
64
|
+
async run() {
|
|
65
|
+
throw new Error('kaboom');
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
await expect(runner.runOnce('boom')).rejects.toThrow('kaboom');
|
|
69
|
+
const last = runner.getLastRun('boom');
|
|
70
|
+
expect(last?.status).toBe('error');
|
|
71
|
+
expect(last?.error).toContain('kaboom');
|
|
72
|
+
await runner.stop();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('runOnce on an unknown job throws ApiError(404) (integration)', async () => {
|
|
76
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
77
|
+
try {
|
|
78
|
+
await runner.runOnce('nope');
|
|
79
|
+
throw new Error('should have thrown');
|
|
80
|
+
} catch (err) {
|
|
81
|
+
expect(err).toBeInstanceOf(ApiError);
|
|
82
|
+
expect((err as ApiError).status).toBe(404);
|
|
83
|
+
}
|
|
84
|
+
await runner.stop();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('a job that exceeds its timeout is aborted and audited as a timeout error', async () => {
|
|
88
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
89
|
+
let sawAbort = false;
|
|
90
|
+
runner.register({
|
|
91
|
+
name: 'slow',
|
|
92
|
+
timeout: 30,
|
|
93
|
+
run({ signal }) {
|
|
94
|
+
// Resolve only when aborted, so the handler promise never leaks.
|
|
95
|
+
return new Promise<void>((resolve) => {
|
|
96
|
+
signal.addEventListener(
|
|
97
|
+
'abort',
|
|
98
|
+
() => {
|
|
99
|
+
sawAbort = true;
|
|
100
|
+
resolve();
|
|
101
|
+
},
|
|
102
|
+
{ once: true },
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
await expect(runner.runOnce('slow')).rejects.toBeInstanceOf(JobTimeoutError);
|
|
108
|
+
expect(sawAbort).toBe(true);
|
|
109
|
+
const last = runner.getLastRun('slow');
|
|
110
|
+
expect(last?.status).toBe('error');
|
|
111
|
+
expect(last?.error).toContain('timed out');
|
|
112
|
+
await runner.stop();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('a cron job fires durably via its worker and audits ok runs (integration)', async () => {
|
|
116
|
+
const runner = createJobRunner(db, { logger: quietLogger, pollIntervalMs: 30 });
|
|
117
|
+
runner.register({ name: 'tick', cron: '* * * * * *', async run() {} });
|
|
118
|
+
runner.start();
|
|
119
|
+
await sleep(160);
|
|
120
|
+
await runner.stop();
|
|
121
|
+
// The first schedule fires immediately (next_run_at = 0), then on the cron.
|
|
122
|
+
expect(runCount(db, 'tick')).toBeGreaterThanOrEqual(1);
|
|
123
|
+
expect(runner.getLastRun('tick')?.status).toBe('ok');
|
|
124
|
+
// plainjob created its own tables — no collision with the job_runs audit table.
|
|
125
|
+
const tables = db
|
|
126
|
+
.query("SELECT name FROM sqlite_master WHERE type='table'")
|
|
127
|
+
.all()
|
|
128
|
+
.map((r) => (r as { name: string }).name);
|
|
129
|
+
expect(tables).toContain('plainjob_jobs');
|
|
130
|
+
expect(tables).toContain('job_runs');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('stop() does NOT close the shared database', async () => {
|
|
134
|
+
const runner = createJobRunner(db, { logger: quietLogger, pollIntervalMs: 30 });
|
|
135
|
+
runner.register({ name: 'tick', cron: '* * * * * *', async run() {} });
|
|
136
|
+
runner.start();
|
|
137
|
+
await sleep(60);
|
|
138
|
+
await runner.stop();
|
|
139
|
+
// Would throw if queue.close() had closed the underlying db.
|
|
140
|
+
expect(() => db.query('SELECT 1 AS x').get()).not.toThrow();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('registering a duplicate name throws', () => {
|
|
144
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
145
|
+
runner.register({ name: 'dup', async run() {} });
|
|
146
|
+
expect(() => runner.register({ name: 'dup', async run() {} })).toThrow('already registered');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('register/start after stop throws', async () => {
|
|
150
|
+
const runner = createJobRunner(db, { logger: quietLogger });
|
|
151
|
+
await runner.stop();
|
|
152
|
+
expect(() => runner.register({ name: 'late', async run() {} })).toThrow('stopped');
|
|
153
|
+
expect(() => runner.start()).toThrow('stopped');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('registering a cron job after start spins up its worker immediately', async () => {
|
|
157
|
+
const runner = createJobRunner(db, { logger: quietLogger, pollIntervalMs: 30 });
|
|
158
|
+
runner.start();
|
|
159
|
+
runner.register({ name: 'late-cron', cron: '* * * * * *', async run() {} });
|
|
160
|
+
await sleep(120);
|
|
161
|
+
await runner.stop();
|
|
162
|
+
expect(runner.getLastRun('late-cron')?.status).toBe('ok');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const seedRun = (name: string, startedAt: string) =>
|
|
166
|
+
db
|
|
167
|
+
.query("INSERT INTO job_runs (id, job_name, started_at, ended_at, status) VALUES (?, ?, ?, ?, 'ok')")
|
|
168
|
+
.run(crypto.randomUUID(), name, startedAt, startedAt);
|
|
169
|
+
|
|
170
|
+
test('retention prunes audit rows older than auditMaxAgeMs after a run', async () => {
|
|
171
|
+
const eightDaysAgo = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString();
|
|
172
|
+
seedRun('aged', eightDaysAgo);
|
|
173
|
+
seedRun('aged', eightDaysAgo);
|
|
174
|
+
// pruneIntervalMs 0 → prune after every run (no lazy gap to wait out in a test).
|
|
175
|
+
const runner = createJobRunner(db, { logger: quietLogger, retention: { pruneIntervalMs: 0 } });
|
|
176
|
+
runner.register({ name: 'aged', async run() {} });
|
|
177
|
+
await runner.runOnce('aged');
|
|
178
|
+
await runner.stop();
|
|
179
|
+
// Both 8-day-old rows are gone; the fresh run survives (default window is 7 days).
|
|
180
|
+
expect(runCount(db, 'aged')).toBe(1);
|
|
181
|
+
expect(runner.getLastRun('aged')?.status).toBe('ok');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('retention caps job_runs at auditMaxRowsPerJob newest rows, per job name', async () => {
|
|
185
|
+
const runner = createJobRunner(db, {
|
|
186
|
+
logger: quietLogger,
|
|
187
|
+
retention: { pruneIntervalMs: 0, auditMaxAgeMs: 0, auditMaxRowsPerJob: 3 },
|
|
188
|
+
});
|
|
189
|
+
runner.register({ name: 'chatty', async run() {} });
|
|
190
|
+
runner.register({ name: 'quiet', async run() {} });
|
|
191
|
+
await runner.runOnce('quiet');
|
|
192
|
+
for (let i = 0; i < 6; i += 1) await runner.runOnce('chatty');
|
|
193
|
+
await runner.stop();
|
|
194
|
+
// The cap applies per job: 'chatty' trimmed to its 3 newest, 'quiet' untouched.
|
|
195
|
+
expect(runCount(db, 'chatty')).toBe(3);
|
|
196
|
+
expect(runCount(db, 'quiet')).toBe(1);
|
|
197
|
+
expect(runner.getLastRun('chatty')?.status).toBe('ok');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('retention fields set to 0 disable pruning entirely', async () => {
|
|
201
|
+
const tenDaysAgo = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString();
|
|
202
|
+
seedRun('keeper', tenDaysAgo);
|
|
203
|
+
const runner = createJobRunner(db, {
|
|
204
|
+
logger: quietLogger,
|
|
205
|
+
retention: { pruneIntervalMs: 0, auditMaxAgeMs: 0, auditMaxRowsPerJob: 0 },
|
|
206
|
+
});
|
|
207
|
+
runner.register({ name: 'keeper', async run() {} });
|
|
208
|
+
for (let i = 0; i < 4; i += 1) await runner.runOnce('keeper');
|
|
209
|
+
await runner.stop();
|
|
210
|
+
// Seeded ancient row + all 4 fresh runs all still present.
|
|
211
|
+
expect(runCount(db, 'keeper')).toBe(5);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { defineQueue, defineWorker, type Queue, type Worker } from 'plainjob';
|
|
3
|
+
import { ApiError } from '../errors';
|
|
4
|
+
import { defaultJobLogger } from './logger';
|
|
5
|
+
import { createPlainjobConnection } from './plainjobAdapter';
|
|
6
|
+
import { withTimeout } from './timeout';
|
|
7
|
+
import {
|
|
8
|
+
type CreateJobRunnerOptions,
|
|
9
|
+
DEFAULT_JOB_RETENTION,
|
|
10
|
+
DEFAULT_JOB_TIMEOUT_MS,
|
|
11
|
+
type JobContext,
|
|
12
|
+
type JobDef,
|
|
13
|
+
type JobLogger,
|
|
14
|
+
type JobRunner,
|
|
15
|
+
type JobRunRecord,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create the durable cron job runner — a thin facade over **plainjob** (MIT,
|
|
20
|
+
* SQLite-backed, no Redis). It generalizes ca's scattered `setInterval` cron-ish
|
|
21
|
+
* pollers + backup-target-hour check into one restart-aware runner whose schedules
|
|
22
|
+
* live in the *same* SQLite file as the business data (no separate store).
|
|
23
|
+
*
|
|
24
|
+
* Audit trail: every run (cron-fired or {@link JobRunner.runOnce}) writes a
|
|
25
|
+
* `job_runs` row — `running` on start, then `ok`/`error` on finish — so the admin
|
|
26
|
+
* "last run" view and {@link JobRunner.getLastRun} reflect reality. plainjob keeps
|
|
27
|
+
* its OWN internal tables (`plainjob_jobs`, `plainjob_scheduled_jobs`); the
|
|
28
|
+
* `job_runs`/`job_state` plumbing tables (task 22) are this wrapper's audit view
|
|
29
|
+
* and never collide with them.
|
|
30
|
+
*
|
|
31
|
+
* History is bounded by default ({@link DEFAULT_JOB_RETENTION}): `job_runs` is
|
|
32
|
+
* pruned lazily (age window + per-job row cap, at most once per prune interval),
|
|
33
|
+
* and plainjob's maintenance timer removes its internal done/failed rows on the
|
|
34
|
+
* configured horizons — so a sub-minute cron can run for months without the DB
|
|
35
|
+
* growing without bound.
|
|
36
|
+
*
|
|
37
|
+
* The `job_runs` table must already exist (the app's migration runner builds it).
|
|
38
|
+
*/
|
|
39
|
+
export function createJobRunner(db: Database, opts: CreateJobRunnerOptions = {}): JobRunner {
|
|
40
|
+
const logger: JobLogger = opts.logger ?? defaultJobLogger;
|
|
41
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 1000;
|
|
42
|
+
const retention = { ...DEFAULT_JOB_RETENTION, ...opts.retention };
|
|
43
|
+
|
|
44
|
+
const defs = new Map<string, JobDef>();
|
|
45
|
+
const workers = new Map<string, Worker>();
|
|
46
|
+
let queue: Queue | null = null;
|
|
47
|
+
let started = false;
|
|
48
|
+
let stopped = false;
|
|
49
|
+
// Aborted on stop() so every in-flight handler's context signal fires.
|
|
50
|
+
const stopController = new AbortController();
|
|
51
|
+
|
|
52
|
+
const insertRun = db.prepare(
|
|
53
|
+
"INSERT INTO job_runs (id, job_name, started_at, status) VALUES (?, ?, ?, 'running')",
|
|
54
|
+
);
|
|
55
|
+
const finishRun = db.prepare(
|
|
56
|
+
'UPDATE job_runs SET ended_at = ?, status = ?, error = ? WHERE id = ?',
|
|
57
|
+
);
|
|
58
|
+
const lastRunQuery = db.query(
|
|
59
|
+
`SELECT started_at AS startedAt, ended_at AS endedAt, status, error
|
|
60
|
+
FROM job_runs WHERE job_name = ? ORDER BY started_at DESC, rowid DESC LIMIT 1`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
let lastPruneAt = 0;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Lazily bound the `job_runs` audit table: an age window (ISO-8601 strings
|
|
67
|
+
* compare lexicographically, so plain `<` is correct) plus a newest-N cap per
|
|
68
|
+
* job name. Runs at most once per `pruneIntervalMs`, after a run finishes, and
|
|
69
|
+
* never lets a prune failure disturb the run that triggered it.
|
|
70
|
+
*/
|
|
71
|
+
function maybePruneAudit(): void {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (now - lastPruneAt < retention.pruneIntervalMs) return;
|
|
74
|
+
lastPruneAt = now;
|
|
75
|
+
try {
|
|
76
|
+
if (retention.auditMaxAgeMs > 0) {
|
|
77
|
+
db.query('DELETE FROM job_runs WHERE started_at < ?').run(
|
|
78
|
+
new Date(now - retention.auditMaxAgeMs).toISOString(),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (retention.auditMaxRowsPerJob > 0) {
|
|
82
|
+
db.query(
|
|
83
|
+
`DELETE FROM job_runs WHERE id IN (
|
|
84
|
+
SELECT id FROM (
|
|
85
|
+
SELECT id, ROW_NUMBER() OVER (
|
|
86
|
+
PARTITION BY job_name ORDER BY started_at DESC, rowid DESC
|
|
87
|
+
) AS rn FROM job_runs
|
|
88
|
+
) WHERE rn > ?
|
|
89
|
+
)`,
|
|
90
|
+
).run(retention.auditMaxRowsPerJob);
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger.warn('[jobs] audit prune failed (non-fatal)', err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// plainjob's queue (and its maintenance timer) is created lazily — a runner with
|
|
98
|
+
// only manual (`runOnce`) jobs never touches plainjob at all. A retention field
|
|
99
|
+
// of 0 means "never remove"; plainjob has no explicit off switch, so it maps to
|
|
100
|
+
// an effectively-infinite horizon.
|
|
101
|
+
function ensureQueue(): Queue {
|
|
102
|
+
if (!queue) {
|
|
103
|
+
queue = defineQueue({
|
|
104
|
+
connection: createPlainjobConnection(db),
|
|
105
|
+
logger,
|
|
106
|
+
removeDoneJobsOlderThan:
|
|
107
|
+
retention.doneJobsMaxAgeMs > 0 ? retention.doneJobsMaxAgeMs : Number.MAX_SAFE_INTEGER,
|
|
108
|
+
removeFailedJobsOlderThan:
|
|
109
|
+
retention.failedJobsMaxAgeMs > 0 ? retention.failedJobsMaxAgeMs : Number.MAX_SAFE_INTEGER,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return queue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run one job: write the audit row, build a per-run abort signal (fires on stop
|
|
117
|
+
* OR timeout), run with the timeout, then record `ok`/`error`. Re-throws only
|
|
118
|
+
* when `rethrow` is set — the cron worker path swallows (the audit row is the
|
|
119
|
+
* record of truth, and plainjob would otherwise double-log + retain the failure),
|
|
120
|
+
* while `runOnce` surfaces the error to its admin caller.
|
|
121
|
+
*/
|
|
122
|
+
async function executeJob(def: JobDef, rethrow: boolean): Promise<void> {
|
|
123
|
+
const id = crypto.randomUUID();
|
|
124
|
+
insertRun.run(id, def.name, new Date().toISOString());
|
|
125
|
+
|
|
126
|
+
const runController = new AbortController();
|
|
127
|
+
const propagateStop = () => runController.abort(stopController.signal.reason);
|
|
128
|
+
if (stopController.signal.aborted) propagateStop();
|
|
129
|
+
else stopController.signal.addEventListener('abort', propagateStop, { once: true });
|
|
130
|
+
|
|
131
|
+
const context: JobContext = { db, signal: runController.signal, logger };
|
|
132
|
+
try {
|
|
133
|
+
await withTimeout(
|
|
134
|
+
Promise.resolve(def.run(context)),
|
|
135
|
+
def.timeout ?? DEFAULT_JOB_TIMEOUT_MS,
|
|
136
|
+
() => runController.abort(new Error(`job "${def.name}" timed out`)),
|
|
137
|
+
);
|
|
138
|
+
finishRun.run(new Date().toISOString(), 'ok', null, id);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
finishRun.run(new Date().toISOString(), 'error', String(err), id);
|
|
141
|
+
logger.error(`[jobs] ${def.name} failed`, err);
|
|
142
|
+
if (rethrow) throw err;
|
|
143
|
+
} finally {
|
|
144
|
+
stopController.signal.removeEventListener('abort', propagateStop);
|
|
145
|
+
maybePruneAudit();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function startWorker(def: JobDef): void {
|
|
150
|
+
const worker = defineWorker(def.name, () => executeJob(def, false), {
|
|
151
|
+
queue: ensureQueue(),
|
|
152
|
+
pollIntervall: pollIntervalMs,
|
|
153
|
+
logger,
|
|
154
|
+
});
|
|
155
|
+
workers.set(def.name, worker);
|
|
156
|
+
// start() resolves only when the worker stops; run it detached.
|
|
157
|
+
void worker.start().catch((err) => logger.error(`[jobs] worker "${def.name}" crashed`, err));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
register(job) {
|
|
162
|
+
if (stopped) throw new Error('cannot register on a stopped job runner');
|
|
163
|
+
if (defs.has(job.name)) throw new Error(`job already registered: ${job.name}`);
|
|
164
|
+
defs.set(job.name, job);
|
|
165
|
+
if (job.cron) {
|
|
166
|
+
// schedule() upserts by type, so re-registering across restarts just
|
|
167
|
+
// refreshes the cron expression (no duplicate scheduled rows).
|
|
168
|
+
ensureQueue().schedule(job.name, { cron: job.cron });
|
|
169
|
+
if (started) startWorker(job);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
start() {
|
|
174
|
+
if (stopped) throw new Error('cannot restart a stopped job runner');
|
|
175
|
+
if (started) return;
|
|
176
|
+
started = true;
|
|
177
|
+
for (const def of defs.values()) if (def.cron) startWorker(def);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async stop() {
|
|
181
|
+
if (stopped) return;
|
|
182
|
+
stopped = true;
|
|
183
|
+
started = false;
|
|
184
|
+
stopController.abort(new Error('job runner stopped'));
|
|
185
|
+
await Promise.all([...workers.values()].map((worker) => worker.stop()));
|
|
186
|
+
workers.clear();
|
|
187
|
+
// Closes the queue's maintenance timer; the adapter's no-op close keeps the
|
|
188
|
+
// shared DB open for the rest of shutdown.
|
|
189
|
+
queue?.close();
|
|
190
|
+
queue = null;
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async runOnce(name) {
|
|
194
|
+
const def = defs.get(name);
|
|
195
|
+
if (!def) throw new ApiError(404, `Unknown job: ${name}`);
|
|
196
|
+
await executeJob(def, true);
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
getLastRun(name) {
|
|
200
|
+
const row = lastRunQuery.get(name) as {
|
|
201
|
+
startedAt: string;
|
|
202
|
+
endedAt: string | null;
|
|
203
|
+
status: string;
|
|
204
|
+
error: string | null;
|
|
205
|
+
} | null;
|
|
206
|
+
if (!row) return null;
|
|
207
|
+
const record: JobRunRecord = { startedAt: row.startedAt, status: row.status };
|
|
208
|
+
if (row.endedAt !== null) record.endedAt = row.endedAt;
|
|
209
|
+
if (row.error !== null) record.error = row.error;
|
|
210
|
+
return record;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { JobTimeoutError, withTimeout } from './timeout';
|
|
3
|
+
|
|
4
|
+
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
5
|
+
|
|
6
|
+
describe('withTimeout', () => {
|
|
7
|
+
test('resolves with the promise value when it settles in time', async () => {
|
|
8
|
+
await expect(withTimeout(Promise.resolve('ok'), 1000)).resolves.toBe('ok');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('rejects with JobTimeoutError when the promise is too slow', async () => {
|
|
12
|
+
const slow = sleep(500).then(() => 'late');
|
|
13
|
+
const p = withTimeout(slow, 20);
|
|
14
|
+
await expect(p).rejects.toBeInstanceOf(JobTimeoutError);
|
|
15
|
+
await expect(p).rejects.toThrow('timed out after 20ms');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('invokes onTimeout (used to abort the handler) on timeout', async () => {
|
|
19
|
+
let aborted = false;
|
|
20
|
+
await withTimeout(sleep(500), 20, () => {
|
|
21
|
+
aborted = true;
|
|
22
|
+
}).catch(() => {});
|
|
23
|
+
expect(aborted).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('a non-positive timeout means "no timeout" — the promise passes through', async () => {
|
|
27
|
+
await expect(withTimeout(Promise.resolve(42), 0)).resolves.toBe(42);
|
|
28
|
+
await expect(withTimeout(Promise.resolve(42), Number.POSITIVE_INFINITY)).resolves.toBe(42);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('propagates a rejection from the underlying promise unchanged', async () => {
|
|
32
|
+
const boom = Promise.reject(new Error('boom'));
|
|
33
|
+
await expect(withTimeout(boom, 1000)).rejects.toThrow('boom');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A promise-timeout for in-process job handlers.
|
|
3
|
+
*
|
|
4
|
+
* NB: cwip's `runWithTimeout` is a *subprocess* runner (it spawns + kills a child
|
|
5
|
+
* process), so it is the wrong tool for racing an in-process handler promise — we
|
|
6
|
+
* own this tiny helper instead. The timer is `unref()`'d so it never keeps the
|
|
7
|
+
* event loop alive, and it is always cleared once the race settles.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Thrown when a job handler exceeds its `timeout`. */
|
|
11
|
+
export class JobTimeoutError extends Error {
|
|
12
|
+
readonly timeoutMs: number;
|
|
13
|
+
constructor(timeoutMs: number) {
|
|
14
|
+
super(`Job timed out after ${timeoutMs}ms`);
|
|
15
|
+
this.name = 'JobTimeoutError';
|
|
16
|
+
this.timeoutMs = timeoutMs;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve/reject with `promise`, but reject with a {@link JobTimeoutError} if it
|
|
22
|
+
* has not settled within `timeoutMs`. `onTimeout` fires on timeout — the runner
|
|
23
|
+
* uses it to abort the handler's context signal so a cooperative handler can
|
|
24
|
+
* short-circuit (a pending promise cannot be force-cancelled).
|
|
25
|
+
*
|
|
26
|
+
* Order matters: the timeout promise is rejected BEFORE `onTimeout` runs. Aborting
|
|
27
|
+
* the signal can synchronously resolve a cooperative handler; if that resolution
|
|
28
|
+
* reached `Promise.race` first the run would be mis-recorded as `ok`. Rejecting
|
|
29
|
+
* first guarantees a timed-out run is recorded as a timeout, not a success.
|
|
30
|
+
*
|
|
31
|
+
* A non-positive / non-finite `timeoutMs` means "no timeout": `promise` is
|
|
32
|
+
* returned unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export function withTimeout<T>(
|
|
35
|
+
promise: Promise<T>,
|
|
36
|
+
timeoutMs: number,
|
|
37
|
+
onTimeout?: () => void,
|
|
38
|
+
): Promise<T> {
|
|
39
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return promise;
|
|
40
|
+
|
|
41
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
42
|
+
const timeout = new Promise<never>((_resolve, reject) => {
|
|
43
|
+
timer = setTimeout(() => {
|
|
44
|
+
reject(new JobTimeoutError(timeoutMs));
|
|
45
|
+
onTimeout?.();
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
timer.unref?.();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
51
|
+
if (timer) clearTimeout(timer);
|
|
52
|
+
}) as Promise<T>;
|
|
53
|
+
}
|