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
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/activity` — the fleet's ONE append-only activity/audit
|
|
3
|
+
* stream: who did what, to which record, when, and how it went.
|
|
4
|
+
*
|
|
5
|
+
* The vocabulary is `cursedbelt-core/activity` (pure), this is the storage + query
|
|
6
|
+
* half, and the console's **Event Logs** tab is the fleet-wide reader — it asks
|
|
7
|
+
* every live deployment through `satellite-kit/metrics`' bearer-gated
|
|
8
|
+
* `?view=events`, so an app is on the board by writing events, with no
|
|
9
|
+
* admin-side wiring at all.
|
|
10
|
+
*
|
|
11
|
+
* ── Retention ───────────────────────────────────────────────────────────────
|
|
12
|
+
* This package never deletes an event. An app whose stream is high-volume opts
|
|
13
|
+
* into the fleet standard (`docs/retention.md` in `cursed-satellites`) by
|
|
14
|
+
* declaring the table, which puts the number under the owner's control in the
|
|
15
|
+
* station rather than in a constant here:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* createSqliteTableTarget({
|
|
19
|
+
* db, table: ACTIVITY_EVENTS_TABLE, timeColumn: "at", timeFormat: "iso",
|
|
20
|
+
* declaration: { app: "notes", id: "activity", label: "Activity log",
|
|
21
|
+
* category: "event-log", unit: "rows", location: "notes.sqlite · activity_events",
|
|
22
|
+
* consequence: "Who did what, for hindsight. Losing it costs no data." },
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* An app whose stream is a handful of rows a month — an audit of destructive
|
|
27
|
+
* actions, say — is right to declare NOTHING and say so: the standard is opt-in
|
|
28
|
+
* by category, and an audit trail of production deletions is precisely the data
|
|
29
|
+
* a 14-day cutoff must not reach.
|
|
30
|
+
*/
|
|
31
|
+
export { ACTIVITY_EVENTS_TABLE, ACTIVITY_MIGRATIONS } from "./migrations";
|
|
32
|
+
export { shareEventsSource, type ShareEventsSourceOptions } from "./shareSource";
|
|
33
|
+
export { createActivityLog } from "./store";
|
|
34
|
+
export type { ActivityFacet, ActivityFacets, ActivityLog, ActivityLogOptions, ActivityPage, ActivityQuery, ActivitySource, } from "./store";
|
|
35
|
+
export { ACTIVITY_OUTCOMES, activityFromShareEvent, activitySubject, describeActivityEvent, describeActivityResource, isActivityAction, isActivityOutcome, } from "cursedbelt-core/activity/model";
|
|
36
|
+
export type { ActivityEvent, ActivityEventInput, ActivityOutcome, ActivityResource, } from "cursedbelt-core/activity/model";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/activity` — the fleet's ONE append-only activity/audit
|
|
3
|
+
* stream: who did what, to which record, when, and how it went.
|
|
4
|
+
*
|
|
5
|
+
* The vocabulary is `cursedbelt-core/activity` (pure), this is the storage + query
|
|
6
|
+
* half, and the console's **Event Logs** tab is the fleet-wide reader — it asks
|
|
7
|
+
* every live deployment through `satellite-kit/metrics`' bearer-gated
|
|
8
|
+
* `?view=events`, so an app is on the board by writing events, with no
|
|
9
|
+
* admin-side wiring at all.
|
|
10
|
+
*
|
|
11
|
+
* ── Retention ───────────────────────────────────────────────────────────────
|
|
12
|
+
* This package never deletes an event. An app whose stream is high-volume opts
|
|
13
|
+
* into the fleet standard (`docs/retention.md` in `cursed-satellites`) by
|
|
14
|
+
* declaring the table, which puts the number under the owner's control in the
|
|
15
|
+
* station rather than in a constant here:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* createSqliteTableTarget({
|
|
19
|
+
* db, table: ACTIVITY_EVENTS_TABLE, timeColumn: "at", timeFormat: "iso",
|
|
20
|
+
* declaration: { app: "notes", id: "activity", label: "Activity log",
|
|
21
|
+
* category: "event-log", unit: "rows", location: "notes.sqlite · activity_events",
|
|
22
|
+
* consequence: "Who did what, for hindsight. Losing it costs no data." },
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* An app whose stream is a handful of rows a month — an audit of destructive
|
|
27
|
+
* actions, say — is right to declare NOTHING and say so: the standard is opt-in
|
|
28
|
+
* by category, and an audit trail of production deletions is precisely the data
|
|
29
|
+
* a 14-day cutoff must not reach.
|
|
30
|
+
*/
|
|
31
|
+
export { ACTIVITY_EVENTS_TABLE, ACTIVITY_MIGRATIONS } from "./migrations";
|
|
32
|
+
export { shareEventsSource } from "./shareSource";
|
|
33
|
+
export { createActivityLog } from "./store";
|
|
34
|
+
// Re-exported so a server consumer needs ONE import for the whole contract; the
|
|
35
|
+
// pure entry (`cursedbelt-core/activity`) stays available for a browser or a spec.
|
|
36
|
+
export { ACTIVITY_OUTCOMES, activityFromShareEvent, activitySubject, describeActivityEvent, describeActivityResource, isActivityAction, isActivityOutcome, } from "cursedbelt-core/activity/model";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Migration } from "../migration/types";
|
|
2
|
+
/** The one table this primitive owns. Exported so an app can declare a retention
|
|
3
|
+
* target over it (see this module's `index.ts` for the recipe). */
|
|
4
|
+
export declare const ACTIVITY_EVENTS_TABLE = "activity_events";
|
|
5
|
+
/**
|
|
6
|
+
* ONE append-only table per app, not a shadow table per business table.
|
|
7
|
+
*
|
|
8
|
+
* The stream's whole value is being readable as one list — "everything that
|
|
9
|
+
* happened in `notes` yesterday", "everything `curt@…` did", "everything that
|
|
10
|
+
* touched person `3f2`" — and a per-table history cannot answer any of those
|
|
11
|
+
* without a UNION nobody maintains. `(kind, resource_id)` plays the role a
|
|
12
|
+
* shadow table would, and is a business key the app already has rather than a
|
|
13
|
+
* migration in `cursedbelt`.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **Append only.** There is no update and no delete verb anywhere in this
|
|
16
|
+
* package. The one sanctioned deleter is the fleet retention standard
|
|
17
|
+
* (`satellite-kit/retention`), which an app opts into by declaring this table as
|
|
18
|
+
* an `event-log`/`audit-log` resource — a control the owner can see and change,
|
|
19
|
+
* rather than a cutoff buried in a store.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ACTIVITY_MIGRATIONS: readonly Migration[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** The one table this primitive owns. Exported so an app can declare a retention
|
|
2
|
+
* target over it (see this module's `index.ts` for the recipe). */
|
|
3
|
+
export const ACTIVITY_EVENTS_TABLE = "activity_events";
|
|
4
|
+
/**
|
|
5
|
+
* ONE append-only table per app, not a shadow table per business table.
|
|
6
|
+
*
|
|
7
|
+
* The stream's whole value is being readable as one list — "everything that
|
|
8
|
+
* happened in `notes` yesterday", "everything `curt@…` did", "everything that
|
|
9
|
+
* touched person `3f2`" — and a per-table history cannot answer any of those
|
|
10
|
+
* without a UNION nobody maintains. `(kind, resource_id)` plays the role a
|
|
11
|
+
* shadow table would, and is a business key the app already has rather than a
|
|
12
|
+
* migration in `cursedbelt`.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 **Append only.** There is no update and no delete verb anywhere in this
|
|
15
|
+
* package. The one sanctioned deleter is the fleet retention standard
|
|
16
|
+
* (`satellite-kit/retention`), which an app opts into by declaring this table as
|
|
17
|
+
* an `event-log`/`audit-log` resource — a control the owner can see and change,
|
|
18
|
+
* rather than a cutoff buried in a store.
|
|
19
|
+
*/
|
|
20
|
+
export const ACTIVITY_MIGRATIONS = [
|
|
21
|
+
{
|
|
22
|
+
name: "0001_activity_events",
|
|
23
|
+
up(db) {
|
|
24
|
+
db.run(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS ${ACTIVITY_EVENTS_TABLE} (
|
|
26
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
at TEXT NOT NULL,
|
|
28
|
+
app TEXT NOT NULL,
|
|
29
|
+
actor TEXT NOT NULL,
|
|
30
|
+
action TEXT NOT NULL,
|
|
31
|
+
kind TEXT,
|
|
32
|
+
resource_id TEXT,
|
|
33
|
+
outcome TEXT NOT NULL DEFAULT 'ok'
|
|
34
|
+
CHECK (outcome IN ('ok', 'denied', 'error')),
|
|
35
|
+
note TEXT NOT NULL DEFAULT '',
|
|
36
|
+
data TEXT
|
|
37
|
+
)
|
|
38
|
+
`);
|
|
39
|
+
// The stream itself, newest first — the read every screen starts from.
|
|
40
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_events_at
|
|
41
|
+
ON ${ACTIVITY_EVENTS_TABLE} (at DESC, event_id DESC)`);
|
|
42
|
+
// "Everything this person did" — the offboarding and the who-broke-it
|
|
43
|
+
// question, and the one a fleet-wide search runs first.
|
|
44
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_events_actor
|
|
45
|
+
ON ${ACTIVITY_EVENTS_TABLE} (actor, event_id DESC)`);
|
|
46
|
+
// "Everything that ever happened to this record."
|
|
47
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_events_resource
|
|
48
|
+
ON ${ACTIVITY_EVENTS_TABLE} (kind, resource_id, event_id DESC)`);
|
|
49
|
+
// "Everything backups ever did" — an action-prefix filter is a range
|
|
50
|
+
// scan on this index, which is why the vocabulary is dotted.
|
|
51
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_events_action
|
|
52
|
+
ON ${ACTIVITY_EVENTS_TABLE} (action, event_id DESC)`);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ActivitySource } from "./store";
|
|
2
|
+
export interface ShareEventsSourceOptions {
|
|
3
|
+
/** The satellite this log speaks for — the same value passed to
|
|
4
|
+
* `createActivityLog`, used where a group event names no resource. */
|
|
5
|
+
app: string;
|
|
6
|
+
/** Overridden only by a test hosting the table under another name. */
|
|
7
|
+
table?: string;
|
|
8
|
+
/** The contributor name, which prefixes every id it produces. */
|
|
9
|
+
name?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function shareEventsSource(options: ShareEventsSourceOptions): ActivitySource;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sharing primitive, as a contributor to the activity stream.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 This is the whole point of `sources`: `cursedbelt-server/sharing` already
|
|
5
|
+
* keeps an audit trail nothing ever deletes, in `share_events`, and it is
|
|
6
|
+
* guaranteed to be complete (no grant can be made without a row — see that
|
|
7
|
+
* store's property 1). So the activity stream READS it where it lives rather
|
|
8
|
+
* than asking an app to mirror it. One row, one truth, no drift, and nothing to
|
|
9
|
+
* reconcile if a mirror write is ever missed.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const activity = createActivityLog({
|
|
13
|
+
* db, app: "family", sources: [shareEventsSource({ app: "family" })],
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The mapping is field-for-field the one `activityFromShareEvent` performs in
|
|
18
|
+
* the vocabulary, and `store.spec.ts` asserts the two agree — a SQL projection
|
|
19
|
+
* and a TypeScript function are two implementations of one shape, so the suite
|
|
20
|
+
* pins them together rather than trusting that they were written on the same
|
|
21
|
+
* afternoon.
|
|
22
|
+
*
|
|
23
|
+
* `share_events.app` is NULL for a GROUP lifecycle event (creating a group is
|
|
24
|
+
* about no single resource), so the app is coalesced to the log's own name: a
|
|
25
|
+
* fleet-wide board merges on that column and an unlabeled row would be
|
|
26
|
+
* unattributable.
|
|
27
|
+
*/
|
|
28
|
+
import { SHARE_EVENTS_TABLE } from "../sharing/migrations";
|
|
29
|
+
export function shareEventsSource(options) {
|
|
30
|
+
const table = options.table ?? SHARE_EVENTS_TABLE;
|
|
31
|
+
const name = options.name ?? "share";
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
requiresTable: table,
|
|
35
|
+
// Column contract: source, seq, at, app, actor, action, kind, resource_id,
|
|
36
|
+
// outcome, note, data — in this order. See `ActivitySource`.
|
|
37
|
+
sql: `SELECT '${name}' AS source, event_id AS seq, at, COALESCE(app, ?) AS app, actor,
|
|
38
|
+
'share.' || action AS action, kind, resource_id,
|
|
39
|
+
'ok' AS outcome, note,
|
|
40
|
+
json_object('subjectType', subject_type, 'subjectId', subject_id,
|
|
41
|
+
'level', level, 'previousLevel', previous_level) AS data
|
|
42
|
+
FROM ${table}`,
|
|
43
|
+
params: [options.app],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The activity store: one append-only table, plus any number of read-only
|
|
3
|
+
* SOURCES projected into the same shape, queried as one stream.
|
|
4
|
+
*
|
|
5
|
+
* ── How an app uses it ──────────────────────────────────────────────────────
|
|
6
|
+
* ```ts
|
|
7
|
+
* const activity = createActivityLog({
|
|
8
|
+
* db,
|
|
9
|
+
* app: "family",
|
|
10
|
+
* // The sharing primitive already audits itself. Read those rows where they
|
|
11
|
+
* // are instead of copying them — see `shareEventsSource`.
|
|
12
|
+
* sources: [shareEventsSource()],
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* activity.record({ actor: viewer.email, action: "person.delete",
|
|
16
|
+
* resource: { kind: "person", id }, note: "duplicate" });
|
|
17
|
+
*
|
|
18
|
+
* activity.query({ since, actor, action: "share.", limit: 100 });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ── 🔴 Four properties this file exists to guarantee ────────────────────────
|
|
22
|
+
* 1. **Append only.** No update verb, no delete verb, no `TRUNCATE`. Ageing
|
|
23
|
+
* rows out is the fleet retention standard's job, through a declaration the
|
|
24
|
+
* owner can see (`satellite-kit/retention`) — never a silent cutoff here.
|
|
25
|
+
* 2. **A source is READ, never copied.** `share_events` stays the canonical
|
|
26
|
+
* sharing trail; this store selects from it. Two tables holding the same
|
|
27
|
+
* audit rows is two tables that will one day disagree, and the one people
|
|
28
|
+
* would trust is whichever the screen happened to read.
|
|
29
|
+
* 3. **A missing source table is an empty source, not a 500.** An app may
|
|
30
|
+
* register `shareEventsSource()` before it has ever created a grant, and a
|
|
31
|
+
* dashboard must not die on the app that has done nothing yet. (The same
|
|
32
|
+
* lesson `orch`'s `plainjob_jobs` retention target learned at boot.)
|
|
33
|
+
* 4. **`action` is validated at the write.** An action vocabulary that drifts
|
|
34
|
+
* cannot be filtered, and filtering is the whole point of the column.
|
|
35
|
+
*/
|
|
36
|
+
import type { Database, SQLQueryBindings } from "bun:sqlite";
|
|
37
|
+
import { type ActivityEvent, type ActivityEventInput, type ActivityOutcome } from "cursedbelt-core/activity/model";
|
|
38
|
+
/**
|
|
39
|
+
* A read-only stream contributor: a SELECT projecting some other table into the
|
|
40
|
+
* union's eleven columns, in this order and with these names —
|
|
41
|
+
*
|
|
42
|
+
* `source, seq, at, app, actor, action, kind, resource_id, outcome, note, data`
|
|
43
|
+
*
|
|
44
|
+
* `seq` is that table's own row id: it breaks ties within one `at`, and it is
|
|
45
|
+
* what makes the event id (`"<source>:<seq>"`) stable and unique across sources.
|
|
46
|
+
*/
|
|
47
|
+
export interface ActivitySource {
|
|
48
|
+
/** Short, stable, unique within the log — it prefixes every id it produces. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** The projection. See the column contract above. */
|
|
51
|
+
sql: string;
|
|
52
|
+
/** Bound in order, before any filter parameter. */
|
|
53
|
+
params?: readonly SQLQueryBindings[];
|
|
54
|
+
/**
|
|
55
|
+
* A table this projection needs. When it does not exist the source is skipped
|
|
56
|
+
* for that call rather than throwing — see property 3 in the header.
|
|
57
|
+
*/
|
|
58
|
+
requiresTable?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Everything the stream can be narrowed by. Every field is optional; the empty
|
|
61
|
+
* query is "the newest `limit` events". */
|
|
62
|
+
export interface ActivityQuery {
|
|
63
|
+
/** ISO-8601, inclusive. */
|
|
64
|
+
since?: string;
|
|
65
|
+
/** ISO-8601, inclusive. */
|
|
66
|
+
until?: string;
|
|
67
|
+
/** Case-insensitive exact match on the account. */
|
|
68
|
+
actor?: string;
|
|
69
|
+
/** Exact, or a PREFIX when it ends in a dot: `"share."` matches `share.grant`. */
|
|
70
|
+
action?: string;
|
|
71
|
+
kind?: string;
|
|
72
|
+
resourceId?: string;
|
|
73
|
+
outcome?: ActivityOutcome;
|
|
74
|
+
/** Restrict to one contributor (`"log"`, `"share"`). */
|
|
75
|
+
source?: string;
|
|
76
|
+
/** Substring, over actor + action + note + resource id. */
|
|
77
|
+
q?: string;
|
|
78
|
+
/** Default 100, hard cap 1000. */
|
|
79
|
+
limit?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface ActivityPage {
|
|
82
|
+
events: ActivityEvent[];
|
|
83
|
+
/** The limit actually applied, after defaulting and capping. */
|
|
84
|
+
limit: number;
|
|
85
|
+
/** There is at least one more row older than the last one returned. */
|
|
86
|
+
truncated: boolean;
|
|
87
|
+
}
|
|
88
|
+
/** One dimension of "what is in this result", for the filter UI. */
|
|
89
|
+
export interface ActivityFacet {
|
|
90
|
+
value: string;
|
|
91
|
+
count: number;
|
|
92
|
+
}
|
|
93
|
+
export interface ActivityFacets {
|
|
94
|
+
actions: ActivityFacet[];
|
|
95
|
+
actors: ActivityFacet[];
|
|
96
|
+
kinds: ActivityFacet[];
|
|
97
|
+
}
|
|
98
|
+
export interface ActivityLogOptions {
|
|
99
|
+
db: Database;
|
|
100
|
+
/** The satellite this log speaks for. Fixed at construction, so a caller
|
|
101
|
+
* cannot write an event under another app's name. */
|
|
102
|
+
app: string;
|
|
103
|
+
/** Read-only contributors merged into the stream. */
|
|
104
|
+
sources?: readonly ActivitySource[];
|
|
105
|
+
/** Injectable clock, ISO-8601. */
|
|
106
|
+
now?: () => string;
|
|
107
|
+
}
|
|
108
|
+
export interface ActivityLog {
|
|
109
|
+
/** The app this log speaks for. */
|
|
110
|
+
readonly app: string;
|
|
111
|
+
/** Append one event. Returns it as it will be read back. */
|
|
112
|
+
record(input: ActivityEventInput): ActivityEvent;
|
|
113
|
+
/** Newest first, bounded. */
|
|
114
|
+
query(query?: ActivityQuery): ActivityPage;
|
|
115
|
+
/** How many events match — the unbounded count behind a bounded page. */
|
|
116
|
+
count(query?: ActivityQuery): number;
|
|
117
|
+
/** What is in the result, per dimension, for a filter UI. */
|
|
118
|
+
facets(query?: ActivityQuery, limit?: number): ActivityFacets;
|
|
119
|
+
/** Which contributors are live right now (a source whose table is missing is
|
|
120
|
+
* absent, which is what the read surface reports). */
|
|
121
|
+
sources(): string[];
|
|
122
|
+
}
|
|
123
|
+
export declare function createActivityLog(options: ActivityLogOptions): ActivityLog;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { isActivityAction, isActivityOutcome, } from "cursedbelt-core/activity/model";
|
|
2
|
+
import { runMigrations } from "../migration/runner";
|
|
3
|
+
import { ACTIVITY_EVENTS_TABLE, ACTIVITY_MIGRATIONS } from "./migrations";
|
|
4
|
+
const DEFAULT_LIMIT = 100;
|
|
5
|
+
const MAX_LIMIT = 1000;
|
|
6
|
+
const MAX_FACET = 25;
|
|
7
|
+
/** `%` and `_` are wildcards and `\` is the escape — a note containing any of
|
|
8
|
+
* them must still search literally. */
|
|
9
|
+
const likeContains = (value) => `%${value.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`;
|
|
10
|
+
const toEvent = (row) => ({
|
|
11
|
+
id: `${row.source}:${row.seq}`,
|
|
12
|
+
source: row.source,
|
|
13
|
+
at: row.at,
|
|
14
|
+
app: row.app,
|
|
15
|
+
actor: row.actor,
|
|
16
|
+
action: row.action,
|
|
17
|
+
resource: row.kind === null || row.resource_id === null
|
|
18
|
+
? null
|
|
19
|
+
: { app: row.app, kind: row.kind, id: row.resource_id },
|
|
20
|
+
outcome: isActivityOutcome(row.outcome) ? row.outcome : "ok",
|
|
21
|
+
note: row.note ?? "",
|
|
22
|
+
data: parseData(row.data),
|
|
23
|
+
});
|
|
24
|
+
/** A row whose `data` is unreadable must not take the page down with it — the
|
|
25
|
+
* event still says who did what, which is the part that matters. */
|
|
26
|
+
function parseData(raw) {
|
|
27
|
+
if (!raw)
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
32
|
+
? parsed
|
|
33
|
+
: { value: parsed };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { unparsed: raw };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function createActivityLog(options) {
|
|
40
|
+
const { db, app } = options;
|
|
41
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
42
|
+
const registered = options.sources ?? [];
|
|
43
|
+
runMigrations(db, ACTIVITY_MIGRATIONS);
|
|
44
|
+
const insert = db.query(`INSERT INTO ${ACTIVITY_EVENTS_TABLE}
|
|
45
|
+
(at, app, actor, action, kind, resource_id, outcome, note, data)
|
|
46
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
47
|
+
const tableExists = (name) => !!db
|
|
48
|
+
.query("SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
49
|
+
.get(name);
|
|
50
|
+
/** The contributors that can actually answer right now. */
|
|
51
|
+
const liveSources = () => registered.filter((source) => !source.requiresTable || tableExists(source.requiresTable));
|
|
52
|
+
const OWN_SELECT = `SELECT 'log' AS source, event_id AS seq, at, app, actor, action,
|
|
53
|
+
kind, resource_id, outcome, note, data
|
|
54
|
+
FROM ${ACTIVITY_EVENTS_TABLE}`;
|
|
55
|
+
/** The union, plus the WHERE the caller asked for, plus their parameters in
|
|
56
|
+
* bind order (sources first — they are inside the FROM). */
|
|
57
|
+
const stream = (query) => {
|
|
58
|
+
const params = [];
|
|
59
|
+
const selects = [OWN_SELECT];
|
|
60
|
+
for (const source of liveSources()) {
|
|
61
|
+
if (query.source && query.source !== source.name)
|
|
62
|
+
continue;
|
|
63
|
+
selects.push(source.sql);
|
|
64
|
+
params.push(...(source.params ?? []));
|
|
65
|
+
}
|
|
66
|
+
const where = [];
|
|
67
|
+
if (query.source) {
|
|
68
|
+
where.push("source = ?");
|
|
69
|
+
params.push(query.source);
|
|
70
|
+
}
|
|
71
|
+
if (query.since) {
|
|
72
|
+
where.push("at >= ?");
|
|
73
|
+
params.push(query.since);
|
|
74
|
+
}
|
|
75
|
+
if (query.until) {
|
|
76
|
+
where.push("at <= ?");
|
|
77
|
+
params.push(query.until);
|
|
78
|
+
}
|
|
79
|
+
if (query.actor) {
|
|
80
|
+
where.push("LOWER(actor) = LOWER(?)");
|
|
81
|
+
params.push(query.actor);
|
|
82
|
+
}
|
|
83
|
+
if (query.action) {
|
|
84
|
+
if (query.action.endsWith(".")) {
|
|
85
|
+
where.push("action LIKE ? ESCAPE '\\'");
|
|
86
|
+
params.push(`${query.action.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
where.push("action = ?");
|
|
90
|
+
params.push(query.action);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (query.kind) {
|
|
94
|
+
where.push("kind = ?");
|
|
95
|
+
params.push(query.kind);
|
|
96
|
+
}
|
|
97
|
+
if (query.resourceId) {
|
|
98
|
+
where.push("resource_id = ?");
|
|
99
|
+
params.push(query.resourceId);
|
|
100
|
+
}
|
|
101
|
+
if (query.outcome) {
|
|
102
|
+
where.push("outcome = ?");
|
|
103
|
+
params.push(query.outcome);
|
|
104
|
+
}
|
|
105
|
+
if (query.q) {
|
|
106
|
+
where.push("(actor LIKE ? ESCAPE '\\' OR action LIKE ? ESCAPE '\\' OR note LIKE ? ESCAPE '\\' OR IFNULL(resource_id, '') LIKE ? ESCAPE '\\')");
|
|
107
|
+
const like = likeContains(query.q);
|
|
108
|
+
params.push(like, like, like, like);
|
|
109
|
+
}
|
|
110
|
+
// `query.source` naming a contributor that is not live leaves only the own
|
|
111
|
+
// select, filtered to a source it cannot be — an empty result, which is the
|
|
112
|
+
// honest answer rather than every row.
|
|
113
|
+
const sql = `SELECT * FROM (${selects.join("\n UNION ALL\n")}) AS stream${where.length ? ` WHERE ${where.join(" AND ")}` : ""}`;
|
|
114
|
+
return { sql, params };
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
app,
|
|
118
|
+
record(input) {
|
|
119
|
+
if (!isActivityAction(input.action)) {
|
|
120
|
+
throw new Error(`activity: "${input.action}" is not an action — use lowercase subject.verb, e.g. "backup.delete"`);
|
|
121
|
+
}
|
|
122
|
+
const actor = input.actor?.trim();
|
|
123
|
+
if (!actor) {
|
|
124
|
+
// An event that cannot say who is a log line, not an audit trail.
|
|
125
|
+
throw new Error(`activity: ${input.action} has no actor`);
|
|
126
|
+
}
|
|
127
|
+
const at = input.at ?? now();
|
|
128
|
+
const outcome = input.outcome ?? "ok";
|
|
129
|
+
const data = input.data ? JSON.stringify(input.data) : null;
|
|
130
|
+
const info = insert.run(at, app, actor, input.action, input.resource?.kind ?? null, input.resource?.id ?? null, outcome, input.note ?? "", data);
|
|
131
|
+
return {
|
|
132
|
+
id: `log:${Number(info.lastInsertRowid)}`,
|
|
133
|
+
source: "log",
|
|
134
|
+
at,
|
|
135
|
+
app,
|
|
136
|
+
actor,
|
|
137
|
+
action: input.action,
|
|
138
|
+
resource: input.resource ? { app, ...input.resource } : null,
|
|
139
|
+
outcome,
|
|
140
|
+
note: input.note ?? "",
|
|
141
|
+
data: input.data ?? null,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
query(query = {}) {
|
|
145
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.floor(query.limit ?? DEFAULT_LIMIT)));
|
|
146
|
+
const { sql, params } = stream(query);
|
|
147
|
+
// One row past the limit, so "there is more" is a fact rather than a
|
|
148
|
+
// guess made from `rows.length === limit`.
|
|
149
|
+
const rows = db
|
|
150
|
+
.query(`${sql} ORDER BY at DESC, seq DESC LIMIT ?`)
|
|
151
|
+
.all(...params, limit + 1);
|
|
152
|
+
const truncated = rows.length > limit;
|
|
153
|
+
return { events: rows.slice(0, limit).map(toEvent), limit, truncated };
|
|
154
|
+
},
|
|
155
|
+
count(query = {}) {
|
|
156
|
+
const { sql, params } = stream(query);
|
|
157
|
+
const row = db.query(`SELECT COUNT(*) AS n FROM (${sql})`).get(...params);
|
|
158
|
+
return row?.n ?? 0;
|
|
159
|
+
},
|
|
160
|
+
facets(query = {}, limit = MAX_FACET) {
|
|
161
|
+
const { sql, params } = stream(query);
|
|
162
|
+
const capped = Math.min(MAX_FACET, Math.max(1, Math.floor(limit)));
|
|
163
|
+
const dimension = (column) => db
|
|
164
|
+
.query(`SELECT ${column} AS value, COUNT(*) AS count FROM (${sql})
|
|
165
|
+
WHERE ${column} IS NOT NULL AND ${column} <> ''
|
|
166
|
+
GROUP BY ${column} ORDER BY count DESC, value ASC LIMIT ?`)
|
|
167
|
+
.all(...params, capped).map((row) => ({ value: row.value, count: row.count }));
|
|
168
|
+
return {
|
|
169
|
+
actions: dimension("action"),
|
|
170
|
+
actors: dimension("actor"),
|
|
171
|
+
kinds: dimension("kind"),
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
sources() {
|
|
175
|
+
return ["log", ...liveSources().map((source) => source.name)];
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type { Server, TLSOptions, WebSocketHandler } from 'bun';
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import type { CursedbeltEnv } from './context';
|
|
5
|
+
import { type CorsOpts } from './middleware/corsAllowlist';
|
|
6
|
+
import { type RateLimitOpts } from './middleware/rateLimit';
|
|
7
|
+
import { type SecurityHeadersOpts } from './middleware/securityHeaders';
|
|
8
|
+
export interface CreateAppOpts {
|
|
9
|
+
/** When provided, the request logger writes metrics/events to this DB. */
|
|
10
|
+
db?: Database;
|
|
11
|
+
/**
|
|
12
|
+
* CORS for a split deploy (SPA origin ≠ API origin). Pass `{ siteDomain }` /
|
|
13
|
+
* `{ origins }` (see {@link CorsOpts}) or `corsOptsFromEnv()` to read the
|
|
14
|
+
* conventional `CORS_SITE_DOMAIN` / `CORS_ORIGINS` env vars. Omit for a
|
|
15
|
+
* same-origin deploy. Mounted FIRST so every response — preflight, rate-limit
|
|
16
|
+
* 429, error envelope — carries the CORS headers a browser needs to read it.
|
|
17
|
+
*/
|
|
18
|
+
cors?: CorsOpts;
|
|
19
|
+
/** Security-header options, or `false` to skip the security-headers middleware. */
|
|
20
|
+
security?: SecurityHeadersOpts | false;
|
|
21
|
+
/** Mount a global rate limiter. (Per-route limiters: use `createRateLimiter` directly.) */
|
|
22
|
+
rateLimit?: RateLimitOpts;
|
|
23
|
+
/**
|
|
24
|
+
* How many trusted reverse proxies sit in front of this app, for the client-IP
|
|
25
|
+
* rate-limit key. Default `0` — `x-forwarded-for` is caller-controlled, so
|
|
26
|
+
* believing it by default let anyone mint a fresh rate-limit bucket per
|
|
27
|
+
* request. Set to the real hop count you operate (nginx in front ⇒ `1`).
|
|
28
|
+
*/
|
|
29
|
+
trustedProxyHops?: number;
|
|
30
|
+
/** Mount the default `GET /health` liveness route. Default: true. */
|
|
31
|
+
health?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the configured cursedbelt Hono app: correlation id → (optional) CORS →
|
|
35
|
+
* security headers →
|
|
36
|
+
* (optional) rate limit → (optional) request logger, a default `/health`, and the
|
|
37
|
+
* canonical error-envelope `onError`. The caller mounts feature routers on the
|
|
38
|
+
* returned app (`app.route('/api/v1', notesRouter)`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function createApp(opts?: CreateAppOpts): Hono<CursedbeltEnv>;
|
|
41
|
+
export interface ServeBunOpts<T = unknown> {
|
|
42
|
+
/** Listen port. Default: 3000. */
|
|
43
|
+
port?: number;
|
|
44
|
+
/** Bind hostname (e.g. `'127.0.0.1'`). Default: Bun's default (all interfaces). */
|
|
45
|
+
hostname?: string;
|
|
46
|
+
/** WebSocket handler from the WS hub (task 28). Only set when the app upgrades. */
|
|
47
|
+
websocket?: WebSocketHandler<T>;
|
|
48
|
+
/** TLS options for HTTPS. */
|
|
49
|
+
tls?: TLSOptions;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Serve a Hono app on `Bun.serve` (`fetch: app.fetch`, zero adapter). Pass
|
|
53
|
+
* `websocket` only when the app actually calls `server.upgrade()` (task 28) — Bun
|
|
54
|
+
* rejects a `websocket` handler that is never used.
|
|
55
|
+
*/
|
|
56
|
+
export declare function serveBun<T = unknown>(app: Hono<CursedbeltEnv>, opts?: ServeBunOpts<T>): Server<T>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { correlationId } from './middleware/correlationId';
|
|
3
|
+
import { corsAllowlist } from './middleware/corsAllowlist';
|
|
4
|
+
import { errorEnvelope } from './middleware/errorEnvelope';
|
|
5
|
+
import { createRateLimiter } from './middleware/rateLimit';
|
|
6
|
+
import { requestLogger } from './middleware/requestLogger';
|
|
7
|
+
import { securityHeaders } from './middleware/securityHeaders';
|
|
8
|
+
/**
|
|
9
|
+
* Build the configured cursedbelt Hono app: correlation id → (optional) CORS →
|
|
10
|
+
* security headers →
|
|
11
|
+
* (optional) rate limit → (optional) request logger, a default `/health`, and the
|
|
12
|
+
* canonical error-envelope `onError`. The caller mounts feature routers on the
|
|
13
|
+
* returned app (`app.route('/api/v1', notesRouter)`).
|
|
14
|
+
*/
|
|
15
|
+
export function createApp(opts = {}) {
|
|
16
|
+
const app = new Hono();
|
|
17
|
+
app.use('*', correlationId());
|
|
18
|
+
if (opts.cors)
|
|
19
|
+
app.use('*', corsAllowlist(opts.cors));
|
|
20
|
+
if (opts.security !== false)
|
|
21
|
+
app.use('*', securityHeaders(opts.security ?? {}));
|
|
22
|
+
if (opts.rateLimit) {
|
|
23
|
+
app.use('*', createRateLimiter({ trustedProxyHops: opts.trustedProxyHops ?? 0, ...opts.rateLimit }));
|
|
24
|
+
}
|
|
25
|
+
if (opts.db)
|
|
26
|
+
app.use('*', requestLogger(opts.db));
|
|
27
|
+
if (opts.health !== false)
|
|
28
|
+
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
29
|
+
app.onError(errorEnvelope);
|
|
30
|
+
return app;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Serve a Hono app on `Bun.serve` (`fetch: app.fetch`, zero adapter). Pass
|
|
34
|
+
* `websocket` only when the app actually calls `server.upgrade()` (task 28) — Bun
|
|
35
|
+
* rejects a `websocket` handler that is never used.
|
|
36
|
+
*/
|
|
37
|
+
export function serveBun(app, opts = {}) {
|
|
38
|
+
const port = opts.port ?? 3000;
|
|
39
|
+
const { hostname, tls, websocket } = opts;
|
|
40
|
+
// Two concrete call shapes (not a spread) so Bun.serve picks the right overload.
|
|
41
|
+
if (websocket) {
|
|
42
|
+
return Bun.serve({ port, hostname, fetch: app.fetch, tls, websocket });
|
|
43
|
+
}
|
|
44
|
+
return Bun.serve({ port, hostname, fetch: app.fetch, tls });
|
|
45
|
+
}
|