@zintrust/core 0.1.20 → 0.1.21

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 (98) hide show
  1. package/package.json +2 -1
  2. package/src/boot/Application.d.ts.map +1 -1
  3. package/src/boot/Application.js +48 -10
  4. package/src/boot/bootstrap.js +2 -0
  5. package/src/cli/commands/MigrateCommand.d.ts.map +1 -1
  6. package/src/cli/commands/MigrateCommand.js +36 -3
  7. package/src/cli/d1/D1SqlMigrations.d.ts.map +1 -1
  8. package/src/cli/d1/D1SqlMigrations.js +6 -1
  9. package/src/cli/scaffolding/ControllerGenerator.js +4 -4
  10. package/src/cli/scaffolding/GovernanceScaffolder.js +1 -1
  11. package/src/cli/scaffolding/MigrationGenerator.js +1 -1
  12. package/src/cli/scaffolding/ModelGenerator.js +1 -1
  13. package/src/cli/scaffolding/RouteGenerator.js +1 -1
  14. package/src/cli/scaffolding/ServiceScaffolder.js +4 -4
  15. package/src/config/broadcast.d.ts +14 -28
  16. package/src/config/broadcast.d.ts.map +1 -1
  17. package/src/config/broadcast.js +69 -35
  18. package/src/config/cache.d.ts +13 -45
  19. package/src/config/cache.d.ts.map +1 -1
  20. package/src/config/cache.js +69 -25
  21. package/src/config/database.d.ts +22 -64
  22. package/src/config/database.d.ts.map +1 -1
  23. package/src/config/database.js +99 -31
  24. package/src/config/env.d.ts +6 -0
  25. package/src/config/env.d.ts.map +1 -1
  26. package/src/config/env.js +7 -0
  27. package/src/config/index.d.ts +32 -136
  28. package/src/config/index.d.ts.map +1 -1
  29. package/src/config/mail.d.ts +19 -55
  30. package/src/config/mail.d.ts.map +1 -1
  31. package/src/config/mail.js +63 -21
  32. package/src/config/middleware.d.ts +24 -0
  33. package/src/config/middleware.d.ts.map +1 -1
  34. package/src/config/middleware.js +72 -52
  35. package/src/config/notification.d.ts +14 -27
  36. package/src/config/notification.d.ts.map +1 -1
  37. package/src/config/notification.js +82 -36
  38. package/src/config/queue.d.ts +21 -51
  39. package/src/config/queue.d.ts.map +1 -1
  40. package/src/config/queue.js +72 -27
  41. package/src/config/storage.d.ts +27 -34
  42. package/src/config/storage.d.ts.map +1 -1
  43. package/src/config/storage.js +97 -56
  44. package/src/config/type.d.ts +12 -1
  45. package/src/config/type.d.ts.map +1 -1
  46. package/src/http/parsers/MultipartParser.d.ts.map +1 -1
  47. package/src/http/parsers/MultipartParser.js +69 -42
  48. package/src/index.d.ts +9 -5
  49. package/src/index.d.ts.map +1 -1
  50. package/src/index.js +1 -0
  51. package/src/microservices/PostgresAdapter.d.ts.map +1 -1
  52. package/src/microservices/PostgresAdapter.js +0 -1
  53. package/src/migrations/MigratorFactory.d.ts.map +1 -1
  54. package/src/migrations/MigratorFactory.js +18 -2
  55. package/src/node-singletons/fs.d.ts +1 -1
  56. package/src/node-singletons/fs.d.ts.map +1 -1
  57. package/src/node-singletons/fs.js +1 -1
  58. package/src/orm/Database.d.ts +2 -1
  59. package/src/orm/Database.d.ts.map +1 -1
  60. package/src/orm/Database.js +110 -67
  61. package/src/orm/DatabaseAdapter.d.ts +1 -0
  62. package/src/orm/DatabaseAdapter.d.ts.map +1 -1
  63. package/src/orm/DatabaseRuntimeRegistration.d.ts.map +1 -1
  64. package/src/orm/DatabaseRuntimeRegistration.js +12 -0
  65. package/src/orm/QueryBuilder.d.ts +1 -1
  66. package/src/orm/QueryBuilder.d.ts.map +1 -1
  67. package/src/orm/QueryBuilder.js +4 -3
  68. package/src/orm/adapters/SQLiteAdapter.js +1 -1
  69. package/src/performance/Optimizer.d.ts +6 -6
  70. package/src/performance/Optimizer.d.ts.map +1 -1
  71. package/src/performance/Optimizer.js +133 -52
  72. package/src/routing/doc.d.ts +4 -5
  73. package/src/routing/doc.d.ts.map +1 -1
  74. package/src/routing/doc.js +35 -20
  75. package/src/routing/publicRoot.d.ts +9 -0
  76. package/src/routing/publicRoot.d.ts.map +1 -1
  77. package/src/routing/publicRoot.js +63 -2
  78. package/src/runtime/StartupConfigFileRegistry.d.ts +20 -0
  79. package/src/runtime/StartupConfigFileRegistry.d.ts.map +1 -0
  80. package/src/runtime/StartupConfigFileRegistry.js +44 -0
  81. package/src/runtime/useFileLoader.d.ts +26 -0
  82. package/src/runtime/useFileLoader.d.ts.map +1 -0
  83. package/src/runtime/useFileLoader.js +188 -0
  84. package/src/scripts/TemplateSync.js +4 -4
  85. package/src/security/XssProtection.d.ts.map +1 -1
  86. package/src/security/XssProtection.js +62 -14
  87. package/src/templates/project/basic/config/broadcast.ts.tpl +33 -17
  88. package/src/templates/project/basic/config/cache.ts.tpl +35 -17
  89. package/src/templates/project/basic/config/database.ts.tpl +68 -32
  90. package/src/templates/project/basic/config/logging/HttpLogger.ts.tpl +7 -114
  91. package/src/templates/project/basic/config/mail.ts.tpl +59 -13
  92. package/src/templates/project/basic/config/notification.ts.tpl +28 -17
  93. package/src/templates/project/basic/config/queue.ts.tpl +49 -17
  94. package/src/templates/project/basic/config/storage.ts.tpl +55 -18
  95. package/src/templates/project/basic/config/type.ts.tpl +0 -1
  96. package/src/templates/project/basic/src/index.ts.tpl +3 -0
  97. package/src/templates/project/basic/config/logging/KvLogger.ts.tpl +0 -181
  98. package/src/templates/project/basic/config/logging/SlackLogger.ts.tpl +0 -156
@@ -3,61 +3,25 @@
3
3
  * Runtime mail drivers and settings
4
4
  * Sealed namespace for immutability
5
5
  */
6
- import type { MailDriverConfig } from './type';
7
- export declare const mailConfig: Readonly<{
8
- /**
9
- * Default mail driver
10
- */
11
- readonly default: string;
12
- /**
13
- * Default "From" identity
14
- */
15
- readonly from: {
16
- readonly address: string;
17
- readonly name: string;
6
+ import type { MailConfigInput, MailDriverConfig } from './type';
7
+ export type MailConfigOverrides = Partial<{
8
+ default: string;
9
+ from: {
10
+ address: string;
11
+ name: string;
18
12
  };
19
- /**
20
- * Driver configs
21
- */
22
- readonly drivers: {
23
- readonly disabled: {
24
- readonly driver: "disabled";
25
- };
26
- readonly sendgrid: {
27
- readonly driver: "sendgrid";
28
- readonly apiKey: string;
29
- };
30
- readonly mailgun: {
31
- readonly driver: "mailgun";
32
- readonly apiKey: string;
33
- readonly domain: string;
34
- readonly baseUrl: string;
35
- };
36
- readonly smtp: {
37
- readonly driver: "smtp";
38
- readonly host: string;
39
- readonly port: number;
40
- readonly username: string;
41
- readonly password: string;
42
- readonly secure: boolean | "starttls";
43
- };
44
- readonly nodemailer: {
45
- readonly driver: "nodemailer";
46
- readonly host: string;
47
- readonly port: number;
48
- readonly username: string;
49
- readonly password: string;
50
- readonly secure: boolean | "starttls";
51
- };
52
- readonly ses: {
53
- readonly driver: "ses";
54
- readonly region: string;
55
- };
56
- };
57
- /**
58
- * Get selected driver config
59
- */
60
- readonly getDriver: (name?: string) => MailDriverConfig;
13
+ drivers: Record<string, MailDriverConfig>;
61
14
  }>;
62
- export type MailConfig = typeof mailConfig;
15
+ declare const createMailConfig: () => {
16
+ default: string;
17
+ from: {
18
+ address: string;
19
+ name: string;
20
+ };
21
+ drivers: Record<string, MailDriverConfig>;
22
+ getDriver: (this: MailConfigInput, name?: string) => MailDriverConfig;
23
+ };
24
+ export type MailConfig = ReturnType<typeof createMailConfig>;
25
+ export declare const mailConfig: MailConfig;
26
+ export {};
63
27
  //# sourceMappingURL=mail.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../../src/config/mail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAkHtE,eAAO,MAAM,UAAU;IA7ErB;;OAEG;;IAGH;;OAEG;;;;;IAMH;;OAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsDH;;OAEG;gCACc,MAAM,KAAG,gBAAgB;EAKU,CAAC;AACvD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../../src/config/mail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGtE,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC3C,CAAC,CAAC;AAmCH,QAAA,MAAM,gBAAgB,QAAO;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,SAAS,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAgGvE,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAqB7D,eAAO,MAAM,UAAU,EAAE,UAYvB,CAAC"}
@@ -3,6 +3,7 @@
3
3
  * Runtime mail drivers and settings
4
4
  * Sealed namespace for immutability
5
5
  */
6
+ import { StartupConfigFile, StartupConfigFileRegistry } from '../runtime/StartupConfigFileRegistry.js';
6
7
  import { Env } from './env.js';
7
8
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
8
9
  const isMailDriverConfig = (value) => {
@@ -34,22 +35,16 @@ const getMailDriver = (config, name) => {
34
35
  }
35
36
  throw ErrorFactory.createConfigError(`Mail driver not configured: ${selected}`);
36
37
  };
37
- const mailConfigObj = {
38
- /**
39
- * Default mail driver
40
- */
41
- default: Env.get('MAIL_CONNECTION', Env.get('MAIL_DRIVER', 'disabled')).trim().toLowerCase(),
42
- /**
43
- * Default "From" identity
44
- */
45
- from: {
38
+ const createMailConfig = () => {
39
+ const overrides = StartupConfigFileRegistry.get(StartupConfigFile.Mail) ?? {};
40
+ const baseDefault = Env.get('MAIL_CONNECTION', Env.get('MAIL_DRIVER', 'disabled'))
41
+ .trim()
42
+ .toLowerCase();
43
+ const baseFrom = {
46
44
  address: Env.get('MAIL_FROM_ADDRESS', ''),
47
45
  name: Env.get('MAIL_FROM_NAME', ''),
48
- },
49
- /**
50
- * Driver configs
51
- */
52
- drivers: {
46
+ };
47
+ const baseDrivers = {
53
48
  disabled: {
54
49
  driver: 'disabled',
55
50
  },
@@ -101,12 +96,59 @@ const mailConfigObj = {
101
96
  driver: 'ses',
102
97
  region: Env.get('AWS_REGION', 'us-east-1'),
103
98
  },
99
+ };
100
+ const mailConfigObj = {
101
+ /**
102
+ * Default mail driver
103
+ */
104
+ default: (overrides.default ?? baseDefault).trim().toLowerCase(),
105
+ /**
106
+ * Default "From" identity
107
+ */
108
+ from: {
109
+ ...baseFrom,
110
+ ...(overrides.from ?? {}),
111
+ },
112
+ /**
113
+ * Driver configs
114
+ */
115
+ drivers: {
116
+ ...baseDrivers,
117
+ ...(overrides.drivers ?? {}),
118
+ },
119
+ /**
120
+ * Get selected driver config
121
+ */
122
+ getDriver(name) {
123
+ return getMailDriver(this, name);
124
+ },
125
+ };
126
+ return Object.freeze(mailConfigObj);
127
+ };
128
+ let cached = null;
129
+ const proxyTarget = {};
130
+ const ensureMailConfig = () => {
131
+ if (cached)
132
+ return cached;
133
+ cached = createMailConfig();
134
+ try {
135
+ Object.defineProperties(proxyTarget, Object.getOwnPropertyDescriptors(cached));
136
+ }
137
+ catch {
138
+ // best-effort
139
+ }
140
+ return cached;
141
+ };
142
+ export const mailConfig = new Proxy(proxyTarget, {
143
+ get(_target, prop) {
144
+ return ensureMailConfig()[prop];
104
145
  },
105
- /**
106
- * Get selected driver config
107
- */
108
- getDriver(name) {
109
- return getMailDriver(this, name);
146
+ ownKeys() {
147
+ ensureMailConfig();
148
+ return Reflect.ownKeys(proxyTarget);
110
149
  },
111
- };
112
- export const mailConfig = Object.freeze(mailConfigObj);
150
+ getOwnPropertyDescriptor(_target, prop) {
151
+ ensureMailConfig();
152
+ return Object.getOwnPropertyDescriptor(proxyTarget, prop);
153
+ },
154
+ });
@@ -1,4 +1,28 @@
1
1
  import type { MiddlewareConfigType } from './type';
2
+ export declare enum MiddlewareBody {
3
+ email = "email",
4
+ password = "password",
5
+ name = "name",
6
+ count = "count"
7
+ }
8
+ export type MiddlewaresType = {
9
+ skipPaths: string[];
10
+ fillRateLimit: {
11
+ windowMs: number;
12
+ max: number;
13
+ message: string;
14
+ };
15
+ authRateLimit: {
16
+ windowMs: number;
17
+ max: number;
18
+ message: string;
19
+ };
20
+ userMutationRateLimit: {
21
+ windowMs: number;
22
+ max: number;
23
+ message: string;
24
+ };
25
+ };
2
26
  export declare const MiddlewareKeys: Readonly<{
3
27
  log: true;
4
28
  error: true;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/config/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AA8DzD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;EAiBuB,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AA2KxD,wBAAgB,sBAAsB,IAAI,oBAAoB,CAkB7D;AA0BD,eAAO,MAAM,gBAAgB,EAAE,oBAY7B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/config/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AA8DzD,oBAAY,cAAc;IACxB,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,qBAAqB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;EAiBuB,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AA0MxD,wBAAgB,sBAAsB,IAAI,oBAAoB,CAqB7D;AA0BD,eAAO,MAAM,gBAAgB,EAAE,oBAY7B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { Env } from './env.js';
1
+ import { StartupConfigFile, StartupConfigFileRegistry } from '../runtime/StartupConfigFileRegistry.js';
2
2
  import { bodyParsingMiddleware } from '../http/middleware/BodyParsingMiddleware.js';
3
3
  import { fileUploadMiddleware } from '../http/middleware/FileUploadMiddleware.js';
4
4
  import { AuthMiddleware } from '../middleware/AuthMiddleware.js';
@@ -12,6 +12,13 @@ import { SecurityMiddleware } from '../middleware/SecurityMiddleware.js';
12
12
  import { ValidationMiddleware } from '../middleware/ValidationMiddleware.js';
13
13
  import { Sanitizer } from '../security/Sanitizer.js';
14
14
  import { Schema } from '../validation/Validator.js';
15
+ export var MiddlewareBody;
16
+ (function (MiddlewareBody) {
17
+ MiddlewareBody["email"] = "email";
18
+ MiddlewareBody["password"] = "password";
19
+ MiddlewareBody["name"] = "name";
20
+ MiddlewareBody["count"] = "count";
21
+ })(MiddlewareBody || (MiddlewareBody = {}));
15
22
  export const MiddlewareKeys = Object.freeze({
16
23
  log: true,
17
24
  error: true,
@@ -33,45 +40,60 @@ export const MiddlewareKeys = Object.freeze({
33
40
  // Ensure `ValidationMiddleware` is strongly typed here to avoid `error`-typed values
34
41
  // triggering `@typescript-eslint/no-unsafe-*` rules.
35
42
  const Validation = ValidationMiddleware;
36
- function createRateLimitMiddlewares() {
43
+ const resolveFillRateLimit = (loadMiddlewareConfig) => {
44
+ return {
45
+ windowMs: loadMiddlewareConfig.fillRateLimit?.windowMs ?? 60_000,
46
+ max: loadMiddlewareConfig.fillRateLimit?.max ?? 5,
47
+ message: loadMiddlewareConfig.fillRateLimit?.message ??
48
+ 'Too many requests, please try again after 1 minute.',
49
+ };
50
+ };
51
+ const resolveAuthRateLimit = (loadMiddlewareConfig) => {
52
+ return {
53
+ windowMs: loadMiddlewareConfig.authRateLimit?.windowMs ?? 60_000,
54
+ max: loadMiddlewareConfig.authRateLimit?.max ?? 10,
55
+ message: loadMiddlewareConfig.authRateLimit?.message ??
56
+ 'Too many login attempts, please try again after 1 minute.',
57
+ };
58
+ };
59
+ const resolveUserMutationRateLimit = (loadMiddlewareConfig) => {
60
+ return {
61
+ windowMs: loadMiddlewareConfig.userMutationRateLimit?.windowMs ?? 60_000,
62
+ max: loadMiddlewareConfig.userMutationRateLimit?.max ?? 20,
63
+ message: loadMiddlewareConfig.userMutationRateLimit?.message ??
64
+ 'Too many user requests, please try again after 1 minute.',
65
+ };
66
+ };
67
+ function createRateLimitMiddlewares(loadMiddlewareConfig) {
68
+ const fillRateLimit = RateLimiter.create(resolveFillRateLimit(loadMiddlewareConfig));
69
+ const authRateLimit = RateLimiter.create(resolveAuthRateLimit(loadMiddlewareConfig));
70
+ const userMutationRateLimit = RateLimiter.create(resolveUserMutationRateLimit(loadMiddlewareConfig));
37
71
  return Object.freeze({
38
72
  rateLimit: RateLimiter.create(),
39
- fillRateLimit: RateLimiter.create({
40
- windowMs: 60_000,
41
- max: 5,
42
- message: 'Too many fill requests, please try again later.',
43
- }),
44
- authRateLimit: RateLimiter.create({
45
- windowMs: 60_000,
46
- max: 10,
47
- message: 'Too many authentication requests, please try again later.',
48
- }),
49
- userMutationRateLimit: RateLimiter.create({
50
- windowMs: 60_000,
51
- max: 20,
52
- message: 'Too many write requests, please try again later.',
53
- }),
73
+ fillRateLimit,
74
+ authRateLimit,
75
+ userMutationRateLimit,
54
76
  });
55
77
  }
56
78
  function createAuthValidationMiddlewares() {
57
79
  return {
58
80
  validateLogin: Validation.createBodyWithSanitization(Schema.typed()
59
- .required('email')
60
- .email('email')
61
- .required('password')
62
- .string('password'), {
81
+ .required(MiddlewareBody.email)
82
+ .email(MiddlewareBody.email)
83
+ .required(MiddlewareBody.password)
84
+ .string(MiddlewareBody.password), {
63
85
  email: (v) => Sanitizer.email(v).trim().toLowerCase(),
64
86
  password: (v) => Sanitizer.safePasswordChars(v),
65
87
  }),
66
88
  validateRegister: Validation.createBodyWithSanitization(Schema.typed()
67
- .required('name')
68
- .string('name')
69
- .minLength('name', 1)
70
- .required('email')
71
- .email('email')
72
- .required('password')
73
- .string('password')
74
- .minLength('password', 8), {
89
+ .required(MiddlewareBody.name)
90
+ .string(MiddlewareBody.name)
91
+ .minLength(MiddlewareBody.name, 1)
92
+ .required(MiddlewareBody.email)
93
+ .email(MiddlewareBody.email)
94
+ .required(MiddlewareBody.password)
95
+ .string(MiddlewareBody.password)
96
+ .minLength(MiddlewareBody.password, 8), {
75
97
  name: (v) => Sanitizer.nameText(v).trim(),
76
98
  email: (v) => Sanitizer.email(v).trim().toLowerCase(),
77
99
  password: (v) => Sanitizer.safePasswordChars(v),
@@ -81,32 +103,32 @@ function createAuthValidationMiddlewares() {
81
103
  function createUserValidationMiddlewares() {
82
104
  return {
83
105
  validateUserStore: Validation.createBodyWithSanitization(Schema.typed()
84
- .required('name')
85
- .string('name')
86
- .minLength('name', 1)
87
- .required('email')
88
- .email('email')
89
- .required('password')
90
- .string('password')
91
- .minLength('password', 8), {
106
+ .required(MiddlewareBody.name)
107
+ .string(MiddlewareBody.name)
108
+ .minLength(MiddlewareBody.name, 1)
109
+ .required(MiddlewareBody.email)
110
+ .email(MiddlewareBody.email)
111
+ .required(MiddlewareBody.password)
112
+ .string(MiddlewareBody.password)
113
+ .minLength(MiddlewareBody.password, 8), {
92
114
  name: (v) => Sanitizer.nameText(v).trim(),
93
115
  email: (v) => Sanitizer.email(v).trim().toLowerCase(),
94
116
  password: (v) => Sanitizer.safePasswordChars(v),
95
117
  }),
96
118
  validateUserUpdate: Validation.createBodyWithSanitization(Schema.typed()
97
- .custom('name', (v) => v === undefined || typeof v === 'string', 'name must be a string')
98
- .minLength('name', 1)
99
- .custom('email', (v) => v === undefined || typeof v === 'string', 'email must be a string')
100
- .custom('password', (v) => v === undefined || typeof v === 'string', 'password must be a string')
101
- .minLength('password', 8), {
119
+ .custom(MiddlewareBody.name, (v) => v === undefined || typeof v === 'string', MiddlewareBody.name + ' must be a string')
120
+ .minLength(MiddlewareBody.name, 1)
121
+ .custom(MiddlewareBody.email, (v) => v === undefined || typeof v === 'string', MiddlewareBody.email + ' must be a string')
122
+ .custom(MiddlewareBody.password, (v) => v === undefined || typeof v === 'string', MiddlewareBody.password + ' must be a string')
123
+ .minLength(MiddlewareBody.password, 8), {
102
124
  name: (v) => Sanitizer.nameText(v).trim(),
103
125
  email: (v) => Sanitizer.email(v).trim().toLowerCase(),
104
126
  password: (v) => Sanitizer.safePasswordChars(v),
105
127
  }),
106
128
  validateUserFill: Validation.createBodyWithSanitization(Schema.typed()
107
- .custom('count', (v) => v === undefined || (typeof v === 'number' && Number.isFinite(v)), 'count must be a number')
108
- .min('count', 1)
109
- .max('count', 100)),
129
+ .custom(MiddlewareBody.count, (v) => v === undefined || (typeof v === 'number' && Number.isFinite(v)), MiddlewareBody.count + ' must be a number')
130
+ .min(MiddlewareBody.count, 1)
131
+ .max(MiddlewareBody.count, 100)),
110
132
  };
111
133
  }
112
134
  function createValidationMiddlewares() {
@@ -115,8 +137,8 @@ function createValidationMiddlewares() {
115
137
  ...createUserValidationMiddlewares(),
116
138
  });
117
139
  }
118
- function createSharedMiddlewares() {
119
- const rateLimits = createRateLimitMiddlewares();
140
+ function createSharedMiddlewares(loadMiddlewareConfig) {
141
+ const rateLimits = createRateLimitMiddlewares(loadMiddlewareConfig);
120
142
  const validations = createValidationMiddlewares();
121
143
  return Object.freeze({
122
144
  log: LoggingMiddleware.create(),
@@ -125,10 +147,7 @@ function createSharedMiddlewares() {
125
147
  sanitizeBody: SanitizeBodyMiddleware.create(),
126
148
  ...rateLimits,
127
149
  csrf: CsrfMiddleware.create({
128
- skipPaths: Env.get('CSRF_SKIP_PATHS', '')
129
- .split(',')
130
- .map((p) => p.trim())
131
- .filter((p) => p.length > 0),
150
+ skipPaths: loadMiddlewareConfig?.skipPaths ?? [],
132
151
  }),
133
152
  auth: AuthMiddleware.create(),
134
153
  jwt: JwtAuthMiddleware.create(),
@@ -136,7 +155,8 @@ function createSharedMiddlewares() {
136
155
  });
137
156
  }
138
157
  export function createMiddlewareConfig() {
139
- const shared = createSharedMiddlewares();
158
+ const loadMiddlewareConfig = StartupConfigFileRegistry.get(StartupConfigFile.Middleware) ?? {};
159
+ const shared = createSharedMiddlewares(loadMiddlewareConfig);
140
160
  const middlewareConfigObj = {
141
161
  global: [
142
162
  shared.log,
@@ -5,33 +5,20 @@
5
5
  * Driver selection must be dynamic (tests may mutate process.env).
6
6
  */
7
7
  import type { KnownNotificationDriverConfig, NotificationDrivers, NotificationProviders } from './type';
8
- export declare const notificationConfig: Readonly<{
9
- /**
10
- * Default notification channel name (normalized).
11
- */
12
- readonly default: string;
13
- /**
14
- * Notification channels.
15
- *
16
- * You may add custom named channels (e.g. `opsSlack`, `smsMarketing`) that
17
- * point to any known driver config.
18
- */
19
- readonly drivers: NotificationDrivers;
20
- /**
21
- * Legacy provider configs (kept for backwards compatibility with wrappers).
22
- */
23
- readonly providers: NotificationProviders;
24
- /**
25
- * Normalized notification channel name.
26
- */
27
- readonly getDriverName: () => string;
28
- /**
29
- * Resolve a channel config.
30
- * - Unknown names throw when explicitly selected.
31
- * - `default` is a reserved alias of the configured default.
32
- */
33
- readonly getDriverConfig: (name?: string) => KnownNotificationDriverConfig;
8
+ export type NotificationConfigOverrides = Partial<{
9
+ default: string;
10
+ drivers: NotificationDrivers;
11
+ providers: NotificationProviders;
34
12
  }>;
35
- export type NotificationConfig = typeof notificationConfig;
13
+ type NotificationRuntimeConfig = {
14
+ default: string;
15
+ drivers: NotificationDrivers;
16
+ providers: NotificationProviders;
17
+ getDriverName: () => string;
18
+ getDriverConfig: (name?: string) => KnownNotificationDriverConfig;
19
+ };
20
+ declare const createNotificationConfig: () => NotificationRuntimeConfig;
21
+ export type NotificationConfig = ReturnType<typeof createNotificationConfig>;
22
+ export declare const notificationConfig: NotificationConfig;
36
23
  export default notificationConfig;
37
24
  //# sourceMappingURL=notification.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification.d.ts","sourceRoot":"","sources":["../../../src/config/notification.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,6BAA6B,EAE7B,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,cAAc,CAAC;AAuHtB,eAAO,MAAM,kBAAkB;IA1C7B;;OAEG;sBACY,MAAM;IAIrB;;;;;OAKG;sBACY,mBAAmB;IAKlC;;OAEG;wBACc,qBAAqB;IAItC;;OAEG;kCACc,MAAM;IAIvB;;;;OAIG;sCACoB,MAAM,KAAG,6BAA6B;EAKO,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC;AAE3D,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"notification.d.ts","sourceRoot":"","sources":["../../../src/config/notification.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,6BAA6B,EAE7B,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,cAAc,CAAC;AAGtB,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAAC;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAAC;AAEH,KAAK,yBAAyB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC;IACjC,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,6BAA6B,CAAC;CACnE,CAAC;AA2EF,QAAA,MAAM,wBAAwB,QAAO,yBA+DpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAqB7E,eAAO,MAAM,kBAAkB,EAAE,kBAY/B,CAAC;AAEH,eAAe,kBAAkB,CAAC"}
@@ -4,6 +4,7 @@
4
4
  * Config-first mapping of notification providers/channels.
5
5
  * Driver selection must be dynamic (tests may mutate process.env).
6
6
  */
7
+ import { StartupConfigFile, StartupConfigFileRegistry } from '../runtime/StartupConfigFileRegistry.js';
7
8
  import { Env } from './env.js';
8
9
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
9
10
  const normalizeName = (value) => value.trim().toLowerCase();
@@ -61,43 +62,88 @@ const getBaseProviders = () => {
61
62
  },
62
63
  };
63
64
  };
64
- const notificationConfigObj = {
65
- /**
66
- * Default notification channel name (normalized).
67
- */
68
- get default() {
69
- return getDefaultChannel(this.drivers);
70
- },
71
- /**
72
- * Notification channels.
73
- *
74
- * You may add custom named channels (e.g. `opsSlack`, `smsMarketing`) that
75
- * point to any known driver config.
76
- */
77
- get drivers() {
78
- // Return a record of channels; can be extended by app-level config.
79
- return getBaseProviders();
80
- },
81
- /**
82
- * Legacy provider configs (kept for backwards compatibility with wrappers).
83
- */
84
- get providers() {
85
- return getBaseProviders();
65
+ const createNotificationConfig = () => {
66
+ const overrides = StartupConfigFileRegistry.get(StartupConfigFile.Notification) ??
67
+ {};
68
+ const notificationConfigObj = {
69
+ /**
70
+ * Default notification channel name (normalized).
71
+ */
72
+ get default() {
73
+ const overrideDefault = overrides.default;
74
+ if (typeof overrideDefault === 'string' && overrideDefault.trim().length > 0) {
75
+ const value = normalizeName(overrideDefault);
76
+ if (value.length > 0 && hasOwn(this.drivers, value))
77
+ return value;
78
+ throw ErrorFactory.createConfigError(`Notification channel not configured: ${value}`);
79
+ }
80
+ return getDefaultChannel(this.drivers);
81
+ },
82
+ /**
83
+ * Notification channels.
84
+ *
85
+ * You may add custom named channels (e.g. `opsSlack`, `smsMarketing`) that
86
+ * point to any known driver config.
87
+ */
88
+ get drivers() {
89
+ // Return a record of channels; can be extended by app-level config.
90
+ return {
91
+ ...getBaseProviders(),
92
+ ...(overrides.providers ?? {}),
93
+ ...(overrides.drivers ?? {}),
94
+ };
95
+ },
96
+ /**
97
+ * Legacy provider configs (kept for backwards compatibility with wrappers).
98
+ */
99
+ get providers() {
100
+ return {
101
+ ...getBaseProviders(),
102
+ ...(overrides.providers ?? {}),
103
+ };
104
+ },
105
+ /**
106
+ * Normalized notification channel name.
107
+ */
108
+ getDriverName() {
109
+ return normalizeName(this.default);
110
+ },
111
+ /**
112
+ * Resolve a channel config.
113
+ * - Unknown names throw when explicitly selected.
114
+ * - `default` is a reserved alias of the configured default.
115
+ */
116
+ getDriverConfig(name) {
117
+ return getNotificationDriver(this, name);
118
+ },
119
+ };
120
+ return Object.freeze(notificationConfigObj);
121
+ };
122
+ let cached = null;
123
+ const proxyTarget = {};
124
+ const ensureNotificationConfig = () => {
125
+ if (cached)
126
+ return cached;
127
+ cached = createNotificationConfig();
128
+ try {
129
+ Object.defineProperties(proxyTarget, Object.getOwnPropertyDescriptors(cached));
130
+ }
131
+ catch {
132
+ // best-effort
133
+ }
134
+ return cached;
135
+ };
136
+ export const notificationConfig = new Proxy(proxyTarget, {
137
+ get(_target, prop) {
138
+ return ensureNotificationConfig()[prop];
86
139
  },
87
- /**
88
- * Normalized notification channel name.
89
- */
90
- getDriverName() {
91
- return normalizeName(this.default);
140
+ ownKeys() {
141
+ ensureNotificationConfig();
142
+ return Reflect.ownKeys(proxyTarget);
92
143
  },
93
- /**
94
- * Resolve a channel config.
95
- * - Unknown names throw when explicitly selected.
96
- * - `default` is a reserved alias of the configured default.
97
- */
98
- getDriverConfig(name) {
99
- return getNotificationDriver(this, name);
144
+ getOwnPropertyDescriptor(_target, prop) {
145
+ ensureNotificationConfig();
146
+ return Object.getOwnPropertyDescriptor(proxyTarget, prop);
100
147
  },
101
- };
102
- export const notificationConfig = Object.freeze(notificationConfigObj);
148
+ });
103
149
  export default notificationConfig;