alepha 0.13.5 → 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.
- package/dist/api-audits/index.browser.js +116 -0
- package/dist/api-audits/index.browser.js.map +1 -0
- package/dist/api-audits/index.d.ts +1194 -0
- package/dist/api-audits/index.js +674 -0
- package/dist/api-audits/index.js.map +1 -0
- package/dist/api-notifications/index.d.ts +147 -147
- package/dist/api-parameters/index.browser.js +36 -5
- package/dist/api-parameters/index.browser.js.map +1 -1
- package/dist/api-parameters/index.d.ts +711 -33
- package/dist/api-parameters/index.js +831 -17
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +16 -3
- package/dist/api-users/index.js +699 -19
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.js +2 -1
- package/dist/api-verifications/index.js.map +1 -1
- package/dist/bin/index.js +1 -0
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +85 -31
- package/dist/cli/index.js +205 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +67 -6
- package/dist/command/index.js +30 -3
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +241 -61
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +170 -90
- package/dist/core/index.js +264 -67
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +248 -65
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.js +15 -10554
- package/dist/email/index.js.map +1 -1
- package/dist/logger/index.d.ts +4 -4
- package/dist/logger/index.js +77 -72
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.d.ts +5 -1
- package/dist/orm/index.js +24 -7
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +4 -4
- package/dist/redis/index.d.ts +10 -10
- package/dist/security/index.d.ts +28 -28
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +20 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.d.ts +163 -152
- package/dist/server-auth/index.js +40 -10
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cookies/index.js +5 -1
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-links/index.d.ts +33 -33
- package/dist/server-security/index.d.ts +9 -9
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +102 -45
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +3 -3
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js +4 -4
- package/dist/websocket/index.js.map +1 -1
- package/package.json +14 -9
- package/src/api-audits/controllers/AuditController.ts +186 -0
- package/src/api-audits/entities/audits.ts +132 -0
- package/src/api-audits/index.browser.ts +18 -0
- package/src/api-audits/index.ts +58 -0
- package/src/api-audits/primitives/$audit.ts +159 -0
- package/src/api-audits/schemas/auditQuerySchema.ts +23 -0
- package/src/api-audits/schemas/auditResourceSchema.ts +9 -0
- package/src/api-audits/schemas/createAuditSchema.ts +27 -0
- package/src/api-audits/services/AuditService.ts +412 -0
- package/src/api-parameters/controllers/ConfigController.ts +324 -0
- package/src/api-parameters/entities/parameters.ts +93 -10
- package/src/api-parameters/index.ts +43 -4
- package/src/api-parameters/primitives/$config.ts +291 -19
- package/src/api-parameters/schedulers/ConfigActivationScheduler.ts +30 -0
- package/src/api-parameters/services/ConfigStore.ts +491 -0
- package/src/api-users/atoms/realmAuthSettingsAtom.ts +19 -0
- package/src/api-users/controllers/UserRealmController.ts +0 -2
- package/src/api-users/index.ts +2 -0
- package/src/api-users/primitives/$userRealm.ts +18 -3
- package/src/api-users/providers/UserRealmProvider.ts +6 -3
- package/src/api-users/services/RegistrationService.ts +2 -1
- package/src/api-users/services/SessionService.ts +4 -0
- package/src/api-users/services/UserService.ts +3 -0
- package/src/api-verifications/index.ts +7 -1
- package/src/bin/index.ts +1 -0
- package/src/cli/assets/biomeJson.ts +1 -1
- package/src/cli/assets/dummySpecTs.ts +7 -0
- package/src/cli/assets/editorconfig.ts +13 -0
- package/src/cli/assets/mainTs.ts +14 -0
- package/src/cli/commands/BiomeCommands.ts +2 -0
- package/src/cli/commands/CoreCommands.ts +28 -9
- package/src/cli/commands/VerifyCommands.ts +2 -1
- package/src/cli/commands/ViteCommands.ts +8 -9
- package/src/cli/services/AlephaCliUtils.ts +214 -23
- package/src/command/helpers/Asker.ts +0 -1
- package/src/command/primitives/$command.ts +67 -0
- package/src/command/providers/CliProvider.ts +39 -8
- package/src/core/Alepha.ts +40 -30
- package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +30 -3
- package/src/core/providers/EventManager.ts +1 -1
- package/src/core/providers/StateManager.ts +23 -12
- package/src/core/providers/TypeProvider.ts +26 -34
- package/src/logger/index.ts +8 -6
- package/src/logger/primitives/$logger.ts +1 -1
- package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
- package/src/orm/index.ts +6 -0
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +11 -7
- package/src/orm/services/Repository.ts +16 -7
- package/src/orm/services/SqliteModelBuilder.ts +10 -0
- package/src/server/index.ts +6 -0
- package/src/server/primitives/$action.ts +10 -1
- package/src/server/providers/ServerBodyParserProvider.ts +11 -5
- package/src/server/providers/ServerRouterProvider.ts +13 -7
- package/src/server-auth/primitives/$auth.ts +7 -0
- package/src/server-auth/providers/ServerAuthProvider.ts +51 -8
- package/src/server-cookies/index.ts +2 -1
- package/src/thread/primitives/$thread.ts +2 -2
- package/src/vite/index.ts +0 -2
- package/src/vite/tasks/buildServer.ts +3 -4
- package/src/vite/tasks/generateCloudflare.ts +35 -19
- package/src/vite/tasks/generateDocker.ts +18 -4
- package/src/vite/tasks/generateSitemap.ts +5 -7
- package/src/vite/tasks/generateVercel.ts +76 -41
- package/src/vite/tasks/runAlepha.ts +16 -1
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
- package/src/websocket/services/WebSocketClient.ts +3 -3
- package/dist/cli/dist-BlfFtOk2.js +0 -2770
- package/dist/cli/dist-BlfFtOk2.js.map +0 -1
- package/src/api-parameters/controllers/ParameterController.ts +0 -45
- package/src/api-parameters/services/ParameterStore.ts +0 -23
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { $inject, t } from "alepha";
|
|
2
|
+
import { $action } from "alepha/server";
|
|
3
|
+
import type { ParameterStatus } from "../entities/parameters.ts";
|
|
4
|
+
import { ConfigStore } from "../services/ConfigStore.ts";
|
|
5
|
+
|
|
6
|
+
// Define response schemas inline to avoid complex entity schema issues
|
|
7
|
+
const parameterResponseSchema = t.object({
|
|
8
|
+
id: t.uuid(),
|
|
9
|
+
createdAt: t.datetime(),
|
|
10
|
+
updatedAt: t.datetime(),
|
|
11
|
+
name: t.text(),
|
|
12
|
+
content: t.json(),
|
|
13
|
+
schemaHash: t.text(),
|
|
14
|
+
status: t.enum(["expired", "current", "next", "future"]),
|
|
15
|
+
activationDate: t.datetime(),
|
|
16
|
+
expiredAt: t.optional(t.datetime()),
|
|
17
|
+
version: t.integer(),
|
|
18
|
+
changeDescription: t.optional(t.text()),
|
|
19
|
+
tags: t.optional(t.array(t.text())),
|
|
20
|
+
creatorId: t.optional(t.uuid()),
|
|
21
|
+
creatorName: t.optional(t.text()),
|
|
22
|
+
previousContent: t.optional(t.json()),
|
|
23
|
+
migrationLog: t.optional(t.text()),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const treeNodeSchema: any = t.object({
|
|
27
|
+
name: t.text(),
|
|
28
|
+
path: t.text(),
|
|
29
|
+
isLeaf: t.boolean(),
|
|
30
|
+
children: t.array(t.any()),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* REST API controller for versioned configuration management.
|
|
35
|
+
*
|
|
36
|
+
* Provides endpoints for:
|
|
37
|
+
* - Listing all configurations (tree view support)
|
|
38
|
+
* - Getting configuration history (all versions)
|
|
39
|
+
* - Getting current/next configuration values
|
|
40
|
+
* - Creating new configuration versions (immediate or scheduled)
|
|
41
|
+
* - Rolling back to previous versions
|
|
42
|
+
* - Activating scheduled versions immediately
|
|
43
|
+
*/
|
|
44
|
+
export class ConfigController {
|
|
45
|
+
protected readonly store = $inject(ConfigStore);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get tree structure of all configuration names.
|
|
49
|
+
* Useful for admin UI navigation.
|
|
50
|
+
*/
|
|
51
|
+
getConfigTree = $action({
|
|
52
|
+
description:
|
|
53
|
+
"Get tree structure of all configuration names for navigation.",
|
|
54
|
+
path: "/configs/tree",
|
|
55
|
+
method: "GET",
|
|
56
|
+
schema: {
|
|
57
|
+
response: t.array(treeNodeSchema),
|
|
58
|
+
},
|
|
59
|
+
handler: async () => {
|
|
60
|
+
return this.store.getConfigTree();
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List all unique configuration names.
|
|
66
|
+
*/
|
|
67
|
+
listConfigNames = $action({
|
|
68
|
+
description: "List all unique configuration names.",
|
|
69
|
+
path: "/configs",
|
|
70
|
+
method: "GET",
|
|
71
|
+
schema: {
|
|
72
|
+
response: t.object({
|
|
73
|
+
names: t.array(t.text()),
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
handler: async () => {
|
|
77
|
+
const names = await this.store.getConfigNames();
|
|
78
|
+
return { names };
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get configurations by status.
|
|
84
|
+
*/
|
|
85
|
+
getByStatus = $action({
|
|
86
|
+
description: "Get all configurations with a specific status.",
|
|
87
|
+
path: "/configs/status/:status",
|
|
88
|
+
method: "GET",
|
|
89
|
+
schema: {
|
|
90
|
+
params: t.object({
|
|
91
|
+
status: t.enum(["expired", "current", "next", "future"]),
|
|
92
|
+
}),
|
|
93
|
+
response: t.object({
|
|
94
|
+
configs: t.array(parameterResponseSchema),
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
handler: async ({ params }) => {
|
|
98
|
+
const configs = await this.store.getByStatus(
|
|
99
|
+
params.status as ParameterStatus,
|
|
100
|
+
);
|
|
101
|
+
return { configs };
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get version history for a specific configuration.
|
|
107
|
+
*/
|
|
108
|
+
getHistory = $action({
|
|
109
|
+
description: "Get all versions of a specific configuration.",
|
|
110
|
+
path: "/configs/:name/history",
|
|
111
|
+
method: "GET",
|
|
112
|
+
schema: {
|
|
113
|
+
params: t.object({
|
|
114
|
+
name: t.text({
|
|
115
|
+
description: "Configuration name (e.g., app.features.flags)",
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
118
|
+
response: t.object({
|
|
119
|
+
versions: t.array(parameterResponseSchema),
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
handler: async ({ params }) => {
|
|
123
|
+
const versions = await this.store.getHistory(params.name);
|
|
124
|
+
return { versions };
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get current and next values for a configuration.
|
|
130
|
+
* Includes defaultValue and currentValue from the registered primitive
|
|
131
|
+
* even if no versions exist in the database yet.
|
|
132
|
+
*/
|
|
133
|
+
getCurrent = $action({
|
|
134
|
+
description: "Get current and next scheduled values for a configuration.",
|
|
135
|
+
path: "/configs/:name",
|
|
136
|
+
method: "GET",
|
|
137
|
+
schema: {
|
|
138
|
+
params: t.object({
|
|
139
|
+
name: t.text({
|
|
140
|
+
description: "Configuration name (e.g., app.features.flags)",
|
|
141
|
+
}),
|
|
142
|
+
}),
|
|
143
|
+
response: t.object({
|
|
144
|
+
current: t.optional(parameterResponseSchema),
|
|
145
|
+
next: t.optional(parameterResponseSchema),
|
|
146
|
+
defaultValue: t.optional(t.json()),
|
|
147
|
+
currentValue: t.optional(t.json()),
|
|
148
|
+
schema: t.optional(t.json()),
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
handler: async ({ params }) => {
|
|
152
|
+
const result = await this.store.getCurrentWithDefault(params.name);
|
|
153
|
+
return {
|
|
154
|
+
current: result.current ?? undefined,
|
|
155
|
+
next: result.next ?? undefined,
|
|
156
|
+
defaultValue: result.defaultValue ?? undefined,
|
|
157
|
+
currentValue: result.currentValue ?? undefined,
|
|
158
|
+
schema: result.schema ?? undefined,
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get a specific version of a configuration.
|
|
165
|
+
*/
|
|
166
|
+
getVersion = $action({
|
|
167
|
+
description: "Get a specific version of a configuration.",
|
|
168
|
+
path: "/configs/:name/versions/:version",
|
|
169
|
+
method: "GET",
|
|
170
|
+
schema: {
|
|
171
|
+
params: t.object({
|
|
172
|
+
name: t.text(),
|
|
173
|
+
version: t.integer(),
|
|
174
|
+
}),
|
|
175
|
+
response: t.object({
|
|
176
|
+
config: t.optional(parameterResponseSchema),
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
handler: async ({ params }) => {
|
|
180
|
+
const config = await this.store.getVersion(params.name, params.version);
|
|
181
|
+
return { config: config ?? undefined };
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a new configuration version.
|
|
187
|
+
*/
|
|
188
|
+
createVersion = $action({
|
|
189
|
+
description:
|
|
190
|
+
"Create a new version of a configuration (immediate or scheduled).",
|
|
191
|
+
path: "/configs/:name",
|
|
192
|
+
method: "POST",
|
|
193
|
+
schema: {
|
|
194
|
+
params: t.object({
|
|
195
|
+
name: t.text({
|
|
196
|
+
description: "Configuration name (e.g., app.features.flags)",
|
|
197
|
+
}),
|
|
198
|
+
}),
|
|
199
|
+
body: t.object({
|
|
200
|
+
content: t.json({ description: "New configuration content" }),
|
|
201
|
+
schemaHash: t.text({
|
|
202
|
+
description: "Hash of the schema for migration detection",
|
|
203
|
+
}),
|
|
204
|
+
activationDate: t.optional(
|
|
205
|
+
t.datetime({ description: "When to activate (default: now)" }),
|
|
206
|
+
),
|
|
207
|
+
changeDescription: t.optional(
|
|
208
|
+
t.text({ description: "Description of changes" }),
|
|
209
|
+
),
|
|
210
|
+
tags: t.optional(t.array(t.text())),
|
|
211
|
+
creatorId: t.optional(t.uuid()),
|
|
212
|
+
creatorName: t.optional(t.text()),
|
|
213
|
+
}),
|
|
214
|
+
response: parameterResponseSchema,
|
|
215
|
+
},
|
|
216
|
+
handler: async ({ params, body }) => {
|
|
217
|
+
return this.store.save(params.name, body.content, body.schemaHash, {
|
|
218
|
+
activationDate: body.activationDate
|
|
219
|
+
? new Date(body.activationDate)
|
|
220
|
+
: undefined,
|
|
221
|
+
changeDescription: body.changeDescription,
|
|
222
|
+
tags: body.tags,
|
|
223
|
+
creatorId: body.creatorId,
|
|
224
|
+
creatorName: body.creatorName,
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Rollback to a previous version.
|
|
231
|
+
*/
|
|
232
|
+
rollback = $action({
|
|
233
|
+
description:
|
|
234
|
+
"Rollback a configuration to a previous version (creates new version with old content).",
|
|
235
|
+
path: "/configs/:name/rollback",
|
|
236
|
+
method: "POST",
|
|
237
|
+
schema: {
|
|
238
|
+
params: t.object({
|
|
239
|
+
name: t.text(),
|
|
240
|
+
}),
|
|
241
|
+
body: t.object({
|
|
242
|
+
targetVersion: t.integer({
|
|
243
|
+
description: "Version number to rollback to",
|
|
244
|
+
}),
|
|
245
|
+
changeDescription: t.optional(t.text()),
|
|
246
|
+
creatorId: t.optional(t.uuid()),
|
|
247
|
+
creatorName: t.optional(t.text()),
|
|
248
|
+
}),
|
|
249
|
+
response: parameterResponseSchema,
|
|
250
|
+
},
|
|
251
|
+
handler: async ({ params, body }) => {
|
|
252
|
+
return this.store.rollback(params.name, body.targetVersion, {
|
|
253
|
+
changeDescription: body.changeDescription,
|
|
254
|
+
creatorId: body.creatorId,
|
|
255
|
+
creatorName: body.creatorName,
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Activate a scheduled version immediately.
|
|
262
|
+
*/
|
|
263
|
+
activateNow = $action({
|
|
264
|
+
description: "Activate a future/next configuration version immediately.",
|
|
265
|
+
path: "/configs/:name/activate",
|
|
266
|
+
method: "POST",
|
|
267
|
+
schema: {
|
|
268
|
+
params: t.object({
|
|
269
|
+
name: t.text(),
|
|
270
|
+
}),
|
|
271
|
+
body: t.object({
|
|
272
|
+
version: t.integer({ description: "Version number to activate" }),
|
|
273
|
+
creatorId: t.optional(t.uuid()),
|
|
274
|
+
creatorName: t.optional(t.text()),
|
|
275
|
+
}),
|
|
276
|
+
response: parameterResponseSchema,
|
|
277
|
+
},
|
|
278
|
+
handler: async ({ params, body }) => {
|
|
279
|
+
const target = await this.store.getVersion(params.name, body.version);
|
|
280
|
+
if (!target) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Version ${body.version} not found for config ${params.name}`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (target.status === "current") {
|
|
287
|
+
return target; // Already current
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (target.status === "expired") {
|
|
291
|
+
throw new Error(
|
|
292
|
+
"Cannot activate an expired version. Use rollback instead.",
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create new version with same content but immediate activation
|
|
297
|
+
return this.store.save(params.name, target.content, target.schemaHash, {
|
|
298
|
+
changeDescription: `Early activation of version ${body.version}`,
|
|
299
|
+
creatorId: body.creatorId,
|
|
300
|
+
creatorName: body.creatorName,
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Trigger activation check for all scheduled configs.
|
|
307
|
+
* Normally called by a scheduler, but exposed for manual triggering.
|
|
308
|
+
*/
|
|
309
|
+
checkScheduled = $action({
|
|
310
|
+
description:
|
|
311
|
+
"Manually trigger activation check for all scheduled configurations.",
|
|
312
|
+
path: "/configs/activate-scheduled",
|
|
313
|
+
method: "POST",
|
|
314
|
+
schema: {
|
|
315
|
+
response: t.object({
|
|
316
|
+
message: t.text(),
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
319
|
+
handler: async () => {
|
|
320
|
+
await this.store.activateScheduledConfigs();
|
|
321
|
+
return { message: "Scheduled configuration activation check completed" };
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
@@ -1,30 +1,113 @@
|
|
|
1
1
|
import { type Static, t } from "alepha";
|
|
2
2
|
import { $entity, pg } from "alepha/orm";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Parameter status values.
|
|
6
|
+
*
|
|
7
|
+
* - EXPIRED: Past version, no longer active
|
|
8
|
+
* - CURRENT: Currently active version
|
|
9
|
+
* - NEXT: Scheduled to become active (closest future date)
|
|
10
|
+
* - FUTURE: Scheduled for activation after NEXT
|
|
11
|
+
*/
|
|
12
|
+
export type ParameterStatus = "expired" | "current" | "next" | "future";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Configuration parameter entity for versioned configuration management.
|
|
16
|
+
*
|
|
17
|
+
* Stores all versions of configuration parameters with:
|
|
18
|
+
* - Automatic status management (expired, current, next, future)
|
|
19
|
+
* - Schema versioning for migrations
|
|
20
|
+
* - Activation scheduling
|
|
21
|
+
* - Audit trail (creator info)
|
|
22
|
+
*/
|
|
4
23
|
export const parameters = $entity({
|
|
5
24
|
name: "parameters",
|
|
6
25
|
schema: t.object({
|
|
7
26
|
id: pg.primaryKey(t.uuid()),
|
|
8
|
-
|
|
9
27
|
createdAt: pg.createdAt(),
|
|
10
|
-
|
|
11
28
|
updatedAt: pg.updatedAt(),
|
|
12
29
|
|
|
13
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Configuration name using dot notation for tree hierarchy.
|
|
32
|
+
* Examples: "app.features", "app.pricing.tiers", "system.limits"
|
|
33
|
+
*/
|
|
34
|
+
name: t.text(),
|
|
14
35
|
|
|
36
|
+
/**
|
|
37
|
+
* The configuration content as JSON.
|
|
38
|
+
*/
|
|
15
39
|
content: t.json(),
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Schema version hash for detecting schema changes.
|
|
43
|
+
* Used for auto-migration when schema evolves.
|
|
44
|
+
*/
|
|
45
|
+
schemaHash: t.text(),
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Current status of this parameter version.
|
|
49
|
+
*/
|
|
50
|
+
status: pg.default(
|
|
51
|
+
t.enum(["expired", "current", "next", "future"]),
|
|
52
|
+
"future",
|
|
53
|
+
),
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* When this version should become active.
|
|
57
|
+
* Default is immediate (now).
|
|
58
|
+
*/
|
|
59
|
+
activationDate: t.datetime(),
|
|
18
60
|
|
|
61
|
+
/**
|
|
62
|
+
* When this version was deactivated (became expired).
|
|
63
|
+
* Null if still active or scheduled.
|
|
64
|
+
*/
|
|
65
|
+
expiredAt: t.optional(t.datetime()),
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Version number for this configuration.
|
|
69
|
+
* Auto-incremented per config name.
|
|
70
|
+
*/
|
|
71
|
+
version: t.integer(),
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Optional description of changes in this version.
|
|
75
|
+
*/
|
|
76
|
+
changeDescription: t.optional(t.text()),
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Optional tags for filtering/categorization.
|
|
80
|
+
*/
|
|
81
|
+
tags: t.optional(t.array(t.text())),
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creator user ID (if available).
|
|
85
|
+
*/
|
|
19
86
|
creatorId: t.optional(t.uuid()),
|
|
20
87
|
|
|
21
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Creator display name for audit trail.
|
|
90
|
+
*/
|
|
91
|
+
creatorName: t.optional(t.text()),
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Previous content before this change (for rollback reference).
|
|
95
|
+
*/
|
|
96
|
+
previousContent: t.optional(t.json()),
|
|
22
97
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Migration log if schema changed.
|
|
100
|
+
*/
|
|
101
|
+
migrationLog: t.optional(t.text()),
|
|
27
102
|
}),
|
|
103
|
+
indexes: [
|
|
104
|
+
{ columns: ["name", "status"] },
|
|
105
|
+
{ columns: ["name", "activationDate"] },
|
|
106
|
+
{ columns: ["name", "version"], unique: true },
|
|
107
|
+
{ columns: ["status"] },
|
|
108
|
+
{ columns: ["activationDate"] },
|
|
109
|
+
],
|
|
28
110
|
});
|
|
29
111
|
|
|
30
|
-
export type
|
|
112
|
+
export type Parameter = Static<typeof parameters.schema>;
|
|
113
|
+
export type ParameterInsert = Omit<Parameter, "id" | "createdAt" | "updatedAt">;
|
|
@@ -1,21 +1,60 @@
|
|
|
1
1
|
import { $module } from "alepha";
|
|
2
|
+
import { ConfigController } from "./controllers/ConfigController.ts";
|
|
3
|
+
import { ConfigActivationScheduler } from "./schedulers/ConfigActivationScheduler.ts";
|
|
4
|
+
import { ConfigStore } from "./services/ConfigStore.ts";
|
|
2
5
|
|
|
3
6
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
4
7
|
|
|
8
|
+
// Controller exports
|
|
9
|
+
export * from "./controllers/ConfigController.ts";
|
|
10
|
+
// Entity exports
|
|
5
11
|
export * from "./entities/parameters.ts";
|
|
12
|
+
// Primitive exports
|
|
6
13
|
export * from "./primitives/$config.ts";
|
|
14
|
+
// Scheduler exports
|
|
15
|
+
export * from "./schedulers/ConfigActivationScheduler.ts";
|
|
16
|
+
// Service exports
|
|
17
|
+
export * from "./services/ConfigStore.ts";
|
|
7
18
|
|
|
8
19
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
9
20
|
|
|
10
21
|
/**
|
|
11
|
-
* Provides
|
|
22
|
+
* Provides versioned configuration management for Alepha applications.
|
|
12
23
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Type-safe, versioned configuration with `$config` primitive
|
|
26
|
+
* - Schema validation with auto-migration detection
|
|
27
|
+
* - Scheduled activation (FUTURE, NEXT, CURRENT, EXPIRED statuses)
|
|
28
|
+
* - PostgreSQL persistence with full version history
|
|
29
|
+
* - Cross-instance synchronization via topic
|
|
30
|
+
* - Tree view support via dot-notation naming
|
|
31
|
+
* - REST API for configuration management
|
|
32
|
+
* - Automatic activation scheduler
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { Alepha } from "alepha";
|
|
37
|
+
* import { AlephaApiParameters } from "alepha/api-parameters";
|
|
38
|
+
*
|
|
39
|
+
* const alepha = Alepha.create();
|
|
40
|
+
* alepha.with(AlephaApiParameters);
|
|
41
|
+
*
|
|
42
|
+
* // Then use $config in your services:
|
|
43
|
+
* class AppConfig {
|
|
44
|
+
* features = $config({
|
|
45
|
+
* name: "app.features.flags",
|
|
46
|
+
* schema: t.object({
|
|
47
|
+
* enableBeta: t.boolean(),
|
|
48
|
+
* maxUploadSize: t.number()
|
|
49
|
+
* }),
|
|
50
|
+
* default: { enableBeta: false, maxUploadSize: 10485760 }
|
|
51
|
+
* });
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
15
54
|
*
|
|
16
55
|
* @module alepha.api.parameters
|
|
17
56
|
*/
|
|
18
57
|
export const AlephaApiParameters = $module({
|
|
19
58
|
name: "alepha.api.parameters",
|
|
20
|
-
services: [],
|
|
59
|
+
services: [ConfigStore, ConfigController, ConfigActivationScheduler],
|
|
21
60
|
});
|