@api-client/core 0.15.1 → 0.16.0
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/data/models/example-generator-api.json +11 -11
- 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,180 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Supported storage formats for monetary values.
|
|
6
|
+
*/
|
|
7
|
+
export type PriceStorageFormat = 'decimal' | 'integer_cents' | 'complex_object'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the Price semantic.
|
|
11
|
+
* Controls monetary value storage, validation, currency handling, and precision.
|
|
12
|
+
*/
|
|
13
|
+
export interface PriceConfig {
|
|
14
|
+
/**
|
|
15
|
+
* How the monetary value is stored in the database.
|
|
16
|
+
*
|
|
17
|
+
* - 'decimal': Store as DECIMAL/NUMERIC type (e.g., 19.99)
|
|
18
|
+
* - 'integer_cents': Store as INTEGER in smallest currency unit (e.g., 1999 for $19.99)
|
|
19
|
+
* - 'complex_object': Store as JSON/JSONB with amount and currency (e.g., {"amount": 1999, "currency": "USD"})
|
|
20
|
+
*/
|
|
21
|
+
storageFormat?: PriceStorageFormat
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default currency code (ISO 4217) when not specified.
|
|
25
|
+
* Required when using 'decimal' or 'integer_cents' format.
|
|
26
|
+
*/
|
|
27
|
+
defaultCurrency?: string
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* List of allowed currency codes (ISO 4217).
|
|
31
|
+
* If not specified, all valid ISO 4217 currencies are allowed.
|
|
32
|
+
*/
|
|
33
|
+
allowedCurrencies?: string[]
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Number of decimal places for precision.
|
|
37
|
+
* Default: 2 (for most currencies like USD, EUR)
|
|
38
|
+
* Some currencies may need 0 (JPY, KRW) or 3 (BHD, KWD)
|
|
39
|
+
*/
|
|
40
|
+
decimalPlaces?: number
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether negative values are allowed (for refunds, discounts, etc.).
|
|
44
|
+
*/
|
|
45
|
+
allowNegative?: boolean
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether to automatically validate currency codes against ISO 4217.
|
|
49
|
+
*/
|
|
50
|
+
validateCurrencyCode?: boolean
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Custom metadata for the price field.
|
|
54
|
+
*/
|
|
55
|
+
metadata?: Record<string, unknown>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Index signature to allow additional properties.
|
|
59
|
+
*/
|
|
60
|
+
[key: string]: unknown
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Type-safe configuration for Price semantic.
|
|
65
|
+
*/
|
|
66
|
+
export interface AppliedPriceSemantic extends AppliedDataSemantic {
|
|
67
|
+
id: SemanticType.Price
|
|
68
|
+
config?: PriceConfig
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if a semantic is a Price semantic.
|
|
73
|
+
*/
|
|
74
|
+
export const isPriceSemantic = (semantic: AppliedDataSemantic): semantic is AppliedPriceSemantic => {
|
|
75
|
+
return semantic.id === SemanticType.Price
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Helper function to create a Price semantic with configuration.
|
|
80
|
+
*/
|
|
81
|
+
export const createPriceSemantic = (config: PriceConfig = {}): AppliedPriceSemantic => {
|
|
82
|
+
const mergedConfig = {
|
|
83
|
+
...DEFAULT_PRICE_CONFIG,
|
|
84
|
+
...config,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Merge metadata separately
|
|
88
|
+
if (config.metadata) {
|
|
89
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: SemanticType.Price,
|
|
94
|
+
config: mergedConfig,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default configuration for Price semantic.
|
|
100
|
+
* Optimized for common e-commerce use cases with USD currency.
|
|
101
|
+
*/
|
|
102
|
+
export const DEFAULT_PRICE_CONFIG: PriceConfig = {
|
|
103
|
+
storageFormat: 'decimal',
|
|
104
|
+
defaultCurrency: 'USD',
|
|
105
|
+
decimalPlaces: 2,
|
|
106
|
+
allowNegative: false,
|
|
107
|
+
validateCurrencyCode: true,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Predefined configurations for common use cases.
|
|
112
|
+
*/
|
|
113
|
+
export const PRICE_PRESETS = {
|
|
114
|
+
/**
|
|
115
|
+
* Simple USD decimal storage - good for most e-commerce sites.
|
|
116
|
+
*/
|
|
117
|
+
USD_DECIMAL: createPriceSemantic({
|
|
118
|
+
storageFormat: 'decimal',
|
|
119
|
+
defaultCurrency: 'USD',
|
|
120
|
+
decimalPlaces: 2,
|
|
121
|
+
allowNegative: false,
|
|
122
|
+
}),
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Integer cents storage - avoids floating point issues, good for financial calculations.
|
|
126
|
+
*/
|
|
127
|
+
USD_CENTS: createPriceSemantic({
|
|
128
|
+
storageFormat: 'integer_cents',
|
|
129
|
+
defaultCurrency: 'USD',
|
|
130
|
+
decimalPlaces: 2,
|
|
131
|
+
allowNegative: false,
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Multi-currency support with complex object storage.
|
|
136
|
+
*/
|
|
137
|
+
MULTI_CURRENCY: createPriceSemantic({
|
|
138
|
+
storageFormat: 'complex_object',
|
|
139
|
+
allowedCurrencies: ['USD', 'EUR', 'GBP', 'JPY', 'CAD'],
|
|
140
|
+
decimalPlaces: 2,
|
|
141
|
+
allowNegative: false,
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Configuration that allows negative values for refunds, discounts, etc.
|
|
146
|
+
*/
|
|
147
|
+
WITH_NEGATIVES: createPriceSemantic({
|
|
148
|
+
storageFormat: 'decimal',
|
|
149
|
+
defaultCurrency: 'USD',
|
|
150
|
+
decimalPlaces: 2,
|
|
151
|
+
allowNegative: true,
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* High-precision configuration for cryptocurrency or precious metals.
|
|
156
|
+
*/
|
|
157
|
+
HIGH_PRECISION: createPriceSemantic({
|
|
158
|
+
storageFormat: 'decimal',
|
|
159
|
+
defaultCurrency: 'BTC',
|
|
160
|
+
decimalPlaces: 8,
|
|
161
|
+
allowNegative: false,
|
|
162
|
+
}),
|
|
163
|
+
} as const
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Helper function to validate a price configuration.
|
|
167
|
+
*/
|
|
168
|
+
export const validatePriceConfig = (config: PriceConfig): string[] => {
|
|
169
|
+
const errors: string[] = []
|
|
170
|
+
|
|
171
|
+
if (config.storageFormat !== 'complex_object' && !config.defaultCurrency) {
|
|
172
|
+
errors.push('defaultCurrency is required when storageFormat is not complex_object')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (config.decimalPlaces !== undefined && config.decimalPlaces < 0) {
|
|
176
|
+
errors.push('decimalPlaces must be non-negative')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return errors
|
|
180
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the PublicUniqueName (slug) semantic.
|
|
6
|
+
* Controls how slugs are generated and validated.
|
|
7
|
+
*/
|
|
8
|
+
export interface PublicUniqueNameConfig {
|
|
9
|
+
/**
|
|
10
|
+
* The source field to generate the slug from (e.g., 'title').
|
|
11
|
+
* Default to the field that is annotated with the Title semantic.
|
|
12
|
+
*/
|
|
13
|
+
sourceField?: string
|
|
14
|
+
/**
|
|
15
|
+
* Separator character to use in the slug (default: '-').
|
|
16
|
+
*/
|
|
17
|
+
separator?: string
|
|
18
|
+
/**
|
|
19
|
+
* Maximum length of the slug (default: 64).
|
|
20
|
+
*/
|
|
21
|
+
maxLength?: number
|
|
22
|
+
/**
|
|
23
|
+
* Whether the slug is case sensitive (default: false).
|
|
24
|
+
*/
|
|
25
|
+
caseSensitive?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* Whether to allow Unicode characters in the slug (default: false).
|
|
28
|
+
*/
|
|
29
|
+
allowUnicode?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Name of a custom generator function to use for slug creation.
|
|
32
|
+
*/
|
|
33
|
+
customGenerator?: string
|
|
34
|
+
/**
|
|
35
|
+
* Whether to allow duplicate slugs (default: false).
|
|
36
|
+
*/
|
|
37
|
+
allowDuplicates?: boolean
|
|
38
|
+
/**
|
|
39
|
+
* Fallback field to use if the source field is empty or invalid.
|
|
40
|
+
*/
|
|
41
|
+
fallbackField?: string
|
|
42
|
+
/**
|
|
43
|
+
* Custom metadata for the slug field.
|
|
44
|
+
*/
|
|
45
|
+
metadata?: Record<string, unknown>
|
|
46
|
+
/**
|
|
47
|
+
* Index signature to allow additional properties.
|
|
48
|
+
*/
|
|
49
|
+
[key: string]: unknown
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Type-safe configuration for PublicUniqueName semantic.
|
|
54
|
+
*/
|
|
55
|
+
export interface AppliedPublicUniqueNameSemantic extends AppliedDataSemantic {
|
|
56
|
+
id: SemanticType.PublicUniqueName
|
|
57
|
+
config?: PublicUniqueNameConfig
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Type guard to check if a semantic is a PublicUniqueName semantic.
|
|
62
|
+
*/
|
|
63
|
+
export const isPublicUniqueNameSemantic = (
|
|
64
|
+
semantic: AppliedDataSemantic
|
|
65
|
+
): semantic is AppliedPublicUniqueNameSemantic => {
|
|
66
|
+
return semantic.id === SemanticType.PublicUniqueName
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Helper function to create a PublicUniqueName semantic with configuration.
|
|
71
|
+
*/
|
|
72
|
+
export const createPublicUniqueNameSemantic = (
|
|
73
|
+
config: PublicUniqueNameConfig = {}
|
|
74
|
+
): AppliedPublicUniqueNameSemantic => {
|
|
75
|
+
const mergedConfig = {
|
|
76
|
+
...DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG,
|
|
77
|
+
...config,
|
|
78
|
+
}
|
|
79
|
+
if (config.metadata) {
|
|
80
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
id: SemanticType.PublicUniqueName,
|
|
85
|
+
config: mergedConfig,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Default configuration for PublicUniqueName semantic.
|
|
91
|
+
*/
|
|
92
|
+
export const DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG: PublicUniqueNameConfig = {
|
|
93
|
+
separator: '-',
|
|
94
|
+
maxLength: 64,
|
|
95
|
+
caseSensitive: false,
|
|
96
|
+
allowUnicode: false,
|
|
97
|
+
allowDuplicates: false,
|
|
98
|
+
}
|
|
@@ -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
|
+
```
|