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
package/dist/crypto/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n}\n","import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n // It's not ideal from a security pov but it's the best for convenience,\n // it can be changed to a hard error in a future release.\n if (\n this.secretKey === DEFAULT_SECRET_KEY_VALUE &&\n this.alepha.isProduction()\n ) {\n this.log.warn(\n \"Using default secret key. Please set a secure APP_SECRET environment variable.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":";;;;AAcA,IAAa,iBAAb,MAAa,eAAe;CAC1B,OAA0B,iBAAgC;EACxD,GAAG;EACH,GAAG;EACH,GAAG;EACJ;CACD,OAA0B,oBAAoB;CAC9C,OAA0B,cAAc;CACxC,OAA0B,gBAAgB;CAC1C,OAA0B,gBAAgB;CAC1C,OAA0B,iBAAiB;CAC3C,OAA0B,iBAAiB;CAE3C,MAAa,aAAa,UAAmC;EAC3D,MAAM,OAAO,YAAY,eAAe,YAAY,CAAC,SAAS,MAAM;AAOpE,SAAO,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n}\n","import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n // It's not ideal from a security pov but it's the best for convenience,\n // it can be changed to a hard error in a future release.\n if (\n this.secretKey === DEFAULT_SECRET_KEY_VALUE &&\n this.alepha.isProduction()\n ) {\n this.log.warn(\n \"Using default secret key. Please set a secure APP_SECRET environment variable.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":"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"}
|
package/dist/datetime/index.d.ts
CHANGED
|
@@ -1,13 +1,72 @@
|
|
|
1
1
|
import * as _$alepha from "alepha";
|
|
2
2
|
import { Alepha, KIND, Middleware, Primitive } from "alepha";
|
|
3
|
-
import dayjsDuration from "dayjs/plugin/duration.js";
|
|
4
|
-
import
|
|
3
|
+
import dayjsDuration, { DurationUnitType } from "dayjs/plugin/duration.js";
|
|
4
|
+
import { Dayjs, ManipulateType, OpUnitType, PluginFunc, QUnitType } from "dayjs";
|
|
5
5
|
|
|
6
6
|
//#region ../../src/datetime/providers/DateTimeProvider.d.ts
|
|
7
|
-
type
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
type DateTimeInput = string | number | Date | DateTime | Dayjs;
|
|
8
|
+
type DurationLike = number | Duration | [number, ManipulateType];
|
|
9
|
+
/**
|
|
10
|
+
* Immutable wrapper around the underlying date-time engine.
|
|
11
|
+
*
|
|
12
|
+
* Designed to isolate consumers from the engine in use (currently dayjs).
|
|
13
|
+
* Methods that produce a new value return a new `DateTime` instance.
|
|
14
|
+
*/
|
|
15
|
+
declare class DateTime {
|
|
16
|
+
protected readonly inner: Dayjs;
|
|
17
|
+
constructor(inner: Dayjs);
|
|
18
|
+
/**
|
|
19
|
+
* Add a duration to this date-time.
|
|
20
|
+
*/
|
|
21
|
+
add(amount: number, unit?: ManipulateType): DateTime;
|
|
22
|
+
add(duration: Duration): DateTime;
|
|
23
|
+
/**
|
|
24
|
+
* Subtract a duration from this date-time.
|
|
25
|
+
*/
|
|
26
|
+
subtract(amount: number, unit?: ManipulateType): DateTime;
|
|
27
|
+
subtract(duration: Duration): DateTime;
|
|
28
|
+
startOf(unit: OpUnitType): DateTime;
|
|
29
|
+
endOf(unit: OpUnitType): DateTime;
|
|
30
|
+
isAfter(other: DateTimeInput): boolean;
|
|
31
|
+
isBefore(other: DateTimeInput): boolean;
|
|
32
|
+
isSame(other: DateTimeInput, unit?: OpUnitType): boolean;
|
|
33
|
+
diff(other: DateTimeInput, unit?: QUnitType | OpUnitType): number;
|
|
34
|
+
tz(timezone: string): DateTime;
|
|
35
|
+
locale(lang: string): DateTime;
|
|
36
|
+
format(template?: string): string;
|
|
37
|
+
fromNow(withoutSuffix?: boolean): string;
|
|
38
|
+
toISOString(): string;
|
|
39
|
+
toDate(): Date;
|
|
40
|
+
valueOf(): number;
|
|
41
|
+
unix(): number;
|
|
42
|
+
toJSON(): string;
|
|
43
|
+
toString(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Escape hatch for the underlying dayjs instance.
|
|
46
|
+
*
|
|
47
|
+
* Use sparingly — anything calling this becomes coupled to dayjs and
|
|
48
|
+
* will need to migrate when the engine is replaced.
|
|
49
|
+
*/
|
|
50
|
+
toDayjs(): Dayjs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Immutable wrapper around the underlying duration engine.
|
|
54
|
+
*/
|
|
55
|
+
declare class Duration {
|
|
56
|
+
protected readonly inner: dayjsDuration.Duration;
|
|
57
|
+
constructor(inner: dayjsDuration.Duration);
|
|
58
|
+
asMilliseconds(): number;
|
|
59
|
+
asSeconds(): number;
|
|
60
|
+
asMinutes(): number;
|
|
61
|
+
asHours(): number;
|
|
62
|
+
asDays(): number;
|
|
63
|
+
as(unit: DurationUnitType): number;
|
|
64
|
+
toISOString(): string;
|
|
65
|
+
/**
|
|
66
|
+
* Escape hatch for the underlying dayjs duration.
|
|
67
|
+
*/
|
|
68
|
+
toDayjs(): dayjsDuration.Duration;
|
|
69
|
+
}
|
|
11
70
|
declare const isDateTime: (value: unknown) => value is DateTime;
|
|
12
71
|
declare class DateTimeProvider {
|
|
13
72
|
static PLUGINS: Array<PluginFunc<any>>;
|
|
@@ -23,15 +82,15 @@ declare class DateTimeProvider {
|
|
|
23
82
|
/**
|
|
24
83
|
* Create a new UTC DateTime instance.
|
|
25
84
|
*/
|
|
26
|
-
utc(date:
|
|
85
|
+
utc(date: DateTimeInput | null | undefined): DateTime;
|
|
27
86
|
/**
|
|
28
87
|
* Create a new DateTime instance.
|
|
29
88
|
*/
|
|
30
|
-
of(date:
|
|
89
|
+
of(date: DateTimeInput | null | undefined): DateTime;
|
|
31
90
|
/**
|
|
32
91
|
* Get the current date as a string.
|
|
33
92
|
*/
|
|
34
|
-
toISOString(date?:
|
|
93
|
+
toISOString(date?: DateTimeInput): string;
|
|
35
94
|
/**
|
|
36
95
|
* Get the current date.
|
|
37
96
|
*/
|
|
@@ -234,5 +293,5 @@ declare const $timeout: (duration: DurationLike) => Middleware;
|
|
|
234
293
|
*/
|
|
235
294
|
declare const AlephaDateTime: _$alepha.Service<_$alepha.Module>;
|
|
236
295
|
//#endregion
|
|
237
|
-
export { $debounce, $interval, $throttle, $timeout, AlephaDateTime, DateTime, DateTimeProvider, DebounceOptions, Duration, DurationLike, Interval, IntervalPrimitive, IntervalPrimitiveOptions, ThrottleOptions, Timeout,
|
|
296
|
+
export { $debounce, $interval, $throttle, $timeout, AlephaDateTime, DateTime, DateTimeInput, DateTimeProvider, DebounceOptions, Duration, DurationLike, type DurationUnitType, Interval, IntervalPrimitive, IntervalPrimitiveOptions, type ManipulateType, type OpUnitType, type QUnitType, ThrottleOptions, Timeout, isDateTime };
|
|
238
297
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"mappings":";;;;;;KAuBY,aAAA,qBAAkC,IAAA,GAAO,QAAA,GAAW,KAAA;AAAA,KAEpD,YAAA,YAAwB,QAAA,YAAoB,cAAA;;;;;;;cAQ3C,QAAA;EAAA,mBACQ,KAAA,EAAO,KAAA;cAEd,KAAA,EAAO,KAAA;EAb2C;;;EAoB9D,GAAA,CAAI,MAAA,UAAgB,IAAA,GAAO,cAAA,GAAiB,QAAA;EAC5C,GAAA,CAAI,QAAA,EAAU,QAAA,GAAW,QAAA;;;;EAWzB,QAAA,CAAS,MAAA,UAAgB,IAAA,GAAO,cAAA,GAAiB,QAAA;EACjD,QAAA,CAAS,QAAA,EAAU,QAAA,GAAW,QAAA;EAQ9B,OAAA,CAAQ,IAAA,EAAM,UAAA,GAAa,QAAA;EAI3B,KAAA,CAAM,IAAA,EAAM,UAAA,GAAa,QAAA;EAIzB,OAAA,CAAQ,KAAA,EAAO,aAAA;EAIf,QAAA,CAAS,KAAA,EAAO,aAAA;EAIhB,MAAA,CAAO,KAAA,EAAO,aAAA,EAAe,IAAA,GAAO,UAAA;EAIpC,IAAA,CAAK,KAAA,EAAO,aAAA,EAAe,IAAA,GAAO,SAAA,GAAY,UAAA;EAI9C,EAAA,CAAG,QAAA,WAAmB,QAAA;EAItB,MAAA,CAAO,IAAA,WAAe,QAAA;EAItB,MAAA,CAAO,QAAA;EAIP,OAAA,CAAQ,aAAA;EAIR,WAAA,CAAA;EAIA,MAAA,CAAA,GAAU,IAAA;EAIV,OAAA,CAAA;EAIA,IAAA,CAAA;EAIA,MAAA,CAAA;EAIA,QAAA,CAAA;EAhDgB;;;;;;EA0DhB,OAAA,CAAA,GAAW,KAAA;AAAA;;;;cAQA,QAAA;EAAA,mBACQ,KAAA,EAAO,aAAA,CAAc,QAAA;cAE5B,KAAA,EAAO,aAAA,CAAc,QAAA;EAIjC,cAAA,CAAA;EAIA,SAAA,CAAA;EAIA,SAAA,CAAA;EAIA,OAAA,CAAA;EAIA,MAAA,CAAA;EAIA,EAAA,CAAG,IAAA,EAAM,gBAAA;EAIT,WAAA,CAAA;EAlI4C;;;EAyI5C,OAAA,CAAA,GAAW,aAAA,CAAc,QAAA;AAAA;AAAA,cAKd,UAAA,GAAc,KAAA,cAAiB,KAAA,IAAS,QAAA;AAAA,cAWxC,gBAAA;EAAA,OACG,OAAA,EAAS,KAAA,CAAM,UAAA;EAAA,UAQnB,MAAA,EAAM,MAAA;EAAA,UACN,GAAA,EAAK,QAAA;EAAA,mBACI,QAAA,EAAU,OAAA;EAAA,mBACV,SAAA,EAAW,QAAA;;qBAQX,OAAA,EARmB,QAAA,CAQZ,aAAA;EAAA,mBAgBP,MAAA,EAhBO,QAAA,CAgBD,aAAA;EAelB,SAAA,CAAU,MAAA;EAIV,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,QAAA;EA3LjB;;;EAkMpB,GAAA,CAAI,IAAA,EAAM,aAAA,sBAAmC,QAAA;EA9L3B;;;EAqMlB,EAAA,CAAG,IAAA,EAAM,aAAA,sBAAmC,QAAA;EA7LnD;;;EAuMO,WAAA,CAAY,IAAA,GAAM,aAAA;EAnMX;;;EA0MP,GAAA,CAAA,GAAO,QAAA;EAtMd;;;;;EA+MO,YAAA,CAAA;EA3MP;;;;;EAuNO,SAAA,CAAA;EA/MP;;;;;EAAA,UA2NU,cAAA,CAAA,GAAkB,QAAA;EA/MlB;;;EA0NH,QAAA,GACL,QAAA,EAAU,YAAA,EACV,IAAA,GAAO,cAAA,KACN,QAAA;EAgBI,cAAA,CAAe,KAAA,YAAiB,KAAA,IAAS,YAAA;EAnNhD;;;;EAqOa,IAAA,CAAA,GAAQ,OAAA;EA7NF;;;;;;EAuOZ,IAAA,CACL,QAAA,EAAU,YAAA,EACV,OAAA;IACE,MAAA,GAAS,WAAA;IACT,GAAA;EAAA,IAED,OAAA;EA2BI,cAAA,CACL,GAAA,iBACA,QAAA,EAAU,YAAA,EACV,KAAA,aACC,QAAA;;;;EAkBI,aAAA,CACL,QAAA,cACA,QAAA,EAAU,YAAA,EACV,GAAA,YACC,OAAA;EAkCI,YAAA,CAAa,OAAA,EAAS,OAAA;EAUtB,aAAA,CAAc,QAAA,EAAU,QAAA;EA/T/B;;;EAwUa,QAAA,GAAA,CACX,EAAA,GAAK,MAAA,EAAQ,WAAA,KAAgB,OAAA,CAAQ,CAAA,GACrC,QAAA,EAAU,YAAA,GACT,OAAA,CAAQ,CAAA;EA/TF;;;EAgVI,MAAA,CACX,QAAA,EAAU,YAAA,EACV,IAAA,GAAO,cAAA,GACN,OAAA;EAxUQ;;;EAgYJ,KAAA,CAAA,GAAS,QAAA;EA3XL;;;EAmYJ,KAAA,CAAA;AAAA;AAAA,UAYQ,QAAA;EACf,KAAA;EACA,QAAA;EACA,GAAA;AAAA;AAAA,UAGe,OAAA;EACf,GAAA;EACA,KAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA;AAAA;;;UC7kBe,eAAA;;;;EAIf,KAAA,EAAO,YAAA;;;;;EAMP,GAAA,OAAU,IAAA;AAAA;;;;;;;;;;;;;ADSZ;;;;;AAQA;cCIa,SAAA,GAAa,OAAA,EAAS,eAAA,KAAkB,UAAA;;;;;;;;cC1BxC,SAAA;EAAA,UAAsB,wBAAA,GAAwB,iBAAA;EAAA;;UAK1C,wBAAA;EFOL;;;EEHV,OAAA;EFGmD;;;EEEnD,QAAA,EAAU,YAAA;AAAA;AAAA,cAKC,iBAAA,SAA0B,SAAA,CAAU,wBAAA;EAAA,mBAC5B,gBAAA,EAAgB,gBAAA;EAE5B,MAAA;EAAA,UAEG,MAAA,CAAA;AAAA;;;UC7BK,eAAA;;;;EAIf,IAAA;;;;EAKA,GAAA,EAAK,YAAA;AAAA;AHQP;;;;;;;;;;;;;AAEA;;;;AAFA,cGYa,SAAA,GAAa,OAAA,EAAS,eAAA,KAAkB,UAAA;;;;;;;;;;;;;AHZrD;;;;;;;;cICa,QAAA,GAAY,QAAA,EAAU,YAAA,KAAe,UAAA;;;;;;;;;;AJDlD;;;;cKAa,cAAA,EAAc,QAAA,CAAA,OAAA,CAIzB,QAAA,CAJyB,MAAA"}
|
package/dist/datetime/index.js
CHANGED
|
@@ -8,9 +8,125 @@ import "dayjs/locale/ar.js";
|
|
|
8
8
|
import "dayjs/locale/fr.js";
|
|
9
9
|
import DayjsApi from "dayjs";
|
|
10
10
|
//#region ../../src/datetime/providers/DateTimeProvider.ts
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Immutable wrapper around the underlying date-time engine.
|
|
13
|
+
*
|
|
14
|
+
* Designed to isolate consumers from the engine in use (currently dayjs).
|
|
15
|
+
* Methods that produce a new value return a new `DateTime` instance.
|
|
16
|
+
*/
|
|
17
|
+
var DateTime = class DateTime {
|
|
18
|
+
inner;
|
|
19
|
+
constructor(inner) {
|
|
20
|
+
this.inner = inner;
|
|
21
|
+
}
|
|
22
|
+
add(amount, unit) {
|
|
23
|
+
if (amount instanceof Duration) return new DateTime(this.inner.add(amount.toDayjs()));
|
|
24
|
+
return new DateTime(this.inner.add(amount, unit));
|
|
25
|
+
}
|
|
26
|
+
subtract(amount, unit) {
|
|
27
|
+
if (amount instanceof Duration) return new DateTime(this.inner.subtract(amount.toDayjs()));
|
|
28
|
+
return new DateTime(this.inner.subtract(amount, unit));
|
|
29
|
+
}
|
|
30
|
+
startOf(unit) {
|
|
31
|
+
return new DateTime(this.inner.startOf(unit));
|
|
32
|
+
}
|
|
33
|
+
endOf(unit) {
|
|
34
|
+
return new DateTime(this.inner.endOf(unit));
|
|
35
|
+
}
|
|
36
|
+
isAfter(other) {
|
|
37
|
+
return this.inner.isAfter(toDayjs(other));
|
|
38
|
+
}
|
|
39
|
+
isBefore(other) {
|
|
40
|
+
return this.inner.isBefore(toDayjs(other));
|
|
41
|
+
}
|
|
42
|
+
isSame(other, unit) {
|
|
43
|
+
return this.inner.isSame(toDayjs(other), unit);
|
|
44
|
+
}
|
|
45
|
+
diff(other, unit) {
|
|
46
|
+
return this.inner.diff(toDayjs(other), unit);
|
|
47
|
+
}
|
|
48
|
+
tz(timezone) {
|
|
49
|
+
return new DateTime(this.inner.tz(timezone));
|
|
50
|
+
}
|
|
51
|
+
locale(lang) {
|
|
52
|
+
return new DateTime(this.inner.locale(lang));
|
|
53
|
+
}
|
|
54
|
+
format(template) {
|
|
55
|
+
return this.inner.format(template);
|
|
56
|
+
}
|
|
57
|
+
fromNow(withoutSuffix) {
|
|
58
|
+
return this.inner.fromNow(withoutSuffix);
|
|
59
|
+
}
|
|
60
|
+
toISOString() {
|
|
61
|
+
return this.inner.toISOString();
|
|
62
|
+
}
|
|
63
|
+
toDate() {
|
|
64
|
+
return this.inner.toDate();
|
|
65
|
+
}
|
|
66
|
+
valueOf() {
|
|
67
|
+
return this.inner.valueOf();
|
|
68
|
+
}
|
|
69
|
+
unix() {
|
|
70
|
+
return this.inner.unix();
|
|
71
|
+
}
|
|
72
|
+
toJSON() {
|
|
73
|
+
return this.inner.toISOString();
|
|
74
|
+
}
|
|
75
|
+
toString() {
|
|
76
|
+
return this.inner.toISOString();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Escape hatch for the underlying dayjs instance.
|
|
80
|
+
*
|
|
81
|
+
* Use sparingly — anything calling this becomes coupled to dayjs and
|
|
82
|
+
* will need to migrate when the engine is replaced.
|
|
83
|
+
*/
|
|
84
|
+
toDayjs() {
|
|
85
|
+
return this.inner;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Immutable wrapper around the underlying duration engine.
|
|
90
|
+
*/
|
|
91
|
+
var Duration = class {
|
|
92
|
+
inner;
|
|
93
|
+
constructor(inner) {
|
|
94
|
+
this.inner = inner;
|
|
95
|
+
}
|
|
96
|
+
asMilliseconds() {
|
|
97
|
+
return this.inner.asMilliseconds();
|
|
98
|
+
}
|
|
99
|
+
asSeconds() {
|
|
100
|
+
return this.inner.asSeconds();
|
|
101
|
+
}
|
|
102
|
+
asMinutes() {
|
|
103
|
+
return this.inner.asMinutes();
|
|
104
|
+
}
|
|
105
|
+
asHours() {
|
|
106
|
+
return this.inner.asHours();
|
|
107
|
+
}
|
|
108
|
+
asDays() {
|
|
109
|
+
return this.inner.asDays();
|
|
110
|
+
}
|
|
111
|
+
as(unit) {
|
|
112
|
+
return this.inner.as(unit);
|
|
113
|
+
}
|
|
114
|
+
toISOString() {
|
|
115
|
+
return this.inner.toISOString();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Escape hatch for the underlying dayjs duration.
|
|
119
|
+
*/
|
|
120
|
+
toDayjs() {
|
|
121
|
+
return this.inner;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
12
124
|
const isDateTime = (value) => {
|
|
13
|
-
return
|
|
125
|
+
return value instanceof DateTime;
|
|
126
|
+
};
|
|
127
|
+
const toDayjs = (value) => {
|
|
128
|
+
if (value instanceof DateTime) return value.toDayjs();
|
|
129
|
+
return DayjsApi(value);
|
|
14
130
|
};
|
|
15
131
|
var DateTimeProvider = class DateTimeProvider {
|
|
16
132
|
static PLUGINS = [
|
|
@@ -25,7 +141,7 @@ var DateTimeProvider = class DateTimeProvider {
|
|
|
25
141
|
timeouts = [];
|
|
26
142
|
intervals = [];
|
|
27
143
|
constructor() {
|
|
28
|
-
for (const plugin of DateTimeProvider.PLUGINS)
|
|
144
|
+
for (const plugin of DateTimeProvider.PLUGINS) DayjsApi.extend(plugin);
|
|
29
145
|
}
|
|
30
146
|
onStart = $hook({
|
|
31
147
|
on: "start",
|
|
@@ -49,22 +165,23 @@ var DateTimeProvider = class DateTimeProvider {
|
|
|
49
165
|
}
|
|
50
166
|
});
|
|
51
167
|
setLocale(locale) {
|
|
52
|
-
|
|
168
|
+
DayjsApi.locale(locale);
|
|
53
169
|
}
|
|
54
170
|
isDateTime(value) {
|
|
55
|
-
return
|
|
171
|
+
return value instanceof DateTime;
|
|
56
172
|
}
|
|
57
173
|
/**
|
|
58
174
|
* Create a new UTC DateTime instance.
|
|
59
175
|
*/
|
|
60
176
|
utc(date) {
|
|
61
|
-
return
|
|
177
|
+
return new DateTime(DayjsApi.utc(unwrap(date)));
|
|
62
178
|
}
|
|
63
179
|
/**
|
|
64
180
|
* Create a new DateTime instance.
|
|
65
181
|
*/
|
|
66
182
|
of(date) {
|
|
67
|
-
|
|
183
|
+
if (date instanceof DateTime) return date;
|
|
184
|
+
return new DateTime(DayjsApi(date));
|
|
68
185
|
}
|
|
69
186
|
/**
|
|
70
187
|
* Get the current date as a string.
|
|
@@ -103,19 +220,20 @@ var DateTimeProvider = class DateTimeProvider {
|
|
|
103
220
|
*/
|
|
104
221
|
getCurrentDate() {
|
|
105
222
|
if (this.ref) return this.ref;
|
|
106
|
-
return
|
|
223
|
+
return new DateTime(DayjsApi());
|
|
107
224
|
}
|
|
108
225
|
/**
|
|
109
226
|
* Create a new Duration instance.
|
|
110
227
|
*/
|
|
111
228
|
duration = (duration, unit) => {
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
229
|
+
if (duration instanceof Duration) return duration;
|
|
230
|
+
if (Array.isArray(duration)) return new Duration(DayjsApi.duration(duration[0], duration[1]));
|
|
231
|
+
if (typeof duration === "number") return new Duration(DayjsApi.duration(duration, unit || "milliseconds"));
|
|
114
232
|
return duration;
|
|
115
233
|
};
|
|
116
234
|
isDurationLike(value) {
|
|
117
235
|
try {
|
|
118
|
-
return
|
|
236
|
+
return DayjsApi.isDuration(this.duration(value).toDayjs());
|
|
119
237
|
} catch {
|
|
120
238
|
return false;
|
|
121
239
|
}
|
|
@@ -165,7 +283,7 @@ var DateTimeProvider = class DateTimeProvider {
|
|
|
165
283
|
*/
|
|
166
284
|
createTimeout(callback, duration, now) {
|
|
167
285
|
if (this.ref && now) {
|
|
168
|
-
if (this.of(now).add(this.duration(duration)) < this.now()) callback();
|
|
286
|
+
if (this.of(now).add(this.duration(duration)).valueOf() < this.now().valueOf()) callback();
|
|
169
287
|
return {
|
|
170
288
|
now,
|
|
171
289
|
duration: 0,
|
|
@@ -258,6 +376,10 @@ var DateTimeProvider = class DateTimeProvider {
|
|
|
258
376
|
this.ref = null;
|
|
259
377
|
}
|
|
260
378
|
};
|
|
379
|
+
const unwrap = (value) => {
|
|
380
|
+
if (value instanceof DateTime) return value.toDayjs();
|
|
381
|
+
return value;
|
|
382
|
+
};
|
|
261
383
|
//#endregion
|
|
262
384
|
//#region ../../src/datetime/primitives/$interval.ts
|
|
263
385
|
/**
|
|
@@ -440,6 +562,6 @@ const AlephaDateTime = $module({
|
|
|
440
562
|
services: [DateTimeProvider]
|
|
441
563
|
});
|
|
442
564
|
//#endregion
|
|
443
|
-
export { $debounce, $interval, $throttle, $timeout, AlephaDateTime, DateTimeProvider,
|
|
565
|
+
export { $debounce, $interval, $throttle, $timeout, AlephaDateTime, DateTime, DateTimeProvider, Duration, IntervalPrimitive, isDateTime };
|
|
444
566
|
|
|
445
567
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type PluginFunc,\n} from \"dayjs\";\nimport dayjsDuration from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type DateTime = DayjsApi.Dayjs;\nexport type Duration = dayjsDuration.Duration;\nexport type DurationLike =\n | number\n | dayjsDuration.Duration\n | [number, ManipulateType];\n\nexport const dayjs = DayjsApi;\nexport const isDateTime = (value: unknown): value is DateTime => {\n return dayjs.isDayjs(value);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n dayjs.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n dayjs.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return dayjs.isDayjs(value);\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(\n date: string | number | Date | Dayjs | null | undefined,\n ): DateTime {\n return dayjs.utc(date);\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: string | number | Date | Dayjs | null | undefined): DateTime {\n return dayjs(date);\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: Date | string | DateTime = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return dayjs();\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (Array.isArray(duration)) {\n return dayjs.duration(duration[0], duration[1]);\n }\n\n if (typeof duration === \"number\") {\n return dayjs.duration(duration, unit || \"milliseconds\");\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return dayjs.isDuration(this.duration(value as DurationLike));\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next < this.now()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed). Use `$rateLimit` for distributed rate limiting.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type OpUnitType,\n type PluginFunc,\n type QUnitType,\n} from \"dayjs\";\nimport dayjsDuration, { type DurationUnitType } from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type { DurationUnitType, ManipulateType, OpUnitType, QUnitType };\n\nexport type DateTimeInput = string | number | Date | DateTime | Dayjs;\n\nexport type DurationLike = number | Duration | [number, ManipulateType];\n\n/**\n * Immutable wrapper around the underlying date-time engine.\n *\n * Designed to isolate consumers from the engine in use (currently dayjs).\n * Methods that produce a new value return a new `DateTime` instance.\n */\nexport class DateTime {\n protected readonly inner: Dayjs;\n\n constructor(inner: Dayjs) {\n this.inner = inner;\n }\n\n /**\n * Add a duration to this date-time.\n */\n add(amount: number, unit?: ManipulateType): DateTime;\n add(duration: Duration): DateTime;\n add(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.add(amount.toDayjs()));\n }\n return new DateTime(this.inner.add(amount, unit));\n }\n\n /**\n * Subtract a duration from this date-time.\n */\n subtract(amount: number, unit?: ManipulateType): DateTime;\n subtract(duration: Duration): DateTime;\n subtract(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.subtract(amount.toDayjs()));\n }\n return new DateTime(this.inner.subtract(amount, unit));\n }\n\n startOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.startOf(unit));\n }\n\n endOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.endOf(unit));\n }\n\n isAfter(other: DateTimeInput): boolean {\n return this.inner.isAfter(toDayjs(other));\n }\n\n isBefore(other: DateTimeInput): boolean {\n return this.inner.isBefore(toDayjs(other));\n }\n\n isSame(other: DateTimeInput, unit?: OpUnitType): boolean {\n return this.inner.isSame(toDayjs(other), unit);\n }\n\n diff(other: DateTimeInput, unit?: QUnitType | OpUnitType): number {\n return this.inner.diff(toDayjs(other), unit);\n }\n\n tz(timezone: string): DateTime {\n return new DateTime(this.inner.tz(timezone));\n }\n\n locale(lang: string): DateTime {\n return new DateTime(this.inner.locale(lang));\n }\n\n format(template?: string): string {\n return this.inner.format(template);\n }\n\n fromNow(withoutSuffix?: boolean): string {\n return this.inner.fromNow(withoutSuffix);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n toDate(): Date {\n return this.inner.toDate();\n }\n\n valueOf(): number {\n return this.inner.valueOf();\n }\n\n unix(): number {\n return this.inner.unix();\n }\n\n toJSON(): string {\n return this.inner.toISOString();\n }\n\n toString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs instance.\n *\n * Use sparingly — anything calling this becomes coupled to dayjs and\n * will need to migrate when the engine is replaced.\n */\n toDayjs(): Dayjs {\n return this.inner;\n }\n}\n\n/**\n * Immutable wrapper around the underlying duration engine.\n */\nexport class Duration {\n protected readonly inner: dayjsDuration.Duration;\n\n constructor(inner: dayjsDuration.Duration) {\n this.inner = inner;\n }\n\n asMilliseconds(): number {\n return this.inner.asMilliseconds();\n }\n\n asSeconds(): number {\n return this.inner.asSeconds();\n }\n\n asMinutes(): number {\n return this.inner.asMinutes();\n }\n\n asHours(): number {\n return this.inner.asHours();\n }\n\n asDays(): number {\n return this.inner.asDays();\n }\n\n as(unit: DurationUnitType): number {\n return this.inner.as(unit);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs duration.\n */\n toDayjs(): dayjsDuration.Duration {\n return this.inner;\n }\n}\n\nexport const isDateTime = (value: unknown): value is DateTime => {\n return value instanceof DateTime;\n};\n\nconst toDayjs = (value: DateTimeInput): Dayjs => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return DayjsApi(value as any);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n DayjsApi.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n DayjsApi.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return value instanceof DateTime;\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(date: DateTimeInput | null | undefined): DateTime {\n return new DateTime(DayjsApi.utc(unwrap(date)));\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: DateTimeInput | null | undefined): DateTime {\n if (date instanceof DateTime) {\n return date;\n }\n return new DateTime(DayjsApi(date as any));\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: DateTimeInput = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return new DateTime(DayjsApi());\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (duration instanceof Duration) {\n return duration;\n }\n\n if (Array.isArray(duration)) {\n return new Duration(DayjsApi.duration(duration[0], duration[1]));\n }\n\n if (typeof duration === \"number\") {\n return new Duration(DayjsApi.duration(duration, unit || \"milliseconds\"));\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return DayjsApi.isDuration(\n this.duration(value as DurationLike).toDayjs(),\n );\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next.valueOf() < this.now().valueOf()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nconst unwrap = (value: DateTimeInput | null | undefined): any => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return value;\n};\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed). Use `$rateLimit` for distributed rate limiting.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/core/providers/EmailProvider.ts","../../../src/email/core/providers/MemoryEmailProvider.ts","../../../src/email/core/primitives/$email.ts","../../../src/email/core/errors/EmailError.ts","../../../src/email/core/providers/LocalEmailProvider.ts","../../../src/email/core/index.ts"],"sourcesContent":["/**\n * Email provider interface.\n *\n * All methods are asynchronous and return promises.\n */\nexport abstract class EmailProvider {\n /**\n * Send an email.\n *\n * @return Promise that resolves when the email is sent.\n */\n public abstract send(options: EmailSendOptions): Promise<void>;\n}\n\nexport type EmailSendOptions = {\n to: string | string[];\n subject: string;\n body: string;\n};\n","import { $logger } from \"alepha/logger\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nexport interface EmailRecord {\n to: string;\n subject: string;\n body: string;\n sentAt: Date;\n}\n\nexport class MemoryEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n public records: EmailRecord[] = [];\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email to memory store\", { to, subject });\n\n for (const recipient of Array.isArray(to) ? to : [to]) {\n this.records.push({\n to: recipient,\n subject,\n body,\n sentAt: new Date(),\n });\n }\n }\n\n /**\n * Get the last email sent (for testing purposes).\n */\n public get last(): EmailRecord | undefined {\n return this.records[this.records.length - 1];\n }\n}\n","import {\n AlephaError,\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n} from \"alepha\";\nimport type { EmailSendOptions } from \"../providers/EmailProvider.ts\";\nimport { EmailProvider } from \"../providers/EmailProvider.ts\";\nimport { MemoryEmailProvider } from \"../providers/MemoryEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const $email = (options: EmailPrimitiveOptions = {}) =>\n createPrimitive(EmailPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface EmailPrimitiveOptions {\n name?: string;\n provider?: InstantiableClass<EmailProvider> | \"memory\";\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email primitive for sending emails through various providers.\n *\n * Usage:\n * ```typescript\n * class MyService {\n * private readonly welcomeEmail = $email({ name: \"welcome\" });\n *\n * async sendWelcome(userEmail: string, userName: string) {\n * await this.welcomeEmail.send({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: `<p>Hello ${userName}!</p>`\n * });\n * }\n * }\n * ```\n */\nexport class EmailPrimitive extends Primitive<EmailPrimitiveOptions> {\n protected readonly provider = this.$provider();\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Send an email using the configured provider.\n */\n public async send(options: EmailSendOptions): Promise<void> {\n await this.alepha.events.emit(\"email:sending\", {\n to: options.to,\n template: this.name,\n variables: {},\n provider: this.provider,\n abort: () => {\n throw new AlephaError(\"Email sending aborted by hook\");\n },\n });\n\n await this.provider.send(options);\n\n await this.alepha.events.emit(\"email:sent\", {\n to: options.to,\n template: this.name,\n provider: this.provider,\n });\n }\n\n protected $provider(): EmailProvider {\n if (!this.options.provider) {\n return this.alepha.inject(EmailProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryEmailProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n$email[KIND] = EmailPrimitive;\n","import { AlephaError } from \"alepha\";\n\nexport class EmailError extends AlephaError {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Local email provider configuration atom\n */\nexport const localEmailOptions = $atom({\n name: \"alepha.email.local.options\",\n schema: t.object({\n directory: t.string({\n description: \"Directory path where email files will be stored\",\n }),\n }),\n default: {\n directory: \"node_modules/.alepha/emails\",\n },\n});\n\nexport type LocalEmailProviderOptions = Static<typeof localEmailOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [localEmailOptions.key]: LocalEmailProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class LocalEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n protected readonly fs = $inject(FileSystemProvider);\n protected readonly options = $state(localEmailOptions);\n\n protected get directory(): string {\n return this.options.directory;\n }\n\n protected onStart = $hook({\n on: \"start\",\n handler: async () => {\n try {\n await this.fs.mkdir(this.directory, { recursive: true });\n this.log.info(\"Email directory OK\", {\n at: this.directory,\n });\n } catch (error) {\n const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { at: this.directory });\n throw new EmailError(\n message,\n error instanceof Error ? error : undefined,\n );\n }\n },\n });\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n\n this.log.debug(\"Sending email to local file\", {\n to,\n subject,\n directory: this.directory,\n });\n\n try {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n for (const recipient of Array.isArray(to) ? to : [to]) {\n const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, \"_\");\n const filename = `${sanitizedEmail},${timestamp}.eml.json`;\n const filepath = this.fs.join(this.directory, filename);\n\n const content = this.createEmailJson({ to: recipient, subject, body });\n await this.fs.writeFile(filepath, JSON.stringify(content, null, 2));\n\n this.log.info(\"Email saved to local file\", { filepath, to, subject });\n }\n } catch (error) {\n const message = `Failed to save email to local file: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject, directory: this.directory });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n public createEmailJson(options: {\n to: string;\n subject: string;\n body: string;\n }): { to: string; subject: string; body: string; sentAt: string } {\n return {\n to: options.to,\n subject: options.subject,\n body: options.body,\n sentAt: new Date().toISOString(),\n };\n }\n}\n","import { $module } from \"alepha\";\nimport { $email } from \"./primitives/$email.ts\";\nimport { EmailProvider } from \"./providers/EmailProvider.ts\";\nimport { LocalEmailProvider } from \"./providers/LocalEmailProvider.ts\";\nimport { MemoryEmailProvider } from \"./providers/MemoryEmailProvider.ts\";\n\n// Exports\nexport * from \"./errors/EmailError.ts\";\nexport * from \"./primitives/$email.ts\";\nexport * from \"./providers/EmailProvider.ts\";\nexport * from \"./providers/LocalEmailProvider.ts\";\nexport * from \"./providers/MemoryEmailProvider.ts\";\n\n// Hook declarations\ndeclare module \"alepha\" {\n interface Hooks {\n \"email:sending\": {\n to: string | string[];\n template: string;\n variables: Record<string, unknown>;\n provider: EmailProvider;\n abort(): void;\n };\n \"email:sent\": {\n to: string | string[];\n template: string;\n provider: EmailProvider;\n };\n }\n}\n\n/**\n * Email delivery with template support.\n *\n * **Features:**\n * - Send emails with templates\n * - Multiple recipients\n * - Local file provider for development\n * - Memory provider for testing\n *\n * For SMTP support, use `AlephaEmailSmtp` from `alepha/email/smtp`.\n * For Brevo support, use `AlephaEmailBrevo` from `alepha/email/brevo`.\n *\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [EmailProvider],\n variants: [MemoryEmailProvider, LocalEmailProvider],\n register: (alepha) => {\n if (alepha.isTest()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: LocalEmailProvider,\n });\n }\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/core/providers/EmailProvider.ts","../../../src/email/core/providers/MemoryEmailProvider.ts","../../../src/email/core/primitives/$email.ts","../../../src/email/core/errors/EmailError.ts","../../../src/email/core/providers/LocalEmailProvider.ts","../../../src/email/core/index.ts"],"sourcesContent":["/**\n * Email provider interface.\n *\n * All methods are asynchronous and return promises.\n */\nexport abstract class EmailProvider {\n /**\n * Send an email.\n *\n * @return Promise that resolves when the email is sent.\n */\n public abstract send(options: EmailSendOptions): Promise<void>;\n}\n\nexport type EmailSendOptions = {\n to: string | string[];\n subject: string;\n body: string;\n};\n","import { $logger } from \"alepha/logger\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nexport interface EmailRecord {\n to: string;\n subject: string;\n body: string;\n sentAt: Date;\n}\n\nexport class MemoryEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n public records: EmailRecord[] = [];\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email to memory store\", { to, subject });\n\n for (const recipient of Array.isArray(to) ? to : [to]) {\n this.records.push({\n to: recipient,\n subject,\n body,\n sentAt: new Date(),\n });\n }\n }\n\n /**\n * Get the last email sent (for testing purposes).\n */\n public get last(): EmailRecord | undefined {\n return this.records[this.records.length - 1];\n }\n}\n","import {\n AlephaError,\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n} from \"alepha\";\nimport type { EmailSendOptions } from \"../providers/EmailProvider.ts\";\nimport { EmailProvider } from \"../providers/EmailProvider.ts\";\nimport { MemoryEmailProvider } from \"../providers/MemoryEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const $email = (options: EmailPrimitiveOptions = {}) =>\n createPrimitive(EmailPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface EmailPrimitiveOptions {\n name?: string;\n provider?: InstantiableClass<EmailProvider> | \"memory\";\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email primitive for sending emails through various providers.\n *\n * Usage:\n * ```typescript\n * class MyService {\n * private readonly welcomeEmail = $email({ name: \"welcome\" });\n *\n * async sendWelcome(userEmail: string, userName: string) {\n * await this.welcomeEmail.send({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: `<p>Hello ${userName}!</p>`\n * });\n * }\n * }\n * ```\n */\nexport class EmailPrimitive extends Primitive<EmailPrimitiveOptions> {\n protected readonly provider = this.$provider();\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Send an email using the configured provider.\n */\n public async send(options: EmailSendOptions): Promise<void> {\n await this.alepha.events.emit(\"email:sending\", {\n to: options.to,\n template: this.name,\n variables: {},\n provider: this.provider,\n abort: () => {\n throw new AlephaError(\"Email sending aborted by hook\");\n },\n });\n\n await this.provider.send(options);\n\n await this.alepha.events.emit(\"email:sent\", {\n to: options.to,\n template: this.name,\n provider: this.provider,\n });\n }\n\n protected $provider(): EmailProvider {\n if (!this.options.provider) {\n return this.alepha.inject(EmailProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryEmailProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n$email[KIND] = EmailPrimitive;\n","import { AlephaError } from \"alepha\";\n\nexport class EmailError extends AlephaError {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Local email provider configuration atom\n */\nexport const localEmailOptions = $atom({\n name: \"alepha.email.local.options\",\n schema: t.object({\n directory: t.string({\n description: \"Directory path where email files will be stored\",\n }),\n }),\n default: {\n directory: \"node_modules/.alepha/emails\",\n },\n});\n\nexport type LocalEmailProviderOptions = Static<typeof localEmailOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [localEmailOptions.key]: LocalEmailProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class LocalEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n protected readonly fs = $inject(FileSystemProvider);\n protected readonly options = $state(localEmailOptions);\n\n protected get directory(): string {\n return this.options.directory;\n }\n\n protected onStart = $hook({\n on: \"start\",\n handler: async () => {\n try {\n await this.fs.mkdir(this.directory, { recursive: true });\n this.log.info(\"Email directory OK\", {\n at: this.directory,\n });\n } catch (error) {\n const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { at: this.directory });\n throw new EmailError(\n message,\n error instanceof Error ? error : undefined,\n );\n }\n },\n });\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n\n this.log.debug(\"Sending email to local file\", {\n to,\n subject,\n directory: this.directory,\n });\n\n try {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n for (const recipient of Array.isArray(to) ? to : [to]) {\n const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, \"_\");\n const filename = `${sanitizedEmail},${timestamp}.eml.json`;\n const filepath = this.fs.join(this.directory, filename);\n\n const content = this.createEmailJson({ to: recipient, subject, body });\n await this.fs.writeFile(filepath, JSON.stringify(content, null, 2));\n\n this.log.info(\"Email saved to local file\", { filepath, to, subject });\n }\n } catch (error) {\n const message = `Failed to save email to local file: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject, directory: this.directory });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n public createEmailJson(options: {\n to: string;\n subject: string;\n body: string;\n }): { to: string; subject: string; body: string; sentAt: string } {\n return {\n to: options.to,\n subject: options.subject,\n body: options.body,\n sentAt: new Date().toISOString(),\n };\n }\n}\n","import { $module } from \"alepha\";\nimport { $email } from \"./primitives/$email.ts\";\nimport { EmailProvider } from \"./providers/EmailProvider.ts\";\nimport { LocalEmailProvider } from \"./providers/LocalEmailProvider.ts\";\nimport { MemoryEmailProvider } from \"./providers/MemoryEmailProvider.ts\";\n\n// Exports\nexport * from \"./errors/EmailError.ts\";\nexport * from \"./primitives/$email.ts\";\nexport * from \"./providers/EmailProvider.ts\";\nexport * from \"./providers/LocalEmailProvider.ts\";\nexport * from \"./providers/MemoryEmailProvider.ts\";\n\n// Hook declarations\ndeclare module \"alepha\" {\n interface Hooks {\n \"email:sending\": {\n to: string | string[];\n template: string;\n variables: Record<string, unknown>;\n provider: EmailProvider;\n abort(): void;\n };\n \"email:sent\": {\n to: string | string[];\n template: string;\n provider: EmailProvider;\n };\n }\n}\n\n/**\n * Email delivery with template support.\n *\n * **Features:**\n * - Send emails with templates\n * - Multiple recipients\n * - Local file provider for development\n * - Memory provider for testing\n *\n * For SMTP support, use `AlephaEmailSmtp` from `alepha/email/smtp`.\n * For Brevo support, use `AlephaEmailBrevo` from `alepha/email/brevo`.\n *\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [EmailProvider],\n variants: [MemoryEmailProvider, LocalEmailProvider],\n register: (alepha) => {\n if (alepha.isTest()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: LocalEmailProvider,\n });\n }\n },\n});\n"],"mappings":"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"}
|