alepha 0.20.2 → 0.20.4
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 +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +2 -61
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +4 -4
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +12 -68
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +57 -4
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.browser.js +6 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +148 -227
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +60 -14
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.d.ts +77 -107
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +153 -5
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +12 -2
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +26 -0
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +11 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +11 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts +7 -5
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -3
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +637 -11660
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +707 -532
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +4 -8
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +20 -16
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +51 -77
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +65 -15
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +10 -13
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +30 -12
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +27 -3
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +8 -11
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -3
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -3
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +69 -10
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +135 -13
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +130 -16
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +30 -2
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +35 -12
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +238 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +198 -67
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +2 -362
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +18 -409
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +41 -194
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +27 -422
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +1 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.d.ts +102 -1
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +65 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +6 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +7 -7
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +7 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +6 -0
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +98 -4
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +58 -5
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +122 -6
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
- package/dist/react/testing/index.js +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +195 -1
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +64 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +2 -2
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +24 -10
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js +10 -3
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +47 -9
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js +19 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +4 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +32 -5
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +42 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +685 -274
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/parameters/services/ParameterProvider.ts +21 -4
- package/src/api/users/__tests__/SessionService.spec.ts +99 -0
- package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/jobs/UserJobs.ts +44 -17
- package/src/api/users/providers/RealmProvider.ts +4 -0
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/SessionService.ts +27 -0
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
- package/src/bucket/index.ts +19 -2
- package/src/bucket/primitives/$bucket.ts +9 -1
- package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
- package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
- package/src/cache/core/index.ts +29 -0
- package/src/cache/core/primitives/$cache.ts +14 -1
- package/src/cli/config/defineConfig.ts +13 -15
- package/src/cli/core/__tests__/init.spec.ts +214 -7
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +315 -33
- package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/agentMd.ts +2 -10
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/devtools/index.ts +12 -26
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/cli/platform/index.ts +15 -24
- package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
- package/src/cli/vendor/index.ts +14 -23
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/Alepha.ts +11 -1
- package/src/core/helpers/ref.ts +18 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/SchemaValidator.ts +9 -1
- package/src/core/providers/TypeProvider.ts +2 -3
- package/src/datetime/REFACTORING.md +118 -0
- package/src/datetime/providers/DateTimeProvider.ts +203 -24
- package/src/lock/core/index.ts +31 -0
- package/src/lock/core/primitives/$lock.ts +14 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
- package/src/mcp/helpers/jsonrpc.ts +26 -1
- package/src/mcp/index.ts +10 -5
- package/src/mcp/interfaces/McpTypes.ts +83 -6
- package/src/mcp/primitives/$prompt.ts +18 -1
- package/src/mcp/primitives/$resource.ts +18 -1
- package/src/mcp/primitives/$tool.ts +83 -7
- package/src/mcp/providers/McpServerProvider.ts +74 -16
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
- package/src/orm/REFACTORING.md +330 -0
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/primitives/$transactional.ts +11 -0
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/schemas/updateSchema.ts +1 -1
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/PgRelationManager.ts +4 -2
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
- package/src/react/core/hooks/useQuery.ts +153 -0
- package/src/react/core/index.ts +1 -0
- package/src/react/form/services/FormModel.ts +15 -6
- package/src/react/form/services/parseField.ts +8 -0
- package/src/react/i18n/providers/I18nProvider.ts +8 -2
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/$page.spec.tsx +0 -16
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/__tests__/ssr.spec.tsx +339 -0
- package/src/react/router/primitives/$page.ts +28 -4
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPageProvider.ts +27 -9
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
- package/src/react/ui/index.ts +6 -0
- package/src/react/ui/services/SchemaControl.ts +209 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/security/primitives/$issuer.ts +6 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
- package/src/server/core/errors/ValidationError.ts +13 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/primitives/$action.ts +16 -5
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +28 -6
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
- package/src/websocket/services/WebSocketClient.ts +11 -5
- package/src/mcp/transports/SseMcpTransport.ts +0 -182
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- package/src/orm/core/primitives/$view.ts +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/verifications/entities/verifications.ts","../../../src/api/verifications/schemas/verificationSettingsSchema.ts","../../../src/api/verifications/parameters/VerificationParameters.ts","../../../src/api/verifications/schemas/requestVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/validateVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/verificationTypeEnumSchema.ts","../../../src/api/verifications/services/VerificationService.ts","../../../src/api/verifications/controllers/VerificationController.ts","../../../src/api/verifications/jobs/VerificationJobs.ts","../../../src/api/verifications/index.ts"],"mappings":";;;;;;;;;cAKa,aAAA,EAAa,aAAA,CAAA,eAAA,UAAA,OAAA;gDAwCxB,QAAA,CAAA,OAAA;;;;;;;;;;cAEW,wBAAA,WAAwB,OAAA;gDAAuB,QAAA,CAAA,OAAA;;;;;;;;;;cAC/C,8BAAA,EAA8B,aAAA,CAAA,aAAA,UAAA,OAAA;gDAA6B,QAAA,CAAA,OAAA;;;;;;;;;;KAC5D,kBAAA,GAAqB,MAAA,QAAc,aAAA,CAAc,MAAA;;;cC9ChD,0BAAA,WAA0B,OAAA;;iBAsErC,QAAA,CAAA,QAAA;;;;;;;;;;;;;;KAEU,oBAAA,GAAuB,MAAA,QAAc,0BAAA;;;;;;cChEpC,mBAAA,EAAmB,QAAA,CAAA,IAAA,UAAA,OAAA;;iBAmB9B,QAAA,CAAA,QAAA;;;;;;;;;;;;;;KAEU,mBAAA,GAAsB,MAAA,QAAc,mBAAA,CAAoB,MAAA;AAAA;EAAA,UAGxD,KAAA;IAAA,CACP,mBAAA,CAAoB,GAAA,GAAM,mBAAA;EAAA;AAAA;AAAA,cAMlB,sBAAA;EAAA,mBACQ,OAAA,EAAO,QAAA;;;;;;;;;;;;;;;;EAEnB,GAAA,iBAAoB,oBAAA,CAAA,CACzB,GAAA,EAAK,CAAA,GACJ,oBAAA,CAAqB,CAAA;AAAA;;;cC5Cb,qCAAA,WAAqC,OAAA;SAgBhD,QAAA,CAAA,OAAA;;;;;KAEU,2BAAA,GAA8B,MAAA,QACjC,qCAAA;;;cCnBI,sCAAA,WAAsC,OAAA;MASjD,QAAA,CAAA,QAAA;;;KAEU,gCAAA,GAAmC,MAAA,QACtC,sCAAA;;;cCZI,0BAAA,EAAqD,QAAA,CAA3B,OAAA;AAAA,KAC3B,oBAAA,GAAuB,MAAA,QAAc,0BAAA;;;cCWpC,mBAAA;EAAA,mBACQ,GAAA,EADW,gBAAA,CACR,MAAA;EAAA,mBACH,gBAAA,EAAgB,gBAAA;EAAA,mBAChB,sBAAA,EAAsB,sBAAA;EAAA,mBACtB,sBAAA,EAAsB,aAAA,CAAA,UAAA,UAAA,OAAA;kDADA,QAAA,CAAA,OAAA;;;;;;;;;;EAG5B,WAAA,CACX,KAAA,EAAO,iBAAA,GACN,OAAA,CAAQ,kBAAA;EAmCJ,kBAAA,CAAmB,KAAA,EAAO,iBAAA,GAAiB,OAAA,eAAA,QAAA,UAAA,OAAA;kDAAA,QAAA,CAAA,OAAA;;;;;;;;;;kDAAA,QAAA,CAAA,OAAA;;;;;;;;;;ENrD1B;;;;;EM+EX,kBAAA,CACX,KAAA,EAAO,iBAAA,GACN,OAAA,CAAQ,2BAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/verifications/entities/verifications.ts","../../../src/api/verifications/schemas/verificationSettingsSchema.ts","../../../src/api/verifications/parameters/VerificationParameters.ts","../../../src/api/verifications/schemas/requestVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/validateVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/verificationTypeEnumSchema.ts","../../../src/api/verifications/services/VerificationService.ts","../../../src/api/verifications/controllers/VerificationController.ts","../../../src/api/verifications/jobs/VerificationJobs.ts","../../../src/api/verifications/index.ts"],"mappings":";;;;;;;;;cAKa,aAAA,EAAa,aAAA,CAAA,eAAA,UAAA,OAAA;gDAwCxB,QAAA,CAAA,OAAA;;;;;;;;;;cAEW,wBAAA,WAAwB,OAAA;gDAAuB,QAAA,CAAA,OAAA;;;;;;;;;;cAC/C,8BAAA,EAA8B,aAAA,CAAA,aAAA,UAAA,OAAA;gDAA6B,QAAA,CAAA,OAAA;;;;;;;;;;KAC5D,kBAAA,GAAqB,MAAA,QAAc,aAAA,CAAc,MAAA;;;cC9ChD,0BAAA,WAA0B,OAAA;;iBAsErC,QAAA,CAAA,QAAA;;;;;;;;;;;;;;KAEU,oBAAA,GAAuB,MAAA,QAAc,0BAAA;;;;;;cChEpC,mBAAA,EAAmB,QAAA,CAAA,IAAA,UAAA,OAAA;;iBAmB9B,QAAA,CAAA,QAAA;;;;;;;;;;;;;;KAEU,mBAAA,GAAsB,MAAA,QAAc,mBAAA,CAAoB,MAAA;AAAA;EAAA,UAGxD,KAAA;IAAA,CACP,mBAAA,CAAoB,GAAA,GAAM,mBAAA;EAAA;AAAA;AAAA,cAMlB,sBAAA;EAAA,mBACQ,OAAA,EAAO,QAAA;;;;;;;;;;;;;;;;EAEnB,GAAA,iBAAoB,oBAAA,CAAA,CACzB,GAAA,EAAK,CAAA,GACJ,oBAAA,CAAqB,CAAA;AAAA;;;cC5Cb,qCAAA,WAAqC,OAAA;SAgBhD,QAAA,CAAA,OAAA;;;;;KAEU,2BAAA,GAA8B,MAAA,QACjC,qCAAA;;;cCnBI,sCAAA,WAAsC,OAAA;MASjD,QAAA,CAAA,QAAA;;;KAEU,gCAAA,GAAmC,MAAA,QACtC,sCAAA;;;cCZI,0BAAA,EAAqD,QAAA,CAA3B,OAAA;AAAA,KAC3B,oBAAA,GAAuB,MAAA,QAAc,0BAAA;;;cCWpC,mBAAA;EAAA,mBACQ,GAAA,EADW,gBAAA,CACR,MAAA;EAAA,mBACH,gBAAA,EAAgB,gBAAA;EAAA,mBAChB,sBAAA,EAAsB,sBAAA;EAAA,mBACtB,sBAAA,EAAsB,aAAA,CAAA,UAAA,UAAA,OAAA;kDADA,QAAA,CAAA,OAAA;;;;;;;;;;EAG5B,WAAA,CACX,KAAA,EAAO,iBAAA,GACN,OAAA,CAAQ,kBAAA;EAmCJ,kBAAA,CAAmB,KAAA,EAAO,iBAAA,GAAiB,OAAA,eAAA,QAAA,UAAA,OAAA;kDAAA,QAAA,CAAA,OAAA;;;;;;;;;;kDAAA,QAAA,CAAA,OAAA;;;;;;;;;;ENrD1B;;;;;EM+EX,kBAAA,CACX,KAAA,EAAO,iBAAA,GACN,OAAA,CAAQ,2BAAA;EAoEE,UAAA,CACX,KAAA,EAAO,iBAAA,EACP,IAAA,WACC,OAAA,CAAQ,gCAAA;EA+EJ,QAAA,CAAS,IAAA;EAIT,aAAA,CAAc,IAAA,EAAM,oBAAA;AAAA;AAAA,UAcZ,iBAAA;EACf,IAAA,EAAM,oBAAA;EACN,MAAA;AAAA;;;cCzPW,sBAAA;EAAA,mBACQ,mBAAA,EAAmB,mBAAA;EAAA,SAEtB,GAAA;EAAA,SACA,KAAA;EAAA,SAEA,uBAAA,mBAAuB,iBAAA;;YALD,QAAA,CAAA,OAAA;IAAA;;;;;;;;;;;WA0BtB,wBAAA,mBAAwB,iBAAA;;YArBD,QAAA,CAAA,OAAA;IAAA;;;;;;;;;;;;;cCN5B,gBAAA;EAAA,mBACQ,sBAAA,EAAsB,aAAA,CAAA,UAAA,UAAA,OAAA;kDADd,QAAA,CAAA,OAAA;;;;;;;;;;qBAER,sBAAA,EAAsB,sBAAA;EAAA,mBACtB,gBAAA,EAAgB,gBAAA;EAAA,SAEnB,YAAA,EAFmB,mBAAA,CAEP,kBAAA;AAAA;;;;ARP9B;;;;;;;;;;cSyBa,qBAAA,EAAqB,QAAA,CAAA,OAAA,CAQhC,QAAA,CARgC,MAAA"}
|
|
@@ -218,7 +218,8 @@ var VerificationService = class {
|
|
|
218
218
|
const verification = await this.verificationRepository.create({
|
|
219
219
|
type: entry.type,
|
|
220
220
|
target: entry.target,
|
|
221
|
-
code: this.hashCode(token)
|
|
221
|
+
code: this.hashCode(token),
|
|
222
|
+
createdAt: this.dateTimeProvider.nowISOString()
|
|
222
223
|
});
|
|
223
224
|
this.log.info("Verification created", {
|
|
224
225
|
id: verification.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/verifications/schemas/requestVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/validateVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/verificationTypeEnumSchema.ts","../../../src/api/verifications/entities/verifications.ts","../../../src/api/verifications/schemas/verificationSettingsSchema.ts","../../../src/api/verifications/parameters/VerificationParameters.ts","../../../src/api/verifications/services/VerificationService.ts","../../../src/api/verifications/controllers/VerificationController.ts","../../../src/api/verifications/jobs/VerificationJobs.ts","../../../src/api/verifications/index.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const requestVerificationCodeResponseSchema = t.object({\n token: t.string({\n description:\n \"The verification token (6-digit code for phone, UUID for email). The caller should send this to the user via their preferred notification method.\",\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before your verification token expires.\",\n }),\n verificationCooldown: t.integer({\n description:\n \"Cooldown period in seconds before you can request another verification.\",\n }),\n maxVerificationAttempts: t.integer({\n description:\n \"Maximum number of verification attempts allowed before the token is locked.\",\n }),\n});\n\nexport type RequestVerificationResponse = Static<\n typeof requestVerificationCodeResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const validateVerificationCodeResponseSchema = t.object({\n ok: t.boolean({\n description: \"Indicates whether the verification was successful.\",\n }),\n alreadyVerified: t.optional(\n t.boolean({\n description: \"Indicates whether the target was already verified.\",\n }),\n ),\n});\n\nexport type ValidateVerificationCodeResponse = Static<\n typeof validateVerificationCodeResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const verificationTypeEnumSchema = t.enum([\"code\", \"link\"]);\nexport type VerificationTypeEnum = Static<typeof verificationTypeEnumSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { verificationTypeEnumSchema } from \"../schemas/verificationTypeEnumSchema.ts\";\n\nexport const verifications = $entity({\n name: \"verification\",\n schema: t.object({\n id: db.primaryKey(t.bigint()),\n\n createdAt: db.createdAt(),\n\n updatedAt: db.updatedAt(),\n\n version: db.version(),\n\n type: verificationTypeEnumSchema,\n\n target: t.text({\n description: \"Can be a phone (E.164 format) or email address\",\n }),\n\n code: t.text({\n description: \"Hashed verification token (n-digit code or UUID)\",\n }),\n\n verifiedAt: t.optional(\n t.datetime({\n description: \"When it was successfully verified\",\n }),\n ),\n\n attempts: db.default(\n t.integer({\n description: \"Number of failed attempts (to prevent brute-force)\",\n }),\n 0,\n ),\n }),\n indexes: [\n \"createdAt\",\n {\n columns: [\"target\", \"code\"],\n },\n ],\n});\n\nexport const verificationEntitySchema = verifications.schema;\nexport const verificationEntityInsertSchema = verifications.insertSchema;\nexport type VerificationEntity = Static<typeof verifications.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const verificationSettingsSchema = t.object({\n code: t.object(\n {\n maxAttempts: t.integer({\n description:\n \"Maximum number of attempts before locking the verification.\",\n minimum: 1,\n maximum: 10,\n }),\n codeLength: t.integer({\n description: \"Length of the verification code.\",\n minimum: 4,\n maximum: 12,\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before the verification code expires.\",\n minimum: 60, // 1 minute\n maximum: 3600, // 1 hour\n }),\n verificationCooldown: t.integer({\n description: \"Cooldown period in seconds after a request verification.\",\n minimum: 0,\n maximum: 3600, // 1 hour\n }),\n limitPerDay: t.integer({\n description:\n \"Maximum number of verification requests per day for one entry.\",\n minimum: 1,\n maximum: 100,\n }),\n },\n {\n description: \"Settings specific to code verifications.\",\n },\n ),\n link: t.object(\n {\n maxAttempts: t.integer({\n description:\n \"Maximum number of attempts before locking the verification.\",\n minimum: 1,\n maximum: 10,\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before the verification token expires.\",\n minimum: 60, // 1 minute\n maximum: 7200, // 2 hours\n }),\n verificationCooldown: t.integer({\n description: \"Cooldown period in seconds after a request verification.\",\n minimum: 0,\n maximum: 3600, // 1 hour\n }),\n limitPerDay: t.integer({\n description:\n \"Maximum number of verification requests per day for one entry.\",\n minimum: 1,\n maximum: 100,\n }),\n },\n {\n description: \"Settings specific to link verifications.\",\n },\n ),\n purgeDays: t.integer({\n description:\n \"Number of days after which expired verifications are automatically deleted. Set to 0 to disable auto-deletion.\",\n minimum: 0,\n maximum: 365,\n }),\n});\n\nexport type VerificationSettings = Static<typeof verificationSettingsSchema>;\n","import { $atom, $state, type Static } from \"alepha\";\nimport {\n type VerificationSettings,\n verificationSettingsSchema,\n} from \"../schemas/verificationSettingsSchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Verification settings configuration atom\n */\nexport const verificationOptions = $atom({\n name: \"alepha.api.verifications.options\",\n schema: verificationSettingsSchema,\n default: {\n code: {\n maxAttempts: 5,\n codeLength: 6,\n codeExpiration: 300, // 5 minutes\n verificationCooldown: 90,\n limitPerDay: 10,\n },\n link: {\n maxAttempts: 3, // Lower since UUIDs are harder to guess\n codeExpiration: 1800, // 30 minutes\n verificationCooldown: 90,\n limitPerDay: 10,\n },\n purgeDays: 1,\n },\n});\n\nexport type VerificationOptions = Static<typeof verificationOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [verificationOptions.key]: VerificationOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class VerificationParameters {\n protected readonly options = $state(verificationOptions);\n\n public get<K extends keyof VerificationSettings>(\n key: K,\n ): VerificationSettings[K] {\n return this.options[key];\n }\n}\n","import { createHash, randomInt, randomUUID } from \"node:crypto\";\nimport { $inject } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { BadRequestError, NotFoundError } from \"alepha/server\";\nimport {\n type VerificationEntity,\n verifications,\n} from \"../entities/verifications.ts\";\nimport { VerificationParameters } from \"../parameters/VerificationParameters.ts\";\nimport type { RequestVerificationResponse } from \"../schemas/requestVerificationCodeResponseSchema.ts\";\nimport type { ValidateVerificationCodeResponse } from \"../schemas/validateVerificationCodeResponseSchema.ts\";\nimport type { VerificationTypeEnum } from \"../schemas/verificationTypeEnumSchema.ts\";\n\nexport class VerificationService {\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly verificationParameters = $inject(VerificationParameters);\n protected readonly verificationRepository = $repository(verifications);\n\n public async findByEntry(\n entry: VerificationEntry,\n ): Promise<VerificationEntity> {\n this.log.trace(\"Finding verification by entry\", {\n type: entry.type,\n target: entry.target,\n });\n\n const results = await this.verificationRepository.findMany({\n limit: 1, // only need the most recent entry\n orderBy: {\n column: \"createdAt\",\n direction: \"desc\",\n },\n where: {\n type: { eq: entry.type },\n target: { eq: entry.target },\n },\n });\n\n if (results.length === 0) {\n this.log.debug(\"Verification entry not found\", {\n type: entry.type,\n target: entry.target,\n });\n throw new NotFoundError(\"Verification entry not found\");\n }\n\n this.log.debug(\"Verification entry found\", {\n id: results[0].id,\n type: entry.type,\n target: entry.target,\n });\n\n return results[0];\n }\n\n public findRecentsByEntry(entry: VerificationEntry) {\n this.log.trace(\"Finding recent verifications by entry\", {\n type: entry.type,\n target: entry.target,\n });\n\n return this.verificationRepository.findMany({\n orderBy: {\n column: \"createdAt\",\n direction: \"desc\",\n },\n where: {\n type: { eq: entry.type },\n target: { eq: entry.target },\n createdAt: {\n gte: this.dateTimeProvider.now().startOf(\"day\").toISOString(),\n },\n },\n });\n }\n\n /**\n * Creates a verification entry and returns the token.\n * The caller is responsible for sending notifications with the token.\n * This allows for context-specific notifications (e.g., password reset vs email verification).\n */\n public async createVerification(\n entry: VerificationEntry,\n ): Promise<RequestVerificationResponse> {\n this.log.trace(\"Creating verification\", {\n type: entry.type,\n target: entry.target,\n });\n\n const settings = this.verificationParameters.get(entry.type);\n\n const recents = await this.findRecentsByEntry(entry);\n if (recents.length >= settings.limitPerDay) {\n this.log.warn(\"Daily verification limit reached\", {\n type: entry.type,\n target: entry.target,\n limit: settings.limitPerDay,\n count: recents.length,\n });\n throw new BadRequestError(\n `Maximum number of verification requests per day reached (${settings.limitPerDay})`,\n );\n }\n\n const existingVerification = recents[0];\n if (existingVerification) {\n const nowSec = this.dateTimeProvider.now().unix();\n const createdAtSec = this.dateTimeProvider\n .of(existingVerification.createdAt)\n .unix();\n\n const diffSec = nowSec - createdAtSec;\n if (diffSec < settings.verificationCooldown) {\n const remainingCooldown = Math.floor(\n settings.verificationCooldown - diffSec,\n );\n this.log.debug(\"Verification on cooldown\", {\n type: entry.type,\n target: entry.target,\n remainingSeconds: remainingCooldown,\n });\n throw new BadRequestError(\n `Verification is on cooldown for ${remainingCooldown} seconds`,\n );\n }\n }\n\n const token = this.generateToken(entry.type);\n\n const verification = await this.verificationRepository.create({\n type: entry.type,\n target: entry.target,\n code: this.hashCode(token),\n });\n\n this.log.info(\"Verification created\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n expiresInSeconds: settings.codeExpiration,\n });\n\n return {\n token,\n codeExpiration: settings.codeExpiration,\n verificationCooldown: settings.verificationCooldown,\n maxVerificationAttempts: settings.maxAttempts,\n };\n }\n\n public async verifyCode(\n entry: VerificationEntry,\n code: string,\n ): Promise<ValidateVerificationCodeResponse> {\n this.log.trace(\"Verifying code\", {\n type: entry.type,\n target: entry.target,\n });\n\n const settings = this.verificationParameters.get(entry.type);\n\n const verification = await this.findByEntry(entry);\n if (verification.verifiedAt) {\n this.log.debug(\"Verification already verified\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n verifiedAt: verification.verifiedAt,\n });\n return { ok: true, alreadyVerified: true };\n }\n\n // DO NOT DELETE THE VERIFICATION WHEN IT IS REJECTED,\n // or we won't be able to cooldown the verification\n\n const now = this.dateTimeProvider.now();\n const expirationDate = this.dateTimeProvider\n .of(verification.createdAt)\n .add(settings.codeExpiration, \"seconds\");\n\n if (now > expirationDate) {\n this.log.warn(\"Verification code expired\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n createdAt: verification.createdAt,\n expiredAt: expirationDate.toISOString(),\n });\n throw new BadRequestError(\"Verification code has expired\");\n }\n\n if (verification.attempts >= settings.maxAttempts) {\n this.log.warn(\"Verification locked due to max attempts\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n attempts: verification.attempts,\n maxAttempts: settings.maxAttempts,\n });\n throw new BadRequestError(\n \"Maximum number of attempts reached - verification is locked\",\n );\n }\n\n if (verification.code !== this.hashCode(code)) {\n const newAttempts = verification.attempts + 1;\n this.log.warn(\"Invalid verification code\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n attempts: newAttempts,\n maxAttempts: settings.maxAttempts,\n });\n await this.verificationRepository.updateById(verification.id, {\n attempts: newAttempts,\n });\n throw new BadRequestError(\"Invalid verification code\");\n }\n\n await this.verificationRepository.updateById(verification.id, {\n verifiedAt: this.dateTimeProvider.nowISOString(),\n });\n\n this.log.info(\"Verification code verified\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n });\n\n return { ok: true };\n }\n\n public hashCode(code: string): string {\n return createHash(\"sha256\").update(code).digest(\"hex\");\n }\n\n public generateToken(type: VerificationTypeEnum): string {\n if (type === \"code\") {\n const settings = this.verificationParameters.get(\"code\");\n return randomInt(0, 1_000_000)\n .toString()\n .padStart(settings.codeLength, \"0\");\n } else if (type === \"link\") {\n return randomUUID();\n }\n\n throw new BadRequestError(`Invalid verification type: ${type}`);\n }\n}\n\nexport interface VerificationEntry {\n type: VerificationTypeEnum;\n target: string;\n}\n","import { $inject, t } from \"alepha\";\nimport { $action } from \"alepha/server\";\nimport { requestVerificationCodeResponseSchema } from \"../schemas/requestVerificationCodeResponseSchema.ts\";\nimport { validateVerificationCodeResponseSchema } from \"../schemas/validateVerificationCodeResponseSchema.ts\";\nimport { verificationTypeEnumSchema } from \"../schemas/verificationTypeEnumSchema.ts\";\nimport { VerificationService } from \"../services/VerificationService.ts\";\n\nexport class VerificationController {\n protected readonly verificationService = $inject(VerificationService);\n\n public readonly url = \"/verifications\";\n public readonly group = \"verifications\";\n\n public readonly requestVerificationCode = $action({\n path: `${this.url}/:type`,\n group: this.group,\n method: \"POST\",\n schema: {\n params: t.object({\n type: verificationTypeEnumSchema,\n }),\n body: t.object({\n target: t.text(),\n }),\n response: requestVerificationCodeResponseSchema,\n },\n handler: async ({ body, params }) => {\n return await this.verificationService.createVerification({\n type: params.type,\n target: body.target,\n });\n },\n });\n\n public readonly validateVerificationCode = $action({\n path: `${this.url}/:type/validate`,\n group: this.group,\n method: \"POST\",\n schema: {\n params: t.object({\n type: verificationTypeEnumSchema,\n }),\n body: t.object({\n target: t.text(),\n token: t.text({\n description:\n \"The verification token (6-digit code for phone, UUID for email).\",\n }),\n }),\n response: validateVerificationCodeResponseSchema,\n },\n handler: async ({ body, params }) => {\n return this.verificationService.verifyCode(\n {\n type: params.type,\n target: body.target,\n },\n body.token,\n );\n },\n });\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $repository } from \"alepha/orm\";\nimport { $scheduler } from \"alepha/scheduler\";\nimport { verifications } from \"../entities/verifications.ts\";\nimport { VerificationParameters } from \"../parameters/VerificationParameters.ts\";\n\nexport class VerificationJobs {\n protected readonly verificationRepository = $repository(verifications);\n protected readonly verificationParameters = $inject(VerificationParameters);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public readonly cleanExpired = $scheduler({\n name: \"api:verifications:cleanExpired\",\n cron: \"0 * * * *\", // Hourly at minute 0\n description: \"Clean expired verifications\",\n handler: async () => {\n const purgeDays = this.verificationParameters.get(\"purgeDays\");\n if (purgeDays <= 0) {\n return; // Auto deletion is disabled\n }\n\n const dayMs = 24 * 60 * 60 * 1000;\n const purgeThreshold =\n this.dateTimeProvider.nowMillis() - purgeDays * dayMs;\n\n await this.verificationRepository.deleteMany({\n createdAt: {\n lt: this.dateTimeProvider.of(purgeThreshold).toISOString(),\n },\n });\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { VerificationController } from \"./controllers/VerificationController.ts\";\nimport { VerificationJobs } from \"./jobs/VerificationJobs.ts\";\nimport { VerificationParameters } from \"./parameters/VerificationParameters.ts\";\nimport { VerificationService } from \"./services/VerificationService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/VerificationController.ts\";\nexport * from \"./entities/verifications.ts\";\nexport * from \"./jobs/VerificationJobs.ts\";\nexport * from \"./parameters/VerificationParameters.ts\";\nexport * from \"./schemas/requestVerificationCodeResponseSchema.ts\";\nexport * from \"./schemas/validateVerificationCodeResponseSchema.ts\";\nexport * from \"./schemas/verificationTypeEnumSchema.ts\";\nexport * from \"./services/VerificationService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email and phone verification workflows.\n *\n * **Features:**\n * - Verification token generation\n * - Verification code sending\n * - Verification completion tracking\n * - Resend functionality\n *\n * @module alepha.api.verifications\n */\nexport const AlephaApiVerification = $module({\n name: \"alepha.api.verifications\",\n services: [\n VerificationController,\n VerificationJobs,\n VerificationService,\n VerificationParameters,\n ],\n});\n"],"mappings":";;;;;;;;AAGA,MAAa,wCAAwC,EAAE,OAAO;CAC5D,OAAO,EAAE,OAAO,EACd,aACE,qJACH,CAAC;CACF,gBAAgB,EAAE,QAAQ,EACxB,aAAa,2DACd,CAAC;CACF,sBAAsB,EAAE,QAAQ,EAC9B,aACE,2EACH,CAAC;CACF,yBAAyB,EAAE,QAAQ,EACjC,aACE,+EACH,CAAC;CACH,CAAC;;;AChBF,MAAa,yCAAyC,EAAE,OAAO;CAC7D,IAAI,EAAE,QAAQ,EACZ,aAAa,sDACd,CAAC;CACF,iBAAiB,EAAE,SACjB,EAAE,QAAQ,EACR,aAAa,sDACd,CAAC,CACH;CACF,CAAC;;;ACTF,MAAa,6BAA6B,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC;;;ACElE,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,QAAQ,EAAE,OAAO;EACf,IAAI,GAAG,WAAW,EAAE,QAAQ,CAAC;EAE7B,WAAW,GAAG,WAAW;EAEzB,WAAW,GAAG,WAAW;EAEzB,SAAS,GAAG,SAAS;EAErB,MAAM;EAEN,QAAQ,EAAE,KAAK,EACb,aAAa,kDACd,CAAC;EAEF,MAAM,EAAE,KAAK,EACX,aAAa,oDACd,CAAC;EAEF,YAAY,EAAE,SACZ,EAAE,SAAS,EACT,aAAa,qCACd,CAAC,CACH;EAED,UAAU,GAAG,QACX,EAAE,QAAQ,EACR,aAAa,sDACd,CAAC,EACF,EACD;EACF,CAAC;CACF,SAAS,CACP,aACA,EACE,SAAS,CAAC,UAAU,OAAO,EAC5B,CACF;CACF,CAAC;AAEF,MAAa,2BAA2B,cAAc;AACtD,MAAa,iCAAiC,cAAc;;;;;;AErC5D,MAAa,sBAAsB,MAAM;CACvC,MAAM;CACN,QDVwC,EAAE,OAAO;EACjD,MAAM,EAAE,OACN;GACE,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACF,YAAY,EAAE,QAAQ;IACpB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,gBAAgB,EAAE,QAAQ;IACxB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,sBAAsB,EAAE,QAAQ;IAC9B,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACH,EACD,EACE,aAAa,4CACd,CACF;EACD,MAAM,EAAE,OACN;GACE,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACF,gBAAgB,EAAE,QAAQ;IACxB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,sBAAsB,EAAE,QAAQ;IAC9B,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACH,EACD,EACE,aAAa,4CACd,CACF;EACD,WAAW,EAAE,QAAQ;GACnB,aACE;GACF,SAAS;GACT,SAAS;GACV,CAAC;EACH,CAAC;CC3DA,SAAS;EACP,MAAM;GACJ,aAAa;GACb,YAAY;GACZ,gBAAgB;GAChB,sBAAsB;GACtB,aAAa;GACd;EACD,MAAM;GACJ,aAAa;GACb,gBAAgB;GAChB,sBAAsB;GACtB,aAAa;GACd;EACD,WAAW;EACZ;CACF,CAAC;AAYF,IAAa,yBAAb,MAAoC;CAClC,UAA6B,OAAO,oBAAoB;CAExD,IACE,KACyB;AACzB,SAAO,KAAK,QAAQ;;;;;ACjCxB,IAAa,sBAAb,MAAiC;CAC/B,MAAyB,SAAS;CAClC,mBAAsC,QAAQ,iBAAiB;CAC/D,yBAA4C,QAAQ,uBAAuB;CAC3E,yBAA4C,YAAY,cAAc;CAEtE,MAAa,YACX,OAC6B;AAC7B,OAAK,IAAI,MAAM,iCAAiC;GAC9C,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,UAAU,MAAM,KAAK,uBAAuB,SAAS;GACzD,OAAO;GACP,SAAS;IACP,QAAQ;IACR,WAAW;IACZ;GACD,OAAO;IACL,MAAM,EAAE,IAAI,MAAM,MAAM;IACxB,QAAQ,EAAE,IAAI,MAAM,QAAQ;IAC7B;GACF,CAAC;AAEF,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAK,IAAI,MAAM,gCAAgC;IAC7C,MAAM,MAAM;IACZ,QAAQ,MAAM;IACf,CAAC;AACF,SAAM,IAAI,cAAc,+BAA+B;;AAGzD,OAAK,IAAI,MAAM,4BAA4B;GACzC,IAAI,QAAQ,GAAG;GACf,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,QAAQ;;CAGjB,mBAA0B,OAA0B;AAClD,OAAK,IAAI,MAAM,yCAAyC;GACtD,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,KAAK,uBAAuB,SAAS;GAC1C,SAAS;IACP,QAAQ;IACR,WAAW;IACZ;GACD,OAAO;IACL,MAAM,EAAE,IAAI,MAAM,MAAM;IACxB,QAAQ,EAAE,IAAI,MAAM,QAAQ;IAC5B,WAAW,EACT,KAAK,KAAK,iBAAiB,KAAK,CAAC,QAAQ,MAAM,CAAC,aAAa,EAC9D;IACF;GACF,CAAC;;;;;;;CAQJ,MAAa,mBACX,OACsC;AACtC,OAAK,IAAI,MAAM,yBAAyB;GACtC,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,WAAW,KAAK,uBAAuB,IAAI,MAAM,KAAK;EAE5D,MAAM,UAAU,MAAM,KAAK,mBAAmB,MAAM;AACpD,MAAI,QAAQ,UAAU,SAAS,aAAa;AAC1C,QAAK,IAAI,KAAK,oCAAoC;IAChD,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,OAAO,SAAS;IAChB,OAAO,QAAQ;IAChB,CAAC;AACF,SAAM,IAAI,gBACR,4DAA4D,SAAS,YAAY,GAClF;;EAGH,MAAM,uBAAuB,QAAQ;AACrC,MAAI,sBAAsB;GAMxB,MAAM,UALS,KAAK,iBAAiB,KAAK,CAAC,MAAM,GAC5B,KAAK,iBACvB,GAAG,qBAAqB,UAAU,CAClC,MAAM;AAGT,OAAI,UAAU,SAAS,sBAAsB;IAC3C,MAAM,oBAAoB,KAAK,MAC7B,SAAS,uBAAuB,QACjC;AACD,SAAK,IAAI,MAAM,4BAA4B;KACzC,MAAM,MAAM;KACZ,QAAQ,MAAM;KACd,kBAAkB;KACnB,CAAC;AACF,UAAM,IAAI,gBACR,mCAAmC,kBAAkB,UACtD;;;EAIL,MAAM,QAAQ,KAAK,cAAc,MAAM,KAAK;EAE5C,MAAM,eAAe,MAAM,KAAK,uBAAuB,OAAO;GAC5D,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,MAAM,KAAK,SAAS,MAAM;GAC3B,CAAC;AAEF,OAAK,IAAI,KAAK,wBAAwB;GACpC,IAAI,aAAa;GACjB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,kBAAkB,SAAS;GAC5B,CAAC;AAEF,SAAO;GACL;GACA,gBAAgB,SAAS;GACzB,sBAAsB,SAAS;GAC/B,yBAAyB,SAAS;GACnC;;CAGH,MAAa,WACX,OACA,MAC2C;AAC3C,OAAK,IAAI,MAAM,kBAAkB;GAC/B,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,WAAW,KAAK,uBAAuB,IAAI,MAAM,KAAK;EAE5D,MAAM,eAAe,MAAM,KAAK,YAAY,MAAM;AAClD,MAAI,aAAa,YAAY;AAC3B,QAAK,IAAI,MAAM,iCAAiC;IAC9C,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,YAAY,aAAa;IAC1B,CAAC;AACF,UAAO;IAAE,IAAI;IAAM,iBAAiB;IAAM;;EAM5C,MAAM,MAAM,KAAK,iBAAiB,KAAK;EACvC,MAAM,iBAAiB,KAAK,iBACzB,GAAG,aAAa,UAAU,CAC1B,IAAI,SAAS,gBAAgB,UAAU;AAE1C,MAAI,MAAM,gBAAgB;AACxB,QAAK,IAAI,KAAK,6BAA6B;IACzC,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,WAAW,aAAa;IACxB,WAAW,eAAe,aAAa;IACxC,CAAC;AACF,SAAM,IAAI,gBAAgB,gCAAgC;;AAG5D,MAAI,aAAa,YAAY,SAAS,aAAa;AACjD,QAAK,IAAI,KAAK,2CAA2C;IACvD,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,UAAU,aAAa;IACvB,aAAa,SAAS;IACvB,CAAC;AACF,SAAM,IAAI,gBACR,8DACD;;AAGH,MAAI,aAAa,SAAS,KAAK,SAAS,KAAK,EAAE;GAC7C,MAAM,cAAc,aAAa,WAAW;AAC5C,QAAK,IAAI,KAAK,6BAA6B;IACzC,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,UAAU;IACV,aAAa,SAAS;IACvB,CAAC;AACF,SAAM,KAAK,uBAAuB,WAAW,aAAa,IAAI,EAC5D,UAAU,aACX,CAAC;AACF,SAAM,IAAI,gBAAgB,4BAA4B;;AAGxD,QAAM,KAAK,uBAAuB,WAAW,aAAa,IAAI,EAC5D,YAAY,KAAK,iBAAiB,cAAc,EACjD,CAAC;AAEF,OAAK,IAAI,KAAK,8BAA8B;GAC1C,IAAI,aAAa;GACjB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,EAAE,IAAI,MAAM;;CAGrB,SAAgB,MAAsB;AACpC,SAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;;CAGxD,cAAqB,MAAoC;AACvD,MAAI,SAAS,QAAQ;GACnB,MAAM,WAAW,KAAK,uBAAuB,IAAI,OAAO;AACxD,UAAO,UAAU,GAAG,IAAU,CAC3B,UAAU,CACV,SAAS,SAAS,YAAY,IAAI;aAC5B,SAAS,OAClB,QAAO,YAAY;AAGrB,QAAM,IAAI,gBAAgB,8BAA8B,OAAO;;;;;AClPnE,IAAa,yBAAb,MAAoC;CAClC,sBAAyC,QAAQ,oBAAoB;CAErE,MAAsB;CACtB,QAAwB;CAExB,0BAA0C,QAAQ;EAChD,MAAM,GAAG,KAAK,IAAI;EAClB,OAAO,KAAK;EACZ,QAAQ;EACR,QAAQ;GACN,QAAQ,EAAE,OAAO,EACf,MAAM,4BACP,CAAC;GACF,MAAM,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EACjB,CAAC;GACF,UAAU;GACX;EACD,SAAS,OAAO,EAAE,MAAM,aAAa;AACnC,UAAO,MAAM,KAAK,oBAAoB,mBAAmB;IACvD,MAAM,OAAO;IACb,QAAQ,KAAK;IACd,CAAC;;EAEL,CAAC;CAEF,2BAA2C,QAAQ;EACjD,MAAM,GAAG,KAAK,IAAI;EAClB,OAAO,KAAK;EACZ,QAAQ;EACR,QAAQ;GACN,QAAQ,EAAE,OAAO,EACf,MAAM,4BACP,CAAC;GACF,MAAM,EAAE,OAAO;IACb,QAAQ,EAAE,MAAM;IAChB,OAAO,EAAE,KAAK,EACZ,aACE,oEACH,CAAC;IACH,CAAC;GACF,UAAU;GACX;EACD,SAAS,OAAO,EAAE,MAAM,aAAa;AACnC,UAAO,KAAK,oBAAoB,WAC9B;IACE,MAAM,OAAO;IACb,QAAQ,KAAK;IACd,EACD,KAAK,MACN;;EAEJ,CAAC;;;;ACrDJ,IAAa,mBAAb,MAA8B;CAC5B,yBAA4C,YAAY,cAAc;CACtE,yBAA4C,QAAQ,uBAAuB;CAC3E,mBAAsC,QAAQ,iBAAiB;CAE/D,eAA+B,WAAW;EACxC,MAAM;EACN,MAAM;EACN,aAAa;EACb,SAAS,YAAY;GACnB,MAAM,YAAY,KAAK,uBAAuB,IAAI,YAAY;AAC9D,OAAI,aAAa,EACf;GAIF,MAAM,iBACJ,KAAK,iBAAiB,WAAW,GAAG,aAFxB,OAAU,KAAK;AAI7B,SAAM,KAAK,uBAAuB,WAAW,EAC3C,WAAW,EACT,IAAI,KAAK,iBAAiB,GAAG,eAAe,CAAC,aAAa,EAC3D,EACF,CAAC;;EAEL,CAAC;;;;;;;;;;;;;;;ACFJ,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,UAAU;EACR;EACA;EACA;EACA;EACD;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/verifications/schemas/requestVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/validateVerificationCodeResponseSchema.ts","../../../src/api/verifications/schemas/verificationTypeEnumSchema.ts","../../../src/api/verifications/entities/verifications.ts","../../../src/api/verifications/schemas/verificationSettingsSchema.ts","../../../src/api/verifications/parameters/VerificationParameters.ts","../../../src/api/verifications/services/VerificationService.ts","../../../src/api/verifications/controllers/VerificationController.ts","../../../src/api/verifications/jobs/VerificationJobs.ts","../../../src/api/verifications/index.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const requestVerificationCodeResponseSchema = t.object({\n token: t.string({\n description:\n \"The verification token (6-digit code for phone, UUID for email). The caller should send this to the user via their preferred notification method.\",\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before your verification token expires.\",\n }),\n verificationCooldown: t.integer({\n description:\n \"Cooldown period in seconds before you can request another verification.\",\n }),\n maxVerificationAttempts: t.integer({\n description:\n \"Maximum number of verification attempts allowed before the token is locked.\",\n }),\n});\n\nexport type RequestVerificationResponse = Static<\n typeof requestVerificationCodeResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const validateVerificationCodeResponseSchema = t.object({\n ok: t.boolean({\n description: \"Indicates whether the verification was successful.\",\n }),\n alreadyVerified: t.optional(\n t.boolean({\n description: \"Indicates whether the target was already verified.\",\n }),\n ),\n});\n\nexport type ValidateVerificationCodeResponse = Static<\n typeof validateVerificationCodeResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const verificationTypeEnumSchema = t.enum([\"code\", \"link\"]);\nexport type VerificationTypeEnum = Static<typeof verificationTypeEnumSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { verificationTypeEnumSchema } from \"../schemas/verificationTypeEnumSchema.ts\";\n\nexport const verifications = $entity({\n name: \"verification\",\n schema: t.object({\n id: db.primaryKey(t.bigint()),\n\n createdAt: db.createdAt(),\n\n updatedAt: db.updatedAt(),\n\n version: db.version(),\n\n type: verificationTypeEnumSchema,\n\n target: t.text({\n description: \"Can be a phone (E.164 format) or email address\",\n }),\n\n code: t.text({\n description: \"Hashed verification token (n-digit code or UUID)\",\n }),\n\n verifiedAt: t.optional(\n t.datetime({\n description: \"When it was successfully verified\",\n }),\n ),\n\n attempts: db.default(\n t.integer({\n description: \"Number of failed attempts (to prevent brute-force)\",\n }),\n 0,\n ),\n }),\n indexes: [\n \"createdAt\",\n {\n columns: [\"target\", \"code\"],\n },\n ],\n});\n\nexport const verificationEntitySchema = verifications.schema;\nexport const verificationEntityInsertSchema = verifications.insertSchema;\nexport type VerificationEntity = Static<typeof verifications.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const verificationSettingsSchema = t.object({\n code: t.object(\n {\n maxAttempts: t.integer({\n description:\n \"Maximum number of attempts before locking the verification.\",\n minimum: 1,\n maximum: 10,\n }),\n codeLength: t.integer({\n description: \"Length of the verification code.\",\n minimum: 4,\n maximum: 12,\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before the verification code expires.\",\n minimum: 60, // 1 minute\n maximum: 3600, // 1 hour\n }),\n verificationCooldown: t.integer({\n description: \"Cooldown period in seconds after a request verification.\",\n minimum: 0,\n maximum: 3600, // 1 hour\n }),\n limitPerDay: t.integer({\n description:\n \"Maximum number of verification requests per day for one entry.\",\n minimum: 1,\n maximum: 100,\n }),\n },\n {\n description: \"Settings specific to code verifications.\",\n },\n ),\n link: t.object(\n {\n maxAttempts: t.integer({\n description:\n \"Maximum number of attempts before locking the verification.\",\n minimum: 1,\n maximum: 10,\n }),\n codeExpiration: t.integer({\n description: \"Time in seconds before the verification token expires.\",\n minimum: 60, // 1 minute\n maximum: 7200, // 2 hours\n }),\n verificationCooldown: t.integer({\n description: \"Cooldown period in seconds after a request verification.\",\n minimum: 0,\n maximum: 3600, // 1 hour\n }),\n limitPerDay: t.integer({\n description:\n \"Maximum number of verification requests per day for one entry.\",\n minimum: 1,\n maximum: 100,\n }),\n },\n {\n description: \"Settings specific to link verifications.\",\n },\n ),\n purgeDays: t.integer({\n description:\n \"Number of days after which expired verifications are automatically deleted. Set to 0 to disable auto-deletion.\",\n minimum: 0,\n maximum: 365,\n }),\n});\n\nexport type VerificationSettings = Static<typeof verificationSettingsSchema>;\n","import { $atom, $state, type Static } from \"alepha\";\nimport {\n type VerificationSettings,\n verificationSettingsSchema,\n} from \"../schemas/verificationSettingsSchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Verification settings configuration atom\n */\nexport const verificationOptions = $atom({\n name: \"alepha.api.verifications.options\",\n schema: verificationSettingsSchema,\n default: {\n code: {\n maxAttempts: 5,\n codeLength: 6,\n codeExpiration: 300, // 5 minutes\n verificationCooldown: 90,\n limitPerDay: 10,\n },\n link: {\n maxAttempts: 3, // Lower since UUIDs are harder to guess\n codeExpiration: 1800, // 30 minutes\n verificationCooldown: 90,\n limitPerDay: 10,\n },\n purgeDays: 1,\n },\n});\n\nexport type VerificationOptions = Static<typeof verificationOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [verificationOptions.key]: VerificationOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class VerificationParameters {\n protected readonly options = $state(verificationOptions);\n\n public get<K extends keyof VerificationSettings>(\n key: K,\n ): VerificationSettings[K] {\n return this.options[key];\n }\n}\n","import { createHash, randomInt, randomUUID } from \"node:crypto\";\nimport { $inject } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { BadRequestError, NotFoundError } from \"alepha/server\";\nimport {\n type VerificationEntity,\n verifications,\n} from \"../entities/verifications.ts\";\nimport { VerificationParameters } from \"../parameters/VerificationParameters.ts\";\nimport type { RequestVerificationResponse } from \"../schemas/requestVerificationCodeResponseSchema.ts\";\nimport type { ValidateVerificationCodeResponse } from \"../schemas/validateVerificationCodeResponseSchema.ts\";\nimport type { VerificationTypeEnum } from \"../schemas/verificationTypeEnumSchema.ts\";\n\nexport class VerificationService {\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly verificationParameters = $inject(VerificationParameters);\n protected readonly verificationRepository = $repository(verifications);\n\n public async findByEntry(\n entry: VerificationEntry,\n ): Promise<VerificationEntity> {\n this.log.trace(\"Finding verification by entry\", {\n type: entry.type,\n target: entry.target,\n });\n\n const results = await this.verificationRepository.findMany({\n limit: 1, // only need the most recent entry\n orderBy: {\n column: \"createdAt\",\n direction: \"desc\",\n },\n where: {\n type: { eq: entry.type },\n target: { eq: entry.target },\n },\n });\n\n if (results.length === 0) {\n this.log.debug(\"Verification entry not found\", {\n type: entry.type,\n target: entry.target,\n });\n throw new NotFoundError(\"Verification entry not found\");\n }\n\n this.log.debug(\"Verification entry found\", {\n id: results[0].id,\n type: entry.type,\n target: entry.target,\n });\n\n return results[0];\n }\n\n public findRecentsByEntry(entry: VerificationEntry) {\n this.log.trace(\"Finding recent verifications by entry\", {\n type: entry.type,\n target: entry.target,\n });\n\n return this.verificationRepository.findMany({\n orderBy: {\n column: \"createdAt\",\n direction: \"desc\",\n },\n where: {\n type: { eq: entry.type },\n target: { eq: entry.target },\n createdAt: {\n gte: this.dateTimeProvider.now().startOf(\"day\").toISOString(),\n },\n },\n });\n }\n\n /**\n * Creates a verification entry and returns the token.\n * The caller is responsible for sending notifications with the token.\n * This allows for context-specific notifications (e.g., password reset vs email verification).\n */\n public async createVerification(\n entry: VerificationEntry,\n ): Promise<RequestVerificationResponse> {\n this.log.trace(\"Creating verification\", {\n type: entry.type,\n target: entry.target,\n });\n\n const settings = this.verificationParameters.get(entry.type);\n\n const recents = await this.findRecentsByEntry(entry);\n if (recents.length >= settings.limitPerDay) {\n this.log.warn(\"Daily verification limit reached\", {\n type: entry.type,\n target: entry.target,\n limit: settings.limitPerDay,\n count: recents.length,\n });\n throw new BadRequestError(\n `Maximum number of verification requests per day reached (${settings.limitPerDay})`,\n );\n }\n\n const existingVerification = recents[0];\n if (existingVerification) {\n const nowSec = this.dateTimeProvider.now().unix();\n const createdAtSec = this.dateTimeProvider\n .of(existingVerification.createdAt)\n .unix();\n\n const diffSec = nowSec - createdAtSec;\n if (diffSec < settings.verificationCooldown) {\n const remainingCooldown = Math.floor(\n settings.verificationCooldown - diffSec,\n );\n this.log.debug(\"Verification on cooldown\", {\n type: entry.type,\n target: entry.target,\n remainingSeconds: remainingCooldown,\n });\n throw new BadRequestError(\n `Verification is on cooldown for ${remainingCooldown} seconds`,\n );\n }\n }\n\n const token = this.generateToken(entry.type);\n\n const verification = await this.verificationRepository.create({\n type: entry.type,\n target: entry.target,\n code: this.hashCode(token),\n createdAt: this.dateTimeProvider.nowISOString(),\n });\n\n this.log.info(\"Verification created\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n expiresInSeconds: settings.codeExpiration,\n });\n\n return {\n token,\n codeExpiration: settings.codeExpiration,\n verificationCooldown: settings.verificationCooldown,\n maxVerificationAttempts: settings.maxAttempts,\n };\n }\n\n public async verifyCode(\n entry: VerificationEntry,\n code: string,\n ): Promise<ValidateVerificationCodeResponse> {\n this.log.trace(\"Verifying code\", {\n type: entry.type,\n target: entry.target,\n });\n\n const settings = this.verificationParameters.get(entry.type);\n\n const verification = await this.findByEntry(entry);\n if (verification.verifiedAt) {\n this.log.debug(\"Verification already verified\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n verifiedAt: verification.verifiedAt,\n });\n return { ok: true, alreadyVerified: true };\n }\n\n // DO NOT DELETE THE VERIFICATION WHEN IT IS REJECTED,\n // or we won't be able to cooldown the verification\n\n const now = this.dateTimeProvider.now();\n const expirationDate = this.dateTimeProvider\n .of(verification.createdAt)\n .add(settings.codeExpiration, \"seconds\");\n\n if (now > expirationDate) {\n this.log.warn(\"Verification code expired\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n createdAt: verification.createdAt,\n expiredAt: expirationDate.toISOString(),\n });\n throw new BadRequestError(\"Verification code has expired\");\n }\n\n if (verification.attempts >= settings.maxAttempts) {\n this.log.warn(\"Verification locked due to max attempts\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n attempts: verification.attempts,\n maxAttempts: settings.maxAttempts,\n });\n throw new BadRequestError(\n \"Maximum number of attempts reached - verification is locked\",\n );\n }\n\n if (verification.code !== this.hashCode(code)) {\n const newAttempts = verification.attempts + 1;\n this.log.warn(\"Invalid verification code\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n attempts: newAttempts,\n maxAttempts: settings.maxAttempts,\n });\n await this.verificationRepository.updateById(verification.id, {\n attempts: newAttempts,\n });\n throw new BadRequestError(\"Invalid verification code\");\n }\n\n await this.verificationRepository.updateById(verification.id, {\n verifiedAt: this.dateTimeProvider.nowISOString(),\n });\n\n this.log.info(\"Verification code verified\", {\n id: verification.id,\n type: entry.type,\n target: entry.target,\n });\n\n return { ok: true };\n }\n\n public hashCode(code: string): string {\n return createHash(\"sha256\").update(code).digest(\"hex\");\n }\n\n public generateToken(type: VerificationTypeEnum): string {\n if (type === \"code\") {\n const settings = this.verificationParameters.get(\"code\");\n return randomInt(0, 1_000_000)\n .toString()\n .padStart(settings.codeLength, \"0\");\n } else if (type === \"link\") {\n return randomUUID();\n }\n\n throw new BadRequestError(`Invalid verification type: ${type}`);\n }\n}\n\nexport interface VerificationEntry {\n type: VerificationTypeEnum;\n target: string;\n}\n","import { $inject, t } from \"alepha\";\nimport { $action } from \"alepha/server\";\nimport { requestVerificationCodeResponseSchema } from \"../schemas/requestVerificationCodeResponseSchema.ts\";\nimport { validateVerificationCodeResponseSchema } from \"../schemas/validateVerificationCodeResponseSchema.ts\";\nimport { verificationTypeEnumSchema } from \"../schemas/verificationTypeEnumSchema.ts\";\nimport { VerificationService } from \"../services/VerificationService.ts\";\n\nexport class VerificationController {\n protected readonly verificationService = $inject(VerificationService);\n\n public readonly url = \"/verifications\";\n public readonly group = \"verifications\";\n\n public readonly requestVerificationCode = $action({\n path: `${this.url}/:type`,\n group: this.group,\n method: \"POST\",\n schema: {\n params: t.object({\n type: verificationTypeEnumSchema,\n }),\n body: t.object({\n target: t.text(),\n }),\n response: requestVerificationCodeResponseSchema,\n },\n handler: async ({ body, params }) => {\n return await this.verificationService.createVerification({\n type: params.type,\n target: body.target,\n });\n },\n });\n\n public readonly validateVerificationCode = $action({\n path: `${this.url}/:type/validate`,\n group: this.group,\n method: \"POST\",\n schema: {\n params: t.object({\n type: verificationTypeEnumSchema,\n }),\n body: t.object({\n target: t.text(),\n token: t.text({\n description:\n \"The verification token (6-digit code for phone, UUID for email).\",\n }),\n }),\n response: validateVerificationCodeResponseSchema,\n },\n handler: async ({ body, params }) => {\n return this.verificationService.verifyCode(\n {\n type: params.type,\n target: body.target,\n },\n body.token,\n );\n },\n });\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $repository } from \"alepha/orm\";\nimport { $scheduler } from \"alepha/scheduler\";\nimport { verifications } from \"../entities/verifications.ts\";\nimport { VerificationParameters } from \"../parameters/VerificationParameters.ts\";\n\nexport class VerificationJobs {\n protected readonly verificationRepository = $repository(verifications);\n protected readonly verificationParameters = $inject(VerificationParameters);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public readonly cleanExpired = $scheduler({\n name: \"api:verifications:cleanExpired\",\n cron: \"0 * * * *\", // Hourly at minute 0\n description: \"Clean expired verifications\",\n handler: async () => {\n const purgeDays = this.verificationParameters.get(\"purgeDays\");\n if (purgeDays <= 0) {\n return; // Auto deletion is disabled\n }\n\n const dayMs = 24 * 60 * 60 * 1000;\n const purgeThreshold =\n this.dateTimeProvider.nowMillis() - purgeDays * dayMs;\n\n await this.verificationRepository.deleteMany({\n createdAt: {\n lt: this.dateTimeProvider.of(purgeThreshold).toISOString(),\n },\n });\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { VerificationController } from \"./controllers/VerificationController.ts\";\nimport { VerificationJobs } from \"./jobs/VerificationJobs.ts\";\nimport { VerificationParameters } from \"./parameters/VerificationParameters.ts\";\nimport { VerificationService } from \"./services/VerificationService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/VerificationController.ts\";\nexport * from \"./entities/verifications.ts\";\nexport * from \"./jobs/VerificationJobs.ts\";\nexport * from \"./parameters/VerificationParameters.ts\";\nexport * from \"./schemas/requestVerificationCodeResponseSchema.ts\";\nexport * from \"./schemas/validateVerificationCodeResponseSchema.ts\";\nexport * from \"./schemas/verificationTypeEnumSchema.ts\";\nexport * from \"./services/VerificationService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email and phone verification workflows.\n *\n * **Features:**\n * - Verification token generation\n * - Verification code sending\n * - Verification completion tracking\n * - Resend functionality\n *\n * @module alepha.api.verifications\n */\nexport const AlephaApiVerification = $module({\n name: \"alepha.api.verifications\",\n services: [\n VerificationController,\n VerificationJobs,\n VerificationService,\n VerificationParameters,\n ],\n});\n"],"mappings":";;;;;;;;AAGA,MAAa,wCAAwC,EAAE,OAAO;CAC5D,OAAO,EAAE,OAAO,EACd,aACE,qJACH,CAAC;CACF,gBAAgB,EAAE,QAAQ,EACxB,aAAa,2DACd,CAAC;CACF,sBAAsB,EAAE,QAAQ,EAC9B,aACE,2EACH,CAAC;CACF,yBAAyB,EAAE,QAAQ,EACjC,aACE,+EACH,CAAC;CACH,CAAC;;;AChBF,MAAa,yCAAyC,EAAE,OAAO;CAC7D,IAAI,EAAE,QAAQ,EACZ,aAAa,sDACd,CAAC;CACF,iBAAiB,EAAE,SACjB,EAAE,QAAQ,EACR,aAAa,sDACd,CAAC,CACH;CACF,CAAC;;;ACTF,MAAa,6BAA6B,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC;;;ACElE,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,QAAQ,EAAE,OAAO;EACf,IAAI,GAAG,WAAW,EAAE,QAAQ,CAAC;EAE7B,WAAW,GAAG,WAAW;EAEzB,WAAW,GAAG,WAAW;EAEzB,SAAS,GAAG,SAAS;EAErB,MAAM;EAEN,QAAQ,EAAE,KAAK,EACb,aAAa,kDACd,CAAC;EAEF,MAAM,EAAE,KAAK,EACX,aAAa,oDACd,CAAC;EAEF,YAAY,EAAE,SACZ,EAAE,SAAS,EACT,aAAa,qCACd,CAAC,CACH;EAED,UAAU,GAAG,QACX,EAAE,QAAQ,EACR,aAAa,sDACd,CAAC,EACF,EACD;EACF,CAAC;CACF,SAAS,CACP,aACA,EACE,SAAS,CAAC,UAAU,OAAO,EAC5B,CACF;CACF,CAAC;AAEF,MAAa,2BAA2B,cAAc;AACtD,MAAa,iCAAiC,cAAc;;;;;;AErC5D,MAAa,sBAAsB,MAAM;CACvC,MAAM;CACN,QDVwC,EAAE,OAAO;EACjD,MAAM,EAAE,OACN;GACE,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACF,YAAY,EAAE,QAAQ;IACpB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,gBAAgB,EAAE,QAAQ;IACxB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,sBAAsB,EAAE,QAAQ;IAC9B,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACH,EACD,EACE,aAAa,4CACd,CACF;EACD,MAAM,EAAE,OACN;GACE,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACF,gBAAgB,EAAE,QAAQ;IACxB,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,sBAAsB,EAAE,QAAQ;IAC9B,aAAa;IACb,SAAS;IACT,SAAS;IACV,CAAC;GACF,aAAa,EAAE,QAAQ;IACrB,aACE;IACF,SAAS;IACT,SAAS;IACV,CAAC;GACH,EACD,EACE,aAAa,4CACd,CACF;EACD,WAAW,EAAE,QAAQ;GACnB,aACE;GACF,SAAS;GACT,SAAS;GACV,CAAC;EACH,CC5DS;CACR,SAAS;EACP,MAAM;GACJ,aAAa;GACb,YAAY;GACZ,gBAAgB;GAChB,sBAAsB;GACtB,aAAa;GACd;EACD,MAAM;GACJ,aAAa;GACb,gBAAgB;GAChB,sBAAsB;GACtB,aAAa;GACd;EACD,WAAW;EACZ;CACF,CAAC;AAYF,IAAa,yBAAb,MAAoC;CAClC,UAA6B,OAAO,oBAAoB;CAExD,IACE,KACyB;AACzB,SAAO,KAAK,QAAQ;;;;;ACjCxB,IAAa,sBAAb,MAAiC;CAC/B,MAAyB,SAAS;CAClC,mBAAsC,QAAQ,iBAAiB;CAC/D,yBAA4C,QAAQ,uBAAuB;CAC3E,yBAA4C,YAAY,cAAc;CAEtE,MAAa,YACX,OAC6B;AAC7B,OAAK,IAAI,MAAM,iCAAiC;GAC9C,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,UAAU,MAAM,KAAK,uBAAuB,SAAS;GACzD,OAAO;GACP,SAAS;IACP,QAAQ;IACR,WAAW;IACZ;GACD,OAAO;IACL,MAAM,EAAE,IAAI,MAAM,MAAM;IACxB,QAAQ,EAAE,IAAI,MAAM,QAAQ;IAC7B;GACF,CAAC;AAEF,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAK,IAAI,MAAM,gCAAgC;IAC7C,MAAM,MAAM;IACZ,QAAQ,MAAM;IACf,CAAC;AACF,SAAM,IAAI,cAAc,+BAA+B;;AAGzD,OAAK,IAAI,MAAM,4BAA4B;GACzC,IAAI,QAAQ,GAAG;GACf,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,QAAQ;;CAGjB,mBAA0B,OAA0B;AAClD,OAAK,IAAI,MAAM,yCAAyC;GACtD,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,KAAK,uBAAuB,SAAS;GAC1C,SAAS;IACP,QAAQ;IACR,WAAW;IACZ;GACD,OAAO;IACL,MAAM,EAAE,IAAI,MAAM,MAAM;IACxB,QAAQ,EAAE,IAAI,MAAM,QAAQ;IAC5B,WAAW,EACT,KAAK,KAAK,iBAAiB,KAAK,CAAC,QAAQ,MAAM,CAAC,aAAa,EAC9D;IACF;GACF,CAAC;;;;;;;CAQJ,MAAa,mBACX,OACsC;AACtC,OAAK,IAAI,MAAM,yBAAyB;GACtC,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,WAAW,KAAK,uBAAuB,IAAI,MAAM,KAAK;EAE5D,MAAM,UAAU,MAAM,KAAK,mBAAmB,MAAM;AACpD,MAAI,QAAQ,UAAU,SAAS,aAAa;AAC1C,QAAK,IAAI,KAAK,oCAAoC;IAChD,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,OAAO,SAAS;IAChB,OAAO,QAAQ;IAChB,CAAC;AACF,SAAM,IAAI,gBACR,4DAA4D,SAAS,YAAY,GAClF;;EAGH,MAAM,uBAAuB,QAAQ;AACrC,MAAI,sBAAsB;GAMxB,MAAM,UALS,KAAK,iBAAiB,KAAK,CAAC,MAKrB,GAJD,KAAK,iBACvB,GAAG,qBAAqB,UAAU,CAClC,MAEkC;AACrC,OAAI,UAAU,SAAS,sBAAsB;IAC3C,MAAM,oBAAoB,KAAK,MAC7B,SAAS,uBAAuB,QACjC;AACD,SAAK,IAAI,MAAM,4BAA4B;KACzC,MAAM,MAAM;KACZ,QAAQ,MAAM;KACd,kBAAkB;KACnB,CAAC;AACF,UAAM,IAAI,gBACR,mCAAmC,kBAAkB,UACtD;;;EAIL,MAAM,QAAQ,KAAK,cAAc,MAAM,KAAK;EAE5C,MAAM,eAAe,MAAM,KAAK,uBAAuB,OAAO;GAC5D,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,MAAM,KAAK,SAAS,MAAM;GAC1B,WAAW,KAAK,iBAAiB,cAAc;GAChD,CAAC;AAEF,OAAK,IAAI,KAAK,wBAAwB;GACpC,IAAI,aAAa;GACjB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,kBAAkB,SAAS;GAC5B,CAAC;AAEF,SAAO;GACL;GACA,gBAAgB,SAAS;GACzB,sBAAsB,SAAS;GAC/B,yBAAyB,SAAS;GACnC;;CAGH,MAAa,WACX,OACA,MAC2C;AAC3C,OAAK,IAAI,MAAM,kBAAkB;GAC/B,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;EAEF,MAAM,WAAW,KAAK,uBAAuB,IAAI,MAAM,KAAK;EAE5D,MAAM,eAAe,MAAM,KAAK,YAAY,MAAM;AAClD,MAAI,aAAa,YAAY;AAC3B,QAAK,IAAI,MAAM,iCAAiC;IAC9C,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,YAAY,aAAa;IAC1B,CAAC;AACF,UAAO;IAAE,IAAI;IAAM,iBAAiB;IAAM;;EAM5C,MAAM,MAAM,KAAK,iBAAiB,KAAK;EACvC,MAAM,iBAAiB,KAAK,iBACzB,GAAG,aAAa,UAAU,CAC1B,IAAI,SAAS,gBAAgB,UAAU;AAE1C,MAAI,MAAM,gBAAgB;AACxB,QAAK,IAAI,KAAK,6BAA6B;IACzC,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,WAAW,aAAa;IACxB,WAAW,eAAe,aAAa;IACxC,CAAC;AACF,SAAM,IAAI,gBAAgB,gCAAgC;;AAG5D,MAAI,aAAa,YAAY,SAAS,aAAa;AACjD,QAAK,IAAI,KAAK,2CAA2C;IACvD,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,UAAU,aAAa;IACvB,aAAa,SAAS;IACvB,CAAC;AACF,SAAM,IAAI,gBACR,8DACD;;AAGH,MAAI,aAAa,SAAS,KAAK,SAAS,KAAK,EAAE;GAC7C,MAAM,cAAc,aAAa,WAAW;AAC5C,QAAK,IAAI,KAAK,6BAA6B;IACzC,IAAI,aAAa;IACjB,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,UAAU;IACV,aAAa,SAAS;IACvB,CAAC;AACF,SAAM,KAAK,uBAAuB,WAAW,aAAa,IAAI,EAC5D,UAAU,aACX,CAAC;AACF,SAAM,IAAI,gBAAgB,4BAA4B;;AAGxD,QAAM,KAAK,uBAAuB,WAAW,aAAa,IAAI,EAC5D,YAAY,KAAK,iBAAiB,cAAc,EACjD,CAAC;AAEF,OAAK,IAAI,KAAK,8BAA8B;GAC1C,IAAI,aAAa;GACjB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACf,CAAC;AAEF,SAAO,EAAE,IAAI,MAAM;;CAGrB,SAAgB,MAAsB;AACpC,SAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;;CAGxD,cAAqB,MAAoC;AACvD,MAAI,SAAS,QAAQ;GACnB,MAAM,WAAW,KAAK,uBAAuB,IAAI,OAAO;AACxD,UAAO,UAAU,GAAG,IAAU,CAC3B,UAAU,CACV,SAAS,SAAS,YAAY,IAAI;aAC5B,SAAS,OAClB,QAAO,YAAY;AAGrB,QAAM,IAAI,gBAAgB,8BAA8B,OAAO;;;;;ACnPnE,IAAa,yBAAb,MAAoC;CAClC,sBAAyC,QAAQ,oBAAoB;CAErE,MAAsB;CACtB,QAAwB;CAExB,0BAA0C,QAAQ;EAChD,MAAM,GAAG,KAAK,IAAI;EAClB,OAAO,KAAK;EACZ,QAAQ;EACR,QAAQ;GACN,QAAQ,EAAE,OAAO,EACf,MAAM,4BACP,CAAC;GACF,MAAM,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EACjB,CAAC;GACF,UAAU;GACX;EACD,SAAS,OAAO,EAAE,MAAM,aAAa;AACnC,UAAO,MAAM,KAAK,oBAAoB,mBAAmB;IACvD,MAAM,OAAO;IACb,QAAQ,KAAK;IACd,CAAC;;EAEL,CAAC;CAEF,2BAA2C,QAAQ;EACjD,MAAM,GAAG,KAAK,IAAI;EAClB,OAAO,KAAK;EACZ,QAAQ;EACR,QAAQ;GACN,QAAQ,EAAE,OAAO,EACf,MAAM,4BACP,CAAC;GACF,MAAM,EAAE,OAAO;IACb,QAAQ,EAAE,MAAM;IAChB,OAAO,EAAE,KAAK,EACZ,aACE,oEACH,CAAC;IACH,CAAC;GACF,UAAU;GACX;EACD,SAAS,OAAO,EAAE,MAAM,aAAa;AACnC,UAAO,KAAK,oBAAoB,WAC9B;IACE,MAAM,OAAO;IACb,QAAQ,KAAK;IACd,EACD,KAAK,MACN;;EAEJ,CAAC;;;;ACrDJ,IAAa,mBAAb,MAA8B;CAC5B,yBAA4C,YAAY,cAAc;CACtE,yBAA4C,QAAQ,uBAAuB;CAC3E,mBAAsC,QAAQ,iBAAiB;CAE/D,eAA+B,WAAW;EACxC,MAAM;EACN,MAAM;EACN,aAAa;EACb,SAAS,YAAY;GACnB,MAAM,YAAY,KAAK,uBAAuB,IAAI,YAAY;AAC9D,OAAI,aAAa,EACf;GAIF,MAAM,iBACJ,KAAK,iBAAiB,WAAW,GAAG,aAFxB,OAAU,KAAK;AAI7B,SAAM,KAAK,uBAAuB,WAAW,EAC3C,WAAW,EACT,IAAI,KAAK,iBAAiB,GAAG,eAAe,CAAC,aAAa,EAC3D,EACF,CAAC;;EAEL,CAAC;;;;;;;;;;;;;;;ACFJ,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,UAAU;EACR;EACA;EACA;EACA;EACD;CACF,CAAC"}
|
package/dist/bucket/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Alepha, AlephaError, FileLike, KIND, Primitive, Service, Static } from
|
|
|
3
3
|
import { FileDetector, FileSystemProvider } from "alepha/system";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as _$alepha_logger0 from "alepha/logger";
|
|
6
|
+
import { S3mini } from "s3mini";
|
|
7
|
+
import { R2Bucket } from "@cloudflare/workers-types";
|
|
6
8
|
|
|
7
9
|
//#region ../../src/bucket/providers/FileStorageProvider.d.ts
|
|
8
10
|
declare abstract class FileStorageProvider {
|
|
@@ -245,7 +247,7 @@ interface BucketFileOptions {
|
|
|
245
247
|
maxSize?: number;
|
|
246
248
|
}
|
|
247
249
|
declare class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {
|
|
248
|
-
readonly provider:
|
|
250
|
+
readonly provider: FileStorageProvider | MemoryFileStorageProvider;
|
|
249
251
|
protected readonly fileSystem: FileSystemProvider;
|
|
250
252
|
get name(): string;
|
|
251
253
|
/**
|
|
@@ -264,7 +266,7 @@ declare class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {
|
|
|
264
266
|
* Downloads a file from the bucket.
|
|
265
267
|
*/
|
|
266
268
|
download(fileId: string): Promise<FileLike>;
|
|
267
|
-
protected $provider():
|
|
269
|
+
protected $provider(): FileStorageProvider | MemoryFileStorageProvider;
|
|
268
270
|
}
|
|
269
271
|
interface BucketFileOptions {
|
|
270
272
|
/**
|
|
@@ -289,110 +291,6 @@ declare class FileNotFoundError extends AlephaError {
|
|
|
289
291
|
}
|
|
290
292
|
//#endregion
|
|
291
293
|
//#region ../../src/bucket/providers/CloudflareR2Provider.d.ts
|
|
292
|
-
/**
|
|
293
|
-
* R2Bucket interface matching Cloudflare's R2 API.
|
|
294
|
-
*/
|
|
295
|
-
interface R2Bucket {
|
|
296
|
-
put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob | null, options?: R2PutOptions): Promise<R2Object | null>;
|
|
297
|
-
get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;
|
|
298
|
-
head(key: string): Promise<R2Object | null>;
|
|
299
|
-
delete(keys: string | string[]): Promise<void>;
|
|
300
|
-
list(options?: R2ListOptions): Promise<R2Objects>;
|
|
301
|
-
createMultipartUpload(key: string, options?: R2MultipartOptions): Promise<R2MultipartUpload>;
|
|
302
|
-
}
|
|
303
|
-
interface R2Object {
|
|
304
|
-
key: string;
|
|
305
|
-
version: string;
|
|
306
|
-
size: number;
|
|
307
|
-
etag: string;
|
|
308
|
-
httpEtag: string;
|
|
309
|
-
checksums: R2Checksums;
|
|
310
|
-
uploaded: Date;
|
|
311
|
-
httpMetadata?: R2HTTPMetadata;
|
|
312
|
-
customMetadata?: Record<string, string>;
|
|
313
|
-
range?: R2Range;
|
|
314
|
-
storageClass: string;
|
|
315
|
-
}
|
|
316
|
-
interface R2ObjectBody extends R2Object {
|
|
317
|
-
body: ReadableStream;
|
|
318
|
-
bodyUsed: boolean;
|
|
319
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
320
|
-
text(): Promise<string>;
|
|
321
|
-
json<T>(): Promise<T>;
|
|
322
|
-
blob(): Promise<Blob>;
|
|
323
|
-
}
|
|
324
|
-
interface R2PutOptions {
|
|
325
|
-
onlyIf?: R2Conditional;
|
|
326
|
-
httpMetadata?: R2HTTPMetadata;
|
|
327
|
-
customMetadata?: Record<string, string>;
|
|
328
|
-
md5?: ArrayBuffer | string;
|
|
329
|
-
sha1?: ArrayBuffer | string;
|
|
330
|
-
sha256?: ArrayBuffer | string;
|
|
331
|
-
sha384?: ArrayBuffer | string;
|
|
332
|
-
sha512?: ArrayBuffer | string;
|
|
333
|
-
storageClass?: string;
|
|
334
|
-
}
|
|
335
|
-
interface R2GetOptions {
|
|
336
|
-
onlyIf?: R2Conditional;
|
|
337
|
-
range?: R2Range;
|
|
338
|
-
}
|
|
339
|
-
interface R2ListOptions {
|
|
340
|
-
limit?: number;
|
|
341
|
-
prefix?: string;
|
|
342
|
-
cursor?: string;
|
|
343
|
-
delimiter?: string;
|
|
344
|
-
startAfter?: string;
|
|
345
|
-
include?: ("httpMetadata" | "customMetadata")[];
|
|
346
|
-
}
|
|
347
|
-
interface R2Objects {
|
|
348
|
-
objects: R2Object[];
|
|
349
|
-
truncated: boolean;
|
|
350
|
-
cursor?: string;
|
|
351
|
-
delimitedPrefixes: string[];
|
|
352
|
-
}
|
|
353
|
-
interface R2Checksums {
|
|
354
|
-
md5?: ArrayBuffer;
|
|
355
|
-
sha1?: ArrayBuffer;
|
|
356
|
-
sha256?: ArrayBuffer;
|
|
357
|
-
sha384?: ArrayBuffer;
|
|
358
|
-
sha512?: ArrayBuffer;
|
|
359
|
-
}
|
|
360
|
-
interface R2HTTPMetadata {
|
|
361
|
-
contentType?: string;
|
|
362
|
-
contentLanguage?: string;
|
|
363
|
-
contentDisposition?: string;
|
|
364
|
-
contentEncoding?: string;
|
|
365
|
-
cacheControl?: string;
|
|
366
|
-
cacheExpiry?: Date;
|
|
367
|
-
}
|
|
368
|
-
interface R2Conditional {
|
|
369
|
-
etagMatches?: string;
|
|
370
|
-
etagDoesNotMatch?: string;
|
|
371
|
-
uploadedBefore?: Date;
|
|
372
|
-
uploadedAfter?: Date;
|
|
373
|
-
secondsGranularity?: boolean;
|
|
374
|
-
}
|
|
375
|
-
interface R2Range {
|
|
376
|
-
offset?: number;
|
|
377
|
-
length?: number;
|
|
378
|
-
suffix?: number;
|
|
379
|
-
}
|
|
380
|
-
interface R2MultipartOptions {
|
|
381
|
-
httpMetadata?: R2HTTPMetadata;
|
|
382
|
-
customMetadata?: Record<string, string>;
|
|
383
|
-
storageClass?: string;
|
|
384
|
-
}
|
|
385
|
-
interface R2MultipartUpload {
|
|
386
|
-
key: string;
|
|
387
|
-
uploadId: string;
|
|
388
|
-
uploadPart(partNumber: number, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob): Promise<R2UploadedPart>;
|
|
389
|
-
abort(): Promise<void>;
|
|
390
|
-
complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;
|
|
391
|
-
}
|
|
392
|
-
interface R2UploadedPart {
|
|
393
|
-
partNumber: number;
|
|
394
|
-
etag: string;
|
|
395
|
-
}
|
|
396
294
|
/**
|
|
397
295
|
* Cloudflare R2 storage provider.
|
|
398
296
|
*
|
|
@@ -501,6 +399,70 @@ declare class LocalFileStorageProvider implements FileStorageProvider {
|
|
|
501
399
|
protected isErrorNoEntry(error: unknown): boolean;
|
|
502
400
|
}
|
|
503
401
|
//#endregion
|
|
402
|
+
//#region ../../src/bucket/providers/NodeS3BucketProvider.d.ts
|
|
403
|
+
declare const envSchema: _$alepha.TObject<{
|
|
404
|
+
/**
|
|
405
|
+
* S3 endpoint URL. The bucket name is appended (path-style) per request.
|
|
406
|
+
*
|
|
407
|
+
* Examples:
|
|
408
|
+
* - AWS S3: `https://s3.us-east-1.amazonaws.com`
|
|
409
|
+
* - Cloudflare R2: `https://<account-id>.r2.cloudflarestorage.com`
|
|
410
|
+
* - MinIO: `http://localhost:9000`
|
|
411
|
+
* - DigitalOcean Spaces: `https://<region>.digitaloceanspaces.com`
|
|
412
|
+
*/
|
|
413
|
+
S3_ENDPOINT: _$alepha.TString;
|
|
414
|
+
/**
|
|
415
|
+
* AWS region or "auto" for R2.
|
|
416
|
+
*
|
|
417
|
+
* @default "auto"
|
|
418
|
+
*/
|
|
419
|
+
S3_REGION: _$alepha.TOptional<_$alepha.TString>;
|
|
420
|
+
/**
|
|
421
|
+
* Access key ID for S3 authentication.
|
|
422
|
+
*/
|
|
423
|
+
S3_ACCESS_KEY_ID: _$alepha.TString;
|
|
424
|
+
/**
|
|
425
|
+
* Secret access key for S3 authentication.
|
|
426
|
+
*/
|
|
427
|
+
S3_SECRET_ACCESS_KEY: _$alepha.TString;
|
|
428
|
+
}>;
|
|
429
|
+
declare module "alepha" {
|
|
430
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* S3-compatible file storage provider for Node.js.
|
|
434
|
+
*
|
|
435
|
+
* Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2,
|
|
436
|
+
* MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service.
|
|
437
|
+
*
|
|
438
|
+
* Uses path-style addressing (`<endpoint>/<bucket>`).
|
|
439
|
+
*/
|
|
440
|
+
declare class NodeS3BucketProvider implements FileStorageProvider {
|
|
441
|
+
protected readonly log: _$alepha_logger0.Logger;
|
|
442
|
+
protected readonly env: {
|
|
443
|
+
S3_REGION?: string | undefined;
|
|
444
|
+
S3_ENDPOINT: string;
|
|
445
|
+
S3_ACCESS_KEY_ID: string;
|
|
446
|
+
S3_SECRET_ACCESS_KEY: string;
|
|
447
|
+
};
|
|
448
|
+
protected readonly alepha: Alepha;
|
|
449
|
+
protected readonly fileSystem: FileSystemProvider;
|
|
450
|
+
protected readonly fileDetector: FileDetector;
|
|
451
|
+
protected readonly clients: Map<string, S3mini>;
|
|
452
|
+
/**
|
|
453
|
+
* Convert bucket name to S3-compatible format.
|
|
454
|
+
* S3 bucket names must be lowercase, 3-63 characters, no underscores.
|
|
455
|
+
*/
|
|
456
|
+
convertName(name: string): string;
|
|
457
|
+
protected getClient(bucketName: string): S3mini;
|
|
458
|
+
protected readonly onStart: _$alepha.HookPrimitive<"start">;
|
|
459
|
+
protected createId(mimeType: string): string;
|
|
460
|
+
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
|
461
|
+
download(bucketName: string, fileId: string): Promise<FileLike>;
|
|
462
|
+
exists(bucketName: string, fileId: string): Promise<boolean>;
|
|
463
|
+
delete(bucketName: string, fileId: string): Promise<void>;
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
504
466
|
//#region ../../src/bucket/index.d.ts
|
|
505
467
|
declare module "alepha" {
|
|
506
468
|
interface Hooks {
|
|
@@ -521,6 +483,14 @@ declare module "alepha" {
|
|
|
521
483
|
id: string;
|
|
522
484
|
bucket: BucketPrimitive;
|
|
523
485
|
};
|
|
486
|
+
/**
|
|
487
|
+
* Triggered when a file is downloaded from a bucket.
|
|
488
|
+
*/
|
|
489
|
+
"bucket:file:downloaded": {
|
|
490
|
+
id: string;
|
|
491
|
+
file: FileLike;
|
|
492
|
+
bucket: BucketPrimitive;
|
|
493
|
+
};
|
|
524
494
|
}
|
|
525
495
|
}
|
|
526
496
|
/**
|
|
@@ -539,5 +509,5 @@ declare module "alepha" {
|
|
|
539
509
|
*/
|
|
540
510
|
declare const AlephaBucket: _$alepha.Service<_$alepha.Module>;
|
|
541
511
|
//#endregion
|
|
542
|
-
export { $bucket, AlephaBucket, BucketFileOptions, BucketPrimitive, BucketPrimitiveOptions, CloudflareR2Provider, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, LocalFileStorageProviderOptions, MemoryFileStorageProvider,
|
|
512
|
+
export { $bucket, AlephaBucket, BucketFileOptions, BucketPrimitive, BucketPrimitiveOptions, CloudflareR2Provider, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, LocalFileStorageProviderOptions, MemoryFileStorageProvider, NodeS3BucketProvider, localFileStorageOptions };
|
|
543
513
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/primitives/$bucket.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/CloudflareR2Provider.ts","../../src/bucket/providers/LocalFileStorageProvider.ts","../../src/bucket/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/primitives/$bucket.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/CloudflareR2Provider.ts","../../src/bucket/providers/LocalFileStorageProvider.ts","../../src/bucket/providers/NodeS3BucketProvider.ts","../../src/bucket/index.ts"],"mappings":"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"}
|
package/dist/bucket/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { mkdir, stat, unlink } from "node:fs/promises";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { $logger } from "alepha/logger";
|
|
9
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
10
|
+
import { S3mini } from "s3mini";
|
|
9
11
|
//#region ../../src/bucket/errors/InvalidFileError.ts
|
|
10
12
|
var InvalidFileError = class extends AlephaError {
|
|
11
13
|
status = 400;
|
|
@@ -164,7 +166,13 @@ var BucketPrimitive = class extends Primitive {
|
|
|
164
166
|
* Downloads a file from the bucket.
|
|
165
167
|
*/
|
|
166
168
|
async download(fileId) {
|
|
167
|
-
|
|
169
|
+
const file = await this.provider.download(this.name, fileId);
|
|
170
|
+
await this.alepha.events.emit("bucket:file:downloaded", {
|
|
171
|
+
id: fileId,
|
|
172
|
+
bucket: this,
|
|
173
|
+
file
|
|
174
|
+
});
|
|
175
|
+
return file;
|
|
168
176
|
}
|
|
169
177
|
$provider() {
|
|
170
178
|
if (!this.options.provider) return this.alepha.inject(FileStorageProvider);
|
|
@@ -265,6 +273,137 @@ var LocalFileStorageProvider = class {
|
|
|
265
273
|
}
|
|
266
274
|
};
|
|
267
275
|
//#endregion
|
|
276
|
+
//#region ../../src/bucket/providers/NodeS3BucketProvider.ts
|
|
277
|
+
const envSchema = t.object({
|
|
278
|
+
/**
|
|
279
|
+
* S3 endpoint URL. The bucket name is appended (path-style) per request.
|
|
280
|
+
*
|
|
281
|
+
* Examples:
|
|
282
|
+
* - AWS S3: `https://s3.us-east-1.amazonaws.com`
|
|
283
|
+
* - Cloudflare R2: `https://<account-id>.r2.cloudflarestorage.com`
|
|
284
|
+
* - MinIO: `http://localhost:9000`
|
|
285
|
+
* - DigitalOcean Spaces: `https://<region>.digitaloceanspaces.com`
|
|
286
|
+
*/
|
|
287
|
+
S3_ENDPOINT: t.string(),
|
|
288
|
+
/**
|
|
289
|
+
* AWS region or "auto" for R2.
|
|
290
|
+
*
|
|
291
|
+
* @default "auto"
|
|
292
|
+
*/
|
|
293
|
+
S3_REGION: t.optional(t.string()),
|
|
294
|
+
/**
|
|
295
|
+
* Access key ID for S3 authentication.
|
|
296
|
+
*/
|
|
297
|
+
S3_ACCESS_KEY_ID: t.string(),
|
|
298
|
+
/**
|
|
299
|
+
* Secret access key for S3 authentication.
|
|
300
|
+
*/
|
|
301
|
+
S3_SECRET_ACCESS_KEY: t.string()
|
|
302
|
+
});
|
|
303
|
+
/**
|
|
304
|
+
* S3-compatible file storage provider for Node.js.
|
|
305
|
+
*
|
|
306
|
+
* Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2,
|
|
307
|
+
* MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service.
|
|
308
|
+
*
|
|
309
|
+
* Uses path-style addressing (`<endpoint>/<bucket>`).
|
|
310
|
+
*/
|
|
311
|
+
var NodeS3BucketProvider = class {
|
|
312
|
+
log = $logger();
|
|
313
|
+
env = $env(envSchema);
|
|
314
|
+
alepha = $inject(Alepha);
|
|
315
|
+
fileSystem = $inject(FileSystemProvider);
|
|
316
|
+
fileDetector = $inject(FileDetector);
|
|
317
|
+
clients = /* @__PURE__ */ new Map();
|
|
318
|
+
/**
|
|
319
|
+
* Convert bucket name to S3-compatible format.
|
|
320
|
+
* S3 bucket names must be lowercase, 3-63 characters, no underscores.
|
|
321
|
+
*/
|
|
322
|
+
convertName(name) {
|
|
323
|
+
return name.replaceAll("/", "-").replaceAll("_", "-").toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
getClient(bucketName) {
|
|
326
|
+
const name = this.convertName(bucketName);
|
|
327
|
+
let client = this.clients.get(name);
|
|
328
|
+
if (!client) {
|
|
329
|
+
const endpoint = this.env.S3_ENDPOINT.replace(/\/+$/, "");
|
|
330
|
+
client = new S3mini({
|
|
331
|
+
accessKeyId: this.env.S3_ACCESS_KEY_ID,
|
|
332
|
+
secretAccessKey: this.env.S3_SECRET_ACCESS_KEY,
|
|
333
|
+
region: this.env.S3_REGION || "auto",
|
|
334
|
+
endpoint: `${endpoint}/${name}`
|
|
335
|
+
});
|
|
336
|
+
this.clients.set(name, client);
|
|
337
|
+
}
|
|
338
|
+
return client;
|
|
339
|
+
}
|
|
340
|
+
onStart = $hook({
|
|
341
|
+
on: "start",
|
|
342
|
+
handler: async () => {
|
|
343
|
+
for (const bucket of this.alepha.primitives($bucket)) {
|
|
344
|
+
if (bucket.provider !== this) continue;
|
|
345
|
+
const name = this.convertName(bucket.name);
|
|
346
|
+
const client = this.getClient(bucket.name);
|
|
347
|
+
this.log.debug(`Preparing S3 bucket '${name}'...`);
|
|
348
|
+
if (!await client.bucketExists()) {
|
|
349
|
+
this.log.debug(`Creating S3 bucket '${name}'...`);
|
|
350
|
+
if (!await client.createBucket()) throw new AlephaError(`Failed to create S3 bucket '${name}'`);
|
|
351
|
+
}
|
|
352
|
+
this.log.info(`S3 bucket '${bucket.name}' OK`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
createId(mimeType) {
|
|
357
|
+
const ext = this.fileDetector.getExtensionFromMimeType(mimeType);
|
|
358
|
+
return `${crypto.randomUUID()}.${ext}`;
|
|
359
|
+
}
|
|
360
|
+
async upload(bucketName, file, fileId) {
|
|
361
|
+
fileId ??= this.createId(file.type);
|
|
362
|
+
this.log.trace(`Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`);
|
|
363
|
+
const client = this.getClient(bucketName);
|
|
364
|
+
try {
|
|
365
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
366
|
+
await client.putObject(fileId, buffer, file.type || "application/octet-stream", void 0, { "x-amz-meta-name": encodeURIComponent(file.name) }, file.size);
|
|
367
|
+
this.log.trace(`File uploaded successfully: ${fileId}`);
|
|
368
|
+
return fileId;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
this.log.error(`Failed to upload file: ${error}`);
|
|
371
|
+
if (error instanceof Error) throw new AlephaError(`Upload failed: ${error.message}`, { cause: error });
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async download(bucketName, fileId) {
|
|
376
|
+
this.log.trace(`Downloading file '${fileId}' from bucket '${bucketName}'...`);
|
|
377
|
+
const response = await this.getClient(bucketName).getObjectResponse(fileId);
|
|
378
|
+
if (!response) throw new FileNotFoundError(`File '${fileId}' not found in bucket '${bucketName}'`);
|
|
379
|
+
const buffer = Buffer$1.from(await response.arrayBuffer());
|
|
380
|
+
const mimeType = response.headers.get("content-type") || this.fileDetector.getContentType(fileId);
|
|
381
|
+
const metaName = response.headers.get("x-amz-meta-name");
|
|
382
|
+
const name = metaName ? decodeURIComponent(metaName) : fileId;
|
|
383
|
+
return this.fileSystem.createFile({
|
|
384
|
+
buffer,
|
|
385
|
+
name,
|
|
386
|
+
type: mimeType,
|
|
387
|
+
size: buffer.length
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async exists(bucketName, fileId) {
|
|
391
|
+
this.log.trace(`Checking existence of file '${fileId}' in bucket '${bucketName}'...`);
|
|
392
|
+
return await this.getClient(bucketName).objectExists(fileId) === true;
|
|
393
|
+
}
|
|
394
|
+
async delete(bucketName, fileId) {
|
|
395
|
+
this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);
|
|
396
|
+
const client = this.getClient(bucketName);
|
|
397
|
+
try {
|
|
398
|
+
await client.deleteObject(fileId);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
this.log.error(`Failed to delete file: ${error}`);
|
|
401
|
+
if (error instanceof Error) throw new FileNotFoundError("Error deleting file", { cause: error });
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
//#endregion
|
|
268
407
|
//#region ../../src/bucket/providers/CloudflareR2Provider.ts
|
|
269
408
|
/**
|
|
270
409
|
* Cloudflare R2 storage provider.
|
|
@@ -314,7 +453,11 @@ var LocalFileStorageProvider = class {
|
|
|
314
453
|
var CloudflareR2Provider = class {
|
|
315
454
|
alepha = $inject(Alepha);
|
|
316
455
|
log = $logger();
|
|
317
|
-
env = $env(t.object({
|
|
456
|
+
env = $env(t.object({
|
|
457
|
+
/**
|
|
458
|
+
* The actual R2 bucket name in Cloudflare.
|
|
459
|
+
*/
|
|
460
|
+
R2_BUCKET_NAME: t.string({ description: "R2 bucket name in Cloudflare" }) }));
|
|
318
461
|
r2;
|
|
319
462
|
/**
|
|
320
463
|
* Get the R2 bucket name from environment.
|
|
@@ -427,16 +570,21 @@ const AlephaBucket = $module({
|
|
|
427
570
|
name: "alepha.bucket",
|
|
428
571
|
primitives: [$bucket],
|
|
429
572
|
services: [FileStorageProvider],
|
|
430
|
-
variants: [
|
|
573
|
+
variants: [
|
|
574
|
+
MemoryFileStorageProvider,
|
|
575
|
+
LocalFileStorageProvider,
|
|
576
|
+
NodeS3BucketProvider
|
|
577
|
+
],
|
|
431
578
|
register: (alepha) => {
|
|
579
|
+
const useS3 = !!alepha.env.S3_ENDPOINT;
|
|
432
580
|
alepha.with({
|
|
433
581
|
optional: true,
|
|
434
582
|
provide: FileStorageProvider,
|
|
435
|
-
use: alepha.isTest() || alepha.isServerless() ? MemoryFileStorageProvider : LocalFileStorageProvider
|
|
583
|
+
use: alepha.isTest() || alepha.isServerless() ? MemoryFileStorageProvider : useS3 ? NodeS3BucketProvider : LocalFileStorageProvider
|
|
436
584
|
});
|
|
437
585
|
}
|
|
438
586
|
});
|
|
439
587
|
//#endregion
|
|
440
|
-
export { $bucket, AlephaBucket, BucketPrimitive, CloudflareR2Provider, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, MemoryFileStorageProvider, localFileStorageOptions };
|
|
588
|
+
export { $bucket, AlephaBucket, BucketPrimitive, CloudflareR2Provider, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, MemoryFileStorageProvider, NodeS3BucketProvider, localFileStorageOptions };
|
|
441
589
|
|
|
442
590
|
//# sourceMappingURL=index.js.map
|