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
@@ -1,17 +1,38 @@
1
- import { createPrimitive, Primitive, type Static, type TObject } from "alepha";
1
+ import {
2
+ $hook,
3
+ $inject,
4
+ createPrimitive,
5
+ KIND,
6
+ Primitive,
7
+ type Static,
8
+ type TObject,
9
+ } from "alepha";
10
+ import { $logger } from "alepha/logger";
2
11
  import type { UserAccount } from "alepha/security";
12
+ import { ConfigStore } from "../services/ConfigStore.ts";
3
13
 
4
14
  /**
5
- * Creates a configuration parameter primitive for managing application settings.
15
+ * Creates a versioned configuration primitive for managing application settings.
6
16
  *
7
- * Provides type-safe, versioned configuration with schema validation, default values,
8
- * and scheduled activation. Useful for feature flags, system parameters, and runtime settings.
17
+ * Provides type-safe, versioned configuration with:
18
+ * - Schema validation with auto-migration detection
19
+ * - Default values for initial state
20
+ * - Scheduled activation (FUTURE, NEXT, CURRENT, EXPIRED statuses)
21
+ * - PostgreSQL persistence with full version history
22
+ * - Cross-instance synchronization via topic
23
+ * - Tree view support via dot-notation naming (e.g., "app.features.flags")
24
+ *
25
+ * Integrates with Alepha's atom system for state management:
26
+ * - Uses `alepha.set(atom, value)` for mutations
27
+ * - Listens to `state:mutate` events to detect changes
28
+ * - Auto-persists changes to database
29
+ * - Syncs across instances via topic
9
30
  *
10
31
  * @example
11
32
  * ```ts
12
33
  * class AppConfig {
13
34
  * features = $config({
14
- * name: "feature-flags",
35
+ * name: "app.features.flags",
15
36
  * schema: t.object({
16
37
  * enableBeta: t.boolean(),
17
38
  * maxUploadSize: t.number()
@@ -19,57 +40,283 @@ import type { UserAccount } from "alepha/security";
19
40
  * default: { enableBeta: false, maxUploadSize: 10485760 }
20
41
  * });
21
42
  *
22
- * async updateFeatures() {
43
+ * async enableBeta() {
44
+ * // Immediate activation
45
+ * await this.features.set({ enableBeta: true, maxUploadSize: 20971520 });
46
+ * }
47
+ *
48
+ * async scheduleBetaRelease() {
49
+ * // Schedule for future activation
23
50
  * await this.features.set(
24
51
  * { enableBeta: true, maxUploadSize: 20971520 },
25
- * { user: currentUser, activationDate: tomorrow }
52
+ * { activationDate: new Date('2024-03-01') }
26
53
  * );
27
54
  * }
28
55
  * }
29
56
  * ```
30
57
  */
31
58
  export interface ConfigPrimitiveOptions<T extends TObject> {
59
+ /**
60
+ * Configuration name using dot notation for tree hierarchy.
61
+ * Examples: "app.features", "app.pricing.tiers", "system.limits"
62
+ */
32
63
  name?: string;
64
+
65
+ /**
66
+ * Human-readable description of the configuration.
67
+ */
33
68
  description?: string;
69
+
70
+ /**
71
+ * TypeBox schema defining the configuration structure.
72
+ */
34
73
  schema: T;
74
+
75
+ /**
76
+ * Default value used when no configuration exists in database.
77
+ */
35
78
  default: Static<T>;
36
79
  }
37
80
 
38
81
  export class ConfigPrimitive<T extends TObject> extends Primitive<
39
82
  ConfigPrimitiveOptions<T>
40
83
  > {
41
- public get name() {
84
+ protected readonly log = $logger();
85
+ protected readonly store = $inject(ConfigStore);
86
+
87
+ /** Internal atom key for state management */
88
+ protected atomKey!: string;
89
+
90
+ /** Schema hash for migration detection */
91
+ protected schemaHash!: string;
92
+
93
+ /** Whether we're currently syncing (to avoid loops) */
94
+ protected syncing = false;
95
+
96
+ /** Whether initial load has completed */
97
+ protected loaded = false;
98
+
99
+ /**
100
+ * Configuration name (uses property key if not specified).
101
+ */
102
+ public get name(): string {
42
103
  return this.options.name || this.config.propertyKey;
43
104
  }
44
105
 
106
+ /**
107
+ * The TypeBox schema for this configuration.
108
+ */
45
109
  public get schema(): T {
46
110
  return this.options.schema;
47
111
  }
48
112
 
113
+ /**
114
+ * Get the current configuration value.
115
+ */
49
116
  public get current(): Static<T> {
50
- return this.options.default;
51
- }
52
-
53
- public get next(): Static<T> | undefined {
54
- return undefined;
117
+ return (
118
+ (this.alepha.store.get(this.atomKey as any) as Static<T>) ??
119
+ this.options.default
120
+ );
55
121
  }
56
122
 
123
+ /**
124
+ * Get a specific field from the current configuration.
125
+ */
57
126
  public get<Key extends keyof Static<T>>(key: Key): Static<T>[Key] {
58
127
  return this.current[key];
59
128
  }
60
129
 
61
130
  /**
62
- * Apply a new configuration object.
131
+ * Set a new configuration value.
132
+ *
133
+ * @param value - The new configuration value
134
+ * @param options - Optional settings (activation date, creator info, etc.)
63
135
  */
64
136
  public async set(
65
137
  value: Static<T>,
66
- options: {
67
- user?: UserAccount;
68
- activationDate?: Date; // default to now
138
+ options: SetConfigOptions = {},
139
+ ): Promise<void> {
140
+ // Save to database
141
+ await this.store.save(this.name, value, this.schemaHash, {
142
+ activationDate: options.activationDate,
143
+ changeDescription: options.changeDescription,
144
+ tags: options.tags,
145
+ creatorId: options.user?.id,
146
+ creatorName: options.user?.name ?? options.user?.email,
147
+ });
148
+
149
+ // If immediate activation (no future date), update state
150
+ const now = new Date();
151
+ if (!options.activationDate || options.activationDate <= now) {
152
+ this.syncing = true;
153
+ try {
154
+ this.alepha.store.set(this.atomKey as any, value);
155
+ } finally {
156
+ this.syncing = false;
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Subscribe to configuration changes.
163
+ */
164
+ public sub(fn: (curr: Static<T>) => void): () => void {
165
+ return this.alepha.events.on("state:mutate", {
166
+ callback: ({ key, value }) => {
167
+ if (key === this.atomKey) {
168
+ fn(value as Static<T>);
169
+ }
170
+ },
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Reload configuration from database.
176
+ * Called when scheduled config activates or sync message received.
177
+ */
178
+ public async reload(): Promise<void> {
179
+ const value = await this.store.load<T>(this.name);
180
+ if (value !== null) {
181
+ this.syncing = true;
182
+ try {
183
+ this.alepha.store.set(this.atomKey as any, value, { skipEvents: true });
184
+ } finally {
185
+ this.syncing = false;
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Update from sync message (called by ConfigStore).
192
+ * Uses skipEvents to avoid infinite loops.
193
+ */
194
+ public async updateFromSync(content: unknown): Promise<void> {
195
+ this.syncing = true;
196
+ try {
197
+ this.alepha.store.set(this.atomKey as any, content as Static<T>, {
198
+ skipEvents: true,
199
+ });
200
+ } finally {
201
+ this.syncing = false;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Get version history for this configuration.
207
+ */
208
+ public async getHistory() {
209
+ return this.store.getHistory(this.name);
210
+ }
211
+
212
+ /**
213
+ * Rollback to a specific version.
214
+ */
215
+ public async rollback(
216
+ version: number,
217
+ options?: SetConfigOptions,
218
+ ): Promise<void> {
219
+ await this.store.rollback(this.name, version, {
220
+ changeDescription: options?.changeDescription,
221
+ creatorId: options?.user?.id,
222
+ creatorName: options?.user?.name ?? options?.user?.email,
223
+ });
224
+ await this.reload();
225
+ }
226
+
227
+ /**
228
+ * Hook to load initial value from database on start.
229
+ */
230
+ protected readonly onStart = $hook({
231
+ on: "start",
232
+ handler: async () => {
233
+ await this.loadInitial();
69
234
  },
70
- ): Promise<void> {}
235
+ });
236
+
237
+ /**
238
+ * Called after primitive creation to initialize.
239
+ */
240
+ protected onInit(): void {
241
+ // Create unique key for state management
242
+ this.atomKey = `config:${this.name}`;
243
+
244
+ // Calculate schema hash for migration detection
245
+ this.schemaHash = this.calculateSchemaHash();
246
+
247
+ // Register with store
248
+ this.store.register(this);
249
+
250
+ // Set initial default using key-based state
251
+ this.alepha.store.set(this.atomKey as any, this.options.default, {
252
+ skipEvents: true,
253
+ });
254
+
255
+ // Listen for state mutations to detect external changes via alepha.set()
256
+ // Note: state:mutate is not in Hooks interface, so we use events.on() directly
257
+ this.alepha.events.on("state:mutate", {
258
+ caller: this.config.service,
259
+ callback: async ({ key, value, prevValue }) => {
260
+ // Only handle our key
261
+ if (key !== this.atomKey) {
262
+ return;
263
+ }
264
+
265
+ // Skip if we're syncing (to avoid infinite loop)
266
+ if (this.syncing) {
267
+ return;
268
+ }
269
+
270
+ // Skip if value hasn't actually changed
271
+ if (JSON.stringify(value) === JSON.stringify(prevValue)) {
272
+ return;
273
+ }
274
+
275
+ // Auto-save to database when state is mutated via alepha.set()
276
+ this.log.debug("Config state mutated, persisting to database", {
277
+ name: this.name,
278
+ });
279
+ await this.store.save(this.name, value as Static<T>, this.schemaHash);
280
+ },
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Load initial value from database.
286
+ */
287
+ protected async loadInitial(): Promise<void> {
288
+ if (this.loaded) {
289
+ return;
290
+ }
291
+
292
+ const value = await this.store.load<T>(this.name);
293
+
294
+ if (value !== null) {
295
+ this.syncing = true;
296
+ try {
297
+ this.alepha.store.set(this.atomKey as any, value, { skipEvents: true });
298
+ } finally {
299
+ this.syncing = false;
300
+ }
301
+ }
71
302
 
72
- public sub(fn: (curr: Static<T>) => void): void {}
303
+ this.loaded = true;
304
+ }
305
+
306
+ /**
307
+ * Calculate a hash of the schema for migration detection.
308
+ */
309
+ protected calculateSchemaHash(): string {
310
+ const schemaJson = JSON.stringify(this.options.schema);
311
+ // Simple hash - in production you might want a proper hash function
312
+ let hash = 0;
313
+ for (let i = 0; i < schemaJson.length; i++) {
314
+ const char = schemaJson.charCodeAt(i);
315
+ hash = (hash << 5) - hash + char;
316
+ hash = hash & hash; // Convert to 32bit integer
317
+ }
318
+ return hash.toString(16);
319
+ }
73
320
  }
74
321
 
75
322
  export const $config = <T extends TObject>(
@@ -77,3 +324,28 @@ export const $config = <T extends TObject>(
77
324
  ) => {
78
325
  return createPrimitive(ConfigPrimitive<T>, options);
79
326
  };
327
+
328
+ $config[KIND] = ConfigPrimitive;
329
+
330
+ export interface SetConfigOptions {
331
+ /**
332
+ * User making the change (for audit trail).
333
+ */
334
+ user?: Pick<UserAccount, "id" | "email" | "name">;
335
+
336
+ /**
337
+ * When this configuration should become active.
338
+ * Default is immediate (now).
339
+ */
340
+ activationDate?: Date;
341
+
342
+ /**
343
+ * Description of the change.
344
+ */
345
+ changeDescription?: string;
346
+
347
+ /**
348
+ * Tags for filtering/categorization.
349
+ */
350
+ tags?: string[];
351
+ }
@@ -0,0 +1,30 @@
1
+ import { $inject } from "alepha";
2
+ import { $logger } from "alepha/logger";
3
+ import { $scheduler } from "alepha/scheduler";
4
+ import { ConfigStore } from "../services/ConfigStore.ts";
5
+
6
+ /**
7
+ * Scheduler that periodically checks for scheduled configurations
8
+ * that should be activated.
9
+ *
10
+ * Runs every minute to check if any NEXT configurations have reached
11
+ * their activation date and need to be promoted to CURRENT.
12
+ */
13
+ export class ConfigActivationScheduler {
14
+ protected readonly log = $logger();
15
+ protected readonly store = $inject(ConfigStore);
16
+
17
+ /**
18
+ * Check for scheduled configurations every minute.
19
+ */
20
+ checkActivations = $scheduler({
21
+ name: "config-activation-check",
22
+ description: "Checks for scheduled configurations that should be activated",
23
+ interval: [1, "minute"],
24
+ lock: true,
25
+ handler: async () => {
26
+ this.log.debug("Checking for scheduled config activations");
27
+ await this.store.activateScheduledConfigs();
28
+ },
29
+ });
30
+ }