alepha 0.13.6 → 0.13.7

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.
Files changed (134) hide show
  1. package/dist/api-audits/index.browser.js +116 -0
  2. package/dist/api-audits/index.browser.js.map +1 -0
  3. package/dist/api-audits/index.d.ts +1194 -0
  4. package/dist/api-audits/index.js +674 -0
  5. package/dist/api-audits/index.js.map +1 -0
  6. package/dist/api-notifications/index.d.ts +147 -147
  7. package/dist/api-parameters/index.browser.js +36 -5
  8. package/dist/api-parameters/index.browser.js.map +1 -1
  9. package/dist/api-parameters/index.d.ts +711 -33
  10. package/dist/api-parameters/index.js +831 -17
  11. package/dist/api-parameters/index.js.map +1 -1
  12. package/dist/api-users/index.d.ts +793 -780
  13. package/dist/api-users/index.js +699 -19
  14. package/dist/api-users/index.js.map +1 -1
  15. package/dist/api-verifications/index.js +2 -1
  16. package/dist/api-verifications/index.js.map +1 -1
  17. package/dist/bin/index.js +1 -0
  18. package/dist/bin/index.js.map +1 -1
  19. package/dist/cli/index.d.ts +85 -31
  20. package/dist/cli/index.js +205 -33
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/command/index.d.ts +67 -6
  23. package/dist/command/index.js +30 -3
  24. package/dist/command/index.js.map +1 -1
  25. package/dist/core/index.browser.js +241 -61
  26. package/dist/core/index.browser.js.map +1 -1
  27. package/dist/core/index.d.ts +170 -90
  28. package/dist/core/index.js +264 -67
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/index.native.js +248 -65
  31. package/dist/core/index.native.js.map +1 -1
  32. package/dist/email/index.js +15 -10554
  33. package/dist/email/index.js.map +1 -1
  34. package/dist/logger/index.d.ts +4 -4
  35. package/dist/logger/index.js +77 -72
  36. package/dist/logger/index.js.map +1 -1
  37. package/dist/orm/index.d.ts +5 -1
  38. package/dist/orm/index.js +24 -7
  39. package/dist/orm/index.js.map +1 -1
  40. package/dist/queue/index.d.ts +4 -4
  41. package/dist/scheduler/index.d.ts +6 -6
  42. package/dist/server/index.d.ts +10 -1
  43. package/dist/server/index.js +20 -6
  44. package/dist/server/index.js.map +1 -1
  45. package/dist/server-auth/index.d.ts +163 -152
  46. package/dist/server-auth/index.js +40 -10
  47. package/dist/server-auth/index.js.map +1 -1
  48. package/dist/server-cookies/index.js +5 -1
  49. package/dist/server-cookies/index.js.map +1 -1
  50. package/dist/server-links/index.d.ts +33 -33
  51. package/dist/server-security/index.d.ts +9 -9
  52. package/dist/thread/index.js +2 -2
  53. package/dist/thread/index.js.map +1 -1
  54. package/dist/vite/index.d.ts +2 -2
  55. package/dist/vite/index.js +102 -45
  56. package/dist/vite/index.js.map +1 -1
  57. package/dist/websocket/index.browser.js +3 -3
  58. package/dist/websocket/index.browser.js.map +1 -1
  59. package/dist/websocket/index.js +4 -4
  60. package/dist/websocket/index.js.map +1 -1
  61. package/package.json +14 -9
  62. package/src/api-audits/controllers/AuditController.ts +186 -0
  63. package/src/api-audits/entities/audits.ts +132 -0
  64. package/src/api-audits/index.browser.ts +18 -0
  65. package/src/api-audits/index.ts +58 -0
  66. package/src/api-audits/primitives/$audit.ts +159 -0
  67. package/src/api-audits/schemas/auditQuerySchema.ts +23 -0
  68. package/src/api-audits/schemas/auditResourceSchema.ts +9 -0
  69. package/src/api-audits/schemas/createAuditSchema.ts +27 -0
  70. package/src/api-audits/services/AuditService.ts +412 -0
  71. package/src/api-parameters/controllers/ConfigController.ts +324 -0
  72. package/src/api-parameters/entities/parameters.ts +93 -10
  73. package/src/api-parameters/index.ts +43 -4
  74. package/src/api-parameters/primitives/$config.ts +291 -19
  75. package/src/api-parameters/schedulers/ConfigActivationScheduler.ts +30 -0
  76. package/src/api-parameters/services/ConfigStore.ts +491 -0
  77. package/src/api-users/atoms/realmAuthSettingsAtom.ts +19 -0
  78. package/src/api-users/controllers/UserRealmController.ts +0 -2
  79. package/src/api-users/index.ts +2 -0
  80. package/src/api-users/primitives/$userRealm.ts +18 -3
  81. package/src/api-users/providers/UserRealmProvider.ts +6 -3
  82. package/src/api-users/services/RegistrationService.ts +2 -1
  83. package/src/api-users/services/SessionService.ts +4 -0
  84. package/src/api-users/services/UserService.ts +3 -0
  85. package/src/api-verifications/index.ts +7 -1
  86. package/src/bin/index.ts +1 -0
  87. package/src/cli/assets/biomeJson.ts +1 -1
  88. package/src/cli/assets/dummySpecTs.ts +7 -0
  89. package/src/cli/assets/editorconfig.ts +13 -0
  90. package/src/cli/assets/mainTs.ts +14 -0
  91. package/src/cli/commands/BiomeCommands.ts +2 -0
  92. package/src/cli/commands/CoreCommands.ts +28 -9
  93. package/src/cli/commands/VerifyCommands.ts +2 -1
  94. package/src/cli/commands/ViteCommands.ts +8 -9
  95. package/src/cli/services/AlephaCliUtils.ts +214 -23
  96. package/src/command/helpers/Asker.ts +0 -1
  97. package/src/command/primitives/$command.ts +67 -0
  98. package/src/command/providers/CliProvider.ts +39 -8
  99. package/src/core/Alepha.ts +40 -30
  100. package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
  101. package/src/core/index.shared.ts +1 -0
  102. package/src/core/index.ts +30 -3
  103. package/src/core/providers/EventManager.ts +1 -1
  104. package/src/core/providers/StateManager.ts +23 -12
  105. package/src/core/providers/TypeProvider.ts +26 -34
  106. package/src/logger/index.ts +8 -6
  107. package/src/logger/primitives/$logger.ts +1 -1
  108. package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
  109. package/src/orm/index.ts +6 -0
  110. package/src/orm/services/PgRelationManager.ts +2 -2
  111. package/src/orm/services/PostgresModelBuilder.ts +11 -7
  112. package/src/orm/services/Repository.ts +16 -7
  113. package/src/orm/services/SqliteModelBuilder.ts +10 -0
  114. package/src/server/index.ts +6 -0
  115. package/src/server/primitives/$action.ts +10 -1
  116. package/src/server/providers/ServerBodyParserProvider.ts +11 -5
  117. package/src/server/providers/ServerRouterProvider.ts +13 -7
  118. package/src/server-auth/primitives/$auth.ts +7 -0
  119. package/src/server-auth/providers/ServerAuthProvider.ts +51 -8
  120. package/src/server-cookies/index.ts +2 -1
  121. package/src/thread/primitives/$thread.ts +2 -2
  122. package/src/vite/index.ts +0 -2
  123. package/src/vite/tasks/buildServer.ts +3 -4
  124. package/src/vite/tasks/generateCloudflare.ts +35 -19
  125. package/src/vite/tasks/generateDocker.ts +18 -4
  126. package/src/vite/tasks/generateSitemap.ts +5 -7
  127. package/src/vite/tasks/generateVercel.ts +76 -41
  128. package/src/vite/tasks/runAlepha.ts +16 -1
  129. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
  130. package/src/websocket/services/WebSocketClient.ts +3 -3
  131. package/dist/cli/dist-BlfFtOk2.js +0 -2770
  132. package/dist/cli/dist-BlfFtOk2.js.map +0 -1
  133. package/src/api-parameters/controllers/ParameterController.ts +0 -45
  134. package/src/api-parameters/services/ParameterStore.ts +0 -23
@@ -169,8 +169,23 @@ export class CliProvider {
169
169
  root: process.cwd(),
170
170
  };
171
171
 
172
+ // Execute pre-hooks
173
+ const preHooks = this.findPreHooks(command.name);
174
+ for (const hook of preHooks) {
175
+ this.log.debug(`Executing pre-hook for '${command.name}'...`);
176
+ await hook.options.handler(args as CommandHandlerArgs<TObject>);
177
+ }
178
+
179
+ // Execute main command
172
180
  await command.options.handler(args as CommandHandlerArgs<TObject>);
173
181
 
182
+ // Execute post-hooks
183
+ const postHooks = this.findPostHooks(command.name);
184
+ for (const hook of postHooks) {
185
+ this.log.debug(`Executing post-hook for '${command.name}'...`);
186
+ await hook.options.handler(args as CommandHandlerArgs<TObject>);
187
+ }
188
+
174
189
  if (command.options.summary !== false) {
175
190
  runner.summary();
176
191
  }
@@ -185,11 +200,25 @@ export class CliProvider {
185
200
  }
186
201
 
187
202
  protected findCommand(name: string): CommandPrimitive<TObject> | undefined {
188
- return this.commands.find(
203
+ return this.commands.findLast(
189
204
  (command) => command.name === name || command.aliases.includes(name),
190
205
  );
191
206
  }
192
207
 
208
+ /**
209
+ * Find all pre-hooks for a command.
210
+ */
211
+ protected findPreHooks(commandName: string): CommandPrimitive<TObject>[] {
212
+ return this.commands.filter((cmd) => cmd.name === `pre${commandName}`);
213
+ }
214
+
215
+ /**
216
+ * Find all post-hooks for a command.
217
+ */
218
+ protected findPostHooks(commandName: string): CommandPrimitive<TObject>[] {
219
+ return this.commands.filter((cmd) => cmd.name === `post${commandName}`);
220
+ }
221
+
193
222
  /**
194
223
  * Get all global flags including those from the root command (name === "")
195
224
  */
@@ -464,8 +493,8 @@ export class CliProvider {
464
493
  const maxCmdLength = this.getMaxCmdLength(this.commands);
465
494
 
466
495
  for (const command of this.commands) {
467
- // skip root command in list
468
- if (command.name === "") {
496
+ // skip root command and hooks in list
497
+ if (command.name === "" || command.options.hide) {
469
498
  continue;
470
499
  }
471
500
 
@@ -495,11 +524,13 @@ export class CliProvider {
495
524
 
496
525
  private getMaxCmdLength(commands: CommandPrimitive[]): number {
497
526
  return Math.max(
498
- ...commands.map((c) => {
499
- const cmdStr = [c.name, ...c.aliases].join(", ");
500
- const argsUsage = this.generateArgsUsage(c.options.args);
501
- return `${cmdStr}${argsUsage}`.length;
502
- }),
527
+ ...commands
528
+ .filter((c) => !c.options.hide && c.name !== "")
529
+ .map((c) => {
530
+ const cmdStr = [c.name, ...c.aliases].join(", ");
531
+ const argsUsage = this.generateArgsUsage(c.options.args);
532
+ return `${cmdStr}${argsUsage}`.length;
533
+ }),
503
534
  );
504
535
  }
505
536
 
@@ -1,4 +1,4 @@
1
- import type { Static, TObject } from "typebox";
1
+ import type { Record, Static, TObject } from "typebox";
2
2
  import { KIND } from "./constants/KIND.ts";
3
3
  import { MODULE } from "./constants/MODULE.ts";
4
4
  import { OPTIONS } from "./constants/OPTIONS.ts";
@@ -165,6 +165,11 @@ export class Alepha {
165
165
  ...state.env,
166
166
  ...process.env,
167
167
  };
168
+
169
+ // force production mode when building with vite
170
+ if (process.env.NODE_ENV === "production") {
171
+ (state.env as Record<string, string>).NODE_ENV ??= "production";
172
+ }
168
173
  }
169
174
 
170
175
  const alepha = new Alepha(state);
@@ -224,13 +229,6 @@ export class Alepha {
224
229
  */
225
230
  protected starting?: PromiseWithResolvers<this>;
226
231
 
227
- /**
228
- * Initial state of the container.
229
- *
230
- * > Used to initialize the StateManager.
231
- */
232
- protected init: Partial<State>;
233
-
234
232
  /**
235
233
  * During the instantiation process, we keep a list of pending instantiations.
236
234
  * > It allows us to detect circular dependencies.
@@ -276,37 +274,24 @@ export class Alepha {
276
274
  *
277
275
  * Mocked for browser environments.
278
276
  */
279
- public get context(): AlsProvider {
280
- return this.inject(AlsProvider);
281
- }
277
+ public context: AlsProvider;
282
278
 
283
279
  /**
284
280
  * Event manager to handle lifecycle events and custom events.
285
281
  */
286
- public get events(): EventManager {
287
- return this.inject(EventManager, {
288
- args: [() => this.log],
289
- });
290
- }
282
+ public events: EventManager;
291
283
 
292
284
  /**
293
285
  * State manager to store arbitrary values.
294
286
  */
295
- public get store(): StateManager<State> {
296
- this.events; // ensure events is initialized first (TODO: move this to constructor?)
297
- return this.inject(StateManager, {
298
- args: [this.init],
299
- });
300
- }
287
+ public store: StateManager<State>;
301
288
 
302
289
  /**
303
290
  * Codec manager for encoding and decoding data with different formats.
304
291
  *
305
292
  * Supports multiple codec formats (JSON, Protobuf, etc.) with a unified interface.
306
293
  */
307
- public get codec(): CodecManager {
308
- return this.inject(CodecManager);
309
- }
294
+ public codec: CodecManager;
310
295
 
311
296
  /**
312
297
  * Get logger instance.
@@ -322,8 +307,14 @@ export class Alepha {
322
307
  return this.store.get("env") ?? {};
323
308
  }
324
309
 
325
- constructor(init: Partial<State> = {}) {
326
- this.init = init;
310
+ constructor(state: Partial<State> = {}) {
311
+ this.store = this.inject(StateManager, {
312
+ args: [state],
313
+ });
314
+ this.events = this.inject(EventManager);
315
+ this.events.logFn = () => this.log;
316
+ this.context = this.inject(AlsProvider);
317
+ this.codec = this.inject(CodecManager);
327
318
  }
328
319
 
329
320
  public set<T extends TAtomObject>(
@@ -416,11 +407,16 @@ export class Alepha {
416
407
  return false;
417
408
  }
418
409
 
410
+ // Vercel support
419
411
  if (this.env.VERCEL_REGION) {
420
412
  return true;
421
413
  }
422
414
 
423
- if (this.env.ALEPHA_SERVERLESS) {
415
+ // Cloudflare Workers support
416
+ if (
417
+ typeof global === "object" &&
418
+ typeof (global as any).Cloudflare === "object"
419
+ ) {
424
420
  return true;
425
421
  }
426
422
 
@@ -471,8 +467,6 @@ export class Alepha {
471
467
  return this.starting.promise;
472
468
  }
473
469
 
474
- this.codec; // ensure codec is initialized
475
-
476
470
  this.starting = Promise.withResolvers();
477
471
 
478
472
  const now = Date.now();
@@ -832,6 +826,22 @@ export class Alepha {
832
826
  return config as Static<T>;
833
827
  }
834
828
 
829
+ /**
830
+ * Get all environment variable schemas and their parsed values.
831
+ *
832
+ * This is useful for DevTools to display all expected environment variables.
833
+ */
834
+ public getEnvSchemas(): Array<{
835
+ schema: TSchema;
836
+ values: Record<string, any>;
837
+ }> {
838
+ const result: Array<{ schema: TSchema; values: Record<string, any> }> = [];
839
+ for (const [schema, values] of this.cacheEnv.entries()) {
840
+ result.push({ schema, values });
841
+ }
842
+ return result;
843
+ }
844
+
835
845
  // -------------------------------------------------------------------------------------------------------------------
836
846
 
837
847
  /**
@@ -0,0 +1,307 @@
1
+ import type {
2
+ TArrayOptions,
3
+ TNumberOptions,
4
+ TObjectOptions,
5
+ TSchema,
6
+ TStringOptions,
7
+ } from "typebox";
8
+ import { t } from "../providers/TypeProvider.ts";
9
+
10
+ /**
11
+ * JSON Schema representation for conversion to TypeBox.
12
+ */
13
+ export interface JsonSchemaObject {
14
+ type?: string | string[];
15
+ properties?: Record<string, JsonSchemaObject>;
16
+ required?: string[];
17
+ items?: JsonSchemaObject;
18
+ enum?: (string | number | boolean)[];
19
+ const?: unknown;
20
+ format?: string;
21
+ title?: string;
22
+ description?: string;
23
+ default?: unknown;
24
+ minLength?: number;
25
+ maxLength?: number;
26
+ minimum?: number;
27
+ maximum?: number;
28
+ exclusiveMinimum?: number;
29
+ exclusiveMaximum?: number;
30
+ multipleOf?: number;
31
+ pattern?: string;
32
+ minItems?: number;
33
+ maxItems?: number;
34
+ uniqueItems?: boolean;
35
+ // TypeBox internal markers (pass through)
36
+ "~kind"?: string;
37
+ // Not supported
38
+ oneOf?: JsonSchemaObject[];
39
+ anyOf?: JsonSchemaObject[];
40
+ allOf?: JsonSchemaObject[];
41
+ not?: JsonSchemaObject;
42
+ $ref?: string;
43
+ }
44
+
45
+ /**
46
+ * Converts a JSON Schema object to a TypeBox schema using Alepha's type system.
47
+ *
48
+ * This is useful when receiving JSON Schema from an API (e.g., configuration schemas)
49
+ * and needing to use them with TypeBox-based form rendering or validation.
50
+ *
51
+ * **Supports:**
52
+ * - Basic types: string, number, integer, boolean, null, object, array
53
+ * - String formats: email, uuid, date-time, date, time, url/uri, binary, bigint, duration, color
54
+ * - Enums (string enums)
55
+ * - Nested objects with required/optional properties
56
+ * - Arrays with item schemas
57
+ * - Common validation options: minLength, maxLength, minimum, maximum, pattern
58
+ *
59
+ * **Not supported:**
60
+ * - oneOf, anyOf, allOf, not (composition schemas)
61
+ * - $ref (references)
62
+ * - additionalProperties, patternProperties
63
+ *
64
+ * @param schema - JSON Schema object to convert
65
+ * @returns TypeBox TSchema
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const jsonSchema = {
70
+ * type: "object",
71
+ * properties: {
72
+ * email: { type: "string", format: "email" },
73
+ * age: { type: "integer", minimum: 0 },
74
+ * active: { type: "boolean" },
75
+ * },
76
+ * required: ["email"]
77
+ * };
78
+ *
79
+ * const typeBoxSchema = jsonSchemaToTypeBox(jsonSchema);
80
+ * // Equivalent to:
81
+ * // t.object({
82
+ * // email: t.email(),
83
+ * // age: t.optional(t.integer({ minimum: 0 })),
84
+ * // active: t.optional(t.boolean()),
85
+ * // })
86
+ * ```
87
+ */
88
+ export function jsonSchemaToTypeBox(schema: JsonSchemaObject): TSchema {
89
+ // If it already has TypeBox marker, return as-is
90
+ if (schema["~kind"]) {
91
+ return schema as unknown as TSchema;
92
+ }
93
+
94
+ // Handle const (literal)
95
+ if (schema.const !== undefined) {
96
+ return t.const(schema.const as string | number | boolean);
97
+ }
98
+
99
+ // Handle enum
100
+ if (schema.enum && Array.isArray(schema.enum)) {
101
+ // String enum
102
+ if (schema.enum.every((v) => typeof v === "string")) {
103
+ return t.enum(
104
+ schema.enum as string[],
105
+ filterUndefined({
106
+ title: schema.title,
107
+ description: schema.description,
108
+ default: schema.default as string,
109
+ }),
110
+ );
111
+ }
112
+ // For non-string enums, use union of literals
113
+ return t.union(
114
+ schema.enum.map((v) => t.const(v as string | number | boolean)),
115
+ );
116
+ }
117
+
118
+ // Handle type
119
+ const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
120
+
121
+ switch (type) {
122
+ case "string":
123
+ return convertString(schema);
124
+ case "number":
125
+ return convertNumber(schema);
126
+ case "integer":
127
+ return convertInteger(schema);
128
+ case "boolean":
129
+ return convertBoolean(schema);
130
+ case "null":
131
+ return t.null();
132
+ case "object":
133
+ return convertObject(schema);
134
+ case "array":
135
+ return convertArray(schema);
136
+ default:
137
+ // If no type specified but has properties, treat as object
138
+ if (schema.properties) {
139
+ return convertObject(schema);
140
+ }
141
+ // Fallback to any
142
+ return t.any();
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Remove undefined values from an object.
148
+ */
149
+ function filterUndefined<T extends Record<string, unknown>>(
150
+ obj: T,
151
+ ): Partial<T> {
152
+ return Object.fromEntries(
153
+ Object.entries(obj).filter(([_, v]) => v !== undefined),
154
+ ) as Partial<T>;
155
+ }
156
+
157
+ /**
158
+ * Convert JSON Schema string type to TypeBox.
159
+ */
160
+ function convertString(schema: JsonSchemaObject): TSchema {
161
+ const options: TStringOptions = filterUndefined({
162
+ title: schema.title,
163
+ description: schema.description,
164
+ default: schema.default as string,
165
+ minLength: schema.minLength,
166
+ maxLength: schema.maxLength,
167
+ pattern: schema.pattern,
168
+ });
169
+
170
+ switch (schema.format) {
171
+ case "email":
172
+ return t.email(options);
173
+ case "uuid":
174
+ return t.uuid(options);
175
+ case "date-time":
176
+ return t.datetime(options);
177
+ case "date":
178
+ return t.date(options);
179
+ case "time":
180
+ return t.time(options);
181
+ case "url":
182
+ case "uri":
183
+ return t.url(options);
184
+ case "binary":
185
+ return t.binary(options);
186
+ case "bigint":
187
+ return t.bigint(options);
188
+ case "duration":
189
+ return t.duration(options);
190
+ case "color":
191
+ return t.text({ ...options, format: "color" });
192
+ case "e164":
193
+ return t.e164(options);
194
+ case "bcp47":
195
+ return t.bcp47(options);
196
+ default:
197
+ // For unknown formats, preserve the format in text
198
+ if (schema.format) {
199
+ return t.text({ ...options, format: schema.format });
200
+ }
201
+ return t.text(options);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Convert JSON Schema number type to TypeBox.
207
+ */
208
+ function convertNumber(schema: JsonSchemaObject): TSchema {
209
+ return t.number(
210
+ filterUndefined({
211
+ title: schema.title,
212
+ description: schema.description,
213
+ default: schema.default as number,
214
+ minimum: schema.minimum,
215
+ maximum: schema.maximum,
216
+ exclusiveMinimum: schema.exclusiveMinimum,
217
+ exclusiveMaximum: schema.exclusiveMaximum,
218
+ multipleOf: schema.multipleOf,
219
+ }) as TNumberOptions,
220
+ );
221
+ }
222
+
223
+ /**
224
+ * Convert JSON Schema integer type to TypeBox.
225
+ */
226
+ function convertInteger(schema: JsonSchemaObject): TSchema {
227
+ return t.integer(
228
+ filterUndefined({
229
+ title: schema.title,
230
+ description: schema.description,
231
+ default: schema.default as number,
232
+ minimum: schema.minimum,
233
+ maximum: schema.maximum,
234
+ exclusiveMinimum: schema.exclusiveMinimum,
235
+ exclusiveMaximum: schema.exclusiveMaximum,
236
+ multipleOf: schema.multipleOf,
237
+ }) as TNumberOptions,
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Convert JSON Schema boolean type to TypeBox.
243
+ */
244
+ function convertBoolean(schema: JsonSchemaObject): TSchema {
245
+ return t.boolean(
246
+ filterUndefined({
247
+ title: schema.title,
248
+ description: schema.description,
249
+ default: schema.default as boolean,
250
+ }),
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Convert JSON Schema object type to TypeBox.
256
+ */
257
+ function convertObject(schema: JsonSchemaObject): TSchema {
258
+ // No properties means it's a generic object/record
259
+ if (!schema.properties) {
260
+ return t.json(
261
+ filterUndefined({
262
+ title: schema.title,
263
+ description: schema.description,
264
+ }),
265
+ );
266
+ }
267
+
268
+ const required = new Set(schema.required ?? []);
269
+ const properties: Record<string, TSchema> = {};
270
+
271
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
272
+ let converted = jsonSchemaToTypeBox(propSchema);
273
+
274
+ // Wrap in optional if not required
275
+ if (!required.has(key)) {
276
+ converted = t.optional(converted);
277
+ }
278
+
279
+ properties[key] = converted;
280
+ }
281
+
282
+ return t.object(
283
+ properties,
284
+ filterUndefined({
285
+ title: schema.title,
286
+ description: schema.description,
287
+ }) as TObjectOptions,
288
+ );
289
+ }
290
+
291
+ /**
292
+ * Convert JSON Schema array type to TypeBox.
293
+ */
294
+ function convertArray(schema: JsonSchemaObject): TSchema {
295
+ const itemSchema = schema.items ? jsonSchemaToTypeBox(schema.items) : t.any();
296
+
297
+ return t.array(
298
+ itemSchema,
299
+ filterUndefined({
300
+ title: schema.title,
301
+ description: schema.description,
302
+ minItems: schema.minItems,
303
+ maxItems: schema.maxItems,
304
+ uniqueItems: schema.uniqueItems,
305
+ }) as TArrayOptions,
306
+ );
307
+ }
@@ -11,6 +11,7 @@ export * from "./errors/TooLateSubstitutionError.ts";
11
11
  export * from "./errors/TypeBoxError.ts";
12
12
  export * from "./helpers/createPagination.ts";
13
13
  export * from "./helpers/FileLike.ts";
14
+ export * from "./helpers/jsonSchemaToTypeBox.ts";
14
15
  export * from "./helpers/primitive.ts";
15
16
  export * from "./interfaces/Async.ts";
16
17
  export * from "./interfaces/LoggerInterface.ts";
package/src/core/index.ts CHANGED
@@ -5,7 +5,15 @@ import { cpus } from "node:os";
5
5
  import { Alepha } from "./Alepha.ts";
6
6
  import type { RunOptions } from "./interfaces/Run.ts";
7
7
  import type { Service } from "./interfaces/Service.ts";
8
+ import { $module } from "./primitives/$module.ts";
8
9
  import { AlsProvider } from "./providers/AlsProvider.ts";
10
+ import { CodecManager } from "./providers/CodecManager.ts";
11
+ import { EventManager } from "./providers/EventManager.ts";
12
+ import { Json } from "./providers/Json.ts";
13
+ import { JsonSchemaCodec } from "./providers/JsonSchemaCodec.ts";
14
+ import { SchemaCodec } from "./providers/SchemaCodec.ts";
15
+ import { SchemaValidator } from "./providers/SchemaValidator.ts";
16
+ import { StateManager } from "./providers/StateManager.ts";
9
17
 
10
18
  // ---------------------------------------------------------------------------------------------------------------------
11
19
 
@@ -13,6 +21,25 @@ export * from "./index.shared.ts";
13
21
 
14
22
  // ---------------------------------------------------------------------------------------------------------------------
15
23
 
24
+ export const AlephaCore = $module({
25
+ name: "alepha.core",
26
+ services: [
27
+ StateManager,
28
+ CodecManager,
29
+ EventManager,
30
+ AlsProvider,
31
+ Json,
32
+ JsonSchemaCodec,
33
+ SchemaCodec,
34
+ SchemaValidator,
35
+ ],
36
+ register: () => {
37
+ // skip registration, Alepha will handle it
38
+ },
39
+ });
40
+
41
+ // ---------------------------------------------------------------------------------------------------------------------
42
+
16
43
  /**
17
44
  * Run Alepha application, trigger start lifecycle.
18
45
  *
@@ -50,6 +77,7 @@ export const run = (
50
77
  // it's not recommended, we should force 'export default run(alepha)'
51
78
  (globalThis as any).__alepha = alepha;
52
79
 
80
+ // when alepha instance is imported via CLI, use a different global variable
53
81
  if (env.ALEPHA_CLI_IMPORT) {
54
82
  (globalThis as any).__cli_alepha = alepha;
55
83
  }
@@ -63,8 +91,7 @@ export const run = (
63
91
  return alepha;
64
92
  }
65
93
 
66
- // default runner
67
- (async () => {
94
+ setTimeout(async () => {
68
95
  try {
69
96
  await opts?.configure?.(alepha);
70
97
 
@@ -106,7 +133,7 @@ export const run = (
106
133
  process.exit(1);
107
134
  }
108
135
  }
109
- })();
136
+ });
110
137
 
111
138
  return alepha;
112
139
  };
@@ -4,7 +4,7 @@ import type { Async } from "../interfaces/Async.ts";
4
4
  import type { LoggerInterface } from "../interfaces/LoggerInterface.ts";
5
5
 
6
6
  export class EventManager {
7
- protected logFn?: () => LoggerInterface | undefined;
7
+ public logFn?: () => LoggerInterface | undefined;
8
8
 
9
9
  /**
10
10
  * List of events that can be triggered. Powered by $hook().
@@ -54,7 +54,9 @@ export class StateManager<State extends object = AlephaState> {
54
54
  if (!this.atoms.has(key)) {
55
55
  this.atoms.set(key, atom);
56
56
  if (!(key in this.store)) {
57
- this.set(key, atom.options.default as State[keyof State]);
57
+ this.set(key, atom.options.default as State[keyof State], {
58
+ skipContext: true,
59
+ });
58
60
  }
59
61
  }
60
62
 
@@ -85,12 +87,14 @@ export class StateManager<State extends object = AlephaState> {
85
87
  public set<T extends TAtomObject>(
86
88
  target: Atom<T>,
87
89
  value: AtomStatic<T>,
90
+ options?: SetStateOptions,
88
91
  ): this;
89
92
  public set<Key extends keyof State>(
90
93
  target: Key,
91
94
  value: State[Key] | undefined,
95
+ options?: SetStateOptions,
92
96
  ): this;
93
- public set(target: any, value: any): this {
97
+ public set(target: any, value: any, options?: SetStateOptions): this {
94
98
  if (target instanceof Atom) {
95
99
  this.register(target);
96
100
  }
@@ -103,19 +107,21 @@ export class StateManager<State extends object = AlephaState> {
103
107
  return this;
104
108
  }
105
109
 
106
- if (this.als?.exists()) {
110
+ if (options?.skipContext !== true && this.als?.exists()) {
107
111
  this.als.set(key as string, value);
108
112
  } else {
109
113
  store[key] = value;
110
114
  }
111
115
 
112
- this.events
113
- ?.emit(
114
- "state:mutate",
115
- { key: key as keyof AlephaState, value, prevValue },
116
- { catch: true },
117
- )
118
- .catch(() => null);
116
+ if (options?.skipEvents !== true) {
117
+ this.events
118
+ ?.emit(
119
+ "state:mutate",
120
+ { key: key as keyof AlephaState, value, prevValue },
121
+ { catch: true },
122
+ )
123
+ .catch(() => null);
124
+ }
119
125
 
120
126
  return this;
121
127
  }
@@ -156,11 +162,11 @@ export class StateManager<State extends object = AlephaState> {
156
162
  */
157
163
  public push<Key extends keyof OnlyArray<State>>(
158
164
  key: Key,
159
- value: NonNullable<State[Key]> extends Array<infer U> ? U : never,
165
+ ...value: Array<NonNullable<State[Key]> extends Array<infer U> ? U : never>
160
166
  ): this {
161
167
  const current = (this.get(key) ?? []) as Array<any>; // default to empty array
162
168
  if (Array.isArray(current)) {
163
- this.set(key, [...current, value] as State[Key]);
169
+ this.set(key, [...current, ...value] as State[Key]);
164
170
  }
165
171
  return this;
166
172
  }
@@ -184,3 +190,8 @@ export class StateManager<State extends object = AlephaState> {
184
190
  type OnlyArray<T extends object> = {
185
191
  [K in keyof T]: NonNullable<T[K]> extends Array<any> ? K : never;
186
192
  };
193
+
194
+ export interface SetStateOptions {
195
+ skipContext?: boolean;
196
+ skipEvents?: boolean;
197
+ }