alepha 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.d.ts CHANGED
@@ -5,9 +5,9 @@ export { Static, StaticDecode, StaticEncode, TObject, TSchema, TypeGuard } from
5
5
  import { TypeCheck } from '@sinclair/typebox/compiler';
6
6
  import { AsyncLocalStorage } from 'node:async_hooks';
7
7
  import { ValueError } from '@sinclair/typebox/errors';
8
+ import { ReadableStream as ReadableStream$1 } from 'node:stream/web';
8
9
  import * as TypeBoxValue from '@sinclair/typebox/value';
9
10
  export { TypeBoxValue };
10
- import { ReadableStream as ReadableStream$1 } from 'node:stream/web';
11
11
  import { Readable } from 'node:stream';
12
12
 
13
13
  /**
@@ -59,10 +59,10 @@ interface ServiceSubstitution<T extends object = any> {
59
59
  */
60
60
  use: Service<T>;
61
61
  /**
62
- * If true, will not throw an error if the service already exists.
62
+ * If true and the service already exists, just ignore the substitution and do not throw an error.
63
63
  * Mostly used for plugins to enforce a substitution without throwing an error.
64
64
  */
65
- default?: boolean;
65
+ optional?: boolean;
66
66
  }
67
67
  /**
68
68
  * Every time you register a service, you can use this type to define it.
@@ -152,41 +152,15 @@ declare const $hook: {
152
152
  [KIND]: string;
153
153
  };
154
154
 
155
- interface ModuleDescriptorOptions<T extends TSchema> {
156
- name: string;
157
- version?: string;
158
- description?: string;
159
- services?: ServiceEntry[] | ((args: Alepha & {
160
- env: Static<T>;
161
- }) => ServiceEntry[]);
162
- env?: T;
163
- }
164
- type ModuleDescriptor<T extends TSchema = TSchema> = {
165
- [KIND]: "MODULE";
166
- [OPTIONS]: ModuleDescriptorOptions<T>;
167
- };
168
- /**
169
- * This descriptor can be used to define the application metadata and services.
170
- */
171
- declare const $module: <T extends TSchema>(opts: ModuleDescriptorOptions<T>) => ModuleDescriptor<T>;
172
155
  interface Module {
173
- /**
174
- * The name of the module.
175
- */
176
- name: string;
177
- /**
178
- * The version of the module.
179
- */
180
- version?: string;
181
- /**
182
- * The description of the module.
183
- */
184
- description?: string;
185
- /**
186
- * The services provided by the module.
187
- */
188
- services?: Service[];
156
+ name?: string;
157
+ $services: (alepha: Alepha) => void | Alepha;
189
158
  }
159
+ interface ModuleDefinition extends Module {
160
+ services: Array<Service>;
161
+ }
162
+ declare const isModule: (value: unknown) => value is Module;
163
+ declare const toModuleName: (name: string) => string;
190
164
 
191
165
  /**
192
166
  * /!\ Global variable /!\
@@ -198,7 +172,11 @@ interface Module {
198
172
  declare const __alephaRef: {
199
173
  context?: Alepha;
200
174
  definition?: Service;
201
- module?: Module;
175
+ module?: ModuleDefinition;
176
+ $services?: {
177
+ module: ModuleDefinition;
178
+ parent: Service;
179
+ };
202
180
  };
203
181
  /**
204
182
  * Cursor descriptor.
@@ -206,7 +184,7 @@ declare const __alephaRef: {
206
184
  interface CursorDescriptor {
207
185
  context: Alepha;
208
186
  definition?: Service;
209
- module?: Module;
187
+ module?: ModuleDefinition;
210
188
  }
211
189
  /**
212
190
  * Get Alepha instance and Class definition from the current context.
@@ -451,9 +429,8 @@ interface MockLoggerStore {
451
429
  stack: Array<{
452
430
  date: string;
453
431
  level: string;
454
- msg: string;
455
- data?: object;
456
- }>;
432
+ message: string;
433
+ } & Record<string, any>>;
457
434
  }
458
435
 
459
436
  interface Env extends LoggerEnv {
@@ -463,7 +440,7 @@ interface Env extends LoggerEnv {
463
440
  */
464
441
  NODE_ENV?: "dev" | "test" | "production";
465
442
  /**
466
- * Optional name of the application. Same as `state.name`.
443
+ * Optional name of the application.
467
444
  */
468
445
  APP_NAME?: string;
469
446
  /**
@@ -541,7 +518,7 @@ interface Hooks {
541
518
  * // state, env, and other properties
542
519
  * })
543
520
  *
544
- * alepha.register(MyService);
521
+ * alepha.with(MyService);
545
522
  *
546
523
  * run(alepha); // trigger .start (and .stop) automatically
547
524
  * ```
@@ -560,7 +537,7 @@ declare class Alepha {
560
537
  /**
561
538
  * List of all services + how they are provided.
562
539
  */
563
- protected registry: Map<Service, Definition>;
540
+ protected registry: Map<Service, ServiceDefinition>;
564
541
  /**
565
542
  * Flag indicating whether the App won't accept any further changes.
566
543
  * Pass to true when #start() is called.
@@ -634,7 +611,7 @@ declare class Alepha {
634
611
  *
635
612
  * Modules are used to group services and provide a way to register them in the container.
636
613
  */
637
- protected modules: Array<Module>;
614
+ protected modules: Array<ModuleDefinition>;
638
615
  /**
639
616
  * Node.js feature that allows to store context across asynchronous calls.
640
617
  *
@@ -662,18 +639,19 @@ declare class Alepha {
662
639
  */
663
640
  state<Key extends keyof State>(key: Key, value?: State[Key]): State[Key];
664
641
  /**
665
- * Dump the current dependency graph of the App.
642
+ * True when start() is called.
666
643
  *
667
- * This method returns a record where the keys are the names of the services.
644
+ * -> No more services can be added, it's over, bye!
668
645
  */
669
- graph(): Record<string, {
670
- from: string[];
671
- as?: string;
672
- }>;
646
+ isLocked(): boolean;
673
647
  /**
674
- * True if the App is running in a browser environment.
648
+ * Returns whether the App is configured.
649
+ *
650
+ * It means that Alepha#configure() has been called.
651
+ *
652
+ * > By default, configure() is called automatically when start() is called, but you can also call it manually.
675
653
  */
676
- isBrowser(): boolean;
654
+ isConfigured(): boolean;
677
655
  /**
678
656
  * Returns whether the App has started.
679
657
  *
@@ -685,19 +663,9 @@ declare class Alepha {
685
663
  */
686
664
  isReady(): boolean;
687
665
  /**
688
- * True when start() is called.
689
- *
690
- * -> No more services can be added, it's over, bye!
691
- */
692
- isLocked(): boolean;
693
- /**
694
- * Returns whether the App is configured.
695
- *
696
- * It means that Alepha#configure() has been called.
697
- *
698
- * > By default, configure() is called automatically when start() is called, but you can also call it manually.
666
+ * True if the App is running in a browser environment.
699
667
  */
700
- isConfigured(): boolean;
668
+ isBrowser(): boolean;
701
669
  /**
702
670
  * Returns whether the App is running in a serverless environment.
703
671
  *
@@ -716,12 +684,6 @@ declare class Alepha {
716
684
  * > This is automatically set by Vite or Vercel. However, you have to set it manually when running Docker apps.
717
685
  */
718
686
  isProduction(): boolean;
719
- /**
720
- * Trigger configuration of the App manually.
721
- *
722
- * > configure() is called automatically when start() is called, you should not need to call it manually.
723
- */
724
- configure(): Promise<this | undefined>;
725
687
  /**
726
688
  * Starts the App.
727
689
  *
@@ -766,10 +728,12 @@ declare class Alepha {
766
728
  /**
767
729
  * Registers the specified service in the container.
768
730
  *
769
- * - If the service is already registered, the method does nothing.
770
- * - If the service is not registered, a new instance is created and registered.
731
+ * - If the service is ALREADY registered, the method does nothing.
732
+ * - If the service is NOT registered, a new instance is created and registered.
771
733
  *
772
- * > ServiceEntry allows to provide a service substitution feature.
734
+ * Method is chainable, so you can register multiple services in a single call.
735
+ *
736
+ * > ServiceEntry allows to provide a service **substitution** feature.
773
737
  *
774
738
  * @example
775
739
  * ```ts
@@ -777,34 +741,28 @@ declare class Alepha {
777
741
  * class B { value = "b"; }
778
742
  * class M { a = $inject(A); }
779
743
  *
780
- * Alepha.create().register({ provide: A, use: B }).get(M).a.value; // "b"
744
+ * Alepha.create().with({ provide: A, use: B }).get(M).a.value; // "b"
781
745
  * ```
782
746
  *
783
- * > Substitution is an advanced feature that allows you to replace a service with another service.
747
+ * > **Substitution** is an advanced feature that allows you to replace a service with another service.
784
748
  * > It's useful for testing or for providing different implementations of a service.
749
+ * > If you are interested in configuring a service, use Alepha#configure() instead.
785
750
  *
786
751
  * @param entry - The service to register in the container.
787
752
  * @return Current instance of Alepha.
788
753
  */
789
- register(...entries: Array<ServiceEntry | ModuleDescriptor>): this;
754
+ with<T extends object>(entry: ServiceEntry<T>): this;
790
755
  /**
791
- * Alias for the 'register' method.
792
- *
793
- * @alias {Alepha#register}
794
- */
795
- with: (...entries: Array<ServiceEntry | ModuleDescriptor>) => this;
796
- /**
797
- * Works like 'Alepha#register' but it will return the instance.
756
+ * Get the instance of the specified service and apply some changes, depending on the options.
757
+ * - If the service is already registered, it will return the existing instance. (except if `skipCache` is true)
758
+ * - If the service is not registered, it will create a new instance and register it. (except if `skipRegistration` is true)
759
+ * - New instance can be created with custom constructor arguments. (`args` option)
798
760
  *
799
761
  * > This method is used by $inject() under the hood.
800
762
  *
801
763
  * @return The instance of the specified class or type.
802
764
  */
803
765
  get<T extends object>(serviceEntry: ServiceEntry<T>, opts?: {
804
- /**
805
- * Parent service that requested the instance.
806
- */
807
- parent?: Service | null;
808
766
  /**
809
767
  * Ignore current existing instance.
810
768
  */
@@ -816,8 +774,36 @@ declare class Alepha {
816
774
  /**
817
775
  * Constructor arguments to pass when creating a new instance.
818
776
  */
819
- args?: any[];
777
+ args?: ConstructorParameters<InstantiableService<T>>;
778
+ /**
779
+ * Parent service that requested the instance.
780
+ * @internal
781
+ */
782
+ parent?: Service | null;
783
+ /**
784
+ * If the service is provided by a module, the module definition.
785
+ * @internal
786
+ */
787
+ module?: ModuleDefinition;
820
788
  }): T;
789
+ /**
790
+ * Configures the specified service with the provided state.
791
+ * If service is not registered, it will do nothing.
792
+ *
793
+ * It's recommended to use this method on the `configure` hook.
794
+ * @example
795
+ * ```ts
796
+ * class AppConfig {
797
+ * configure = $hook({
798
+ * name: "configure",
799
+ * handler: (a) => {
800
+ * a.configure(MyProvider, { some: "data" });
801
+ * }
802
+ * })
803
+ * }
804
+ * ```
805
+ */
806
+ configure<T extends object>(service: Service<T>, state: Partial<T>): void;
821
807
  /**
822
808
  * Registers a hook for the specified event.
823
809
  */
@@ -888,11 +874,15 @@ declare class Alepha {
888
874
  */
889
875
  parseEnv<T extends TObject>(schema: T): Static<T>;
890
876
  /**
891
- * Create a new instance of a logger.
877
+ * Dump the current dependency graph of the App.
892
878
  *
893
- * @returns The newly created logger instance.
879
+ * This method returns a record where the keys are the names of the services.
894
880
  */
895
- protected createLogger(env: Env): Logger;
881
+ graph(): Record<string, {
882
+ from: string[];
883
+ as?: string;
884
+ module?: string;
885
+ }>;
896
886
  /**
897
887
  * @internal
898
888
  */
@@ -900,16 +890,20 @@ declare class Alepha {
900
890
  /**
901
891
  * @internal
902
892
  */
903
- protected new<T extends object>(definition: Service<T>, args?: any[]): T;
893
+ protected new<T extends object>(definition: Service<T>, args?: any[], module?: ModuleDefinition): T;
894
+ /**
895
+ * @internal
896
+ */
897
+ protected createLogger(env: Env): Logger;
904
898
  /**
905
- * @interface
899
+ * @internal
906
900
  */
907
- moduleOf(service: Service): Module | undefined;
901
+ getModuleOf(service: Service): Module | undefined;
908
902
  }
909
903
  /**
910
904
  * This is how we store services in the Alepha container.
911
905
  */
912
- interface Definition<T extends object = any> {
906
+ interface ServiceDefinition<T extends object = any> {
913
907
  /**
914
908
  * The class or type definition to provide.
915
909
  */
@@ -927,6 +921,31 @@ interface Definition<T extends object = any> {
927
921
  * List of classes which use this class.
928
922
  */
929
923
  parents: Array<Service | null>;
924
+ /**
925
+ * If the service is provided by a module, the module definition.
926
+ */
927
+ module?: ModuleDefinition;
928
+ }
929
+
930
+ interface RunOptions {
931
+ /**
932
+ * Environment variables to be used by the application.
933
+ * If not provided, it will use the current process environment.
934
+ */
935
+ env?: Env;
936
+ /**
937
+ * A callback that will be executed before the application starts.
938
+ */
939
+ configure?: (alepha: Alepha) => Async<void>;
940
+ /**
941
+ * A callback that will be executed once the application is ready.
942
+ * This is useful for initializing resources or starting background tasks.
943
+ */
944
+ ready?: (alepha: Alepha) => Async<void>;
945
+ /**
946
+ * If true, the application will stop after the ready callback is executed.
947
+ */
948
+ once?: boolean;
930
949
  }
931
950
 
932
951
  /**
@@ -973,6 +992,12 @@ declare const $env: typeof $inject;
973
992
  */
974
993
  declare const $logger: (name?: string) => Logger;
975
994
 
995
+ /**
996
+ * Default error class for Alepha.
997
+ */
998
+ declare class AlephaError extends Error {
999
+ }
1000
+
976
1001
  declare class AppNotStartedError extends Error {
977
1002
  constructor();
978
1003
  }
@@ -1231,36 +1256,6 @@ interface AlephaStringOptions extends StringOptions {
1231
1256
  declare const t: TypeProvider;
1232
1257
  declare const isUUID: (value: string) => boolean;
1233
1258
 
1234
- declare const substitute: <T extends object>(rule: {
1235
- provide: T;
1236
- use: T;
1237
- default?: boolean;
1238
- }) => {
1239
- use: T;
1240
- provide: T;
1241
- default?: boolean;
1242
- };
1243
- interface RunOptions {
1244
- /**
1245
- * Environment variables to be used by the application.
1246
- * If not provided, it will use the current process environment.
1247
- */
1248
- env?: Env;
1249
- /**
1250
- * A callback that will be executed before the application starts.
1251
- */
1252
- configure?: (alepha: Alepha) => Async<void>;
1253
- /**
1254
- * A callback that will be executed once the application is ready.
1255
- * This is useful for initializing resources or starting background tasks.
1256
- */
1257
- ready?: (alepha: Alepha) => Async<void>;
1258
- /**
1259
- * If true, the application will stop after the ready callback is executed.
1260
- */
1261
- once?: boolean;
1262
- }
1263
-
1264
1259
  declare const run: (entry: Alepha | Service | Array<Service>, opts?: RunOptions) => Alepha;
1265
1260
 
1266
- export { $cursor, $env, $hook, $inject, $logger, $module, type AbstractService, Alepha, type AlephaStringOptions, AppNotStartedError, type Async, type AsyncFn, type AsyncLocalStorageData, AsyncLocalStorageProvider, COLORS, CircularDependencyError, ContainerLockedError, type CursorDescriptor, type Descriptor, type DescriptorIdentifier, type DescriptorItem, type Env, type FileLike, type Hook, type HookDescriptor, type HookOptions, type Hooks, type InstantiableService, KIND, LEVEL_COLORS, type LogLevel, Logger, type LoggerEnv, type LoggerOptions, type MaybePromise, MockLogger, type MockLoggerStore, type Module, type ModuleDescriptor, type ModuleDescriptorOptions, NotImplementedError, OPTIONS, type PromiseFn, type RunOptions, type Service, type ServiceEntry, type ServiceSubstitution, type State, type StreamLike, type TFile, type TStream, type TextLength, TypeBoxError, TypeProvider, __alephaRef, __bind, __descriptor, descriptorEvents, isDescriptorValue, isFileLike, isTypeFile, isTypeStream, isUUID, run, substitute, t };
1261
+ export { $cursor, $env, $hook, $inject, $logger, type AbstractService, Alepha, AlephaError, type AlephaStringOptions, AppNotStartedError, type Async, type AsyncFn, type AsyncLocalStorageData, AsyncLocalStorageProvider, COLORS, CircularDependencyError, ContainerLockedError, type CursorDescriptor, type Descriptor, type DescriptorIdentifier, type DescriptorItem, type Env, type FileLike, type Hook, type HookDescriptor, type HookOptions, type Hooks, type InstantiableService, KIND, LEVEL_COLORS, type LogLevel, Logger, type LoggerEnv, type LoggerOptions, type MaybePromise, MockLogger, type MockLoggerStore, type Module, type ModuleDefinition, NotImplementedError, OPTIONS, type PromiseFn, type Service, type ServiceEntry, type ServiceSubstitution, type State, type StreamLike, type TFile, type TStream, type TextLength, TypeBoxError, TypeProvider, __alephaRef, __bind, __descriptor, descriptorEvents, isDescriptorValue, isFileLike, isModule, isTypeFile, isTypeStream, isUUID, run, t, toModuleName };
package/datetime.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _alepha_core from '@alepha/core';
2
2
  import { Async } from '@alepha/core';
3
3
  import dayjs, { ManipulateType, Dayjs } from 'dayjs';
4
- import dayjsDuration from 'dayjs/plugin/duration';
4
+ import dayjsDuration from 'dayjs/plugin/duration.js';
5
5
 
6
6
  declare class Interval {
7
7
  private timer;
package/lock/redis.cjs ADDED
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ var lock = require('@alepha/lock');
4
+ var topicRedis = require('@alepha/topic-redis');
5
+ var core = require('@alepha/core');
6
+ var redis = require('@alepha/redis');
7
+
8
+ class RedisLockProvider {
9
+ log = core.$logger();
10
+ redisProvider = core.$inject(redis.RedisProvider);
11
+ async set(key, value, nx, px) {
12
+ const options = {
13
+ GET: true
14
+ // all the secrets of $lock is based on this
15
+ };
16
+ if (px) {
17
+ options.expiration = {
18
+ type: "PX",
19
+ value: px
20
+ };
21
+ }
22
+ if (nx) {
23
+ options.condition = "NX";
24
+ }
25
+ const resp = await this.redisProvider.set(key, value, options);
26
+ if (resp === null) {
27
+ this.log.debug(`Lock already exists`, { key, value });
28
+ return value;
29
+ }
30
+ return resp.toString("utf-8");
31
+ }
32
+ async del(...keys) {
33
+ await this.redisProvider.del(keys);
34
+ }
35
+ }
36
+ class AlephaLock {
37
+ name = "alepha.lock.redis";
38
+ $services = (alepha) => alepha.with({
39
+ provide: lock.LockTopicProvider,
40
+ use: topicRedis.RedisTopicProvider,
41
+ optional: true
42
+ }).with({
43
+ provide: lock.LockProvider,
44
+ use: RedisLockProvider,
45
+ optional: true
46
+ }).with(AlephaLock);
47
+ }
48
+
49
+ exports.AlephaLock = AlephaLock;
50
+ exports.RedisLockProvider = RedisLockProvider;
@@ -0,0 +1,26 @@
1
+ import * as _alepha_core from '@alepha/core';
2
+ import { Alepha } from '@alepha/core';
3
+ import { LockProvider } from '@alepha/lock';
4
+ import { RedisProvider } from '@alepha/redis';
5
+
6
+ declare class RedisLockProvider implements LockProvider {
7
+ protected readonly log: _alepha_core.Logger;
8
+ protected readonly redisProvider: RedisProvider;
9
+ set(key: string, value: string, nx?: boolean, px?: number): Promise<string>;
10
+ del(...keys: string[]): Promise<void>;
11
+ }
12
+
13
+ /**
14
+ * Alepha Lock Redis Module
15
+ *
16
+ * Plugin for Alepha that provides a locking mechanism.
17
+ *
18
+ * @see {@link RedisLockProvider}
19
+ * @module alepha.lock.redis
20
+ */
21
+ declare class AlephaLock {
22
+ readonly name = "alepha.lock.redis";
23
+ readonly $services: (alepha: Alepha) => Alepha;
24
+ }
25
+
26
+ export { AlephaLock, RedisLockProvider };
package/lock/redis.js ADDED
@@ -0,0 +1,47 @@
1
+ import { LockTopicProvider, LockProvider } from '@alepha/lock';
2
+ import { RedisTopicProvider } from '@alepha/topic-redis';
3
+ import { $logger, $inject } from '@alepha/core';
4
+ import { RedisProvider } from '@alepha/redis';
5
+
6
+ class RedisLockProvider {
7
+ log = $logger();
8
+ redisProvider = $inject(RedisProvider);
9
+ async set(key, value, nx, px) {
10
+ const options = {
11
+ GET: true
12
+ // all the secrets of $lock is based on this
13
+ };
14
+ if (px) {
15
+ options.expiration = {
16
+ type: "PX",
17
+ value: px
18
+ };
19
+ }
20
+ if (nx) {
21
+ options.condition = "NX";
22
+ }
23
+ const resp = await this.redisProvider.set(key, value, options);
24
+ if (resp === null) {
25
+ this.log.debug(`Lock already exists`, { key, value });
26
+ return value;
27
+ }
28
+ return resp.toString("utf-8");
29
+ }
30
+ async del(...keys) {
31
+ await this.redisProvider.del(keys);
32
+ }
33
+ }
34
+ class AlephaLock {
35
+ name = "alepha.lock.redis";
36
+ $services = (alepha) => alepha.with({
37
+ provide: LockTopicProvider,
38
+ use: RedisTopicProvider,
39
+ optional: true
40
+ }).with({
41
+ provide: LockProvider,
42
+ use: RedisLockProvider,
43
+ optional: true
44
+ }).with(AlephaLock);
45
+ }
46
+
47
+ export { AlephaLock, RedisLockProvider };