alepha 0.13.0 → 0.13.2
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/README.md +1 -1
- package/dist/api-files/index.d.ts +28 -91
- package/dist/api-files/index.js +10 -755
- package/dist/api-files/index.js.map +1 -1
- package/dist/api-jobs/index.d.ts +67 -67
- package/dist/api-jobs/index.js +13 -13
- package/dist/api-jobs/index.js.map +1 -1
- package/dist/api-notifications/index.d.ts +129 -146
- package/dist/api-notifications/index.js +17 -39
- package/dist/api-notifications/index.js.map +1 -1
- package/dist/api-parameters/index.d.ts +21 -22
- package/dist/api-parameters/index.js +22 -22
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +224 -2001
- package/dist/api-users/index.js +914 -4787
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.d.ts +96 -96
- package/dist/batch/index.d.ts +13 -13
- package/dist/batch/index.js +8 -8
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +14 -14
- package/dist/bucket/index.js +12 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/index.d.ts +11 -11
- package/dist/cache/index.js +9 -9
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
- package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
- package/dist/cli/index.d.ts +31 -37
- package/dist/cli/index.js +152 -83
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +19 -19
- package/dist/command/index.js +25 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +218 -218
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +232 -232
- package/dist/core/index.js +218 -218
- package/dist/core/index.js.map +1 -1
- package/dist/core/{index.cjs → index.native.js} +304 -455
- package/dist/core/index.native.js.map +1 -0
- package/dist/datetime/index.d.ts +9 -9
- package/dist/datetime/index.js +7 -7
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.js +80 -82
- package/dist/email/index.js.map +1 -1
- package/dist/file/index.js +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/index.d.ts +9 -9
- package/dist/lock/index.js +8 -8
- package/dist/lock/index.js.map +1 -1
- package/dist/lock-redis/index.js +3 -66
- package/dist/lock-redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -5
- package/dist/logger/index.js +8 -8
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.browser.js +114 -114
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +219 -219
- package/dist/orm/index.js +46 -46
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +25 -25
- package/dist/queue/index.js +20 -20
- package/dist/queue/index.js.map +1 -1
- package/dist/queue-redis/index.d.ts +2 -2
- package/dist/redis/index.d.ts +10 -10
- package/dist/retry/index.d.ts +20 -20
- package/dist/retry/index.js +9 -9
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +12 -12
- package/dist/scheduler/index.js +9 -9
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +53 -53
- package/dist/security/index.js +32 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/index.browser.js +1 -1
- package/dist/server/index.browser.js.map +1 -1
- package/dist/server/index.d.ts +101 -101
- package/dist/server/index.js +17 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.browser.js +4 -982
- package/dist/server-auth/index.browser.js.map +1 -1
- package/dist/server-auth/index.d.ts +204 -785
- package/dist/server-auth/index.js +47 -1239
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cache/index.d.ts +10 -10
- package/dist/server-cache/index.js +2 -2
- package/dist/server-cache/index.js.map +1 -1
- package/dist/server-compress/index.d.ts +4 -4
- package/dist/server-compress/index.js +1 -1
- package/dist/server-compress/index.js.map +1 -1
- package/dist/server-cookies/index.browser.js +8 -8
- package/dist/server-cookies/index.browser.js.map +1 -1
- package/dist/server-cookies/index.d.ts +17 -17
- package/dist/server-cookies/index.js +10 -10
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-cors/index.d.ts +17 -17
- package/dist/server-cors/index.js +9 -9
- package/dist/server-cors/index.js.map +1 -1
- package/dist/server-health/index.d.ts +2 -2
- package/dist/server-helmet/index.d.ts +1 -1
- package/dist/server-links/index.browser.js +12 -12
- package/dist/server-links/index.browser.js.map +1 -1
- package/dist/server-links/index.d.ts +59 -251
- package/dist/server-links/index.js +23 -502
- package/dist/server-links/index.js.map +1 -1
- package/dist/server-metrics/index.d.ts +4 -4
- package/dist/server-metrics/index.js +170 -174
- package/dist/server-metrics/index.js.map +1 -1
- package/dist/server-multipart/index.d.ts +2 -2
- package/dist/server-proxy/index.d.ts +12 -12
- package/dist/server-proxy/index.js +10 -10
- package/dist/server-proxy/index.js.map +1 -1
- package/dist/server-rate-limit/index.d.ts +22 -22
- package/dist/server-rate-limit/index.js +12 -12
- package/dist/server-rate-limit/index.js.map +1 -1
- package/dist/server-security/index.d.ts +24 -24
- package/dist/server-security/index.js +15 -15
- package/dist/server-security/index.js.map +1 -1
- package/dist/server-static/index.d.ts +14 -14
- package/dist/server-static/index.js +8 -8
- package/dist/server-static/index.js.map +1 -1
- package/dist/server-swagger/index.d.ts +25 -184
- package/dist/server-swagger/index.js +21 -724
- package/dist/server-swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +14 -14
- package/dist/sms/index.js +9 -9
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +11 -11
- package/dist/thread/index.js +17 -17
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/index.d.ts +26 -26
- package/dist/topic/index.js +16 -16
- package/dist/topic/index.js.map +1 -1
- package/dist/topic-redis/index.d.ts +1 -1
- package/dist/vite/index.d.ts +3 -3
- package/dist/vite/index.js +12 -13
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -11
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +51 -51
- package/dist/websocket/index.js +13 -13
- package/dist/websocket/index.js.map +1 -1
- package/package.json +62 -52
- package/src/api-files/services/FileService.ts +5 -7
- package/src/api-jobs/index.ts +1 -1
- package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
- package/src/api-jobs/providers/JobProvider.ts +9 -9
- package/src/api-jobs/services/JobService.ts +5 -5
- package/src/api-notifications/index.ts +5 -15
- package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
- package/src/api-notifications/services/NotificationSenderService.ts +3 -3
- package/src/api-parameters/index.ts +1 -1
- package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
- package/src/api-users/index.ts +1 -1
- package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
- package/src/api-users/providers/UserRealmProvider.ts +1 -1
- package/src/batch/index.ts +3 -3
- package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
- package/src/bucket/index.ts +8 -8
- package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
- package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
- package/src/cache/index.ts +4 -4
- package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
- package/src/cli/apps/AlephaPackageBuilderCli.ts +30 -3
- package/src/cli/assets/appRouterTs.ts +9 -0
- package/src/cli/assets/indexHtml.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +10 -0
- package/src/cli/commands/CoreCommands.ts +6 -5
- package/src/cli/commands/DrizzleCommands.ts +69 -61
- package/src/cli/commands/VerifyCommands.ts +2 -2
- package/src/cli/commands/ViteCommands.ts +6 -1
- package/src/cli/services/ProjectUtils.ts +78 -41
- package/src/command/index.ts +5 -5
- package/src/command/{descriptors → primitives}/$command.ts +9 -12
- package/src/command/providers/CliProvider.ts +10 -10
- package/src/core/Alepha.ts +30 -33
- package/src/core/constants/KIND.ts +1 -1
- package/src/core/constants/OPTIONS.ts +1 -1
- package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
- package/src/core/helpers/ref.ts +1 -1
- package/src/core/index.shared.ts +8 -8
- package/src/core/{descriptors → primitives}/$context.ts +5 -5
- package/src/core/{descriptors → primitives}/$hook.ts +4 -4
- package/src/core/{descriptors → primitives}/$inject.ts +2 -2
- package/src/core/{descriptors → primitives}/$module.ts +9 -9
- package/src/core/{descriptors → primitives}/$use.ts +2 -2
- package/src/core/providers/CodecManager.ts +1 -1
- package/src/core/providers/JsonSchemaCodec.ts +1 -1
- package/src/core/providers/StateManager.ts +2 -2
- package/src/datetime/index.ts +3 -3
- package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
- package/src/email/index.ts +4 -4
- package/src/email/{descriptors → primitives}/$email.ts +8 -8
- package/src/file/index.ts +1 -1
- package/src/lock/index.ts +3 -3
- package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
- package/src/logger/index.ts +8 -8
- package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
- package/src/logger/services/Logger.ts +1 -1
- package/src/orm/constants/PG_SYMBOLS.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.ts +8 -8
- package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
- package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
- package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
- package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
- package/src/orm/providers/DrizzleKitProvider.ts +1 -1
- package/src/orm/providers/PostgresTypeProvider.ts +3 -3
- package/src/orm/providers/RepositoryProvider.ts +4 -4
- package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
- package/src/orm/services/ModelBuilder.ts +9 -9
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +5 -5
- package/src/orm/services/Repository.ts +7 -7
- package/src/orm/services/SqliteModelBuilder.ts +5 -5
- package/src/queue/index.ts +7 -7
- package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
- package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
- package/src/queue/providers/WorkerProvider.ts +7 -7
- package/src/retry/index.ts +3 -3
- package/src/retry/{descriptors → primitives}/$retry.ts +19 -17
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/index.ts +9 -9
- package/src/security/{descriptors → primitives}/$permission.ts +7 -7
- package/src/security/{descriptors → primitives}/$realm.ts +6 -12
- package/src/security/{descriptors → primitives}/$role.ts +12 -12
- package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
- package/src/server/index.browser.ts +1 -1
- package/src/server/index.ts +14 -14
- package/src/server/{descriptors → primitives}/$action.ts +13 -13
- package/src/server/{descriptors → primitives}/$route.ts +9 -9
- package/src/server/providers/NodeHttpServerProvider.ts +2 -2
- package/src/server/services/HttpClient.ts +1 -1
- package/src/server-auth/index.browser.ts +1 -1
- package/src/server-auth/index.ts +6 -6
- package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
- package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
- package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
- package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
- package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
- package/src/server-cookies/index.browser.ts +2 -2
- package/src/server-cookies/index.ts +5 -5
- package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
- package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
- package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
- package/src/server-cookies/services/CookieParser.ts +1 -1
- package/src/server-cors/index.ts +3 -3
- package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
- package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
- package/src/server-links/index.browser.ts +5 -5
- package/src/server-links/index.ts +9 -9
- package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
- package/src/server-links/providers/LinkProvider.ts +7 -7
- package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
- package/src/server-links/providers/ServerLinksProvider.ts +3 -3
- package/src/server-proxy/index.ts +3 -3
- package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
- package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
- package/src/server-rate-limit/index.ts +6 -6
- package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
- package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
- package/src/server-security/index.ts +3 -3
- package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
- package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
- package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
- package/src/server-static/index.ts +3 -3
- package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
- package/src/server-static/providers/ServerStaticProvider.ts +6 -6
- package/src/server-swagger/index.ts +5 -5
- package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
- package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
- package/src/sms/index.ts +4 -4
- package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
- package/src/thread/index.ts +3 -3
- package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
- package/src/thread/providers/ThreadProvider.ts +7 -9
- package/src/topic/index.ts +5 -5
- package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
- package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
- package/src/topic/providers/TopicProvider.ts +4 -4
- package/src/vite/helpers/boot.ts +3 -3
- package/src/vite/tasks/copyAssets.ts +1 -1
- package/src/vite/tasks/generateSitemap.ts +3 -3
- package/src/vite/tasks/prerenderPages.ts +2 -2
- package/src/vite/tasks/runAlepha.ts +2 -2
- package/src/websocket/index.browser.ts +3 -3
- package/src/websocket/index.shared.ts +2 -2
- package/src/websocket/index.ts +4 -4
- package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
- package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
- package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
- package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
- package/src/websocket/services/WebSocketClient.ts +5 -5
- package/dist/api-files/index.cjs +0 -1293
- package/dist/api-files/index.cjs.map +0 -1
- package/dist/api-files/index.d.cts +0 -829
- package/dist/api-jobs/index.cjs +0 -274
- package/dist/api-jobs/index.cjs.map +0 -1
- package/dist/api-jobs/index.d.cts +0 -654
- package/dist/api-notifications/index.cjs +0 -380
- package/dist/api-notifications/index.cjs.map +0 -1
- package/dist/api-notifications/index.d.cts +0 -289
- package/dist/api-parameters/index.cjs +0 -66
- package/dist/api-parameters/index.cjs.map +0 -1
- package/dist/api-parameters/index.d.cts +0 -84
- package/dist/api-users/index.cjs +0 -6009
- package/dist/api-users/index.cjs.map +0 -1
- package/dist/api-users/index.d.cts +0 -4740
- package/dist/api-verifications/index.cjs +0 -407
- package/dist/api-verifications/index.cjs.map +0 -1
- package/dist/api-verifications/index.d.cts +0 -207
- package/dist/batch/index.cjs +0 -408
- package/dist/batch/index.cjs.map +0 -1
- package/dist/batch/index.d.cts +0 -330
- package/dist/bin/index.cjs +0 -17
- package/dist/bin/index.cjs.map +0 -1
- package/dist/bin/index.d.cts +0 -1
- package/dist/bucket/index.cjs +0 -303
- package/dist/bucket/index.cjs.map +0 -1
- package/dist/bucket/index.d.cts +0 -355
- package/dist/cache/index.cjs +0 -241
- package/dist/cache/index.cjs.map +0 -1
- package/dist/cache/index.d.cts +0 -202
- package/dist/cache-redis/index.cjs +0 -84
- package/dist/cache-redis/index.cjs.map +0 -1
- package/dist/cache-redis/index.d.cts +0 -40
- package/dist/cli/chunk-DSlc6foC.cjs +0 -43
- package/dist/cli/dist-BBPjuQ56.js +0 -2778
- package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
- package/dist/cli/index.cjs +0 -1241
- package/dist/cli/index.cjs.map +0 -1
- package/dist/cli/index.d.cts +0 -422
- package/dist/command/index.cjs +0 -693
- package/dist/command/index.cjs.map +0 -1
- package/dist/command/index.d.cts +0 -340
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -1927
- package/dist/datetime/index.cjs +0 -318
- package/dist/datetime/index.cjs.map +0 -1
- package/dist/datetime/index.d.cts +0 -145
- package/dist/email/index.cjs +0 -10874
- package/dist/email/index.cjs.map +0 -1
- package/dist/email/index.d.cts +0 -186
- package/dist/fake/index.cjs +0 -34641
- package/dist/fake/index.cjs.map +0 -1
- package/dist/fake/index.d.cts +0 -74
- package/dist/file/index.cjs +0 -1212
- package/dist/file/index.cjs.map +0 -1
- package/dist/file/index.d.cts +0 -698
- package/dist/lock/index.cjs +0 -226
- package/dist/lock/index.cjs.map +0 -1
- package/dist/lock/index.d.cts +0 -361
- package/dist/lock-redis/index.cjs +0 -113
- package/dist/lock-redis/index.cjs.map +0 -1
- package/dist/lock-redis/index.d.cts +0 -24
- package/dist/logger/index.cjs +0 -521
- package/dist/logger/index.cjs.map +0 -1
- package/dist/logger/index.d.cts +0 -281
- package/dist/orm/index.cjs +0 -2986
- package/dist/orm/index.cjs.map +0 -1
- package/dist/orm/index.d.cts +0 -2213
- package/dist/queue/index.cjs +0 -1044
- package/dist/queue/index.cjs.map +0 -1
- package/dist/queue/index.d.cts +0 -1265
- package/dist/queue-redis/index.cjs +0 -873
- package/dist/queue-redis/index.cjs.map +0 -1
- package/dist/queue-redis/index.d.cts +0 -82
- package/dist/redis/index.cjs +0 -153
- package/dist/redis/index.cjs.map +0 -1
- package/dist/redis/index.d.cts +0 -82
- package/dist/retry/index.cjs +0 -146
- package/dist/retry/index.cjs.map +0 -1
- package/dist/retry/index.d.cts +0 -172
- package/dist/router/index.cjs +0 -111
- package/dist/router/index.cjs.map +0 -1
- package/dist/router/index.d.cts +0 -46
- package/dist/scheduler/index.cjs +0 -576
- package/dist/scheduler/index.cjs.map +0 -1
- package/dist/scheduler/index.d.cts +0 -145
- package/dist/security/index.cjs +0 -2402
- package/dist/security/index.cjs.map +0 -1
- package/dist/security/index.d.cts +0 -598
- package/dist/server/index.cjs +0 -1680
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.d.cts +0 -810
- package/dist/server-auth/index.cjs +0 -3146
- package/dist/server-auth/index.cjs.map +0 -1
- package/dist/server-auth/index.d.cts +0 -1164
- package/dist/server-cache/index.cjs +0 -252
- package/dist/server-cache/index.cjs.map +0 -1
- package/dist/server-cache/index.d.cts +0 -164
- package/dist/server-compress/index.cjs +0 -141
- package/dist/server-compress/index.cjs.map +0 -1
- package/dist/server-compress/index.d.cts +0 -38
- package/dist/server-cookies/index.cjs +0 -234
- package/dist/server-cookies/index.cjs.map +0 -1
- package/dist/server-cookies/index.d.cts +0 -144
- package/dist/server-cors/index.cjs +0 -201
- package/dist/server-cors/index.cjs.map +0 -1
- package/dist/server-cors/index.d.cts +0 -140
- package/dist/server-health/index.cjs +0 -62
- package/dist/server-health/index.cjs.map +0 -1
- package/dist/server-health/index.d.cts +0 -58
- package/dist/server-helmet/index.cjs +0 -131
- package/dist/server-helmet/index.cjs.map +0 -1
- package/dist/server-helmet/index.d.cts +0 -97
- package/dist/server-links/index.cjs +0 -992
- package/dist/server-links/index.cjs.map +0 -1
- package/dist/server-links/index.d.cts +0 -513
- package/dist/server-metrics/index.cjs +0 -4535
- package/dist/server-metrics/index.cjs.map +0 -1
- package/dist/server-metrics/index.d.cts +0 -35
- package/dist/server-multipart/index.cjs +0 -237
- package/dist/server-multipart/index.cjs.map +0 -1
- package/dist/server-multipart/index.d.cts +0 -50
- package/dist/server-proxy/index.cjs +0 -186
- package/dist/server-proxy/index.cjs.map +0 -1
- package/dist/server-proxy/index.d.cts +0 -234
- package/dist/server-rate-limit/index.cjs +0 -241
- package/dist/server-rate-limit/index.cjs.map +0 -1
- package/dist/server-rate-limit/index.d.cts +0 -183
- package/dist/server-security/index.cjs +0 -316
- package/dist/server-security/index.cjs.map +0 -1
- package/dist/server-security/index.d.cts +0 -173
- package/dist/server-static/index.cjs +0 -170
- package/dist/server-static/index.cjs.map +0 -1
- package/dist/server-static/index.d.cts +0 -121
- package/dist/server-swagger/index.cjs +0 -1021
- package/dist/server-swagger/index.cjs.map +0 -1
- package/dist/server-swagger/index.d.cts +0 -382
- package/dist/sms/index.cjs +0 -221
- package/dist/sms/index.cjs.map +0 -1
- package/dist/sms/index.d.cts +0 -130
- package/dist/thread/index.cjs +0 -350
- package/dist/thread/index.cjs.map +0 -1
- package/dist/thread/index.d.cts +0 -260
- package/dist/topic/index.cjs +0 -282
- package/dist/topic/index.cjs.map +0 -1
- package/dist/topic/index.d.cts +0 -523
- package/dist/topic-redis/index.cjs +0 -71
- package/dist/topic-redis/index.cjs.map +0 -1
- package/dist/topic-redis/index.d.cts +0 -42
- package/dist/vite/index.cjs +0 -1077
- package/dist/vite/index.cjs.map +0 -1
- package/dist/vite/index.d.cts +0 -542
- package/dist/websocket/index.cjs +0 -1117
- package/dist/websocket/index.cjs.map +0 -1
- package/dist/websocket/index.d.cts +0 -861
- package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
- package/src/api-notifications/providers/SmsProvider.ts +0 -8
- /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
- /package/src/core/{descriptors → primitives}/$env.ts +0 -0
- /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
- /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
package/dist/api-files/index.cjs
DELETED
|
@@ -1,1293 +0,0 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
-
value: mod,
|
|
24
|
-
enumerable: true
|
|
25
|
-
}) : target, mod));
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
let alepha = require("alepha");
|
|
29
|
-
let alepha_bucket = require("alepha/bucket");
|
|
30
|
-
let alepha_cache = require("alepha/cache");
|
|
31
|
-
let node_crypto = require("node:crypto");
|
|
32
|
-
let alepha_datetime = require("alepha/datetime");
|
|
33
|
-
let alepha_logger = require("alepha/logger");
|
|
34
|
-
let alepha_server = require("alepha/server");
|
|
35
|
-
let node_fs = require("node:fs");
|
|
36
|
-
let node_fs_promises = require("node:fs/promises");
|
|
37
|
-
let node_os = require("node:os");
|
|
38
|
-
node_os = __toESM(node_os);
|
|
39
|
-
let node_stream_web = require("node:stream/web");
|
|
40
|
-
let alepha_orm = require("alepha/orm");
|
|
41
|
-
let alepha_security = require("alepha/security");
|
|
42
|
-
let alepha_scheduler = require("alepha/scheduler");
|
|
43
|
-
|
|
44
|
-
//#region src/server-cache/providers/ServerCacheProvider.ts
|
|
45
|
-
alepha_server.ActionDescriptor.prototype.invalidate = async function() {
|
|
46
|
-
await this.alepha.inject(ServerCacheProvider).invalidate(this.route);
|
|
47
|
-
};
|
|
48
|
-
var ServerCacheProvider = class {
|
|
49
|
-
log = (0, alepha_logger.$logger)();
|
|
50
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
51
|
-
time = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
|
|
52
|
-
cache = (0, alepha_cache.$cache)({ provider: "memory" });
|
|
53
|
-
generateETag(content) {
|
|
54
|
-
return `"${(0, node_crypto.createHash)("md5").update(content).digest("hex")}"`;
|
|
55
|
-
}
|
|
56
|
-
async invalidate(route) {
|
|
57
|
-
if (!route.cache) return;
|
|
58
|
-
await this.cache.invalidate(this.createCacheKey(route));
|
|
59
|
-
}
|
|
60
|
-
onActionRequest = (0, alepha.$hook)({
|
|
61
|
-
on: "action:onRequest",
|
|
62
|
-
handler: async ({ action, request }) => {
|
|
63
|
-
const cache = action.route.cache;
|
|
64
|
-
if (this.shouldStore(cache)) {
|
|
65
|
-
const key = this.createCacheKey(action.route, request);
|
|
66
|
-
const cached = await this.cache.get(key);
|
|
67
|
-
if (cached) {
|
|
68
|
-
const body = cached.contentType === "application/json" ? JSON.parse(cached.body) : cached.body;
|
|
69
|
-
this.log.trace("Cache hit for action", {
|
|
70
|
-
key,
|
|
71
|
-
action: action.name
|
|
72
|
-
});
|
|
73
|
-
request.reply.body = body;
|
|
74
|
-
} else this.log.trace("Cache miss for action", {
|
|
75
|
-
key,
|
|
76
|
-
action: action.name
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
onActionResponse = (0, alepha.$hook)({
|
|
82
|
-
on: "action:onResponse",
|
|
83
|
-
handler: async ({ action, request, response }) => {
|
|
84
|
-
const cache = action.route.cache;
|
|
85
|
-
if (!this.shouldStore(cache) || !response) return;
|
|
86
|
-
if (request.reply.status && request.reply.status >= 400) return;
|
|
87
|
-
const contentType = typeof response === "string" ? "text/plain" : "application/json";
|
|
88
|
-
const body = contentType === "text/plain" ? response : JSON.stringify(response);
|
|
89
|
-
const generatedEtag = this.generateETag(body);
|
|
90
|
-
const lastModified = this.time.toISOString();
|
|
91
|
-
const key = this.createCacheKey(action.route, request);
|
|
92
|
-
this.log.trace("Storing response", {
|
|
93
|
-
key,
|
|
94
|
-
action: action.name
|
|
95
|
-
});
|
|
96
|
-
await this.cache.set(key, {
|
|
97
|
-
body,
|
|
98
|
-
lastModified,
|
|
99
|
-
contentType,
|
|
100
|
-
hash: generatedEtag
|
|
101
|
-
});
|
|
102
|
-
const cacheControl = this.buildCacheControlHeader(cache);
|
|
103
|
-
if (cacheControl) request.reply.setHeader("cache-control", cacheControl);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
onRequest = (0, alepha.$hook)({
|
|
107
|
-
on: "server:onRequest",
|
|
108
|
-
handler: async ({ route, request }) => {
|
|
109
|
-
const cache = route.cache;
|
|
110
|
-
const shouldStore = this.shouldStore(cache);
|
|
111
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
112
|
-
if (!shouldStore && !shouldUseEtag) return;
|
|
113
|
-
const key = this.createCacheKey(route, request);
|
|
114
|
-
const cached = await this.cache.get(key);
|
|
115
|
-
if (cached) {
|
|
116
|
-
if (request.headers["if-none-match"] === cached.hash || request.headers["if-modified-since"] === cached.lastModified) {
|
|
117
|
-
request.reply.status = 304;
|
|
118
|
-
request.reply.setHeader("etag", cached.hash);
|
|
119
|
-
request.reply.setHeader("last-modified", cached.lastModified);
|
|
120
|
-
this.log.trace("ETag match, returning 304", {
|
|
121
|
-
route: route.path,
|
|
122
|
-
etag: cached.hash
|
|
123
|
-
});
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (shouldStore) {
|
|
127
|
-
this.log.trace("Cache hit for route", {
|
|
128
|
-
key,
|
|
129
|
-
route: route.path
|
|
130
|
-
});
|
|
131
|
-
request.reply.body = cached.body;
|
|
132
|
-
request.reply.status = cached.status ?? 200;
|
|
133
|
-
if (cached.contentType) request.reply.setHeader("Content-Type", cached.contentType);
|
|
134
|
-
request.reply.setHeader("etag", cached.hash);
|
|
135
|
-
request.reply.setHeader("last-modified", cached.lastModified);
|
|
136
|
-
}
|
|
137
|
-
} else if (shouldStore) this.log.trace("Cache miss for route", {
|
|
138
|
-
key,
|
|
139
|
-
route: route.path
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
onSend = (0, alepha.$hook)({
|
|
144
|
-
on: "server:onSend",
|
|
145
|
-
handler: async ({ route, request }) => {
|
|
146
|
-
const cache = route.cache;
|
|
147
|
-
const shouldStore = this.shouldStore(cache);
|
|
148
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
149
|
-
if (request.reply.headers.etag) return;
|
|
150
|
-
if (!shouldStore && shouldUseEtag && request.reply.body != null && (typeof request.reply.body === "string" || Buffer.isBuffer(request.reply.body))) {
|
|
151
|
-
const generatedEtag = this.generateETag(request.reply.body);
|
|
152
|
-
if (request.headers["if-none-match"] === generatedEtag) {
|
|
153
|
-
request.reply.status = 304;
|
|
154
|
-
request.reply.body = void 0;
|
|
155
|
-
request.reply.setHeader("etag", generatedEtag);
|
|
156
|
-
this.log.trace("ETag match on send, returning 304", {
|
|
157
|
-
route: route.path,
|
|
158
|
-
etag: generatedEtag
|
|
159
|
-
});
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
onResponse = (0, alepha.$hook)({
|
|
166
|
-
on: "server:onResponse",
|
|
167
|
-
priority: "first",
|
|
168
|
-
handler: async ({ route, request, response }) => {
|
|
169
|
-
const cache = route.cache;
|
|
170
|
-
const cacheControl = this.buildCacheControlHeader(cache);
|
|
171
|
-
if (cacheControl) response.headers["cache-control"] = cacheControl;
|
|
172
|
-
const shouldStore = this.shouldStore(cache);
|
|
173
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
174
|
-
if (!shouldStore && !shouldUseEtag) return;
|
|
175
|
-
if (typeof response.body !== "string") return;
|
|
176
|
-
if (response.status && response.status >= 400) return;
|
|
177
|
-
const key = this.createCacheKey(route, request);
|
|
178
|
-
const generatedEtag = this.generateETag(response.body);
|
|
179
|
-
const lastModified = this.time.toISOString();
|
|
180
|
-
response.headers ??= {};
|
|
181
|
-
if (shouldStore) {
|
|
182
|
-
this.log.trace("Storing response", {
|
|
183
|
-
key,
|
|
184
|
-
route: route.path,
|
|
185
|
-
cache: !!cache,
|
|
186
|
-
etag: shouldUseEtag
|
|
187
|
-
});
|
|
188
|
-
await this.cache.set(key, {
|
|
189
|
-
body: response.body,
|
|
190
|
-
status: response.status,
|
|
191
|
-
contentType: response.headers?.["content-type"],
|
|
192
|
-
lastModified,
|
|
193
|
-
hash: generatedEtag
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
if (shouldUseEtag) {
|
|
197
|
-
response.headers.etag = generatedEtag;
|
|
198
|
-
response.headers["last-modified"] = lastModified;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
buildCacheControlHeader(cache) {
|
|
203
|
-
if (!cache) return;
|
|
204
|
-
if (cache === true || typeof cache === "string" || typeof cache === "number") return;
|
|
205
|
-
const control = cache.control;
|
|
206
|
-
if (!control) return;
|
|
207
|
-
if (typeof control === "string") return control;
|
|
208
|
-
if (control === true) return "public, max-age=300";
|
|
209
|
-
const directives = [];
|
|
210
|
-
if (control.public) directives.push("public");
|
|
211
|
-
if (control.private) directives.push("private");
|
|
212
|
-
if (control.noCache) directives.push("no-cache");
|
|
213
|
-
if (control.noStore) directives.push("no-store");
|
|
214
|
-
if (control.maxAge !== void 0) {
|
|
215
|
-
const maxAgeSeconds = this.durationToSeconds(control.maxAge);
|
|
216
|
-
directives.push(`max-age=${maxAgeSeconds}`);
|
|
217
|
-
}
|
|
218
|
-
if (control.sMaxAge !== void 0) {
|
|
219
|
-
const sMaxAgeSeconds = this.durationToSeconds(control.sMaxAge);
|
|
220
|
-
directives.push(`s-maxage=${sMaxAgeSeconds}`);
|
|
221
|
-
}
|
|
222
|
-
if (control.mustRevalidate) directives.push("must-revalidate");
|
|
223
|
-
if (control.proxyRevalidate) directives.push("proxy-revalidate");
|
|
224
|
-
if (control.immutable) directives.push("immutable");
|
|
225
|
-
return directives.length > 0 ? directives.join(", ") : void 0;
|
|
226
|
-
}
|
|
227
|
-
durationToSeconds(duration) {
|
|
228
|
-
if (typeof duration === "number") return duration;
|
|
229
|
-
return this.time.duration(duration).asSeconds();
|
|
230
|
-
}
|
|
231
|
-
shouldStore(cache) {
|
|
232
|
-
if (!cache) return false;
|
|
233
|
-
if (cache === true) return true;
|
|
234
|
-
if (typeof cache === "object" && cache.store) return true;
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
shouldUseEtag(cache) {
|
|
238
|
-
if (cache === true) return true;
|
|
239
|
-
if (typeof cache === "object" && cache.etag) return true;
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
createCacheKey(route, config) {
|
|
243
|
-
const params = [];
|
|
244
|
-
for (const [key, value] of Object.entries(config?.params ?? {})) params.push(`${key}=${value}`);
|
|
245
|
-
for (const [key, value] of Object.entries(config?.query ?? {})) params.push(`${key}=${value}`);
|
|
246
|
-
return `${route.method}:${route.path.replaceAll(":", "")}:${params.join(",").replaceAll(":", "")}`;
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
//#endregion
|
|
251
|
-
//#region src/server-cache/index.ts
|
|
252
|
-
/**
|
|
253
|
-
* Plugin for Alepha Server that provides server-side caching capabilities.
|
|
254
|
-
* It uses the Alepha Cache module to cache responses from server actions ($action).
|
|
255
|
-
* It also provides a ETag-based cache invalidation mechanism.
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* ```ts
|
|
259
|
-
* import { Alepha } from "alepha";
|
|
260
|
-
* import { $action } from "alepha/server";
|
|
261
|
-
* import { AlephaServerCache } from "alepha/server/cache";
|
|
262
|
-
*
|
|
263
|
-
* class ApiServer {
|
|
264
|
-
* hello = $action({
|
|
265
|
-
* cache: true,
|
|
266
|
-
* handler: () => "Hello, World!",
|
|
267
|
-
* });
|
|
268
|
-
* }
|
|
269
|
-
*
|
|
270
|
-
* const alepha = Alepha.create()
|
|
271
|
-
* .with(AlephaServerCache)
|
|
272
|
-
* .with(ApiServer);
|
|
273
|
-
*
|
|
274
|
-
* run(alepha);
|
|
275
|
-
* ```
|
|
276
|
-
*
|
|
277
|
-
* @see {@link ServerCacheProvider}
|
|
278
|
-
* @module alepha.server.cache
|
|
279
|
-
*/
|
|
280
|
-
const AlephaServerCache = (0, alepha.$module)({
|
|
281
|
-
name: "alepha.server.cache",
|
|
282
|
-
services: [alepha_cache.AlephaCache, ServerCacheProvider]
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region src/server-multipart/providers/ServerMultipartProvider.ts
|
|
287
|
-
const envSchema = alepha.t.object({
|
|
288
|
-
SERVER_MULTIPART_LIMIT: alepha.t.integer({
|
|
289
|
-
default: 1e7,
|
|
290
|
-
min: 0,
|
|
291
|
-
description: "Maximum total size of multipart request body in bytes."
|
|
292
|
-
}),
|
|
293
|
-
SERVER_MULTIPART_FILE_LIMIT: alepha.t.integer({
|
|
294
|
-
default: 5e6,
|
|
295
|
-
min: 0,
|
|
296
|
-
description: "Maximum size of a single file in bytes."
|
|
297
|
-
}),
|
|
298
|
-
SERVER_MULTIPART_FILE_COUNT: alepha.t.integer({
|
|
299
|
-
default: 10,
|
|
300
|
-
min: 1,
|
|
301
|
-
description: "Maximum number of files allowed in a single request."
|
|
302
|
-
})
|
|
303
|
-
});
|
|
304
|
-
var ServerMultipartProvider = class {
|
|
305
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
306
|
-
env = (0, alepha.$env)(envSchema);
|
|
307
|
-
log = (0, alepha_logger.$logger)();
|
|
308
|
-
onRequest = (0, alepha.$hook)({
|
|
309
|
-
on: "server:onRequest",
|
|
310
|
-
handler: async ({ route, request }) => {
|
|
311
|
-
if (request.body) return;
|
|
312
|
-
if (!route.schema?.body) return;
|
|
313
|
-
let webRequest;
|
|
314
|
-
if (request.raw.web?.req) webRequest = request.raw.web.req;
|
|
315
|
-
else if (request.raw.node?.req) webRequest = new Request(request.url, {
|
|
316
|
-
method: request.method,
|
|
317
|
-
headers: request.headers,
|
|
318
|
-
body: node_stream_web.ReadableStream.from(request.raw.node.req),
|
|
319
|
-
duplex: "half"
|
|
320
|
-
});
|
|
321
|
-
if (!webRequest) return;
|
|
322
|
-
const contentType = request.headers["content-type"];
|
|
323
|
-
const contentLength = request.headers["content-length"];
|
|
324
|
-
if (contentLength) {
|
|
325
|
-
const size = Number.parseInt(contentLength, 10);
|
|
326
|
-
if (!Number.isNaN(size) && size > this.env.SERVER_MULTIPART_LIMIT) {
|
|
327
|
-
this.log.error(`Multipart request size limit exceeded: ${size} > ${this.env.SERVER_MULTIPART_LIMIT}`);
|
|
328
|
-
throw new alepha_server.HttpError({
|
|
329
|
-
status: 413,
|
|
330
|
-
message: `Request body size limit exceeded. Maximum allowed: ${this.env.SERVER_MULTIPART_LIMIT} bytes`
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (!contentType?.startsWith("multipart/form-data")) {
|
|
335
|
-
if (!(0, alepha_server.isMultipart)(route)) return;
|
|
336
|
-
throw new alepha_server.HttpError({
|
|
337
|
-
status: 415,
|
|
338
|
-
message: `Invalid content-type: ${contentType} - only "multipart/form-data" is accepted`
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
const { body, cleanup } = await this.handleMultipartBodyFromWeb(route, webRequest);
|
|
342
|
-
request.body = body;
|
|
343
|
-
request.metadata.multipart = { cleanup };
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
onResponse = (0, alepha.$hook)({
|
|
347
|
-
on: "server:onResponse",
|
|
348
|
-
handler: async ({ request }) => {
|
|
349
|
-
const cleanup = request.metadata.multipart?.cleanup;
|
|
350
|
-
if (typeof cleanup === "function") await cleanup();
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
async handleMultipartBodyFromWeb(route, request) {
|
|
354
|
-
let formData;
|
|
355
|
-
try {
|
|
356
|
-
formData = await request.formData();
|
|
357
|
-
} catch (error) {
|
|
358
|
-
throw new alepha_server.HttpError({
|
|
359
|
-
status: 400,
|
|
360
|
-
message: "Malformed multipart/form-data"
|
|
361
|
-
}, error);
|
|
362
|
-
}
|
|
363
|
-
const body = {};
|
|
364
|
-
const tempFiles = [];
|
|
365
|
-
const cleanupOnError = async () => {
|
|
366
|
-
for (const file of tempFiles) try {
|
|
367
|
-
await file.cleanup();
|
|
368
|
-
} catch {}
|
|
369
|
-
};
|
|
370
|
-
try {
|
|
371
|
-
let fileCount = 0;
|
|
372
|
-
let totalSize = 0;
|
|
373
|
-
if (route.schema?.body && alepha.t.schema.isObject(route.schema.body)) {
|
|
374
|
-
for (const [key, value] of Object.entries(route.schema.body.properties)) if (alepha.t.schema.isSchema(value)) if ((0, alepha.isTypeFile)(value)) {
|
|
375
|
-
const file = formData.get(key);
|
|
376
|
-
if (file && typeof file === "object" && "arrayBuffer" in file) {
|
|
377
|
-
const blob = file;
|
|
378
|
-
fileCount++;
|
|
379
|
-
if (fileCount > this.env.SERVER_MULTIPART_FILE_COUNT) {
|
|
380
|
-
this.log.error(`Too many files in multipart request: ${fileCount} > ${this.env.SERVER_MULTIPART_FILE_COUNT}`);
|
|
381
|
-
throw new alepha_server.HttpError({
|
|
382
|
-
status: 413,
|
|
383
|
-
message: `Too many files. Maximum allowed: ${this.env.SERVER_MULTIPART_FILE_COUNT}`
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
if (blob.size > this.env.SERVER_MULTIPART_FILE_LIMIT) {
|
|
387
|
-
this.log.error(`File "${key}" exceeds size limit: ${blob.size} > ${this.env.SERVER_MULTIPART_FILE_LIMIT}`);
|
|
388
|
-
throw new alepha_server.HttpError({
|
|
389
|
-
status: 413,
|
|
390
|
-
message: `File "${key}" exceeds size limit. Maximum allowed: ${this.env.SERVER_MULTIPART_FILE_LIMIT} bytes`
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
totalSize += blob.size;
|
|
394
|
-
if (totalSize > this.env.SERVER_MULTIPART_LIMIT) {
|
|
395
|
-
this.log.error(`Total multipart size exceeds limit: ${totalSize} > ${this.env.SERVER_MULTIPART_LIMIT}`);
|
|
396
|
-
throw new alepha_server.HttpError({
|
|
397
|
-
status: 413,
|
|
398
|
-
message: `Total request size exceeds limit. Maximum allowed: ${this.env.SERVER_MULTIPART_LIMIT} bytes`
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
const hybridFile = await this.createHybridFile(blob, key);
|
|
402
|
-
body[key] = hybridFile;
|
|
403
|
-
tempFiles.push(hybridFile);
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
const fieldValue = formData.get(key);
|
|
407
|
-
if (fieldValue !== null) {
|
|
408
|
-
const stringValue = typeof fieldValue === "string" ? fieldValue : "";
|
|
409
|
-
body[key] = this.alepha.codec.decode(value, stringValue);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return {
|
|
414
|
-
body,
|
|
415
|
-
cleanup: async () => {
|
|
416
|
-
for (const file of tempFiles) await file.cleanup();
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
} catch (error) {
|
|
420
|
-
await cleanupOnError();
|
|
421
|
-
throw error;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* This is a legacy code, previously we used "busboy" to parse multipart in Node.js environment.
|
|
426
|
-
* Now we rely on Web Request's formData() method, which is supported in modern Node.js versions.
|
|
427
|
-
* However, we still need to create temporary files for uploaded files to provide a consistent File-like interface.
|
|
428
|
-
*
|
|
429
|
-
* TODO: In future, we might want to refactor this to avoid using temporary files if not necessary?
|
|
430
|
-
*/
|
|
431
|
-
async createHybridFile(file, fieldName) {
|
|
432
|
-
const tmpPath = `${node_os.tmpdir()}/${(0, node_crypto.randomUUID)()}`;
|
|
433
|
-
const arrayBuffer = await file.arrayBuffer();
|
|
434
|
-
await (0, node_fs_promises.writeFile)(tmpPath, Buffer.from(arrayBuffer));
|
|
435
|
-
const fileName = file.name || `${fieldName}_${Date.now()}`;
|
|
436
|
-
return {
|
|
437
|
-
_state: {
|
|
438
|
-
cleanup: false,
|
|
439
|
-
size: file.size,
|
|
440
|
-
tmpPath
|
|
441
|
-
},
|
|
442
|
-
name: fileName,
|
|
443
|
-
type: file.type || "application/octet-stream",
|
|
444
|
-
lastModified: file.lastModified || Date.now(),
|
|
445
|
-
filepath: tmpPath,
|
|
446
|
-
get size() {
|
|
447
|
-
return this._state.size;
|
|
448
|
-
},
|
|
449
|
-
stream() {
|
|
450
|
-
return (0, node_fs.createReadStream)(tmpPath);
|
|
451
|
-
},
|
|
452
|
-
async arrayBuffer() {
|
|
453
|
-
const content = await (0, node_fs_promises.readFile)(tmpPath);
|
|
454
|
-
return content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
|
|
455
|
-
},
|
|
456
|
-
text: async () => {
|
|
457
|
-
return await (0, node_fs_promises.readFile)(tmpPath, "utf-8");
|
|
458
|
-
},
|
|
459
|
-
async cleanup() {
|
|
460
|
-
if (this._state.cleanup) return;
|
|
461
|
-
await (0, node_fs_promises.unlink)(tmpPath);
|
|
462
|
-
this._state.cleanup = true;
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
//#endregion
|
|
469
|
-
//#region src/server-multipart/index.ts
|
|
470
|
-
/**
|
|
471
|
-
* This module provides support for handling multipart/form-data requests.
|
|
472
|
-
* It allows to parse body data containing t.file().
|
|
473
|
-
*
|
|
474
|
-
* @see {@link ServerMultipartProvider}
|
|
475
|
-
* @module alepha.server.multipart
|
|
476
|
-
*/
|
|
477
|
-
const AlephaServerMultipart = (0, alepha.$module)({
|
|
478
|
-
name: "alepha.server.multipart",
|
|
479
|
-
services: [alepha_server.AlephaServer, ServerMultipartProvider]
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
//#endregion
|
|
483
|
-
//#region src/api-files/schemas/fileQuerySchema.ts
|
|
484
|
-
const fileQuerySchema = alepha.t.extend(alepha_orm.pageQuerySchema, {
|
|
485
|
-
bucket: alepha.t.optional(alepha.t.string()),
|
|
486
|
-
tags: alepha.t.optional(alepha.t.array(alepha.t.string())),
|
|
487
|
-
name: alepha.t.optional(alepha.t.string()),
|
|
488
|
-
mimeType: alepha.t.optional(alepha.t.string()),
|
|
489
|
-
creator: alepha.t.optional(alepha.t.uuid()),
|
|
490
|
-
createdAfter: alepha.t.optional(alepha.t.datetime()),
|
|
491
|
-
createdBefore: alepha.t.optional(alepha.t.datetime())
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
//#endregion
|
|
495
|
-
//#region src/api-files/entities/files.ts
|
|
496
|
-
const files = (0, alepha_orm.$entity)({
|
|
497
|
-
name: "files",
|
|
498
|
-
schema: alepha.t.object({
|
|
499
|
-
id: alepha_orm.pg.primaryKey(alepha.t.uuid()),
|
|
500
|
-
version: alepha_orm.pg.version(),
|
|
501
|
-
createdAt: alepha_orm.pg.createdAt(),
|
|
502
|
-
updatedAt: alepha_orm.pg.updatedAt(),
|
|
503
|
-
blobId: alepha.t.text(),
|
|
504
|
-
creator: alepha.t.optional(alepha.t.uuid()),
|
|
505
|
-
creatorRealm: alepha.t.optional(alepha.t.string()),
|
|
506
|
-
creatorName: alepha.t.optional(alepha.t.string()),
|
|
507
|
-
bucket: alepha.t.text(),
|
|
508
|
-
expirationDate: alepha.t.optional(alepha.t.datetime()),
|
|
509
|
-
name: alepha.t.text(),
|
|
510
|
-
size: alepha.t.number(),
|
|
511
|
-
mimeType: alepha.t.string(),
|
|
512
|
-
tags: alepha.t.optional(alepha.t.array(alepha.t.text())),
|
|
513
|
-
checksum: alepha.t.optional(alepha.t.string())
|
|
514
|
-
}),
|
|
515
|
-
indexes: [
|
|
516
|
-
"expirationDate",
|
|
517
|
-
"bucket",
|
|
518
|
-
"creator",
|
|
519
|
-
"createdAt",
|
|
520
|
-
"mimeType",
|
|
521
|
-
{ columns: ["bucket", "createdAt"] }
|
|
522
|
-
]
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
//#endregion
|
|
526
|
-
//#region src/api-files/schemas/fileResourceSchema.ts
|
|
527
|
-
const fileResourceSchema = alepha.t.extend(files.schema, {}, {
|
|
528
|
-
title: "FileResource",
|
|
529
|
-
description: "A file resource representing a file stored in the system."
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
//#endregion
|
|
533
|
-
//#region src/api-files/services/FileService.ts
|
|
534
|
-
var FileService = class {
|
|
535
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
536
|
-
log = (0, alepha_logger.$logger)();
|
|
537
|
-
fileRepository = (0, alepha_orm.$repository)(files);
|
|
538
|
-
dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
|
|
539
|
-
defaultBucket = (0, alepha_bucket.$bucket)({ name: "default" });
|
|
540
|
-
onUploadFile = (0, alepha.$hook)({
|
|
541
|
-
on: "bucket:file:uploaded",
|
|
542
|
-
handler: async ({ file, bucket, options, id }) => {
|
|
543
|
-
if (options.persist === false) return;
|
|
544
|
-
const checksum = await this.calculateChecksum(file);
|
|
545
|
-
await this.fileRepository.create({
|
|
546
|
-
blobId: id,
|
|
547
|
-
mimeType: file.type,
|
|
548
|
-
name: file.name,
|
|
549
|
-
size: file.size,
|
|
550
|
-
creator: options.user?.id,
|
|
551
|
-
creatorRealm: options.user?.realm,
|
|
552
|
-
expirationDate: this.getExpirationDate(options.ttl),
|
|
553
|
-
bucket: bucket.name,
|
|
554
|
-
checksum
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
onDeleteBucketFile = (0, alepha.$hook)({
|
|
559
|
-
on: "bucket:file:deleted",
|
|
560
|
-
handler: async ({ bucket, id }) => {
|
|
561
|
-
await this.fileRepository.deleteMany({
|
|
562
|
-
blobId: { eq: id },
|
|
563
|
-
bucket: { eq: bucket.name }
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
/**
|
|
568
|
-
* Calculates SHA-256 checksum of a file.
|
|
569
|
-
*
|
|
570
|
-
* @param file - The file to calculate checksum for
|
|
571
|
-
* @returns Hexadecimal string representation of the SHA-256 hash
|
|
572
|
-
* @protected
|
|
573
|
-
*/
|
|
574
|
-
async calculateChecksum(file) {
|
|
575
|
-
const buffer = await file.arrayBuffer();
|
|
576
|
-
const hash = (0, node_crypto.createHash)("sha256");
|
|
577
|
-
hash.update(Buffer.from(buffer));
|
|
578
|
-
return hash.digest("hex");
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Gets a bucket descriptor by name.
|
|
582
|
-
*
|
|
583
|
-
* @param bucketName - The name of the bucket to retrieve (defaults to "default")
|
|
584
|
-
* @returns The bucket descriptor
|
|
585
|
-
* @throws {NotFoundError} If the bucket is not found
|
|
586
|
-
*/
|
|
587
|
-
bucket(bucketName = this.defaultBucket.name) {
|
|
588
|
-
const bucket = this.alepha.descriptors(alepha_bucket.$bucket).find((it) => it.name === bucketName);
|
|
589
|
-
if (!bucket) throw new alepha_server.NotFoundError(`Bucket '${bucketName}' not found.`);
|
|
590
|
-
return bucket;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Finds files matching the given query criteria with pagination support.
|
|
594
|
-
* Supports filtering by bucket, tags, name, mimeType, creator, and date range.
|
|
595
|
-
*
|
|
596
|
-
* @param q - Query parameters including bucket, tags, name, mimeType, creator, date range, pagination, and sorting
|
|
597
|
-
* @returns Paginated list of file entities
|
|
598
|
-
*/
|
|
599
|
-
async findFiles(q = {}) {
|
|
600
|
-
q.sort ??= "-createdAt";
|
|
601
|
-
const where = this.fileRepository.createQueryWhere();
|
|
602
|
-
if (q.bucket) where.bucket = { eq: q.bucket };
|
|
603
|
-
if (q.tags) where.tags = { arrayContains: q.tags };
|
|
604
|
-
if (q.name) where.name = { ilike: `%${q.name}%` };
|
|
605
|
-
if (q.mimeType) where.mimeType = { eq: q.mimeType };
|
|
606
|
-
if (q.creator) where.creator = { eq: q.creator };
|
|
607
|
-
if (q.createdAfter && q.createdBefore) where.createdAt = {
|
|
608
|
-
gte: q.createdAfter,
|
|
609
|
-
lte: q.createdBefore
|
|
610
|
-
};
|
|
611
|
-
else if (q.createdAfter) where.createdAt = { gte: q.createdAfter };
|
|
612
|
-
else if (q.createdBefore) where.createdAt = { lte: q.createdBefore };
|
|
613
|
-
return await this.fileRepository.paginate(q, { where }, { count: true }).then((page) => {
|
|
614
|
-
return {
|
|
615
|
-
...page,
|
|
616
|
-
content: page.content.map((it) => this.entityToResource(it))
|
|
617
|
-
};
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Finds files that have expired based on their expiration date.
|
|
622
|
-
* Limited to 1000 files per call to prevent memory issues.
|
|
623
|
-
*
|
|
624
|
-
* @returns Array of expired file entities
|
|
625
|
-
*/
|
|
626
|
-
async findExpiredFiles() {
|
|
627
|
-
return await this.fileRepository.findMany({
|
|
628
|
-
limit: 1e3,
|
|
629
|
-
where: { expirationDate: { lte: this.dateTimeProvider.nowISOString() } }
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Calculates an expiration date based on a TTL (time to live) duration.
|
|
634
|
-
*
|
|
635
|
-
* @param ttl - Duration like "1 day", "2 hours", etc.
|
|
636
|
-
* @returns DateTime representation of the expiration date, or undefined if no TTL provided
|
|
637
|
-
* @protected
|
|
638
|
-
*/
|
|
639
|
-
getExpirationDate(ttl) {
|
|
640
|
-
return ttl ? this.dateTimeProvider.now().add(this.dateTimeProvider.duration(ttl)).toISOString() : void 0;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Uploads a file to a bucket and creates a database record with metadata.
|
|
644
|
-
* Automatically calculates and stores the file checksum (SHA-256).
|
|
645
|
-
*
|
|
646
|
-
* @param file - The file to upload
|
|
647
|
-
* @param options - Upload options including bucket, expiration, user, and tags
|
|
648
|
-
* @param options.bucket - Target bucket name (defaults to "default")
|
|
649
|
-
* @param options.expirationDate - When the file should expire
|
|
650
|
-
* @param options.user - User performing the upload (for audit trail)
|
|
651
|
-
* @param options.tags - Tags to associate with the file
|
|
652
|
-
* @returns The created file entity with all metadata
|
|
653
|
-
* @throws {NotFoundError} If the specified bucket doesn't exist
|
|
654
|
-
*/
|
|
655
|
-
async uploadFile(file, options = {}) {
|
|
656
|
-
const bucket = this.bucket(options.bucket);
|
|
657
|
-
const checksum = await this.calculateChecksum(file);
|
|
658
|
-
const blobId = await bucket.upload(file, { persist: false });
|
|
659
|
-
let expirationDate;
|
|
660
|
-
if (options.expirationDate) expirationDate = this.dateTimeProvider.of(options.expirationDate).toISOString();
|
|
661
|
-
else if (bucket.options.ttl) expirationDate = this.getExpirationDate(bucket.options.ttl);
|
|
662
|
-
return await this.fileRepository.create({
|
|
663
|
-
blobId,
|
|
664
|
-
mimeType: file.type,
|
|
665
|
-
name: file.name,
|
|
666
|
-
size: file.size,
|
|
667
|
-
creator: options.user?.id,
|
|
668
|
-
creatorRealm: options.user?.realm,
|
|
669
|
-
creatorName: options.user?.name,
|
|
670
|
-
expirationDate,
|
|
671
|
-
bucket: bucket.name,
|
|
672
|
-
tags: options.tags,
|
|
673
|
-
checksum
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Streams a file from storage by its database ID.
|
|
678
|
-
*
|
|
679
|
-
* @param id - The database ID (UUID) of the file to stream
|
|
680
|
-
* @returns The file object ready for streaming/downloading
|
|
681
|
-
* @throws {NotFoundError} If the file doesn't exist in the database
|
|
682
|
-
* @throws {FileNotFoundError} If the file exists in database but not in storage
|
|
683
|
-
*/
|
|
684
|
-
async streamFile(id) {
|
|
685
|
-
const entity = await this.getFileById(id);
|
|
686
|
-
return await this.bucket(entity.bucket).download(entity.blobId);
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Updates file metadata (name, tags, expiration date).
|
|
690
|
-
* Does not modify the actual file content in storage.
|
|
691
|
-
*
|
|
692
|
-
* @param id - The database ID (UUID) of the file to update
|
|
693
|
-
* @param data - Partial file data to update
|
|
694
|
-
* @param data.name - New file name
|
|
695
|
-
* @param data.tags - New tags array
|
|
696
|
-
* @param data.expirationDate - New expiration date
|
|
697
|
-
* @returns The updated file entity
|
|
698
|
-
* @throws {NotFoundError} If the file doesn't exist in the database
|
|
699
|
-
*/
|
|
700
|
-
async updateFile(id, data) {
|
|
701
|
-
const file = await this.getFileById(id);
|
|
702
|
-
const updateData = {};
|
|
703
|
-
if (data.name !== void 0) updateData.name = data.name;
|
|
704
|
-
if (data.tags !== void 0) updateData.tags = data.tags;
|
|
705
|
-
if (data.expirationDate !== void 0) updateData.expirationDate = this.dateTimeProvider.of(data.expirationDate).toISOString();
|
|
706
|
-
return await this.fileRepository.updateById(file.id, updateData);
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Deletes a file from both storage and database.
|
|
710
|
-
* Handles cases where file is already deleted from storage gracefully.
|
|
711
|
-
* Always ensures database record is removed even if storage deletion fails.
|
|
712
|
-
*
|
|
713
|
-
* @param id - The database ID (UUID) of the file to delete
|
|
714
|
-
* @returns Success response with the deleted file ID
|
|
715
|
-
* @throws {NotFoundError} If the file doesn't exist in the database
|
|
716
|
-
*/
|
|
717
|
-
async deleteFile(id) {
|
|
718
|
-
const file = await this.getFileById(id);
|
|
719
|
-
const bucket = this.bucket(file.bucket);
|
|
720
|
-
await this.fileRepository.deleteById(file.id);
|
|
721
|
-
try {
|
|
722
|
-
await bucket.delete(file.blobId, true);
|
|
723
|
-
} catch (e) {
|
|
724
|
-
if (e instanceof alepha_bucket.FileNotFoundError) this.log.debug(`File ${file.blobId} not found in bucket ${bucket.name}, cleaning up database record`);
|
|
725
|
-
else this.log.warn(`Failed to delete file ${file.blobId} from bucket ${bucket.name}`, e);
|
|
726
|
-
}
|
|
727
|
-
return {
|
|
728
|
-
ok: true,
|
|
729
|
-
id: String(file.id)
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Retrieves a file entity by its ID.
|
|
734
|
-
* If already an entity object, returns it as-is (convenience method).
|
|
735
|
-
*
|
|
736
|
-
* @param id - Either a UUID string or an existing FileEntity object
|
|
737
|
-
* @returns The file entity
|
|
738
|
-
* @throws {NotFoundError} If the file doesn't exist in the database
|
|
739
|
-
*/
|
|
740
|
-
async getFileById(id) {
|
|
741
|
-
if (typeof id === "object") return id;
|
|
742
|
-
return await this.fileRepository.findById(id);
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Gets storage statistics including total size, file count, and breakdowns by bucket and MIME type.
|
|
746
|
-
*
|
|
747
|
-
* @returns Storage statistics with aggregated data
|
|
748
|
-
*/
|
|
749
|
-
async getStorageStats() {
|
|
750
|
-
const allFiles = await this.fileRepository.findMany({});
|
|
751
|
-
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
|
|
752
|
-
const totalFiles = allFiles.length;
|
|
753
|
-
const bucketMap = /* @__PURE__ */ new Map();
|
|
754
|
-
for (const file of allFiles) {
|
|
755
|
-
const existing = bucketMap.get(file.bucket) || {
|
|
756
|
-
totalSize: 0,
|
|
757
|
-
fileCount: 0
|
|
758
|
-
};
|
|
759
|
-
existing.totalSize += file.size;
|
|
760
|
-
existing.fileCount += 1;
|
|
761
|
-
bucketMap.set(file.bucket, existing);
|
|
762
|
-
}
|
|
763
|
-
const mimeTypeMap = /* @__PURE__ */ new Map();
|
|
764
|
-
for (const file of allFiles) {
|
|
765
|
-
const existing = mimeTypeMap.get(file.mimeType) || 0;
|
|
766
|
-
mimeTypeMap.set(file.mimeType, existing + 1);
|
|
767
|
-
}
|
|
768
|
-
return {
|
|
769
|
-
totalSize,
|
|
770
|
-
totalFiles,
|
|
771
|
-
byBucket: Array.from(bucketMap.entries()).map(([bucket, stats]) => ({
|
|
772
|
-
bucket,
|
|
773
|
-
totalSize: stats.totalSize,
|
|
774
|
-
fileCount: stats.fileCount
|
|
775
|
-
})),
|
|
776
|
-
byMimeType: Array.from(mimeTypeMap.entries()).map(([mimeType, fileCount]) => ({
|
|
777
|
-
mimeType,
|
|
778
|
-
fileCount
|
|
779
|
-
}))
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Converts a file entity to a file resource (API response format).
|
|
784
|
-
* Currently a pass-through, but allows for future transformation logic.
|
|
785
|
-
*
|
|
786
|
-
* @param entity - The file entity to convert
|
|
787
|
-
* @returns The file resource for API responses
|
|
788
|
-
*/
|
|
789
|
-
entityToResource(entity) {
|
|
790
|
-
return entity;
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
//#endregion
|
|
795
|
-
//#region src/api-files/controllers/FileController.ts
|
|
796
|
-
/**
|
|
797
|
-
* REST API controller for file management operations.
|
|
798
|
-
* Provides endpoints for uploading, downloading, listing, and deleting files.
|
|
799
|
-
*/
|
|
800
|
-
var FileController = class {
|
|
801
|
-
url = "/files";
|
|
802
|
-
group = "files";
|
|
803
|
-
fileService = (0, alepha.$inject)(FileService);
|
|
804
|
-
/**
|
|
805
|
-
* GET /files - Lists files with optional filtering and pagination.
|
|
806
|
-
* Supports filtering by bucket and tags.
|
|
807
|
-
*/
|
|
808
|
-
findFiles = (0, alepha_server.$action)({
|
|
809
|
-
path: this.url,
|
|
810
|
-
group: this.group,
|
|
811
|
-
description: "List files with filtering and pagination",
|
|
812
|
-
schema: {
|
|
813
|
-
query: fileQuerySchema,
|
|
814
|
-
response: alepha_orm.pg.page(fileResourceSchema)
|
|
815
|
-
},
|
|
816
|
-
handler: ({ query }) => this.fileService.findFiles(query)
|
|
817
|
-
});
|
|
818
|
-
/**
|
|
819
|
-
* DELETE /files/:id - Deletes a file from both storage and database.
|
|
820
|
-
* Removes the file from the bucket and cleans up the database record.
|
|
821
|
-
*/
|
|
822
|
-
deleteFile = (0, alepha_server.$action)({
|
|
823
|
-
method: "DELETE",
|
|
824
|
-
path: `${this.url}/:id`,
|
|
825
|
-
group: this.group,
|
|
826
|
-
description: "Delete a file",
|
|
827
|
-
schema: {
|
|
828
|
-
params: alepha.t.object({ id: alepha.t.uuid() }),
|
|
829
|
-
response: alepha_server.okSchema
|
|
830
|
-
},
|
|
831
|
-
handler: ({ params }) => this.fileService.deleteFile(params.id)
|
|
832
|
-
});
|
|
833
|
-
/**
|
|
834
|
-
* POST /files - Uploads a new file to storage.
|
|
835
|
-
* Creates a database record with metadata and calculates checksum.
|
|
836
|
-
* Optionally specify bucket and expiration date.
|
|
837
|
-
*/
|
|
838
|
-
uploadFile = (0, alepha_server.$action)({
|
|
839
|
-
path: this.url,
|
|
840
|
-
group: this.group,
|
|
841
|
-
description: "Upload a new file",
|
|
842
|
-
schema: {
|
|
843
|
-
body: alepha.t.object({ file: alepha.t.file() }),
|
|
844
|
-
query: alepha.t.object({
|
|
845
|
-
expirationDate: alepha.t.optional(alepha.t.datetime()),
|
|
846
|
-
bucket: alepha.t.optional(alepha.t.string())
|
|
847
|
-
}),
|
|
848
|
-
response: fileResourceSchema
|
|
849
|
-
},
|
|
850
|
-
handler: async ({ body, user, query }) => this.fileService.uploadFile(body.file, {
|
|
851
|
-
user,
|
|
852
|
-
...query
|
|
853
|
-
})
|
|
854
|
-
});
|
|
855
|
-
/**
|
|
856
|
-
* PATCH /files/:id - Updates file metadata.
|
|
857
|
-
* Allows updating name, tags, and expiration date without modifying file content.
|
|
858
|
-
*/
|
|
859
|
-
updateFile = (0, alepha_server.$action)({
|
|
860
|
-
method: "PATCH",
|
|
861
|
-
path: `${this.url}/:id`,
|
|
862
|
-
group: this.group,
|
|
863
|
-
description: "Update file metadata",
|
|
864
|
-
schema: {
|
|
865
|
-
params: alepha.t.object({ id: alepha.t.uuid() }),
|
|
866
|
-
body: alepha.t.object({
|
|
867
|
-
name: alepha.t.optional(alepha.t.string()),
|
|
868
|
-
tags: alepha.t.optional(alepha.t.array(alepha.t.string())),
|
|
869
|
-
expirationDate: alepha.t.optional(alepha.t.datetime())
|
|
870
|
-
}),
|
|
871
|
-
response: fileResourceSchema
|
|
872
|
-
},
|
|
873
|
-
handler: ({ params, body }) => this.fileService.updateFile(params.id, body)
|
|
874
|
-
});
|
|
875
|
-
/**
|
|
876
|
-
* GET /files/:id - Streams/downloads a file by its ID.
|
|
877
|
-
* Returns the file content with appropriate Content-Type header.
|
|
878
|
-
* Cached with ETag support for 1 year (immutable).
|
|
879
|
-
*/
|
|
880
|
-
streamFile = (0, alepha_server.$action)({
|
|
881
|
-
path: `${this.url}/:id`,
|
|
882
|
-
group: this.group,
|
|
883
|
-
description: "Download a file",
|
|
884
|
-
cache: {
|
|
885
|
-
etag: true,
|
|
886
|
-
control: {
|
|
887
|
-
public: true,
|
|
888
|
-
maxAge: [1, "year"],
|
|
889
|
-
immutable: true
|
|
890
|
-
}
|
|
891
|
-
},
|
|
892
|
-
schema: {
|
|
893
|
-
params: alepha.t.object({ id: alepha.t.uuid() }),
|
|
894
|
-
response: alepha.t.file()
|
|
895
|
-
},
|
|
896
|
-
handler: async ({ params }) => {
|
|
897
|
-
return await this.fileService.streamFile(params.id);
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
//#endregion
|
|
903
|
-
//#region src/server-security/providers/ServerBasicAuthProvider.ts
|
|
904
|
-
var ServerBasicAuthProvider = class {
|
|
905
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
906
|
-
log = (0, alepha_logger.$logger)();
|
|
907
|
-
routerProvider = (0, alepha.$inject)(alepha_server.ServerRouterProvider);
|
|
908
|
-
realm = "Secure Area";
|
|
909
|
-
/**
|
|
910
|
-
* Registered basic auth descriptors with their configurations
|
|
911
|
-
*/
|
|
912
|
-
registeredAuths = [];
|
|
913
|
-
/**
|
|
914
|
-
* Register a basic auth configuration (called by descriptors)
|
|
915
|
-
*/
|
|
916
|
-
registerAuth(config) {
|
|
917
|
-
this.registeredAuths.push(config);
|
|
918
|
-
}
|
|
919
|
-
onStart = (0, alepha.$hook)({
|
|
920
|
-
on: "start",
|
|
921
|
-
handler: async () => {
|
|
922
|
-
for (const auth of this.registeredAuths) if (auth.paths) for (const pattern of auth.paths) {
|
|
923
|
-
const matchedRoutes = this.routerProvider.getRoutes(pattern);
|
|
924
|
-
for (const route of matchedRoutes) route.secure = { basic: {
|
|
925
|
-
username: auth.username,
|
|
926
|
-
password: auth.password
|
|
927
|
-
} };
|
|
928
|
-
}
|
|
929
|
-
if (this.registeredAuths.length > 0) this.log.info(`Initialized with ${this.registeredAuths.length} registered basic-auth configurations.`);
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
/**
|
|
933
|
-
* Hook into server:onRequest to check basic auth
|
|
934
|
-
*/
|
|
935
|
-
onRequest = (0, alepha.$hook)({
|
|
936
|
-
on: "server:onRequest",
|
|
937
|
-
handler: async ({ route, request }) => {
|
|
938
|
-
const routeAuth = route.secure;
|
|
939
|
-
if (typeof routeAuth === "object" && "basic" in routeAuth && routeAuth.basic) this.checkAuth(request, routeAuth.basic);
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
/**
|
|
943
|
-
* Hook into action:onRequest to check basic auth for actions
|
|
944
|
-
*/
|
|
945
|
-
onActionRequest = (0, alepha.$hook)({
|
|
946
|
-
on: "action:onRequest",
|
|
947
|
-
handler: async ({ action, request }) => {
|
|
948
|
-
const routeAuth = action.route.secure;
|
|
949
|
-
if (isBasicAuth(routeAuth)) this.checkAuth(request, routeAuth.basic);
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
/**
|
|
953
|
-
* Check basic authentication
|
|
954
|
-
*/
|
|
955
|
-
checkAuth(request, options) {
|
|
956
|
-
const authHeader = request.headers?.authorization;
|
|
957
|
-
if (!authHeader || !authHeader.startsWith("Basic ")) {
|
|
958
|
-
this.sendAuthRequired(request);
|
|
959
|
-
throw new alepha_server.HttpError({
|
|
960
|
-
status: 401,
|
|
961
|
-
message: "Authentication required"
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
const base64Credentials = authHeader.slice(6);
|
|
965
|
-
const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
|
|
966
|
-
const colonIndex = credentials.indexOf(":");
|
|
967
|
-
const username = colonIndex !== -1 ? credentials.slice(0, colonIndex) : credentials;
|
|
968
|
-
const password = colonIndex !== -1 ? credentials.slice(colonIndex + 1) : "";
|
|
969
|
-
if (!this.timingSafeCredentialCheck(username, password, options.username, options.password)) {
|
|
970
|
-
this.sendAuthRequired(request);
|
|
971
|
-
this.log.warn(`Failed basic auth attempt for user`, { username });
|
|
972
|
-
throw new alepha_server.HttpError({
|
|
973
|
-
status: 401,
|
|
974
|
-
message: "Invalid credentials"
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Performs a timing-safe comparison of credentials to prevent timing attacks.
|
|
980
|
-
* Always compares both username and password to avoid leaking which one is wrong.
|
|
981
|
-
*/
|
|
982
|
-
timingSafeCredentialCheck(inputUsername, inputPassword, expectedUsername, expectedPassword) {
|
|
983
|
-
const inputUserBuf = Buffer.from(inputUsername, "utf-8");
|
|
984
|
-
const expectedUserBuf = Buffer.from(expectedUsername, "utf-8");
|
|
985
|
-
const inputPassBuf = Buffer.from(inputPassword, "utf-8");
|
|
986
|
-
const expectedPassBuf = Buffer.from(expectedPassword, "utf-8");
|
|
987
|
-
return (this.safeCompare(inputUserBuf, expectedUserBuf) & this.safeCompare(inputPassBuf, expectedPassBuf)) === 1;
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Compares two buffers in constant time, handling different lengths safely.
|
|
991
|
-
* Returns 1 if equal, 0 if not equal.
|
|
992
|
-
*/
|
|
993
|
-
safeCompare(input, expected) {
|
|
994
|
-
if (input.length !== expected.length) {
|
|
995
|
-
(0, node_crypto.timingSafeEqual)(input, input);
|
|
996
|
-
return 0;
|
|
997
|
-
}
|
|
998
|
-
return (0, node_crypto.timingSafeEqual)(input, expected) ? 1 : 0;
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Send WWW-Authenticate header
|
|
1002
|
-
*/
|
|
1003
|
-
sendAuthRequired(request) {
|
|
1004
|
-
request.reply.setHeader("WWW-Authenticate", `Basic realm="${this.realm}"`);
|
|
1005
|
-
}
|
|
1006
|
-
};
|
|
1007
|
-
const isBasicAuth = (value) => {
|
|
1008
|
-
return typeof value === "object" && !!value && "basic" in value && !!value.basic;
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
//#endregion
|
|
1012
|
-
//#region src/server-security/descriptors/$basicAuth.ts
|
|
1013
|
-
/**
|
|
1014
|
-
* Declares HTTP Basic Authentication for server routes.
|
|
1015
|
-
* This descriptor provides methods to protect routes with username/password authentication.
|
|
1016
|
-
*/
|
|
1017
|
-
const $basicAuth = (options) => {
|
|
1018
|
-
return (0, alepha.createDescriptor)(BasicAuthDescriptor, options);
|
|
1019
|
-
};
|
|
1020
|
-
var BasicAuthDescriptor = class extends alepha.Descriptor {
|
|
1021
|
-
serverBasicAuthProvider = (0, alepha.$inject)(ServerBasicAuthProvider);
|
|
1022
|
-
get name() {
|
|
1023
|
-
return this.options.name ?? `${this.config.propertyKey}`;
|
|
1024
|
-
}
|
|
1025
|
-
onInit() {
|
|
1026
|
-
this.serverBasicAuthProvider.registerAuth(this.options);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Checks basic auth for the given request using this descriptor's configuration.
|
|
1030
|
-
*/
|
|
1031
|
-
check(request, options) {
|
|
1032
|
-
const mergedOptions = {
|
|
1033
|
-
...this.options,
|
|
1034
|
-
...options
|
|
1035
|
-
};
|
|
1036
|
-
this.serverBasicAuthProvider.checkAuth(request, mergedOptions);
|
|
1037
|
-
}
|
|
1038
|
-
};
|
|
1039
|
-
$basicAuth[alepha.KIND] = BasicAuthDescriptor;
|
|
1040
|
-
|
|
1041
|
-
//#endregion
|
|
1042
|
-
//#region src/server-security/providers/ServerSecurityProvider.ts
|
|
1043
|
-
var ServerSecurityProvider = class {
|
|
1044
|
-
log = (0, alepha_logger.$logger)();
|
|
1045
|
-
securityProvider = (0, alepha.$inject)(alepha_security.SecurityProvider);
|
|
1046
|
-
jwtProvider = (0, alepha.$inject)(alepha_security.JwtProvider);
|
|
1047
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
1048
|
-
onConfigure = (0, alepha.$hook)({
|
|
1049
|
-
on: "configure",
|
|
1050
|
-
handler: async () => {
|
|
1051
|
-
for (const action of this.alepha.descriptors(alepha_server.$action)) {
|
|
1052
|
-
if (action.options.disabled || action.options.secure === false || this.securityProvider.getRealms().length === 0) continue;
|
|
1053
|
-
if (typeof action.options.secure !== "object") this.securityProvider.createPermission({
|
|
1054
|
-
name: action.name,
|
|
1055
|
-
group: action.group,
|
|
1056
|
-
method: action.route.method,
|
|
1057
|
-
path: action.route.path
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
onActionRequest = (0, alepha.$hook)({
|
|
1063
|
-
on: "action:onRequest",
|
|
1064
|
-
handler: async ({ action, request, options }) => {
|
|
1065
|
-
if (action.options.secure === false && !options.user) {
|
|
1066
|
-
this.log.trace("Skipping security check for route");
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
if (isBasicAuth(action.route.secure)) return;
|
|
1070
|
-
const permission = this.securityProvider.getPermissions().find((it) => it.path === action.route.path && it.method === action.route.method);
|
|
1071
|
-
try {
|
|
1072
|
-
request.user = this.createUserFromLocalFunctionContext(options, permission);
|
|
1073
|
-
const route = action.route;
|
|
1074
|
-
if (typeof route.secure === "object") this.check(request.user, route.secure);
|
|
1075
|
-
this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(alepha_security.userAccountInfoSchema, request.user));
|
|
1076
|
-
} catch (error) {
|
|
1077
|
-
if (action.options.secure || permission) throw error;
|
|
1078
|
-
this.log.trace("Skipping security check for action");
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
onRequest = (0, alepha.$hook)({
|
|
1083
|
-
on: "server:onRequest",
|
|
1084
|
-
priority: "last",
|
|
1085
|
-
handler: async ({ request, route }) => {
|
|
1086
|
-
if (route.secure === false) {
|
|
1087
|
-
this.log.trace("Skipping security check for route - explicitly disabled");
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
if (isBasicAuth(route.secure)) return;
|
|
1091
|
-
const permission = this.securityProvider.getPermissions().find((it) => it.path === route.path && it.method === route.method);
|
|
1092
|
-
if (!request.headers.authorization && !route.secure && !permission) {
|
|
1093
|
-
this.log.trace("Skipping security check for route - no authorization header and not secure");
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
try {
|
|
1097
|
-
request.user = await this.securityProvider.createUserFromToken(request.headers.authorization, { permission });
|
|
1098
|
-
if (typeof route.secure === "object") this.check(request.user, route.secure);
|
|
1099
|
-
this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(alepha_security.userAccountInfoSchema, request.user));
|
|
1100
|
-
this.log.trace("User set from request token", {
|
|
1101
|
-
user: request.user,
|
|
1102
|
-
permission
|
|
1103
|
-
});
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
if (route.secure || permission) throw error;
|
|
1106
|
-
this.log.trace("Skipping security check for route - error occurred", error);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
|
-
check(user, secure) {
|
|
1111
|
-
if (secure.realm) {
|
|
1112
|
-
if (user.realm !== secure.realm) throw new alepha_server.ForbiddenError(`User must belong to realm '${secure.realm}' to access this route`);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Get the user account token for a local action call.
|
|
1117
|
-
* There are three possible sources for the user:
|
|
1118
|
-
* - `options.user`: the user passed in the options
|
|
1119
|
-
* - `"system"`: the system user from the state (you MUST set state `server.security.system.user`)
|
|
1120
|
-
* - `"context"`: the user from the request context (you MUST be in an HTTP request context)
|
|
1121
|
-
*
|
|
1122
|
-
* Priority order: `options.user` > `"system"` > `"context"`.
|
|
1123
|
-
*
|
|
1124
|
-
* In testing environment, if no user is provided, a test user is created based on the SecurityProvider's roles.
|
|
1125
|
-
*/
|
|
1126
|
-
createUserFromLocalFunctionContext(options, permission) {
|
|
1127
|
-
const fromOptions = typeof options.user === "object" ? options.user : void 0;
|
|
1128
|
-
const type = typeof options.user === "string" ? options.user : void 0;
|
|
1129
|
-
let user;
|
|
1130
|
-
const fromContext = this.alepha.context.get("request")?.user;
|
|
1131
|
-
const fromSystem = this.alepha.state.get("alepha.server.security.system.user");
|
|
1132
|
-
if (type === "system") user = fromSystem;
|
|
1133
|
-
else if (type === "context") user = fromContext;
|
|
1134
|
-
else user = fromOptions ?? fromContext ?? fromSystem;
|
|
1135
|
-
if (!user) {
|
|
1136
|
-
if (this.alepha.isTest() && !("user" in options)) return this.createTestUser();
|
|
1137
|
-
throw new alepha_server.UnauthorizedError("User is required for calling this action");
|
|
1138
|
-
}
|
|
1139
|
-
const roles = user.roles ?? (this.alepha.isTest() ? this.securityProvider.getRoles().map((role) => role.name) : []);
|
|
1140
|
-
let ownership;
|
|
1141
|
-
if (permission) {
|
|
1142
|
-
const result = this.securityProvider.checkPermission(permission, ...roles);
|
|
1143
|
-
if (!result.isAuthorized) throw new alepha_server.ForbiddenError(`Permission '${this.securityProvider.permissionToString(permission)}' is required for this route`);
|
|
1144
|
-
ownership = result.ownership;
|
|
1145
|
-
}
|
|
1146
|
-
return {
|
|
1147
|
-
...user,
|
|
1148
|
-
ownership
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
createTestUser() {
|
|
1152
|
-
return {
|
|
1153
|
-
id: (0, node_crypto.randomUUID)(),
|
|
1154
|
-
name: "Test",
|
|
1155
|
-
roles: this.securityProvider.getRoles().map((role) => role.name)
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
onClientRequest = (0, alepha.$hook)({
|
|
1159
|
-
on: "client:onRequest",
|
|
1160
|
-
handler: async ({ request, options }) => {
|
|
1161
|
-
if (!this.alepha.isTest()) return;
|
|
1162
|
-
if ("user" in options && options.user === void 0) return;
|
|
1163
|
-
request.headers = new Headers(request.headers);
|
|
1164
|
-
if (!request.headers.has("authorization")) {
|
|
1165
|
-
const test = this.createTestUser();
|
|
1166
|
-
const user = typeof options?.user === "object" ? options.user : void 0;
|
|
1167
|
-
const sub = user?.id ?? test.id;
|
|
1168
|
-
const roles = user?.roles ?? test.roles;
|
|
1169
|
-
const token = await this.jwtProvider.create({
|
|
1170
|
-
sub,
|
|
1171
|
-
roles
|
|
1172
|
-
}, user?.realm ?? this.securityProvider.getRealms()[0]?.name);
|
|
1173
|
-
request.headers.set("authorization", `Bearer ${token}`);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
};
|
|
1178
|
-
|
|
1179
|
-
//#endregion
|
|
1180
|
-
//#region src/server-security/index.ts
|
|
1181
|
-
/**
|
|
1182
|
-
* Plugin for Alepha Server that provides security features. Based on the Alepha Security module.
|
|
1183
|
-
*
|
|
1184
|
-
* By default, all $action will be guarded by a permission check.
|
|
1185
|
-
*
|
|
1186
|
-
* @see {@link ServerSecurityProvider}
|
|
1187
|
-
* @module alepha.server.security
|
|
1188
|
-
*/
|
|
1189
|
-
const AlephaServerSecurity = (0, alepha.$module)({
|
|
1190
|
-
name: "alepha.server.security",
|
|
1191
|
-
descriptors: [
|
|
1192
|
-
alepha_security.$realm,
|
|
1193
|
-
alepha_security.$role,
|
|
1194
|
-
alepha_security.$permission,
|
|
1195
|
-
$basicAuth
|
|
1196
|
-
],
|
|
1197
|
-
services: [
|
|
1198
|
-
alepha_server.AlephaServer,
|
|
1199
|
-
alepha_security.AlephaSecurity,
|
|
1200
|
-
ServerSecurityProvider,
|
|
1201
|
-
ServerBasicAuthProvider
|
|
1202
|
-
]
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
//#endregion
|
|
1206
|
-
//#region src/api-files/schemas/storageStatsSchema.ts
|
|
1207
|
-
const bucketStatsSchema = alepha.t.object({
|
|
1208
|
-
bucket: alepha.t.string(),
|
|
1209
|
-
totalSize: alepha.t.number(),
|
|
1210
|
-
fileCount: alepha.t.number()
|
|
1211
|
-
});
|
|
1212
|
-
const mimeTypeStatsSchema = alepha.t.object({
|
|
1213
|
-
mimeType: alepha.t.string(),
|
|
1214
|
-
fileCount: alepha.t.number()
|
|
1215
|
-
});
|
|
1216
|
-
const storageStatsSchema = alepha.t.object({
|
|
1217
|
-
totalSize: alepha.t.number(),
|
|
1218
|
-
totalFiles: alepha.t.number(),
|
|
1219
|
-
byBucket: alepha.t.array(bucketStatsSchema),
|
|
1220
|
-
byMimeType: alepha.t.array(mimeTypeStatsSchema)
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
//#endregion
|
|
1224
|
-
//#region src/api-files/controllers/StorageStatsController.ts
|
|
1225
|
-
/**
|
|
1226
|
-
* REST API controller for storage analytics and statistics.
|
|
1227
|
-
* Provides endpoints for viewing storage usage metrics.
|
|
1228
|
-
*/
|
|
1229
|
-
var StorageStatsController = class {
|
|
1230
|
-
url = "/files/stats";
|
|
1231
|
-
group = "files";
|
|
1232
|
-
fileService = (0, alepha.$inject)(FileService);
|
|
1233
|
-
/**
|
|
1234
|
-
* GET /files/stats - Gets storage statistics.
|
|
1235
|
-
* Returns aggregated data including total size, file count,
|
|
1236
|
-
* and breakdowns by bucket and MIME type.
|
|
1237
|
-
*/
|
|
1238
|
-
getStats = (0, alepha_server.$action)({
|
|
1239
|
-
path: this.url,
|
|
1240
|
-
group: this.group,
|
|
1241
|
-
description: "Get storage statistics",
|
|
1242
|
-
schema: { response: storageStatsSchema },
|
|
1243
|
-
handler: () => this.fileService.getStorageStats()
|
|
1244
|
-
});
|
|
1245
|
-
};
|
|
1246
|
-
|
|
1247
|
-
//#endregion
|
|
1248
|
-
//#region src/api-files/jobs/FileJobs.ts
|
|
1249
|
-
var FileJobs = class {
|
|
1250
|
-
fileService = (0, alepha.$inject)(FileService);
|
|
1251
|
-
purgeFiles = (0, alepha_scheduler.$scheduler)({
|
|
1252
|
-
description: "Purge files that are marked for deletion",
|
|
1253
|
-
cron: "*/15 * * * *",
|
|
1254
|
-
handler: async () => {
|
|
1255
|
-
const files$1 = await this.fileService.findExpiredFiles();
|
|
1256
|
-
await Promise.all(files$1.map((file) => this.fileService.deleteFile(file.id)));
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1261
|
-
//#endregion
|
|
1262
|
-
//#region src/api-files/index.ts
|
|
1263
|
-
/**
|
|
1264
|
-
* Provides file management API endpoints for Alepha applications.
|
|
1265
|
-
*
|
|
1266
|
-
* This module includes file upload, download, storage management,
|
|
1267
|
-
* and file metadata operations.
|
|
1268
|
-
*
|
|
1269
|
-
* @module alepha.api.files
|
|
1270
|
-
*/
|
|
1271
|
-
const AlephaApiFiles = (0, alepha.$module)({
|
|
1272
|
-
name: "alepha.api.files",
|
|
1273
|
-
services: [
|
|
1274
|
-
FileController,
|
|
1275
|
-
StorageStatsController,
|
|
1276
|
-
FileJobs,
|
|
1277
|
-
FileService
|
|
1278
|
-
],
|
|
1279
|
-
register: (alepha$1) => {
|
|
1280
|
-
alepha$1.with(alepha_bucket.AlephaBucket).with(AlephaServerCache).with(AlephaServerMultipart).with(FileController).with(StorageStatsController).with(FileJobs);
|
|
1281
|
-
}
|
|
1282
|
-
});
|
|
1283
|
-
|
|
1284
|
-
//#endregion
|
|
1285
|
-
exports.AlephaApiFiles = AlephaApiFiles;
|
|
1286
|
-
exports.FileController = FileController;
|
|
1287
|
-
exports.FileService = FileService;
|
|
1288
|
-
exports.StorageStatsController = StorageStatsController;
|
|
1289
|
-
exports.bucketStatsSchema = bucketStatsSchema;
|
|
1290
|
-
exports.files = files;
|
|
1291
|
-
exports.mimeTypeStatsSchema = mimeTypeStatsSchema;
|
|
1292
|
-
exports.storageStatsSchema = storageStatsSchema;
|
|
1293
|
-
//# sourceMappingURL=index.cjs.map
|