@things-factory/setting-base 4.3.752 → 4.3.764

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/README.md ADDED
@@ -0,0 +1,523 @@
1
+ # Setting Base - Extensible Validation System
2
+
3
+ This package provides a common setting management system with an extensible validation framework that allows each application (operato-wms, operato-hub, etc.) to register their own validators for specific settings without modifying the core mutation functions.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Architecture](#architecture)
9
+ - [Getting Started](#getting-started)
10
+ - [Usage Guide](#usage-guide)
11
+ - [Validator Types](#validator-types)
12
+ - [Examples](#examples)
13
+ - [Best Practices](#best-practices)
14
+ - [Error Handling](#error-handling)
15
+ - [Testing](#testing)
16
+ - [Troubleshooting](#troubleshooting)
17
+
18
+ ## Overview
19
+
20
+ The validation system uses a **registry pattern** that allows each application to register their own validators for specific settings. This design:
21
+
22
+ - ✅ Keeps `setting-mutation.ts` as a common function
23
+ - ✅ Avoids circular dependencies
24
+ - ✅ Allows applications to add custom validation logic
25
+ - ✅ Supports both exact name matching and pattern matching
26
+ - ✅ Works seamlessly with the shared `setting-ui` component
27
+
28
+ ## Architecture
29
+
30
+ ### Dependency Flow
31
+
32
+ The validator system is designed to avoid circular dependencies:
33
+
34
+ ```
35
+ ┌─────────────────┐
36
+ │ setting-base │ (Common package - no app dependencies)
37
+ │ │
38
+ │ - Registry │
39
+ │ - Mutations │
40
+ └────────┬────────┘
41
+ │ exports registry
42
+
43
+
44
+ ┌─────────────────┐ ┌─────────────────┐
45
+ │ operato-wms │ │ operato-hub │
46
+ │ │ │ │
47
+ │ - Validators │ │ - Validators │
48
+ │ - Registers │ │ - Registers │
49
+ └────────┬────────┘ └────────┬────────┘
50
+ │ │
51
+ │ imports registry │ imports registry
52
+ │ │
53
+ └────────┬───────────────┘
54
+
55
+
56
+ ┌─────────────────┐
57
+ │ setting-ui │
58
+ │ │
59
+ │ - UI Component │
60
+ │ - Uses mutations│
61
+ └──────────────────┘
62
+ ```
63
+
64
+ ### Key Components
65
+
66
+ - **`setting-validator.ts`**: Contains the validator registry and validation logic
67
+ - **`setting-mutation.ts`**: Common mutation functions that call validators before saving
68
+ - **Application validators**: Each application registers its own validators
69
+
70
+ ### How It Works
71
+
72
+ 1. **Module Load Phase**: Applications register validators when their server code is imported
73
+ 2. **Schema Build Phase**: GraphQL schema is built with mutation resolvers
74
+ 3. **Runtime Phase**: When a setting is created/updated, the mutation function calls registered validators
75
+ 4. **Validation Result**: If validation fails, an error is thrown before saving; if it passes, the setting is saved normally
76
+
77
+ ### Package Dependencies
78
+
79
+ ```
80
+ setting-base:
81
+ - No dependencies on applications ✅
82
+ - Only depends on: auth-base, code-base
83
+
84
+ operato-wms:
85
+ - Depends on: setting-base ✅
86
+ - Registers validators at module load ✅
87
+
88
+ operato-hub:
89
+ - Depends on: setting-base ✅
90
+ - Registers validators at module load ✅
91
+
92
+ setting-ui:
93
+ - Depends on: setting-base ✅
94
+ - Uses mutations (which call validators) ✅
95
+ ```
96
+
97
+ ## Getting Started
98
+
99
+ ### Step 1: Create Validator Functions
100
+
101
+ Create a validator file in your application (e.g., `packages/operato-wms/server/validators/setting-validators.ts`):
102
+
103
+ ```typescript
104
+ import { settingValidatorRegistry, SettingValidationResult } from '@things-factory/setting-base'
105
+ import { Domain } from '@things-factory/shell'
106
+ import { User } from '@things-factory/auth-base'
107
+ import { NewSetting, SettingPatch } from '@things-factory/setting-base'
108
+ import { Setting } from '@things-factory/setting-base'
109
+
110
+ // Validator for creating a setting
111
+ async function validateBatchPickingLimit(
112
+ setting: NewSetting,
113
+ context: { domain: Domain; user: User }
114
+ ): Promise<SettingValidationResult> {
115
+ if (!setting.value) {
116
+ return { valid: false, error: 'batch-picking-limit value is required' }
117
+ }
118
+
119
+ const limit = parseInt(setting.value, 10)
120
+ if (isNaN(limit) || limit < 1 || limit > 1000) {
121
+ return {
122
+ valid: false,
123
+ error: 'batch-picking-limit must be a number between 1 and 1000'
124
+ }
125
+ }
126
+
127
+ return { valid: true }
128
+ }
129
+
130
+ // Validator for updating a setting
131
+ async function validateBatchPickingLimitUpdate(
132
+ existingSetting: Setting,
133
+ patch: SettingPatch,
134
+ context: { domain: Domain; user: User }
135
+ ): Promise<SettingValidationResult> {
136
+ // Only validate if value is being updated
137
+ if (patch.value === undefined) {
138
+ return { valid: true }
139
+ }
140
+
141
+ return await validateBatchPickingLimit({ ...existingSetting, value: patch.value } as NewSetting, context)
142
+ }
143
+
144
+ // Register function
145
+ export function registerOperatoWmsSettingValidators(): void {
146
+ settingValidatorRegistry.registerCreateValidator('batch-picking-limit', validateBatchPickingLimit)
147
+ settingValidatorRegistry.registerUpdateValidator('batch-picking-limit', validateBatchPickingLimitUpdate)
148
+ }
149
+ ```
150
+
151
+ ### Step 2: Register Validators at Module Load Time
152
+
153
+ **IMPORTANT**: Validators MUST be registered at module load time (when your application's server code is imported), NOT in bootstrap events. This ensures validators are registered before the GraphQL schema is built.
154
+
155
+ In your application's server entry file (e.g., `packages/operato-wms/server/index.ts`):
156
+
157
+ ```typescript
158
+ export * from './graphql'
159
+ export * from './migrations'
160
+ export * from './entities'
161
+
162
+ import './routes'
163
+
164
+ // Register setting validators at module load time (before schema is built)
165
+ // This ensures validators are available when mutations are called
166
+ import { registerOperatoWmsSettingValidators } from './validators/setting-validators'
167
+ registerOperatoWmsSettingValidators()
168
+ ```
169
+
170
+ **Why module load time?**
171
+
172
+ - The GraphQL schema is built during server startup, before `bootstrap-module-start` event
173
+ - Validators are called at runtime when mutations execute
174
+ - Registering at module load ensures validators are ready before any mutations are called
175
+ - No circular dependencies because `setting-base` doesn't import application packages
176
+
177
+ ## Usage Guide
178
+
179
+ ### Registering Validators
180
+
181
+ #### Exact Name Matching
182
+
183
+ Register validators for specific setting names:
184
+
185
+ ```typescript
186
+ settingValidatorRegistry.registerCreateValidator('batch-picking-limit', validateBatchPickingLimit)
187
+ settingValidatorRegistry.registerUpdateValidator('batch-picking-limit', validateBatchPickingLimitUpdate)
188
+ ```
189
+
190
+ #### Pattern Matching
191
+
192
+ For settings that follow a naming pattern, you can use pattern matching:
193
+
194
+ ```typescript
195
+ // Register validators using regex pattern
196
+ settingValidatorRegistry.registerPatternValidator(
197
+ /^rule-for-.*$/,
198
+ validateLocationSortingRule,
199
+ validateLocationSortingRule
200
+ )
201
+
202
+ // Or use a function for more complex matching
203
+ settingValidatorRegistry.registerPatternValidator(
204
+ (name: string) => name.startsWith('enable-') || name.startsWith('disable-'),
205
+ validateBooleanSetting,
206
+ validateBooleanSetting
207
+ )
208
+ ```
209
+
210
+ ### Complete Flow Example
211
+
212
+ 1. **Application Registers Validators**:
213
+
214
+ ```typescript
215
+ // packages/operato-wms/server/index.ts
216
+ import { registerOperatoWmsSettingValidators } from './validators/setting-validators'
217
+ registerOperatoWmsSettingValidators() // Module loads → validators registered
218
+ ```
219
+
220
+ 2. **Server Starts**:
221
+
222
+ ```typescript
223
+ // shell/server/server.ts
224
+ const builtSchema = await schema() // Schema built with SettingMutation resolver
225
+ ```
226
+
227
+ 3. **User Makes Request**:
228
+ ```typescript
229
+ // User calls: mutation { createSetting(setting: {...}) }
230
+ // setting-base/server/service/setting/setting-mutation.ts
231
+ async createSetting(...) {
232
+ await settingValidatorRegistry.validateCreate(setting, { domain, user })
233
+ // ↑ Calls registered validators from operato-wms
234
+ }
235
+ ```
236
+
237
+ ## Validator Types
238
+
239
+ ### SettingCreateValidator
240
+
241
+ ```typescript
242
+ type SettingCreateValidator = (
243
+ setting: NewSetting,
244
+ context: { domain: Domain; user: User }
245
+ ) => Promise<SettingValidationResult> | SettingValidationResult
246
+ ```
247
+
248
+ ### SettingUpdateValidator
249
+
250
+ ```typescript
251
+ type SettingUpdateValidator = (
252
+ setting: Setting,
253
+ patch: SettingPatch,
254
+ context: { domain: Domain; user: User }
255
+ ) => Promise<SettingValidationResult> | SettingValidationResult
256
+ ```
257
+
258
+ ### SettingValidationResult
259
+
260
+ ```typescript
261
+ interface SettingValidationResult {
262
+ valid: boolean
263
+ error?: string // Error message if validation fails
264
+ }
265
+ ```
266
+
267
+ ## Examples
268
+
269
+ ### Example 1: Validate JSON Format
270
+
271
+ ```typescript
272
+ async function validateStackingOptionLimit(
273
+ setting: NewSetting,
274
+ context: { domain: Domain; user: User }
275
+ ): Promise<SettingValidationResult> {
276
+ if (!setting.value) {
277
+ return { valid: false, error: 'stacking-option-limit value is required' }
278
+ }
279
+
280
+ try {
281
+ const parsed = JSON.parse(setting.value)
282
+ if (typeof parsed.Min !== 'number' || typeof parsed.Max !== 'number') {
283
+ return {
284
+ valid: false,
285
+ error: 'stacking-option-limit must have Min and Max as numbers'
286
+ }
287
+ }
288
+ if (parsed.Min < 0 || parsed.Max < parsed.Min) {
289
+ return {
290
+ valid: false,
291
+ error: 'Min must be >= 0 and Max must be >= Min'
292
+ }
293
+ }
294
+ } catch (e) {
295
+ return {
296
+ valid: false,
297
+ error: 'stacking-option-limit must be valid JSON: { "Min": 1, "Max": 1 }'
298
+ }
299
+ }
300
+
301
+ return { valid: true }
302
+ }
303
+ ```
304
+
305
+ ### Example 2: Validate Boolean Setting
306
+
307
+ ```typescript
308
+ async function validateBooleanSetting(
309
+ setting: NewSetting,
310
+ context: { domain: Domain; user: User }
311
+ ): Promise<SettingValidationResult> {
312
+ if (setting.value && setting.value !== 'true' && setting.value !== 'false') {
313
+ return {
314
+ valid: false,
315
+ error: `${setting.name} must be "true" or "false"`
316
+ }
317
+ }
318
+ return { valid: true }
319
+ }
320
+
321
+ // Register for multiple boolean settings
322
+ const booleanSettings = ['enable-bin-picking', 'enable-product-scanning', 'enable-beta-feature']
323
+ booleanSettings.forEach(settingName => {
324
+ settingValidatorRegistry.registerCreateValidator(settingName, validateBooleanSetting)
325
+ settingValidatorRegistry.registerUpdateValidator(settingName, validateBooleanSetting)
326
+ })
327
+ ```
328
+
329
+ ### Example 3: Validate Integer (No Decimals, No Negatives)
330
+
331
+ ```typescript
332
+ async function validateMinimumSealNumber(
333
+ setting: NewSetting,
334
+ context: { domain: Domain; user: User }
335
+ ): Promise<SettingValidationResult> {
336
+ if (!setting.value) {
337
+ return { valid: false, error: 'minimum-seal-number value is required' }
338
+ }
339
+
340
+ // Check if value contains decimal point
341
+ if (setting.value.includes('.')) {
342
+ return {
343
+ valid: false,
344
+ error: 'minimum-seal-number must be an integer (no decimals allowed)'
345
+ }
346
+ }
347
+
348
+ const number = parseInt(setting.value, 10)
349
+
350
+ // Check if parsing resulted in NaN
351
+ if (isNaN(number)) {
352
+ return {
353
+ valid: false,
354
+ error: 'minimum-seal-number must be a valid number'
355
+ }
356
+ }
357
+
358
+ // Check if the parsed value matches the original string (to catch cases like "123abc")
359
+ if (number.toString() !== setting.value.trim()) {
360
+ return {
361
+ valid: false,
362
+ error: 'minimum-seal-number must be a valid integer'
363
+ }
364
+ }
365
+
366
+ // Check if negative
367
+ if (number < 0) {
368
+ return {
369
+ valid: false,
370
+ error: 'minimum-seal-number must be 0 or above (negative numbers not allowed)'
371
+ }
372
+ }
373
+
374
+ return { valid: true }
375
+ }
376
+ ```
377
+
378
+ ### Example 4: Validate with Database Check
379
+
380
+ ```typescript
381
+ import { getRepository } from 'typeorm'
382
+ import { SomeEntity } from './entities'
383
+
384
+ async function validateSettingWithDbCheck(
385
+ setting: NewSetting,
386
+ context: { domain: Domain; user: User }
387
+ ): Promise<SettingValidationResult> {
388
+ // Example: Check if referenced entity exists
389
+ if (setting.name === 'referenced-entity-id') {
390
+ const entity = await getRepository(SomeEntity).findOne({
391
+ where: { id: setting.value, domain: context.domain }
392
+ })
393
+
394
+ if (!entity) {
395
+ return {
396
+ valid: false,
397
+ error: `Referenced entity not found: ${setting.value}`
398
+ }
399
+ }
400
+ }
401
+
402
+ return { valid: true }
403
+ }
404
+ ```
405
+
406
+ ## Best Practices
407
+
408
+ 1. **Keep validators focused**: Each validator should validate one specific aspect
409
+ 2. **Return clear error messages**: Help users understand what went wrong
410
+ 3. **Use pattern matching**: For settings that follow naming conventions
411
+ 4. **Register at module load time**: Ensure validators are registered before the application starts
412
+ 5. **Handle async operations**: Validators can be async if you need to check database or external services
413
+ 6. **Skip validation when not needed**: In update validators, check if the field being validated is actually being updated
414
+
415
+ ## Error Handling
416
+
417
+ When validation fails, the mutation will throw an error with the message from `SettingValidationResult.error`. This error will be caught by GraphQL and returned to the client.
418
+
419
+ Example error response:
420
+
421
+ ```json
422
+ {
423
+ "errors": [
424
+ {
425
+ "message": "batch-picking-limit must be a number between 1 and 1000",
426
+ "extensions": {
427
+ "code": "INTERNAL_SERVER_ERROR"
428
+ }
429
+ }
430
+ ]
431
+ }
432
+ ```
433
+
434
+ ## Testing
435
+
436
+ You can test validators independently:
437
+
438
+ ```typescript
439
+ // In operato-wms tests
440
+ import { settingValidatorRegistry } from '@things-factory/setting-base'
441
+ import { registerOperatoWmsSettingValidators } from '../validators/setting-validators'
442
+
443
+ beforeAll(() => {
444
+ registerOperatoWmsSettingValidators()
445
+ })
446
+
447
+ test('validates batch-picking-limit', async () => {
448
+ const result = await settingValidatorRegistry.validateCreate(
449
+ { name: 'batch-picking-limit', value: '50', category: 'id-rule' },
450
+ { domain: mockDomain, user: mockUser }
451
+ )
452
+ // No error means validation passed
453
+ expect(result).toBeUndefined()
454
+ })
455
+ ```
456
+
457
+ ## Troubleshooting
458
+
459
+ ### Common Mistakes to Avoid
460
+
461
+ ❌ **DON'T register in bootstrap-module-start**:
462
+
463
+ ```typescript
464
+ // WRONG - too late!
465
+ process.on('bootstrap-module-start', () => {
466
+ registerValidators() // Schema already built
467
+ })
468
+ ```
469
+
470
+ ✅ **DO register at module load**:
471
+
472
+ ```typescript
473
+ // CORRECT - before schema build
474
+ import { registerValidators } from './validators'
475
+ registerValidators() // Module loads first
476
+ ```
477
+
478
+ ❌ **DON'T import application packages in setting-base**:
479
+
480
+ ```typescript
481
+ // WRONG - creates circular dependency!
482
+ import { operatoWmsValidators } from '@things-factory/operato-wms'
483
+ ```
484
+
485
+ ✅ **DO let applications register themselves**:
486
+
487
+ ```typescript
488
+ // CORRECT - applications register to shared registry
489
+ // setting-base just exports the registry
490
+ export { settingValidatorRegistry }
491
+ ```
492
+
493
+ ### Error: "Cannot read properties of undefined (reading 'registerCreateValidator')"
494
+
495
+ This error occurs when `settingValidatorRegistry` is undefined. Make sure:
496
+
497
+ 1. You've imported from the correct path: `@things-factory/setting-base`
498
+ 2. The `setting-base` package has been rebuilt after adding the validator exports
499
+ 3. Your application has been rebuilt to pick up the updated `setting-base` exports
500
+
501
+ ### Validators Not Being Called
502
+
503
+ If validators aren't being called:
504
+
505
+ 1. Verify validators are registered at module load time (not in bootstrap events)
506
+ 2. Check that the setting name matches exactly (case-sensitive)
507
+ 3. For pattern matching, verify the regex or function matches correctly
508
+ 4. Ensure the mutation is actually calling `settingValidatorRegistry.validateCreate()` or `validateUpdate()`
509
+
510
+ ## Migration Guide
511
+
512
+ If you have existing validation logic in your application:
513
+
514
+ 1. Extract validation logic into validator functions
515
+ 2. Register validators at module load time in your server entry file
516
+ 3. Remove any custom validation from mutation hooks or middleware
517
+ 4. Test thoroughly to ensure validation still works
518
+
519
+ ## See Also
520
+
521
+ - Example validators:
522
+ - `packages/operato-wms/server/validators/setting-validators.ts`
523
+ - `packages/operato-hub/server/validators/setting-validators.ts`
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.schema = exports.entities = void 0;
17
+ exports.schema = exports.entities = exports.settingValidatorRegistry = void 0;
18
18
  /* IMPORT ENTITIES AND RESOLVERS */
19
19
  const partner_setting_1 = require("./partner-setting");
20
20
  const setting_1 = require("./setting");
@@ -24,6 +24,9 @@ __exportStar(require("./setting/setting"), exports);
24
24
  /* EXPORT TYPES */
25
25
  __exportStar(require("./partner-setting/partner-setting-type"), exports);
26
26
  __exportStar(require("./setting/setting-type"), exports);
27
+ /* EXPORT VALIDATOR REGISTRY */
28
+ var setting_validator_1 = require("./setting/setting-validator");
29
+ Object.defineProperty(exports, "settingValidatorRegistry", { enumerable: true, get: function () { return setting_validator_1.settingValidatorRegistry; } });
27
30
  exports.entities = [
28
31
  /* ENTITIES */
29
32
  ...partner_setting_1.entities,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mCAAmC;AACnC,uDAA4G;AAC5G,uCAAsF;AAEtF,yBAAyB;AACzB,oEAAiD;AACjD,oDAAiC;AAEjC,kBAAkB;AAClB,yEAAsD;AACtD,yDAAsC;AAEzB,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,0BAAsB;IACzB,GAAG,kBAAe;CACnB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,2BAAuB;QAC1B,GAAG,mBAAgB;KACpB;CACF,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mCAAmC;AACnC,uDAA4G;AAC5G,uCAAsF;AAEtF,yBAAyB;AACzB,oEAAiD;AACjD,oDAAiC;AAEjC,kBAAkB;AAClB,yEAAsD;AACtD,yDAAsC;AAEtC,+BAA+B;AAC/B,iEAMoC;AALlC,6HAAA,wBAAwB,OAAA;AAOb,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,0BAAsB;IACzB,GAAG,kBAAe;CACnB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,2BAAuB;QAC1B,GAAG,mBAAgB;KACpB;CACF,CAAA"}
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolvers = exports.entities = void 0;
3
+ exports.settingValidatorRegistry = exports.resolvers = exports.entities = void 0;
4
4
  const setting_1 = require("./setting");
5
5
  const setting_query_1 = require("./setting-query");
6
6
  const setting_mutation_1 = require("./setting-mutation");
7
7
  exports.entities = [setting_1.Setting];
8
8
  exports.resolvers = [setting_query_1.SettingQuery, setting_mutation_1.SettingMutation];
9
+ // Export validator registry and types for applications to use
10
+ var setting_validator_1 = require("./setting-validator");
11
+ Object.defineProperty(exports, "settingValidatorRegistry", { enumerable: true, get: function () { return setting_validator_1.settingValidatorRegistry; } });
9
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/setting/index.ts"],"names":[],"mappings":";;;AAAA,uCAAmC;AACnC,mDAA8C;AAC9C,yDAAoD;AAEvC,QAAA,QAAQ,GAAG,CAAC,iBAAO,CAAC,CAAA;AACpB,QAAA,SAAS,GAAG,CAAC,4BAAY,EAAE,kCAAe,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/setting/index.ts"],"names":[],"mappings":";;;AAAA,uCAAmC;AACnC,mDAA8C;AAC9C,yDAAoD;AAEvC,QAAA,QAAQ,GAAG,CAAC,iBAAO,CAAC,CAAA;AACpB,QAAA,SAAS,GAAG,CAAC,4BAAY,EAAE,kCAAe,CAAC,CAAA;AAExD,8DAA8D;AAC9D,yDAM4B;AAL1B,6HAAA,wBAAwB,OAAA"}
@@ -17,16 +17,24 @@ const type_graphql_1 = require("type-graphql");
17
17
  const typeorm_1 = require("typeorm");
18
18
  const setting_1 = require("./setting");
19
19
  const setting_type_1 = require("./setting-type");
20
+ const setting_validator_1 = require("./setting-validator");
20
21
  let SettingMutation = class SettingMutation {
21
22
  async createSetting(setting, context) {
22
23
  const { tx, domain, user } = context.state;
24
+ // Validate before creating
25
+ await setting_validator_1.settingValidatorRegistry.validateCreate(setting, { domain, user });
23
26
  return await tx.getRepository(setting_1.Setting).save(Object.assign(Object.assign({ domain }, setting), { creator: user, updater: user }));
24
27
  }
25
28
  async updateSetting(name, patch, context) {
26
29
  const { tx, domain, user } = context.state;
27
30
  const repository = tx.getRepository(setting_1.Setting);
28
31
  const setting = await repository.findOne({ domain, name });
29
- return await repository.save(Object.assign(Object.assign(Object.assign({ creater: user }, setting), patch), { domain, updater: user }));
32
+ if (!setting) {
33
+ throw new Error(`Setting not found: ${name}`);
34
+ }
35
+ // Validate before updating
36
+ await setting_validator_1.settingValidatorRegistry.validateUpdate(setting, patch, { domain, user });
37
+ return await repository.save(Object.assign(Object.assign(Object.assign({ creator: user }, setting), patch), { domain, updater: user }));
30
38
  }
31
39
  async updateMultipleSetting(patches, context) {
32
40
  const { tx, domain, user } = context.state;
@@ -37,14 +45,35 @@ let SettingMutation = class SettingMutation {
37
45
  if (_createRecords.length > 0) {
38
46
  for (let i = 0; i < _createRecords.length; i++) {
39
47
  const newRecord = _createRecords[i];
48
+ // Validate before creating - ensure required fields are present
49
+ if (newRecord.name && newRecord.category) {
50
+ await setting_validator_1.settingValidatorRegistry.validateCreate({
51
+ name: newRecord.name,
52
+ description: newRecord.description,
53
+ category: newRecord.category,
54
+ value: newRecord.value
55
+ }, { domain, user });
56
+ }
40
57
  const result = await settingRepo.save(Object.assign({ domain, creator: user, updater: user }, newRecord));
41
58
  results.push(Object.assign(Object.assign({}, result), { cuFlag: '+' }));
42
59
  }
43
60
  }
44
61
  if (_updateRecords.length > 0) {
62
+ // Load all existing settings first
63
+ const settingIds = _updateRecords.map((record) => record.id).filter(Boolean);
64
+ const existingSettingsList = settingIds.length > 0 ? await settingRepo.findByIds(settingIds) : [];
65
+ const existingSettingsMap = new Map(existingSettingsList.map((s) => [s.id, s]));
66
+ // Batch validation: validate all patches together before individual validation
67
+ // This prevents race conditions when multiple related settings are updated simultaneously
68
+ await setting_validator_1.settingValidatorRegistry.validateBatch(_updateRecords, existingSettingsMap, { domain, user });
45
69
  for (let i = 0; i < _updateRecords.length; i++) {
46
70
  const newRecord = _updateRecords[i];
47
- const setting = await settingRepo.findOne({ id: newRecord.id });
71
+ const setting = existingSettingsMap.get(newRecord.id);
72
+ if (!setting) {
73
+ throw new Error(`Setting not found: ${newRecord.id}`);
74
+ }
75
+ // Validate before updating (individual validators)
76
+ await setting_validator_1.settingValidatorRegistry.validateUpdate(setting, newRecord, { domain, user });
48
77
  const result = await settingRepo.save(Object.assign(Object.assign(Object.assign({}, setting), newRecord), { updater: user }));
49
78
  results.push(Object.assign(Object.assign({}, result), { cuFlag: 'M' }));
50
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setting-mutation.js","sourceRoot":"","sources":["../../../server/service/setting/setting-mutation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setting-mutation.js","sourceRoot":"","sources":["../../../server/service/setting/setting-mutation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.settingValidatorRegistry = void 0;
4
+ /**
5
+ * Validator registry that stores validators by setting name
6
+ */
7
+ class SettingValidatorRegistry {
8
+ constructor() {
9
+ this.createValidators = new Map();
10
+ this.updateValidators = new Map();
11
+ this.batchValidators = [];
12
+ this.patternCreateValidators = [];
13
+ this.patternUpdateValidators = [];
14
+ }
15
+ /**
16
+ * Register a validator for a specific setting name
17
+ * @param settingName - The name of the setting to validate
18
+ * @param validator - The validator function for create operations
19
+ */
20
+ registerCreateValidator(settingName, validator) {
21
+ if (!this.createValidators.has(settingName)) {
22
+ this.createValidators.set(settingName, []);
23
+ }
24
+ this.createValidators.get(settingName).push(validator);
25
+ }
26
+ /**
27
+ * Register a validator for a specific setting name
28
+ * @param settingName - The name of the setting to validate
29
+ * @param validator - The validator function for update operations
30
+ */
31
+ registerUpdateValidator(settingName, validator) {
32
+ if (!this.updateValidators.has(settingName)) {
33
+ this.updateValidators.set(settingName, []);
34
+ }
35
+ this.updateValidators.get(settingName).push(validator);
36
+ }
37
+ /**
38
+ * Register validators for multiple setting names using a pattern
39
+ * @param pattern - Regex pattern or function to match setting names
40
+ * @param createValidator - Validator for create operations (optional)
41
+ * @param updateValidator - Validator for update operations (optional)
42
+ */
43
+ registerPatternValidator(pattern, createValidator, updateValidator) {
44
+ // Store pattern validators separately for matching
45
+ if (createValidator) {
46
+ // For pattern matching, we'll check during validation
47
+ this.patternCreateValidators.push({ pattern, validator: createValidator });
48
+ }
49
+ if (updateValidator) {
50
+ this.patternUpdateValidators.push({ pattern, validator: updateValidator });
51
+ }
52
+ }
53
+ /**
54
+ * Get all validators for a setting name (exact match + pattern matches)
55
+ */
56
+ getCreateValidators(settingName) {
57
+ const validators = [];
58
+ // Add exact match validators
59
+ const exactValidators = this.createValidators.get(settingName);
60
+ if (exactValidators) {
61
+ validators.push(...exactValidators);
62
+ }
63
+ // Add pattern match validators
64
+ for (const { pattern, validator } of this.patternCreateValidators) {
65
+ const matches = pattern instanceof RegExp ? pattern.test(settingName) : pattern(settingName);
66
+ if (matches) {
67
+ validators.push(validator);
68
+ }
69
+ }
70
+ return validators;
71
+ }
72
+ /**
73
+ * Get all validators for a setting name (exact match + pattern matches)
74
+ */
75
+ getUpdateValidators(settingName) {
76
+ const validators = [];
77
+ // Add exact match validators
78
+ const exactValidators = this.updateValidators.get(settingName);
79
+ if (exactValidators) {
80
+ validators.push(...exactValidators);
81
+ }
82
+ // Add pattern match validators
83
+ for (const { pattern, validator } of this.patternUpdateValidators) {
84
+ const matches = pattern instanceof RegExp ? pattern.test(settingName) : pattern(settingName);
85
+ if (matches) {
86
+ validators.push(validator);
87
+ }
88
+ }
89
+ return validators;
90
+ }
91
+ /**
92
+ * Validate a setting before creation
93
+ */
94
+ async validateCreate(setting, context) {
95
+ const validators = this.getCreateValidators(setting.name);
96
+ for (const validator of validators) {
97
+ const result = await validator(setting, context);
98
+ if (!result.valid) {
99
+ throw new Error(result.error || `Validation failed for setting: ${setting.name}`);
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Register a batch validator for validating multiple settings together
105
+ * @param validator - The batch validator function
106
+ */
107
+ registerBatchValidator(validator) {
108
+ this.batchValidators.push(validator);
109
+ }
110
+ /**
111
+ * Validate multiple settings together before update
112
+ * This is useful for cross-validation between related settings
113
+ */
114
+ async validateBatch(patches, existingSettings, context) {
115
+ for (const validator of this.batchValidators) {
116
+ const result = await validator(patches, existingSettings, context);
117
+ if (!result.valid) {
118
+ throw new Error(result.error || 'Batch validation failed');
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Validate a setting before update
124
+ */
125
+ async validateUpdate(setting, patch, context) {
126
+ const validators = this.getUpdateValidators(setting.name);
127
+ for (const validator of validators) {
128
+ const result = await validator(setting, patch, context);
129
+ if (!result.valid) {
130
+ throw new Error(result.error || `Validation failed for setting: ${setting.name}`);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ // Export singleton instance
136
+ exports.settingValidatorRegistry = new SettingValidatorRegistry();
137
+ //# sourceMappingURL=setting-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setting-validator.js","sourceRoot":"","sources":["../../../server/service/setting/setting-validator.ts"],"names":[],"mappings":";;;AAuCA;;GAEG;AACH,MAAM,wBAAwB;IAA9B;QACU,qBAAgB,GAA0C,IAAI,GAAG,EAAE,CAAA;QACnE,qBAAgB,GAA0C,IAAI,GAAG,EAAE,CAAA;QACnE,oBAAe,GAA4B,EAAE,CAAA;QA+C7C,4BAAuB,GAG1B,EAAE,CAAA;QACC,4BAAuB,GAG1B,EAAE,CAAA;IAoGT,CAAC;IAxJC;;;;OAIG;IACH,uBAAuB,CAAC,WAAmB,EAAE,SAAiC;QAC5E,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;YAC3C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;SAC3C;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACzD,CAAC;IAED;;;;OAIG;IACH,uBAAuB,CAAC,WAAmB,EAAE,SAAiC;QAC5E,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;YAC3C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;SAC3C;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACzD,CAAC;IAED;;;;;OAKG;IACH,wBAAwB,CACtB,OAA6C,EAC7C,eAAwC,EACxC,eAAwC;QAExC,mDAAmD;QACnD,IAAI,eAAe,EAAE;YACnB,sDAAsD;YACtD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAA;SAC3E;QACD,IAAI,eAAe,EAAE;YACnB,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAA;SAC3E;IACH,CAAC;IAWD;;OAEG;IACH,mBAAmB,CAAC,WAAmB;QACrC,MAAM,UAAU,GAA6B,EAAE,CAAA;QAE/C,6BAA6B;QAC7B,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC9D,IAAI,eAAe,EAAE;YACnB,UAAU,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAA;SACpC;QAED,+BAA+B;QAC/B,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,uBAAuB,EAAE;YACjE,MAAM,OAAO,GAAG,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;YAC5F,IAAI,OAAO,EAAE;gBACX,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;aAC3B;SACF;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,WAAmB;QACrC,MAAM,UAAU,GAA6B,EAAE,CAAA;QAE/C,6BAA6B;QAC7B,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC9D,IAAI,eAAe,EAAE;YACnB,UAAU,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAA;SACpC;QAED,+BAA+B;QAC/B,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,uBAAuB,EAAE;YACjE,MAAM,OAAO,GAAG,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;YAC5F,IAAI,OAAO,EAAE;gBACX,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;aAC3B;SACF;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,OAAmB,EAAE,OAAuC;QAC/E,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAEzD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAChD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;gBACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,kCAAkC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;aAClF;SACF;IACH,CAAC;IAED;;;OAGG;IACH,sBAAsB,CAAC,SAAgC;QACrD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACtC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa,CACjB,OAAuB,EACvB,gBAAsC,EACtC,OAAuC;QAEvC,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE;YAC5C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAA;YAClE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;gBACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,yBAAyB,CAAC,CAAA;aAC3D;SACF;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,OAAgB,EAAE,KAAmB,EAAE,OAAuC;QACjG,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAEzD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;YACvD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;gBACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,kCAAkC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;aAClF;SACF;IACH,CAAC;CACF;AAED,4BAA4B;AACf,QAAA,wBAAwB,GAAG,IAAI,wBAAwB,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/setting-base",
3
- "version": "4.3.752",
3
+ "version": "4.3.764",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -23,8 +23,8 @@
23
23
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create -d ./server/migrations"
24
24
  },
25
25
  "dependencies": {
26
- "@things-factory/auth-base": "^4.3.752",
27
- "@things-factory/code-base": "^4.3.752"
26
+ "@things-factory/auth-base": "^4.3.764",
27
+ "@things-factory/code-base": "^4.3.764"
28
28
  },
29
- "gitHead": "cc2f8abfd00055dfbd1737e3016fe4a464af2548"
29
+ "gitHead": "4dd8055a25e773814603c4be21fc70930d0cfb13"
30
30
  }
@@ -10,6 +10,15 @@ export * from './setting/setting'
10
10
  export * from './partner-setting/partner-setting-type'
11
11
  export * from './setting/setting-type'
12
12
 
13
+ /* EXPORT VALIDATOR REGISTRY */
14
+ export {
15
+ settingValidatorRegistry,
16
+ SettingValidationResult,
17
+ SettingCreateValidator,
18
+ SettingUpdateValidator,
19
+ SettingBatchValidator
20
+ } from './setting/setting-validator'
21
+
13
22
  export const entities = [
14
23
  /* ENTITIES */
15
24
  ...PartnerSettingEntities,
@@ -4,3 +4,12 @@ import { SettingMutation } from './setting-mutation'
4
4
 
5
5
  export const entities = [Setting]
6
6
  export const resolvers = [SettingQuery, SettingMutation]
7
+
8
+ // Export validator registry and types for applications to use
9
+ export {
10
+ settingValidatorRegistry,
11
+ SettingValidationResult,
12
+ SettingCreateValidator,
13
+ SettingUpdateValidator,
14
+ SettingBatchValidator
15
+ } from './setting-validator'
@@ -4,6 +4,7 @@ import { Domain } from '@things-factory/shell'
4
4
  import { User } from '@things-factory/auth-base'
5
5
  import { Setting } from './setting'
6
6
  import { NewSetting, SettingPatch } from './setting-type'
7
+ import { settingValidatorRegistry } from './setting-validator'
7
8
 
8
9
  @Resolver(Setting)
9
10
  export class SettingMutation {
@@ -12,6 +13,10 @@ export class SettingMutation {
12
13
  @Mutation(returns => Setting, { description: 'To create new Setting' })
13
14
  async createSetting(@Arg('setting') setting: NewSetting, @Ctx() context: any): Promise<Setting> {
14
15
  const { tx, domain, user }: { tx: EntityManager; domain: Domain; user: User } = context.state
16
+
17
+ // Validate before creating
18
+ await settingValidatorRegistry.validateCreate(setting, { domain, user })
19
+
15
20
  return await tx.getRepository(Setting).save({
16
21
  domain,
17
22
  ...setting,
@@ -33,8 +38,15 @@ export class SettingMutation {
33
38
  const repository = tx.getRepository(Setting)
34
39
  const setting = await repository.findOne({ domain, name })
35
40
 
41
+ if (!setting) {
42
+ throw new Error(`Setting not found: ${name}`)
43
+ }
44
+
45
+ // Validate before updating
46
+ await settingValidatorRegistry.validateUpdate(setting, patch, { domain, user })
47
+
36
48
  return await repository.save({
37
- creater: user,
49
+ creator: user,
38
50
  ...setting,
39
51
  ...patch,
40
52
  domain,
@@ -60,6 +72,19 @@ export class SettingMutation {
60
72
  for (let i = 0; i < _createRecords.length; i++) {
61
73
  const newRecord = _createRecords[i]
62
74
 
75
+ // Validate before creating - ensure required fields are present
76
+ if (newRecord.name && newRecord.category) {
77
+ await settingValidatorRegistry.validateCreate(
78
+ {
79
+ name: newRecord.name,
80
+ description: newRecord.description,
81
+ category: newRecord.category,
82
+ value: newRecord.value
83
+ },
84
+ { domain, user }
85
+ )
86
+ }
87
+
63
88
  const result = await settingRepo.save({
64
89
  domain,
65
90
  creator: user,
@@ -72,9 +97,25 @@ export class SettingMutation {
72
97
  }
73
98
 
74
99
  if (_updateRecords.length > 0) {
100
+ // Load all existing settings first
101
+ const settingIds = _updateRecords.map((record: any) => record.id).filter(Boolean)
102
+ const existingSettingsList = settingIds.length > 0 ? await settingRepo.findByIds(settingIds) : []
103
+ const existingSettingsMap = new Map(existingSettingsList.map((s: Setting) => [s.id, s]))
104
+
105
+ // Batch validation: validate all patches together before individual validation
106
+ // This prevents race conditions when multiple related settings are updated simultaneously
107
+ await settingValidatorRegistry.validateBatch(_updateRecords, existingSettingsMap, { domain, user })
108
+
75
109
  for (let i = 0; i < _updateRecords.length; i++) {
76
110
  const newRecord = _updateRecords[i]
77
- const setting = await settingRepo.findOne({ id: newRecord.id })
111
+ const setting = existingSettingsMap.get(newRecord.id)
112
+
113
+ if (!setting) {
114
+ throw new Error(`Setting not found: ${newRecord.id}`)
115
+ }
116
+
117
+ // Validate before updating (individual validators)
118
+ await settingValidatorRegistry.validateUpdate(setting, newRecord, { domain, user })
78
119
 
79
120
  const result = await settingRepo.save({
80
121
  ...setting,
@@ -0,0 +1,203 @@
1
+ import { Domain } from '@things-factory/shell'
2
+ import { User } from '@things-factory/auth-base'
3
+ import { NewSetting, SettingPatch } from './setting-type'
4
+ import { Setting } from './setting'
5
+
6
+ /**
7
+ * Validation result returned by validators
8
+ */
9
+ export interface SettingValidationResult {
10
+ valid: boolean
11
+ error?: string
12
+ }
13
+
14
+ /**
15
+ * Validator function type for creating settings
16
+ */
17
+ export type SettingCreateValidator = (
18
+ setting: NewSetting,
19
+ context: { domain: Domain; user: User }
20
+ ) => Promise<SettingValidationResult> | SettingValidationResult
21
+
22
+ /**
23
+ * Validator function type for updating settings
24
+ */
25
+ export type SettingUpdateValidator = (
26
+ setting: Setting,
27
+ patch: SettingPatch,
28
+ context: { domain: Domain; user: User }
29
+ ) => Promise<SettingValidationResult> | SettingValidationResult
30
+
31
+ /**
32
+ * Batch validator function type for validating multiple settings together
33
+ */
34
+ export type SettingBatchValidator = (
35
+ patches: SettingPatch[],
36
+ existingSettings: Map<string, Setting>,
37
+ context: { domain: Domain; user: User }
38
+ ) => Promise<SettingValidationResult> | SettingValidationResult
39
+
40
+ /**
41
+ * Validator registry that stores validators by setting name
42
+ */
43
+ class SettingValidatorRegistry {
44
+ private createValidators: Map<string, SettingCreateValidator[]> = new Map()
45
+ private updateValidators: Map<string, SettingUpdateValidator[]> = new Map()
46
+ private batchValidators: SettingBatchValidator[] = []
47
+
48
+ /**
49
+ * Register a validator for a specific setting name
50
+ * @param settingName - The name of the setting to validate
51
+ * @param validator - The validator function for create operations
52
+ */
53
+ registerCreateValidator(settingName: string, validator: SettingCreateValidator): void {
54
+ if (!this.createValidators.has(settingName)) {
55
+ this.createValidators.set(settingName, [])
56
+ }
57
+ this.createValidators.get(settingName)!.push(validator)
58
+ }
59
+
60
+ /**
61
+ * Register a validator for a specific setting name
62
+ * @param settingName - The name of the setting to validate
63
+ * @param validator - The validator function for update operations
64
+ */
65
+ registerUpdateValidator(settingName: string, validator: SettingUpdateValidator): void {
66
+ if (!this.updateValidators.has(settingName)) {
67
+ this.updateValidators.set(settingName, [])
68
+ }
69
+ this.updateValidators.get(settingName)!.push(validator)
70
+ }
71
+
72
+ /**
73
+ * Register validators for multiple setting names using a pattern
74
+ * @param pattern - Regex pattern or function to match setting names
75
+ * @param createValidator - Validator for create operations (optional)
76
+ * @param updateValidator - Validator for update operations (optional)
77
+ */
78
+ registerPatternValidator(
79
+ pattern: RegExp | ((name: string) => boolean),
80
+ createValidator?: SettingCreateValidator,
81
+ updateValidator?: SettingUpdateValidator
82
+ ): void {
83
+ // Store pattern validators separately for matching
84
+ if (createValidator) {
85
+ // For pattern matching, we'll check during validation
86
+ this.patternCreateValidators.push({ pattern, validator: createValidator })
87
+ }
88
+ if (updateValidator) {
89
+ this.patternUpdateValidators.push({ pattern, validator: updateValidator })
90
+ }
91
+ }
92
+
93
+ private patternCreateValidators: Array<{
94
+ pattern: RegExp | ((name: string) => boolean)
95
+ validator: SettingCreateValidator
96
+ }> = []
97
+ private patternUpdateValidators: Array<{
98
+ pattern: RegExp | ((name: string) => boolean)
99
+ validator: SettingUpdateValidator
100
+ }> = []
101
+
102
+ /**
103
+ * Get all validators for a setting name (exact match + pattern matches)
104
+ */
105
+ getCreateValidators(settingName: string): SettingCreateValidator[] {
106
+ const validators: SettingCreateValidator[] = []
107
+
108
+ // Add exact match validators
109
+ const exactValidators = this.createValidators.get(settingName)
110
+ if (exactValidators) {
111
+ validators.push(...exactValidators)
112
+ }
113
+
114
+ // Add pattern match validators
115
+ for (const { pattern, validator } of this.patternCreateValidators) {
116
+ const matches = pattern instanceof RegExp ? pattern.test(settingName) : pattern(settingName)
117
+ if (matches) {
118
+ validators.push(validator)
119
+ }
120
+ }
121
+
122
+ return validators
123
+ }
124
+
125
+ /**
126
+ * Get all validators for a setting name (exact match + pattern matches)
127
+ */
128
+ getUpdateValidators(settingName: string): SettingUpdateValidator[] {
129
+ const validators: SettingUpdateValidator[] = []
130
+
131
+ // Add exact match validators
132
+ const exactValidators = this.updateValidators.get(settingName)
133
+ if (exactValidators) {
134
+ validators.push(...exactValidators)
135
+ }
136
+
137
+ // Add pattern match validators
138
+ for (const { pattern, validator } of this.patternUpdateValidators) {
139
+ const matches = pattern instanceof RegExp ? pattern.test(settingName) : pattern(settingName)
140
+ if (matches) {
141
+ validators.push(validator)
142
+ }
143
+ }
144
+
145
+ return validators
146
+ }
147
+
148
+ /**
149
+ * Validate a setting before creation
150
+ */
151
+ async validateCreate(setting: NewSetting, context: { domain: Domain; user: User }): Promise<void> {
152
+ const validators = this.getCreateValidators(setting.name)
153
+
154
+ for (const validator of validators) {
155
+ const result = await validator(setting, context)
156
+ if (!result.valid) {
157
+ throw new Error(result.error || `Validation failed for setting: ${setting.name}`)
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Register a batch validator for validating multiple settings together
164
+ * @param validator - The batch validator function
165
+ */
166
+ registerBatchValidator(validator: SettingBatchValidator): void {
167
+ this.batchValidators.push(validator)
168
+ }
169
+
170
+ /**
171
+ * Validate multiple settings together before update
172
+ * This is useful for cross-validation between related settings
173
+ */
174
+ async validateBatch(
175
+ patches: SettingPatch[],
176
+ existingSettings: Map<string, Setting>,
177
+ context: { domain: Domain; user: User }
178
+ ): Promise<void> {
179
+ for (const validator of this.batchValidators) {
180
+ const result = await validator(patches, existingSettings, context)
181
+ if (!result.valid) {
182
+ throw new Error(result.error || 'Batch validation failed')
183
+ }
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Validate a setting before update
189
+ */
190
+ async validateUpdate(setting: Setting, patch: SettingPatch, context: { domain: Domain; user: User }): Promise<void> {
191
+ const validators = this.getUpdateValidators(setting.name)
192
+
193
+ for (const validator of validators) {
194
+ const result = await validator(setting, patch, context)
195
+ if (!result.valid) {
196
+ throw new Error(result.error || `Validation failed for setting: ${setting.name}`)
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ // Export singleton instance
203
+ export const settingValidatorRegistry = new SettingValidatorRegistry()