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
|
@@ -0,0 +1,2113 @@
|
|
|
1
|
+
import { Compile } from "typebox/compile";
|
|
2
|
+
import * as TypeBox from "typebox";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import TypeBoxFormat from "typebox/format";
|
|
5
|
+
import { Locale } from "typebox/system";
|
|
6
|
+
import * as TypeBoxValue from "typebox/value";
|
|
7
|
+
|
|
8
|
+
//#region src/core/constants/KIND.ts
|
|
9
|
+
/**
|
|
10
|
+
* Used for identifying primitives.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
const KIND = Symbol.for("Alepha.Kind");
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/core/constants/MODULE.ts
|
|
18
|
+
/**
|
|
19
|
+
* Used for identifying modules.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
const MODULE = Symbol.for("Alepha.Module");
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/core/constants/OPTIONS.ts
|
|
27
|
+
/**
|
|
28
|
+
* Used for primitives options.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
const OPTIONS = Symbol.for("Alepha.Options");
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/core/errors/AlephaError.ts
|
|
36
|
+
/**
|
|
37
|
+
* Default error class for Alepha.
|
|
38
|
+
*/
|
|
39
|
+
var AlephaError = class extends Error {
|
|
40
|
+
name = "AlephaError";
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/core/errors/CircularDependencyError.ts
|
|
45
|
+
var CircularDependencyError = class extends AlephaError {
|
|
46
|
+
name = "CircularDependencyError";
|
|
47
|
+
constructor(provider, parents) {
|
|
48
|
+
super(`Instance not available. Looks like a circular dependency. ? -> ${parents?.map((name) => `${name} -> `).join("")}${provider} -> ?`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/core/errors/ContainerLockedError.ts
|
|
54
|
+
var ContainerLockedError = class extends AlephaError {
|
|
55
|
+
name = "ContainerLockedError";
|
|
56
|
+
constructor(message = "Container is locked. No more providers can be added.") {
|
|
57
|
+
super(message);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/core/errors/TooLateSubstitutionError.ts
|
|
63
|
+
var TooLateSubstitutionError = class extends AlephaError {
|
|
64
|
+
name = "TooLateSubstitutionError";
|
|
65
|
+
constructor(original, substitution) {
|
|
66
|
+
super(`Service already substituted. Please, substitute Service '${original}' with Service '${substitution}' before using it.`);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/core/errors/MissingContextError.ts
|
|
72
|
+
var MissingContextError = class extends AlephaError {
|
|
73
|
+
name = "MissingContextError";
|
|
74
|
+
constructor() {
|
|
75
|
+
super("Missing context. Did you forget to call Alepha.create()?");
|
|
76
|
+
this.name = "MissingContextError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/core/helpers/ref.ts
|
|
82
|
+
/**
|
|
83
|
+
* Store the current context and definition during injection phase.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
const __alephaRef = {};
|
|
88
|
+
/**
|
|
89
|
+
* Note:
|
|
90
|
+
*
|
|
91
|
+
* This file is used to share context between $primitives and the Alepha core during the injection phase.
|
|
92
|
+
*
|
|
93
|
+
* There is no side effect as long as Alepha is not used concurrently in multiple contexts (which is not the case).
|
|
94
|
+
*
|
|
95
|
+
* // __alephaRef === undefined
|
|
96
|
+
* // begin alepha.with()
|
|
97
|
+
* // __alephaRef.context = alepha
|
|
98
|
+
* // ... injection phase ...
|
|
99
|
+
* // __alephaRef.context = undefined
|
|
100
|
+
* // end alepha.with()
|
|
101
|
+
* // __alephaRef === undefined
|
|
102
|
+
*
|
|
103
|
+
* As .with() is synchronous, there is no risk of context leakage.
|
|
104
|
+
*
|
|
105
|
+
* ---------------------------------------------------------------------------------------------------------------------
|
|
106
|
+
*
|
|
107
|
+
* Why this helper?
|
|
108
|
+
*
|
|
109
|
+
* It allows to avoid passing Alepha instance to every $hook, $inject, etc. calls. It's a beautiful syntactic sugar.
|
|
110
|
+
*
|
|
111
|
+
* With sugar:
|
|
112
|
+
*
|
|
113
|
+
* class A {
|
|
114
|
+
* on = $hook( ... ) // <- __alephaRef is set here
|
|
115
|
+
* }
|
|
116
|
+
*
|
|
117
|
+
* Without sugar:
|
|
118
|
+
*
|
|
119
|
+
* class A {
|
|
120
|
+
* constructor(alepha: Alepha) {
|
|
121
|
+
* this.on = $hook(alepha, ... ) // <- no need of __alephaRef
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* One main goal of Alepha is working with classes but without the class verbosity.
|
|
126
|
+
* Forcing to pass Alepha instance in constructors would be a step back in that direction!
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/core/primitives/$context.ts
|
|
131
|
+
/**
|
|
132
|
+
* Get Alepha instance and current service from the current context.
|
|
133
|
+
*
|
|
134
|
+
* It can only be used inside $primitive functions.
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { $context } from "alepha";
|
|
138
|
+
*
|
|
139
|
+
* const $hello = () => {
|
|
140
|
+
* const { alepha, service, module } = $context();
|
|
141
|
+
*
|
|
142
|
+
* // alepha - alepha instance
|
|
143
|
+
* // service - class which is creating this primitive, this is NOT the instance but the service definition
|
|
144
|
+
* // module - module definition, if any
|
|
145
|
+
*
|
|
146
|
+
* return {};
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
* class MyService {
|
|
150
|
+
* hello = $hello();
|
|
151
|
+
* }
|
|
152
|
+
*
|
|
153
|
+
* const alepha = new Alepha().with(MyService);
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
const $context = () => {
|
|
159
|
+
if (!__alephaRef.alepha) throw new MissingContextError();
|
|
160
|
+
return {
|
|
161
|
+
alepha: __alephaRef.alepha,
|
|
162
|
+
service: __alephaRef.service,
|
|
163
|
+
module: __alephaRef.service?.[MODULE]
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/core/helpers/primitive.ts
|
|
169
|
+
var Primitive = class {
|
|
170
|
+
alepha;
|
|
171
|
+
options;
|
|
172
|
+
config;
|
|
173
|
+
constructor(args) {
|
|
174
|
+
this.alepha = args.alepha;
|
|
175
|
+
this.options = args.options;
|
|
176
|
+
this.config = {
|
|
177
|
+
propertyKey: "",
|
|
178
|
+
service: args.service,
|
|
179
|
+
module: args.module
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Called automatically by Alepha after the primitive is created.
|
|
184
|
+
*/
|
|
185
|
+
onInit() {}
|
|
186
|
+
};
|
|
187
|
+
const createPrimitive = (primitive, options) => {
|
|
188
|
+
const { alepha, service } = $context();
|
|
189
|
+
if (MODULE in primitive && primitive[MODULE]) alepha.with(primitive[MODULE]);
|
|
190
|
+
return alepha.inject(primitive, {
|
|
191
|
+
lifetime: "transient",
|
|
192
|
+
args: [{
|
|
193
|
+
options,
|
|
194
|
+
alepha,
|
|
195
|
+
service: service ?? Alepha
|
|
196
|
+
}]
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/core/interfaces/Service.ts
|
|
202
|
+
function isClass(func) {
|
|
203
|
+
if (typeof func !== "function") return false;
|
|
204
|
+
const descriptor = Object.getOwnPropertyDescriptor(func, "prototype");
|
|
205
|
+
return !!descriptor && !descriptor.writable;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/core/primitives/$module.ts
|
|
210
|
+
/**
|
|
211
|
+
* Wrap Services and Primitives into a Module.
|
|
212
|
+
*
|
|
213
|
+
* - A module is just a Service with some extra {@link Module}.
|
|
214
|
+
* - You must attach a `name` to it.
|
|
215
|
+
* - Name must follow the pattern: `project.module.submodule`. (e.g. `myapp.users.auth`).
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* import { $module } from "alepha";
|
|
220
|
+
* import { MyService } from "./MyService.ts";
|
|
221
|
+
*
|
|
222
|
+
* // export MyService, so it can be used everywhere (optional)
|
|
223
|
+
* export * from "./MyService.ts";
|
|
224
|
+
*
|
|
225
|
+
* export default $module({
|
|
226
|
+
* name: "my.project.module",
|
|
227
|
+
* // MyService will have a module context "my.project.module"
|
|
228
|
+
* services: [MyService],
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* ### Why Modules?
|
|
233
|
+
*
|
|
234
|
+
* #### Logging
|
|
235
|
+
*
|
|
236
|
+
* By default, AlephaLogger will log the module name in the logs.
|
|
237
|
+
* This helps to identify where the logs are coming from.
|
|
238
|
+
*
|
|
239
|
+
* You can also set different log levels for different modules.
|
|
240
|
+
* It means you can set 'some.very.specific.module' to 'debug' and keep the rest of the application to 'info'.
|
|
241
|
+
*
|
|
242
|
+
* #### Modulith
|
|
243
|
+
*
|
|
244
|
+
* Force to structure your application in modules, even if it's a single deployable unit.
|
|
245
|
+
* It helps to keep a clean architecture and avoid monolithic applications.
|
|
246
|
+
*
|
|
247
|
+
* A strict mode flag will probably come to enforce module boundaries.
|
|
248
|
+
* -> Throwing errors when a service from another module is injected.
|
|
249
|
+
* But it's not implemented yet.
|
|
250
|
+
*
|
|
251
|
+
* ### When not to use Modules?
|
|
252
|
+
*
|
|
253
|
+
* Small applications does not need modules. It's better to keep it simple.
|
|
254
|
+
* Modules are more useful when the application grows and needs to be structured.
|
|
255
|
+
* If we speak with number of `$actions`, a module should be used when you have more than 30 actions in a single module.
|
|
256
|
+
* Meaning that if you have 100 actions, you should have at least 3 modules.
|
|
257
|
+
*/
|
|
258
|
+
const $module = (options) => {
|
|
259
|
+
const { services = [], primitives = [], name } = options;
|
|
260
|
+
if (!name || !Module.NAME_REGEX.test(name)) throw new AlephaError(`Invalid module name '${name}'. It should be in the format of 'project.module.submodule'`);
|
|
261
|
+
const $ = class extends Module {
|
|
262
|
+
options = options;
|
|
263
|
+
register(alepha) {
|
|
264
|
+
if (typeof options.register === "function") {
|
|
265
|
+
options.register(alepha);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const service of services) alepha.inject(service, { parent: this.constructor });
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
Object.defineProperty($, "name", {
|
|
272
|
+
value: name,
|
|
273
|
+
writable: false
|
|
274
|
+
});
|
|
275
|
+
for (const service of services) if (!Module.is(service)) service[MODULE] = $;
|
|
276
|
+
for (const factory of primitives) if (typeof factory[KIND] === "function") factory[KIND][MODULE] = $;
|
|
277
|
+
return $;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Base class for all modules.
|
|
281
|
+
*/
|
|
282
|
+
var Module = class Module {
|
|
283
|
+
static NAME_REGEX = /^[a-z]+(\.[a-z][a-z0-9-]*)*$/;
|
|
284
|
+
/**
|
|
285
|
+
* Check if a Service is a Module.
|
|
286
|
+
*/
|
|
287
|
+
static is(ctor) {
|
|
288
|
+
return ctor.prototype instanceof Module;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get the Module of a Service.
|
|
292
|
+
*
|
|
293
|
+
* Returns undefined if the Service is not part of a Module.
|
|
294
|
+
*/
|
|
295
|
+
static of(ctor) {
|
|
296
|
+
return ctor[MODULE];
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/core/providers/AlsProvider.ts
|
|
302
|
+
var AlsProvider = class AlsProvider {
|
|
303
|
+
static create = () => {};
|
|
304
|
+
als;
|
|
305
|
+
constructor() {
|
|
306
|
+
this.als = AlsProvider.create();
|
|
307
|
+
}
|
|
308
|
+
createContextId() {
|
|
309
|
+
return crypto.randomUUID();
|
|
310
|
+
}
|
|
311
|
+
run(callback, data = {}) {
|
|
312
|
+
if (!this.als) return callback();
|
|
313
|
+
data.registry ??= /* @__PURE__ */ new Map();
|
|
314
|
+
data.context ??= this.createContextId();
|
|
315
|
+
return this.als.run(data, callback);
|
|
316
|
+
}
|
|
317
|
+
exists() {
|
|
318
|
+
return !!this.get("context");
|
|
319
|
+
}
|
|
320
|
+
get(key) {
|
|
321
|
+
if (!this.als) return;
|
|
322
|
+
const store = this.als.getStore();
|
|
323
|
+
if (store) return store[key];
|
|
324
|
+
}
|
|
325
|
+
set(key, value) {
|
|
326
|
+
if (!this.als) return;
|
|
327
|
+
const store = this.als.getStore();
|
|
328
|
+
if (store) store[key] = value;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/core/primitives/$inject.ts
|
|
334
|
+
/**
|
|
335
|
+
* Get the instance of the specified type from the context.
|
|
336
|
+
*
|
|
337
|
+
* ```ts
|
|
338
|
+
* class A { }
|
|
339
|
+
* class B {
|
|
340
|
+
* a = $inject(A);
|
|
341
|
+
* }
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
const $inject = (type, opts = {}) => {
|
|
345
|
+
const { alepha, service } = $context();
|
|
346
|
+
if (type === alepha.constructor) return alepha;
|
|
347
|
+
return alepha.inject(type, {
|
|
348
|
+
parent: service ?? alepha.constructor,
|
|
349
|
+
...opts
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
var InjectPrimitive = class extends Primitive {};
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/core/providers/Json.ts
|
|
356
|
+
/**
|
|
357
|
+
* Mimics the JSON global object with stringify and parse methods.
|
|
358
|
+
*
|
|
359
|
+
* Used across the codebase via dependency injection.
|
|
360
|
+
*/
|
|
361
|
+
var Json = class {
|
|
362
|
+
stringify(value, replacer, space) {
|
|
363
|
+
return JSON.stringify(value, replacer, space);
|
|
364
|
+
}
|
|
365
|
+
parse(text, reviver) {
|
|
366
|
+
return JSON.parse(text, reviver);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/core/providers/SchemaCodec.ts
|
|
372
|
+
var SchemaCodec = class {};
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/core/providers/JsonSchemaCodec.ts
|
|
376
|
+
var JsonSchemaCodec = class extends SchemaCodec {
|
|
377
|
+
json = $inject(Json);
|
|
378
|
+
encoder = new TextEncoder();
|
|
379
|
+
decoder = new TextDecoder();
|
|
380
|
+
encodeToString(schema, value) {
|
|
381
|
+
return this.json.stringify(value);
|
|
382
|
+
}
|
|
383
|
+
encodeToBinary(schema, value) {
|
|
384
|
+
return this.encoder.encode(this.encodeToString(schema, value));
|
|
385
|
+
}
|
|
386
|
+
decode(schema, value) {
|
|
387
|
+
if (value instanceof Uint8Array) {
|
|
388
|
+
const text = this.decoder.decode(value);
|
|
389
|
+
return this.json.parse(text);
|
|
390
|
+
}
|
|
391
|
+
if (typeof value === "string") {
|
|
392
|
+
const trimmed = value.trim();
|
|
393
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return this.json.parse(value);
|
|
394
|
+
return value;
|
|
395
|
+
}
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/core/errors/TypeBoxError.ts
|
|
402
|
+
var TypeBoxError = class extends AlephaError {
|
|
403
|
+
name = "TypeBoxError";
|
|
404
|
+
cause;
|
|
405
|
+
value;
|
|
406
|
+
constructor(error) {
|
|
407
|
+
super(`Invalid input: ${error.message}${error.instancePath ? ` at ${error.instancePath}` : ""}`, { cause: error });
|
|
408
|
+
const params = error.params;
|
|
409
|
+
if (params?.requiredProperties) this.value = {
|
|
410
|
+
path: `/${params.requiredProperties[0]}`,
|
|
411
|
+
message: "must be defined"
|
|
412
|
+
};
|
|
413
|
+
else this.value = {
|
|
414
|
+
path: error.instancePath,
|
|
415
|
+
message: error.message
|
|
416
|
+
};
|
|
417
|
+
this.cause = error;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/core/providers/SchemaValidator.ts
|
|
423
|
+
var SchemaValidator = class {
|
|
424
|
+
cache = /* @__PURE__ */ new Map();
|
|
425
|
+
/**
|
|
426
|
+
* Validate the value against the provided schema.
|
|
427
|
+
*
|
|
428
|
+
* Validation create a new value by applying some preprocessing. (e.g., trimming text)
|
|
429
|
+
*/
|
|
430
|
+
validate(schema, value, options = {}) {
|
|
431
|
+
const newValue = this.beforeParse(schema, value, {
|
|
432
|
+
trim: options.trim ?? true,
|
|
433
|
+
nullToUndefined: options.nullToUndefined ?? true,
|
|
434
|
+
deleteUndefined: options.deleteUndefined ?? true
|
|
435
|
+
});
|
|
436
|
+
try {
|
|
437
|
+
return this.getValidator(schema).Parse(newValue);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error.cause?.errors?.[0]) throw new TypeBoxError(error.cause.errors[0]);
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
getValidator(schema) {
|
|
444
|
+
if (this.cache.has(schema)) return this.cache.get(schema);
|
|
445
|
+
const validator = Compile(schema);
|
|
446
|
+
this.cache.set(schema, validator);
|
|
447
|
+
return validator;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Preprocess the value based on the schema before validation.
|
|
451
|
+
*
|
|
452
|
+
* - If the value is `null` and the schema does not allow `null`, it converts it to `undefined`.
|
|
453
|
+
* - If the value is a string and the schema has a `~options.trim` flag, it trims whitespace from the string.
|
|
454
|
+
*/
|
|
455
|
+
beforeParse(schema, value, options) {
|
|
456
|
+
if (!schema) return value;
|
|
457
|
+
if (value === null && !this.isSchemaNullable(schema) && options.nullToUndefined) return;
|
|
458
|
+
if (Array.isArray(value)) return value.map((it) => this.beforeParse(schema.items, it, options));
|
|
459
|
+
if (typeof value === "string" && schema.type === "string") {
|
|
460
|
+
let str = value;
|
|
461
|
+
if (options.trim && schema["~options"]?.trim) str = str.trim();
|
|
462
|
+
if (schema["~options"]?.lowercase) str = str.toLowerCase();
|
|
463
|
+
return str;
|
|
464
|
+
}
|
|
465
|
+
if (typeof value === "object" && value !== null && schema.type === "object") {
|
|
466
|
+
const obj = {};
|
|
467
|
+
for (const key in value) {
|
|
468
|
+
const newValue = this.beforeParse(schema.properties?.[key], value[key], options);
|
|
469
|
+
if (newValue === void 0 && options.deleteUndefined) continue;
|
|
470
|
+
obj[key] = newValue;
|
|
471
|
+
}
|
|
472
|
+
return obj;
|
|
473
|
+
}
|
|
474
|
+
return value;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Used by `beforeParse` to determine if a schema allows null values.
|
|
478
|
+
*/
|
|
479
|
+
isSchemaNullable = (schema) => {
|
|
480
|
+
if (!schema) return false;
|
|
481
|
+
if (schema.type === "null") return true;
|
|
482
|
+
if (Array.isArray(schema.type) && schema.type.includes("null")) return true;
|
|
483
|
+
if (schema.anyOf) return schema.anyOf.some((it) => this.isSchemaNullable(it));
|
|
484
|
+
if (schema.oneOf) return schema.oneOf.some((it) => this.isSchemaNullable(it));
|
|
485
|
+
if (schema.allOf) return schema.allOf.some((it) => this.isSchemaNullable(it));
|
|
486
|
+
return false;
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/core/providers/CodecManager.ts
|
|
492
|
+
/**
|
|
493
|
+
* CodecManager manages multiple codec formats and provides a unified interface
|
|
494
|
+
* for encoding and decoding data with different formats.
|
|
495
|
+
*/
|
|
496
|
+
var CodecManager = class {
|
|
497
|
+
codecs = /* @__PURE__ */ new Map();
|
|
498
|
+
jsonCodec = $inject(JsonSchemaCodec);
|
|
499
|
+
schemaValidator = $inject(SchemaValidator);
|
|
500
|
+
default = "json";
|
|
501
|
+
constructor() {
|
|
502
|
+
this.register(this.default, this.jsonCodec);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Register a new codec format.
|
|
506
|
+
*
|
|
507
|
+
* @param name - The name of the codec (e.g., 'json', 'protobuf')
|
|
508
|
+
* @param codec - The codec implementation
|
|
509
|
+
*/
|
|
510
|
+
register(name, codec) {
|
|
511
|
+
this.codecs.set(name, codec);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get a specific codec by name.
|
|
515
|
+
*
|
|
516
|
+
* @param name - The name of the codec
|
|
517
|
+
* @returns The codec instance
|
|
518
|
+
* @throws {AlephaError} If the codec is not found
|
|
519
|
+
*/
|
|
520
|
+
getCodec(name) {
|
|
521
|
+
const codec = this.codecs.get(name);
|
|
522
|
+
if (!codec) throw new AlephaError(`Codec "${name}" not found. Available codecs: ${Array.from(this.codecs.keys()).join(", ")}`);
|
|
523
|
+
return codec;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Encode data using the specified codec and output format.
|
|
527
|
+
*/
|
|
528
|
+
encode(schema, value, options) {
|
|
529
|
+
const codec = this.getCodec(options?.encoder ?? this.default);
|
|
530
|
+
const as = options?.as ?? "object";
|
|
531
|
+
if (options?.validation !== false) value = this.schemaValidator.validate(schema, value, options?.validation);
|
|
532
|
+
if (as === "object") return value;
|
|
533
|
+
if (as === "binary") return codec.encodeToBinary(schema, value);
|
|
534
|
+
return codec.encodeToString(schema, value);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Decode data using the specified codec.
|
|
538
|
+
*/
|
|
539
|
+
decode(schema, data, options) {
|
|
540
|
+
const encoderName = options?.encoder ?? this.default;
|
|
541
|
+
let value = this.getCodec(encoderName).decode(schema, data);
|
|
542
|
+
if (options?.validation !== false) value = this.schemaValidator.validate(schema, value, options?.validation);
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Validate decoded data against the schema.
|
|
547
|
+
*
|
|
548
|
+
* This is automatically called before encoding or after decoding.
|
|
549
|
+
*/
|
|
550
|
+
validate(schema, value, options) {
|
|
551
|
+
return this.schemaValidator.validate(schema, value, options);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/core/providers/EventManager.ts
|
|
557
|
+
var EventManager = class {
|
|
558
|
+
logFn;
|
|
559
|
+
/**
|
|
560
|
+
* List of events that can be triggered. Powered by $hook().
|
|
561
|
+
*/
|
|
562
|
+
events = {};
|
|
563
|
+
constructor(logFn) {
|
|
564
|
+
this.logFn = logFn;
|
|
565
|
+
}
|
|
566
|
+
get log() {
|
|
567
|
+
return this.logFn?.();
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Registers a hook for the specified event.
|
|
571
|
+
*/
|
|
572
|
+
on(event, hookOrFunc) {
|
|
573
|
+
if (!this.events[event]) this.events[event] = [];
|
|
574
|
+
const hook = typeof hookOrFunc === "function" ? { callback: hookOrFunc } : hookOrFunc;
|
|
575
|
+
if (hook.priority === "first") this.events[event].unshift(hook);
|
|
576
|
+
else if (hook.priority === "last") this.events[event].push(hook);
|
|
577
|
+
else {
|
|
578
|
+
const index = this.events[event].findIndex((it) => it.priority === "last");
|
|
579
|
+
if (index !== -1) this.events[event].splice(index, 0, hook);
|
|
580
|
+
else this.events[event].push(hook);
|
|
581
|
+
}
|
|
582
|
+
return () => {
|
|
583
|
+
this.events[event] = this.events[event].filter((it) => it.callback !== hook.callback);
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Emits the specified event with the given payload.
|
|
588
|
+
*/
|
|
589
|
+
async emit(func, payload, options = {}) {
|
|
590
|
+
const ctx = {};
|
|
591
|
+
if (options.log) {
|
|
592
|
+
ctx.now = Date.now();
|
|
593
|
+
this.log?.trace(`${func} ...`);
|
|
594
|
+
}
|
|
595
|
+
let events = this.events[func] ?? [];
|
|
596
|
+
if (options.reverse) events = events.toReversed();
|
|
597
|
+
for (const hook of events) {
|
|
598
|
+
const name = hook.caller?.name ?? "unknown";
|
|
599
|
+
if (options.log) {
|
|
600
|
+
ctx.now2 = Date.now();
|
|
601
|
+
this.log?.trace(`${func}(${name}) ...`);
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
await hook.callback(payload);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (options.catch) {
|
|
607
|
+
this.log?.error(`${func}(${name}) ERROR`, error);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (options.log) throw new AlephaError(`Failed during '${func}()' hook for service: ${name}`, { cause: error });
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
if (options.log) this.log?.debug(`${func}(${name}) OK [${Date.now() - ctx.now2}ms]`);
|
|
614
|
+
}
|
|
615
|
+
if (options.log) this.log?.debug(`${func} OK [${Date.now() - ctx.now}ms]`);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/core/primitives/$atom.ts
|
|
621
|
+
/**
|
|
622
|
+
* Define an atom for state management.
|
|
623
|
+
*
|
|
624
|
+
* Atom lets you define a piece of state with a name, schema, and default value.
|
|
625
|
+
*
|
|
626
|
+
* By default, Alepha state is just a simple key-value store.
|
|
627
|
+
* Using atoms allows you to have type safety, validation, and default values for your state.
|
|
628
|
+
*
|
|
629
|
+
* You control how state is structured and validated.
|
|
630
|
+
*
|
|
631
|
+
* Features:
|
|
632
|
+
* - Set a schema for validation
|
|
633
|
+
* - Set a default value for initial state
|
|
634
|
+
* - Rules, like read-only, custom validation, etc.
|
|
635
|
+
* - Automatic getter access in services with {@link $use}
|
|
636
|
+
* - SSR support (server state automatically serialized and hydrated on client)
|
|
637
|
+
* - React integration (useAtom hook for automatic component re-renders)
|
|
638
|
+
* - Middleware
|
|
639
|
+
* - Persistence adapters (localStorage, Redis, database, file system, cookie, etc.)
|
|
640
|
+
* - State migrations (version upgrades when schema changes)
|
|
641
|
+
* - Documentation generation & devtools integration
|
|
642
|
+
*
|
|
643
|
+
* Common use cases:
|
|
644
|
+
* - user preferences
|
|
645
|
+
* - feature flags
|
|
646
|
+
* - configuration options
|
|
647
|
+
* - session data
|
|
648
|
+
*
|
|
649
|
+
* Atom must contain only serializable data.
|
|
650
|
+
* Avoid storing complex objects like class instances, functions, or DOM elements.
|
|
651
|
+
* If you need to store complex data, consider using identifiers or references instead.
|
|
652
|
+
*/
|
|
653
|
+
const $atom = (options) => {
|
|
654
|
+
return new Atom(options);
|
|
655
|
+
};
|
|
656
|
+
var Atom = class {
|
|
657
|
+
options;
|
|
658
|
+
get schema() {
|
|
659
|
+
return this.options.schema;
|
|
660
|
+
}
|
|
661
|
+
get key() {
|
|
662
|
+
return this.options.name;
|
|
663
|
+
}
|
|
664
|
+
constructor(options) {
|
|
665
|
+
this.options = options;
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
$atom[KIND] = "atom";
|
|
669
|
+
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/core/providers/StateManager.ts
|
|
672
|
+
var StateManager = class {
|
|
673
|
+
als = $inject(AlsProvider);
|
|
674
|
+
events = $inject(EventManager);
|
|
675
|
+
codec = $inject(JsonSchemaCodec);
|
|
676
|
+
atoms = /* @__PURE__ */ new Map();
|
|
677
|
+
store = {};
|
|
678
|
+
constructor(store = {}) {
|
|
679
|
+
this.store = store;
|
|
680
|
+
}
|
|
681
|
+
getAtoms(context = true) {
|
|
682
|
+
const atoms = [];
|
|
683
|
+
if (context && this.als?.exists()) for (const atom of this.atoms.values()) {
|
|
684
|
+
const value = this.als.get(atom.key);
|
|
685
|
+
if (value !== void 0) atoms.push({
|
|
686
|
+
atom,
|
|
687
|
+
value
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
else for (const [key, atom] of this.atoms.entries()) atoms.push({
|
|
691
|
+
atom,
|
|
692
|
+
value: this.store[key]
|
|
693
|
+
});
|
|
694
|
+
return atoms;
|
|
695
|
+
}
|
|
696
|
+
register(atom) {
|
|
697
|
+
const key = atom.key;
|
|
698
|
+
if (!this.atoms.has(key)) {
|
|
699
|
+
this.atoms.set(key, atom);
|
|
700
|
+
if (!(key in this.store)) this.set(key, atom.options.default);
|
|
701
|
+
}
|
|
702
|
+
return this;
|
|
703
|
+
}
|
|
704
|
+
get(target) {
|
|
705
|
+
if (target instanceof Atom) this.register(target);
|
|
706
|
+
const key = target instanceof Atom ? target.key : target;
|
|
707
|
+
const store = this.store;
|
|
708
|
+
return this.als?.exists() ? this.als.get(key) ?? store[key] : store[key];
|
|
709
|
+
}
|
|
710
|
+
set(target, value) {
|
|
711
|
+
if (target instanceof Atom) this.register(target);
|
|
712
|
+
const key = target instanceof Atom ? target.key : target;
|
|
713
|
+
const store = this.store;
|
|
714
|
+
const prevValue = this.get(key);
|
|
715
|
+
if (prevValue === value) return this;
|
|
716
|
+
if (this.als?.exists()) this.als.set(key, value);
|
|
717
|
+
else store[key] = value;
|
|
718
|
+
this.events?.emit("state:mutate", {
|
|
719
|
+
key,
|
|
720
|
+
value,
|
|
721
|
+
prevValue
|
|
722
|
+
}, { catch: true }).catch(() => null);
|
|
723
|
+
return this;
|
|
724
|
+
}
|
|
725
|
+
mut(target, mutator) {
|
|
726
|
+
const updated = mutator(this.get(target));
|
|
727
|
+
return this.set(target, updated);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Check if a key exists in the state
|
|
731
|
+
*/
|
|
732
|
+
has(key) {
|
|
733
|
+
return key in this.store;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Delete a key from the state (set to undefined)
|
|
737
|
+
*/
|
|
738
|
+
del(key) {
|
|
739
|
+
return this.set(key, void 0);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Push a value to an array in the state
|
|
743
|
+
*/
|
|
744
|
+
push(key, value) {
|
|
745
|
+
const current = this.get(key) ?? [];
|
|
746
|
+
if (Array.isArray(current)) this.set(key, [...current, value]);
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Clear all state
|
|
751
|
+
*/
|
|
752
|
+
clear() {
|
|
753
|
+
this.store = {};
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Get all keys that exist in the state
|
|
758
|
+
*/
|
|
759
|
+
keys() {
|
|
760
|
+
return Object.keys(this.store);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region src/core/Alepha.ts
|
|
766
|
+
/**
|
|
767
|
+
* Core container of the Alepha framework.
|
|
768
|
+
*
|
|
769
|
+
* It is responsible for managing the lifecycle of services,
|
|
770
|
+
* handling dependency injection,
|
|
771
|
+
* and providing a unified interface for the application.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```ts
|
|
775
|
+
* import { Alepha, run } from "alepha";
|
|
776
|
+
*
|
|
777
|
+
* class MyService {
|
|
778
|
+
* // business logic here
|
|
779
|
+
* }
|
|
780
|
+
*
|
|
781
|
+
* const alepha = Alepha.create({
|
|
782
|
+
* // state, env, and other properties
|
|
783
|
+
* })
|
|
784
|
+
*
|
|
785
|
+
* alepha.with(MyService);
|
|
786
|
+
*
|
|
787
|
+
* run(alepha); // trigger .start (and .stop) automatically
|
|
788
|
+
* ```
|
|
789
|
+
*
|
|
790
|
+
* ### Alepha Factory
|
|
791
|
+
*
|
|
792
|
+
* Alepha.create() is an enhanced version of new Alepha().
|
|
793
|
+
* - It merges `process.env` with the provided state.env when available.
|
|
794
|
+
* - It populates the test hooks for Vitest or Jest environments when available.
|
|
795
|
+
*
|
|
796
|
+
* new Alepha() is fine if you don't need these helpers.
|
|
797
|
+
*
|
|
798
|
+
* ### Platforms & Environments
|
|
799
|
+
*
|
|
800
|
+
* Alepha is designed to work in various environments:
|
|
801
|
+
* - **Browser**: Runs in the browser, using the global `window` object.
|
|
802
|
+
* - **Serverless**: Runs in serverless environments like Vercel or Vite.
|
|
803
|
+
* - **Test**: Runs in test environments like Jest or Vitest.
|
|
804
|
+
* - **Production**: Runs in production environments, typically with NODE_ENV set to "production".
|
|
805
|
+
* * You can check the current environment using the following methods:
|
|
806
|
+
*
|
|
807
|
+
* - `isBrowser()`: Returns true if the App is running in a browser environment.
|
|
808
|
+
* - `isServerless()`: Returns true if the App is running in a serverless environment.
|
|
809
|
+
* - `isTest()`: Returns true if the App is running in a test environment.
|
|
810
|
+
* - `isProduction()`: Returns true if the App is running in a production environment.
|
|
811
|
+
*
|
|
812
|
+
* ### State & Environment
|
|
813
|
+
*
|
|
814
|
+
* The state of the Alepha container is stored in the `store` property.
|
|
815
|
+
* Most important property is `store.env`, which contains the environment variables.
|
|
816
|
+
*
|
|
817
|
+
* ```ts
|
|
818
|
+
* const alepha = Alepha.create({ env: { MY_VAR: "value" } });
|
|
819
|
+
*
|
|
820
|
+
* // You can access the environment variables using alepha.env
|
|
821
|
+
* console.log(alepha.env.MY_VAR); // "value"
|
|
822
|
+
*
|
|
823
|
+
* // But you should use $env() primitive to get typed values from the environment.
|
|
824
|
+
* class App {
|
|
825
|
+
* env = $env(
|
|
826
|
+
* t.object({
|
|
827
|
+
* MY_VAR: t.text(),
|
|
828
|
+
* })
|
|
829
|
+
* );
|
|
830
|
+
* }
|
|
831
|
+
* ```
|
|
832
|
+
*
|
|
833
|
+
* ### Modules
|
|
834
|
+
*
|
|
835
|
+
* Modules are a way to group services together.
|
|
836
|
+
* You can register a module using the `$module` primitive.
|
|
837
|
+
*
|
|
838
|
+
* ```ts
|
|
839
|
+
* import { $module } from "alepha";
|
|
840
|
+
*
|
|
841
|
+
* class MyLib {}
|
|
842
|
+
*
|
|
843
|
+
* const myModule = $module({
|
|
844
|
+
* name: "my.project.module",
|
|
845
|
+
* services: [MyLib],
|
|
846
|
+
* });
|
|
847
|
+
* ```
|
|
848
|
+
*
|
|
849
|
+
* Do not use modules for small applications.
|
|
850
|
+
*
|
|
851
|
+
* ### Hooks
|
|
852
|
+
*
|
|
853
|
+
* Hooks are a way to run async functions from all registered providers/services.
|
|
854
|
+
* You can register a hook using the `$hook` primitive.
|
|
855
|
+
*
|
|
856
|
+
* ```ts
|
|
857
|
+
* import { $hook } from "alepha";
|
|
858
|
+
*
|
|
859
|
+
* class App {
|
|
860
|
+
* log = $logger();
|
|
861
|
+
* onCustomerHook = $hook({
|
|
862
|
+
* on: "my:custom:hook",
|
|
863
|
+
* handler: () => {
|
|
864
|
+
* this.log?.info("App is being configured");
|
|
865
|
+
* },
|
|
866
|
+
* });
|
|
867
|
+
* }
|
|
868
|
+
*
|
|
869
|
+
* Alepha.create()
|
|
870
|
+
* .with(App)
|
|
871
|
+
* .start()
|
|
872
|
+
* .then(alepha => alepha.events.emit("my:custom:hook"));
|
|
873
|
+
* ```
|
|
874
|
+
*
|
|
875
|
+
* Hooks are fully typed. You can create your own hooks by using module augmentation:
|
|
876
|
+
*
|
|
877
|
+
* ```ts
|
|
878
|
+
* declare module "alepha" {
|
|
879
|
+
* interface Hooks {
|
|
880
|
+
* "my:custom:hook": {
|
|
881
|
+
* arg1: string;
|
|
882
|
+
* }
|
|
883
|
+
* }
|
|
884
|
+
* }
|
|
885
|
+
* ```
|
|
886
|
+
*
|
|
887
|
+
* @module alepha
|
|
888
|
+
*/
|
|
889
|
+
var Alepha = class Alepha {
|
|
890
|
+
/**
|
|
891
|
+
* Creates a new instance of the Alepha container with some helpers:
|
|
892
|
+
*
|
|
893
|
+
* - merges `process.env` with the provided state.env when available.
|
|
894
|
+
* - populates the test hooks for Vitest or Jest environments when available.
|
|
895
|
+
*
|
|
896
|
+
* If you are not interested about these helpers, you can use the constructor directly.
|
|
897
|
+
*/
|
|
898
|
+
static create(state = {}) {
|
|
899
|
+
if (typeof process === "object" && typeof process.env === "object") state.env = {
|
|
900
|
+
...state.env,
|
|
901
|
+
...process.env
|
|
902
|
+
};
|
|
903
|
+
const alepha = new Alepha(state);
|
|
904
|
+
if (alepha.isTest()) {
|
|
905
|
+
const g = globalThis;
|
|
906
|
+
const beforeAll = state["alepha.test.beforeAll"] ?? g.beforeAll;
|
|
907
|
+
const afterAll = state["alepha.test.afterAll"] ?? g.afterAll;
|
|
908
|
+
const afterEach = state["alepha.test.afterEach"] ?? g.afterEach;
|
|
909
|
+
const onTestFinished = state["alepha.test.onTestFinished"] ?? g.onTestFinished;
|
|
910
|
+
beforeAll?.(() => alepha.start());
|
|
911
|
+
afterAll?.(() => alepha.stop());
|
|
912
|
+
try {
|
|
913
|
+
onTestFinished?.(() => alepha.stop());
|
|
914
|
+
} catch (_error) {}
|
|
915
|
+
alepha.store.set("alepha.test.beforeAll", beforeAll).set("alepha.test.afterAll", afterAll).set("alepha.test.afterEach", afterEach).set("alepha.test.onTestFinished", onTestFinished);
|
|
916
|
+
}
|
|
917
|
+
return alepha;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Flag indicating whether the App won't accept any further changes.
|
|
921
|
+
* Pass to true when #start() is called.
|
|
922
|
+
*/
|
|
923
|
+
locked = false;
|
|
924
|
+
/**
|
|
925
|
+
* True if the App has been configured.
|
|
926
|
+
*/
|
|
927
|
+
configured = false;
|
|
928
|
+
/**
|
|
929
|
+
* True if the App has started.
|
|
930
|
+
*/
|
|
931
|
+
started = false;
|
|
932
|
+
/**
|
|
933
|
+
* True if the App is ready.
|
|
934
|
+
*/
|
|
935
|
+
ready = false;
|
|
936
|
+
/**
|
|
937
|
+
* A promise that resolves when the App has started.
|
|
938
|
+
*/
|
|
939
|
+
starting;
|
|
940
|
+
/**
|
|
941
|
+
* Initial state of the container.
|
|
942
|
+
*
|
|
943
|
+
* > Used to initialize the StateManager.
|
|
944
|
+
*/
|
|
945
|
+
init;
|
|
946
|
+
/**
|
|
947
|
+
* During the instantiation process, we keep a list of pending instantiations.
|
|
948
|
+
* > It allows us to detect circular dependencies.
|
|
949
|
+
*/
|
|
950
|
+
pendingInstantiations = [];
|
|
951
|
+
/**
|
|
952
|
+
* Cache for environment variables.
|
|
953
|
+
* > It allows us to avoid parsing the same schema multiple times.
|
|
954
|
+
*/
|
|
955
|
+
cacheEnv = /* @__PURE__ */ new Map();
|
|
956
|
+
/**
|
|
957
|
+
* List of modules that are registered in the container.
|
|
958
|
+
*
|
|
959
|
+
* Modules are used to group services and provide a way to register them in the container.
|
|
960
|
+
*/
|
|
961
|
+
modules = [];
|
|
962
|
+
/**
|
|
963
|
+
* List of service substitutions.
|
|
964
|
+
*
|
|
965
|
+
* Services registered here will be replaced by the specified service when injected.
|
|
966
|
+
*/
|
|
967
|
+
substitutions = /* @__PURE__ */ new Map();
|
|
968
|
+
/**
|
|
969
|
+
* Registry of primitives.
|
|
970
|
+
*/
|
|
971
|
+
primitiveRegistry = /* @__PURE__ */ new Map();
|
|
972
|
+
/**
|
|
973
|
+
* List of all services + how they are provided.
|
|
974
|
+
*/
|
|
975
|
+
registry = /* @__PURE__ */ new Map();
|
|
976
|
+
/**
|
|
977
|
+
* Node.js feature that allows to store context across asynchronous calls.
|
|
978
|
+
*
|
|
979
|
+
* This is used for logging, tracing, and other context-related features.
|
|
980
|
+
*
|
|
981
|
+
* Mocked for browser environments.
|
|
982
|
+
*/
|
|
983
|
+
get context() {
|
|
984
|
+
return this.inject(AlsProvider);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Event manager to handle lifecycle events and custom events.
|
|
988
|
+
*/
|
|
989
|
+
get events() {
|
|
990
|
+
return this.inject(EventManager, { args: [() => this.log] });
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* State manager to store arbitrary values.
|
|
994
|
+
*/
|
|
995
|
+
get store() {
|
|
996
|
+
this.events;
|
|
997
|
+
return this.inject(StateManager, { args: [this.init] });
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Codec manager for encoding and decoding data with different formats.
|
|
1001
|
+
*
|
|
1002
|
+
* Supports multiple codec formats (JSON, Protobuf, etc.) with a unified interface.
|
|
1003
|
+
*/
|
|
1004
|
+
get codec() {
|
|
1005
|
+
return this.inject(CodecManager);
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Get logger instance.
|
|
1009
|
+
*/
|
|
1010
|
+
get log() {
|
|
1011
|
+
return this.store.get("alepha.logger");
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* The environment variables for the App.
|
|
1015
|
+
*/
|
|
1016
|
+
get env() {
|
|
1017
|
+
return this.store.get("env") ?? {};
|
|
1018
|
+
}
|
|
1019
|
+
constructor(init = {}) {
|
|
1020
|
+
this.init = init;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* True when start() is called.
|
|
1024
|
+
*
|
|
1025
|
+
* -> No more services can be added, it's over, bye!
|
|
1026
|
+
*/
|
|
1027
|
+
isLocked() {
|
|
1028
|
+
return this.locked;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Returns whether the App is configured.
|
|
1032
|
+
*
|
|
1033
|
+
* It means that Alepha#configure() has been called.
|
|
1034
|
+
*
|
|
1035
|
+
* > By default, configure() is called automatically when start() is called, but you can also call it manually.
|
|
1036
|
+
*/
|
|
1037
|
+
isConfigured() {
|
|
1038
|
+
return this.configured;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Returns whether the App has started.
|
|
1042
|
+
*
|
|
1043
|
+
* It means that #start() has been called but maybe not all services are ready.
|
|
1044
|
+
*/
|
|
1045
|
+
isStarted() {
|
|
1046
|
+
return this.started;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* True if the App is ready. It means that Alepha is started AND ready() hook has beed called.
|
|
1050
|
+
*/
|
|
1051
|
+
isReady() {
|
|
1052
|
+
return this.ready;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* True if the App is running in a Continuous Integration environment.
|
|
1056
|
+
*/
|
|
1057
|
+
isCI() {
|
|
1058
|
+
if (this.env.GITHUB_ACTIONS) return true;
|
|
1059
|
+
return !!this.env.CI;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* True if the App is running in a browser environment.
|
|
1063
|
+
*/
|
|
1064
|
+
isBrowser() {
|
|
1065
|
+
return typeof window !== "undefined";
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Returns whether the App is running in Vite dev mode.
|
|
1069
|
+
*/
|
|
1070
|
+
isViteDev() {
|
|
1071
|
+
if (this.isBrowser()) return false;
|
|
1072
|
+
return !!this.env.VITE_ALEPHA_DEV;
|
|
1073
|
+
}
|
|
1074
|
+
isBun() {
|
|
1075
|
+
return "Bun" in globalThis;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Returns whether the App is running in a serverless environment.
|
|
1079
|
+
*/
|
|
1080
|
+
isServerless() {
|
|
1081
|
+
if (this.isBrowser()) return false;
|
|
1082
|
+
if (this.env.VERCEL_REGION) return true;
|
|
1083
|
+
if (this.env.ALEPHA_SERVERLESS) return true;
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Returns whether the App is in test mode. (Running in a test environment)
|
|
1088
|
+
*
|
|
1089
|
+
* > This is automatically set when running tests with Jest or Vitest.
|
|
1090
|
+
*/
|
|
1091
|
+
isTest() {
|
|
1092
|
+
return this.env.NODE_ENV === "test";
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Returns whether the App is in production mode. (Running in a production environment)
|
|
1096
|
+
*
|
|
1097
|
+
* > This is automatically set by Vite or Vercel. However, you have to set it manually when running Docker apps.
|
|
1098
|
+
*/
|
|
1099
|
+
isProduction() {
|
|
1100
|
+
return this.env.NODE_ENV === "production";
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Starts the App.
|
|
1104
|
+
*
|
|
1105
|
+
* - Lock any further changes to the container.
|
|
1106
|
+
* - Run "configure" hook for all services. Primitives will be processed.
|
|
1107
|
+
* - Run "start" hook for all services. Providers will connect/listen/...
|
|
1108
|
+
* - Run "ready" hook for all services. This is the point where the App is ready to serve requests.
|
|
1109
|
+
*
|
|
1110
|
+
* @return A promise that resolves when the App has started.
|
|
1111
|
+
*/
|
|
1112
|
+
async start() {
|
|
1113
|
+
if (this.ready) {
|
|
1114
|
+
this.log?.debug("App is already started, skipping...");
|
|
1115
|
+
return this;
|
|
1116
|
+
}
|
|
1117
|
+
if (this.starting) {
|
|
1118
|
+
this.log?.warn("App is already starting, waiting for it to finish...");
|
|
1119
|
+
return this.starting.promise;
|
|
1120
|
+
}
|
|
1121
|
+
this.codec;
|
|
1122
|
+
this.starting = Promise.withResolvers();
|
|
1123
|
+
const now = Date.now();
|
|
1124
|
+
this.log?.info("Starting App...");
|
|
1125
|
+
for (const [key] of this.substitutions.entries()) this.inject(key);
|
|
1126
|
+
const target = this.store.get("alepha.target");
|
|
1127
|
+
if (target) {
|
|
1128
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
1129
|
+
this.primitiveRegistry = /* @__PURE__ */ new Map();
|
|
1130
|
+
this.with(target);
|
|
1131
|
+
}
|
|
1132
|
+
this.locked = true;
|
|
1133
|
+
await this.events.emit("configure", this, { log: true });
|
|
1134
|
+
this.configured = true;
|
|
1135
|
+
await this.events.emit("start", this, { log: true });
|
|
1136
|
+
this.started = true;
|
|
1137
|
+
await this.events.emit("ready", this, { log: true });
|
|
1138
|
+
this.log?.info(`App is now ready [${Date.now() - now}ms]`);
|
|
1139
|
+
this.ready = true;
|
|
1140
|
+
this.starting.resolve(this);
|
|
1141
|
+
this.starting = void 0;
|
|
1142
|
+
return this;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Stops the App.
|
|
1146
|
+
*
|
|
1147
|
+
* - Run "stop" hook for all services.
|
|
1148
|
+
*
|
|
1149
|
+
* Stop will NOT reset the container.
|
|
1150
|
+
* Stop will NOT unlock the container.
|
|
1151
|
+
*
|
|
1152
|
+
* > Stop is used to gracefully shut down the application, nothing more. There is no "restart".
|
|
1153
|
+
*
|
|
1154
|
+
* @return A promise that resolves when the App has stopped.
|
|
1155
|
+
*/
|
|
1156
|
+
async stop() {
|
|
1157
|
+
if (!this.started) return;
|
|
1158
|
+
this.log?.info("Stopping App...");
|
|
1159
|
+
await this.events.emit("stop", this, {
|
|
1160
|
+
reverse: true,
|
|
1161
|
+
log: true
|
|
1162
|
+
});
|
|
1163
|
+
this.log?.info("App is now off");
|
|
1164
|
+
this.started = false;
|
|
1165
|
+
this.ready = false;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Check if entry is registered in the container.
|
|
1169
|
+
*/
|
|
1170
|
+
has(entry, opts = {}) {
|
|
1171
|
+
if (entry === Alepha) return true;
|
|
1172
|
+
const { inStack = true, inRegistry = true, inSubstitutions = true, registry = this.registry } = opts;
|
|
1173
|
+
const { provide } = typeof entry === "object" && "provide" in entry ? entry : { provide: entry };
|
|
1174
|
+
if (inSubstitutions) {
|
|
1175
|
+
if (this.substitutions.get(provide)) return true;
|
|
1176
|
+
}
|
|
1177
|
+
if (inRegistry) {
|
|
1178
|
+
if (registry.get(provide)) return true;
|
|
1179
|
+
}
|
|
1180
|
+
if (inStack) {
|
|
1181
|
+
const substitute = this.substitutions.get(provide)?.use;
|
|
1182
|
+
if (substitute && this.pendingInstantiations.includes(substitute)) return true;
|
|
1183
|
+
return this.pendingInstantiations.includes(provide);
|
|
1184
|
+
}
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Registers the specified service in the container.
|
|
1189
|
+
*
|
|
1190
|
+
* - If the service is ALREADY registered, the method does nothing.
|
|
1191
|
+
* - If the service is NOT registered, a new instance is created and registered.
|
|
1192
|
+
*
|
|
1193
|
+
* Method is chainable, so you can register multiple services in a single call.
|
|
1194
|
+
*
|
|
1195
|
+
* > ServiceEntry allows to provide a service **substitution** feature.
|
|
1196
|
+
*
|
|
1197
|
+
* @example
|
|
1198
|
+
* ```ts
|
|
1199
|
+
* class A { value = "a"; }
|
|
1200
|
+
* class B { value = "b"; }
|
|
1201
|
+
* class M { a = $inject(A); }
|
|
1202
|
+
*
|
|
1203
|
+
* Alepha.create().with({ provide: A, use: B }).get(M).a.value; // "b"
|
|
1204
|
+
* ```
|
|
1205
|
+
*
|
|
1206
|
+
* > **Substitution** is an advanced feature that allows you to replace a service with another service.
|
|
1207
|
+
* > It's useful for testing or for providing different implementations of a service.
|
|
1208
|
+
* > If you are interested in configuring a service, use Alepha#configure() instead.
|
|
1209
|
+
*
|
|
1210
|
+
* @param serviceEntry - The service to register in the container.
|
|
1211
|
+
* @return Current instance of Alepha.
|
|
1212
|
+
*/
|
|
1213
|
+
with(serviceEntry) {
|
|
1214
|
+
const entry = "default" in serviceEntry ? serviceEntry.default : serviceEntry;
|
|
1215
|
+
if (this.has(entry, {
|
|
1216
|
+
inSubstitutions: false,
|
|
1217
|
+
inRegistry: false
|
|
1218
|
+
})) return this;
|
|
1219
|
+
if (typeof entry === "object") {
|
|
1220
|
+
if (entry.provide === entry.use) {
|
|
1221
|
+
this.inject(entry.provide);
|
|
1222
|
+
return this;
|
|
1223
|
+
}
|
|
1224
|
+
if (!this.substitutions.has(entry.provide) && !this.has(entry.provide)) {
|
|
1225
|
+
if (this.started) throw new ContainerLockedError();
|
|
1226
|
+
if (MODULE in entry.provide && typeof entry.provide[MODULE] === "function") entry.use[MODULE] ??= entry.provide[MODULE];
|
|
1227
|
+
this.substitutions.set(entry.provide, { use: entry.use });
|
|
1228
|
+
} else if (!entry.optional) throw new TooLateSubstitutionError(entry.provide.name, entry.use.name);
|
|
1229
|
+
return this;
|
|
1230
|
+
}
|
|
1231
|
+
this.inject(entry);
|
|
1232
|
+
return this;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get an instance of the specified service from the container.
|
|
1236
|
+
*
|
|
1237
|
+
* @see {@link InjectOptions} for the available options.
|
|
1238
|
+
*/
|
|
1239
|
+
inject(service, opts = {}) {
|
|
1240
|
+
const lifetime = opts.lifetime ?? "singleton";
|
|
1241
|
+
const parent = opts.parent !== void 0 ? opts.parent : __alephaRef?.parent ?? Alepha;
|
|
1242
|
+
const transient = lifetime === "transient";
|
|
1243
|
+
const registry = lifetime === "scoped" ? this.context.get("registry") ?? this.registry : this.registry;
|
|
1244
|
+
if (service === Alepha) return this;
|
|
1245
|
+
if (typeof service === "string") {
|
|
1246
|
+
for (const [key, value] of registry.entries()) if (key.name === service) return value.instance;
|
|
1247
|
+
throw new AlephaError(`Service not found: ${service}`);
|
|
1248
|
+
}
|
|
1249
|
+
const substitute = this.substitutions.get(service);
|
|
1250
|
+
if (substitute) return this.inject(substitute.use, {
|
|
1251
|
+
parent,
|
|
1252
|
+
lifetime
|
|
1253
|
+
});
|
|
1254
|
+
const index = this.pendingInstantiations.indexOf(service);
|
|
1255
|
+
if (index !== -1 && !transient) throw new CircularDependencyError(service.name, this.pendingInstantiations.slice(0, index).map((it) => it.name));
|
|
1256
|
+
if (!transient) {
|
|
1257
|
+
const match = registry.get(service);
|
|
1258
|
+
if (match) {
|
|
1259
|
+
if (!match.parents.includes(parent) && parent !== service) match.parents.push(parent);
|
|
1260
|
+
return match.instance;
|
|
1261
|
+
}
|
|
1262
|
+
if (this.started) throw new ContainerLockedError(`Container is locked. No more services can be added. ${parent?.name} -> ${service.name}`);
|
|
1263
|
+
}
|
|
1264
|
+
const module = service[MODULE];
|
|
1265
|
+
if (module && typeof module === "function") this.with(module);
|
|
1266
|
+
if (this.has(service, { registry }) && !transient) return this.inject(service, {
|
|
1267
|
+
parent,
|
|
1268
|
+
lifetime
|
|
1269
|
+
});
|
|
1270
|
+
const instance = this.new(service, opts.args);
|
|
1271
|
+
const definition = {
|
|
1272
|
+
parents: [parent],
|
|
1273
|
+
instance
|
|
1274
|
+
};
|
|
1275
|
+
if (!transient) registry.set(service, definition);
|
|
1276
|
+
if (instance instanceof Module) {
|
|
1277
|
+
this.modules.push(instance);
|
|
1278
|
+
const parent$1 = __alephaRef.parent;
|
|
1279
|
+
__alephaRef.parent = instance.constructor;
|
|
1280
|
+
instance.register(this);
|
|
1281
|
+
__alephaRef.parent = parent$1;
|
|
1282
|
+
}
|
|
1283
|
+
return instance;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Applies environment variables to the provided schema and state object.
|
|
1287
|
+
*
|
|
1288
|
+
* It replaces also all templated $ENV inside string values.
|
|
1289
|
+
*
|
|
1290
|
+
* @param schema - The schema object to apply environment variables to.
|
|
1291
|
+
* @return The schema object with environment variables applied.
|
|
1292
|
+
*/
|
|
1293
|
+
parseEnv(schema) {
|
|
1294
|
+
if (this.cacheEnv.has(schema)) return this.cacheEnv.get(schema);
|
|
1295
|
+
const config = this.codec.validate(schema, this.env);
|
|
1296
|
+
for (const key in config) if (typeof config[key] === "string") for (const env in config) config[key] = config[key].replace(new RegExp(`\\$${env}`, "gim"), config[env]);
|
|
1297
|
+
this.cacheEnv.set(schema, config);
|
|
1298
|
+
return config;
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Dump the current dependency graph of the App.
|
|
1302
|
+
*
|
|
1303
|
+
* This method returns a record where the keys are the names of the services.
|
|
1304
|
+
*/
|
|
1305
|
+
graph() {
|
|
1306
|
+
for (const [key] of this.substitutions.entries()) if (!this.has(key)) this.inject(key);
|
|
1307
|
+
const graph = {};
|
|
1308
|
+
for (const [provide, { parents }] of this.registry.entries()) {
|
|
1309
|
+
graph[provide.name] = { from: parents.filter((it) => !!it).map((it) => it.name) };
|
|
1310
|
+
const aliases = this.substitutions.entries().filter((it) => it[1].use === provide).map((it) => it[0].name).toArray();
|
|
1311
|
+
if (aliases.length) graph[provide.name].as = aliases;
|
|
1312
|
+
const module = Module.of(provide);
|
|
1313
|
+
if (module) graph[provide.name].module = module.name;
|
|
1314
|
+
}
|
|
1315
|
+
return graph;
|
|
1316
|
+
}
|
|
1317
|
+
services(base) {
|
|
1318
|
+
const list = [];
|
|
1319
|
+
for (const [key, value] of this.registry.entries()) if (value.instance instanceof base) list.push(value.instance);
|
|
1320
|
+
return list;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Get all primitives of the specified type.
|
|
1324
|
+
*/
|
|
1325
|
+
primitives(factory) {
|
|
1326
|
+
if (typeof factory === "string") {
|
|
1327
|
+
const key1 = factory.toLowerCase().replace("$", "");
|
|
1328
|
+
const key2 = `${key1}primitive`;
|
|
1329
|
+
for (const [key, value] of this.primitiveRegistry.entries()) {
|
|
1330
|
+
const name = key.name.toLowerCase();
|
|
1331
|
+
if (name === key1 || name === key2) return value;
|
|
1332
|
+
}
|
|
1333
|
+
return [];
|
|
1334
|
+
}
|
|
1335
|
+
return this.primitiveRegistry.get(factory[KIND]) ?? [];
|
|
1336
|
+
}
|
|
1337
|
+
new(service, args = []) {
|
|
1338
|
+
this.pendingInstantiations.push(service);
|
|
1339
|
+
__alephaRef.alepha = this;
|
|
1340
|
+
__alephaRef.service = service;
|
|
1341
|
+
const instance = isClass(service) ? new service(...args) : service(...args) ?? {};
|
|
1342
|
+
const obj = instance;
|
|
1343
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1344
|
+
if (value instanceof Primitive) this.processPrimitive(value, key);
|
|
1345
|
+
if (typeof value === "object" && value !== null && typeof value[OPTIONS] === "object" && "getter" in value[OPTIONS]) {
|
|
1346
|
+
const getter = value[OPTIONS].getter;
|
|
1347
|
+
Object.defineProperty(obj, key, { get: () => this.store.get(getter) });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
this.pendingInstantiations.pop();
|
|
1351
|
+
if (this.pendingInstantiations.length === 0) __alephaRef.alepha = void 0;
|
|
1352
|
+
__alephaRef.service = this.pendingInstantiations[this.pendingInstantiations.length - 1];
|
|
1353
|
+
return instance;
|
|
1354
|
+
}
|
|
1355
|
+
processPrimitive(value, propertyKey = "") {
|
|
1356
|
+
value.config.propertyKey = propertyKey;
|
|
1357
|
+
value.onInit();
|
|
1358
|
+
const kind = value.constructor;
|
|
1359
|
+
const list = this.primitiveRegistry.get(kind) ?? [];
|
|
1360
|
+
this.primitiveRegistry.set(kind, [...list, value]);
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region src/core/errors/AppNotStartedError.ts
|
|
1366
|
+
var AppNotStartedError = class extends AlephaError {
|
|
1367
|
+
name = "AppNotStartedError";
|
|
1368
|
+
constructor() {
|
|
1369
|
+
super("App not started. Please start the app before.");
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
//#endregion
|
|
1374
|
+
//#region src/core/helpers/createPagination.ts
|
|
1375
|
+
/**
|
|
1376
|
+
* Create a pagination object from an array of entities.
|
|
1377
|
+
*
|
|
1378
|
+
* This is a pure function that works with any data source (databases, APIs, caches, arrays, etc.).
|
|
1379
|
+
* It handles the core pagination logic including:
|
|
1380
|
+
* - Slicing the content to the requested page size
|
|
1381
|
+
* - Calculating pagination metadata (offset, page number, etc.)
|
|
1382
|
+
* - Determining navigation state (isFirst, isLast)
|
|
1383
|
+
* - Including sort metadata when provided
|
|
1384
|
+
*
|
|
1385
|
+
* @param entities - The entities to paginate (should include one extra item to detect if there's a next page)
|
|
1386
|
+
* @param limit - The limit of the pagination (page size)
|
|
1387
|
+
* @param offset - The offset of the pagination (starting position)
|
|
1388
|
+
* @param sort - Optional sort metadata to include in response
|
|
1389
|
+
* @returns A complete Page object with content and metadata
|
|
1390
|
+
*
|
|
1391
|
+
* @example Basic pagination
|
|
1392
|
+
* ```ts
|
|
1393
|
+
* const users = await fetchUsers({ limit: 11, offset: 0 }); // Fetch limit + 1
|
|
1394
|
+
* const page = createPagination(users, 10, 0);
|
|
1395
|
+
* // page.content has max 10 items
|
|
1396
|
+
* // page.page.isLast tells us if there are more pages
|
|
1397
|
+
* ```
|
|
1398
|
+
*
|
|
1399
|
+
* @example With sorting
|
|
1400
|
+
* ```ts
|
|
1401
|
+
* const page = createPagination(
|
|
1402
|
+
* entities,
|
|
1403
|
+
* 10,
|
|
1404
|
+
* 0,
|
|
1405
|
+
* [{ column: "name", direction: "asc" }]
|
|
1406
|
+
* );
|
|
1407
|
+
* ```
|
|
1408
|
+
*
|
|
1409
|
+
* @example In a custom service
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* class MyService {
|
|
1412
|
+
* async listItems(page: number, size: number) {
|
|
1413
|
+
* const items = await this.fetchItems({ limit: size + 1, offset: page * size });
|
|
1414
|
+
* return createPagination(items, size, page * size);
|
|
1415
|
+
* }
|
|
1416
|
+
* }
|
|
1417
|
+
* ```
|
|
1418
|
+
*/
|
|
1419
|
+
function createPagination(entities, limit = 10, offset = 0, sort) {
|
|
1420
|
+
const content = entities.slice(0, limit);
|
|
1421
|
+
const hasNext = entities.length === limit + 1;
|
|
1422
|
+
const pageNumber = Math.floor(offset / limit);
|
|
1423
|
+
return {
|
|
1424
|
+
content,
|
|
1425
|
+
page: {
|
|
1426
|
+
number: pageNumber,
|
|
1427
|
+
size: limit,
|
|
1428
|
+
offset,
|
|
1429
|
+
numberOfElements: content.length,
|
|
1430
|
+
isEmpty: content.length === 0,
|
|
1431
|
+
isFirst: pageNumber === 0,
|
|
1432
|
+
isLast: !hasNext,
|
|
1433
|
+
...sort && sort.length > 0 ? { sort: {
|
|
1434
|
+
sorted: true,
|
|
1435
|
+
fields: sort.map((s) => ({
|
|
1436
|
+
field: s.column,
|
|
1437
|
+
direction: s.direction
|
|
1438
|
+
}))
|
|
1439
|
+
} } : {}
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
//#endregion
|
|
1445
|
+
//#region src/core/helpers/FileLike.ts
|
|
1446
|
+
const isTypeFile = (value) => {
|
|
1447
|
+
return value && typeof value === "object" && "format" in value && value.format === "binary";
|
|
1448
|
+
};
|
|
1449
|
+
const isFileLike = (value) => {
|
|
1450
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && typeof value.name === "string" && typeof value.type === "string" && typeof value.size === "number" && typeof value.stream.bind(value) === "function";
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/core/providers/TypeProvider.ts
|
|
1455
|
+
const isUUID = TypeBoxFormat.IsUuid;
|
|
1456
|
+
const isEmail = TypeBoxFormat.IsEmail;
|
|
1457
|
+
const isURL = TypeBoxFormat.IsUrl;
|
|
1458
|
+
const isDateTime = TypeBoxFormat.IsDateTime;
|
|
1459
|
+
const isDate = TypeBoxFormat.IsDate;
|
|
1460
|
+
const isTime = TypeBoxFormat.IsTime;
|
|
1461
|
+
const isDuration = TypeBoxFormat.IsDuration;
|
|
1462
|
+
var TypeGuard = class {
|
|
1463
|
+
isBigInt = (value) => TypeBox.IsString(value) && "format" in value && value.format === "bigint";
|
|
1464
|
+
isUUID = (value) => TypeBox.IsString(value) && "format" in value && value.format === "uuid";
|
|
1465
|
+
isObject = TypeBox.IsObject;
|
|
1466
|
+
isNumber = TypeBox.IsNumber;
|
|
1467
|
+
isString = TypeBox.IsString;
|
|
1468
|
+
isBoolean = TypeBox.IsBoolean;
|
|
1469
|
+
isAny = TypeBox.IsAny;
|
|
1470
|
+
isArray = TypeBox.IsArray;
|
|
1471
|
+
isOptional = TypeBox.IsOptional;
|
|
1472
|
+
isUnion = TypeBox.IsUnion;
|
|
1473
|
+
isInteger = TypeBox.IsInteger;
|
|
1474
|
+
isNull = TypeBox.IsNull;
|
|
1475
|
+
isUndefined = TypeBox.IsUndefined;
|
|
1476
|
+
isUnsafe = TypeBox.IsUnsafe;
|
|
1477
|
+
isRecord = TypeBox.IsRecord;
|
|
1478
|
+
isTuple = TypeBox.IsTuple;
|
|
1479
|
+
isVoid = TypeBox.IsVoid;
|
|
1480
|
+
isLiteral = TypeBox.IsLiteral;
|
|
1481
|
+
isSchema = TypeBox.IsSchema;
|
|
1482
|
+
isFile = isTypeFile;
|
|
1483
|
+
isDateTime = (schema) => {
|
|
1484
|
+
return t.schema.isString(schema) && schema.format === "date-time";
|
|
1485
|
+
};
|
|
1486
|
+
isDate = (schema) => {
|
|
1487
|
+
return t.schema.isString(schema) && schema.format === "date";
|
|
1488
|
+
};
|
|
1489
|
+
isTime = (schema) => {
|
|
1490
|
+
return t.schema.isString(schema) && schema.format === "time";
|
|
1491
|
+
};
|
|
1492
|
+
isDuration = (schema) => {
|
|
1493
|
+
return t.schema.isString(schema) && schema.format === "duration";
|
|
1494
|
+
};
|
|
1495
|
+
};
|
|
1496
|
+
var TypeProvider = class TypeProvider {
|
|
1497
|
+
static format = TypeBoxFormat;
|
|
1498
|
+
static {
|
|
1499
|
+
TypeBoxFormat.Set("bigint", (value) => TypeProvider.isValidBigInt(value));
|
|
1500
|
+
}
|
|
1501
|
+
static translateError(error, locale) {
|
|
1502
|
+
if (!locale) return error.cause.message;
|
|
1503
|
+
for (const [key, value] of Object.entries(Locale)) {
|
|
1504
|
+
if (key === "Set" || key === "Get" || key === "Reset") continue;
|
|
1505
|
+
if (key === locale || key.startsWith(`${locale}_`)) return value(error.cause);
|
|
1506
|
+
}
|
|
1507
|
+
return error.cause.message;
|
|
1508
|
+
}
|
|
1509
|
+
static setLocale(locale) {
|
|
1510
|
+
for (const [key, value] of Object.entries(Locale)) {
|
|
1511
|
+
if (key === "Set" || key === "Get" || key === "Reset") continue;
|
|
1512
|
+
if (key === locale || key.startsWith(`${locale}_`)) {
|
|
1513
|
+
Locale.Set(value);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
throw new AlephaError(`Locale not found: ${locale}`);
|
|
1518
|
+
}
|
|
1519
|
+
static isValidBigInt(value) {
|
|
1520
|
+
if (typeof value === "number") return Number.isInteger(value);
|
|
1521
|
+
if (!value.trim()) return false;
|
|
1522
|
+
if (!/^-?\d+$/.test(value)) return false;
|
|
1523
|
+
try {
|
|
1524
|
+
BigInt(value);
|
|
1525
|
+
return true;
|
|
1526
|
+
} catch {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Default maximum length for strings.
|
|
1532
|
+
*
|
|
1533
|
+
* It can be set to a larger value:
|
|
1534
|
+
* ```ts
|
|
1535
|
+
* TypeProvider.DEFAULT_STRING_MAX_LENGTH = 1000000;
|
|
1536
|
+
* TypeProvider.DEFAULT_STRING_MAX_LENGTH = undefined; // no limit (not recommended)
|
|
1537
|
+
* ```
|
|
1538
|
+
*/
|
|
1539
|
+
static DEFAULT_STRING_MAX_LENGTH = 255;
|
|
1540
|
+
/**
|
|
1541
|
+
* Maximum length for short strings, such as names or titles.
|
|
1542
|
+
*/
|
|
1543
|
+
static DEFAULT_SHORT_STRING_MAX_LENGTH = 64;
|
|
1544
|
+
/**
|
|
1545
|
+
* Maximum length for long strings, such as descriptions or comments.
|
|
1546
|
+
* It can be overridden in the string options.
|
|
1547
|
+
*
|
|
1548
|
+
* It can be set to a larger value:
|
|
1549
|
+
* ```ts
|
|
1550
|
+
* TypeProvider.DEFAULT_LONG_STRING_MAX_LENGTH = 2048;
|
|
1551
|
+
* ```
|
|
1552
|
+
*/
|
|
1553
|
+
static DEFAULT_LONG_STRING_MAX_LENGTH = 1024;
|
|
1554
|
+
/**
|
|
1555
|
+
* Maximum length for rich strings, such as HTML or Markdown.
|
|
1556
|
+
* This is a large value to accommodate rich text content.
|
|
1557
|
+
* > It's also the maximum length of PG's TEXT type.
|
|
1558
|
+
*
|
|
1559
|
+
* It can be overridden in the string options.
|
|
1560
|
+
*
|
|
1561
|
+
* It can be set to a larger value:
|
|
1562
|
+
* ```ts
|
|
1563
|
+
* TypeProvider.DEFAULT_RICH_STRING_MAX_LENGTH = 1000000;
|
|
1564
|
+
* ```
|
|
1565
|
+
*/
|
|
1566
|
+
static DEFAULT_RICH_STRING_MAX_LENGTH = 65535;
|
|
1567
|
+
/**
|
|
1568
|
+
* Maximum number of items in an array.
|
|
1569
|
+
* This is a default value to prevent excessive memory usage.
|
|
1570
|
+
* It can be overridden in the array options.
|
|
1571
|
+
*/
|
|
1572
|
+
static DEFAULT_ARRAY_MAX_ITEMS = 1e3;
|
|
1573
|
+
raw = Type;
|
|
1574
|
+
any = Type.Any;
|
|
1575
|
+
void = Type.Void;
|
|
1576
|
+
undefined = Type.Undefined;
|
|
1577
|
+
record = Type.Record;
|
|
1578
|
+
union = Type.Union;
|
|
1579
|
+
tuple = Type.Tuple;
|
|
1580
|
+
null = Type.Null;
|
|
1581
|
+
const = Type.Literal;
|
|
1582
|
+
options = Type.Options;
|
|
1583
|
+
/**
|
|
1584
|
+
* Type guards to check the type of schema.
|
|
1585
|
+
* This is not a runtime type check, but a compile-time type guard.
|
|
1586
|
+
*
|
|
1587
|
+
* @example
|
|
1588
|
+
* ```ts
|
|
1589
|
+
* if (t.schema.isString(schema)) {
|
|
1590
|
+
* // schema is TString
|
|
1591
|
+
* }
|
|
1592
|
+
* ```
|
|
1593
|
+
*/
|
|
1594
|
+
schema = new TypeGuard();
|
|
1595
|
+
extend(schema, properties, options) {
|
|
1596
|
+
return Type.Interface(Array.isArray(schema) ? schema : [schema], properties, {
|
|
1597
|
+
additionalProperties: false,
|
|
1598
|
+
...options
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
pick(schema, keys, options) {
|
|
1602
|
+
return Type.Pick(schema, keys, {
|
|
1603
|
+
additionalProperties: false,
|
|
1604
|
+
...options
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
omit(schema, keys, options) {
|
|
1608
|
+
return Type.Omit(schema, keys, {
|
|
1609
|
+
additionalProperties: false,
|
|
1610
|
+
...options
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
partial(schema, options) {
|
|
1614
|
+
return Type.Partial(schema, {
|
|
1615
|
+
additionalProperties: false,
|
|
1616
|
+
...options
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Create a schema for an object.
|
|
1621
|
+
* By default, additional properties are not allowed.
|
|
1622
|
+
*
|
|
1623
|
+
* @example
|
|
1624
|
+
* ```ts
|
|
1625
|
+
* const userSchema = t.object({
|
|
1626
|
+
* id: t.integer(),
|
|
1627
|
+
* name: t.string(),
|
|
1628
|
+
* });
|
|
1629
|
+
* ```
|
|
1630
|
+
*/
|
|
1631
|
+
object(properties, options) {
|
|
1632
|
+
return Type.Object(properties, {
|
|
1633
|
+
additionalProperties: false,
|
|
1634
|
+
...options
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Create a schema for an array.
|
|
1639
|
+
* By default, the maximum number of items is limited to prevent excessive memory usage.
|
|
1640
|
+
*
|
|
1641
|
+
* @see TypeProvider.DEFAULT_ARRAY_MAX_ITEMS
|
|
1642
|
+
*/
|
|
1643
|
+
array(schema, options) {
|
|
1644
|
+
return Type.Array(schema, {
|
|
1645
|
+
maxItems: TypeProvider.DEFAULT_ARRAY_MAX_ITEMS,
|
|
1646
|
+
...options
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Create a schema for a string.
|
|
1651
|
+
* For db or input fields, consider using `t.text()` instead, which has length limits.
|
|
1652
|
+
*
|
|
1653
|
+
* If you need a string with specific format (e.g. email, uuid), consider using the corresponding method (e.g. `t.email()`, `t.uuid()`).
|
|
1654
|
+
*/
|
|
1655
|
+
string(options = {}) {
|
|
1656
|
+
return Type.String({ ...options });
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Create a schema for a string with length limits.
|
|
1660
|
+
* For internal strings without length limits, consider using `t.string()` instead.
|
|
1661
|
+
*
|
|
1662
|
+
* Default size is "regular", which has a max length of 255 characters.
|
|
1663
|
+
*/
|
|
1664
|
+
text(options = {}) {
|
|
1665
|
+
const { size, ...rest } = options;
|
|
1666
|
+
const maxLength = size === "short" ? TypeProvider.DEFAULT_SHORT_STRING_MAX_LENGTH : size === "long" ? TypeProvider.DEFAULT_LONG_STRING_MAX_LENGTH : size === "rich" ? TypeProvider.DEFAULT_RICH_STRING_MAX_LENGTH : TypeProvider.DEFAULT_STRING_MAX_LENGTH;
|
|
1667
|
+
return Type.String({
|
|
1668
|
+
maxLength,
|
|
1669
|
+
"~options": {
|
|
1670
|
+
trim: options.trim ?? true,
|
|
1671
|
+
lowercase: options.lowercase ?? false
|
|
1672
|
+
},
|
|
1673
|
+
...rest
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Create a schema for a JSON object.
|
|
1678
|
+
* This is a record with string keys and any values.
|
|
1679
|
+
*/
|
|
1680
|
+
json(options) {
|
|
1681
|
+
return t.record(t.text(), t.any(), options);
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Create a schema for a boolean.
|
|
1685
|
+
*/
|
|
1686
|
+
boolean(options) {
|
|
1687
|
+
return Type.Boolean({ ...options });
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Create a schema for a number.
|
|
1691
|
+
*/
|
|
1692
|
+
number(options) {
|
|
1693
|
+
return Type.Number({ ...options });
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Create a schema for an integer.
|
|
1697
|
+
*/
|
|
1698
|
+
integer(options) {
|
|
1699
|
+
return Type.Integer({ ...options });
|
|
1700
|
+
}
|
|
1701
|
+
int32(options) {
|
|
1702
|
+
return Type.Integer({
|
|
1703
|
+
minimum: -2147483647,
|
|
1704
|
+
maximum: 2147483647,
|
|
1705
|
+
...options
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Mimic a signed 64-bit integer.
|
|
1710
|
+
*
|
|
1711
|
+
* This is NOT a true int64, as JavaScript cannot represent all int64 values.
|
|
1712
|
+
* It is a number that is an integer and between -9007199254740991 and 9007199254740991.
|
|
1713
|
+
* Use `t.bigint()` for true int64 values represented as strings.
|
|
1714
|
+
*/
|
|
1715
|
+
int64(options) {
|
|
1716
|
+
return Type.Number({
|
|
1717
|
+
format: "int64",
|
|
1718
|
+
multipleOf: 1,
|
|
1719
|
+
minimum: -9007199254740991,
|
|
1720
|
+
maximum: 9007199254740991,
|
|
1721
|
+
...options
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Make a schema optional.
|
|
1726
|
+
*/
|
|
1727
|
+
optional(schema) {
|
|
1728
|
+
return Type.Optional(schema);
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Make a schema nullable.
|
|
1732
|
+
*/
|
|
1733
|
+
nullable(schema, options) {
|
|
1734
|
+
return Type.Union([Type.Null(), schema], options);
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Create a schema that maps all properties of an object schema to nullable.
|
|
1738
|
+
*/
|
|
1739
|
+
nullify = (schema, options) => Type.Mapped(Type.Identifier("K"), Type.KeyOf(schema), Type.Ref("K"), Type.Union([Type.Index(schema, Type.Ref("K")), Type.Null()]), options);
|
|
1740
|
+
/**
|
|
1741
|
+
* Create a schema for a string enum.
|
|
1742
|
+
*/
|
|
1743
|
+
enum(values, options) {
|
|
1744
|
+
return Type.Unsafe(t.text({
|
|
1745
|
+
enum: values,
|
|
1746
|
+
pattern: values.map((v) => `^${v}$`).join("|"),
|
|
1747
|
+
...options
|
|
1748
|
+
}));
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Create a schema for a bigint represented as a string.
|
|
1752
|
+
* This is a string that validates bigint format (e.g. "123456789").
|
|
1753
|
+
*/
|
|
1754
|
+
bigint(options) {
|
|
1755
|
+
return t.string({
|
|
1756
|
+
...options,
|
|
1757
|
+
format: "bigint"
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Create a schema for a URL represented as a string.
|
|
1762
|
+
*/
|
|
1763
|
+
url(options) {
|
|
1764
|
+
return this.string({
|
|
1765
|
+
...options,
|
|
1766
|
+
format: "url"
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Create a schema for binary data represented as a base64 string.
|
|
1771
|
+
*/
|
|
1772
|
+
binary(options) {
|
|
1773
|
+
return this.string({
|
|
1774
|
+
...options,
|
|
1775
|
+
format: "binary"
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Create a schema for uuid.
|
|
1780
|
+
*/
|
|
1781
|
+
uuid(options) {
|
|
1782
|
+
return this.string({
|
|
1783
|
+
...options,
|
|
1784
|
+
format: "uuid"
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Create a schema for a file-like object.
|
|
1789
|
+
*
|
|
1790
|
+
* File like mimics the File API in browsers, but is adapted to work in Node.js as well.
|
|
1791
|
+
*
|
|
1792
|
+
* Implementation of file-like objects is handled by "alepha/file" package.
|
|
1793
|
+
*/
|
|
1794
|
+
file(options) {
|
|
1795
|
+
return Type.Unsafe(Type.Any({
|
|
1796
|
+
[OPTIONS]: options,
|
|
1797
|
+
format: "binary"
|
|
1798
|
+
}));
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* @experimental
|
|
1802
|
+
*/
|
|
1803
|
+
stream() {
|
|
1804
|
+
return Type.Unsafe(Type.Any({
|
|
1805
|
+
format: "stream",
|
|
1806
|
+
type: "string"
|
|
1807
|
+
}));
|
|
1808
|
+
}
|
|
1809
|
+
email(options) {
|
|
1810
|
+
return this.text({
|
|
1811
|
+
...options,
|
|
1812
|
+
format: "email",
|
|
1813
|
+
trim: true,
|
|
1814
|
+
lowercase: true
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
e164(options) {
|
|
1818
|
+
return this.text({
|
|
1819
|
+
...options,
|
|
1820
|
+
description: "Phone number in E.164 format, e.g. +1234567890.",
|
|
1821
|
+
pattern: "^\\+[1-9]\\d{1,14}$"
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
bcp47(options) {
|
|
1825
|
+
return this.text({
|
|
1826
|
+
...options,
|
|
1827
|
+
description: "BCP 47 language tag, e.g. en, en-US, fr, fr-CA.",
|
|
1828
|
+
pattern: "^[a-z]{2,3}(?:-[A-Z]{2})?$"
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Create a schema for short text, such as names or titles.
|
|
1833
|
+
* Default max length is 64 characters.
|
|
1834
|
+
*/
|
|
1835
|
+
shortText(options) {
|
|
1836
|
+
return this.text({
|
|
1837
|
+
size: "short",
|
|
1838
|
+
...options
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Create a schema for long text, such as descriptions or comments.
|
|
1843
|
+
* Default max length is 1024 characters.
|
|
1844
|
+
*/
|
|
1845
|
+
longText(options) {
|
|
1846
|
+
return this.text({
|
|
1847
|
+
size: "long",
|
|
1848
|
+
...options
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Create a schema for rich text, such as HTML or Markdown.
|
|
1853
|
+
* Default max length is 65535 characters.
|
|
1854
|
+
*/
|
|
1855
|
+
richText(options) {
|
|
1856
|
+
return this.text({
|
|
1857
|
+
size: "rich",
|
|
1858
|
+
...options
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Create a schema for a string enum e.g. LIKE_THIS.
|
|
1863
|
+
*/
|
|
1864
|
+
snakeCase = (options) => this.text({
|
|
1865
|
+
pattern: "^[A-Z_-]+$",
|
|
1866
|
+
...options
|
|
1867
|
+
});
|
|
1868
|
+
/**
|
|
1869
|
+
* Create a schema for an object with a value and label.
|
|
1870
|
+
*/
|
|
1871
|
+
valueLabel = (options) => this.object({
|
|
1872
|
+
value: this.snakeCase({ description: "Machine-readable value." }),
|
|
1873
|
+
label: this.text({ description: "Human-readable label." }),
|
|
1874
|
+
description: this.optional(this.text({
|
|
1875
|
+
description: "Description of the value.",
|
|
1876
|
+
size: "long"
|
|
1877
|
+
}))
|
|
1878
|
+
}, options);
|
|
1879
|
+
datetime = (options) => t.text({
|
|
1880
|
+
...options,
|
|
1881
|
+
format: "date-time"
|
|
1882
|
+
});
|
|
1883
|
+
date = (options) => t.text({
|
|
1884
|
+
...options,
|
|
1885
|
+
format: "date"
|
|
1886
|
+
});
|
|
1887
|
+
time = (options) => t.text({
|
|
1888
|
+
...options,
|
|
1889
|
+
format: "time"
|
|
1890
|
+
});
|
|
1891
|
+
duration = (options) => t.text({
|
|
1892
|
+
...options,
|
|
1893
|
+
format: "duration"
|
|
1894
|
+
});
|
|
1895
|
+
};
|
|
1896
|
+
const t = new TypeProvider();
|
|
1897
|
+
|
|
1898
|
+
//#endregion
|
|
1899
|
+
//#region src/core/primitives/$env.ts
|
|
1900
|
+
/**
|
|
1901
|
+
* Get typed values from environment variables.
|
|
1902
|
+
*
|
|
1903
|
+
* @example
|
|
1904
|
+
* ```ts
|
|
1905
|
+
* const alepha = Alepha.create({
|
|
1906
|
+
* env: {
|
|
1907
|
+
* // Alepha.create() will also use process.env when running on Node.js
|
|
1908
|
+
* HELLO: "world",
|
|
1909
|
+
* }
|
|
1910
|
+
* });
|
|
1911
|
+
*
|
|
1912
|
+
* class App {
|
|
1913
|
+
* log = $logger();
|
|
1914
|
+
*
|
|
1915
|
+
* // program expect a var env "HELLO" as string to works
|
|
1916
|
+
* env = $env(t.object({
|
|
1917
|
+
* HELLO: t.text()
|
|
1918
|
+
* }));
|
|
1919
|
+
*
|
|
1920
|
+
* sayHello = () => this.log.info("Hello ${this.env.HELLO}")
|
|
1921
|
+
* }
|
|
1922
|
+
*
|
|
1923
|
+
* run(alepha.with(App));
|
|
1924
|
+
* ```
|
|
1925
|
+
*/
|
|
1926
|
+
const $env = (type) => {
|
|
1927
|
+
const { alepha } = $context();
|
|
1928
|
+
if (!t.schema.isObject(type)) throw new AlephaError("Type must be an TObject");
|
|
1929
|
+
return alepha.parseEnv(type);
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
//#endregion
|
|
1933
|
+
//#region src/core/primitives/$hook.ts
|
|
1934
|
+
/**
|
|
1935
|
+
* Registers a new hook.
|
|
1936
|
+
*
|
|
1937
|
+
* ```ts
|
|
1938
|
+
* import { $hook } from "alepha";
|
|
1939
|
+
*
|
|
1940
|
+
* class MyProvider {
|
|
1941
|
+
* onStart = $hook({
|
|
1942
|
+
* name: "start", // or "configure", "ready", "stop", ...
|
|
1943
|
+
* handler: async (app) => {
|
|
1944
|
+
* // await db.connect(); ...
|
|
1945
|
+
* }
|
|
1946
|
+
* });
|
|
1947
|
+
* }
|
|
1948
|
+
* ```
|
|
1949
|
+
*
|
|
1950
|
+
* Hooks are used to run async functions from all registered providers/services.
|
|
1951
|
+
*
|
|
1952
|
+
* You can't register a hook after the App has started.
|
|
1953
|
+
*
|
|
1954
|
+
* It's used under the hood by the `configure`, `start`, and `stop` methods.
|
|
1955
|
+
* Some modules also use hooks to run their own logic. (e.g. `alepha/server`).
|
|
1956
|
+
*
|
|
1957
|
+
* You can create your own hooks by using module augmentation:
|
|
1958
|
+
*
|
|
1959
|
+
* ```ts
|
|
1960
|
+
* declare module "alepha" {
|
|
1961
|
+
*
|
|
1962
|
+
* interface Hooks {
|
|
1963
|
+
* "my:custom:hook": {
|
|
1964
|
+
* arg1: string;
|
|
1965
|
+
* }
|
|
1966
|
+
* }
|
|
1967
|
+
* }
|
|
1968
|
+
*
|
|
1969
|
+
* await alepha.events.emit("my:custom:hook", { arg1: "value" });
|
|
1970
|
+
* ```
|
|
1971
|
+
*
|
|
1972
|
+
*/
|
|
1973
|
+
const $hook = (options) => createPrimitive(HookPrimitive, options);
|
|
1974
|
+
var HookPrimitive = class extends Primitive {
|
|
1975
|
+
called = 0;
|
|
1976
|
+
onInit() {
|
|
1977
|
+
this.alepha.events.on(this.options.on, {
|
|
1978
|
+
caller: this.config.service,
|
|
1979
|
+
priority: this.options.priority,
|
|
1980
|
+
callback: async (args) => {
|
|
1981
|
+
this.called += 1;
|
|
1982
|
+
await this.options.handler(args);
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
$hook[KIND] = HookPrimitive;
|
|
1988
|
+
|
|
1989
|
+
//#endregion
|
|
1990
|
+
//#region src/core/primitives/$use.ts
|
|
1991
|
+
/**
|
|
1992
|
+
* Subscribes to an atom's state and returns its current value for use in components.
|
|
1993
|
+
*
|
|
1994
|
+
* Creates a reactive connection between an atom and a component, automatically registering
|
|
1995
|
+
* the atom in the application state if not already registered. The returned value is reactive
|
|
1996
|
+
* and will update when the atom's state changes.
|
|
1997
|
+
*
|
|
1998
|
+
* **Use Cases**: Accessing global state, sharing data between components, reactive UI updates
|
|
1999
|
+
*
|
|
2000
|
+
* @example
|
|
2001
|
+
* ```ts
|
|
2002
|
+
* const userState = $atom({ schema: t.object({ name: t.text(), role: t.text() }) });
|
|
2003
|
+
*
|
|
2004
|
+
* class UserComponent {
|
|
2005
|
+
* user = $use(userState); // Reactive reference to atom state
|
|
2006
|
+
*
|
|
2007
|
+
* render() {
|
|
2008
|
+
* return <div>Hello {this.user.name}!</div>;
|
|
2009
|
+
* }
|
|
2010
|
+
* }
|
|
2011
|
+
* ```
|
|
2012
|
+
*/
|
|
2013
|
+
const $use = (atom) => {
|
|
2014
|
+
const { alepha } = $context();
|
|
2015
|
+
alepha.store.register(atom);
|
|
2016
|
+
const init = alepha.store.get(atom.key);
|
|
2017
|
+
return {
|
|
2018
|
+
[OPTIONS]: { getter: atom.key },
|
|
2019
|
+
...init
|
|
2020
|
+
};
|
|
2021
|
+
};
|
|
2022
|
+
|
|
2023
|
+
//#endregion
|
|
2024
|
+
//#region src/core/schemas/pageQuerySchema.ts
|
|
2025
|
+
const pageQuerySchema = t.object({
|
|
2026
|
+
page: t.optional(t.integer({
|
|
2027
|
+
description: "The page number to retrieve.",
|
|
2028
|
+
minimum: 0,
|
|
2029
|
+
default: 0
|
|
2030
|
+
})),
|
|
2031
|
+
size: t.optional(t.integer({
|
|
2032
|
+
description: "The number of items per page.",
|
|
2033
|
+
minimum: 1,
|
|
2034
|
+
maximum: 100,
|
|
2035
|
+
default: 10
|
|
2036
|
+
})),
|
|
2037
|
+
sort: t.optional(t.text({ description: "Sort by field(s). Multiple columns separated by comma. Prefix with '-' for DESC. Examples: 'name' (ASC), '-createdAt' (DESC), 'role,-name' (role ASC, name DESC)" }))
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
//#endregion
|
|
2041
|
+
//#region src/core/schemas/pageSchema.ts
|
|
2042
|
+
const pageMetadataSchema = t.object({
|
|
2043
|
+
number: t.integer({ description: "Page number, starting from 0" }),
|
|
2044
|
+
size: t.integer({ description: "Number of items per page (requested page size)" }),
|
|
2045
|
+
offset: t.integer({ description: "Offset in the dataset (page × size)" }),
|
|
2046
|
+
numberOfElements: t.integer({ description: "Number of elements in THIS page (content.length). Different from totalElements which is the total across all pages." }),
|
|
2047
|
+
totalElements: t.optional(t.integer({ description: "Total number of elements across all pages. Only available when counting is enabled." })),
|
|
2048
|
+
totalPages: t.optional(t.integer({ description: "Total number of pages. Only available when `totalElements` is present." })),
|
|
2049
|
+
isEmpty: t.boolean({ description: "Indicates if the current page has no items (numberOfElements === 0)" }),
|
|
2050
|
+
isFirst: t.boolean({ description: "Indicates if this is the first page (number === 0)" }),
|
|
2051
|
+
isLast: t.boolean({ description: "Indicates if this is the last page (no more pages after this)" }),
|
|
2052
|
+
sort: t.optional(t.object({
|
|
2053
|
+
sorted: t.boolean({ description: "Whether the results are sorted" }),
|
|
2054
|
+
fields: t.array(t.object({
|
|
2055
|
+
field: t.text({ description: "The field used for sorting" }),
|
|
2056
|
+
direction: t.enum(["asc", "desc"], { description: "The direction of the sort. Either 'asc' or 'desc'" })
|
|
2057
|
+
}))
|
|
2058
|
+
}))
|
|
2059
|
+
});
|
|
2060
|
+
/**
|
|
2061
|
+
* Create a pagination schema for the given object schema.
|
|
2062
|
+
*
|
|
2063
|
+
* Provides a standardized pagination response format compatible with Spring Data's Page interface.
|
|
2064
|
+
* This schema can be used across any data source (databases, APIs, caches, etc.).
|
|
2065
|
+
*
|
|
2066
|
+
* @example
|
|
2067
|
+
* ```ts
|
|
2068
|
+
* const userSchema = t.object({ id: t.integer(), name: t.text() });
|
|
2069
|
+
* const userPageSchema = pageSchema(userSchema);
|
|
2070
|
+
* ```
|
|
2071
|
+
*
|
|
2072
|
+
* @example In an API endpoint
|
|
2073
|
+
* ```ts
|
|
2074
|
+
* $action({
|
|
2075
|
+
* output: pageSchema(UserSchema),
|
|
2076
|
+
* handler: async () => {
|
|
2077
|
+
* return await userRepo.paginate();
|
|
2078
|
+
* }
|
|
2079
|
+
* })
|
|
2080
|
+
* ```
|
|
2081
|
+
*/
|
|
2082
|
+
const pageSchema = (objectSchema, options) => t.object({
|
|
2083
|
+
content: t.array(objectSchema),
|
|
2084
|
+
page: pageMetadataSchema
|
|
2085
|
+
}, options);
|
|
2086
|
+
TypeProvider.prototype.page = (itemSchema) => pageSchema(itemSchema);
|
|
2087
|
+
|
|
2088
|
+
//#endregion
|
|
2089
|
+
//#region src/core/index.native.ts
|
|
2090
|
+
if (!Promise.withResolvers) Promise.withResolvers = () => {
|
|
2091
|
+
let resolve;
|
|
2092
|
+
let reject;
|
|
2093
|
+
return {
|
|
2094
|
+
promise: new Promise((res, rej) => {
|
|
2095
|
+
resolve = res;
|
|
2096
|
+
reject = rej;
|
|
2097
|
+
}),
|
|
2098
|
+
resolve,
|
|
2099
|
+
reject
|
|
2100
|
+
};
|
|
2101
|
+
};
|
|
2102
|
+
const run = (entry, opts) => {
|
|
2103
|
+
const alepha = entry instanceof Alepha ? entry : Alepha.create({ env: { ...opts?.env } });
|
|
2104
|
+
if (!(entry instanceof Alepha)) {
|
|
2105
|
+
const entries = Array.isArray(entry) ? entry : [entry];
|
|
2106
|
+
for (const e of entries) alepha.with(e);
|
|
2107
|
+
}
|
|
2108
|
+
return alepha;
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
//#endregion
|
|
2112
|
+
export { $atom, $context, $env, $hook, $inject, $module, $use, Alepha, AlephaError, AlsProvider, AppNotStartedError, Atom, CircularDependencyError, CodecManager, ContainerLockedError, EventManager, HookPrimitive, InjectPrimitive, JsonSchemaCodec, KIND, Module, OPTIONS, Primitive, SchemaCodec, StateManager, TooLateSubstitutionError, TypeBox, TypeBoxError, TypeBoxFormat, TypeBoxValue, TypeGuard, TypeProvider, createPagination, createPrimitive, isClass, isDate, isDateTime, isDuration, isEmail, isFileLike, isTime, isTypeFile, isURL, isUUID, pageMetadataSchema, pageQuerySchema, pageSchema, run, t };
|
|
2113
|
+
//# sourceMappingURL=index.native.js.map
|