@api-client/core 0.15.1 → 0.16.1
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/TESTING_READY.md +114 -0
- package/TESTING_SETUP.md +198 -0
- package/build/src/modeling/Semantics.d.ts +126 -2
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +281 -13
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/definitions/Calculated.d.ts +54 -0
- package/build/src/modeling/definitions/Calculated.d.ts.map +1 -0
- package/build/src/modeling/definitions/Calculated.js +31 -0
- package/build/src/modeling/definitions/Calculated.js.map +1 -0
- package/build/src/modeling/definitions/Categories.d.ts +60 -0
- package/build/src/modeling/definitions/Categories.d.ts.map +1 -0
- package/build/src/modeling/definitions/Categories.js +33 -0
- package/build/src/modeling/definitions/Categories.js.map +1 -0
- package/build/src/modeling/definitions/Derived.d.ts +54 -0
- package/build/src/modeling/definitions/Derived.d.ts.map +1 -0
- package/build/src/modeling/definitions/Derived.js +31 -0
- package/build/src/modeling/definitions/Derived.js.map +1 -0
- package/build/src/modeling/definitions/Description.d.ts +36 -0
- package/build/src/modeling/definitions/Description.d.ts.map +1 -0
- package/build/src/modeling/definitions/Description.js +28 -0
- package/build/src/modeling/definitions/Description.js.map +1 -0
- package/build/src/modeling/definitions/Email.d.ts +66 -0
- package/build/src/modeling/definitions/Email.d.ts.map +1 -0
- package/build/src/modeling/definitions/Email.js +33 -0
- package/build/src/modeling/definitions/Email.js.map +1 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.d.ts +212 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.d.ts.map +1 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.js +129 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.js.map +1 -0
- package/build/src/modeling/definitions/HTML.d.ts +88 -0
- package/build/src/modeling/definitions/HTML.d.ts.map +1 -0
- package/build/src/modeling/definitions/HTML.js +42 -0
- package/build/src/modeling/definitions/HTML.js.map +1 -0
- package/build/src/modeling/definitions/Markdown.d.ts +84 -0
- package/build/src/modeling/definitions/Markdown.d.ts.map +1 -0
- package/build/src/modeling/definitions/Markdown.js +41 -0
- package/build/src/modeling/definitions/Markdown.js.map +1 -0
- package/build/src/modeling/definitions/Password.d.ts +112 -0
- package/build/src/modeling/definitions/Password.d.ts.map +1 -0
- package/build/src/modeling/definitions/Password.js +57 -0
- package/build/src/modeling/definitions/Password.js.map +1 -0
- package/build/src/modeling/definitions/Phone.d.ts +83 -0
- package/build/src/modeling/definitions/Phone.d.ts.map +1 -0
- package/build/src/modeling/definitions/Phone.js +39 -0
- package/build/src/modeling/definitions/Phone.js.map +1 -0
- package/build/src/modeling/definitions/Price.d.ts +102 -0
- package/build/src/modeling/definitions/Price.d.ts.map +1 -0
- package/build/src/modeling/definitions/Price.js +99 -0
- package/build/src/modeling/definitions/Price.js.map +1 -0
- package/build/src/modeling/definitions/PublicUniqueName.d.ts +69 -0
- package/build/src/modeling/definitions/PublicUniqueName.d.ts.map +1 -0
- package/build/src/modeling/definitions/PublicUniqueName.js +34 -0
- package/build/src/modeling/definitions/PublicUniqueName.js.map +1 -0
- package/build/src/modeling/definitions/SKU.d.ts +127 -0
- package/build/src/modeling/definitions/SKU.d.ts.map +1 -0
- package/build/src/modeling/definitions/SKU.js +142 -0
- package/build/src/modeling/definitions/SKU.js.map +1 -0
- package/build/src/modeling/definitions/Status.d.ts +150 -0
- package/build/src/modeling/definitions/Status.d.ts.map +1 -0
- package/build/src/modeling/definitions/Status.js +60 -0
- package/build/src/modeling/definitions/Status.js.map +1 -0
- package/build/src/modeling/definitions/Summary.d.ts +53 -0
- package/build/src/modeling/definitions/Summary.d.ts.map +1 -0
- package/build/src/modeling/definitions/Summary.js +50 -0
- package/build/src/modeling/definitions/Summary.js.map +1 -0
- package/build/src/modeling/definitions/Tags.d.ts +52 -0
- package/build/src/modeling/definitions/Tags.d.ts.map +1 -0
- package/build/src/modeling/definitions/Tags.js +32 -0
- package/build/src/modeling/definitions/Tags.js.map +1 -0
- package/build/src/modeling/definitions/URL.d.ts +68 -0
- package/build/src/modeling/definitions/URL.d.ts.map +1 -0
- package/build/src/modeling/definitions/URL.js +37 -0
- package/build/src/modeling/definitions/URL.js.map +1 -0
- package/build/src/modeling/validation/semantic_validation.d.ts +4 -0
- package/build/src/modeling/validation/semantic_validation.d.ts.map +1 -1
- package/build/src/modeling/validation/semantic_validation.js +32 -1
- package/build/src/modeling/validation/semantic_validation.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/modeling/Semantics.ts +297 -14
- package/src/modeling/definitions/Calculated.ts +76 -0
- package/src/modeling/definitions/Categories.ts +84 -0
- package/src/modeling/definitions/Derived.ts +76 -0
- package/src/modeling/definitions/Description.ts +55 -0
- package/src/modeling/definitions/Email.ts +90 -0
- package/src/modeling/definitions/GeospatialCoordinates.ts +274 -0
- package/src/modeling/definitions/HTML.ts +121 -0
- package/src/modeling/definitions/Markdown.ts +116 -0
- package/src/modeling/definitions/Password.ts +156 -0
- package/src/modeling/definitions/Phone.ts +116 -0
- package/src/modeling/definitions/Price.examples.md +158 -0
- package/src/modeling/definitions/Price.ts +180 -0
- package/src/modeling/definitions/PublicUniqueName.ts +98 -0
- package/src/modeling/definitions/SKU.examples.md +230 -0
- package/src/modeling/definitions/SKU.ts +254 -0
- package/src/modeling/definitions/Status.ts +227 -0
- package/src/modeling/definitions/Summary.ts +73 -0
- package/src/modeling/definitions/Tags.ts +75 -0
- package/src/modeling/definitions/URL.ts +96 -0
- package/src/modeling/validation/semantic_validation.ts +35 -1
- package/tests/example-test-setup.ts +133 -0
- package/tests/template-node.spec.ts +75 -0
- package/tests/test-utils.ts +293 -0
- package/tests/unit/modeling/definitions/calculated.spec.ts +33 -0
- package/tests/unit/modeling/definitions/categories.spec.ts +38 -0
- package/tests/unit/modeling/definitions/derived.spec.ts +34 -0
- package/tests/unit/modeling/definitions/description.spec.ts +38 -0
- package/tests/unit/modeling/definitions/email.spec.ts +38 -0
- package/tests/unit/modeling/definitions/geospatial-coordinates.spec.ts +41 -0
- package/tests/unit/modeling/definitions/html.spec.ts +38 -0
- package/tests/unit/modeling/definitions/markdown.spec.ts +38 -0
- package/tests/unit/modeling/definitions/password.spec.ts +347 -0
- package/tests/unit/modeling/definitions/phone.spec.ts +38 -0
- package/tests/unit/modeling/definitions/price.spec.ts +465 -0
- package/tests/unit/modeling/definitions/public-unique-name.spec.ts +38 -0
- package/tests/unit/modeling/definitions/sku.spec.ts +240 -0
- package/tests/unit/modeling/definitions/status.spec.ts +37 -0
- package/tests/unit/modeling/definitions/summary.spec.ts +36 -0
- package/tests/unit/modeling/definitions/tags.spec.ts +38 -0
- package/tests/unit/modeling/definitions/url.spec.ts +38 -0
- package/tests/unit/modeling/domain_property.spec.ts +106 -0
- package/tests/unit/modeling/domain_validation.spec.ts +5 -5
- package/tests/unit/modeling/semantic-configs.spec.ts +569 -0
- package/tests/unit/modeling/semantics.spec.ts +52 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# SKU Semantic Examples
|
|
2
|
+
|
|
3
|
+
This document provides examples of how to use the SKU semantic with different configurations.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createSKUSemantic, SKU_PRESETS } from './SKU.js'
|
|
9
|
+
|
|
10
|
+
// Simple SKU with default settings
|
|
11
|
+
const basicSKU = createSKUSemantic()
|
|
12
|
+
|
|
13
|
+
// Using a preset for product catalogs
|
|
14
|
+
const productSKU = SKU_PRESETS.PRODUCT_STANDARD
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration Examples
|
|
18
|
+
|
|
19
|
+
### 1. Standard Product Catalog
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const productCatalogSKU = createSKUSemantic({
|
|
23
|
+
validationMode: 'strict',
|
|
24
|
+
caseMode: 'uppercase',
|
|
25
|
+
prefix: 'PROD-',
|
|
26
|
+
enforceUniqueness: true,
|
|
27
|
+
validateReservedWords: true,
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Simple Inventory System
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const simpleSKU = createSKUSemantic({
|
|
35
|
+
validationMode: 'lenient',
|
|
36
|
+
caseMode: 'preserve',
|
|
37
|
+
enforceUniqueness: true,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Auto-generating SKUs
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const autoGeneratedSKU = createSKUSemantic({
|
|
45
|
+
validationMode: 'strict',
|
|
46
|
+
caseMode: 'uppercase',
|
|
47
|
+
autoGenerate: true,
|
|
48
|
+
autoGenerateSource: 'name', // Generate from product name
|
|
49
|
+
enforceUniqueness: true,
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. Custom Pattern Validation
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const customPatternSKU = createSKUSemantic({
|
|
57
|
+
validationMode: 'custom',
|
|
58
|
+
customPattern: '^[A-Z]{3}-\\d{4}-[A-Z]{2}$', // Format: ABC-1234-XY
|
|
59
|
+
caseMode: 'uppercase',
|
|
60
|
+
enforceUniqueness: true,
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 5. Flexible Multi-category System
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const flexibleSKU = createSKUSemantic({
|
|
68
|
+
validationMode: 'lenient',
|
|
69
|
+
caseMode: 'preserve',
|
|
70
|
+
enforceUniqueness: true,
|
|
71
|
+
validateReservedWords: false, // Allow more flexibility
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Storage Format Details
|
|
76
|
+
|
|
77
|
+
### Database Schema Generation
|
|
78
|
+
|
|
79
|
+
The runtime will generate appropriate database schemas with unique constraints:
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
CREATE TABLE products (
|
|
83
|
+
id SERIAL PRIMARY KEY,
|
|
84
|
+
name VARCHAR(255),
|
|
85
|
+
sku VARCHAR(50) UNIQUE NOT NULL,
|
|
86
|
+
description TEXT
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
-- Automatically creates unique index
|
|
90
|
+
CREATE UNIQUE INDEX idx_products_sku ON products(sku);
|
|
91
|
+
|
|
92
|
+
-- For case-insensitive uniqueness (depending on configuration)
|
|
93
|
+
CREATE UNIQUE INDEX idx_products_sku_lower ON products(LOWER(sku));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Case Transformation Examples
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Uppercase transformation
|
|
100
|
+
const uppercaseSKU = createSKUSemantic({ caseMode: 'uppercase' })
|
|
101
|
+
// Input: "prod-123" → Stored: "PROD-123"
|
|
102
|
+
|
|
103
|
+
// Lowercase transformation
|
|
104
|
+
const lowercaseSKU = createSKUSemantic({ caseMode: 'lowercase' })
|
|
105
|
+
// Input: "PROD-123" → Stored: "prod-123"
|
|
106
|
+
|
|
107
|
+
// Preserve original case
|
|
108
|
+
const preserveCaseSKU = createSKUSemantic({ caseMode: 'preserve' })
|
|
109
|
+
// Input: "Prod-123" → Stored: "Prod-123"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Validation Examples
|
|
113
|
+
|
|
114
|
+
### Strict Validation
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const strictSKU = createSKUSemantic({ validationMode: 'strict' })
|
|
118
|
+
|
|
119
|
+
// Valid SKUs
|
|
120
|
+
strictSKU.validate('PRODUCT123') // ✓ Valid
|
|
121
|
+
strictSKU.validate('PROD-123') // ✓ Valid
|
|
122
|
+
strictSKU.validate('PROD_123') // ✓ Valid
|
|
123
|
+
|
|
124
|
+
// Invalid SKUs
|
|
125
|
+
strictSKU.validate('PROD.123') // ✗ Contains dot
|
|
126
|
+
strictSKU.validate('PROD@123') // ✗ Contains special character
|
|
127
|
+
strictSKU.validate('PROD 123') // ✗ Contains space
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Lenient Validation
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const lenientSKU = createSKUSemantic({ validationMode: 'lenient' })
|
|
134
|
+
|
|
135
|
+
// Valid SKUs
|
|
136
|
+
lenientSKU.validate('PRODUCT123') // ✓ Valid
|
|
137
|
+
lenientSKU.validate('PROD-123') // ✓ Valid
|
|
138
|
+
lenientSKU.validate('PROD.123') // ✓ Valid (dots allowed)
|
|
139
|
+
|
|
140
|
+
// Invalid SKUs
|
|
141
|
+
lenientSKU.validate('PROD@123') // ✗ Special characters not allowed
|
|
142
|
+
lenientSKU.validate('PROD#123') // ✗ Hash not allowed
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Pattern Validation
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const customSKU = createSKUSemantic({
|
|
149
|
+
validationMode: 'custom',
|
|
150
|
+
customPattern: '^[A-Z]{2}\\d{4}$' // Two letters followed by four digits
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Valid SKUs
|
|
154
|
+
customSKU.validate('AB1234') // ✓ Valid
|
|
155
|
+
customSKU.validate('XY9999') // ✓ Valid
|
|
156
|
+
|
|
157
|
+
// Invalid SKUs
|
|
158
|
+
customSKU.validate('ABC123') // ✗ Wrong format
|
|
159
|
+
customSKU.validate('ab1234') // ✗ Lowercase letters
|
|
160
|
+
customSKU.validate('A1234') // ✗ Only one letter
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Reserved Words
|
|
164
|
+
|
|
165
|
+
The SKU semantic includes protection against common reserved values:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const skuConfig = createSKUSemantic({
|
|
169
|
+
validateReservedWords: true,
|
|
170
|
+
reservedValues: ['ADMIN', 'TEST', 'NULL', 'DEFAULT', 'SAMPLE']
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// These will be rejected
|
|
174
|
+
skuConfig.validate('ADMIN') // ✗ Reserved word
|
|
175
|
+
skuConfig.validate('test') // ✗ Reserved word (case-insensitive)
|
|
176
|
+
skuConfig.validate('NULL') // ✗ Reserved word
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Auto-generation Behavior
|
|
180
|
+
|
|
181
|
+
When `autoGenerate` is enabled, the runtime automatically creates SKUs:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const autoSKU = createSKUSemantic({
|
|
185
|
+
autoGenerate: true,
|
|
186
|
+
autoGenerateSource: 'name',
|
|
187
|
+
prefix: 'PROD-',
|
|
188
|
+
caseMode: 'uppercase'
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// For a product named "Blue Widget"
|
|
192
|
+
// Generated SKU might be: "PROD-BLUE-WIDGET" or "PROD-BLUE-WIDGET-001"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Error Handling
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { validateSKUConfig, validateSKUValue } from './SKU.js'
|
|
199
|
+
|
|
200
|
+
// Validate configuration
|
|
201
|
+
const configErrors = validateSKUConfig({
|
|
202
|
+
validationMode: 'custom' // Invalid: missing customPattern
|
|
203
|
+
})
|
|
204
|
+
console.log(configErrors) // ['customPattern is required when validationMode is custom']
|
|
205
|
+
|
|
206
|
+
// Validate SKU values
|
|
207
|
+
const valueErrors = validateSKUValue('TEST')
|
|
208
|
+
console.log(valueErrors) // ['SKU cannot use reserved value: TEST']
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Integration with Domain Entities
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// In your domain model
|
|
215
|
+
const productEntity = domain.addEntity({
|
|
216
|
+
name: 'Product',
|
|
217
|
+
properties: [
|
|
218
|
+
{
|
|
219
|
+
name: 'sku',
|
|
220
|
+
type: 'string',
|
|
221
|
+
semantics: [createSKUSemantic({
|
|
222
|
+
validationMode: 'strict',
|
|
223
|
+
caseMode: 'uppercase',
|
|
224
|
+
prefix: 'PROD-',
|
|
225
|
+
enforceUniqueness: true
|
|
226
|
+
})]
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
})
|
|
230
|
+
```
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Supported SKU validation modes.
|
|
6
|
+
*/
|
|
7
|
+
export type SKUValidationMode = 'strict' | 'lenient' | 'custom'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Supported SKU case transformation modes.
|
|
11
|
+
*/
|
|
12
|
+
export type SKUCaseMode = 'uppercase' | 'lowercase' | 'preserve'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Configuration options for the SKU semantic.
|
|
16
|
+
* Controls SKU validation, formatting, uniqueness enforcement, and pattern matching.
|
|
17
|
+
*/
|
|
18
|
+
export interface SKUConfig {
|
|
19
|
+
/**
|
|
20
|
+
* The validation mode for SKU format.
|
|
21
|
+
*
|
|
22
|
+
* - 'strict': Enforces alphanumeric characters with optional hyphens/underscores
|
|
23
|
+
* - 'lenient': Allows more special characters but still validates basic format
|
|
24
|
+
* - 'custom': Uses custom regex pattern defined in `customPattern`
|
|
25
|
+
*/
|
|
26
|
+
validationMode?: SKUValidationMode
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Custom regex pattern for SKU validation when using 'custom' validation mode.
|
|
30
|
+
* Only used when validationMode is 'custom'.
|
|
31
|
+
*/
|
|
32
|
+
customPattern?: string
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Case transformation to apply to SKUs.
|
|
36
|
+
* - 'uppercase': Convert to uppercase (recommended for consistency)
|
|
37
|
+
* - 'lowercase': Convert to lowercase
|
|
38
|
+
* - 'preserve': Keep original case
|
|
39
|
+
*/
|
|
40
|
+
caseMode?: SKUCaseMode
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Prefix to automatically add to SKUs if not present.
|
|
44
|
+
* Useful for organizing SKUs by category (e.g., 'PROD-', 'SKU-').
|
|
45
|
+
*/
|
|
46
|
+
prefix?: string
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether to enforce global uniqueness across all entities.
|
|
50
|
+
* When true, creates unique database constraints.
|
|
51
|
+
* Default: true
|
|
52
|
+
*/
|
|
53
|
+
enforceUniqueness?: boolean
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether to auto-generate SKUs when not provided.
|
|
57
|
+
* When true, generates SKUs based on other fields or random values.
|
|
58
|
+
* Default: false
|
|
59
|
+
*/
|
|
60
|
+
autoGenerate?: boolean
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Field name to use as source for auto-generation.
|
|
64
|
+
* Only used when autoGenerate is true.
|
|
65
|
+
* If not specified, uses random generation.
|
|
66
|
+
*/
|
|
67
|
+
autoGenerateSource?: string
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether to validate that SKU doesn't conflict with reserved words or patterns.
|
|
71
|
+
* Default: true
|
|
72
|
+
*/
|
|
73
|
+
validateReservedWords?: boolean
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* List of reserved SKU values that cannot be used.
|
|
77
|
+
* Common examples: 'ADMIN', 'TEST', 'NULL', 'DEFAULT'
|
|
78
|
+
*/
|
|
79
|
+
reservedValues?: string[]
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Custom metadata for the SKU field.
|
|
83
|
+
*/
|
|
84
|
+
metadata?: Record<string, unknown>
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Index signature to allow additional properties.
|
|
88
|
+
*/
|
|
89
|
+
[key: string]: unknown
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Type-safe configuration for SKU semantic.
|
|
94
|
+
*/
|
|
95
|
+
export interface AppliedSKUSemantic extends AppliedDataSemantic {
|
|
96
|
+
id: SemanticType.SKU
|
|
97
|
+
config?: SKUConfig
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Type guard to check if a semantic is a SKU semantic.
|
|
102
|
+
*/
|
|
103
|
+
export const isSKUSemantic = (semantic: AppliedDataSemantic): semantic is AppliedSKUSemantic => {
|
|
104
|
+
return semantic.id === SemanticType.SKU
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Helper function to create a SKU semantic with configuration.
|
|
109
|
+
*/
|
|
110
|
+
export const createSKUSemantic = (config: SKUConfig = {}): AppliedSKUSemantic => {
|
|
111
|
+
const mergedConfig = {
|
|
112
|
+
...DEFAULT_SKU_CONFIG,
|
|
113
|
+
...config,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Merge metadata separately
|
|
117
|
+
if (config.metadata) {
|
|
118
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
id: SemanticType.SKU,
|
|
123
|
+
config: mergedConfig,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Default configuration for SKU semantic.
|
|
129
|
+
* Optimized for common product catalog use cases.
|
|
130
|
+
*/
|
|
131
|
+
export const DEFAULT_SKU_CONFIG: SKUConfig = {
|
|
132
|
+
validationMode: 'strict',
|
|
133
|
+
caseMode: 'uppercase',
|
|
134
|
+
enforceUniqueness: true,
|
|
135
|
+
autoGenerate: false,
|
|
136
|
+
validateReservedWords: true,
|
|
137
|
+
reservedValues: ['ADMIN', 'TEST', 'NULL', 'DEFAULT', 'UNDEFINED', 'SAMPLE', 'DEMO'],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Predefined configurations for common use cases.
|
|
142
|
+
*/
|
|
143
|
+
export const SKU_PRESETS = {
|
|
144
|
+
/**
|
|
145
|
+
* Standard product SKU with strict validation and uppercase formatting.
|
|
146
|
+
*/
|
|
147
|
+
PRODUCT_STANDARD: createSKUSemantic({
|
|
148
|
+
validationMode: 'strict',
|
|
149
|
+
caseMode: 'uppercase',
|
|
150
|
+
prefix: 'PROD-',
|
|
151
|
+
enforceUniqueness: true,
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Simple SKU configuration for basic catalogs.
|
|
156
|
+
*/
|
|
157
|
+
SIMPLE: createSKUSemantic({
|
|
158
|
+
validationMode: 'lenient',
|
|
159
|
+
caseMode: 'preserve',
|
|
160
|
+
enforceUniqueness: true,
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Auto-generating SKU from product name.
|
|
165
|
+
*/
|
|
166
|
+
AUTO_GENERATE: createSKUSemantic({
|
|
167
|
+
validationMode: 'strict',
|
|
168
|
+
caseMode: 'uppercase',
|
|
169
|
+
autoGenerate: true,
|
|
170
|
+
autoGenerateSource: 'name',
|
|
171
|
+
enforceUniqueness: true,
|
|
172
|
+
}),
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Flexible SKU for variable product types.
|
|
176
|
+
*/
|
|
177
|
+
FLEXIBLE: createSKUSemantic({
|
|
178
|
+
validationMode: 'lenient',
|
|
179
|
+
caseMode: 'preserve',
|
|
180
|
+
enforceUniqueness: true,
|
|
181
|
+
validateReservedWords: false,
|
|
182
|
+
}),
|
|
183
|
+
} as const
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Helper function to validate a SKU configuration.
|
|
187
|
+
*/
|
|
188
|
+
export const validateSKUConfig = (config: SKUConfig): string[] => {
|
|
189
|
+
const errors: string[] = []
|
|
190
|
+
|
|
191
|
+
if (config.validationMode === 'custom' && !config.customPattern) {
|
|
192
|
+
errors.push('customPattern is required when validationMode is custom')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (config.customPattern) {
|
|
196
|
+
try {
|
|
197
|
+
new RegExp(config.customPattern)
|
|
198
|
+
} catch {
|
|
199
|
+
errors.push('customPattern must be a valid regular expression')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (config.prefix && config.prefix.length === 0) {
|
|
204
|
+
errors.push('prefix cannot be empty string')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (config.autoGenerateSource && !config.autoGenerate) {
|
|
208
|
+
errors.push('autoGenerate must be true when autoGenerateSource is specified')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return errors
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Helper function to validate SKU value against configuration.
|
|
216
|
+
*/
|
|
217
|
+
export const validateSKUValue = (value: string, config: SKUConfig = DEFAULT_SKU_CONFIG): string[] => {
|
|
218
|
+
const errors: string[] = []
|
|
219
|
+
const mergedConfig = { ...DEFAULT_SKU_CONFIG, ...config }
|
|
220
|
+
|
|
221
|
+
if (!value || typeof value !== 'string') {
|
|
222
|
+
errors.push('SKU value must be a non-empty string')
|
|
223
|
+
return errors
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (mergedConfig.validateReservedWords && mergedConfig.reservedValues) {
|
|
227
|
+
const normalizedValue = value.toUpperCase()
|
|
228
|
+
if (mergedConfig.reservedValues.some((reserved) => reserved.toUpperCase() === normalizedValue)) {
|
|
229
|
+
errors.push(`SKU cannot use reserved value: ${value}`)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Validation pattern checks
|
|
234
|
+
if (mergedConfig.validationMode === 'strict') {
|
|
235
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
236
|
+
errors.push('SKU can only contain alphanumeric characters, hyphens, and underscores')
|
|
237
|
+
}
|
|
238
|
+
} else if (mergedConfig.validationMode === 'lenient') {
|
|
239
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(value)) {
|
|
240
|
+
errors.push('SKU contains invalid characters')
|
|
241
|
+
}
|
|
242
|
+
} else if (mergedConfig.validationMode === 'custom' && mergedConfig.customPattern) {
|
|
243
|
+
try {
|
|
244
|
+
const regex = new RegExp(mergedConfig.customPattern)
|
|
245
|
+
if (!regex.test(value)) {
|
|
246
|
+
errors.push('SKU does not match the required pattern')
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
errors.push('Invalid custom pattern configuration')
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return errors
|
|
254
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the Status semantic.
|
|
6
|
+
* These options control state management and workflow behavior.
|
|
7
|
+
*/
|
|
8
|
+
export interface StatusConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Allowed states for this status field.
|
|
11
|
+
* Only required when the field doesn't have enum values defined in the schema.
|
|
12
|
+
* If enum values are defined in DomainProperty > Schema, those values will be used instead.
|
|
13
|
+
*/
|
|
14
|
+
allowedStates?: string[]
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default state when creating new records.
|
|
18
|
+
* Must be one of the allowedStates (if specified) or enum values from the schema.
|
|
19
|
+
*/
|
|
20
|
+
defaultState: string
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* State transitions configuration.
|
|
24
|
+
* Maps from current state to array of allowed next states.
|
|
25
|
+
*/
|
|
26
|
+
transitions?: Record<string, string[]>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* State-specific behaviors and permissions.
|
|
30
|
+
*/
|
|
31
|
+
stateBehaviors?: Record<
|
|
32
|
+
string,
|
|
33
|
+
{
|
|
34
|
+
/**
|
|
35
|
+
* Whether records in this state are publicly visible.
|
|
36
|
+
* Defaults to true if not specified.
|
|
37
|
+
*/
|
|
38
|
+
isPublic?: boolean
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether records in this state can be edited.
|
|
42
|
+
* Defaults to true if not specified.
|
|
43
|
+
*/
|
|
44
|
+
isEditable?: boolean
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether records in this state can be deleted.
|
|
48
|
+
* Defaults to false if not specified.
|
|
49
|
+
*/
|
|
50
|
+
isDeletable?: boolean
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether this state requires approval before transition.
|
|
54
|
+
* Defaults to false if not specified.
|
|
55
|
+
*/
|
|
56
|
+
requiresApproval?: boolean
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Custom display name for this state.
|
|
60
|
+
*/
|
|
61
|
+
displayName?: string
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Custom description for this state.
|
|
65
|
+
*/
|
|
66
|
+
description?: string
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Color or visual indicator for this state.
|
|
70
|
+
*/
|
|
71
|
+
color?: string
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Icon for this state.
|
|
75
|
+
*/
|
|
76
|
+
icon?: string
|
|
77
|
+
}
|
|
78
|
+
>
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Workflow configuration for status management.
|
|
82
|
+
*/
|
|
83
|
+
workflow?: {
|
|
84
|
+
/**
|
|
85
|
+
* Whether status changes require approval.
|
|
86
|
+
* Defaults to false if not specified.
|
|
87
|
+
*/
|
|
88
|
+
requiresApproval?: boolean
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Roles that can approve status changes.
|
|
92
|
+
* Required if requiresApproval is true.
|
|
93
|
+
*/
|
|
94
|
+
approvalRoles?: string[]
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether to send notifications on status changes.
|
|
98
|
+
* Defaults to false if not specified.
|
|
99
|
+
*/
|
|
100
|
+
sendNotifications?: boolean
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Notification channels to use.
|
|
104
|
+
*/
|
|
105
|
+
notificationChannels?: ('email' | 'sms' | 'push' | 'webhook')[]
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether to log status change history.
|
|
109
|
+
* Defaults to true if not specified.
|
|
110
|
+
*/
|
|
111
|
+
logHistory?: boolean
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether to allow bulk status changes.
|
|
115
|
+
* Defaults to false if not specified.
|
|
116
|
+
*/
|
|
117
|
+
allowBulkChanges?: boolean
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Auto-transitions based on conditions.
|
|
122
|
+
*/
|
|
123
|
+
autoTransitions?: {
|
|
124
|
+
/**
|
|
125
|
+
* Current state that triggers the auto-transition.
|
|
126
|
+
*/
|
|
127
|
+
from: string
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Target state to transition to.
|
|
131
|
+
*/
|
|
132
|
+
to: string
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Condition that triggers the transition.
|
|
136
|
+
* Examples: "after 24 hours", "when approved", "when payment received"
|
|
137
|
+
*/
|
|
138
|
+
condition: string
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether this auto-transition requires approval.
|
|
142
|
+
* Defaults to false if not specified.
|
|
143
|
+
*/
|
|
144
|
+
requiresApproval?: boolean
|
|
145
|
+
}[]
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Custom metadata for the status field.
|
|
149
|
+
*/
|
|
150
|
+
metadata?: Record<string, unknown>
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Index signature to allow additional properties.
|
|
154
|
+
*/
|
|
155
|
+
[key: string]: unknown
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Type-safe configuration for Status semantic.
|
|
160
|
+
*/
|
|
161
|
+
export interface AppliedStatusSemantic extends AppliedDataSemantic {
|
|
162
|
+
id: SemanticType.Status
|
|
163
|
+
config?: StatusConfig
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Type guard to check if a semantic is a Status semantic.
|
|
168
|
+
*/
|
|
169
|
+
export const isStatusSemantic = (semantic: AppliedDataSemantic): semantic is AppliedStatusSemantic => {
|
|
170
|
+
return semantic.id === SemanticType.Status
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Helper function to create a Status semantic with configuration.
|
|
175
|
+
*/
|
|
176
|
+
export const createStatusSemantic = (config: Partial<StatusConfig> = {}): AppliedStatusSemantic => {
|
|
177
|
+
const mergedConfig = {
|
|
178
|
+
...DEFAULT_STATUS_CONFIG,
|
|
179
|
+
...config,
|
|
180
|
+
// Deep merge nested objects
|
|
181
|
+
stateBehaviors: config.stateBehaviors
|
|
182
|
+
? { ...DEFAULT_STATUS_CONFIG.stateBehaviors, ...config.stateBehaviors }
|
|
183
|
+
: DEFAULT_STATUS_CONFIG.stateBehaviors,
|
|
184
|
+
workflow: config.workflow
|
|
185
|
+
? { ...DEFAULT_STATUS_CONFIG.workflow, ...config.workflow }
|
|
186
|
+
: DEFAULT_STATUS_CONFIG.workflow,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (config.metadata) {
|
|
190
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
id: SemanticType.Status,
|
|
195
|
+
config: mergedConfig,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Default configuration for Status semantic.
|
|
201
|
+
*/
|
|
202
|
+
export const DEFAULT_STATUS_CONFIG: StatusConfig = {
|
|
203
|
+
defaultState: 'draft',
|
|
204
|
+
stateBehaviors: {
|
|
205
|
+
draft: {
|
|
206
|
+
isPublic: false,
|
|
207
|
+
isEditable: true,
|
|
208
|
+
isDeletable: true,
|
|
209
|
+
},
|
|
210
|
+
published: {
|
|
211
|
+
isPublic: true,
|
|
212
|
+
isEditable: true,
|
|
213
|
+
isDeletable: false,
|
|
214
|
+
},
|
|
215
|
+
archived: {
|
|
216
|
+
isPublic: false,
|
|
217
|
+
isEditable: false,
|
|
218
|
+
isDeletable: false,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
workflow: {
|
|
222
|
+
requiresApproval: false,
|
|
223
|
+
sendNotifications: false,
|
|
224
|
+
logHistory: true,
|
|
225
|
+
allowBulkChanges: false,
|
|
226
|
+
},
|
|
227
|
+
}
|