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.
- 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 +793 -780
- 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/scheduler/index.d.ts +6 -6
- 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.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
|
@@ -24,29 +24,21 @@ import type {
|
|
|
24
24
|
TUnion,
|
|
25
25
|
TUnsafe,
|
|
26
26
|
} from "typebox";
|
|
27
|
-
import * as TypeBox from "typebox";
|
|
28
27
|
import { Type } from "typebox";
|
|
29
28
|
import type {
|
|
30
29
|
TLocalizedValidationError,
|
|
31
30
|
TLocalizedValidationMessageCallback,
|
|
32
31
|
} from "typebox/error";
|
|
33
|
-
import
|
|
32
|
+
import Format from "typebox/format";
|
|
34
33
|
import { Locale } from "typebox/system";
|
|
35
|
-
import * as
|
|
34
|
+
import * as Value from "typebox/value";
|
|
36
35
|
import { OPTIONS } from "../constants/OPTIONS.ts";
|
|
37
36
|
import { AlephaError } from "../errors/AlephaError.ts";
|
|
38
37
|
import type { TypeBoxError } from "../errors/TypeBoxError.ts";
|
|
39
38
|
import { isTypeFile, type TFile, type TStream } from "../helpers/FileLike.ts";
|
|
40
39
|
|
|
41
|
-
export {
|
|
42
|
-
|
|
43
|
-
export const isUUID = TypeBoxFormat.IsUuid;
|
|
44
|
-
export const isEmail = TypeBoxFormat.IsEmail;
|
|
45
|
-
export const isURL = TypeBoxFormat.IsUrl;
|
|
46
|
-
export const isDateTime = TypeBoxFormat.IsDateTime;
|
|
47
|
-
export const isDate = TypeBoxFormat.IsDate;
|
|
48
|
-
export const isTime = TypeBoxFormat.IsTime;
|
|
49
|
-
export const isDuration = TypeBoxFormat.IsDuration;
|
|
40
|
+
export { Type, Value, Format };
|
|
41
|
+
export const isUUID = Format.IsUuid;
|
|
50
42
|
|
|
51
43
|
export type {
|
|
52
44
|
StaticDecode,
|
|
@@ -82,26 +74,26 @@ export type {
|
|
|
82
74
|
export class TypeGuard {
|
|
83
75
|
// -------------------------------------------------------------------------------------------------------------------
|
|
84
76
|
isBigInt = (value: TSchema): value is TString =>
|
|
85
|
-
|
|
77
|
+
Type.IsString(value) && "format" in value && value.format === "bigint";
|
|
86
78
|
isUUID = (value: TSchema): value is TString =>
|
|
87
|
-
|
|
88
|
-
isObject =
|
|
89
|
-
isNumber =
|
|
90
|
-
isString =
|
|
91
|
-
isBoolean =
|
|
92
|
-
isAny =
|
|
93
|
-
isArray =
|
|
94
|
-
isOptional =
|
|
95
|
-
isUnion =
|
|
96
|
-
isInteger =
|
|
97
|
-
isNull =
|
|
98
|
-
isUndefined =
|
|
99
|
-
isUnsafe =
|
|
100
|
-
isRecord =
|
|
101
|
-
isTuple =
|
|
102
|
-
isVoid =
|
|
103
|
-
isLiteral =
|
|
104
|
-
isSchema =
|
|
79
|
+
Type.IsString(value) && "format" in value && value.format === "uuid";
|
|
80
|
+
isObject = Type.IsObject;
|
|
81
|
+
isNumber = Type.IsNumber;
|
|
82
|
+
isString = Type.IsString;
|
|
83
|
+
isBoolean = Type.IsBoolean;
|
|
84
|
+
isAny = Type.IsAny;
|
|
85
|
+
isArray = Type.IsArray;
|
|
86
|
+
isOptional = Type.IsOptional;
|
|
87
|
+
isUnion = Type.IsUnion;
|
|
88
|
+
isInteger = Type.IsInteger;
|
|
89
|
+
isNull = Type.IsNull;
|
|
90
|
+
isUndefined = Type.IsUndefined;
|
|
91
|
+
isUnsafe = Type.IsUnsafe;
|
|
92
|
+
isRecord = Type.IsRecord;
|
|
93
|
+
isTuple = Type.IsTuple;
|
|
94
|
+
isVoid = Type.IsVoid;
|
|
95
|
+
isLiteral = Type.IsLiteral;
|
|
96
|
+
isSchema = Type.IsSchema;
|
|
105
97
|
// -------------------------------------------------------------------------------------------------------------------
|
|
106
98
|
isFile = isTypeFile;
|
|
107
99
|
isDateTime = (schema: unknown) => {
|
|
@@ -131,10 +123,10 @@ declare module "typebox" {
|
|
|
131
123
|
}
|
|
132
124
|
|
|
133
125
|
export class TypeProvider {
|
|
134
|
-
static format =
|
|
126
|
+
static format = Format;
|
|
135
127
|
|
|
136
128
|
static {
|
|
137
|
-
|
|
129
|
+
Format.Set("bigint", (value: string | number) =>
|
|
138
130
|
TypeProvider.isValidBigInt(value),
|
|
139
131
|
);
|
|
140
132
|
}
|
|
@@ -398,7 +390,7 @@ export class TypeProvider {
|
|
|
398
390
|
* Create a schema for a JSON object.
|
|
399
391
|
* This is a record with string keys and any values.
|
|
400
392
|
*/
|
|
401
|
-
public json(options?: TSchemaOptions): TRecord<string, TAny> {
|
|
393
|
+
public json<T = any>(options?: TSchemaOptions): TRecord<string, TAny> {
|
|
402
394
|
return t.record(t.text(), t.any(), options);
|
|
403
395
|
}
|
|
404
396
|
|
package/src/logger/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { $module, type Static, t } from "alepha";
|
|
2
2
|
import { $logger } from "./primitives/$logger.ts";
|
|
3
|
+
import { ConsoleColorProvider } from "./providers/ConsoleColorProvider.ts";
|
|
3
4
|
import { ConsoleDestinationProvider } from "./providers/ConsoleDestinationProvider.ts";
|
|
4
5
|
import { JsonFormatterProvider } from "./providers/JsonFormatterProvider.ts";
|
|
5
6
|
import { LogDestinationProvider } from "./providers/LogDestinationProvider.ts";
|
|
6
7
|
import { LogFormatterProvider } from "./providers/LogFormatterProvider.ts";
|
|
7
8
|
import { MemoryDestinationProvider } from "./providers/MemoryDestinationProvider.ts";
|
|
9
|
+
import { PrettyFormatterProvider } from "./providers/PrettyFormatterProvider.ts";
|
|
8
10
|
import { RawFormatterProvider } from "./providers/RawFormatterProvider.ts";
|
|
9
|
-
import { SimpleFormatterProvider } from "./providers/SimpleFormatterProvider.ts";
|
|
10
11
|
import type { LogEntry } from "./schemas/logEntrySchema.ts";
|
|
11
12
|
import { Logger } from "./services/Logger.ts";
|
|
12
13
|
|
|
@@ -19,7 +20,7 @@ export * from "./providers/JsonFormatterProvider.ts";
|
|
|
19
20
|
export * from "./providers/LogDestinationProvider.ts";
|
|
20
21
|
export * from "./providers/LogFormatterProvider.ts";
|
|
21
22
|
export * from "./providers/MemoryDestinationProvider.ts";
|
|
22
|
-
export * from "./providers/
|
|
23
|
+
export * from "./providers/PrettyFormatterProvider.ts";
|
|
23
24
|
export * from "./schemas/logEntrySchema.ts";
|
|
24
25
|
export * from "./services/Logger.ts";
|
|
25
26
|
|
|
@@ -101,8 +102,9 @@ export const AlephaLogger = $module({
|
|
|
101
102
|
ConsoleDestinationProvider,
|
|
102
103
|
MemoryDestinationProvider,
|
|
103
104
|
JsonFormatterProvider,
|
|
104
|
-
|
|
105
|
+
PrettyFormatterProvider,
|
|
105
106
|
RawFormatterProvider,
|
|
107
|
+
ConsoleColorProvider,
|
|
106
108
|
],
|
|
107
109
|
register: (alepha) => {
|
|
108
110
|
const env = alepha.parseEnv(envSchema);
|
|
@@ -141,14 +143,14 @@ export const AlephaLogger = $module({
|
|
|
141
143
|
if (env.LOG_FORMAT === "raw") {
|
|
142
144
|
return RawFormatterProvider;
|
|
143
145
|
}
|
|
144
|
-
return
|
|
146
|
+
return PrettyFormatterProvider;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
if (alepha.isProduction() && !alepha.isBrowser()) {
|
|
148
150
|
return JsonFormatterProvider;
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
return
|
|
153
|
+
return PrettyFormatterProvider;
|
|
152
154
|
};
|
|
153
155
|
|
|
154
156
|
alepha.with({
|
|
@@ -202,7 +204,7 @@ const envSchema = t.object({
|
|
|
202
204
|
/**
|
|
203
205
|
* Built-in log formats.
|
|
204
206
|
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
|
|
205
|
-
* - "pretty" - Simple text format, human-readable, with colors. {@link
|
|
207
|
+
* - "pretty" - Simple text format, human-readable, with colors. {@link PrettyFormatterProvider}
|
|
206
208
|
* - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
|
|
207
209
|
*/
|
|
208
210
|
LOG_FORMAT: t.optional(
|
|
@@ -26,7 +26,7 @@ export const $logger = (options: LoggerPrimitiveOptions = {}): Logger => {
|
|
|
26
26
|
return $inject(Logger, {
|
|
27
27
|
lifetime: "transient",
|
|
28
28
|
args: [
|
|
29
|
-
options.name ?? service?.name,
|
|
29
|
+
options.name ?? service?.name ?? "Func",
|
|
30
30
|
module?.name ?? alepha.env.MODULE_NAME ?? "app",
|
|
31
31
|
],
|
|
32
32
|
});
|
|
@@ -3,7 +3,7 @@ import type { LogEntry } from "../schemas/logEntrySchema.ts";
|
|
|
3
3
|
import { ConsoleColorProvider } from "./ConsoleColorProvider.ts";
|
|
4
4
|
import { LogFormatterProvider } from "./LogFormatterProvider.ts";
|
|
5
5
|
|
|
6
|
-
export class
|
|
6
|
+
export class PrettyFormatterProvider extends LogFormatterProvider {
|
|
7
7
|
protected color = $inject(ConsoleColorProvider);
|
|
8
8
|
protected alepha = $inject(Alepha);
|
|
9
9
|
|
|
@@ -107,6 +107,15 @@ export class SimpleFormatterProvider extends LogFormatterProvider {
|
|
|
107
107
|
return "";
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
if (this.alepha.isViteDev()) {
|
|
111
|
+
// Node.js - try to fix stack trace with Vite SSR helper
|
|
112
|
+
// Actually, it works only because we have a global helper in viteAlephaDev.ts
|
|
113
|
+
const gl = globalThis as Record<string, unknown>;
|
|
114
|
+
if (typeof gl === "object" && typeof gl.ssrFixStacktrace === "function") {
|
|
115
|
+
gl.ssrFixStacktrace(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
110
119
|
let str = error.stack ?? error.message;
|
|
111
120
|
|
|
112
121
|
const anyError = error as any;
|
package/src/orm/index.ts
CHANGED
|
@@ -10,7 +10,10 @@ import { NodePostgresProvider } from "./providers/drivers/NodePostgresProvider.t
|
|
|
10
10
|
import { NodeSqliteProvider } from "./providers/drivers/NodeSqliteProvider.ts";
|
|
11
11
|
import { PglitePostgresProvider } from "./providers/drivers/PglitePostgresProvider.ts";
|
|
12
12
|
import { RepositoryProvider } from "./providers/RepositoryProvider.ts";
|
|
13
|
+
import { PgJsonQueryManager } from "./services/PgJsonQueryManager.ts";
|
|
14
|
+
import { PgRelationManager } from "./services/PgRelationManager.ts";
|
|
13
15
|
import { PostgresModelBuilder } from "./services/PostgresModelBuilder.ts";
|
|
16
|
+
import { QueryManager } from "./services/QueryManager.ts";
|
|
14
17
|
import { Repository } from "./services/Repository.ts";
|
|
15
18
|
import { SqliteModelBuilder } from "./services/SqliteModelBuilder.ts";
|
|
16
19
|
|
|
@@ -176,6 +179,9 @@ export const AlephaPostgres = $module({
|
|
|
176
179
|
DrizzleKitProvider,
|
|
177
180
|
RepositoryProvider,
|
|
178
181
|
Repository,
|
|
182
|
+
PgRelationManager,
|
|
183
|
+
PgJsonQueryManager,
|
|
184
|
+
QueryManager,
|
|
179
185
|
],
|
|
180
186
|
register: (alepha: Alepha) => {
|
|
181
187
|
const env = alepha.parseEnv(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TObject,
|
|
1
|
+
import { type TObject, t, Value } from "alepha";
|
|
2
2
|
import { getTableName, type SQL, sql } from "drizzle-orm";
|
|
3
3
|
import type { PgSelectBase, PgTableWithColumns } from "drizzle-orm/pg-core";
|
|
4
4
|
import { isSQLWrapper } from "drizzle-orm/sql/sql";
|
|
@@ -103,7 +103,7 @@ export class PgRelationManager {
|
|
|
103
103
|
joins: PgJoin[],
|
|
104
104
|
parentPath?: string,
|
|
105
105
|
): TObject {
|
|
106
|
-
const schema =
|
|
106
|
+
const schema = Value.Clone(baseSchema) as TObject;
|
|
107
107
|
|
|
108
108
|
// Group joins by parent
|
|
109
109
|
const joinsAtThisLevel = joins.filter((j) => j.parent === parentPath);
|
|
@@ -279,6 +279,13 @@ export class PostgresModelBuilder extends ModelBuilder {
|
|
|
279
279
|
return schema(key, value);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
const isTypeEnum = (value: any): value is { enum: any[] } =>
|
|
283
|
+
t.schema.isUnsafe(value) &&
|
|
284
|
+
"type" in value &&
|
|
285
|
+
value.type === "string" &&
|
|
286
|
+
"enum" in value &&
|
|
287
|
+
Array.isArray(value.enum);
|
|
288
|
+
|
|
282
289
|
if (t.schema.isArray(value)) {
|
|
283
290
|
if (t.schema.isObject(value.items)) {
|
|
284
291
|
return schema(key, value);
|
|
@@ -298,16 +305,13 @@ export class PostgresModelBuilder extends ModelBuilder {
|
|
|
298
305
|
if (t.schema.isBoolean(value.items)) {
|
|
299
306
|
return pg.boolean(key).array();
|
|
300
307
|
}
|
|
308
|
+
if (isTypeEnum(value.items)) {
|
|
309
|
+
return pg.text(key).array();
|
|
310
|
+
}
|
|
301
311
|
}
|
|
302
312
|
|
|
303
313
|
// Enum handling
|
|
304
|
-
if (
|
|
305
|
-
t.schema.isUnsafe(value) &&
|
|
306
|
-
"type" in value &&
|
|
307
|
-
value.type === "string" &&
|
|
308
|
-
"enum" in value &&
|
|
309
|
-
Array.isArray(value.enum)
|
|
310
|
-
) {
|
|
314
|
+
if (isTypeEnum(value)) {
|
|
311
315
|
if (!value.enum.every((it) => typeof it === "string")) {
|
|
312
316
|
throw new AlephaError(
|
|
313
317
|
`Enum for ${fieldName} must be an array of strings, got ${JSON.stringify(
|
|
@@ -546,13 +546,15 @@ export abstract class Repository<T extends TObject> {
|
|
|
546
546
|
/**
|
|
547
547
|
* Create many entities.
|
|
548
548
|
*
|
|
549
|
+
* Inserts are batched in chunks of 1000 to avoid hitting database limits.
|
|
550
|
+
*
|
|
549
551
|
* @param values The entities to create.
|
|
550
552
|
* @param opts The statement options.
|
|
551
553
|
* @returns The created entities.
|
|
552
554
|
*/
|
|
553
555
|
public async createMany(
|
|
554
556
|
values: Array<Static<TObjectInsert<T>>>,
|
|
555
|
-
opts: StatementOptions = {},
|
|
557
|
+
opts: StatementOptions & { batchSize?: number } = {},
|
|
556
558
|
): Promise<Static<T>[]> {
|
|
557
559
|
if (values.length === 0) {
|
|
558
560
|
return [];
|
|
@@ -563,19 +565,26 @@ export abstract class Repository<T extends TObject> {
|
|
|
563
565
|
data: values,
|
|
564
566
|
});
|
|
565
567
|
|
|
568
|
+
const batchSize = opts.batchSize ?? 1000;
|
|
569
|
+
const allEntities: Static<T>[] = [];
|
|
570
|
+
|
|
566
571
|
try {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
.
|
|
570
|
-
|
|
572
|
+
for (let i = 0; i < values.length; i += batchSize) {
|
|
573
|
+
const batch = values.slice(i, i + batchSize);
|
|
574
|
+
const entities = await this.rawInsert(opts)
|
|
575
|
+
.values(batch.map((data) => this.cast(data, true)))
|
|
576
|
+
.returning(this.table)
|
|
577
|
+
.then((rows) => rows.map((it) => this.clean(it, this.entity.schema)));
|
|
578
|
+
allEntities.push(...entities);
|
|
579
|
+
}
|
|
571
580
|
|
|
572
581
|
await this.alepha.events.emit("repository:create:after", {
|
|
573
582
|
tableName: this.tableName,
|
|
574
583
|
data: values,
|
|
575
|
-
entity:
|
|
584
|
+
entity: allEntities,
|
|
576
585
|
});
|
|
577
586
|
|
|
578
|
-
return
|
|
587
|
+
return allEntities;
|
|
579
588
|
} catch (error) {
|
|
580
589
|
throw this.handleError(error, "Insert query has failed");
|
|
581
590
|
}
|
|
@@ -190,6 +190,16 @@ export class SqliteModelBuilder extends ModelBuilder {
|
|
|
190
190
|
return pg.integer(key);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
if (t.schema.isBigInt(value)) {
|
|
194
|
+
if (PG_PRIMARY_KEY in value || PG_IDENTITY in value) {
|
|
195
|
+
return pg
|
|
196
|
+
.integer(key, { mode: "number" })
|
|
197
|
+
.primaryKey({ autoIncrement: true });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return pg.integer(key, { mode: "number" });
|
|
201
|
+
}
|
|
202
|
+
|
|
193
203
|
if (t.schema.isNumber(value)) {
|
|
194
204
|
if (PG_IDENTITY in value) {
|
|
195
205
|
return pg
|
package/src/server/index.ts
CHANGED
|
@@ -22,9 +22,12 @@ import { ServerBodyParserProvider } from "./providers/ServerBodyParserProvider.t
|
|
|
22
22
|
import { ServerLoggerProvider } from "./providers/ServerLoggerProvider.ts";
|
|
23
23
|
import { ServerNotReadyProvider } from "./providers/ServerNotReadyProvider.ts";
|
|
24
24
|
import { ServerProvider } from "./providers/ServerProvider.ts";
|
|
25
|
+
import { ServerRouterProvider } from "./providers/ServerRouterProvider.ts";
|
|
25
26
|
import { ServerTimingProvider } from "./providers/ServerTimingProvider.ts";
|
|
26
27
|
import type { FetchOptions, HttpAction } from "./services/HttpClient.ts";
|
|
27
28
|
import { HttpClient } from "./services/HttpClient.ts";
|
|
29
|
+
import { ServerRequestParser } from "./services/ServerRequestParser.ts";
|
|
30
|
+
import { UserAgentParser } from "./services/UserAgentParser.ts";
|
|
28
31
|
|
|
29
32
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
30
33
|
|
|
@@ -132,6 +135,9 @@ export const AlephaServer = $module({
|
|
|
132
135
|
ServerNotReadyProvider,
|
|
133
136
|
ServerTimingProvider,
|
|
134
137
|
HttpClient,
|
|
138
|
+
UserAgentParser,
|
|
139
|
+
ServerRequestParser,
|
|
140
|
+
ServerRouterProvider,
|
|
135
141
|
],
|
|
136
142
|
register: (alepha: Alepha) => {
|
|
137
143
|
if (!alepha.isServerless() && !alepha.isViteDev()) {
|
|
@@ -44,7 +44,16 @@ import {
|
|
|
44
44
|
* - Automatic content-type handling (JSON, form-data, plain text)
|
|
45
45
|
*
|
|
46
46
|
* **URL Generation**
|
|
47
|
-
*
|
|
47
|
+
*
|
|
48
|
+
* **Important:** All `$action` paths are automatically prefixed with `/api`.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* $action({ path: "/users" }) // → GET /api/users
|
|
52
|
+
* $action({ path: "/users/:id" }) // → GET /api/users/:id
|
|
53
|
+
* $action({ path: "/hello" }) // → GET /api/hello
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* This prefix is configurable via the `SERVER_API_PREFIX` environment variable.
|
|
48
57
|
* HTTP method defaults to GET, or POST if body schema is provided.
|
|
49
58
|
*
|
|
50
59
|
* **Common Use Cases**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReadableStream as WebStream } from "node:stream/web";
|
|
2
2
|
import { createBrotliDecompress, createGunzip, createInflate } from "node:zlib";
|
|
3
|
+
import type { TSchema } from "alepha";
|
|
3
4
|
import { $env, $hook, $inject, Alepha, t } from "alepha";
|
|
4
5
|
import { $logger } from "alepha/logger";
|
|
5
6
|
import { HttpError } from "../errors/HttpError.ts";
|
|
@@ -44,7 +45,11 @@ export class ServerBodyParserProvider {
|
|
|
44
45
|
|
|
45
46
|
if (route.schema?.body) {
|
|
46
47
|
try {
|
|
47
|
-
const body = await this.parse(
|
|
48
|
+
const body = await this.parse(
|
|
49
|
+
stream,
|
|
50
|
+
request.headers,
|
|
51
|
+
route.schema.body,
|
|
52
|
+
);
|
|
48
53
|
if (body) {
|
|
49
54
|
request.body = body;
|
|
50
55
|
}
|
|
@@ -68,18 +73,19 @@ export class ServerBodyParserProvider {
|
|
|
68
73
|
public async parse(
|
|
69
74
|
stream: ReadableStream,
|
|
70
75
|
headers: Record<string, string>,
|
|
76
|
+
schema: TSchema,
|
|
71
77
|
): Promise<object | string | undefined> {
|
|
72
78
|
const contentType = headers["content-type"];
|
|
73
79
|
const contentEncoding = headers["content-encoding"];
|
|
74
80
|
|
|
75
81
|
if (!contentType) return undefined;
|
|
76
82
|
|
|
77
|
-
if (contentType.startsWith("
|
|
78
|
-
return this.
|
|
83
|
+
if (contentType.startsWith("text/plain") || t.schema.isString(schema)) {
|
|
84
|
+
return this.parseText(stream, contentEncoding);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
if (contentType.startsWith("
|
|
82
|
-
return this.
|
|
87
|
+
if (contentType.startsWith("application/json")) {
|
|
88
|
+
return this.parseJson(stream, contentEncoding);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
@@ -391,13 +391,19 @@ export class ServerRouterProvider extends RouterProvider<ServerRouteMatcher> {
|
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
if (route.schema?.body) {
|
|
394
|
-
|
|
395
|
-
request.body
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
if (t.schema.isString(route.schema.body)) {
|
|
395
|
+
if (typeof request.body !== "string") {
|
|
396
|
+
throw new ValidationError("Request body is not a string");
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
try {
|
|
400
|
+
request.body = this.alepha.codec.decode(
|
|
401
|
+
route.schema.body,
|
|
402
|
+
request.body,
|
|
403
|
+
);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
throw new ValidationError("Invalid request body", error);
|
|
406
|
+
}
|
|
401
407
|
}
|
|
402
408
|
}
|
|
403
409
|
}
|
|
@@ -261,6 +261,13 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
|
|
|
261
261
|
return this.options.name ?? this.config.propertyKey;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
public get realm(): RealmPrimitive | undefined {
|
|
265
|
+
if ("realm" in this.options) {
|
|
266
|
+
return this.options.realm;
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
264
271
|
public get jwks_uri(): string {
|
|
265
272
|
const jwks = this.oauth?.serverMetadata().jwks_uri;
|
|
266
273
|
if (!jwks) {
|
|
@@ -41,6 +41,7 @@ export class ServerAuthProvider {
|
|
|
41
41
|
httpOnly: true,
|
|
42
42
|
schema: t.object({
|
|
43
43
|
provider: t.text(),
|
|
44
|
+
realm: t.optional(t.text()),
|
|
44
45
|
codeVerifier: t.optional(t.text({ size: "long" })),
|
|
45
46
|
redirectUri: t.optional(t.text({ size: "long" })),
|
|
46
47
|
state: t.optional(t.text()),
|
|
@@ -163,7 +164,7 @@ export class ServerAuthProvider {
|
|
|
163
164
|
protected async cookiesToTokens(
|
|
164
165
|
cookies: Cookies,
|
|
165
166
|
): Promise<Tokens | undefined> {
|
|
166
|
-
const tokens = this.
|
|
167
|
+
const tokens = this.getTokens(cookies);
|
|
167
168
|
if (!tokens) {
|
|
168
169
|
// no cookie, no tokens
|
|
169
170
|
this.log.trace("No tokens found in cookies");
|
|
@@ -247,7 +248,7 @@ export class ServerAuthProvider {
|
|
|
247
248
|
response: userinfoResponseSchema,
|
|
248
249
|
},
|
|
249
250
|
handler: async ({ user, headers, cookies }) => {
|
|
250
|
-
const tokens = this.
|
|
251
|
+
const tokens = this.getTokens(cookies);
|
|
251
252
|
if (tokens) {
|
|
252
253
|
const provider = this.provider(tokens);
|
|
253
254
|
if (!("realm" in provider.options)) {
|
|
@@ -323,6 +324,9 @@ export class ServerAuthProvider {
|
|
|
323
324
|
schema: {
|
|
324
325
|
query: t.object({
|
|
325
326
|
provider: t.text(),
|
|
327
|
+
realm: t.optional(
|
|
328
|
+
t.text({ description: "Realm name for multi-realm setups" }),
|
|
329
|
+
),
|
|
326
330
|
}),
|
|
327
331
|
body: t.object({
|
|
328
332
|
username: t.text(),
|
|
@@ -331,7 +335,11 @@ export class ServerAuthProvider {
|
|
|
331
335
|
response: tokenResponseSchema,
|
|
332
336
|
},
|
|
333
337
|
handler: async ({ query, body, cookies }) => {
|
|
334
|
-
const provider = this.provider(
|
|
338
|
+
const provider = this.provider({
|
|
339
|
+
provider: query.provider,
|
|
340
|
+
realm: query.realm,
|
|
341
|
+
});
|
|
342
|
+
|
|
335
343
|
const realm = "realm" in provider.options && provider.options.realm;
|
|
336
344
|
if (!realm) {
|
|
337
345
|
throw new SecurityError(
|
|
@@ -348,6 +356,8 @@ export class ServerAuthProvider {
|
|
|
348
356
|
);
|
|
349
357
|
}
|
|
350
358
|
|
|
359
|
+
console.log("->", body);
|
|
360
|
+
|
|
351
361
|
let user: UserAccount | undefined;
|
|
352
362
|
try {
|
|
353
363
|
user = await credentials.account(body);
|
|
@@ -392,11 +402,17 @@ export class ServerAuthProvider {
|
|
|
392
402
|
schema: {
|
|
393
403
|
query: t.object({
|
|
394
404
|
provider: t.text(),
|
|
405
|
+
realm: t.optional(
|
|
406
|
+
t.text({ description: "Realm name for multi-realm setups" }),
|
|
407
|
+
),
|
|
395
408
|
redirect_uri: t.optional(t.text({ size: "rich" })),
|
|
396
409
|
}),
|
|
397
410
|
},
|
|
398
411
|
handler: async ({ query, url, reply }) => {
|
|
399
|
-
const provider = this.provider(
|
|
412
|
+
const provider = this.provider({
|
|
413
|
+
provider: query.provider,
|
|
414
|
+
realm: query.realm,
|
|
415
|
+
});
|
|
400
416
|
const oauth = provider.oauth;
|
|
401
417
|
if (!oauth) {
|
|
402
418
|
throw new SecurityError(
|
|
@@ -433,6 +449,7 @@ export class ServerAuthProvider {
|
|
|
433
449
|
nonce: parameters.nonce,
|
|
434
450
|
redirectUri: query.redirect_uri ?? "/",
|
|
435
451
|
provider: query.provider,
|
|
452
|
+
realm: query.realm,
|
|
436
453
|
});
|
|
437
454
|
|
|
438
455
|
reply.redirect(buildAuthorizationUrl(oauth, parameters).toString());
|
|
@@ -456,6 +473,7 @@ export class ServerAuthProvider {
|
|
|
456
473
|
codeVerifier,
|
|
457
474
|
redirectUri: query.redirect_uri ?? "/",
|
|
458
475
|
provider: query.provider,
|
|
476
|
+
realm: query.realm,
|
|
459
477
|
});
|
|
460
478
|
|
|
461
479
|
reply.redirect(buildAuthorizationUrl(oauth, parameters).toString());
|
|
@@ -543,7 +561,7 @@ export class ServerAuthProvider {
|
|
|
543
561
|
},
|
|
544
562
|
handler: async ({ query, reply, cookies }) => {
|
|
545
563
|
const redirect = query.post_logout_redirect_uri ?? "/";
|
|
546
|
-
const tokens = this.
|
|
564
|
+
const tokens = this.getTokens(cookies);
|
|
547
565
|
if (!tokens) {
|
|
548
566
|
reply.redirect(redirect);
|
|
549
567
|
return;
|
|
@@ -603,17 +621,42 @@ export class ServerAuthProvider {
|
|
|
603
621
|
},
|
|
604
622
|
});
|
|
605
623
|
|
|
606
|
-
|
|
624
|
+
/**
|
|
625
|
+
* Find an auth provider by name and optionally by realm.
|
|
626
|
+
* When realm is specified, it filters providers by both name and realm.
|
|
627
|
+
* This enables multi-realm setups where multiple providers share the same name (e.g., "credentials").
|
|
628
|
+
*/
|
|
629
|
+
protected provider(
|
|
630
|
+
opts: string | { provider: string; realm?: string },
|
|
631
|
+
): AuthPrimitive {
|
|
607
632
|
const name = typeof opts === "string" ? opts : opts.provider;
|
|
608
|
-
const
|
|
633
|
+
const realmName = typeof opts === "string" ? undefined : opts.realm;
|
|
634
|
+
|
|
635
|
+
const identity = this.identities.find((identity) => {
|
|
636
|
+
if (identity.name !== name) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// If realm filter is specified, match against provider's realm
|
|
641
|
+
if (realmName && identity.realm?.name !== realmName) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return true;
|
|
646
|
+
});
|
|
609
647
|
|
|
610
648
|
if (!identity) {
|
|
611
|
-
|
|
649
|
+
const realmInfo = realmName ? ` for realm '${realmName}'` : "";
|
|
650
|
+
throw new SecurityError(`Auth provider '${name}'${realmInfo} not found`);
|
|
612
651
|
}
|
|
613
652
|
|
|
614
653
|
return identity;
|
|
615
654
|
}
|
|
616
655
|
|
|
656
|
+
protected getTokens(cookies?: Cookies): Tokens | undefined {
|
|
657
|
+
return this.tokens.get({ cookies });
|
|
658
|
+
}
|
|
659
|
+
|
|
617
660
|
protected setTokens(tokens: Tokens, cookies?: Cookies): void {
|
|
618
661
|
const exp =
|
|
619
662
|
tokens.refresh_token_expires_in ||
|
|
@@ -2,6 +2,7 @@ import { $module } from "alepha";
|
|
|
2
2
|
import { AlephaServer } from "alepha/server";
|
|
3
3
|
import { $cookie, type Cookies } from "./primitives/$cookie.ts";
|
|
4
4
|
import { ServerCookiesProvider } from "./providers/ServerCookiesProvider.ts";
|
|
5
|
+
import { CookieParser } from "./services/CookieParser.ts";
|
|
5
6
|
|
|
6
7
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
7
8
|
|
|
@@ -29,5 +30,5 @@ declare module "alepha/server" {
|
|
|
29
30
|
export const AlephaServerCookies = $module({
|
|
30
31
|
name: "alepha.server.cookies",
|
|
31
32
|
primitives: [$cookie],
|
|
32
|
-
services: [AlephaServer, ServerCookiesProvider],
|
|
33
|
+
services: [AlephaServer, ServerCookiesProvider, CookieParser],
|
|
33
34
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cpus } from "node:os";
|
|
2
2
|
import { MessageChannel, type MessagePort, Worker } from "node:worker_threads";
|
|
3
3
|
import type { TSchema } from "alepha";
|
|
4
|
-
import { createPrimitive, KIND, Primitive,
|
|
4
|
+
import { createPrimitive, KIND, Primitive, Value } from "alepha";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a worker thread primitive for offloading CPU-intensive tasks to separate threads.
|
|
@@ -244,7 +244,7 @@ export class ThreadPrimitive extends Primitive<ThreadPrimitiveOptions> {
|
|
|
244
244
|
public async execute<T = any>(data?: any, schema?: TSchema): Promise<T> {
|
|
245
245
|
if (schema && data) {
|
|
246
246
|
try {
|
|
247
|
-
|
|
247
|
+
Value.Decode(schema, data);
|
|
248
248
|
} catch (error) {
|
|
249
249
|
throw new Error(
|
|
250
250
|
`Invalid data: ${error instanceof Error ? error.message : error}`,
|