tcdona_unilib 1.0.11 → 1.0.12
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/.prettierrc.json +6 -0
- package/animejs.ts +13 -1
- package/ast-grep.ts +42 -0
- package/big.ts +2 -2
- package/clipboardy.ts +1 -1
- package/dayjs.ts +2 -2
- package/dotenvx.ts +15 -2
- package/eff/eff.delay.ts +21 -0
- package/eff/eff.ts +226 -0
- package/eff/effcan.ts +286 -0
- package/eff/throwable.ts +11 -0
- package/effector.ts +33 -2
- package/es-toolkit.ts +1 -7
- package/exome.ts +10 -2
- package/hono.ts +18 -2
- package/hotkeys-js.ts +3 -2
- package/idmeta.json +42 -0
- package/inquirer.ts +1 -1
- package/koka/async.ts +2 -7
- package/koka/ctx.ts +2 -9
- package/koka/err.ts +2 -9
- package/koka/gen.ts +2 -9
- package/koka/index.ts +1 -1
- package/koka/opt.ts +2 -7
- package/koka/result.ts +6 -9
- package/koka/task.ts +2 -8
- package/koka-domain.ts +13 -16
- package/koka.ts +29 -34
- package/magic-regexp.ts +28 -2
- package/marked.ts +28 -2
- package/nanoid.ts +7 -1
- package/nanostores.ts +31 -2
- package/neverthrow.ts +2 -2
- package/package.json +11 -22
- package/pathe.ts +16 -1
- package/pinyin-pro.ts +16 -2
- package/prettier.ts +20 -2
- package/staticMeta/enum.api.ts +111 -82
- package/staticMeta/err.ts +64 -0
- package/staticMeta/file.yml.ts +12 -12
- package/staticMeta/md.html2md.ts +38 -0
- package/staticMeta/md.md2html.ts +203 -0
- package/staticMeta/path.init.ts +12 -12
- package/staticMeta/string.nanoid.ts +7 -7
- package/staticMeta/url.ts +57 -54
- package/tinypool.ts +29 -2
- package/turndown.ts +1 -1
- package/vite.ts +2 -2
- package/viteplugin/md.plugin.dist.d.ts +18 -0
- package/viteplugin/md.plugin.dist.js +1133 -0
- package/viteplugin/md.plugin.ts +22 -0
- package/viteplugin/vite-env.d.ts +6 -0
- package/viteplugin/vite.config.ts +62 -0
- package/vue.ts +2 -12
- package/zod.ts +19 -2
- package/zx.ts +25 -1
- package/@ast-grep.ts +0 -18
- package/comctx.ts +0 -2
- package/hono/cors.ts +0 -1
- package/hono/logger.ts +0 -1
- package/hono/timeout.ts +0 -1
- package/staticMeta/ast.scan.ts +0 -131
- package/staticMeta/ast.ts +0 -259
- package/staticMeta/eff.delay.ts +0 -17
- package/staticMeta/eff.ts +0 -203
- package/staticMeta/iduniq.ts +0 -320
- package/staticMeta/idupdate.ts +0 -374
- package/staticMeta/pkg.json.ts +0 -138
- package/staticMeta/project.ts +0 -98
- package/staticMeta/sync.ts +0 -296
package/.prettierrc.json
ADDED
package/animejs.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
animate,
|
|
3
|
+
createTimeline,
|
|
4
|
+
createTimer,
|
|
5
|
+
createScope,
|
|
6
|
+
createSpring,
|
|
7
|
+
createDraggable,
|
|
8
|
+
stagger,
|
|
9
|
+
eases,
|
|
10
|
+
utils,
|
|
11
|
+
svg,
|
|
12
|
+
engine,
|
|
13
|
+
} from 'animejs'
|
package/ast-grep.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// -----Type Only Export!-----//
|
|
2
|
+
export type {
|
|
3
|
+
FileOption,
|
|
4
|
+
FindConfig,
|
|
5
|
+
NapiConfig,
|
|
6
|
+
DynamicLangRegistrations,
|
|
7
|
+
Edit,
|
|
8
|
+
Pos,
|
|
9
|
+
Range,
|
|
10
|
+
Rule,
|
|
11
|
+
Lang,
|
|
12
|
+
SgNode,
|
|
13
|
+
} from '@ast-grep/napi'
|
|
14
|
+
|
|
15
|
+
// -----Runtime Value Export!-----//
|
|
16
|
+
// 导入所有需要的模块
|
|
17
|
+
import {
|
|
18
|
+
findInFiles,
|
|
19
|
+
kind,
|
|
20
|
+
parse,
|
|
21
|
+
parseAsync,
|
|
22
|
+
parseFiles,
|
|
23
|
+
pattern,
|
|
24
|
+
registerDynamicLanguage,
|
|
25
|
+
Lang,
|
|
26
|
+
SgNode,
|
|
27
|
+
SgRoot,
|
|
28
|
+
} from '@ast-grep/napi'
|
|
29
|
+
|
|
30
|
+
// 导出为对象,避免命名冲突
|
|
31
|
+
export const astgrep = {
|
|
32
|
+
findInFiles,
|
|
33
|
+
kind,
|
|
34
|
+
parse,
|
|
35
|
+
parseAsync,
|
|
36
|
+
parseFiles,
|
|
37
|
+
pattern,
|
|
38
|
+
registerDynamicLanguage,
|
|
39
|
+
Lang,
|
|
40
|
+
SgNode,
|
|
41
|
+
SgRoot,
|
|
42
|
+
}
|
package/big.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as Big } from 'big.js'
|
|
2
|
-
export type { Big as BigType, BigSource } from 'big.js'
|
|
1
|
+
export { default as Big } from 'big.js'
|
|
2
|
+
export type { Big as BigType, BigSource } from 'big.js'
|
package/clipboardy.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as clipboard } from 'clipboardy'
|
|
1
|
+
export { default as clipboard } from 'clipboardy'
|
package/dayjs.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as dayjs } from 'dayjs'
|
|
2
|
-
export type { Dayjs, ConfigType, OptionType } from 'dayjs'
|
|
1
|
+
export { default as dayjs } from 'dayjs'
|
|
2
|
+
export type { Dayjs, ConfigType, OptionType } from 'dayjs'
|
package/dotenvx.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
// 导入所有需要的模块
|
|
2
|
+
import { config, parse, set, get, ls, genexample } from '@dotenvx/dotenvx'
|
|
3
|
+
|
|
4
|
+
// 导出为对象,避免命名冲突
|
|
5
|
+
export const dotenvx = {
|
|
6
|
+
config,
|
|
7
|
+
parse,
|
|
8
|
+
set,
|
|
9
|
+
get,
|
|
10
|
+
ls,
|
|
11
|
+
genexample,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 导出类型
|
|
2
15
|
export type {
|
|
3
16
|
DotenvConfigOptions,
|
|
4
17
|
DotenvConfigOutput,
|
|
@@ -10,4 +23,4 @@ export type {
|
|
|
10
23
|
SetOutput,
|
|
11
24
|
GetOptions,
|
|
12
25
|
GenExampleOutput,
|
|
13
|
-
} from
|
|
26
|
+
} from '@dotenvx/dotenvx'
|
package/eff/eff.delay.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ok } from 'neverthrow'
|
|
2
|
+
import { Eff } from './eff'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 异步延迟函数:返回延迟是否完成(true)或被取消(false)
|
|
6
|
+
*/
|
|
7
|
+
export const delayEff = (time: number, parentSignal: AbortSignal) => {
|
|
8
|
+
return Eff.create<boolean, never>(({ signal }) => {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const timer = setTimeout(() => resolve(ok(true)), time)
|
|
11
|
+
signal.addEventListener(
|
|
12
|
+
'abort',
|
|
13
|
+
() => {
|
|
14
|
+
clearTimeout(timer)
|
|
15
|
+
resolve(ok(false))
|
|
16
|
+
},
|
|
17
|
+
{ once: true },
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
}, parentSignal)
|
|
21
|
+
}
|
package/eff/eff.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eff 框架:基于 Result 的异步任务管理
|
|
3
|
+
* - root/create:创建异步任务
|
|
4
|
+
* - all/any/race/allSettled:并行或竞速执行多个任务
|
|
5
|
+
* - Signal 传播:支持任务取消和自动清理
|
|
6
|
+
* - 支持返回 Result AsyncResult 不支持直接返回值
|
|
7
|
+
*/
|
|
8
|
+
import type { Result } from 'neverthrow'
|
|
9
|
+
import { err, ok, ResultAsync } from 'neverthrow'
|
|
10
|
+
|
|
11
|
+
export interface EffContext {
|
|
12
|
+
signal: AbortSignal
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 任务执行器类型
|
|
17
|
+
*
|
|
18
|
+
* 设计说明:
|
|
19
|
+
* - 只接受函数,不接受直接的 ResultAsync<T, E>
|
|
20
|
+
* - 原因:函数形式表示延迟执行(调用时才执行),而 ResultAsync 表示已开始执行
|
|
21
|
+
* - 与 Promise.all 设计一致:接受函数数组而非 Promise 数组
|
|
22
|
+
* - 保持类型简单,避免运行时 instanceof 检查
|
|
23
|
+
* - 未来不应修改为支持 ResultAsync,以保持语义清晰和性能最优
|
|
24
|
+
*/
|
|
25
|
+
type EffExecutor<T, E> = (ctx: EffContext) => PromiseLike<Result<T, E>>
|
|
26
|
+
type NonEmptyArray<T> = readonly [T, ...T[]]
|
|
27
|
+
|
|
28
|
+
const ABORT_REASON = { SETTLED: 'settled', COMPLETED: 'completed' } as const
|
|
29
|
+
const noop = () => {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 将父 signal abort 传播到子 controller,返回解绑函数
|
|
33
|
+
*/
|
|
34
|
+
const linkSignal = (
|
|
35
|
+
parentSignal: AbortSignal,
|
|
36
|
+
controller: AbortController,
|
|
37
|
+
): (() => void) => {
|
|
38
|
+
if (parentSignal.aborted) {
|
|
39
|
+
controller.abort(parentSignal.reason)
|
|
40
|
+
return noop
|
|
41
|
+
}
|
|
42
|
+
const handler = () => controller.abort(parentSignal.reason)
|
|
43
|
+
parentSignal.addEventListener('abort', handler, { once: true })
|
|
44
|
+
return () => parentSignal.removeEventListener('abort', handler)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 并行运行多个任务,通过回调函数允许调用方控制何时 settle
|
|
49
|
+
*
|
|
50
|
+
* 类型参数说明:
|
|
51
|
+
* - T: 各个任务的成功值类型
|
|
52
|
+
* - E: 各个任务的错误值类型
|
|
53
|
+
* - R: 最终返回的成功值类型(由 onResult 的 settle 决定)
|
|
54
|
+
* - ER: 最终返回的错误值类型(由 onResult 的 settle 决定)
|
|
55
|
+
*/
|
|
56
|
+
const runAll = <T, E, R, ER>(
|
|
57
|
+
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
58
|
+
parentSignal: AbortSignal,
|
|
59
|
+
onResult: (
|
|
60
|
+
result: Result<T, E>,
|
|
61
|
+
index: number,
|
|
62
|
+
settle: (r: Result<R, ER>) => void,
|
|
63
|
+
context: {
|
|
64
|
+
resultCount: number
|
|
65
|
+
okValues: T[]
|
|
66
|
+
errValues: E[]
|
|
67
|
+
allResults: Result<T, E>[]
|
|
68
|
+
},
|
|
69
|
+
) => void,
|
|
70
|
+
): ResultAsync<R, ER> =>
|
|
71
|
+
Eff.create(({ signal }) => {
|
|
72
|
+
const controller = new AbortController()
|
|
73
|
+
const childSignal = controller.signal
|
|
74
|
+
const unlink = linkSignal(signal, controller)
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
let done = false
|
|
78
|
+
const context = {
|
|
79
|
+
resultCount: 0,
|
|
80
|
+
okValues: [] as T[],
|
|
81
|
+
errValues: [] as E[],
|
|
82
|
+
allResults: [] as Result<T, E>[],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const settle = (r: Result<R, ER>) => {
|
|
86
|
+
if (done) return
|
|
87
|
+
done = true
|
|
88
|
+
unlink()
|
|
89
|
+
controller.abort(ABORT_REASON.SETTLED)
|
|
90
|
+
resolve(r)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (signal.aborted) {
|
|
94
|
+
settle(ok([] as R))
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let remaining = executors.length
|
|
99
|
+
|
|
100
|
+
executors.forEach((executor, i) => {
|
|
101
|
+
Eff.create(executor, childSignal).then((r) => {
|
|
102
|
+
if (done) return
|
|
103
|
+
remaining--
|
|
104
|
+
onResult(r, i, settle, context)
|
|
105
|
+
if (remaining === 0) settle(ok(context.allResults as R))
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
}, parentSignal)
|
|
110
|
+
|
|
111
|
+
export class Eff {
|
|
112
|
+
/**
|
|
113
|
+
* 创建根级任务(无父 signal)
|
|
114
|
+
*/
|
|
115
|
+
static root<T, E>(executor: EffExecutor<T, E>): ResultAsync<T, E> {
|
|
116
|
+
const controller = new AbortController()
|
|
117
|
+
const promise: Promise<Result<T, E>> = Promise.resolve()
|
|
118
|
+
.then(() => executor({ signal: controller.signal }))
|
|
119
|
+
.catch((e) => err(e as E))
|
|
120
|
+
.finally(() => controller.abort(ABORT_REASON.COMPLETED))
|
|
121
|
+
return new ResultAsync(promise)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 创建子任务(继承父 signal,支持取消传播)
|
|
126
|
+
*/
|
|
127
|
+
static create<T, E>(
|
|
128
|
+
executor: EffExecutor<T, E>,
|
|
129
|
+
parentSignal: AbortSignal,
|
|
130
|
+
): ResultAsync<T, E> {
|
|
131
|
+
const controller = new AbortController()
|
|
132
|
+
const unlink = linkSignal(parentSignal, controller)
|
|
133
|
+
|
|
134
|
+
const promise: Promise<Result<T, E>> = Promise.resolve()
|
|
135
|
+
.then(() => executor({ signal: controller.signal }))
|
|
136
|
+
.catch((e) => err(e as E))
|
|
137
|
+
.finally(() => {
|
|
138
|
+
unlink()
|
|
139
|
+
controller.abort(ABORT_REASON.COMPLETED)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
return new ResultAsync(promise)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 全部成功才成功,任一失败立即失败并 abort 其他
|
|
147
|
+
*/
|
|
148
|
+
static all<T, E>(
|
|
149
|
+
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
150
|
+
parentSignal: AbortSignal,
|
|
151
|
+
): ResultAsync<T[], E> {
|
|
152
|
+
return runAll<T, E, T[], E>(
|
|
153
|
+
executors,
|
|
154
|
+
parentSignal,
|
|
155
|
+
(result, index, settle, context) => {
|
|
156
|
+
result.match(
|
|
157
|
+
(v) => {
|
|
158
|
+
context.okValues[index] = v
|
|
159
|
+
context.resultCount++
|
|
160
|
+
if (context.resultCount === executors.length) {
|
|
161
|
+
settle(ok(context.okValues))
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
(e) => settle(err(e)),
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 任一成功立即成功并 abort 其他,全部失败才失败
|
|
172
|
+
*/
|
|
173
|
+
static any<T, E>(
|
|
174
|
+
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
175
|
+
parentSignal: AbortSignal,
|
|
176
|
+
): ResultAsync<T, E[]> {
|
|
177
|
+
return runAll<T, E, T, E[]>(
|
|
178
|
+
executors,
|
|
179
|
+
parentSignal,
|
|
180
|
+
(result, index, settle, context) => {
|
|
181
|
+
result.match(
|
|
182
|
+
(v) => settle(ok(v)),
|
|
183
|
+
(e) => {
|
|
184
|
+
context.errValues[index] = e
|
|
185
|
+
context.resultCount++
|
|
186
|
+
if (context.resultCount === executors.length) {
|
|
187
|
+
settle(err(context.errValues))
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 任一完成立即返回其结果并 abort 其他
|
|
197
|
+
*/
|
|
198
|
+
static race<T, E>(
|
|
199
|
+
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
200
|
+
parentSignal: AbortSignal,
|
|
201
|
+
): ResultAsync<T, E> {
|
|
202
|
+
return runAll<T, E, T, E>(
|
|
203
|
+
executors,
|
|
204
|
+
parentSignal,
|
|
205
|
+
(result, _index, settle) => {
|
|
206
|
+
settle(result)
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 等待全部完成,收集所有结果(不因某个失败而 abort)
|
|
213
|
+
*/
|
|
214
|
+
static allSettled<T, E>(
|
|
215
|
+
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
216
|
+
parentSignal: AbortSignal,
|
|
217
|
+
): ResultAsync<Result<T, E>[], never> {
|
|
218
|
+
return runAll<T, E, Result<T, E>[], never>(
|
|
219
|
+
executors,
|
|
220
|
+
parentSignal,
|
|
221
|
+
(result, index, _settle, context) => {
|
|
222
|
+
context.allResults[index] = result
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
package/eff/effcan.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EffCan 框架:基于 ResultAsync 的异步任务管理
|
|
3
|
+
* 与 Eff 的区别:
|
|
4
|
+
* - Executor 返回 ResultAsync<T, E> 而非 PromiseLike<Result<T, E>>
|
|
5
|
+
* - 假设 executor 不会抛出错误(无 catch 处理)
|
|
6
|
+
* - 保持 signal 生成/传递的语义和无内存泄露风险
|
|
7
|
+
*
|
|
8
|
+
* 设计原则与注意事项:
|
|
9
|
+
* 1. 内存管理
|
|
10
|
+
* - linkSignal 返回的解绑函数必须被调用(在 runAll 的 settle 中执行)
|
|
11
|
+
* - cleanup 需要在 Ok/Err 都执行(通过 andTee + orTee 组合实现)
|
|
12
|
+
* - 若修改清理逻辑,务必确保两个分支都能执行
|
|
13
|
+
*
|
|
14
|
+
* 2. Signal abort 阶段
|
|
15
|
+
* - SETTLED: 标志多任务中至少一个已确定结果,触发其他任务中断
|
|
16
|
+
* - COMPLETED: 标志单个任务完成,用于事后清理
|
|
17
|
+
* - 注意顺序:settle() 先调用 unlink(),再 abort(SETTLED),保证解绑后才中断
|
|
18
|
+
*
|
|
19
|
+
* 3. runAll 中的 done 标志
|
|
20
|
+
* - 防止 settle 被多次调用(Promise resolve 只有一次效果,但可能重复执行副作用)
|
|
21
|
+
* - 也防止已 settle 后的结果被重复处理
|
|
22
|
+
*
|
|
23
|
+
* 4. allSettled 的特殊性
|
|
24
|
+
* - 不因任一失败而 abort,但仍需执行并发控制
|
|
25
|
+
* - 返回类型 never 表示这个操作本身不会产生错误
|
|
26
|
+
*/
|
|
27
|
+
import type { Result } from 'neverthrow'
|
|
28
|
+
import { err, ok, ResultAsync } from 'neverthrow'
|
|
29
|
+
|
|
30
|
+
export interface EffContext {
|
|
31
|
+
signal: AbortSignal
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 任务执行器类型
|
|
36
|
+
* - 返回 ResultAsync<T, E>,表示异步任务已开始执行
|
|
37
|
+
* - 假设不会抛出错误,错误应通过 Err 返回
|
|
38
|
+
*/
|
|
39
|
+
type EffCanExecutor<T, E> = (ctx: EffContext) => ResultAsync<T, E>
|
|
40
|
+
type NonEmptyArray<T> = readonly [T, ...T[]]
|
|
41
|
+
|
|
42
|
+
const ABORT_REASON = { SETTLED: 'settled', COMPLETED: 'completed' } as const
|
|
43
|
+
const noop = () => {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 将父 signal abort 传播到子 controller,返回解绑函数
|
|
47
|
+
*/
|
|
48
|
+
const linkSignal = (
|
|
49
|
+
parentSignal: AbortSignal,
|
|
50
|
+
controller: AbortController,
|
|
51
|
+
): (() => void) => {
|
|
52
|
+
if (parentSignal.aborted) {
|
|
53
|
+
controller.abort(parentSignal.reason)
|
|
54
|
+
return noop
|
|
55
|
+
}
|
|
56
|
+
const handler = () => controller.abort(parentSignal.reason)
|
|
57
|
+
parentSignal.addEventListener('abort', handler, { once: true })
|
|
58
|
+
return () => parentSignal.removeEventListener('abort', handler)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 并行运行多个任务,通过回调函数允许调用方控制何时 settle
|
|
63
|
+
*
|
|
64
|
+
* 类型参数说明:
|
|
65
|
+
* - T: 各个任务的成功值类型
|
|
66
|
+
* - E: 各个任务的错误值类型
|
|
67
|
+
* - R: 最终返回的成功值类型(由 onResult 的 settle 决定)
|
|
68
|
+
* - ER: 最终返回的错误值类型(由 onResult 的 settle 决定)
|
|
69
|
+
*
|
|
70
|
+
* 关键设计细节:
|
|
71
|
+
* - 使用 childSignal 而非 signal,确保所有子任务中断后再结束
|
|
72
|
+
* - settle 被调用时:
|
|
73
|
+
* 1. done 标志防止重复处理
|
|
74
|
+
* 2. unlink() 移除父子 signal 关联(防止内存泄露)
|
|
75
|
+
* 3. abort(SETTLED) 中断所有未完成的子任务
|
|
76
|
+
* 4. resolve(r) Promise 才真正完成
|
|
77
|
+
* - 若所有任务都完成但没被 settle,则在 remaining === 0 时调用
|
|
78
|
+
* - 初始 signal.aborted 检查处理已被中止的父任务场景
|
|
79
|
+
*/
|
|
80
|
+
const runAll = <T, E, R, ER>(
|
|
81
|
+
executors: NonEmptyArray<EffCanExecutor<T, E>>,
|
|
82
|
+
parentSignal: AbortSignal,
|
|
83
|
+
onResult: (
|
|
84
|
+
result: Result<T, E>,
|
|
85
|
+
index: number,
|
|
86
|
+
settle: (r: Result<R, ER>) => void,
|
|
87
|
+
context: {
|
|
88
|
+
resultCount: number
|
|
89
|
+
okValues: T[]
|
|
90
|
+
errValues: E[]
|
|
91
|
+
allResults: Result<T, E>[]
|
|
92
|
+
},
|
|
93
|
+
) => void,
|
|
94
|
+
): ResultAsync<R, ER> =>
|
|
95
|
+
EffCan.create(({ signal }) => {
|
|
96
|
+
const controller = new AbortController()
|
|
97
|
+
const childSignal = controller.signal
|
|
98
|
+
const unlink = linkSignal(signal, controller)
|
|
99
|
+
|
|
100
|
+
const promise = new Promise<Result<R, ER>>((resolve) => {
|
|
101
|
+
let done = false
|
|
102
|
+
const context = {
|
|
103
|
+
resultCount: 0,
|
|
104
|
+
okValues: [] as T[],
|
|
105
|
+
errValues: [] as E[],
|
|
106
|
+
allResults: [] as Result<T, E>[],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const settle = (r: Result<R, ER>) => {
|
|
110
|
+
if (done) return
|
|
111
|
+
done = true
|
|
112
|
+
unlink()
|
|
113
|
+
controller.abort(ABORT_REASON.SETTLED)
|
|
114
|
+
resolve(r)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (signal.aborted) {
|
|
118
|
+
settle(ok([] as R))
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let remaining = executors.length
|
|
123
|
+
|
|
124
|
+
executors.forEach((executor, i) => {
|
|
125
|
+
EffCan.create(executor, childSignal).then((r) => {
|
|
126
|
+
if (done) return
|
|
127
|
+
remaining--
|
|
128
|
+
onResult(r, i, settle, context)
|
|
129
|
+
if (remaining === 0) settle(ok(context.allResults as R))
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
return new ResultAsync(promise)
|
|
135
|
+
}, parentSignal)
|
|
136
|
+
|
|
137
|
+
export class EffCan {
|
|
138
|
+
/**
|
|
139
|
+
* 创建根级任务(无父 signal)
|
|
140
|
+
*
|
|
141
|
+
* 关键细节:
|
|
142
|
+
* - resultAsync.andTee(cleanup) 在 Ok 分支执行清理
|
|
143
|
+
* - resultAsync.orTee(cleanup) 在 Err 分支执行清理
|
|
144
|
+
* - 类型断言 as ResultAsync<T, E> 是必要的,因为 orTee 返回的类型推导会变复杂
|
|
145
|
+
* - cleanup 在两个分支都调用确保 abort(COMPLETED) 被执行
|
|
146
|
+
*
|
|
147
|
+
* 为何用 andTee + orTee 而不是其他方式:
|
|
148
|
+
* - .map() 只在 Ok 时执行(Err 时内存未释放)
|
|
149
|
+
* - .then() 返回 PromiseLike 而 ResultAsync 需要 Promise(不兼容)
|
|
150
|
+
* - andTee + orTee 是 neverthrow 提供的原生方式,无副作用污染
|
|
151
|
+
*/
|
|
152
|
+
static root<T, E>(executor: EffCanExecutor<T, E>): ResultAsync<T, E> {
|
|
153
|
+
const controller = new AbortController()
|
|
154
|
+
const resultAsync = executor({ signal: controller.signal })
|
|
155
|
+
|
|
156
|
+
const cleanup = () => {
|
|
157
|
+
controller.abort(ABORT_REASON.COMPLETED)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return resultAsync.andTee(cleanup).orTee(cleanup) as ResultAsync<T, E>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 创建子任务(继承父 signal,支持取消传播)
|
|
165
|
+
*
|
|
166
|
+
* 关键细节:
|
|
167
|
+
* - parentSignal 中止时会自动中止 controller(通过 linkSignal)
|
|
168
|
+
* - unlink() 必须在清理时调用,否则会形成 EventListener 泄露
|
|
169
|
+
* - unlink() 调用顺序很重要:先 unlink,再 abort,避免竞态条件
|
|
170
|
+
* - cleanup 需要在两个分支都执行(通过 andTee + orTee)
|
|
171
|
+
*
|
|
172
|
+
* 内存泄露风险点:
|
|
173
|
+
* 1. 若遗漏 unlink() 调用,parentSignal 的 abort 监听器永不移除 → 内存泄露
|
|
174
|
+
* 2. 若只用 .map() 而不用 .orTee(),Err 分支的 unlink 不执行 → 内存泄露
|
|
175
|
+
* 3. 若改为手动 Promise 包装,需确保 finally 块执行(当前 andTee+orTee 方式更安全)
|
|
176
|
+
*/
|
|
177
|
+
static create<T, E>(
|
|
178
|
+
executor: EffCanExecutor<T, E>,
|
|
179
|
+
parentSignal: AbortSignal,
|
|
180
|
+
): ResultAsync<T, E> {
|
|
181
|
+
const controller = new AbortController()
|
|
182
|
+
const unlink = linkSignal(parentSignal, controller)
|
|
183
|
+
|
|
184
|
+
const resultAsync = executor({ signal: controller.signal })
|
|
185
|
+
|
|
186
|
+
const cleanup = () => {
|
|
187
|
+
unlink()
|
|
188
|
+
controller.abort(ABORT_REASON.COMPLETED)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return resultAsync.andTee(cleanup).orTee(cleanup) as ResultAsync<T, E>
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 全部成功才成功,任一失败立即失败并 abort 其他
|
|
196
|
+
*/
|
|
197
|
+
static all<T, E>(
|
|
198
|
+
executors: NonEmptyArray<EffCanExecutor<T, E>>,
|
|
199
|
+
parentSignal: AbortSignal,
|
|
200
|
+
): ResultAsync<T[], E> {
|
|
201
|
+
return runAll<T, E, T[], E>(
|
|
202
|
+
executors,
|
|
203
|
+
parentSignal,
|
|
204
|
+
(result, index, settle, context) => {
|
|
205
|
+
result.match(
|
|
206
|
+
(v) => {
|
|
207
|
+
context.okValues[index] = v
|
|
208
|
+
context.resultCount++
|
|
209
|
+
if (context.resultCount === executors.length) {
|
|
210
|
+
settle(ok(context.okValues))
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
(e) => settle(err(e)),
|
|
214
|
+
)
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 任一成功立即成功并 abort 其他,全部失败才失败
|
|
221
|
+
*/
|
|
222
|
+
static any<T, E>(
|
|
223
|
+
executors: NonEmptyArray<EffCanExecutor<T, E>>,
|
|
224
|
+
parentSignal: AbortSignal,
|
|
225
|
+
): ResultAsync<T, E[]> {
|
|
226
|
+
return runAll<T, E, T, E[]>(
|
|
227
|
+
executors,
|
|
228
|
+
parentSignal,
|
|
229
|
+
(result, index, settle, context) => {
|
|
230
|
+
result.match(
|
|
231
|
+
(v) => settle(ok(v)),
|
|
232
|
+
(e) => {
|
|
233
|
+
context.errValues[index] = e
|
|
234
|
+
context.resultCount++
|
|
235
|
+
if (context.resultCount === executors.length) {
|
|
236
|
+
settle(err(context.errValues))
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 任一完成立即返回其结果并 abort 其他
|
|
246
|
+
*/
|
|
247
|
+
static race<T, E>(
|
|
248
|
+
executors: NonEmptyArray<EffCanExecutor<T, E>>,
|
|
249
|
+
parentSignal: AbortSignal,
|
|
250
|
+
): ResultAsync<T, E> {
|
|
251
|
+
return runAll<T, E, T, E>(
|
|
252
|
+
executors,
|
|
253
|
+
parentSignal,
|
|
254
|
+
(result, _index, settle) => {
|
|
255
|
+
settle(result)
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 等待全部完成,收集所有结果(不因某个失败而 abort)
|
|
262
|
+
*
|
|
263
|
+
* 关键特性:
|
|
264
|
+
* - 与 all/any/race 不同,allSettled 不提前 settle,总是等所有任务完成
|
|
265
|
+
* - onResult 中不调用 settle,只收集结果
|
|
266
|
+
* - 当 remaining === 0 时才调用 settle(ok(context.allResults))(在 runAll 中)
|
|
267
|
+
* - 返回类型 never 表示外层 Result 的 Err 永不出现(永远 Ok)
|
|
268
|
+
*
|
|
269
|
+
* 用途:
|
|
270
|
+
* 1. 需要所有结果而不关心单个失败时
|
|
271
|
+
* 2. 并发执行多个独立任务并收集所有状态
|
|
272
|
+
* 3. 无需因一个失败而中止其他任务的场景
|
|
273
|
+
*/
|
|
274
|
+
static allSettled<T, E>(
|
|
275
|
+
executors: NonEmptyArray<EffCanExecutor<T, E>>,
|
|
276
|
+
parentSignal: AbortSignal,
|
|
277
|
+
): ResultAsync<Result<T, E>[], never> {
|
|
278
|
+
return runAll<T, E, Result<T, E>[], never>(
|
|
279
|
+
executors,
|
|
280
|
+
parentSignal,
|
|
281
|
+
(result, index, _settle, context) => {
|
|
282
|
+
context.allResults[index] = result
|
|
283
|
+
},
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
package/eff/throwable.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { fromThrowable } from 'neverthrow'
|
|
2
|
+
import { TcErr, fileInit } from '../staticMeta/enum.api'
|
|
3
|
+
|
|
4
|
+
const fileId = fileInit('eff/throwable.ts')
|
|
5
|
+
|
|
6
|
+
export const jsonParse = fromThrowable(JSON.parse, (e) => {
|
|
7
|
+
new TcErr({
|
|
8
|
+
fileId: fileId('jsonParse'),
|
|
9
|
+
cause: e,
|
|
10
|
+
})
|
|
11
|
+
})
|
package/effector.ts
CHANGED
|
@@ -1,2 +1,33 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
1
|
+
export {
|
|
2
|
+
createEvent,
|
|
3
|
+
createStore,
|
|
4
|
+
createEffect,
|
|
5
|
+
createDomain,
|
|
6
|
+
sample,
|
|
7
|
+
combine,
|
|
8
|
+
attach,
|
|
9
|
+
merge,
|
|
10
|
+
split,
|
|
11
|
+
forward,
|
|
12
|
+
guard,
|
|
13
|
+
restore,
|
|
14
|
+
scopeBind,
|
|
15
|
+
allSettled,
|
|
16
|
+
fork,
|
|
17
|
+
serialize,
|
|
18
|
+
hydrate,
|
|
19
|
+
clearNode,
|
|
20
|
+
withRegion,
|
|
21
|
+
launch,
|
|
22
|
+
is,
|
|
23
|
+
} from 'effector'
|
|
24
|
+
export type {
|
|
25
|
+
Event,
|
|
26
|
+
Store,
|
|
27
|
+
Effect,
|
|
28
|
+
Domain,
|
|
29
|
+
Scope,
|
|
30
|
+
Unit,
|
|
31
|
+
EventCallable,
|
|
32
|
+
StoreWritable,
|
|
33
|
+
} from 'effector'
|