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,104 @@
|
|
|
1
|
+
// The binary-server transcode API as a plain job client — submit, poll, and the two
|
|
2
|
+
// derivative shapes the fleet actually asks for.
|
|
3
|
+
//
|
|
4
|
+
// It was already here, inlined inside `createBinaryServerProcessing`, but only reachable
|
|
5
|
+
// through that module's two seams (`VideoProcessor` / `ImageDerivatives`), which are shaped
|
|
6
|
+
// for the cursedbelt file CATALOGUE: they take a `MediaStore`, a `workDir`, an owner and a
|
|
7
|
+
// catalogue id. An app that stores binaries some other way — apps/notes keys attachments by
|
|
8
|
+
// sha256 through satellite-kit's binary-store and has no catalogue at all — could not reach
|
|
9
|
+
// the pipeline without either adopting the catalogue or writing a second HTTP client.
|
|
10
|
+
//
|
|
11
|
+
// So the transport lives here and the catalogue seams are built on top of it. There is ONE
|
|
12
|
+
// derivative pipeline in the fleet, and this is its entry point.
|
|
13
|
+
//
|
|
14
|
+
// ── Why binary-server rather than deriving locally ──────────────────────────────────────
|
|
15
|
+
// Derivatives are made where the SOURCE BYTES already are. binary-server is the fleet's
|
|
16
|
+
// binary store, it runs on a Mac with real CPU, disk, ffmpeg and (for HEIC) sips, and the
|
|
17
|
+
// derived bytes land back in the same tenant beside their source. Pulling a multi-GB video
|
|
18
|
+
// down to an app host to run ffmpeg there defeats the point, and the shared prod t3.micro
|
|
19
|
+
// has neither the disk nor the ffmpeg to do it at all.
|
|
20
|
+
//
|
|
21
|
+
// ── Who may call it ─────────────────────────────────────────────────────────────────────
|
|
22
|
+
// `/api/transcode` is server-to-server: it is gated on the shared internal secret AND, since
|
|
23
|
+
// the 2026-08-05 delete-surface hardening, 404s outright from binary-server's public host.
|
|
24
|
+
// A prod-hosted app therefore CANNOT drive it; the Mac side of an app must.
|
|
25
|
+
const DEFAULT_POLL_MS = 1500;
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27
|
+
export function createTranscodeJobClient(cfg) {
|
|
28
|
+
const fetchImpl = cfg.fetchImpl ?? fetch;
|
|
29
|
+
const sleep = cfg.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
30
|
+
const pollMs = cfg.pollMs ?? DEFAULT_POLL_MS;
|
|
31
|
+
const timeoutMs = cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32
|
+
const appKey = (key) => `${cfg.appId}/${key}`;
|
|
33
|
+
const headers = {
|
|
34
|
+
'content-type': 'application/json',
|
|
35
|
+
'x-internal-secret': cfg.internalSecret,
|
|
36
|
+
};
|
|
37
|
+
const submit = async (body) => {
|
|
38
|
+
const res = await fetchImpl(`${cfg.baseUrl}/api/transcode`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers,
|
|
41
|
+
body: JSON.stringify(body),
|
|
42
|
+
});
|
|
43
|
+
if (res.status !== 202) {
|
|
44
|
+
throw new Error(`binary-server transcode submit: ${res.status} ${await res.text()}`);
|
|
45
|
+
}
|
|
46
|
+
const { jobId } = (await res.json());
|
|
47
|
+
return jobId;
|
|
48
|
+
};
|
|
49
|
+
const poll = async (jobId, onProgress) => {
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
for (;;) {
|
|
52
|
+
const res = await fetchImpl(`${cfg.baseUrl}/api/transcode/${encodeURIComponent(jobId)}`, {
|
|
53
|
+
headers,
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
throw new Error(`binary-server transcode poll: ${res.status}`);
|
|
57
|
+
const job = (await res.json());
|
|
58
|
+
if (job.status === 'ready' || job.status === 'failed')
|
|
59
|
+
return job;
|
|
60
|
+
if (typeof job.progress === 'number')
|
|
61
|
+
onProgress?.(job.progress);
|
|
62
|
+
if (Date.now() > deadline)
|
|
63
|
+
throw new Error(`binary-server transcode ${jobId}: timed out`);
|
|
64
|
+
await sleep(pollMs);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const imageThumbnail = async (opts) => {
|
|
68
|
+
const jobId = await submit({
|
|
69
|
+
strategy: 'image',
|
|
70
|
+
nodeId: opts.nodeId ?? opts.sourceKey,
|
|
71
|
+
sourceKey: appKey(opts.sourceKey),
|
|
72
|
+
// The image strategy reads `binaryKey` as the source it fetches; the two are the same
|
|
73
|
+
// object here because nothing is being replaced, only derived from.
|
|
74
|
+
binaryKey: appKey(opts.sourceKey),
|
|
75
|
+
posterKey: appKey(opts.thumbKey),
|
|
76
|
+
...(opts.displayKey ? { displayKey: appKey(opts.displayKey) } : {}),
|
|
77
|
+
});
|
|
78
|
+
const job = await poll(jobId);
|
|
79
|
+
if (job.status !== 'ready')
|
|
80
|
+
return { hasThumbnail: false, hasDisplay: false };
|
|
81
|
+
return {
|
|
82
|
+
hasThumbnail: job.result?.hasThumbnail ?? false,
|
|
83
|
+
hasDisplay: job.result?.hasDisplay ?? false,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const videoPoster = async (opts) => {
|
|
87
|
+
const jobId = await submit({
|
|
88
|
+
strategy: 'library',
|
|
89
|
+
nodeId: opts.nodeId ?? opts.sourceKey,
|
|
90
|
+
sourceKey: appKey(opts.sourceKey),
|
|
91
|
+
binaryKey: appKey(opts.sourceKey),
|
|
92
|
+
posterKey: appKey(opts.posterKey),
|
|
93
|
+
posterOnly: true,
|
|
94
|
+
});
|
|
95
|
+
const job = await poll(jobId);
|
|
96
|
+
if (job.status !== 'ready')
|
|
97
|
+
return { hasPoster: false, durationSeconds: 0 };
|
|
98
|
+
return {
|
|
99
|
+
hasPoster: job.result?.hasPoster ?? false,
|
|
100
|
+
durationSeconds: job.result?.durationSeconds ?? 0,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
return { appKey, submit, poll, imageThumbnail, videoPoster };
|
|
104
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { ProcessingStatus } from 'cursedbelt-core/media';
|
|
2
|
+
/** Identity/tenant context threaded from an app's auth into every store/source call, so keys
|
|
3
|
+
* namespace per-owner and one tenant can never read another's media. */
|
|
4
|
+
export type MediaCtx = {
|
|
5
|
+
userId: string;
|
|
6
|
+
tenantId: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The byte-storage adapter. The caller owns the key namespace (see keyspace helpers). One
|
|
10
|
+
* interface, many backends. Web-standard streams throughout (Bun/Hono) — the stream hands
|
|
11
|
+
* straight to a `Response` body with no node-stream conversion.
|
|
12
|
+
*/
|
|
13
|
+
export interface MediaStore {
|
|
14
|
+
/** Store bytes under an opaque key. */
|
|
15
|
+
put(key: string, bytes: Uint8Array, mime: string): Promise<void>;
|
|
16
|
+
/** Stream a body to storage without buffering it — for multi-GB writes (a transcode source,
|
|
17
|
+
* HLS segments) where a Uint8Array would be a full heap copy. */
|
|
18
|
+
putStream(key: string, body: ReadableStream<Uint8Array>, mime: string, opts?: {
|
|
19
|
+
contentLength?: number;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
/** Read bytes back, or null if the key is gone. */
|
|
22
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
23
|
+
/** Best-effort delete; must not throw if the key is already absent. */
|
|
24
|
+
remove(key: string): Promise<void>;
|
|
25
|
+
/** Recursively delete everything under a key prefix (an HLS tree, a user's media). */
|
|
26
|
+
removePrefix(prefix: string): Promise<void>;
|
|
27
|
+
/** Total byte size of a stored object, or null if gone. Answers Range requests without
|
|
28
|
+
* reading the whole object first. */
|
|
29
|
+
stat(key: string): Promise<{
|
|
30
|
+
size: number;
|
|
31
|
+
} | null>;
|
|
32
|
+
/** A web stream for the inclusive byte range [start, end], or the whole object when `range`
|
|
33
|
+
* is omitted; null if gone. Serves 206 Partial Content for seeking. */
|
|
34
|
+
getRange(key: string, range?: {
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
stream: ReadableStream<Uint8Array>;
|
|
39
|
+
} | null>;
|
|
40
|
+
/** Absolute on-disk path for a key if this backend is local — lets ffmpeg read the source
|
|
41
|
+
* directly. null/absent for remote backends (the pipeline downloads the source first). */
|
|
42
|
+
localPath?(key: string): Promise<string | null>;
|
|
43
|
+
/** For a user-facing download, some backends (binary-server) prefer redirecting the browser
|
|
44
|
+
* to a signed edge URL instead of proxying bytes. Returns the URL, or null to fall back. */
|
|
45
|
+
redirectUrl?(key: string, opts: {
|
|
46
|
+
mime: string;
|
|
47
|
+
filename?: string;
|
|
48
|
+
}): Promise<string | null>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
|
|
52
|
+
* canonical shape for a binary-server file token across the fleet: cursedbelt's
|
|
53
|
+
* injected-signer `createBinaryServerStore` and `@satellites/satellite-kit`'s
|
|
54
|
+
* self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
|
|
55
|
+
* the claim set must never drift — satellite-kit imports this exact type rather than
|
|
56
|
+
* redeclaring it, so a token one side signs is always shaped for the other to read. The
|
|
57
|
+
* app's signer turns these into an RS256 JWT.
|
|
58
|
+
*/
|
|
59
|
+
export type FileTokenClaims = {
|
|
60
|
+
/** Exact authorized path, app-prefixed: `<app>/<key>`. */
|
|
61
|
+
k?: string;
|
|
62
|
+
/** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
|
|
63
|
+
p?: string;
|
|
64
|
+
/** Acting user/app id — audit trail on binary-server's events, never authz. */
|
|
65
|
+
uid?: string;
|
|
66
|
+
/** Chunk-session id (must match across every chunk of one upload). */
|
|
67
|
+
sid?: string;
|
|
68
|
+
/** Chunk index (0-based). */
|
|
69
|
+
ci?: number;
|
|
70
|
+
/** Total chunk count. */
|
|
71
|
+
tc?: number;
|
|
72
|
+
/** Download filename → Content-Disposition. */
|
|
73
|
+
fn?: string;
|
|
74
|
+
dl?: 'inline' | 'attachment';
|
|
75
|
+
/** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
|
|
76
|
+
* (in addition to the internal secret) — a plain read/download token can never be replayed
|
|
77
|
+
* as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
|
|
78
|
+
op?: 'delete';
|
|
79
|
+
};
|
|
80
|
+
export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
|
|
81
|
+
export type BinaryServerConfig = {
|
|
82
|
+
/** e.g. 'https://binary-server.cursedalchemy.com'. */
|
|
83
|
+
baseUrl: string;
|
|
84
|
+
/** App namespace (path + token scope), e.g. 'cursedalchemy'. */
|
|
85
|
+
appId: string;
|
|
86
|
+
/** App-provided RS256 signer (app holds the private key). */
|
|
87
|
+
signFileToken: FileTokenSigner;
|
|
88
|
+
/** Shared secret for internal ops (delete). Sent as x-internal-secret. */
|
|
89
|
+
internalSecret?: string;
|
|
90
|
+
};
|
|
91
|
+
/** A source of videos for a gallery/catalogue. Apps implement this differently (a disk folder,
|
|
92
|
+
* a binary-server app, ca's Files tree + private set). Consumers are source-agnostic. */
|
|
93
|
+
export interface MediaSource {
|
|
94
|
+
id: string;
|
|
95
|
+
title: string;
|
|
96
|
+
list(ctx: MediaCtx, opts: {
|
|
97
|
+
cursor?: string;
|
|
98
|
+
tag?: string;
|
|
99
|
+
query?: string;
|
|
100
|
+
limit?: number;
|
|
101
|
+
/** Listing order (backend maps to its own sorts; default newest-first). `random` is a
|
|
102
|
+
* seeded shuffle (stable across its own pagination); `lastViewed` orders by watch-open
|
|
103
|
+
* recency. A cursor is only valid under the sort that minted it. */
|
|
104
|
+
sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
|
|
105
|
+
}): Promise<{
|
|
106
|
+
items: import('cursedbelt-core/media').PlayableVideo[];
|
|
107
|
+
nextCursor?: string;
|
|
108
|
+
}>;
|
|
109
|
+
resolve(ctx: MediaCtx, videoId: string): Promise<import('cursedbelt-core/media').PlayableVideo | null>;
|
|
110
|
+
tags?(ctx: MediaCtx): Promise<string[]>;
|
|
111
|
+
ingest?(ctx: MediaCtx, upload: {
|
|
112
|
+
name: string;
|
|
113
|
+
mime: string;
|
|
114
|
+
body: ReadableStream<Uint8Array>;
|
|
115
|
+
size?: number;
|
|
116
|
+
}): Promise<import('cursedbelt-core/media').PlayableVideo>;
|
|
117
|
+
remove?(ctx: MediaCtx, videoId: string): Promise<void>;
|
|
118
|
+
/** Record a watch-open of a video — the signal the `lastViewed` sort orders by. Optional: a
|
|
119
|
+
* read-only source (a disk folder) has nothing to record. */
|
|
120
|
+
recordView?(ctx: MediaCtx, videoId: string): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/** A source of photos for an image gallery — the image sibling of {@link MediaSource}. Same
|
|
123
|
+
* shape (cursor listing, tag facets, ingest, view ledger), photo-typed items. */
|
|
124
|
+
export interface PhotoSource {
|
|
125
|
+
id: string;
|
|
126
|
+
title: string;
|
|
127
|
+
list(ctx: MediaCtx, opts: {
|
|
128
|
+
cursor?: string;
|
|
129
|
+
tag?: string;
|
|
130
|
+
query?: string;
|
|
131
|
+
limit?: number;
|
|
132
|
+
sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
|
|
133
|
+
}): Promise<{
|
|
134
|
+
items: import('cursedbelt-core/media').GalleryPhoto[];
|
|
135
|
+
nextCursor?: string;
|
|
136
|
+
}>;
|
|
137
|
+
resolve(ctx: MediaCtx, photoId: string): Promise<import('cursedbelt-core/media').GalleryPhoto | null>;
|
|
138
|
+
tags?(ctx: MediaCtx): Promise<string[]>;
|
|
139
|
+
ingest?(ctx: MediaCtx, upload: {
|
|
140
|
+
name: string;
|
|
141
|
+
mime: string;
|
|
142
|
+
body: ReadableStream<Uint8Array>;
|
|
143
|
+
size?: number;
|
|
144
|
+
tags?: string[];
|
|
145
|
+
}): Promise<import('cursedbelt-core/media').GalleryPhoto>;
|
|
146
|
+
remove?(ctx: MediaCtx, photoId: string): Promise<void>;
|
|
147
|
+
recordView?(ctx: MediaCtx, photoId: string): Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
/** A source of MIXED media (photos + videos) for a combined gallery — the union of
|
|
150
|
+
* {@link MediaSource} and {@link PhotoSource} over ONE catalogue. `list`/`resolve`
|
|
151
|
+
* yield `GalleryMedia` (a `mediaKind`-tagged photo|video), and a `kind` filter narrows
|
|
152
|
+
* to just photos or just videos. Same cursor/tag/sort listing contract as its siblings. */
|
|
153
|
+
export interface MediaGallerySource {
|
|
154
|
+
id: string;
|
|
155
|
+
title: string;
|
|
156
|
+
list(ctx: MediaCtx, opts: {
|
|
157
|
+
/** Narrow the mixed feed — `all` (default) lists photos + videos. */
|
|
158
|
+
kind?: import('cursedbelt-core/media').MediaKindFilter;
|
|
159
|
+
cursor?: string;
|
|
160
|
+
tag?: string;
|
|
161
|
+
query?: string;
|
|
162
|
+
limit?: number;
|
|
163
|
+
sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
|
|
164
|
+
}): Promise<{
|
|
165
|
+
items: import('cursedbelt-core/media').GalleryMedia[];
|
|
166
|
+
nextCursor?: string;
|
|
167
|
+
}>;
|
|
168
|
+
resolve(ctx: MediaCtx, itemId: string): Promise<import('cursedbelt-core/media').GalleryMedia | null>;
|
|
169
|
+
tags?(ctx: MediaCtx): Promise<string[]>;
|
|
170
|
+
remove?(ctx: MediaCtx, itemId: string): Promise<void>;
|
|
171
|
+
recordView?(ctx: MediaCtx, itemId: string): Promise<void>;
|
|
172
|
+
}
|
|
173
|
+
export type { ProcessingStatus };
|
|
174
|
+
export declare const fsSourceKey: (owner: string, id: string) => string;
|
|
175
|
+
export declare const fsMp4Key: (owner: string, id: string) => string;
|
|
176
|
+
export declare const fsHlsPrefix: (owner: string, id: string) => string;
|
|
177
|
+
export declare const fsHlsMasterKey: (owner: string, id: string) => string;
|
|
178
|
+
export declare const fsHlsFileKey: (owner: string, id: string, rel: string) => string;
|
|
179
|
+
export declare const fsPosterKey: (owner: string, id: string) => string;
|
|
180
|
+
/** Full-size browser-safe JPEG derivative for images the browser can't render natively (HEIC/HEIF).
|
|
181
|
+
* The lightbox's full-view URL points here instead of the raw original. */
|
|
182
|
+
export declare const fsDisplayKey: (owner: string, id: string) => string;
|
|
183
|
+
export declare const fsSpriteKey: (owner: string, id: string) => string;
|
|
184
|
+
export declare const fsVttKey: (owner: string, id: string) => string;
|
|
185
|
+
/** Private (encrypted) namespace — the private-media layer stores ciphertext here. The HLS
|
|
186
|
+
* segments under it are AES-128-encrypted at rest (the player fetches the content key via the
|
|
187
|
+
* gated key route and decrypts); a distinct keyspace from public `hls/` so the two never collide. */
|
|
188
|
+
export declare const privateFsKey: (owner: string, id: string) => string;
|
|
189
|
+
export declare const privateHlsPrefix: (owner: string, id: string) => string;
|
|
190
|
+
export declare const privateHlsMasterKey: (owner: string, id: string) => string;
|
|
191
|
+
export declare const privateHlsFileKey: (owner: string, id: string, rel: string) => string;
|
|
192
|
+
export declare const privatePosterKey: (owner: string, id: string) => string;
|
|
193
|
+
export declare const privateSourceKey: (owner: string, id: string) => string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// cursedbelt-server/storage — the byte-storage layer cursedbelt's media pipeline was
|
|
2
|
+
// missing. `server/media` (media-bun) owns the codec/pixel work; this owns *where the bytes
|
|
3
|
+
// live and how they're served*: a backend-agnostic MediaStore (disk / binary-server / S3), a
|
|
4
|
+
// Range→Response helper for seekable <video>, path-embedded stream tokens (so a media element
|
|
5
|
+
// with no Bearer can still be authorized), a source registry, and the ingest→derivatives
|
|
6
|
+
// pipeline glue. Ported/generalised from ca's proven media stack; the app maps its own records
|
|
7
|
+
// to a MediaSource. Pairs with the `PlayableVideo` contract in `cursedbelt-core/media`.
|
|
8
|
+
//
|
|
9
|
+
// 🔴 THIS FILE IS A PUBLISHED LEAF — `cursedbelt-server/storage/types` — AND IT MUST STAY
|
|
10
|
+
// IMPORT-FREE AT RUNTIME. `src/leafSubpathsImportNothing.spec.ts` imports it in a fixture with
|
|
11
|
+
// no packages resolvable at all and fails if it throws, so an `import` here that survives
|
|
12
|
+
// transpilation (anything that is not `import type`) turns that spec red rather than turning
|
|
13
|
+
// up in a consumer's install. The subpath exists because `./server/storage`'s own index is a
|
|
14
|
+
// barrel: it reaches `streamToken` → `auth/jwt` → jose, the whole SQLite file catalogue under
|
|
15
|
+
// `files/`, and a lazily-imported `sharp` — while an app that wants only the SHAPE of a file
|
|
16
|
+
// token (apps/collections wanted exactly `FileTokenClaims`, a type TypeScript erases) was made
|
|
17
|
+
// to install a one-time-password library to get it. Add types here freely; add a value import
|
|
18
|
+
// here never.
|
|
19
|
+
// ── Keyspace helpers ──────────────────────────────────────────────────────────────
|
|
20
|
+
// One source of truth for every storage key. Sources + serve routes + the pipeline all
|
|
21
|
+
// address bytes through these, so a key layout is never hand-spelled twice.
|
|
22
|
+
export const fsSourceKey = (owner, id) => `source/${owner}/${id}`;
|
|
23
|
+
export const fsMp4Key = (owner, id) => `fs/${owner}/${id}.mp4`;
|
|
24
|
+
export const fsHlsPrefix = (owner, id) => `hls/${owner}/${id}`;
|
|
25
|
+
export const fsHlsMasterKey = (owner, id) => `hls/${owner}/${id}/master.m3u8`;
|
|
26
|
+
export const fsHlsFileKey = (owner, id, rel) => `hls/${owner}/${id}/${rel}`;
|
|
27
|
+
export const fsPosterKey = (owner, id) => `fs/${owner}/${id}-poster.jpg`;
|
|
28
|
+
/** Full-size browser-safe JPEG derivative for images the browser can't render natively (HEIC/HEIF).
|
|
29
|
+
* The lightbox's full-view URL points here instead of the raw original. */
|
|
30
|
+
export const fsDisplayKey = (owner, id) => `fs/${owner}/${id}-display.jpg`;
|
|
31
|
+
export const fsSpriteKey = (owner, id) => `fs/${owner}/${id}-sprites.jpg`;
|
|
32
|
+
export const fsVttKey = (owner, id) => `fs/${owner}/${id}-sprites.vtt`;
|
|
33
|
+
/** Private (encrypted) namespace — the private-media layer stores ciphertext here. The HLS
|
|
34
|
+
* segments under it are AES-128-encrypted at rest (the player fetches the content key via the
|
|
35
|
+
* gated key route and decrypts); a distinct keyspace from public `hls/` so the two never collide. */
|
|
36
|
+
export const privateFsKey = (owner, id) => `pfs/${owner}/${id}`;
|
|
37
|
+
export const privateHlsPrefix = (owner, id) => `pfs/${owner}/${id}/hls`;
|
|
38
|
+
export const privateHlsMasterKey = (owner, id) => `pfs/${owner}/${id}/hls/master.m3u8`;
|
|
39
|
+
export const privateHlsFileKey = (owner, id, rel) => `pfs/${owner}/${id}/hls/${rel}`;
|
|
40
|
+
export const privatePosterKey = (owner, id) => `pfs/${owner}/${id}/poster.enc`;
|
|
41
|
+
export const privateSourceKey = (owner, id) => `pfs/${owner}/${id}/source.enc`;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── The NAMES of every video derivative binary-server writes ────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* A derivative's key is COMPUTED from its source's, never bookkept. A consumer finds one by
|
|
5
|
+
* deriving the name and asking `/meta` whether it exists yet, so there is no webhook, no job
|
|
6
|
+
* id, no schema column and nothing to reconcile: the absence of the object IS the "not ready
|
|
7
|
+
* yet" state, and it answers itself.
|
|
8
|
+
*
|
|
9
|
+
* ── 🔴 Why these live in cursedbelt ─────────────────────────────────────────────────────
|
|
10
|
+
* Owner, 2026-08-14: *"Make it in cb because many apps may move to cloudflare and share the
|
|
11
|
+
* logic."* This contract is the seam between three repos that must never disagree —
|
|
12
|
+
* binary-server WRITES these names, `@satellites/satellite-kit` re-exports them for the
|
|
13
|
+
* satellite apps, and cursedbelt's own `binaryServerStore` mints the tokens that authorize
|
|
14
|
+
* them. A second hand-typed `${key}.web.mp4` anywhere in that triangle is a silent
|
|
15
|
+
* no-preview the day binary-server renames anything, and that has already happened once:
|
|
16
|
+
* `apps/collections` had the contract and `apps/family` shipped video without it, so family
|
|
17
|
+
* had no faststart stream, no scrub previews and no processing state at all.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 What is shared here is the STRATEGY, never the media. Nothing in this module can resolve
|
|
20
|
+
* a stored object — every function is a pure string transform over a key the caller already
|
|
21
|
+
* holds. Each app is its own binary-server tenant with its own signing key, so family cannot
|
|
22
|
+
* reach a collections object through anything here, and that must stay true.
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 This is NOT the `fs*` keyspace in `./types.ts`, and they must not be merged ───────
|
|
25
|
+
* `fsHlsMasterKey(owner, id)` → `hls/<owner>/<id>/master.m3u8` and its siblings address the
|
|
26
|
+
* FileCatalogue: a caller that holds an owner and an item id, whose derivatives live in their
|
|
27
|
+
* own top-level namespaces. This module addresses a caller that holds a binary-server KEY and
|
|
28
|
+
* derives sibling names from it, which is what makes "ask /meta for a name you computed" work
|
|
29
|
+
* with no catalogue at all.
|
|
30
|
+
*
|
|
31
|
+
* Two consumers, two shapes, both correct:
|
|
32
|
+
* · `fs*` — cursedbelt's own FileCatalogue / the `library` transcode strategy.
|
|
33
|
+
* · this module — binary-server's derivative convention, used by the satellite apps.
|
|
34
|
+
*
|
|
35
|
+
* Collapsing either into the other renames objects that already exist in the store, and a
|
|
36
|
+
* renamed derivative is indistinguishable from an unbuilt one (absence is the "not ready"
|
|
37
|
+
* state) — so the visible result is every video in the fleet losing its previews at once,
|
|
38
|
+
* silently, with the rebuild costing hours of ffmpeg.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The browser-playable remux: the same pictures with the index at the front.
|
|
42
|
+
*
|
|
43
|
+
* A phone MP4/MOV stores its `moov` index at the END, and a browser cannot decode frame one
|
|
44
|
+
* until it has that index — so it downloads the whole file first. Measured on the owner's
|
|
45
|
+
* 90,339,606-byte upload (2026-08-13): the index began at byte 90,305,514, 99.96% of the way
|
|
46
|
+
* in. That is the "videos stay on a spinner for minutes" report, and no amount of Range
|
|
47
|
+
* support, caching or edge tuning fixes it. A `+faststart` remux is a STREAM COPY for
|
|
48
|
+
* h264+aac sources, which is what phones produce: no re-encode, nothing lost.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* ── 🔴 THE ENCODER GENERATION IN THE NAME (2026-09-09) ──────────────────────────────────
|
|
52
|
+
*
|
|
53
|
+
* binary-server rebuilds every video derivative when its encoder improves, and a rebuild
|
|
54
|
+
* used to write **the same filename with different bytes**. That silently cancels out an
|
|
55
|
+
* `immutable` cache lifetime — a browser told `immutable` never re-checks, for a year, and
|
|
56
|
+
* **that cannot be revoked for a copy already on somebody's phone**. So the owner's decision
|
|
57
|
+
* (A, 2026-09-09) is that new bytes get a new address: `clip.web.mp4` → `clip.g2.web.mp4`.
|
|
58
|
+
*
|
|
59
|
+
* 🔴 **Generation 1 is UNTAGGED and always will be.** Every derivative in the store today is
|
|
60
|
+
* at the untagged name; tagging generation 1 would orphan all of them and re-encode the
|
|
61
|
+
* whole library for byte-identical output. Untagged is not a legacy case — it IS generation
|
|
62
|
+
* one.
|
|
63
|
+
*
|
|
64
|
+
* 🔴 **This constant must match binary-server's `VIDEO_ENCODER_GENERATION`**
|
|
65
|
+
* (`src/media/encoderGeneration.ts`). They are in different repos and neither can import the
|
|
66
|
+
* other, so binary-server carries a test that goes RED the moment somebody bumps its number,
|
|
67
|
+
* naming this file. If you are here because of that test: bump this in the same commit, and
|
|
68
|
+
* remember that `notes`, `collections` and `family` all reach these names through
|
|
69
|
+
* `@satellites/satellite-kit/video-derivatives`. Getting it wrong is not an error anywhere —
|
|
70
|
+
* it is a 404 that reads as "no preview available", on every video.
|
|
71
|
+
*/
|
|
72
|
+
export declare const VIDEO_KEY_GENERATION = 1;
|
|
73
|
+
/** The generation tag a derivative's name carries. Empty for generation 1 — see above. */
|
|
74
|
+
export declare const videoGenerationTag: (generation?: number) => string;
|
|
75
|
+
export declare const webPlayableKeyFor: (key: string, generation?: number) => string;
|
|
76
|
+
/** The scrub-preview sprite sheet — tiled frames from across the clip, as one JPEG. */
|
|
77
|
+
export declare const spriteKeyFor: (key: string) => string;
|
|
78
|
+
/** The WebVTT mapping a playhead position onto a tile of {@link spriteKeyFor}. */
|
|
79
|
+
export declare const spriteVttKeyFor: (key: string) => string;
|
|
80
|
+
/**
|
|
81
|
+
* The single still a gallery draws on a tile.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately the SAME name a still photograph's thumbnail gets, so a consumer has one rule
|
|
84
|
+
* — *the tile for anything is `<key>.thumb.jpg`, and its absence means not ready* — rather
|
|
85
|
+
* than one rule per media kind. The sprite sheet is not a substitute: it is a GRID, so drawing
|
|
86
|
+
* it on a tile shows a contact sheet, and using one cell means every consumer carries the
|
|
87
|
+
* VTT's geometry in order to crop it.
|
|
88
|
+
*/
|
|
89
|
+
export declare const posterKeyFor: (key: string) => string;
|
|
90
|
+
/**
|
|
91
|
+
* ── The HLS tree: `<key>.hls/` ──────────────────────────────────────────────────────────
|
|
92
|
+
*
|
|
93
|
+
* 🔴 ONE container, and the trailing name is load-bearing. binary-server's `storage.ts`
|
|
94
|
+
* co-locates a container's whole subtree under a single shard and `removeContainer()` wipes it
|
|
95
|
+
* atomically — so a ladder that is one container is deleted, migrated and reconciled as one
|
|
96
|
+
* thing. Segments scattered under sibling keys would be none of those, and orphaning half a
|
|
97
|
+
* ladder is worse than having none: the master would keep advertising rungs whose segments
|
|
98
|
+
* 404, which a player retries rather than falls back from.
|
|
99
|
+
*
|
|
100
|
+
* 🔴 Why HLS at all, now that Range works. The Range bug (an edge Worker answering 200 + the
|
|
101
|
+
* whole 209 MB to a ranged read) was fixed 2026-08-14 and is NOT what this is for. A single
|
|
102
|
+
* progressive MP4, however well served, cannot ADAPT: a weak cell connection stalls on a
|
|
103
|
+
* 23 Mbps master instead of dropping to a lower rung. Each segment is also a small object that
|
|
104
|
+
* caches WHOLE at the edge, which sidesteps both the 512 MiB single-entry ceiling and the
|
|
105
|
+
* segment-assembly machinery that exists to work around it for big films.
|
|
106
|
+
*/
|
|
107
|
+
export declare const hlsPrefixFor: (key: string, generation?: number) => string;
|
|
108
|
+
/**
|
|
109
|
+
* The playlist a player is pointed at — the one object whose presence means "the ladder is
|
|
110
|
+
* ready". It is written LAST, after every rung, so a consumer that finds it can trust that
|
|
111
|
+
* what it names exists. Absence is normal and means "play the faststart remux".
|
|
112
|
+
*/
|
|
113
|
+
export declare const hlsMasterKeyFor: (key: string, generation?: number) => string;
|
|
114
|
+
/**
|
|
115
|
+
* Every derivative suffix, for the "is this key itself a derivative" question.
|
|
116
|
+
*
|
|
117
|
+
* 🔴 Load-bearing in three directions, each of which has already cost something:
|
|
118
|
+
* 1. A derivative must never become the source of another, or a sweep builds
|
|
119
|
+
* `clip.web.mp4.web.mp4` and then `…web.mp4.web.mp4.web.mp4`, forever.
|
|
120
|
+
* 2. A sweep must not count a derivative as work still owed, or the backlog is permanently
|
|
121
|
+
* non-empty and ffmpeg re-runs on every pass.
|
|
122
|
+
* 3. A duplicate scan must not propose a file against its own derivative. They share a title
|
|
123
|
+
* by construction, so every video would pair with its own remux and a review queue would
|
|
124
|
+
* contain nothing else.
|
|
125
|
+
*/
|
|
126
|
+
export declare const DERIVATIVE_SUFFIXES: readonly [".web.mp4", ".sprite.jpg", ".sprite.vtt", ".thumb.jpg", ".web.jpg"];
|
|
127
|
+
export declare const isDerivativeKey: (key: string) => boolean;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── The NAMES of every video derivative binary-server writes ────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* A derivative's key is COMPUTED from its source's, never bookkept. A consumer finds one by
|
|
5
|
+
* deriving the name and asking `/meta` whether it exists yet, so there is no webhook, no job
|
|
6
|
+
* id, no schema column and nothing to reconcile: the absence of the object IS the "not ready
|
|
7
|
+
* yet" state, and it answers itself.
|
|
8
|
+
*
|
|
9
|
+
* ── 🔴 Why these live in cursedbelt ─────────────────────────────────────────────────────
|
|
10
|
+
* Owner, 2026-08-14: *"Make it in cb because many apps may move to cloudflare and share the
|
|
11
|
+
* logic."* This contract is the seam between three repos that must never disagree —
|
|
12
|
+
* binary-server WRITES these names, `@satellites/satellite-kit` re-exports them for the
|
|
13
|
+
* satellite apps, and cursedbelt's own `binaryServerStore` mints the tokens that authorize
|
|
14
|
+
* them. A second hand-typed `${key}.web.mp4` anywhere in that triangle is a silent
|
|
15
|
+
* no-preview the day binary-server renames anything, and that has already happened once:
|
|
16
|
+
* `apps/collections` had the contract and `apps/family` shipped video without it, so family
|
|
17
|
+
* had no faststart stream, no scrub previews and no processing state at all.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 What is shared here is the STRATEGY, never the media. Nothing in this module can resolve
|
|
20
|
+
* a stored object — every function is a pure string transform over a key the caller already
|
|
21
|
+
* holds. Each app is its own binary-server tenant with its own signing key, so family cannot
|
|
22
|
+
* reach a collections object through anything here, and that must stay true.
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 This is NOT the `fs*` keyspace in `./types.ts`, and they must not be merged ───────
|
|
25
|
+
* `fsHlsMasterKey(owner, id)` → `hls/<owner>/<id>/master.m3u8` and its siblings address the
|
|
26
|
+
* FileCatalogue: a caller that holds an owner and an item id, whose derivatives live in their
|
|
27
|
+
* own top-level namespaces. This module addresses a caller that holds a binary-server KEY and
|
|
28
|
+
* derives sibling names from it, which is what makes "ask /meta for a name you computed" work
|
|
29
|
+
* with no catalogue at all.
|
|
30
|
+
*
|
|
31
|
+
* Two consumers, two shapes, both correct:
|
|
32
|
+
* · `fs*` — cursedbelt's own FileCatalogue / the `library` transcode strategy.
|
|
33
|
+
* · this module — binary-server's derivative convention, used by the satellite apps.
|
|
34
|
+
*
|
|
35
|
+
* Collapsing either into the other renames objects that already exist in the store, and a
|
|
36
|
+
* renamed derivative is indistinguishable from an unbuilt one (absence is the "not ready"
|
|
37
|
+
* state) — so the visible result is every video in the fleet losing its previews at once,
|
|
38
|
+
* silently, with the rebuild costing hours of ffmpeg.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The browser-playable remux: the same pictures with the index at the front.
|
|
42
|
+
*
|
|
43
|
+
* A phone MP4/MOV stores its `moov` index at the END, and a browser cannot decode frame one
|
|
44
|
+
* until it has that index — so it downloads the whole file first. Measured on the owner's
|
|
45
|
+
* 90,339,606-byte upload (2026-08-13): the index began at byte 90,305,514, 99.96% of the way
|
|
46
|
+
* in. That is the "videos stay on a spinner for minutes" report, and no amount of Range
|
|
47
|
+
* support, caching or edge tuning fixes it. A `+faststart` remux is a STREAM COPY for
|
|
48
|
+
* h264+aac sources, which is what phones produce: no re-encode, nothing lost.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* ── 🔴 THE ENCODER GENERATION IN THE NAME (2026-09-09) ──────────────────────────────────
|
|
52
|
+
*
|
|
53
|
+
* binary-server rebuilds every video derivative when its encoder improves, and a rebuild
|
|
54
|
+
* used to write **the same filename with different bytes**. That silently cancels out an
|
|
55
|
+
* `immutable` cache lifetime — a browser told `immutable` never re-checks, for a year, and
|
|
56
|
+
* **that cannot be revoked for a copy already on somebody's phone**. So the owner's decision
|
|
57
|
+
* (A, 2026-09-09) is that new bytes get a new address: `clip.web.mp4` → `clip.g2.web.mp4`.
|
|
58
|
+
*
|
|
59
|
+
* 🔴 **Generation 1 is UNTAGGED and always will be.** Every derivative in the store today is
|
|
60
|
+
* at the untagged name; tagging generation 1 would orphan all of them and re-encode the
|
|
61
|
+
* whole library for byte-identical output. Untagged is not a legacy case — it IS generation
|
|
62
|
+
* one.
|
|
63
|
+
*
|
|
64
|
+
* 🔴 **This constant must match binary-server's `VIDEO_ENCODER_GENERATION`**
|
|
65
|
+
* (`src/media/encoderGeneration.ts`). They are in different repos and neither can import the
|
|
66
|
+
* other, so binary-server carries a test that goes RED the moment somebody bumps its number,
|
|
67
|
+
* naming this file. If you are here because of that test: bump this in the same commit, and
|
|
68
|
+
* remember that `notes`, `collections` and `family` all reach these names through
|
|
69
|
+
* `@satellites/satellite-kit/video-derivatives`. Getting it wrong is not an error anywhere —
|
|
70
|
+
* it is a 404 that reads as "no preview available", on every video.
|
|
71
|
+
*/
|
|
72
|
+
export const VIDEO_KEY_GENERATION = 1;
|
|
73
|
+
/** The generation tag a derivative's name carries. Empty for generation 1 — see above. */
|
|
74
|
+
export const videoGenerationTag = (generation = VIDEO_KEY_GENERATION) => generation <= 1 ? "" : `.g${generation}`;
|
|
75
|
+
export const webPlayableKeyFor = (key, generation = VIDEO_KEY_GENERATION) => `${key}${videoGenerationTag(generation)}.web.mp4`;
|
|
76
|
+
/** The scrub-preview sprite sheet — tiled frames from across the clip, as one JPEG. */
|
|
77
|
+
export const spriteKeyFor = (key) => `${key}.sprite.jpg`;
|
|
78
|
+
/** The WebVTT mapping a playhead position onto a tile of {@link spriteKeyFor}. */
|
|
79
|
+
export const spriteVttKeyFor = (key) => `${key}.sprite.vtt`;
|
|
80
|
+
/**
|
|
81
|
+
* The single still a gallery draws on a tile.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately the SAME name a still photograph's thumbnail gets, so a consumer has one rule
|
|
84
|
+
* — *the tile for anything is `<key>.thumb.jpg`, and its absence means not ready* — rather
|
|
85
|
+
* than one rule per media kind. The sprite sheet is not a substitute: it is a GRID, so drawing
|
|
86
|
+
* it on a tile shows a contact sheet, and using one cell means every consumer carries the
|
|
87
|
+
* VTT's geometry in order to crop it.
|
|
88
|
+
*/
|
|
89
|
+
export const posterKeyFor = (key) => `${key}.thumb.jpg`;
|
|
90
|
+
/**
|
|
91
|
+
* ── The HLS tree: `<key>.hls/` ──────────────────────────────────────────────────────────
|
|
92
|
+
*
|
|
93
|
+
* 🔴 ONE container, and the trailing name is load-bearing. binary-server's `storage.ts`
|
|
94
|
+
* co-locates a container's whole subtree under a single shard and `removeContainer()` wipes it
|
|
95
|
+
* atomically — so a ladder that is one container is deleted, migrated and reconciled as one
|
|
96
|
+
* thing. Segments scattered under sibling keys would be none of those, and orphaning half a
|
|
97
|
+
* ladder is worse than having none: the master would keep advertising rungs whose segments
|
|
98
|
+
* 404, which a player retries rather than falls back from.
|
|
99
|
+
*
|
|
100
|
+
* 🔴 Why HLS at all, now that Range works. The Range bug (an edge Worker answering 200 + the
|
|
101
|
+
* whole 209 MB to a ranged read) was fixed 2026-08-14 and is NOT what this is for. A single
|
|
102
|
+
* progressive MP4, however well served, cannot ADAPT: a weak cell connection stalls on a
|
|
103
|
+
* 23 Mbps master instead of dropping to a lower rung. Each segment is also a small object that
|
|
104
|
+
* caches WHOLE at the edge, which sidesteps both the 512 MiB single-entry ceiling and the
|
|
105
|
+
* segment-assembly machinery that exists to work around it for big films.
|
|
106
|
+
*/
|
|
107
|
+
export const hlsPrefixFor = (key, generation = VIDEO_KEY_GENERATION) => `${key}${videoGenerationTag(generation)}.hls/`;
|
|
108
|
+
/**
|
|
109
|
+
* The playlist a player is pointed at — the one object whose presence means "the ladder is
|
|
110
|
+
* ready". It is written LAST, after every rung, so a consumer that finds it can trust that
|
|
111
|
+
* what it names exists. Absence is normal and means "play the faststart remux".
|
|
112
|
+
*/
|
|
113
|
+
export const hlsMasterKeyFor = (key, generation = VIDEO_KEY_GENERATION) => `${hlsPrefixFor(key, generation)}master.m3u8`;
|
|
114
|
+
/**
|
|
115
|
+
* Every derivative suffix, for the "is this key itself a derivative" question.
|
|
116
|
+
*
|
|
117
|
+
* 🔴 Load-bearing in three directions, each of which has already cost something:
|
|
118
|
+
* 1. A derivative must never become the source of another, or a sweep builds
|
|
119
|
+
* `clip.web.mp4.web.mp4` and then `…web.mp4.web.mp4.web.mp4`, forever.
|
|
120
|
+
* 2. A sweep must not count a derivative as work still owed, or the backlog is permanently
|
|
121
|
+
* non-empty and ffmpeg re-runs on every pass.
|
|
122
|
+
* 3. A duplicate scan must not propose a file against its own derivative. They share a title
|
|
123
|
+
* by construction, so every video would pair with its own remux and a review queue would
|
|
124
|
+
* contain nothing else.
|
|
125
|
+
*/
|
|
126
|
+
export const DERIVATIVE_SUFFIXES = [
|
|
127
|
+
".web.mp4",
|
|
128
|
+
".sprite.jpg",
|
|
129
|
+
".sprite.vtt",
|
|
130
|
+
".thumb.jpg",
|
|
131
|
+
".web.jpg",
|
|
132
|
+
];
|
|
133
|
+
/**
|
|
134
|
+
* Is this key something binary-server DERIVED rather than something a person uploaded?
|
|
135
|
+
*
|
|
136
|
+
* The HLS tree is matched by its container segment rather than a suffix, because every object
|
|
137
|
+
* inside it (`master.m3u8`, `720/index.m3u8`, `720/000.ts`) is a derivative and none of them
|
|
138
|
+
* ends in a name from the list above.
|
|
139
|
+
*/
|
|
140
|
+
const DERIVATIVE_RE = new RegExp(
|
|
141
|
+
// 🔴 The optional `.g<N>` is the encoder generation a video derivative's name carries
|
|
142
|
+
// (see VIDEO_KEY_GENERATION above). Without it, `clip.g2.web.mp4` reads as a SOURCE — and
|
|
143
|
+
// then a sweep derives `clip.g2.web.mp4.web.mp4` from it, for ever. Built from the suffix
|
|
144
|
+
// list rather than written out, so the two cannot drift apart.
|
|
145
|
+
`(?:\\.g\\d+)?(?:${DERIVATIVE_SUFFIXES.map((x) => x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`);
|
|
146
|
+
export const isDerivativeKey = (key) => DERIVATIVE_RE.test(key) ||
|
|
147
|
+
key.includes(".hls/") ||
|
|
148
|
+
/-thumb\.(jpg|webp|png)$/.test(key) ||
|
|
149
|
+
/\.shot\.\d+\.jpg$/.test(key);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ProcessingStatus } from 'cursedbelt-core/media';
|
|
2
|
+
import { type MediaStore } from './types';
|
|
3
|
+
export type ProcessProgress = (status: ProcessingStatus, pct: number) => void;
|
|
4
|
+
export type ProcessResult = {
|
|
5
|
+
status: ProcessingStatus;
|
|
6
|
+
durationSeconds: number;
|
|
7
|
+
hasHls: boolean;
|
|
8
|
+
hasSprite: boolean;
|
|
9
|
+
hasPoster: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type ProcessOptions = {
|
|
12
|
+
store: MediaStore;
|
|
13
|
+
/** Scratch directory for ffmpeg inputs/outputs (cleaned up afterwards). */
|
|
14
|
+
workDir: string;
|
|
15
|
+
owner: string;
|
|
16
|
+
id: string;
|
|
17
|
+
/** Storage key of the already-persisted raw source. */
|
|
18
|
+
sourceKey: string;
|
|
19
|
+
/** HLS rung heights to attempt (clamped to the source height; never upscaled). */
|
|
20
|
+
ladderHeights?: number[];
|
|
21
|
+
/**
|
|
22
|
+
* Poster-only mode for memory-constrained hosts: probe duration + extract a single-frame
|
|
23
|
+
* poster (cheap, ~tens of MB peak) and skip the heavy re-encode/HLS/sprite work that OOM'd
|
|
24
|
+
* the 1GB prod box (2026-07-04). The raw source still plays progressively; this only buys a
|
|
25
|
+
* grid thumbnail + duration. Best-effort — a failed poster still lands the row `ready`.
|
|
26
|
+
*/
|
|
27
|
+
posterOnly?: boolean;
|
|
28
|
+
onProgress?: ProcessProgress;
|
|
29
|
+
};
|
|
30
|
+
export declare function processVideo(opts: ProcessOptions): Promise<ProcessResult>;
|