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,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minting a `MASTER_LOCK_SEED` from a password nobody has to guess.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this exists (2026-08-20) ────────────────────────────────────────────
|
|
5
|
+
* The lock's seed is `{kdf, verifierHash, idleMs}` — a KDF descriptor and an
|
|
6
|
+
* argon2id hash of a PBKDF2 verifier. Every one of those steps is derivable from
|
|
7
|
+
* the modules beside this one, and until now every one of them was something an
|
|
8
|
+
* operator had to reproduce by hand in a REPL to arm a new instance. That is the
|
|
9
|
+
* shape of instruction that gets written into a runbook and then gets a digit
|
|
10
|
+
* wrong; the fleet's standing answer to that is a function
|
|
11
|
+
* (`fleet/rules/automation-over-documentation.md`).
|
|
12
|
+
*
|
|
13
|
+
* The immediate caller is the ephemeral **stage** environment, whose entire
|
|
14
|
+
* purpose is to have a master-lock password an AGENT knows, so it can open
|
|
15
|
+
* `collections` and the binary-server inspector and actually look at them — two
|
|
16
|
+
* screens no agent has ever seen, because production's password is the vault's
|
|
17
|
+
* and is the owner's alone.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 **A different PASSWORD, never a different SCHEME.** The derivation stays
|
|
20
|
+
* byte-identical to `apps/vault`'s (see `../../core/master-lock/kdf.ts`), because
|
|
21
|
+
* a stage that unlocks under different rules from production proves nothing about
|
|
22
|
+
* production. This function chooses the salt and the password; it changes nothing
|
|
23
|
+
* else.
|
|
24
|
+
*/
|
|
25
|
+
import { DEFAULT_IDLE_MS } from "cursedbelt-core/master-lock/policy";
|
|
26
|
+
import {
|
|
27
|
+
type MasterLockKdfParams,
|
|
28
|
+
deriveMasterLockVerifier,
|
|
29
|
+
newMasterLockKdfParams,
|
|
30
|
+
} from "cursedbelt-core/master-lock/kdf";
|
|
31
|
+
import type { MasterLockRecord } from "./masterLock";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The idle window a freshly minted lock starts with.
|
|
35
|
+
*
|
|
36
|
+
* 🔴 It is `DEFAULT_IDLE_MS` ITSELF, not a copy that claims to match it. Until
|
|
37
|
+
* 2026-08-25 this read `15 * 60 * 1000` under a comment saying "matches the store's
|
|
38
|
+
* default", which is 5 minutes — so any app armed through `mintMasterLockSeed` would
|
|
39
|
+
* have silently got three times the window the owner asked for. Live prod was spared
|
|
40
|
+
* only by accident: both seeds omit `idleMs` entirely, so the store's own default
|
|
41
|
+
* applied. A second constant that must equal a first is a drift waiting for a reader
|
|
42
|
+
* who trusts the comment; there is now only one number.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_SEED_IDLE_MS = DEFAULT_IDLE_MS;
|
|
45
|
+
|
|
46
|
+
export interface MintMasterLockSeedOptions {
|
|
47
|
+
/** Override the idle timeout baked into the seed. */
|
|
48
|
+
idleMs?: number;
|
|
49
|
+
/** Reuse existing params instead of generating a salt — for a rotation. */
|
|
50
|
+
kdf?: MasterLockKdfParams;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `password` → the record an app can be seeded with.
|
|
55
|
+
*
|
|
56
|
+
* Returns the parsed record; {@link serializeMasterLockSeed} is what turns it
|
|
57
|
+
* into the env value, because that step has its own trap (see below).
|
|
58
|
+
*/
|
|
59
|
+
export async function mintMasterLockSeed(
|
|
60
|
+
password: string,
|
|
61
|
+
options: MintMasterLockSeedOptions = {},
|
|
62
|
+
): Promise<MasterLockRecord> {
|
|
63
|
+
if (password.length < 8) {
|
|
64
|
+
// Not a policy — a floor. A seed minted from a two-character password would
|
|
65
|
+
// arm a lock that reads as protection and is not.
|
|
66
|
+
throw new Error("master-lock password must be at least 8 characters");
|
|
67
|
+
}
|
|
68
|
+
const kdf = options.kdf ?? newMasterLockKdfParams();
|
|
69
|
+
const verifier = await deriveMasterLockVerifier(password, kdf);
|
|
70
|
+
return {
|
|
71
|
+
kdf,
|
|
72
|
+
verifierHash: await Bun.password.hash(verifier, { algorithm: "argon2id" }),
|
|
73
|
+
idleMs: options.idleMs ?? DEFAULT_SEED_IDLE_MS,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The record as it belongs in a 0600 `.env` file.
|
|
79
|
+
*
|
|
80
|
+
* 🔴 **SINGLE-QUOTED, and this is not cosmetic.** An argon2id PHC string contains
|
|
81
|
+
* `$`, the file is sourced by a shell, and an unquoted value therefore arrives at
|
|
82
|
+
* the app with `$argon2id` expanded to nothing. `readRecord` strips exactly this
|
|
83
|
+
* quote pair — `apps/collections` shipped a *doubly*-quoted hash to prod on
|
|
84
|
+
* 2026-08-12 and spent a day being told its correct passphrase was wrong, which
|
|
85
|
+
* is why the strip is on the whole blob rather than on the field.
|
|
86
|
+
*/
|
|
87
|
+
export function serializeMasterLockSeed(record: MasterLockRecord): string {
|
|
88
|
+
return `'${JSON.stringify(record)}'`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A password worth typing into a stage: unambiguous, and long enough that the
|
|
93
|
+
* hostname being on the internet behind Cloudflare Access is not the only thing
|
|
94
|
+
* protecting it.
|
|
95
|
+
*
|
|
96
|
+
* No `l`, `1`, `O` or `0` — a stage password gets read out of a terminal and
|
|
97
|
+
* typed into a browser by hand, and this is the one place where that matters.
|
|
98
|
+
*/
|
|
99
|
+
export function generateStagePassword(words = 4): string {
|
|
100
|
+
const alphabet = "abcdefghijkmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
101
|
+
const bytes = new Uint8Array(words * 5);
|
|
102
|
+
crypto.getRandomValues(bytes);
|
|
103
|
+
const chunks: string[] = [];
|
|
104
|
+
for (let w = 0; w < words; w += 1) {
|
|
105
|
+
let chunk = "";
|
|
106
|
+
for (let i = 0; i < 5; i += 1) {
|
|
107
|
+
chunk += alphabet[(bytes[w * 5 + i] as number) % alphabet.length];
|
|
108
|
+
}
|
|
109
|
+
chunks.push(chunk);
|
|
110
|
+
}
|
|
111
|
+
return chunks.join("-");
|
|
112
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two ways to keep the record, so no app has to invent a third.
|
|
3
|
+
*
|
|
4
|
+
* The record is one JSON blob on purpose: every app that will ever mount this already has
|
|
5
|
+
* somewhere to put a string under a key — binary-server's `settings` table, a satellite's
|
|
6
|
+
* `app_settings`, a `Map` in a test. Asking for a whole table would be asking each adopter
|
|
7
|
+
* to write a migration for three fields.
|
|
8
|
+
*/
|
|
9
|
+
import type { MasterLockStore } from "./masterLock";
|
|
10
|
+
|
|
11
|
+
/** The default key, so two apps' rows look the same when somebody goes looking. */
|
|
12
|
+
export const MASTER_LOCK_SETTING_KEY = "master_lock:record";
|
|
13
|
+
|
|
14
|
+
/** Wrap an app's existing key/value settings surface. */
|
|
15
|
+
export function createKvMasterLockStore(
|
|
16
|
+
kv: { get(key: string): string | null | undefined; set(key: string, value: string): void },
|
|
17
|
+
key: string = MASTER_LOCK_SETTING_KEY,
|
|
18
|
+
): MasterLockStore {
|
|
19
|
+
return {
|
|
20
|
+
read: () => kv.get(key) ?? null,
|
|
21
|
+
write: (json) => kv.set(key, json),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** For tests, and for a runtime with nowhere to persist — where it means the lock re-seeds
|
|
26
|
+
* from the environment on every boot, which is the correct fallback rather than a failure. */
|
|
27
|
+
export function createMemoryMasterLockStore(initial?: string | null): MasterLockStore {
|
|
28
|
+
let value = initial ?? null;
|
|
29
|
+
return {
|
|
30
|
+
read: () => value,
|
|
31
|
+
write: (json) => {
|
|
32
|
+
value = json;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { ImagePipelineSpec } from 'cursedbelt-core/media';
|
|
3
|
+
import { renderImage } from './image';
|
|
4
|
+
|
|
5
|
+
// sharp is an OPTIONAL peer — skip this whole suite cleanly when it isn't installed.
|
|
6
|
+
let sharpFactory: typeof import('sharp').default | null = null;
|
|
7
|
+
try {
|
|
8
|
+
sharpFactory = (await import('sharp')).default;
|
|
9
|
+
} catch {
|
|
10
|
+
sharpFactory = null;
|
|
11
|
+
}
|
|
12
|
+
const sharp = sharpFactory;
|
|
13
|
+
|
|
14
|
+
/** A solid-color JPEG of `w×h` to feed renderImage as a real encoded source. */
|
|
15
|
+
async function jpeg(w: number, h: number, color = { r: 200, g: 50, b: 50 }): Promise<Buffer> {
|
|
16
|
+
if (!sharp) throw new Error('sharp unavailable');
|
|
17
|
+
return sharp({ create: { width: w, height: h, channels: 3, background: color } })
|
|
18
|
+
.jpeg()
|
|
19
|
+
.toBuffer();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// SKIPPED when the OPTIONAL `sharp` peer is not installed — it is this suite's subject. Runs by default when it is.
|
|
23
|
+
describe.skipIf(!sharp)('renderImage (sharp)', () => {
|
|
24
|
+
test('resize + format-convert → webp with the right dimensions (acceptance #8)', async () => {
|
|
25
|
+
const src = await jpeg(400, 300);
|
|
26
|
+
const spec: ImagePipelineSpec = {
|
|
27
|
+
input: src,
|
|
28
|
+
operations: [{ type: 'resize', width: 200 }],
|
|
29
|
+
output: { format: 'webp', quality: 80 },
|
|
30
|
+
};
|
|
31
|
+
const out = await renderImage(spec);
|
|
32
|
+
expect(out.length).toBeGreaterThan(0);
|
|
33
|
+
const meta = await sharp!(out).metadata();
|
|
34
|
+
expect(meta.format).toBe('webp');
|
|
35
|
+
expect(meta.width).toBe(200);
|
|
36
|
+
expect(meta.height).toBe(150); // aspect preserved (cover with width-only)
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('crop extracts the requested region', async () => {
|
|
40
|
+
const src = await jpeg(100, 100);
|
|
41
|
+
const out = await renderImage({
|
|
42
|
+
input: src,
|
|
43
|
+
operations: [{ type: 'crop', left: 10, top: 20, width: 40, height: 30 }],
|
|
44
|
+
output: { format: 'png' },
|
|
45
|
+
});
|
|
46
|
+
const meta = await sharp!(out).metadata();
|
|
47
|
+
expect(meta.width).toBe(40);
|
|
48
|
+
expect(meta.height).toBe(30);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('rotate by 90° swaps dimensions', async () => {
|
|
52
|
+
const src = await jpeg(120, 60);
|
|
53
|
+
const out = await renderImage({
|
|
54
|
+
input: src,
|
|
55
|
+
operations: [{ type: 'rotate', angle: 90 }],
|
|
56
|
+
output: { format: 'png' },
|
|
57
|
+
});
|
|
58
|
+
const meta = await sharp!(out).metadata();
|
|
59
|
+
expect(meta.width).toBe(60);
|
|
60
|
+
expect(meta.height).toBe(120);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('accepts a file path input + jpeg output', async () => {
|
|
64
|
+
const src = await jpeg(50, 50);
|
|
65
|
+
const tmp = `${require('node:os').tmpdir()}/cb-media-${process.pid}-src.jpg`;
|
|
66
|
+
await Bun.write(tmp, src);
|
|
67
|
+
const out = await renderImage({
|
|
68
|
+
input: tmp,
|
|
69
|
+
operations: [],
|
|
70
|
+
output: { format: 'jpeg', quality: 70 },
|
|
71
|
+
});
|
|
72
|
+
expect((await sharp!(out).metadata()).format).toBe('jpeg');
|
|
73
|
+
require('node:fs').rmSync(tmp, { force: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('remove-bg clears the edge-connected background to transparent', async () => {
|
|
77
|
+
if (!sharp) return;
|
|
78
|
+
// 8×8: red border, blue 4×4 center.
|
|
79
|
+
const W = 8;
|
|
80
|
+
const H = 8;
|
|
81
|
+
const raw = Buffer.alloc(W * H * 3);
|
|
82
|
+
for (let y = 0; y < H; y++) {
|
|
83
|
+
for (let x = 0; x < W; x++) {
|
|
84
|
+
const i = (y * W + x) * 3;
|
|
85
|
+
const center = x >= 2 && x <= 5 && y >= 2 && y <= 5;
|
|
86
|
+
raw[i] = center ? 0 : 255;
|
|
87
|
+
raw[i + 1] = 0;
|
|
88
|
+
raw[i + 2] = center ? 255 : 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const src = await sharp(raw, { raw: { width: W, height: H, channels: 3 } })
|
|
92
|
+
.png()
|
|
93
|
+
.toBuffer();
|
|
94
|
+
const out = await renderImage({
|
|
95
|
+
input: src,
|
|
96
|
+
operations: [{ type: 'remove-bg', tolerance: 10 }],
|
|
97
|
+
output: { format: 'png' },
|
|
98
|
+
});
|
|
99
|
+
const { data, info } = await sharp(out)
|
|
100
|
+
.ensureAlpha()
|
|
101
|
+
.raw()
|
|
102
|
+
.toBuffer({ resolveWithObject: true });
|
|
103
|
+
expect(info.channels).toBe(4);
|
|
104
|
+
const alphaAt = (x: number, y: number) => data[(y * info.width + x) * 4 + 3];
|
|
105
|
+
expect(alphaAt(0, 0)).toBe(0); // corner background gone
|
|
106
|
+
expect(alphaAt(3, 3)).toBe(255); // subject intact
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('rejects an invalid spec before touching sharp', async () => {
|
|
110
|
+
// input '' is a valid string type but an invalid value → validation rejects pre-sharp.
|
|
111
|
+
await expect(
|
|
112
|
+
renderImage({ input: '', operations: [], output: { format: 'webp' } }),
|
|
113
|
+
).rejects.toThrow(/Invalid ImagePipelineSpec/);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Image rendering via sharp. sharp is an OPTIONAL peer dep, imported LAZILY inside
|
|
2
|
+
// renderImage so merely importing `cursedbelt-server` never loads the native binary
|
|
3
|
+
// — only an app that actually renders an image pays for it. The transforms are driven
|
|
4
|
+
// by the media-core `ImagePipelineSpec`; `remove-bg` runs the pure media-core BFS on
|
|
5
|
+
// sharp's raw RGBA buffer, then re-wraps the result.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
assertValidImagePipelineSpec,
|
|
9
|
+
clampQuality,
|
|
10
|
+
floodFillBGS,
|
|
11
|
+
type ImageOperation,
|
|
12
|
+
type ImageOutputFormat,
|
|
13
|
+
type ImagePipelineSpec,
|
|
14
|
+
} from 'cursedbelt-core/media';
|
|
15
|
+
|
|
16
|
+
// Type-only — erased at runtime, so this does NOT eagerly load sharp.
|
|
17
|
+
type SharpModule = typeof import('sharp');
|
|
18
|
+
type SharpFactory = SharpModule['default'];
|
|
19
|
+
type SharpInstance = ReturnType<SharpFactory>;
|
|
20
|
+
|
|
21
|
+
let sharpPromise: Promise<SharpFactory> | null = null;
|
|
22
|
+
|
|
23
|
+
/** Lazily import sharp once; a clear error if the optional peer isn't installed. */
|
|
24
|
+
async function loadSharp(): Promise<SharpFactory> {
|
|
25
|
+
if (!sharpPromise) {
|
|
26
|
+
sharpPromise = import('sharp')
|
|
27
|
+
.then((m) => m.default)
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
sharpPromise = null;
|
|
30
|
+
throw new Error(
|
|
31
|
+
`cursedbelt media: 'sharp' is required for renderImage but could not be loaded. ` +
|
|
32
|
+
`Install it in the app (it is an optional peer dependency). Cause: ${
|
|
33
|
+
err instanceof Error ? err.message : String(err)
|
|
34
|
+
}`,
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return sharpPromise;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Run a sharp pipeline to a fresh, transparent RGBA buffer, BFS it, re-wrap it. */
|
|
42
|
+
async function applyRemoveBg(
|
|
43
|
+
sharp: SharpFactory,
|
|
44
|
+
img: SharpInstance,
|
|
45
|
+
op: Extract<ImageOperation, { type: 'remove-bg' }>,
|
|
46
|
+
): Promise<SharpInstance> {
|
|
47
|
+
const { data, info } = await img.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
48
|
+
const out = floodFillBGS(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), {
|
|
49
|
+
width: info.width,
|
|
50
|
+
height: info.height,
|
|
51
|
+
channels: 4,
|
|
52
|
+
tolerance: op.tolerance,
|
|
53
|
+
});
|
|
54
|
+
return sharp(Buffer.from(out.buffer, out.byteOffset, out.byteLength), {
|
|
55
|
+
raw: { width: info.width, height: info.height, channels: 4 },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyResize(
|
|
60
|
+
img: SharpInstance,
|
|
61
|
+
op: Extract<ImageOperation, { type: 'resize' }>,
|
|
62
|
+
): SharpInstance {
|
|
63
|
+
return img.resize({
|
|
64
|
+
width: op.width,
|
|
65
|
+
height: op.height,
|
|
66
|
+
fit: op.fit ?? 'cover',
|
|
67
|
+
withoutEnlargement: false,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render an `ImagePipelineSpec` to an encoded image buffer. Operations are applied in
|
|
73
|
+
* order; `compress` overrides the output quality; `format-convert` is a no-op marker
|
|
74
|
+
* (the output format does the conversion). Throws on an invalid spec (before sharp).
|
|
75
|
+
*/
|
|
76
|
+
export async function renderImage(spec: ImagePipelineSpec): Promise<Buffer> {
|
|
77
|
+
assertValidImagePipelineSpec(spec);
|
|
78
|
+
const sharp = await loadSharp();
|
|
79
|
+
|
|
80
|
+
const source =
|
|
81
|
+
typeof spec.input === 'string'
|
|
82
|
+
? spec.input
|
|
83
|
+
: Buffer.from(spec.input.buffer, spec.input.byteOffset, spec.input.byteLength);
|
|
84
|
+
// `failOn: 'none'` keeps a slightly-truncated upload from hard-failing the pipeline.
|
|
85
|
+
let img = sharp(source as string | Buffer, { failOn: 'none' });
|
|
86
|
+
|
|
87
|
+
let quality = spec.output.quality;
|
|
88
|
+
for (const op of spec.operations) {
|
|
89
|
+
switch (op.type) {
|
|
90
|
+
case 'resize':
|
|
91
|
+
img = applyResize(img, op);
|
|
92
|
+
break;
|
|
93
|
+
case 'crop':
|
|
94
|
+
img = img.extract({ left: op.left, top: op.top, width: op.width, height: op.height });
|
|
95
|
+
break;
|
|
96
|
+
case 'rotate':
|
|
97
|
+
// Default background transparent so a non-90° rotate doesn't fill with black.
|
|
98
|
+
img = img.rotate(op.angle, { background: { r: 0, g: 0, b: 0, alpha: 0 } });
|
|
99
|
+
break;
|
|
100
|
+
case 'flip':
|
|
101
|
+
img = op.direction === 'vertical' ? img.flip() : img.flop();
|
|
102
|
+
break;
|
|
103
|
+
case 'remove-bg':
|
|
104
|
+
img = await applyRemoveBg(sharp, img, op);
|
|
105
|
+
break;
|
|
106
|
+
case 'compress':
|
|
107
|
+
quality = op.quality;
|
|
108
|
+
break;
|
|
109
|
+
case 'format-convert':
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return encode(img, spec.output.format, quality);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function encode(
|
|
118
|
+
img: SharpInstance,
|
|
119
|
+
format: ImageOutputFormat,
|
|
120
|
+
quality: number | undefined,
|
|
121
|
+
): Promise<Buffer> {
|
|
122
|
+
const q = clampQuality(quality);
|
|
123
|
+
switch (format) {
|
|
124
|
+
case 'jpeg':
|
|
125
|
+
return img.jpeg({ quality: q, mozjpeg: true }).toBuffer();
|
|
126
|
+
case 'png':
|
|
127
|
+
// PNG is lossless; map quality → effort/compression for a size/CPU trade-off.
|
|
128
|
+
return img.png({ compressionLevel: 9 }).toBuffer();
|
|
129
|
+
case 'webp':
|
|
130
|
+
return img.webp({ quality: q }).toBuffer();
|
|
131
|
+
case 'avif':
|
|
132
|
+
return img.avif({ quality: q }).toBuffer();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/server/media-bun — the Bun server adapter, re-exported from `cursedbelt-server`, for the media-core
|
|
2
|
+
// specs: image rendering via sharp (lazy/optional), video trim + probe + thumbnail
|
|
3
|
+
// via native ffmpeg/ffprobe. App handlers keep auth, FsNode lookup, quota, and
|
|
4
|
+
// storeFile; this owns the actual pixel/codec work.
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
buildProbeArgs,
|
|
8
|
+
buildThumbnailArgs,
|
|
9
|
+
buildTrimArgs,
|
|
10
|
+
ffmpegTimeToSeconds,
|
|
11
|
+
type ProbeResult,
|
|
12
|
+
parseFfmpegProgress,
|
|
13
|
+
parseFfprobe,
|
|
14
|
+
type VideoCodecPlan,
|
|
15
|
+
videoCodecPlan,
|
|
16
|
+
} from 'cursedbelt-core/media/ffmpeg';
|
|
17
|
+
export { renderImage } from './image';
|
|
18
|
+
// Streaming pipeline: faststart remux, HLS adaptive-bitrate ladder, scrub sprite + WebVTT,
|
|
19
|
+
// poster, codec-aware probe — pure builders/parsers (testable without ffmpeg).
|
|
20
|
+
export {
|
|
21
|
+
aacWebAudioArgs,
|
|
22
|
+
buildFrameJpegArgs,
|
|
23
|
+
buildSpriteVtt,
|
|
24
|
+
buildStreamProbeArgs,
|
|
25
|
+
canStreamCopy,
|
|
26
|
+
EMPTY_PROBE,
|
|
27
|
+
FASTSTART,
|
|
28
|
+
faststartArgs,
|
|
29
|
+
h264WebVideoArgs,
|
|
30
|
+
hlsLadderArgs,
|
|
31
|
+
imageThumbnailArgs,
|
|
32
|
+
LIBRARY_CRF,
|
|
33
|
+
LIBRARY_SCALE,
|
|
34
|
+
parseProgressMicros,
|
|
35
|
+
parseStreamProbe,
|
|
36
|
+
planSprite,
|
|
37
|
+
posterArgs,
|
|
38
|
+
resolveLadder,
|
|
39
|
+
type SpritePlan,
|
|
40
|
+
spriteArgs,
|
|
41
|
+
type VideoProbe,
|
|
42
|
+
} from './streaming';
|
|
43
|
+
export {
|
|
44
|
+
extractFrameJpeg,
|
|
45
|
+
extractThumbnail,
|
|
46
|
+
isFfmpegAvailable,
|
|
47
|
+
isFfprobeAvailable,
|
|
48
|
+
probeMedia,
|
|
49
|
+
probeVideoStream,
|
|
50
|
+
runFfmpegProgress,
|
|
51
|
+
trimVideo,
|
|
52
|
+
} from './video';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
buildSpriteVtt,
|
|
4
|
+
canStreamCopy,
|
|
5
|
+
faststartArgs,
|
|
6
|
+
hlsLadderArgs,
|
|
7
|
+
parseProgressMicros,
|
|
8
|
+
parseStreamProbe,
|
|
9
|
+
planSprite,
|
|
10
|
+
resolveLadder,
|
|
11
|
+
type VideoProbe,
|
|
12
|
+
} from './streaming';
|
|
13
|
+
|
|
14
|
+
const probe = (over: Partial<VideoProbe> = {}): VideoProbe => ({
|
|
15
|
+
duration: 100,
|
|
16
|
+
width: 1280,
|
|
17
|
+
height: 720,
|
|
18
|
+
vcodec: 'h264',
|
|
19
|
+
acodec: 'aac',
|
|
20
|
+
pixfmt: 'yuv420p',
|
|
21
|
+
hasAudio: true,
|
|
22
|
+
...over,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('parseStreamProbe', () => {
|
|
26
|
+
test('pulls codecs/dims/duration/audio from ffprobe json', () => {
|
|
27
|
+
const r = parseStreamProbe({
|
|
28
|
+
format: { duration: '12.5' },
|
|
29
|
+
streams: [
|
|
30
|
+
{ codec_type: 'video', codec_name: 'h264', width: 1920, height: 1080, pix_fmt: 'yuv420p' },
|
|
31
|
+
{ codec_type: 'audio', codec_name: 'aac' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
expect(r).toEqual({
|
|
35
|
+
duration: 12.5,
|
|
36
|
+
width: 1920,
|
|
37
|
+
height: 1080,
|
|
38
|
+
vcodec: 'h264',
|
|
39
|
+
acodec: 'aac',
|
|
40
|
+
pixfmt: 'yuv420p',
|
|
41
|
+
hasAudio: true,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
test('handles a video-only file', () => {
|
|
45
|
+
const r = parseStreamProbe({ streams: [{ codec_type: 'video', codec_name: 'vp9' }] });
|
|
46
|
+
expect(r.hasAudio).toBe(false);
|
|
47
|
+
expect(r.acodec).toBe('');
|
|
48
|
+
expect(r.vcodec).toBe('vp9');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('canStreamCopy', () => {
|
|
53
|
+
test('true for web-safe h264/yuv420p/aac within width', () => {
|
|
54
|
+
expect(canStreamCopy(probe())).toBe(true);
|
|
55
|
+
expect(canStreamCopy(probe({ acodec: '' }))).toBe(true); // no audio is fine
|
|
56
|
+
});
|
|
57
|
+
test('false for non-h264, wrong pixfmt, non-aac audio, or too wide', () => {
|
|
58
|
+
expect(canStreamCopy(probe({ vcodec: 'hevc' }))).toBe(false);
|
|
59
|
+
expect(canStreamCopy(probe({ pixfmt: 'yuv444p' }))).toBe(false);
|
|
60
|
+
expect(canStreamCopy(probe({ acodec: 'mp3' }))).toBe(false);
|
|
61
|
+
expect(canStreamCopy(probe({ width: 3840 }))).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('faststartArgs', () => {
|
|
66
|
+
test('copy mode just remuxes with +faststart', () => {
|
|
67
|
+
const a = faststartArgs('in.mp4', 'out.mp4', { copy: true });
|
|
68
|
+
expect(a).toEqual(['-i', 'in.mp4', '-c', 'copy', '-movflags', '+faststart', 'out.mp4']);
|
|
69
|
+
});
|
|
70
|
+
test('re-encode mode scales + uses web-safe h264/aac + faststart', () => {
|
|
71
|
+
const a = faststartArgs('in.mov', 'out.mp4', { copy: false, crf: 20 });
|
|
72
|
+
expect(a).toContain('libx264');
|
|
73
|
+
expect(a).toContain('yuv420p');
|
|
74
|
+
expect(a.join(' ')).toContain('-crf 20');
|
|
75
|
+
expect(a.slice(-3)).toEqual(['-movflags', '+faststart', 'out.mp4']);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('parseProgressMicros', () => {
|
|
80
|
+
test('reads out_time_us and out_time_ms (ms is really µs in ffmpeg, ×1000)', () => {
|
|
81
|
+
expect(parseProgressMicros('out_time_us=5000000')).toBe(5_000_000);
|
|
82
|
+
expect(parseProgressMicros('out_time_ms=5000')).toBe(5_000_000);
|
|
83
|
+
expect(parseProgressMicros('frame=12')).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('resolveLadder', () => {
|
|
88
|
+
test('drops rungs taller than source, dedupes + sorts ascending', () => {
|
|
89
|
+
expect(resolveLadder([1080, 720, 360, 720], 720)).toEqual([360, 720]);
|
|
90
|
+
});
|
|
91
|
+
test('falls back to source-or-720 when nothing valid', () => {
|
|
92
|
+
expect(resolveLadder([], 1080)).toEqual([720]);
|
|
93
|
+
expect(resolveLadder([2160], 480)).toEqual([480]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('hlsLadderArgs', () => {
|
|
98
|
+
test('emits a named multi-variant HLS transcode with audio maps', () => {
|
|
99
|
+
const a = hlsLadderArgs('src.mp4', '/out', { heights: [480, 720], crf: 23, hasAudio: true });
|
|
100
|
+
const s = a.join(' ');
|
|
101
|
+
expect(s).toContain('-filter_complex');
|
|
102
|
+
expect(s).toContain('split=2');
|
|
103
|
+
expect(s).toContain('-master_pl_name master.m3u8');
|
|
104
|
+
expect(s).toContain('-var_stream_map v:0,a:0,name:480 v:1,a:1,name:720');
|
|
105
|
+
expect(s).toContain('/out/%v/playlist.m3u8');
|
|
106
|
+
});
|
|
107
|
+
test('omits audio maps when the source has none', () => {
|
|
108
|
+
const a = hlsLadderArgs('src.mp4', '/out', { heights: [720], crf: 23, hasAudio: false });
|
|
109
|
+
expect(a.join(' ')).toContain('-var_stream_map v:0,name:720');
|
|
110
|
+
expect(a).not.toContain('a:0');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('planSprite + buildSpriteVtt', () => {
|
|
115
|
+
test('bounds the tile count and builds matching geometry', () => {
|
|
116
|
+
const plan = planSprite(60, 2, 1280, 720); // 30 tiles @ 2s
|
|
117
|
+
expect(plan.count).toBe(30);
|
|
118
|
+
expect(plan.cols * plan.rows).toBeGreaterThanOrEqual(plan.count);
|
|
119
|
+
expect(plan.thumbW).toBe(160);
|
|
120
|
+
expect(plan.thumbH % 2).toBe(0); // even (yuv420p)
|
|
121
|
+
});
|
|
122
|
+
test('widens the interval so a very long video still fits one sheet', () => {
|
|
123
|
+
const plan = planSprite(10000, 2, 1280, 720);
|
|
124
|
+
expect(plan.count).toBeLessThanOrEqual(144);
|
|
125
|
+
expect(plan.effInterval).toBeGreaterThan(2);
|
|
126
|
+
});
|
|
127
|
+
test('VTT has a header, one cue per tile, and xywh fragments', () => {
|
|
128
|
+
const plan = planSprite(20, 2, 1280, 720); // 10 tiles
|
|
129
|
+
const vtt = buildSpriteVtt(plan);
|
|
130
|
+
expect(vtt.startsWith('WEBVTT')).toBe(true);
|
|
131
|
+
expect((vtt.match(/-->/g) ?? []).length).toBe(plan.count);
|
|
132
|
+
expect(vtt).toContain('sprites.jpg#xywh=');
|
|
133
|
+
});
|
|
134
|
+
});
|