@banbolee/dsh-fish-shell 0.7.0 → 0.8.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +310 -13
  3. package/README.zh.md +99 -4
  4. package/THIRD-PARTY-NOTICES.md +91 -0
  5. package/cordis.patch.yml +56 -0
  6. package/node_modules/@deepseek-ai/cosmokit/LICENSE +21 -0
  7. package/node_modules/@deepseek-ai/cosmokit/README.md +30 -0
  8. package/node_modules/@deepseek-ai/cosmokit/lib/index.js +418 -0
  9. package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts +15 -0
  10. package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts.map +1 -0
  11. package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts +13 -0
  12. package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts.map +1 -0
  13. package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts +41 -0
  14. package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts.map +1 -0
  15. package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts +89 -0
  16. package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts.map +1 -0
  17. package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts +19 -0
  18. package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts.map +1 -0
  19. package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts +42 -0
  20. package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts.map +1 -0
  21. package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts +40 -0
  22. package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts.map +1 -0
  23. package/node_modules/@deepseek-ai/cosmokit/package.json +32 -0
  24. package/node_modules/@deepseek-ai/cosmokit/src/array.ts +42 -0
  25. package/node_modules/@deepseek-ai/cosmokit/src/index.ts +12 -0
  26. package/node_modules/@deepseek-ai/cosmokit/src/misc.ts +78 -0
  27. package/node_modules/@deepseek-ai/cosmokit/src/string.ts +113 -0
  28. package/node_modules/@deepseek-ai/cosmokit/src/time.ts +92 -0
  29. package/node_modules/@deepseek-ai/cosmokit/src/types.ts +168 -0
  30. package/node_modules/@deepseek-ai/cosmokit/src/volatile.ts +86 -0
  31. package/node_modules/@deepseek-ai/dsh-tool-terminal/LICENSE +21 -0
  32. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.i18n.yaml +100 -0
  33. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.md +186 -0
  34. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.zh.md +186 -0
  35. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/index.js +646 -0
  36. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/background.d.ts +20 -0
  37. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/index.d.ts +32 -0
  38. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/render.d.ts +81 -0
  39. package/node_modules/@deepseek-ai/dsh-tool-terminal/package.json +63 -0
  40. package/node_modules/@deepseek-ai/schemastery/LICENSE +21 -0
  41. package/node_modules/@deepseek-ai/schemastery/README.md +393 -0
  42. package/node_modules/@deepseek-ai/schemastery/lib/index.cjs +635 -0
  43. package/node_modules/@deepseek-ai/schemastery/lib/index.mjs +635 -0
  44. package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts +211 -0
  45. package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts.map +1 -0
  46. package/node_modules/@deepseek-ai/schemastery/package.json +39 -0
  47. package/node_modules/@deepseek-ai/schemastery/src/index.ts +962 -0
  48. package/node_modules/@standard-schema/spec/LICENSE +21 -0
  49. package/node_modules/@standard-schema/spec/README.md +198 -0
  50. package/node_modules/@standard-schema/spec/dist/index.cjs +18 -0
  51. package/node_modules/@standard-schema/spec/dist/index.d.cts +119 -0
  52. package/node_modules/@standard-schema/spec/dist/index.d.ts +119 -0
  53. package/node_modules/@standard-schema/spec/dist/index.js +0 -0
  54. package/node_modules/@standard-schema/spec/package.json +52 -0
  55. package/package.json +17 -3
  56. package/terminal-tools.js +259 -0
  57. package/terminal.js +297 -0
@@ -0,0 +1,962 @@
1
+ import { Binary, clone, deepEqual, filterKeys, isNullable, isPlainObject, pick, valueMap, type Dict } from '@deepseek-ai/cosmokit'
2
+ import { createVolatile, isVolatile, type Volatile } from '@deepseek-ai/cosmokit'
3
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
4
+
5
+ const kSchema = Symbol.for('schemastery')
6
+ const kValidationError = Symbol.for('ValidationError')
7
+
8
+ declare global {
9
+ namespace Schemastery {
10
+ /** Convert primitive constructors, constants, and existing schemas into a schema type. */
11
+ export type From<X> =
12
+ | X extends string | number | boolean ? Schema<X>
13
+ : X extends Schema<any, any, SchemaMode> ? X
14
+ : X extends typeof String ? Schema<string>
15
+ : X extends typeof Number ? Schema<number>
16
+ : X extends typeof Boolean ? Schema<boolean>
17
+ : X extends typeof Function ? Schema<Function, (...args: any[]) => any>
18
+ : X extends Constructor<infer S> ? Schema<S>
19
+ : never
20
+
21
+ type TypeS1<X> = X extends Schema<infer S, infer _T, infer _M> ? S : never
22
+ type Inverse<X> = X extends Schema<infer _S, infer T, infer M> ? (arg: SchemaOutput<T, M>) => void : never
23
+
24
+ /** Input type accepted by a schema-like value. */
25
+ export type TypeS<X> = TypeS1<From<X>>
26
+ /** Output type returned by a schema-like value after validation. */
27
+ export type TypeT<X> = ReturnType<From<X>>
28
+ /** Resolver callback used by custom schema types registered with `Schema.extend()`. */
29
+ export type Resolve = (data: any, schema: Schema, options: Options, strict?: boolean) => [any, any?]
30
+
31
+ /** Input type accepted by one schema in an intersection. */
32
+ export type IntersectS<X> = From<X> extends Schema<infer S, infer _T, infer _M> ? S : never
33
+ /** Output type returned by one schema in an intersection. */
34
+ export type IntersectT<X> = Inverse<From<X>> extends ((arg: infer T) => void) ? T : never
35
+
36
+ type TupleS<X extends readonly any[]> = X extends readonly [infer L, ...infer R] ? [TypeS<L>?, ...TupleS<R>] : any[]
37
+ type TupleT<X extends readonly any[]> = X extends readonly [infer L, ...infer R] ? [TypeT<L>?, ...TupleT<R>] : any[]
38
+ type ObjectS<X extends Dict> = { [K in keyof X]?: TypeS<X[K]> | null } & Dict
39
+ type ObjectT<X extends Dict> = { [K in keyof X]: TypeT<X[K]> } & Dict
40
+ type Constructor<T = any> = new (...args: any[]) => T
41
+
42
+ /** Static constructor and factory methods exposed by the default `Schema` export. */
43
+ export interface Static {
44
+ <T = any>(options: Partial<Schema<T>>): Schema<T>
45
+ new <T = any>(options: Partial<Schema<T>>): Schema<T>
46
+ prototype: Schema
47
+ /** Validate a value against a schema node and return `[output, adaptedInput?]`. */
48
+ resolve: Resolve
49
+ /** Infer a schema from a primitive value, constructor, or existing schema. */
50
+ from<X = any>(source?: X): From<X>
51
+ /** Register a resolver for a custom schema `type`. */
52
+ extend(type: string, resolve: Resolve): void
53
+ /** Accept any value without validation. */
54
+ any<T = any>(): Schema<T>
55
+ /** Accept only nullable input. */
56
+ never(): Schema<never>
57
+ /** Accept exactly one constant value. */
58
+ const<const T>(value: T): Schema<T>
59
+ /** Accept strings, with optional metadata constraints added by instance methods. */
60
+ string(): Schema<string>
61
+ /** Accept numbers, with optional range and step constraints. */
62
+ number(): Schema<number>
63
+ /** Accept non-negative integer numbers. */
64
+ natural(): Schema<number>
65
+ /** Accept a number between 0 and 1 and mark it as a slider. */
66
+ percent(): Schema<number>
67
+ /** Accept booleans. */
68
+ boolean(): Schema<boolean>
69
+ /** Accept `Date` instances or parse datetime strings into `Date` objects. */
70
+ date(): Schema<string | Date, Date>
71
+ /** Accept `RegExp` instances or parse strings into regular expressions. */
72
+ regExp(flag?: string): Schema<string | RegExp, RegExp>
73
+ /** Accept binary sources and normalize them to `ArrayBufferLike`. */
74
+ arrayBuffer(): Schema<Binary.Source, ArrayBufferLike>
75
+ arrayBuffer(encoding: 'hex' | 'base64'): Schema<Binary.Source | string, ArrayBufferLike>
76
+ /** Accept a numeric bitset or string keys and normalize to a number. */
77
+ bitset<K extends string>(bits: Partial<Record<K, number>>): Schema<number | readonly K[], number>
78
+ /** Accept functions. */
79
+ function(): Schema<Function, (...args: any[]) => any>
80
+ /** Accept instances of a constructor or objects whose constructor name matches. */
81
+ is(constructor: string): Schema
82
+ is<T>(constructor: Constructor<T>): Schema<T>
83
+ /** Accept arrays whose elements match `inner`. */
84
+ array<X>(inner: X): Schema<TypeS<X>[], TypeT<X>[]>
85
+ /** Accept plain objects with values matching `inner` and optional key schema. */
86
+ dict<X, Y extends Schema<any, string> = Schema<string>>(inner: X, sKey?: Y): Schema<Dict<TypeS<X>, TypeS<Y>>, Dict<TypeT<X>, TypeT<Y>>>
87
+ /** Accept tuple arrays where each index matches the corresponding schema. */
88
+ tuple<const X extends readonly any[]>(list: X): Schema<TupleS<X>, TupleT<X>>
89
+ /** Accept plain objects; infer fields from the dictionary, not the enclosing schema's output type. */
90
+ object<X extends Dict>(dict: X): Schema<ObjectS<NoInfer<X>>, ObjectT<NoInfer<X>>>
91
+ /** Accept values matching at least one schema in `list`. */
92
+ union<const X>(list: readonly X[]): Schema<TypeS<X>, TypeT<X>>
93
+ /** Accept values matching every schema in `list`, merging object outputs. */
94
+ intersect<const X>(list: readonly X[]): Schema<IntersectS<X>, IntersectT<X>>
95
+ /** Validate with `inner`, then convert the result with `callback`. */
96
+ transform<X, T>(inner: X, callback: (value: TypeS<X>, options: Schemastery.Options) => T, preserve?: boolean): Schema<TypeS<X>, T>
97
+ /** Defer construction of a recursive schema until validation or serialization. */
98
+ lazy<X extends Schema<any, any, SchemaMode>>(callback: () => X): X
99
+ ValidationError: typeof ValidationError
100
+ }
101
+
102
+ /** Runtime validation options shared by all schema calls. */
103
+ interface Options {
104
+ /** Remove invalid object properties instead of throwing when possible. */
105
+ autofix?: boolean
106
+ /** Skip validation for selected values and schema nodes. */
107
+ ignore?(data: any, schema: Schema): boolean
108
+ /** Path used to format nested validation errors. */
109
+ path?: (keyof any)[]
110
+ }
111
+
112
+ /** UI and validation metadata attached by schema builder methods. */
113
+ export interface Meta<T = any> {
114
+ default?: T extends {} ? Partial<T> : T
115
+ required?: boolean
116
+ /** Parse this node as a stable config reference; its type and UI metadata remain unchanged. */
117
+ volatile?: boolean
118
+ disabled?: boolean
119
+ collapse?: boolean
120
+ badges?: { text: string; type: string }[]
121
+ hidden?: boolean
122
+ loose?: boolean
123
+ role?: string
124
+ extra?: any
125
+ link?: string
126
+ description?: string | Dict<string>
127
+ comment?: string
128
+ pattern?: { source: string; flags?: string }
129
+ max?: number
130
+ min?: number
131
+ step?: number
132
+ }
133
+ }
134
+
135
+ /** Callable schema instance that validates input and returns normalized output. */
136
+ interface Schemastery<S = any, T = S, Mode extends SchemaMode = 'plain'> {
137
+ (data?: S | null, options?: Schemastery.Options): SchemaOutput<T, Mode>
138
+ new (data?: S | null, options?: Schemastery.Options): SchemaOutput<T, Mode>
139
+ [kSchema]: true
140
+ uid: number
141
+ meta: Schemastery.Meta<T>
142
+ type: string
143
+ sKey?: Schema
144
+ inner?: Schema
145
+ list?: Schema[]
146
+ dict?: Dict<Schema>
147
+ bits?: Dict<number>
148
+ callback?: Function
149
+ constructor?: string | Function
150
+ builder?: Function
151
+ value?: T
152
+ refs?: Dict<Schema>
153
+ preserve?: boolean
154
+ '~standard': StandardSchemaV1.Props // <S, T>
155
+ /** Format this schema as a compact TypeScript-like type string. */
156
+ toString(inline?: boolean): string
157
+ /** Serialize this schema, preserving shared and recursive references. */
158
+ toJSON(): Schema<S, T, Mode>
159
+ /** Mark nullable input as invalid unless a default supplies a fallback. */
160
+ required<R extends boolean = true>(value?: R): Schema<S, T, SetRequired<Mode, R>>
161
+ /**
162
+ * Parse this config field as a stable reference containing immutable data.
163
+ * @returns a schema whose output supports get(), including when the field is absent.
164
+ */
165
+ volatile(): Schema<NoInfer<S>, NoInfer<T>, Mode extends 'defined' | 'volatile-defined' ? 'volatile-defined' : 'volatile'>
166
+ /** Hide this schema node from UI renderers. */
167
+ hidden(value?: boolean): Schema<S, T, Mode>
168
+ /** Return the default value instead of throwing when validation fails. */
169
+ loose(value?: boolean): Schema<S, T, Mode>
170
+ /** Attach a renderer role and optional role-specific metadata. */
171
+ role(text: string, extra?: any): Schema<S, T, Mode>
172
+ /** Attach an external documentation link. */
173
+ link(link: string): Schema<S, T, Mode>
174
+ /** Set the fallback value used for nullable input. */
175
+ default(value: T | NoInfer<Partial<S>>): Schema<S, T, SetRequired<Mode, true>>
176
+ /** Attach an auxiliary comment for documentation or form UIs. */
177
+ comment(text: string): Schema<S, T, Mode>
178
+ /** Attach a localized or plain description for documentation or form UIs. */
179
+ description(text: string): Schema<S, T, Mode>
180
+ /** Mark this schema node as disabled for form UIs. */
181
+ disabled(value?: boolean): Schema<S, T, Mode>
182
+ /** Request collapsed rendering for nested form UIs. */
183
+ collapse(value?: boolean): Schema<S, T, Mode>
184
+ /** Add a deprecated badge to this schema node. */
185
+ deprecated(): Schema<S, T, Mode>
186
+ /** Add an experimental badge to this schema node. */
187
+ experimental(): Schema<S, T, Mode>
188
+ /** Require strings to match a regular expression. */
189
+ pattern(regexp: RegExp): Schema<S, T, Mode>
190
+ /** Set an inclusive maximum for numbers or collection lengths. */
191
+ max(value: number): Schema<S, T, Mode>
192
+ /** Set an inclusive minimum for numbers or collection lengths. */
193
+ min(value: number): Schema<S, T, Mode>
194
+ /** Set the numeric increment constraint. */
195
+ step(value: number): Schema<S, T, Mode>
196
+ /** Add or replace an object property schema. */
197
+ set(key: string, value: Schema): Schema<S, T, Mode>
198
+ /** Append a tuple, union, or intersection member schema. */
199
+ push(value: Schema): Schema<S, T, Mode>
200
+ /** Remove values equal to schema defaults from normalized output. */
201
+ simplify(value?: any): any
202
+ /** Return a schema clone with descriptions merged from locale messages. */
203
+ i18n(messages: Dict): Schema<S, T, Mode>
204
+ /** Attach arbitrary metadata consumed by form renderers and downstream tools. */
205
+ extra<K extends keyof Schemastery.Meta>(key: K, value: Schemastery.Meta[K]): Schema<S, T, Mode>
206
+ }
207
+ }
208
+
209
+ declare namespace globalThis {
210
+ // eslint-disable-next-line @typescript-eslint/naming-convention
211
+ export let __schemastery_index__: number
212
+ export let __schemastery_refs__: Record<number, Schema> | undefined
213
+ }
214
+
215
+ globalThis.__schemastery_index__ ??= 0
216
+ globalThis.__schemastery_refs__ = undefined
217
+
218
+ class ValidationError extends TypeError {
219
+ name = 'ValidationError'
220
+
221
+ constructor(message: string, public options: Schemastery.Options) {
222
+ let prefix = '$'
223
+ for (const segment of options.path || []) {
224
+ if (typeof segment === 'string') {
225
+ prefix += '.' + segment
226
+ } else if (typeof segment === 'number') {
227
+ prefix += '[' + segment + ']'
228
+ } else if (typeof segment === 'symbol') {
229
+ prefix += `[Symbol(${segment.toString()})]`
230
+ }
231
+ }
232
+ if (prefix.startsWith('.')) prefix = prefix.slice(1)
233
+ super((prefix === '$' ? '' : `${prefix} `) + message)
234
+ }
235
+
236
+ static is(error: any): error is ValidationError {
237
+ return !!error?.[kValidationError]
238
+ }
239
+ }
240
+
241
+ Object.defineProperty(ValidationError.prototype, kValidationError, {
242
+ value: true,
243
+ })
244
+
245
+ type SchemaMode = 'plain' | 'defined' | 'volatile' | 'volatile-defined'
246
+ type SchemaOutput<T, M extends SchemaMode> = M extends 'volatile' ? Volatile<T | undefined>
247
+ : M extends 'volatile-defined' ? Volatile<T> : T
248
+ type SetRequired<M extends SchemaMode, R extends boolean> = M extends 'volatile' | 'volatile-defined'
249
+ ? R extends true ? 'volatile-defined' : 'volatile'
250
+ : R extends true ? 'defined' : 'plain'
251
+
252
+ type Schema<S = any, T = S, Mode extends SchemaMode = 'plain'> = Schemastery<S, T, Mode>
253
+
254
+ const Schema = function (options: Schema) {
255
+ const schema = function (data: any, options: Schemastery.Options = {}) {
256
+ return Schema.resolve(data, schema, options)[0]
257
+ } as Schema
258
+
259
+ if (options.refs) {
260
+ const refs = valueMap(options.refs, options => new Schema(options))
261
+ const getRef = (uid: any) => refs[uid]!
262
+ for (const key in refs) {
263
+ const options = refs[key]!
264
+ options.sKey = getRef(options.sKey)
265
+ options.inner = getRef(options.inner)
266
+ options.list = options.list && options.list.map(getRef)
267
+ options.dict = options.dict && valueMap(options.dict, getRef)
268
+ }
269
+ return refs[options.uid!]
270
+ }
271
+
272
+ Object.assign(schema, options)
273
+ if (typeof schema.callback === 'string') {
274
+ try {
275
+ // eslint-disable-next-line no-new-func
276
+ schema.callback = new Function('return ' + schema.callback)()
277
+ } catch {}
278
+ }
279
+ Object.defineProperty(schema, 'uid', { value: globalThis.__schemastery_index__++ })
280
+ Object.setPrototypeOf(schema, Schema.prototype)
281
+ schema.meta ||= {}
282
+ schema.toString = schema.toString.bind(schema)
283
+ return schema
284
+ } as Schemastery.Static
285
+
286
+ Schema.prototype = Object.create(Function.prototype)
287
+
288
+ Schema.prototype[kSchema] = true
289
+
290
+ Object.defineProperty(Schema.prototype, '~standard', {
291
+ get(this: Schema) {
292
+ return {
293
+ version: 1,
294
+ vendor: 'schemastery',
295
+ validate: (value: unknown) => {
296
+ try {
297
+ return { value: Schema.resolve(value, this, {})[0] }
298
+ } catch (error) {
299
+ if (ValidationError.is(error)) {
300
+ return { issues: [{ message: error.message, path: error.options.path }] }
301
+ }
302
+ throw error
303
+ }
304
+ },
305
+ }
306
+ },
307
+ })
308
+
309
+ Schema.ValidationError = ValidationError
310
+
311
+ Schema.prototype.toJSON = function toJSON() {
312
+ if (globalThis.__schemastery_refs__) {
313
+ globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }))
314
+ return this.uid as any
315
+ }
316
+
317
+ globalThis.__schemastery_refs__ = { [this.uid]: { ...this } as Schema }
318
+ globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }))
319
+ const result = { uid: this.uid, refs: globalThis.__schemastery_refs__ }
320
+ globalThis.__schemastery_refs__ = undefined
321
+ return result
322
+ }
323
+
324
+ Schema.prototype.set = function set(key, value) {
325
+ this.dict![key] = value
326
+ return this
327
+ }
328
+
329
+ Schema.prototype.push = function push(value) {
330
+ this.list!.push(value)
331
+ return this
332
+ }
333
+
334
+ function mergeDesc(original: undefined | string | Dict<string>, messages: Dict) {
335
+ const result: Dict<string> = typeof original === 'string' ? { '': original } : { ...original }
336
+ for (const locale in messages) {
337
+ const value = messages[locale]
338
+ if (value?.$description || value?.$desc) {
339
+ result[locale] = value.$description || value.$desc
340
+ } else if (typeof value === 'string') {
341
+ result[locale] = value
342
+ }
343
+ }
344
+ return result
345
+ }
346
+
347
+ function getInner(value: any) {
348
+ return value?.$value ?? value?.$inner
349
+ }
350
+
351
+ function extractKeys(data: any) {
352
+ return filterKeys(data ?? {}, key => !key.startsWith('$'))
353
+ }
354
+
355
+ Schema.prototype.i18n = function i18n(messages) {
356
+ const schema = Schema(this)
357
+ const desc = mergeDesc(schema.meta.description, messages)
358
+ if (Object.keys(desc).length) schema.meta.description = desc
359
+ if (schema.dict) {
360
+ schema.dict = valueMap(schema.dict, (inner, key) => {
361
+ return inner.i18n(valueMap(messages, (data) => getInner(data)?.[key] ?? data?.[key]))
362
+ })
363
+ }
364
+ if (schema.list) {
365
+ schema.list = schema.list!.map((inner, index) => {
366
+ return inner.i18n(valueMap(messages, (data = {}) => {
367
+ if (Array.isArray(getInner(data))) return getInner(data)[index]
368
+ if (Array.isArray(data)) return data[index]
369
+ return extractKeys(data)
370
+ }))
371
+ })
372
+ }
373
+ if (schema.inner) {
374
+ schema.inner = schema.inner.i18n(valueMap(messages, (data) => {
375
+ if (getInner(data)) return getInner(data)
376
+ return extractKeys(data)
377
+ }))
378
+ }
379
+ if (schema.sKey) {
380
+ schema.sKey = schema.sKey.i18n(valueMap(messages, (data) => data?.$key))
381
+ }
382
+ return schema
383
+ }
384
+
385
+ Schema.prototype.extra = function extra(key, value) {
386
+ const schema = Schema(this)
387
+ schema.meta = { ...schema.meta, [key]: value }
388
+ return schema
389
+ }
390
+
391
+ for (const key of ['required', 'disabled', 'collapse', 'hidden', 'loose']) {
392
+ Object.assign(Schema.prototype, {
393
+ [key](this: Schema, value = true) {
394
+ const schema = Schema(this)
395
+ schema.meta = { ...schema.meta, [key]: value }
396
+ return schema
397
+ },
398
+ })
399
+ }
400
+
401
+ Schema.prototype.deprecated = function deprecated() {
402
+ const schema = Schema(this)
403
+ schema.meta.badges ||= []
404
+ schema.meta.badges.push({ text: 'deprecated', type: 'danger' })
405
+ return schema
406
+ }
407
+
408
+ Schema.prototype.experimental = function experimental() {
409
+ const schema = Schema(this)
410
+ schema.meta.badges ||= []
411
+ schema.meta.badges.push({ text: 'experimental', type: 'warning' })
412
+ return schema
413
+ }
414
+
415
+ Schema.prototype.pattern = function pattern(regexp) {
416
+ const schema = Schema(this)
417
+ const pattern = pick(regexp, ['source', 'flags'])
418
+ schema.meta = { ...schema.meta, pattern }
419
+ return schema
420
+ }
421
+
422
+ Schema.prototype.simplify = function simplify(this: Schema, value) {
423
+ if (isVolatile(value)) value = value.get()
424
+ if (deepEqual(value, this.meta.default, this.type === 'dict')) return null
425
+ if (isNullable(value)) return value
426
+ if (this.type === 'object' || this.type === 'dict') {
427
+ const result: Dict = {}
428
+ for (const key in value) {
429
+ const schema = this.type === 'object' ? this.dict![key] : this.inner
430
+ const item = schema?.simplify(value[key])
431
+ if (this.type === 'dict' || !isNullable(item)) result[key] = item
432
+ }
433
+ if (deepEqual(result, this.meta.default, this.type === 'dict')) return null
434
+ return result
435
+ } else if (this.type === 'array' || this.type === 'tuple') {
436
+ const result: any[] = []
437
+ ;(value as any[]).forEach((value, index) => {
438
+ const schema = this.type === 'array' ? this.inner : this.list![index]
439
+ const item = schema ? schema.simplify(value) : value
440
+ result.push(item)
441
+ })
442
+ return result
443
+ } else if (this.type === 'intersect') {
444
+ const result: Dict = {}
445
+ for (const item of this.list!) {
446
+ Object.assign(result, item.simplify(value))
447
+ }
448
+ return result
449
+ } else if (this.type === 'union') {
450
+ for (const schema of this.list!) {
451
+ try {
452
+ Schema.resolve(value, schema, {})
453
+ return schema.simplify(value)
454
+ } catch {}
455
+ }
456
+ }
457
+ return value
458
+ }
459
+
460
+ Schema.prototype.toString = function toString(inline?: boolean) {
461
+ return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`
462
+ }
463
+
464
+ Schema.prototype.role = function role(role, extra) {
465
+ const schema = Schema(this)
466
+ schema.meta = { ...schema.meta, role, extra }
467
+ return schema
468
+ }
469
+
470
+ for (const key of ['default', 'link', 'comment', 'description', 'max', 'min', 'step']) {
471
+ Object.assign(Schema.prototype, {
472
+ [key](this: Schema, value: any) {
473
+ const schema = Schema(this)
474
+ schema.meta = { ...schema.meta, [key]: value }
475
+ return schema
476
+ },
477
+ })
478
+ }
479
+
480
+ Schema.prototype.volatile = function volatile() {
481
+ if (this.meta.volatile) throw new TypeError('volatile schema is already wrapped')
482
+ return this.extra('volatile', true)
483
+ }
484
+
485
+ const resolvers: Dict<Schemastery.Resolve> = {}
486
+ const checkedVolatile = Symbol('checked-volatile-schema')
487
+
488
+ function validateVolatileSchema(schema: Schema, path: (keyof any)[] = [], blocked = false, seen = new Map<Schema, Set<boolean>>()) {
489
+ const states = seen.get(schema) ?? new Set<boolean>()
490
+ if (states.has(blocked)) return
491
+ states.add(blocked)
492
+ seen.set(schema, states)
493
+ if (schema.meta?.volatile && blocked) {
494
+ throw new ValidationError('volatile fields require a fixed object path without an enclosing volatile field', { path })
495
+ }
496
+ const nested = blocked || !!schema.meta?.volatile
497
+ if (schema.dict) {
498
+ for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen)
499
+ }
500
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, '<key>'], true, seen)
501
+ if (schema.inner && (schema.type !== 'lazy' || schema.inner[kSchema])) {
502
+ validateVolatileSchema(schema.inner, [...path, '*'], true, seen)
503
+ }
504
+ if (schema.list) {
505
+ for (let index = 0; index < schema.list.length; index++) {
506
+ validateVolatileSchema(schema.list[index]!, [...path, String(index)], true, seen)
507
+ }
508
+ }
509
+ }
510
+
511
+ Schema.extend = function extend(type, resolve) {
512
+ resolvers[type] = resolve
513
+ }
514
+
515
+ Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
516
+ if (!schema) return [data]
517
+ if (!(options as Schemastery.Options & { [checkedVolatile]?: boolean })[checkedVolatile]) {
518
+ validateVolatileSchema(schema, options.path)
519
+ options = { ...options, [checkedVolatile]: true } as Schemastery.Options
520
+ }
521
+ if (schema.meta?.volatile) {
522
+ const inner = Schema(schema)
523
+ inner.meta = { ...schema.meta, volatile: false }
524
+ const [value, adapted] = Schema.resolve(data, inner, options, strict)
525
+ try {
526
+ return [createVolatile(value), adapted]
527
+ } catch (error) {
528
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options)
529
+ }
530
+ }
531
+ if (options.ignore?.(data, schema)) return [data]
532
+
533
+ if (isNullable(data) && schema.type !== 'lazy') {
534
+ if (schema.meta.required) throw new ValidationError(`missing required value`, options)
535
+ let current = schema
536
+ let fallback = schema.meta.default
537
+ while (current?.type === 'intersect' && isNullable(fallback)) {
538
+ current = current.list![0]
539
+ fallback = current?.meta.default
540
+ }
541
+ if (isNullable(fallback)) return [data]
542
+ data = clone(fallback)
543
+ }
544
+
545
+ const callback = resolvers[schema.type]
546
+ if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options)
547
+
548
+ try {
549
+ return callback(data, schema, options, strict)
550
+ } catch (error) {
551
+ if (!schema.meta.loose) throw error
552
+ return [schema.meta.default]
553
+ }
554
+ }
555
+
556
+ Schema.from = function from(source: any) {
557
+ if (isNullable(source)) {
558
+ return Schema.any()
559
+ } else if (['string', 'number', 'boolean'].includes(typeof source)) {
560
+ return Schema.const(source).required()
561
+ } else if (source[kSchema]) {
562
+ return source
563
+ } else if (typeof source === 'function') {
564
+ switch (source) {
565
+ case String: return Schema.string().required()
566
+ case Number: return Schema.number().required()
567
+ case Boolean: return Schema.boolean().required()
568
+ case Function: return Schema.function().required()
569
+ default: return Schema.is(source).required()
570
+ }
571
+ } else {
572
+ throw new TypeError(`cannot infer schema from ${source}`)
573
+ }
574
+ }
575
+
576
+ Schema.lazy = function lazy(builder) {
577
+ const toJSON = () => {
578
+ if (!schema.inner![kSchema]) {
579
+ schema.inner = schema.builder!()
580
+ schema.inner!.meta = { ...schema.meta, ...schema.inner!.meta }
581
+ }
582
+ return schema.inner!.toJSON()
583
+ }
584
+ const schema = new Schema({ type: 'lazy', builder, inner: { toJSON } as any })
585
+ return schema as any
586
+ }
587
+
588
+ Schema.natural = function natural() {
589
+ return Schema.number().step(1).min(0)
590
+ }
591
+
592
+ Schema.percent = function percent() {
593
+ return Schema.number().step(0.01).min(0).max(1).role('slider')
594
+ }
595
+
596
+ Schema.date = function date() {
597
+ return Schema.union([
598
+ Schema.is(Date),
599
+ Schema.transform(Schema.string().role('datetime'), (value, options) => {
600
+ const date = new Date(value)
601
+ if (isNaN(+date)) throw new ValidationError(`invalid date "${value}"`, options)
602
+ return date
603
+ }, true),
604
+ ])
605
+ }
606
+
607
+ Schema.regExp = function regExp(flag = '') {
608
+ return Schema.union([
609
+ Schema.is(RegExp),
610
+ Schema.transform(Schema.string().role('regexp', { flag }), (value, options) => {
611
+ try {
612
+ return new RegExp(value, flag)
613
+ } catch (e: any) {
614
+ throw new ValidationError(e.message, options)
615
+ }
616
+ }, true),
617
+ ])
618
+ }
619
+
620
+ Schema.arrayBuffer = function arrayBuffer(encoding?: 'hex' | 'base64'): any {
621
+ return Schema.union([
622
+ Schema.is(ArrayBuffer),
623
+ Schema.is(SharedArrayBuffer),
624
+ Schema.transform(Schema.any<ArrayBufferView>(), (value, options) => {
625
+ if (Binary.isSource(value)) return Binary.fromSource(value)
626
+ throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options)
627
+ }, true),
628
+ ...encoding ? [Schema.transform(Schema.string(), (value, options) => {
629
+ try {
630
+ return encoding === 'base64'
631
+ ? Binary.fromBase64(value)
632
+ : Binary.fromHex(value)
633
+ } catch (e: any) {
634
+ throw new ValidationError(e.message, options)
635
+ }
636
+ }, true)] as const : [],
637
+ ])
638
+ }
639
+
640
+ Schema.extend('lazy', (data, schema, options, strict) => {
641
+ if (!schema.inner![kSchema]) {
642
+ schema.inner = schema.builder!()
643
+ schema.inner!.meta = { ...schema.meta, ...schema.inner!.meta }
644
+ validateVolatileSchema(schema.inner!, options.path, true)
645
+ }
646
+ return Schema.resolve(data, schema.inner!, options, strict)
647
+ })
648
+
649
+ Schema.extend('any', (data) => {
650
+ return [data]
651
+ })
652
+
653
+ Schema.extend('never', (data, _, options) => {
654
+ throw new ValidationError(`expected nullable but got ${data}`, options)
655
+ })
656
+
657
+ Schema.extend('const', (data, { value }, options) => {
658
+ if (deepEqual(data, value)) return [value]
659
+ throw new ValidationError(`expected ${value} but got ${data}`, options)
660
+ })
661
+
662
+ function checkWithinRange(data: number, meta: Schemastery.Meta<any>, description: string, options: Schemastery.Options, skipMin = false) {
663
+ const { max = Infinity, min = -Infinity } = meta
664
+ if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options)
665
+ if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options)
666
+ }
667
+
668
+ Schema.extend('string', (data, { meta }, options) => {
669
+ if (typeof data !== 'string') throw new ValidationError(`expected string but got ${data}`, options)
670
+ if (meta.pattern) {
671
+ const regexp = new RegExp(meta.pattern.source, meta.pattern.flags)
672
+ if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options)
673
+ }
674
+ checkWithinRange(data.length, meta, 'string length', options)
675
+ return [data]
676
+ })
677
+
678
+ function decimalShift(data: number, digits: number) {
679
+ const str = data.toString()
680
+ if (str.includes('e')) return data * Math.pow(10, digits)
681
+ const index = str.indexOf('.')
682
+ if (index === -1) return data * Math.pow(10, digits)
683
+ const frac = str.slice(index + 1)
684
+ const integer = str.slice(0, index)
685
+ if (frac.length <= digits) return +(integer + frac.padEnd(digits, '0'))
686
+ return +(integer + frac.slice(0, digits) + '.' + frac.slice(digits))
687
+ }
688
+
689
+ function isMultipleOf(data: number, min: number, step: number) {
690
+ step = Math.abs(step)
691
+ if (!/^\d+\.\d+$/.test(step.toString())) {
692
+ return (data - min) % step === 0
693
+ }
694
+ const index = step.toString().indexOf('.')
695
+ const digits = step.toString().slice(index + 1).length
696
+ return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0
697
+ }
698
+
699
+ Schema.extend('number', (data, { meta }, options) => {
700
+ if (typeof data !== 'number') throw new ValidationError(`expected number but got ${data}`, options)
701
+ checkWithinRange(data, meta, 'number', options)
702
+ const { step } = meta
703
+ if (step && !isMultipleOf(data, meta.min ?? 0, step)) {
704
+ throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options)
705
+ }
706
+ return [data]
707
+ })
708
+
709
+ Schema.extend('boolean', (data, _, options) => {
710
+ if (typeof data === 'boolean') return [data]
711
+ throw new ValidationError(`expected boolean but got ${data}`, options)
712
+ })
713
+
714
+ Schema.extend('bitset', (data, { bits, meta }, options) => {
715
+ let value = 0, keys: string[] = []
716
+ if (typeof data === 'number') {
717
+ value = data
718
+ for (const key in bits!) {
719
+ if (data & bits![key]!) {
720
+ keys.push(key)
721
+ }
722
+ }
723
+ } else if (Array.isArray(data)) {
724
+ keys = data
725
+ for (const key of keys) {
726
+ if (typeof key !== 'string') throw new ValidationError(`expected string but got ${key}`, options)
727
+ if (key in bits!) value |= bits![key]!
728
+ }
729
+ } else {
730
+ throw new ValidationError(`expected number or array but got ${data}`, options)
731
+ }
732
+ if (value === meta.default) return [value]
733
+ return [value, keys]
734
+ })
735
+
736
+ Schema.extend('function', (data, _, options) => {
737
+ if (typeof data === 'function') return [data]
738
+ throw new ValidationError(`expected function but got ${data}`, options)
739
+ })
740
+
741
+ Schema.extend('is', (data, { constructor }, options) => {
742
+ if (typeof constructor === 'function') {
743
+ if (data instanceof constructor) return [data]
744
+ throw new ValidationError(`expected ${constructor.name} but got ${data}`, options)
745
+ } else {
746
+ if (isNullable(data)) {
747
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options)
748
+ }
749
+ let prototype = Object.getPrototypeOf(data)
750
+ while (prototype) {
751
+ if (prototype.constructor?.name === constructor) return [data]
752
+ prototype = Object.getPrototypeOf(prototype)
753
+ }
754
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options)
755
+ }
756
+ })
757
+
758
+ function property(data: any, key: keyof any, schema: Schema, options: Schemastery.Options) {
759
+ try {
760
+ const [value, adapted] = Schema.resolve(data[key], schema, {
761
+ ...options,
762
+ path: [...options.path || [], key],
763
+ })
764
+ if (adapted !== undefined) data[key] = adapted
765
+ return value
766
+ } catch (e) {
767
+ if (!options?.autofix) throw e
768
+ delete data[key]
769
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default
770
+ }
771
+ }
772
+
773
+ Schema.extend('array', (data, { inner, meta }, options) => {
774
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options)
775
+ checkWithinRange(data.length, meta, 'array length', options, !isNullable(inner!.meta.default))
776
+ return [data.map((_, index) => property(data, index, inner!, options))]
777
+ })
778
+
779
+ Schema.extend('dict', (data, { inner, sKey }, options, strict) => {
780
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options)
781
+ const result: any = {}
782
+ for (const key in data) {
783
+ let rKey: string
784
+ try {
785
+ rKey = Schema.resolve(key, sKey!, options)[0]
786
+ } catch (error) {
787
+ if (strict) continue
788
+ throw error
789
+ }
790
+ result[rKey] = property(data, key, inner!, options)
791
+ data[rKey] = data[key]
792
+ if (key !== rKey) delete data[key]
793
+ }
794
+ return [result]
795
+ })
796
+
797
+ Schema.extend('tuple', (data, { list }, options, strict) => {
798
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options)
799
+ const result = list!.map((inner, index) => property(data, index, inner, options))
800
+ if (strict) return [result]
801
+ result.push(...data.slice(list!.length))
802
+ return [result]
803
+ })
804
+
805
+ function merge(result: any, data: any) {
806
+ for (const key in data) {
807
+ if (key in result) continue
808
+ result[key] = data[key]
809
+ }
810
+ }
811
+
812
+ Schema.extend('object', (data, { dict }, options, strict) => {
813
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options)
814
+ const result: any = {}
815
+ for (const key in dict) {
816
+ const value = property(data, key, dict![key]!, options)
817
+ if (!isNullable(value) || key in data) {
818
+ result[key] = value
819
+ }
820
+ }
821
+ if (!strict) merge(result, data)
822
+ return [result]
823
+ })
824
+
825
+ Schema.extend('union', (data, { list, toString }, options, strict) => {
826
+ const messages: any[] = []
827
+ for (const inner of list!) {
828
+ try {
829
+ return Schema.resolve(data, inner, options, strict)
830
+ } catch (error) {
831
+ messages.push(error)
832
+ }
833
+ }
834
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options)
835
+ })
836
+
837
+ Schema.extend('intersect', (data, { list, toString }, options, strict) => {
838
+ if (!list!.length) return [data]
839
+ let result
840
+ for (const inner of list!) {
841
+ const value: any = Schema.resolve(data, inner, options, true)[0]
842
+ if (isNullable(value)) continue
843
+ if (isNullable(result)) {
844
+ result = value
845
+ } else if (typeof result !== typeof value) {
846
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options)
847
+ } else if (typeof value === 'object') {
848
+ merge(result ??= {}, value)
849
+ } else if (result !== value) {
850
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options)
851
+ }
852
+ }
853
+ if (!strict && isPlainObject(data)) merge(result, data)
854
+ return [result]
855
+ })
856
+
857
+ Schema.extend('transform', (data, { inner, callback, preserve }, options) => {
858
+ const [result, adapted = data] = Schema.resolve(data, inner!, options, true)
859
+ if (preserve) {
860
+ return [callback!(result)]
861
+ // } else if (isPlainObject(data)) {
862
+ // const temp: any = {}
863
+ // for (const key in result) {
864
+ // if (!(key in data)) continue
865
+ // temp[key] = data[key]
866
+ // delete data[key]
867
+ // }
868
+ // Object.assign(data, callback!(temp))
869
+ // return [callback!(result)]
870
+ } else {
871
+ return [callback!(result), callback!(adapted)]
872
+ }
873
+ })
874
+
875
+ type Formatter = (schema: Schema, inline?: boolean) => string
876
+ const formatters: Dict<Formatter> = {}
877
+
878
+ function defineMethod(name: string, keys: (keyof Schema)[], format: Formatter) {
879
+ formatters[name] = format
880
+ Object.assign(Schema, {
881
+ [name](...args: any[]) {
882
+ const schema = new Schema({ type: name } as Schema)
883
+ keys.forEach((key, index) => {
884
+ switch (key) {
885
+ case 'sKey': schema.sKey = args[index] ?? Schema.string(); break
886
+ case 'inner': schema.inner = Schema.from(args[index]); break
887
+ case 'list': schema.list = args[index].map(Schema.from); break
888
+ case 'dict': schema.dict = valueMap(args[index], Schema.from); break
889
+ case 'bits': {
890
+ schema.bits = {}
891
+ for (const key in args[index]) {
892
+ if (typeof args[index][key] !== 'number') continue
893
+ schema.bits[key] = args[index][key]
894
+ }
895
+ break
896
+ }
897
+ case 'callback': {
898
+ const callback = schema.callback = args[index]
899
+ ;callback['toJSON'] ||= () => callback.toString()
900
+ break
901
+ }
902
+ case 'constructor': {
903
+ const constructor = schema.constructor = args[index]
904
+ if (typeof constructor === 'function') {
905
+ ;constructor['toJSON'] ||= () => constructor['name']
906
+ }
907
+ break
908
+ }
909
+ default: schema[key] = args[index] as never
910
+ }
911
+ })
912
+ if (name === 'object' || name === 'dict') {
913
+ schema.meta.default = {}
914
+ } else if (name === 'array' || name === 'tuple') {
915
+ schema.meta.default = []
916
+ } else if (name === 'bitset') {
917
+ schema.meta.default = 0
918
+ }
919
+ return schema
920
+ },
921
+ })
922
+ }
923
+
924
+ defineMethod('is', ['constructor'], ({ constructor }) => {
925
+ if (typeof constructor === 'function') {
926
+ return constructor.name
927
+ } else {
928
+ return constructor!
929
+ }
930
+ })
931
+
932
+ defineMethod('any', [], () => 'any')
933
+ defineMethod('never', [], () => 'never')
934
+ defineMethod('const', ['value'], ({ value }) => typeof value === 'string' ? JSON.stringify(value) : value)
935
+ defineMethod('string', [], () => 'string')
936
+ defineMethod('number', [], () => 'number')
937
+ defineMethod('boolean', [], () => 'boolean')
938
+ defineMethod('bitset', ['bits'], () => 'bitset')
939
+ defineMethod('function', [], () => 'function')
940
+ defineMethod('array', ['inner'], ({ inner }) => `${inner!.toString(true)}[]`)
941
+ defineMethod('dict', ['inner', 'sKey'], ({ inner, sKey }) => `{ [key: ${sKey!.toString()}]: ${inner!.toString()} }`)
942
+ defineMethod('tuple', ['list'], ({ list }) => `[${list!.map((inner) => inner.toString()).join(', ')}]`)
943
+
944
+ defineMethod('object', ['dict'], ({ dict }) => {
945
+ if (Object.keys(dict!).length === 0) return '{}'
946
+ return `{ ${Object.entries(dict!).map(([key, inner]) => {
947
+ return `${key}${inner!.meta.required ? '' : '?'}: ${inner!.toString()}`
948
+ }).join(', ')} }`
949
+ })
950
+
951
+ defineMethod('union', ['list'], ({ list }, inline) => {
952
+ const result = list!.map(({ toString: format }) => format()).join(' | ')
953
+ return inline ? `(${result})` : result
954
+ })
955
+
956
+ defineMethod('intersect', ['list'], ({ list }) => {
957
+ return `${list!.map((inner) => inner.toString(true)).join(' & ')}`
958
+ })
959
+
960
+ defineMethod('transform', ['inner', 'callback', 'preserve'], ({ inner }, isInner) => inner!.toString(isInner))
961
+
962
+ export default Schema