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,175 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { beforeEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { applyFtsSchema, buildFtsSchema, createFts5Search, sanitizeFtsQuery } from './fts5';
|
|
4
|
+
|
|
5
|
+
/** A representative content table + its FTS index, the way an app migration would. */
|
|
6
|
+
function makeNotesDb(): Database {
|
|
7
|
+
const db = new Database(':memory:');
|
|
8
|
+
db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL)');
|
|
9
|
+
applyFtsSchema(db, { table: 'notes_fts', contentTable: 'notes', columns: ['body'] });
|
|
10
|
+
return db;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('sanitizeFtsQuery', () => {
|
|
14
|
+
test('wraps each word as an AND-of-prefix token', () => {
|
|
15
|
+
expect(sanitizeFtsQuery('hello')).toBe('"hello"*');
|
|
16
|
+
expect(sanitizeFtsQuery('hello world')).toBe('"hello"* "world"*');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('strips FTS5 operators so input cannot inject syntax or error out', () => {
|
|
20
|
+
expect(sanitizeFtsQuery('"; DROP--')).toBe('"DROP"*');
|
|
21
|
+
expect(sanitizeFtsQuery('foo:bar^2 (baz)')).toBe('"foo"* "bar"* "2"* "baz"*');
|
|
22
|
+
expect(sanitizeFtsQuery('a AND b')).toBe('"a"* "AND"* "b"*'); // AND is just a literal term
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('returns empty string when nothing searchable remains', () => {
|
|
26
|
+
expect(sanitizeFtsQuery('')).toBe('');
|
|
27
|
+
expect(sanitizeFtsQuery(' ')).toBe('');
|
|
28
|
+
expect(sanitizeFtsQuery('***')).toBe('');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('keeps unicode letters/digits', () => {
|
|
32
|
+
expect(sanitizeFtsQuery('café 2024')).toBe('"café"* "2024"*');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('buildFtsSchema', () => {
|
|
37
|
+
test('emits an external-content table + three sync triggers', () => {
|
|
38
|
+
const sql = buildFtsSchema({ table: 'notes_fts', contentTable: 'notes', columns: ['body'] });
|
|
39
|
+
expect(sql).toContain('USING fts5(\n "body",\n content="notes"');
|
|
40
|
+
expect(sql).toContain('content_rowid="id"');
|
|
41
|
+
expect(sql).toContain('AFTER INSERT ON "notes"');
|
|
42
|
+
expect(sql).toContain('AFTER DELETE ON "notes"');
|
|
43
|
+
expect(sql).toContain('AFTER UPDATE ON "notes"');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('rejects non-identifier names and empty column lists', () => {
|
|
47
|
+
expect(() =>
|
|
48
|
+
buildFtsSchema({ table: 'a"; DROP', contentTable: 'n', columns: ['c'] }),
|
|
49
|
+
).toThrow();
|
|
50
|
+
expect(() => buildFtsSchema({ table: 'a_fts', contentTable: 'n', columns: [] })).toThrow();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('supports a custom rowid and multiple columns', () => {
|
|
54
|
+
const db = new Database(':memory:');
|
|
55
|
+
db.run('CREATE TABLE c (pk INTEGER PRIMARY KEY, first TEXT, last TEXT)');
|
|
56
|
+
applyFtsSchema(db, {
|
|
57
|
+
table: 'c_fts',
|
|
58
|
+
contentTable: 'c',
|
|
59
|
+
contentRowid: 'pk',
|
|
60
|
+
columns: ['first', 'last'],
|
|
61
|
+
});
|
|
62
|
+
db.run("INSERT INTO c (pk, first, last) VALUES (5, 'Ada', 'Lovelace')");
|
|
63
|
+
const hits = createFts5Search(db).search({ table: 'c_fts', query: 'lovelace' });
|
|
64
|
+
expect(hits).toHaveLength(1);
|
|
65
|
+
expect(hits[0].rowid).toBe(5);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('createFts5Search.search', () => {
|
|
70
|
+
let db: Database;
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
db = makeNotesDb();
|
|
73
|
+
db.run("INSERT INTO notes (id, body) VALUES (1, 'Hello world')");
|
|
74
|
+
db.run("INSERT INTO notes (id, body) VALUES (2, 'Goodbye moon')");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('finds a single match and ranks it (bm25, negative)', () => {
|
|
78
|
+
const results = createFts5Search(db).search({ table: 'notes_fts', query: 'hello' });
|
|
79
|
+
expect(results).toHaveLength(1);
|
|
80
|
+
expect(results[0].rowid).toBe(1);
|
|
81
|
+
expect(results[0].rank).toBeLessThan(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('multi-word query ANDs the terms', () => {
|
|
85
|
+
const fts = createFts5Search(db);
|
|
86
|
+
expect(fts.search({ table: 'notes_fts', query: 'hello world' })).toHaveLength(1);
|
|
87
|
+
expect(fts.search({ table: 'notes_fts', query: 'hello moon' })).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('no-match and prefix-match behave as expected', () => {
|
|
91
|
+
const fts = createFts5Search(db);
|
|
92
|
+
expect(fts.search({ table: 'notes_fts', query: 'xyz123xyz' })).toHaveLength(0);
|
|
93
|
+
expect(fts.search({ table: 'notes_fts', query: 'hel' })).toHaveLength(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('empty/operator-only query returns [] without touching the DB', () => {
|
|
97
|
+
expect(createFts5Search(db).search({ table: 'notes_fts', query: ' ' })).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('orders results by relevance (rank ascending)', () => {
|
|
101
|
+
db.run("INSERT INTO notes (id, body) VALUES (3, 'hello hello hello')");
|
|
102
|
+
const results = createFts5Search(db).search({ table: 'notes_fts', query: 'hello' });
|
|
103
|
+
expect(results.map((r) => r.rowid)).toEqual([3, 1]); // denser match ranks first
|
|
104
|
+
expect(results[0].rank).toBeLessThanOrEqual(results[1].rank);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('snippet + highlight columns are returned only when requested', () => {
|
|
108
|
+
const plain = createFts5Search(db).search({ table: 'notes_fts', query: 'hello' })[0];
|
|
109
|
+
expect(plain.snippet).toBeUndefined();
|
|
110
|
+
expect(plain.highlight).toBeUndefined();
|
|
111
|
+
|
|
112
|
+
const rich = createFts5Search(db).search({
|
|
113
|
+
table: 'notes_fts',
|
|
114
|
+
query: 'hello',
|
|
115
|
+
snippet: { startMatch: '[', endMatch: ']' },
|
|
116
|
+
highlight: { column: 0, startMatch: '<', endMatch: '>' },
|
|
117
|
+
})[0];
|
|
118
|
+
expect(rich.snippet).toBe('[Hello] world');
|
|
119
|
+
expect(rich.highlight).toBe('<Hello> world');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('limit + offset paginate', () => {
|
|
123
|
+
db.run("INSERT INTO notes (id, body) VALUES (3, 'hello there')");
|
|
124
|
+
const fts = createFts5Search(db);
|
|
125
|
+
const page1 = fts.search({ table: 'notes_fts', query: 'hello', limit: 1, offset: 0 });
|
|
126
|
+
const page2 = fts.search({ table: 'notes_fts', query: 'hello', limit: 1, offset: 1 });
|
|
127
|
+
expect(page1).toHaveLength(1);
|
|
128
|
+
expect(page2).toHaveLength(1);
|
|
129
|
+
expect(page1[0].rowid).not.toBe(page2[0].rowid);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('rejects a non-identifier table name', () => {
|
|
133
|
+
expect(() => createFts5Search(db).search({ table: 'notes_fts; DROP', query: 'x' })).toThrow();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('createFts5Search triggers + maintenance', () => {
|
|
138
|
+
test('triggers keep the index in sync on insert / update / delete', () => {
|
|
139
|
+
const db = makeNotesDb();
|
|
140
|
+
const fts = createFts5Search(db);
|
|
141
|
+
db.run("INSERT INTO notes (id, body) VALUES (1, 'alpha')");
|
|
142
|
+
expect(fts.search({ table: 'notes_fts', query: 'alpha' })).toHaveLength(1);
|
|
143
|
+
|
|
144
|
+
db.run("UPDATE notes SET body = 'beta' WHERE id = 1");
|
|
145
|
+
expect(fts.search({ table: 'notes_fts', query: 'alpha' })).toHaveLength(0);
|
|
146
|
+
expect(fts.search({ table: 'notes_fts', query: 'beta' })).toHaveLength(1);
|
|
147
|
+
|
|
148
|
+
db.run('DELETE FROM notes WHERE id = 1');
|
|
149
|
+
expect(fts.search({ table: 'notes_fts', query: 'beta' })).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('clear empties the index; rebuild repopulates it from content', () => {
|
|
153
|
+
const db = makeNotesDb();
|
|
154
|
+
db.run("INSERT INTO notes (id, body) VALUES (1, 'searchable text')");
|
|
155
|
+
const fts = createFts5Search(db, { tables: ['notes_fts'] });
|
|
156
|
+
expect(fts.search({ table: 'notes_fts', query: 'searchable' })).toHaveLength(1);
|
|
157
|
+
|
|
158
|
+
fts.clear('notes_fts');
|
|
159
|
+
expect(fts.search({ table: 'notes_fts', query: 'searchable' })).toHaveLength(0);
|
|
160
|
+
|
|
161
|
+
fts.rebuildAll();
|
|
162
|
+
expect(fts.search({ table: 'notes_fts', query: 'searchable' })).toHaveLength(1);
|
|
163
|
+
|
|
164
|
+
// optimize is a maintenance no-op for correctness — it must not throw or lose rows.
|
|
165
|
+
fts.optimize('notes_fts');
|
|
166
|
+
expect(fts.search({ table: 'notes_fts', query: 'searchable' })).toHaveLength(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('rebuildAll iterates only the configured tables', () => {
|
|
170
|
+
const db = makeNotesDb();
|
|
171
|
+
db.run("INSERT INTO notes (id, body) VALUES (1, 'x')");
|
|
172
|
+
// No tables configured → rebuildAll is a no-op (does not throw).
|
|
173
|
+
expect(() => createFts5Search(db).rebuildAll()).not.toThrow();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FTS5 full-text search for `bun:sqlite` — the from-scratch replacement for the apps'
|
|
5
|
+
* hand-rolled `LIKE '%' || ? || '%'` search. Three pieces:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link buildFtsSchema} — generates the `CREATE VIRTUAL TABLE` + the three
|
|
8
|
+
* external-content sync triggers for one indexed table. Apps call it from a
|
|
9
|
+
* migration instead of hand-writing the (highly repetitive) trigger SQL 4×.
|
|
10
|
+
* 2. {@link createFts5Search} — a ranked query builder (bm25 `rank`, `snippet`,
|
|
11
|
+
* `highlight`) over those tables, plus the FTS5 maintenance commands
|
|
12
|
+
* (`rebuild`/`clear`/`optimize`) the ETL and bulk importers need.
|
|
13
|
+
* 3. {@link sanitizeFtsQuery} — turns arbitrary user input into a safe AND-of-prefix
|
|
14
|
+
* FTS5 MATCH expression (no operator injection, no parse errors).
|
|
15
|
+
*
|
|
16
|
+
* FTS5 is compiled into `bun:sqlite` (NOT `node:sqlite`, which omits it). Everything
|
|
17
|
+
* here is app-agnostic: the indexed table names are a *constructor option*, never
|
|
18
|
+
* hard-coded — ca and ru index different tables.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** A safe, unquoted SQL identifier (table/column name). */
|
|
22
|
+
const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
23
|
+
|
|
24
|
+
function assertIdent(name: string, what: string): string {
|
|
25
|
+
if (!IDENT.test(name)) {
|
|
26
|
+
throw new Error(`Invalid ${what}: ${JSON.stringify(name)} (expected a plain SQL identifier)`);
|
|
27
|
+
}
|
|
28
|
+
return name;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Double-quote an identifier (validated first, so the quoting can't be escaped out of). */
|
|
32
|
+
const q = (name: string): string => `"${name}"`;
|
|
33
|
+
|
|
34
|
+
/** Single-quote a string literal for inlining (doubles embedded quotes). */
|
|
35
|
+
const lit = (s: string): string => `'${s.replace(/'/g, "''")}'`;
|
|
36
|
+
|
|
37
|
+
/** Coerce to a bounded non-negative integer for inlining (snippet token counts, etc.). */
|
|
38
|
+
const int = (n: number, fallback: number): number =>
|
|
39
|
+
Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
40
|
+
|
|
41
|
+
// ── Schema generation ────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export interface FtsTableSpec {
|
|
44
|
+
/** The FTS5 virtual table to create, e.g. `'notes_fts'`. */
|
|
45
|
+
table: string;
|
|
46
|
+
/** The source ("content") table whose text it indexes, e.g. `'notes'`. */
|
|
47
|
+
contentTable: string;
|
|
48
|
+
/** The content table's rowid/primary-key column. Default: `'id'`. */
|
|
49
|
+
contentRowid?: string;
|
|
50
|
+
/** Indexed columns — must exist on the content table, in this order. */
|
|
51
|
+
columns: string[];
|
|
52
|
+
/**
|
|
53
|
+
* FTS5 tokenizer directive. Default: `unicode61 remove_diacritics 2` (case- and
|
|
54
|
+
* accent-folding, the right default for human text). Pass e.g. `'porter unicode61'`
|
|
55
|
+
* for stemming, or `'trigram'` for substring search.
|
|
56
|
+
*/
|
|
57
|
+
tokenize?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the DDL for one external-content FTS5 table and its three sync triggers
|
|
62
|
+
* (insert/update/delete), as a single multi-statement string ready for `db.run()`.
|
|
63
|
+
*
|
|
64
|
+
* External content (`content=<table>, content_rowid=<pk>`) means the FTS index stores
|
|
65
|
+
* only the inverted index, never a second copy of the text — the triggers mirror every
|
|
66
|
+
* write into the index. This is the canonical pattern from the SQLite FTS5 docs,
|
|
67
|
+
* generated once so apps don't repeat it per table.
|
|
68
|
+
*/
|
|
69
|
+
export function buildFtsSchema(spec: FtsTableSpec): string {
|
|
70
|
+
const table = assertIdent(spec.table, 'fts table name');
|
|
71
|
+
const content = assertIdent(spec.contentTable, 'content table name');
|
|
72
|
+
const rowid = assertIdent(spec.contentRowid ?? 'id', 'content rowid column');
|
|
73
|
+
if (spec.columns.length === 0) {
|
|
74
|
+
throw new Error(`buildFtsSchema(${table}): at least one indexed column is required`);
|
|
75
|
+
}
|
|
76
|
+
const cols = spec.columns.map((c) => assertIdent(c, 'indexed column'));
|
|
77
|
+
const tokenize = spec.tokenize ?? 'unicode61 remove_diacritics 2';
|
|
78
|
+
|
|
79
|
+
const colList = cols.map(q).join(', ');
|
|
80
|
+
const newVals = cols.map((c) => `new.${q(c)}`).join(', ');
|
|
81
|
+
const oldVals = cols.map((c) => `old.${q(c)}`).join(', ');
|
|
82
|
+
|
|
83
|
+
// `INSERT INTO <fts>(<fts>, rowid, …) VALUES('delete', …)` is FTS5's documented way
|
|
84
|
+
// to remove the prior term rows for a content row before re-indexing it.
|
|
85
|
+
return `
|
|
86
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ${q(table)} USING fts5(
|
|
87
|
+
${colList},
|
|
88
|
+
content=${q(content)},
|
|
89
|
+
content_rowid=${q(rowid)},
|
|
90
|
+
tokenize=${lit(tokenize)}
|
|
91
|
+
);
|
|
92
|
+
CREATE TRIGGER IF NOT EXISTS ${q(`${table}_ai`)} AFTER INSERT ON ${q(content)} BEGIN
|
|
93
|
+
INSERT INTO ${q(table)}(rowid, ${colList}) VALUES (new.${q(rowid)}, ${newVals});
|
|
94
|
+
END;
|
|
95
|
+
CREATE TRIGGER IF NOT EXISTS ${q(`${table}_ad`)} AFTER DELETE ON ${q(content)} BEGIN
|
|
96
|
+
INSERT INTO ${q(table)}(${q(table)}, rowid, ${colList}) VALUES ('delete', old.${q(rowid)}, ${oldVals});
|
|
97
|
+
END;
|
|
98
|
+
CREATE TRIGGER IF NOT EXISTS ${q(`${table}_au`)} AFTER UPDATE ON ${q(content)} BEGIN
|
|
99
|
+
INSERT INTO ${q(table)}(${q(table)}, rowid, ${colList}) VALUES ('delete', old.${q(rowid)}, ${oldVals});
|
|
100
|
+
INSERT INTO ${q(table)}(rowid, ${colList}) VALUES (new.${q(rowid)}, ${newVals});
|
|
101
|
+
END;`.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Create every table in `specs` and its triggers on `db` (one `db.run` per spec). */
|
|
105
|
+
export function applyFtsSchema(db: Database, ...specs: FtsTableSpec[]): void {
|
|
106
|
+
for (const spec of specs) db.run(buildFtsSchema(spec));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Search ───────────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
export interface FtsResult {
|
|
112
|
+
/** The content row's rowid (its `content_rowid` value). */
|
|
113
|
+
rowid: number;
|
|
114
|
+
/** FTS5 bm25 relevance — lower (more negative) is more relevant. */
|
|
115
|
+
rank: number;
|
|
116
|
+
/** Present only when `snippet` was requested: a truncated, match-marked excerpt. */
|
|
117
|
+
snippet?: string;
|
|
118
|
+
/** Present only when `highlight` was requested: the full column with matches marked. */
|
|
119
|
+
highlight?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface FtsSnippetOpts {
|
|
123
|
+
/** FTS column index to excerpt; `-1` (default) auto-picks the best-matching column. */
|
|
124
|
+
column?: number;
|
|
125
|
+
/** Wraps each match, default `'<b>'`. */
|
|
126
|
+
startMatch?: string;
|
|
127
|
+
/** Closes each match, default `'</b>'`. */
|
|
128
|
+
endMatch?: string;
|
|
129
|
+
/** Inserted where text is trimmed, default `'…'`. */
|
|
130
|
+
ellipsis?: string;
|
|
131
|
+
/** Max tokens in the excerpt (1–64), default `16`. */
|
|
132
|
+
maxTokens?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface FtsHighlightOpts {
|
|
136
|
+
/** FTS column index to return (required — `highlight()` has no auto column). */
|
|
137
|
+
column: number;
|
|
138
|
+
/** Wraps each match, default `'<b>'`. */
|
|
139
|
+
startMatch?: string;
|
|
140
|
+
/** Closes each match, default `'</b>'`. */
|
|
141
|
+
endMatch?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface FtsSearchOpts {
|
|
145
|
+
/** The FTS5 table to query, e.g. `'notes_fts'`. */
|
|
146
|
+
table: string;
|
|
147
|
+
/** Raw user query — sanitized into a prefix-match expression before MATCH. */
|
|
148
|
+
query: string;
|
|
149
|
+
/** Page size, default `20`. */
|
|
150
|
+
limit?: number;
|
|
151
|
+
/** Row offset, default `0`. */
|
|
152
|
+
offset?: number;
|
|
153
|
+
/** Request a `snippet(...)` excerpt column. */
|
|
154
|
+
snippet?: FtsSnippetOpts;
|
|
155
|
+
/** Request a `highlight(...)` column. */
|
|
156
|
+
highlight?: FtsHighlightOpts;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface Fts5Search {
|
|
160
|
+
/** Ranked search; returns `[]` for an empty/operator-only query (never throws on it). */
|
|
161
|
+
search(opts: FtsSearchOpts): FtsResult[];
|
|
162
|
+
/** Rebuild one FTS index from its content table (post-ETL / after `clear`). */
|
|
163
|
+
rebuild(table: string): void;
|
|
164
|
+
/** Rebuild every table passed to {@link createFts5Search} (`tables` option). */
|
|
165
|
+
rebuildAll(): void;
|
|
166
|
+
/** Empty an FTS index (the `'delete-all'` command) — pair with `rebuild` for bulk loads. */
|
|
167
|
+
clear(table: string): void;
|
|
168
|
+
/** Merge the index's b-tree segments (the `'optimize'` command) for faster queries. */
|
|
169
|
+
optimize(table: string): void;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function snippetExpr(table: string, o: FtsSnippetOpts): string {
|
|
173
|
+
const col = int(o.column ?? -1, -1);
|
|
174
|
+
const start = lit(o.startMatch ?? '<b>');
|
|
175
|
+
const end = lit(o.endMatch ?? '</b>');
|
|
176
|
+
const ell = lit(o.ellipsis ?? '…');
|
|
177
|
+
const tokens = Math.min(64, Math.max(1, int(o.maxTokens ?? 16, 16)));
|
|
178
|
+
return `snippet(${q(table)}, ${col}, ${start}, ${end}, ${ell}, ${tokens}) AS snippet`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function highlightExpr(table: string, o: FtsHighlightOpts): string {
|
|
182
|
+
const col = Math.max(0, int(o.column, 0));
|
|
183
|
+
const start = lit(o.startMatch ?? '<b>');
|
|
184
|
+
const end = lit(o.endMatch ?? '</b>');
|
|
185
|
+
return `highlight(${q(table)}, ${col}, ${start}, ${end}) AS highlight`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Build a ranked-search facade over the FTS5 tables named in `opts.tables` (the set
|
|
190
|
+
* `rebuildAll()` iterates). The `db` handle is the same one the app uses — one
|
|
191
|
+
* connection, one WAL.
|
|
192
|
+
*/
|
|
193
|
+
export function createFts5Search(db: Database, opts: { tables?: string[] } = {}): Fts5Search {
|
|
194
|
+
const tables = (opts.tables ?? []).map((t) => assertIdent(t, 'fts table name'));
|
|
195
|
+
|
|
196
|
+
const facade: Fts5Search = {
|
|
197
|
+
search(o) {
|
|
198
|
+
const match = sanitizeFtsQuery(o.query);
|
|
199
|
+
if (!match) return [];
|
|
200
|
+
const table = assertIdent(o.table, 'fts table name');
|
|
201
|
+
|
|
202
|
+
const projection = ['rowid', 'rank'];
|
|
203
|
+
if (o.snippet) projection.push(snippetExpr(table, o.snippet));
|
|
204
|
+
if (o.highlight) projection.push(highlightExpr(table, o.highlight));
|
|
205
|
+
|
|
206
|
+
const sql = `SELECT ${projection.join(', ')} FROM ${q(table)} WHERE ${q(table)} MATCH ? ORDER BY rank LIMIT ? OFFSET ?`;
|
|
207
|
+
return db
|
|
208
|
+
.query<FtsResult, [string, number, number]>(sql)
|
|
209
|
+
.all(match, Math.max(0, int(o.limit ?? 20, 20)), Math.max(0, int(o.offset ?? 0, 0)));
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
rebuild(table) {
|
|
213
|
+
const t = assertIdent(table, 'fts table name');
|
|
214
|
+
db.run(`INSERT INTO ${q(t)}(${q(t)}) VALUES('rebuild')`);
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
rebuildAll() {
|
|
218
|
+
for (const t of tables) facade.rebuild(t);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
clear(table) {
|
|
222
|
+
const t = assertIdent(table, 'fts table name');
|
|
223
|
+
db.run(`INSERT INTO ${q(t)}(${q(t)}) VALUES('delete-all')`);
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
optimize(table) {
|
|
227
|
+
const t = assertIdent(table, 'fts table name');
|
|
228
|
+
db.run(`INSERT INTO ${q(t)}(${q(t)}) VALUES('optimize')`);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return facade;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── Query sanitization ─────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Convert arbitrary user input into a safe FTS5 MATCH expression: every run of
|
|
239
|
+
* letters/digits/underscore becomes a quoted prefix token (`"term"*`), joined by
|
|
240
|
+
* spaces (FTS5's implicit AND). All FTS5 operators (`" * : ^ ( ) - + NEAR …`) are
|
|
241
|
+
* dropped with the surrounding punctuation, so the query can neither inject syntax
|
|
242
|
+
* nor raise a parse error. Returns `''` when nothing searchable remains.
|
|
243
|
+
*
|
|
244
|
+
* Examples: `hello` → `"hello"*`; `hello world` → `"hello"* "world"*` (both must
|
|
245
|
+
* match); `DROP TABLE` → `"DROP"* "TABLE"*` (a literal search, harmless).
|
|
246
|
+
*/
|
|
247
|
+
export function sanitizeFtsQuery(raw: string, opts: { mode?: 'all' | 'any' } = {}): string {
|
|
248
|
+
const tokens = raw.match(/[\p{L}\p{N}_]+/gu);
|
|
249
|
+
if (!tokens || tokens.length === 0) return '';
|
|
250
|
+
// 'all' (default) = AND-of-prefix — precision, right for search-as-you-type.
|
|
251
|
+
// 'any' = OR-of-prefix — recall, right for retrieval from a natural-language
|
|
252
|
+
// question where requiring every word ("what do I have about…") matches nothing;
|
|
253
|
+
// bm25 still ranks docs hitting more/rarer terms first.
|
|
254
|
+
return tokens.map((t) => `"${t}"*`).join(opts.mode === 'any' ? ' OR ' : ' ');
|
|
255
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createSqliteHealthChecks } from './health';
|
|
7
|
+
|
|
8
|
+
const find = (db: Database, opts: Parameters<typeof createSqliteHealthChecks>[1], name: string) =>
|
|
9
|
+
createSqliteHealthChecks(db, opts).find((c) => c.name === name);
|
|
10
|
+
|
|
11
|
+
describe('createSqliteHealthChecks', () => {
|
|
12
|
+
const dirs: string[] = [];
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
const tmp = () => {
|
|
17
|
+
const d = mkdtempSync(join(tmpdir(), 'cb-health-'));
|
|
18
|
+
dirs.push(d);
|
|
19
|
+
return d;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
test('exposes the three named probes', () => {
|
|
23
|
+
expect(createSqliteHealthChecks(new Database(':memory:'), {}).map((c) => c.name)).toEqual([
|
|
24
|
+
'sqlite:integrity',
|
|
25
|
+
'sqlite:size',
|
|
26
|
+
'sqlite:wal',
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('integrity is ok on a healthy DB', async () => {
|
|
31
|
+
const db = new Database(':memory:');
|
|
32
|
+
db.run('CREATE TABLE t (id INTEGER PRIMARY KEY)');
|
|
33
|
+
const result = await find(db, {}, 'sqlite:integrity')!.check();
|
|
34
|
+
expect(result.status).toBe('ok');
|
|
35
|
+
expect(result.message).toBe('ok');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('size reports ok below the threshold and warn above it', async () => {
|
|
39
|
+
const db = new Database(':memory:');
|
|
40
|
+
db.run('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)');
|
|
41
|
+
|
|
42
|
+
const ok = await find(db, {}, 'sqlite:size')!.check();
|
|
43
|
+
expect(ok.status).toBe('ok');
|
|
44
|
+
expect(ok.message).toMatch(/MB$/);
|
|
45
|
+
|
|
46
|
+
const warn = await find(db, { maxSizeBytes: 0 }, 'sqlite:size')!.check();
|
|
47
|
+
expect(warn.status).toBe('warn');
|
|
48
|
+
expect(warn.message).toContain('threshold');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('wal is ok on a non-WAL (:memory:) DB', async () => {
|
|
52
|
+
const result = await find(new Database(':memory:'), {}, 'sqlite:wal')!.check();
|
|
53
|
+
expect(result.status).toBe('ok');
|
|
54
|
+
expect(result.message).toBe('0 WAL pages');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('wal warns when uncheckpointed pages exceed the threshold', async () => {
|
|
58
|
+
const db = new Database(join(tmp(), 'wal.sqlite'), { create: true });
|
|
59
|
+
db.run('PRAGMA journal_mode = WAL');
|
|
60
|
+
db.run('PRAGMA wal_autocheckpoint = 100000'); // keep frames in the WAL
|
|
61
|
+
db.run('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)');
|
|
62
|
+
for (let i = 0; i < 300; i++) db.run('INSERT INTO t (v) VALUES (?)', [`row-${i}`]);
|
|
63
|
+
|
|
64
|
+
const result = await find(db, { maxWalPages: 0 }, 'sqlite:wal')!.check();
|
|
65
|
+
expect(result.status).toBe('warn');
|
|
66
|
+
expect(result.message).toMatch(/WAL has \d+ pages/);
|
|
67
|
+
db.close();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a check never throws — corrupt-page detail surfaces as an error status', async () => {
|
|
71
|
+
// quick_check on a valid DB returns 'ok'; this asserts the contract shape: status
|
|
72
|
+
// is always one of ok/warn/error and check() resolves rather than rejecting.
|
|
73
|
+
const checks = createSqliteHealthChecks(new Database(':memory:'), {});
|
|
74
|
+
for (const c of checks) {
|
|
75
|
+
const r = await c.check();
|
|
76
|
+
expect(['ok', 'warn', 'error']).toContain(r.status);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DB-layer health probes for `bun:sqlite`. Each is a small, self-describing descriptor
|
|
5
|
+
* — `name` + `description` + an async `check()` returning a status — so it can be
|
|
6
|
+
* rendered in an operator console and drive readiness from one source.
|
|
7
|
+
*
|
|
8
|
+
* The shape is intentionally dependency-free (it does NOT import cwip's health types):
|
|
9
|
+
* apps adapt a descriptor into cwip's `HealthCheck` registry with a trivial map, e.g.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* registry.register(...createSqliteHealthChecks(db, {}).map((p) => async () => {
|
|
13
|
+
* const r = await p.check();
|
|
14
|
+
* return { id: p.name, title: p.description, category: 'Database',
|
|
15
|
+
* severity: 'warn', status: r.status, detail: r.message ?? '', remediation: [] };
|
|
16
|
+
* }));
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* `status` values (`ok`/`warn`/`error`) already line up with cwip's `HealthStatus`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type SqliteHealthStatus = 'ok' | 'warn' | 'error';
|
|
23
|
+
|
|
24
|
+
export interface SqliteHealthResult {
|
|
25
|
+
status: SqliteHealthStatus;
|
|
26
|
+
/** Human-readable detail for the current status. */
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SqliteHealthCheck {
|
|
31
|
+
/** Stable id, e.g. `'sqlite:integrity'`. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Short human description. */
|
|
34
|
+
description: string;
|
|
35
|
+
check(): Promise<SqliteHealthResult>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SqliteHealthOpts {
|
|
39
|
+
/** Warn when the DB file exceeds this many bytes. Default: 500 MB. */
|
|
40
|
+
maxSizeBytes?: number;
|
|
41
|
+
/** Warn when the WAL holds more than this many pages. Default: 1000. */
|
|
42
|
+
maxWalPages?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const BYTES_PER_MB = 1_048_576;
|
|
46
|
+
const mb = (bytes: number): string => `${(bytes / BYTES_PER_MB).toFixed(1)}MB`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build the SQLite health probes for `db`. Use `quick_check` (fast, still catches
|
|
50
|
+
* corruption) in the loop; expose the exhaustive `integrity_check` as an
|
|
51
|
+
* admin-triggered action elsewhere.
|
|
52
|
+
*/
|
|
53
|
+
export function createSqliteHealthChecks(
|
|
54
|
+
db: Database,
|
|
55
|
+
opts: SqliteHealthOpts = {},
|
|
56
|
+
): SqliteHealthCheck[] {
|
|
57
|
+
const maxSizeBytes = opts.maxSizeBytes ?? 500 * BYTES_PER_MB;
|
|
58
|
+
const maxWalPages = opts.maxWalPages ?? 1000;
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
name: 'sqlite:integrity',
|
|
63
|
+
description: 'SQLite quick_check integrity',
|
|
64
|
+
async check() {
|
|
65
|
+
const row = db.query<Record<string, string>, []>('PRAGMA quick_check').get();
|
|
66
|
+
const outcome = row ? Object.values(row)[0] : undefined;
|
|
67
|
+
return outcome === 'ok'
|
|
68
|
+
? { status: 'ok', message: 'ok' }
|
|
69
|
+
: { status: 'error', message: outcome ?? 'unknown' };
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'sqlite:size',
|
|
74
|
+
description: 'DB file size within threshold',
|
|
75
|
+
async check() {
|
|
76
|
+
const info = db
|
|
77
|
+
.query<{ page_count: number; page_size: number }, []>(
|
|
78
|
+
'SELECT page_count, page_size FROM pragma_page_count(), pragma_page_size()',
|
|
79
|
+
)
|
|
80
|
+
.get();
|
|
81
|
+
const bytes = (info?.page_count ?? 0) * (info?.page_size ?? 4096);
|
|
82
|
+
return bytes > maxSizeBytes
|
|
83
|
+
? { status: 'warn', message: `DB is ${mb(bytes)} (threshold: ${mb(maxSizeBytes)})` }
|
|
84
|
+
: { status: 'ok', message: mb(bytes) };
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'sqlite:wal',
|
|
89
|
+
description: 'WAL checkpoint lag',
|
|
90
|
+
async check() {
|
|
91
|
+
// PRAGMA wal_checkpoint returns one row {busy, log, checkpointed}; `log` is the
|
|
92
|
+
// total WAL pages, or -1 when the DB is not in WAL mode (treat as 0).
|
|
93
|
+
const row = db
|
|
94
|
+
.query<{ busy: number; log: number; checkpointed: number }, []>(
|
|
95
|
+
'PRAGMA wal_checkpoint(PASSIVE)',
|
|
96
|
+
)
|
|
97
|
+
.get();
|
|
98
|
+
const pages = row && row.log > 0 ? row.log : 0;
|
|
99
|
+
return pages > maxWalPages
|
|
100
|
+
? { status: 'warn', message: `WAL has ${pages} pages (threshold: ${maxWalPages})` }
|
|
101
|
+
: { status: 'ok', message: `${pages} WAL pages` };
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|