rockbed 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/dist/assert/index.d.ts +6 -0
- package/dist/assert/index.js +27 -0
- package/dist/assert/index.js.map +1 -0
- package/dist/async/index.d.ts +105 -0
- package/dist/async/index.js +380 -0
- package/dist/async/index.js.map +1 -0
- package/dist/dispose/index.d.ts +47 -0
- package/dist/dispose/index.js +166 -0
- package/dist/dispose/index.js.map +1 -0
- package/dist/dispose-base-CAeXDpjg.d.ts +6 -0
- package/dist/error/index.d.ts +46 -0
- package/dist/error/index.js +150 -0
- package/dist/error/index.js.map +1 -0
- package/dist/error-base-BWuBlS2k.d.ts +28 -0
- package/dist/event/index.d.ts +57 -0
- package/dist/event/index.js +167 -0
- package/dist/event/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +989 -0
- package/dist/index.js.map +1 -0
- package/dist/json/index.d.ts +4 -0
- package/dist/json/index.js +20 -0
- package/dist/json/index.js.map +1 -0
- package/dist/lock/index.d.ts +106 -0
- package/dist/lock/index.js +404 -0
- package/dist/lock/index.js.map +1 -0
- package/dist/response/index.d.ts +17 -0
- package/dist/response/index.js +19 -0
- package/dist/response/index.js.map +1 -0
- package/package.json +80 -0
- package/src/assert/assert.ts +24 -0
- package/src/assert/index.ts +2 -0
- package/src/async/barrier.ts +47 -0
- package/src/async/index.ts +8 -0
- package/src/async/promise.ts +316 -0
- package/src/async/wait.ts +7 -0
- package/src/dispose/disposable-store.ts +68 -0
- package/src/dispose/disposable-t.ts +85 -0
- package/src/dispose/disposable-utils.ts +13 -0
- package/src/dispose/dispose-base.ts +9 -0
- package/src/dispose/index.ts +8 -0
- package/src/dispose/logger.ts +41 -0
- package/src/error/error-base.ts +39 -0
- package/src/error/error-code.ts +48 -0
- package/src/error/error-const.ts +16 -0
- package/src/error/error-or.ts +2 -0
- package/src/error/error-t.ts +93 -0
- package/src/error/index.ts +5 -0
- package/src/event/emitter.ts +193 -0
- package/src/event/index.ts +10 -0
- package/src/index.ts +8 -0
- package/src/json/index.ts +1 -0
- package/src/json/json.ts +15 -0
- package/src/lock/README.md +11 -0
- package/src/lock/capability.ts +89 -0
- package/src/lock/index.ts +2 -0
- package/src/lock/semaphore.ts +21 -0
- package/src/lock/shared-mutex.ts +256 -0
- package/src/response/index.ts +1 -0
- package/src/response/response.ts +27 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/assert/assert.ts","../../src/dispose/disposable-t.ts","../../src/dispose/disposable-utils.ts","../../src/event/emitter.ts","../../src/lock/capability.ts","../../src/lock/semaphore.ts","../../src/lock/shared-mutex.ts"],"sourcesContent":["function abort(reason: string): never {\n throw new Error(`lvAssert(${reason})`);\n}\n\nexport function lvAssert(expr: unknown, reason?: string): asserts expr {\n if (!expr) {\n abort(reason ?? '#expr is false');\n }\n}\n\nexport function lvAssertNotHere(reason?: string): never {\n abort(reason ?? 'unreachable code flow');\n}\n\nexport function lvAssertNever(member: never, message = 'Illegal value:'): never {\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n abort(`${message}: ${member}`);\n}\n\nexport function lvAssertNotNil<T>(val: T, reason?: string): asserts val is NonNullable<T> {\n if (val === null || val === undefined) {\n abort(reason ?? '#val is nil');\n }\n}\n","import { DisposableStore } from './disposable-store';\nimport type { IDisposable } from './dispose-base';\nimport { BRANCH_DISPOSE } from './logger';\n\n//\n// Disposable基类\n//\n// 自动添加DisposableStore,提供默认的dispose和register方法\n//\nexport abstract class Disposable implements IDisposable {\n protected readonly _store = new DisposableStore();\n\n // 销毁该节点和所有的子节点\n dispose(): void {\n BRANCH_DISPOSE(this.constructor.name, this._store.constructor.name);\n\n this._store.dispose();\n }\n\n // 挂载子节点\n protected _register<T extends IDisposable>(o: T): T {\n if ((o as unknown as Disposable) === this) {\n throw new Error('Cannot register a disposable on itself!');\n }\n return this._store.add(o);\n }\n}\n\nexport class MutableDisposable<T extends IDisposable> implements IDisposable {\n private _value?: T;\n private _isDisposed = false;\n\n constructor(value?: T) {\n this.value = value;\n }\n\n get value(): T | undefined {\n return this._isDisposed ? undefined : this._value;\n }\n\n set value(value: T | undefined) {\n if (this._isDisposed || value === this._value) {\n return;\n }\n\n this._value?.dispose();\n this._value = value;\n }\n\n clear(): void {\n this.value = undefined;\n }\n\n dispose(): void {\n this._isDisposed = true;\n this._value?.dispose();\n this._value = undefined;\n }\n\n release(): T | undefined {\n const oldValue = this._value;\n this._value = undefined;\n return oldValue;\n }\n}\n\nexport class SafeDisposable<T extends IDisposable> implements IDisposable {\n private _value: T | null = null;\n\n constructor(value: T) {\n this._value = value;\n }\n\n isEmpty() {\n return this._value === null;\n }\n\n dispose() {\n if (!this._value) {\n return;\n }\n this._value.dispose();\n this._value = null;\n }\n}\n","import { SafeDisposable } from './disposable-t';\nimport { EmptyDispose } from './dispose-base';\n\nexport function makeSafeDisposable(fn: (...args: any) => any) {\n const disposable = new SafeDisposable({\n dispose: fn,\n });\n return disposable;\n}\n\nexport function makeEmptyDisposable() {\n return EmptyDispose;\n}\n","/* eslint-disable @typescript-eslint/no-redundant-type-constituents */\nimport { makeSafeDisposable } from '../dispose';\nimport type { IDisposable } from '../dispose';\n\nexport interface EmitterOptions {\n onAddListener?: (...args: any) => any;\n onRemoveListener?: (...args: any) => any;\n onListenerError?: (e: any) => void;\n}\n\n/**\n * 默认错误处理:异步抛出监听器错误,避免阻塞当前事件分发。\n */\nexport function asyncUnexpectedErrorHandler(e: unknown): undefined {\n setTimeout(() => {\n throw e;\n }, 0);\n}\n\n/**\n * 同步抛出监听器错误,适合测试或需要立即失败的场景。\n */\nexport function syncUnexpectedError(e: unknown): undefined {\n throw e;\n}\n\n/**\n * 忽略监听器错误,适合明确允许 best-effort 通知的场景。\n */\nexport function ignoreUnexpectedError(_e: unknown): undefined {}\n\n//\n// 事件监听中的回调实体\n//\nclass Listener<TArgs extends any[]> {\n private readonly _callback: (...args: TArgs) => void;\n private readonly _callbackThis: any | undefined;\n\n constructor(callback: (...args: TArgs) => void, callbackThis: any | undefined) {\n this._callback = callback;\n this._callbackThis = callbackThis;\n }\n\n invoke(...args: TArgs): void {\n this._callback.call(this._callbackThis, ...args);\n }\n}\n\n//\n// 存放在EventDeliveryQueue中的元素\n//\nclass EventDeliveryQueueElement<TArgs extends any[]> {\n readonly emitter: Emitter<TArgs>;\n readonly listener: Listener<TArgs>;\n readonly event: TArgs;\n constructor(emitter: Emitter<TArgs>, listener: Listener<TArgs>, event: TArgs) {\n this.emitter = emitter;\n this.listener = listener;\n this.event = event;\n }\n}\n\nexport class EventDeliveryQueue {\n protected _queue: EventDeliveryQueueElement<any>[] = [];\n\n constructor(\n private readonly _onListenerError: (e: unknown) => void = asyncUnexpectedErrorHandler,\n ) {}\n\n get size(): number {\n return this._queue.length;\n }\n\n push<TArgs extends any[]>(\n emitter: Emitter<TArgs>,\n listener: Listener<TArgs>,\n event: TArgs,\n ): void {\n this._queue.push(new EventDeliveryQueueElement(emitter, listener, event));\n }\n\n clear<TArgs extends any[]>(emitter: Emitter<TArgs>): void {\n this._queue = this._queue.filter((element) => element.emitter !== emitter);\n }\n\n deliver(): void {\n while (this._queue.length > 0) {\n const element = this._queue.shift()!;\n try {\n element.listener.invoke(...element.event);\n } catch (e) {\n this._onListenerError(e);\n }\n }\n }\n}\n\nexport interface Event<T extends any[]> {\n (listener: (...args: T) => any, thisArgs?: any): IDisposable;\n}\n\nexport class Emitter<TArgs extends any[]> {\n protected _listeners?: Set<Listener<TArgs>>;\n private readonly _options?: EmitterOptions;\n private _disposed = false;\n private _event?: Event<TArgs>;\n private _deliveryQueue?: EventDeliveryQueue;\n\n constructor(options?: EmitterOptions) {\n this._options = options;\n }\n\n get event(): Event<TArgs> {\n if (this._event) {\n return this._event;\n }\n\n this._event = (callback: (...args: TArgs) => any, thisArgs?: any): IDisposable => {\n if (!this._listeners) {\n this._listeners = new Set();\n }\n\n const listener = new Listener(callback, thisArgs);\n this._listeners.add(listener);\n\n if (this._options?.onAddListener) {\n this._options.onAddListener(this, callback, thisArgs);\n }\n\n // 生成可销毁函数返回\n const result = () => {\n if (!this._disposed) {\n this._listeners?.delete(listener);\n if (this._options?.onRemoveListener) {\n this._options.onRemoveListener(this, callback, thisArgs);\n }\n }\n };\n\n return makeSafeDisposable(result);\n };\n\n return this._event;\n }\n\n dispose(): void {\n if (this._disposed) {\n return;\n }\n this._disposed = true;\n this._listeners?.clear();\n this._deliveryQueue?.clear(this);\n }\n\n fire(...event: TArgs): void {\n if (!this._listeners) {\n return;\n }\n this._deliveryQueue ??= new EventDeliveryQueue(this._options?.onListenerError);\n\n for (const listener of this._listeners) {\n this._deliveryQueue.push(this, listener, event);\n }\n this._deliveryQueue.deliver();\n }\n}\n\n// 辅助能力:只监听某个事件一次\nexport function listenOnce<TArgs extends any[]>(event: Event<TArgs>): Event<TArgs> {\n return (listener, thisArgs = null) => {\n let didFire = false;\n let result: IDisposable | undefined = undefined;\n\n result = event((...args) => {\n if (didFire) {\n return;\n }\n if (result) {\n result.dispose();\n } else {\n didFire = true;\n }\n\n return listener.call(thisArgs, ...args);\n }, null);\n\n if (didFire) {\n result.dispose();\n }\n\n return result;\n };\n}\n","import { lvAssert } from '../assert';\nimport { Emitter, type Event } from '../event';\n\n//\n// 资源对应的是标准库中的 unsigned state 以及相关的位运算\n// 我们用两个具体的Capability结构实现(Capability命名来源于标准库)\n//\n\n/**\n * 资源状态\n */\nexport enum CapabilityStatus {\n Unlocked,\n Locked,\n}\n\n/**\n * 独享的资源\n *\n * acquire 获取控制权\n * release 释放控制权\n */\nexport class Capability {\n public onUnlocked: Event<[]>;\n\n private readonly _onUnlocked = new Emitter<[]>();\n private _status = CapabilityStatus.Unlocked;\n\n constructor() {\n this.onUnlocked = this._onUnlocked.event;\n }\n\n get status(): CapabilityStatus {\n return this._status;\n }\n\n public acquire(): void {\n lvAssert(this._status === CapabilityStatus.Unlocked);\n this._status = CapabilityStatus.Locked;\n }\n\n public release(): void {\n lvAssert(this._status === CapabilityStatus.Locked);\n this._status = CapabilityStatus.Unlocked;\n this._onUnlocked.fire();\n }\n}\n\n/**\n * 共享的资源\n *\n * acquire 获取控制权\n * release 释放控制权\n */\nexport class SharedCapability {\n public onUnlocked: Event<[]>;\n\n private readonly _onUnlocked = new Emitter<[]>();\n private _status = CapabilityStatus.Unlocked;\n private _counter = 0;\n\n constructor() {\n this.onUnlocked = this._onUnlocked.event;\n }\n\n get status(): CapabilityStatus {\n return this._status;\n }\n\n get counter(): number {\n return this._counter;\n }\n\n public acquire() {\n if (this._status === CapabilityStatus.Unlocked) {\n this._status = CapabilityStatus.Locked;\n }\n this._counter++;\n }\n\n public release() {\n lvAssert(this._counter > 0);\n this._counter--;\n if (this._counter === 0) {\n this._status = CapabilityStatus.Unlocked;\n this._onUnlocked.fire();\n }\n }\n}\n","import type { Event } from '../event';\nimport { Emitter } from '../event';\n\n/**\n * 信号\n *\n * 用来模拟标准库的condition_variable\n * 提供监听某个信号被激活的能力\n */\nexport class Semaphore {\n public onActive: Event<[]>;\n private readonly _onActive = new Emitter<[]>();\n\n constructor() {\n this.onActive = this._onActive.event;\n }\n\n public notify(): void {\n this._onActive.fire();\n }\n}\n","import { lvAssert, lvAssertNotNil } from '../assert';\nimport { listenOnce } from '../event';\nimport { SharedCapability, Capability, CapabilityStatus } from './capability';\nimport { Semaphore } from './semaphore';\n\n/**\n * 提供读写能力的共享互斥量\n *\n * 参考C++17标准库双门思想实现\n * 接口也与标准库保持一致\n * 方法内部禁止promise,只可以对外暴露promise\n *\n * 核心\n * - 写写互斥,读写互斥,读读可重入\n *\n * 使用举例:\n * class Foo {\n * private _mutex = new SharedMutex();\n *\n * async add() {\n * // 上写锁\n * await this._mutex.lock();\n * // ...write something\n * this._mutex.unlock();\n * }\n *\n * async getSomething1() {\n * // 上读锁\n * await this._mutex.lockShared();\n * try {\n * return xxx;\n * } finally {\n * this._mutex.unlockShared();\n * }\n * }\n *\n * async getSomething2() {\n * // 上读锁\n * await this._mutex.lockShared();\n * try {\n * return xxx;\n * } finally {\n * this._mutex.unlockShared();\n * }\n * }\n * }\n */\nexport class SharedMutex {\n // 在第一道门外等待的写者\n private readonly _waitingWriters: Semaphore[] = [];\n\n // 已经通过了第一道门的写者\n // 如果在第二道门外等待,状态为sharedLocked\n // 如果已经进入到第二道门内拿到了锁,状态为locked\n private _writer?: Capability;\n\n // 在第一道门外等待的读者\n private _waitingReader?: Semaphore;\n\n // 拿到锁的读者\n private _reader?: SharedCapability;\n\n /**\n * 是否被锁住\n */\n public isLocked(): boolean {\n return Boolean(this._writer) || this._readerCount !== 0;\n }\n\n /**\n * 等待并获取写锁\n */\n public lock(): Promise<void> {\n return new Promise<void>((resolve) => {\n // 第一道门\n if (this._writer) {\n // 如果已经有写者进入了,其他写者等待\n const token = new Semaphore();\n this._waitingWriters.push(token);\n token.onActive(() => {\n this._writerEnterGate1(resolve);\n });\n } else {\n this._writerEnterGate1(resolve);\n }\n });\n }\n\n /**\n * 尝试获取写锁,立刻返回结果\n */\n public tryLock(): boolean {\n if (this._writer || this._readerCount > 0) {\n return false;\n }\n // 这里不需要await,一定可以上锁\n this.lock();\n return true;\n }\n\n /**\n * 解除写锁\n */\n public unLock(): void {\n lvAssertNotNil(this._writer);\n\n // 打开第一道门\n this._writer.release();\n }\n\n /**\n * 解除写锁。`unLock` 的常规拼写别名。\n */\n public unlock(): void {\n this.unLock();\n }\n\n /**\n * 等待并获取读锁\n */\n public lockShared(): Promise<void> {\n return new Promise<void>((resolve) => {\n // 读者只需要进第一道门\n if (this._writer) {\n // 如果有写者已经进入了第一道门,读者等待\n if (!this._waitingReader) {\n this._waitingReader = new Semaphore();\n }\n this._waitingReader.onActive(() => {\n this._readerEnterGate1(resolve);\n });\n } else {\n this._readerEnterGate1(resolve);\n }\n });\n }\n\n /**\n * 尝试获取读锁,立刻返回结果\n */\n public tryLockShared(): boolean {\n if (this._writer) {\n return false;\n }\n // 不需要await,一定可以上锁\n this.lockShared();\n return true;\n }\n\n /**\n * 解除读锁\n */\n public unLockShared(): void {\n lvAssertNotNil(this._reader);\n if (this._writer) {\n // TODO(niurouwan): 暂时保留,方便验证,稳定后可以去掉\n lvAssert(this._writer.status === CapabilityStatus.Unlocked);\n }\n\n this._reader.release();\n }\n\n /**\n * 解除读锁。`unLockShared` 的常规拼写别名。\n */\n public unlockShared(): void {\n this.unLockShared();\n }\n\n /**\n * 获取当前读者数量\n */\n private get _readerCount(): number {\n return this._reader ? this._reader.counter : 0;\n }\n\n /**\n * 写者进入第一道门\n */\n private _writerEnterGate1(resolve: () => void): void {\n lvAssert(!this._writer);\n // 确定写者,关第一道门\n this._writer = new Capability();\n\n // 第二道门\n // 等待所有读者出去\n if (this._readerCount > 0) {\n listenOnce(this._reader!.onUnlocked)(() => {\n this._writerEnterGate2(resolve);\n });\n } else {\n this._writerEnterGate2(resolve);\n }\n }\n\n /**\n * 写者进入第二道门\n */\n private _writerEnterGate2(resolve: () => void): void {\n lvAssertNotNil(this._writer);\n lvAssert(this._readerCount === 0);\n\n // 成功加锁\n this._writer.acquire();\n listenOnce(this._writer.onUnlocked)(() => {\n lvAssertNotNil(this._writer);\n // 不再持有\n this._writer = undefined;\n this._moveForward();\n });\n resolve();\n }\n\n /**\n * 读者进入第一道门\n */\n private _readerEnterGate1(resolve: () => void): void {\n lvAssert(!this._writer);\n\n // 门外等待的读者清除\n this._waitingReader = undefined;\n\n if (!this._reader) {\n this._reader = new SharedCapability();\n this._reader.acquire();\n listenOnce(this._reader.onUnlocked)(() => {\n this._moveForward();\n });\n } else {\n this._reader.acquire();\n }\n resolve();\n }\n\n /**\n * 锁释放时推进流程\n */\n private _moveForward(): void {\n // 如果有写者在等待在第二道门前面,那么此时推进的一定是读锁释放,直接return即可\n if (this._writer) {\n return;\n }\n\n // 写者优先,优先通知在第一道门前面的写者\n if (this._waitingWriters.length > 0) {\n const semaphore = this._waitingWriters.shift()!;\n semaphore.notify();\n return;\n }\n\n // 最后通知第一道门前面的读者\n if (this._waitingReader) {\n this._waitingReader.notify();\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface ISuccessResponse<T = any> {
|
|
2
|
+
code: 0;
|
|
3
|
+
data: T;
|
|
4
|
+
msg: string;
|
|
5
|
+
}
|
|
6
|
+
interface IErrorResponse {
|
|
7
|
+
code: Exclude<number, 0>;
|
|
8
|
+
msg: string;
|
|
9
|
+
}
|
|
10
|
+
type IResponse<T = any> = ISuccessResponse<T> | IErrorResponse;
|
|
11
|
+
declare function makeSuccessResponse<T = any>(data: T): ISuccessResponse<T>;
|
|
12
|
+
declare function makeErrorResponse(code: number, msg: string): {
|
|
13
|
+
code: number;
|
|
14
|
+
msg: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { type IErrorResponse, type IResponse, type ISuccessResponse, makeErrorResponse, makeSuccessResponse };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/response/response.ts
|
|
2
|
+
function makeSuccessResponse(data) {
|
|
3
|
+
return {
|
|
4
|
+
code: 0,
|
|
5
|
+
data,
|
|
6
|
+
msg: "success"
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function makeErrorResponse(code, msg) {
|
|
10
|
+
return {
|
|
11
|
+
code,
|
|
12
|
+
msg
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
makeErrorResponse,
|
|
17
|
+
makeSuccessResponse
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/response/response.ts"],"sourcesContent":["export interface ISuccessResponse<T = any> {\n code: 0;\n data: T;\n msg: string;\n}\n\nexport interface IErrorResponse {\n code: Exclude<number, 0>;\n msg: string;\n}\n\nexport type IResponse<T = any> = ISuccessResponse<T> | IErrorResponse;\n\nexport function makeSuccessResponse<T = any>(data: T): ISuccessResponse<T> {\n return {\n code: 0,\n data,\n msg: 'success',\n };\n}\n\nexport function makeErrorResponse(code: number, msg: string) {\n return {\n code,\n msg,\n };\n}\n"],"mappings":";AAaO,SAAS,oBAA6B,MAA8B;AACzE,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,KAAK;AAAA,EACP;AACF;AAEO,SAAS,kBAAkB,MAAc,KAAa;AAC3D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rockbed",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Small TypeScript primitives for errors, events, disposables, async helpers, locks, JSON, assertions, and responses.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./assert": {
|
|
14
|
+
"types": "./dist/assert/index.d.ts",
|
|
15
|
+
"import": "./dist/assert/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./async": {
|
|
18
|
+
"types": "./dist/async/index.d.ts",
|
|
19
|
+
"import": "./dist/async/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./dispose": {
|
|
22
|
+
"types": "./dist/dispose/index.d.ts",
|
|
23
|
+
"import": "./dist/dispose/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./error": {
|
|
26
|
+
"types": "./dist/error/index.d.ts",
|
|
27
|
+
"import": "./dist/error/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./event": {
|
|
30
|
+
"types": "./dist/event/index.d.ts",
|
|
31
|
+
"import": "./dist/event/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./json": {
|
|
34
|
+
"types": "./dist/json/index.d.ts",
|
|
35
|
+
"import": "./dist/json/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./lock": {
|
|
38
|
+
"types": "./dist/lock/index.d.ts",
|
|
39
|
+
"import": "./dist/lock/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./response": {
|
|
42
|
+
"types": "./dist/response/index.d.ts",
|
|
43
|
+
"import": "./dist/response/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"src",
|
|
50
|
+
"README.md",
|
|
51
|
+
"tsconfig.json"
|
|
52
|
+
],
|
|
53
|
+
"sideEffects": false,
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"clean": "rm -rf dist",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"prepack": "npm run clean && npm run typecheck && npm run build"
|
|
59
|
+
},
|
|
60
|
+
"keywords": [
|
|
61
|
+
"typescript",
|
|
62
|
+
"error-result",
|
|
63
|
+
"event-emitter",
|
|
64
|
+
"disposable",
|
|
65
|
+
"async",
|
|
66
|
+
"lock"
|
|
67
|
+
],
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^22.10.0",
|
|
71
|
+
"tsup": "^8.5.1",
|
|
72
|
+
"typescript": "^5.7.0"
|
|
73
|
+
},
|
|
74
|
+
"overrides": {
|
|
75
|
+
"esbuild": "^0.28.1"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=18.0.0"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function abort(reason: string): never {
|
|
2
|
+
throw new Error(`lvAssert(${reason})`);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function lvAssert(expr: unknown, reason?: string): asserts expr {
|
|
6
|
+
if (!expr) {
|
|
7
|
+
abort(reason ?? '#expr is false');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function lvAssertNotHere(reason?: string): never {
|
|
12
|
+
abort(reason ?? 'unreachable code flow');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function lvAssertNever(member: never, message = 'Illegal value:'): never {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
17
|
+
abort(`${message}: ${member}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function lvAssertNotNil<T>(val: T, reason?: string): asserts val is NonNullable<T> {
|
|
21
|
+
if (val === null || val === undefined) {
|
|
22
|
+
abort(reason ?? '#val is nil');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface IBarrier {
|
|
2
|
+
isOpen: () => boolean;
|
|
3
|
+
open: () => void;
|
|
4
|
+
wait: () => Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A barrier that is initially closed and then becomes opened permanently after a certain period of
|
|
9
|
+
* time or when open is called explicitly
|
|
10
|
+
*/
|
|
11
|
+
export class Barrier {
|
|
12
|
+
private _isOpen: boolean;
|
|
13
|
+
private readonly _promise: Promise<boolean>;
|
|
14
|
+
private _completePromise!: (v: boolean) => void;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this._isOpen = false;
|
|
18
|
+
this._promise = new Promise<boolean>((c, e) => {
|
|
19
|
+
this._completePromise = c;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
isOpen(): boolean {
|
|
24
|
+
return this._isOpen;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
open(): void {
|
|
28
|
+
this._isOpen = true;
|
|
29
|
+
this._completePromise(true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
wait(): Promise<boolean> {
|
|
33
|
+
return this._promise;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function makeBarrierByPromise(promise: Promise<any>, openWhenReject: boolean = false) {
|
|
38
|
+
const barrier = new Barrier();
|
|
39
|
+
promise.then(() => barrier.open());
|
|
40
|
+
if (openWhenReject) {
|
|
41
|
+
promise.catch((err) => {
|
|
42
|
+
barrier.open();
|
|
43
|
+
throw err;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return barrier;
|
|
47
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
|
|
2
|
+
import {
|
|
3
|
+
makeOkWith,
|
|
4
|
+
type ILvErrorOr,
|
|
5
|
+
cancelledError,
|
|
6
|
+
GenericError,
|
|
7
|
+
timeoutError,
|
|
8
|
+
type ILvErrorRef,
|
|
9
|
+
makeOk,
|
|
10
|
+
isLvErrorRef,
|
|
11
|
+
} from '../error';
|
|
12
|
+
import { EmptyDispose, makeSafeDisposable, type IDisposable } from '../dispose';
|
|
13
|
+
|
|
14
|
+
export interface ICancellationToken {
|
|
15
|
+
readonly isCancellationRequested: boolean;
|
|
16
|
+
readonly reason?: string;
|
|
17
|
+
readonly onCancellationRequested: (
|
|
18
|
+
listener: () => any,
|
|
19
|
+
thisArgs?: any,
|
|
20
|
+
) => IDisposable;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const shortcutCancellationEvent = (callback: () => any, context?: any): IDisposable => {
|
|
24
|
+
const handle = setTimeout(callback.bind(context), 0);
|
|
25
|
+
return makeSafeDisposable(() => clearTimeout(handle));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class MutableToken implements ICancellationToken, IDisposable {
|
|
29
|
+
private _isCancelled = false;
|
|
30
|
+
private _reason?: string;
|
|
31
|
+
private readonly _listeners = new Set<() => void>();
|
|
32
|
+
|
|
33
|
+
get isCancellationRequested(): boolean {
|
|
34
|
+
return this._isCancelled;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get reason(): string | undefined {
|
|
38
|
+
return this._reason;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onCancellationRequested(listener: () => any, thisArgs?: any): IDisposable {
|
|
42
|
+
if (this._isCancelled) {
|
|
43
|
+
return shortcutCancellationEvent(listener, thisArgs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const wrappedListener = listener.bind(thisArgs);
|
|
47
|
+
this._listeners.add(wrappedListener);
|
|
48
|
+
return makeSafeDisposable(() => {
|
|
49
|
+
this._listeners.delete(wrappedListener);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cancel(reason?: string): void {
|
|
54
|
+
if (this._isCancelled) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._reason = reason;
|
|
59
|
+
this._isCancelled = true;
|
|
60
|
+
for (const listener of this._listeners) {
|
|
61
|
+
listener();
|
|
62
|
+
}
|
|
63
|
+
this._listeners.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
dispose(): void {
|
|
67
|
+
this._listeners.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class CancellationToken {
|
|
72
|
+
static None = Object.freeze<ICancellationToken>({
|
|
73
|
+
isCancellationRequested: false,
|
|
74
|
+
onCancellationRequested: () => EmptyDispose,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
static Cancelled = Object.freeze<ICancellationToken>({
|
|
78
|
+
isCancellationRequested: true,
|
|
79
|
+
onCancellationRequested: shortcutCancellationEvent,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
static Make(reason: string): ICancellationToken {
|
|
83
|
+
return Object.freeze<ICancellationToken>({
|
|
84
|
+
isCancellationRequested: true,
|
|
85
|
+
onCancellationRequested: shortcutCancellationEvent,
|
|
86
|
+
reason,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class CancellationTokenSource implements IDisposable {
|
|
92
|
+
private _token?: ICancellationToken;
|
|
93
|
+
private _abortController?: AbortController;
|
|
94
|
+
|
|
95
|
+
get token(): ICancellationToken {
|
|
96
|
+
if (!this._token) {
|
|
97
|
+
this._token = new MutableToken();
|
|
98
|
+
}
|
|
99
|
+
return this._token;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get signal(): AbortSignal {
|
|
103
|
+
if (!this._abortController) {
|
|
104
|
+
this._abortController = new AbortController();
|
|
105
|
+
}
|
|
106
|
+
if (this._token?.isCancellationRequested) {
|
|
107
|
+
this._abortController.abort(this._token.reason);
|
|
108
|
+
}
|
|
109
|
+
return this._abortController.signal;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cancel(reason?: string): void {
|
|
113
|
+
if (!this._token) {
|
|
114
|
+
this._token =
|
|
115
|
+
reason === undefined ? CancellationToken.Cancelled : CancellationToken.Make(reason);
|
|
116
|
+
} else if (this._token instanceof MutableToken) {
|
|
117
|
+
this._token.cancel(reason);
|
|
118
|
+
}
|
|
119
|
+
this._abortController?.abort(reason);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
dispose(cancel = false): void {
|
|
123
|
+
if (cancel) {
|
|
124
|
+
this.cancel();
|
|
125
|
+
}
|
|
126
|
+
if (!this._token) {
|
|
127
|
+
this._token = CancellationToken.None;
|
|
128
|
+
} else if (this._token instanceof MutableToken) {
|
|
129
|
+
this._token.dispose();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 一般来说Promise常用的场景有:
|
|
136
|
+
* 1. 单次异步调用(new Promise)
|
|
137
|
+
* 2. 批量promise调用(Promise.all)
|
|
138
|
+
* 3. promise竞速调用(Promise.race)
|
|
139
|
+
*
|
|
140
|
+
* 但是原生能力有如下的缺陷
|
|
141
|
+
* 1. 不支持取消某一次promise调用
|
|
142
|
+
* 2. 不支持错误语义,promise.catch的重视程度比try catch还低
|
|
143
|
+
* 3. 超时行为需要基于race封装
|
|
144
|
+
*
|
|
145
|
+
* 针对这三点,基建侧提供了高阶的promise能力
|
|
146
|
+
* 1. makeCancelablePromise 返回一个可取消的promise
|
|
147
|
+
* const promise = makeCancelablePromise(() => { ... });
|
|
148
|
+
* promise.then((res) => {
|
|
149
|
+
* // res是一个ILvErrorRef对象
|
|
150
|
+
* });
|
|
151
|
+
* // 可以直接取消promise
|
|
152
|
+
* promise.cancel();
|
|
153
|
+
*
|
|
154
|
+
* 2. parallelPromise 并发执行promise,会自动观测ILvErrorRef语义
|
|
155
|
+
* 当单个promise出现错误返回时,promise执行失败,尽可能调用所有cancelablePromise
|
|
156
|
+
* 当单个promise出现reject时,promise执行失败,尽可能调用所有cancelablePromise,并且将reject继续上抛
|
|
157
|
+
*
|
|
158
|
+
* 3. makePromiseWithTimeout 让promise支持超时,可以提供默认值
|
|
159
|
+
*
|
|
160
|
+
* ——————————————————
|
|
161
|
+
* Promise.allSettled和Promise.any在实际使用中场景较少,暂不提供错误语义包装
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
//
|
|
165
|
+
// 可以cancel的promise对象
|
|
166
|
+
//
|
|
167
|
+
export interface CancelablePromise<T> extends Promise<T> {
|
|
168
|
+
cancel: () => void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 快速生成一个CancelablePromise对象
|
|
173
|
+
*/
|
|
174
|
+
export function makeCancelablePromise<T>(
|
|
175
|
+
callback: (token: ICancellationToken) => Promise<T | ILvErrorOr<T>>,
|
|
176
|
+
): CancelablePromise<ILvErrorOr<T>> {
|
|
177
|
+
const source = new CancellationTokenSource();
|
|
178
|
+
const thenable = callback(source.token);
|
|
179
|
+
const promise = new Promise<ILvErrorOr<T>>((resolve, reject) => {
|
|
180
|
+
const subscription = source.token.onCancellationRequested(() => {
|
|
181
|
+
subscription.dispose();
|
|
182
|
+
source.dispose();
|
|
183
|
+
resolve(cancelledError());
|
|
184
|
+
});
|
|
185
|
+
Promise.resolve(thenable).then(
|
|
186
|
+
(value) => {
|
|
187
|
+
subscription.dispose();
|
|
188
|
+
source.dispose();
|
|
189
|
+
if (isLvErrorRef(value)) {
|
|
190
|
+
resolve(value);
|
|
191
|
+
} else {
|
|
192
|
+
resolve(makeOkWith(value as T));
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
(err) => {
|
|
196
|
+
subscription.dispose();
|
|
197
|
+
source.dispose();
|
|
198
|
+
reject(err);
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
return new (class {
|
|
203
|
+
cancel() {
|
|
204
|
+
source.cancel();
|
|
205
|
+
}
|
|
206
|
+
then<TResult1 = T, TResult2 = never>(
|
|
207
|
+
resolve?: ((value: ILvErrorOr<T>) => TResult1 | Promise<TResult1>) | null,
|
|
208
|
+
reject?: ((reason: any) => TResult2 | Promise<TResult2>) | null,
|
|
209
|
+
): Promise<TResult1 | TResult2> {
|
|
210
|
+
return promise.then(resolve, reject);
|
|
211
|
+
}
|
|
212
|
+
catch<TResult = never>(
|
|
213
|
+
reject?: ((reason: any) => TResult | Promise<TResult>) | null,
|
|
214
|
+
): Promise<T | TResult> {
|
|
215
|
+
return this.then(undefined, reject);
|
|
216
|
+
}
|
|
217
|
+
finally(onfinally?: (() => void) | null): Promise<ILvErrorOr<T>> {
|
|
218
|
+
return promise.finally(onfinally);
|
|
219
|
+
}
|
|
220
|
+
})() as CancelablePromise<ILvErrorOr<T>>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 并行执行promise,当某一个失败,本次调用失败
|
|
225
|
+
* 本质上和Promise.all差不多,相比较之下
|
|
226
|
+
* 1. 包装了错误语义判断
|
|
227
|
+
* 2. 当失败时,会尽可能尝试cancel其他promise
|
|
228
|
+
*
|
|
229
|
+
* 注意:某一个promise失败时,会尽可能尝试调用其他promise的cancel,但不保证一定有效
|
|
230
|
+
* (有可能promiseA已经成功,但是primiseB在之后失败了)
|
|
231
|
+
*/
|
|
232
|
+
export function parallelPromise(promiseList: Promise<any>[]): Promise<ILvErrorRef> {
|
|
233
|
+
if (promiseList.length === 0) {
|
|
234
|
+
return Promise.resolve(makeOk());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let todo = promiseList.length;
|
|
238
|
+
const finish = () => {
|
|
239
|
+
todo = -1;
|
|
240
|
+
for (const promise of promiseList) {
|
|
241
|
+
(promise as Partial<CancelablePromise<any>>).cancel?.();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return new Promise<ILvErrorRef>((resolve, reject) => {
|
|
246
|
+
for (const promise of promiseList) {
|
|
247
|
+
promise
|
|
248
|
+
|
|
249
|
+
.then((res) => {
|
|
250
|
+
if (isLvErrorRef(res) && !res.ok) {
|
|
251
|
+
finish();
|
|
252
|
+
resolve(res as ILvErrorRef);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
todo--;
|
|
256
|
+
if (todo === 0) {
|
|
257
|
+
resolve(makeOk());
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
.catch((err) => {
|
|
261
|
+
finish();
|
|
262
|
+
reject(err);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 包装一个promise,提供超时能力
|
|
270
|
+
*/
|
|
271
|
+
export function makePromiseWithTimeout<T>(
|
|
272
|
+
callback: (token: ICancellationToken) => Promise<T | ILvErrorOr<T>>,
|
|
273
|
+
timeout: number,
|
|
274
|
+
defaultValue?: T, // 当发生超时时,提供的默认值
|
|
275
|
+
): Promise<ILvErrorOr<T>> {
|
|
276
|
+
const cancellable = makeCancelablePromise<T>(callback);
|
|
277
|
+
const timer = setTimeout(() => {
|
|
278
|
+
cancellable.cancel();
|
|
279
|
+
}, timeout);
|
|
280
|
+
return cancellable.then((res: ILvErrorOr<T>) => {
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
if (res.ok) {
|
|
283
|
+
return res;
|
|
284
|
+
}
|
|
285
|
+
// 如果是被取消,触发了超时
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
287
|
+
if (res.code === GenericError.Cancelled) {
|
|
288
|
+
if (defaultValue !== undefined) {
|
|
289
|
+
// 希望返回默认值
|
|
290
|
+
return makeOkWith(defaultValue);
|
|
291
|
+
}
|
|
292
|
+
return timeoutError();
|
|
293
|
+
} else {
|
|
294
|
+
// 遇到其他错误时透传错误
|
|
295
|
+
return res;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 如果 proposal-promise-with-resolvers 通过了,那么这个函数就可以去掉了
|
|
301
|
+
// https://github.com/microsoft/TypeScript/blob/1d96eb489e559f4f61522edb3c8b5987bbe948af/src/harness/util.ts#L115
|
|
302
|
+
export interface Deferred<T> {
|
|
303
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
304
|
+
reject: (reason: unknown) => void;
|
|
305
|
+
promise: Promise<T>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function defer<T = void>(): Deferred<T> {
|
|
309
|
+
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
310
|
+
let reject!: (reason: unknown) => void;
|
|
311
|
+
const promise = new Promise<T>((_resolve, _reject) => {
|
|
312
|
+
resolve = _resolve;
|
|
313
|
+
reject = _reject;
|
|
314
|
+
});
|
|
315
|
+
return { resolve, reject, promise };
|
|
316
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { IDisposable } from './dispose-base';
|
|
2
|
+
import { BRANCH_DISPOSE } from './logger';
|
|
3
|
+
|
|
4
|
+
export class DisposableStore implements IDisposable {
|
|
5
|
+
static DISABLE_DISPOSED_WARNING = false;
|
|
6
|
+
|
|
7
|
+
private readonly _toDispose = new Set<IDisposable>();
|
|
8
|
+
private _isDisposed = false;
|
|
9
|
+
|
|
10
|
+
get isDisposed(): boolean {
|
|
11
|
+
return this._isDisposed;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
dispose(): void {
|
|
15
|
+
if (this._isDisposed) {
|
|
16
|
+
// 不应该出现重复dispose
|
|
17
|
+
/* eslint-disable no-console */
|
|
18
|
+
console.warn(new Error('DisposableStore has disposed.').stack);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this._isDisposed = true;
|
|
23
|
+
this.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear(): void {
|
|
27
|
+
if (this._toDispose.size === 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const disposable of this._toDispose) {
|
|
32
|
+
BRANCH_DISPOSE(this.constructor.name, disposable.constructor.name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// TODO(vorshen): 考虑异常情况
|
|
37
|
+
for (const disposable of this._toDispose) {
|
|
38
|
+
disposable.dispose();
|
|
39
|
+
}
|
|
40
|
+
} finally {
|
|
41
|
+
this._toDispose.clear();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
add<T extends IDisposable>(o: T): T {
|
|
46
|
+
if (!o) {
|
|
47
|
+
return o;
|
|
48
|
+
}
|
|
49
|
+
if ((o as unknown as DisposableStore) === this) {
|
|
50
|
+
throw new Error('Cannot register a disposable on itself.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this._isDisposed) {
|
|
54
|
+
if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
|
|
55
|
+
/* eslint-disable no-console */
|
|
56
|
+
console.warn(
|
|
57
|
+
new Error(
|
|
58
|
+
'Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!',
|
|
59
|
+
).stack,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
this._toDispose.add(o);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return o;
|
|
67
|
+
}
|
|
68
|
+
}
|