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,484 @@
|
|
|
1
|
+
import { levelsMeeting, normalizeSubjectId, strongerAccess, } from "cursedbelt-core/sharing/model";
|
|
2
|
+
import { runMigrations } from "../migration/runner";
|
|
3
|
+
import { SHARE_EVENTS_TABLE, SHARE_GRANTS_TABLE, SHARE_GROUPS_TABLE, SHARE_GROUP_MEMBERS_TABLE, SHARE_MIGRATIONS, } from "./migrations";
|
|
4
|
+
import { sharedResourceSql } from "./sql";
|
|
5
|
+
/** Shorthand for the common subject. `group(id)` is its sibling. */
|
|
6
|
+
export const user = (id) => ({ type: "user", id });
|
|
7
|
+
export const group = (id) => ({ type: "group", id });
|
|
8
|
+
const toGrant = (row) => ({
|
|
9
|
+
resource: { app: row.app, kind: row.kind, id: row.resource_id },
|
|
10
|
+
subject: { type: row.subject_type, id: row.subject_id },
|
|
11
|
+
level: row.level,
|
|
12
|
+
grantedBy: row.granted_by,
|
|
13
|
+
grantedAt: row.granted_at,
|
|
14
|
+
note: row.note,
|
|
15
|
+
});
|
|
16
|
+
const toEvent = (row) => ({
|
|
17
|
+
id: row.event_id,
|
|
18
|
+
at: row.at,
|
|
19
|
+
action: row.action,
|
|
20
|
+
resource: row.app === null || row.kind === null || row.resource_id === null
|
|
21
|
+
? null
|
|
22
|
+
: { app: row.app, kind: row.kind, id: row.resource_id },
|
|
23
|
+
subject: { type: row.subject_type, id: row.subject_id },
|
|
24
|
+
level: row.level,
|
|
25
|
+
previousLevel: row.previous_level,
|
|
26
|
+
actor: row.actor,
|
|
27
|
+
note: row.note,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Open (and migrate) the sharing tables in this database, returning the store.
|
|
31
|
+
*
|
|
32
|
+
* Idempotent: safe to call once per boot per `db`, and safe to call in a database
|
|
33
|
+
* that already has the tables (the migration runner records what it applied).
|
|
34
|
+
*/
|
|
35
|
+
export function createShareStore(options) {
|
|
36
|
+
const { db, app } = options;
|
|
37
|
+
const clock = options.now ?? (() => new Date().toISOString());
|
|
38
|
+
if (!app.trim())
|
|
39
|
+
throw new Error("[cursedbelt-core/sharing] a share store needs an `app`");
|
|
40
|
+
runMigrations(db, SHARE_MIGRATIONS);
|
|
41
|
+
/** Collected inside a transaction, delivered after it commits — see property 4
|
|
42
|
+
* in the file header. */
|
|
43
|
+
const emit = (events) => {
|
|
44
|
+
const hook = options.onEvent;
|
|
45
|
+
if (!hook)
|
|
46
|
+
return;
|
|
47
|
+
for (const event of events) {
|
|
48
|
+
try {
|
|
49
|
+
hook(event);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.warn(`[cursedbelt-core/sharing] a share-event listener threw: ${String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const recordEvent = (input) => {
|
|
57
|
+
db.query(`INSERT INTO ${SHARE_EVENTS_TABLE}
|
|
58
|
+
(at, action, app, kind, resource_id, subject_type, subject_id, level, previous_level, actor, note)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.at, input.action, input.resource?.app ?? null, input.resource?.kind ?? null, input.resource?.id ?? null, input.subject.type, input.subject.id, input.level, input.previousLevel ?? null, input.actor, input.note ?? "");
|
|
60
|
+
const id = db.query("SELECT last_insert_rowid() AS id").get().id;
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
at: input.at,
|
|
64
|
+
action: input.action,
|
|
65
|
+
resource: input.resource,
|
|
66
|
+
subject: input.subject,
|
|
67
|
+
level: input.level,
|
|
68
|
+
previousLevel: input.previousLevel ?? null,
|
|
69
|
+
actor: input.actor,
|
|
70
|
+
note: input.note ?? "",
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const normalized = (subject) => ({
|
|
74
|
+
type: subject.type,
|
|
75
|
+
id: normalizeSubjectId(subject.type, subject.id),
|
|
76
|
+
});
|
|
77
|
+
const readGrant = (target, subject) => {
|
|
78
|
+
const row = db
|
|
79
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE}
|
|
80
|
+
WHERE app = ? AND kind = ? AND resource_id = ? AND subject_type = ? AND subject_id = ?`)
|
|
81
|
+
.get(app, target.kind, target.id, subject.type, subject.id);
|
|
82
|
+
return row ? toGrant(row) : null;
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
/** The app this store speaks for — exposed so a consumer can build its own
|
|
86
|
+
* `sharedResourceSql` call without re-declaring the name. */
|
|
87
|
+
app,
|
|
88
|
+
/**
|
|
89
|
+
* Give `subject` access to a resource, or change the access they have.
|
|
90
|
+
*
|
|
91
|
+
* An unchanged re-grant is still recorded as a `grant` event: "the owner
|
|
92
|
+
* confirmed this on Tuesday" is a fact somebody reading the trail wants, and
|
|
93
|
+
* suppressing it would make an idempotent API silently unauditable.
|
|
94
|
+
*/
|
|
95
|
+
grant(input) {
|
|
96
|
+
const subject = normalized(input.subject);
|
|
97
|
+
if (!subject.id)
|
|
98
|
+
throw new Error("[cursedbelt-core/sharing] a grant needs a subject");
|
|
99
|
+
if (!input.id)
|
|
100
|
+
throw new Error("[cursedbelt-core/sharing] a grant needs a resource id");
|
|
101
|
+
const at = clock();
|
|
102
|
+
const events = [];
|
|
103
|
+
const grant = db.transaction(() => {
|
|
104
|
+
const existing = readGrant(input, subject);
|
|
105
|
+
db.query(`INSERT INTO ${SHARE_GRANTS_TABLE}
|
|
106
|
+
(app, kind, resource_id, subject_type, subject_id, level, granted_by, granted_at, note)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
ON CONFLICT (app, kind, resource_id, subject_type, subject_id)
|
|
109
|
+
DO UPDATE SET level = excluded.level, granted_by = excluded.granted_by,
|
|
110
|
+
granted_at = excluded.granted_at, note = excluded.note`).run(app, input.kind, input.id, subject.type, subject.id, input.level, input.grantedBy, at, input.note ?? "");
|
|
111
|
+
const resource = { app, kind: input.kind, id: input.id };
|
|
112
|
+
events.push(recordEvent({
|
|
113
|
+
action: existing && existing.level !== input.level ? "regrade" : "grant",
|
|
114
|
+
resource,
|
|
115
|
+
subject,
|
|
116
|
+
level: input.level,
|
|
117
|
+
previousLevel: existing && existing.level !== input.level ? existing.level : null,
|
|
118
|
+
actor: input.grantedBy,
|
|
119
|
+
note: input.note,
|
|
120
|
+
at,
|
|
121
|
+
}));
|
|
122
|
+
return {
|
|
123
|
+
resource,
|
|
124
|
+
subject,
|
|
125
|
+
level: input.level,
|
|
126
|
+
grantedBy: input.grantedBy,
|
|
127
|
+
grantedAt: at,
|
|
128
|
+
note: input.note ?? "",
|
|
129
|
+
};
|
|
130
|
+
})();
|
|
131
|
+
emit(events);
|
|
132
|
+
return grant;
|
|
133
|
+
},
|
|
134
|
+
/** Take access away. Returns the level that was removed, or null when there
|
|
135
|
+
* was nothing to remove — in which case nothing is logged, because an audit
|
|
136
|
+
* trail full of "revoked nothing" is one nobody reads. */
|
|
137
|
+
revoke(input) {
|
|
138
|
+
const subject = normalized(input.subject);
|
|
139
|
+
const at = clock();
|
|
140
|
+
const events = [];
|
|
141
|
+
const removed = db.transaction(() => {
|
|
142
|
+
const existing = readGrant(input, subject);
|
|
143
|
+
if (!existing)
|
|
144
|
+
return null;
|
|
145
|
+
db.query(`DELETE FROM ${SHARE_GRANTS_TABLE}
|
|
146
|
+
WHERE app = ? AND kind = ? AND resource_id = ? AND subject_type = ? AND subject_id = ?`).run(app, input.kind, input.id, subject.type, subject.id);
|
|
147
|
+
events.push(recordEvent({
|
|
148
|
+
action: "revoke",
|
|
149
|
+
resource: { app, kind: input.kind, id: input.id },
|
|
150
|
+
subject,
|
|
151
|
+
level: existing.level,
|
|
152
|
+
actor: input.revokedBy,
|
|
153
|
+
note: input.note,
|
|
154
|
+
at,
|
|
155
|
+
}));
|
|
156
|
+
return existing.level;
|
|
157
|
+
})();
|
|
158
|
+
emit(events);
|
|
159
|
+
return removed;
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* Revoke EVERY grant on one resource — what an app calls when it deletes the
|
|
163
|
+
* record. Returns how many were removed.
|
|
164
|
+
*
|
|
165
|
+
* 🔴 Not optional housekeeping. Resource ids are app-minted, and a grant left
|
|
166
|
+
* behind for a deleted id becomes access to whatever is created with that id
|
|
167
|
+
* later. `apps/family` calls this in the same transaction as deleting a
|
|
168
|
+
* person.
|
|
169
|
+
*/
|
|
170
|
+
revokeResource(target, revokedBy) {
|
|
171
|
+
const at = clock();
|
|
172
|
+
const events = [];
|
|
173
|
+
const count = db.transaction(() => {
|
|
174
|
+
const rows = db
|
|
175
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND kind = ? AND resource_id = ?`)
|
|
176
|
+
.all(app, target.kind, target.id);
|
|
177
|
+
if (rows.length === 0)
|
|
178
|
+
return 0;
|
|
179
|
+
db.query(`DELETE FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND kind = ? AND resource_id = ?`).run(app, target.kind, target.id);
|
|
180
|
+
for (const row of rows) {
|
|
181
|
+
events.push(recordEvent({
|
|
182
|
+
action: "revoke",
|
|
183
|
+
resource: { app, kind: row.kind, id: row.resource_id },
|
|
184
|
+
subject: { type: row.subject_type, id: row.subject_id },
|
|
185
|
+
level: row.level,
|
|
186
|
+
actor: revokedBy,
|
|
187
|
+
note: "the record was deleted",
|
|
188
|
+
at,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
return rows.length;
|
|
192
|
+
})();
|
|
193
|
+
emit(events);
|
|
194
|
+
return count;
|
|
195
|
+
},
|
|
196
|
+
/** Revoke everything shared with one subject, across every kind — the
|
|
197
|
+
* offboarding call. A person losing their fleet account keeps nothing. */
|
|
198
|
+
revokeSubject(subject, revokedBy) {
|
|
199
|
+
const normal = normalized(subject);
|
|
200
|
+
const at = clock();
|
|
201
|
+
const events = [];
|
|
202
|
+
const count = db.transaction(() => {
|
|
203
|
+
const rows = db
|
|
204
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND subject_type = ? AND subject_id = ?`)
|
|
205
|
+
.all(app, normal.type, normal.id);
|
|
206
|
+
if (rows.length === 0)
|
|
207
|
+
return 0;
|
|
208
|
+
db.query(`DELETE FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND subject_type = ? AND subject_id = ?`).run(app, normal.type, normal.id);
|
|
209
|
+
for (const row of rows) {
|
|
210
|
+
events.push(recordEvent({
|
|
211
|
+
action: "revoke",
|
|
212
|
+
resource: { app, kind: row.kind, id: row.resource_id },
|
|
213
|
+
subject: normal,
|
|
214
|
+
level: row.level,
|
|
215
|
+
actor: revokedBy,
|
|
216
|
+
note: "every share with this subject was revoked",
|
|
217
|
+
at,
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
return rows.length;
|
|
221
|
+
})();
|
|
222
|
+
emit(events);
|
|
223
|
+
return count;
|
|
224
|
+
},
|
|
225
|
+
/** Who can see this record, strongest first then oldest — the panel's read. */
|
|
226
|
+
listForResource(target) {
|
|
227
|
+
const rows = db
|
|
228
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE}
|
|
229
|
+
WHERE app = ? AND kind = ? AND resource_id = ?
|
|
230
|
+
-- Strongest first, then oldest. Spelled as a CASE rather than an
|
|
231
|
+
-- ORDER BY on level, which happens to work only because 'write'
|
|
232
|
+
-- sorts after 'read' and would silently mis-order a third level.
|
|
233
|
+
ORDER BY CASE level WHEN 'write' THEN 0 ELSE 1 END, granted_at ASC`)
|
|
234
|
+
.all(app, target.kind, target.id);
|
|
235
|
+
return rows.map(toGrant);
|
|
236
|
+
},
|
|
237
|
+
/** Everything shared with one subject — "shared with you". Group grants are
|
|
238
|
+
* NOT expanded here; use {@link resourceIdsFor} for the resolved answer. */
|
|
239
|
+
listForSubject(subject) {
|
|
240
|
+
const normal = normalized(subject);
|
|
241
|
+
const rows = db
|
|
242
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE}
|
|
243
|
+
WHERE app = ? AND subject_type = ? AND subject_id = ?
|
|
244
|
+
ORDER BY granted_at DESC`)
|
|
245
|
+
.all(app, normal.type, normal.id);
|
|
246
|
+
return rows.map(toGrant);
|
|
247
|
+
},
|
|
248
|
+
/**
|
|
249
|
+
* What may this viewer do with this resource — the resolver.
|
|
250
|
+
*
|
|
251
|
+
* The strongest of their own grant and every group they are in, computed in
|
|
252
|
+
* SQL. `resolveAccess` in `cursedbelt-core/sharing` is the same rule written as a
|
|
253
|
+
* pure function, and `store.spec.ts` checks the two agree.
|
|
254
|
+
*/
|
|
255
|
+
accessFor(target, viewer) {
|
|
256
|
+
const subjectId = normalizeSubjectId("user", viewer.subjectId);
|
|
257
|
+
if (!subjectId)
|
|
258
|
+
return "none";
|
|
259
|
+
const rows = db
|
|
260
|
+
.query(`SELECT level FROM ${SHARE_GRANTS_TABLE}
|
|
261
|
+
WHERE app = ? AND kind = ? AND resource_id = ?
|
|
262
|
+
AND ( (subject_type = 'user' AND subject_id = ?)
|
|
263
|
+
OR (subject_type = 'group' AND subject_id IN
|
|
264
|
+
(SELECT group_id FROM ${SHARE_GROUP_MEMBERS_TABLE} WHERE subject_id = ?)) )`)
|
|
265
|
+
.all(app, target.kind, target.id, subjectId, subjectId);
|
|
266
|
+
let access = "none";
|
|
267
|
+
for (const row of rows)
|
|
268
|
+
access = strongerAccess(access, row.level);
|
|
269
|
+
return access;
|
|
270
|
+
},
|
|
271
|
+
/** Every resource of `kind` this viewer reaches at `atLeast` or above.
|
|
272
|
+
* For filtering rows in the app's OWN query, prefer `sharedResourceSql`. */
|
|
273
|
+
resourceIdsFor(kind, viewer, atLeast = "read") {
|
|
274
|
+
const subjectId = normalizeSubjectId("user", viewer.subjectId);
|
|
275
|
+
if (!subjectId)
|
|
276
|
+
return [];
|
|
277
|
+
const levels = levelsMeeting(atLeast);
|
|
278
|
+
const rows = db
|
|
279
|
+
.query(`SELECT DISTINCT resource_id FROM ${SHARE_GRANTS_TABLE}
|
|
280
|
+
WHERE app = ? AND kind = ? AND level IN (${levels.map(() => "?").join(", ")})
|
|
281
|
+
AND ( (subject_type = 'user' AND subject_id = ?)
|
|
282
|
+
OR (subject_type = 'group' AND subject_id IN
|
|
283
|
+
(SELECT group_id FROM ${SHARE_GROUP_MEMBERS_TABLE} WHERE subject_id = ?)) )
|
|
284
|
+
ORDER BY resource_id`)
|
|
285
|
+
.all(app, kind, ...levels, subjectId, subjectId);
|
|
286
|
+
return rows.map((row) => row.resource_id);
|
|
287
|
+
},
|
|
288
|
+
/** The WHERE-clause half, bound to this store's app. See `./sql.ts` for why
|
|
289
|
+
* a fragment rather than a list of ids. */
|
|
290
|
+
sharedSql(input) {
|
|
291
|
+
return sharedResourceSql({ app, ...input });
|
|
292
|
+
},
|
|
293
|
+
// ── Groups ───────────────────────────────────────────────────────────────
|
|
294
|
+
createGroup(input) {
|
|
295
|
+
const id = normalizeSubjectId("group", input.id);
|
|
296
|
+
if (!id)
|
|
297
|
+
throw new Error("[cursedbelt-core/sharing] a group needs an id");
|
|
298
|
+
const at = clock();
|
|
299
|
+
const events = [];
|
|
300
|
+
const created = db.transaction(() => {
|
|
301
|
+
db.query(`INSERT INTO ${SHARE_GROUPS_TABLE} (group_id, name, created_by, created_at, note)
|
|
302
|
+
VALUES (?, ?, ?, ?, ?)
|
|
303
|
+
ON CONFLICT (group_id) DO UPDATE SET name = excluded.name, note = excluded.note`).run(id, input.name, input.createdBy, at, input.note ?? "");
|
|
304
|
+
events.push(recordEvent({
|
|
305
|
+
action: "group.create",
|
|
306
|
+
resource: null,
|
|
307
|
+
subject: { type: "group", id },
|
|
308
|
+
level: null,
|
|
309
|
+
actor: input.createdBy,
|
|
310
|
+
note: input.name,
|
|
311
|
+
at,
|
|
312
|
+
}));
|
|
313
|
+
return {
|
|
314
|
+
id,
|
|
315
|
+
name: input.name,
|
|
316
|
+
createdBy: input.createdBy,
|
|
317
|
+
createdAt: at,
|
|
318
|
+
note: input.note ?? "",
|
|
319
|
+
};
|
|
320
|
+
})();
|
|
321
|
+
emit(events);
|
|
322
|
+
return created;
|
|
323
|
+
},
|
|
324
|
+
renameGroup(id, name, actor) {
|
|
325
|
+
const groupId = normalizeSubjectId("group", id);
|
|
326
|
+
const at = clock();
|
|
327
|
+
const events = [];
|
|
328
|
+
db.transaction(() => {
|
|
329
|
+
db.query(`UPDATE ${SHARE_GROUPS_TABLE} SET name = ? WHERE group_id = ?`).run(name, groupId);
|
|
330
|
+
events.push(recordEvent({
|
|
331
|
+
action: "group.rename",
|
|
332
|
+
resource: null,
|
|
333
|
+
subject: { type: "group", id: groupId },
|
|
334
|
+
level: null,
|
|
335
|
+
actor,
|
|
336
|
+
note: name,
|
|
337
|
+
at,
|
|
338
|
+
}));
|
|
339
|
+
})();
|
|
340
|
+
emit(events);
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* Delete a group — and every grant made TO it.
|
|
344
|
+
*
|
|
345
|
+
* 🔴 The grants go with it, explicitly, each with its own revoke event. The
|
|
346
|
+
* `ON DELETE CASCADE` on membership does not reach `share_grants` (a group
|
|
347
|
+
* subject is a string, not a foreign key, because the same column holds user
|
|
348
|
+
* addresses), so a group deleted without this leaves rows nobody can see in
|
|
349
|
+
* any UI and nothing can revoke.
|
|
350
|
+
*/
|
|
351
|
+
deleteGroup(id, actor) {
|
|
352
|
+
const groupId = normalizeSubjectId("group", id);
|
|
353
|
+
const at = clock();
|
|
354
|
+
const events = [];
|
|
355
|
+
db.transaction(() => {
|
|
356
|
+
const grants = db
|
|
357
|
+
.query(`SELECT * FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND subject_type = 'group' AND subject_id = ?`)
|
|
358
|
+
.all(app, groupId);
|
|
359
|
+
db.query(`DELETE FROM ${SHARE_GRANTS_TABLE} WHERE app = ? AND subject_type = 'group' AND subject_id = ?`).run(app, groupId);
|
|
360
|
+
for (const row of grants) {
|
|
361
|
+
events.push(recordEvent({
|
|
362
|
+
action: "revoke",
|
|
363
|
+
resource: { app, kind: row.kind, id: row.resource_id },
|
|
364
|
+
subject: { type: "group", id: groupId },
|
|
365
|
+
level: row.level,
|
|
366
|
+
actor,
|
|
367
|
+
note: "the group was deleted",
|
|
368
|
+
at,
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
db.query(`DELETE FROM ${SHARE_GROUPS_TABLE} WHERE group_id = ?`).run(groupId);
|
|
372
|
+
events.push(recordEvent({
|
|
373
|
+
action: "group.delete",
|
|
374
|
+
resource: null,
|
|
375
|
+
subject: { type: "group", id: groupId },
|
|
376
|
+
level: null,
|
|
377
|
+
actor,
|
|
378
|
+
note: "",
|
|
379
|
+
at,
|
|
380
|
+
}));
|
|
381
|
+
})();
|
|
382
|
+
emit(events);
|
|
383
|
+
},
|
|
384
|
+
listGroups() {
|
|
385
|
+
const rows = db
|
|
386
|
+
.query(`SELECT * FROM ${SHARE_GROUPS_TABLE} ORDER BY name`)
|
|
387
|
+
.all();
|
|
388
|
+
return rows.map((row) => ({
|
|
389
|
+
id: row.group_id,
|
|
390
|
+
name: row.name,
|
|
391
|
+
createdBy: row.created_by,
|
|
392
|
+
createdAt: row.created_at,
|
|
393
|
+
note: row.note,
|
|
394
|
+
}));
|
|
395
|
+
},
|
|
396
|
+
addGroupMember(groupId, subjectId, actor) {
|
|
397
|
+
const id = normalizeSubjectId("group", groupId);
|
|
398
|
+
const member = normalizeSubjectId("user", subjectId);
|
|
399
|
+
if (!member)
|
|
400
|
+
throw new Error("[cursedbelt-core/sharing] a group member needs an address");
|
|
401
|
+
const at = clock();
|
|
402
|
+
const events = [];
|
|
403
|
+
db.transaction(() => {
|
|
404
|
+
db.query(`INSERT INTO ${SHARE_GROUP_MEMBERS_TABLE} (group_id, subject_id, added_by, added_at)
|
|
405
|
+
VALUES (?, ?, ?, ?) ON CONFLICT (group_id, subject_id) DO NOTHING`).run(id, member, actor, at);
|
|
406
|
+
events.push(recordEvent({
|
|
407
|
+
action: "group.join",
|
|
408
|
+
resource: null,
|
|
409
|
+
subject: { type: "group", id },
|
|
410
|
+
level: null,
|
|
411
|
+
actor,
|
|
412
|
+
note: member,
|
|
413
|
+
at,
|
|
414
|
+
}));
|
|
415
|
+
})();
|
|
416
|
+
emit(events);
|
|
417
|
+
},
|
|
418
|
+
removeGroupMember(groupId, subjectId, actor) {
|
|
419
|
+
const id = normalizeSubjectId("group", groupId);
|
|
420
|
+
const member = normalizeSubjectId("user", subjectId);
|
|
421
|
+
const at = clock();
|
|
422
|
+
const events = [];
|
|
423
|
+
db.transaction(() => {
|
|
424
|
+
db.query(`DELETE FROM ${SHARE_GROUP_MEMBERS_TABLE} WHERE group_id = ? AND subject_id = ?`).run(id, member);
|
|
425
|
+
events.push(recordEvent({
|
|
426
|
+
action: "group.leave",
|
|
427
|
+
resource: null,
|
|
428
|
+
subject: { type: "group", id },
|
|
429
|
+
level: null,
|
|
430
|
+
actor,
|
|
431
|
+
note: member,
|
|
432
|
+
at,
|
|
433
|
+
}));
|
|
434
|
+
})();
|
|
435
|
+
emit(events);
|
|
436
|
+
},
|
|
437
|
+
listGroupMembers(groupId) {
|
|
438
|
+
const rows = db
|
|
439
|
+
.query(`SELECT * FROM ${SHARE_GROUP_MEMBERS_TABLE} WHERE group_id = ? ORDER BY subject_id`)
|
|
440
|
+
.all(normalizeSubjectId("group", groupId));
|
|
441
|
+
return rows.map((row) => ({
|
|
442
|
+
groupId: row.group_id,
|
|
443
|
+
subjectId: row.subject_id,
|
|
444
|
+
addedBy: row.added_by,
|
|
445
|
+
addedAt: row.added_at,
|
|
446
|
+
}));
|
|
447
|
+
},
|
|
448
|
+
/** Which groups is this person in — the one place membership is resolved. */
|
|
449
|
+
groupIdsFor(subjectId) {
|
|
450
|
+
const member = normalizeSubjectId("user", subjectId);
|
|
451
|
+
if (!member)
|
|
452
|
+
return [];
|
|
453
|
+
const rows = db
|
|
454
|
+
.query(`SELECT group_id FROM ${SHARE_GROUP_MEMBERS_TABLE} WHERE subject_id = ? ORDER BY group_id`)
|
|
455
|
+
.all(member);
|
|
456
|
+
return rows.map((row) => row.group_id);
|
|
457
|
+
},
|
|
458
|
+
// ── The audit trail ──────────────────────────────────────────────────────
|
|
459
|
+
/** The trail, newest first. Filter by resource, by subject, or neither. */
|
|
460
|
+
events(query = {}) {
|
|
461
|
+
const clauses = ["app IS NULL OR app = ?"];
|
|
462
|
+
const params = [app];
|
|
463
|
+
if (query.kind !== undefined) {
|
|
464
|
+
clauses.push("kind = ?");
|
|
465
|
+
params.push(query.kind);
|
|
466
|
+
}
|
|
467
|
+
if (query.id !== undefined) {
|
|
468
|
+
clauses.push("resource_id = ?");
|
|
469
|
+
params.push(query.id);
|
|
470
|
+
}
|
|
471
|
+
if (query.subject) {
|
|
472
|
+
const normal = normalized(query.subject);
|
|
473
|
+
clauses.push("subject_type = ? AND subject_id = ?");
|
|
474
|
+
params.push(normal.type, normal.id);
|
|
475
|
+
}
|
|
476
|
+
const where = clauses.map((clause) => `(${clause})`).join(" AND ");
|
|
477
|
+
const limit = Math.max(1, Math.min(query.limit ?? 200, 1000));
|
|
478
|
+
const rows = db
|
|
479
|
+
.query(`SELECT * FROM ${SHARE_EVENTS_TABLE} WHERE ${where} ORDER BY event_id DESC LIMIT ?`)
|
|
480
|
+
.all(...params, limit);
|
|
481
|
+
return rows.map(toEvent);
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
export interface SqliteBackupS3Opts {
|
|
3
|
+
bucket: string;
|
|
4
|
+
/** Key prefix within the bucket, e.g. `'ca/backups'`. */
|
|
5
|
+
prefix: string;
|
|
6
|
+
/** AWS region; falls back to Bun's env resolution when omitted. */
|
|
7
|
+
region?: string;
|
|
8
|
+
/** Custom endpoint (e.g. an S3-compatible store like R2/MinIO). */
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SqliteBackupOpts {
|
|
12
|
+
/** Directory backups are written to (created if missing). */
|
|
13
|
+
backupDir: string;
|
|
14
|
+
/** How many local backups to retain after each run. Default: 7. */
|
|
15
|
+
maxBackups?: number;
|
|
16
|
+
/** Mirror each backup to S3 after writing it locally. */
|
|
17
|
+
s3?: SqliteBackupS3Opts;
|
|
18
|
+
}
|
|
19
|
+
export interface BackupInfo {
|
|
20
|
+
path: string;
|
|
21
|
+
createdAt: Date;
|
|
22
|
+
sizeMb: number;
|
|
23
|
+
}
|
|
24
|
+
export interface SqliteBackupService {
|
|
25
|
+
/** Write a fresh backup now, mirror to S3 (if configured), prune, and describe it. */
|
|
26
|
+
runNow(): Promise<{
|
|
27
|
+
path: string;
|
|
28
|
+
sizeMb: number;
|
|
29
|
+
}>;
|
|
30
|
+
/** Local backups, newest first. */
|
|
31
|
+
listBackups(): BackupInfo[];
|
|
32
|
+
/** Delete the oldest backups beyond `maxBackups`. */
|
|
33
|
+
pruneOld(): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `VACUUM INTO` a source db path to a fresh destination file — the path-level
|
|
37
|
+
* snapshot primitive behind `createSqliteBackupService` and the satellites' own
|
|
38
|
+
* backup scripts. It is transactionally consistent even while the db is being
|
|
39
|
+
* written under WAL (unlike `cp`), and it REFUSES an existing target, so a
|
|
40
|
+
* same-instant re-run errors rather than half-overwriting a previous snapshot.
|
|
41
|
+
* Opens the source read-only, closes it, and returns the snapshot's byte size.
|
|
42
|
+
*
|
|
43
|
+
* Use this when the caller chooses the exact destination path (a dated dir, a
|
|
44
|
+
* `latest.sqlite`, a remote-pull staging file); use {@link createSqliteBackupService}
|
|
45
|
+
* when you want a managed backup DIRECTORY with timestamped names + retention + S3.
|
|
46
|
+
*/
|
|
47
|
+
export declare function snapshotSqlite(sourcePath: string, destPath: string): number;
|
|
48
|
+
export declare function createSqliteBackupService(db: Database, opts: SqliteBackupOpts): SqliteBackupService;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* SQLite backup service for `bun:sqlite` — the from-scratch replacement for the apps'
|
|
7
|
+
* `backupService.ts`. It uses `VACUUM INTO`, the correct primitive for `bun:sqlite`:
|
|
8
|
+
* it writes a compact, defragmented copy with all WAL data merged in (the Node
|
|
9
|
+
* `better-sqlite3` online `.backup()` API does not exist here). Reads continue under
|
|
10
|
+
* WAL during the copy; only writes serialize behind it — negligible for normal DB
|
|
11
|
+
* sizes, so the `daily-backup` cron job (task 29) runs it off-peak on the main thread.
|
|
12
|
+
*
|
|
13
|
+
* Optional S3 mirroring uses Bun's native S3 client (credentials from the environment,
|
|
14
|
+
* never stored here). The job *definition* lives in the app; this is the reusable
|
|
15
|
+
* mechanism.
|
|
16
|
+
*/
|
|
17
|
+
const BACKUP_PREFIX = 'backup-';
|
|
18
|
+
const BACKUP_EXT = '.sqlite';
|
|
19
|
+
const BYTES_PER_MB = 1_048_576;
|
|
20
|
+
/**
|
|
21
|
+
* `VACUUM INTO` a source db path to a fresh destination file — the path-level
|
|
22
|
+
* snapshot primitive behind `createSqliteBackupService` and the satellites' own
|
|
23
|
+
* backup scripts. It is transactionally consistent even while the db is being
|
|
24
|
+
* written under WAL (unlike `cp`), and it REFUSES an existing target, so a
|
|
25
|
+
* same-instant re-run errors rather than half-overwriting a previous snapshot.
|
|
26
|
+
* Opens the source read-only, closes it, and returns the snapshot's byte size.
|
|
27
|
+
*
|
|
28
|
+
* Use this when the caller chooses the exact destination path (a dated dir, a
|
|
29
|
+
* `latest.sqlite`, a remote-pull staging file); use {@link createSqliteBackupService}
|
|
30
|
+
* when you want a managed backup DIRECTORY with timestamped names + retention + S3.
|
|
31
|
+
*/
|
|
32
|
+
export function snapshotSqlite(sourcePath, destPath) {
|
|
33
|
+
const source = new Database(sourcePath, { readonly: true });
|
|
34
|
+
try {
|
|
35
|
+
source.run(`VACUUM INTO '${destPath.replaceAll("'", "''")}'`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
source.close();
|
|
39
|
+
}
|
|
40
|
+
return statSync(destPath).size;
|
|
41
|
+
}
|
|
42
|
+
const hasBunS3 = () => typeof Bun.S3Client === 'function';
|
|
43
|
+
async function uploadToS3(localPath, s3) {
|
|
44
|
+
if (!hasBunS3()) {
|
|
45
|
+
console.warn('[sqlite-backup] S3 is configured but Bun.S3Client is unavailable; skipping upload. ' +
|
|
46
|
+
'Upgrade Bun (≥1.1) or remove the s3 option.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const client = new Bun.S3Client({
|
|
50
|
+
bucket: s3.bucket,
|
|
51
|
+
region: s3.region,
|
|
52
|
+
endpoint: s3.endpoint,
|
|
53
|
+
});
|
|
54
|
+
const key = `${s3.prefix.replace(/\/+$/, '')}/${localPath.split('/').pop()}`;
|
|
55
|
+
await client.file(key).write(Bun.file(localPath));
|
|
56
|
+
}
|
|
57
|
+
export function createSqliteBackupService(db, opts) {
|
|
58
|
+
const { backupDir, maxBackups = 7, s3 } = opts;
|
|
59
|
+
if (s3 && !hasBunS3()) {
|
|
60
|
+
console.warn('[sqlite-backup] S3 mirroring requested but Bun.S3Client is unavailable in this runtime; ' +
|
|
61
|
+
'backups will be written locally only.');
|
|
62
|
+
}
|
|
63
|
+
const service = {
|
|
64
|
+
async runNow() {
|
|
65
|
+
mkdirSync(backupDir, { recursive: true });
|
|
66
|
+
// ISO timestamp (filesystem-safe) + a short random suffix so rapid successive
|
|
67
|
+
// runs in the same millisecond never collide onto one path.
|
|
68
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
69
|
+
const outPath = join(backupDir, `${BACKUP_PREFIX}${stamp}-${randomBytes(3).toString('hex')}${BACKUP_EXT}`);
|
|
70
|
+
// VACUUM INTO checkpoints the WAL and writes a clean, compact copy.
|
|
71
|
+
db.run('VACUUM INTO ?', [outPath]);
|
|
72
|
+
const sizeMb = statSync(outPath).size / BYTES_PER_MB;
|
|
73
|
+
if (s3)
|
|
74
|
+
await uploadToS3(outPath, s3);
|
|
75
|
+
service.pruneOld();
|
|
76
|
+
return { path: outPath, sizeMb };
|
|
77
|
+
},
|
|
78
|
+
listBackups() {
|
|
79
|
+
if (!existsSync(backupDir))
|
|
80
|
+
return [];
|
|
81
|
+
return readdirSync(backupDir)
|
|
82
|
+
.filter((f) => f.startsWith(BACKUP_PREFIX) && f.endsWith(BACKUP_EXT))
|
|
83
|
+
.map((f) => {
|
|
84
|
+
const path = join(backupDir, f);
|
|
85
|
+
const stat = statSync(path);
|
|
86
|
+
return { path, createdAt: stat.mtime, sizeMb: stat.size / BYTES_PER_MB };
|
|
87
|
+
})
|
|
88
|
+
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || b.path.localeCompare(a.path));
|
|
89
|
+
},
|
|
90
|
+
pruneOld() {
|
|
91
|
+
for (const old of service.listBackups().slice(maxBackups)) {
|
|
92
|
+
rmSync(old.path, { force: true });
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return service;
|
|
97
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
/** Clamp a requested page size into `[1, MAX_PAGE]`, falling back when unparseable. */
|
|
3
|
+
export declare function clampLimit(raw: number | string | undefined, fallback: number): number;
|
|
4
|
+
/** Clamp an offset to a non-negative integer (0 when unparseable). */
|
|
5
|
+
export declare function clampOffset(raw: number | string | undefined): number;
|
|
6
|
+
/** Is `name` an actual table in this DB? (The injection guard for `SELECT * FROM`.) */
|
|
7
|
+
export declare function isKnownTable(db: Database, name: string): boolean;
|
|
8
|
+
/** Double-quote a name already proven to exist in `sqlite_master`. */
|
|
9
|
+
export declare function quoteIdent(name: string): string;
|
|
10
|
+
export interface SqliteTableInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
/** User tables (FTS shadow + `sqlite_*` internals excluded) with row counts. */
|
|
15
|
+
export declare function listSqliteTables(db: Database): SqliteTableInfo[];
|
|
16
|
+
export interface SqliteTablePage {
|
|
17
|
+
items: Record<string, unknown>[];
|
|
18
|
+
total: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
offset: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class UnknownTableError extends Error {
|
|
23
|
+
readonly table: string;
|
|
24
|
+
constructor(table: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A clamped, paginated window over one table. Throws {@link UnknownTableError} for a
|
|
28
|
+
* table that isn't in `sqlite_master` (callers map that to a 404).
|
|
29
|
+
*/
|
|
30
|
+
export declare function readSqliteTable(db: Database, table: string, opts?: {
|
|
31
|
+
limit?: number | string;
|
|
32
|
+
offset?: number | string;
|
|
33
|
+
}): SqliteTablePage;
|
|
34
|
+
export declare class NotReadOnlyError extends Error {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run an ad-hoc read-only query with bound params. Throws {@link NotReadOnlyError}
|
|
39
|
+
* for anything the {@link isReadOnlySql} guard rejects (non-SELECT, stacked
|
|
40
|
+
* statements, PRAGMA, …); SQLite errors (bad column/table) propagate as `Error`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runReadonlySqlite(db: Database, sql: string, params?: unknown[]): Record<string, unknown>[];
|