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,168 @@
|
|
|
1
|
+
import type { Server, ServerWebSocket } from 'bun';
|
|
2
|
+
import type { MiddlewareHandler } from 'hono';
|
|
3
|
+
import { ApiError, toErrorEnvelope } from '../errors';
|
|
4
|
+
import { computePresence } from './presence';
|
|
5
|
+
import { joinRoom, ROOM_META_KEY } from './room';
|
|
6
|
+
import type {
|
|
7
|
+
WsChannel,
|
|
8
|
+
WsClient,
|
|
9
|
+
WsData,
|
|
10
|
+
WsHandshake,
|
|
11
|
+
WsHub,
|
|
12
|
+
WsHubOpts,
|
|
13
|
+
WsMessage,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_HEARTBEAT_MS = 30_000;
|
|
17
|
+
const DEFAULT_STALE_MS = 120_000;
|
|
18
|
+
/** Bun's `idleTimeout` ceiling (seconds). */
|
|
19
|
+
const BUN_IDLE_TIMEOUT_MAX_S = 960;
|
|
20
|
+
|
|
21
|
+
/** Serialize the wire envelope once per fan-out. */
|
|
22
|
+
const encode = (event: string, data: unknown): string => JSON.stringify({ event, data });
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The heartbeat sweep, factored out as a pure function so it's testable without faking
|
|
26
|
+
* timers: ping every live client, close (don't delete — let the `close` handler fire
|
|
27
|
+
* `onDisconnect` + remove) any unheard-from past `staleMs`. The hub's `setInterval`
|
|
28
|
+
* calls this with `Date.now()`.
|
|
29
|
+
*/
|
|
30
|
+
export function sweepStaleClients(clients: Iterable<WsClient>, now: number, staleMs: number): void {
|
|
31
|
+
for (const client of clients) {
|
|
32
|
+
if (now - client.lastPingAt > staleMs) {
|
|
33
|
+
client.ws.close(1001, 'stale');
|
|
34
|
+
} else {
|
|
35
|
+
client.ws.ping();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a typed WebSocket hub on Bun's native upgrade. The hub owns the transport —
|
|
42
|
+
* connection registry, channel routing, heartbeat/stale-cull, and presence-as-a-view;
|
|
43
|
+
* the registered {@link WsChannel}s own the business logic. Mount {@link WsHub.upgradeHandler}
|
|
44
|
+
* on the Hono upgrade route(s) and hand {@link WsHub.handlers} to `serveBun`'s `websocket`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const hub = createWsHub({ channels: wsChannels, authenticate });
|
|
48
|
+
* app.get('/ws/:channel', hub.upgradeHandler);
|
|
49
|
+
* serveBun(app, { port, websocket: hub.handlers });
|
|
50
|
+
*/
|
|
51
|
+
export function createWsHub<T extends Record<string, WsChannel<any>>>(opts: WsHubOpts<T>): WsHub {
|
|
52
|
+
const clients = new Map<string, WsClient>();
|
|
53
|
+
const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
54
|
+
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
55
|
+
|
|
56
|
+
const channelOf = (name: string): WsChannel<any> | undefined => opts.channels[name];
|
|
57
|
+
const clientFromWs = (ws: ServerWebSocket<WsData>): WsClient | undefined =>
|
|
58
|
+
clients.get(ws.data.clientId);
|
|
59
|
+
|
|
60
|
+
// Our heartbeat is authoritative (it also drives presence), so disable Bun's auto-ping
|
|
61
|
+
// and let its idle timeout backstop just beyond our stale window.
|
|
62
|
+
const idleTimeout = Math.min(Math.ceil(staleMs / 1000), BUN_IDLE_TIMEOUT_MAX_S);
|
|
63
|
+
|
|
64
|
+
const heartbeat = setInterval(() => {
|
|
65
|
+
sweepStaleClients(clients.values(), Date.now(), staleMs);
|
|
66
|
+
}, heartbeatMs);
|
|
67
|
+
// Don't keep the process alive for the heartbeat alone (server tests, graceful exit).
|
|
68
|
+
heartbeat.unref?.();
|
|
69
|
+
|
|
70
|
+
const upgradeHandler: MiddlewareHandler = async (c) => {
|
|
71
|
+
const channel = c.req.param('channel') ?? c.req.query('channel') ?? 'default';
|
|
72
|
+
const room = c.req.query('room') ?? undefined;
|
|
73
|
+
const handshake: WsHandshake = { headers: c.req.raw.headers, url: c.req.url, channel, room };
|
|
74
|
+
|
|
75
|
+
let user: Awaited<ReturnType<typeof opts.authenticate>>;
|
|
76
|
+
try {
|
|
77
|
+
user = await opts.authenticate(handshake);
|
|
78
|
+
} catch {
|
|
79
|
+
user = null;
|
|
80
|
+
}
|
|
81
|
+
if (!user) return c.json(toErrorEnvelope(new ApiError(401, 'Unauthorized')), 401);
|
|
82
|
+
|
|
83
|
+
const data: WsData = { clientId: crypto.randomUUID(), user, channel, room };
|
|
84
|
+
const server = c.env as unknown as Server<WsData>;
|
|
85
|
+
const upgraded = server.upgrade(c.req.raw, { data });
|
|
86
|
+
if (!upgraded) return c.text('Upgrade required', 426);
|
|
87
|
+
// Bun has taken over the socket and sent the 101; this Response is discarded by
|
|
88
|
+
// Bun.serve. (A real 101 can't be constructed — the Fetch Response ctor forbids 1xx.)
|
|
89
|
+
return c.body(null, 204);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const hub: WsHub = {
|
|
93
|
+
upgradeHandler,
|
|
94
|
+
handlers: {
|
|
95
|
+
idleTimeout,
|
|
96
|
+
sendPings: false,
|
|
97
|
+
open(ws) {
|
|
98
|
+
const { clientId, user, channel, room } = ws.data;
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const client: WsClient = {
|
|
101
|
+
id: clientId,
|
|
102
|
+
user,
|
|
103
|
+
ws,
|
|
104
|
+
channel,
|
|
105
|
+
connectedAt: now,
|
|
106
|
+
lastPingAt: now,
|
|
107
|
+
meta: {},
|
|
108
|
+
};
|
|
109
|
+
if (room) joinRoom(client, room);
|
|
110
|
+
clients.set(clientId, client);
|
|
111
|
+
channelOf(channel)?.onConnect?.(client, hub);
|
|
112
|
+
},
|
|
113
|
+
message(ws, raw) {
|
|
114
|
+
const client = clientFromWs(ws);
|
|
115
|
+
if (!client) return;
|
|
116
|
+
client.lastPingAt = Date.now();
|
|
117
|
+
let msg: WsMessage;
|
|
118
|
+
try {
|
|
119
|
+
msg = JSON.parse(typeof raw === 'string' ? raw : raw.toString());
|
|
120
|
+
} catch {
|
|
121
|
+
return; // ignore malformed frames rather than crash the socket
|
|
122
|
+
}
|
|
123
|
+
channelOf(client.channel)?.onMessage(client, msg, hub);
|
|
124
|
+
},
|
|
125
|
+
// A pong (reply to our heartbeat ping) proves liveness without an app message.
|
|
126
|
+
pong(ws) {
|
|
127
|
+
const client = clientFromWs(ws);
|
|
128
|
+
if (client) client.lastPingAt = Date.now();
|
|
129
|
+
},
|
|
130
|
+
close(ws) {
|
|
131
|
+
const client = clientFromWs(ws);
|
|
132
|
+
if (!client) return;
|
|
133
|
+
channelOf(client.channel)?.onDisconnect?.(client, hub);
|
|
134
|
+
clients.delete(client.id);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
broadcast(channel, event, data) {
|
|
138
|
+
const payload = encode(event, data);
|
|
139
|
+
for (const client of clients.values()) {
|
|
140
|
+
if (client.channel === channel) client.ws.send(payload);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
broadcastToRoom(channel, roomId, event, data) {
|
|
144
|
+
const payload = encode(event, data);
|
|
145
|
+
for (const client of clients.values()) {
|
|
146
|
+
if (client.channel === channel && client.meta[ROOM_META_KEY] === roomId) {
|
|
147
|
+
client.ws.send(payload);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
send(clientId, event, data) {
|
|
152
|
+
clients.get(clientId)?.ws.send(encode(event, data));
|
|
153
|
+
},
|
|
154
|
+
getClients(channel) {
|
|
155
|
+
const all = [...clients.values()];
|
|
156
|
+
return channel ? all.filter((client) => client.channel === channel) : all;
|
|
157
|
+
},
|
|
158
|
+
getClient: (id) => clients.get(id),
|
|
159
|
+
getPresence(channel) {
|
|
160
|
+
return computePresence(clients.values(), channel, channelOf(channel)?.presence ?? false);
|
|
161
|
+
},
|
|
162
|
+
close() {
|
|
163
|
+
clearInterval(heartbeat);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return hub;
|
|
168
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// cursedbelt-server WS hub — Bun-native upgrade, typed channel registry, heartbeat,
|
|
2
|
+
// presence-as-a-view, and the push-only event-bus subset. Pairs with the partysocket
|
|
3
|
+
// client in `cursedbelt/client`.
|
|
4
|
+
export { createEventBus } from './eventBus';
|
|
5
|
+
export { createWsHub } from './hub';
|
|
6
|
+
export { computePresence } from './presence';
|
|
7
|
+
export { joinRoom, leaveRoom, ROOM_META_KEY, roomOf } from './room';
|
|
8
|
+
export type {
|
|
9
|
+
EventBus,
|
|
10
|
+
EventBusData,
|
|
11
|
+
PresenceEntry,
|
|
12
|
+
PresenceState,
|
|
13
|
+
WsChannel,
|
|
14
|
+
WsClient,
|
|
15
|
+
WsData,
|
|
16
|
+
WsHandshake,
|
|
17
|
+
WsHub,
|
|
18
|
+
WsHubOpts,
|
|
19
|
+
WsMessage,
|
|
20
|
+
} from './types';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { AuthUser } from '../auth/types';
|
|
3
|
+
import { computePresence } from './presence';
|
|
4
|
+
import type { WsClient } from './types';
|
|
5
|
+
|
|
6
|
+
const user = (id: string): AuthUser => ({ id, roles: [], getOnly: false, sessionId: `s-${id}` });
|
|
7
|
+
const client = (id: string, channel: string, connectedAt: number): WsClient =>
|
|
8
|
+
({ id, user: user(id), channel, connectedAt, meta: {} }) as unknown as WsClient;
|
|
9
|
+
|
|
10
|
+
describe('computePresence', () => {
|
|
11
|
+
test('returns one row per client on the channel when enabled', () => {
|
|
12
|
+
const clients = [client('a', 'chat', 100), client('b', 'chat', 200), client('c', 'game', 300)];
|
|
13
|
+
expect(computePresence(clients, 'chat', true)).toEqual([
|
|
14
|
+
{ userId: 'a', connectedAt: 100 },
|
|
15
|
+
{ userId: 'b', connectedAt: 200 },
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('returns empty when presence is not enabled for the channel', () => {
|
|
20
|
+
const clients = [client('a', 'chat', 100)];
|
|
21
|
+
expect(computePresence(clients, 'chat', false)).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('returns empty for a channel with no clients', () => {
|
|
25
|
+
expect(computePresence([], 'chat', true)).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PresenceState, WsClient } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Presence is a *view* over the live client map, not a tracked structure — there is no
|
|
5
|
+
* `PresenceTracker` class to keep in sync. The hub's `getPresence(channel)` delegates
|
|
6
|
+
* here; a channel that wants to push presence does
|
|
7
|
+
* `hub.broadcast('chat', 'presence', hub.getPresence('chat'))` from its lifecycle hooks.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The presence snapshot for `channel`: one `{ userId, connectedAt }` row per live
|
|
11
|
+
* client on that channel, or empty when the channel doesn't opt into presence.
|
|
12
|
+
* Pure (no timers, no state) so it's trivially testable. */
|
|
13
|
+
export function computePresence(
|
|
14
|
+
clients: Iterable<WsClient>,
|
|
15
|
+
channel: string,
|
|
16
|
+
presenceEnabled: boolean,
|
|
17
|
+
): PresenceState {
|
|
18
|
+
if (!presenceEnabled) return [];
|
|
19
|
+
const rows: PresenceState = [];
|
|
20
|
+
for (const client of clients) {
|
|
21
|
+
if (client.channel === channel) {
|
|
22
|
+
rows.push({ userId: client.user.id, connectedAt: client.connectedAt });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return rows;
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { joinRoom, leaveRoom, ROOM_META_KEY, roomOf } from './room';
|
|
3
|
+
import type { WsClient } from './types';
|
|
4
|
+
|
|
5
|
+
const client = (): WsClient => ({ id: 'c', meta: {} }) as unknown as WsClient;
|
|
6
|
+
|
|
7
|
+
describe('room helpers', () => {
|
|
8
|
+
test('joinRoom sets the room meta under ROOM_META_KEY; roomOf reads it', () => {
|
|
9
|
+
const c = client();
|
|
10
|
+
expect(roomOf(c)).toBeUndefined();
|
|
11
|
+
joinRoom(c, 'lobby');
|
|
12
|
+
expect(c.meta[ROOM_META_KEY]).toBe('lobby');
|
|
13
|
+
expect(roomOf(c)).toBe('lobby');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('joinRoom replaces the current room', () => {
|
|
17
|
+
const c = client();
|
|
18
|
+
joinRoom(c, 'a');
|
|
19
|
+
joinRoom(c, 'b');
|
|
20
|
+
expect(roomOf(c)).toBe('b');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('leaveRoom clears membership', () => {
|
|
24
|
+
const c = client();
|
|
25
|
+
joinRoom(c, 'a');
|
|
26
|
+
leaveRoom(c);
|
|
27
|
+
expect(roomOf(c)).toBeUndefined();
|
|
28
|
+
expect(c.meta[ROOM_META_KEY]).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('roomOf ignores a non-string meta value', () => {
|
|
32
|
+
const c = client();
|
|
33
|
+
c.meta[ROOM_META_KEY] = 42;
|
|
34
|
+
expect(roomOf(c)).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { WsClient } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rooms are not a separate stateful abstraction — they are a typed convention over
|
|
5
|
+
* {@link WsClient.meta}. A channel puts a client in a room with {@link joinRoom}; the
|
|
6
|
+
* hub's `broadcastToRoom` matches on the same key. This replaces the old `wsRoom.ts` /
|
|
7
|
+
* `reconnectableRoom.ts` classes: the hub owns connections + fan-out, the channel owns
|
|
8
|
+
* membership + any history buffer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The {@link WsClient.meta} key a client's current room id lives under. The hub's
|
|
12
|
+
* `broadcastToRoom` reads the same key — keep them in lockstep. */
|
|
13
|
+
export const ROOM_META_KEY = 'roomId';
|
|
14
|
+
|
|
15
|
+
/** Put `client` in `roomId` (replaces any current room). */
|
|
16
|
+
export function joinRoom(client: WsClient, roomId: string): void {
|
|
17
|
+
client.meta[ROOM_META_KEY] = roomId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Remove `client` from its room (a subsequent room broadcast won't reach it). */
|
|
21
|
+
export function leaveRoom(client: WsClient): void {
|
|
22
|
+
delete client.meta[ROOM_META_KEY];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The client's current room id, or undefined if it isn't in one. */
|
|
26
|
+
export function roomOf(client: WsClient): string | undefined {
|
|
27
|
+
const room = client.meta[ROOM_META_KEY];
|
|
28
|
+
return typeof room === 'string' ? room : undefined;
|
|
29
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ServerWebSocket, WebSocketHandler } from 'bun';
|
|
2
|
+
import type { MiddlewareHandler } from 'hono';
|
|
3
|
+
import type { AuthUser } from '../auth/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared types for the cursedbelt WebSocket hub.
|
|
7
|
+
*
|
|
8
|
+
* cursedbelt owns the *transport* — connection lifecycle, channel routing,
|
|
9
|
+
* heartbeat/stale-cull, presence-as-a-view, and an injected `authenticate`
|
|
10
|
+
* callback. The *channels* (what each message means, the per-feature game/chat
|
|
11
|
+
* state) stay app-business; an app registers its channels into the hub and
|
|
12
|
+
* consumes the broadcast helpers. ru's push-only variant is {@link EventBus}.
|
|
13
|
+
*
|
|
14
|
+
* Built on Bun's native WS upgrade (`Server.upgrade` + `Bun.serve { websocket }`),
|
|
15
|
+
* surfaced through Hono via {@link WsHub.upgradeHandler}. No `ws`-lib, no adapter.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The JSON envelope every frame uses, both directions: `{ event, data }`. The hub
|
|
19
|
+
* emits this from {@link WsHub.broadcast}/`send`; channels receive the parsed inbound
|
|
20
|
+
* envelope as their `onMessage` payload. `T` narrows `data` for a typed channel. */
|
|
21
|
+
export interface WsMessage<T = unknown> {
|
|
22
|
+
event: string;
|
|
23
|
+
data: T;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** What the hub stashes on the Bun `ServerWebSocket.data` at upgrade time so `open`
|
|
27
|
+
* can register the client without a second handshake round-trip (see the gotcha in
|
|
28
|
+
* the task spec). `room` is the optional `?room=` query param — a named room within
|
|
29
|
+
* the channel, applied to {@link WsClient.meta} on connect. */
|
|
30
|
+
export interface WsData {
|
|
31
|
+
clientId: string;
|
|
32
|
+
user: AuthUser;
|
|
33
|
+
channel: string;
|
|
34
|
+
room?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The minimal view of the WS handshake handed to {@link WsHubOpts.authenticate}. The
|
|
38
|
+
* app reads its auth cookie off `headers` and verifies it (cursedbelt never reads the
|
|
39
|
+
* cookie itself — auth *model* is app business). */
|
|
40
|
+
export interface WsHandshake {
|
|
41
|
+
headers: Headers;
|
|
42
|
+
url: string;
|
|
43
|
+
channel: string;
|
|
44
|
+
room?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A live connection. `meta` is the per-channel scratch bag (room membership via the
|
|
48
|
+
* `room.ts` helpers, game seat, typing flag, …) — the hub never interprets it beyond
|
|
49
|
+
* {@link WsHub.broadcastToRoom}'s room match. `lastPingAt` is "last time we heard from
|
|
50
|
+
* this client" (an inbound message OR a pong); the heartbeat culls on it. */
|
|
51
|
+
export interface WsClient {
|
|
52
|
+
id: string;
|
|
53
|
+
user: AuthUser;
|
|
54
|
+
ws: ServerWebSocket<WsData>;
|
|
55
|
+
channel: string;
|
|
56
|
+
connectedAt: number;
|
|
57
|
+
lastPingAt: number;
|
|
58
|
+
meta: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A business channel registered into the hub. The hub routes inbound frames to
|
|
62
|
+
* `onMessage` by {@link WsClient.channel} and fires the lifecycle hooks; everything
|
|
63
|
+
* the handler *does* (broadcast shape, game state, history) is app business. */
|
|
64
|
+
export interface WsChannel<TMsg = WsMessage> {
|
|
65
|
+
name: string;
|
|
66
|
+
/** Fired after the client is registered (room meta already applied). */
|
|
67
|
+
onConnect?(client: WsClient, hub: WsHub): void;
|
|
68
|
+
/** Fired once per inbound frame (the parsed `{ event, data }` envelope). */
|
|
69
|
+
onMessage(client: WsClient, msg: TMsg, hub: WsHub): void;
|
|
70
|
+
/** Fired when the socket closes (explicit, stale-culled, or peer-dropped). */
|
|
71
|
+
onDisconnect?(client: WsClient, hub: WsHub): void;
|
|
72
|
+
/** Whether this channel contributes to {@link WsHub.getPresence}. Default: false. */
|
|
73
|
+
presence?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A presence row — the public, business-safe view of a connected client. */
|
|
77
|
+
export interface PresenceEntry {
|
|
78
|
+
userId: string;
|
|
79
|
+
connectedAt: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The presence snapshot for a channel: one row per live, presence-enabled client. */
|
|
83
|
+
export type PresenceState = PresenceEntry[];
|
|
84
|
+
|
|
85
|
+
export interface WsHubOpts<TChannelMap extends Record<string, WsChannel<any>>> {
|
|
86
|
+
/** The business channel registry, keyed by channel name (the `?channel=`/`:channel`
|
|
87
|
+
* the client connects to). */
|
|
88
|
+
channels: TChannelMap;
|
|
89
|
+
/** Cookie→user verification for the handshake. Return `null` to reject the upgrade
|
|
90
|
+
* with a 401 (an HTTP 401, not a WS close). */
|
|
91
|
+
authenticate(handshake: WsHandshake): Promise<AuthUser | null>;
|
|
92
|
+
/** How often the heartbeat pings live clients + sweeps stale ones. Default 30_000. */
|
|
93
|
+
heartbeatIntervalMs?: number;
|
|
94
|
+
/** A client unheard-from for longer than this is culled. Default 120_000 (2 min). */
|
|
95
|
+
staleMs?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface WsHub {
|
|
99
|
+
/** The Bun `websocket` config — pass straight to `serveBun(app, { websocket: hub.handlers })`.
|
|
100
|
+
* `message` is required by Bun; `open`/`close`/`pong` drive lifecycle + liveness. */
|
|
101
|
+
handlers: WebSocketHandler<WsData>;
|
|
102
|
+
/** Mount on the Hono upgrade route(s): `app.get('/ws/:channel', hub.upgradeHandler)`.
|
|
103
|
+
* Authenticates, then calls `Server.upgrade` (Bun-native). */
|
|
104
|
+
upgradeHandler: MiddlewareHandler;
|
|
105
|
+
/** Send `{ event, data }` to every client on `channel`. */
|
|
106
|
+
broadcast(channel: string, event: string, data: unknown): void;
|
|
107
|
+
/** Send `{ event, data }` to every client on `channel` whose room (meta) is `roomId`. */
|
|
108
|
+
broadcastToRoom(channel: string, roomId: string, event: string, data: unknown): void;
|
|
109
|
+
/** Send `{ event, data }` to one client by id (no-op if gone). */
|
|
110
|
+
send(clientId: string, event: string, data: unknown): void;
|
|
111
|
+
/** Live clients, optionally filtered to one channel. */
|
|
112
|
+
getClients(channel?: string): WsClient[];
|
|
113
|
+
/** One client by id, or undefined. */
|
|
114
|
+
getClient(clientId: string): WsClient | undefined;
|
|
115
|
+
/** The presence snapshot for a presence-enabled channel (empty otherwise). */
|
|
116
|
+
getPresence(channel: string): PresenceState;
|
|
117
|
+
/** Stop the heartbeat timer (call on shutdown / in tests to avoid a leaked interval). */
|
|
118
|
+
close(): void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** ru's push-only subset: broadcast-to-all, no auth, no inbound routing. One bus,
|
|
122
|
+
* mounted once; ru's task queue / caSync / Forge workers call `broadcast`. */
|
|
123
|
+
export interface EventBus {
|
|
124
|
+
/** Bun `websocket` config. `message` is a documented no-op (push-only). */
|
|
125
|
+
handlers: WebSocketHandler<EventBusData>;
|
|
126
|
+
upgradeHandler: MiddlewareHandler;
|
|
127
|
+
/** Push `{ event, data }` to every connected client. */
|
|
128
|
+
broadcast(event: string, data: unknown): void;
|
|
129
|
+
/** Number of connected clients. */
|
|
130
|
+
clientCount(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The event bus carries no per-socket data (no auth, no channel). */
|
|
134
|
+
export type EventBusData = Record<never, never>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔴 NO COMMENT, NO SKIP — and the doc that answers "what were they for" must stay true.
|
|
3
|
+
*
|
|
4
|
+
* ## What this cost, 2026-09-13
|
|
5
|
+
*
|
|
6
|
+
* `bun run verify` reported **5,132 pass / 82 skip / 0 fail**. Nobody could say what the 82
|
|
7
|
+
* were. Each had been written because something mattered and each had stopped running for a
|
|
8
|
+
* reason that was, by then, invisible: the gate condition was in the code
|
|
9
|
+
* (`describe.skipIf(!URL_)`), but *why that variable is unset on every host we own* was in
|
|
10
|
+
* nobody's head. A suite that carries a number like that quietly is a suite whose green means
|
|
11
|
+
* less than it says.
|
|
12
|
+
*
|
|
13
|
+
* Answering it took 16m 5s of agent time — the figure the runner's ledger recorded for the task,
|
|
14
|
+
* not an estimate — spent booting a throwaway Postgres, locating a binary-server checkout, and
|
|
15
|
+
* running each gated suite by hand to learn whether it still passes. All 82 turned out to be
|
|
16
|
+
* healthy, environment-gated proofs. That is the good outcome, and it is exactly why the next
|
|
17
|
+
* person must not have to re-derive it: the cost was never the fixing, it was the *finding
|
|
18
|
+
* out*.
|
|
19
|
+
*
|
|
20
|
+
* ## So this is a check, not a paragraph
|
|
21
|
+
*
|
|
22
|
+
* Two assertions, both of which fail the moment the record stops matching the code:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Every skip site in `src` carries its reason in the comment directly above it.** A new
|
|
25
|
+
* `describe.skipIf` with no comment is a red gate, at the moment it is written, by the
|
|
26
|
+
* person who knows the answer — which is the only moment the answer is cheap.
|
|
27
|
+
* 2. **`docs/skipped-tests.md` names exactly the files that have one.** A skip that is
|
|
28
|
+
* deleted or added without updating the record reds too, so the doc cannot rot into the
|
|
29
|
+
* confident-but-wrong state that makes a doc worse than nothing.
|
|
30
|
+
*
|
|
31
|
+
* It deliberately does NOT assert a skip COUNT. The counts move with the host — ffmpeg
|
|
32
|
+
* installed or not, `CC_PG_URL` set or not — and a check that reds because someone installed
|
|
33
|
+
* `sharp` teaches people to delete the check.
|
|
34
|
+
*/
|
|
35
|
+
import { describe, expect, test } from 'bun:test';
|
|
36
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
|
|
40
|
+
const SRC = fileURLToPath(new URL('.', import.meta.url));
|
|
41
|
+
const REPO = path.resolve(SRC, '..');
|
|
42
|
+
const SELF = 'src/skippedTestsAreAnswered.spec.ts';
|
|
43
|
+
const DOC = 'docs/skipped-tests.md';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A skip site: `describe.skip`, `.todo`, `.failing`, `.skipIf`, `.todoIf` on any of bun's three
|
|
47
|
+
* entry points, plus the jasmine-era `xit(` / `xdescribe(`.
|
|
48
|
+
*
|
|
49
|
+
* Anchored on the entry point so that ordinary identifiers containing "skip" — `skipDirs`,
|
|
50
|
+
* `skipToken`, `skipKdfReadinessGate`, all of which live in this package — are not sites.
|
|
51
|
+
*/
|
|
52
|
+
const SKIP_SITE = /\b(?:describe|it|test)\s*\.\s*(?:skip|todo|failing|skipIf|todoIf)\b|\bx(?:it|describe)\s*\(/;
|
|
53
|
+
|
|
54
|
+
/** A comment line, in either style, as it appears immediately above a skip site. */
|
|
55
|
+
const COMMENT_LINE = /^\s*(?:\/\/|\/\*|\*)/;
|
|
56
|
+
|
|
57
|
+
export interface SkipSite {
|
|
58
|
+
/** 1-indexed, so a failure message is clickable. */
|
|
59
|
+
readonly line: number;
|
|
60
|
+
readonly text: string;
|
|
61
|
+
/** The contiguous comment block directly above, with no blank line between it and the site. */
|
|
62
|
+
readonly reason: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Every skip site in one file's source, each carrying whatever comment sits directly above it.
|
|
67
|
+
*
|
|
68
|
+
* "Directly above" is strict on purpose: a blank line between the comment and the site ends the
|
|
69
|
+
* block. The failure this guards against is a skip drifting away from the explanation that was
|
|
70
|
+
* once next to it, and a blank line is exactly how that drift starts.
|
|
71
|
+
*/
|
|
72
|
+
export const findSkipSites = (source: string): SkipSite[] => {
|
|
73
|
+
const lines = source.split('\n');
|
|
74
|
+
const sites: SkipSite[] = [];
|
|
75
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
76
|
+
const text = lines[i] as string;
|
|
77
|
+
if (!SKIP_SITE.test(text)) continue;
|
|
78
|
+
const above: string[] = [];
|
|
79
|
+
for (let j = i - 1; j >= 0; j -= 1) {
|
|
80
|
+
const prev = lines[j] as string;
|
|
81
|
+
if (!COMMENT_LINE.test(prev)) break;
|
|
82
|
+
above.unshift(prev);
|
|
83
|
+
}
|
|
84
|
+
sites.push({ line: i + 1, text: text.trim(), reason: above.join('\n') });
|
|
85
|
+
}
|
|
86
|
+
return sites;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** A reason has to be ABOUT the skip. Any comment at all would pass otherwise. */
|
|
90
|
+
export const isAnswered = (site: SkipSite): boolean => /skip/i.test(site.reason);
|
|
91
|
+
|
|
92
|
+
/** Every `src` file that contains at least one skip site, repo-relative and sorted. */
|
|
93
|
+
const scanSrc = (): Map<string, SkipSite[]> => {
|
|
94
|
+
const found = new Map<string, SkipSite[]>();
|
|
95
|
+
for (const entry of readdirSync(SRC, { recursive: true })) {
|
|
96
|
+
const name = String(entry);
|
|
97
|
+
if (!/\.tsx?$/.test(name)) continue;
|
|
98
|
+
const rel = `src/${name.split(path.sep).join('/')}`;
|
|
99
|
+
if (rel === SELF) continue; // the checker names the patterns it bans, so it cannot scan itself
|
|
100
|
+
const sites = findSkipSites(readFileSync(path.join(SRC, name), 'utf8'));
|
|
101
|
+
if (sites.length > 0) found.set(rel, sites);
|
|
102
|
+
}
|
|
103
|
+
return new Map([...found].sort(([a], [b]) => a.localeCompare(b)));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The suites `docs/skipped-tests.md` claims to cover.
|
|
108
|
+
*
|
|
109
|
+
* Only backticked paths in a TABLE ROW count, so the prose above and below the table stays free
|
|
110
|
+
* to mention any file it likes — including this one.
|
|
111
|
+
*/
|
|
112
|
+
const documentedSuites = async (): Promise<Set<string>> => {
|
|
113
|
+
const doc = await Bun.file(path.join(REPO, DOC)).text();
|
|
114
|
+
const rows = doc.split('\n').filter((l) => l.trimStart().startsWith('|'));
|
|
115
|
+
const named = new Set<string>();
|
|
116
|
+
for (const row of rows) for (const m of row.matchAll(/`(src\/[^`]+\.tsx?)`/g)) named.add(m[1] as string);
|
|
117
|
+
return named;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
describe('every skip in src answers for itself', () => {
|
|
121
|
+
test('each skip site carries its reason in the comment directly above it', () => {
|
|
122
|
+
const unanswered: string[] = [];
|
|
123
|
+
for (const [file, sites] of scanSrc())
|
|
124
|
+
for (const site of sites) if (!isAnswered(site)) unanswered.push(`${file}:${site.line} ${site.text}`);
|
|
125
|
+
|
|
126
|
+
expect(
|
|
127
|
+
unanswered,
|
|
128
|
+
`A skipped test with no stated reason is a question nobody can answer later — answering the 82 already here\n` +
|
|
129
|
+
`cost a task of its own on 2026-09-13 (16m 5s, per the runner's ledger). Put one line directly above the\n` +
|
|
130
|
+
`skip saying what it is WAITING FOR (a service, a fixture, a\n` +
|
|
131
|
+
`browser, a decision), mentioning the word "skip", and add a row to ${DOC}. If it passes today, unskip it\n` +
|
|
132
|
+
`instead. Never delete a test to make the number fall.\n\nUnanswered:\n ${unanswered.join('\n ')}`,
|
|
133
|
+
).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe(`${DOC} matches the suite`, () => {
|
|
138
|
+
test('it names every file that carries a skip', async () => {
|
|
139
|
+
const documented = await documentedSuites();
|
|
140
|
+
const missing = [...scanSrc().keys()].filter((f) => !documented.has(f));
|
|
141
|
+
expect(
|
|
142
|
+
missing,
|
|
143
|
+
`These files gained a skip that ${DOC} does not mention. Add a table row: what it waits for, how to run it,\n` +
|
|
144
|
+
`and when it was last seen green.\n\n ${missing.join('\n ')}`,
|
|
145
|
+
).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('it names no file that has stopped carrying one', async () => {
|
|
149
|
+
const live = scanSrc();
|
|
150
|
+
const stale = [...(await documentedSuites())].filter((f) => !live.has(f));
|
|
151
|
+
expect(
|
|
152
|
+
stale,
|
|
153
|
+
`${DOC} still lists these as skipped, but they no longer contain a skip site. Good news — delete the rows\n` +
|
|
154
|
+
`(and move the count) so the record stops claiming something that is no longer true.\n\n ${stale.join('\n ')}`,
|
|
155
|
+
).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The detector's own failure path. A gate nobody has seen RED is a gate nobody knows works, and
|
|
161
|
+
* this one's whole value is that it fires on the day a skip is introduced.
|
|
162
|
+
*/
|
|
163
|
+
describe('the detector itself', () => {
|
|
164
|
+
test('an uncommented skip is a finding', () => {
|
|
165
|
+
const sites = findSkipSites(["describe.skipIf(!URL_)('x', () => {", '});'].join('\n'));
|
|
166
|
+
expect(sites).toHaveLength(1);
|
|
167
|
+
expect(isAnswered(sites[0] as SkipSite)).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('a skip whose comment explains the skip is not', () => {
|
|
171
|
+
const sites = findSkipSites(
|
|
172
|
+
['// SKIPPED unless CC_PG_URL names a real Postgres.', "describe.skipIf(!URL_)('x', () => {", '});'].join('\n'),
|
|
173
|
+
);
|
|
174
|
+
expect(isAnswered(sites[0] as SkipSite)).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('a comment about something else does NOT answer for the skip', () => {
|
|
178
|
+
const sites = findSkipSites(
|
|
179
|
+
['// posterOnly is the memory-constrained-host path.', "describe.skipIf(!ffmpeg)('x', () => {", '});'].join('\n'),
|
|
180
|
+
);
|
|
181
|
+
expect(isAnswered(sites[0] as SkipSite)).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('a blank line between the comment and the skip breaks the block', () => {
|
|
185
|
+
const sites = findSkipSites(
|
|
186
|
+
['// SKIPPED unless CC_PG_URL is set.', '', "describe.skipIf(!URL_)('x', () => {", '});'].join('\n'),
|
|
187
|
+
);
|
|
188
|
+
expect(sites[0]?.reason).toBe('');
|
|
189
|
+
expect(isAnswered(sites[0] as SkipSite)).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('a JSDoc block above the skip counts as the reason', () => {
|
|
193
|
+
const sites = findSkipSites(
|
|
194
|
+
['/**', ' * SKIPPED until the browser harness exists.', ' */', "it.skip('x', () => {});"].join('\n'),
|
|
195
|
+
);
|
|
196
|
+
expect(isAnswered(sites[0] as SkipSite)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('it finds the other spellings too', () => {
|
|
200
|
+
expect(findSkipSites("it.todo('later');")).toHaveLength(1);
|
|
201
|
+
expect(findSkipSites("test.failing('known bad');")).toHaveLength(1);
|
|
202
|
+
expect(findSkipSites("xit('old style', () => {});")).toHaveLength(1);
|
|
203
|
+
expect(findSkipSites("xdescribe('old style', () => {});")).toHaveLength(1);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('an identifier that merely contains "skip" is not a skip site', () => {
|
|
207
|
+
expect(findSkipSites('const skip = new Set(opts.skipDirs ?? []);')).toEqual([]);
|
|
208
|
+
expect(findSkipSites('if (options.skipKdfReadinessGate !== true) assertArgon2idReady();')).toEqual([]);
|
|
209
|
+
expect(findSkipSites('const q = useQuery({ queryKey: k, initialPageParam: skipToken });')).toEqual([]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('this package has skip sites to find — a detector that finds nothing proves nothing', () => {
|
|
213
|
+
expect(scanSrc().size).toBeGreaterThan(0);
|
|
214
|
+
});
|
|
215
|
+
});
|