alepha 0.7.3 → 0.7.5

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;
158
+ }
159
+ interface ModuleDefinition extends Module {
160
+ services: Array<Service>;
189
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
  * ```
@@ -553,14 +530,18 @@ interface Hooks {
553
530
  */
554
531
  declare class Alepha {
555
532
  /**
556
- * Syntactic sugar for creating a new instance of the container.
557
- * Equivalent to `Alepha.create()`.
533
+ * Creates a new instance of the Alepha container with some helpers:
534
+ *
535
+ * - merges `process.env` with the provided state.env when available.
536
+ * - populates the test hooks for Vitest or Jest environments when available.
537
+ *
538
+ * If you are not interested about these helpers, you can use the constructor directly.
558
539
  */
559
540
  static create(state?: Partial<State>): Alepha;
560
541
  /**
561
542
  * List of all services + how they are provided.
562
543
  */
563
- protected registry: Map<Service, Definition>;
544
+ protected registry: Map<Service, ServiceDefinition>;
564
545
  /**
565
546
  * Flag indicating whether the App won't accept any further changes.
566
547
  * Pass to true when #start() is called.
@@ -634,7 +615,7 @@ declare class Alepha {
634
615
  *
635
616
  * Modules are used to group services and provide a way to register them in the container.
636
617
  */
637
- protected modules: Array<Module>;
618
+ protected modules: Array<ModuleDefinition>;
638
619
  /**
639
620
  * Node.js feature that allows to store context across asynchronous calls.
640
621
  *
@@ -662,18 +643,19 @@ declare class Alepha {
662
643
  */
663
644
  state<Key extends keyof State>(key: Key, value?: State[Key]): State[Key];
664
645
  /**
665
- * Dump the current dependency graph of the App.
646
+ * True when start() is called.
666
647
  *
667
- * This method returns a record where the keys are the names of the services.
648
+ * -> No more services can be added, it's over, bye!
668
649
  */
669
- graph(): Record<string, {
670
- from: string[];
671
- as?: string;
672
- }>;
650
+ isLocked(): boolean;
673
651
  /**
674
- * True if the App is running in a browser environment.
652
+ * Returns whether the App is configured.
653
+ *
654
+ * It means that Alepha#configure() has been called.
655
+ *
656
+ * > By default, configure() is called automatically when start() is called, but you can also call it manually.
675
657
  */
676
- isBrowser(): boolean;
658
+ isConfigured(): boolean;
677
659
  /**
678
660
  * Returns whether the App has started.
679
661
  *
@@ -685,19 +667,9 @@ declare class Alepha {
685
667
  */
686
668
  isReady(): boolean;
687
669
  /**
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.
670
+ * True if the App is running in a browser environment.
699
671
  */
700
- isConfigured(): boolean;
672
+ isBrowser(): boolean;
701
673
  /**
702
674
  * Returns whether the App is running in a serverless environment.
703
675
  *
@@ -716,12 +688,6 @@ declare class Alepha {
716
688
  * > This is automatically set by Vite or Vercel. However, you have to set it manually when running Docker apps.
717
689
  */
718
690
  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
691
  /**
726
692
  * Starts the App.
727
693
  *
@@ -766,10 +732,12 @@ declare class Alepha {
766
732
  /**
767
733
  * Registers the specified service in the container.
768
734
  *
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.
735
+ * - If the service is ALREADY registered, the method does nothing.
736
+ * - If the service is NOT registered, a new instance is created and registered.
737
+ *
738
+ * Method is chainable, so you can register multiple services in a single call.
771
739
  *
772
- * > ServiceEntry allows to provide a service substitution feature.
740
+ * > ServiceEntry allows to provide a service **substitution** feature.
773
741
  *
774
742
  * @example
775
743
  * ```ts
@@ -777,34 +745,28 @@ declare class Alepha {
777
745
  * class B { value = "b"; }
778
746
  * class M { a = $inject(A); }
779
747
  *
780
- * Alepha.create().register({ provide: A, use: B }).get(M).a.value; // "b"
748
+ * Alepha.create().with({ provide: A, use: B }).get(M).a.value; // "b"
781
749
  * ```
782
750
  *
783
- * > Substitution is an advanced feature that allows you to replace a service with another service.
751
+ * > **Substitution** is an advanced feature that allows you to replace a service with another service.
784
752
  * > It's useful for testing or for providing different implementations of a service.
753
+ * > If you are interested in configuring a service, use Alepha#configure() instead.
785
754
  *
786
755
  * @param entry - The service to register in the container.
787
756
  * @return Current instance of Alepha.
788
757
  */
789
- register(...entries: Array<ServiceEntry | ModuleDescriptor>): this;
758
+ with<T extends object>(entry: ServiceEntry<T>): this;
790
759
  /**
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.
760
+ * Get the instance of the specified service and apply some changes, depending on the options.
761
+ * - If the service is already registered, it will return the existing instance. (except if `skipCache` is true)
762
+ * - If the service is not registered, it will create a new instance and register it. (except if `skipRegistration` is true)
763
+ * - New instance can be created with custom constructor arguments. (`args` option)
798
764
  *
799
765
  * > This method is used by $inject() under the hood.
800
766
  *
801
767
  * @return The instance of the specified class or type.
802
768
  */
803
769
  get<T extends object>(serviceEntry: ServiceEntry<T>, opts?: {
804
- /**
805
- * Parent service that requested the instance.
806
- */
807
- parent?: Service | null;
808
770
  /**
809
771
  * Ignore current existing instance.
810
772
  */
@@ -816,8 +778,36 @@ declare class Alepha {
816
778
  /**
817
779
  * Constructor arguments to pass when creating a new instance.
818
780
  */
819
- args?: any[];
781
+ args?: ConstructorParameters<InstantiableService<T>>;
782
+ /**
783
+ * Parent service that requested the instance.
784
+ * @internal
785
+ */
786
+ parent?: Service | null;
787
+ /**
788
+ * If the service is provided by a module, the module definition.
789
+ * @internal
790
+ */
791
+ module?: ModuleDefinition;
820
792
  }): T;
793
+ /**
794
+ * Configures the specified service with the provided state.
795
+ * If service is not registered, it will do nothing.
796
+ *
797
+ * It's recommended to use this method on the `configure` hook.
798
+ * @example
799
+ * ```ts
800
+ * class AppConfig {
801
+ * configure = $hook({
802
+ * name: "configure",
803
+ * handler: (a) => {
804
+ * a.configure(MyProvider, { some: "data" });
805
+ * }
806
+ * })
807
+ * }
808
+ * ```
809
+ */
810
+ configure<T extends object>(service: Service<T>, state: Partial<T>): void;
821
811
  /**
822
812
  * Registers a hook for the specified event.
823
813
  */
@@ -888,11 +878,15 @@ declare class Alepha {
888
878
  */
889
879
  parseEnv<T extends TObject>(schema: T): Static<T>;
890
880
  /**
891
- * Create a new instance of a logger.
881
+ * Dump the current dependency graph of the App.
892
882
  *
893
- * @returns The newly created logger instance.
883
+ * This method returns a record where the keys are the names of the services.
894
884
  */
895
- protected createLogger(env: Env): Logger;
885
+ graph(): Record<string, {
886
+ from: string[];
887
+ as?: string;
888
+ module?: string;
889
+ }>;
896
890
  /**
897
891
  * @internal
898
892
  */
@@ -900,16 +894,20 @@ declare class Alepha {
900
894
  /**
901
895
  * @internal
902
896
  */
903
- protected new<T extends object>(definition: Service<T>, args?: any[]): T;
897
+ protected new<T extends object>(definition: Service<T>, args?: any[], module?: ModuleDefinition): T;
904
898
  /**
905
- * @interface
899
+ * @internal
906
900
  */
907
- moduleOf(service: Service): Module | undefined;
901
+ protected createLogger(env: Env): Logger;
902
+ /**
903
+ * @internal
904
+ */
905
+ getModuleOf(service: Service): Module | undefined;
908
906
  }
909
907
  /**
910
908
  * This is how we store services in the Alepha container.
911
909
  */
912
- interface Definition<T extends object = any> {
910
+ interface ServiceDefinition<T extends object = any> {
913
911
  /**
914
912
  * The class or type definition to provide.
915
913
  */
@@ -927,6 +925,31 @@ interface Definition<T extends object = any> {
927
925
  * List of classes which use this class.
928
926
  */
929
927
  parents: Array<Service | null>;
928
+ /**
929
+ * If the service is provided by a module, the module definition.
930
+ */
931
+ module?: ModuleDefinition;
932
+ }
933
+
934
+ interface RunOptions {
935
+ /**
936
+ * Environment variables to be used by the application.
937
+ * If not provided, it will use the current process environment.
938
+ */
939
+ env?: Env;
940
+ /**
941
+ * A callback that will be executed before the application starts.
942
+ */
943
+ configure?: (alepha: Alepha) => Async<void>;
944
+ /**
945
+ * A callback that will be executed once the application is ready.
946
+ * This is useful for initializing resources or starting background tasks.
947
+ */
948
+ ready?: (alepha: Alepha) => Async<void>;
949
+ /**
950
+ * If true, the application will stop after the ready callback is executed.
951
+ */
952
+ once?: boolean;
930
953
  }
931
954
 
932
955
  /**
@@ -973,6 +996,12 @@ declare const $env: typeof $inject;
973
996
  */
974
997
  declare const $logger: (name?: string) => Logger;
975
998
 
999
+ /**
1000
+ * Default error class for Alepha.
1001
+ */
1002
+ declare class AlephaError extends Error {
1003
+ }
1004
+
976
1005
  declare class AppNotStartedError extends Error {
977
1006
  constructor();
978
1007
  }
@@ -1231,36 +1260,6 @@ interface AlephaStringOptions extends StringOptions {
1231
1260
  declare const t: TypeProvider;
1232
1261
  declare const isUUID: (value: string) => boolean;
1233
1262
 
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
1263
  declare const run: (entry: Alepha | Service | Array<Service>, opts?: RunOptions) => Alepha;
1265
1264
 
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 };
1265
+ 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;
@@ -94,12 +94,18 @@ declare class DateTimeProvider {
94
94
  tick(): Promise<void>;
95
95
  /**
96
96
  * Wait for a certain duration.
97
+ *
98
+ * You can clear the timeout by using the `AbortSignal` API.
99
+ * Aborted signal will resolve the promise immediately, it does not reject it.
97
100
  */
98
- wait(duration: DurationLike, signal?: AbortSignal): Promise<void>;
101
+ wait(duration: DurationLike, options?: {
102
+ signal?: AbortSignal;
103
+ now?: number;
104
+ }): Promise<void>;
99
105
  /**
100
106
  * Run a callback after a certain duration.
101
107
  */
102
- timeout(callback: () => void, duration: DurationLike): Timeout;
108
+ timeout(callback: () => void, duration: DurationLike, now?: number): Timeout;
103
109
  /**
104
110
  * Create an interval.
105
111
  *
@@ -113,7 +119,7 @@ declare class DateTimeProvider {
113
119
  /**
114
120
  * Add time to the current date.
115
121
  */
116
- travel(duration: DurationLike): Promise<void>;
122
+ travel(duration: DurationLike, unit?: ManipulateType): Promise<void>;
117
123
  /**
118
124
  * Stop the time.
119
125
  */
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 AlephaLockRedis {
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(lock.AlephaLock);
47
+ }
48
+
49
+ exports.AlephaLockRedis = AlephaLockRedis;
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 AlephaLockRedis {
22
+ readonly name = "alepha.lock.redis";
23
+ readonly $services: (alepha: Alepha) => Alepha;
24
+ }
25
+
26
+ export { AlephaLockRedis, RedisLockProvider };
package/lock/redis.js ADDED
@@ -0,0 +1,47 @@
1
+ import { LockTopicProvider, LockProvider, AlephaLock } 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 AlephaLockRedis {
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 { AlephaLockRedis, RedisLockProvider };