@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 +523 -0
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/setting/index.js +4 -1
- package/dist-server/service/setting/index.js.map +1 -1
- package/dist-server/service/setting/setting-mutation.js +31 -2
- package/dist-server/service/setting/setting-mutation.js.map +1 -1
- package/dist-server/service/setting/setting-validator.js +137 -0
- package/dist-server/service/setting/setting-validator.js.map +1 -0
- package/package.json +4 -4
- package/server/service/index.ts +9 -0
- package/server/service/setting/index.ts +9 -0
- package/server/service/setting/setting-mutation.ts +43 -2
- package/server/service/setting/setting-validator.ts +203 -0
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;
|
|
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
|
-
|
|
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 =
|
|
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":";;;;;;;;;;;;;;;AAAA,+CAAsE;AACtE,qCAA0D;AAG1D,uCAAmC;AACnC,iDAAyD;
|
|
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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/setting-base",
|
|
3
|
-
"version": "4.3.
|
|
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.
|
|
27
|
-
"@things-factory/code-base": "^4.3.
|
|
26
|
+
"@things-factory/auth-base": "^4.3.764",
|
|
27
|
+
"@things-factory/code-base": "^4.3.764"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "4dd8055a25e773814603c4be21fc70930d0cfb13"
|
|
30
30
|
}
|
package/server/service/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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()
|