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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where "the last place you were" is kept — one small table, in the app's own
|
|
3
|
+
* database, keyed by the app's own user id.
|
|
4
|
+
*
|
|
5
|
+
* ── Why server-side at all ──────────────────────────────────────────────────
|
|
6
|
+
* `localStorage` would be one line and would answer the wrong question. The
|
|
7
|
+
* owner's report is about coming BACK to an app, and he does that from another
|
|
8
|
+
* device — the 2026-08-13 music sign-in from a second laptop is the same shape.
|
|
9
|
+
* A place remembered only in the browser that saw it is not remembered.
|
|
10
|
+
*
|
|
11
|
+
* ── Why the app's database and not a fleet service ──────────────────────────
|
|
12
|
+
* Same reasoning as `../notifications/migrations.ts`: the SCHEMA is shared, the
|
|
13
|
+
* DATA is local. A resume row is one string per user per app; a central service
|
|
14
|
+
* would put a network hop in front of the first paint of every app in the fleet,
|
|
15
|
+
* and the first thing each would do is cache it.
|
|
16
|
+
*
|
|
17
|
+
* The DDL is idempotent, so calling this twice — or beside a migration runner —
|
|
18
|
+
* converges on one table.
|
|
19
|
+
*/
|
|
20
|
+
import type { Database } from 'bun:sqlite';
|
|
21
|
+
/** Longer than any real place, short enough that nobody can park a payload here. */
|
|
22
|
+
export declare const MAX_LOCATION_LENGTH = 2048;
|
|
23
|
+
export interface ResumeRecord {
|
|
24
|
+
location: string;
|
|
25
|
+
updatedAt: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ResumeStore {
|
|
28
|
+
read(userId: string, key?: string): ResumeRecord | null;
|
|
29
|
+
write(userId: string, location: string, key?: string): void;
|
|
30
|
+
/** No key clears every remembered place for that user. */
|
|
31
|
+
clear(userId: string, key?: string): void;
|
|
32
|
+
}
|
|
33
|
+
export interface ResumeStoreOptions {
|
|
34
|
+
table?: string;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
}
|
|
37
|
+
export declare function createSqliteResumeStore(db: Database, options?: ResumeStoreOptions): ResumeStore;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Longer than any real place, short enough that nobody can park a payload here. */
|
|
2
|
+
export const MAX_LOCATION_LENGTH = 2048;
|
|
3
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
4
|
+
const DEFAULT_KEY = 'default';
|
|
5
|
+
export function createSqliteResumeStore(db, options = {}) {
|
|
6
|
+
const table = options.table ?? 'cb_resume';
|
|
7
|
+
if (!IDENTIFIER.test(table))
|
|
8
|
+
throw new Error(`invalid resume table name: ${table}`);
|
|
9
|
+
const now = options.now ?? Date.now;
|
|
10
|
+
db.run(`CREATE TABLE IF NOT EXISTS ${table} (
|
|
11
|
+
user_id TEXT NOT NULL,
|
|
12
|
+
place_key TEXT NOT NULL,
|
|
13
|
+
location TEXT NOT NULL,
|
|
14
|
+
updated_at INTEGER NOT NULL,
|
|
15
|
+
PRIMARY KEY (user_id, place_key)
|
|
16
|
+
)`);
|
|
17
|
+
return {
|
|
18
|
+
read(userId, key = DEFAULT_KEY) {
|
|
19
|
+
const row = db
|
|
20
|
+
.query(`SELECT location, updated_at FROM ${table} WHERE user_id = ? AND place_key = ?`)
|
|
21
|
+
.get(userId, key);
|
|
22
|
+
return row ? { location: row.location, updatedAt: row.updated_at } : null;
|
|
23
|
+
},
|
|
24
|
+
write(userId, location, key = DEFAULT_KEY) {
|
|
25
|
+
// Truncated rather than refused: a place that is somehow too long should
|
|
26
|
+
// cost the reader nothing, and the next navigation overwrites it anyway.
|
|
27
|
+
const value = location.slice(0, MAX_LOCATION_LENGTH);
|
|
28
|
+
db.run(`INSERT INTO ${table} (user_id, place_key, location, updated_at) VALUES (?, ?, ?, ?)
|
|
29
|
+
ON CONFLICT(user_id, place_key) DO UPDATE SET location = excluded.location, updated_at = excluded.updated_at`, [userId, key, value, now()]);
|
|
30
|
+
},
|
|
31
|
+
clear(userId, key) {
|
|
32
|
+
if (key === undefined)
|
|
33
|
+
db.run(`DELETE FROM ${table} WHERE user_id = ?`, [userId]);
|
|
34
|
+
else
|
|
35
|
+
db.run(`DELETE FROM ${table} WHERE user_id = ? AND place_key = ?`, [userId, key]);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** One sealed archive file. */
|
|
2
|
+
export interface ArchiveSegment {
|
|
3
|
+
/** Absolute path on this machine. */
|
|
4
|
+
path: string;
|
|
5
|
+
/** Basename — what a collector and the index line name it by. */
|
|
6
|
+
file: string;
|
|
7
|
+
rows: number;
|
|
8
|
+
bytes: number;
|
|
9
|
+
sha256: string;
|
|
10
|
+
at: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ArchiveSink {
|
|
13
|
+
/** Where segments are written. */
|
|
14
|
+
readonly dir: string;
|
|
15
|
+
/** True when THIS machine is the archive's final home (no collector is owed). */
|
|
16
|
+
readonly terminal: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Bytes of sealed segments still waiting for a collector. Always 0 when
|
|
19
|
+
* `terminal` — a home archive is never "pending", it is the archive.
|
|
20
|
+
*/
|
|
21
|
+
pendingBytes(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Seal one segment, proven on disk. THROWS if it cannot prove it: the caller
|
|
24
|
+
* treats that as "these rows stay in the table".
|
|
25
|
+
*/
|
|
26
|
+
write(records: ReadonlyArray<Record<string, unknown>>, at: number): ArchiveSegment;
|
|
27
|
+
}
|
|
28
|
+
/** The default budget of uncollected archive on a staged (deployed-box) home.
|
|
29
|
+
* Generous next to the tables it governs — notes' whole `ops` log is ~15 MB —
|
|
30
|
+
* and small next to a 6.7 GB root. Reached only if the collector has stopped. */
|
|
31
|
+
export declare const DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES: number;
|
|
32
|
+
/** `20260825-114233` — sortable, second-resolution, and the same shape the
|
|
33
|
+
* fleet's dated backup dirs already use, so one eye reads both. */
|
|
34
|
+
export declare function archiveStamp(date: Date): string;
|
|
35
|
+
export interface ArchiveHome {
|
|
36
|
+
dir: string;
|
|
37
|
+
terminal: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Where this app's archive lives on THIS machine, and whether the machine keeps
|
|
41
|
+
* it.
|
|
42
|
+
*
|
|
43
|
+
* `<app-data-home>/cursed-satellites/archive/<app>/`, reached through the same env
|
|
44
|
+
* ladder the retention home uses, so a test, a container and a deployed box each
|
|
45
|
+
* land somewhere writable rather than somewhere assumed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveArchiveHome(app: string, env?: Record<string, string | undefined>): ArchiveHome;
|
|
48
|
+
export interface JsonlArchiveOptions {
|
|
49
|
+
app: string;
|
|
50
|
+
/** Stable, app-scoped resource id — the segment name's prefix. */
|
|
51
|
+
resource: string;
|
|
52
|
+
/** Overrides the resolved home. Tests and callers that already resolved it. */
|
|
53
|
+
home?: ArchiveHome;
|
|
54
|
+
env?: Record<string, string | undefined>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fleet's archive sink: newline-delimited JSON segments plus an append-only
|
|
58
|
+
* `index.jsonl` naming every segment ever sealed here.
|
|
59
|
+
*
|
|
60
|
+
* The index is what makes the archive answerable without reading gigabytes —
|
|
61
|
+
* "what is in here, how many rows, and does it still hash the same" — and it is
|
|
62
|
+
* appended rather than rewritten so a crash mid-write can cost at most the last
|
|
63
|
+
* line, never the catalogue.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createJsonlArchive(options: JsonlArchiveOptions): ArchiveSink;
|
|
66
|
+
/**
|
|
67
|
+
* Re-verify every segment in an archive directory against the sha256 the index
|
|
68
|
+
* recorded — the answer to "is this archive still real?" without a restore.
|
|
69
|
+
*
|
|
70
|
+
* Exists because an archive nobody can check is a belief, and this whole
|
|
71
|
+
* category is a promise that the rows still exist somewhere.
|
|
72
|
+
*/
|
|
73
|
+
export declare function verifyArchive(dir: string): {
|
|
74
|
+
segments: number;
|
|
75
|
+
rows: number;
|
|
76
|
+
bytes: number;
|
|
77
|
+
bad: string[];
|
|
78
|
+
};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ARCHIVE half of `archived-history` — where the rows go before they are
|
|
3
|
+
* allowed to leave the instance.
|
|
4
|
+
*
|
|
5
|
+
* ── The ordering rule IS the safety argument, and this fleet has broken it ───
|
|
6
|
+
* prod's `diskGuard.sh` rule 1 deletes the oldest `VACUUM INTO` sqlite backups
|
|
7
|
+
* to free space, and its own comment calls those files "NOT regenerable". A
|
|
8
|
+
* cleanup routine that runs before the copy is data loss wearing housekeeping's
|
|
9
|
+
* name, and it is the exact failure this module exists to make impossible. So:
|
|
10
|
+
*
|
|
11
|
+
* 1. serialize the rows,
|
|
12
|
+
* 2. write them to a NEW file, fsync it,
|
|
13
|
+
* 3. RE-READ that file off the disk and prove it — byte length, sha256, line
|
|
14
|
+
* count, and that every line parses back to an object,
|
|
15
|
+
* 4. only then may the caller delete those rows.
|
|
16
|
+
*
|
|
17
|
+
* `write()` throws rather than returning a failure, because the one thing a
|
|
18
|
+
* caller must never be able to do is treat "the archive did not happen" as
|
|
19
|
+
* "nothing to delete". A throw ends the sweep for that resource with the rows
|
|
20
|
+
* still in the table, which is the correct outcome every time.
|
|
21
|
+
*
|
|
22
|
+
* ── A segment per sweep, never an appended file ─────────────────────────────
|
|
23
|
+
* "Append-only" is a property of the DIRECTORY here: each sweep seals its own
|
|
24
|
+
* `<resource>-<stamp>.jsonl` and nothing is ever rewritten. That is not a style
|
|
25
|
+
* choice. On a deployed box the archive is a WAY-STATION — the Mac's
|
|
26
|
+
* `backups:pull-prod` copies segments down and removes the box's copy — and a
|
|
27
|
+
* collector that scp'd a file the app was still appending to would take a
|
|
28
|
+
* prefix and then delete the rest. A sealed file cannot be half a file.
|
|
29
|
+
*
|
|
30
|
+
* ── Terminal vs staged, and the staleness refusal ───────────────────────────
|
|
31
|
+
* On a workstation the archive is home: `<app-data-home>/cursed-satellites/
|
|
32
|
+
* archive/<app>/`, it is `terminal`, and it simply grows. The home is
|
|
33
|
+
* CONFIGURATION ({@link resolveAppDataHome}) — `$APP_DATA_HOME`, else the OS's own
|
|
34
|
+
* convention — never a folder this library invented in somebody's home; that was
|
|
35
|
+
* the published defect fixed 2026-09-13. On a deployed box nothing outside the
|
|
36
|
+
* app's own data dir is writable, so it resolves to `$APP_DATA_DIR/archive` and
|
|
37
|
+
* is `staged` — something else has to come and get it. That distinction is what lets the
|
|
38
|
+
* sweeper honor "refuse to trim if the last successful pull is stale" using a
|
|
39
|
+
* fact the box can actually observe: how many bytes of sealed segments are still
|
|
40
|
+
* sitting there uncollected. If the collector stops, the archive fills to its
|
|
41
|
+
* budget and the trim STOPS — bounded archive, complete table — rather than the
|
|
42
|
+
* table trimming into an archive nobody is taking away.
|
|
43
|
+
*/
|
|
44
|
+
import { createHash } from "node:crypto";
|
|
45
|
+
import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, writeSync, } from "node:fs";
|
|
46
|
+
import { join } from "node:path";
|
|
47
|
+
import { APP_DATA_HOME_ENV, resolveAppDataHome } from "../paths/appDataHome";
|
|
48
|
+
/** The default budget of uncollected archive on a staged (deployed-box) home.
|
|
49
|
+
* Generous next to the tables it governs — notes' whole `ops` log is ~15 MB —
|
|
50
|
+
* and small next to a 6.7 GB root. Reached only if the collector has stopped. */
|
|
51
|
+
export const DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES = 64 * 1024 * 1024;
|
|
52
|
+
const SAFE_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
53
|
+
/** `20260825-114233` — sortable, second-resolution, and the same shape the
|
|
54
|
+
* fleet's dated backup dirs already use, so one eye reads both. */
|
|
55
|
+
export function archiveStamp(date) {
|
|
56
|
+
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
57
|
+
return (`${date.getUTCFullYear()}${p(date.getUTCMonth() + 1)}${p(date.getUTCDate())}-` +
|
|
58
|
+
`${p(date.getUTCHours())}${p(date.getUTCMinutes())}${p(date.getUTCSeconds())}`);
|
|
59
|
+
}
|
|
60
|
+
/** Can we create and write inside this directory? Probed, never assumed —
|
|
61
|
+
* the same reasoning as `resolveRetentionHome`, and the same failure it
|
|
62
|
+
* prevents: a writer that silently EROFS-es every time. */
|
|
63
|
+
function isUsable(dir) {
|
|
64
|
+
try {
|
|
65
|
+
mkdirSync(dir, { recursive: true });
|
|
66
|
+
const probe = join(dir, ".write-probe");
|
|
67
|
+
writeFileSync(probe, "");
|
|
68
|
+
rmSync(probe, { force: true });
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Where this app's archive lives on THIS machine, and whether the machine keeps
|
|
77
|
+
* it.
|
|
78
|
+
*
|
|
79
|
+
* `<app-data-home>/cursed-satellites/archive/<app>/`, reached through the same env
|
|
80
|
+
* ladder the retention home uses, so a test, a container and a deployed box each
|
|
81
|
+
* land somewhere writable rather than somewhere assumed.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveArchiveHome(app, env = process.env) {
|
|
84
|
+
if (!SAFE_NAME.test(app))
|
|
85
|
+
throw new Error(`archive: app key "${app}" must be lower-kebab`);
|
|
86
|
+
const candidates = [];
|
|
87
|
+
if (env.RETENTION_ARCHIVE_HOME) {
|
|
88
|
+
candidates.push({ dir: join(env.RETENTION_ARCHIVE_HOME, app), terminal: true });
|
|
89
|
+
}
|
|
90
|
+
// An explicitly CONFIGURED home is used whether or not it exists yet — the
|
|
91
|
+
// operator naming it is the statement that it is the right place.
|
|
92
|
+
const configured = env[APP_DATA_HOME_ENV]?.trim();
|
|
93
|
+
if (configured) {
|
|
94
|
+
candidates.push({
|
|
95
|
+
dir: join(configured, "cursed-satellites", "archive", app),
|
|
96
|
+
terminal: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const conventional = resolveAppDataHome({ env });
|
|
100
|
+
// The OS convention is only PREFERRED when it is already there: creating it on
|
|
101
|
+
// a deployed box would strand the archive where no collector looks.
|
|
102
|
+
if (!configured && existsSync(conventional)) {
|
|
103
|
+
candidates.push({
|
|
104
|
+
dir: join(conventional, "cursed-satellites", "archive", app),
|
|
105
|
+
terminal: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// A deployed box: only the app's own data dir is writable under
|
|
109
|
+
// `ProtectSystem=strict`, and something else has to come and collect it.
|
|
110
|
+
if (env.APP_DATA_DIR)
|
|
111
|
+
candidates.push({ dir: join(env.APP_DATA_DIR, "archive"), terminal: false });
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
if (isUsable(candidate.dir))
|
|
114
|
+
return candidate;
|
|
115
|
+
}
|
|
116
|
+
// Nothing writable. Return the conventional path and let `write()` throw when
|
|
117
|
+
// it is actually used — which, by this module's contract, means nothing is
|
|
118
|
+
// deleted. Never a boot failure.
|
|
119
|
+
return { dir: join(conventional, "cursed-satellites", "archive", app), terminal: true };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The fleet's archive sink: newline-delimited JSON segments plus an append-only
|
|
123
|
+
* `index.jsonl` naming every segment ever sealed here.
|
|
124
|
+
*
|
|
125
|
+
* The index is what makes the archive answerable without reading gigabytes —
|
|
126
|
+
* "what is in here, how many rows, and does it still hash the same" — and it is
|
|
127
|
+
* appended rather than rewritten so a crash mid-write can cost at most the last
|
|
128
|
+
* line, never the catalogue.
|
|
129
|
+
*/
|
|
130
|
+
export function createJsonlArchive(options) {
|
|
131
|
+
const { app, resource } = options;
|
|
132
|
+
if (!SAFE_NAME.test(resource)) {
|
|
133
|
+
throw new Error(`archive: resource id "${resource}" must be lower-kebab`);
|
|
134
|
+
}
|
|
135
|
+
const home = options.home ?? resolveArchiveHome(app, options.env);
|
|
136
|
+
const dir = home.dir;
|
|
137
|
+
const terminal = home.terminal;
|
|
138
|
+
const indexPath = join(dir, "index.jsonl");
|
|
139
|
+
return {
|
|
140
|
+
dir,
|
|
141
|
+
terminal,
|
|
142
|
+
pendingBytes() {
|
|
143
|
+
if (terminal)
|
|
144
|
+
return 0;
|
|
145
|
+
let total = 0;
|
|
146
|
+
let names;
|
|
147
|
+
try {
|
|
148
|
+
names = readdirSync(dir);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return 0; // not created yet — nothing is owed
|
|
152
|
+
}
|
|
153
|
+
for (const name of names) {
|
|
154
|
+
if (!name.endsWith(".jsonl") || name === "index.jsonl")
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
total += statSync(join(dir, name)).size;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// vanished between readdir and stat — the collector took it
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return total;
|
|
164
|
+
},
|
|
165
|
+
write(records, at) {
|
|
166
|
+
if (records.length === 0)
|
|
167
|
+
throw new Error("archive: refusing to seal an empty segment");
|
|
168
|
+
const body = `${records.map((row) => JSON.stringify(row)).join("\n")}\n`;
|
|
169
|
+
const bytes = Buffer.byteLength(body);
|
|
170
|
+
const sha256 = createHash("sha256").update(body).digest("hex");
|
|
171
|
+
const file = `${resource}-${archiveStamp(new Date(at))}.jsonl`;
|
|
172
|
+
const path = join(dir, file);
|
|
173
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
174
|
+
mkdirSync(dir, { recursive: true });
|
|
175
|
+
// Write + fsync THE FILE. Without the fsync the bytes can be in the page
|
|
176
|
+
// cache while the rows are already gone from a database that fsyncs its
|
|
177
|
+
// own WAL — a power cut between the two loses exactly the rows this
|
|
178
|
+
// module exists to keep.
|
|
179
|
+
const fd = openSync(tmp, "w");
|
|
180
|
+
try {
|
|
181
|
+
writeSync(fd, body);
|
|
182
|
+
fsyncSync(fd);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
closeSync(fd);
|
|
186
|
+
}
|
|
187
|
+
// Prove it, off the disk, before anything is deleted. Re-reading is the
|
|
188
|
+
// only check that covers a short write, a full disk that reported
|
|
189
|
+
// success, and a filesystem that lied.
|
|
190
|
+
let readBack;
|
|
191
|
+
try {
|
|
192
|
+
readBack = readFileSync(tmp, "utf8");
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
rmSync(tmp, { force: true });
|
|
196
|
+
throw new Error(`archive: could not read back ${tmp} — ${error instanceof Error ? error.message : String(error)}`);
|
|
197
|
+
}
|
|
198
|
+
const verify = (ok, what) => {
|
|
199
|
+
if (ok)
|
|
200
|
+
return;
|
|
201
|
+
rmSync(tmp, { force: true });
|
|
202
|
+
throw new Error(`archive: ${file} failed verification (${what}) — nothing was deleted`);
|
|
203
|
+
};
|
|
204
|
+
verify(Buffer.byteLength(readBack) === bytes, `expected ${bytes} bytes`);
|
|
205
|
+
verify(createHash("sha256").update(readBack).digest("hex") === sha256, "sha256 mismatch");
|
|
206
|
+
const lines = readBack.split("\n").filter((line) => line.length > 0);
|
|
207
|
+
verify(lines.length === records.length, `expected ${records.length} rows, read ${lines.length}`);
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
let parsed;
|
|
210
|
+
try {
|
|
211
|
+
parsed = JSON.parse(line);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
verify(false, "a row did not parse back");
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
verify(typeof parsed === "object" && parsed !== null, "a row read back as a non-object");
|
|
218
|
+
}
|
|
219
|
+
// Sealed. `rename` inside one directory is atomic on every filesystem
|
|
220
|
+
// this fleet runs on, so a collector never sees a partial segment.
|
|
221
|
+
renameSync(tmp, path);
|
|
222
|
+
const segment = { path, file, rows: records.length, bytes, sha256, at };
|
|
223
|
+
try {
|
|
224
|
+
appendFileSync(indexPath, `${JSON.stringify({ app, resource, ...segment, path: undefined, file })}\n`);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// The catalogue is a convenience; the segment is the archive. A failed
|
|
228
|
+
// index append must never make a sealed segment look unsealed and send
|
|
229
|
+
// the same rows through again.
|
|
230
|
+
}
|
|
231
|
+
return segment;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Re-verify every segment in an archive directory against the sha256 the index
|
|
237
|
+
* recorded — the answer to "is this archive still real?" without a restore.
|
|
238
|
+
*
|
|
239
|
+
* Exists because an archive nobody can check is a belief, and this whole
|
|
240
|
+
* category is a promise that the rows still exist somewhere.
|
|
241
|
+
*/
|
|
242
|
+
export function verifyArchive(dir) {
|
|
243
|
+
const result = { segments: 0, rows: 0, bytes: 0, bad: [] };
|
|
244
|
+
let index;
|
|
245
|
+
try {
|
|
246
|
+
index = readFileSync(join(dir, "index.jsonl"), "utf8");
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
for (const line of index.split("\n")) {
|
|
252
|
+
if (!line.trim())
|
|
253
|
+
continue;
|
|
254
|
+
let entry;
|
|
255
|
+
try {
|
|
256
|
+
entry = JSON.parse(line);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
result.bad.push(`unparseable index line: ${line.slice(0, 80)}`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!entry.file || !entry.sha256)
|
|
263
|
+
continue;
|
|
264
|
+
result.segments++;
|
|
265
|
+
const path = join(dir, entry.file);
|
|
266
|
+
let body;
|
|
267
|
+
try {
|
|
268
|
+
body = readFileSync(path, "utf8");
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// On a STAGED archive a collected segment is the healthy outcome, so
|
|
272
|
+
// absence is reported as a fact rather than as corruption.
|
|
273
|
+
result.bad.push(`${entry.file}: absent (collected, or lost)`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (createHash("sha256").update(body).digest("hex") !== entry.sha256) {
|
|
277
|
+
result.bad.push(`${entry.file}: sha256 mismatch`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
result.rows += entry.rows ?? 0;
|
|
281
|
+
result.bytes += entry.bytes ?? 0;
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read model behind the station's Retention view: every app's published
|
|
3
|
+
* resources joined to the owner's overrides, with the effective policy resolved
|
|
4
|
+
* by the SAME function the sweeps use.
|
|
5
|
+
*
|
|
6
|
+
* That last point is the whole reason this is a shared function rather than a
|
|
7
|
+
* few lines in the station's route handler. A UI that computes "effective days"
|
|
8
|
+
* its own way eventually disagrees with the sweeper, and the disagreement shows
|
|
9
|
+
* up as data that vanished earlier than the screen said it would — which is the
|
|
10
|
+
* one failure mode that would make the owner stop trusting the control.
|
|
11
|
+
*/
|
|
12
|
+
import type { EffectiveRetentionPolicy, RetentionOverride } from "./policy";
|
|
13
|
+
import type { PublishedResource } from "./store";
|
|
14
|
+
export interface RetentionFleetRow extends PublishedResource {
|
|
15
|
+
key: string;
|
|
16
|
+
app: string;
|
|
17
|
+
policy: EffectiveRetentionPolicy;
|
|
18
|
+
override?: RetentionOverride;
|
|
19
|
+
/** The category's description, denormalized so the UI needs no second lookup. */
|
|
20
|
+
category: {
|
|
21
|
+
label: string;
|
|
22
|
+
defaultDays: number;
|
|
23
|
+
summary: string;
|
|
24
|
+
};
|
|
25
|
+
/** Default days for this resource before any override — what "Reset" returns to. */
|
|
26
|
+
defaultDays: number;
|
|
27
|
+
/** The publishing app's last heartbeat, so the UI can label a stale row. */
|
|
28
|
+
publishedAt: number;
|
|
29
|
+
instance?: string;
|
|
30
|
+
sweepDisabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface RetentionFleetView {
|
|
33
|
+
home: string;
|
|
34
|
+
updatedAt: number;
|
|
35
|
+
rows: RetentionFleetRow[];
|
|
36
|
+
totals: {
|
|
37
|
+
resources: number;
|
|
38
|
+
apps: number;
|
|
39
|
+
items: number;
|
|
40
|
+
bytes: number;
|
|
41
|
+
};
|
|
42
|
+
/** Overrides with no matching declaration — an app renamed or retired a resource. */
|
|
43
|
+
orphanedOverrides: Array<{
|
|
44
|
+
key: string;
|
|
45
|
+
override: RetentionOverride;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export declare function buildRetentionFleetView(options?: {
|
|
49
|
+
home?: string;
|
|
50
|
+
}): RetentionFleetView;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { RETENTION_CATEGORIES, resolveRetentionPolicy } from "./policy";
|
|
2
|
+
import { readRetentionOverrides, readRetentionRegistry, resolveRetentionHome } from "./store";
|
|
3
|
+
export function buildRetentionFleetView(options = {}) {
|
|
4
|
+
const home = options.home ?? resolveRetentionHome();
|
|
5
|
+
const publications = readRetentionRegistry(home);
|
|
6
|
+
const overridesFile = readRetentionOverrides(home);
|
|
7
|
+
const rows = [];
|
|
8
|
+
const claimed = new Set();
|
|
9
|
+
for (const publication of publications) {
|
|
10
|
+
for (const resource of publication.resources) {
|
|
11
|
+
const declaration = resource.declaration;
|
|
12
|
+
if (!declaration || typeof declaration.app !== "string")
|
|
13
|
+
continue;
|
|
14
|
+
const key = `${declaration.app}:${declaration.id}`;
|
|
15
|
+
const override = overridesFile.overrides[key];
|
|
16
|
+
let policy;
|
|
17
|
+
try {
|
|
18
|
+
policy = resolveRetentionPolicy(declaration, override);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// A publication written by an older/newer app version than this reader
|
|
22
|
+
// understands is skipped rather than allowed to break the whole view.
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
claimed.add(key);
|
|
26
|
+
const category = RETENTION_CATEGORIES[declaration.category];
|
|
27
|
+
rows.push({
|
|
28
|
+
...resource,
|
|
29
|
+
key,
|
|
30
|
+
app: declaration.app,
|
|
31
|
+
policy,
|
|
32
|
+
...(override ? { override } : {}),
|
|
33
|
+
category,
|
|
34
|
+
defaultDays: declaration.defaultDays ?? category.defaultDays,
|
|
35
|
+
publishedAt: publication.publishedAt,
|
|
36
|
+
...(publication.instance ? { instance: publication.instance } : {}),
|
|
37
|
+
...(publication.sweepDisabled ? { sweepDisabled: true } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Biggest first: the view exists to answer "what is filling my disk".
|
|
42
|
+
rows.sort((a, b) => {
|
|
43
|
+
const bytes = (b.measurement?.bytes ?? 0) - (a.measurement?.bytes ?? 0);
|
|
44
|
+
if (bytes !== 0)
|
|
45
|
+
return bytes;
|
|
46
|
+
const items = (b.measurement?.items ?? 0) - (a.measurement?.items ?? 0);
|
|
47
|
+
if (items !== 0)
|
|
48
|
+
return items;
|
|
49
|
+
return a.key.localeCompare(b.key);
|
|
50
|
+
});
|
|
51
|
+
const orphanedOverrides = Object.entries(overridesFile.overrides)
|
|
52
|
+
.filter(([key]) => !claimed.has(key))
|
|
53
|
+
.map(([key, override]) => ({ key, override }));
|
|
54
|
+
return {
|
|
55
|
+
home,
|
|
56
|
+
updatedAt: overridesFile.updatedAt,
|
|
57
|
+
rows,
|
|
58
|
+
totals: {
|
|
59
|
+
resources: rows.length,
|
|
60
|
+
apps: new Set(rows.map((r) => r.app)).size,
|
|
61
|
+
items: rows.reduce((sum, r) => sum + (r.measurement?.items ?? 0), 0),
|
|
62
|
+
bytes: rows.reduce((sum, r) => sum + (r.measurement?.bytes ?? 0), 0),
|
|
63
|
+
},
|
|
64
|
+
orphanedOverrides,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/retention` — THE fleet's one retention standard.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this lives in cursedbelt ────────────────────────────────────────────
|
|
5
|
+
* Retention used to be spoken in three unrelated vocabularies: this
|
|
6
|
+
* category-based opt-in design (born in `cursed-satellites/satellite-kit`), the
|
|
7
|
+
* DB-backed audit|meter WINDOWS in `cursedbelt-cc/drivers`' retentionPolicy
|
|
8
|
+
* (billing evidence — a genuinely different concern that keeps the name
|
|
9
|
+
* {@link RetentionWindows}), and `deploy-engine/backupRetention`'s artifact
|
|
10
|
+
* pruning. DEDUP-8(c) made THIS one canonical and moved it here, so a single
|
|
11
|
+
* copy serves cursed-core, cursed-satellites and every generated app, and
|
|
12
|
+
* `RetentionPolicy` names exactly one thing across the fleet. `satellite-kit`'s
|
|
13
|
+
* `./retention` subpath is now a thin re-export of this module.
|
|
14
|
+
*
|
|
15
|
+
* An app declares what it owns and starts a sweeper; the station reads and
|
|
16
|
+
* edits the policies. Nothing else in the fleet should contain a timer that
|
|
17
|
+
* deletes old rows.
|
|
18
|
+
*
|
|
19
|
+
* const sweeper = createRetentionSweeper({
|
|
20
|
+
* app: "orch",
|
|
21
|
+
* targets: [
|
|
22
|
+
* createSqliteTableTarget({ db, declaration: {...}, table: "events", timeColumn: "at" }),
|
|
23
|
+
* createFileDirTarget({ declaration: {...}, dir: attemptLogs.dir, match: /\.jsonl$/ }),
|
|
24
|
+
* ],
|
|
25
|
+
* });
|
|
26
|
+
* sweeper.start();
|
|
27
|
+
*
|
|
28
|
+
* See `policy.ts` for the rule that decides whether a resource may be governed
|
|
29
|
+
* at all — retention is opt-in by category, and no category can describe data
|
|
30
|
+
* the owner would miss (which is exactly why billing evidence is NOT a category
|
|
31
|
+
* and lives in `retentionPolicy.ts` instead).
|
|
32
|
+
*/
|
|
33
|
+
export { archiveStamp, createJsonlArchive, DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES, resolveArchiveHome, verifyArchive, type ArchiveHome, type ArchiveSegment, type ArchiveSink, type JsonlArchiveOptions, } from "./archive";
|
|
34
|
+
export { ARCHIVING_RETENTION_CATEGORIES, assertRetentionDeclaration, categoryArchivesBeforeDelete, cutoffFor, DAY_MS, FLEET_DEFAULT_RETENTION_DAYS, RETENTION_CATEGORIES, RETENTION_CATEGORY_KEYS, RETENTION_MAX_DAYS, RETENTION_MIN_DAYS, resolveRetentionPolicy, retentionKey, type EffectiveRetentionPolicy, type RetentionPolicy, type RetentionCategory, type RetentionDeclaration, type RetentionFieldSource, type RetentionOverride, type RetentionUnit, } from "./policy";
|
|
35
|
+
export { publishRetention, readRetentionOverrides, readRetentionRegistry, resolveRetentionHome, setRetentionHomeForTests, writeRetentionOverride, type PublishedResource, type RetentionMeasurement, type RetentionOverridesFile, type RetentionPublication, type RetentionSweepReport, } from "./store";
|
|
36
|
+
export { createArchivingSqliteTableTarget, createFileDirTarget, createSqliteTableTarget, type ArchivingSqliteTableTargetOptions, type FileDirTargetOptions, type RetentionPreview, type RetentionTarget, type SqliteTableTargetOptions, } from "./target";
|
|
37
|
+
export { createRetentionSweeper, DEFAULT_SWEEP_INTERVAL_MS, OVERRIDES_PULSE_MS, type CreateRetentionSweeperOptions, type ResourceOutcome, type RetentionRunReport, type RetentionSweeper, } from "./sweeper";
|
|
38
|
+
export { buildRetentionFleetView, type RetentionFleetRow, type RetentionFleetView } from "./fleet";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/retention` — THE fleet's one retention standard.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this lives in cursedbelt ────────────────────────────────────────────
|
|
5
|
+
* Retention used to be spoken in three unrelated vocabularies: this
|
|
6
|
+
* category-based opt-in design (born in `cursed-satellites/satellite-kit`), the
|
|
7
|
+
* DB-backed audit|meter WINDOWS in `cursedbelt-cc/drivers`' retentionPolicy
|
|
8
|
+
* (billing evidence — a genuinely different concern that keeps the name
|
|
9
|
+
* {@link RetentionWindows}), and `deploy-engine/backupRetention`'s artifact
|
|
10
|
+
* pruning. DEDUP-8(c) made THIS one canonical and moved it here, so a single
|
|
11
|
+
* copy serves cursed-core, cursed-satellites and every generated app, and
|
|
12
|
+
* `RetentionPolicy` names exactly one thing across the fleet. `satellite-kit`'s
|
|
13
|
+
* `./retention` subpath is now a thin re-export of this module.
|
|
14
|
+
*
|
|
15
|
+
* An app declares what it owns and starts a sweeper; the station reads and
|
|
16
|
+
* edits the policies. Nothing else in the fleet should contain a timer that
|
|
17
|
+
* deletes old rows.
|
|
18
|
+
*
|
|
19
|
+
* const sweeper = createRetentionSweeper({
|
|
20
|
+
* app: "orch",
|
|
21
|
+
* targets: [
|
|
22
|
+
* createSqliteTableTarget({ db, declaration: {...}, table: "events", timeColumn: "at" }),
|
|
23
|
+
* createFileDirTarget({ declaration: {...}, dir: attemptLogs.dir, match: /\.jsonl$/ }),
|
|
24
|
+
* ],
|
|
25
|
+
* });
|
|
26
|
+
* sweeper.start();
|
|
27
|
+
*
|
|
28
|
+
* See `policy.ts` for the rule that decides whether a resource may be governed
|
|
29
|
+
* at all — retention is opt-in by category, and no category can describe data
|
|
30
|
+
* the owner would miss (which is exactly why billing evidence is NOT a category
|
|
31
|
+
* and lives in `retentionPolicy.ts` instead).
|
|
32
|
+
*/
|
|
33
|
+
export { archiveStamp, createJsonlArchive, DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES, resolveArchiveHome, verifyArchive, } from "./archive";
|
|
34
|
+
export { ARCHIVING_RETENTION_CATEGORIES, assertRetentionDeclaration, categoryArchivesBeforeDelete, cutoffFor, DAY_MS, FLEET_DEFAULT_RETENTION_DAYS, RETENTION_CATEGORIES, RETENTION_CATEGORY_KEYS, RETENTION_MAX_DAYS, RETENTION_MIN_DAYS, resolveRetentionPolicy, retentionKey, } from "./policy";
|
|
35
|
+
export { publishRetention, readRetentionOverrides, readRetentionRegistry, resolveRetentionHome, setRetentionHomeForTests, writeRetentionOverride, } from "./store";
|
|
36
|
+
export { createArchivingSqliteTableTarget, createFileDirTarget, createSqliteTableTarget, } from "./target";
|
|
37
|
+
export { createRetentionSweeper, DEFAULT_SWEEP_INTERVAL_MS, OVERRIDES_PULSE_MS, } from "./sweeper";
|
|
38
|
+
export { buildRetentionFleetView } from "./fleet";
|