alepha 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api-files/index.d.ts +28 -91
- package/dist/api-files/index.js +10 -755
- package/dist/api-files/index.js.map +1 -1
- package/dist/api-jobs/index.d.ts +46 -46
- package/dist/api-jobs/index.js +13 -13
- package/dist/api-jobs/index.js.map +1 -1
- package/dist/api-notifications/index.d.ts +129 -146
- package/dist/api-notifications/index.js +17 -39
- package/dist/api-notifications/index.js.map +1 -1
- package/dist/api-parameters/index.d.ts +21 -22
- package/dist/api-parameters/index.js +22 -22
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +223 -2000
- package/dist/api-users/index.js +914 -4787
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.d.ts +96 -96
- package/dist/batch/index.d.ts +13 -13
- package/dist/batch/index.js +8 -8
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +14 -14
- package/dist/bucket/index.js +12 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/index.d.ts +11 -11
- package/dist/cache/index.js +9 -9
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.d.ts +28 -26
- package/dist/cli/index.js +50 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +19 -19
- package/dist/command/index.js +25 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +218 -218
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +232 -232
- package/dist/core/index.js +218 -218
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +2113 -0
- package/dist/core/index.native.js.map +1 -0
- package/dist/datetime/index.d.ts +9 -9
- package/dist/datetime/index.js +7 -7
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.js +9 -9
- package/dist/email/index.js.map +1 -1
- package/dist/file/index.js +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/index.d.ts +9 -9
- package/dist/lock/index.js +8 -8
- package/dist/lock/index.js.map +1 -1
- package/dist/lock-redis/index.js +3 -66
- package/dist/lock-redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -5
- package/dist/logger/index.js +8 -8
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.browser.js +114 -114
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +218 -218
- package/dist/orm/index.js +46 -46
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +29 -29
- package/dist/queue/index.js +20 -20
- package/dist/queue/index.js.map +1 -1
- package/dist/queue-redis/index.d.ts +2 -2
- package/dist/redis/index.d.ts +10 -10
- package/dist/retry/index.d.ts +19 -19
- package/dist/retry/index.js +7 -7
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +16 -16
- package/dist/scheduler/index.js +9 -9
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +80 -80
- package/dist/security/index.js +32 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/index.browser.js +1 -1
- package/dist/server/index.browser.js.map +1 -1
- package/dist/server/index.d.ts +101 -101
- package/dist/server/index.js +16 -16
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.browser.js +4 -982
- package/dist/server-auth/index.browser.js.map +1 -1
- package/dist/server-auth/index.d.ts +204 -785
- package/dist/server-auth/index.js +47 -1239
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cache/index.d.ts +10 -10
- package/dist/server-cache/index.js +2 -2
- package/dist/server-cache/index.js.map +1 -1
- package/dist/server-compress/index.d.ts +4 -4
- package/dist/server-compress/index.js +1 -1
- package/dist/server-compress/index.js.map +1 -1
- package/dist/server-cookies/index.browser.js +8 -8
- package/dist/server-cookies/index.browser.js.map +1 -1
- package/dist/server-cookies/index.d.ts +17 -17
- package/dist/server-cookies/index.js +10 -10
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-cors/index.d.ts +17 -17
- package/dist/server-cors/index.js +9 -9
- package/dist/server-cors/index.js.map +1 -1
- package/dist/server-health/index.d.ts +19 -19
- package/dist/server-helmet/index.d.ts +1 -1
- package/dist/server-links/index.browser.js +12 -12
- package/dist/server-links/index.browser.js.map +1 -1
- package/dist/server-links/index.d.ts +59 -251
- package/dist/server-links/index.js +23 -502
- package/dist/server-links/index.js.map +1 -1
- package/dist/server-metrics/index.d.ts +4 -4
- package/dist/server-multipart/index.d.ts +2 -2
- package/dist/server-proxy/index.d.ts +12 -12
- package/dist/server-proxy/index.js +10 -10
- package/dist/server-proxy/index.js.map +1 -1
- package/dist/server-rate-limit/index.d.ts +22 -22
- package/dist/server-rate-limit/index.js +12 -12
- package/dist/server-rate-limit/index.js.map +1 -1
- package/dist/server-security/index.d.ts +22 -22
- package/dist/server-security/index.js +15 -15
- package/dist/server-security/index.js.map +1 -1
- package/dist/server-static/index.d.ts +14 -14
- package/dist/server-static/index.js +8 -8
- package/dist/server-static/index.js.map +1 -1
- package/dist/server-swagger/index.d.ts +25 -184
- package/dist/server-swagger/index.js +21 -724
- package/dist/server-swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +14 -14
- package/dist/sms/index.js +9 -9
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +11 -11
- package/dist/thread/index.js +17 -17
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/index.d.ts +26 -26
- package/dist/topic/index.js +16 -16
- package/dist/topic/index.js.map +1 -1
- package/dist/topic-redis/index.d.ts +1 -1
- package/dist/vite/index.d.ts +3 -3
- package/dist/vite/index.js +8 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -11
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +58 -58
- package/dist/websocket/index.js +13 -13
- package/dist/websocket/index.js.map +1 -1
- package/package.json +113 -52
- package/src/api-files/services/FileService.ts +5 -7
- package/src/api-jobs/index.ts +1 -1
- package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
- package/src/api-jobs/providers/JobProvider.ts +9 -9
- package/src/api-jobs/services/JobService.ts +5 -5
- package/src/api-notifications/index.ts +5 -15
- package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
- package/src/api-notifications/services/NotificationSenderService.ts +3 -3
- package/src/api-parameters/index.ts +1 -1
- package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
- package/src/api-users/index.ts +1 -1
- package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
- package/src/api-users/providers/UserRealmProvider.ts +1 -1
- package/src/batch/index.ts +3 -3
- package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
- package/src/bucket/index.ts +8 -8
- package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
- package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
- package/src/cache/index.ts +4 -4
- package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
- package/src/cli/apps/AlephaPackageBuilderCli.ts +24 -2
- package/src/cli/commands/DrizzleCommands.ts +6 -6
- package/src/cli/commands/VerifyCommands.ts +1 -1
- package/src/cli/commands/ViteCommands.ts +6 -1
- package/src/cli/services/ProjectUtils.ts +34 -3
- package/src/command/index.ts +5 -5
- package/src/command/{descriptors → primitives}/$command.ts +9 -12
- package/src/command/providers/CliProvider.ts +10 -10
- package/src/core/Alepha.ts +30 -33
- package/src/core/constants/KIND.ts +1 -1
- package/src/core/constants/OPTIONS.ts +1 -1
- package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
- package/src/core/helpers/ref.ts +1 -1
- package/src/core/index.shared.ts +8 -8
- package/src/core/{descriptors → primitives}/$context.ts +5 -5
- package/src/core/{descriptors → primitives}/$hook.ts +4 -4
- package/src/core/{descriptors → primitives}/$inject.ts +2 -2
- package/src/core/{descriptors → primitives}/$module.ts +9 -9
- package/src/core/{descriptors → primitives}/$use.ts +2 -2
- package/src/core/providers/CodecManager.ts +1 -1
- package/src/core/providers/JsonSchemaCodec.ts +1 -1
- package/src/core/providers/StateManager.ts +2 -2
- package/src/datetime/index.ts +3 -3
- package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
- package/src/email/index.ts +4 -4
- package/src/email/{descriptors → primitives}/$email.ts +8 -8
- package/src/file/index.ts +1 -1
- package/src/lock/index.ts +3 -3
- package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
- package/src/logger/index.ts +8 -8
- package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
- package/src/logger/services/Logger.ts +1 -1
- package/src/orm/constants/PG_SYMBOLS.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.ts +8 -8
- package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
- package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
- package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
- package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
- package/src/orm/providers/PostgresTypeProvider.ts +3 -3
- package/src/orm/providers/RepositoryProvider.ts +4 -4
- package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
- package/src/orm/services/ModelBuilder.ts +9 -9
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +5 -5
- package/src/orm/services/Repository.ts +7 -7
- package/src/orm/services/SqliteModelBuilder.ts +5 -5
- package/src/queue/index.ts +7 -7
- package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
- package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
- package/src/queue/providers/WorkerProvider.ts +7 -7
- package/src/retry/index.ts +3 -3
- package/src/retry/{descriptors → primitives}/$retry.ts +14 -14
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/index.ts +9 -9
- package/src/security/{descriptors → primitives}/$permission.ts +7 -7
- package/src/security/{descriptors → primitives}/$realm.ts +6 -12
- package/src/security/{descriptors → primitives}/$role.ts +12 -12
- package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
- package/src/server/index.browser.ts +1 -1
- package/src/server/index.ts +14 -14
- package/src/server/{descriptors → primitives}/$action.ts +13 -13
- package/src/server/{descriptors → primitives}/$route.ts +9 -9
- package/src/server/providers/NodeHttpServerProvider.ts +1 -1
- package/src/server/services/HttpClient.ts +1 -1
- package/src/server-auth/index.browser.ts +1 -1
- package/src/server-auth/index.ts +6 -6
- package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
- package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
- package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
- package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
- package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
- package/src/server-cookies/index.browser.ts +2 -2
- package/src/server-cookies/index.ts +5 -5
- package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
- package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
- package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
- package/src/server-cookies/services/CookieParser.ts +1 -1
- package/src/server-cors/index.ts +3 -3
- package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
- package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
- package/src/server-links/index.browser.ts +5 -5
- package/src/server-links/index.ts +9 -9
- package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
- package/src/server-links/providers/LinkProvider.ts +7 -7
- package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
- package/src/server-links/providers/ServerLinksProvider.ts +3 -3
- package/src/server-proxy/index.ts +3 -3
- package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
- package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
- package/src/server-rate-limit/index.ts +6 -6
- package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
- package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
- package/src/server-security/index.ts +3 -3
- package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
- package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
- package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
- package/src/server-static/index.ts +3 -3
- package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
- package/src/server-static/providers/ServerStaticProvider.ts +6 -6
- package/src/server-swagger/index.ts +5 -5
- package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
- package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
- package/src/sms/index.ts +4 -4
- package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
- package/src/thread/index.ts +3 -3
- package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
- package/src/thread/providers/ThreadProvider.ts +7 -9
- package/src/topic/index.ts +5 -5
- package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
- package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
- package/src/topic/providers/TopicProvider.ts +4 -4
- package/src/vite/tasks/copyAssets.ts +1 -1
- package/src/vite/tasks/generateSitemap.ts +3 -3
- package/src/vite/tasks/prerenderPages.ts +2 -2
- package/src/vite/tasks/runAlepha.ts +2 -2
- package/src/websocket/index.browser.ts +3 -3
- package/src/websocket/index.shared.ts +2 -2
- package/src/websocket/index.ts +4 -4
- package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
- package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
- package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
- package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
- package/src/websocket/services/WebSocketClient.ts +5 -5
- package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
- package/src/api-notifications/providers/SmsProvider.ts +0 -8
- /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
- /package/src/core/{descriptors → primitives}/$env.ts +0 -0
- /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
- /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
package/dist/bucket/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError,
|
|
1
|
+
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
2
|
import { FileDetector, FileSystemProvider } from "alepha/file";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { createReadStream } from "node:fs";
|
|
@@ -58,9 +58,9 @@ var MemoryFileStorageProvider = class {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
//#endregion
|
|
61
|
-
//#region src/bucket/
|
|
61
|
+
//#region src/bucket/primitives/$bucket.ts
|
|
62
62
|
/**
|
|
63
|
-
* Creates a bucket
|
|
63
|
+
* Creates a bucket primitive for file storage and management with configurable validation.
|
|
64
64
|
*
|
|
65
65
|
* Provides a comprehensive file storage system that handles uploads, downloads, validation,
|
|
66
66
|
* and management across multiple storage backends with MIME type and size limit controls.
|
|
@@ -108,8 +108,8 @@ var MemoryFileStorageProvider = class {
|
|
|
108
108
|
* }
|
|
109
109
|
* ```
|
|
110
110
|
*/
|
|
111
|
-
const $bucket = (options) =>
|
|
112
|
-
var
|
|
111
|
+
const $bucket = (options) => createPrimitive(BucketPrimitive, options);
|
|
112
|
+
var BucketPrimitive = class extends Primitive {
|
|
113
113
|
provider = this.$provider();
|
|
114
114
|
fileSystem = $inject(FileSystemProvider);
|
|
115
115
|
get name() {
|
|
@@ -169,7 +169,7 @@ var BucketDescriptor = class extends Descriptor {
|
|
|
169
169
|
return this.alepha.inject(this.options.provider);
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
-
$bucket[KIND] =
|
|
172
|
+
$bucket[KIND] = BucketPrimitive;
|
|
173
173
|
|
|
174
174
|
//#endregion
|
|
175
175
|
//#region src/bucket/providers/LocalFileStorageProvider.ts
|
|
@@ -193,7 +193,7 @@ var LocalFileStorageProvider = class {
|
|
|
193
193
|
onConfigure = $hook({
|
|
194
194
|
on: "configure",
|
|
195
195
|
handler: async () => {
|
|
196
|
-
if (this.alepha.isTest() && this.storagePath === localFileStorageOptions.options.default.storagePath) this.alepha.
|
|
196
|
+
if (this.alepha.isTest() && this.storagePath === localFileStorageOptions.options.default.storagePath) this.alepha.store.set(localFileStorageOptions, { storagePath: join(tmpdir(), `alepha-test-${Date.now()}`) });
|
|
197
197
|
}
|
|
198
198
|
});
|
|
199
199
|
onStart = $hook({
|
|
@@ -202,7 +202,7 @@ var LocalFileStorageProvider = class {
|
|
|
202
202
|
try {
|
|
203
203
|
await mkdir(this.storagePath, { recursive: true });
|
|
204
204
|
} catch {}
|
|
205
|
-
for (const bucket of this.alepha.
|
|
205
|
+
for (const bucket of this.alepha.primitives($bucket)) {
|
|
206
206
|
if (bucket.provider !== this) continue;
|
|
207
207
|
await mkdir(join(this.storagePath, bucket.name), { recursive: true });
|
|
208
208
|
this.log.debug(`Bucket '${bucket.name}' at ${this.storagePath} OK`);
|
|
@@ -266,9 +266,9 @@ var LocalFileStorageProvider = class {
|
|
|
266
266
|
//#endregion
|
|
267
267
|
//#region src/bucket/index.ts
|
|
268
268
|
/**
|
|
269
|
-
* Provides file storage capabilities through declarative bucket
|
|
269
|
+
* Provides file storage capabilities through declarative bucket primitives with support for multiple storage backends.
|
|
270
270
|
*
|
|
271
|
-
* The bucket module enables unified file operations across different storage systems using the `$bucket`
|
|
271
|
+
* The bucket module enables unified file operations across different storage systems using the `$bucket` primitive
|
|
272
272
|
* on class properties. It abstracts storage provider differences, offering consistent APIs for local filesystem,
|
|
273
273
|
* cloud storage, or in-memory storage for testing environments.
|
|
274
274
|
*
|
|
@@ -278,7 +278,7 @@ var LocalFileStorageProvider = class {
|
|
|
278
278
|
*/
|
|
279
279
|
const AlephaBucket = $module({
|
|
280
280
|
name: "alepha.bucket",
|
|
281
|
-
|
|
281
|
+
primitives: [$bucket],
|
|
282
282
|
services: [
|
|
283
283
|
FileStorageProvider,
|
|
284
284
|
MemoryFileStorageProvider,
|
|
@@ -292,5 +292,5 @@ const AlephaBucket = $module({
|
|
|
292
292
|
});
|
|
293
293
|
|
|
294
294
|
//#endregion
|
|
295
|
-
export { $bucket, AlephaBucket,
|
|
295
|
+
export { $bucket, AlephaBucket, BucketPrimitive, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, MemoryFileStorageProvider, localFileStorageOptions };
|
|
296
296
|
//# sourceMappingURL=index.js.map
|
package/dist/bucket/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/bucket/errors/InvalidFileError.ts","../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/descriptors/$bucket.ts","../../src/bucket/providers/LocalFileStorageProvider.ts","../../src/bucket/index.ts"],"sourcesContent":["export class InvalidFileError extends Error {\n public readonly status = 400;\n}\n","import type { FileLike } from \"alepha\";\n\nexport abstract class FileStorageProvider {\n /**\n * Uploads a file to the storage.\n *\n * @param bucketName - Container name\n * @param file - File to upload\n * @param fileId - Optional file identifier. If not provided, a unique ID will be generated.\n * @return The identifier of the uploaded file.\n */\n abstract upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string>;\n\n /**\n * Downloads a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to download\n * @return The downloaded file as a FileLike object.\n */\n abstract download(bucketName: string, fileId: string): Promise<FileLike>;\n\n /**\n * Check if fileId exists in the storage bucket.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to stream\n * @return True is the file exists, false otherwise.\n */\n abstract exists(bucketName: string, fileId: string): Promise<boolean>;\n\n /**\n * Delete permanently a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to delete\n */\n abstract delete(bucketName: string, fileId: string): Promise<void>;\n}\n","import { AlephaError } from \"alepha\";\n\nexport class FileNotFoundError extends AlephaError {\n public readonly status = 404;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { $inject, type FileLike } from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/file\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\nexport class MemoryFileStorageProvider implements FileStorageProvider {\n public readonly files: Record<string, FileLike> = {};\n protected readonly fileSystem = $inject(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId();\n\n this.files[`${bucketName}/${fileId}`] = this.fileSystem.createFile({\n stream: file.stream(),\n name: file.name,\n type: file.type,\n size: file.size,\n });\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const fileKey = `${bucketName}/${fileId}`;\n const file = this.files[fileKey];\n\n if (!file) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n return file;\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n return `${bucketName}/${fileId}` in this.files;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const fileKey = `${bucketName}/${fileId}`;\n if (!(fileKey in this.files)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n delete this.files[fileKey];\n }\n\n protected createId(): string {\n return randomUUID();\n }\n}\n","import {\n $inject,\n createDescriptor,\n Descriptor,\n type FileLike,\n KIND,\n type Service,\n} from \"alepha\";\nimport { FileSystemProvider } from \"alepha/file\";\nimport { InvalidFileError } from \"../errors/InvalidFileError.ts\";\nimport { FileStorageProvider } from \"../providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"../providers/MemoryFileStorageProvider.ts\";\n\n/**\n * Creates a bucket descriptor for file storage and management with configurable validation.\n *\n * Provides a comprehensive file storage system that handles uploads, downloads, validation,\n * and management across multiple storage backends with MIME type and size limit controls.\n *\n * **Key Features**\n * - Multi-provider support (filesystem, cloud storage, in-memory)\n * - Automatic MIME type and file size validation\n * - Event integration for file operations monitoring\n * - Flexible per-bucket and per-operation configuration\n * - Smart file type and size detection\n *\n * **Common Use Cases**\n * - User profile pictures and document uploads\n * - Product images and media management\n * - Document storage and retrieval systems\n *\n * @example\n * ```ts\n * class MediaService {\n * images = $bucket({\n * name: \"user-images\",\n * mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\"],\n * maxSize: 5 // 5MB limit\n * });\n *\n * documents = $bucket({\n * name: \"documents\",\n * mimeTypes: [\"application/pdf\", \"text/plain\"],\n * maxSize: 50 // 50MB limit\n * });\n *\n * async uploadProfileImage(file: FileLike, userId: string): Promise<string> {\n * const fileId = await this.images.upload(file);\n * await this.userService.updateProfileImage(userId, fileId);\n * return fileId;\n * }\n *\n * async downloadDocument(documentId: string): Promise<FileLike> {\n * return await this.documents.download(documentId);\n * }\n *\n * async deleteDocument(documentId: string): Promise<void> {\n * await this.documents.delete(documentId);\n * }\n * }\n * ```\n */\nexport const $bucket = (options: BucketDescriptorOptions) =>\n createDescriptor(BucketDescriptor, options);\n\nexport interface BucketDescriptorOptions extends BucketFileOptions {\n /**\n * File storage provider configuration for the bucket.\n *\n * Options:\n * - **\"memory\"**: In-memory storage (default for development, lost on restart)\n * - **Service<FileStorageProvider>**: Custom provider class (e.g., S3FileStorageProvider, AzureBlobProvider)\n * - **undefined**: Uses the default file storage provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - **Development**: Use \"memory\" for fast, simple testing without external dependencies\n * - **Production**: Use cloud providers (S3, Azure Blob, Google Cloud Storage) for scalability\n * - **Local deployment**: Use filesystem providers for on-premise installations\n * - **Hybrid**: Use different providers for different bucket types (temp files vs permanent storage)\n *\n * **Provider Capabilities**:\n * - File persistence and durability guarantees\n * - Scalability and performance characteristics\n * - Geographic distribution and CDN integration\n * - Cost implications for storage and bandwidth\n * - Backup and disaster recovery features\n *\n * @default Uses injected FileStorageProvider\n * @example \"memory\"\n * @example S3FileStorageProvider\n * @example AzureBlobStorageProvider\n */\n provider?: Service<FileStorageProvider> | \"memory\";\n\n /**\n * Unique name identifier for the bucket.\n *\n * This name is used for:\n * - Storage backend organization and partitioning\n * - File path generation and URL construction\n * - Logging, monitoring, and debugging\n * - Access control and permissions management\n * - Backup and replication configuration\n *\n * **Naming Conventions**:\n * - Use lowercase with hyphens for consistency\n * - Include purpose or content type in the name\n * - Avoid spaces and special characters\n * - Consider environment prefixes for deployment isolation\n *\n * If not provided, defaults to the property key where the bucket is declared.\n *\n * @example \"user-avatars\"\n * @example \"product-images\"\n * @example \"legal-documents\"\n * @example \"temp-processing-files\"\n */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Human-readable description of the bucket's purpose and contents.\n *\n * Used for:\n * - Documentation generation and API references\n * - Developer onboarding and system understanding\n * - Monitoring dashboards and admin interfaces\n * - Compliance and audit documentation\n *\n * **Description Best Practices**:\n * - Explain what types of files this bucket stores\n * - Mention any special handling or processing requirements\n * - Include information about retention policies if applicable\n * - Note any compliance or security considerations\n *\n * @example \"User profile pictures and avatar images\"\n * @example \"Product catalog images with automated thumbnail generation\"\n * @example \"Legal documents requiring long-term retention\"\n * @example \"Temporary files for data processing workflows\"\n */\n description?: string;\n\n /**\n * Array of allowed MIME types for files uploaded to this bucket.\n *\n * When specified, only files with these exact MIME types will be accepted.\n * Files with disallowed MIME types will be rejected with an InvalidFileError.\n *\n * **MIME Type Categories**:\n * - Images: \"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\", \"image/svg+xml\"\n * - Documents: \"application/pdf\", \"text/plain\", \"text/csv\"\n * - Office: \"application/msword\", \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n * - Archives: \"application/zip\", \"application/x-tar\", \"application/gzip\"\n * - Media: \"video/mp4\", \"audio/mpeg\", \"audio/wav\"\n *\n * **Security Considerations**:\n * - Always validate MIME types for user uploads\n * - Be cautious with executable file types\n * - Consider using allow-lists rather than deny-lists\n * - Remember that MIME types can be spoofed by malicious users\n *\n * If not specified, all MIME types are allowed (not recommended for user uploads).\n *\n * @example [\"image/jpeg\", \"image/png\"] // Only JPEG and PNG images\n * @example [\"application/pdf\", \"text/plain\"] // Documents only\n * @example [\"video/mp4\", \"video/webm\"] // Video files\n */\n mimeTypes?: string[];\n\n /**\n * Maximum file size allowed in megabytes (MB).\n *\n * Files larger than this limit will be rejected with an InvalidFileError.\n * This helps prevent:\n * - Storage quota exhaustion\n * - Memory issues during file processing\n * - Long upload times and timeouts\n * - Abuse of storage resources\n *\n * **Size Guidelines by File Type**:\n * - Profile images: 1-5 MB\n * - Product photos: 5-10 MB\n * - Documents: 10-50 MB\n * - Video files: 50-500 MB\n * - Data files: 100-1000 MB\n *\n * **Considerations**:\n * - Consider your storage costs and limits\n * - Factor in network upload speeds for users\n * - Account for processing requirements (thumbnails, compression)\n * - Set reasonable limits based on actual use cases\n *\n * @default 10 MB\n *\n * @example 1 // 1MB for small images\n * @example 25 // 25MB for documents\n * @example 100 // 100MB for media files\n */\n maxSize?: number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BucketDescriptor extends Descriptor<BucketDescriptorOptions> {\n public readonly provider = this.$provider();\n private readonly fileSystem = $inject(FileSystemProvider);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Uploads a file to the bucket.\n */\n public async upload(\n file: FileLike,\n options?: BucketFileOptions,\n ): Promise<string> {\n if (file instanceof File) {\n // our createFile is smarter than the browser's File constructor\n // by doing this, we can guess the MIME type and size!\n file = this.fileSystem.createFile({ file });\n }\n\n options = {\n ...this.options,\n ...options,\n };\n\n const mimeTypes = options.mimeTypes ?? undefined;\n const maxSize = options.maxSize ?? 10; // Default to 10 MB if not specified\n\n if (mimeTypes) {\n const mimeType = file.type || \"application/octet-stream\";\n if (!mimeTypes.includes(mimeType)) {\n throw new InvalidFileError(\n `MIME type ${mimeType} is not allowed in bucket ${this.name}`,\n );\n }\n }\n\n // check size in bytes, convert MB to bytes\n if (file.size > maxSize * 1024 * 1024) {\n throw new InvalidFileError(\n `File size ${file.size} exceeds the maximum size of ${this.options.maxSize} MB in bucket ${this.name}`,\n );\n }\n\n const id = await this.provider.upload(this.name, file);\n\n await this.alepha.events.emit(\"bucket:file:uploaded\", {\n id,\n bucket: this,\n file,\n options,\n });\n\n return id;\n }\n\n /**\n * Delete permanently a file from the bucket.\n */\n public async delete(fileId: string, skipHook = false): Promise<void> {\n await this.provider.delete(this.name, fileId);\n\n if (skipHook) {\n return;\n }\n\n await this.alepha.events.emit(\"bucket:file:deleted\", {\n id: fileId,\n bucket: this,\n });\n }\n\n /**\n * Checks if a file exists in the bucket.\n */\n public async exists(fileId: string): Promise<boolean> {\n return this.provider.exists(this.name, fileId);\n }\n\n /**\n * Downloads a file from the bucket.\n */\n public async download(fileId: string): Promise<FileLike> {\n return this.provider.download(this.name, fileId);\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(FileStorageProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryFileStorageProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$bucket[KIND] = BucketDescriptor;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Optional description of the bucket.\n */\n description?: string;\n\n /**\n * Allowed MIME types.\n */\n mimeTypes?: string[];\n\n /**\n * Maximum size of the files in the bucket.\n *\n * @default 10\n */\n maxSize?: number;\n}\n","import { randomUUID } from \"node:crypto\";\nimport type * as fs from \"node:fs\";\nimport { createReadStream } from \"node:fs\";\nimport { mkdir, stat, unlink } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n $atom,\n $hook,\n $inject,\n $use,\n Alepha,\n AlephaError,\n type FileLike,\n type Static,\n t,\n} from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\nimport { $bucket } from \"../descriptors/$bucket.ts\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Local file storage configuration atom\n */\nexport const localFileStorageOptions = $atom({\n name: \"alepha.bucket.local.options\",\n schema: t.object({\n storagePath: t.string({\n description: \"Directory path where files will be stored\",\n }),\n }),\n default: {\n storagePath: \"node_modules/.alepha/buckets\",\n },\n});\n\nexport type LocalFileStorageProviderOptions = Static<\n typeof localFileStorageOptions.schema\n>;\n\ndeclare module \"alepha\" {\n interface State {\n [localFileStorageOptions.key]: LocalFileStorageProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class LocalFileStorageProvider implements FileStorageProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly fileSystemProvider = $inject(FileSystemProvider);\n protected readonly options = $use(localFileStorageOptions);\n\n protected get storagePath(): string {\n return this.options.storagePath;\n }\n\n protected readonly onConfigure = $hook({\n on: \"configure\",\n handler: async () => {\n if (\n this.alepha.isTest() &&\n this.storagePath === localFileStorageOptions.options.default.storagePath\n ) {\n this.alepha.state.set(localFileStorageOptions, {\n storagePath: join(tmpdir(), `alepha-test-${Date.now()}`),\n });\n }\n },\n });\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n try {\n await mkdir(this.storagePath, { recursive: true });\n } catch {}\n\n for (const bucket of this.alepha.descriptors($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n\n await mkdir(join(this.storagePath, bucket.name), {\n recursive: true,\n });\n\n this.log.debug(`Bucket '${bucket.name}' at ${this.storagePath} OK`);\n }\n },\n });\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId(file.type);\n\n this.log.trace(`Uploading file to ${bucketName}`);\n\n await this.fileSystemProvider.writeFile(\n this.path(bucketName, fileId),\n file,\n );\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const filePath = this.path(bucketName, fileId);\n\n try {\n const stats = await stat(filePath);\n const mimeType = this.fileDetector.getContentType(fileId);\n\n return this.fileSystemProvider.createFile({\n stream: createReadStream(filePath),\n name: fileId,\n type: mimeType,\n size: stats.size,\n });\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n throw new AlephaError(\"Invalid file operation\", { cause: error });\n }\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n try {\n await stat(this.path(bucketName, fileId));\n return true;\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n return false;\n }\n throw new AlephaError(\"Error checking file existence\", { cause: error });\n }\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n try {\n return await unlink(this.path(bucketName, fileId));\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n throw new AlephaError(\"Error deleting file\", { cause: error });\n }\n }\n\n protected stat(bucket: string, fileId: string): Promise<fs.Stats> {\n return stat(this.path(bucket, fileId));\n }\n\n protected createId(mimeType: string): string {\n const ext = this.fileDetector.getExtensionFromMimeType(mimeType);\n return `${randomUUID()}.${ext}`;\n }\n\n protected path(bucket: string, fileId = \"\"): string {\n return join(this.storagePath, bucket, fileId);\n }\n\n protected isErrorNoEntry(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n }\n}\n","import { $module, type FileLike } from \"alepha\";\nimport {\n $bucket,\n type BucketDescriptor,\n type BucketFileOptions,\n} from \"./descriptors/$bucket.ts\";\nimport { FileStorageProvider } from \"./providers/FileStorageProvider.ts\";\nimport { LocalFileStorageProvider } from \"./providers/LocalFileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$bucket.ts\";\nexport * from \"./errors/FileNotFoundError.ts\";\nexport * from \"./providers/FileStorageProvider.ts\";\nexport * from \"./providers/LocalFileStorageProvider.ts\";\nexport * from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Triggered when a file is uploaded to a bucket.\n * Can be used to perform actions after a file is uploaded, like creating a database record!\n */\n \"bucket:file:uploaded\": {\n id: string;\n file: FileLike;\n bucket: BucketDescriptor;\n options: BucketFileOptions;\n };\n /**\n * Triggered when a file is deleted from a bucket.\n */\n \"bucket:file:deleted\": {\n id: string;\n bucket: BucketDescriptor;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides file storage capabilities through declarative bucket descriptors with support for multiple storage backends.\n *\n * The bucket module enables unified file operations across different storage systems using the `$bucket` descriptor\n * on class properties. It abstracts storage provider differences, offering consistent APIs for local filesystem,\n * cloud storage, or in-memory storage for testing environments.\n *\n * @see {@link $bucket}\n * @see {@link FileStorageProvider}\n * @module alepha.bucket\n */\nexport const AlephaBucket = $module({\n name: \"alepha.bucket\",\n descriptors: [$bucket],\n services: [\n FileStorageProvider,\n MemoryFileStorageProvider,\n LocalFileStorageProvider,\n ],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: FileStorageProvider,\n use: alepha.isTest()\n ? MemoryFileStorageProvider\n : LocalFileStorageProvider,\n }),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/bucket/errors/InvalidFileError.ts","../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/primitives/$bucket.ts","../../src/bucket/providers/LocalFileStorageProvider.ts","../../src/bucket/index.ts"],"sourcesContent":["export class InvalidFileError extends Error {\n public readonly status = 400;\n}\n","import type { FileLike } from \"alepha\";\n\nexport abstract class FileStorageProvider {\n /**\n * Uploads a file to the storage.\n *\n * @param bucketName - Container name\n * @param file - File to upload\n * @param fileId - Optional file identifier. If not provided, a unique ID will be generated.\n * @return The identifier of the uploaded file.\n */\n abstract upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string>;\n\n /**\n * Downloads a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to download\n * @return The downloaded file as a FileLike object.\n */\n abstract download(bucketName: string, fileId: string): Promise<FileLike>;\n\n /**\n * Check if fileId exists in the storage bucket.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to stream\n * @return True is the file exists, false otherwise.\n */\n abstract exists(bucketName: string, fileId: string): Promise<boolean>;\n\n /**\n * Delete permanently a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to delete\n */\n abstract delete(bucketName: string, fileId: string): Promise<void>;\n}\n","import { AlephaError } from \"alepha\";\n\nexport class FileNotFoundError extends AlephaError {\n public readonly status = 404;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { $inject, type FileLike } from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/file\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\nexport class MemoryFileStorageProvider implements FileStorageProvider {\n public readonly files: Record<string, FileLike> = {};\n protected readonly fileSystem = $inject(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId();\n\n this.files[`${bucketName}/${fileId}`] = this.fileSystem.createFile({\n stream: file.stream(),\n name: file.name,\n type: file.type,\n size: file.size,\n });\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const fileKey = `${bucketName}/${fileId}`;\n const file = this.files[fileKey];\n\n if (!file) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n return file;\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n return `${bucketName}/${fileId}` in this.files;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const fileKey = `${bucketName}/${fileId}`;\n if (!(fileKey in this.files)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n delete this.files[fileKey];\n }\n\n protected createId(): string {\n return randomUUID();\n }\n}\n","import {\n $inject,\n createPrimitive,\n type FileLike,\n KIND,\n Primitive,\n type Service,\n} from \"alepha\";\nimport { FileSystemProvider } from \"alepha/file\";\nimport { InvalidFileError } from \"../errors/InvalidFileError.ts\";\nimport { FileStorageProvider } from \"../providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"../providers/MemoryFileStorageProvider.ts\";\n\n/**\n * Creates a bucket primitive for file storage and management with configurable validation.\n *\n * Provides a comprehensive file storage system that handles uploads, downloads, validation,\n * and management across multiple storage backends with MIME type and size limit controls.\n *\n * **Key Features**\n * - Multi-provider support (filesystem, cloud storage, in-memory)\n * - Automatic MIME type and file size validation\n * - Event integration for file operations monitoring\n * - Flexible per-bucket and per-operation configuration\n * - Smart file type and size detection\n *\n * **Common Use Cases**\n * - User profile pictures and document uploads\n * - Product images and media management\n * - Document storage and retrieval systems\n *\n * @example\n * ```ts\n * class MediaService {\n * images = $bucket({\n * name: \"user-images\",\n * mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\"],\n * maxSize: 5 // 5MB limit\n * });\n *\n * documents = $bucket({\n * name: \"documents\",\n * mimeTypes: [\"application/pdf\", \"text/plain\"],\n * maxSize: 50 // 50MB limit\n * });\n *\n * async uploadProfileImage(file: FileLike, userId: string): Promise<string> {\n * const fileId = await this.images.upload(file);\n * await this.userService.updateProfileImage(userId, fileId);\n * return fileId;\n * }\n *\n * async downloadDocument(documentId: string): Promise<FileLike> {\n * return await this.documents.download(documentId);\n * }\n *\n * async deleteDocument(documentId: string): Promise<void> {\n * await this.documents.delete(documentId);\n * }\n * }\n * ```\n */\nexport const $bucket = (options: BucketPrimitiveOptions) =>\n createPrimitive(BucketPrimitive, options);\n\nexport interface BucketPrimitiveOptions extends BucketFileOptions {\n /**\n * File storage provider configuration for the bucket.\n *\n * Options:\n * - **\"memory\"**: In-memory storage (default for development, lost on restart)\n * - **Service<FileStorageProvider>**: Custom provider class (e.g., S3FileStorageProvider, AzureBlobProvider)\n * - **undefined**: Uses the default file storage provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - **Development**: Use \"memory\" for fast, simple testing without external dependencies\n * - **Production**: Use cloud providers (S3, Azure Blob, Google Cloud Storage) for scalability\n * - **Local deployment**: Use filesystem providers for on-premise installations\n * - **Hybrid**: Use different providers for different bucket types (temp files vs permanent storage)\n *\n * **Provider Capabilities**:\n * - File persistence and durability guarantees\n * - Scalability and performance characteristics\n * - Geographic distribution and CDN integration\n * - Cost implications for storage and bandwidth\n * - Backup and disaster recovery features\n *\n * @default Uses injected FileStorageProvider\n * @example \"memory\"\n * @example S3FileStorageProvider\n * @example AzureBlobStorageProvider\n */\n provider?: Service<FileStorageProvider> | \"memory\";\n\n /**\n * Unique name identifier for the bucket.\n *\n * This name is used for:\n * - Storage backend organization and partitioning\n * - File path generation and URL construction\n * - Logging, monitoring, and debugging\n * - Access control and permissions management\n * - Backup and replication configuration\n *\n * **Naming Conventions**:\n * - Use lowercase with hyphens for consistency\n * - Include purpose or content type in the name\n * - Avoid spaces and special characters\n * - Consider environment prefixes for deployment isolation\n *\n * If not provided, defaults to the property key where the bucket is declared.\n *\n * @example \"user-avatars\"\n * @example \"product-images\"\n * @example \"legal-documents\"\n * @example \"temp-processing-files\"\n */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Human-readable description of the bucket's purpose and contents.\n *\n * Used for:\n * - Documentation generation and API references\n * - Developer onboarding and system understanding\n * - Monitoring dashboards and admin interfaces\n * - Compliance and audit documentation\n *\n * **Description Best Practices**:\n * - Explain what types of files this bucket stores\n * - Mention any special handling or processing requirements\n * - Include information about retention policies if applicable\n * - Note any compliance or security considerations\n *\n * @example \"User profile pictures and avatar images\"\n * @example \"Product catalog images with automated thumbnail generation\"\n * @example \"Legal documents requiring long-term retention\"\n * @example \"Temporary files for data processing workflows\"\n */\n description?: string;\n\n /**\n * Array of allowed MIME types for files uploaded to this bucket.\n *\n * When specified, only files with these exact MIME types will be accepted.\n * Files with disallowed MIME types will be rejected with an InvalidFileError.\n *\n * **MIME Type Categories**:\n * - Images: \"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\", \"image/svg+xml\"\n * - Documents: \"application/pdf\", \"text/plain\", \"text/csv\"\n * - Office: \"application/msword\", \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n * - Archives: \"application/zip\", \"application/x-tar\", \"application/gzip\"\n * - Media: \"video/mp4\", \"audio/mpeg\", \"audio/wav\"\n *\n * **Security Considerations**:\n * - Always validate MIME types for user uploads\n * - Be cautious with executable file types\n * - Consider using allow-lists rather than deny-lists\n * - Remember that MIME types can be spoofed by malicious users\n *\n * If not specified, all MIME types are allowed (not recommended for user uploads).\n *\n * @example [\"image/jpeg\", \"image/png\"] // Only JPEG and PNG images\n * @example [\"application/pdf\", \"text/plain\"] // Documents only\n * @example [\"video/mp4\", \"video/webm\"] // Video files\n */\n mimeTypes?: string[];\n\n /**\n * Maximum file size allowed in megabytes (MB).\n *\n * Files larger than this limit will be rejected with an InvalidFileError.\n * This helps prevent:\n * - Storage quota exhaustion\n * - Memory issues during file processing\n * - Long upload times and timeouts\n * - Abuse of storage resources\n *\n * **Size Guidelines by File Type**:\n * - Profile images: 1-5 MB\n * - Product photos: 5-10 MB\n * - Documents: 10-50 MB\n * - Video files: 50-500 MB\n * - Data files: 100-1000 MB\n *\n * **Considerations**:\n * - Consider your storage costs and limits\n * - Factor in network upload speeds for users\n * - Account for processing requirements (thumbnails, compression)\n * - Set reasonable limits based on actual use cases\n *\n * @default 10 MB\n *\n * @example 1 // 1MB for small images\n * @example 25 // 25MB for documents\n * @example 100 // 100MB for media files\n */\n maxSize?: number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {\n public readonly provider = this.$provider();\n private readonly fileSystem = $inject(FileSystemProvider);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Uploads a file to the bucket.\n */\n public async upload(\n file: FileLike,\n options?: BucketFileOptions,\n ): Promise<string> {\n if (file instanceof File) {\n // our createFile is smarter than the browser's File constructor\n // by doing this, we can guess the MIME type and size!\n file = this.fileSystem.createFile({ file });\n }\n\n options = {\n ...this.options,\n ...options,\n };\n\n const mimeTypes = options.mimeTypes ?? undefined;\n const maxSize = options.maxSize ?? 10; // Default to 10 MB if not specified\n\n if (mimeTypes) {\n const mimeType = file.type || \"application/octet-stream\";\n if (!mimeTypes.includes(mimeType)) {\n throw new InvalidFileError(\n `MIME type ${mimeType} is not allowed in bucket ${this.name}`,\n );\n }\n }\n\n // check size in bytes, convert MB to bytes\n if (file.size > maxSize * 1024 * 1024) {\n throw new InvalidFileError(\n `File size ${file.size} exceeds the maximum size of ${this.options.maxSize} MB in bucket ${this.name}`,\n );\n }\n\n const id = await this.provider.upload(this.name, file);\n\n await this.alepha.events.emit(\"bucket:file:uploaded\", {\n id,\n bucket: this,\n file,\n options,\n });\n\n return id;\n }\n\n /**\n * Delete permanently a file from the bucket.\n */\n public async delete(fileId: string, skipHook = false): Promise<void> {\n await this.provider.delete(this.name, fileId);\n\n if (skipHook) {\n return;\n }\n\n await this.alepha.events.emit(\"bucket:file:deleted\", {\n id: fileId,\n bucket: this,\n });\n }\n\n /**\n * Checks if a file exists in the bucket.\n */\n public async exists(fileId: string): Promise<boolean> {\n return this.provider.exists(this.name, fileId);\n }\n\n /**\n * Downloads a file from the bucket.\n */\n public async download(fileId: string): Promise<FileLike> {\n return this.provider.download(this.name, fileId);\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(FileStorageProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryFileStorageProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$bucket[KIND] = BucketPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Optional description of the bucket.\n */\n description?: string;\n\n /**\n * Allowed MIME types.\n */\n mimeTypes?: string[];\n\n /**\n * Maximum size of the files in the bucket.\n *\n * @default 10\n */\n maxSize?: number;\n}\n","import { randomUUID } from \"node:crypto\";\nimport type * as fs from \"node:fs\";\nimport { createReadStream } from \"node:fs\";\nimport { mkdir, stat, unlink } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n $atom,\n $hook,\n $inject,\n $use,\n Alepha,\n AlephaError,\n type FileLike,\n type Static,\n t,\n} from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport { $bucket } from \"../primitives/$bucket.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Local file storage configuration atom\n */\nexport const localFileStorageOptions = $atom({\n name: \"alepha.bucket.local.options\",\n schema: t.object({\n storagePath: t.string({\n description: \"Directory path where files will be stored\",\n }),\n }),\n default: {\n storagePath: \"node_modules/.alepha/buckets\",\n },\n});\n\nexport type LocalFileStorageProviderOptions = Static<\n typeof localFileStorageOptions.schema\n>;\n\ndeclare module \"alepha\" {\n interface State {\n [localFileStorageOptions.key]: LocalFileStorageProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class LocalFileStorageProvider implements FileStorageProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly fileSystemProvider = $inject(FileSystemProvider);\n protected readonly options = $use(localFileStorageOptions);\n\n protected get storagePath(): string {\n return this.options.storagePath;\n }\n\n protected readonly onConfigure = $hook({\n on: \"configure\",\n handler: async () => {\n if (\n this.alepha.isTest() &&\n this.storagePath === localFileStorageOptions.options.default.storagePath\n ) {\n this.alepha.store.set(localFileStorageOptions, {\n storagePath: join(tmpdir(), `alepha-test-${Date.now()}`),\n });\n }\n },\n });\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n try {\n await mkdir(this.storagePath, { recursive: true });\n } catch {}\n\n for (const bucket of this.alepha.primitives($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n\n await mkdir(join(this.storagePath, bucket.name), {\n recursive: true,\n });\n\n this.log.debug(`Bucket '${bucket.name}' at ${this.storagePath} OK`);\n }\n },\n });\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId(file.type);\n\n this.log.trace(`Uploading file to ${bucketName}`);\n\n await this.fileSystemProvider.writeFile(\n this.path(bucketName, fileId),\n file,\n );\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const filePath = this.path(bucketName, fileId);\n\n try {\n const stats = await stat(filePath);\n const mimeType = this.fileDetector.getContentType(fileId);\n\n return this.fileSystemProvider.createFile({\n stream: createReadStream(filePath),\n name: fileId,\n type: mimeType,\n size: stats.size,\n });\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n throw new AlephaError(\"Invalid file operation\", { cause: error });\n }\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n try {\n await stat(this.path(bucketName, fileId));\n return true;\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n return false;\n }\n throw new AlephaError(\"Error checking file existence\", { cause: error });\n }\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n try {\n return await unlink(this.path(bucketName, fileId));\n } catch (error) {\n if (this.isErrorNoEntry(error)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n throw new AlephaError(\"Error deleting file\", { cause: error });\n }\n }\n\n protected stat(bucket: string, fileId: string): Promise<fs.Stats> {\n return stat(this.path(bucket, fileId));\n }\n\n protected createId(mimeType: string): string {\n const ext = this.fileDetector.getExtensionFromMimeType(mimeType);\n return `${randomUUID()}.${ext}`;\n }\n\n protected path(bucket: string, fileId = \"\"): string {\n return join(this.storagePath, bucket, fileId);\n }\n\n protected isErrorNoEntry(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n }\n}\n","import { $module, type FileLike } from \"alepha\";\nimport {\n $bucket,\n type BucketFileOptions,\n type BucketPrimitive,\n} from \"./primitives/$bucket.ts\";\nimport { FileStorageProvider } from \"./providers/FileStorageProvider.ts\";\nimport { LocalFileStorageProvider } from \"./providers/LocalFileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/FileNotFoundError.ts\";\nexport * from \"./primitives/$bucket.ts\";\nexport * from \"./providers/FileStorageProvider.ts\";\nexport * from \"./providers/LocalFileStorageProvider.ts\";\nexport * from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Triggered when a file is uploaded to a bucket.\n * Can be used to perform actions after a file is uploaded, like creating a database record!\n */\n \"bucket:file:uploaded\": {\n id: string;\n file: FileLike;\n bucket: BucketPrimitive;\n options: BucketFileOptions;\n };\n /**\n * Triggered when a file is deleted from a bucket.\n */\n \"bucket:file:deleted\": {\n id: string;\n bucket: BucketPrimitive;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides file storage capabilities through declarative bucket primitives with support for multiple storage backends.\n *\n * The bucket module enables unified file operations across different storage systems using the `$bucket` primitive\n * on class properties. It abstracts storage provider differences, offering consistent APIs for local filesystem,\n * cloud storage, or in-memory storage for testing environments.\n *\n * @see {@link $bucket}\n * @see {@link FileStorageProvider}\n * @module alepha.bucket\n */\nexport const AlephaBucket = $module({\n name: \"alepha.bucket\",\n primitives: [$bucket],\n services: [\n FileStorageProvider,\n MemoryFileStorageProvider,\n LocalFileStorageProvider,\n ],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: FileStorageProvider,\n use: alepha.isTest()\n ? MemoryFileStorageProvider\n : LocalFileStorageProvider,\n }),\n});\n"],"mappings":"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"}
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as alepha0 from "alepha";
|
|
2
|
-
import {
|
|
2
|
+
import { InstantiableClass, KIND, Primitive } from "alepha";
|
|
3
3
|
import { DateTimeProvider, DurationLike, Timeout } from "alepha/datetime";
|
|
4
4
|
import * as alepha_logger0 from "alepha/logger";
|
|
5
5
|
|
|
@@ -46,9 +46,9 @@ declare abstract class CacheProvider {
|
|
|
46
46
|
abstract clear(): Promise<void>;
|
|
47
47
|
}
|
|
48
48
|
//#endregion
|
|
49
|
-
//#region src/cache/
|
|
49
|
+
//#region src/cache/primitives/$cache.d.ts
|
|
50
50
|
/**
|
|
51
|
-
* Creates a cache
|
|
51
|
+
* Creates a cache primitive for high-performance data caching with automatic management.
|
|
52
52
|
*
|
|
53
53
|
* Provides a caching layer that improves application performance by storing frequently accessed
|
|
54
54
|
* data in memory or external stores like Redis, with support for both function result caching
|
|
@@ -96,10 +96,10 @@ declare abstract class CacheProvider {
|
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
98
|
declare const $cache: {
|
|
99
|
-
<TReturn = string, TParameter extends any[] = any[]>(options?:
|
|
100
|
-
[KIND]: typeof
|
|
99
|
+
<TReturn = string, TParameter extends any[] = any[]>(options?: CachePrimitiveOptions<TReturn, TParameter>): CachePrimitiveFn<TReturn, TParameter>;
|
|
100
|
+
[KIND]: typeof CachePrimitive;
|
|
101
101
|
};
|
|
102
|
-
interface
|
|
102
|
+
interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]> {
|
|
103
103
|
/**
|
|
104
104
|
* The cache name. This is useful for invalidating multiple caches at once.
|
|
105
105
|
*
|
|
@@ -134,7 +134,7 @@ interface CacheDescriptorOptions<TReturn = any, TParameter extends any[] = any[]
|
|
|
134
134
|
*/
|
|
135
135
|
disabled?: boolean;
|
|
136
136
|
}
|
|
137
|
-
declare class
|
|
137
|
+
declare class CachePrimitive<TReturn = any, TParameter extends any[] = any[]> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {
|
|
138
138
|
protected readonly env: {
|
|
139
139
|
CACHE_ENABLED: boolean;
|
|
140
140
|
CACHE_DEFAULT_TTL: number;
|
|
@@ -158,9 +158,9 @@ declare class CacheDescriptor<TReturn = any, TParameter extends any[] = any[]> e
|
|
|
158
158
|
protected deserialize<TReturn>(uint8Array: Uint8Array): Promise<TReturn>;
|
|
159
159
|
protected $provider(): CacheProvider;
|
|
160
160
|
}
|
|
161
|
-
interface
|
|
161
|
+
interface CachePrimitiveFn<TReturn = any, TParameter extends any[] = any[]> extends CachePrimitive<TReturn, TParameter> {
|
|
162
162
|
/**
|
|
163
|
-
* Run the cache
|
|
163
|
+
* Run the cache primitive with the provided arguments.
|
|
164
164
|
*/
|
|
165
165
|
(...args: TParameter): Promise<TReturn>;
|
|
166
166
|
}
|
|
@@ -188,7 +188,7 @@ declare class MemoryCacheProvider implements CacheProvider {
|
|
|
188
188
|
/**
|
|
189
189
|
* Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.
|
|
190
190
|
*
|
|
191
|
-
* The cache module enables declarative caching through the `$cache`
|
|
191
|
+
* The cache module enables declarative caching through the `$cache` primitive, allowing you to cache method results,
|
|
192
192
|
* API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and
|
|
193
193
|
* persistent storage backends for different performance and durability requirements.
|
|
194
194
|
*
|
|
@@ -198,5 +198,5 @@ declare class MemoryCacheProvider implements CacheProvider {
|
|
|
198
198
|
*/
|
|
199
199
|
declare const AlephaCache: alepha0.Service<alepha0.Module>;
|
|
200
200
|
//#endregion
|
|
201
|
-
export { $cache, AlephaCache,
|
|
201
|
+
export { $cache, AlephaCache, CachePrimitive, CachePrimitiveFn, CachePrimitiveOptions, CacheProvider, MemoryCacheProvider };
|
|
202
202
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cache/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env, $inject, $module, AlephaError,
|
|
1
|
+
import { $env, $inject, $module, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
|
|
@@ -82,9 +82,9 @@ var MemoryCacheProvider = class {
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
//#endregion
|
|
85
|
-
//#region src/cache/
|
|
85
|
+
//#region src/cache/primitives/$cache.ts
|
|
86
86
|
/**
|
|
87
|
-
* Creates a cache
|
|
87
|
+
* Creates a cache primitive for high-performance data caching with automatic management.
|
|
88
88
|
*
|
|
89
89
|
* Provides a caching layer that improves application performance by storing frequently accessed
|
|
90
90
|
* data in memory or external stores like Redis, with support for both function result caching
|
|
@@ -132,7 +132,7 @@ var MemoryCacheProvider = class {
|
|
|
132
132
|
* ```
|
|
133
133
|
*/
|
|
134
134
|
const $cache = (options = {}) => {
|
|
135
|
-
const instance =
|
|
135
|
+
const instance = createPrimitive(CachePrimitive, options);
|
|
136
136
|
const fn = (...args) => instance.run(...args);
|
|
137
137
|
return Object.setPrototypeOf(fn, instance);
|
|
138
138
|
};
|
|
@@ -143,7 +143,7 @@ const envSchema = t.object({
|
|
|
143
143
|
description: "The default time to live for cache entries. In seconds."
|
|
144
144
|
})
|
|
145
145
|
});
|
|
146
|
-
var
|
|
146
|
+
var CachePrimitive = class extends Primitive {
|
|
147
147
|
env = $env(envSchema);
|
|
148
148
|
dateTimeProvider = $inject(DateTimeProvider);
|
|
149
149
|
provider = this.$provider();
|
|
@@ -206,14 +206,14 @@ var CacheDescriptor = class extends Descriptor {
|
|
|
206
206
|
return this.alepha.inject(this.options.provider);
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
|
-
$cache[KIND] =
|
|
209
|
+
$cache[KIND] = CachePrimitive;
|
|
210
210
|
|
|
211
211
|
//#endregion
|
|
212
212
|
//#region src/cache/index.ts
|
|
213
213
|
/**
|
|
214
214
|
* Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.
|
|
215
215
|
*
|
|
216
|
-
* The cache module enables declarative caching through the `$cache`
|
|
216
|
+
* The cache module enables declarative caching through the `$cache` primitive, allowing you to cache method results,
|
|
217
217
|
* API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and
|
|
218
218
|
* persistent storage backends for different performance and durability requirements.
|
|
219
219
|
*
|
|
@@ -223,7 +223,7 @@ $cache[KIND] = CacheDescriptor;
|
|
|
223
223
|
*/
|
|
224
224
|
const AlephaCache = $module({
|
|
225
225
|
name: "alepha.cache",
|
|
226
|
-
|
|
226
|
+
primitives: [$cache],
|
|
227
227
|
services: [CacheProvider, MemoryCacheProvider],
|
|
228
228
|
register: (alepha) => alepha.with({
|
|
229
229
|
optional: true,
|
|
@@ -233,5 +233,5 @@ const AlephaCache = $module({
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
//#endregion
|
|
236
|
-
export { $cache, AlephaCache,
|
|
236
|
+
export { $cache, AlephaCache, CachePrimitive, CacheProvider, MemoryCacheProvider };
|
|
237
237
|
//# sourceMappingURL=index.js.map
|
package/dist/cache/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["keysToDelete: string[]"],"sources":["../../src/cache/errors/CacheError.ts","../../src/cache/providers/CacheProvider.ts","../../src/cache/providers/MemoryCacheProvider.ts","../../src/cache/descriptors/$cache.ts","../../src/cache/index.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\nexport class MemoryCacheProvider implements CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n return this.store[name]?.[key]?.data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n}\n","import {\n $env,\n $inject,\n createDescriptor,\n Descriptor,\n type InstantiableClass,\n KIND,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheError } from \"../errors/CacheError.ts\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache descriptor for high-performance data caching with automatic management.\n *\n * Provides a caching layer that improves application performance by storing frequently accessed\n * data in memory or external stores like Redis, with support for both function result caching\n * and manual cache operations.\n *\n * **Key Features**\n * - Automatic function result caching based on input parameters\n * - Multiple storage backends (in-memory, Redis, custom providers)\n * - Intelligent serialization for JSON, strings, and binary data\n * - Configurable TTL with automatic expiration\n * - Pattern-based cache invalidation with wildcard support\n * - Environment controls to enable/disable caching\n *\n * **Storage Backends**\n * - Memory: Fast in-memory cache (default for development)\n * - Redis: Distributed cache for production environments\n * - Custom providers: Implement your own storage backend\n *\n * @example\n * ```ts\n * class DataService {\n * // Function result caching\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n *\n * // Manual cache operations\n * sessionCache = $cache<UserSession>({\n * name: \"sessions\",\n * ttl: [1, \"hour\"]\n * });\n *\n * async storeSession(id: string, session: UserSession) {\n * await this.sessionCache.set(id, session);\n * }\n *\n * async invalidateUserSessions(userId: string) {\n * await this.sessionCache.invalidate(`user:${userId}:*`);\n * }\n * }\n * ```\n */\nexport const $cache = <TReturn = string, TParameter extends any[] = any[]>(\n options: CacheDescriptorOptions<TReturn, TParameter> = {},\n): CacheDescriptorFn<TReturn, TParameter> => {\n const instance = createDescriptor(\n CacheDescriptor<TReturn, TParameter>,\n options,\n );\n const fn = (...args: TParameter): Promise<TReturn> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance) as CacheDescriptorFn<\n TReturn,\n TParameter\n >;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CacheDescriptorOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n * If not provided, the default store provider will be used.\n */\n provider?: InstantiableClass<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n CACHE_ENABLED: t.boolean({ default: true }),\n CACHE_DEFAULT_TTL: t.number({\n default: 300, // 5 minutes\n description: \"The default time to live for cache entries. In seconds.\",\n }),\n});\n\nexport class CacheDescriptor<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Descriptor<CacheDescriptorOptions<TReturn, TParameter>> {\n protected readonly env = $env(envSchema);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly provider = this.$provider();\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n };\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new Error(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const cached = await this.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await handler(...args);\n // note: when exception occurs, don't cache the result\n\n await this.set(key, result);\n\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.provider.keys(\n this.container,\n key.slice(0, -1),\n );\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.provider.del(this.container, ...keysToDelete);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n const px = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.env.CACHE_DEFAULT_TTL, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n await this.provider.set(\n this.container,\n key,\n this.serialize(value),\n px > 0 ? px : undefined,\n );\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.env.CACHE_ENABLED\n ) {\n return undefined;\n }\n\n const data = await this.provider.get(this.container, key);\n if (data) {\n return await this.deserialize<TReturn>(data);\n }\n\n return undefined;\n }\n\n protected serialize<TReturn>(value: TReturn): Uint8Array {\n if (value instanceof Uint8Array) {\n return new Uint8Array([this.codes.BINARY, ...value]); // TODO: check if copy is ok?\n }\n\n if (typeof value === \"string\") {\n return new Uint8Array([this.codes.STRING, ...this.encoder.encode(value)]);\n }\n\n return new Uint8Array([\n this.codes.JSON,\n ...this.encoder.encode(JSON.stringify(value)),\n ]);\n }\n\n protected async deserialize<TReturn>(\n uint8Array: Uint8Array,\n ): Promise<TReturn> {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as TReturn;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as TReturn;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as TReturn;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n}\n\nexport interface CacheDescriptorFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CacheDescriptor<TReturn, TParameter> {\n /**\n * Run the cache descriptor with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n$cache[KIND] = CacheDescriptor;\n","import { $module } from \"alepha\";\nimport { $cache } from \"./descriptors/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.\n *\n * The cache module enables declarative caching through the `$cache` descriptor, allowing you to cache method results,\n * API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and\n * persistent storage backends for different performance and durability requirements.\n *\n * @see {@link $cache}\n * @see {@link CacheProvider}\n * @module alepha.cache\n */\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n descriptors: [$cache],\n services: [CacheProvider, MemoryCacheProvider],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: MemoryCacheProvider,\n }),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["keysToDelete: string[]"],"sources":["../../src/cache/errors/CacheError.ts","../../src/cache/providers/CacheProvider.ts","../../src/cache/providers/MemoryCacheProvider.ts","../../src/cache/primitives/$cache.ts","../../src/cache/index.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\nexport class MemoryCacheProvider implements CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n return this.store[name]?.[key]?.data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n}\n","import {\n $env,\n $inject,\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheError } from \"../errors/CacheError.ts\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache primitive for high-performance data caching with automatic management.\n *\n * Provides a caching layer that improves application performance by storing frequently accessed\n * data in memory or external stores like Redis, with support for both function result caching\n * and manual cache operations.\n *\n * **Key Features**\n * - Automatic function result caching based on input parameters\n * - Multiple storage backends (in-memory, Redis, custom providers)\n * - Intelligent serialization for JSON, strings, and binary data\n * - Configurable TTL with automatic expiration\n * - Pattern-based cache invalidation with wildcard support\n * - Environment controls to enable/disable caching\n *\n * **Storage Backends**\n * - Memory: Fast in-memory cache (default for development)\n * - Redis: Distributed cache for production environments\n * - Custom providers: Implement your own storage backend\n *\n * @example\n * ```ts\n * class DataService {\n * // Function result caching\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n *\n * // Manual cache operations\n * sessionCache = $cache<UserSession>({\n * name: \"sessions\",\n * ttl: [1, \"hour\"]\n * });\n *\n * async storeSession(id: string, session: UserSession) {\n * await this.sessionCache.set(id, session);\n * }\n *\n * async invalidateUserSessions(userId: string) {\n * await this.sessionCache.invalidate(`user:${userId}:*`);\n * }\n * }\n * ```\n */\nexport const $cache = <TReturn = string, TParameter extends any[] = any[]>(\n options: CachePrimitiveOptions<TReturn, TParameter> = {},\n): CachePrimitiveFn<TReturn, TParameter> => {\n const instance = createPrimitive(\n CachePrimitive<TReturn, TParameter>,\n options,\n );\n const fn = (...args: TParameter): Promise<TReturn> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance) as CachePrimitiveFn<\n TReturn,\n TParameter\n >;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CachePrimitiveOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n * If not provided, the default store provider will be used.\n */\n provider?: InstantiableClass<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n CACHE_ENABLED: t.boolean({ default: true }),\n CACHE_DEFAULT_TTL: t.number({\n default: 300, // 5 minutes\n description: \"The default time to live for cache entries. In seconds.\",\n }),\n});\n\nexport class CachePrimitive<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {\n protected readonly env = $env(envSchema);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly provider = this.$provider();\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n };\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new Error(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const cached = await this.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await handler(...args);\n // note: when exception occurs, don't cache the result\n\n await this.set(key, result);\n\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.provider.keys(\n this.container,\n key.slice(0, -1),\n );\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.provider.del(this.container, ...keysToDelete);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n const px = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.env.CACHE_DEFAULT_TTL, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n await this.provider.set(\n this.container,\n key,\n this.serialize(value),\n px > 0 ? px : undefined,\n );\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.env.CACHE_ENABLED\n ) {\n return undefined;\n }\n\n const data = await this.provider.get(this.container, key);\n if (data) {\n return await this.deserialize<TReturn>(data);\n }\n\n return undefined;\n }\n\n protected serialize<TReturn>(value: TReturn): Uint8Array {\n if (value instanceof Uint8Array) {\n return new Uint8Array([this.codes.BINARY, ...value]); // TODO: check if copy is ok?\n }\n\n if (typeof value === \"string\") {\n return new Uint8Array([this.codes.STRING, ...this.encoder.encode(value)]);\n }\n\n return new Uint8Array([\n this.codes.JSON,\n ...this.encoder.encode(JSON.stringify(value)),\n ]);\n }\n\n protected async deserialize<TReturn>(\n uint8Array: Uint8Array,\n ): Promise<TReturn> {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as TReturn;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as TReturn;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as TReturn;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n}\n\nexport interface CachePrimitiveFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CachePrimitive<TReturn, TParameter> {\n /**\n * Run the cache primitive with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n$cache[KIND] = CachePrimitive;\n","import { $module } from \"alepha\";\nimport { $cache } from \"./primitives/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.\n *\n * The cache module enables declarative caching through the `$cache` primitive, allowing you to cache method results,\n * API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and\n * persistent storage backends for different performance and durability requirements.\n *\n * @see {@link $cache}\n * @see {@link CacheProvider}\n * @module alepha.cache\n */\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n primitives: [$cache],\n services: [CacheProvider, MemoryCacheProvider],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: MemoryCacheProvider,\n }),\n});\n"],"mappings":"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"}
|