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,261 @@
|
|
|
1
|
+
import type { PlayableVideo, ProcessingStatus } from 'cursedbelt-core/media';
|
|
2
|
+
import type { PrivateIndex } from '../../private-media/index-store';
|
|
3
|
+
import type { MediaCtx } from '../types';
|
|
4
|
+
export type FileType = 'image' | 'video' | 'audio' | 'doc' | 'code' | 'other';
|
|
5
|
+
/** User-defined groupings (Apple-Photos-style). `favorites` is a system singleton per owner;
|
|
6
|
+
* `custom` are free-form. */
|
|
7
|
+
export type CollectionKind = 'custom' | 'favorites';
|
|
8
|
+
/** `normal` — always visible; `unlock` — hidden unless caller passes `includeUnlockable: true`. */
|
|
9
|
+
export type CollectionVisibility = 'normal' | 'unlock';
|
|
10
|
+
export type FileCollection = {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
kind: CollectionKind;
|
|
14
|
+
visibility: CollectionVisibility;
|
|
15
|
+
/** Live count of member files. */
|
|
16
|
+
fileCount: number;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
};
|
|
20
|
+
/** The clean, UI-facing record — explicit capability booleans, no storage keys leaked (ca's
|
|
21
|
+
* worst inconsistency was `thumbnailKey`-vs-`hasThumbnail` across two shapes). */
|
|
22
|
+
export type FileRecord = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
mime: string;
|
|
26
|
+
size: number;
|
|
27
|
+
fileType: FileType;
|
|
28
|
+
tags: string[];
|
|
29
|
+
isPrivate: boolean;
|
|
30
|
+
hasThumbnail: boolean;
|
|
31
|
+
hasHls: boolean;
|
|
32
|
+
hasSprites: boolean;
|
|
33
|
+
durationSeconds?: number;
|
|
34
|
+
processingStatus: ProcessingStatus;
|
|
35
|
+
processingProgress: number;
|
|
36
|
+
/** True when this file is a member of the caller's Favorites collection. */
|
|
37
|
+
favorite: boolean;
|
|
38
|
+
/** All collection ids this file belongs to (within the owner's scope). */
|
|
39
|
+
collectionIds: string[];
|
|
40
|
+
createdAt: number;
|
|
41
|
+
updatedAt: number;
|
|
42
|
+
/** Set once `trash()` soft-deletes this file (epoch ms) — cleared by `restore()`. Undefined for
|
|
43
|
+
* a live (non-trashed) file. */
|
|
44
|
+
trashedAt?: number;
|
|
45
|
+
};
|
|
46
|
+
/** A tag with how many files carry it — the facet the flat browser renders as filter chips. */
|
|
47
|
+
export type TagFacet = {
|
|
48
|
+
tag: string;
|
|
49
|
+
count: number;
|
|
50
|
+
};
|
|
51
|
+
/** A page of results with an opaque cursor for the next page (cursor-keyed, not OFFSET). */
|
|
52
|
+
export type FilePage = {
|
|
53
|
+
items: FileRecord[];
|
|
54
|
+
nextCursor?: string;
|
|
55
|
+
};
|
|
56
|
+
/** Every listing order the catalogue understands — the single source of truth the route layer
|
|
57
|
+
* validates against and the `FileSort` union derives from. `recent` = newest first (default);
|
|
58
|
+
* `oldest` = oldest first; `name` = A→Z; `size` = largest first; `random` = a seeded shuffle
|
|
59
|
+
* (the seed is minted per session and folded into the cursor, so a visit reshuffles but its own
|
|
60
|
+
* pagination is stable); `lastViewed` = most-recently-opened first (never-viewed files last). */
|
|
61
|
+
export declare const FILE_SORTS: readonly ["recent", "oldest", "name", "size", "random", "lastViewed"];
|
|
62
|
+
/** Listing order. Every sort is cursor-paginated (the cursor encodes the sort key + id tie-break,
|
|
63
|
+
* plus the shuffle seed for `random`). A cursor is only valid under the sort that minted it. */
|
|
64
|
+
export type FileSort = (typeof FILE_SORTS)[number];
|
|
65
|
+
export type ListFilesOptions = {
|
|
66
|
+
/** Smart-view filter (indexed). */
|
|
67
|
+
type?: FileType;
|
|
68
|
+
/** Single-tag filter (indexed via the join table). */
|
|
69
|
+
tag?: string;
|
|
70
|
+
/** Name substring (public files only — private names are sealed). */
|
|
71
|
+
query?: string;
|
|
72
|
+
/** Include the caller's private files (requires an unlocked set — the caller resolves which
|
|
73
|
+
* ones actually open). Omitted ⇒ public only, matching ca's default listing. */
|
|
74
|
+
includePrivate?: boolean;
|
|
75
|
+
/** Filter to members of a specific collection. An unlock-gated collection yields empty unless
|
|
76
|
+
* `includeUnlockable` is also true. */
|
|
77
|
+
collectionId?: string;
|
|
78
|
+
/** Filter to files that are favorited (i.e. members of the owner's Favorites collection). */
|
|
79
|
+
favorite?: boolean;
|
|
80
|
+
/** Expose unlock-gated collections/results. Default false — the route layer gates this. */
|
|
81
|
+
includeUnlockable?: boolean;
|
|
82
|
+
/** Listing order (default 'recent'). A cursor is only valid for the sort it was minted under. */
|
|
83
|
+
sort?: FileSort;
|
|
84
|
+
cursor?: string;
|
|
85
|
+
limit?: number;
|
|
86
|
+
/** Browse the trash bin instead of the live library: true ⇒ ONLY trashed rows, omitted/false ⇒
|
|
87
|
+
* ONLY live rows (the default — trashed files never appear in a normal listing). */
|
|
88
|
+
trashedOnly?: boolean;
|
|
89
|
+
};
|
|
90
|
+
/** Server-authoritative smart-view classification, computed ONCE at write time and stored.
|
|
91
|
+
* `isText` (a non-binary inline file) ⇒ code; else by mime. */
|
|
92
|
+
export declare function classifyFileType(mime: string, isText?: boolean): FileType;
|
|
93
|
+
/** The catalogue contract. A SQLite implementation is the production default; the interface lets
|
|
94
|
+
* an app swap it. All calls are owner-scoped via {@link MediaCtx} (one tenant never sees another). */
|
|
95
|
+
/** Cheap owner-scoped aggregates (COUNT/SUM over the files table) — the admin
|
|
96
|
+
* surface's "site stats" source. Private items report count + bytes only
|
|
97
|
+
* (names/kinds stay sealed). */
|
|
98
|
+
export type CatalogueStats = {
|
|
99
|
+
files: number;
|
|
100
|
+
bytes: number;
|
|
101
|
+
byType: Partial<Record<FileType, number>>;
|
|
102
|
+
privateItems: number;
|
|
103
|
+
privateBytes: number;
|
|
104
|
+
processing: number;
|
|
105
|
+
failed: number;
|
|
106
|
+
};
|
|
107
|
+
export interface FileCatalogue {
|
|
108
|
+
list(ctx: MediaCtx, opts?: ListFilesOptions): Promise<FilePage>;
|
|
109
|
+
get(ctx: MediaCtx, id: string): Promise<FileRecord | null>;
|
|
110
|
+
/** Owner-scoped aggregates for admin/stats surfaces. */
|
|
111
|
+
stats(ctx: MediaCtx): Promise<CatalogueStats>;
|
|
112
|
+
tagFacets(ctx: MediaCtx, opts?: {
|
|
113
|
+
includePrivate?: boolean;
|
|
114
|
+
}): Promise<TagFacet[]>;
|
|
115
|
+
/** Ingest a public upload: persist bytes, classify, kick off processing (thumbnail/HLS). */
|
|
116
|
+
ingest(ctx: MediaCtx, upload: {
|
|
117
|
+
name: string;
|
|
118
|
+
mime: string;
|
|
119
|
+
body: ReadableStream<Uint8Array>;
|
|
120
|
+
size?: number;
|
|
121
|
+
tags?: string[];
|
|
122
|
+
}): Promise<FileRecord>;
|
|
123
|
+
/** Register bytes uploaded OUT OF BAND (a chunked upload sent direct to the backend) — insert the
|
|
124
|
+
* row + kick processing WITHOUT re-storing bytes (they already sit at the catalogue's key for this
|
|
125
|
+
* id: fsSourceKey for video, fileBinaryKey otherwise). */
|
|
126
|
+
registerUploaded(ctx: MediaCtx, upload: {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
mime: string;
|
|
130
|
+
size: number;
|
|
131
|
+
tags?: string[];
|
|
132
|
+
}): Promise<FileRecord>;
|
|
133
|
+
/** Rename / retag a file. */
|
|
134
|
+
update(ctx: MediaCtx, id: string, patch: {
|
|
135
|
+
name?: string;
|
|
136
|
+
tags?: string[];
|
|
137
|
+
}): Promise<FileRecord | null>;
|
|
138
|
+
/** Overwrite a file's BYTES in place — the identity (id, tags, collections, favorites, view
|
|
139
|
+
* history) survives while mime/size/fileType re-derive, stale derivatives are wiped, and
|
|
140
|
+
* processing re-kicks (video transcode / image thumbnail). Public files only — replacing an
|
|
141
|
+
* encrypted private item throws (delete + re-upload instead). Returns the refreshed record,
|
|
142
|
+
* or null if the id is unknown/other-owner. */
|
|
143
|
+
replaceContent(ctx: MediaCtx, id: string, upload: {
|
|
144
|
+
mime: string;
|
|
145
|
+
body: ReadableStream<Uint8Array>;
|
|
146
|
+
size?: number;
|
|
147
|
+
name?: string;
|
|
148
|
+
}): Promise<FileRecord | null>;
|
|
149
|
+
/** PERMANENTLY delete a file + all its derivatives + tags + collection memberships. This is the
|
|
150
|
+
* hard-delete — a deliberate second action, typically only called on an already-trashed row
|
|
151
|
+
* (see {@link trash}). No-ops (never throws) for an unknown or other-owner id. */
|
|
152
|
+
remove(ctx: MediaCtx, id: string): Promise<void>;
|
|
153
|
+
/** Soft-delete: marks the file trashed (hidden from normal `list()`) without touching its bytes
|
|
154
|
+
* or metadata — recoverable via {@link restore}. The PRIMARY delete action a user takes.
|
|
155
|
+
* No-ops for an unknown/other-owner id or one already trashed. */
|
|
156
|
+
trash(ctx: MediaCtx, id: string): Promise<void>;
|
|
157
|
+
/** Undo `trash()`: clears trashed_at so the file reappears in normal listings. No-ops for an
|
|
158
|
+
* unknown/other-owner id. */
|
|
159
|
+
restore(ctx: MediaCtx, id: string): Promise<void>;
|
|
160
|
+
/** Ingest file(s) from a LOCAL path already on this host's disk (a directory imports every
|
|
161
|
+
* regular file inside it, recursively; a file imports just itself). Each imported file is
|
|
162
|
+
* tagged with its immediate containing folder name (the catalogue is flat — folder structure
|
|
163
|
+
* survives as a tag, not a tree) plus any `tags` passed. Unreadable/unsupported entries are
|
|
164
|
+
* skipped, not thrown — the return value reports both. */
|
|
165
|
+
importFromPath(ctx: MediaCtx, absPath: string, opts?: {
|
|
166
|
+
tags?: string[];
|
|
167
|
+
}): Promise<{
|
|
168
|
+
imported: FileRecord[];
|
|
169
|
+
skipped: {
|
|
170
|
+
path: string;
|
|
171
|
+
reason: string;
|
|
172
|
+
}[];
|
|
173
|
+
}>;
|
|
174
|
+
/** Recover from a restart that lost in-flight transcodes: re-kick public video rows still marked
|
|
175
|
+
* pending/processing (their source persists), and mark stuck private rows failed. Call once at
|
|
176
|
+
* app startup. Returns how many were re-queued vs. failed. */
|
|
177
|
+
recoverProcessing(): {
|
|
178
|
+
requeued: number;
|
|
179
|
+
failed: number;
|
|
180
|
+
};
|
|
181
|
+
/** Backfill grid thumbnails for finished public video rows that have no poster (landed while
|
|
182
|
+
* transcoding was off, or failed before the poster step). Re-kicks the poster pipeline for each.
|
|
183
|
+
* Returns how many were re-queued. */
|
|
184
|
+
backfillVideoPosters(): {
|
|
185
|
+
requeued: number;
|
|
186
|
+
};
|
|
187
|
+
/** Backfill grid thumbnails for public image rows with no poster (e.g. HEIC photos whose sharp
|
|
188
|
+
* thumbnail failed before the ffmpeg fallback). Re-kicks the image pipeline. */
|
|
189
|
+
backfillImageThumbnails(): {
|
|
190
|
+
requeued: number;
|
|
191
|
+
};
|
|
192
|
+
/** List the owner's collections. Unlock-gated ones are excluded unless `includeUnlockable`. */
|
|
193
|
+
listCollections(ctx: MediaCtx, opts?: {
|
|
194
|
+
includeUnlockable?: boolean;
|
|
195
|
+
}): Promise<FileCollection[]>;
|
|
196
|
+
/** Create a custom collection. Name is trimmed; empty names throw. Duplicate names are allowed. */
|
|
197
|
+
createCollection(ctx: MediaCtx, input: {
|
|
198
|
+
name: string;
|
|
199
|
+
visibility?: CollectionVisibility;
|
|
200
|
+
}): Promise<FileCollection>;
|
|
201
|
+
/** Rename or change visibility of an owned collection. Returns null if not found. */
|
|
202
|
+
updateCollection(ctx: MediaCtx, id: string, patch: {
|
|
203
|
+
name?: string;
|
|
204
|
+
visibility?: CollectionVisibility;
|
|
205
|
+
}): Promise<FileCollection | null>;
|
|
206
|
+
/** Delete a custom collection (and its memberships). Never deletes files. Favorites cannot be
|
|
207
|
+
* removed — throws an Error. */
|
|
208
|
+
removeCollection(ctx: MediaCtx, id: string): Promise<void>;
|
|
209
|
+
/** Add files to a collection. Idempotent (INSERT OR IGNORE). Unknown or other-owner file ids are
|
|
210
|
+
* silently skipped. */
|
|
211
|
+
addToCollection(ctx: MediaCtx, collectionId: string, fileIds: string[]): Promise<void>;
|
|
212
|
+
/** Remove files from a collection. Silently ignores ids not in the collection. */
|
|
213
|
+
removeFromCollection(ctx: MediaCtx, collectionId: string, fileIds: string[]): Promise<void>;
|
|
214
|
+
/** Toggle the favorite state of a file. Lazily creates the owner's Favorites collection on first
|
|
215
|
+
* use. */
|
|
216
|
+
setFavorite(ctx: MediaCtx, fileId: string, favorite: boolean): Promise<void>;
|
|
217
|
+
/** Record a view/watch-open of a file (upserts its last-viewed timestamp + bumps a count) — the
|
|
218
|
+
* signal the `lastViewed` sort orders by. No-ops for an unknown or other-owner file id. */
|
|
219
|
+
recordView(ctx: MediaCtx, fileId: string): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
/** Resolve a video FileRecord to a PlayableVideo (URLs baked server-side). Non-video ⇒ null. */
|
|
222
|
+
export type FileToPlayable = (ctx: MediaCtx, file: FileRecord) => Promise<PlayableVideo | null>;
|
|
223
|
+
/** How a private item's content key is wrapped: `standard` (server-recoverable) vs `deniable`
|
|
224
|
+
* (password-derived; zero-knowledge). Mirrors private-media `KeyMode`. */
|
|
225
|
+
export type PrivateKeyMode = 'standard' | 'deniable';
|
|
226
|
+
/** The OPAQUE locked-index row — only non-sensitive metadata (so a user can prune media whose
|
|
227
|
+
* password is lost). NEVER name/tags/thumbnail: those are sealed. No set identifier is exposed. */
|
|
228
|
+
export type PrivateFileItem = {
|
|
229
|
+
id: string;
|
|
230
|
+
size: number;
|
|
231
|
+
createdAt: number;
|
|
232
|
+
processingStatus: ProcessingStatus;
|
|
233
|
+
processingProgress: number;
|
|
234
|
+
hasHls: boolean;
|
|
235
|
+
};
|
|
236
|
+
/** Private-media capability of a catalogue. `createSqliteFileCatalogue` implements this alongside
|
|
237
|
+
* {@link FileCatalogue}, storing private items as sealed rows in the SAME table (one source of
|
|
238
|
+
* truth). Membership is by decrypt-success — no stored password/set hash.
|
|
239
|
+
*
|
|
240
|
+
* Deliberately small: the catalogue owns only INGEST (the new private-upload path). Everything
|
|
241
|
+
* else — the locked index, unlock, try-a-password reveal, the gated content-key route, and the
|
|
242
|
+
* encrypted-HLS byte serving — is the existing `mountPrivateRoutes`, which operates through a
|
|
243
|
+
* {@link PrivateIndex}. `asPrivateIndex()` is the view that lets those routes read/write the
|
|
244
|
+
* catalogue's private rows, so there is exactly one store and one set of routes. */
|
|
245
|
+
export interface PrivateFileCatalogue {
|
|
246
|
+
/** Ingest an encrypted video: stage plaintext (never persisted), transcode to AES-128 HLS, seal
|
|
247
|
+
* the descriptor + wrap the content key under `setKey`, and insert a private row. */
|
|
248
|
+
ingestPrivate(ctx: MediaCtx, upload: {
|
|
249
|
+
name: string;
|
|
250
|
+
mime: string;
|
|
251
|
+
body: ReadableStream<Uint8Array>;
|
|
252
|
+
size?: number;
|
|
253
|
+
tags?: string[];
|
|
254
|
+
}, opts: {
|
|
255
|
+
setKey: Buffer;
|
|
256
|
+
keyMode: PrivateKeyMode;
|
|
257
|
+
}): Promise<PrivateFileItem>;
|
|
258
|
+
/** A {@link PrivateIndex} view over this catalogue's private rows — pass to `mountPrivateRoutes`
|
|
259
|
+
* so unlock/index/reveal/key/HLS all operate on the catalogue. `remove` also drops the bytes. */
|
|
260
|
+
asPrivateIndex(): PrivateIndex;
|
|
261
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Every listing order the catalogue understands — the single source of truth the route layer
|
|
2
|
+
* validates against and the `FileSort` union derives from. `recent` = newest first (default);
|
|
3
|
+
* `oldest` = oldest first; `name` = A→Z; `size` = largest first; `random` = a seeded shuffle
|
|
4
|
+
* (the seed is minted per session and folded into the cursor, so a visit reshuffles but its own
|
|
5
|
+
* pagination is stable); `lastViewed` = most-recently-opened first (never-viewed files last). */
|
|
6
|
+
export const FILE_SORTS = ['recent', 'oldest', 'name', 'size', 'random', 'lastViewed'];
|
|
7
|
+
const DOC_MIME = /pdf|word|excel|spreadsheet|presentation|csv|officedocument|rtf|epub|^text\//;
|
|
8
|
+
/** Server-authoritative smart-view classification, computed ONCE at write time and stored.
|
|
9
|
+
* `isText` (a non-binary inline file) ⇒ code; else by mime. */
|
|
10
|
+
export function classifyFileType(mime, isText = false) {
|
|
11
|
+
if (mime.startsWith('image/'))
|
|
12
|
+
return 'image';
|
|
13
|
+
if (mime.startsWith('video/'))
|
|
14
|
+
return 'video';
|
|
15
|
+
if (mime.startsWith('audio/'))
|
|
16
|
+
return 'audio';
|
|
17
|
+
if (isText)
|
|
18
|
+
return 'code';
|
|
19
|
+
if (DOC_MIME.test(mime))
|
|
20
|
+
return 'doc';
|
|
21
|
+
return 'other';
|
|
22
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** True when a browser can render this image mime inline (no JPEG derivative needed). */
|
|
2
|
+
export declare function isBrowserRenderableImage(mime: string): boolean;
|
|
3
|
+
export declare function ffmpegImageThumbnail(bytes: Uint8Array, workDir: string, id: string, maxWidth?: number): Promise<Uint8Array | null>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { imageThumbnailArgs, runFfmpegProgress } from '../media-bun';
|
|
4
|
+
// Image mimes browsers CANNOT render inline (so the full-view <img> needs a JPEG derivative, not
|
|
5
|
+
// the original). Chrome/Firefox reject HEIC/HEIF; the rest (jpeg/png/webp/gif/avif/bmp/svg) render
|
|
6
|
+
// natively. Safari can show HEIC, but we normalize for everyone.
|
|
7
|
+
const BROWSER_INCOMPATIBLE_IMAGE_MIMES = new Set([
|
|
8
|
+
'image/heic',
|
|
9
|
+
'image/heif',
|
|
10
|
+
'image/heic-sequence',
|
|
11
|
+
'image/heif-sequence',
|
|
12
|
+
]);
|
|
13
|
+
/** True when a browser can render this image mime inline (no JPEG derivative needed). */
|
|
14
|
+
export function isBrowserRenderableImage(mime) {
|
|
15
|
+
return mime.startsWith('image/') && !BROWSER_INCOMPATIBLE_IMAGE_MIMES.has(mime.toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
// Produce a width-capped JPEG thumbnail from arbitrary image bytes using ffmpeg — the fallback for
|
|
18
|
+
// formats sharp's prebuilt binary can't decode (notably HEIC/HEVC iPhone photos: the licensed-out
|
|
19
|
+
// libheif build only accepts AVIF). Best-effort: returns null on any failure. Uses a temp file
|
|
20
|
+
// because ffmpeg's HEIF demuxer needs seekable input (a pipe won't do).
|
|
21
|
+
export async function ffmpegImageThumbnail(bytes, workDir, id, maxWidth = 512) {
|
|
22
|
+
const dir = path.join(workDir, '_imgthumb', id);
|
|
23
|
+
try {
|
|
24
|
+
await mkdir(dir, { recursive: true });
|
|
25
|
+
const inPath = path.join(dir, 'in.bin');
|
|
26
|
+
await Bun.write(inPath, bytes);
|
|
27
|
+
const outPath = path.join(dir, 'out.jpg');
|
|
28
|
+
let stderrTail = '';
|
|
29
|
+
const ok = await runFfmpegProgress(imageThumbnailArgs(inPath, outPath, maxWidth), 0, undefined, (tail) => {
|
|
30
|
+
stderrTail = tail;
|
|
31
|
+
});
|
|
32
|
+
if (!ok) {
|
|
33
|
+
// The "undecodable bytes" fixture test deliberately hits this path too — tagged
|
|
34
|
+
// `[expected-in-tests]` (grep-filterable) rather than silenced, so a real decode
|
|
35
|
+
// regression still surfaces.
|
|
36
|
+
console.warn(`[imageThumbnail][expected-in-tests] ffmpeg decode failed: ${stderrTail.slice(-400)}`);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const out = Bun.file(outPath);
|
|
40
|
+
if (!(await out.exists()))
|
|
41
|
+
return null;
|
|
42
|
+
return new Uint8Array(await out.arrayBuffer());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { DERIVATIVE_SUFFIXES, hlsMasterKeyFor, hlsPrefixFor, isDerivativeKey, posterKeyFor, spriteKeyFor, spriteVttKeyFor, webPlayableKeyFor, } from './videoDerivativeKeys';
|
|
2
|
+
export type { BinaryServerConfig, FileTokenClaims, FileTokenSigner } from './binaryServerStore';
|
|
3
|
+
export { createBinaryServerStore } from './binaryServerStore';
|
|
4
|
+
export { type BinaryServerProcessingConfig, createBinaryServerProcessing, } from './binaryServerProcessing';
|
|
5
|
+
export { createTranscodeJobClient, type TranscodeJobClient, type TranscodeJobClientConfig, type TranscodeJobResult, type TranscodeJobStatus, type TranscodeJobStatusBody, } from './transcodeJobs';
|
|
6
|
+
export { createDiskStore } from './diskStore';
|
|
7
|
+
export { type CatalogueStats, type ChunkedInit, type ChunkedUploadService, catalogueUploadKey, classifyFileType, createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createChunkedUploadService, createFilesStack, createFileToPlayable, createFileUrls, createLedgeredDeletes, createSqliteFileCatalogue, createStorageOpsLedger, createStorageReconciler, FILE_CATALOGUE_MIGRATIONS, type FileCatalogue, type FilePage, type FileRecord, type FilesStack, type FilesStackMountAuth, type FilesStackOptions, type FileToPlayable, type FileType, type FileUrls, fileBinaryKey, type ImageDerivatives, type ImageDerivativesOptions, type ListFilesOptions, type MintChunkUpload, type MountFileRoutesOptions, mountFileRoutes, type PrivateFileCatalogue, type PrivateFileItem, type PrivateKeyMode, type SqliteFileCatalogueOptions, type StorageAction, type StorageOpRow, type StorageOpsLedger, type StorageReconciler, type VideoProcessor, } from './files';
|
|
8
|
+
export { type CorsResolver, type MountMediaOptions, mountMediaRoutes } from './mountMediaRoutes';
|
|
9
|
+
export { createDiskSource, createMediaUrls, type DiskSource, type MediaUrls, SourceRegistry, } from './sources';
|
|
10
|
+
export { createS3ObjectStore, type S3ClientLike, type S3ObjectHandle, type S3ObjectStoreOptions, type S3ObjectWriter, } from './s3ObjectStore';
|
|
11
|
+
export { decodeRange, type RangeDecision, type ServeRangeOpts, serveRange } from './streamRange';
|
|
12
|
+
export { createStreamTokenService, type StreamGrant, type StreamTokenService } from './streamToken';
|
|
13
|
+
export { fsHlsFileKey, fsHlsMasterKey, fsHlsPrefix, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, type MediaCtx, type MediaGallerySource, type MediaSource, type MediaStore, type PhotoSource, privateFsKey, } from './types';
|
|
14
|
+
export { type ProcessOptions, type ProcessResult, processVideo, } from './videoProcessor';
|
|
15
|
+
export { objectStoreAsMediaStore } from './objectStoreMediaStore';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// cursedbelt-server/storage — byte-storage layer for the media pipeline: MediaStore adapters
|
|
2
|
+
// (disk / binary-server), a Range→Response helper, path-embedded stream tokens, a source
|
|
3
|
+
// registry, and the ingest→derivatives orchestrator. Builds on `server/media` (codec work) +
|
|
4
|
+
// `server/auth` (jose tokens). The `PlayableVideo` contract lives in `cursedbelt-core/media`.
|
|
5
|
+
// The derivative NAME contract — the seam between binary-server (which writes these names),
|
|
6
|
+
// satellite-kit (which re-exports them for the satellite apps) and this package's own token
|
|
7
|
+
// minting. Pure string transforms: nothing here can resolve a stored object.
|
|
8
|
+
export { DERIVATIVE_SUFFIXES, hlsMasterKeyFor, hlsPrefixFor, isDerivativeKey, posterKeyFor, spriteKeyFor, spriteVttKeyFor, webPlayableKeyFor, } from './videoDerivativeKeys';
|
|
9
|
+
export { createBinaryServerStore } from './binaryServerStore';
|
|
10
|
+
export { createBinaryServerProcessing, } from './binaryServerProcessing';
|
|
11
|
+
// The transcode API as a plain job client, for apps whose binaries do NOT live in the file
|
|
12
|
+
// catalogue (apps/notes keys attachments by sha256). One derivative pipeline, two doors.
|
|
13
|
+
export { createTranscodeJobClient, } from './transcodeJobs';
|
|
14
|
+
export { createDiskStore } from './diskStore';
|
|
15
|
+
// The FileCatalogue: a SQLite-backed tagged flat file system over this storage layer — one
|
|
16
|
+
// source of truth for a user's binaries (the video gallery is a video-filtered view over it).
|
|
17
|
+
export { catalogueUploadKey, classifyFileType, createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createChunkedUploadService, createFilesStack, createFileToPlayable, createFileUrls, createLedgeredDeletes, createSqliteFileCatalogue, createStorageOpsLedger, createStorageReconciler, FILE_CATALOGUE_MIGRATIONS, fileBinaryKey, mountFileRoutes, } from './files';
|
|
18
|
+
export { mountMediaRoutes } from './mountMediaRoutes';
|
|
19
|
+
export { createDiskSource, createMediaUrls, SourceRegistry, } from './sources';
|
|
20
|
+
// The saas profile's `ObjectStore` (08 §1b). Its local counterpart is
|
|
21
|
+
// `createFsObjectStore` in `cwip/storage` — same interface, different issuer.
|
|
22
|
+
export { createS3ObjectStore, } from './s3ObjectStore';
|
|
23
|
+
export { decodeRange, serveRange } from './streamRange';
|
|
24
|
+
export { createStreamTokenService } from './streamToken';
|
|
25
|
+
export { fsHlsFileKey, fsHlsMasterKey, fsHlsPrefix, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, privateFsKey, } from './types';
|
|
26
|
+
export { processVideo, } from './videoProcessor';
|
|
27
|
+
export { objectStoreAsMediaStore } from './objectStoreMediaStore';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
|
+
import type { SourceRegistry } from './sources';
|
|
3
|
+
import type { StreamGrant, StreamTokenService } from './streamToken';
|
|
4
|
+
import { type MediaCtx, type MediaStore } from './types';
|
|
5
|
+
/** Resolves the Access-Control-Allow-Origin for a request origin: a fixed origin, an allowlist
|
|
6
|
+
* (echoes the request origin when allowed), or a custom resolver (return null to deny). */
|
|
7
|
+
export type CorsResolver = string | string[] | ((requestOrigin: string | undefined) => string | null | undefined);
|
|
8
|
+
export type MountMediaOptions = {
|
|
9
|
+
/** Byte backend shared by this mount (keys carry the owner). */
|
|
10
|
+
store: MediaStore;
|
|
11
|
+
/** Verifies the path token → owner/source/video grant. */
|
|
12
|
+
token: StreamTokenService;
|
|
13
|
+
/** On-server route mount path (path-only; must share its trailing path with the createMediaUrls
|
|
14
|
+
* baseUrl). Default '/_media'. */
|
|
15
|
+
basePath?: string;
|
|
16
|
+
/** Sources, for upload ingest. */
|
|
17
|
+
registry?: SourceRegistry;
|
|
18
|
+
/** Cross-origin access for split-origin deploys (media on a different origin than the SPA).
|
|
19
|
+
* Omit for same-origin. */
|
|
20
|
+
cors?: CorsResolver;
|
|
21
|
+
/** Authorizes an upload request (it CAN send the Bearer, unlike a media element). Return a
|
|
22
|
+
* MediaCtx to allow, or null to reject. Omit ⇒ uploads are disabled on this mount. */
|
|
23
|
+
authorizeUpload?: (c: Context) => Promise<MediaCtx | null> | MediaCtx | null;
|
|
24
|
+
/** Optional hook to decrypt bytes for the private (pfs/…) keyspace before serving. Wired by
|
|
25
|
+
* the private-media layer; omitted ⇒ all bytes served as stored. */
|
|
26
|
+
decryptStream?: (stream: ReadableStream<Uint8Array>, grant: StreamGrant) => ReadableStream<Uint8Array>;
|
|
27
|
+
};
|
|
28
|
+
export declare function mountMediaRoutes(app: Hono, opts: MountMediaOptions): void;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { serveRange } from './streamRange';
|
|
2
|
+
import { fsDisplayKey, fsHlsFileKey, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, } from './types';
|
|
3
|
+
const EXPOSE_HEADERS = 'Content-Range, Content-Length, Accept-Ranges';
|
|
4
|
+
function resolveAcao(cors, reqOrigin) {
|
|
5
|
+
if (!cors)
|
|
6
|
+
return null;
|
|
7
|
+
if (typeof cors === 'string')
|
|
8
|
+
return cors;
|
|
9
|
+
if (Array.isArray(cors))
|
|
10
|
+
return reqOrigin && cors.includes(reqOrigin) ? reqOrigin : null;
|
|
11
|
+
return cors(reqOrigin) ?? null;
|
|
12
|
+
}
|
|
13
|
+
export function mountMediaRoutes(app, opts) {
|
|
14
|
+
const base = opts.basePath ?? '/_media';
|
|
15
|
+
const { store, token } = opts;
|
|
16
|
+
// Stamp CORS headers on a media response (and expose the range headers a cross-origin seeking
|
|
17
|
+
// <video> needs). A no-op when `cors` is unset (same-origin).
|
|
18
|
+
const cors = (c, res) => {
|
|
19
|
+
const acao = resolveAcao(opts.cors, c.req.header('origin'));
|
|
20
|
+
if (acao) {
|
|
21
|
+
res.headers.set('Access-Control-Allow-Origin', acao);
|
|
22
|
+
res.headers.set('Access-Control-Expose-Headers', EXPOSE_HEADERS);
|
|
23
|
+
res.headers.append('Vary', 'Origin');
|
|
24
|
+
}
|
|
25
|
+
return res;
|
|
26
|
+
};
|
|
27
|
+
// Preflight for cross-origin media/upload requests.
|
|
28
|
+
if (opts.cors) {
|
|
29
|
+
app.options(`${base}/*`, (c) => {
|
|
30
|
+
const acao = resolveAcao(opts.cors, c.req.header('origin'));
|
|
31
|
+
const headers = {
|
|
32
|
+
'Access-Control-Allow-Methods': 'GET, PUT, OPTIONS',
|
|
33
|
+
'Access-Control-Allow-Headers': 'Range, Content-Type, X-Filename, Authorization',
|
|
34
|
+
'Access-Control-Max-Age': '86400',
|
|
35
|
+
};
|
|
36
|
+
if (acao) {
|
|
37
|
+
headers['Access-Control-Allow-Origin'] = acao;
|
|
38
|
+
headers.Vary = 'Origin';
|
|
39
|
+
}
|
|
40
|
+
return c.body(null, 204, headers);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Resolve + validate the token a serve route carries, and confirm it matches the source/video
|
|
44
|
+
// in the path (so a token minted for video A can't fetch video B).
|
|
45
|
+
const grantFor = async (c, expectSource, expectVideo) => {
|
|
46
|
+
const raw = c.req.param('token');
|
|
47
|
+
if (!raw)
|
|
48
|
+
return null;
|
|
49
|
+
const grant = await token.verify(raw);
|
|
50
|
+
if (!grant)
|
|
51
|
+
return null;
|
|
52
|
+
if (grant.sourceId !== expectSource || grant.videoId !== expectVideo)
|
|
53
|
+
return null;
|
|
54
|
+
return grant;
|
|
55
|
+
};
|
|
56
|
+
// GET progressive MP4 (Range). Prefers the faststart derivative; falls back to the raw source
|
|
57
|
+
// when the pipeline hasn't produced one (ffmpeg absent / still processing).
|
|
58
|
+
app.get(`${base}/s/:token/media/:source/:video`, async (c) => {
|
|
59
|
+
const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
|
|
60
|
+
if (!grant)
|
|
61
|
+
return c.body(null, 401);
|
|
62
|
+
const mp4 = fsMp4Key(grant.owner, grant.videoId);
|
|
63
|
+
const key = (await store.stat(mp4)) ? mp4 : fsSourceKey(grant.owner, grant.videoId);
|
|
64
|
+
if (store.redirectUrl) {
|
|
65
|
+
const url = await store.redirectUrl(key, { mime: 'video/mp4' });
|
|
66
|
+
if (url)
|
|
67
|
+
return cors(c, c.redirect(url, 303));
|
|
68
|
+
}
|
|
69
|
+
return cors(c, await serveRange(store, key, 'video/mp4', c.req.header('range'), {
|
|
70
|
+
contentDisposition: 'inline',
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
// GET an HLS file (master.m3u8 / <height>/playlist.m3u8 / <height>/seg_*.ts). Relative URIs
|
|
74
|
+
// inside the playlists resolve under this same /s/:token/hls/:source/:video/ prefix.
|
|
75
|
+
app.get(`${base}/s/:token/hls/:source/:video/*`, async (c) => {
|
|
76
|
+
const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
|
|
77
|
+
if (!grant)
|
|
78
|
+
return c.body(null, 401);
|
|
79
|
+
const prefix = `${base}/s/${c.req.param('token')}/hls/${grant.sourceId}/${grant.videoId}/`;
|
|
80
|
+
const rel = c.req.path.slice(prefix.length);
|
|
81
|
+
if (!rel || rel.includes('..'))
|
|
82
|
+
return c.body(null, 400);
|
|
83
|
+
const mime = rel.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t';
|
|
84
|
+
return cors(c, await serveRange(store, fsHlsFileKey(grant.owner, grant.videoId, rel), mime, c.req.header('range')));
|
|
85
|
+
});
|
|
86
|
+
app.get(`${base}/s/:token/poster/:source/:video`, async (c) => {
|
|
87
|
+
const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
|
|
88
|
+
if (!grant)
|
|
89
|
+
return c.body(null, 401);
|
|
90
|
+
return cors(c, await serveRange(store, fsPosterKey(grant.owner, grant.videoId), 'image/jpeg', c.req.header('range')));
|
|
91
|
+
});
|
|
92
|
+
// Full-size browser-safe JPEG for images the browser can't render natively (HEIC/HEIF). Serves
|
|
93
|
+
// the display derivative; falls back to the small poster if it isn't built yet (the boot backfill
|
|
94
|
+
// produces it, so the fallback is momentary) — never a raw HEIC the browser would fail on.
|
|
95
|
+
app.get(`${base}/s/:token/display/:source/:video`, async (c) => {
|
|
96
|
+
const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
|
|
97
|
+
if (!grant)
|
|
98
|
+
return c.body(null, 401);
|
|
99
|
+
const displayKey = fsDisplayKey(grant.owner, grant.videoId);
|
|
100
|
+
const key = (await store.stat(displayKey))
|
|
101
|
+
? displayKey
|
|
102
|
+
: fsPosterKey(grant.owner, grant.videoId);
|
|
103
|
+
return cors(c, await serveRange(store, key, 'image/jpeg', c.req.header('range')));
|
|
104
|
+
});
|
|
105
|
+
// Sprite sheet + its WebVTT track, served as siblings so the VTT's relative 'sprites.jpg' cue
|
|
106
|
+
// targets resolve to the jpg route.
|
|
107
|
+
app.get(`${base}/s/:token/sprite/:source/:video/:file`, async (c) => {
|
|
108
|
+
const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
|
|
109
|
+
if (!grant)
|
|
110
|
+
return c.body(null, 401);
|
|
111
|
+
const file = c.req.param('file');
|
|
112
|
+
if (file === 'sprites.vtt') {
|
|
113
|
+
return cors(c, await serveRange(store, fsVttKey(grant.owner, grant.videoId), 'text/vtt', c.req.header('range')));
|
|
114
|
+
}
|
|
115
|
+
if (file === 'sprites.jpg') {
|
|
116
|
+
return cors(c, await serveRange(store, fsSpriteKey(grant.owner, grant.videoId), 'image/jpeg', c.req.header('range')));
|
|
117
|
+
}
|
|
118
|
+
return c.body(null, 404);
|
|
119
|
+
});
|
|
120
|
+
// PUT upload → ingest. Guarded by the app-supplied authorizer (a fetch CAN send the Bearer).
|
|
121
|
+
const { authorizeUpload, registry } = opts;
|
|
122
|
+
if (authorizeUpload && registry) {
|
|
123
|
+
app.put(`${base}/upload/:source`, async (c) => {
|
|
124
|
+
const ctx = await authorizeUpload(c);
|
|
125
|
+
if (!ctx)
|
|
126
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
127
|
+
const source = registry.get(c.req.param('source'));
|
|
128
|
+
if (!source?.ingest)
|
|
129
|
+
return c.json({ error: 'source not found' }, 404);
|
|
130
|
+
const body = c.req.raw.body;
|
|
131
|
+
if (!body)
|
|
132
|
+
return c.json({ error: 'empty body' }, 400);
|
|
133
|
+
const video = await source.ingest(ctx, {
|
|
134
|
+
name: c.req.header('x-filename') ?? 'upload',
|
|
135
|
+
mime: c.req.header('content-type') ?? 'application/octet-stream',
|
|
136
|
+
body,
|
|
137
|
+
});
|
|
138
|
+
return c.json({ video });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ObjectStore } from 'cwip/storage';
|
|
2
|
+
import type { MediaStore } from './types';
|
|
3
|
+
/** Adapt an {@link ObjectStore} to the {@link MediaStore} contract `processVideo` expects. */
|
|
4
|
+
export declare function objectStoreAsMediaStore(objects: ObjectStore): MediaStore;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Bridge the framework's stream-based `ObjectStore` (cwip/storage) to the
|
|
2
|
+
// byte/stream `MediaStore` the proven `processVideo` pipeline drives (§2d).
|
|
3
|
+
//
|
|
4
|
+
// `processVideo` predates the engine `ObjectStore`: it speaks `MediaStore`
|
|
5
|
+
// (put(bytes)/putStream/get(bytes)/stat/localPath). The engine wires an
|
|
6
|
+
// `ObjectStore` (put(stream)/get(stream)/stat/delete/signedUrl). Rather than
|
|
7
|
+
// fork the whole transcode orchestrator onto a second store type, the asset
|
|
8
|
+
// `transcode` handler wraps its `ObjectStore` in this adapter and reuses the
|
|
9
|
+
// pipeline unchanged. There is deliberately NO `localPath`, so `processVideo`
|
|
10
|
+
// materialises the source by download — correct for a stream store where the
|
|
11
|
+
// bytes may live off-box (S3/R2).
|
|
12
|
+
const streamToBytes = async (stream) => new Uint8Array(await new Response(stream).arrayBuffer());
|
|
13
|
+
const bytesToStream = (bytes) => new ReadableStream({
|
|
14
|
+
start(controller) {
|
|
15
|
+
controller.enqueue(bytes);
|
|
16
|
+
controller.close();
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
/** Adapt an {@link ObjectStore} to the {@link MediaStore} contract `processVideo` expects. */
|
|
20
|
+
export function objectStoreAsMediaStore(objects) {
|
|
21
|
+
return {
|
|
22
|
+
async put(key, bytes, mime) {
|
|
23
|
+
await objects.put(key, bytesToStream(bytes), { contentType: mime });
|
|
24
|
+
},
|
|
25
|
+
async putStream(key, body, mime) {
|
|
26
|
+
await objects.put(key, body, { contentType: mime });
|
|
27
|
+
},
|
|
28
|
+
async get(key) {
|
|
29
|
+
const stream = await objects.get(key);
|
|
30
|
+
return stream ? await streamToBytes(stream) : null;
|
|
31
|
+
},
|
|
32
|
+
async remove(key) {
|
|
33
|
+
await objects.delete(key);
|
|
34
|
+
},
|
|
35
|
+
async removePrefix() {
|
|
36
|
+
// The `ObjectStore` contract has no prefix delete, and `processVideo` never
|
|
37
|
+
// calls this — surface misuse loudly rather than silently no-op.
|
|
38
|
+
throw new Error('objectStoreAsMediaStore.removePrefix is not supported over an ObjectStore.');
|
|
39
|
+
},
|
|
40
|
+
async stat(key) {
|
|
41
|
+
const stat = await objects.stat(key);
|
|
42
|
+
return stat ? { size: stat.size } : null;
|
|
43
|
+
},
|
|
44
|
+
async getRange(key, range) {
|
|
45
|
+
const stream = await objects.get(key);
|
|
46
|
+
if (!stream)
|
|
47
|
+
return null;
|
|
48
|
+
if (!range)
|
|
49
|
+
return { stream };
|
|
50
|
+
// Whole-object read then slice: unused by `processVideo`, present only to
|
|
51
|
+
// satisfy the interface. A hot Range-serving path uses the real store.
|
|
52
|
+
const bytes = await streamToBytes(stream);
|
|
53
|
+
return { stream: bytesToStream(bytes.subarray(range.start, range.end + 1)) };
|
|
54
|
+
},
|
|
55
|
+
// No `localPath`: force `processVideo` down its download path (stream store).
|
|
56
|
+
};
|
|
57
|
+
}
|