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,237 @@
|
|
|
1
|
+
// Pure ffmpeg/ffprobe argument builders + parsers for the STREAMING pipeline: faststart
|
|
2
|
+
// remux, the HLS adaptive-bitrate ladder, scrub-preview sprite sheets + WebVTT, poster
|
|
3
|
+
// frames, and a codec-aware probe. No Bun.spawn, no I/O — every argv + parser here is unit
|
|
4
|
+
// tested without ffmpeg present; `video.ts` is the thin spawn shell that runs them
|
|
5
|
+
// (probeVideoStream / runFfmpegProgress / extractFrameJpeg).
|
|
6
|
+
//
|
|
7
|
+
// Companion to `ffmpeg.ts` (which owns the trim/thumbnail builders + basic probe): these
|
|
8
|
+
// are the additional pieces a streaming server (cursedalchemy's media pipeline, the
|
|
9
|
+
// binary-server) needs to turn an upload into web-seekable, adaptively-streamable output.
|
|
10
|
+
//
|
|
11
|
+
// ffmpeg/ffprobe are expected on PATH (or via FFMPEG_PATH/FFPROBE_PATH) — never an npm dep.
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
// ── Web-safe encode primitives (one source of truth for "decodes everywhere") ──────────
|
|
14
|
+
// libx264 defaults to HIGH profile, whose CABAC/8x8-transform features some hardware
|
|
15
|
+
// decoders mis-handle (solid GREEN frame); Main profile drops those at negligible cost and
|
|
16
|
+
// is universally supported. yuv420p is the only chroma layout browsers decode reliably.
|
|
17
|
+
export const h264WebVideoArgs = (crf) => [
|
|
18
|
+
'-c:v',
|
|
19
|
+
'libx264',
|
|
20
|
+
'-preset',
|
|
21
|
+
'veryfast',
|
|
22
|
+
'-crf',
|
|
23
|
+
crf,
|
|
24
|
+
'-profile:v',
|
|
25
|
+
'main',
|
|
26
|
+
'-pix_fmt',
|
|
27
|
+
'yuv420p',
|
|
28
|
+
];
|
|
29
|
+
export const aacWebAudioArgs = (bitrate) => ['-c:a', 'aac', '-b:a', bitrate];
|
|
30
|
+
/** Move the moov atom to the front so playback can start before the full download. */
|
|
31
|
+
export const FASTSTART = ['-movflags', '+faststart'];
|
|
32
|
+
/** Cap the long edge at ~1080p, keep aspect, force even dimensions (yuv420p needs even). */
|
|
33
|
+
export const LIBRARY_SCALE = "scale='min(1920,iw)':'-2'";
|
|
34
|
+
/** Higher quality than ephemeral Moments (28) — this output is retained. */
|
|
35
|
+
export const LIBRARY_CRF = '23';
|
|
36
|
+
export const EMPTY_PROBE = {
|
|
37
|
+
duration: 0,
|
|
38
|
+
width: 0,
|
|
39
|
+
height: 0,
|
|
40
|
+
vcodec: '',
|
|
41
|
+
acodec: '',
|
|
42
|
+
pixfmt: '',
|
|
43
|
+
hasAudio: false,
|
|
44
|
+
};
|
|
45
|
+
/** ffprobe argv: one call for everything the pipeline decides on (codecs + dims + duration). */
|
|
46
|
+
export const buildStreamProbeArgs = (filePath) => [
|
|
47
|
+
'-v',
|
|
48
|
+
'error',
|
|
49
|
+
'-show_entries',
|
|
50
|
+
'format=duration:stream=index,codec_type,codec_name,width,height,pix_fmt',
|
|
51
|
+
'-of',
|
|
52
|
+
'json',
|
|
53
|
+
filePath,
|
|
54
|
+
];
|
|
55
|
+
/** Parse the `buildStreamProbeArgs` JSON into a {@link VideoProbe}. */
|
|
56
|
+
export const parseStreamProbe = (json) => {
|
|
57
|
+
const streams = Array.isArray(json?.streams) ? json.streams : [];
|
|
58
|
+
const v = streams.find((s) => s.codec_type === 'video') ?? {};
|
|
59
|
+
const a = streams.find((s) => s.codec_type === 'audio');
|
|
60
|
+
return {
|
|
61
|
+
duration: Number(json?.format?.duration) || 0,
|
|
62
|
+
width: Number(v.width) || 0,
|
|
63
|
+
height: Number(v.height) || 0,
|
|
64
|
+
vcodec: String(v.codec_name ?? ''),
|
|
65
|
+
acodec: String(a?.codec_name ?? ''),
|
|
66
|
+
pixfmt: String(v.pix_fmt ?? ''),
|
|
67
|
+
hasAudio: Boolean(a),
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
// ── Faststart: stream-copy a source that's already web-safe (fast, lossless — just move the
|
|
71
|
+
// moov atom), else re-encode to the web-safe ladder. ─────────────────────────────────
|
|
72
|
+
export const canStreamCopy = (p, maxWidth = 1920) => p.vcodec === 'h264' &&
|
|
73
|
+
p.pixfmt === 'yuv420p' &&
|
|
74
|
+
(p.acodec === '' || p.acodec === 'aac') &&
|
|
75
|
+
p.width > 0 &&
|
|
76
|
+
p.width <= maxWidth;
|
|
77
|
+
export const faststartArgs = (inPath, outPath, opts) => {
|
|
78
|
+
if (opts.copy)
|
|
79
|
+
return ['-i', inPath, '-c', 'copy', ...FASTSTART, outPath];
|
|
80
|
+
const crf = String(opts.crf ?? Number(LIBRARY_CRF));
|
|
81
|
+
return [
|
|
82
|
+
'-i',
|
|
83
|
+
inPath,
|
|
84
|
+
'-vf',
|
|
85
|
+
LIBRARY_SCALE,
|
|
86
|
+
...h264WebVideoArgs(crf),
|
|
87
|
+
...aacWebAudioArgs('160k'),
|
|
88
|
+
...FASTSTART,
|
|
89
|
+
outPath,
|
|
90
|
+
];
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Parse one ffmpeg `-progress pipe:1` line into elapsed microseconds, or null if the line
|
|
94
|
+
* carries no out_time. `runFfmpegProgress` turns a stream of these into a 0..99 percent.
|
|
95
|
+
*/
|
|
96
|
+
export const parseProgressMicros = (line) => {
|
|
97
|
+
const m = /^out_time_us=(\d+)/.exec(line) ?? /^out_time_ms=(\d+)/.exec(line);
|
|
98
|
+
if (!m)
|
|
99
|
+
return null;
|
|
100
|
+
return line.startsWith('out_time_ms') ? Number(m[1]) * 1000 : Number(m[1]);
|
|
101
|
+
};
|
|
102
|
+
// ── HLS adaptive-bitrate ladder ─────────────────────────────────────────────────────────
|
|
103
|
+
// Per-rung video bitrate cap + buffer + audio rate. One source of truth for rung tuning.
|
|
104
|
+
const LADDER_PRESETS = {
|
|
105
|
+
360: { maxrate: '856k', bufsize: '1200k', abr: '96k' },
|
|
106
|
+
480: { maxrate: '1400k', bufsize: '2100k', abr: '128k' },
|
|
107
|
+
720: { maxrate: '2800k', bufsize: '4200k', abr: '128k' },
|
|
108
|
+
1080: { maxrate: '5000k', bufsize: '7500k', abr: '192k' },
|
|
109
|
+
1440: { maxrate: '8000k', bufsize: '12000k', abr: '192k' },
|
|
110
|
+
2160: { maxrate: '16000k', bufsize: '24000k', abr: '192k' },
|
|
111
|
+
};
|
|
112
|
+
/** Drop unknown/too-tall rungs (never upscale); always keep at least the source-or-720 rung. */
|
|
113
|
+
export const resolveLadder = (heights, sourceHeight) => {
|
|
114
|
+
const valid = heights
|
|
115
|
+
.filter((h) => LADDER_PRESETS[h])
|
|
116
|
+
.filter((h) => sourceHeight <= 0 || h <= sourceHeight);
|
|
117
|
+
const ladder = valid.length ? valid : [Math.min(720, sourceHeight || 720)];
|
|
118
|
+
return [...new Set(ladder)].sort((a, b) => a - b);
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Build the HLS multi-rendition transcode. `outDir/<height>/` subdirs must already exist.
|
|
122
|
+
* Emits master.m3u8 + per-rung playlist.m3u8 + mpegts segments, variants NAMED by height
|
|
123
|
+
* so storage keys read `…/720/seg_00001.ts` rather than an opaque index.
|
|
124
|
+
*/
|
|
125
|
+
export const hlsLadderArgs = (inPath, outDir, opts) => {
|
|
126
|
+
const hs = opts.heights.length ? opts.heights : [720];
|
|
127
|
+
const n = hs.length;
|
|
128
|
+
const filter = `[0:v]split=${n}${hs.map((_, i) => `[v${i}]`).join('')};` +
|
|
129
|
+
hs.map((h, i) => `[v${i}]scale=-2:${h}[vo${i}]`).join(';');
|
|
130
|
+
const args = ['-i', inPath, '-filter_complex', filter];
|
|
131
|
+
hs.forEach((h, i) => {
|
|
132
|
+
const p = LADDER_PRESETS[h] ?? LADDER_PRESETS[720];
|
|
133
|
+
args.push('-map', `[vo${i}]`, `-c:v:${i}`, 'libx264', '-preset', 'veryfast', `-crf:v:${i}`, String(opts.crf), `-profile:v:${i}`, 'main', `-maxrate:v:${i}`, p.maxrate, `-bufsize:v:${i}`, p.bufsize);
|
|
134
|
+
});
|
|
135
|
+
args.push('-pix_fmt', 'yuv420p');
|
|
136
|
+
if (opts.hasAudio) {
|
|
137
|
+
hs.forEach((h, i) => {
|
|
138
|
+
const p = LADDER_PRESETS[h] ?? LADDER_PRESETS[720];
|
|
139
|
+
args.push('-map', 'a:0', `-c:a:${i}`, 'aac', `-b:a:${i}`, p.abr, '-ac', '2');
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const varMap = hs
|
|
143
|
+
.map((h, i) => (opts.hasAudio ? `v:${i},a:${i},name:${h}` : `v:${i},name:${h}`))
|
|
144
|
+
.join(' ');
|
|
145
|
+
args.push('-f', 'hls', '-hls_time', '4', '-hls_playlist_type', 'vod', '-hls_segment_type', 'mpegts', ...(opts.keyInfoFile ? ['-hls_key_info_file', opts.keyInfoFile] : []), '-master_pl_name', 'master.m3u8', '-hls_segment_filename', path.join(outDir, '%v', 'seg_%05d.ts'), '-var_stream_map', varMap, path.join(outDir, '%v', 'playlist.m3u8'));
|
|
146
|
+
return args;
|
|
147
|
+
};
|
|
148
|
+
/** Poster: a frame `atSeconds` in (default ~1s, to skip black intros), scaled to the library
|
|
149
|
+
* width, as JPEG. Pass a smaller seek (or 0) for very short clips — seeking past EOF yields no
|
|
150
|
+
* frame and the extract fails, so callers that know the duration should clamp `atSeconds`. */
|
|
151
|
+
export const posterArgs = (inPath, outPath, atSeconds = 1) => [
|
|
152
|
+
'-ss',
|
|
153
|
+
String(Math.max(0, atSeconds)),
|
|
154
|
+
'-i',
|
|
155
|
+
inPath,
|
|
156
|
+
'-vframes',
|
|
157
|
+
'1',
|
|
158
|
+
'-vf',
|
|
159
|
+
LIBRARY_SCALE,
|
|
160
|
+
outPath,
|
|
161
|
+
];
|
|
162
|
+
/** Decode a still image to a width-capped JPEG thumbnail. The fallback for formats sharp's
|
|
163
|
+
* prebuilt binary can't decode — notably HEIC/HEVC (iPhone photos), which the licensed-out
|
|
164
|
+
* libheif build rejects but ffmpeg's hevc decoder handles. `-update 1` writes the single frame
|
|
165
|
+
* to a fixed filename (no image-sequence pattern); height rounds to even (`-2`) for the encoder. */
|
|
166
|
+
export const imageThumbnailArgs = (inPath, outPath, maxWidth = 512) => [
|
|
167
|
+
'-i',
|
|
168
|
+
inPath,
|
|
169
|
+
'-vf',
|
|
170
|
+
`scale='min(${maxWidth},iw)':-2`,
|
|
171
|
+
'-frames:v',
|
|
172
|
+
'1',
|
|
173
|
+
'-update',
|
|
174
|
+
'1',
|
|
175
|
+
outPath,
|
|
176
|
+
];
|
|
177
|
+
/** ffmpeg argv to grab one scaled JPEG frame at `atSeconds` on stdout (`-ss` before `-i`
|
|
178
|
+
* fast-seeks — instant on a faststart file). The spawn runner reads pipe:1. */
|
|
179
|
+
export const buildFrameJpegArgs = (filePath, atSeconds, maxWidth = 240) => [
|
|
180
|
+
'-ss',
|
|
181
|
+
String(Math.max(0, atSeconds)),
|
|
182
|
+
'-i',
|
|
183
|
+
filePath,
|
|
184
|
+
'-frames:v',
|
|
185
|
+
'1',
|
|
186
|
+
'-vf',
|
|
187
|
+
`scale=${maxWidth}:-2`,
|
|
188
|
+
'-f',
|
|
189
|
+
'mjpeg',
|
|
190
|
+
'pipe:1',
|
|
191
|
+
];
|
|
192
|
+
// ── Scrub-preview sprite sheet + WebVTT (YouTube-style hover/drag thumbnails) ───────────
|
|
193
|
+
const SPRITE_THUMB_WIDTH = 160;
|
|
194
|
+
const SPRITE_MAX_TILES = 144; // a single ~12×12 sheet; widen the interval past this
|
|
195
|
+
/** Sheet geometry: sample every `interval`s, widening it so a long video still fits one sheet. */
|
|
196
|
+
export const planSprite = (duration, interval, srcW, srcH) => {
|
|
197
|
+
const dur = Math.max(1, duration);
|
|
198
|
+
const effInterval = Math.max(1, interval, Math.ceil(dur / SPRITE_MAX_TILES));
|
|
199
|
+
const count = Math.max(1, Math.min(SPRITE_MAX_TILES, Math.ceil(dur / effInterval)));
|
|
200
|
+
const cols = Math.ceil(Math.sqrt(count));
|
|
201
|
+
const rows = Math.ceil(count / cols);
|
|
202
|
+
const thumbW = SPRITE_THUMB_WIDTH;
|
|
203
|
+
const thumbH = srcW > 0 ? Math.max(2, Math.round((thumbW * srcH) / srcW / 2) * 2) : 90;
|
|
204
|
+
return { effInterval, count, cols, rows, thumbW, thumbH };
|
|
205
|
+
};
|
|
206
|
+
export const spriteArgs = (inPath, outPath, plan) => [
|
|
207
|
+
'-i',
|
|
208
|
+
inPath,
|
|
209
|
+
'-vf',
|
|
210
|
+
`fps=1/${plan.effInterval},scale=${plan.thumbW}:-2,tile=${plan.cols}x${plan.rows}`,
|
|
211
|
+
'-frames:v',
|
|
212
|
+
'1',
|
|
213
|
+
'-q:v',
|
|
214
|
+
'5',
|
|
215
|
+
outPath,
|
|
216
|
+
];
|
|
217
|
+
const vttTime = (s) => {
|
|
218
|
+
const h = Math.floor(s / 3600);
|
|
219
|
+
const m = Math.floor((s % 3600) / 60);
|
|
220
|
+
const sec = s % 60;
|
|
221
|
+
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${sec.toFixed(3).padStart(6, '0')}`;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* WebVTT thumbnail track: one cue per tile pointing at a `#xywh` media fragment of the sheet.
|
|
225
|
+
* The sprite URL is relative (default "sprites.jpg") so it resolves against the VTT's own route.
|
|
226
|
+
*/
|
|
227
|
+
export const buildSpriteVtt = (plan, spriteName = 'sprites.jpg') => {
|
|
228
|
+
let out = 'WEBVTT\n\n';
|
|
229
|
+
for (let i = 0; i < plan.count; i++) {
|
|
230
|
+
const start = i * plan.effInterval;
|
|
231
|
+
const end = (i + 1) * plan.effInterval;
|
|
232
|
+
const x = (i % plan.cols) * plan.thumbW;
|
|
233
|
+
const y = Math.floor(i / plan.cols) * plan.thumbH;
|
|
234
|
+
out += `${vttTime(start)} --> ${vttTime(end)}\n${spriteName}#xywh=${x},${y},${plan.thumbW},${plan.thumbH}\n\n`;
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type VideoProgressEvent, type VideoTrimSpec } from 'cursedbelt-core/media';
|
|
2
|
+
import { type ProbeResult } from 'cursedbelt-core/media/ffmpeg';
|
|
3
|
+
import { type VideoProbe } from './streaming';
|
|
4
|
+
/** Is ffmpeg runnable? Cheap probe used to skip server media ops / tests cleanly. */
|
|
5
|
+
export declare function isFfmpegAvailable(bin?: string): Promise<boolean>;
|
|
6
|
+
/** Is ffprobe runnable? */
|
|
7
|
+
export declare function isFfprobeAvailable(): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Trim `[startSeconds, endSeconds]` from `inputPath` into `outputPath`, re-encoding to
|
|
10
|
+
* the requested format/quality. `onProgress` receives `encode` events (parsed from
|
|
11
|
+
* ffmpeg stderr) and a final `done`; on a non-zero exit it emits `error` and throws.
|
|
12
|
+
*/
|
|
13
|
+
export declare function trimVideo(spec: VideoTrimSpec, onProgress?: (event: VideoProgressEvent) => void): Promise<void>;
|
|
14
|
+
/** Probe a media file's primary video dimensions + duration + container format. */
|
|
15
|
+
export declare function probeMedia(inputPath: string): Promise<ProbeResult>;
|
|
16
|
+
/** Extract a single frame at `atSeconds` from `inputPath` and write it to `output`. */
|
|
17
|
+
export declare function extractThumbnail(inputPath: string, atSeconds: number, output: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Codec-aware probe for the streaming pipeline (codecs + pixfmt + audio, so a caller can
|
|
20
|
+
* decide whether to stream-copy). Best-effort: returns {@link EMPTY_PROBE} on any failure —
|
|
21
|
+
* the pipeline stays usable (a node is always playable as raw range meanwhile).
|
|
22
|
+
*/
|
|
23
|
+
export declare function probeVideoStream(inputPath: string): Promise<VideoProbe>;
|
|
24
|
+
/**
|
|
25
|
+
* Run an ffmpeg transcode while parsing its `-progress pipe:1` stream into a 0..99 percent
|
|
26
|
+
* (so a slow re-encode reports live progress). `durationSec` is the source duration; 0 ⇒ no
|
|
27
|
+
* percent. `args` should NOT include the leading `ffmpeg`/`-y`. Resolves true on success;
|
|
28
|
+
* on failure it passes the trailing stderr to `onError` (the app logs) and resolves false.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runFfmpegProgress(args: string[], durationSec: number, onProgress?: (pct: number) => void, onError?: (stderrTail: string) => void): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* On-demand single JPEG frame at `atSeconds` (scaled to `maxWidth`) as bytes, or null on
|
|
33
|
+
* failure. `-ss` before `-i` fast-seeks — instant on a faststart file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractFrameJpeg(filePath: string, atSeconds: number, maxWidth?: number): Promise<Uint8Array | null>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Video trim / probe / thumbnail via native ffmpeg + ffprobe, spawned with Bun.spawn.
|
|
2
|
+
// All argv construction + output parsing lives in the pure `ffmpeg.ts` (tested without
|
|
3
|
+
// ffmpeg); this file is the thin process shell. The binaries must be on PATH, or set
|
|
4
|
+
// `FFMPEG_PATH` / `FFPROBE_PATH`. Replaces ca's hand-rolled spawn boilerplate.
|
|
5
|
+
import { assertValidVideoTrimSpec, } from 'cursedbelt-core/media';
|
|
6
|
+
import { buildProbeArgs, buildThumbnailArgs, buildTrimArgs, parseFfmpegProgress, parseFfprobe, } from 'cursedbelt-core/media/ffmpeg';
|
|
7
|
+
import { buildFrameJpegArgs, buildStreamProbeArgs, EMPTY_PROBE, parseProgressMicros, parseStreamProbe, } from './streaming';
|
|
8
|
+
const FFMPEG = process.env.FFMPEG_PATH || 'ffmpeg';
|
|
9
|
+
const FFPROBE = process.env.FFPROBE_PATH || 'ffprobe';
|
|
10
|
+
/** Is ffmpeg runnable? Cheap probe used to skip server media ops / tests cleanly. */
|
|
11
|
+
export async function isFfmpegAvailable(bin = FFMPEG) {
|
|
12
|
+
try {
|
|
13
|
+
const proc = Bun.spawn([bin, '-version'], { stdout: 'ignore', stderr: 'ignore' });
|
|
14
|
+
await proc.exited;
|
|
15
|
+
return proc.exitCode === 0;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Is ffprobe runnable? */
|
|
22
|
+
export function isFfprobeAvailable() {
|
|
23
|
+
return isFfmpegAvailable(FFPROBE);
|
|
24
|
+
}
|
|
25
|
+
/** Drain a stderr stream line-by-line (ffmpeg uses \r to update progress in place). */
|
|
26
|
+
async function readProgress(stream, totalSeconds, onProgress) {
|
|
27
|
+
const decoder = new TextDecoder();
|
|
28
|
+
let buffer = '';
|
|
29
|
+
let tail = '';
|
|
30
|
+
for await (const chunk of stream) {
|
|
31
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
32
|
+
buffer += text;
|
|
33
|
+
tail = (tail + text).slice(-4000); // keep a bounded tail for an error message
|
|
34
|
+
const segments = buffer.split(/[\r\n]/);
|
|
35
|
+
buffer = segments.pop() ?? '';
|
|
36
|
+
if (onProgress) {
|
|
37
|
+
for (const seg of segments) {
|
|
38
|
+
const ev = parseFfmpegProgress(seg, totalSeconds);
|
|
39
|
+
if (ev)
|
|
40
|
+
onProgress(ev);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return tail;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Trim `[startSeconds, endSeconds]` from `inputPath` into `outputPath`, re-encoding to
|
|
48
|
+
* the requested format/quality. `onProgress` receives `encode` events (parsed from
|
|
49
|
+
* ffmpeg stderr) and a final `done`; on a non-zero exit it emits `error` and throws.
|
|
50
|
+
*/
|
|
51
|
+
export async function trimVideo(spec, onProgress) {
|
|
52
|
+
assertValidVideoTrimSpec(spec);
|
|
53
|
+
const total = spec.endSeconds - spec.startSeconds;
|
|
54
|
+
onProgress?.({ phase: 'encode', progressPercent: 0 });
|
|
55
|
+
const proc = Bun.spawn([FFMPEG, ...buildTrimArgs(spec)], { stdout: 'ignore', stderr: 'pipe' });
|
|
56
|
+
const tail = await readProgress(proc.stderr, total, onProgress);
|
|
57
|
+
await proc.exited;
|
|
58
|
+
if (proc.exitCode !== 0) {
|
|
59
|
+
const message = `ffmpeg trim failed (exit ${proc.exitCode}): ${tail
|
|
60
|
+
.trim()
|
|
61
|
+
.split(/[\r\n]/)
|
|
62
|
+
.pop() ?? ''}`;
|
|
63
|
+
onProgress?.({ phase: 'error', error: message });
|
|
64
|
+
throw new Error(message);
|
|
65
|
+
}
|
|
66
|
+
onProgress?.({ phase: 'done', progressPercent: 100 });
|
|
67
|
+
}
|
|
68
|
+
/** Probe a media file's primary video dimensions + duration + container format. */
|
|
69
|
+
export async function probeMedia(inputPath) {
|
|
70
|
+
const proc = Bun.spawn([FFPROBE, ...buildProbeArgs(inputPath)], {
|
|
71
|
+
stdout: 'pipe',
|
|
72
|
+
stderr: 'pipe',
|
|
73
|
+
});
|
|
74
|
+
const stdout = await new Response(proc.stdout).text();
|
|
75
|
+
await proc.exited;
|
|
76
|
+
if (proc.exitCode !== 0) {
|
|
77
|
+
throw new Error(`ffprobe failed (exit ${proc.exitCode}) for ${inputPath}`);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return parseFfprobe(JSON.parse(stdout));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
throw new Error(`ffprobe returned unparseable JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Extract a single frame at `atSeconds` from `inputPath` and write it to `output`. */
|
|
87
|
+
export async function extractThumbnail(inputPath, atSeconds, output) {
|
|
88
|
+
const proc = Bun.spawn([FFMPEG, ...buildThumbnailArgs(inputPath, atSeconds, output)], {
|
|
89
|
+
stdout: 'ignore',
|
|
90
|
+
stderr: 'pipe',
|
|
91
|
+
});
|
|
92
|
+
const tail = await new Response(proc.stderr).text();
|
|
93
|
+
await proc.exited;
|
|
94
|
+
if (proc.exitCode !== 0) {
|
|
95
|
+
throw new Error(`ffmpeg thumbnail failed (exit ${proc.exitCode}): ${tail
|
|
96
|
+
.trim()
|
|
97
|
+
.split(/[\r\n]/)
|
|
98
|
+
.pop() ?? ''}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ── Streaming-pipeline spawn runners (companions to streaming.ts's pure builders) ───────
|
|
102
|
+
/**
|
|
103
|
+
* Codec-aware probe for the streaming pipeline (codecs + pixfmt + audio, so a caller can
|
|
104
|
+
* decide whether to stream-copy). Best-effort: returns {@link EMPTY_PROBE} on any failure —
|
|
105
|
+
* the pipeline stays usable (a node is always playable as raw range meanwhile).
|
|
106
|
+
*/
|
|
107
|
+
export async function probeVideoStream(inputPath) {
|
|
108
|
+
try {
|
|
109
|
+
const proc = Bun.spawn([FFPROBE, ...buildStreamProbeArgs(inputPath)], {
|
|
110
|
+
stdout: 'pipe',
|
|
111
|
+
stderr: 'ignore',
|
|
112
|
+
});
|
|
113
|
+
const stdout = await new Response(proc.stdout).text();
|
|
114
|
+
await proc.exited;
|
|
115
|
+
if (proc.exitCode !== 0)
|
|
116
|
+
return EMPTY_PROBE;
|
|
117
|
+
return parseStreamProbe(JSON.parse(stdout));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return EMPTY_PROBE;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Run an ffmpeg transcode while parsing its `-progress pipe:1` stream into a 0..99 percent
|
|
125
|
+
* (so a slow re-encode reports live progress). `durationSec` is the source duration; 0 ⇒ no
|
|
126
|
+
* percent. `args` should NOT include the leading `ffmpeg`/`-y`. Resolves true on success;
|
|
127
|
+
* on failure it passes the trailing stderr to `onError` (the app logs) and resolves false.
|
|
128
|
+
*/
|
|
129
|
+
export async function runFfmpegProgress(args, durationSec, onProgress, onError) {
|
|
130
|
+
const proc = Bun.spawn([FFMPEG, '-y', '-progress', 'pipe:1', '-nostats', ...args], {
|
|
131
|
+
stdout: 'pipe',
|
|
132
|
+
stderr: 'pipe',
|
|
133
|
+
});
|
|
134
|
+
if (durationSec > 0 && onProgress && proc.stdout) {
|
|
135
|
+
void (async () => {
|
|
136
|
+
const decoder = new TextDecoder();
|
|
137
|
+
let buf = '';
|
|
138
|
+
try {
|
|
139
|
+
for await (const chunk of proc.stdout) {
|
|
140
|
+
buf += decoder.decode(chunk, { stream: true });
|
|
141
|
+
const lines = buf.split('\n');
|
|
142
|
+
buf = lines.pop() ?? '';
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const us = parseProgressMicros(line);
|
|
145
|
+
if (us === null)
|
|
146
|
+
continue;
|
|
147
|
+
onProgress(Math.max(0, Math.min(99, Math.round((us / 1e6 / durationSec) * 100))));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* progress is best-effort */
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
}
|
|
156
|
+
await proc.exited;
|
|
157
|
+
if (proc.exitCode !== 0) {
|
|
158
|
+
if (onError)
|
|
159
|
+
onError((await new Response(proc.stderr).text()).slice(-600));
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* On-demand single JPEG frame at `atSeconds` (scaled to `maxWidth`) as bytes, or null on
|
|
166
|
+
* failure. `-ss` before `-i` fast-seeks — instant on a faststart file.
|
|
167
|
+
*/
|
|
168
|
+
export async function extractFrameJpeg(filePath, atSeconds, maxWidth = 240) {
|
|
169
|
+
try {
|
|
170
|
+
const proc = Bun.spawn([FFMPEG, '-y', ...buildFrameJpegArgs(filePath, atSeconds, maxWidth)], {
|
|
171
|
+
stdout: 'pipe',
|
|
172
|
+
stderr: 'ignore',
|
|
173
|
+
});
|
|
174
|
+
const bytes = new Uint8Array(await new Response(proc.stdout).arrayBuffer());
|
|
175
|
+
await proc.exited;
|
|
176
|
+
return proc.exitCode === 0 && bytes.length > 0 ? bytes : null;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory, batched writer for `request_metrics`. To keep request latency
|
|
4
|
+
* untouched we never write per request: rows land in a buffer and a single batched
|
|
5
|
+
* transaction flushes them on an interval (and eagerly when the buffer is large).
|
|
6
|
+
*
|
|
7
|
+
* This is the reusable successor to ca's `metricsBuffer` module, retargeted at the
|
|
8
|
+
* task-22 `request_metrics` schema (no `created_at` column — `ts` is the epoch-ms
|
|
9
|
+
* timestamp). The flush timer is `unref()`'d so it never keeps the process alive.
|
|
10
|
+
*/
|
|
11
|
+
export interface MetricRow {
|
|
12
|
+
/** Epoch ms. */
|
|
13
|
+
ts: number;
|
|
14
|
+
method: string | null;
|
|
15
|
+
/** Normalized route label (see `normalizeRoute`). */
|
|
16
|
+
route: string | null;
|
|
17
|
+
status: number | null;
|
|
18
|
+
durationMs: number | null;
|
|
19
|
+
bytesOut: number | null;
|
|
20
|
+
userId: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface MetricsBufferOptions {
|
|
23
|
+
db: Database;
|
|
24
|
+
/** Flush cadence in ms. Default: 1500. */
|
|
25
|
+
flushIntervalMs?: number;
|
|
26
|
+
/** Eager-flush threshold — caps memory if a flush stalls. Default: 5000. */
|
|
27
|
+
maxSize?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface MetricsBuffer {
|
|
30
|
+
/** Enqueue one request's metrics (non-blocking; flushed in batches). */
|
|
31
|
+
record(row: MetricRow): void;
|
|
32
|
+
/** Force-flush the buffer now (also used on shutdown + in tests). */
|
|
33
|
+
flush(): void;
|
|
34
|
+
/** Stop the flush timer and drain whatever remains. */
|
|
35
|
+
stop(): void;
|
|
36
|
+
/** Rows currently buffered (not yet flushed). */
|
|
37
|
+
readonly size: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function createMetricsBuffer(opts: MetricsBufferOptions): MetricsBuffer;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function createMetricsBuffer(opts) {
|
|
2
|
+
const { db, flushIntervalMs = 1500, maxSize = 5000 } = opts;
|
|
3
|
+
let buffer = [];
|
|
4
|
+
const insert = db.prepare(`INSERT INTO request_metrics (id, ts, method, route, status, duration_ms, bytes_out, user_id)
|
|
5
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
6
|
+
const insertMany = db.transaction((rows) => {
|
|
7
|
+
for (const r of rows) {
|
|
8
|
+
insert.run(crypto.randomUUID(), r.ts, r.method, r.route, r.status, r.durationMs, r.bytesOut, r.userId);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const flush = () => {
|
|
12
|
+
if (buffer.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
const batch = buffer;
|
|
15
|
+
buffer = [];
|
|
16
|
+
insertMany(batch);
|
|
17
|
+
};
|
|
18
|
+
const timer = setInterval(flush, flushIntervalMs);
|
|
19
|
+
// Never let the metrics flusher hold the event loop / process open.
|
|
20
|
+
timer.unref?.();
|
|
21
|
+
return {
|
|
22
|
+
record(row) {
|
|
23
|
+
buffer.push(row);
|
|
24
|
+
if (buffer.length >= maxSize)
|
|
25
|
+
flush();
|
|
26
|
+
},
|
|
27
|
+
flush,
|
|
28
|
+
stop() {
|
|
29
|
+
clearInterval(timer);
|
|
30
|
+
flush();
|
|
31
|
+
},
|
|
32
|
+
get size() {
|
|
33
|
+
return buffer.length;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a request into a stable, low-cardinality route label for metric aggregation
|
|
3
|
+
* (e.g. `/api/v1/notes/:id`). The method is tracked separately.
|
|
4
|
+
*
|
|
5
|
+
* Two wrinkles, same as the old Express normalizer:
|
|
6
|
+
* 1. ctgr GET-tunnelled mutations match the `/ctgr<xx>` twin route — we map them
|
|
7
|
+
* back to the real verb + path so a POST and its ctgr twin aggregate together.
|
|
8
|
+
* 2. Unmatched requests (404s, static, the SPA fallback) have no matched route
|
|
9
|
+
* pattern, so we collapse dynamic-looking segments to avoid unbounded
|
|
10
|
+
* cardinality blowing up the metrics table.
|
|
11
|
+
*/
|
|
12
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
const LONG_HEX_RE = /^[0-9a-f]{16,}$/i;
|
|
14
|
+
// The ctgr twin URL is `/ctgr<last-2-of-method><url>`, so the two-letter suffix
|
|
15
|
+
// recovers the original verb (POST→st, PUT→ut, PATCH→ch, DELETE→te).
|
|
16
|
+
const CTGR_METHOD = { st: 'POST', ut: 'PUT', ch: 'PATCH', te: 'DELETE' };
|
|
17
|
+
function collapseSegment(seg) {
|
|
18
|
+
if (!seg)
|
|
19
|
+
return seg;
|
|
20
|
+
if (/^\d+$/.test(seg))
|
|
21
|
+
return ':id';
|
|
22
|
+
if (UUID_RE.test(seg))
|
|
23
|
+
return ':id';
|
|
24
|
+
if (LONG_HEX_RE.test(seg))
|
|
25
|
+
return ':id';
|
|
26
|
+
// Long opaque tokens (encoded paths, blobs) collapse so they don't explode route
|
|
27
|
+
// cardinality. Plain words stay as-is.
|
|
28
|
+
if (seg.length >= 24)
|
|
29
|
+
return ':id';
|
|
30
|
+
return seg;
|
|
31
|
+
}
|
|
32
|
+
function collapsePath(path) {
|
|
33
|
+
const parts = path.split('/').filter(Boolean).map(collapseSegment);
|
|
34
|
+
return `/${parts.join('/')}`;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeRoute(c) {
|
|
37
|
+
let method = c.req.method;
|
|
38
|
+
// Hono's matched route pattern (with :params) is the ideal label. A wildcard
|
|
39
|
+
// (`/*`, `*`) or empty pattern means "no real match" → collapse the raw path.
|
|
40
|
+
// `c.req.routePath` THROWS when nothing matched (e.g. a 404 / the SPA fallback),
|
|
41
|
+
// so guard it — an unmatched request must still produce a metrics label.
|
|
42
|
+
let matched;
|
|
43
|
+
try {
|
|
44
|
+
matched = c.req.routePath;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
matched = undefined;
|
|
48
|
+
}
|
|
49
|
+
let route = matched && !matched.includes('*') ? matched : collapsePath(c.req.path.split('?')[0]) || '/';
|
|
50
|
+
// Map a ctgr GET-twin back to its real verb + url:
|
|
51
|
+
// /api/ctgrch/notes/:id → PATCH /api/notes/:id
|
|
52
|
+
const ctgr = route.match(/\/ctgr([a-z]{2})(?=\/|$)/i);
|
|
53
|
+
if (ctgr) {
|
|
54
|
+
method = CTGR_METHOD[ctgr[1].toLowerCase()] ?? method;
|
|
55
|
+
route = route.replace(/\/ctgr[a-z]{2}/i, '') || '/';
|
|
56
|
+
}
|
|
57
|
+
return { method, route };
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { CursedbeltEnv } from '../context';
|
|
3
|
+
/**
|
|
4
|
+
* Assign every request a correlation id: reuse a safe inbound `x-correlation-id`
|
|
5
|
+
* (so a trace survives the proxy hop), else mint a `crypto.randomUUID()`. Stored on
|
|
6
|
+
* `c.set('correlationId', id)` for downstream middleware (request logger, error
|
|
7
|
+
* envelope) and echoed on the response header. Mount this FIRST.
|
|
8
|
+
*/
|
|
9
|
+
export declare function correlationId(): MiddlewareHandler<CursedbeltEnv>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const HEADER = 'x-correlation-id';
|
|
2
|
+
// Accept a client-supplied id only if it is a sane, header-safe token (no CRLF /
|
|
3
|
+
// control chars / unbounded length) — otherwise mint a fresh one. This both
|
|
4
|
+
// preserves trace ids across a proxy hop and blocks response-header injection.
|
|
5
|
+
const SAFE_ID = /^[A-Za-z0-9._:-]{1,200}$/;
|
|
6
|
+
/**
|
|
7
|
+
* Assign every request a correlation id: reuse a safe inbound `x-correlation-id`
|
|
8
|
+
* (so a trace survives the proxy hop), else mint a `crypto.randomUUID()`. Stored on
|
|
9
|
+
* `c.set('correlationId', id)` for downstream middleware (request logger, error
|
|
10
|
+
* envelope) and echoed on the response header. Mount this FIRST.
|
|
11
|
+
*/
|
|
12
|
+
export function correlationId() {
|
|
13
|
+
return async (c, next) => {
|
|
14
|
+
const inbound = c.req.header(HEADER);
|
|
15
|
+
const id = inbound && SAFE_ID.test(inbound) ? inbound : crypto.randomUUID();
|
|
16
|
+
c.set('correlationId', id);
|
|
17
|
+
c.header(HEADER, id);
|
|
18
|
+
await next();
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type OriginRule } from 'cwip/http';
|
|
2
|
+
import type { MiddlewareHandler } from 'hono';
|
|
3
|
+
/**
|
|
4
|
+
* Cross-app CORS for the split deploy shape every cursedbelt app shares (SPA on
|
|
5
|
+
* one origin, API on another — e.g. `example.com` → `api.example.com`), built on
|
|
6
|
+
* Hono's `cors` + cwip's `compileOriginMatcher` rather than a per-app allow-list
|
|
7
|
+
* reimplementation.
|
|
8
|
+
*
|
|
9
|
+
* The one-line configuration is `siteDomain`: it allows the https apex AND every
|
|
10
|
+
* subdomain (`siteOriginRules`), so standing up a new subdomain can never break
|
|
11
|
+
* CORS again. `origins` adds exact origins / `*.domain` wildcards / RegExps on
|
|
12
|
+
* top (dev Vite ports, a partner origin). Responses to allowed origins echo the
|
|
13
|
+
* origin with `Access-Control-Allow-Credentials: true` (cookie-auth apps);
|
|
14
|
+
* disallowed origins get NO CORS headers, which is the browser-visible rejection.
|
|
15
|
+
*/
|
|
16
|
+
export interface CorsOpts {
|
|
17
|
+
/**
|
|
18
|
+
* The site's apex domain (`'example.com'`) — allows `https://example.com` and
|
|
19
|
+
* `https://<any-subdomain>.example.com` in one rule.
|
|
20
|
+
*/
|
|
21
|
+
siteDomain?: string;
|
|
22
|
+
/** Additional origin rules: exact, `*.domain.tld` wildcard, or RegExp. */
|
|
23
|
+
origins?: OriginRule[];
|
|
24
|
+
/** Send `Access-Control-Allow-Credentials: true` (default `true` — cookie auth). */
|
|
25
|
+
credentials?: boolean;
|
|
26
|
+
/** Called with each rejected `Origin` (for logging/metrics). */
|
|
27
|
+
onReject?: (origin: string) => void;
|
|
28
|
+
}
|
|
29
|
+
/** Build the Hono middleware from {@link CorsOpts}. Compiles the matcher once. */
|
|
30
|
+
export declare const corsAllowlist: (opts: CorsOpts) => MiddlewareHandler;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve {@link CorsOpts} from the conventional env vars, `undefined` when
|
|
33
|
+
* neither is set (same-origin deploy — no CORS middleware needed):
|
|
34
|
+
* - `CORS_SITE_DOMAIN=example.com` — the systemic knob: apex + every subdomain.
|
|
35
|
+
* - `CORS_ORIGINS=https://a.com,*.b.com` — exact/wildcard extras (or the whole
|
|
36
|
+
* list, for apps that predate `CORS_SITE_DOMAIN`).
|
|
37
|
+
*/
|
|
38
|
+
export declare const corsOptsFromEnv: (env?: Record<string, string | undefined>) => CorsOpts | undefined;
|