@zhin.js/core 1.0.39 → 1.0.41

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 (46) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/lib/ai/index.d.ts +10 -5
  3. package/lib/ai/index.d.ts.map +1 -1
  4. package/lib/ai/index.js +7 -4
  5. package/lib/ai/index.js.map +1 -1
  6. package/lib/built/common-adapter-tools.d.ts.map +1 -1
  7. package/lib/built/common-adapter-tools.js +38 -32
  8. package/lib/built/common-adapter-tools.js.map +1 -1
  9. package/lib/cron.d.ts +2 -43
  10. package/lib/cron.d.ts.map +1 -1
  11. package/lib/cron.js +2 -126
  12. package/lib/cron.js.map +1 -1
  13. package/lib/errors.d.ts +3 -146
  14. package/lib/errors.d.ts.map +1 -1
  15. package/lib/errors.js +3 -279
  16. package/lib/errors.js.map +1 -1
  17. package/lib/feature.d.ts +5 -87
  18. package/lib/feature.d.ts.map +1 -1
  19. package/lib/feature.js +4 -105
  20. package/lib/feature.js.map +1 -1
  21. package/lib/index.d.ts +1 -0
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/scheduler/index.d.ts +3 -7
  24. package/lib/scheduler/index.d.ts.map +1 -1
  25. package/lib/scheduler/index.js +2 -9
  26. package/lib/scheduler/index.js.map +1 -1
  27. package/lib/types.d.ts +8 -1
  28. package/lib/types.d.ts.map +1 -1
  29. package/lib/utils.d.ts +7 -52
  30. package/lib/utils.d.ts.map +1 -1
  31. package/lib/utils.js +9 -325
  32. package/lib/utils.js.map +1 -1
  33. package/package.json +6 -4
  34. package/src/ai/index.ts +15 -9
  35. package/src/built/common-adapter-tools.ts +38 -32
  36. package/src/cron.ts +2 -140
  37. package/src/errors.ts +15 -334
  38. package/src/feature.ts +5 -154
  39. package/src/index.ts +3 -1
  40. package/src/scheduler/index.ts +8 -17
  41. package/src/types.ts +10 -2
  42. package/src/utils.ts +37 -334
  43. package/tests/cron.test.ts +4 -299
  44. package/tests/errors.test.ts +17 -307
  45. package/tests/utils.test.ts +11 -516
  46. package/tests/feature.test.ts +0 -145
@@ -1,167 +1,8 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import {
3
- compiler,
4
- evaluate,
5
- compose,
6
- segment,
7
- remove,
8
- isEmpty,
9
- Time,
10
- clearEvalCache,
11
- getEvalCacheStats,
12
- execute,
13
- getValueWithRuntime,
14
- sleep
15
- } from '../src/utils'
16
-
17
- describe('Template Security', () => {
18
-
19
- it('should prevent access to process.env', () => {
20
- const template = 'Node env: ${process.env.NODE_ENV}'
21
- const result = compiler(template, {})
22
- expect(result).toBe('Node env: undefined')
23
- })
24
-
25
- it('should prevent access to global object', () => {
26
- const template = 'Global: ${global}'
27
- const result = compiler(template, {})
28
- expect(result).toBe('Global: undefined')
29
- })
30
-
31
- it('should prevent access to require function', () => {
32
- const template = 'Require: ${require}'
33
- const result = compiler(template, {})
34
- expect(result).toBe('Require: undefined')
35
- })
36
-
37
- it('should allow access to provided context variables', () => {
38
- const template = 'Hello ${name}!'
39
- const result = compiler(template, { name: 'World' })
40
- expect(result).toBe('Hello World!')
41
- })
42
-
43
- it('should allow complex expressions with safe context', () => {
44
- const template = 'Result: ${Math.max(1, 2, 3)}'
45
- const result = compiler(template, {})
46
- expect(result).toBe('Result: 3')
47
- })
48
-
49
- it('should handle nested object access safely', () => {
50
- const template = 'User: ${user.name} (${user.age})'
51
- const result = compiler(template, { user: { name: 'Alice', age: 25 } })
52
- expect(result).toBe('User: Alice (25)')
53
- })
54
-
55
- it('should allow safe Math expressions', () => {
56
- const result = evaluate('Math.PI', {})
57
- expect(result).toBeCloseTo(3.14159)
58
- })
59
-
60
- it('should allow access to safe process properties', () => {
61
- const result = evaluate('process.version', {})
62
- expect(result).toBe(process.version)
63
- })
64
-
65
- it('should block Buffer access', () => {
66
- const result = evaluate('Buffer', {})
67
- expect(result).toBeUndefined()
68
- })
69
-
70
- it('should block crypto access', () => {
71
- const result = evaluate('crypto', {})
72
- expect(result).toBeUndefined()
73
- })
74
- })
75
-
76
- describe('Template Functionality', () => {
77
- it('should handle multiple template variables', () => {
78
- const template = 'Hello ${name}, you are ${age} years old!'
79
- const result = compiler(template, { name: 'Bob', age: 30 })
80
- expect(result).toBe('Hello Bob, you are 30 years old!')
81
- })
82
-
83
- it('should handle JSON objects in templates', () => {
84
- const template = 'Config: ${config}'
85
- const config = { debug: true, port: 3000 }
86
- const result = compiler(template, { config })
87
- expect(result).toBe(`Config: ${JSON.stringify(config, null, 2)}`)
88
- })
89
-
90
- it('should handle template expressions that fail gracefully', () => {
91
- const template = 'Result: ${undefined.property}'
92
- const result = compiler(template, {})
93
- // Should return template with undefined when evaluation fails
94
- expect(result).toBe('Result: undefined')
95
- })
96
-
97
- it('should handle templates without variables', () => {
98
- const template = 'Hello World!'
99
- const result = compiler(template, {})
100
- expect(result).toBe('Hello World!')
101
- })
102
-
103
- it('should handle empty template', () => {
104
- const template = ''
105
- const result = compiler(template, {})
106
- expect(result).toBe('')
107
- })
108
- })
109
-
110
- describe('evaluate and execute', () => {
111
- beforeEach(() => {
112
- clearEvalCache()
113
- })
114
-
115
- it('should evaluate simple expressions', () => {
116
- expect(evaluate('1 + 1', {})).toBe(2)
117
- expect(evaluate('2 * 3', {})).toBe(6)
118
- })
119
-
120
- it('should return undefined for blocked access', () => {
121
- expect(evaluate('global.something', {})).toBeUndefined()
122
- })
123
-
124
- it('should use cache for repeated expressions', () => {
125
- const expr = '1 + 1'
126
- const result1 = execute(expr, {})
127
- const result2 = execute(expr, {})
128
- expect(result1).toBe(result2)
129
- expect(getEvalCacheStats().size).toBe(1)
130
- })
131
-
132
- it('should limit cache size', () => {
133
- clearEvalCache()
134
- // 添加超过 MAX_EVAL_CACHE_SIZE 的表达式
135
- for (let i = 0; i < 150; i++) {
136
- execute(`1 + ${i}`, {})
137
- }
138
- const stats = getEvalCacheStats()
139
- expect(stats.size).toBeLessThanOrEqual(stats.maxSize)
140
- })
141
-
142
- it('should handle invalid expressions gracefully', () => {
143
- expect(() => execute('invalid syntax here !!!', {})).toThrow()
144
- })
145
-
146
- it('should provide safe process context', () => {
147
- const result = execute('return process.platform', {})
148
- expect(result).toBe(process.platform)
149
- })
150
- })
151
-
152
- describe('getValueWithRuntime', () => {
153
- it('should return value from context', () => {
154
- expect(getValueWithRuntime('name', { name: 'Alice' })).toBe('Alice')
155
- })
156
-
157
- it('should return undefined for blocked access', () => {
158
- expect(getValueWithRuntime('global', {})).toBeUndefined()
159
- })
160
-
161
- it('should handle complex expressions', () => {
162
- expect(getValueWithRuntime('user.name', { user: { name: 'Bob' } })).toBe('Bob')
163
- })
164
- })
1
+ /**
2
+ * Core 特有的工具函数测试(通用工具测试已迁移到 @zhin.js/kernel)
3
+ */
4
+ import { describe, it, expect } from 'vitest'
5
+ import { compose, segment } from '../src/utils'
165
6
 
166
7
  describe('compose middleware', () => {
167
8
  it('should return empty function for empty middlewares', async () => {
@@ -219,22 +60,10 @@ describe('compose middleware', () => {
219
60
  })
220
61
 
221
62
  it('should catch and rethrow middleware errors', async () => {
222
- const middleware = async (msg: any, next: any) => {
223
- throw new Error('Middleware error')
224
- }
63
+ const middleware = async () => { throw new Error('Middleware error') }
225
64
  const composed = compose([middleware])
226
65
  await expect(composed({} as any, async () => {})).rejects.toThrow('Middleware error')
227
66
  })
228
-
229
- it('should call final next function', async () => {
230
- let finalCalled = false
231
- const middleware = async (msg: any, next: any) => {
232
- await next()
233
- }
234
- const composed = compose([middleware])
235
- await composed({} as any, async () => { finalCalled = true })
236
- expect(finalCalled).toBe(true)
237
- })
238
67
  })
239
68
 
240
69
  describe('segment utilities', () => {
@@ -246,11 +75,6 @@ describe('segment utilities', () => {
246
75
  it('should unescape HTML entities', () => {
247
76
  expect(segment.unescape('&lt;div&gt;&amp;&quot;&#39;&lt;/div&gt;')).toBe('<div>&"\'</div>')
248
77
  })
249
-
250
- it('should handle non-string values', () => {
251
- expect(segment.escape(123 as any)).toBe(123)
252
- expect(segment.unescape(null as any)).toBe(null)
253
- })
254
78
  })
255
79
 
256
80
  describe('text and face', () => {
@@ -259,9 +83,9 @@ describe('segment utilities', () => {
259
83
  })
260
84
 
261
85
  it('should create face segment', () => {
262
- expect(segment.face('smile', '😊')).toEqual({
263
- type: 'face',
264
- data: { id: 'smile', text: '😊' }
86
+ expect(segment.face('smile', '😊')).toEqual({
87
+ type: 'face',
88
+ data: { id: 'smile', text: '😊' }
265
89
  })
266
90
  })
267
91
  })
@@ -272,83 +96,15 @@ describe('segment utilities', () => {
272
96
  expect(result).toEqual([{ type: 'text', data: { text: 'Hello World' } }])
273
97
  })
274
98
 
275
- it('should parse self-closing tags with proper spacing', () => {
99
+ it('should parse self-closing tags', () => {
276
100
  const result = segment.from('<image url="test.jpg" />')
277
- expect(result.length).toBeGreaterThan(0)
278
- // 检查是否包含 image 类型
279
- const imageElement = result.find(el => el.type === 'image')
280
- expect(imageElement).toBeDefined()
281
- })
282
-
283
- it('should parse paired tags', () => {
284
- const result = segment.from('<quote>Hello</quote>')
285
- // 检查结果中是否有 quote 元素
286
- const quoteElement = result.find(el => el.type === 'quote')
287
- expect(quoteElement).toBeDefined()
288
- })
289
-
290
- it('should parse mixed content', () => {
291
- const result = segment.from('Text <image url="pic.jpg" /> More text')
292
- expect(result.length).toBeGreaterThan(0)
293
- // 应该包含文本和图片元素
294
- expect(result.some(el => el.type === 'text')).toBe(true)
295
- })
296
-
297
- it('should handle attributes with single quotes', () => {
298
- const result = segment.from("<image url='test.jpg' />")
299
- const imageElement = result.find(el => el.type === 'image')
300
- expect(imageElement).toBeDefined()
301
- })
302
-
303
- it('should handle multiple attributes', () => {
304
- const result = segment.from('<image url="test.jpg" width="100" height="200" />')
305
- const imageElement = result.find(el => el.type === 'image')
306
- // 如果找到了 image 元素,检查它有数据
307
- if (imageElement) {
308
- expect(imageElement.data).toBeDefined()
309
- // 至少应该有一些属性
310
- expect(Object.keys(imageElement.data).length).toBeGreaterThanOrEqual(0)
311
- } else {
312
- // 如果没找到,至少应该有元素被解析
313
- expect(result.length).toBeGreaterThan(0)
314
- }
315
- })
316
-
317
- it('should handle nested tags', () => {
318
- const result = segment.from('<quote><text>Hello</text></quote>')
319
- // 应该有元素被解析
320
- expect(result.length).toBeGreaterThan(0)
321
- })
322
-
323
- it('should handle array input', () => {
324
- const result = segment.from(['Hello', ' ', 'World'])
325
- expect(result.length).toBeGreaterThan(0)
326
- })
327
-
328
- it('should handle MessageElement input', () => {
329
- const input = { type: 'text', data: { text: 'Hello' } }
330
- const result = segment.from(input)
331
- expect(result).toEqual([input])
332
- })
333
-
334
- it('should parse JSON values in attributes', () => {
335
- const result = segment.from('<data value="123" />')
336
- const dataElement = result.find(el => el.type === 'data')
337
- // 如果找到了 data 元素,检查值是否被解析
338
- if (dataElement && dataElement.data.value !== undefined) {
339
- expect(typeof dataElement.data.value).toBe('number')
340
- }
101
+ expect(result.find(el => el.type === 'image')).toBeDefined()
341
102
  })
342
103
 
343
104
  it('should handle malformed templates gracefully', () => {
344
105
  const result = segment.from('Hello <unclosed')
345
106
  expect(result[0].type).toBe('text')
346
107
  })
347
-
348
- it('should handle templates with escaped characters', () => {
349
- const result = segment.from('&lt;div&gt;')
350
- expect(result[0].data.text).toBe('<div>')
351
- })
352
108
  })
353
109
 
354
110
  describe('raw', () => {
@@ -360,11 +116,6 @@ describe('segment utilities', () => {
360
116
  expect(segment.raw(content)).toBe('Hello{face}(😊)')
361
117
  })
362
118
 
363
- it('should handle segments without text', () => {
364
- const content = [{ type: 'image', data: { url: 'test.jpg' } }]
365
- expect(segment.raw(content)).toBe('{image}')
366
- })
367
-
368
119
  it('should handle string input', () => {
369
120
  expect(segment.raw('Hello')).toBe('Hello')
370
121
  })
@@ -379,262 +130,6 @@ describe('segment utilities', () => {
379
130
  const result = segment.toString(content)
380
131
  expect(result).toContain('Hello')
381
132
  expect(result).toContain('<image')
382
- expect(result).toContain('url=')
383
133
  })
384
-
385
- it('should handle function types', () => {
386
- const content = [{ type: function MyType() {} as any, data: { value: 1 } }]
387
- const result = segment.toString(content)
388
- expect(result).toContain('MyType')
389
- })
390
-
391
- it('should escape attribute values', () => {
392
- const content = [{ type: 'tag', data: { attr: '<script>' } }]
393
- const result = segment.toString(content)
394
- expect(result).toContain('&lt;')
395
- })
396
- })
397
- })
398
-
399
- describe('remove utility', () => {
400
- it('should remove item by value', () => {
401
- const list = [1, 2, 3, 4]
402
- remove(list, 3)
403
- expect(list).toEqual([1, 2, 4])
404
- })
405
-
406
- it('should remove item by predicate', () => {
407
- const list = [1, 2, 3, 4]
408
- remove(list, (x) => x > 2)
409
- expect(list).toEqual([1, 2, 4])
410
- })
411
-
412
- it('should do nothing if item not found', () => {
413
- const list = [1, 2, 3]
414
- remove(list, 5)
415
- expect(list).toEqual([1, 2, 3])
416
- })
417
-
418
- it('should handle function items', () => {
419
- const fn1 = () => 1
420
- const fn2 = () => 2
421
- const list = [fn1, fn2]
422
- remove(list, fn1)
423
- expect(list).toEqual([fn2])
424
- })
425
- })
426
-
427
- describe('isEmpty utility', () => {
428
- it('should return true for empty array', () => {
429
- expect(isEmpty([])).toBe(true)
430
- })
431
-
432
- it('should return false for non-empty array', () => {
433
- expect(isEmpty([1, 2])).toBe(false)
434
- })
435
-
436
- it('should return true for empty object', () => {
437
- expect(isEmpty({})).toBe(true)
438
- })
439
-
440
- it('should return false for non-empty object', () => {
441
- expect(isEmpty({ a: 1 })).toBe(false)
442
- })
443
-
444
- it('should return true for null', () => {
445
- expect(isEmpty(null)).toBe(true)
446
- })
447
-
448
- it('should return false for non-empty values', () => {
449
- expect(isEmpty('string')).toBe(false)
450
- expect(isEmpty(123)).toBe(false)
451
- })
452
- })
453
-
454
- describe('Time utilities', () => {
455
- describe('constants', () => {
456
- it('should have correct time constants', () => {
457
- expect(Time.second).toBe(1000)
458
- expect(Time.minute).toBe(60000)
459
- expect(Time.hour).toBe(3600000)
460
- expect(Time.day).toBe(86400000)
461
- expect(Time.week).toBe(604800000)
462
- })
463
- })
464
-
465
- describe('timezone', () => {
466
- it('should get and set timezone offset', () => {
467
- const original = Time.getTimezoneOffset()
468
- Time.setTimezoneOffset(480)
469
- expect(Time.getTimezoneOffset()).toBe(480)
470
- Time.setTimezoneOffset(original)
471
- })
472
- })
473
-
474
- describe('getDateNumber and fromDateNumber', () => {
475
- it('should convert date to number and back', () => {
476
- const date = new Date('2024-01-01T00:00:00Z')
477
- const num = Time.getDateNumber(date, 0)
478
- const restored = Time.fromDateNumber(num, 0)
479
- expect(restored.getUTCDate()).toBe(date.getUTCDate())
480
- })
481
-
482
- it('should handle timestamp input', () => {
483
- const timestamp = Date.now()
484
- const num = Time.getDateNumber(timestamp)
485
- expect(typeof num).toBe('number')
486
- })
487
- })
488
-
489
- describe('parseTime', () => {
490
- it('should parse time strings', () => {
491
- expect(Time.parseTime('1d')).toBe(Time.day)
492
- expect(Time.parseTime('2h')).toBe(Time.hour * 2)
493
- expect(Time.parseTime('30m')).toBe(Time.minute * 30)
494
- expect(Time.parseTime('45s')).toBe(Time.second * 45)
495
- })
496
-
497
- it('should parse combined time strings', () => {
498
- expect(Time.parseTime('1d2h')).toBe(Time.day + Time.hour * 2)
499
- expect(Time.parseTime('1w3d')).toBe(Time.week + Time.day * 3)
500
- })
501
-
502
- it('should return 0 for invalid strings', () => {
503
- expect(Time.parseTime('invalid')).toBe(0)
504
- expect(Time.parseTime('')).toBe(0)
505
- })
506
-
507
- it('should handle decimal values', () => {
508
- expect(Time.parseTime('1.5h')).toBe(Time.hour * 1.5)
509
- })
510
- })
511
-
512
- describe('parseDate', () => {
513
- it('should parse relative time', () => {
514
- const now = Date.now()
515
- const result = Time.parseDate('1h')
516
- expect(result.getTime()).toBeGreaterThan(now)
517
- })
518
-
519
- it('should parse time-only format', () => {
520
- const result = Time.parseDate('14:30:00')
521
- expect(result).toBeInstanceOf(Date)
522
- })
523
-
524
- it('should parse short date format', () => {
525
- const result = Time.parseDate('12-25-14:30:00')
526
- expect(result).toBeInstanceOf(Date)
527
- })
528
-
529
- it('should return current date for invalid input', () => {
530
- const result = Time.parseDate('')
531
- expect(result).toBeInstanceOf(Date)
532
- })
533
- })
534
-
535
- describe('formatTimeShort', () => {
536
- it('should format days', () => {
537
- expect(Time.formatTimeShort(Time.day * 2)).toBe('2d')
538
- })
539
-
540
- it('should format hours', () => {
541
- expect(Time.formatTimeShort(Time.hour * 3)).toBe('3h')
542
- })
543
-
544
- it('should format minutes', () => {
545
- expect(Time.formatTimeShort(Time.minute * 45)).toBe('45m')
546
- })
547
-
548
- it('should format seconds', () => {
549
- expect(Time.formatTimeShort(Time.second * 30)).toBe('30s')
550
- })
551
-
552
- it('should format milliseconds', () => {
553
- expect(Time.formatTimeShort(500)).toBe('500ms')
554
- })
555
-
556
- it('should handle negative values', () => {
557
- expect(Time.formatTimeShort(-Time.hour * 2)).toBe('-2h')
558
- })
559
- })
560
-
561
- describe('formatTime', () => {
562
- it('should format days with hours', () => {
563
- const result = Time.formatTime(Time.day + Time.hour * 3)
564
- expect(result).toContain('天')
565
- expect(result).toContain('小时')
566
- })
567
-
568
- it('should format hours with minutes', () => {
569
- const result = Time.formatTime(Time.hour * 2 + Time.minute * 30)
570
- expect(result).toContain('小时')
571
- expect(result).toContain('分钟')
572
- })
573
-
574
- it('should format minutes with seconds', () => {
575
- const result = Time.formatTime(Time.minute * 5 + Time.second * 30)
576
- expect(result).toContain('分钟')
577
- expect(result).toContain('秒')
578
- })
579
-
580
- it('should format seconds only', () => {
581
- const result = Time.formatTime(Time.second * 30)
582
- expect(result).toContain('秒')
583
- })
584
- })
585
-
586
- describe('template', () => {
587
- it('should format date template', () => {
588
- const date = new Date('2024-01-15T14:30:45.123')
589
- const result = Time.template('yyyy-MM-dd hh:mm:ss', date)
590
- expect(result).toBe('2024-01-15 14:30:45')
591
- })
592
-
593
- it('should handle short year format', () => {
594
- const date = new Date('2024-01-15')
595
- const result = Time.template('yy-MM-dd', date)
596
- expect(result).toBe('24-01-15')
597
- })
598
-
599
- it('should handle milliseconds', () => {
600
- const date = new Date('2024-01-15T14:30:45.123')
601
- const result = Time.template('SSS', date)
602
- expect(result).toBe('123')
603
- })
604
- })
605
-
606
- describe('formatTimeInterval', () => {
607
- it('should format without interval', () => {
608
- const date = new Date('2024-01-15T14:30:45')
609
- const result = Time.formatTimeInterval(date)
610
- expect(result).toContain('2024-01-15')
611
- })
612
-
613
- it('should format daily interval', () => {
614
- const date = new Date('2024-01-15T14:30:00')
615
- const result = Time.formatTimeInterval(date, Time.day)
616
- expect(result).toContain('每天')
617
- })
618
-
619
- it('should format weekly interval', () => {
620
- const date = new Date('2024-01-15T14:30:00')
621
- const result = Time.formatTimeInterval(date, Time.week)
622
- expect(result).toContain('每周')
623
- })
624
-
625
- it('should format custom interval', () => {
626
- const date = new Date('2024-01-15T14:30:00')
627
- const result = Time.formatTimeInterval(date, Time.hour * 6)
628
- expect(result).toContain('每隔')
629
- })
630
- })
631
- })
632
-
633
- describe('sleep utility', () => {
634
- it('should sleep for specified time', async () => {
635
- const start = Date.now()
636
- await sleep(100)
637
- const elapsed = Date.now() - start
638
- expect(elapsed).toBeGreaterThanOrEqual(90) // 允许一些误差
639
134
  })
640
135
  })