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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createS3ObjectStore` — the SAAS profile's binary tier (08 §1b, 01 §2.5).
|
|
3
|
+
*
|
|
4
|
+
* The companion to `createFsObjectStore` (cwip): same `ObjectStore` contract,
|
|
5
|
+
* different issuer. Everything above this line — `AssetService`, the asset
|
|
6
|
+
* mixin, the client — is written against the interface, so switching profiles
|
|
7
|
+
* swaps this factory in and changes nothing else. That is the whole reason the
|
|
8
|
+
* local store bothers to sign its URLs.
|
|
9
|
+
*
|
|
10
|
+
* Built on `Bun.S3Client`, which ships in the runtime cursedbelt already
|
|
11
|
+
* requires — no `aws-sdk`, no transitive credential-chain surprises. Works
|
|
12
|
+
* against S3, R2, MinIO or any other S3-compatible endpoint.
|
|
13
|
+
*
|
|
14
|
+
* Three things this does that a naive `PutObject` wrapper does not:
|
|
15
|
+
*
|
|
16
|
+
* * **Streams through a multipart writer.** A 4GB upload is fed part-by-part
|
|
17
|
+
* (default 8MiB) straight from the request stream. Nothing buffers the
|
|
18
|
+
* object, so request memory is O(part), not O(file).
|
|
19
|
+
* * **Aborts the multipart on failure.** `NetworkSink.end(error)` tells Bun to
|
|
20
|
+
* abort rather than complete the upload; an abandoned multipart otherwise
|
|
21
|
+
* accrues storage cost that never appears in a bucket listing — an invisible
|
|
22
|
+
* bill, which is the worst kind.
|
|
23
|
+
* * **Presigns for real.** `signedUrl` returns a genuine presigned GET so the
|
|
24
|
+
* bytes leave via the CDN/edge, not through the app process. The asset
|
|
25
|
+
* mixin's bytes routes stay as the fallback for private/derived content.
|
|
26
|
+
*
|
|
27
|
+
* Credentials are OPTIONS, never `process.env`: the host resolves them from
|
|
28
|
+
* GuardEngine secrets or its deploy environment (01 §3.3) and injects them, so
|
|
29
|
+
* this module has no ambient authority and a test can hand it a fake client.
|
|
30
|
+
*/
|
|
31
|
+
import { assertObjectKey } from 'cwip/storage';
|
|
32
|
+
import type { ObjectStat, ObjectStore } from 'cwip/storage';
|
|
33
|
+
import { AppError } from '../errors';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The slice of `Bun.S3File` this store uses.
|
|
37
|
+
*
|
|
38
|
+
* Declared structurally rather than imported as `Bun.S3File` so the contract
|
|
39
|
+
* suite can drive BOTH implementations against an in-process fake bucket. A
|
|
40
|
+
* store whose only proof is "it compiles against the real SDK types" is a store
|
|
41
|
+
* whose behavior has never been tested.
|
|
42
|
+
*/
|
|
43
|
+
export interface S3ObjectHandle {
|
|
44
|
+
stream(): ReadableStream<Uint8Array>;
|
|
45
|
+
writer(options?: { partSize?: number; queueSize?: number; retry?: number }): S3ObjectWriter;
|
|
46
|
+
exists(): Promise<boolean>;
|
|
47
|
+
stat(): Promise<{ size: number; type?: string; lastModified?: Date; etag?: string }>;
|
|
48
|
+
delete(): Promise<void>;
|
|
49
|
+
presign(options?: { expiresIn?: number; method?: 'GET' }): string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface S3ObjectWriter {
|
|
53
|
+
write(chunk: Uint8Array): number | Promise<number>;
|
|
54
|
+
/** Passing an error aborts the multipart upload instead of completing it. */
|
|
55
|
+
end(error?: Error): number | Promise<number>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface S3ClientLike {
|
|
59
|
+
file(key: string, options?: { type?: string }): S3ObjectHandle;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface S3ObjectStoreOptions {
|
|
63
|
+
/** Pre-built client. Supply this OR the credential fields below. */
|
|
64
|
+
client?: S3ClientLike;
|
|
65
|
+
bucket?: string;
|
|
66
|
+
/** e.g. `https://<account>.r2.cloudflarestorage.com`. */
|
|
67
|
+
endpoint?: string;
|
|
68
|
+
region?: string;
|
|
69
|
+
accessKeyId?: string;
|
|
70
|
+
secretAccessKey?: string;
|
|
71
|
+
sessionToken?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Namespace every key under this prefix, so ONE bucket can host many apps
|
|
74
|
+
* without collisions. Tenant scoping stays in the key shape (`<tenant>/<file>`)
|
|
75
|
+
* — the prefix is deployment topology, not a security boundary.
|
|
76
|
+
*/
|
|
77
|
+
keyPrefix?: string;
|
|
78
|
+
/** Multipart part size. Default 8MiB. */
|
|
79
|
+
partSize?: number;
|
|
80
|
+
/** Default presigned-URL lifetime in seconds. */
|
|
81
|
+
defaultExpiresInSeconds?: number;
|
|
82
|
+
/** Ceiling on a caller-requested lifetime. */
|
|
83
|
+
maxExpiresInSeconds?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const DEFAULT_PART_SIZE = 8 * 1024 * 1024;
|
|
87
|
+
const DEFAULT_EXPIRES_IN_SECONDS = 300;
|
|
88
|
+
const MAX_EXPIRES_IN_SECONDS = 86_400;
|
|
89
|
+
|
|
90
|
+
/** `Bun.S3Client` typed down to the slice we use; Bun ships it natively. */
|
|
91
|
+
type BunWithS3 = { S3Client: new (options: Record<string, unknown>) => S3ClientLike };
|
|
92
|
+
|
|
93
|
+
const buildClient = (options: S3ObjectStoreOptions): S3ClientLike => {
|
|
94
|
+
if (options.client) return options.client;
|
|
95
|
+
if (!options.bucket)
|
|
96
|
+
throw new AppError('createS3ObjectStore needs either a `client` or a `bucket` (plus credentials).', {
|
|
97
|
+
code: 'OBJECT_STORE_MISCONFIGURED',
|
|
98
|
+
status: 500,
|
|
99
|
+
category: 'storage',
|
|
100
|
+
isOperational: false,
|
|
101
|
+
});
|
|
102
|
+
const S3Client = (Bun as unknown as BunWithS3).S3Client;
|
|
103
|
+
if (typeof S3Client !== 'function')
|
|
104
|
+
throw new AppError('This Bun runtime has no S3Client; pass a `client` implementing the S3 subset instead.', {
|
|
105
|
+
code: 'OBJECT_STORE_MISCONFIGURED',
|
|
106
|
+
status: 500,
|
|
107
|
+
category: 'storage',
|
|
108
|
+
isOperational: false,
|
|
109
|
+
});
|
|
110
|
+
return new S3Client({
|
|
111
|
+
bucket: options.bucket,
|
|
112
|
+
endpoint: options.endpoint,
|
|
113
|
+
region: options.region,
|
|
114
|
+
accessKeyId: options.accessKeyId,
|
|
115
|
+
secretAccessKey: options.secretAccessKey,
|
|
116
|
+
sessionToken: options.sessionToken,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export function createS3ObjectStore(options: S3ObjectStoreOptions): ObjectStore {
|
|
121
|
+
const client = buildClient(options);
|
|
122
|
+
const partSize = options.partSize ?? DEFAULT_PART_SIZE;
|
|
123
|
+
const defaultExpiry = options.defaultExpiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS;
|
|
124
|
+
const maxExpiry = options.maxExpiresInSeconds ?? MAX_EXPIRES_IN_SECONDS;
|
|
125
|
+
const prefix = options.keyPrefix ? `${options.keyPrefix.replace(/^\/+|\/+$/g, '')}/` : '';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The SAME key whitelist the fs store enforces.
|
|
129
|
+
*
|
|
130
|
+
* S3 has no directories, so traversal is not the risk here — divergence is. A
|
|
131
|
+
* key that one profile accepts and the other refuses turns a profile switch
|
|
132
|
+
* into a data-loss migration, so both stores validate identically and the
|
|
133
|
+
* contract suite asserts it.
|
|
134
|
+
*/
|
|
135
|
+
const physical = (key: string): string => {
|
|
136
|
+
assertObjectKey(key);
|
|
137
|
+
return `${prefix}${key}`;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const statOf = async (key: string): Promise<ObjectStat | null> => {
|
|
141
|
+
const handle = client.file(physical(key));
|
|
142
|
+
try {
|
|
143
|
+
const info = await handle.stat();
|
|
144
|
+
return {
|
|
145
|
+
key,
|
|
146
|
+
size: info.size,
|
|
147
|
+
contentType: info.type,
|
|
148
|
+
modifiedAt: (info.lastModified ?? new Date()).toISOString(),
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (isNotFound(error)) return null;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
async put(key, body, putOptions): Promise<ObjectStat> {
|
|
158
|
+
const contentType = putOptions?.contentType;
|
|
159
|
+
const handle = client.file(physical(key), contentType ? { type: contentType } : undefined);
|
|
160
|
+
const writer = handle.writer({ partSize });
|
|
161
|
+
const reader = body.getReader();
|
|
162
|
+
let size = 0;
|
|
163
|
+
try {
|
|
164
|
+
while (true) {
|
|
165
|
+
const { done, value } = await reader.read();
|
|
166
|
+
if (done) break;
|
|
167
|
+
if (value.byteLength === 0) continue;
|
|
168
|
+
await writer.write(value);
|
|
169
|
+
size += value.byteLength;
|
|
170
|
+
}
|
|
171
|
+
await writer.end();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// Abort, don't complete. Best-effort: if the abort itself fails there is
|
|
174
|
+
// nothing further this process can do, and swallowing it would mask the
|
|
175
|
+
// ORIGINAL upload failure, which is the one worth reporting.
|
|
176
|
+
await Promise.resolve(writer.end(error instanceof Error ? error : new Error(String(error)))).catch(() => {});
|
|
177
|
+
throw error;
|
|
178
|
+
} finally {
|
|
179
|
+
reader.releaseLock();
|
|
180
|
+
}
|
|
181
|
+
return { key, size, contentType, modifiedAt: new Date().toISOString() };
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async get(key): Promise<ReadableStream<Uint8Array> | null> {
|
|
185
|
+
const handle = client.file(physical(key));
|
|
186
|
+
if (!(await handle.exists())) return null;
|
|
187
|
+
try {
|
|
188
|
+
return handle.stream();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// Raced with a delete between `exists` and `stream` — a missing object
|
|
191
|
+
// is `null`, not a 500.
|
|
192
|
+
if (isNotFound(error)) return null;
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
stat: statOf,
|
|
198
|
+
|
|
199
|
+
async delete(key): Promise<boolean> {
|
|
200
|
+
const existed = (await statOf(key)) !== null;
|
|
201
|
+
try {
|
|
202
|
+
await client.file(physical(key)).delete();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (!isNotFound(error)) throw error;
|
|
205
|
+
}
|
|
206
|
+
return existed;
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
async signedUrl(key, urlOptions): Promise<string> {
|
|
210
|
+
const requested = urlOptions?.expiresInSeconds ?? defaultExpiry;
|
|
211
|
+
if (!Number.isFinite(requested) || requested <= 0)
|
|
212
|
+
throw new AppError(`Signed-url lifetime for '${key}' must be a positive number of seconds.`, {
|
|
213
|
+
code: 'OBJECT_KEY_INVALID',
|
|
214
|
+
status: 400,
|
|
215
|
+
category: 'storage',
|
|
216
|
+
context: { key, requested },
|
|
217
|
+
});
|
|
218
|
+
return client
|
|
219
|
+
.file(physical(key))
|
|
220
|
+
.presign({ expiresIn: Math.min(Math.floor(requested), maxExpiry), method: 'GET' });
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Is this the store telling us the object is not there?
|
|
227
|
+
*
|
|
228
|
+
* S3-compatible endpoints disagree on the shape (`NoSuchKey`, `NotFound`, a
|
|
229
|
+
* bare 404), and Bun surfaces its own `code`. Matching all of them keeps a
|
|
230
|
+
* missing object a `null` on every backend rather than a 500 on MinIO and a
|
|
231
|
+
* `null` on R2.
|
|
232
|
+
*/
|
|
233
|
+
const isNotFound = (error: unknown): boolean => {
|
|
234
|
+
if (!error || typeof error !== 'object') return false;
|
|
235
|
+
const candidate = error as { code?: unknown; name?: unknown; status?: unknown; statusCode?: unknown };
|
|
236
|
+
const code = String(candidate.code ?? candidate.name ?? '');
|
|
237
|
+
if (/NoSuchKey|NotFound|ENOENT|ERR_S3_FILE_NOT_FOUND/i.test(code)) return true;
|
|
238
|
+
return candidate.status === 404 || candidate.statusCode === 404;
|
|
239
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { PlayableVideo, ProcessingStatus } from 'cursedbelt-core/media';
|
|
2
|
+
import type { StreamTokenService } from './streamToken';
|
|
3
|
+
import {
|
|
4
|
+
fsMp4Key,
|
|
5
|
+
fsPosterKey,
|
|
6
|
+
fsSourceKey,
|
|
7
|
+
fsSpriteKey,
|
|
8
|
+
fsVttKey,
|
|
9
|
+
type MediaCtx,
|
|
10
|
+
type MediaSource,
|
|
11
|
+
type MediaStore,
|
|
12
|
+
} from './types';
|
|
13
|
+
import { processVideo } from './videoProcessor';
|
|
14
|
+
|
|
15
|
+
// ── Media URL builder ───────────────────────────────────────────────────────────
|
|
16
|
+
// Builds the path-embedded-token URLs the serve routes expect. One token per video authorizes
|
|
17
|
+
// all its derivative URLs; carried as a leading `/s/<token>/` path segment so hls.js's relative
|
|
18
|
+
// segment/playlist fetches inherit it (see streamToken.ts).
|
|
19
|
+
//
|
|
20
|
+
// SPLIT-ORIGIN: these URLs are built SERVER-SIDE and baked into the PlayableVideo the client
|
|
21
|
+
// receives — the browser never derives a media URL from `window.location`. So `baseUrl` may be
|
|
22
|
+
// ABSOLUTE (e.g. 'https://api.example.com/_media') to point media at an API subdomain / EC2 /
|
|
23
|
+
// binary-server edge that differs from where the SPA is served (Cloudflare Pages). It is
|
|
24
|
+
// intentionally SEPARATE from `mountMediaRoutes`' `basePath` (the on-server route mount, always
|
|
25
|
+
// path-only): the two only need to share the same trailing path (e.g. '/_media'). For a
|
|
26
|
+
// same-origin app, pass the plain path ('/_media'). When absolute (cross-origin), the media
|
|
27
|
+
// routes must send CORS headers — pass `cors` to `mountMediaRoutes`.
|
|
28
|
+
export interface MediaUrls {
|
|
29
|
+
build(
|
|
30
|
+
ctx: MediaCtx,
|
|
31
|
+
sourceId: string,
|
|
32
|
+
videoId: string,
|
|
33
|
+
): Promise<{
|
|
34
|
+
streamUrl: string;
|
|
35
|
+
hlsUrl: string;
|
|
36
|
+
posterUrl: string;
|
|
37
|
+
displayUrl: string;
|
|
38
|
+
vttUrl: string;
|
|
39
|
+
spriteUrl: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createMediaUrls(opts: {
|
|
44
|
+
/** Public base for browser media URLs. Path-only ('/_media') for same-origin, or an absolute
|
|
45
|
+
* origin+path ('https://api.example.com/_media') for a split-origin deploy. Must share its
|
|
46
|
+
* trailing path with `mountMediaRoutes`' `basePath`. */
|
|
47
|
+
baseUrl: string;
|
|
48
|
+
token: StreamTokenService;
|
|
49
|
+
/** Owner keyspace segment for a request. Defaults to tenantId (a tenant-shared gallery). */
|
|
50
|
+
owner?: (ctx: MediaCtx) => string;
|
|
51
|
+
}): MediaUrls {
|
|
52
|
+
const ownerOf = opts.owner ?? ((c: MediaCtx) => c.tenantId);
|
|
53
|
+
// Trim a trailing slash so 'https://api…/_media/' and '/_media' behave identically.
|
|
54
|
+
const base = opts.baseUrl.replace(/\/$/, '');
|
|
55
|
+
return {
|
|
56
|
+
async build(ctx, sourceId, videoId) {
|
|
57
|
+
const token = await opts.token.sign({ owner: ownerOf(ctx), sourceId, videoId });
|
|
58
|
+
const p = `${base}/s/${token}`;
|
|
59
|
+
return {
|
|
60
|
+
streamUrl: `${p}/media/${sourceId}/${videoId}`,
|
|
61
|
+
hlsUrl: `${p}/hls/${sourceId}/${videoId}/master.m3u8`,
|
|
62
|
+
posterUrl: `${p}/poster/${sourceId}/${videoId}`,
|
|
63
|
+
displayUrl: `${p}/display/${sourceId}/${videoId}`,
|
|
64
|
+
vttUrl: `${p}/sprite/${sourceId}/${videoId}/sprites.vtt`,
|
|
65
|
+
spriteUrl: `${p}/sprite/${sourceId}/${videoId}/sprites.jpg`,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Source registry ───────────────────────────────────────────────────────────────
|
|
72
|
+
export class SourceRegistry {
|
|
73
|
+
private sources = new Map<string, MediaSource>();
|
|
74
|
+
register(source: MediaSource): this {
|
|
75
|
+
this.sources.set(source.id, source);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
get(id: string): MediaSource | undefined {
|
|
79
|
+
return this.sources.get(id);
|
|
80
|
+
}
|
|
81
|
+
list(): MediaSource[] {
|
|
82
|
+
return [...this.sources.values()];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Disk-backed source ──────────────────────────────────────────────────────────
|
|
87
|
+
// A MediaSource over a MediaStore: ingests uploads, runs the pipeline in the background, and
|
|
88
|
+
// resolves videos to tokenised PlayableVideos. Metadata is held in memory keyed by tenant (an
|
|
89
|
+
// app that needs persistence swaps this for a DB-backed MediaSource — cursedbelt-server/sqlite
|
|
90
|
+
// — without touching consumers).
|
|
91
|
+
type VideoMeta = {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
tags: string[];
|
|
95
|
+
mime: string;
|
|
96
|
+
durationSeconds?: number;
|
|
97
|
+
status: ProcessingStatus;
|
|
98
|
+
progress: number;
|
|
99
|
+
hasHls: boolean;
|
|
100
|
+
hasSprite: boolean;
|
|
101
|
+
hasPoster: boolean;
|
|
102
|
+
createdAt: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type DiskSource = MediaSource & {
|
|
106
|
+
/** Read current processing status (for the gallery's status poll / tests). */
|
|
107
|
+
status(ctx: MediaCtx, videoId: string): { status: ProcessingStatus; progress: number } | null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function createDiskSource(cfg: {
|
|
111
|
+
id: string;
|
|
112
|
+
title: string;
|
|
113
|
+
store: MediaStore;
|
|
114
|
+
/** ffmpeg scratch dir. */
|
|
115
|
+
workDir: string;
|
|
116
|
+
urls: MediaUrls;
|
|
117
|
+
/** Owner keyspace segment; defaults to tenantId. */
|
|
118
|
+
owner?: (ctx: MediaCtx) => string;
|
|
119
|
+
/** Injectable clock (tests). */
|
|
120
|
+
now?: () => number;
|
|
121
|
+
}): DiskSource {
|
|
122
|
+
const ownerOf = cfg.owner ?? ((c: MediaCtx) => c.tenantId);
|
|
123
|
+
const now = cfg.now ?? (() => Date.now());
|
|
124
|
+
const byTenant = new Map<string, Map<string, VideoMeta>>();
|
|
125
|
+
const bucket = (ctx: MediaCtx) => {
|
|
126
|
+
let m = byTenant.get(ctx.tenantId);
|
|
127
|
+
if (!m) {
|
|
128
|
+
m = new Map();
|
|
129
|
+
byTenant.set(ctx.tenantId, m);
|
|
130
|
+
}
|
|
131
|
+
return m;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const toPlayable = async (ctx: MediaCtx, meta: VideoMeta): Promise<PlayableVideo> => {
|
|
135
|
+
const u = await cfg.urls.build(ctx, cfg.id, meta.id);
|
|
136
|
+
return {
|
|
137
|
+
id: meta.id,
|
|
138
|
+
name: meta.name,
|
|
139
|
+
streamUrl: u.streamUrl,
|
|
140
|
+
hlsUrl: meta.hasHls ? u.hlsUrl : undefined,
|
|
141
|
+
posterUrl: meta.hasPoster ? u.posterUrl : undefined,
|
|
142
|
+
vttUrl: meta.hasSprite ? u.vttUrl : undefined,
|
|
143
|
+
spriteUrl: meta.hasSprite ? u.spriteUrl : undefined,
|
|
144
|
+
durationSeconds: meta.durationSeconds,
|
|
145
|
+
tags: meta.tags,
|
|
146
|
+
processingStatus: meta.status,
|
|
147
|
+
processingProgress: meta.progress,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
id: cfg.id,
|
|
153
|
+
title: cfg.title,
|
|
154
|
+
|
|
155
|
+
async list(ctx, opts) {
|
|
156
|
+
const all = [...bucket(ctx).values()].sort((a, b) => b.createdAt - a.createdAt);
|
|
157
|
+
const q = opts.query?.trim().toLowerCase();
|
|
158
|
+
const filtered = all.filter(
|
|
159
|
+
(m) =>
|
|
160
|
+
(!opts.tag || m.tags.includes(opts.tag)) &&
|
|
161
|
+
(!q || m.name.toLowerCase().includes(q) || m.tags.some((t) => t.includes(q))),
|
|
162
|
+
);
|
|
163
|
+
const limit = opts.limit ?? 24;
|
|
164
|
+
const start = opts.cursor ? Number(opts.cursor) || 0 : 0;
|
|
165
|
+
const page = filtered.slice(start, start + limit);
|
|
166
|
+
const items = await Promise.all(page.map((m) => toPlayable(ctx, m)));
|
|
167
|
+
const next = start + limit;
|
|
168
|
+
return { items, nextCursor: next < filtered.length ? String(next) : undefined };
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
async resolve(ctx, videoId) {
|
|
172
|
+
const meta = bucket(ctx).get(videoId);
|
|
173
|
+
return meta ? toPlayable(ctx, meta) : null;
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
async tags(ctx) {
|
|
177
|
+
const set = new Set<string>();
|
|
178
|
+
for (const m of bucket(ctx).values()) {
|
|
179
|
+
for (const t of m.tags) set.add(t);
|
|
180
|
+
}
|
|
181
|
+
return [...set].sort();
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async ingest(ctx, upload) {
|
|
185
|
+
const owner = ownerOf(ctx);
|
|
186
|
+
const id = crypto.randomUUID();
|
|
187
|
+
await cfg.store.putStream(fsSourceKey(owner, id), upload.body, upload.mime, {
|
|
188
|
+
contentLength: upload.size,
|
|
189
|
+
});
|
|
190
|
+
const meta: VideoMeta = {
|
|
191
|
+
id,
|
|
192
|
+
name: upload.name,
|
|
193
|
+
tags: [],
|
|
194
|
+
mime: upload.mime,
|
|
195
|
+
status: 'processing',
|
|
196
|
+
progress: 0,
|
|
197
|
+
hasHls: false,
|
|
198
|
+
hasSprite: false,
|
|
199
|
+
hasPoster: false,
|
|
200
|
+
createdAt: now(),
|
|
201
|
+
};
|
|
202
|
+
bucket(ctx).set(id, meta);
|
|
203
|
+
|
|
204
|
+
// Fire-and-forget the pipeline; the client polls status via the source's status()/list().
|
|
205
|
+
void processVideo({
|
|
206
|
+
store: cfg.store,
|
|
207
|
+
workDir: cfg.workDir,
|
|
208
|
+
owner,
|
|
209
|
+
id,
|
|
210
|
+
sourceKey: fsSourceKey(owner, id),
|
|
211
|
+
onProgress: (status, pct) => {
|
|
212
|
+
meta.status = status;
|
|
213
|
+
meta.progress = pct;
|
|
214
|
+
},
|
|
215
|
+
}).then((res) => {
|
|
216
|
+
meta.status = res.status;
|
|
217
|
+
meta.progress = res.status === 'ready' ? 100 : meta.progress;
|
|
218
|
+
meta.durationSeconds = res.durationSeconds || meta.durationSeconds;
|
|
219
|
+
meta.hasHls = res.hasHls;
|
|
220
|
+
meta.hasSprite = res.hasSprite;
|
|
221
|
+
meta.hasPoster = res.hasPoster;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return toPlayable(ctx, meta);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
async remove(ctx, videoId) {
|
|
228
|
+
const owner = ownerOf(ctx);
|
|
229
|
+
await Promise.all([
|
|
230
|
+
cfg.store.remove(fsSourceKey(owner, videoId)),
|
|
231
|
+
cfg.store.remove(fsMp4Key(owner, videoId)),
|
|
232
|
+
cfg.store.remove(fsPosterKey(owner, videoId)),
|
|
233
|
+
cfg.store.remove(fsSpriteKey(owner, videoId)),
|
|
234
|
+
cfg.store.remove(fsVttKey(owner, videoId)),
|
|
235
|
+
cfg.store.removePrefix(`hls/${owner}/${videoId}`),
|
|
236
|
+
]);
|
|
237
|
+
bucket(ctx).delete(videoId);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
status(ctx, videoId) {
|
|
241
|
+
const meta = bucket(ctx).get(videoId);
|
|
242
|
+
return meta ? { status: meta.status, progress: meta.progress } : null;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|