-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin-system.ts
More file actions
196 lines (158 loc) · 6.89 KB
/
plugin-system.ts
File metadata and controls
196 lines (158 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import * as schemaDsl from '../../dist/index.js'
import {
DslBuilder,
ObjectDslBuilder,
PluginManager,
dsl,
validate,
type DslDefinition,
type JSONSchema,
} from '../../dist/index.js'
import customFormatPlugin from '../../dist/plugins/custom-format.js'
function objectDsl(definition: DslDefinition): ObjectDslBuilder {
return new ObjectDslBuilder(dsl(definition) as JSONSchema)
}
// ============================================================
// 1. PluginManager — lifecycle: register, install, has, runHook, uninstall
// ============================================================
const pluginManager = new PluginManager()
// Track lifecycle calls for demonstration
const callLog: string[] = []
const lifecyclePlugin = {
name: 'lifecycle-demo',
options: { version: '1.0.0' },
install(_core: unknown, options: Record<string, unknown>, context: { plugins: Map<string, unknown> }) {
callLog.push(`install:v${String(options.version)}`)
callLog.push(`peers:${context.plugins.size}`)
},
uninstall(_core: unknown) {
callLog.push('uninstall')
},
hooks: {
// Called before every validate() when the plugin is installed
onBeforeValidate(schema: unknown, data: unknown): string {
return `schema:${typeof schema}|data:${typeof data}`
},
// Called after every validate()
onAfterValidate(_schema: unknown, _data: unknown, result: unknown): string {
return `valid:${(result as { valid?: boolean })?.valid}`
},
},
}
// Register plugin definition (does not install yet)
pluginManager.register(lifecyclePlugin)
console.log('plugin-system.registered.has =', pluginManager.has('lifecycle-demo')) // true
// Install the plugin — calls plugin.install()
pluginManager.install(schemaDsl, 'lifecycle-demo', { version: '2.5.0' })
console.log('plugin-system.lifecycle.install.log =', callLog[0]) // 'install:v2.5.0'
// Run a hook — broadcasts to all installed plugins
const hookResults = await pluginManager.runHook(
'onBeforeValidate',
{ type: 'string' },
'hello'
)
console.log('plugin-system.lifecycle.hook.result =', hookResults[0]) // 'schema:object|data:string'
// Uninstall — calls plugin.uninstall()
pluginManager.uninstall('lifecycle-demo', schemaDsl)
console.log('plugin-system.lifecycle.uninstall.log =', callLog[callLog.length - 1]) // 'uninstall'
// ============================================================
// 2. DslBuilder.registerType — custom type registration at the builder level
// ============================================================
DslBuilder.clearCustomTypes()
// Register a 'sku' type: uppercase alphanumeric with a dash separator
DslBuilder.registerType('sku', {
type: 'string',
pattern: '^[A-Z]{2,4}-[0-9]{4,8}$',
description: 'Stock-keeping unit — e.g. PROD-001234',
} as any)
// Register a 'score' type: integer 0–100
DslBuilder.registerType('score', {
type: 'integer',
minimum: 0,
maximum: 100,
} as any)
console.log('plugin-system.registerType.hasSku =', DslBuilder.hasType('sku')) // true
console.log('plugin-system.registerType.hasScore =', DslBuilder.hasType('score')) // true
const productSchema = dsl({ sku: 'sku!', qualityScore: 'score', name: 'string:2-100!' })
console.log('plugin-system.custom.type.valid =',
validate(productSchema, { sku: 'PROD-001234', qualityScore: 85, name: 'Widget Pro' }).valid) // true
console.log('plugin-system.custom.type.invalid.sku =',
validate(productSchema, { sku: 'bad_sku', qualityScore: 85, name: 'Widget' }).valid) // false
console.log('plugin-system.custom.type.invalid.score =',
validate(productSchema, { sku: 'PROD-001234', qualityScore: 150, name: 'Widget' }).valid) // false
// List all registered custom types
const customTypes = DslBuilder.getCustomTypes()
console.log('plugin-system.registerType.list =', customTypes.slice().sort().join(',')) // 'score,sku'
// ============================================================
// 3. custom-format plugin — adds phone-cn, email-cn, etc.
// ============================================================
const pm2 = new PluginManager()
pm2.register(customFormatPlugin)
pm2.install(schemaDsl, 'custom-format')
console.log('plugin-system.customFormat.installed =', pm2.has('custom-format')) // true
console.log('plugin-system.customFormat.hasSku =', DslBuilder.hasType('phone-cn')) // true — registered by plugin
const phoneSchema = dsl({ phone: 'phone-cn!' })
console.log('plugin-system.customFormat.phone.valid =',
validate(phoneSchema, { phone: '13800138000' }).valid) // true
console.log('plugin-system.customFormat.phone.invalid =',
validate(phoneSchema, { phone: '99999999999' }).valid) // false
// ============================================================
// 4. Authoring a plugin — complete plugin pattern
// ============================================================
// A plugin that registers a 'latitude' and 'longitude' custom type
const geoTypesPlugin = {
name: 'geo-types',
options: {},
install(core: typeof schemaDsl): void {
// Register geo types through the documented public extension API
core.DslBuilder.registerType('latitude', {
type: 'number',
minimum: -90,
maximum: 90,
})
core.DslBuilder.registerType('longitude', {
type: 'number',
minimum: -180,
maximum: 180,
})
},
uninstall(core: typeof schemaDsl): void {
// Clean up registered types
core.TypeRegistry.unregister('latitude')
core.TypeRegistry.unregister('longitude')
},
}
const pm3 = new PluginManager()
pm3.register(geoTypesPlugin)
pm3.install(schemaDsl, 'geo-types')
const locationSchema = dsl({ lat: 'latitude!', lng: 'longitude!', label: 'string:1-100' })
console.log('plugin-system.geo.valid =',
validate(locationSchema, { lat: 37.7749, lng: -122.4194, label: 'San Francisco' }).valid) // true
console.log('plugin-system.geo.invalid.lat =',
validate(locationSchema, { lat: 95, lng: 0 }).valid) // false — lat > 90
console.log('plugin-system.geo.invalid.lng =',
validate(locationSchema, { lat: 0, lng: 200 }).valid) // false — lng > 180
pm3.uninstall('geo-types', schemaDsl)
// ============================================================
// 5. validateNestingDepth — guard against deeply nested object schemas
// ============================================================
// Build a schema with 5 levels of object nesting
const deepObjSchema = objectDsl({
a: {
b: {
c: {
d: {
e: 'string',
},
},
},
},
}).toSchema()
// depth 4 — exceeds limit of 3
const depthResult = DslBuilder.validateNestingDepth(deepObjSchema as any, 3)
console.log('plugin-system.nestingDepth.tooDeep =', !depthResult.valid) // true
// depth 2 — within limit
const shallowObjSchema = objectDsl({ a: dsl({ b: 'string' }) }).toSchema()
const shallowResult = DslBuilder.validateNestingDepth(shallowObjSchema as any, 4)
console.log('plugin-system.nestingDepth.allowed =', shallowResult.valid) // true
DslBuilder.clearCustomTypes()